# 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*
