import { Inject, Injectable } from '@nestjs/common'; import type { DependencyHealthDto, 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; } @Injectable() /** 聚合依赖状态并统一清洗底层错误,禁止连接信息进入 HTTP 响应。 */ export class HealthService { constructor( @Inject(REDIS_HEALTH_PORT) private readonly redis: DependencyHealthPort, @Inject(MYSQL_HEALTH_PORT) private readonly mysql: DependencyHealthPort, ) {} async check(): Promise { const [redis, mysql] = await Promise.all([ this.checkSafely(this.redis), this.checkSafely(this.mysql), ]); const status = redis.status === 'healthy' && mysql.status !== 'unavailable' ? 'healthy' : 'unavailable'; return { status, process: 'api', checkedAt: new Date().toISOString(), dependencies: { redis, mysql, rocketMq: { status: 'disabled', detail: 'GOV-009 尚未启用 RocketMQ 客户端' }, }, }; } private async checkSafely(port: DependencyHealthPort): Promise { try { const result = await port.check(); if (result.status === 'healthy' || result.status === 'disabled') return result; } catch { // 外部错误可能包含 DSN、用户名或密码;HTTP 健康响应只暴露固定安全文案。 } return { status: 'unavailable', detail: '依赖检查失败' }; } }