82 lines
2.7 KiB
TypeScript
82 lines
2.7 KiB
TypeScript
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;
|
|
}
|
|
|
|
/** MySQL 健康客户端只校验元数据,不读取表数据或执行写入。 */
|
|
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 未返回元数据');
|
|
const versionMatch = /^(5\.7\.\d+)(?:[-+].*)?$/.exec(row.version ?? '');
|
|
if (!versionMatch) {
|
|
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 ${versionMatch[1]} / ${row.databaseName} / ${row.timezone}`,
|
|
};
|
|
}
|
|
|
|
async close(): Promise<void> {
|
|
await this.client.end();
|
|
}
|
|
|
|
async onModuleDestroy(): Promise<void> {
|
|
await this.close();
|
|
}
|
|
}
|
|
|
|
/** 创建最多两个连接的只读健康池,并显式应用 MySQL 5.7.x 会话时区。 */
|
|
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);
|
|
}
|