Développeurs Node.js ayant une certaine base
有一定基础的Node.js开发人员
中等
今天来谈谈node后端中路由的问题。【相关教程推荐:nodejs视频教程】
我们前端同学或者是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上。
{ '/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,列表去匹配, 如果有就执行。
think-controller的匹配逻辑详见 github.com/thinkjs/thi…
1、 think think-router解析完, think-controller去匹配执行, 他这个是动态匹配。
2、koa-router 匹配到路由后, 自己再用koa-compose组装一个小洋葱圈去执行
! 这种我的理解是程序启动就注册好的顺序image.png
总结:thinkjs是先把你的控制器和方法加载出来, 最后当请求进来的时候,利用think-controller
Moyen
Contexte
Aujourd'hui, parlons des problèmes de routage dans le backend du nœud. [Tutoriels associés recommandés : tutoriel vidéo nodejs]Nos camarades de classe front-end ou nodejs Étudiants côté serveur, lorsque vous utilisez express et koajs pour écrire des interfaces, nous n'avons pas besoin d'écrire des routes, par exemple comme suit
🎜Interface de connexionrouter.post('/user/login', user.login );
🎜🎜Obtenir l'interface d'informations utilisateurrouter.get('/user/info', checkAuth, user.xxx);
🎜🎜Cette méthode d'écriture est très courante. Enregistrez d'abord la route. , puis spécifiez les méthodes d'exécution ultérieures. 🎜🎜🎜Mais quand il y a de plus en plus d'interfaces, par exemple 1000 interfaces, vous devez vous inscrire comme ça 1000 fois. S'il y a plus d'interfaces, je pense que c'est une chose très gênante et inélégante🎜🎜🎜exemple d'enregistrement de routage koa&express. 🎜'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> {} }</void>
@Get("/") public async index(): Promise<void> {} @Post("/update") public async update(): Promise<void> {}</void></void>
'use strict'; import { Application, Context } from 'egg'; import 'reflect-metadata'; const CONTROLLER_PREFIX: string = ''; const methodMap: Map<string> = new Map<string>(); 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); }); };</any></any></string></string>
Admin/adList/index
🎜🎜🎜🎜🎜2. Chargement automatique des routes thinkjs🎜🎜🎜Logique de chargement automatique du fichier du contrôleur🎜🎜1), initialisation de l'application, création d'instancePièce de correspondance du contrôleur
est effectuée lorsque la demande arrive. 🎜🎜C'est-à-dire que lorsqu'une requête arrive, elle sera transmise en premier. Think-router analyse le module, le contrôleur, l'action et le suspend sur ctx. 🎜🎜Ici, prenez le module, le contrôleur et l'action de cette requête sur ctx et faites-les correspondre avec le module, le contrôleur, l'action et la liste accrochés dans l'application au démarrage. S'il y en a un, exécutez-le. 🎜🎜La logique de correspondance de Think-controller est détaillée dans github.com/thinkjs/thi…🎜think-controller
pour faire correspondre d'abord le module/contrôleur, puis la méthode s'il y en a une, elle sera exécutée pour vous. Sinon, juste 404🎜🎜🎜🎜 2. En prenant comme exemple la version modifiée d'oeuf, l'itinéraire du décorateur est automatiquement chargé🎜🎜🎜🎜🎜L'écriture du décorateur est similaire aux annotations dans java spring🎜🎜node框架中 nestjs
和midwayjs
已经全面拥抱了装饰器路由。
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> {} }</void>
2、方法装饰器 @Get('/export')、 @Post('/list')
get接口 就是 @Get()
post的接口 就是 @Post()
@Get("/") public async index(): Promise<void> {} @Post("/update") public async update(): Promise<void> {}</void></void>
3、装饰器路由统一注册
这里统一按egg的方法循环注册路由
'use strict'; import { Application, Context } from 'egg'; import 'reflect-metadata'; const CONTROLLER_PREFIX: string = ''; const methodMap: Map<string> = new Map<string>(); 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); }); };</any></any></string></string>
建议使用node写服务直接上midwayjs或者nestjs
通过如上比较,相信你对think系列框架堵文件的路由自动加载和装饰器的路由加载,有了一定了解, 他们的这种设计思想值得学习吧
, 希望对你有所启发。
还有我认为装饰器的路由写起来,比较优雅, 不知道各位小伙伴怎么看,评论区说说?
更多node相关知识,请访问:nodejs 教程!
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!