feat(GOV-009): 建立后端契约与健康检查

This commit is contained in:
2026-08-13 17:26:51 +08:00
parent 3fc703de0b
commit 420aa22342
32 changed files with 1625 additions and 8 deletions
@@ -0,0 +1,75 @@
import { createPool, type Pool } from 'mysql2/promise';
import type { OnModuleDestroy } from '@nestjs/common';
import type { MysqlRuntimeConfig } from '../../config/src/runtime-config';
import type { DependencyHealthDto } from '../../http-contracts/src/health.dto';
export const MYSQL_HEALTH_QUERY = `SELECT
VERSION() AS version,
DATABASE() AS databaseName,
@@character_set_database AS charset,
@@collation_database AS collation,
@@session.time_zone AS timezone`;
interface MysqlQueryPort {
query(sql: string): Promise<[unknown, unknown]>;
end(): Promise<void>;
}
interface MysqlMetadataRow {
version: string;
databaseName: string;
charset: string;
collation: string;
timezone: string;
}
export class MysqlHealthClient implements OnModuleDestroy {
constructor(
private readonly client: MysqlQueryPort,
private readonly expectedDatabase: string,
) {}
async check(): Promise<DependencyHealthDto> {
const [rows] = await this.client.query(MYSQL_HEALTH_QUERY);
const row = (rows as unknown as MysqlMetadataRow[])[0];
if (!row) throw new Error('MySQL 未返回元数据');
if (!row.version.startsWith('5.7.')) throw new Error('MySQL 5.7.x 版本要求未满足');
if (row.databaseName !== this.expectedDatabase) {
throw new Error(`MySQL 健康检查必须连接 ${this.expectedDatabase}`);
}
if (row.charset !== 'utf8mb4') throw new Error('MySQL 字符集必须为 utf8mb4');
if (row.collation !== 'utf8mb4_general_ci') {
throw new Error('MySQL 排序规则必须为 utf8mb4_general_ci');
}
if (row.timezone !== '+08:00') throw new Error('MySQL 会话时区必须为 +08:00');
return {
status: 'healthy',
detail: `MySQL 5.7 / ${row.databaseName} / ${row.timezone}`,
};
}
async close(): Promise<void> {
await this.client.end();
}
async onModuleDestroy(): Promise<void> {
await this.close();
}
}
export function createMysqlHealthClient(config: MysqlRuntimeConfig): MysqlHealthClient {
const pool: Pool = createPool({
host: config.host,
port: config.port,
database: config.database,
user: config.username,
password: config.password,
charset: config.charset,
timezone: config.timezone,
connectionLimit: 2,
enableKeepAlive: false,
});
return new MysqlHealthClient(pool, config.database);
}