import * as co from 'co' ; import { Controller , EggAppConfig } from 'egg' ; import { getGlobalType } from 'power-di/utils' ; import { ControllerMetadataType , ControllerType , RouteType , MiddlewareType , MethodType , } from './type' ; import { isGeneratorFunction , getNameAndMethod , getCurrentLoadFilePath } from './util' ; import { route } from './route' ; const CtrlMetaSymbol = Symbol ( 'CtrlMetaSymbol' ) ; const controllers = [ ] ; /** 控制器列表 */ export function getControllers ( config ) { controllers . filter ( info => ! info . init ) . forEach ( info => initController ( info . classType , config ) ) ; return controllers ; } /** 路由列表 */ export function getRoutes < ExtType = any > ( config ) { return getControllers ( config ) . map ( ctrl => ctrl . routes ) . reduce ( ( pv , cv ) => pv . concat ( cv ) , [ ] ) ; } /** global default middleware */ const defaultMiddleware = [ ] ; export function addDefaultMiddleware ( middleware ) { defaultMiddleware . push ( ... [ ] . concat ( middleware ) ) ; } export function getControllerMetadata ( CtrlType ) { const descriptor = Object . getOwnPropertyDescriptor ( CtrlType , CtrlMetaSymbol ) ; if ( ! descriptor ) { const value = { classType : CtrlType , filePath : getCurrentLoadFilePath ( ) , routes : [ ] , middleware : [ ] , init : false , } ; controllers . push ( value ) ; Object . defineProperty ( CtrlType , CtrlMetaSymbol , { enumerable : false , configurable : false , value , } ) ; return getControllerMetadata ( CtrlType ) ; } return descriptor . value ; } export function controller ( meta = { } ) { return target => { const metadata = getControllerMetadata ( target ) ; Object . assign ( metadata , meta ) ; } ; } function initController ( target , config ? ) { const typeGlobalName = getGlobalType ( target ) ; const metadata = getControllerMetadata ( target ) ; if ( metadata . init ) { return ; } metadata . init = true ; const prefix = metadata . prefix || `/${ typeGlobalName . split ( '_' ) [ 0 ] . toLowerCase ( ) . replace ( 'controller' , '' ) }` ; if ( metadata . restful ) { const restDefine = [ { name : 'index' , method : 'get' , path : `${ prefix }` } , { name : 'new' , method : 'get' , path : `${ prefix }/new` } , { name : 'show' , method : 'get' , path : `${ prefix }/:id` } , { name : 'edit' , method : 'get' , path : `${ prefix }/:id/edit` } , { name : 'create' , method : 'post' , path : `${ prefix }` } , { name : 'update' , method : 'put' , path : `${ prefix }/:id` } , { name : 'destroy' , method : 'delete' , path : `${ prefix }/:id` } , ] ; restDefine . forEach ( rest => { const func = target . prototype [ rest . name ] ; if ( typeof func !== 'function' ) { return ; } const routeInfo = metadata . routes . find ( r => r . functionName === rest . name ) ; if ( routeInfo ) { if ( ! routeInfo . method ) routeInfo . method = rest . method ; if ( ! routeInfo . url ) routeInfo . url = rest . path ; } else { route ( { method : rest . method , url : rest . path , } ) ( target . prototype , rest . name , undefined ) ; } } ) ; } metadata . routes . forEach ( route => { route . middleware . unshift ( ... metadata . middleware ) ; route . middleware . unshift ( ... defaultMiddleware ) ; route . middleware = route . middleware . map ( mw => { return ( app , typeInfo ) => { const func = mw ( app , typeInfo ) ; return [ ] . concat ( func ) . filter ( s => s ) . map ( item => { return async function ( ctx , next ) { try { if ( isGeneratorFunction ( item ) ) { return await co ( item . apply ( this , [ ctx , next ] ) ) ; } else { return await item . apply ( this , [ ctx , next ] ) ; } } catch ( error ) { typeInfo . onError ( ctx , error ) ; } } ; } ) ; } ; } ) ; /** complete path & method info */ const parsedPath = getNameAndMethod ( route . functionName ) ; if ( ! route . url ) { route . url = `${ prefix }/${ parsedPath . name }` ; } else if ( typeof route . url === 'string' ) { const methodAndPath = route . url . split ( /\s+/ ) . map ( s => s . trim ( ) ) ; if ( methodAndPath . length > 1 && [ 'get' , 'put' , 'post' , 'delete' , 'patch' ] . indexOf ( methodAndPath [ 0 ] . toLowerCase ( ) ) >= 0 ) { route . method = [ ... new Set ( [ ] . concat ( methodAndPath [ 0 ] || [ ] ) . concat ( route . method || [ ] ) ) ] ; route . url = methodAndPath [ 1 ] ; } if ( ! route . url . startsWith ( '/' ) ) { route . url = `${ prefix }/${ route . url }` ; } } if ( ! route . method ) { route . method = parsedPath . method ; } route . url = [ ] . concat ( route . url ) . map ( url => { return typeof url === 'function' ? url ( config ) : url ; } ) ; [ ] . concat ( route . url ) . forEach ( url => { if ( typeof url === 'string' ) { url . split ( '/' ) . forEach ( item => { if ( item . startsWith ( ':' ) ) { const paramName = item . substr ( 1 ) ; const paramType = route . paramTypes . find ( pt => pt . paramName === paramName ) ; if ( paramType ) { paramType . source = 'Param' ; } else { route . paramTypes . push ( { name : paramName , paramName : paramName , type : String , source : 'Param' , validateType : undefined , } ) ; } } } ) ; } } ) ; } ) ; } export abstract class RESTfulController extends Controller { abstract index ( ) ; abstract new ( ) ; abstract show ( id ) ; abstract edit ( id ) ; abstract create ( ) ; abstract update ( id ) ; abstract destroy ( id ) ; }