zoukankan      html  css  js  c++  java
  • koa下实现路由自动注册与参数绑定

    在koa下实现路由注册与参数绑定,我们要达到下面的效果:

    import {Controller, RequestBody, RequestMapping, RequestParam} from '../decorator/RouterDecrator';
    import {LoggerFactory} from '../util/logger';
    import {timeCounter} from '../middlewares/TimeCounter';
    import {User} from '../domain/User';
    
    const logger = LoggerFactory.getLogger('LeadController');
    
    @Controller('/user', [timeCounter])
    export default class UserController {
    
        @RequestMapping({path: '/get', method: 'get'})
        public async getUser (@RequestParam('id') userId: number){
            return {id: userId, name: 'test'};
        }
    
        @RequestMapping({path: '/add', method: 'post'})
        public async addUer (@RequestParam('token') token: string, @RequestBody() user: User){
            logger.info('UserController.addUer');
            return {token, user};
        }
    }

    首先我们需要几个装饰器,分别作用于类,方法和参数

    import {NextFunction} from 'express';
    import {Context} from 'koa';
    
    export const REQUEST_BODY = 'RequestBody';
    
    export type MiddleWare = (context: Context, next: NextFunction) => void;
    
    /**
     * 各个装饰器在类的原型上添加数据
     * path+subPath 完整路径
     * method 请求方法get,post等
     * middleWares 中间件
     */
    
    // 类装饰器
    export function Controller (path= '/', middleWares?: MiddleWare[]) {
        return (target: any) => {
            target.prototype.path = path;
            target.prototype.middleWares = middleWares;
        };
    
    }
    
    // 方法装饰器
    export function RequestMapping (config: {path: string, method: string,
                                             middleWares?: MiddleWare[]}) {
    
        return (target: any, name: string, descriptor: PropertyDescriptor) => {
            target[name].subPath = config.path;
            target[name].requestMethod = config.method;
            target[name].middleWares = config.middleWares;
        };
    
    }
    
    // 参数装饰器
    export function RequestParam (paramName: string) {
        return (target: any, methodName: string, index: number) => {
    
            const  params = target[methodName].paramList || {};
            params[paramName] = index;
            target[methodName].paramList = params;
        };
    }
    
    // 参数装饰器
    export function RequestBody () {
        return (target: any, methodName: string, index: number) => {
    
            const  params = target[methodName].paramList || {};
            params[REQUEST_BODY] = index;
            target[methodName].paramList = params;
    
        };
    }

    接下来,需要对koa提供的类进行包装,将路由注册之后,再暴露给外部。此外,由于方法装饰器和类装饰器在类被加载的时候才会生效,所以需要加载所有的controller类,这是用了fs模块递归加载。同时由于这个方法只在启动时调用一次,所以可以调用fs模块的同步方法。

    import Koa, {Context} from 'koa';
    import Router from 'koa-router';
    import {MiddleWare, REQUEST_BODY} from './decorator/RouterDecrator';
    import * as path from 'path';
    import * as fs from 'fs';
    import bodyParser from 'koa-bodyparser';
    import {LoggerFactory} from './util/logger';
    import {responseMethod} from './middlewares/ResHandle';
    
    const logger = LoggerFactory.getLogger('Application');
    export class Application {
    
        private app: Koa;
        private globalRouter: Router;
    
        constructor () {
            this.app = new Koa();
            this.globalRouter = new Router();
            this.app.on('error', (err) => {
                throw err;
            });
            this.app.use(bodyParser());
    
            this.app.use(responseMethod);
    
            this.loadControllers(path.join(__dirname, './controller'));
    
            this.app.use(this.globalRouter.routes());
        }
    
        // 递归加载controller目录下的ts文件
        private loadControllers (filePath: string): void{
            const files = fs.readdirSync(filePath);
            files.forEach((file) => {
                const newFilePath = path.join(filePath, file);
                if (fs.statSync(newFilePath).isDirectory()){
                    this.loadControllers(newFilePath);
                }else{
                    const controller = require(newFilePath);
                    this.registerRouters(controller);
                }
                }
            );
        }
    
        // 注册路由
        private registerRouters (controller: any): void{
            if (!controller){
                return;
            }
    
            const proto = controller.default.prototype;
            const prefix = proto.path;
            const middleWares: MiddleWare[] = proto.middleWares;
    
            const properties = Object.getOwnPropertyNames(proto);
    
            properties.forEach((property) => {
                if (proto[property] && proto[property].subPath){
                    const fullPath = (prefix + proto[property].subPath).replace(//{2,}/g, '/');
                    const method = proto[property].requestMethod;
    
                    // 累加中间件
                    const fullMiddleWares: MiddleWare[] = [];
                    if (middleWares){
                        fullMiddleWares.concat(middleWares);
                    }
                    if (proto[property].middleWares){
                        fullMiddleWares.concat(proto[property].middleWares);
                    }
    
                    const router = new Router();
                    logger.info(`add url:${fullPath}`);
                    const  asyncMethod = async (context: Context) => {
    
                        const paramList = proto[property].paramList;
                        const args: any = [];
                        if (paramList) {
    
                            // 参数绑定
                            const paramKeys = Object.getOwnPropertyNames(paramList);
                            paramKeys.forEach((paramName) => {
                                const index = paramList[paramName];
                                args[index] = paramName === REQUEST_BODY ?
                                    JSON.parse(JSON.stringify(context.request.body)) : context.query[paramName];
                            });
                        }
                        context.body = await proto[property].apply(proto, args);
    
                    };
    
                    // 添加中间件
                    if (middleWares){
                        router.use(...middleWares);
                    }
                    router[method](fullPath, asyncMethod);
                    this.globalRouter.use(router.routes());
                    this.globalRouter.use(router.allowedMethods());
            }
            });
    
        }
    
        public listen (port: number){
            this.app.listen(port);
        }
    
    }

    最后,写一个入口文件启动服务

    // bootstrap.ts
    
    import {Application} from './application';
    
    const app = new Application();
    app.listen(3000);

    最终效果如图:

     源码地址: github地址

  • 相关阅读:
    JMeter——请求元件——配置元件——参数化——用户自定义变量
    JMeter——结合fiddler查看响应结果
    JMeter——断言——xpath Assertion
    JMeter——断言——响应断言
    JMeter——配置元件——http信息头管理器使用
    JMeter——查看结果树——html使用
    JMeter——查看结果树 ——css_jquery_tester(css选择器测试)
    JMeter——查看结果树
    JMeter——http请求默认值
    java.lang.RuntimeException: Cannot reconnect.
  • 原文地址:https://www.cnblogs.com/wkzhao/p/10597063.html
Copyright © 2011-2022 走看看