前言
需要用到的工具:
- 参数装饰器:记录参数的索引,方法名;
- tslib_1.__metadata:这是ts的内置方法用于保存方法的参数类型、方法类型以及方法返回数据的类型;
- reflect-metadata:保存参数的数据;
- class-validator:对类成员进行校验;
- class-transformer:使用class-transformer的plainToClass方法将对象转化成指定类的实例。
参数装饰器保存相关元数据
参数装饰器返回返回三个参数:1.构造函数的原型对象;2.参数所属方法名;3.参数的索引。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
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
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| 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
编译后的代码如下:
1 2 3 4 5 6 7 8
| 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
方法:
1
| Reflect.defineMetadata(metadataKey, metadataValue, C.prototype, "method");
|
关于tslib_1.__metadata
的具体实现可以参考附录中的tslib_1.__metadata。
由上可知,在执行参数装饰器时,将方法的参数类型作为一个数组保存到构造函数的原型对象->参数所属方法名->metadataKey之中。
参数校验
根据构造函数原型对象引用、参数所属方法名、metadata key即刚刚的design:paramtypes
就可以拿到刚刚存储的参数类型数组。
1 2 3 4 5 6
| const handlerArgsTypes: any[] = Reflect.getMetadata( ReflectDefaultMetadata.DESGIN_PARAMTYPES, prototype, methodName, );
|
然后在根据存储的参数索引即可拿到参数对应的类型。
有了类型信息,再加上请求是传过来的对象,就可以使用class-transformer的plainToClass
方法将请求的参数转化成参数类型的实例:
1
| clsObj = plainToClass(type, param)
|
有了实例,就可以使用class-validator
的validate
方法对请求参数进行校验。class-validator
这个库提供了许多装饰器可以对类实例的成员进行校验,比如@isInt
可以校验可以校验整型数据,@isString
可以校验字符类型的数据
1 2 3 4 5 6 7 8 9 10 11
| import { IsInt, IsString, validate} from 'class-validator';
class AgeInfo { @IsInt({ message: '$property必须是整型' }) num: number; @IsString() born: string; }
const errors = await validate(clsObj);
|
附录
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| __metadata = function (metadataKey, metadataValue) { if ( typeof Reflect === "object" && typeof Reflect.metadata === "function" ) { return Reflect.metadata(metadataKey, metadataValue); } }
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; }
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| __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; };
|