Add comprehensive CORS fix guide
This commit is contained in:
366
CORS_FIX_GUIDE.md
Normal file
366
CORS_FIX_GUIDE.md
Normal file
@@ -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
|
||||
Reference in New Issue
Block a user