/** * 核心装饰器 * @author ranyunlong<549510622@qq.com> * license MIT */ import { TKServer } from "./Server" import chalk from 'chalk' import 'reflect-metadata' import { validator } from './Validator' import * as Knex from 'knex' interface Options { target?: TKServer.Controller; propertyKey?: string | symbol; descriptor?: TypedPropertyDescriptor parameterIndex?: number; } type CreateDecoratorCallback = (options: TKServer.ControllerOptions, propertyKey?: string| symbol, descriptorOrIndex?:TypedPropertyDescriptor | number) => void /** * 自定义指令方法 * @param callback 回调函数 返回核心的options选项 * @param Controller 可选自定义装饰器获取到的 构造函数 */ export function createDecorator(callback: CreateDecoratorCallback, Controller?: TKServer.Controller): TKServer.Decorator { function decorator(target: C): C | void; function decorator(target: C, propertyKey: string | symbol): void; function decorator(target: C, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor): void; function decorator(target: C, propertyKey: string | symbol, parameterIndex: number):void; function decorator(...args:any[]): any { if(args.length === 1) { // 只有一个参数时是类装饰器 否则是其他类型装饰器 const [ target ] = args; const meta = Reflect.getMetadata('design:paramtypes', target) || [] const options = target.prototype.$options = target.prototype.$options || {} if(options.metaData) { options.metaData = [...options.metaData, ...meta] } else { options.metaData = meta } target.prototype.$options = { propertys: options.propertys || {}, methods: options.methods || {}, metaData: options.metaData, parameters: options.parameters || {}, databases: options.databases || {}, target: target } callback(target.prototype.$options) } else { const [target, propertyKey, descriptor] = args const options = target.$options = target.$options || {} target.$options = { propertys: options.propertys || {}, methods: options.methods || {}, parameters: options.parameters || {}, databases: options.databases || {} } callback(target.$options, propertyKey, descriptor) } } if(!Controller) { return decorator } else { const meta = Reflect.getMetadata('design:paramtypes', Controller) if (Controller.prototype) { const options = Controller.prototype.$options if(options.metaData) { options.metaData = [...options.metaData, ...meta] } else { options.metaData = meta } Controller.prototype.$options = options || {} Controller.prototype.$options = { propertys: options.propertys || {}, methods: options.methods || {}, metaData: options.metaData, parameters: options.parameters || {}, databases: options.databases || {} } callback(Controller.prototype.$options) } else { Controller.$options = Controller.$options || {} const options = Controller.$options Controller.$options = { propertys: options.propertys || {}, methods: options.methods || {}, metaData: options.metaData || [], parameters: options.parameters || {}, databases: options.databases || {} } callback(Controller.$options) } } } /** * PropertyDecorator * Files * * Examples * ``` * import { Controller, Files } from '@tkrjs/Core' * @Controller('/') * export class IndexController { * } * ``` */ export function Controller(path: string): TKServer.ClassDecorator { return createDecorator((options: TKServer.ControllerOptions) => { options.baseRoute = path // 混入参数 options.mixinsParameters = function(ctx: TKServer.Context, propertyKey: string, tkOption: TKServer.Options) { // 获取parameters 装饰的参数 const parameters = options.parameters // 装饰器参数数组 const params:Array<{[key:string]: any | TKServer.Validator}> = [] // 检测路由方法上是否有相关的装饰器数据 if (!Array.isArray(parameters[propertyKey])) return []; // 遍历当前的参数 parameters[propertyKey].forEach((k: TKServer.Parameters)=> { params[k.parameterIndex] = params[k.parameterIndex] || {} if(Array.isArray(k.args)) { k.args.forEach((value)=>{ const data = (ctx as any)[k.type] || (ctx.request as any)[k.type] if(typeof data === 'object') { params[k.parameterIndex][value] = data[value] } }) } else if (typeof k.args === 'object'){ const data: object | undefined = (ctx as any)[k.type] || (ctx.request as any)[k.type] params[k.parameterIndex] = validator(k.args as any, data, tkOption) } else if(typeof k.args === 'string') { params[k.parameterIndex] = (ctx as any)[k.type][k.args] || (ctx.request as any)[k.type][k.args] } else { params[k.parameterIndex] = (ctx as any)[k.type] || (ctx.request as any)[k.type] } }) return params } // 混入属性 options.mixinsPropertys = function(ctx: TKServer.Context) { const propertys = Object.keys(options.propertys) propertys.forEach((key: string) => { const type = options.propertys[key] if(type === 'session') { options.target.prototype[key] = ctx['session'] } else { options.target.prototype[key] = (ctx.request as { [key:string]:any })[type] } }) } // 混入数据库 options.mixinsDatabases = function(ctx: TKServer.Context, config: TKServer.ServerDatabaseOptions) { const databases = options.databases Object.keys(databases).forEach((key: string) => { if (typeof databases[key] === 'string') { options.target.prototype[key] = Knex(config)(databases[key]) } else { options.target.prototype[key] = Knex(config) } }) } // 注册服务 options.registerServices = function(ctx: TKServer.Context) { return options.metaData.map(K => { return new K(ctx) }) } }) as TKServer.ClassDecorator } /** * createPropertyDecorator * @param type */ function createPropertyOrParameterDecorator(type: TKServer.PropertyDecoratorTypes) { function decorator(target: Object, propertyKey: string | symbol):void; function decorator(target: Object, propertyKey: string | symbol, parameterIndex: number):void; function decorator(field: string | string [] | object): ParameterDecorator; function decorator(...args: any[]): any { function handler (options: TKServer.ControllerOptions, propertyKey: string | symbol, parameterIndex: number) { if (typeof parameterIndex === 'number') { // 参数装饰器 const key = propertyKey as string options.parameters[key] = options.parameters[key] || [] options.parameters[key].push({ parameterIndex, type, args: args.length === 1 ? args[0] : undefined }) } else { // 属性装饰器 options.propertys[propertyKey as string] = type } } if (args.length === 1) { return createDecorator((options: TKServer.ControllerOptions, propertyKey: string | symbol, parameterIndex: number)=>{ handler(options, propertyKey, parameterIndex) }) } else { const [ target, propertyKey, parameterIndex ] = args; createDecorator((options: TKServer.ControllerOptions)=>{ handler(options, propertyKey, parameterIndex) },target) } } return decorator } /** * PropertyDecorator * Query * * Examples * ``` * import { Controller, Get, Query, TKServer } from '@tkrjs/Core' * interface Datas { * aa: string; * bb: string; * } * @Controller('/') * export class IndexController { * @Query query!: object; * @Get * private async index( * @Query param1:string, * @Query(['aa','bb']) param2:string, * @Query({aa: String, bb: Number }) param3:TKServer.Validator * ){ * console.log(param1) * console.log(param2) * console.log(this.query) * if(param3.getError()){ * return param3.getError() * } else { * return param3.data * } * } * } * ``` */ export const Query = createPropertyOrParameterDecorator('query') /** * PropertyDecorator * Param * * Examples * ``` * import { Controller, Get, Param, TKServer } from '@tkrjs/Core' * interface Datas { * id: number; * news: string; * } * @Controller('/') * export class IndexController { * @Get(':id') * @Post(':id/:news') * private async index( * @Param param:string, * @Param('id') param1: string; * @Param(['id','news']) param2: string; * @Param({id:Number, news:string}) param3: param3:TKServer.Validator; * ){ * console.log(param, param1, param2, param3) * } * } * ``` */ export const Param = createPropertyOrParameterDecorator('params') /** * PropertyDecorator * Body * * Examples * ``` * import { Controller, Post, Body, TKServer } from '@tkrjs/Core' * interface Datas { * aa: string; * bb: string; * } * @Controller('/') * export class IndexController { * @Body body!: string; * @Post * private async index( * @Body param1:string, * @Body(['aa','bb']) param2:string, * @Body({aa: String, bb: Number }) param3:TKServer.Validator * ){ * console.log(param1) * console.log(param2) * console.log(this.body) * if(param3.getError()){ * return param3.getError() * } else { * return param3.data * } * } * } * ``` */ export const Body = createPropertyOrParameterDecorator('body') /** * PropertyDecorator * Body * * Examples * ``` * import { Controller, Post, Session } from '@tkrjs/Core' * @Controller('/') * export class IndexController { * * @Session public session!: object; * * @Body * private async index( * @Session param:string, * @Session('user') user:string, * ){ * console.log(param) * console.log(user) * console.log(this.session) * } * } * ``` */ export const Session = createPropertyOrParameterDecorator('session') /** * PropertyDecorator * Files * * Examples * ``` * import { Controller, Files } from '@tkrjs/Core' * @Controller('/') * export class IndexController { * * @Files public files!: object; * * @Post * private async index( * @Files param:string, * ){ * console.log(param) * console.log(this.files) * } * } * ``` */ export const Files = createPropertyOrParameterDecorator('files') export const Header = createPropertyOrParameterDecorator('header') export const Headers = createPropertyOrParameterDecorator('headers') /** * createRequestMethodDecorator * @param type */ function createRequestMethodDecorator(type: TKServer.RequestMethodTypes) { function decorator(target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor): void; function decorator(route: string): MethodDecorator; function decorator(...args: any[]): any { function handler(options: TKServer.ControllerOptions, propertyKey: string, descriptor: TypedPropertyDescriptor, path: string) { // 如果路径不是以 '/' 开头 添加 '/' if (!/^\//.test(path)) { path = '/' + path } // 检测路由集合中是否已包含该方法 if(options.methods[path]) { // 如果method 存在 说明该路由已包含一种请求方式 if(options.methods[path].type) { // 如果为数组说明已经包含2个及以上的请求方式 if(Array.isArray(options.methods[path].type)){ const types = options.methods[path].type as TKServer.RequestMethodTypes[] if((types.indexOf(type) !== -1)) { throw new Error(chalk.redBright(`router path ${path} ${type} is repeat in method ${descriptor.value.name}`)) } types.push(type) } else { options.methods[path].type = [options.methods[path].type as TKServer.RequestMethodTypes, type] } } } else { options.methods[path] = { propertyKey, type, method: descriptor.value } } } if (args.length === 1) { const [ route ] = args; return createDecorator((options, propertyKey, descriptor) => { handler(options, propertyKey as string, descriptor as TypedPropertyDescriptor, route || propertyKey) }) } else { const [ target, propertyKey, descriptor ] = args; createDecorator((options) => { handler(options, propertyKey, descriptor, propertyKey ) }, target) } } return decorator } /** * RequestMethodDecorators * Get * * Examples * ``` * import { Controller, Get } from '@tkrjs/Core' * @Controller('/') * export class IndexController{ * @Get * private async index() { * // Request method Get * return 'http://localhost:3000/index' * } * * @Get * private async test() { * // Request method Get * return 'http://localhost:3000/test' * } * * @Get('api') * private async test1() { * // Request method Get * return 'http://localhost:3000/api' * } * * @Get('api/:id') * private async test1() { * // Request method Get * // {id} Is any data * return 'http://localhost:3000/api/{id}' * } * } * ``` */ export const Get = createRequestMethodDecorator('get') /** * RequestMethodDecorators * All * * Examples * ``` * import { Controller, All } from '@tkrjs/Core' * @Controller('/') * export class IndexController{ * @All * private async index() { * // Request method all * return 'http://localhost:3000/index' * } * * @All * private async test() { * // Request method all * return 'http://localhost:3000/test' * } * * @All('api') * private async test1() { * // Request method all * return 'http://localhost:3000/api' * } * * @All('api/:id') * private async test1() { * // Request method all * // {id} Is any data * return 'http://localhost:3000/api/{id}' * } * } * ``` */ export const All = createRequestMethodDecorator('all') /** * RequestMethodDecorators * Delete * * Examples * ``` * import { Controller, Delete } from '@tkrjs/Core' * @Controller('/') * export class IndexController{ * @Delete * private async index() { * // Request method Delete * return 'http://localhost:3000/index' * } * * @Delete * private async test() { * // Request method Delete * return 'http://localhost:3000/test' * } * * @Delete('api') * private async test1() { * // Request method Delete * return 'http://localhost:3000/api' * } * * @Delete('api/:id') * private async test1() { * // Request method Delete * // {id} Is any data * return 'http://localhost:3000/api/{id}' * } * } * ``` */ export const Delete = createRequestMethodDecorator('delete') /** * RequestMethodDecorators * Head * * Examples * ``` * import { Controller, Head } from '@tkrjs/Core' * @Controller('/') * export class IndexController{ * @Head * private async index() { * // Request method Head * return 'http://localhost:3000/index' * } * * @Head * private async test() { * // Request method Head * return 'http://localhost:3000/test' * } * * @Head('api') * private async test1() { * // Request method Head * return 'http://localhost:3000/api' * } * * @Head('api/:id') * private async test1() { * // Request method Head * // {id} Is any data * return 'http://localhost:3000/api/{id}' * } * } * ``` */ export const Head = createRequestMethodDecorator('head') /** * RequestMethodDecorators * Options * * Examples * ``` * import { Controller, Options } from '@tkrjs/Core' * @Controller('/') * export class IndexController{ * @Options * private async index() { * // Request method Options * return 'http://localhost:3000/index' * } * * @Options * private async test() { * // Request method Options * return 'http://localhost:3000/test' * } * * @Options('api') * private async test1() { * // Request method Options * return 'http://localhost:3000/api' * } * * @Options('api/:id') * private async test1() { * // Request method Options * // {id} Is any data * return 'http://localhost:3000/api/{id}' * } * } * ``` */ export const Options = createRequestMethodDecorator('options') /** * RequestMethodDecorators * Patch * * Examples * ``` * import { Controller, Patch } from '@tkrjs/Core' * @Controller('/') * export class IndexController{ * @Patch * private async index() { * // Request method Patch * return 'http://localhost:3000/index' * } * * @Patch * private async test() { * // Request method Patch * return 'http://localhost:3000/test' * } * * @Patch('api') * private async test1() { * // Request method Patch * return 'http://localhost:3000/api' * } * * @Patch('api/:id') * private async test1() { * // Request method Patch * // {id} Is any data * return 'http://localhost:3000/api/{id}' * } * } * ``` */ export const Patch = createRequestMethodDecorator('patch') /** * RequestMethodDecorators * Post * * Examples * ``` * import { Controller, Post } from '@tkrjs/Core' * @Controller('/') * export class IndexController{ * @Post * private async index() { * // Request method Post * return 'http://localhost:3000/index' * } * * @Post * private async test() { * // Request method Post * return 'http://localhost:3000/test' * } * * @Post('api') * private async test1() { * // Request method Post * return 'http://localhost:3000/api' * } * * @Post('api/:id') * private async test1() { * // Request method Post * // {id} Is any data * return 'http://localhost:3000/api/{id}' * } * } * ``` */ export const Post = createRequestMethodDecorator('post') /** * RequestMethodDecorators * Put * * Examples * ``` * import { Controller, Put } from '@tkrjs/Core' * @Controller('/') * export class IndexController{ * @Put * private async index() { * // Request method Put * return 'http://localhost:3000/index' * } * * @Put * private async test() { * // Request method Put * return 'http://localhost:3000/test' * } * * @Put('api') * private async test1() { * // Request method Put * return 'http://localhost:3000/api' * } * * @Put('api/:id') * private async test1() { * // Request method Put * // {id} Is any data * return 'http://localhost:3000/api/{id}' * } * } * ``` */ export const Put = createRequestMethodDecorator('put') export const Request = createPropertyOrParameterDecorator('request') export const Response = createPropertyOrParameterDecorator('response') /** * PropertyDecorator * * Examples * ``` * @Controller('/') * export class IndexController { * constructor() {} * @Database('users') db: TKServer.Database; * @Get('/') * private async index() { * return await this.db.select() * } * } * ``` */ export function Database(target: Object, propertyKey: string): void; export function Database(table: string): PropertyDecorator; export function Database(...args: Array) :any { if(args.length === 1) { const table: string = args[0] as string; return createDecorator((options: TKServer.ControllerOptions, propertyKey: string)=> { options.databases[propertyKey] = table; }) } else { const target = args[0] as C const propertyKey = args[1] as string createDecorator((options: TKServer.ControllerOptions) => { options.databases[propertyKey] = null; }, target) } }