463 lines
14 KiB
TypeScript
463 lines
14 KiB
TypeScript
/**
|
||
* @file Drizzle ORM服务类
|
||
* @author hotok
|
||
* @date 2025-06-29
|
||
* @lastEditor hotok
|
||
* @lastEditTime 2025-06-29
|
||
* @description 专业的Drizzle ORM数据库连接服务类,支持连接池管理、状态跟踪和优雅关闭
|
||
*/
|
||
|
||
import { drizzle } from 'drizzle-orm/mysql2';
|
||
import mysql from 'mysql2/promise';
|
||
import { dbConfig } from '@/config';
|
||
import { Logger } from '@/plugins/logger/logger.service';
|
||
import * as schema from '../../eneities';
|
||
import type { DrizzleDB, ConnectionStatus, DatabaseConnectionInfo } from '@/type/drizzle.type';
|
||
|
||
/**
|
||
* Drizzle数据库服务类
|
||
* 使用单例模式管理数据库连接
|
||
*/
|
||
export class DrizzleService {
|
||
/** 单例实例 */
|
||
private static instance: DrizzleService | null = null;
|
||
|
||
/** 数据库实例 */
|
||
private _db: DrizzleDB | null = null;
|
||
|
||
/** 连接池实例 */
|
||
private _connectionPool: mysql.Pool | null = null;
|
||
|
||
/** 连接状态信息 */
|
||
private _connectionInfo: DatabaseConnectionInfo;
|
||
|
||
/** 初始化标志 */
|
||
private _isInitialized = false;
|
||
|
||
/** 连接池配置 */
|
||
private readonly _poolConfig = {
|
||
/** 最大连接数 */
|
||
connectionLimit: Number(process.env.DB_CONNECTION_LIMIT) || 10,
|
||
/** 队列限制 */
|
||
queueLimit: Number(process.env.DB_QUEUE_LIMIT) || 0,
|
||
/** 等待连接 */
|
||
waitForConnections: true,
|
||
// 启用此选项后,MySQL驱动程序将支持大数字(big numbers),这对于存储和处理 bigint 类型的数据尤为重要。
|
||
// 如果不启用此选项,MySQL驱动程序可能无法正确处理超过 JavaScript 数字精度范围的大数值,导致数据精度丢失。
|
||
supportBigNumbers: true,
|
||
// 启用此选项后,MySQL驱动程序将在接收 bigint 或其他大数值时,将其作为字符串返回,而不是作为JavaScript数字。
|
||
// 这种处理方式可以避免JavaScript本身的数值精度限制问题,确保大数值在应用程序中保持精确。
|
||
bigNumberStrings: true,
|
||
};
|
||
|
||
/**
|
||
* 私有构造函数,防止外部实例化
|
||
*/
|
||
private constructor() {
|
||
this._connectionInfo = {
|
||
status: 'disconnected',
|
||
host: dbConfig.host,
|
||
port: dbConfig.port,
|
||
database: dbConfig.database,
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 获取单例实例
|
||
*/
|
||
public static getInstance(): DrizzleService {
|
||
if (!DrizzleService.instance) {
|
||
DrizzleService.instance = new DrizzleService();
|
||
}
|
||
return DrizzleService.instance;
|
||
}
|
||
|
||
/**
|
||
* 获取数据库实例
|
||
*/
|
||
public get db(): DrizzleDB {
|
||
if (!this._db) {
|
||
throw new Error('数据库未初始化,请先调用 initialize() 方法');
|
||
}
|
||
return this._db;
|
||
}
|
||
|
||
/**
|
||
* 获取连接状态信息
|
||
*/
|
||
public get connectionInfo(): DatabaseConnectionInfo {
|
||
return { ...this._connectionInfo };
|
||
}
|
||
|
||
/**
|
||
* 检查是否已初始化
|
||
*/
|
||
public get isInitialized(): boolean {
|
||
return this._isInitialized;
|
||
}
|
||
|
||
/**
|
||
* 验证数据库配置
|
||
*/
|
||
private validateConfig(): void {
|
||
const requiredFields = ['host', 'port', 'user', 'password', 'database'];
|
||
|
||
for (const field of requiredFields) {
|
||
if (!dbConfig[field as keyof typeof dbConfig]) {
|
||
throw new Error(`数据库配置缺少必需字段: ${field}`);
|
||
}
|
||
}
|
||
|
||
if (dbConfig.port < 1 || dbConfig.port > 65535) {
|
||
throw new Error(`数据库端口号无效: ${dbConfig.port}`);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 更新连接状态
|
||
*/
|
||
private updateConnectionStatus(status: ConnectionStatus, error?: string): void {
|
||
this._connectionInfo.status = status;
|
||
this._connectionInfo.error = error;
|
||
|
||
if (status === 'connected') {
|
||
this._connectionInfo.connectedAt = new Date();
|
||
this._connectionInfo.error = undefined;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 创建MySQL连接池
|
||
*/
|
||
private async createConnection(): Promise<mysql.Pool> {
|
||
try {
|
||
this.validateConfig();
|
||
|
||
this.updateConnectionStatus('connecting');
|
||
|
||
/** MySQL连接池配置 */
|
||
const connection = mysql.createPool({
|
||
host: dbConfig.host,
|
||
port: dbConfig.port,
|
||
user: dbConfig.user,
|
||
password: dbConfig.password,
|
||
database: dbConfig.database,
|
||
...this._poolConfig,
|
||
});
|
||
|
||
// 测试连接
|
||
const testConnection = await connection.getConnection();
|
||
await testConnection.ping();
|
||
testConnection.release();
|
||
|
||
this.updateConnectionStatus('connected');
|
||
Logger.info({
|
||
message: 'MySQL连接池创建成功',
|
||
host: dbConfig.host,
|
||
port: dbConfig.port,
|
||
database: dbConfig.database,
|
||
connectionLimit: this._poolConfig.connectionLimit,
|
||
});
|
||
|
||
return connection;
|
||
} catch (error) {
|
||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||
this.updateConnectionStatus('error', errorMessage);
|
||
Logger.error(new Error(`MySQL连接池创建失败: ${errorMessage}`));
|
||
throw new Error(`MySQL连接池创建失败: ${errorMessage}`);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 初始化数据库连接
|
||
*/
|
||
public async initialize(): Promise<DrizzleDB> {
|
||
// 防止重复初始化
|
||
if (this._isInitialized && this._db) {
|
||
Logger.info('Drizzle ORM 已初始化,返回现有实例');
|
||
return this._db;
|
||
}
|
||
|
||
try {
|
||
this._connectionPool = await this.createConnection();
|
||
|
||
console.log(process.env.NODE_ENV, process.env.NODE_ENV === 'development');
|
||
/** Drizzle数据库实例 */
|
||
this._db = drizzle(this._connectionPool, {
|
||
schema,
|
||
mode: 'default',
|
||
logger:
|
||
process.env.NODE_ENV === 'development'
|
||
? {
|
||
logQuery: (query, params) => {
|
||
Logger.debug({
|
||
type: 'SQL_QUERY',
|
||
query: query.replace(/\s+/g, ' ').trim(),
|
||
params: params,
|
||
});
|
||
},
|
||
}
|
||
: false,
|
||
});
|
||
|
||
this._isInitialized = true;
|
||
Logger.info({
|
||
message: 'Drizzle ORM 初始化成功',
|
||
schema: Object.keys(schema).length > 0 ? Object.keys(schema) : ['无schema'],
|
||
loggerEnabled: process.env.NODE_ENV === 'development',
|
||
});
|
||
|
||
return this._db;
|
||
} catch (error) {
|
||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||
this.updateConnectionStatus('error', errorMessage);
|
||
Logger.error(new Error(`Drizzle ORM 初始化失败: ${errorMessage}`));
|
||
throw new Error(`Drizzle ORM 初始化失败: ${errorMessage}`);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 检查数据库连接状态
|
||
*/
|
||
public async checkConnection(): Promise<boolean> {
|
||
try {
|
||
if (!this._connectionPool) {
|
||
return false;
|
||
}
|
||
|
||
const connection = await this._connectionPool.getConnection();
|
||
await connection.ping();
|
||
connection.release();
|
||
|
||
return true;
|
||
} catch (error) {
|
||
Logger.error(error instanceof Error ? error : new Error('数据库连接检查失败'));
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 优雅关闭数据库连接
|
||
*/
|
||
public async close(): Promise<void> {
|
||
try {
|
||
if (this._connectionPool) {
|
||
await this._connectionPool.end();
|
||
this._connectionPool = null;
|
||
this._db = null;
|
||
this.updateConnectionStatus('disconnected');
|
||
this._isInitialized = false;
|
||
Logger.info('数据库连接已关闭');
|
||
}
|
||
} catch (error) {
|
||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||
Logger.error(new Error(`关闭数据库连接时出错: ${errorMessage}`));
|
||
throw new Error(`关闭数据库连接失败: ${errorMessage}`);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 重新连接数据库
|
||
*/
|
||
public async reconnect(): Promise<DrizzleDB> {
|
||
Logger.info('正在重新连接数据库...');
|
||
|
||
// 先关闭现有连接
|
||
await this.close();
|
||
|
||
// 重新初始化连接
|
||
return await this.initialize();
|
||
}
|
||
|
||
/**
|
||
* 获取连接池统计信息
|
||
*/
|
||
public getPoolStats(): {
|
||
connectionLimit: number;
|
||
queueLimit: number;
|
||
waitForConnections: boolean;
|
||
} | null {
|
||
if (!this._connectionPool) {
|
||
return null;
|
||
}
|
||
|
||
return {
|
||
connectionLimit: this._poolConfig.connectionLimit,
|
||
queueLimit: this._poolConfig.queueLimit,
|
||
waitForConnections: this._poolConfig.waitForConnections,
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 执行健康检查
|
||
*/
|
||
public async healthCheck(): Promise<{
|
||
status: 'healthy' | 'unhealthy';
|
||
connectionInfo: DatabaseConnectionInfo;
|
||
isConnected: boolean;
|
||
poolStats?: ReturnType<DrizzleService['getPoolStats']>;
|
||
}> {
|
||
const isConnected = await this.checkConnection();
|
||
|
||
return {
|
||
status: isConnected ? 'healthy' : 'unhealthy',
|
||
connectionInfo: this.connectionInfo,
|
||
isConnected,
|
||
poolStats: this.getPoolStats(),
|
||
};
|
||
}
|
||
}
|
||
|
||
/**
|
||
* ==============================================
|
||
* 主要导出 - 推荐使用的API
|
||
* ==============================================
|
||
*/
|
||
|
||
/**
|
||
* Drizzle服务单例实例
|
||
*
|
||
* @description 获取DrizzleService的单例实例,推荐的使用方式
|
||
* @example
|
||
* ```typescript
|
||
* import { drizzleService } from '@/plugins/drizzle/drizzle.service';
|
||
*
|
||
* // 初始化数据库
|
||
* await drizzleService.initialize();
|
||
*
|
||
* // 获取数据库实例
|
||
* const database = drizzleService.db;
|
||
*
|
||
* // 检查连接状态
|
||
* const isConnected = await drizzleService.checkConnection();
|
||
* ```
|
||
*/
|
||
export const drizzleService = DrizzleService.getInstance();
|
||
|
||
/**
|
||
* ==============================================
|
||
* 向后兼容导出 - 保持原有函数式API
|
||
* ==============================================
|
||
*/
|
||
|
||
/**
|
||
* 创建并初始化Drizzle数据库连接
|
||
*
|
||
* @description 向后兼容的初始化方法,内部调用drizzleService.initialize()
|
||
* @returns {Promise<DrizzleDB>} 返回初始化后的Drizzle数据库实例
|
||
*
|
||
* @example
|
||
* ```typescript
|
||
* import { createDrizzleDB } from '@/plugins/drizzle/drizzle.service';
|
||
*
|
||
* const database = await createDrizzleDB();
|
||
* ```
|
||
*
|
||
* @deprecated 推荐使用 drizzleService.initialize() 替代
|
||
*/
|
||
export const createDrizzleDB = () => drizzleService.initialize();
|
||
|
||
/**
|
||
* 获取数据库连接状态信息
|
||
*
|
||
* @description 向后兼容的状态获取方法,内部调用drizzleService.connectionInfo
|
||
* @returns {DatabaseConnectionInfo} 返回数据库连接状态信息
|
||
*
|
||
* @example
|
||
* ```typescript
|
||
* import { getConnectionInfo } from '@/plugins/drizzle/drizzle.service';
|
||
*
|
||
* const info = getConnectionInfo();
|
||
* console.log(`数据库状态: ${info.status}`);
|
||
* ```
|
||
*
|
||
* @deprecated 推荐使用 drizzleService.connectionInfo 替代
|
||
*/
|
||
export const getConnectionInfo = () => drizzleService.connectionInfo;
|
||
|
||
/**
|
||
* 检查数据库连接状态
|
||
*
|
||
* @description 向后兼容的连接检查方法,内部调用drizzleService.checkConnection()
|
||
* @returns {Promise<boolean>} 返回连接是否正常
|
||
*
|
||
* @example
|
||
* ```typescript
|
||
* import { checkConnection } from '@/plugins/drizzle/drizzle.service';
|
||
*
|
||
* const isConnected = await checkConnection();
|
||
* if (!isConnected) {
|
||
* console.log('数据库连接异常');
|
||
* }
|
||
* ```
|
||
*
|
||
* @deprecated 推荐使用 drizzleService.checkConnection() 替代
|
||
*/
|
||
export const checkConnection = () => drizzleService.checkConnection();
|
||
|
||
/**
|
||
* 优雅关闭数据库连接
|
||
*
|
||
* @description 向后兼容的连接关闭方法,内部调用drizzleService.close()
|
||
* @returns {Promise<void>} 返回关闭操作的Promise
|
||
*
|
||
* @example
|
||
* ```typescript
|
||
* import { closeDrizzleDB } from '@/plugins/drizzle/drizzle.service';
|
||
*
|
||
* // 应用关闭时清理资源
|
||
* process.on('SIGTERM', async () => {
|
||
* await closeDrizzleDB();
|
||
* process.exit(0);
|
||
* });
|
||
* ```
|
||
*
|
||
* @deprecated 推荐使用 drizzleService.close() 替代
|
||
*/
|
||
export const closeDrizzleDB = () => drizzleService.close();
|
||
|
||
/**
|
||
* 重新连接数据库
|
||
*
|
||
* @description 向后兼容的重连方法,内部调用drizzleService.reconnect()
|
||
* @returns {Promise<DrizzleDB>} 返回重新连接后的数据库实例
|
||
*
|
||
* @example
|
||
* ```typescript
|
||
* import { reconnectDB } from '@/plugins/drizzle/drizzle.service';
|
||
*
|
||
* try {
|
||
* const database = await reconnectDB();
|
||
* console.log('数据库重连成功');
|
||
* } catch (error) {
|
||
* console.error('数据库重连失败:', error);
|
||
* }
|
||
* ```
|
||
*
|
||
* @deprecated 推荐使用 drizzleService.reconnect() 替代
|
||
*/
|
||
export const reconnectDB = () => drizzleService.reconnect();
|
||
|
||
/**
|
||
* 获取数据库实例
|
||
*
|
||
* @description 向后兼容的数据库实例获取方法,内部调用drizzleService.db
|
||
* @returns {DrizzleDB} 返回Drizzle数据库实例
|
||
* @throws {Error} 如果数据库未初始化则抛出错误
|
||
*
|
||
* @example
|
||
* ```typescript
|
||
* import { db } from '@/plugins/drizzle/drizzle.service';
|
||
*
|
||
* // 确保先初始化
|
||
* await createDrizzleDB();
|
||
*
|
||
* // 获取数据库实例
|
||
* const database = db();
|
||
* const users = await database.select().from(usersTable);
|
||
* ```
|
||
*
|
||
* @deprecateds 推荐使用 drizzleService.db 替代
|
||
*/
|
||
export const db = () => drizzleService.db;
|