Files
SaaS2/src/utils/ConnectDB.ts
2025-06-05 23:05:33 +08:00

241 lines
6.5 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.
/**
* 文件: src/utils/ConnectDB.ts
* 作者: 阿瑞
* 功能: MongoDB数据库连接管理工具 - 高阶函数装饰器
* 版本: v2.0.0
* @description 为Next.js API路由提供数据库连接管理支持连接复用、错误重试、连接超时等功能
*/
import mongoose from 'mongoose';
import { NextApiRequest, NextApiResponse } from 'next';
// ===========================================
// 类型定义区域
// ===========================================
/**
* 数据库连接配置接口
*/
interface DatabaseConfig {
maxRetries?: number; // 最大重试次数
retryDelay?: number; // 重试延迟(毫秒)
connectTimeout?: number; // 连接超时(毫秒)
enableLogging?: boolean; // 是否启用日志
}
/**
* API处理函数类型定义
*/
type ApiHandler = (req: NextApiRequest, res: NextApiResponse) => Promise<void>;
// ===========================================
// 配置常量区域
// ===========================================
/**
* 默认数据库连接配置
*/
const DEFAULT_CONFIG: Required<DatabaseConfig> = {
maxRetries: 3, // 默认重试3次
retryDelay: 1000, // 默认延迟1秒
connectTimeout: 10000, // 默认10秒超时
enableLogging: process.env.NODE_ENV === 'development', // 开发环境启用日志
};
/**
* Mongoose连接状态枚举
* 0 = 断开连接, 1 = 已连接, 2 = 正在连接, 3 = 正在断开连接
*/
const CONNECTION_STATES = {
DISCONNECTED: 0,
CONNECTED: 1,
CONNECTING: 2,
DISCONNECTING: 3,
} as const;
// ===========================================
// 工具函数区域
// ===========================================
/**
* 安全的日志记录函数
* @param message 日志消息
* @param data 可选的数据对象
* @param isError 是否为错误日志
*/
const safeLog = (message: string, data?: any, isError = false): void => {
if (DEFAULT_CONFIG.enableLogging) {
if (isError) {
console.error(`[DB Error] ${message}`, data);
} else {
console.log(`[DB Info] ${message}`, data);
}
}
};
/**
* 延迟函数
* @param ms 延迟毫秒数
*/
const delay = (ms: number): Promise<void> =>
new Promise(resolve => setTimeout(resolve, ms));
/**
* 检查数据库连接状态
* @returns 是否已连接
*/
const isDbConnected = (): boolean => {
const connection = mongoose.connections[0];
return connection && connection.readyState === CONNECTION_STATES.CONNECTED;
};
/**
* 获取数据库连接URI
* @returns 数据库URI或null
*/
const getDatabaseUri = (): string | null => {
const dbUri = process.env.MONGODB_URI;
if (!dbUri) {
safeLog('数据库URI未在环境变量中配置', null, true);
return null;
}
return dbUri;
};
// ===========================================
// 核心连接功能区域
// ===========================================
/**
* 执行数据库连接(带重试机制)
* @param dbUri 数据库连接URI
* @param config 连接配置
* @returns Promise<boolean> 连接是否成功
*/
const connectWithRetry = async (
dbUri: string,
config: Required<DatabaseConfig>
): Promise<boolean> => {
for (let attempt = 1; attempt <= config.maxRetries; attempt++) {
try {
safeLog(`尝试连接数据库 (第${attempt}/${config.maxRetries}次)`);
// 设置mongoose连接选项
const connectOptions = {
serverSelectionTimeoutMS: config.connectTimeout,
socketTimeoutMS: config.connectTimeout,
family: 4, // 使用IPv4
maxPoolSize: 10, // 连接池大小
retryWrites: true,
w: 'majority' as const, // 写关注级别 - 修复类型错误
};
await mongoose.connect(dbUri, connectOptions);
safeLog('数据库连接成功');
return true;
} catch (error) {
const isLastAttempt = attempt === config.maxRetries;
safeLog(
`数据库连接失败 (第${attempt}/${config.maxRetries}次)`,
{ error: error instanceof Error ? error.message : 'Unknown error' },
true
);
if (!isLastAttempt) {
safeLog(`等待${config.retryDelay}ms后重试...`);
await delay(config.retryDelay);
}
}
}
return false;
};
// ===========================================
// 主要导出功能区域
// ===========================================
/**
* 数据库连接高阶函数装饰器
* @description 为API路由提供数据库连接管理包含连接复用、错误重试、超时处理等功能
* @param handler API处理函数
* @param config 可选的数据库连接配置
* @returns 包装后的API处理函数
*
* @example
* ```typescript
* export default connectDB(async (req, res) => {
* // 你的API逻辑
* });
* ```
*/
const connectDB = (
handler: ApiHandler,
config: DatabaseConfig = {}
) => async (req: NextApiRequest, res: NextApiResponse): Promise<void> => {
// 合并配置
const finalConfig: Required<DatabaseConfig> = { ...DEFAULT_CONFIG, ...config };
try {
// 检查现有连接状态
if (isDbConnected()) {
safeLog('使用现有数据库连接');
return await handler(req, res);
}
// 获取数据库URI
const dbUri = getDatabaseUri();
if (!dbUri) {
return res.status(500).json({
error: 'Database configuration error',
message: process.env.NODE_ENV === 'development'
? 'MONGODB_URI not found in environment variables'
: 'Database configuration is missing'
});
}
// 尝试连接数据库
const connected = await connectWithRetry(dbUri, finalConfig);
if (!connected) {
safeLog('所有数据库连接尝试均失败', null, true);
return res.status(500).json({
error: 'Database connection failed',
message: 'Unable to establish database connection after multiple attempts'
});
}
// 成功连接后执行API处理函数
return await handler(req, res);
} catch (error) {
// 全局错误捕获
safeLog('数据库连接装饰器发生未预期错误', error, true);
return res.status(500).json({
error: 'Internal server error',
message: process.env.NODE_ENV === 'development'
? (error instanceof Error ? error.message : 'Unknown error occurred')
: 'An unexpected error occurred'
});
}
};
// ===========================================
// 导出区域
// ===========================================
export default connectDB;
/**
* 导出配置常量供外部使用
*/
export { DEFAULT_CONFIG, CONNECTION_STATES };
/**
* 导出类型定义供外部使用
*/
export type { DatabaseConfig, ApiHandler };