1 装饰器路由注册
Express 框架可以支持装饰器,但它本身并未原生提供装饰器功能。要在 Express 中使用装饰器功能,通常需要借助 TypeScript 和第三方库(如 reflect-metadata
和 class-transformer
)来实现。这种方式适合构建更加结构化、模块化的应用程序,尤其是在大型项目中。
1. 安装
# 安装用于验证和转换的库
npm install class-validator class-transformer
1.2 配置 tsconfig.json
{
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"compilerOptions": {
"target": "ES6",
"module": "commonjs",
"rootDir": "./src",
"outDir": "./dist",
"strict": true,
"esModuleInterop": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true
},
"include": [
"src"
]
}
1.3 路由装饰器util 支持async
``src/utils/route.decorator.ts:
import { Router, Request, Response, NextFunction } from 'express';
const router = Router();
export function Route(method: 'get' | 'post' | 'put' | 'delete', path: string) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = async (req: Request, res: Response, next: NextFunction) => {
try {
await originalMethod(req, res, next);
} catch (error) {
next(error);
}
};
router[method](path, descriptor.value);
};
}
export { router };
1.4 路由案例
DecoratorController.ts
import { Request, Response } from 'express';
import { Route } from '../utils/route.decorator';
import ResponseUtil from "../utils/ResponseUtil";
export class DecoratorController {
@Route('get', '/user2/:id')
async getUserById(req: Request, res: Response) {
const { id } = req.params;
const data = { id };
await new Promise((resolve) => setTimeout(resolve, 100));
ResponseUtil.success(res, data, '查询成功', 20000);
}
}
1.5 注册
server.ts
import './controller_decorator/DecoratorController';
import { router } from './utils/route.decorator';
app.use('/api/v1', router);

2 装饰器 登录验证
2.1 定义登录验证装饰器
创建一个装饰器 @Auth
,用于检查请求头中是否包含有效的 token
。可以结合业务逻辑验证 token
,例如解码、检查过期等。
``src/filter/auth.ts:
import { Request, Response, NextFunction } from 'express';
export function Auth(validateTokenFunction: (token: string) => boolean) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = async (req: Request, res: Response, next: NextFunction) => {
const authorization = req.headers['authorization'];
console.log(`authorization: ${authorization}`);
if (!authorization || !validateTokenFunction(authorization)) {
return res.status(401).json({
code: 401,
message: 'Unauthorized: Invalid or missing token',
});
}
await originalMethod(req, res, next);
};
};
}
2.2 登录校验
token_util.ts
npm install jsonwebtoken
import jwt from 'jsonwebtoken';
export class TokenUtil {
private static secret = '123456有勇气的牛排';
static validateToken(token: string): boolean {
console.log("token验证")
return true;
}
}
2.3 注册到路由
import { Request, Response } from 'express';
import { Route } from '../utils/route.decorator';
import ResponseUtil from "../utils/ResponseUtil";
import { Auth } from "../filter/auth";
import { TokenUtil } from "./token_util";
export class DecoratorController {
@Route('get', '/user2/:id')
async getUserById(req: Request, res: Response) {
const { id } = req.params;
const data = { id };
await new Promise((resolve) => setTimeout(resolve, 100));
ResponseUtil.success(res, data, '查询成功', 20000);
}
@Route('get', '/user3/:id')
@Auth(TokenUtil.validateToken)
async getUserById2(req: Request, res: Response) {
const { id } = req.params;
const data = { id };
console.log('999');
await new Promise((resolve) => setTimeout(resolve, 100));
ResponseUtil.success(res, data, '查询成功', 20000);
}
}
<h2><a id="1__0"></a>1 装饰器路由注册</h2>
<p>Express 框架可以支持装饰器,但它本身并未原生提供装饰器功能。要在 Express 中使用装饰器功能,通常需要借助 TypeScript 和第三方库(如 <code>reflect-metadata</code> 和 <code>class-transformer</code>)来实现。这种方式适合构建更加结构化、模块化的应用程序,尤其是在大型项目中。</p>
<h3><a id="1__4"></a>1. 安装</h3>
<pre><div class="hljs"><code class="lang-shell"><span class="hljs-meta"># </span><span class="language-bash">安装用于验证和转换的库</span>
npm install class-validator class-transformer
</code></div></pre>
<h3><a id="12__tsconfigjson_11"></a>1.2 配置 tsconfig.json</h3>
<pre><div class="hljs"><code class="lang-ts">{
<span class="hljs-string">"esModuleInterop"</span>: <span class="hljs-literal">true</span>,
<span class="hljs-string">"allowSyntheticDefaultImports"</span>: <span class="hljs-literal">true</span>,
<span class="hljs-string">"compilerOptions"</span>: {
<span class="hljs-string">"target"</span>: <span class="hljs-string">"ES6"</span>,
<span class="hljs-string">"module"</span>: <span class="hljs-string">"commonjs"</span>,
<span class="hljs-string">"rootDir"</span>: <span class="hljs-string">"./src"</span>,
<span class="hljs-string">"outDir"</span>: <span class="hljs-string">"./dist"</span>,
<span class="hljs-string">"strict"</span>: <span class="hljs-literal">true</span>,
<span class="hljs-string">"esModuleInterop"</span>: <span class="hljs-literal">true</span>,
<span class="hljs-comment">// 启用装饰器支持</span>
<span class="hljs-string">"experimentalDecorators"</span>: <span class="hljs-literal">true</span>,
<span class="hljs-string">"emitDecoratorMetadata"</span>: <span class="hljs-literal">true</span>
},
<span class="hljs-string">"include"</span>: [
<span class="hljs-string">"src"</span>
]
}
</code></div></pre>
<h3><a id="13_util_async_34"></a>1.3 路由装饰器util 支持async</h3>
<p>``src/utils/route.decorator.ts<code>:</code></p>
<pre><div class="hljs"><code class="lang-tsx"><span class="hljs-keyword">import</span> { <span class="hljs-title class_">Router</span>, <span class="hljs-title class_">Request</span>, <span class="hljs-title class_">Response</span>, <span class="hljs-title class_">NextFunction</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">'express'</span>;
<span class="hljs-comment">// 全局路由对象</span>
<span class="hljs-keyword">const</span> router = <span class="hljs-title class_">Router</span>();
<span class="hljs-comment">/**
* 路由装饰器
* <span class="hljs-doctag">@param</span> method 请求方法 ('get', 'post', 'put', 'delete')
* <span class="hljs-doctag">@param</span> path 路由路径
*/</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">Route</span>(<span class="hljs-params">method: <span class="hljs-string">'get'</span> | <span class="hljs-string">'post'</span> | <span class="hljs-string">'put'</span> | <span class="hljs-string">'delete'</span>, path: <span class="hljs-built_in">string</span></span>) {
<span class="hljs-keyword">return</span> <span class="hljs-keyword">function</span> (<span class="hljs-params">target: <span class="hljs-built_in">any</span>, propertyKey: <span class="hljs-built_in">string</span>, descriptor: PropertyDescriptor</span>) {
<span class="hljs-keyword">const</span> originalMethod = descriptor.<span class="hljs-property">value</span>;
<span class="hljs-comment">// 包装异步函数,处理错误</span>
descriptor.<span class="hljs-property">value</span> = <span class="hljs-keyword">async</span> (<span class="hljs-attr">req</span>: <span class="hljs-title class_">Request</span>, <span class="hljs-attr">res</span>: <span class="hljs-title class_">Response</span>, <span class="hljs-attr">next</span>: <span class="hljs-title class_">NextFunction</span>) => {
<span class="hljs-keyword">try</span> {
<span class="hljs-keyword">await</span> <span class="hljs-title function_">originalMethod</span>(req, res, next);
} <span class="hljs-keyword">catch</span> (error) {
<span class="hljs-title function_">next</span>(error); <span class="hljs-comment">// 将错误交给 Express 的错误处理中间件</span>
}
};
<span class="hljs-comment">// 将路由注册到全局 router 对象</span>
router[method](path, descriptor.<span class="hljs-property">value</span>);
};
}
<span class="hljs-comment">// 导出全局 router</span>
<span class="hljs-keyword">export</span> { router };
</code></div></pre>
<h3><a id="14__71"></a>1.4 路由案例</h3>
<p><code>DecoratorController.ts</code></p>
<pre><div class="hljs"><code class="lang-tsx"><span class="hljs-keyword">import</span> { <span class="hljs-title class_">Request</span>, <span class="hljs-title class_">Response</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">'express'</span>;
<span class="hljs-keyword">import</span> { <span class="hljs-title class_">Route</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">'../utils/route.decorator'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-title class_">ResponseUtil</span> <span class="hljs-keyword">from</span> <span class="hljs-string">"../utils/ResponseUtil"</span>;
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">DecoratorController</span> {
<span class="hljs-comment">// http://127.0.0.1:3000/api/v1/user2/2</span>
<span class="hljs-meta">@Route</span>(<span class="hljs-string">'get'</span>, <span class="hljs-string">'/user2/:id'</span>)
<span class="hljs-keyword">async</span> <span class="hljs-title function_">getUserById</span>(<span class="hljs-params">req: Request, res: Response</span>) {
<span class="hljs-keyword">const</span> { id } = req.<span class="hljs-property">params</span>; <span class="hljs-comment">// 从动态路由中提取参数</span>
<span class="hljs-keyword">const</span> data = { id };
<span class="hljs-comment">// 假设这里是异步数据库查询操作</span>
<span class="hljs-keyword">await</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve</span>) =></span> <span class="hljs-built_in">setTimeout</span>(resolve, <span class="hljs-number">100</span>));
<span class="hljs-title class_">ResponseUtil</span>.<span class="hljs-title function_">success</span>(res, data, <span class="hljs-string">'查询成功'</span>, <span class="hljs-number">20000</span>);
}
}
</code></div></pre>
<h3><a id="15__96"></a>1.5 注册</h3>
<p><code>server.ts</code></p>
<pre><div class="hljs"><code class="lang-tsx"><span class="hljs-comment">// 装饰器路由注册</span>
<span class="hljs-keyword">import</span> <span class="hljs-string">'./controller_decorator/DecoratorController'</span>; <span class="hljs-comment">// 确保装饰器逻辑生效</span>
<span class="hljs-keyword">import</span> { router } <span class="hljs-keyword">from</span> <span class="hljs-string">'./utils/route.decorator'</span>;
app.<span class="hljs-title function_">use</span>(<span class="hljs-string">'/api/v1'</span>, router);
</code></div></pre>
<p><img src="https://static.couragesteak.com/article/344633e9d92483d0be7c17005bb12c0b.png" alt="image.png" /></p>
<h2><a id="2___109"></a>2 装饰器 登录验证</h2>
<h3><a id="21__111"></a>2.1 定义登录验证装饰器</h3>
<p>创建一个装饰器 <code>@Auth</code>,用于检查请求头中是否包含有效的 <code>token</code>。可以结合业务逻辑验证 <code>token</code>,例如解码、检查过期等。</p>
<p>``src/filter/auth.ts<code>:</code></p>
<pre><div class="hljs"><code class="lang-tsx"><span class="hljs-keyword">import</span> { <span class="hljs-title class_">Request</span>, <span class="hljs-title class_">Response</span>, <span class="hljs-title class_">NextFunction</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">'express'</span>;
<span class="hljs-comment">/**
* Auth 装饰器:验证请求头中的 token
* <span class="hljs-doctag">@param</span> validateTokenFunction 自定义的 token 验证逻辑
*/</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">Auth</span>(<span class="hljs-params">validateTokenFunction: (token: <span class="hljs-built_in">string</span>) => <span class="hljs-built_in">boolean</span></span>) {
<span class="hljs-keyword">return</span> <span class="hljs-keyword">function</span> (<span class="hljs-params">target: <span class="hljs-built_in">any</span>, propertyKey: <span class="hljs-built_in">string</span>, descriptor: PropertyDescriptor</span>) {
<span class="hljs-keyword">const</span> originalMethod = descriptor.<span class="hljs-property">value</span>;
<span class="hljs-comment">// 包装原始方法,添加 token 验证逻辑</span>
descriptor.<span class="hljs-property">value</span> = <span class="hljs-keyword">async</span> (<span class="hljs-attr">req</span>: <span class="hljs-title class_">Request</span>, <span class="hljs-attr">res</span>: <span class="hljs-title class_">Response</span>, <span class="hljs-attr">next</span>: <span class="hljs-title class_">NextFunction</span>) => {
<span class="hljs-comment">// const authorization = req.headers['authorization']?.replace('Bearer ', '');</span>
<span class="hljs-keyword">const</span> authorization = req.<span class="hljs-property">headers</span>[<span class="hljs-string">'authorization'</span>];
<span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">`authorization: <span class="hljs-subst">${authorization}</span>`</span>);
<span class="hljs-keyword">if</span> (!authorization || !<span class="hljs-title function_">validateTokenFunction</span>(authorization)) {
<span class="hljs-comment">// 如果 token 无效,返回 401</span>
<span class="hljs-keyword">return</span> res.<span class="hljs-title function_">status</span>(<span class="hljs-number">401</span>).<span class="hljs-title function_">json</span>({
<span class="hljs-attr">code</span>: <span class="hljs-number">401</span>,
<span class="hljs-attr">message</span>: <span class="hljs-string">'Unauthorized: Invalid or missing token'</span>,
});
}
<span class="hljs-comment">// 验证通过,继续执行原始路由逻辑</span>
<span class="hljs-keyword">await</span> <span class="hljs-title function_">originalMethod</span>(req, res, next);
};
};
}
</code></div></pre>
<h3><a id="22__149"></a>2.2 登录校验</h3>
<p><code>token_util.ts</code></p>
<pre><div class="hljs"><code class="lang-shell">npm install jsonwebtoken
</code></div></pre>
<pre><div class="hljs"><code class="lang-tsx"><span class="hljs-keyword">import</span> jwt <span class="hljs-keyword">from</span> <span class="hljs-string">'jsonwebtoken'</span>;
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">TokenUtil</span> {
<span class="hljs-comment">// 用于签名的密钥</span>
<span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> secret = <span class="hljs-string">'123456有勇气的牛排'</span>;
<span class="hljs-comment">// 验证 token 是否有效 实际逻辑根据业务自己补充吧</span>
<span class="hljs-keyword">static</span> <span class="hljs-title function_">validateToken</span>(<span class="hljs-attr">token</span>: <span class="hljs-built_in">string</span>): <span class="hljs-built_in">boolean</span> {
<span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">"token验证"</span>)
<span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
<span class="hljs-comment">// try {</span>
<span class="hljs-comment">// const decoded = jwt.verify(token, this.secret);</span>
<span class="hljs-comment">// console.log('Token is valid:', decoded);</span>
<span class="hljs-comment">// return true;</span>
<span class="hljs-comment">// } catch (err) {</span>
<span class="hljs-comment">// console.error('Invalid token:', err.message);</span>
<span class="hljs-comment">// return false;</span>
<span class="hljs-comment">// }</span>
}
}
</code></div></pre>
<h3><a id="23__181"></a>2.3 注册到路由</h3>
<pre><div class="hljs"><code class="lang-tsx"><span class="hljs-keyword">import</span> { <span class="hljs-title class_">Request</span>, <span class="hljs-title class_">Response</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">'express'</span>;
<span class="hljs-keyword">import</span> { <span class="hljs-title class_">Route</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">'../utils/route.decorator'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-title class_">ResponseUtil</span> <span class="hljs-keyword">from</span> <span class="hljs-string">"../utils/ResponseUtil"</span>;
<span class="hljs-keyword">import</span> { <span class="hljs-title class_">Auth</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">"../filter/auth"</span>;
<span class="hljs-keyword">import</span> { <span class="hljs-title class_">TokenUtil</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">"./token_util"</span>;
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">DecoratorController</span> {
<span class="hljs-comment">// http://127.0.0.1:3000/api/v1/user2/2</span>
<span class="hljs-meta">@Route</span>(<span class="hljs-string">'get'</span>, <span class="hljs-string">'/user2/:id'</span>)
<span class="hljs-keyword">async</span> <span class="hljs-title function_">getUserById</span>(<span class="hljs-params">req: Request, res: Response</span>) {
<span class="hljs-keyword">const</span> { id } = req.<span class="hljs-property">params</span>; <span class="hljs-comment">// 从动态路由中提取参数</span>
<span class="hljs-keyword">const</span> data = { id };
<span class="hljs-comment">// 假设这里是异步数据库查询操作</span>
<span class="hljs-keyword">await</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve</span>) =></span> <span class="hljs-built_in">setTimeout</span>(resolve, <span class="hljs-number">100</span>)); <span class="hljs-comment">// 模拟延迟</span>
<span class="hljs-title class_">ResponseUtil</span>.<span class="hljs-title function_">success</span>(res, data, <span class="hljs-string">'查询成功'</span>, <span class="hljs-number">20000</span>);
}
<span class="hljs-comment">// 登录验证</span>
<span class="hljs-comment">// http://127.0.0.1:3000/api/v1/user3/2</span>
<span class="hljs-meta">@Route</span>(<span class="hljs-string">'get'</span>, <span class="hljs-string">'/user3/:id'</span>)
<span class="hljs-meta">@Auth</span>(<span class="hljs-title class_">TokenUtil</span>.<span class="hljs-property">validateToken</span>) <span class="hljs-comment">// 应用 Auth 装饰器</span>
<span class="hljs-keyword">async</span> <span class="hljs-title function_">getUserById2</span>(<span class="hljs-params">req: Request, res: Response</span>) {
<span class="hljs-keyword">const</span> { id } = req.<span class="hljs-property">params</span>; <span class="hljs-comment">// 从动态路由中提取参数</span>
<span class="hljs-keyword">const</span> data = { id };
<span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">'999'</span>);
<span class="hljs-comment">// 假设这里是异步数据库查询操作</span>
<span class="hljs-keyword">await</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve</span>) =></span> <span class="hljs-built_in">setTimeout</span>(resolve, <span class="hljs-number">100</span>)); <span class="hljs-comment">// 模拟延迟</span>
<span class="hljs-title class_">ResponseUtil</span>.<span class="hljs-title function_">success</span>(res, data, <span class="hljs-string">'查询成功'</span>, <span class="hljs-number">20000</span>);
}
}
</code></div></pre>
留言