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

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 文章目录前情概要路由、action的扫描、发现、注册搞定之后,后来我发现在我们的action里面获取参数往往都是通过request对象来一个一个获取。同样的一行代码我们不厌其烦的重复写了无数次。遂想着那我们能不能像后端程序一样做得更自动化一些呢?所以,接下来我们再来完成一个比较重要的功能,那就是参数的自动绑定。

文章目录

前情概要

路由、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.name
    if (val.parameterType) (val as any).parameterTypeName = val.parameterType.name
    console.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.body
        case '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个属性。这是不可能的。
但从前端的角度来讲,这也许是一个比较好的特性。某些时候更省事情。比较接口部分参数透传的时候之类的。

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

目录
相关文章
|
8天前
|
Web App开发 JavaScript 前端开发
深入浅出Node.js后端框架
【10月更文挑战第34天】在数字化时代,后端开发如同一座桥梁,连接着用户界面与数据处理的两端。本文将通过Node.js这一轻量级、高效的平台,带领读者领略后端框架的魅力。我们将从基础概念出发,逐步深入到实战应用,最后探讨如何通过代码示例来巩固学习成果,使读者能够在理论与实践之间架起自己的桥梁。
|
1月前
|
JavaScript 前端开发 中间件
探索后端技术:Node.js与Express框架的完美融合
【10月更文挑战第7天】 在当今数字化时代,Web应用已成为日常生活不可或缺的一部分。本文将深入探讨后端技术的两大重要角色——Node.js和Express框架,分析它们如何通过其独特的特性和优势,为现代Web开发提供强大支持。我们将从Node.js的非阻塞I/O和事件驱动机制,到Express框架的简洁路由和中间件特性,全面解析它们的工作原理及应用场景。此外,本文还将分享一些实际开发中的小技巧,帮助你更有效地利用这些技术构建高效、可扩展的Web应用。无论你是刚入门的新手,还是经验丰富的开发者,相信这篇文章都能为你带来新的启发和思考。
|
14天前
|
JavaScript 中间件 API
Node.js进阶:Koa框架下的RESTful API设计与实现
【10月更文挑战第28天】本文介绍了如何在Koa框架下设计与实现RESTful API。首先概述了Koa框架的特点,接着讲解了RESTful API的设计原则,包括无状态和统一接口。最后,通过一个简单的博客系统示例,详细展示了如何使用Koa和koa-router实现常见的CRUD操作,包括获取、创建、更新和删除文章。
35 4
|
21天前
|
Web App开发 JavaScript 中间件
构建高效后端服务:Node.js与Express框架的完美结合
【10月更文挑战第21天】本文将引导你走进Node.js和Express框架的世界,探索它们如何共同打造一个高效、可扩展的后端服务。通过深入浅出的解释和实际代码示例,我们将一起理解这一组合的魅力所在,并学习如何利用它们来构建现代Web应用。
41 1
|
9天前
|
Web App开发 JavaScript 前端开发
构建高效后端服务:Node.js与Express框架的实践
【10月更文挑战第33天】在数字化时代的浪潮中,后端服务的效率和可靠性成为企业竞争的关键。本文将深入探讨如何利用Node.js和Express框架构建高效且易于维护的后端服务。通过实践案例和代码示例,我们将揭示这一组合如何简化开发流程、优化性能,并提升用户体验。无论你是初学者还是有经验的开发者,这篇文章都将为你提供宝贵的见解和实用技巧。
|
11天前
|
Web App开发 JavaScript 中间件
构建高效后端服务:Node.js与Express框架的融合之道
【10月更文挑战第31天】在追求快速、灵活和高效的后端开发领域,Node.js与Express框架的结合如同咖啡遇见了奶油——完美融合。本文将带你探索这一组合如何让后端服务搭建变得既轻松又充满乐趣,同时确保你的应用能够以光速运行。
21 0
|
1月前
|
JSON JavaScript 前端开发
Node.js Express 框架
10月更文挑战第7天
27 2
|
1月前
|
Web App开发 JavaScript 前端开发
使用Node.js和Express框架构建Web服务器
使用Node.js和Express框架构建Web服务器
|
1月前
|
Web App开发 JavaScript API
构建高效后端系统:Node.js与Express框架的实践之路
【9月更文挑战第37天】在数字化时代的浪潮中,后端开发作为技术架构的核心,承载着数据处理和业务逻辑的重要职责。本文将深入探讨如何利用Node.js及其强大的Express框架来搭建一个高效、可扩展的后端系统。我们将从基础概念讲起,逐步引导读者理解并实践如何设计、开发和维护一个高性能的后端服务。通过实际代码示例和清晰的步骤说明,本文旨在为初学者和有经验的开发者提供一个全面的指南,帮助他们在后端开发的旅途上走得更远。
48 3
|
1月前
|
开发框架 JavaScript 前端开发
一个适用于 ASP.NET Core 的轻量级插件框架
一个适用于 ASP.NET Core 的轻量级插件框架

热门文章

最新文章