# CORS 錯誤解決方案 ## 🔴 您遇到的錯誤 ### 錯誤訊息 1: CORS Policy ``` Access to fetch at 'https://api.anthropic.com/v1/messages' from origin 'http://127.0.0.1:5000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. ``` ### 錯誤訊息 2: Storage Access ``` Uncaught (in promise) Error: Access to storage is not allowed from this context. ``` ### 錯誤訊息 3: Failed to Fetch ``` POST https://api.anthropic.com/v1/messages net::ERR_FAILED TypeError: Failed to fetch ``` ## 🤔 為什麼會發生這些錯誤? ### 1. **CORS(跨來源資源共用)限制** - 瀏覽器的安全機制,防止網頁向不同來源的伺服器發送請求 - Anthropic API 不允許直接從瀏覽器呼叫(沒有設定 CORS 標頭) - 這是為了保護 API 金鑰不被暴露 ### 2. **API 金鑰安全問題** ```javascript // ❌ 危險!前端直接呼叫會暴露 API 金鑰 const apiKey = 'sk-ant-xxxxx'; // 所有人都能看到 fetch('https://api.anthropic.com/v1/messages', { headers: { 'x-api-key': apiKey } }); ``` ### 3. **Storage 限制** - 使用 `file://` 協定開啟的 HTML 檔案無法使用 localStorage - 必須透過 HTTP 伺服器運行 ## ✅ 正確的解決方案 ### 架構圖 ``` ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ │ │ │ │ 前端 │ ─────▶ │ 後端 │ ─────▶ │ Claude API │ │ (瀏覽器) │ HTTP │ (Express) │ HTTPS │ (Anthropic)│ │ │ │ │ │ │ └─────────────┘ └─────────────┘ └─────────────┘ ↑ │ API 金鑰 │ 安全儲存 ``` ### 步驟 1: 安裝依賴 ```bash npm install ``` 這會安裝: - `express` - Web 伺服器框架 - `cors` - CORS 中介層 - `axios` - HTTP 客戶端 - `dotenv` - 環境變數管理 - 其他依賴... ### 步驟 2: 設定環境變數 編輯 [.env](.env) 檔案,加入您的 Claude API 金鑰: ```env # Claude API CLAUDE_API_KEY=sk-ant-api03-xxxxxxxxxxxxx CLAUDE_API_URL=https://api.anthropic.com/v1 CLAUDE_MODEL=claude-3-5-sonnet-20241022 ``` ⚠️ **重要**:`.env` 檔案已被 `.gitignore` 排除,不會被提交到 Git ### 步驟 3: 啟動後端伺服器 ```bash # 開發模式(自動重啟) npm run dev # 或生產模式 npm start ``` 您應該會看到: ``` ================================================== 🚀 HR Performance System API Server ================================================== 📡 Server running on: http://localhost:3000 🌍 Environment: development 📅 Started at: 2025-12-03 ... ================================================== 📚 Available endpoints: GET / - API information GET /health - Health check POST /api/llm/test/* - Test LLM connections POST /api/llm/generate - Generate content with LLM ✨ Server is ready to accept connections! ``` ### 步驟 4: 測試 API 開啟瀏覽器訪問: ``` http://localhost:3000/api-proxy-example.html ``` 或使用 curl 測試: ```bash # 測試 Claude 連線 curl -X POST http://localhost:3000/api/llm/test/claude # 生成內容 curl -X POST http://localhost:3000/api/llm/generate \ -H "Content-Type: application/json" \ -d '{ "prompt": "介紹 HR 績效評核系統", "provider": "claude", "options": { "temperature": 0.7, "maxTokens": 200 } }' ``` ## 📝 前端程式碼範例 ### ❌ 錯誤的做法(直接呼叫) ```javascript // 不要這樣做!會被 CORS 阻擋 async function callClaudeAPI() { const response = await fetch('https://api.anthropic.com/v1/messages', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': 'sk-ant-xxxxx', // ❌ API 金鑰暴露! 'anthropic-version': '2023-06-01' }, body: JSON.stringify({ model: 'claude-3-5-sonnet-20241022', messages: [{ role: 'user', content: 'Hello' }] }) }); } ``` ### ✅ 正確的做法(透過後端代理) ```javascript // 透過後端 API 呼叫 async function generateContent(prompt) { try { const response = await fetch('http://localhost:3000/api/llm/generate', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ prompt: prompt, provider: 'claude', options: { temperature: 0.7, maxTokens: 2000 } }) }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const result = await response.json(); if (result.success) { console.log('生成的內容:', result.content); return result.content; } else { throw new Error(result.error?.message || '生成失敗'); } } catch (error) { console.error('錯誤:', error); // 顯示錯誤訊息給使用者 showErrorModal({ title: 'API 錯誤', message: error.message }); } } // 使用範例 generateContent('請介紹 HR 績效評核系統的四卡循環'); ``` ## 🎯 API 端點說明 ### 測試連線 ```javascript // 測試 Claude API POST http://localhost:3000/api/llm/test/claude // 測試所有 LLM POST http://localhost:3000/api/llm/test/all // 回應格式 { "success": true, "message": "Claude API connection successful", "provider": "claude", "model": "claude-3-5-sonnet-20241022" } ``` ### 生成內容 ```javascript POST http://localhost:3000/api/llm/generate Content-Type: application/json { "prompt": "你的提示內容", "provider": "claude", // 可選: claude, gemini, deepseek, openai "options": { "temperature": 0.7, // 可選: 0.0-1.0 "maxTokens": 2000 // 可選: 最大生成長度 } } // 回應格式 { "success": true, "content": "生成的內容...", "provider": "claude" } ``` ### Help Me AI(智能填寫) ```javascript POST http://localhost:3000/api/llm/help-me-fill Content-Type: application/json { "cardType": "performance", "cardId": "PF-2024-001", "filledFields": { "kra1_title": "產品上市時程", "kra1_weight": 40 }, "emptyFields": [ "kra1_output", "kra1_self_note" ], "context": { "roleCard": {...}, "competencyCard": {...} } } // 回應格式 { "success": true, "filledCount": 2, "suggestions": { "kra1_output": "Q1 完成 MVP 開發...", "kra1_self_note": "超額達成目標..." } } ``` ## 🔒 安全性考量 ### 1. API 金鑰保護 - ✅ 儲存在 `.env` 檔案 - ✅ 不提交到 Git(已在 `.gitignore` 中) - ✅ 只在後端使用 - ❌ 絕不在前端程式碼中暴露 ### 2. CORS 設定 ```javascript // server.js 中的 CORS 設定 app.use(cors({ origin: process.env.FRONTEND_URL || '*', // 生產環境應限制來源 methods: ['GET', 'POST', 'PUT', 'DELETE'], allowedHeaders: ['Content-Type', 'Authorization'], credentials: true })); ``` ### 3. 錯誤處理 - 統一的錯誤格式 - 不洩露敏感資訊 - 記錄錯誤日誌 ## 🐛 常見問題 ### Q1: 為什麼還是顯示 CORS 錯誤? **A:** 確認以下事項: 1. 後端伺服器是否正在運行?(`npm run dev`) 2. 前端是否呼叫正確的 API 地址?(`http://localhost:3000`) 3. 瀏覽器是否快取了舊的錯誤?(清除快取或使用無痕模式) ### Q2: API 回傳 "Claude API key not configured" **A:** 檢查: 1. `.env` 檔案中是否設定了 `CLAUDE_API_KEY` 2. 環境變數的值是否正確 3. 重新啟動伺服器以載入新的環境變數 ### Q3: 連線超時 **A:** 可能原因: 1. 網路連線問題 2. API 金鑰無效 3. Anthropic 服務暫時無法使用 4. 超時設定太短(預設 30 秒) ### Q4: 如何在生產環境部署? **A:** 部署步驟: 1. 設定環境變數(不要使用 `.env` 檔案) 2. 限制 CORS 來源為您的網域 3. 使用 HTTPS 4. 設定適當的速率限制 5. 記錄和監控 API 使用情況 ## 📚 相關檔案 - [server.js](server.js) - Express 伺服器 - [routes/llm.routes.js](routes/llm.routes.js) - API 路由 - [services/llm.service.js](services/llm.service.js) - LLM 服務 - [config/llm.config.js](config/llm.config.js) - LLM 配置 - [utils/errorHandler.js](utils/errorHandler.js) - 錯誤處理 - [public/api-proxy-example.html](public/api-proxy-example.html) - 範例頁面 ## 🎉 完成! 現在您可以安全地透過後端 API 呼叫 Claude 和其他 LLM 服務,不再有 CORS 錯誤! 如有任何問題,請參考: - [README.md](README.md) - 專案說明 - [database/README.md](database/README.md) - 資料庫文件 - [docs/GITEA_SETUP.md](docs/GITEA_SETUP.md) - Git 設定 --- **最後更新**: 2025-12-03 **問題回報**: https://gitea.theaken.com/donald/hr-performance-system/issues