Skip to content
On this page

0.重构

定义模块 路由配置 详细告知报错原因

0.1. app2.controller.ts

src/app2.controller.ts

js
import { Controller, Get } from '@nestjs/common'
import { AppService } from './app.service';
@Controller('app2')
export class App2Controller {
   constructor(
      private readonly appService: AppService
   ) { }
   @Get()
   index() {
      return this.appService.getPrefix() + 'app2';
   }
}

0.2. http-exception.filter.ts

src/@nestjs/common/http-exception.filter.ts

js
import { ArgumentsHost, ExceptionFilter, HttpException, HttpStatus } from "@nestjs/common";
import { Response } from 'express';
export class GlobalHttpExectionFilter implements ExceptionFilter {
    catch(exception: any, host: ArgumentsHost) {
        const ctx = host.switchToHttp();
        const response = ctx.getResponse<Response>();
+       if (response.headersSent) {
+           return
+       }
        if (exception instanceof HttpException) {
            if (typeof exception.getResponse() === 'string') {
                const status: any = exception.getStatus();
                response.status(status).json({
                    statusCode: status,
                    message: exception.getResponse()
                })
            } else {
                response.status(exception.getStatus()).json(exception.getResponse())
            }
        } else {
            return response.status(500).json({
                statusCode: HttpStatus.INTERNAL_SERVER_ERROR,
+               error: exception.message,
                message: "Internal server error"
            });
        }
    }
}

0.3. module.decorator.ts

src/@nestjs/common/module.decorator.ts

js
import 'reflect-metadata';
interface ModuleMetadata {
 controllers?: Function[];
 providers?: any[];
 exports?: any[];
 imports?: any[];
}
export function Module(metadata: ModuleMetadata): ClassDecorator {
 return (target: Function) => {
   Reflect.defineMetadata('isModule', true, target);
   defineModule(target, metadata.controllers);
   Reflect.defineMetadata('controllers', metadata.controllers, target);
+   defineModule(target, (metadata.providers ?? []).map(provider => provider instanceof Function ? provider : provider.useClass).filter(Boolean));
   Reflect.defineMetadata('providers', metadata.providers, target);
   Reflect.defineMetadata('exports', metadata.exports, target);
   Reflect.defineMetadata('imports', metadata.imports, target);
 }
}
export function defineModule(nestModule, targets = []) {
 targets.forEach(target => {
   Reflect.defineMetadata('module', nestModule, target);
  })
}
export function Global() {
 return (target: Function) => {
   Reflect.defineMetadata('global', true, target);
  }
}
export interface DynamicModule extends ModuleMetadata {
 module: Function
}

0.4. nest-application.ts

src/@nestjs/core/nest-application.ts

js
import 'reflect-metadata';
import express, { Express, Request as ExpressRequest, Response as ExpressResponse, NextFunction } from 'express'
import { Logger } from "./logger";
import path from 'path'
import { ArgumentsHost, RequestMethod, GlobalHttpExectionFilter, defineModule, INJECTED_TOKENS, DESIGN_PARAMTYPES } from '@nestjs/common'
import { APP_FILTER } from './constants';
export class NestApplication {
    private readonly app: Express = express()
    private readonly providerInstances = new Map()
    private readonly globalProviders = new Set()
    private readonly moduleProviers = new Map()
    private readonly middlewares = []
    private readonly excludedRoutes = []
    private readonly defaultGlobalHttpExceptionFilter = new GlobalHttpExectionFilter()
    private readonly globalHttpExceptionFilters = []
    constructor(protected readonly module) {
        this.app.use(express.json());
        this.app.use(express.urlencoded({ extended: true }));
    }
    useGlobalFilters(...filters) {
        defineModule(this.module, filters.filter(filter => filter instanceof Function));
        this.globalHttpExceptionFilters.push(...filters);
    }
    exclude(...routeInfos): this {
        this.excludedRoutes.push(...routeInfos.map(this.normalizeRouteInfo));
        return this;
    }
    initMiddlewares() {
        this.module.prototype.configure?.(this);
    }
    apply(...middleware) {
        defineModule(this.module, middleware)
        this.middlewares.push(...middleware);
        return this;
    }
    getMiddelwareInstance(middleware) {
        if (middleware instanceof Function) {
            const dependencies = this.resolveDependencies(middleware);
            return new middleware(...dependencies);
        }
        return middleware;
    }
    isExcluded(reqPath: string, method: RequestMethod) {
        return this.excludedRoutes.some(routeInfo => {
            const { routePath, routeMethod } = routeInfo;
            return routePath === reqPath && (routeMethod === RequestMethod.ALL || routeMethod === method)
        });
    }
    forRoutes(...routes) {
        for (const route of routes) {
            for (const middleware of this.middlewares) {
                const { routePath, routeMethod } = this.normalizeRouteInfo(route);
                this.app.use(routePath, (req, res, next) => {
                    if (this.isExcluded(req.originalUrl, req.method)) {
                        return next();
                    }
                    if (routeMethod === RequestMethod.ALL || routeMethod === req.method) {
                        if ('use' in middleware.prototype || 'use' in middleware) {
                            const middlewareInstance = this.getMiddelwareInstance(middleware);
                            middlewareInstance.use(req, res, next);
                        } else if (middleware instanceof Function) {
                            middleware(req, res, next);
                        } else {
                            next();
                        }
                    } else {
                        next();
                    }
                });
            }
        }
+       this.middlewares.length = 0;
        return this;
    }
    private normalizeRouteInfo(route) {
        let routePath = '';
        let routeMethod = RequestMethod.ALL;
        if (typeof route === 'string') {
            routePath = route;
        } else if ('path' in route) {
            routePath = route.path;
            routeMethod = route.method ?? RequestMethod.ALL;
        } else if (route instanceof Function) {
            routePath = Reflect.getMetadata('prefix', route);
        }
        routePath = path.posix.join('/', routePath);
        return { routePath, routeMethod }
    }
    async initProviders() {
        const imports = Reflect.getMetadata('imports', this.module) ?? [];
        for (const importModule of imports) {
            let importedModule = importModule;
            if (importModule instanceof Promise) {
                importedModule = await importedModule;
            }
            if ('module' in importedModule) {
                const { module, providers, controllers, exports } = importedModule;
                const oldControllers = Reflect.getMetadata('controllers', module)
                const newControllers = [...(oldControllers ?? []), ...(controllers ?? [])];
                defineModule(module, newControllers);
                const oldProviders = Reflect.getMetadata('providers', module)
                const newProviders = [...(oldProviders ?? []), ...(providers ?? [])];
                defineModule(module, newProviders);
                const oldExports = Reflect.getMetadata('exports', module)
                const newExports = [...(oldExports ?? []), ...(exports ?? [])];
                Reflect.defineMetadata('controllers', newControllers, module)
                Reflect.defineMetadata('providers', newProviders, module)
                Reflect.defineMetadata('exports', newExports, module)
                this.registerProvidersFromModule(module, this.module);
            } else {
                this.registerProvidersFromModule(importedModule, this.module);
            }
        }
        const providers = Reflect.getMetadata('providers', this.module) ?? [];
        for (const provider of providers) {
            this.addProvider(provider, this.module);
        }
    }
    private registerProvidersFromModule(module, ...parentModules) {
        const global = Reflect.getMetadata('global', module);
        const importedProviders = Reflect.getMetadata('providers', module) ?? [];
        const exports = Reflect.getMetadata('exports', module) ?? [];
        for (const exportToken of exports) {
            if (this.isModule(exportToken)) {
                this.registerProvidersFromModule(exportToken, module, ...parentModules);
            } else {
                const provider = importedProviders.find(provider => provider === exportToken || provider.provide == exportToken);
                if (provider) {
                    [module, ...parentModules].forEach(module => {
                        this.addProvider(provider, module, global);
                    });
                }
            }
        }
    }
    private isModule(exportToken) {
        return exportToken && exportToken instanceof Function && Reflect.getMetadata('isModule', exportToken);
    }
    addProvider(provider, module, global = false) {
        const providers = global ? this.globalProviders : (this.moduleProviers.get(module) || new Set());
        if (!global) {
            this.moduleProviers.set(module, providers);
        }
        const injectToken = provider.provide ?? provider;
        if (this.providerInstances.has(injectToken)) {
            if (!providers.has(injectToken)) {
                providers.add(injectToken);
            }
            return;
        }
        if (provider.provide && provider.useClass) {
            const Clazz = provider.useClass;
            const dependencies = this.resolveDependencies(Clazz);
            const value = new Clazz(...dependencies);
            this.providerInstances.set(provider.provide, value);
            providers.add(provider.provide);
        } else if (provider.provide && provider.useValue) {
            this.providerInstances.set(provider.provide, provider.useValue);
            providers.add(provider.provide);
        } else if (provider.provide && provider.useFactory) {
            const inject = provider.inject ?? [];
            const injectedValues = inject.map(injectToken => this.getProviderByToken(injectToken, module));
            const value = provider.useFactory(...injectedValues);
            this.providerInstances.set(provider.provide, value);
            providers.add(provider.provide);
        } else {
            const dependencies = this.resolveDependencies(provider);
            const value = new provider(...dependencies);
            this.providerInstances.set(provider, value);
            providers.add(provider);
        }
    }
    use(middleware) {
        this.app.use(middleware);
    }
    private getProviderByToken = (injectedToken, module) => {
        if (this.moduleProviers.get(module)?.has(injectedToken) || this.globalProviders.has(injectedToken)) {
            return this.providerInstances.get(injectedToken);
        } else {
            return null;
        }
    }
    private resolveDependencies(Clazz) {
        const injectedTokens = Reflect.getMetadata(INJECTED_TOKENS, Clazz) ?? [];
        const constructorParams = Reflect.getMetadata(DESIGN_PARAMTYPES, Clazz) ?? [];
        return constructorParams.map((param, index) => {
            const module = Reflect.getMetadata('module', Clazz);
            console.log('module', module);
            console.log('token', injectedTokens[index] ?? param);
            return this.getProviderByToken(injectedTokens[index] ?? param, module);
        });
    }
    async init() {
        const controllers = Reflect.getMetadata('controllers', this.module) || [];
        Logger.log(`AppModule dependencies initialized`, 'InstanceLoader');
        for (const Controller of controllers) {
            const dependencies = this.resolveDependencies(Controller);
            const controller = new Controller(...dependencies);
            const prefix = Reflect.getMetadata('prefix', Controller) || '/';
            Logger.log(`${Controller.name} {${prefix}}`, 'RoutesResolver');
            const controllerPrototype = Controller.prototype;
            const controllerFilters = Reflect.getMetadata('filters', Controller) ?? [];
            defineModule(this.module, controllerFilters);
            for (const methodName of Object.getOwnPropertyNames(controllerPrototype)) {
                const method = controllerPrototype[methodName];
                const httpMethod = Reflect.getMetadata('method', method);
                const pathMetadata = Reflect.getMetadata('path', method);
                const redirectUrl = Reflect.getMetadata('redirectUrl', method);
                const redirectStatusCode = Reflect.getMetadata('redirectStatusCode', method);
                const statusCode = Reflect.getMetadata('statusCode', method);
                const headers = Reflect.getMetadata('headers', method) ?? [];
                const methodFilters = Reflect.getMetadata('filters', method) ?? [];
                defineModule(this.module, methodFilters);
                if (!httpMethod) continue;
                const routePath = path.posix.join('/', prefix, pathMetadata)
                this.app[httpMethod.toLowerCase()](routePath, async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
                    const host: ArgumentsHost = {
                        switchToHttp: () => ({
                            getRequest: () => req,
                            getResponse: () => res,
                            getNext: () => next,
                        })
                    }
                    try {
                        const args = this.resolveParams(controller, methodName, req, res, next, host);
                        const result = await method.call(controller, ...args);
                        if (result?.url) {
                            return res.redirect(result.statusCode || 302, result.url);
                        }
                        if (redirectUrl) {
                            return res.redirect(redirectStatusCode || 302, redirectUrl);
                        }
                        if (statusCode) {
                            res.statusCode = statusCode;
                        } else if (httpMethod === 'POST') {
                            res.statusCode = 201;
                        }
                        const responseMetadata = this.getResponseMetadata(controller, methodName);
                        if (!responseMetadata || (responseMetadata?.data?.passthrough)) {
                            headers.forEach(({ name, value }) => {
                                res.setHeader(name, value);
                            });
                            res.send(result);
                        }
                    } catch (error) {
                        await this.callExceptionFilters(error, host, methodFilters, controllerFilters)
                    }
                })
                Logger.log(`Mapped {${routePath}, ${httpMethod}} route`, 'RoutesResolver');
            }
        }
        Logger.log(`Nest application successfully started`, 'NestApplication');
    }
    getFilterInstance(filter) {
        if (filter instanceof Function) {
            const dependencies = this.resolveDependencies(filter);
            console.log('dependencies', dependencies);
            return new filter(...dependencies);
        }
        return filter;
    }
    private callExceptionFilters(error, host, methodFilters, controllerFilters) {
        const allFilters = [...methodFilters, ...controllerFilters, ...this.globalHttpExceptionFilters, this.defaultGlobalHttpExceptionFilter];
        for (const filter of allFilters) {
            let filterInstance = this.getFilterInstance(filter);
            const exceptions = Reflect.getMetadata('catch', filterInstance.constructor) ?? [];
            if (exceptions.length === 0 || exceptions.some(exception => error instanceof exception)) {
                filterInstance.catch(error, host)
                break;
            }
        }
    }
    private getResponseMetadata(controller, methodName) {
        const paramsMetaData = Reflect.getMetadata(`params`, controller, methodName) ?? [];
        return paramsMetaData.filter(Boolean).find((param) =>
            param.key === 'Response' || param.key === 'Res' || param.key === 'Next');
    }
    private resolveParams(instance: any, methodName: string, req: ExpressRequest, res: ExpressResponse, next: NextFunction, host) {
        const paramsMetaData = Reflect.getMetadata(`params`, instance, methodName) ?? [];
        return paramsMetaData.map((paramMetaData) => {
            const { key, data, factory } = paramMetaData;
            switch (key) {
                case "Request":
                case "Req":
                    return req;
                case "Query":
                    return data ? req.query[data] : req.query;
                case "Headers":
                    return data ? req.headers[data] : req.headers;
                case 'Session':
                    return data ? req.session[data] : req.session;
                case 'Ip':
                    return req.ip;
                case 'Param':
                    return data ? req.params[data] : req.params;
                case 'Body':
                    return data ? req.body[data] : req.body;
                case "Response":
                case "Res":
                    return res;
                case "Next":
                    return next;
                case "DecoratorFactory":
                    return factory(data, host);
                default:
                    return null;
            }
        })
    }
    async initGlobalFilters() {
        const providers = Reflect.getMetadata('providers', this.module) ?? [];
        for (const provider of providers) {
            if (provider.provide === APP_FILTER) {
                const providerInstance = this.getProviderByToken(APP_FILTER, this.module);
                this.useGlobalFilters(providerInstance)
            }
        }
    }
    async listen(port) {
        await this.initProviders();
        await this.initMiddlewares();
        await this.initGlobalFilters();
        await this.init();
        this.app.listen(port, () => {
            Logger.log(`Application is running on http://localhost:${port}`, 'NestApplication');
        });
    }
}

0.5. app.controller.ts

src/app.controller.ts

js
+import { Controller, Get } from '@nestjs/common'
+import { AppService } from './app.service';
+@Controller('app')
export class AppController {
+  constructor(
+     private readonly appService: AppService
+  ) { }
+  @Get()
+  index() {
+     return this.appService.getPrefix() + 'app';
   }
}

0.6. app.module.ts

src/app.module.ts

js
+import { MiddlewareConsumer, Module, NestModule } from "@nestjs/common";
import { AppController } from './app.controller';
+import { App2Controller } from './app2.controller';
+import { Request, Response, NextFunction } from 'express';
+import { AppService } from './app.service';
+export function logger1(req: Request, res: Response, next: NextFunction) {
+   console.log(`logger1...`);
+   next();
+};
+export function logger2(req: Request, res: Response, next: NextFunction) {
+   console.log(`logger2...`);
+   next();
+};
@Module({
+   controllers: [AppController, App2Controller],
+   providers: [
        {
+           provide: 'PREFIX',
+           useValue: "prefix"
        },
+       AppService
    ]
})
+export class AppModule implements NestModule {
+   configure(consumer: MiddlewareConsumer) {
+       consumer
+           .apply(logger1)
+           .forRoutes(AppController)
+           .apply(logger2)
+           .forRoutes(App2Controller)
+   }
}

0.7. app.service.ts

src/app.service.ts

js
+import { Inject } from '@nestjs/common';
+export class AppService {
    constructor(
+       @Inject('PREFIX') private readonly prefix: string
+   ) {
    }
+   getPrefix() {
+       return this.prefix
    }
}