【nodejs】让nodejs像后端mvc框架(asp.net mvc)一样处理请求--参数自动映射篇(6/8)...

文章目录

前情概要

路由、action的扫描、发现、注册搞定之后,后来我发现在我们的action里面获取参数往往都是通过request对象来一个一个获取。同样的一行代码我们不厌其烦的重复写了无数次。遂想着那我们能不能像后端程序一样做得更自动化一些呢?
所以,接下来我们再来完成一个比较重要的功能,那就是参数的自动绑定。

参数的自动绑定实现思路

依靠ts的装饰器特性,我们能做在方法上,在类上,在方法的参数上,在类的属性成员上通通可以加上装饰器来存放一些额外的数据。那理论上我们在编码阶段就可以通过一定的手段把这个标记加载我们需要处理的方法、类、参数等上面,等到运行时的时候可以根据这些额外的参数来帮我们做一些重复性的工作。

  1. 在需要使用到的方法参数、类、属性上增加我们的特定标识,标记当前参数需要自动解析,并记录一些诸如类型拉、名称啦等的一些额外属性。
  2. 在action的调用阶段,根据规则先把参数解析好。在传递进去。
  3. 完事儿,这就是我们的参数自动绑定功能。

参数的自动绑定实现---装饰器实现

部分代码,只贴了fromquery,其他几个formbody,fromheader之类的基本一样,都是调用makeActionParameterDescriptor方法

/***  指示当前参数从request对象的query中解析* * @export* @param {(target?: any) => Function} type * @returns {Function} */
export function fromQuery(type: (target?: any) => Function): Function;
/*** 指示当前参数从request对象的query中解析* * @export* @returns {Function} */
export function fromQuery(): Function {var thatArg = arguments;return function (target: Object, propertyKey: string, parameterIndex: number) {makeActionParameterDescriptor('query', thatArg, target, propertyKey, parameterIndex);}
}
function makeActionParameterDescriptor(parameterFromType: parameterFromType, thatArg: IArguments, target: Object, propertyKey: string, parameterIndex: number) {//非声明在属性和参数上if (!propertyKey) return;var paramType = undefined;var val = new ActionParamDescriptor();val.parameterName = propertyKey;val.target = target;val.parameterIndex = parameterIndex;val.parameterFromType = parameterFromType;val.parameterTypeType = 'simple'if (typeof parameterIndex === 'undefined') {//声明在类的属性上val.localtionType = 'classProperty'} else {//声明在action的参数上val.localtionType = 'methodParameter'val.actionMethodName = propertyKey;val.parameterName = getArgs((target as any)[propertyKey])[parameterIndex];}//复杂类型if (thatArg.length > 0) {val.parameterTypeType = 'complex'val.parameterType = thatArg[0](target);}SetActionParamDescriptor(val);
}
function getArgs(func: Object) {//匹配函数括号里的参数  var method = func.toString();method = method.length > 500 ? method.substring(0, 500) : method;method = method.replace("\r|\n|\\s", "")var args = method.match(/.*?\(.*?\)/i);if (args == null) throw Error('can not match method parameters');method = args[0];method = method.replace(/.*?\(|\)/, "").replace(')', '');//分解参数成数组  var arr = method.split(",").map(function (arg) {//去空格和内联注释  return arg.replace(/\/\*.*\*\//, "").trim();}).filter(function (args) {//确保没有undefineds  return args;});return arr
}

ActionParamDescriptor 对象结构

export declare type parameterFromType = 'query' | 'body' | 'form' | 'header' | 'cookie' | 'auto'
export class ActionParamDescriptor {/*** action参数的action名称* * @type {string}* @memberof ActionParamDescriptor*/actionMethodName: string/*** 参数名称* * @type {string}* @memberof ActionParamDescriptor*/parameterName: string/*** 参数所在类* * @type {Object}* @memberof ActionParamDescriptor*/target: Object/*** 参数类型的类别* * @type {('complex' | 'simple')}* @memberof ActionParamDescriptor*/parameterTypeType: 'complex' | 'simple'/*** 参数对象的类型(class)对象* * @type {Function}* @memberof ActionParamDescriptor*/parameterType: Function/*** 参数所在参数类别的顺序* * @type {(number | undefined)}* @memberof ActionParamDescriptor*/parameterIndex: number | undefined/*** 当前参数属性属于什么类型* * @type {('classProperty'|'methodParameter')}* @memberof ActionParamDescriptor*/localtionType: 'classProperty' | 'methodParameter'/*** 标记参数应该从什么地方解析* * @type {parameterFromType}* @memberof ActionParamDescriptor*/parameterFromType: parameterFromType
}

参数的自动绑定实现---基本使用方法

可以在action上标记某一个参数从什么地方(query、form、body、cookie、header)进行解析,
也可以标记某个参数是一个复杂的查询参数,可以指定这个参数的类型。
当然复杂的查询class的每一个属性都可以指定解析来源,当然也必须使用装饰器来修饰一下,不然我们就没法知道有这个属性需要进行解析啦。

import { BaseController, post, fromQuery, fromBody, fromCookie, fromHeader, property } from "../src/index"
export class demoActionBodyParams {id: string;name: string;pageSize: number;body: {req_bb: string}
}
export class demoActionQueryParams {@property()id: string;@property()name: string;@property()pageSize: number;@fromCookie()cookieName: string;@fromHeader()headerName: string;@fromBody()body: any;
}
export class demoController extends BaseController {@post()demoAction(@fromQuery(type => demoActionQueryParams) query: demoActionQueryParams,@fromQuery() p2: string,@fromBody() req_body: demoActionBodyParams) {return { query, p2, req_body }}
}

参数的自动绑定实现---参数的说明元数据保存

reflect-metadata 目前来说也还是ts的一个实验性特性。可以用来辅助我们保存一些额外的数据。或者也可以理解成它是一个系统级别的静态字典。
那我们把对参数的一些特别设置都通过reflect-metadata保存下来,其实这里我们自己使用一个对象来保存也是可以的。

const request_params_auto_bind_MetadataKey = Symbol("request_params_auto_bind_MetadataKey");
export function SetActionParamDescriptor(val: ActionParamDescriptor) {(val as any).targetName = val.target.constructor.nameif (val.parameterType) (val as any).parameterTypeName = val.parameterType.nameconsole.log('SetActionParamDescriptor', JSON.stringify(val));var arr: ActionParamDescriptor[] = [];if (val.localtionType === 'methodParameter') {arr = Reflect.getMetadata(request_params_auto_bind_MetadataKey, val.target, val.actionMethodName) || [];arr.push(val);Reflect.defineMetadata(request_params_auto_bind_MetadataKey, arr, val.target, val.actionMethodName);} else {arr = Reflect.getMetadata(request_params_auto_bind_MetadataKey, val.target) || [];arr.push(val);Reflect.defineMetadata(request_params_auto_bind_MetadataKey, arr, val.target);}
}

参数的自动绑定实现---参数的自动解析和对象生成

嗯,大概是一些杂乱无章的代码(^_^)。
主要思路:

  1. 获得当前action的参数描述对象
  2. 根据参数描述对象中的配置来解析参数
  3. 就这么简单,完事儿
//开始参数的自动解析操作var agrs = bindActionParameter(desc.ControllerType, desc.ControllerTypeName, desc.ActionType, desc.ActionName, req)function bindActionParameter(controllerType: Function, controllerName: string, actionType: Object, actionName: string, req: core.Request) {
//获得当前action的所有参数描述对象var arr = Reflect.getMetadata(request_params_auto_bind_MetadataKey, controllerType.prototype, actionName) || [] as ActionParamDescriptor[];var args = [arr.length];for (let index = 0; index < arr.length; index++) {args[arr[index].parameterIndex as number] = getParameterValue(req, arr[index], arr[index])//循环挨个进行解析}return args;
}
function bindClassParameter(req: core.Request, target: any, methodParmeterdesc: ActionParamDescriptor): any {var arr = Reflect.getMetadata(request_params_auto_bind_MetadataKey, target.prototype) as ActionParamDescriptor[];var obj = new target();for (let index = 0; index < arr.length; index++) {var desc = arr[index];obj[desc.parameterName] = getParameterValue(req, desc, methodParmeterdesc);}return obj;
}
function getParameterValue(req: core.Request, desc: ActionParamDescriptor, methodParmeterdesc: ActionParamDescriptor): any {//判断当前action的参数是基本类型参数,还是复杂类型参数。如果是复杂类型就走class绑定逻辑。if (desc.parameterTypeType === 'simple' || (desc.localtionType === 'methodParameter' && desc.parameterFromType === 'body')) {return getparameterInRequest(desc.parameterFromType, desc.parameterName, req, methodParmeterdesc);} else if (desc.parameterTypeType === 'complex') {return bindClassParameter(req, desc.parameterType, methodParmeterdesc)}else throw Error('not support parameter type ' + desc.parameterTypeType)
}
//根据参数的不同配置进行不同解析。
function getparameterInRequest(fromType: parameterFromType, parameterName: string, req: core.Request, methodParmeterdesc: ActionParamDescriptor): any {switch (fromType) {case 'query':return getCompatibleParam(req.query, parameterName)case 'body':return req.bodycase 'header':return getCompatibleParam(req.headers, parameterName)case 'cookie':return getCompatibleParam(req.cookies, parameterName)case 'form':return getCompatibleParam(req.body, parameterName)case 'auto':return getparameterInRequest(methodParmeterdesc.parameterFromType, parameterName, req, methodParmeterdesc);}return undefined;
}
//忽略参数的大小写问题。
function getCompatibleParam(obj: any, propertyName: string) {var lower = propertyName.toLowerCase();for (const key in obj) {if (obj.hasOwnProperty(key) && key.toLowerCase() == lower) {return obj[key];}}
}

需要说明的是,在这里有一个问题没有解决。当参数指定类型为body的时候,我们没有对参数进行更多的解析。也就意味着我申明的对象只有2个属性,提交的body有3个属性,最终在action里面的这个参数能拿到3个属性。一直犹豫是否要做这里是否要做filter。
从后端的角度来说是毫无疑问的,不可能我一个class只声明了2个属性,而到运行时的时候能取出来3个属性。这是不可能的。
但从前端的角度来讲,这也许是一个比较好的特性。某些时候更省事情。比较接口部分参数透传的时候之类的。

参数的自动解析大致就到这里了,嗯,这部分代码可能有点小逻辑。又加上没有注释有点难理解。不过我觉得这样挺好的,哈哈哈

转载于:https://www.cnblogs.com/calvinK/p/nodejs-mvc-params-auto-mapping.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/413653.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java String常用的数据类型转换

工作写代码经常遇到string的数据类型转换&#xff0c;每次都去搜索如何转换很不方便。写篇博客做个总结&#xff0c;以后看这篇就好了。 1.string-bigDecimal 2.string-date Testpublic void StringToDate() throws ParseException {Date date new SimpleDateFormat("yy…

深度学习之Batch Normalization

1、Batch Normalization的引入 在机器学习领域有个很重要的假设&#xff1a;IID独立同分布假设&#xff0c;也就是假设训练数据和测试数据是满足相同分布的&#xff0c;这是通过训练数据获得的模型能够在测试集上获得好的效果的一个基本保障。在深度学习网络中&#xff0c;后一…

[css] 为什么说css的选择器一般不要超过三级?

[css] 为什么说css的选择器一般不要超过三级&#xff1f; CSS的解析过程&#xff0c;在解析选择器的时候&#xff0c;是从右向左&#xff0c;从上到下及逆行解析的。 超过三级会使的css解析树复杂度呈指数级增加&#xff0c;降低css加载性能个人简介 我是歌谣&#xff0c;欢迎…

调用支付宝接口 alipay.data.bill.accountlog.query,提示:ISV权限不足

使用环境&#xff1a;正式环境 接口名称&#xff1a;alipay.data.bill.accountlog.query(支付宝商家账户账务明细查询) 提示&#xff1a;ISV权限不足&#xff0c;建议在开发者中心检查签约是否已经生效 参考&#xff1a; 支付宝商家账户账务明细查询接口文档 自己按照支付宝官…

工作309:uni-获取vuex里面的值

marketId:this.vuex_user.market_id

vue给同一元素绑定单击click和双击事件dblclick,执行不同逻辑

在做项目过程中&#xff0c;需求是点击孔位单击弹出对话框查看产品总数&#xff0c;双击弹出对话框查看详情。一开始直接click和dblclick写在标签里面&#xff0c;但是不管怎么样&#xff0c;总是执行单击事件 解决办法&#xff1a;利用计时器&#xff0c;在大概时间模拟双击事…

CentOS7 1.搭建环境脚本

公司没有专职运维&#xff0c;搭建服务器全部都是程序员操作。以前手动搭建需要1天时间&#xff0c;搭建过程很慢、很无聊&#xff0c;于是自己总结了如下脚本&#xff0c;执行完需要18分钟&#xff0c;以后再也不用苦逼的搭建环境了。 build.sh #!/bin/bashecho "新建de…

工作310:uni-初始获取数据onload

/* 第一步定义onload方法 */onLoad(){/* 调用市场信息的接口 分别 vuex获取 其他三项 */this.$u.api.getpartyList({marketId:this.vuex_user.market_id,type:"通知公告",pageNo:1,pageSize :20}).then(res>{if(res.data.code200){this.tableDatares.data.data.re…

LeetCode 258 Add Digits

leetcode 上做了一题比较有意思&#xff0c;记录一下&#xff0c;传送门&#xff1a;https://leetcode.com/problems/add-digits/description/ 题目意思是&#xff1a;给一个正整数a&#xff0c;让其个位&#xff0c;十位&#xff0c;百位一直到最高位加起来得到另一个数字b&am…

工作311:uni-携带当前参数跳转页面传值

第一步 <view v-for"(item,index) in tableData" click"getDetail(item.id)" class" box"><view class"u-line-1 u-m-b-24 u-font-30 color-3">{{item.title}}&#xff01;</view><view class"u-flex u-ro…

web自动化测试(java)---测试过程中遇到的错误合集

摸索测试&#xff0c;不管是安装、调测第一个用例都会遇到各种各样的问题&#xff0c;或是自己的问题或是程序本身设置问题 只有把所有问题记录下来&#xff0c;才对得起自己的经历 1、设置firefox的执行文件错误 Exception in thread "main" org.openqa.selenium.We…

CentOS7 3.项目持续交付脚本

#停止原有工程 ps -ef | grep demo | grep -v grep | awk {print $2} | xargs kill#删除原有工程 rm -rf /opt/app/demo.jar#解压压缩包指定文件&#xff0c;并存入指定路径 #tar -zxf 压缩包 -C 解压到的路径 压缩包中指定文件名 tar -zxf /opt/app/demo.tgz -C /opt/app .…

工作312:uni-弹出框显示数据

<template><view class"wrap"><u-form :model"form" :rules"rules" ref"uForm" :errorType"errorType"><u-form-item label"标题" label-width"140" style"margin-left:30rp…

运算符和编码

格式化输出 现在有以下需求,让用户输入name, age, job,hobby 然后输出如下所⽰示:------------ info of Alex Li -----------Name : Alex LiAge : 22job : TeacherHobbie: girl------------- end -----------------你怎么实现呢&#xff1f;你会发现&#xff0c;用字符拼接的方…

CentOS7 2.新项目上线脚本

我司一个客户项目对应代码仓库的一个分支&#xff0c;每次新项目上线总是要从master复制一个分支&#xff0c;接着git clone到本地&#xff0c;在IDEA里面替换配置文件内容&#xff0c;上传到代码仓库&#xff0c;启动项目&#xff0c;配置nginx.conf&#xff0c;每次新项目上线…

工作312:uni-时间戳处理

第一步 <u-form-item label"结束时间" prop"endDate" label-width"200"><u-input type"select" v-model"form.endDate" placeholder"请选择" click"end_time_show true"></u-input>…

informix如何查询第一条记录

1.select first 1 * from shop;    正序查询第一条数据 2.select first 1 * from shop order by create_time desc;    按创建时间倒序查询第一条数据 3.select first 1 shopid from shop;    正序查询第一条数据中的shopid字段 4.select first 1 shopid from shop…

工作314:uni-提交成功加入表单验证

增加验证规则 <u-form :model"form" :rules"rules" ref"uForm" :errorType"errorType"><navigator url"../LevineHua-editor/LevineHua-editor" class"single"><u-form-item label"荣誉照片&…

HDOJ 1233 (克鲁斯卡尔+并查集)

还是畅通工程 Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) Total Submission(s): 58011 Accepted Submission(s): 26354 Problem Description 某省调查乡村交通状况&#xff0c;得到的统计表中列出了任意两村庄间的距离。省政府“…

mysql8优化实战

最近上线了一个10万户的管理系统&#xff0c;以前的客户没有这么多用户量&#xff0c;隐藏在代码中的慢sql渐渐显现出来了。 下面是我最近一周慢sql优化的总结&#xff1a; 多表sql优化、count sql优化、超过10 0000条limit优化一、多表sql优化 二、count sql优化 该表有21350…