/** * @file js/config/proxy-config.js * @description * 代理服务器地址统一配置文件 * * 集中管理所有代理服务器地址、API 端点和端口配置。 * * 使用方式: * 1. 前端代码:window.ProxyConfig.getProxyUrl() * 2. 支持环境变量覆盖:window.ENV_PROXY_URL * 3. 支持 localStorage 用户自定义 */ (function () { 'use strict'; // ==================== 默认配置 ==================== const DEFAULT_CONFIG = { // 本地代理服务器端口 LOCAL_PROXY_PORT: 3456, // 前端静态服务端口 FRONTEND_PORT: 8080, // 后端 API 服务端口(Docker 部署时) BACKEND_API_PORT: 3000, // 本地代理服务器地址 LOCAL_PROXY_URL: 'http://localhost:3456', // 生产环境代理地址(相对路径,由 Nginx 等代理) PROD_PROXY_URL: '/api', // 各服务端点路径 ENDPOINTS: { // OCR 相关 MISTRAL_BASE: '/api/mistral', MINERU_BASE: '/mineru', DOC2X_BASE: '/doc2x', // LLM 代理 LLM_PROXY: '/api/llm', // OSS 上传 OSS_UPLOAD: '/api/upload/oss', // 学术搜索 SEMANTIC_SCHOLAR: '/api/semanticscholar', PUBMED: '/api/pubmed', CROSSREF: '/api/crossref', OPENALEX: '/api/openalex', ARXIV: '/api/arxiv', // PDF 下载代理 PDF_DOWNLOAD: '/api/pdf/download', // 健康检查 HEALTH: '/health', // 后端 API BACKEND_API: '/api' }, // 外部服务直连地址(供参考,实际通过代理访问) EXTERNAL_SERVICES: { MINERU_API: 'https://mineru.net/api/v4', DOC2X_API: 'https://v2.doc2x.noedgeai.com', MISTRAL_API: 'https://api.mistral.ai', OPENAI_API: 'https://api.openai.com', DEEPSEEK_API: 'https://api.deepseek.com', ANTHROPIC_API: 'https://api.anthropic.com', GEMINI_API: 'https://generativelanguage.googleapis.com', ALIYUN_DASHSCOPE: 'https://dashscope.aliyuncs.com/compatible-mode', ZHIPU_API: 'https://open.bigmodel.cn/api/paas/v4' } }; // ==================== 配置管理类 ==================== class ProxyConfig { constructor() { this.config = { ...DEFAULT_CONFIG }; this._loadUserConfig(); } /** * 加载用户自定义配置 * 优先级:环境变量 > localStorage > 默认值 */ _loadUserConfig() { // 1. 检查环境变量覆盖 if (typeof window !== 'undefined') { if (window.ENV_PROXY_URL) { this.config.LOCAL_PROXY_URL = window.ENV_PROXY_URL; } if (window.ENV_API_BASE_URL) { this.config.PROD_PROXY_URL = window.ENV_API_BASE_URL; } if (window.ENV_LOCAL_PROXY_PORT) { this.config.LOCAL_PROXY_PORT = parseInt(window.ENV_LOCAL_PROXY_PORT, 10); } } // 2. 检查 localStorage 用户配置 try { const savedProxyUrl = localStorage.getItem('proxyUrl'); if (savedProxyUrl) { this.config.LOCAL_PROXY_URL = savedProxyUrl; } const savedPort = localStorage.getItem('proxyPort'); if (savedPort) { this.config.LOCAL_PROXY_PORT = parseInt(savedPort, 10); } } catch (e) { // localStorage 不可用时忽略 } } /** * 判断是否为本地开发环境 * @returns {boolean} */ isLocalDevelopment() { if (typeof window === 'undefined') return false; const hostname = window.location.hostname; return hostname === 'localhost' || hostname === '127.0.0.1' || hostname.startsWith('192.168.') || hostname.startsWith('10.') || hostname.endsWith('.local'); } /** * 判断是否为 file:// 协议(纯本地文件访问) * @returns {boolean} */ isFileProtocol() { if (typeof window === 'undefined') return false; return window.location.protocol === 'file:'; } /** * 获取代理服务器基础 URL * @returns {string} */ getProxyUrl() { // file:// 协议无法访问本地服务器 if (this.isFileProtocol()) { console.warn('[ProxyConfig] file:// 协议无法访问代理服务器'); return ''; } // 本地开发环境使用完整 URL if (this.isLocalDevelopment()) { return this.config.LOCAL_PROXY_URL; } // 生产环境使用相对路径 return this.config.PROD_PROXY_URL; } /** * 获取本地代理服务器端口 * @returns {number} */ getLocalProxyPort() { return this.config.LOCAL_PROXY_PORT; } /** * 获取前端服务端口 * @returns {number} */ getFrontendPort() { return this.config.FRONTEND_PORT; } /** * 获取指定端点的完整 URL * @param {string} endpointName - 端点名称(ENDPOINTS 中的 key) * @param {string} [suffix=''] - 路径后缀 * @returns {string} */ getEndpointUrl(endpointName, suffix = '') { const basePath = this.config.ENDPOINTS[endpointName]; if (!basePath) { console.warn(`[ProxyConfig] Unknown endpoint: ${endpointName}`); return ''; } return `${this.getProxyUrl()}${basePath}${suffix}`; } // ==================== 便捷方法:各服务 URL ==================== /** * Mistral OCR 服务 URL * @param {string} [path=''] - 路径后缀 * @returns {string} */ getMistralUrl(path = '') { const base = this.config.ENDPOINTS.MISTRAL_BASE; return `${this.getProxyUrl()}${base}${path}`; } /** * MinerU OCR 服务 URL * @param {string} [path=''] - 路径后缀 * @returns {string} */ getMinerUUrl(path = '') { const base = this.config.ENDPOINTS.MINERU_BASE; return `${this.getProxyUrl()}${base}${path}`; } /** * Doc2X OCR 服务 URL * @param {string} [path=''] - 路径后缀 * @returns {string} */ getDoc2XUrl(path = '') { const base = this.config.ENDPOINTS.DOC2X_BASE; return `${this.getProxyUrl()}${base}${path}`; } /** * LLM 代理服务 URL * @param {string} provider - 提供商名称(openai, deepseek, anthropic 等) * @param {string} [path=''] - 路径后缀 * @returns {string} */ getLLMProxyUrl(provider, path = '') { const base = this.config.ENDPOINTS.LLM_PROXY; return `${this.getProxyUrl()}${base}/${provider}${path}`; } /** * OSS 上传服务 URL * @returns {string} */ getOssUploadUrl() { return `${this.getProxyUrl()}${this.config.ENDPOINTS.OSS_UPLOAD}`; } /** * 健康检查 URL * @returns {string} */ getHealthCheckUrl() { return `${this.getProxyUrl()}${this.config.ENDPOINTS.HEALTH}`; } /** * 后端 API URL(用于后端模式) * @param {string} [path=''] - 路径后缀 * @returns {string} */ getBackendApiUrl(path = '') { return `${this.config.PROD_PROXY_URL}${path}`; } // ==================== 配置更新方法 ==================== /** * 更新代理服务器 URL * @param {string} url - 新的代理 URL */ setProxyUrl(url) { this.config.LOCAL_PROXY_URL = url.replace(/\/+$/, ''); // 移除末尾斜杠 try { localStorage.setItem('proxyUrl', this.config.LOCAL_PROXY_URL); console.log(`[ProxyConfig] 代理地址已更新为: ${this.config.LOCAL_PROXY_URL}`); } catch (e) { console.warn('[ProxyConfig] 无法保存代理地址到 localStorage'); } } /** * 更新代理服务器端口 * @param {number} port - 新的端口 */ setProxyPort(port) { this.config.LOCAL_PROXY_PORT = port; this.config.LOCAL_PROXY_URL = `http://localhost:${port}`; try { localStorage.setItem('proxyPort', String(port)); localStorage.setItem('proxyUrl', this.config.LOCAL_PROXY_URL); console.log(`[ProxyConfig] 代理端口已更新为: ${port}`); } catch (e) { console.warn('[ProxyConfig] 无法保存代理端口到 localStorage'); } } /** * 重置为默认配置 */ resetToDefault() { this.config = { ...DEFAULT_CONFIG }; try { localStorage.removeItem('proxyUrl'); localStorage.removeItem('proxyPort'); console.log('[ProxyConfig] 已重置为默认配置'); } catch (e) { // 忽略 } } /** * 获取完整配置对象(只读) * @returns {Object} */ getConfig() { return Object.freeze({ ...this.config }); } /** * 获取默认配置(只读) * @returns {Object} */ getDefaultConfig() { return Object.freeze({ ...DEFAULT_CONFIG }); } } // ==================== 初始化并导出 ==================== const proxyConfig = new ProxyConfig(); // 导出到全局 if (typeof window !== 'undefined') { window.ProxyConfig = proxyConfig; // 同时暴露类定义,方便扩展 window.ProxyConfigClass = ProxyConfig; // 兼容旧代码:设置全局代理地址变量 // 这些变量会被其他模块引用 if (!window.PBX_PROXY_BASE_URL) { window.PBX_PROXY_BASE_URL = proxyConfig.getProxyUrl(); } if (!window.PBX_PROXY_MODE) { window.PBX_PROXY_MODE = 'auto'; } } // 兼容 CommonJS if (typeof module !== 'undefined' && module.exports) { module.exports = { ProxyConfig, proxyConfig, DEFAULT_CONFIG }; } // 初始化日志 console.log(`[ProxyConfig] 已初始化`); console.log(`[ProxyConfig] 本地开发环境: ${proxyConfig.isLocalDevelopment()}`); console.log(`[ProxyConfig] 代理地址: ${proxyConfig.getProxyUrl()}`); })();