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
}
}