Migrate project from typing_practiceweb
This commit is contained in:
323
API_CONFIG_GUIDE.md
Normal file
323
API_CONFIG_GUIDE.md
Normal file
@@ -0,0 +1,323 @@
|
||||
# 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*
|
||||
Reference in New Issue
Block a user