From c7b229dc93e02017fd93e9f39fcdb861c55e6d1b Mon Sep 17 00:00:00 2001 From: donald Date: Thu, 4 Dec 2025 00:07:38 +0800 Subject: [PATCH] Add comprehensive CORS fix guide --- CORS_FIX_GUIDE.md | 366 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 366 insertions(+) create mode 100644 CORS_FIX_GUIDE.md diff --git a/CORS_FIX_GUIDE.md b/CORS_FIX_GUIDE.md new file mode 100644 index 0000000..dcc0846 --- /dev/null +++ b/CORS_FIX_GUIDE.md @@ -0,0 +1,366 @@ +# 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