Open isaaxite opened 4 years ago
参数装饰器返回返回三个参数:1.构造函数的原型对象;2.参数所属方法名;3.参数的索引。
// 1.target: 构造函数的原型对象;
// 2.key: 参数所属方法名;
// 3.index: 参数的索引
const createRouteParamDecorator = (paramtype) => {
return (data?: any, ...pipes: PipeTransform[]) => {
return (target, key, index) => {
const args = Reflect.getMetadata(ROUTE_ARGS_METADATA, target, key) || {};
Reflect.defineMetadata(
ROUTE_ARGS_METADATA,
assignMetadata(args, paramtype, index, data, ...pipes),
target,
key,
);
};
};
};
export const Query = (property?: string, ...pipes: PipeTransform[]) => {
return createRouteParamDecorator(RouteParamtypesEnum.QUERY)(property, ...pipes);
};
在参数装饰器保存相关元数据分别保存了三个数据,但是为什么呢?自然是用于取出保存的某数据,而这个某数据就是参数定义的类型,比如现在有一个类Foo
,并定义了一个setInfo
成员方法,这个方法的参数定义了一个类型NameInfo
:
class NameInfo {
ch: string;
en: string;
}
class AgeInfo {
num: number;
born: string;
}
class PersonInfo {
name: NameInfo;
age: AgeInfo;
}
@Control('foo')
class Foo {
private name: NameInfo;
private age: AgeInfo;
@Put('name')
setInfo(
@Query() _name: NameInfo,
@Query() _age: AgeInfo,
): PersonInfo {
this.name = _name;
this.age = _age;
return { name: this.name, age: this.age };
}
}
setInfo
编译后的代码如下:
tslib_1.__decorate([
index_1.Put('name'),
tslib_1.__param(0, param_dec_1.Query()),
tslib_1.__param(1, param_dec_1.Query()),
tslib_1.__metadata("design:type", Function),
tslib_1.__metadata("design:paramtypes", [NameInfo, AgeInfo]),
tslib_1.__metadata("design:returntype", PersonInfo)
], Foo.prototype, "setInfo", null);
在定义的参数装饰器外,还另外多定义了三个tslib_1.__metadata
装饰器。这个三个tslib_1.__metadata
的定义分别是:
tslib_1.__metadata("design:type", Function)
:存储成员方法的方法类型;
tslib_1.__metadata("design:paramtypes", [NameInfo, AgeInfo])
:方法的参数类型;
tslib_1.__metadata("design:returntype", PersonInfo)
:方法返回的类型。
而这个tslib_1.__metadata
方法相当于调用reflect-metadata库的Reflect.defineMetadata
方法:
Reflect.defineMetadata(metadataKey, metadataValue, C.prototype, "method");
关于tslib_1.__metadata
的具体实现可以参考附录中的tslib_1.__metadata。
由上可知,在执行参数装饰器时,将方法的参数类型作为一个数组保存到构造函数的原型对象->参数所属方法名->metadataKey之中。
根据构造函数原型对象引用、参数所属方法名、metadata key即刚刚的design:paramtypes
就可以拿到刚刚存储的参数类型数组。
const handlerArgsTypes: any[] = Reflect.getMetadata(
ReflectDefaultMetadata.DESGIN_PARAMTYPES,
prototype,
methodName,
);
// [NameInfo, AgeInfo]
然后在根据存储的参数索引即可拿到参数对应的类型。
有了类型信息,再加上请求是传过来的对象,就可以使用class-transformer的plainToClass
方法将请求的参数转化成参数类型的实例:
clsObj = plainToClass(type, param)
有了实例,就可以使用class-validator
的validate
方法对请求参数进行校验。class-validator
这个库提供了许多装饰器可以对类实例的成员进行校验,比如@isInt
可以校验可以校验整型数据,@isString
可以校验字符类型的数据
import { IsInt, IsString, validate} from 'class-validator';
class AgeInfo {
@IsInt({ message: '$property必须是整型' })
num: number;
@IsString()
born: string;
}
const errors = await validate(clsObj);
ts会默认将方法的参数类型作为一个数组以指定路径存储起来,这路径是以构造函数的原型对象引用、方法名、默认的metadata key(design:paramtypes)组成;
使用参数装饰器将参数的索引、所属方法名、构造函数的原型对象引用存储起来,后续就可以将参数类型取出;
使用class-transformer
库的plainToClass
即可以将请求参数转化成参数对应类型的实例;
使用class-validator
的校验装饰器对参数类型的成员进行类型描述,最后就可以根据class-transformer
转化来的实例,配合class-validator
的vadilate
方法对请求参数进行校验,并可以定制错误信息。
// tslib_1.__metadata("design:paramtypes", [NameInfo, AgeInfo])
__metadata = function (metadataKey, metadataValue) {
if (
typeof Reflect === "object"
&& typeof Reflect.metadata === "function"
) {
return Reflect.metadata(metadataKey, metadataValue);
}
}
// Reflect.metadata的实现
function metadata(metadataKey, metadataValue) {
function decorator(target, propertyKey) {
if (!IsObject(target))
throw new TypeError();
if (!IsUndefined(propertyKey) && !IsPropertyKey(propertyKey))
throw new TypeError();
OrdinaryDefineOwnMetadata(metadataKey, metadataValue, target, propertyKey);
}
return decorator;
}
// Reflect.defineMetadata的实现
function defineMetadata(metadataKey, metadataValue, target, propertyKey) {
if (!IsObject(target))
throw new TypeError();
if (!IsUndefined(propertyKey))
propertyKey = ToPropertyKey(propertyKey);
return OrdinaryDefineOwnMetadata(metadataKey, metadataValue, target, propertyKey);
}
__decorate = function (decorators, target, key, desc) {
var c = arguments.length,
r = c < 3
? target
: desc === null
? desc = Object.getOwnPropertyDescriptor(target, key)
: desc,
d;
if (
typeof Reflect === "object"
&& typeof Reflect.decorate === "function"
) {
r = Reflect.decorate(decorators, target, key, desc);
} else {
for (var i = decorators.length - 1; i >= 0; i--) {
if (d = decorators[i]) {
r = (
c < 3
? d(r)
: c > 3
? d(target, key, r)
: d(target, key)
) || r;
}
}
}
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
前言
需要用到的工具: