fix(GOV-009): 完成脚手架审查整改
This commit is contained in:
@@ -1,29 +1,44 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { type DynamicModule, Module } from '@nestjs/common';
|
||||
|
||||
import { loadApiProcessConfig } from '../../../libs/config/src/load-process-env';
|
||||
import type { ApiRuntimeConfig } from '../../../libs/config/src/runtime-config';
|
||||
import { DisabledHealthClient } from '../../../libs/infrastructure/src/disabled-health.client';
|
||||
import { createMysqlHealthClient } from '../../../libs/infrastructure/src/mysql-health.client';
|
||||
import { createRedisHealthClient } from '../../../libs/infrastructure/src/redis-health.client';
|
||||
import { HealthController } from './health.controller';
|
||||
import { HealthService, MYSQL_HEALTH_PORT, REDIS_HEALTH_PORT } from './health.service';
|
||||
|
||||
const apiConfig = loadApiProcessConfig();
|
||||
/** API 进程的只读配置注入令牌,确保测试和启动入口使用同一份显式配置。 */
|
||||
export const API_RUNTIME_CONFIG = Symbol('API_RUNTIME_CONFIG');
|
||||
|
||||
@Module({
|
||||
controllers: [HealthController],
|
||||
providers: [
|
||||
HealthService,
|
||||
{
|
||||
provide: REDIS_HEALTH_PORT,
|
||||
useFactory: () => createRedisHealthClient(apiConfig.redis),
|
||||
},
|
||||
{
|
||||
provide: MYSQL_HEALTH_PORT,
|
||||
useFactory: () =>
|
||||
apiConfig.mysql.enabled
|
||||
? createMysqlHealthClient(apiConfig.mysql)
|
||||
: new DisabledHealthClient('MySQL 未启用'),
|
||||
},
|
||||
],
|
||||
})
|
||||
export class ApiModule {}
|
||||
@Module({})
|
||||
export class ApiModule {
|
||||
/**
|
||||
* 由启动入口传入已经校验和冻结的运行配置,禁止模块加载阶段隐式读取环境变量,
|
||||
* 从而让 API 测试、OpenAPI 生成和生产启动之间不存在隐藏配置差异。
|
||||
*/
|
||||
static register(config: ApiRuntimeConfig): DynamicModule {
|
||||
return {
|
||||
module: ApiModule,
|
||||
controllers: [HealthController],
|
||||
providers: [
|
||||
HealthService,
|
||||
{ provide: API_RUNTIME_CONFIG, useValue: config },
|
||||
{
|
||||
provide: REDIS_HEALTH_PORT,
|
||||
inject: [API_RUNTIME_CONFIG],
|
||||
useFactory: (runtimeConfig: ApiRuntimeConfig) =>
|
||||
createRedisHealthClient(runtimeConfig.redis),
|
||||
},
|
||||
{
|
||||
provide: MYSQL_HEALTH_PORT,
|
||||
inject: [API_RUNTIME_CONFIG],
|
||||
useFactory: (runtimeConfig: ApiRuntimeConfig) =>
|
||||
runtimeConfig.mysql.enabled
|
||||
? createMysqlHealthClient(runtimeConfig.mysql)
|
||||
: new DisabledHealthClient('MySQL 未启用'),
|
||||
},
|
||||
],
|
||||
exports: [API_RUNTIME_CONFIG],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { HealthService } from './health.service';
|
||||
|
||||
@ApiTags('health')
|
||||
@Controller('health')
|
||||
/** 将内部健康结果映射为 200/503,同时保持两种响应共用同一 OpenAPI DTO。 */
|
||||
export class HealthController {
|
||||
constructor(@Inject(HealthService) private readonly healthService: HealthService) {}
|
||||
|
||||
|
||||
@@ -5,14 +5,17 @@ import type {
|
||||
HealthResponseDto,
|
||||
} from '../../../libs/http-contracts/src/health.dto';
|
||||
|
||||
/** 依赖注入令牌避免健康服务耦合 ioredis 或 mysql2 客户端。 */
|
||||
export const REDIS_HEALTH_PORT = Symbol('REDIS_HEALTH_PORT');
|
||||
export const MYSQL_HEALTH_PORT = Symbol('MYSQL_HEALTH_PORT');
|
||||
|
||||
/** 健康端口只要求只读检查,便于禁用态和测试实现遵循同一契约。 */
|
||||
export interface DependencyHealthPort {
|
||||
check(): Promise<DependencyHealthDto>;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
/** 聚合依赖状态并统一清洗底层错误,禁止连接信息进入 HTTP 响应。 */
|
||||
export class HealthService {
|
||||
constructor(
|
||||
@Inject(REDIS_HEALTH_PORT) private readonly redis: DependencyHealthPort,
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'reflect-metadata';
|
||||
|
||||
import { type INestApplication, type NestApplicationOptions } from '@nestjs/common';
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import path from 'node:path';
|
||||
|
||||
import { loadApiProcessConfig } from '../../../libs/config/src/load-process-env';
|
||||
import {
|
||||
@@ -10,14 +11,18 @@ import {
|
||||
} from '../../../libs/config/src/runtime-config';
|
||||
import { ApiModule } from './api.module';
|
||||
|
||||
/** API 引导参数只接受已完成进程级校验的配置,测试可覆盖监听端口和日志器。 */
|
||||
interface BootstrapApiOptions {
|
||||
config: ApiRuntimeConfig;
|
||||
port?: number;
|
||||
logger?: NestApplicationOptions['logger'];
|
||||
}
|
||||
|
||||
/** 启动可独立部署的 API;配置由入口显式注入,监听前已完成全部校验。 */
|
||||
export async function bootstrapApi(options: BootstrapApiOptions): Promise<INestApplication> {
|
||||
const app = await NestFactory.create(ApiModule, { logger: options.logger });
|
||||
const app = await NestFactory.create(ApiModule.register(options.config), {
|
||||
logger: options.logger,
|
||||
});
|
||||
app.setGlobalPrefix('api/v1');
|
||||
app.enableShutdownHooks();
|
||||
await app.listen(options.port ?? options.config.api.port, options.config.api.host);
|
||||
@@ -30,6 +35,6 @@ async function runApi(): Promise<void> {
|
||||
await bootstrapApi({ config });
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
if (process.argv[1] && path.resolve(process.argv[1]) === path.resolve(__filename)) {
|
||||
void runApi();
|
||||
}
|
||||
|
||||
@@ -18,12 +18,9 @@ describe('Server OpenAPI 单向发布', () => {
|
||||
path.resolve(__dirname, '../../../openapi/openapi.json'),
|
||||
'utf8',
|
||||
);
|
||||
const document = JSON.parse(content) as {
|
||||
paths: Record<string, unknown>;
|
||||
components?: { schemas?: Record<string, unknown> };
|
||||
};
|
||||
const committedDocument = JSON.parse(content) as Record<string, unknown>;
|
||||
const liveDocument = await createOpenApiDocument();
|
||||
|
||||
expect(document.paths).toHaveProperty('/api/v1/health');
|
||||
expect(document.components?.schemas).toHaveProperty('HealthResponseDto');
|
||||
expect(committedDocument).toEqual(liveDocument);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,35 +2,39 @@ import 'reflect-metadata';
|
||||
|
||||
import { type INestApplicationContext, type NestApplicationOptions } from '@nestjs/common';
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import path from 'node:path';
|
||||
|
||||
import { loadSchedulerProcessConfig } from '../../../libs/config/src/load-process-env';
|
||||
import {
|
||||
toSafeRuntimeSummary,
|
||||
type SchedulerRuntimeConfig,
|
||||
} from '../../../libs/config/src/runtime-config';
|
||||
import { waitForShutdownSignal } from '../../../libs/infrastructure/src/process-lifecycle';
|
||||
import { SchedulerModule } from './scheduler.module';
|
||||
|
||||
/** Scheduler 引导参数确保模块只读取本进程已校验的最小配置。 */
|
||||
interface BootstrapSchedulerOptions {
|
||||
config: SchedulerRuntimeConfig;
|
||||
logger?: NestApplicationOptions['logger'];
|
||||
}
|
||||
|
||||
/** 启动无 HTTP 端口的 Scheduler 应用上下文,生命周期由命令入口持有。 */
|
||||
export async function bootstrapScheduler(
|
||||
options: BootstrapSchedulerOptions,
|
||||
): Promise<INestApplicationContext> {
|
||||
const app = await NestFactory.createApplicationContext(SchedulerModule, {
|
||||
const app = await NestFactory.createApplicationContext(SchedulerModule.register(options.config), {
|
||||
logger: options.logger,
|
||||
});
|
||||
app.enableShutdownHooks();
|
||||
return app;
|
||||
}
|
||||
|
||||
async function runScheduler(): Promise<void> {
|
||||
const config = loadSchedulerProcessConfig();
|
||||
console.info('Scheduler 进程启动配置', toSafeRuntimeSummary(config));
|
||||
await bootstrapScheduler({ config });
|
||||
const app = await bootstrapScheduler({ config });
|
||||
await waitForShutdownSignal(app);
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
if (process.argv[1] && path.resolve(process.argv[1]) === path.resolve(__filename)) {
|
||||
void runScheduler();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,18 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { type DynamicModule, Module } from '@nestjs/common';
|
||||
|
||||
import type { SchedulerRuntimeConfig } from '../../../libs/config/src/runtime-config';
|
||||
|
||||
/** Scheduler 的最小权限配置令牌,不向 API 或 Worker 暴露其任务调度配置。 */
|
||||
export const SCHEDULER_RUNTIME_CONFIG = Symbol('SCHEDULER_RUNTIME_CONFIG');
|
||||
|
||||
@Module({})
|
||||
export class SchedulerModule {}
|
||||
export class SchedulerModule {
|
||||
/** 将入口校验后的 Scheduler 配置注册进当前进程容器。 */
|
||||
static register(config: SchedulerRuntimeConfig): DynamicModule {
|
||||
return {
|
||||
module: SchedulerModule,
|
||||
providers: [{ provide: SCHEDULER_RUNTIME_CONFIG, useValue: config }],
|
||||
exports: [SCHEDULER_RUNTIME_CONFIG],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,35 +2,39 @@ import 'reflect-metadata';
|
||||
|
||||
import { type INestApplicationContext, type NestApplicationOptions } from '@nestjs/common';
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import path from 'node:path';
|
||||
|
||||
import { loadWorkerProcessConfig } from '../../../libs/config/src/load-process-env';
|
||||
import {
|
||||
toSafeRuntimeSummary,
|
||||
type WorkerRuntimeConfig,
|
||||
} from '../../../libs/config/src/runtime-config';
|
||||
import { waitForShutdownSignal } from '../../../libs/infrastructure/src/process-lifecycle';
|
||||
import { WorkerModule } from './worker.module';
|
||||
|
||||
/** Worker 引导参数确保模块只读取本进程已校验的最小配置。 */
|
||||
interface BootstrapWorkerOptions {
|
||||
config: WorkerRuntimeConfig;
|
||||
logger?: NestApplicationOptions['logger'];
|
||||
}
|
||||
|
||||
/** 启动无 HTTP 端口的 Worker 应用上下文,生命周期由命令入口持有。 */
|
||||
export async function bootstrapWorker(
|
||||
options: BootstrapWorkerOptions,
|
||||
): Promise<INestApplicationContext> {
|
||||
const app = await NestFactory.createApplicationContext(WorkerModule, {
|
||||
const app = await NestFactory.createApplicationContext(WorkerModule.register(options.config), {
|
||||
logger: options.logger,
|
||||
});
|
||||
app.enableShutdownHooks();
|
||||
return app;
|
||||
}
|
||||
|
||||
async function runWorker(): Promise<void> {
|
||||
const config = loadWorkerProcessConfig();
|
||||
console.info('Worker 进程启动配置', toSafeRuntimeSummary(config));
|
||||
await bootstrapWorker({ config });
|
||||
const app = await bootstrapWorker({ config });
|
||||
await waitForShutdownSignal(app);
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
if (process.argv[1] && path.resolve(process.argv[1]) === path.resolve(__filename)) {
|
||||
void runWorker();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,18 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { type DynamicModule, Module } from '@nestjs/common';
|
||||
|
||||
import type { WorkerRuntimeConfig } from '../../../libs/config/src/runtime-config';
|
||||
|
||||
/** Worker 的最小权限配置令牌,仅包含采集、持久化、消息与 AI 处理所需配置。 */
|
||||
export const WORKER_RUNTIME_CONFIG = Symbol('WORKER_RUNTIME_CONFIG');
|
||||
|
||||
@Module({})
|
||||
export class WorkerModule {}
|
||||
export class WorkerModule {
|
||||
/** 将入口校验后的 Worker 配置注册进当前进程容器。 */
|
||||
static register(config: WorkerRuntimeConfig): DynamicModule {
|
||||
return {
|
||||
module: WorkerModule,
|
||||
providers: [{ provide: WORKER_RUNTIME_CONFIG, useValue: config }],
|
||||
exports: [WORKER_RUNTIME_CONFIG],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user