add route module

This commit is contained in:
liuyi 2025-06-14 20:57:47 +08:00
parent fc9b8f7f2a
commit a5b7a9bd5d
5 changed files with 168 additions and 25 deletions

View File

@ -1,4 +1,5 @@
import { createApp, listened, startApp } from './modules/core/helpers/app';
import { createApp, startApp } from './modules/core/helpers/app';
import { listened } from './modules/restful/utils';
import { createOptions } from './options';
startApp(createApp(createOptions), listened);

View File

@ -1,11 +1,9 @@
import { BadGatewayException, Global, Module, ModuleMetadata, Type } from '@nestjs/common';
import { APP_FILTER, APP_INTERCEPTOR, APP_PIPE } from '@nestjs/core';
import { NestFastifyApplication } from '@nestjs/platform-fastify';
import chalk from 'chalk';
import { useContainer } from 'class-validator';
import { isNil, omit } from 'lodash';
import { omit } from 'lodash';
import { ConfigModule } from '@/modules/config/config.module';
import { Configure } from '@/modules/config/configure';
@ -104,20 +102,3 @@ export async function startApp(
const { port, host } = await configure.get<AppConfig>('app');
await container.listen(port, host, listened(app, startTime));
}
export async function echoApi(configure: Configure, container: NestFastifyApplication) {
const appUrl = await configure.get<string>('app.url');
const urlPrefix = await configure.get<string>('api.prefix', undefined);
const apiUrl = isNil(urlPrefix)
? appUrl
: `${appUrl}${urlPrefix.length > 0 ? `/${urlPrefix}` : urlPrefix}`;
console.log(`- RestAPI: ${chalk.green.underline(apiUrl)}`);
}
export const listened: (app: App, startyTime: Date) => () => Promise<void> =
({ configure, container }, startTime) =>
async () => {
console.log();
await echoApi(configure, container);
console.log('used time: ', chalk.cyan(`${new Date().getTime() - startTime.getTime()}`));
};

View File

@ -0,0 +1,24 @@
import { DynamicModule } from '@nestjs/common';
import { Configure } from '../config/configure';
import { Restful } from './restful';
export class RestfulModule {
static async forRoot(configure: Configure): Promise<DynamicModule> {
const restful = new Restful(configure);
await restful.create(await configure.get('api'));
return {
module: RestfulModule,
global: true,
imports: restful.getModuleImports(),
providers: [
{
provide: Restful,
useValue: restful,
},
],
exports: [Restful],
};
}
}

View File

@ -1,10 +1,18 @@
import { Type } from '@nestjs/common';
import { INestApplication, Type } from '@nestjs/common';
import { RouterModule } from '@nestjs/core';
import { omit } from 'lodash';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { omit, trim } from 'lodash';
import { BaseRestful } from './base';
import { ApiConfig, ApiDocOption, ApiDocSource, RouteOption, SwaggerOption } from './types';
import {
ApiConfig,
ApiDocOption,
ApiDocSource,
RouteOption,
SwaggerOption,
VersionOption,
} from './types';
import { trimPath } from './utils';
export class Restful extends BaseRestful {
@ -71,6 +79,80 @@ export class Restful extends BaseRestful {
(module) => !excludeModules.find((emodule) => emodule === module),
);
}
protected getDocOption(name: string, voption: VersionOption, isDefault = false) {
const docConfig: ApiDocOption = {};
const defaultDoc = {
title: voption.title!,
description: voption.description!,
tags: voption.tags ?? [],
auth: voption.auth ?? false,
version: name,
path: trim(`${this.config.docuri}${isDefault ? '' : `/${name}`}`, '/'),
};
const routesDoc = isDefault
? this.getRouteDocs(defaultDoc, voption.routes ?? [])
: this.getRouteDocs(defaultDoc, voption.routes ?? [], name);
if (Object.keys(routesDoc).length > 0) {
docConfig.routes = routesDoc;
}
const routeModules = isDefault
? this.getRouteModules(voption.routes ?? [])
: this.getRouteModules(voption.routes ?? [], name);
const include = this.filterExcludeModules(routeModules);
if (include.length > 0 || !docConfig.routes) {
docConfig.default = { ...defaultDoc, include };
}
return docConfig;
}
protected createDocs() {
const versionMaps = Object.entries(this.config.versions);
const vDocs = versionMaps.map(([name, version]) => [
name,
this.getDocOption(name, version),
]);
this._docs = Object.fromEntries(vDocs);
const defaultVersion = this.config.versions[this._default];
this._docs.default = this.getDocOption(this._default, defaultVersion, true);
}
async factoryDocs<T extends INestApplication>(container: T) {
const docs = Object.values(this._docs)
.map((doc) => [doc.default, ...Object.values(doc.routes ?? [])])
.reduce((o, n) => [...o, ...n], [])
.filter((i) => !!i);
for (const voption of docs) {
const { title, description, version, auth, include, tags } = voption!;
const builder = new DocumentBuilder();
if (title) {
builder.setTitle(title);
}
if (description) {
builder.setDescription(description);
}
if (auth) {
builder.addBearerAuth();
}
if (tags) {
tags.forEach((tag) =>
typeof tag === 'string'
? builder.addTag(tag)
: builder.addTag(tag.name, tag.description, tag.externalDocs),
);
}
builder.setVersion(version);
const document = SwaggerModule.createDocument(container, builder.build(), {
include: include.length > 0 ? include : [() => undefined as any],
ignoreGlobalPrefix: true,
deepScanRoutes: true,
});
SwaggerModule.setup(voption!.path, container, document);
}
}
}
export function genDocPath(routePath: string, prefix?: string, version?: string) {

View File

@ -1,14 +1,19 @@
import { Type } from '@nestjs/common';
import { Routes, RouteTree } from '@nestjs/core';
import { NestFastifyApplication } from '@nestjs/platform-fastify';
import { ApiTags } from '@nestjs/swagger';
import chalk from 'chalk';
import { camelCase, isNil, omit, trim, upperFirst } from 'lodash';
import { Configure } from '../config/configure';
import { CreateModule } from '../core/helpers';
import { App } from '../core/types';
import { CONTROLLER_DEPENDS } from './constants';
import { RouteOption } from './types';
import { Restful } from './restful';
import { ApiDocOption, RouteOption } from './types';
export const trimPath = (routePath: string, addPrefix = true) =>
`${addPrefix ? '/' : ''}${trim(routePath.replace('//', '/'), '/')}`;
@ -84,3 +89,53 @@ export function createRouteModuleTree(
}),
);
}
export async function echoApi(configure: Configure, container: NestFastifyApplication) {
const appUrl = await configure.get<string>('app.url');
const urlPrefix = await configure.get<string>('api.prefix', undefined);
const apiUrl = isNil(urlPrefix)
? appUrl
: `${appUrl}${urlPrefix.length > 0 ? `/${urlPrefix}` : urlPrefix}`;
console.log(`- RestAPI: ${chalk.green.underline(apiUrl)}`);
console.log('- RestDocs');
const factory = container.get(Restful);
const { default: defaultDoc, ...docs } = factory.docs;
await echoApiDocs('default', defaultDoc, appUrl);
for (const [name, doc] of Object.entries(docs)) {
console.log();
echoApiDocs(name, doc, appUrl);
}
}
export const listened: (app: App, startyTime: Date) => () => Promise<void> =
({ configure, container }, startTime) =>
async () => {
console.log();
await echoApi(configure, container);
console.log('used time: ', chalk.cyan(`${new Date().getTime() - startTime.getTime()}`));
};
async function echoApiDocs(name: string, doc: ApiDocOption, appUrl: string) {
const getDocPath = (path: string) => `${appUrl}/${path}`;
if (!doc.routes && doc.default) {
console.log(
`[${chalk.blue(name.toUpperCase())}]:${chalk.green.underline(
getDocPath(doc.default.path),
)}`,
);
return;
}
console.log(`[${chalk.blue(name.toUpperCase())}]`);
if (doc.default) {
console.log(`default:${chalk.green.underline(getDocPath(doc.default.path))}`);
}
if (doc.routes) {
Object.entries(doc.routes).forEach(([routeName, docs]) => {
console.log(
`<${chalk.yellowBright.bold(docs.title)}>: ${chalk.green.underline(
getDocPath(docs.path),
)}`,
);
});
}
}