Files
d1kt/API_CONFIG_GUIDE.md

324 lines
7.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# API 路径配置说明
## 📋 概述
本项目使用统一的 API 路径配置机制,通过环境变量和辅助函数确保开发环境和生产环境的 API 调用一致性。
---
## 🔧 配置方式
### 1. **环境变量配置**
#### **开发环境** (`.env`)
```env
REACT_APP_API_BASE_URL=http://localhost:5001
```
#### **生产环境** (`.env.production`)
```env
REACT_APP_API_BASE_URL=https://d1kt.cn/api
```
### 2. **代码中的使用**
所有 API 调用统一使用 `api` 对象(来自 `src/api/apiClient.ts`
```typescript
import { api } from './api/apiClient';
// ✅ 正确用法
const users = await api.get<User[]>('/admin/users');
// ❌ 不要直接使用 apiClient
// const response = await apiClient.get('/admin/users');
```
---
## 🌐 URL 构造流程
### **开发环境**
```
1. 代码调用api.get('/admin/users')
2. getFullApiPath('/admin/users')
→ '/api/admin/users'
3. axios.get(API_BASE_URL + '/api/admin/users')
→ axios.get('http://localhost:5001/api/admin/users')
4. 最终请求http://localhost:5001/api/admin/users
5. 后端接收:/api/admin/users ✅
```
### **生产环境**
```
1. 代码调用api.get('/admin/users')
2. getFullApiPath('/admin/users')
→ '/api/admin/users'
3. axios.get(API_BASE_URL + '/api/admin/users')
→ axios.get('https://d1kt.cn/api/api/admin/users')
4. 浏览器请求https://d1kt.cn/api/api/admin/users
5. Nginx 处理:
location /api/ {
proxy_pass http://localhost:5001/;
}
→ 去掉第一个 /api转发到后端
6. 后端接收http://localhost:5001/api/admin/users ✅
```
---
## 📊 关键点说明
### ✅ **为什么生产环境需要两个 `/api`**
1. **第一个 `/api`**Nginx 反向代理的路径匹配
```nginx
location /api/ {
proxy_pass http://localhost:5001/;
}
```
- 浏览器请求 `https://d1kt.cn/api/xxx`
- Nginx 匹配到 `/api/` 后,去掉这部分,转发到后端
- 后端收到 `http://localhost:5001/xxx`
2. **第二个 `/api`**:后端路由的前缀
- 后端所有路由都以 `/api` 开头
- 例如:`app.use('/api/admin', adminRoutes)`
- 所以需要请求 `/api/admin/users`
3. **组合结果**
```
浏览器 → https://d1kt.cn/api/api/admin/users
Nginx → 转发 http://localhost:5001/api/admin/users
后端 → 路由匹配 /api/admin/users ✅
```
---
## 🔍 统一的调用方式
### **所有页面都使用相同的逻辑**
| 功能模块 | API 调用示例 | 最终 URL开发 | 最终 URL生产 |
|---------|------------|---------------|---------------|
| 打字练习 | `api.get('/practice-records/statistics')` | `http://localhost:5001/api/practice-records/statistics` | `https://d1kt.cn/api/api/practice-records/statistics` |
| 用户管理 | `api.get('/admin/users')` | `http://localhost:5001/api/admin/users` | `https://d1kt.cn/api/api/admin/users` |
| 词汇学习 | `api.get('/vocabulary/word-sets')` | `http://localhost:5001/api/vocabulary/word-sets` | `https://d1kt.cn/api/api/vocabulary/word-sets` |
| 扫雷游戏 | `api.get('/minesweeper/leaderboard/beginner')` | `http://localhost:5001/api/minesweeper/leaderboard/beginner` | `https://d1kt.cn/api/api/minesweeper/leaderboard/beginner` |
---
## 🚀 部署步骤
### **生产环境部署**
1. **在服务器上创建 `.env.production` 文件**
```bash
cd /path/to/typing_practiceweb
cp .env.production.example .env.production
nano .env.production
```
2. **配置生产环境变量**
```env
REACT_APP_API_BASE_URL=https://d1kt.cn/api
```
3. **构建前端**
```bash
npm run build
```
这会自动读取 `.env.production` 文件
4. **确认 Nginx 配置**
```nginx
location /api/ {
proxy_pass http://localhost:5001/;
}
```
5. **重启服务**
```bash
sudo nginx -s reload
pm2 restart typing-backend
```
---
## ⚠️ 常见错误
### ❌ **错误配置示例**
```env
# ❌ 生产环境配置为空字符串
REACT_APP_API_BASE_URL=
# 结果:请求变成 /api/admin/users相对路径
# 问题Nginx 无法正确代理
```
```env
# ❌ 开发环境包含 /api
REACT_APP_API_BASE_URL=http://localhost:5001/api
# 结果http://localhost:5001/api/api/admin/users
# 问题:多了一层 /api后端无法匹配路由
```
### ✅ **正确配置**
```env
# ✅ 开发环境
REACT_APP_API_BASE_URL=http://localhost:5001
# ✅ 生产环境
REACT_APP_API_BASE_URL=https://d1kt.cn/api
```
---
## 🧪 验证方法
### **开发环境验证**
```bash
# 1. 启动服务
npm run start
# 2. 浏览器访问
http://localhost:3001/admin
# 3. 打开 Network 面板,查看请求
# 应该看到http://localhost:5001/api/admin/users ✅
```
### **生产环境验证**
```bash
# 1. 构建并部署
npm run build
pm2 restart typing-backend
# 2. 浏览器访问
https://d1kt.cn/admin
# 3. 打开 Network 面板,查看请求
# 应该看到https://d1kt.cn/api/api/admin/users ✅
```
---
## 📝 总结
- ✅ **统一使用 `api` 对象**进行所有 API 调用
- ✅ **通过环境变量**控制不同环境的基础 URL
- ✅ **`getFullApiPath` 函数**自动添加 `/api` 前缀
- ✅ **生产环境需要两个 `/api`**:第一个给 Nginx第二个给后端路由
- ✅ **开发环境和生产环境使用完全相同的代码逻辑**
---
## 🌐 WebSocket 连接配置
### **WebSocket 路径配置**
扫雷游戏的 WebSocket 连接需要正确处理路径前缀,以匹配 Nginx 代理配置:
#### **客户端配置**
- `REACT_APP_API_BASE_URL`: API 基础地址(如 `/api` 或 `https://d1kt.cn/api`
- `REACT_APP_CLIENT_URL`: 客户端域名(如 `https://d1kt.cn`
#### **服务器端配置**
- `REACT_APP_API_BASE_URL`: 与客户端相同的环境变量,用于确定 WebSocket 路径前缀
### **配置示例**
#### **开发环境** (`.env`)
```env
REACT_APP_API_BASE_URL=http://localhost:5001
REACT_APP_CLIENT_URL=http://localhost:3001
```
#### **生产环境** (`.env.production`)
```env
REACT_APP_API_BASE_URL=/api
REACT_APP_CLIENT_URL=https://d1kt.cn
```
或使用完整 URL
```env
REACT_APP_API_BASE_URL=https://d1kt.cn/api
REACT_APP_CLIENT_URL=https://d1kt.cn
```
### **WebSocket 连接流程**
```
开发环境:
1. 客户端连接: ws://localhost:5001/socket.io
2. 服务器监听: /socket.io
生产环境:
1. 客户端连接: wss://d1kt.cn/api/socket.io
2. Nginx 代理: location /api/ { proxy_pass http://localhost:5001/; }
3. 服务器监听: /socket.io
```
### **Nginx WebSocket 代理配置**
确保 Nginx 配置包含 WebSocket 代理头:
```nginx
location /api/ {
proxy_pass http://localhost:5001/;
proxy_http_version 1.1;
# WebSocket 代理头
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
# 超时设置
proxy_read_timeout 86400;
proxy_send_timeout 86400;
}
```
### **故障排查**
#### **WebSocket 连接失败**
1. **检查 Nginx 配置**:确认包含 `Upgrade` 和 `Connection` 头
2. **检查服务器日志**:查看 Socket.IO 服务器是否启动
3. **验证环境变量**:确保 `REACT_APP_API_BASE_URL` 正确设置
#### **路径不匹配**
- 客户端日志显示连接地址:`wss://d1kt.cn/api/socket.io`
- 服务器日志显示连接日志:`用户连接: [socket-id]`
#### **生产环境验证命令**
```bash
# 测试 HTTP 握手
curl "https://d1kt.cn/api/socket.io/?EIO=4&transport=polling"
# 预期响应:
# 0{"sid":"xxx","upgrades":["websocket"],"pingInterval":25000,"pingTimeout":20000}
```
### **已完成的修复**
1. ✅ 客户端 WebSocket 路径配置 (`getWebSocketPath()`)
2. ✅ 服务器端 Socket.IO 路径配置 (`getSocketIoPath()`)
3. ✅ Nginx WebSocket 代理配置示例
---
*最后更新2025-10-30*