cursor-init/src/plugins/drizzle/drizzle.service.ts

463 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* @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;