AIClient-2-API/src/services/api-manager.js
Wenaixi 1018750388 fix: 深度review后续修复——安全强化、i18n补全、代码清理
安全修复:
- PBKDF2迭代次数从100k提升至310k(OWASP 2023 SHA-512标准)
- 密码最小长度从8位提升至12位
- sanitizeProviderData正则加强:data:协议拒绝而非部分移除,
  on\w+事件处理器更严格,javascript:加单词边界防止误匹配
- withFileLock错误处理改为重新抛出,不再静默吞错误
- 后端interval上限校验(MAX_INTERVAL_MS)确保配置一致性

功能修复:
- 重命名performHealthChecks/performScheduledHealthChecks方法,
  明确区分初始化检查和定时检查的职责
- generateUUID回退方案兼容Node.js <14.17.0
- 凭据无expiry字段时强制刷新(安全措施)

代码清理:
- 移除未使用的RETRY.DEFAULT_RETRIES常量
- 添加定时健康检查完整英文i18n翻译
2026-04-03 02:56:34 +08:00

116 lines
No EOL
5.3 KiB
JavaScript

import {
handleModelListRequest,
handleContentGenerationRequest,
API_ACTIONS,
ENDPOINT_TYPE
} from '../utils/common.js';
import { getProviderPoolManager } from './service-manager.js';
import logger from '../utils/logger.js';
/**
* Handle API authentication and routing
* @param {string} method - The HTTP method
* @param {string} path - The request path
* @param {http.IncomingMessage} req - The HTTP request object
* @param {http.ServerResponse} res - The HTTP response object
* @param {Object} currentConfig - The current configuration object
* @param {Object} apiService - The API service instance
* @param {Object} providerPoolManager - The provider pool manager instance
* @param {string} promptLogFilename - The prompt log filename
* @returns {Promise<boolean>} - True if the request was handled by API
*/
export async function handleAPIRequests(method, path, req, res, currentConfig, apiService, providerPoolManager, promptLogFilename) {
// Route model list requests
if (method === 'GET') {
if (path === '/v1/models') {
await handleModelListRequest(req, res, apiService, ENDPOINT_TYPE.OPENAI_MODEL_LIST, currentConfig, providerPoolManager, currentConfig.uuid);
return true;
}
if (path === '/v1beta/models') {
await handleModelListRequest(req, res, apiService, ENDPOINT_TYPE.GEMINI_MODEL_LIST, currentConfig, providerPoolManager, currentConfig.uuid);
return true;
}
}
// Route content generation requests
if (method === 'POST') {
if (path === '/v1/chat/completions') {
await handleContentGenerationRequest(req, res, apiService, ENDPOINT_TYPE.OPENAI_CHAT, currentConfig, promptLogFilename, providerPoolManager, currentConfig.uuid, path);
return true;
}
if (path === '/v1/responses') {
await handleContentGenerationRequest(req, res, apiService, ENDPOINT_TYPE.OPENAI_RESPONSES, currentConfig, promptLogFilename, providerPoolManager, currentConfig.uuid, path);
return true;
}
const geminiUrlPattern = new RegExp(`/v1beta/models/(.+?):(${API_ACTIONS.GENERATE_CONTENT}|${API_ACTIONS.STREAM_GENERATE_CONTENT})`);
if (geminiUrlPattern.test(path)) {
await handleContentGenerationRequest(req, res, apiService, ENDPOINT_TYPE.GEMINI_CONTENT, currentConfig, promptLogFilename, providerPoolManager, currentConfig.uuid, path);
return true;
}
if (path === '/v1/messages') {
await handleContentGenerationRequest(req, res, apiService, ENDPOINT_TYPE.CLAUDE_MESSAGE, currentConfig, promptLogFilename, providerPoolManager, currentConfig.uuid, path);
return true;
}
}
return false;
}
/**
* Initialize API management features
* @param {Object} services - The initialized services
* @returns {Function} - The heartbeat and token refresh function
*/
export function initializeAPIManagement(services) {
const providerPoolManager = getProviderPoolManager();
return async function heartbeatAndRefreshToken() {
logger.info(`[Heartbeat] Server is running. Current time: ${new Date().toLocaleString()}`, Object.keys(services));
// 循环遍历所有已初始化的服务适配器,并尝试刷新令牌
// if (getProviderPoolManager()) {
// await getProviderPoolManager().performInitialHealthChecks(); // 定期执行健康检查
// }
for (const providerKey in services) {
const serviceAdapter = services[providerKey];
try {
// For pooled providers, refreshToken should be handled by individual instances
// For single instances, this remains relevant
if (serviceAdapter.config?.uuid && providerPoolManager) {
providerPoolManager._enqueueRefresh(serviceAdapter.config.MODEL_PROVIDER, {
config: serviceAdapter.config,
uuid: serviceAdapter.config.uuid
});
} else {
await serviceAdapter.refreshToken();
}
// logger.info(`[Token Refresh] Refreshed token for ${providerKey}`);
} catch (error) {
logger.error(`[Token Refresh Error] Failed to refresh token for ${providerKey}: ${error.message}`);
// 如果是号池中的某个实例刷新失败,这里需要捕获并更新其状态
// 现有的 serviceInstances 存储的是每个配置对应的单例,而非池中的成员
// 这意味着如果一个池成员的 token 刷新失败,需要找到它并更新其在 poolManager 中的状态
// 暂时通过捕获错误日志来发现问题,更精细的控制需要在 refreshToken 中抛出更多信息
}
}
};
}
/**
* Helper function to read request body
* @param {http.IncomingMessage} req The HTTP request object.
* @returns {Promise<string>} The request body as string.
*/
export function readRequestBody(req) {
return new Promise((resolve, reject) => {
let body = '';
req.on('data', chunk => {
body += chunk.toString();
});
req.on('end', () => {
resolve(body);
});
req.on('error', err => {
reject(err);
});
});
}