9.0 KiB
9.0 KiB
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 金鑰安全問題
// ❌ 危險!前端直接呼叫會暴露 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: 安裝依賴
npm install
這會安裝:
express- Web 伺服器框架cors- CORS 中介層axios- HTTP 客戶端dotenv- 環境變數管理- 其他依賴...
步驟 2: 設定環境變數
編輯 .env 檔案,加入您的 Claude API 金鑰:
# 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: 啟動後端伺服器
# 開發模式(自動重啟)
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 測試:
# 測試 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
}
}'
📝 前端程式碼範例
❌ 錯誤的做法(直接呼叫)
// 不要這樣做!會被 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' }]
})
});
}
✅ 正確的做法(透過後端代理)
// 透過後端 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 端點說明
測試連線
// 測試 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"
}
生成內容
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(智能填寫)
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 設定
// 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: 確認以下事項:
- 後端伺服器是否正在運行?(
npm run dev) - 前端是否呼叫正確的 API 地址?(
http://localhost:3000) - 瀏覽器是否快取了舊的錯誤?(清除快取或使用無痕模式)
Q2: API 回傳 "Claude API key not configured"
A: 檢查:
.env檔案中是否設定了CLAUDE_API_KEY- 環境變數的值是否正確
- 重新啟動伺服器以載入新的環境變數
Q3: 連線超時
A: 可能原因:
- 網路連線問題
- API 金鑰無效
- Anthropic 服務暫時無法使用
- 超時設定太短(預設 30 秒)
Q4: 如何在生產環境部署?
A: 部署步驟:
- 設定環境變數(不要使用
.env檔案) - 限制 CORS 來源為您的網域
- 使用 HTTPS
- 設定適當的速率限制
- 記錄和監控 API 使用情況
📚 相關檔案
- server.js - Express 伺服器
- routes/llm.routes.js - API 路由
- services/llm.service.js - LLM 服務
- config/llm.config.js - LLM 配置
- utils/errorHandler.js - 錯誤處理
- public/api-proxy-example.html - 範例頁面
🎉 完成!
現在您可以安全地透過後端 API 呼叫 Claude 和其他 LLM 服務,不再有 CORS 錯誤!
如有任何問題,請參考:
- README.md - 專案說明
- database/README.md - 資料庫文件
- docs/GITEA_SETUP.md - Git 設定
最後更新: 2025-12-03 問題回報: https://gitea.theaken.com/donald/hr-performance-system/issues