前言
在 NestJS 中,@Query
装饰器让我们能够优雅地从请求中提取查询参数。本文将通过一个简单的 demo 来深入理解它的实现原理。
一、基本概念
1.1 装饰器的作用
在这个实现中,我们使用了两个关键的装饰器:
@Query
:参数装饰器,负责收集和存储参数信息@LogMethod
:方法装饰器,负责处理和注入参数
1.2 元数据的作用
使用 reflect-metadata
来存储和读取装饰器的元数据,这是实现依赖注入的关键。
二、核心实现
2.1 参数装饰器 @Query
typescript
function Query(paramName: string) {
return function (target: Object, methodKey: string, parameterIndex: number) {
const existingParams = Reflect.getMetadata(QUERY_METADATA_KEY, target, methodKey) || [];
existingParams.push({
index: parameterIndex,
name: paramName
});
Reflect.defineMetadata(QUERY_METADATA_KEY, existingParams, target, methodKey);
};
}
这个装饰器的职责是:
- 收集参数信息(参数名和位置)
- 将信息存储到元数据中
2.2 方法装饰器 @LogMethod
typescript
function LogMethod() {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (req: Request) {
const queryParams = Reflect.getMetadata(QUERY_METADATA_KEY, target, propertyKey) || [];
const args = queryParams
.sort((a, b) => a.index - b.index)
.map(param => req.query[param.name]);
return originalMethod.apply(this, args);
};
};
}
这个装饰器的职责是:
- 读取存储的参数信息
- 从请求中提取参数值
- 按正确顺序组装参数
- 调用原始方法
三、使用示例
typescript
class TestController {
@LogMethod()
getUsers(@Query('page') page: string, @Query('size') size: string) {
return `Getting users from page ${page} with size ${size}`;
}
}
// 使用
const controller = new TestController();
const request = {
query: {
page: '1',
size: '10'
}
};
const result = controller.getUsers(request);
四、工作流程
-
参数装饰器执行:
@Query('page')
存储第一个参数信息@Query('size')
存储第二个参数信息
-
方法装饰器执行:
- 读取存储的参数信息
- 从请求中提取对应的值
- 按顺序组装参数数组
- 调用原始方法
-
最终效果:
- 输入:
{ query: { page: '1', size: '10' } }
- 转换为:
getUsers('1', '10')
- 输入:
五、实现原理总结
-
参数装饰器
@Query
的作用:- 只负责解析和保存参数信息
- 将参数位置和名称存储到元数据中
-
方法装饰器
@LogMethod
的作用:- 读取存储的参数信息
- 从请求中提取实际值
- 重新组装参数并调用方法
这种设计模式让我们能够:
- 优雅地处理请求参数
- 实现依赖注入
- 分离关注点
- 提高代码可维护性
六、应用场景
- 请求参数处理
- 依赖注入实现
- 参数转换和验证
- API 接口开发
完整代码
js
import 'reflect-metadata';
// 定义装饰器元数据的 key
const QUERY_METADATA_KEY = 'query_params';
// 创建参数装饰器
function Query(paramName: string) {
return function (target: Object, methodKey: string | symbol, parameterIndex: number) {
console.log('Query target====>', target);
console.log('Query methodKey====>', methodKey);
console.log('Query parameterIndex====>', parameterIndex);
// 获取现有的参数元数据或初始化新的
const existingParams: Array<{ index: number; name: string }> =
Reflect.getMetadata(QUERY_METADATA_KEY, target, methodKey) || [];
// 添加新的参数信息
existingParams.push({
index: parameterIndex,
name: paramName
});
console.log('existingParams====>', existingParams);
// 保存更新后的元数据
Reflect.defineMetadata(QUERY_METADATA_KEY, existingParams, target, methodKey);
};
}
// 模拟请求对象
interface Request {
query: Record<string, string>;
}
// 模拟控制器
class TestController {
@LogMethod()
getUsers(@Query('page') page: string, @Query('size') size: string) {
return `Getting users from page ${page} with size ${size}`;
}
}
// 方法装饰器 - 用于处理请求
function LogMethod() {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
console.log('target====>', target);
console.log('propertyKey====>', propertyKey);
console.log('descriptor====>', descriptor);
const originalMethod = descriptor.value;
descriptor.value = function (req: Request) {
// 获取查询参数的元数据
const queryParams: Array<{ index: number; name: string }> =
Reflect.getMetadata(QUERY_METADATA_KEY, target, propertyKey) || [];
// 根据元数据构建方法参数
const args = queryParams
.sort((a, b) => a.index - b.index)
.map(param => req.query[param.name]);
console.log('args====>', args);
// 调用原始方法
const result = originalMethod.apply(this, args);
console.log(`====>Method ${propertyKey} called with query params:`, req.query);
return result;
};
return descriptor;
};
}
// 使用示例
const controller = new TestController();
// 模拟请求
const request: Request = {
query: {
page: '1',
size: '10'
}
};
// 测试调用
const result = (controller.getUsers as any)(request);
console.log('Result=====>', result);