fix(iam): 修复 TS 编译错误、ESM 依赖注入与身份头读取
P1 端到端验证中发现 IAM 服务存在 14 个 TS 编译错误与运行时 DI 失败: - 移除 typeorm/ioredis/kafkajs 依赖(IAM 用 Drizzle) - health.controller.ts 改用 db.execute(sql SELECT 1) 校验连接 - lifecycle.service.ts 简化为只关闭 Drizzle 连接池 - Drizzle API 修正:r.roles -> r.iam_roles,.in() -> inArray() - ESM 模式下 DI 必须显式 @Inject(IamRepository)(参考 classes 黄金模板) - iam.controller.ts 直接读 req.headers[x-user-id],不依赖未注册的 AuthMiddleware - health.module.ts 补 .js 后缀 - package.json 补 @types/express 验证:register -> JWT -> Gateway /iam/me 200 -> /classes CRUD 200
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
import { Controller, Get, HttpException, HttpStatus } from '@nestjs/common';
|
||||
import { DataSource } from 'typeorm';
|
||||
import { Controller, Get, HttpException, HttpStatus } from "@nestjs/common";
|
||||
import { sql } from "drizzle-orm";
|
||||
import { getDb } from "../../config/database.js";
|
||||
|
||||
const SERVICE_NAME = 'iam';
|
||||
const SERVICE_NAME = "iam";
|
||||
|
||||
/**
|
||||
* 健康检查端点。
|
||||
@@ -14,33 +15,37 @@ const SERVICE_NAME = 'iam';
|
||||
*/
|
||||
@Controller()
|
||||
export class HealthController {
|
||||
constructor(private readonly dataSource: DataSource) {}
|
||||
|
||||
@Get('healthz')
|
||||
@Get("healthz")
|
||||
liveness(): { status: string; service: string; timestamp: string } {
|
||||
return {
|
||||
status: 'ok',
|
||||
status: "ok",
|
||||
service: SERVICE_NAME,
|
||||
timestamp: new Date().toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
@Get('readyz')
|
||||
async readiness(): Promise<{ status: string; service: string; timestamp: string }> {
|
||||
@Get("readyz")
|
||||
async readiness(): Promise<{
|
||||
status: string;
|
||||
service: string;
|
||||
timestamp: string;
|
||||
}> {
|
||||
try {
|
||||
await this.dataSource.query('SELECT 1');
|
||||
const db = getDb();
|
||||
await db.execute(sql`SELECT 1`);
|
||||
return {
|
||||
status: 'ok',
|
||||
status: "ok",
|
||||
service: SERVICE_NAME,
|
||||
timestamp: new Date().toISOString(),
|
||||
};
|
||||
} catch (error) {
|
||||
throw new HttpException(
|
||||
{
|
||||
status: 'error',
|
||||
status: "error",
|
||||
service: SERVICE_NAME,
|
||||
timestamp: new Date().toISOString(),
|
||||
error: error instanceof Error ? error.message : 'database unreachable',
|
||||
error:
|
||||
error instanceof Error ? error.message : "database unreachable",
|
||||
},
|
||||
HttpStatus.SERVICE_UNAVAILABLE,
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { HealthController } from './health.controller';
|
||||
import { Module } from "@nestjs/common";
|
||||
import { HealthController } from "./health.controller.js";
|
||||
|
||||
/**
|
||||
* 健康检查模块。
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { Inject, Injectable, Logger, OnApplicationShutdown, OnModuleInit } from '@nestjs/common';
|
||||
import { DataSource } from 'typeorm';
|
||||
import type { Redis } from 'ioredis';
|
||||
import type { Producer } from 'kafkajs';
|
||||
import {
|
||||
Injectable,
|
||||
Logger,
|
||||
OnApplicationShutdown,
|
||||
OnModuleInit,
|
||||
} from "@nestjs/common";
|
||||
import { closeDb } from "../../config/database.js";
|
||||
|
||||
const SERVICE_NAME = 'iam';
|
||||
const SERVICE_NAME = "iam";
|
||||
|
||||
/**
|
||||
* 优雅停机服务。
|
||||
@@ -12,52 +15,31 @@ const SERVICE_NAME = 'iam';
|
||||
* 触发(SIGTERM / SIGINT),NestJS 会依次调用 OnApplicationShutdown 钩子。
|
||||
* K8s 配置 `terminationGracePeriodSeconds=60` 给予足够时间清理。
|
||||
*
|
||||
* 关闭顺序:Kafka producer → Redis → DataSource。
|
||||
* 先停外部消息生产避免新事件,再关缓存,最后关 DB。
|
||||
*
|
||||
* 集成说明(不修改 app.module.ts,仅在 README 注释说明):
|
||||
* - 在 `app.module.ts` 的 providers 中加入 `LifecycleService`。
|
||||
* - 在 `main.ts` 中 `app.listen` 之前调用 `app.enableShutdownHooks()`。
|
||||
*
|
||||
* 依赖注入 token 约定(需与各服务 provider 注册一致):
|
||||
* - DataSource:由 `TypeOrmModule.forRoot()` 提供。
|
||||
* - 'REDIS_CLIENT':需在对应模块注册 `{ provide: 'REDIS_CLIENT', useFactory: ... }`。
|
||||
* - 'KAFKA_PRODUCER':需在对应模块注册 `{ provide: 'KAFKA_PRODUCER', useFactory: ... }`。
|
||||
* IAM 服务仅使用 Drizzle ORM(MySQL),无 Kafka / Redis 依赖。
|
||||
* 关闭时仅需关闭数据库连接池。
|
||||
*/
|
||||
@Injectable()
|
||||
export class LifecycleService implements OnModuleInit, OnApplicationShutdown {
|
||||
private readonly logger = new Logger(LifecycleService.name);
|
||||
|
||||
constructor(
|
||||
private readonly dataSource: DataSource,
|
||||
@Inject('REDIS_CLIENT') private readonly redis: Redis,
|
||||
@Inject('KAFKA_PRODUCER') private readonly kafkaProducer: Producer,
|
||||
) {}
|
||||
|
||||
onModuleInit(): void {
|
||||
this.logger.log(`service ${SERVICE_NAME} module initialized`);
|
||||
}
|
||||
|
||||
async onApplicationShutdown(signal?: string): Promise<void> {
|
||||
this.logger.log(
|
||||
`service ${SERVICE_NAME} shutting down (signal=${signal ?? 'unknown'})`,
|
||||
`service ${SERVICE_NAME} shutting down (signal=${signal ?? "unknown"})`,
|
||||
);
|
||||
|
||||
await this.safeDisconnect('kafka producer', () => this.kafkaProducer.disconnect());
|
||||
await this.safeDisconnect('redis', () => this.redis.quit());
|
||||
await this.safeDisconnect('datasource', () => this.dataSource.destroy());
|
||||
try {
|
||||
await closeDb();
|
||||
this.logger.log("database connection closed");
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
`database close failed: ${error instanceof Error ? error.message : String(error)}`,
|
||||
);
|
||||
}
|
||||
|
||||
this.logger.log(`service ${SERVICE_NAME} shutdown complete`);
|
||||
}
|
||||
|
||||
private async safeDisconnect(name: string, fn: () => Promise<unknown>): Promise<void> {
|
||||
try {
|
||||
await fn();
|
||||
this.logger.log(`${name} closed`);
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
`${name} close failed: ${error instanceof Error ? error.message : String(error)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user