367 lines
9.0 KiB
Markdown
367 lines
9.0 KiB
Markdown
# 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
|