Migrate project from typing_practiceweb

This commit is contained in:
2026-05-14 16:04:14 +08:00
parent dbc5425706
commit 0e87d5546d
225 changed files with 92811 additions and 0 deletions

323
API_CONFIG_GUIDE.md Normal file
View 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*