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