加入收藏 | 设为首页 | 会员中心 | 我要投稿 航空爱好网 (https://www.52kongjun.com/)- 自然语言处理、云硬盘、数据治理、数据工坊、存储容灾!
当前位置: 首页 > 教程 > 正文

node.js的后端路由自动加载如何实现

发布时间:2023-08-02 11:01:05 所属栏目:教程 来源:未知
导读:   这篇文章主要介绍了node.js的后端路由自动加载怎么实现的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇node.js的后端路由自动加载怎么实现文章都会有所
  这篇文章主要介绍了node.js的后端路由自动加载怎么实现的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇node.js的后端路由自动加载怎么实现文章都会有所收获,下面我们一起来看看吧。
 
  背景
 
  我们前端同学或者是nodejs服务端的同学,在你们使用express和koajs写接口的时候, 咱们是不都要写路由 比如如下
 
  登录接口router.post('/user/login', user.login);
 
  获取用户信息接口router.get('/user/info', checkAuth, user.xxx);
 
  这种写法很常见, 先注册路由,再指定后续要执行的中间件方法。
 
  可是当接口越来越多,比如有1000个接口, 就得这样注册1000次,多了我认为是一件很麻烦且不优雅的事情
 
  koa&express 路由注册示例
 
  const express = require('express');
 
  const router = express.Router();
 
  const user = require('../../controllers/user');
 
  const tokenCheck = require('../../middleware/token_check_api');
 
  //用户注册
 
  router.post('/user/register', user.register);
 
  //用户登录
 
  router.post('/user/login', user.login);
 
  router.post('xxx', tokenCheck, user.xxx);
 
  ...假装还有有1000个
 
  写1000个接口就要在router.js里注册1000次吗?
 
  eggjs路由注册示例
 
  'use strict';
 
  // egg-router extends koa-router
 
  import { Application } from 'egg';
 
  export default (app: Application) => {
 
    const { router, controller, middleware } = app;
 
    router.get('/', middleware.special(), controller.home.index);
 
    router.get('/1', middleware.special(), controller.home.index1);
 
    ....
 
    router.get('/error', controller.home.error);
 
  };
 
  **这种项目扩大时候, 我认为这种配置会显得很冗余,所以就需要实现一种路由自动加载的机制来改善它优化它。
 
  1、提升效率
 
  2、更优雅的编写
 
  常见的路由自动加载
 
  接触下来, 我发现有几个框架用不同方法实现了路由自动加载。
 
  一、think系列
 
  第一个是thinkPHP和thinkjs, 参考链接 thinkjs.org/zh-cn/doc/3…
 
  他两的关系属于是thinkjs是后来按照thinkPHP的思想设计开发的。
 
  他两路由自动加载属于基于文件的, 就是说你按控制器的名字和方法名写好,直接就可以访问路由,不需要额外的配置。
 
  1、thinkphp的路由自动加载
 
  tp是 按模块/控制器/方法文件名 自动加载
 
  module?/controller/Action
 
  比方下面这个Admin模块下, AdlistController.class.php里 index方法 他的路由就会自动加载为 Admin/adList/index
 
  2、thinkjs的路由自动加载
 
  控制器文件文件自动加载逻辑
 
  1)、应用初始化,创建实例
 
  ....
 
  2)、遍历控制器目录, 加载控制器
 
  得到目录文件对应的导出class的 Map
 
  例如 Controller目录下 他会加载出来模块、控制器、方法挂在他的app上。
 
  node.js的后端路由自动加载怎么实现
 
  {
 
    '/order': [class default_1 extends default_1],
 
    '/user': [class default_1 extends default_1]
 
  }
 
  3、控制器匹配部分
 
  上一步是在thinkjs应用启动阶段做的事情。
 
  这一步 控制器匹配部分 是在当请求进来的时候做的事情。
 
  就是当请求进来,会先进过,think-router 把module, controller, action ,解析出来挂在ctx上。
 
  在这里拿ctx上本次请求的module, controller, action去和启动时挂在app的 module, controller, action,列表去匹配, 如果有就执行。
 
  thinkjs和koa-router路由匹配的区别
 
  1、 think  think-router解析完, think-controller去匹配执行, 他这个是动态匹配。
 
  2、koa-router 匹配到路由后, 自己再用koa-compose组装一个小洋葱圈去执行
 
  ! 这种我的理解是程序启动就注册好的顺序image.png
 
  node.js的后端路由自动加载怎么实现
 
  node.js的后端路由自动加载怎么实现
 
  总结:thinkjs是先把你的控制器和方法加载出来, 最后当请求进来的时候,利用think-controller 去先匹配模块/控制器,再匹配方法, 如果有的话就帮你执行,没有的话,就404
 
  二、以egg改造版为例 装饰器的路由自动加载
 
  装饰器的写法类似于 java spring中的注解
 
  node框架中 nestjs和midwayjs已经全面拥抱了装饰器路由。
 
  写法比较优雅
 
  建议控制器的文件名和控制器名字保持一致, 这样你找api也比较好找 比如控制的文件名字叫 home.ts , 那你控制器注册也写 @controller('/home') 来保持一致。
 
  1、 控制器装饰器 @controller('/order')
 
  'use strict';
 
  import { Context } from 'egg';
 
  import BaseController from './base';
 
  import { formatDate } from '~/app/lib/utils';
 
  import { SelfController, Get } from './../router'
 
  @SelfController('/home')
 
  export default class HomeController extends BaseController {
 
    [x: string]: any;
 
    @validate()
 
    @Get("/")
 
    public async index(): Promise<void> {}
 
    
 
  }
 
  2、方法装饰器 @Get('/export')、 @Post('/list')
 
  get接口 就是 @Get()
 
  post的接口 就是 @Post()
 
    @Get("/")
 
    public async index(): Promise<void> {}
 
    @Post("/update")
 
    public async update(): Promise<void> {}
 
  3、装饰器路由统一注册
 
  这里统一按egg的方法循环注册路由
 
  'use strict';
 
  import { Application, Context } from 'egg';
 
  import 'reflect-metadata';
 
  const CONTROLLER_PREFIX: string = '';
 
  const methodMap: Map<string, any> = new Map<string, any>();
 
  const rootApiPath: string = '';
 
  interface CurController {
 
    pathName: string;
 
    fullPath: string;
 
  }
 
  /**
 
   * controller 装饰器,设置api公共前缀
 
   * @param pathPrefix {string}
 
   * @constructor
 
   */
 
  export const SelfController = (pathPrefix?: string): ClassDecorator => (targetClass): void => {
 
    // 在controller上定义pathPrefix的元数据
 
    // https://github.com/rbuckton/reflect-metadata
 
    (Reflect as any).defineMetadata(CONTROLLER_PREFIX, pathPrefix, targetClass);
 
  };
 
  const methodWrap = (path: string, requestMethod: string): MethodDecorator => (target, methodName): void => {
 
    // 路由装饰器参数为空时,路由为方法名
 
    const key = path ? `${requestMethod}·${path}·${String(methodName)}` : `${requestMethod}·${String(methodName)}·/${String(methodName)}`;
 
    methodMap.set(key, target);
 
  };
 
  // Post 请求
 
  export const Post = (path: string = ''): MethodDecorator => methodWrap(path, 'post');
 
  // Get 请求
 
  export const Get = (path: string = ''): MethodDecorator => methodWrap(path, 'get');
 
  export default (app: Application): void => {
 
    const { router } = app;
 
    // 遍历methodMap, 注册路由
 
    methodMap.forEach((curController: CurController, configString: string) => {
 
      // 请求方法, 请求路径, 方法名
 
      const [ requestMethod, path, methodName ] = configString.split(`·`);
 
      // 获取controller装饰器设置的公共前缀
 
      // 如果controller没有添加SelfController装饰器,则取文件名作为路径
 
      let controllerPrefix: string | undefined | null = (Reflect as any).getMetadata(CONTROLLER_PREFIX, curController.constructor);
 
      if (!(Reflect as any).hasMetadata(CONTROLLER_PREFIX, curController.constructor)) {
 
        controllerPrefix = `/${curController.pathName.split(`.`).reverse()[0]}`;
 
      }
 
      const func: (this: Context, ...args: any[]) => Promise<any> = async function (...args: any[]): Promise<any> {
 
        return new (curController.constructor as any)(this)[methodName](...args);
 
      };
 
      // 注册路由
 
      router[requestMethod](rootApiPath + controllerPrefix + path, func);
 
    });
 
  };
 
  建议使用node写服务直接上midwayjs或者nestjs
 

(编辑:航空爱好网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章