55 lines
1.8 KiB
TypeScript
55 lines
1.8 KiB
TypeScript
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<DependencyHealthDto>;
|
|
}
|
|
|
|
@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<HealthResponseDto> {
|
|
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<DependencyHealthDto> {
|
|
try {
|
|
const result = await port.check();
|
|
if (result.status === 'healthy' || result.status === 'disabled') return result;
|
|
} catch {
|
|
// 外部错误可能包含 DSN、用户名或密码;HTTP 健康响应只暴露固定安全文案。
|
|
}
|
|
return { status: 'unavailable', detail: '依赖检查失败' };
|
|
}
|
|
}
|