Nest源码随记

源码版本:5.0.0

Nest使用入口main.ts

import { NestFactory } from '@nestjs/core';
import { ApplicationModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(ApplicationModule);
  await app.listen(3000);
}
bootstrap();

第一个对象NestFactory,源码位置在根目录./packages/core/nest-factory.ts。

export const NestFactory = new NestFactoryStatic();

看源码知道,NestFactoryStatic没有构造器函数constructor,所以接着看create函数。
该函数使用了重载定义,直接复制最后一个。

初步了解到:

  • 第一个参数是使用是传入的类似angularmodule的对象,
  • 第二个参数可以是设置参数,也可以是类似express的MVC,如官网提到的fastify,覆盖默认使用的express的。
  • 第三个参数就只能是设置参数
public async create(
    module: any,
    serverOrOptions?: any,
    options?: NestApplicationOptions,
  ): Promise<
    INestApplication & (INestExpressApplication | INestFastifyApplication)
  > {
    const isHttpServer = serverOrOptions && serverOrOptions.patch;
    // tslint:disable-next-line:prefer-const
    let [httpServer, appOptions] = isHttpServer
      ? [serverOrOptions, options]
      : [ExpressFactory.create()/* 默认使用Express*/, serverOrOptions];

    const applicationConfig = new ApplicationConfig();
    const container = new NestContainer(applicationConfig);
    httpServer = this.applyExpressAdapter(httpServer);

    this.applyLogger(appOptions);
    await this.initialize(module, container, applicationConfig, httpServer);
    return this.createNestInstance<NestApplication>(
      new NestApplication(container, httpServer, applicationConfig, appOptions),
    );
  }

配置options先略过,直接看ExpressFactory.create(); 返回的是new ExpressAdapter(express())。作用很简单,只是重新转发同样名字的方法给express()

接着生成应用的配置对象applicationConfig;

  private globalPipes: PipeTransform<any>[] = [];
  private globalFilters: ExceptionFilter[] = [];
  private globalInterceptors: NestInterceptor[] = [];
  private globalGuards: CanActivate[] = [];
  private globalPrefix = '';

可以知道,我们全局配置的管道、过滤器等就储存到此对象中。

下一步生成容器container;传入了配置对象

const container = new NestContainer(applicationConfig);

constructor只是保存了该配置,其它方法跳过。

下一步再次初始化http服务。

httpServer = this.applyExpressAdapter(httpServer);

若是从外部传入的MVC对象,必须经过ExpressAdapter再包装。从是否有getHttpServer方法去判断。

接着看是否要覆盖默认的日志输出对象。

this.applyLogger(options);

接着等待初始化

await this.initialize(module, container, applicationConfig);
private async initialize(
    module,
    container: NestContainer,
    config = new ApplicationConfig(),
    httpServer: HttpServer = null,
  ) {
    const instanceLoader = new InstanceLoader(container);
    const dependenciesScanner = new DependenciesScanner(
      container,
      new MetadataScanner(),
      config,
    );
    container.setApplicationRef(httpServer);
    try {
      this.logger.log(messages.APPLICATION_START);
      await ExceptionsZone.asyncRun(async () => {
        await dependenciesScanner.scan(module);
        await instanceLoader.createInstancesOfDependencies();
        dependenciesScanner.applyApplicationProviders();
      });
    } catch (e) {
      process.abort();
    }
  }

生成实例加载器和依赖扫描器。

接着容器与httpServer绑定。

接着使用内部异常处理器(为了统一异常处理)异步去运行最后的步骤。

  • 扫描当前文件的依赖
public async scan(module: Type<any>) {
    await this.scanForModules(module);
    await this.scanModulesForDependencies();
    this.container.bindGlobalScope();
  }

scanForModules;这去扫描及保存模块(包括imports进来的模块)

public async scanForModules(
    module: Type<any> | DynamicModule,
    scope: Type<any>[] = [],
  ) {
    await this.storeModule(module, scope);

    const modules = this.reflectMetadata(module, metadata.MODULES);
    for (const innerModule of modules) {
      await this.scanForModules(innerModule, [].concat(scope, module));
    }
  }

await this.storeModule(module, scope); 有使用到类似angular的forwardRef方法。

然后进入容器对象的addModule方法里,模块编译器先编译模块。

await this.moduleCompiler.compile(
      metatype,
      scope,
    );

先统一一下返回的格式,对于延期加载(metatype是Promise对象的,如使用forwardRef的,先等待加载)。然后判断是否是动态模块,而官网中动态模块的对象类似这样

{
      module: DatabaseModule,
      providers: providers,
      exports: providers,
    };

特别的动态模块是有独有的module对象,所以由此判断。

  • 非动态模块,直接返回{type: metatype}
  • 是动态模块,返回{ type/*module*/, dynamicMetadata/*其余对象*/ }

然后把type 、scope、dynamicMetadata传入模块token工厂(有用到映射,后期返回再看),生成唯一标识。

最后返回模块解析器解析返回{ type, dynamicMetadata, token }

回到容器对象的addModule方法里,往下走,根据token判断是否已保存。没有保存则创建一个Module实例。

Module对象能保存组件、注入的服务、路由、导出的对象及相关模块。创建时先保存核心的注入(模块的实例,模块的配置、映射器、MVC对象、外部的上下文生成器、该模块的的所有模块)。

然后把Module实例保存在容器的modules对象中

接着对动态模块保存在dynamicModulesMetadata对象中,然后对动态模块里的模块(modules\imports)都执行下addModule方法,共用一个作用域数组

接着判断是否是全局的模块。判断逻辑是去映射中找

!!Reflect.getMetadata(GLOBAL_MODULE_METADATA, metatype);

为什么可以这样了,其实到Global的装饰器中看看就知道:

function Global(): ClassDecorator {
  return (target: any) => {
    Reflect.defineMetadata(GLOBAL_MODULE_METADATA, true, target);
  };
}

是全局模块则添加到globalModules的Set对象中。

现在上面的storeModule方法结束,

然后去映射中找外部imports进来的模块,要了解这一块,需要去看module装饰器

export function Module(metadata: ModuleMetadata): ClassDecorator {
  const propsKeys = Object.keys(metadata);

  validateKeys(propsKeys);
  showDeprecatedWarnings(metadata);
  overrideModuleMetadata(metadata);

  return (target: object) => {
    for (const property in metadata) {
      if (metadata.hasOwnProperty(property)) {
        Reflect.defineMetadata(property, metadata[property], target);
      }
    }
  };
}

function overrideModuleMetadata(moduleMetadata: ModuleMetadata) {
  moduleMetadata.modules = moduleMetadata.imports
    ? moduleMetadata.imports
    : moduleMetadata.modules;

  moduleMetadata.components = moduleMetadata.providers
    ? moduleMetadata.providers
    : moduleMetadata.components;
}

function showDeprecatedWarnings(moduleMetadata: ModuleMetadata) {
  const modulesDeprecatedWarning =
    'The "modules" key in the @Module() decorator is deprecated and will be removed within next major release. Use the "imports" key instead.';
  const componentsDeprecatetWarning =
    'The "components" key in the @Module() decorator is deprecated and will be removed within next major release. Use the "providers" key instead.';

  moduleMetadata.modules && deprecate(modulesDeprecatedWarning);
  moduleMetadata.components && deprecate(componentsDeprecatetWarning);
}

这里还能看出有在兼容以前的版本,并作警告。

外部imports的模块将都赋值到modules对象,最后开始映射,所以能从映射中找到imports的模块。

然后迭代imports的模块,再次运行scanForModules函数。

现在scanForModules运行结束,把模块都保存了。

接着扫描模块里的依赖项,作依赖注入的准备工作,其实就是处理Module的配置项

await this.scanModulesForDependencies();

对获得map类型的modules,然后迭代

  1. 映射相关的modules,其实就是绑定模块里的外部模块与主模块的关系(对将废弃的modules也是兼容)。做法是先从映射中获得主模块里的外部imports模块,然后把动态模块(未必有)中的imports的模块也从容器的map类型dynamicModulesMetadata(之前addModule时有设置)获取到,把这些模块都组成新数组。

  2. 然后迭代数组,将模块与taken绑定。能发现循环依赖的判断是迭代的值是undefined,还没实例化,这时用forwardRef就好了。最后调用容器的addRelatedModule方法。

  3. addRelatedModule方法: 先判读是否保存在modules对象中,没有则返回。有则获取出来(主模块),然后把获得的模块的作用域与父作用域组成一个新数组(加上父级作用域到最后,应该是为了让使用了SingleScope的装饰器的模块在后面获取与之前一样的token值)。接着使用模块编译器获取token值,然后去modules对象读取对应的Module实例(被绑定的模块)。

  4. 运行主模块的addRelatedModule方法绑定被绑定的模块related,逻辑就是保存在_relatedModules的Set类型对象中

然后到映射服务(ps:内部方法名没有改,但装饰器是providers对象导入了)

this.reflectComponents(metatype, token);

与上面的模块绑定一样,先组成一个数组,然后迭代,但这次是运行:

components.map(component => {
      this.storeComponent(component, token);
      this.reflectComponentMetadata(component, token);
      this.reflectDynamicMetadata(component, token);
    });

storeComponent保存服务, 一开始的逻辑可以发现,区分自定义服务与普通服务是一个provide属性,普通服务没有这个属性的。自定义服务需要根据配置去生成服务对象。先针对provide属性,如果是内置的值(注册全局的拦截器、管道、守卫、过滤器),则需要再修改下provide的值,因为内置的是一个常亮值,使用Math.random().toString(36).substring(2, 32)去获得一个随机值,去取代provide的值,这样的基本不会给用户注入的服务覆盖掉了,然后把改变后的值与之前的值及token值组成一个对象保存在applicationProvidersApplyMap数组中,方便以后对照。最后都会运行容器的addComponent.

return this.container.addComponent(component, token);
    }

逻辑是由token获得module,然后运行

module.addComponent(component);

函数内同样要分情况处理:

  1. 非用户自定义的服务(无provide对象): 直接保存
this._components.set((component as Type<Injectable>).name, {
      name: (component as Type<Injectable>).name,
      metatype: component as Type<Injectable>,
      instance: null,
      isResolved: false,
    });
  1. 用户自定义的服务,由addCustomProvider函数去添加,先由provide获得name,根据第三个属性去进行添加
  • 对于有useClass属性的,就是把useClass赋值给metatype
collection.set(name, {
      name,
      metatype: useClass,
      instance: null,
      isResolved: false,
    });
  • 对于有useValue属性的:看出可以是Promise实例
collection.set(name, {
      name,
      metatype: null,
      instance: value,
      isResolved: true,
      isNotMetatype: true,
      async: value instanceof Promise,
    });
  • 对于有useFactory属性的:isNotMetatype应该是指不需要依赖注入,因为自带注入器对象了inject
const { provide, name, useFactory: factory, inject } = component;
    collection.set(name, {
      name,
      metatype: factory as any,
      instance: null,
      isResolved: false,
      inject: inject || [],
      isNotMetatype: true,
    });

最后容器的addComponent返回服务的name

然后服务迭代的下一步:

this.reflectComponentMetadata(component, token);

这个的话是对使用WebSocketGateway装饰器里配置的middleware属性的值再次进行上面的storeComponent

然后服务迭代的最后一步:注入装饰器保存的对象,装饰器有(UseInterceptors,````UseGuards,UseFilters,UsePipes```,及参数的【太多:Query()等等等等】)

对于非参数的装饰器,是使用如:

this.reflectInjectables(obj, token, GUARDS_METADATA);
  1. 先通过映射获得该类别所有的值
  2. 然后把服务的原型链上的所有方法(除最底层的Object的方法及所有构造器函数,设置了set或的get的也不要)组成一个数组。
  3. 然后去映射中看看上面的方法有没有也被该装饰器装饰了,有就,获得映射的值并过滤出来,赋值给methodsInjectables.
  4. 然后因为装饰器传入的可能是一个数组,所以要转化成一维数组。
  5. 然后把类装饰数组与方法装饰数组都合成一个数组。
  6. 最后遍历最后的数组,运行this.storeInjectable(injectable, token) => this.container.addInjectable(component, token); => module.addInjectable(injectable);。最后的方法上面已经说明过。

而对于参数装饰器,因为也可以添加管道,所以需要获得所有参数装饰器中保存管道的pipes对象,然后运行上面的第六点。

this.reflectComponents(metatype, token);结束

接着: 映射控制器

this.reflectControllers(metatype, token);

逻辑与上面映射服务的差不多,区别是最后遍历是

this.storeRoute(route, token);
this.reflectDynamicMetadata(route, token); / 这个与服务的一样。

storeRoute: => this.container.addController(route, token); => module.addRoute(controller); =>

this._routes.set(route.name, {
      name: route.name,
      metatype: route,
      instance: null,
      isResolved: false,
    });

其实就是上面对于非自定义的服务的保存逻辑,因为控制器只能是一个Class,很好理解

this.reflectControllers(metatype, token);结束

接着映射导出的对象:

this.reflectExports(metatype, token);

前面逻辑类似,也是看最后遍历:

this.storeExportedComponent(exportedComponent, token)

=>

this.container.addExportedComponent(exportedComponent, token);

=>

module.addExportedComponent(exportedComponent);

=>

public addExportedComponent(
    exportedComponent: ComponentMetatype | string | DynamicModule,
  ) {
    const addExportedUnit = (token: string) =>
      this._exports.add(this.validateExportedProvider(token));

    if (this.isCustomProvider(exportedComponent as any)) {
      return this.addCustomExportedComponent(exportedComponent as any);
    } else if (isString(exportedComponent)) {
      return addExportedUnit(exportedComponent);
    } else if (this.isDynamicModule(exportedComponent)) {
      const { module } = exportedComponent;
      return addExportedUnit(module.name);
    }
    addExportedUnit(exportedComponent.name);
  }

根据类别去添加到this._exports中;

最后添加前都要通过这个validateExportedProvider去验证,逻辑就是this._components有这个值,或者this._relatedModules有这个值。否则就是没有注册的对象,抛出错误

this.reflectExports(metatype, token);结束

此时使用模块装饰器配置的对象全部读取注册完毕。

所以await this.scanModulesForDependencies();依赖注入的准备完成。

接着对所有模块绑定全局注册的模块

this.container.bindGlobalScope();

=>

public bindGlobalScope() {
    this.modules.forEach(module => this.bindGlobalsToRelatedModules(module));
  }

public bindGlobalsToRelatedModules(module: Module) {
    this.globalModules.forEach(globalModule =>
      this.bindGlobalModuleToModule(module, globalModule),
    );
  }

  public bindGlobalModuleToModule(module: Module, globalModule: Module) {
    if (module === globalModule) {
      return undefined;
    }
    module.addRelatedModule(globalModule);
  }

因为所有模块都已经注册完毕了,所以逻辑很简单。

此时,模块注册机依赖注入的准备工作已经完成。

NestFactory.initialize里的await dependenciesScanner.scan(module);完成。

接着创建依赖注入的实例

await instanceLoader.createInstancesOfDependencies();

=>

public async createInstancesOfDependencies() {
    const modules = this.container.getModules();

    this.createPrototypes(modules);
    await this.createInstances(modules);
  }

private createPrototypes(modules: Map<string, Module>) {
    modules.forEach(module => {
      this.createPrototypesOfComponents(module);
      this.createPrototypesOfInjectables(module);
      this.createPrototypesOfRoutes(module);
    });
  }

需要实例化的先原型实例化(构造器无参数),可能是可以优化速度

接着,正式开始实例化。

private async createInstances(modules: Map<string, Module>) {
    await Promise.all(
      [...modules.values()].map(async module => {
        await this.createInstancesOfComponents(module);
        await this.createInstancesOfInjectables(module);
        await this.createInstancesOfRoutes(module);

        const { name } = module.metatype;
        this.logger.log(moduleInitMessage(name));
      }),
    );
  }

第一步对模块使用的组件进行实例化

private async createInstancesOfComponents(module: Module) {
    await Promise.all(
      [...module.components.values()].map(
        async wrapper =>
          await this.injector.loadInstanceOfComponent(wrapper, module),
      ),
    );
  }

=>

public async loadInstanceOfComponent(
    wrapper: InstanceWrapper<Injectable>,
    module: Module,
  ) {
    const components = module.components;
    await this.loadInstance<Injectable>(wrapper, components, module);
  }

loadInstance: 首先判断是否是Promise,是则返回wrapper.done$(其实这是从后面得出的逻辑,因为实例的获取是Promise的话,可以从此知道实例完成时间。)。

然后对包装对象wrappe的对象done$赋值promise,并取出resolve,方便后期运行promise的回调。

接着如果isResolved逻辑正的话,说明已实例话。结束方法。
否则,就要去获取构造器的参数去实例化wrapper。参数的注入可以在wrapper配置,但配置了的话会不再去映射中找design:paramtypes的值(构造函数的元数据),获取元数据时,因为参数也可以配置装饰器,所有也有可能注入,所以去映射reflectSelfParams, 替换同样位置的元数据。
(ps: 为什么配置了通过这个值design:paramtypes可以映射得到构造器的元数据呢,查看对应的装饰器(如:Injectable),是没有相关逻辑。其实这是typescript编译器帮忙做了,配置文件里:````"emitDecoratorMetadata": true,/有装饰器的自动保存目标的构造器的元数据在映射中,使用的key正是design:paramtypes/
"experimentalDecorators": true,/开启装饰器功能/```)

然后遍历参数(注入),如果参数需要实例化的,需要再次运行loadInstanceOfComponent去实例化

最后获得的参数,也会根据isResolved判断一下是否已经获取实例,逻辑负的话,运行callback去实例化,将实例赋值给currentMetatype.instance,把isResolved赋值true, 表明实例化完成,并调用上面done的resolve,这就对应上面的逻辑, 所以这样其它父层可以通过```wrapper.done```知道实例完成的时间,不需要再运行一次,从而保证单例。

callback实例化逻辑,如果不是通过inject去配置的,则直接实例化,并覆盖原来实例

currentMetatype.instance = Object.assign(
            currentMetatype.instance,
            new metatype(...instances),
          );

否则实例化方法有可能使Promise对象。需要这样:

const factoryResult = currentMetatype.metatype(...instances);
          currentMetatype.instance = await this.resolveFactoryInstance(
            factoryResult,
          );

内部组件实例化完成,接着服务实例化:

await this.createInstancesOfInjectables(module);

逻辑与上面:只是loadInstance的第二个参数为遍历module.injectables的value();

接着控制器路由实例化逻辑与上面:只是loadInstance的第二个参数为遍历module.routes的value();

此时单个module里所有实例化完成,最后log打印信息。然后继续遍历。

NestFactory.initialize里的await instanceLoader.createInstancesOfDependencies();完成。

接着注册应用的(全局)的服务提供者,之前我们在map中保存的对应关系,通过moduleKey及providerKey找到实例,然后传入服务提供者的初始化函数,注册完毕。

到此NestFactory.initialize完成。回到ceate, 剩最后一步:

return this.createNestInstance<NestApplication>(
      new NestApplication(container, httpServer, applicationConfig, appOptions),
    );

把之前准备好的容器、MVC实例、应用配置、用户配置生成一个Nest应用实例。

关键:

new NestApplication(container, httpServer, applicationConfig, appOptions)

构造器:

constructor(
container: NestContainer,
private readonly httpAdapter: HttpServer,
private readonly config: ApplicationConfig,
private readonly appOptions: NestApplicationOptions = {},
) {
super(container, [], null);

this.applyOptions();/*查看配置,可以开启cors跨域*/
this.selectContextModule();/*获得container的第一个module,就是入口module*/
this.registerHttpServer();/*注册MVC成http服务或https服务*/

this.routesResolver = new RoutesResolver(this.container, this.config);/* 把控制器的路由注册到MVC上 */

}

new RoutesResolver(this.container, this.config);

=>

constructor(
    private readonly container: NestContainer,
    private readonly config: ApplicationConfig,
  ) {
    this.routerExceptionsFilter = new RouterExceptionFilters(
      container,
      config,
      container.getApplicationRef()/* 获得MVC对象 */,
    );
    this.routerBuilder = new RouterExplorer(
      new MetadataScanner(),
      this.container,
      this.routerProxy,
      this.routerExceptionsFilter,
      this.config,
    );
  }

创建路由异常处理器和逻辑处理器(处理各种装饰器)。

然后运行createNestInstance方法,其实是新建一个代理去代理实例,禁止了写操作及读取原型链的属性,对于函数属性,用异常处理包装去运行。

目前入口const app = await NestFactory.create(ApplicationModule);已完成。

最后这个

await app.listen(3000);
// nest-application.ts

public async listen(port: number | string, callback?: () => void);
  public async listen(
    port: number | string,
    hostname: string,
    callback?: () => void,
  );
  public async listen(port: number | string, ...args) {
    !this.isInitialized && (await this.init());

    this.httpServer.listen(port, ...args);
    return this.httpServer;
  }

关键逻辑在await this.init()

// nest-application.ts

public async init(): Promise<this> {
    const useBodyParser =
      this.appOptions && this.appOptions.bodyParser !== false;
    useBodyParser && this.registerParserMiddleware();/* 首先判断是否要加载 BodyParser 中间件,个人觉得这个可以放到applyOptions里*/

    await this.registerModules();
    await this.registerRouter();
    await this.callInitHook();

    this.isInitialized = true;
    this.logger.log(messages.APPLICATION_READY);
    return this;
  }

await this.registerModules(); 可注册socket、微服务、和中间件

暂时只看中间件的注册, 这是准备工作,只是保存在中间件容器的对象中,非注册到MVC中:

// middleware-module.ts

public async resolveMiddleware(
    middlewareContainer: MiddlewareContainer,
    modules: Map<string, Module>,
  ) {
    await Promise.all(
      [...modules.entries()].map(async ([name, module]) => {
        const instance = module.instance;

        this.loadConfiguration(middlewareContainer, instance, name);
        await this.resolver.resolveInstances(module, name);
      }),
    );
  }

public loadConfiguration(
    middlewareContainer: MiddlewareContainer,
    instance: NestModule,
    module: string,
  ) {
    if (!instance.configure) return; /* 中间件是在实例原型链上的configure方面,没有的话,说明该模块没有配置中间件 */

    const middlewareBuilder = new MiddlewareBuilder(this.routesMapper);
    instance.configure(middlewareBuilder);

    if (!(middlewareBuilder instanceof MiddlewareBuilder)) return; /* 放置configure函数修改此变量后报错 */

    const config = middlewareBuilder.build();
    middlewareContainer.addConfig(config, module);
  }

中间件配置例子:

export class ApplicationModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes('/cats');
  }
}

从例子可以看到,需要传入MiddlewareConsumer类型的实例,而MiddlewareBuilder是以其为接口实现的。所以接下来看MiddlewareBuilderapply函数

// packages\core\middleware\builder.ts

public apply(
    ...middleware: Array<Type<any> | Function | any>,
  ): MiddlewareConfigProxy {
    return new MiddlewareBuilder.ConfigProxy(this, flatten(middleware));
  }

中间件转成一维数组,然后使用代理,先对中间过滤非function及把非Class的转化为Class结构(方便后期实例化)。

// packages\core\middleware\builder.ts

public forRoutes(
      ...routes: Array<string | Type<any> | RouteInfo>,
    ): MiddlewareConsumer {
      const {
        middlewareCollection,
        bindValuesToResolve,
        routesMapper,
      } = this.builder;

      const forRoutes = this.mapRoutesToFlatList(
        routes.map(route => routesMapper.mapRouteToRouteInfo(route)),
      );
      const configuration = {
        middleware: bindValuesToResolve(
          this.includedRoutes,
          this.contextParameters,
        ),
        forRoutes: forRoutes.filter(route => !this.isRouteExcluded(route)),
      };
      middlewareCollection.add(configuration);
      return this.builder;
    }

先获得配置了中间间的路由信息(路径、http请求类型、回调方法(可空)、回调方法名(可空))

\\ packages\core\middleware\routes-mapper.ts

public mapRouteToRouteInfo(
    route: Type<any> | RouteInfo | string,
  ): RouteInfo[] {
    if (isString(route)) {
      return [
        {
          path: this.validateRoutePath(route),
          method: RequestMethod.ALL,
        },
      ];
    }
    const routePath: string = Reflect.getMetadata(PATH_METADATA, route);
    if (this.isRouteInfo(routePath, route)) {
      return [
        {
          path: this.validateRoutePath(route.path),
          method: route.method,
        },
      ];
    }
    const paths = this.routerExplorer.scanForPaths(
      Object.create(route),
      route.prototype,
    );
    return paths.map(item => ({
      path:
        this.validateGlobalPath(routePath) + this.validateRoutePath(item.path),
      method: item.requestMethod,
    }));
  }

从源码可以知道,forRoutes可以接受三种类型的

  1. 字符串类型,直接为路由路径,接受所有请求类型。返回
[
        {
          path: this.validateRoutePath(route),
          method: RequestMethod.ALL,
        },
      ];
  1. 是一个配置对象,非控制器,所以映射不到路由路径。返回:
[
        {
          path: this.validateRoutePath(route.path),
          method: route.method,/* 可通过传入枚举类型的RequestMethod 去定义 */
        },
      ];
  1. 是一个控制器,因为控制器可映射路径,里面的方法也可以映射二级路径,方法拦截的请求类型也可以映射,所以需要一一去获取。使用routerExplorer工具的scanForPaths。获取逻辑是,从一级原型链中获得所有的非构造器的方法,然后一一去映射中获取拦截的请求类型及二级路径。返回二级路由信息对象数组,最后路由路径要与控制器映射的一级路径合并。
// packages\core\router\router-explorer.ts
public exploreMethodMetadata(
    instance: Controller,
    instancePrototype,
    methodName: string,
  ): RoutePathProperties {
    const targetCallback = instancePrototype[methodName];
    const routePath = Reflect.getMetadata(PATH_METADATA, targetCallback);
    if (isUndefined(routePath)) {
      return null;
    }
    const requestMethod: RequestMethod = Reflect.getMetadata(
      METHOD_METADATA,
      targetCallback,
    );
    return {
      path: this.validateRoutePath(routePath),
      requestMethod,
      targetCallback,
      methodName,
    };
  }

接着组成路由的配置项,覆盖中间件的resolve方法,添加可能从外部添加的参数,及把上面的路由信息去除设定中为排除在外的路径的信息。
然后把配置项添加到middlewareCollection。

回到loadConfiguration

const config = middlewareBuilder.build();获取上面的配置信息。

然后调用nest-application.ts的对象去添加配置

middlewareContainer.addConfig(config, module);

容器会分模块name保存中间件,及整体配置

回到middleware-module.ts

接着实例化中间件,也就是依赖注入

await this.registerRouter(); 注册路由

public async registerRouter() {
    await this.registerMiddleware(this.httpAdapter);
    const prefix = this.config.getGlobalPrefix();
    const basePath = prefix ? validatePath(prefix) : '';
    this.routesResolver.resolve(this.httpAdapter, basePath);
  }

第一步,把之前的中间件绑定对应的路由

// packages\core\middleware\middleware-module.ts

public async registerMiddleware(
    middlewareContainer: MiddlewareContainer,
    applicationRef: any,
  ) {
    const configs = middlewareContainer.getConfigs();/* 获得中间件与路由的关系 */
    const registerAllConfigs = (
      module: string,
      middlewareConfig: MiddlewareConfiguration[],
    ) =>
      middlewareConfig.map(async (config: MiddlewareConfiguration) => {
        await this.registerMiddlewareConfig(
          middlewareContainer,
          config,
          module,
          applicationRef,
        );
      });

    await Promise.all(
      [...configs.entries()].map(async ([module, moduleConfigs]) => {
        await Promise.all(registerAllConfigs(module, [...moduleConfigs]));
      }),
    );
  }

public async registerMiddlewareConfig(
    middlewareContainer: MiddlewareContainer,
    config: MiddlewareConfiguration,
    module: string,
    applicationRef: any,
  ) {
    const { forRoutes } = config;
    await Promise.all(
      forRoutes.map(async (routeInfo: RouteInfo) => {
        await this.registerRouteMiddleware(
          middlewareContainer,
          routeInfo,
          config,
          module,
          applicationRef,
        );
      }),
    );
  }

public async registerRouteMiddleware(
    middlewareContainer: MiddlewareContainer,
    routeInfo: RouteInfo,
    config: MiddlewareConfiguration,
    module: string,
    applicationRef: any,
  ) {
    const middlewareCollection = [].concat(config.middleware);
    await Promise.all(
      middlewareCollection.map(async (metatype: Type<NestMiddleware>) => {
        const collection = middlewareContainer.getMiddleware(module);
        const middleware = collection.get(metatype.name);
        if (isUndefined(middleware)) {
          throw new RuntimeException();
        }

        const { instance } = middleware as MiddlewareWrapper;
        await this.bindHandler(
          instance,
          metatype,
          applicationRef,
          routeInfo.method,
          routeInfo.path,
        );
      }),
    );
  }

private async bindHandler(
    instance: NestMiddleware,
    metatype: Type<NestMiddleware>,
    applicationRef: HttpServer,
    method: RequestMethod,
    path: string,
  ) {
    if (isUndefined(instance.resolve)) {
      throw new InvalidMiddlewareException(metatype.name);
    }
    const exceptionsHandler = this.routerExceptionFilter.create(
      instance,
      instance.resolve,
      undefined,
    );
    const router = applicationRef.createMiddlewareFactory(method);/* 获得MVC的对应方法 */
    const bindWithProxy = middlewareInstance =>
      this.bindHandlerWithProxy(
        exceptionsHandler,
        router,
        middlewareInstance,
        path,
      );
    const resolve = instance.resolve();

    if (!(resolve instanceof Promise)) {
      bindWithProxy(resolve);
      return;
    }
    const middleware = await resolve;
    bindWithProxy(middleware);
  }

private bindHandlerWithProxy(
    exceptionsHandler: ExceptionsHandler,
    router: (...args) => void,
    middleware: (req, res, next) => void,
    path: string,
  ) {
    const proxy = this.routerProxy.createProxy(middleware, exceptionsHandler);/* 代理一样,为了处理异常 */
    const prefix = this.config.getGlobalPrefix();
    const basePath = prefix ? validatePath(prefix) : '';
    router(basePath + path, proxy); /* 中间件注册成功 */
  }

接着是注册路由主体了

this.routesResolver.resolve(this.httpAdapter, basePath);
// nest\packages\core\router\routes-resolver.ts

public resolve(appInstance, basePath: string) {
    const modules = this.container.getModules();
    modules.forEach(({ routes, metatype }, moduleName) => {
      let path = metatype
        ? Reflect.getMetadata(MODULE_PATH, metatype)
        : undefined;
      path = path ? path + basePath : basePath;
      this.registerRouters(routes, moduleName, path, appInstance);
    });
    this.registerNotFoundHandler();
    this.registerExceptionHandler();
  }

MODULE_PATH应该是module上配置的前缀,但在源码上找不到对应设置的地方。

接着注册路由

public registerRouters(
    routes: Map<string, InstanceWrapper<Controller>>,
    moduleName: string,
    basePath: string,
    appInstance: HttpServer,
  ) {
    routes.forEach(({ instance, metatype }) => {
// 根据Controller装饰器获得该controller实例的前缀
      const path = this.routerBuilder.extractRouterPath(metatype, basePath);
      const controllerName = metatype.name;
// 打印匹配信息
      this.logger.log(controllerMappingMessage(controllerName, path));
      this.routerBuilder.explore(
        instance,
        metatype,
        moduleName,
        appInstance,
        path,
      );
    });
  }
//  nest\packages\core\router\router-explorer.ts

public explore(
    instance: Controller,// 实例
    metatype: Type<Controller>,// 构造器
    module: string,// module名
    appInstance,// MVC实例
    basePath: string, // 前缀
  ) {
    const routerPaths = this.scanForPaths(instance);
    this.applyPathsToRouterProxy(
      appInstance,
      routerPaths,
      instance,
      module,
      basePath,
    );
  }

// 从原型方法的装饰器中查找请求方法及路径,并返回关系对象
public scanForPaths(instance: Controller, prototype?): RoutePathProperties[] {
    const instancePrototype = isUndefined(prototype)
      ? Object.getPrototypeOf(instance)
      : prototype;
    return this.metadataScanner.scanFromPrototype<
      Controller,
      RoutePathProperties
    >(instance, instancePrototype, method =>
      this.exploreMethodMetadata(instance, instancePrototype, method),
    );
  }

public applyPathsToRouterProxy(
    router,
    routePaths: RoutePathProperties[],
    instance: Controller,
    module: string,
    basePath: string,
  ) {
    (routePaths || []).map(pathProperties => {
      const { path, requestMethod } = pathProperties;
      this.applyCallbackToRouter(
        router,
        pathProperties,
        instance,
        module,
        basePath,
      );
// 打印次级路由匹配信息
      this.logger.log(routeMappedMessage(path, requestMethod));
    });
  }

private applyCallbackToRouter(
    router,
    pathProperties: RoutePathProperties,
    instance: Controller,
    module: string,
    basePath: string,
  ) {
    const { path, requestMethod, targetCallback, methodName } = pathProperties;
    const routerMethod = this.routerMethodFactory
      .get(router, requestMethod)
      .bind(router);

// 包装代理,为了处理异常及其它装饰器配置
    const proxy = this.createCallbackProxy(
      instance,
      targetCallback,
      methodName,
      module,
      requestMethod,
    );
    const stripSlash = str =>
      str[str.length - 1] === '/' ? str.slice(0, str.length - 1) : str;
    const fullPath = stripSlash(basePath) + path;
    routerMethod(stripSlash(fullPath) || '/', proxy);// 注册成功
  }

我们再看看包装代理里的

private createCallbackProxy(
    instance: Controller,
    callback: RouterProxyCallback,
    methodName: string,
    module: string,
    requestMethod,
  ) {
    const executionContext = this.executionContextCreator.create(
      instance,
      callback,
      methodName,
      module,
      requestMethod,
    );
    const exceptionFilter = this.exceptionsFilter.create(
      instance,
      callback,
      module,
    );
    return this.routerProxy.createProxy(executionContext, exceptionFilter);
  }

executionContextCreator为路由方法添加配置的管道、看守器、拦截器,及注入需要的参数。

// packages\core\router\router-execution-context.ts

public create(
    instance: Controller,
    callback: (...args) => any,
    methodName: string,
    module: string,
    requestMethod: RequestMethod,
  ) {
    const metadata = this.reflectCallbackMetadata(instance, methodName) || {}; // 获得参数装饰器的保存信息,如{"query:1":{index:0,data: 0} }(@Query())
    const keys = Object.keys(metadata);
    const argsLength = this.getArgumentsLength(keys, metadata); // 获得对象中最大的index+1值,就是参数的总数量
    const pipes = this.pipesContextCreator.create(instance, callback, module); // 获得管道方法的数组,从下标0开始顺序是全局管道,控制器管道,方法管道
    const paramtypes = this.reflectCallbackParamtypes(instance, methodName); // 获得原始参数类型
    const guards = this.guardsContextCreator.create(instance, callback, module); // 获得看守器的数组
    const interceptors = this.interceptorsContextCreator.create(
      instance,
      callback,
      module,
    );// 获得拦截器的数组
    const httpCode = this.reflectHttpStatusCode(callback);获得需要返回的httpCode
// 下面展开说
    const paramsMetadata = this.exchangeKeysForValues(keys, metadata, module);
    const isResponseHandled = paramsMetadata.some(
      ({ type }) =>
        type === RouteParamtypes.RESPONSE || type === RouteParamtypes.NEXT,
    );// 判断是否是请求处理方法
    const paramsOptions = this.mergeParamsMetatypes(paramsMetadata, paramtypes); // 是覆盖传入的对象
    const httpStatusCode = httpCode
      ? httpCode
      : this.responseController.getStatusByMethod(requestMethod);// 获得回复的httpCode

下面展开
    const fnCanActivate = this.createGuardsFn(guards, instance, callback);
    const fnApplyPipes = this.createPipesFn(pipes, paramsOptions);
    const fnHandleResponse = this.createHandleResponseFn(
      callback,
      isResponseHandled,
      httpStatusCode,
    );
    const handler = (args, req, res, next) => async () => {
      fnApplyPipes && (await fnApplyPipes(args, req, res, next));
      return callback.apply(instance, args);
    };

public exchangeKeysForValues(
    keys: string[],
    metadata: RouteParamsMetadata,
    moduleContext: string,
  ): ParamProperties[] {
    this.pipesContextCreator.setModuleContext(moduleContext);
    return keys.map(key => {
      const { index, data, pipes: pipesCollection } = metadata[key];
      const pipes = this.pipesContextCreator.createConcreteContext(
        pipesCollection,
      );
      const type = this.mapParamType(key);

      if (key.includes(CUSTOM_ROUTE_AGRS_METADATA)) {
        const { factory } = metadata[key];
        const customExtractValue = this.getCustomFactory(factory, data);
        return { index, extractValue: customExtractValue, type, data, pipes };
      }
      const nType = Number(type);
      const extractValue = (req, res, next) =>
        this.paramsFactory.exchangeKeyForValue(nType, data, { req, res, next });
      return { index, extractValue, type: nType, data, pipes };
    });
  }

上面是路由参数的转换,首先我们要了解参数装饰器的用法,如Query

export function Query();
export function Query(...pipes: (Type<PipeTransform> | PipeTransform)[]);
export function Query(
  property: string,
  ...pipes: (Type<PipeTransform> | PipeTransform)[],
);
export function Query(
  property?: string | (Type<PipeTransform> | PipeTransform),
  ...pipes: (Type<PipeTransform> | PipeTransform)[],
) {
  return createPipesRouteParamDecorator(RouteParamtypes.QUERY)(
    property,
    ...pipes,
  );
}

直接使用可以获得request.query的对象,可装入query的对象名去获取此value,也可以注入管道对象对query进行转换。

OK,回到exchangeKeysForValues方法里,

 const pipes = this.pipesContextCreator.createConcreteContext(
        pipesCollection,
      );

获得管道的transform方法的数组。

const type = this.mapParamType(```RouteParamtypes```的枚举值);

获得属性名中除位置的字段()

if (key.includes(CUSTOM_ROUTE_AGRS_METADATA)) {
        const { factory } = metadata[key]; // 拿到定义装饰器时传入的工厂函数
        const customExtractValue = this.getCustomFactory(factory, data); // 把装饰器传入的属性加入工厂函数成一个新函数
        return { index, extractValue: customExtractValue, type, data, pipes };

public getCustomFactory(factory: (...args) => void, data): (...args) => any {
    return !isUndefined(factory) && isFunction(factory)
      ? (req, res, next) => factory(data, req)
      : () => null;
  }
      

这里是处理自定义的参数装饰器createParamDecorator

const nType = Number(type);
      const extractValue = (req, res, next) =>
        this.paramsFactory.exchangeKeyForValue(nType, data, { req, res, next }); // 通过枚举对应值,就可以获得返回的对象
      return { index, extractValue, type: nType, data, pipes };
const fnCanActivate = this.createGuardsFn(guards, instance, callback);

这是生成守护者的总函数。

最后要返回的是这个函数

async (args: any[]) => {
      const canActivate = await this.guardsConsumer.tryActivate(
        guards,
        args,
        instance,
        callback,
      );
      if (!canActivate) {
        throw new ForbiddenException(FORBIDDEN_MESSAGE);
      }
    };

tryActivate

public async tryActivate(
    guards: CanActivate[],
    args: any[],
    instance: Controller,
    callback: (...args) => any,
  ): Promise<boolean> {
    if (!guards || isEmpty(guards)) {
      return true;
    }
    const context = this.createContext(args, instance, callback);生成传入的参数
    for (const guard of guards) {
      const result = guard.canActivate(context);
      if (await this.pickResult(result)) {
        continue;
      }
      return false;
    }
    return true;
  }

所以在守护者使用中,我们可以通过context: ExecutionContext获得MVC路由参数,控制器的构造器及处理方法

跟着的获得管道的总方法也差不多一样,只是还要把之前获得的管道与每个参数的管道合并一个数组,然后逐个去运行对参数进行转换。初始value就是从参数的extractValue方法去转换获得。

接下来是增加路由方法的返回响应的逻辑,因为我们写路由处理逻辑时是多数没有用到response对象的,这里我们去增加处理返回响应。

const fnHandleResponse = this.createHandleResponseFn(
      callback,
      isResponseHandled,
      httpStatusCode,
    );

public createHandleResponseFn(
    callback,
    isResponseHandled: boolean,
    httpStatusCode: number,
  ) {
    const renderTemplate = this.reflectRenderTemplate(callback);// 获得映射中的渲染模板
    const responseHeaders = this.reflectResponseHeaders(callback);// 获得映射中的响应头

    if (renderTemplate) {
      return async (result, res) => {
        this.responseController.setHeaders(res, responseHeaders);
        await this.responseController.render(result, res, renderTemplate);
      };
    }
    return async (result, res) => {
      this.responseController.setHeaders(res, responseHeaders);

      !isResponseHandled &&
        (await this.responseController.apply(result, res, httpStatusCode));
    };
  }

如果需要返回渲染的模板,则最后调用MVC的render方法,给模板传入的变量就是我们编写路由的返回值,就是result

没有模块的话,要判断是否是响应处理者(就是参数中是否有传入response或者next),有则只是设置下响应头,默认不作响应,即使我们没有使用到response的响应方法及next(),因为无法判断有没有用。所以传入了那些的话,要自己处理响应,不过官网好像也不推荐,只是为了兼容旧版本。

好了,现在有4个方法(守卫,管道,业务回调,响应方法)了,要按顺序组装。先守卫,然后管道处理参数,然后参数传入回调,最后响应方法。

const handler = (args, req, res, next) => async () => {
      fnApplyPipes && (await fnApplyPipes(args, req, res, next));
      return callback.apply(instance, args);
    };

    return async (req, res, next) => {
      const args = this.createNullArray(argsLength);
      fnCanActivate && (await fnCanActivate([req, res]));

      const result = await this.interceptorsConsumer.intercept(
        interceptors,
        [req, res],
        instance,
        callback,
        handler(args, req, res, next),
      );
      await fnHandleResponse(result, res); // 最后响应
    };

但中间还有一个拦截器,若有拦截器,则需要将处理逻辑方法转换成 stream 流,来给拦截器去操作(加工,更改等),rxjs的强大操作符都可以使用了。

public async intercept(
    interceptors: NestInterceptor[],
    args: any[],
    instance: Controller,
    callback: (...args) => any,
    next: () => Promise<any>,
  ): Promise<any> {
    if (!interceptors || isEmpty(interceptors)) {
      return await await next();
    }
    const context = this.createContext(args, instance, callback); // 与上面的守护一致
    const start$ = defer(() => this.transformDeffered(next)); // defer操作符是订阅时才执行参数方法。所以可使用缓存策略,使得减少路由业务逻辑的频率。
    const result$ = await interceptors.reduce(
      async (stream$, interceptor) =>
        await interceptor.intercept(context, await stream$),
      Promise.resolve(start$),
    ); // 一层层数据流,可以知道,若拦截器不返回流,则无响应。
    return await result$.toPromise();
  }

此时正常路由处理的封装已完成,回到createCallbackProxy

\\  packages\core\router\router-explorer.ts

private createCallbackProxy(
    instance: Controller,
    callback: RouterProxyCallback,
    methodName: string,
    module: string,
    requestMethod,
  ) {
    const executionContext = this.executionContextCreator.create(
      instance,
      callback,
      methodName,
      module,
      requestMethod,
    );
    const exceptionFilter = this.exceptionsFilter.create(
      instance,
      callback,
      module,
    );
    return this.routerProxy.createProxy(executionContext, exceptionFilter);
  }

还要封装异常过滤处理的方法。这个的create与上面的管道,守卫等差不多,也是先获得一个全局到控制器再到方法得过滤方法数组,只是数组内的成员不是方法,而是一个对象,除了有一个func的方法外,还有一个exceptionMetatypes的对象,这个是指拦截的错误类型。因为过滤器的装饰器是可以传参的,参数就是exceptionMetatypes。数组的获取逻辑如下

//   packages\core\exceptions\base-exception-filter-context.ts

public createConcreteContext<T extends any[], R extends any[]>(
    metadata: T,
  ): R {
    if (isUndefined(metadata) || isEmpty(metadata)) {
      return [] as R;
    }
    return iterate(metadata)
      .filter(
        instance => instance && (isFunction(instance.catch) || instance.name),
      )
      .map(filter => this.getFilterInstance(filter))
      .map(instance => ({
        func: instance.catch.bind(instance),
        exceptionMetatypes: this.reflectCatchExceptions(instance),
      }))
      .toArray() as R;
  }

获取过滤数组后,若数组不为空,是要覆盖框架内的错误处理数组,还要先对数组反转,因为错误的抛出应该是从内到外的。

//  packages\core\router\router-exception-filters.ts

public create(
    instance: Controller,
    callback: RouterProxyCallback,
    module: string,
  ): ExceptionsHandler {
    this.moduleContext = module;

    const exceptionHandler = new ExceptionsHandler(this.applicationRef);
    const filters = this.createContext(
      instance,
      callback,
      EXCEPTION_FILTERS_METADATA,
    );
    if (isEmpty(filters)) {
      return exceptionHandler;
    }
    exceptionHandler.setCustomFilters(filters.reverse());
    return exceptionHandler;
  }

异常过滤准备后了,要组合正常与过滤了。


//  packages\core\router\router-proxy.ts

public createProxy(
    targetCallback: RouterProxyCallback,
    exceptionsHandler: ExceptionsHandler,
  ) {
    return async (req, res, next) => {
      try {
        await targetCallback(req, res, next);
      } catch (e) {
        const host = new ExecutionContextHost([req, res]);
        exceptionsHandler.next(e, host);  // 这两参数就是过滤器的参数,此方法也只是从```filters```数组中find目标,去运行,找不到就使用内部的。
      }
    };
  }

好了router-explorer的createCallbackProxy完成,回到applyCallbackToRouter,现在只剩下最终的绑定路由了

//  packages\core\router\router-explorer.ts

private applyCallbackToRouter(
    router,
    pathProperties: RoutePathProperties,
    instance: Controller,
    module: string,
    basePath: string,
  ) {
    const { path, requestMethod, targetCallback, methodName } = pathProperties;
    const routerMethod = this.routerMethodFactory
      .get(router, requestMethod)
      .bind(router);

    const proxy = this.createCallbackProxy(
      instance,
      targetCallback,
      methodName,
      module,
      requestMethod,
    );
    const stripSlash = str =>
      str[str.length - 1] === '/' ? str.slice(0, str.length - 1) : str;
    const fullPath = stripSlash(basePath) + path;
    routerMethod(stripSlash(fullPath) || '/', proxy);
  }

此方法完成后会打印路由关系日志。

设置的路由已完成注册。还要注册路由找不到时的处理方法,及非路由逻辑的异常处理方法。看下面的后面两步

//   packages\core\router\routes-resolver.ts
public resolve(appInstance, basePath: string) {
    const modules = this.container.getModules();
    modules.forEach(({ routes, metatype }, moduleName) => {
      let path = metatype
        ? Reflect.getMetadata(MODULE_PATH, metatype)
        : undefined;
      path = path ? path + basePath : basePath;
      this.registerRouters(routes, moduleName, path, appInstance);
    });
    this.registerNotFoundHandler();
    this.registerExceptionHandler();
  }

(突然发现丢失了最后的部分,记得保存的了。)

后面两个异常处理简单说说,就是自己声明一个callback,然后调用app.use(callback)而已,一点都不绕。

最后调用各模块的生命周期函数onModuleInit。

基础nest应用启动完成!!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,497评论 18 139
  • Spring Web MVC Spring Web MVC 是包含在 Spring 框架中的 Web 框架,建立于...
    Hsinwong阅读 22,222评论 1 92
  • 阿里云服务器自带的有python,在终端查询一下就行了: 用Xftp将python项目丢到服务器的某个文件夹下,然...
    紫灬楓阅读 1,150评论 0 0
  • 人真是一个奇怪的生物。 关于童年的记忆,很多事情即使有人提醒我都记不起来了,可是故乡的风景却像一幅...
    M夏语冰阅读 408评论 2 3
  • 清晨,两只蝉在树叶间相遇 面对一颗露珠,互相谦让 你请 你请 他们同时伸出了吸管,嘴唇相碰 在一颗水滴中接吻 他们...
    栾语阅读 180评论 0 2