feat(msg): 完整实现 msg 消息服务

包含 channels/preferences/templates/grpc/kafka/outbox/push/redis 等完整实现
This commit is contained in:
SpecialX
2026-07-10 19:09:52 +08:00
parent 21530dc7f6
commit 7b7abbb309
51 changed files with 5204 additions and 371 deletions

View File

@@ -6,22 +6,42 @@ import {
} from "@nestjs/common";
import { closeDb } from "../../config/database.js";
import { closeEs } from "../../config/elasticsearch.js";
import { connectKafka, disconnectKafka } from "../kafka/kafka.client.js";
import { closeRedis } from "../redis/redis.client.js";
import { outboxPublisher } from "../outbox/outbox.publisher.js";
const SERVICE_NAME = "msg";
/**
* 优雅停机服务。
* 优雅停机服务 + 资源生命周期管理
*
* 信号处理由 NestJS 在 `app.listen` 之前调用 `app.enableShutdownHooks()`
* 触发SIGTERM / SIGINTNestJS 会依次调用 OnApplicationShutdown 钩子。
* 仲裁依据 G2统一管理 DB/ES/Redis/Kafka/OutboxPublisher 生命周期。
*
* 关闭顺序ES → Drizzle。先关搜索索引避免新数据丢失再关 DB。
* 启动顺序onModuleInit
* 1. connectKafka():连接 producer + consumer幂等KafkaConsumerService 也会调用)
* 2. outboxPublisher.start():启动轮询 worker投递 pending 事件到 Kafka
*
* 关闭顺序onApplicationShutdown在 OnModuleDestroy 之后执行):
* 1. outboxPublisher.stop():停止轮询
* 2. disconnectKafka():断开 producer + consumer
* 3. closeRedis():关闭 Redis 连接
* 4. closeEs():关闭 ES 客户端
* 5. closeDb():关闭 MySQL 连接池
*
* 注KafkaConsumerService 实现 OnModuleDestroy其 stop() 会在
* onApplicationShutdown 之前被 NestJS 调用。disconnectKafka() 是幂等的。
*/
@Injectable()
export class LifecycleService implements OnModuleInit, OnApplicationShutdown {
private readonly logger = new Logger(LifecycleService.name);
onModuleInit(): void {
async onModuleInit(): Promise<void> {
// 1. 连接 Kafkaproducer + consumer
await connectKafka();
// 2. 启动 Outbox publisher 轮询
await outboxPublisher.start();
this.logger.log(`service ${SERVICE_NAME} module initialized`);
}
@@ -30,15 +50,19 @@ export class LifecycleService implements OnModuleInit, OnApplicationShutdown {
`service ${SERVICE_NAME} shutting down (signal=${signal ?? "unknown"})`,
);
await this.safeDisconnect("elasticsearch", () => closeEs());
await this.safeDisconnect("drizzle", () => closeDb());
// 按依赖反序关闭
await this.safeStop("outboxPublisher", () => outboxPublisher.stop());
await this.safeStop("kafka", () => disconnectKafka());
await this.safeStop("redis", () => closeRedis());
await this.safeStop("elasticsearch", () => closeEs());
await this.safeStop("drizzle", () => closeDb());
this.logger.log(`service ${SERVICE_NAME} shutdown complete`);
}
private async safeDisconnect(
private async safeStop(
name: string,
fn: () => Promise<void>,
fn: () => Promise<unknown>,
): Promise<void> {
try {
await fn();