feat(ai): 添加AI报告生成历史记录功能,支持每日生成限制和双API提供商
This commit is contained in:
@@ -1,140 +0,0 @@
|
||||
# 喝水记录功能
|
||||
|
||||
## 功能概述
|
||||
|
||||
新增了用户喝水记录功能,支持用户记录每日的喝水情况,设置喝水目标,并查看统计信息。
|
||||
|
||||
## 新增文件
|
||||
|
||||
### 模型文件
|
||||
- `src/users/models/user-water-history.model.ts` - 喝水记录模型
|
||||
- 更新了 `src/users/models/user-profile.model.ts` - 添加了 `dailyWaterGoal` 字段
|
||||
|
||||
### DTO文件
|
||||
- `src/users/dto/water-record.dto.ts` - 喝水记录相关的DTO
|
||||
|
||||
### 服务文件
|
||||
- `src/users/services/water-record.service.ts` - 喝水记录服务
|
||||
|
||||
### 数据库脚本
|
||||
- `sql-scripts/user-water-records-table.sql` - 数据库迁移脚本
|
||||
|
||||
### 测试脚本
|
||||
- `test-water-records.sh` - API接口测试脚本
|
||||
|
||||
## API接口
|
||||
|
||||
### 1. 创建喝水记录
|
||||
```
|
||||
POST /users/water-records
|
||||
```
|
||||
|
||||
请求体:
|
||||
```json
|
||||
{
|
||||
"amount": 250,
|
||||
"source": "manual",
|
||||
"remark": "早晨第一杯水"
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 获取喝水记录列表
|
||||
```
|
||||
GET /users/water-records?limit=10&offset=0
|
||||
```
|
||||
|
||||
### 3. 更新喝水记录
|
||||
```
|
||||
PUT /users/water-records/:id
|
||||
```
|
||||
|
||||
请求体:
|
||||
```json
|
||||
{
|
||||
"amount": 300,
|
||||
"remark": "修改后的备注"
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 删除喝水记录
|
||||
```
|
||||
DELETE /users/water-records/:id
|
||||
```
|
||||
|
||||
### 5. 更新喝水目标
|
||||
```
|
||||
PUT /users/water-goal
|
||||
```
|
||||
|
||||
请求体:
|
||||
```json
|
||||
{
|
||||
"dailyWaterGoal": 2000
|
||||
}
|
||||
```
|
||||
|
||||
### 6. 获取今日喝水统计
|
||||
```
|
||||
GET /users/water-stats/today
|
||||
```
|
||||
|
||||
响应:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"totalAmount": 1500,
|
||||
"recordCount": 6,
|
||||
"dailyGoal": 2000,
|
||||
"completionRate": 0.75
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 数据库表结构
|
||||
|
||||
### t_user_water_history (喝水记录表)
|
||||
- `id` - 主键,自增
|
||||
- `user_id` - 用户ID
|
||||
- `amount` - 喝水量(毫升)
|
||||
- `source` - 记录来源(manual/auto/other)
|
||||
- `remark` - 备注
|
||||
- `created_at` - 创建时间
|
||||
- `updated_at` - 更新时间
|
||||
|
||||
### t_user_profile (用户档案表 - 新增字段)
|
||||
- `daily_water_goal` - 每日喝水目标(毫升)
|
||||
|
||||
## 功能特点
|
||||
|
||||
1. **完整的CRUD操作** - 支持喝水记录的增删改查
|
||||
2. **目标设置** - 用户可以设置每日喝水目标
|
||||
3. **统计功能** - 提供今日喝水统计,包括总量、记录数、完成率等
|
||||
4. **数据验证** - 对输入数据进行严格验证
|
||||
5. **错误处理** - 完善的错误处理机制
|
||||
6. **日志记录** - 详细的操作日志
|
||||
7. **权限控制** - 所有接口都需要JWT认证
|
||||
|
||||
## 部署说明
|
||||
|
||||
1. 运行数据库迁移脚本:
|
||||
```bash
|
||||
mysql -u username -p database_name < sql-scripts/user-water-records-table.sql
|
||||
```
|
||||
|
||||
2. 重启应用服务
|
||||
|
||||
3. 使用测试脚本验证功能:
|
||||
```bash
|
||||
./test-water-records.sh
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 喝水目标字段是可选的,可以为空
|
||||
2. 喝水记录的来源默认为 'manual'
|
||||
3. 喝水量的范围限制在 1-5000 毫升之间
|
||||
4. 喝水目标的范围限制在 500-10000 毫升之间
|
||||
5. 获取profile接口会返回用户的喝水目标
|
||||
6. 喝水目标的更新集成在喝水接口中,避免用户服务文件过大
|
||||
@@ -1,230 +0,0 @@
|
||||
# 话题收藏功能 API 文档
|
||||
|
||||
## 概述
|
||||
|
||||
话题收藏功能允许用户收藏喜欢的话题,方便后续查看和使用。本文档描述了话题收藏相关的所有API接口。
|
||||
|
||||
## 功能特性
|
||||
|
||||
- ✅ 收藏话题
|
||||
- ✅ 取消收藏话题
|
||||
- ✅ 获取收藏话题列表
|
||||
- ✅ 话题列表中显示收藏状态
|
||||
- ✅ 防重复收藏
|
||||
- ✅ 完整的错误处理
|
||||
|
||||
## API 接口
|
||||
|
||||
### 1. 收藏话题
|
||||
|
||||
**接口地址:** `POST /api/topic/favorite`
|
||||
|
||||
**请求参数:**
|
||||
```json
|
||||
{
|
||||
"topicId": 123
|
||||
}
|
||||
```
|
||||
|
||||
**响应示例:**
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "收藏成功",
|
||||
"data": {
|
||||
"success": true,
|
||||
"isFavorited": true,
|
||||
"topicId": 123
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**错误情况:**
|
||||
- 话题不存在:`{ "code": 400, "message": "话题不存在" }`
|
||||
- 已收藏:`{ "code": 200, "message": "已经收藏过该话题" }`
|
||||
|
||||
### 2. 取消收藏话题
|
||||
|
||||
**接口地址:** `POST /api/topic/unfavorite`
|
||||
|
||||
**请求参数:**
|
||||
```json
|
||||
{
|
||||
"topicId": 123
|
||||
}
|
||||
```
|
||||
|
||||
**响应示例:**
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "取消收藏成功",
|
||||
"data": {
|
||||
"success": true,
|
||||
"isFavorited": false,
|
||||
"topicId": 123
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 获取收藏话题列表
|
||||
|
||||
**接口地址:** `POST /api/topic/favorites`
|
||||
|
||||
**请求参数:**
|
||||
```json
|
||||
{
|
||||
"page": 1,
|
||||
"pageSize": 10
|
||||
}
|
||||
```
|
||||
|
||||
**响应示例:**
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"list": [
|
||||
{
|
||||
"id": 123,
|
||||
"topic": "约会话题",
|
||||
"opening": {
|
||||
"text": "今天天气真不错...",
|
||||
"scenarios": ["咖啡厅", "公园"]
|
||||
},
|
||||
"scriptType": "初识破冰",
|
||||
"scriptTopic": "天气",
|
||||
"keywords": "天气,约会,轻松",
|
||||
"isFavorited": true,
|
||||
"createdAt": "2024-01-01T00:00:00.000Z",
|
||||
"updatedAt": "2024-01-01T00:00:00.000Z"
|
||||
}
|
||||
],
|
||||
"total": 5,
|
||||
"page": 1,
|
||||
"pageSize": 10
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 获取话题列表(已包含收藏状态)
|
||||
|
||||
**接口地址:** `POST /api/topic/list`
|
||||
|
||||
现在所有话题列表都会包含 `isFavorited` 字段,表示当前用户是否已收藏该话题。
|
||||
|
||||
**响应示例:**
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"list": [
|
||||
{
|
||||
"id": 123,
|
||||
"topic": "约会话题",
|
||||
"opening": {
|
||||
"text": "今天天气真不错...",
|
||||
"scenarios": ["咖啡厅", "公园"]
|
||||
},
|
||||
"scriptType": "初识破冰",
|
||||
"scriptTopic": "天气",
|
||||
"keywords": "天气,约会,轻松",
|
||||
"isFavorited": true, // ← 新增的收藏状态字段
|
||||
"createdAt": "2024-01-01T00:00:00.000Z",
|
||||
"updatedAt": "2024-01-01T00:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"id": 124,
|
||||
"topic": "工作话题",
|
||||
"opening": "关于工作的开场白...",
|
||||
"scriptType": "深度交流",
|
||||
"scriptTopic": "职业",
|
||||
"keywords": "工作,职业,发展",
|
||||
"isFavorited": false, // ← 未收藏
|
||||
"createdAt": "2024-01-01T00:00:00.000Z",
|
||||
"updatedAt": "2024-01-01T00:00:00.000Z"
|
||||
}
|
||||
],
|
||||
"total": 20,
|
||||
"page": 1,
|
||||
"pageSize": 10
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 数据库变更
|
||||
|
||||
### 新增表:t_topic_favorites
|
||||
|
||||
```sql
|
||||
CREATE TABLE `t_topic_favorites` (
|
||||
`id` int NOT NULL AUTO_INCREMENT,
|
||||
`user_id` varchar(255) NOT NULL COMMENT '用户ID',
|
||||
`topic_id` int NOT NULL COMMENT '话题ID',
|
||||
`created_at` datetime DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `unique_user_topic_favorite` (`user_id`, `topic_id`),
|
||||
KEY `idx_user_id` (`user_id`),
|
||||
KEY `idx_topic_id` (`topic_id`),
|
||||
FOREIGN KEY (`user_id`) REFERENCES `t_users` (`id`) ON DELETE CASCADE,
|
||||
FOREIGN KEY (`topic_id`) REFERENCES `t_topic_library` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
```
|
||||
|
||||
## 使用场景
|
||||
|
||||
### 1. 用户收藏话题
|
||||
```javascript
|
||||
// 收藏话题
|
||||
const response = await fetch('/api/topic/favorite', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'Bearer your-jwt-token'
|
||||
},
|
||||
body: JSON.stringify({ topicId: 123 })
|
||||
});
|
||||
```
|
||||
|
||||
### 2. 取消收藏话题
|
||||
```javascript
|
||||
// 取消收藏
|
||||
const response = await fetch('/api/topic/unfavorite', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'Bearer your-jwt-token'
|
||||
},
|
||||
body: JSON.stringify({ topicId: 123 })
|
||||
});
|
||||
```
|
||||
|
||||
### 3. 查看收藏列表
|
||||
```javascript
|
||||
// 获取收藏的话题
|
||||
const response = await fetch('/api/topic/favorites', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'Bearer your-jwt-token'
|
||||
},
|
||||
body: JSON.stringify({ page: 1, pageSize: 10 })
|
||||
});
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **防重复收藏:** 数据库层面通过唯一索引保证同一用户不能重复收藏同一话题
|
||||
2. **级联删除:** 用户删除或话题删除时,相关收藏记录会自动删除
|
||||
3. **性能优化:** 获取话题列表时通过单次查询获取用户所有收藏状态,避免N+1查询问题
|
||||
4. **权限控制:** 所有接口都需要用户登录(JWT认证)
|
||||
|
||||
## 错误码说明
|
||||
|
||||
- `200`: 操作成功
|
||||
- `400`: 请求参数错误(如话题不存在)
|
||||
- `401`: 未授权(需要登录)
|
||||
- `500`: 服务器内部错误
|
||||
@@ -1,473 +0,0 @@
|
||||
# 喝水记录 API 客户端接入说明
|
||||
|
||||
## 概述
|
||||
|
||||
喝水记录 API 提供了完整的喝水记录管理功能,包括记录创建、查询、更新、删除,以及喝水目标设置和统计查询等功能。
|
||||
|
||||
## 基础信息
|
||||
|
||||
- **Base URL**: `https://your-api-domain.com/api`
|
||||
- **认证方式**: JWT Bearer Token
|
||||
- **Content-Type**: `application/json`
|
||||
|
||||
## 认证
|
||||
|
||||
所有 API 请求都需要在请求头中包含有效的 JWT Token:
|
||||
|
||||
```http
|
||||
Authorization: Bearer <your-jwt-token>
|
||||
```
|
||||
|
||||
## API 接口列表
|
||||
|
||||
### 1. 创建喝水记录
|
||||
|
||||
**接口地址**: `POST /water-records`
|
||||
|
||||
**描述**: 创建一条新的喝水记录
|
||||
|
||||
**请求参数**:
|
||||
```json
|
||||
{
|
||||
"amount": 250, // 必填,喝水量(毫升),范围:1-5000
|
||||
"recordedAt": "2023-12-01T10:00:00.000Z", // 可选,记录时间,默认为当前时间
|
||||
"source": "Manual", // 可选,记录来源:Manual(手动) | Auto(自动)
|
||||
"note": "早晨第一杯水" // 可选,备注,最大100字符
|
||||
}
|
||||
```
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "操作成功",
|
||||
"data": {
|
||||
"id": 1,
|
||||
"amount": 250,
|
||||
"recordedAt": "2023-12-01T10:00:00.000Z",
|
||||
"note": "早晨第一杯水",
|
||||
"createdAt": "2023-12-01T10:00:00.000Z",
|
||||
"updatedAt": "2023-12-01T10:00:00.000Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**客户端示例代码**:
|
||||
```javascript
|
||||
// JavaScript/TypeScript
|
||||
const createWaterRecord = async (recordData) => {
|
||||
const response = await fetch('/api/water-records', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${token}`
|
||||
},
|
||||
body: JSON.stringify(recordData)
|
||||
});
|
||||
return response.json();
|
||||
};
|
||||
|
||||
// 使用示例
|
||||
const result = await createWaterRecord({
|
||||
amount: 250,
|
||||
note: "早晨第一杯水"
|
||||
});
|
||||
```
|
||||
|
||||
### 2. 获取喝水记录列表
|
||||
|
||||
**接口地址**: `GET /water-records`
|
||||
|
||||
**描述**: 获取用户的喝水记录列表,支持分页和日期筛选
|
||||
|
||||
**查询参数**:
|
||||
- `startDate` (可选): 开始日期,格式:YYYY-MM-DD
|
||||
- `endDate` (可选): 结束日期,格式:YYYY-MM-DD
|
||||
- `page` (可选): 页码,默认1
|
||||
- `limit` (可选): 每页数量,默认20,最大100
|
||||
|
||||
**请求示例**:
|
||||
```
|
||||
GET /water-records?startDate=2023-12-01&endDate=2023-12-31&page=1&limit=20
|
||||
```
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "操作成功",
|
||||
"data": {
|
||||
"records": [
|
||||
{
|
||||
"id": 1,
|
||||
"amount": 250,
|
||||
"recordedAt": "2023-12-01T10:00:00.000Z",
|
||||
"note": "早晨第一杯水",
|
||||
"createdAt": "2023-12-01T10:00:00.000Z",
|
||||
"updatedAt": "2023-12-01T10:00:00.000Z"
|
||||
}
|
||||
],
|
||||
"pagination": {
|
||||
"page": 1,
|
||||
"limit": 20,
|
||||
"total": 100,
|
||||
"totalPages": 5
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**客户端示例代码**:
|
||||
```javascript
|
||||
const getWaterRecords = async (params = {}) => {
|
||||
const queryString = new URLSearchParams(params).toString();
|
||||
const response = await fetch(`/api/water-records?${queryString}`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`
|
||||
}
|
||||
});
|
||||
return response.json();
|
||||
};
|
||||
|
||||
// 使用示例
|
||||
const records = await getWaterRecords({
|
||||
startDate: '2023-12-01',
|
||||
endDate: '2023-12-31',
|
||||
page: 1,
|
||||
limit: 20
|
||||
});
|
||||
```
|
||||
|
||||
### 3. 更新喝水记录
|
||||
|
||||
**接口地址**: `PUT /water-records/:id`
|
||||
|
||||
**描述**: 更新指定的喝水记录
|
||||
|
||||
**路径参数**:
|
||||
- `id`: 记录ID
|
||||
|
||||
**请求参数** (所有字段都是可选的):
|
||||
```json
|
||||
{
|
||||
"amount": 300, // 可选,喝水量(毫升)
|
||||
"recordedAt": "2023-12-01T11:00:00.000Z", // 可选,记录时间
|
||||
"source": "Manual", // 可选,记录来源
|
||||
"note": "修改后的备注" // 可选,备注
|
||||
}
|
||||
```
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "操作成功",
|
||||
"data": {
|
||||
"id": 1,
|
||||
"amount": 300,
|
||||
"recordedAt": "2023-12-01T11:00:00.000Z",
|
||||
"note": "修改后的备注",
|
||||
"createdAt": "2023-12-01T10:00:00.000Z",
|
||||
"updatedAt": "2023-12-01T11:30:00.000Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**客户端示例代码**:
|
||||
```javascript
|
||||
const updateWaterRecord = async (recordId, updateData) => {
|
||||
const response = await fetch(`/api/water-records/${recordId}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${token}`
|
||||
},
|
||||
body: JSON.stringify(updateData)
|
||||
});
|
||||
return response.json();
|
||||
};
|
||||
```
|
||||
|
||||
### 4. 删除喝水记录
|
||||
|
||||
**接口地址**: `DELETE /water-records/:id`
|
||||
|
||||
**描述**: 删除指定的喝水记录
|
||||
|
||||
**路径参数**:
|
||||
- `id`: 记录ID
|
||||
|
||||
**响应**: HTTP 204 No Content (成功删除)
|
||||
|
||||
**客户端示例代码**:
|
||||
```javascript
|
||||
const deleteWaterRecord = async (recordId) => {
|
||||
const response = await fetch(`/api/water-records/${recordId}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`
|
||||
}
|
||||
});
|
||||
return response.status === 204;
|
||||
};
|
||||
```
|
||||
|
||||
### 5. 更新每日喝水目标
|
||||
|
||||
**接口地址**: `PUT /water-records/goal/daily`
|
||||
|
||||
**描述**: 设置或更新用户的每日喝水目标
|
||||
|
||||
**请求参数**:
|
||||
```json
|
||||
{
|
||||
"dailyWaterGoal": 2000 // 必填,每日喝水目标(毫升),范围:500-10000
|
||||
}
|
||||
```
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "操作成功",
|
||||
"data": {
|
||||
"dailyWaterGoal": 2000,
|
||||
"updatedAt": "2023-12-01T12:00:00.000Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**客户端示例代码**:
|
||||
```javascript
|
||||
const updateWaterGoal = async (goalAmount) => {
|
||||
const response = await fetch('/api/water-records/goal/daily', {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${token}`
|
||||
},
|
||||
body: JSON.stringify({ dailyWaterGoal: goalAmount })
|
||||
});
|
||||
return response.json();
|
||||
};
|
||||
```
|
||||
|
||||
### 6. 获取指定日期的喝水统计
|
||||
|
||||
**接口地址**: `GET /water-records/stats`
|
||||
|
||||
**描述**: 获取指定日期的喝水统计信息,包括总量、完成率等
|
||||
|
||||
**查询参数**:
|
||||
- `date` (可选): 查询日期,格式:YYYY-MM-DD,不传则默认为今天
|
||||
|
||||
**请求示例**:
|
||||
```
|
||||
GET /water-records/stats?date=2023-12-01
|
||||
```
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "操作成功",
|
||||
"data": {
|
||||
"date": "2023-12-01",
|
||||
"totalAmount": 1500, // 当日总喝水量(毫升)
|
||||
"dailyGoal": 2000, // 每日目标(毫升)
|
||||
"completionRate": 75.0, // 完成率(百分比)
|
||||
"recordCount": 6, // 记录次数
|
||||
"records": [ // 当日所有记录
|
||||
{
|
||||
"id": 1,
|
||||
"amount": 250,
|
||||
"recordedAt": "2023-12-01T08:00:00.000Z",
|
||||
"note": "早晨第一杯水"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"amount": 300,
|
||||
"recordedAt": "2023-12-01T10:30:00.000Z",
|
||||
"note": null
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**客户端示例代码**:
|
||||
```javascript
|
||||
const getWaterStats = async (date) => {
|
||||
const params = date ? `?date=${date}` : '';
|
||||
const response = await fetch(`/api/water-records/stats${params}`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`
|
||||
}
|
||||
});
|
||||
return response.json();
|
||||
};
|
||||
|
||||
// 使用示例
|
||||
const todayStats = await getWaterStats(); // 获取今天的统计
|
||||
const specificDateStats = await getWaterStats('2023-12-01'); // 获取指定日期的统计
|
||||
```
|
||||
|
||||
## 错误处理
|
||||
|
||||
### 常见错误码
|
||||
|
||||
- `400 Bad Request`: 请求参数错误
|
||||
- `401 Unauthorized`: 未授权,Token无效或过期
|
||||
- `404 Not Found`: 资源不存在
|
||||
- `422 Unprocessable Entity`: 数据验证失败
|
||||
- `500 Internal Server Error`: 服务器内部错误
|
||||
|
||||
### 错误响应格式
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"message": "错误描述",
|
||||
"error": {
|
||||
"code": "ERROR_CODE",
|
||||
"details": "详细错误信息"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 客户端错误处理示例
|
||||
|
||||
```javascript
|
||||
const handleApiCall = async (apiFunction) => {
|
||||
try {
|
||||
const result = await apiFunction();
|
||||
if (result.success) {
|
||||
return result.data;
|
||||
} else {
|
||||
throw new Error(result.message);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('API调用失败:', error.message);
|
||||
// 根据错误类型进行相应处理
|
||||
if (error.status === 401) {
|
||||
// Token过期,重新登录
|
||||
redirectToLogin();
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## 数据类型说明
|
||||
|
||||
### WaterRecordSource 枚举
|
||||
|
||||
```typescript
|
||||
enum WaterRecordSource {
|
||||
Manual = 'Manual', // 手动记录
|
||||
Auto = 'Auto' // 自动记录
|
||||
}
|
||||
```
|
||||
|
||||
### 日期格式
|
||||
|
||||
- 所有日期时间字段使用 ISO 8601 格式:`YYYY-MM-DDTHH:mm:ss.sssZ`
|
||||
- 查询参数中的日期使用简化格式:`YYYY-MM-DD`
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. **错误处理**: 始终检查响应的 `success` 字段,并妥善处理错误情况
|
||||
2. **Token管理**: 实现Token自动刷新机制,避免因Token过期导致的请求失败
|
||||
3. **数据验证**: 在发送请求前进行客户端数据验证,提升用户体验
|
||||
4. **缓存策略**: 对于统计数据等相对稳定的信息,可以实现适当的缓存策略
|
||||
5. **分页处理**: 处理列表数据时,注意分页信息,避免一次性加载过多数据
|
||||
|
||||
## 完整的客户端封装示例
|
||||
|
||||
```javascript
|
||||
class WaterRecordsAPI {
|
||||
constructor(baseURL, token) {
|
||||
this.baseURL = baseURL;
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
async request(endpoint, options = {}) {
|
||||
const url = `${this.baseURL}${endpoint}`;
|
||||
const config = {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${this.token}`,
|
||||
...options.headers
|
||||
},
|
||||
...options
|
||||
};
|
||||
|
||||
const response = await fetch(url, config);
|
||||
const data = await response.json();
|
||||
|
||||
if (!data.success) {
|
||||
throw new Error(data.message);
|
||||
}
|
||||
|
||||
return data.data;
|
||||
}
|
||||
|
||||
// 创建喝水记录
|
||||
async createRecord(recordData) {
|
||||
return this.request('/water-records', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(recordData)
|
||||
});
|
||||
}
|
||||
|
||||
// 获取记录列表
|
||||
async getRecords(params = {}) {
|
||||
const queryString = new URLSearchParams(params).toString();
|
||||
return this.request(`/water-records?${queryString}`);
|
||||
}
|
||||
|
||||
// 更新记录
|
||||
async updateRecord(recordId, updateData) {
|
||||
return this.request(`/water-records/${recordId}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(updateData)
|
||||
});
|
||||
}
|
||||
|
||||
// 删除记录
|
||||
async deleteRecord(recordId) {
|
||||
await this.request(`/water-records/${recordId}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
// 更新喝水目标
|
||||
async updateGoal(goalAmount) {
|
||||
return this.request('/water-records/goal/daily', {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({ dailyWaterGoal: goalAmount })
|
||||
});
|
||||
}
|
||||
|
||||
// 获取统计数据
|
||||
async getStats(date) {
|
||||
const params = date ? `?date=${date}` : '';
|
||||
return this.request(`/water-records/stats${params}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 使用示例
|
||||
const api = new WaterRecordsAPI('https://your-api-domain.com/api', 'your-jwt-token');
|
||||
|
||||
// 创建记录
|
||||
const newRecord = await api.createRecord({
|
||||
amount: 250,
|
||||
note: '早晨第一杯水'
|
||||
});
|
||||
|
||||
// 获取今日统计
|
||||
const todayStats = await api.getStats();
|
||||
```
|
||||
|
||||
这个API封装提供了完整的喝水记录管理功能,可以直接在客户端项目中使用。
|
||||
@@ -1,233 +0,0 @@
|
||||
# Winston Logger 配置指南
|
||||
|
||||
本项目已配置了基于 Winston 的日志系统,支持日志文件输出、按日期滚动和自动清理。
|
||||
|
||||
## 功能特性
|
||||
|
||||
- ✅ **日志文件输出**: 自动将日志写入文件
|
||||
- ✅ **按日期滚动**: 每天生成新的日志文件
|
||||
- ✅ **自动清理**: 保留最近7天的日志文件
|
||||
- ✅ **分级日志**: 支持不同级别的日志分离
|
||||
- ✅ **结构化日志**: 支持JSON格式的结构化日志
|
||||
- ✅ **异常处理**: 自动记录未捕获的异常和Promise拒绝
|
||||
|
||||
## 日志文件结构
|
||||
|
||||
```
|
||||
logs/
|
||||
├── app-2025-07-21.log # 应用日志 (info级别及以上)
|
||||
├── error-2025-07-21.log # 错误日志 (error级别)
|
||||
├── debug-2025-07-21.log # 调试日志 (仅开发环境)
|
||||
├── exceptions-2025-07-21.log # 未捕获异常
|
||||
├── rejections-2025-07-21.log # 未处理的Promise拒绝
|
||||
└── .audit-*.json # 日志轮转审计文件
|
||||
```
|
||||
|
||||
## 配置说明
|
||||
|
||||
### 日志级别
|
||||
- **生产环境**: `info` 及以上级别
|
||||
- **开发环境**: `debug` 及以上级别
|
||||
|
||||
### 文件轮转配置
|
||||
- **日期模式**: `YYYY-MM-DD`
|
||||
- **保留天数**: 7天
|
||||
- **单文件大小**: 最大20MB
|
||||
- **自动压缩**: 支持
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 1. 在服务中使用 NestJS Logger
|
||||
|
||||
```typescript
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class YourService {
|
||||
private readonly logger = new Logger(YourService.name);
|
||||
|
||||
someMethod() {
|
||||
this.logger.log('这是一条信息日志');
|
||||
this.logger.warn('这是一条警告日志');
|
||||
this.logger.error('这是一条错误日志');
|
||||
this.logger.debug('这是一条调试日志');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 直接使用 Winston Logger (推荐用于结构化日志)
|
||||
|
||||
```typescript
|
||||
import { Injectable, Inject } from '@nestjs/common';
|
||||
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
|
||||
import { Logger as WinstonLogger } from 'winston';
|
||||
|
||||
@Injectable()
|
||||
export class YourService {
|
||||
constructor(
|
||||
@Inject(WINSTON_MODULE_PROVIDER)
|
||||
private readonly winstonLogger: WinstonLogger,
|
||||
) {}
|
||||
|
||||
someMethod() {
|
||||
// 结构化日志
|
||||
this.winstonLogger.info('用户登录', {
|
||||
context: 'AuthService',
|
||||
userId: 'user123',
|
||||
email: 'user@example.com',
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
|
||||
// 错误日志
|
||||
this.winstonLogger.error('数据库连接失败', {
|
||||
context: 'DatabaseService',
|
||||
error: 'Connection timeout',
|
||||
retryCount: 3
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 在控制器中使用
|
||||
|
||||
```typescript
|
||||
import { Controller, Logger } from '@nestjs/common';
|
||||
|
||||
@Controller('users')
|
||||
export class UsersController {
|
||||
private readonly logger = new Logger(UsersController.name);
|
||||
|
||||
@Get()
|
||||
findAll() {
|
||||
this.logger.log('获取用户列表请求');
|
||||
// 业务逻辑
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 日志格式
|
||||
|
||||
### 控制台输出格式
|
||||
```
|
||||
2025-07-21 10:08:38 info [ServiceName] 日志消息
|
||||
```
|
||||
|
||||
### 文件输出格式
|
||||
```
|
||||
2025-07-21 10:08:38 [INFO] [ServiceName] 日志消息
|
||||
```
|
||||
|
||||
### 结构化日志格式
|
||||
```json
|
||||
{
|
||||
"timestamp": "2025-07-21 10:08:38",
|
||||
"level": "info",
|
||||
"message": "用户登录",
|
||||
"context": "AuthService",
|
||||
"userId": "user123",
|
||||
"email": "user@example.com"
|
||||
}
|
||||
```
|
||||
|
||||
## 环境变量配置
|
||||
|
||||
可以通过环境变量调整日志行为:
|
||||
|
||||
```bash
|
||||
# 日志级别 (development: debug, production: info)
|
||||
NODE_ENV=production
|
||||
|
||||
# 自定义日志目录 (可选)
|
||||
LOG_DIR=/var/log/pilates-server
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 1. 使用合适的日志级别
|
||||
- `error`: 错误和异常
|
||||
- `warn`: 警告信息
|
||||
- `info`: 重要的业务信息
|
||||
- `debug`: 调试信息 (仅开发环境)
|
||||
|
||||
### 2. 结构化日志
|
||||
对于重要的业务事件,使用结构化日志:
|
||||
|
||||
```typescript
|
||||
this.winstonLogger.info('订单创建', {
|
||||
context: 'OrderService',
|
||||
orderId: order.id,
|
||||
userId: user.id,
|
||||
amount: order.amount,
|
||||
currency: 'CNY'
|
||||
});
|
||||
```
|
||||
|
||||
### 3. 错误日志包含上下文
|
||||
```typescript
|
||||
try {
|
||||
// 业务逻辑
|
||||
} catch (error) {
|
||||
this.winstonLogger.error('处理订单失败', {
|
||||
context: 'OrderService',
|
||||
orderId: order.id,
|
||||
error: error.message,
|
||||
stack: error.stack
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 避免敏感信息
|
||||
不要在日志中记录密码、令牌等敏感信息:
|
||||
|
||||
```typescript
|
||||
// ❌ 错误
|
||||
this.logger.log(`用户登录: ${JSON.stringify(loginData)}`);
|
||||
|
||||
// ✅ 正确
|
||||
this.logger.log(`用户登录: ${loginData.email}`);
|
||||
```
|
||||
|
||||
## 监控和维护
|
||||
|
||||
### 查看实时日志
|
||||
```bash
|
||||
# 查看应用日志
|
||||
tail -f logs/app-$(date +%Y-%m-%d).log
|
||||
|
||||
# 查看错误日志
|
||||
tail -f logs/error-$(date +%Y-%m-%d).log
|
||||
```
|
||||
|
||||
### 日志分析
|
||||
```bash
|
||||
# 统计错误数量
|
||||
grep -c "ERROR" logs/app-*.log
|
||||
|
||||
# 查找特定用户的日志
|
||||
grep "userId.*user123" logs/app-*.log
|
||||
```
|
||||
|
||||
### 清理旧日志
|
||||
日志系统会自动清理7天前的日志文件,无需手动维护。
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 1. 日志文件未生成
|
||||
- 检查 `logs` 目录权限
|
||||
- 确认应用有写入权限
|
||||
- 查看控制台是否有错误信息
|
||||
|
||||
### 2. 日志级别不正确
|
||||
- 检查 `NODE_ENV` 环境变量
|
||||
- 确认 winston 配置中的日志级别设置
|
||||
|
||||
### 3. 日志文件过大
|
||||
- 检查日志轮转配置
|
||||
- 确认 `maxSize` 和 `maxFiles` 设置
|
||||
|
||||
## 相关文件
|
||||
|
||||
- [`src/common/logger/winston.config.ts`](../src/common/logger/winston.config.ts) - Winston 配置
|
||||
- [`src/common/logger/logger.module.ts`](../src/common/logger/logger.module.ts) - Logger 模块
|
||||
- [`src/main.ts`](../src/main.ts) - 应用启动配置
|
||||
- [`src/app.module.ts`](../src/app.module.ts) - 应用模块配置
|
||||
@@ -1,245 +0,0 @@
|
||||
# 训练会话 API 使用指南
|
||||
|
||||
## 架构说明
|
||||
|
||||
新的训练系统采用了分离的架构设计,符合健身应用的最佳实践:
|
||||
|
||||
### 1. 训练计划模板 (Training Plans)
|
||||
- **用途**: 用户创建和管理的训练计划模板
|
||||
- **表**: `t_training_plans` + `t_schedule_exercises`
|
||||
- **特点**: 静态配置,不包含完成状态
|
||||
- **API**: `/training-plans`
|
||||
|
||||
### 2. 训练会话实例 (Workout Sessions)
|
||||
- **用途**: 每日实际训练,从训练计划模板复制而来
|
||||
- **表**: `t_workout_sessions` + `t_workout_exercises`
|
||||
- **特点**: 动态数据,包含完成状态、进度追踪
|
||||
- **API**: `/workouts`
|
||||
|
||||
## API 使用流程
|
||||
|
||||
### 第一步:创建训练计划模板
|
||||
|
||||
```bash
|
||||
# 1. 创建训练计划
|
||||
POST /training-plans
|
||||
{
|
||||
"name": "全身力量训练",
|
||||
"startDate": "2024-01-15T00:00:00.000Z",
|
||||
"mode": "daysOfWeek",
|
||||
"daysOfWeek": [1, 3, 5],
|
||||
"goal": "core_strength"
|
||||
}
|
||||
|
||||
# 2. 添加训练动作到计划
|
||||
POST /training-plans/{planId}/exercises
|
||||
{
|
||||
"exerciseKey": "squat",
|
||||
"name": "深蹲训练",
|
||||
"sets": 3,
|
||||
"reps": 15,
|
||||
"itemType": "exercise"
|
||||
}
|
||||
|
||||
# 3. 激活训练计划
|
||||
POST /training-plans/{planId}/activate
|
||||
```
|
||||
|
||||
### 第二步:开始训练(三种方式)
|
||||
|
||||
#### 方式一:自动获取今日训练(推荐)
|
||||
```bash
|
||||
# 获取今日训练会话(如不存在则自动创建)
|
||||
GET /workouts/today
|
||||
# 系统会自动基于激活的训练计划创建今日训练会话
|
||||
```
|
||||
|
||||
#### 方式二:基于训练计划手动创建
|
||||
```bash
|
||||
POST /workouts/sessions
|
||||
{
|
||||
"trainingPlanId": "{planId}",
|
||||
"name": "晚间训练",
|
||||
"scheduledDate": "2024-01-15T19:00:00.000Z"
|
||||
}
|
||||
```
|
||||
|
||||
#### 方式三:创建完全自定义训练
|
||||
```bash
|
||||
POST /workouts/sessions
|
||||
{
|
||||
"name": "自定义核心训练",
|
||||
"scheduledDate": "2024-01-15T15:00:00.000Z",
|
||||
"customExercises": [
|
||||
{
|
||||
"exerciseKey": "plank",
|
||||
"name": "平板支撑",
|
||||
"plannedDurationSec": 60,
|
||||
"itemType": "exercise",
|
||||
"sortOrder": 1
|
||||
},
|
||||
{
|
||||
"name": "休息",
|
||||
"plannedDurationSec": 30,
|
||||
"itemType": "rest",
|
||||
"sortOrder": 2
|
||||
},
|
||||
{
|
||||
"name": "自定义深蹲变式",
|
||||
"plannedSets": 3,
|
||||
"plannedReps": 20,
|
||||
"note": "脚距离肩膀更宽",
|
||||
"itemType": "exercise",
|
||||
"sortOrder": 3
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 第三步:执行训练
|
||||
|
||||
```bash
|
||||
# 1. 开始训练会话(可选)
|
||||
POST /workouts/sessions/{sessionId}/start
|
||||
|
||||
# 2. 动态添加动作(如果需要)
|
||||
POST /workouts/sessions/{sessionId}/exercises
|
||||
{
|
||||
"name": "额外的拉伸",
|
||||
"plannedDurationSec": 120,
|
||||
"itemType": "exercise"
|
||||
}
|
||||
|
||||
# 3. 开始特定动作
|
||||
POST /workouts/sessions/{sessionId}/exercises/{exerciseId}/start
|
||||
|
||||
# 4. 完成动作
|
||||
POST /workouts/sessions/{sessionId}/exercises/{exerciseId}/complete
|
||||
{
|
||||
"completedSets": 3,
|
||||
"completedReps": 15,
|
||||
"actualDurationSec": 180,
|
||||
"performanceData": {
|
||||
"sets": [
|
||||
{ "reps": 15, "difficulty": 7 },
|
||||
{ "reps": 12, "difficulty": 8 },
|
||||
{ "reps": 10, "difficulty": 9 }
|
||||
],
|
||||
"perceivedExertion": 8
|
||||
}
|
||||
}
|
||||
|
||||
# 注意:当所有动作完成后,训练会话会自动标记为完成
|
||||
```
|
||||
|
||||
## 主要优势
|
||||
|
||||
### 1. 数据分离
|
||||
- 训练计划是可重用的模板
|
||||
- 每日训练是独立的实例
|
||||
- 修改计划不影响历史训练记录
|
||||
|
||||
### 2. 灵活的创建方式
|
||||
- 自动创建:基于激活计划的今日训练
|
||||
- 计划创建:基于指定训练计划创建
|
||||
- 自定义创建:完全自定义的训练动作
|
||||
|
||||
### 3. 进度追踪
|
||||
- 每个训练会话都有完整的状态跟踪
|
||||
- 支持详细的性能数据记录
|
||||
- 可以分析训练趋势和进步情况
|
||||
|
||||
### 4. 动态调整能力
|
||||
- 支持训练中动态添加动作
|
||||
- 支持跳过或修改特定动作
|
||||
- 自动完成会话管理
|
||||
- 自动计算训练统计数据
|
||||
|
||||
## API 端点总览
|
||||
|
||||
### 训练会话管理
|
||||
- `GET /workouts/today` - 获取/自动创建今日训练会话 ⭐
|
||||
- `POST /workouts/sessions` - 手动创建训练会话(支持基于计划或自定义动作)
|
||||
- `GET /workouts/sessions` - 获取训练会话列表
|
||||
- `GET /workouts/sessions/{id}` - 获取训练会话详情
|
||||
- `POST /workouts/sessions/{id}/start` - 开始训练(可选)
|
||||
- `DELETE /workouts/sessions/{id}` - 删除训练会话
|
||||
- 注意:训练会话在所有动作完成后自动完成
|
||||
|
||||
### 训练动作管理
|
||||
- `POST /workouts/sessions/{id}/exercises` - 向训练会话添加自定义动作 ⭐
|
||||
- `GET /workouts/sessions/{id}/exercises` - 获取训练动作列表
|
||||
- `GET /workouts/sessions/{id}/exercises/{exerciseId}` - 获取动作详情
|
||||
- `POST /workouts/sessions/{id}/exercises/{exerciseId}/start` - 开始动作
|
||||
- `POST /workouts/sessions/{id}/exercises/{exerciseId}/complete` - 完成动作
|
||||
- `POST /workouts/sessions/{id}/exercises/{exerciseId}/skip` - 跳过动作
|
||||
- `PUT /workouts/sessions/{id}/exercises/{exerciseId}` - 更新动作信息
|
||||
|
||||
### 统计和快捷功能
|
||||
- `GET /workouts/sessions/{id}/stats` - 获取训练统计
|
||||
- `GET /workouts/recent` - 获取最近训练
|
||||
|
||||
## 数据模型
|
||||
|
||||
### WorkoutSession (训练会话)
|
||||
```typescript
|
||||
{
|
||||
id: string;
|
||||
userId: string;
|
||||
trainingPlanId: string; // 关联的训练计划模板
|
||||
name: string;
|
||||
scheduledDate: Date;
|
||||
startedAt?: Date;
|
||||
completedAt?: Date;
|
||||
status: 'planned' | 'in_progress' | 'completed' | 'skipped';
|
||||
totalDurationSec?: number;
|
||||
summary?: string;
|
||||
caloriesBurned?: number;
|
||||
stats?: {
|
||||
totalExercises: number;
|
||||
completedExercises: number;
|
||||
totalSets: number;
|
||||
completedSets: number;
|
||||
// ...
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### WorkoutExercise (训练动作实例)
|
||||
```typescript
|
||||
{
|
||||
id: string;
|
||||
workoutSessionId: string;
|
||||
exerciseKey?: string; // 关联动作库
|
||||
name: string;
|
||||
plannedSets?: number; // 计划数值
|
||||
completedSets?: number; // 实际完成数值
|
||||
plannedReps?: number;
|
||||
completedReps?: number;
|
||||
plannedDurationSec?: number;
|
||||
actualDurationSec?: number;
|
||||
status: 'pending' | 'in_progress' | 'completed' | 'skipped';
|
||||
startedAt?: Date;
|
||||
completedAt?: Date;
|
||||
performanceData?: { // 详细性能数据
|
||||
sets: Array<{
|
||||
reps?: number;
|
||||
weight?: number;
|
||||
difficulty?: number;
|
||||
notes?: string;
|
||||
}>;
|
||||
heartRate?: { avg: number; max: number };
|
||||
perceivedExertion?: number; // RPE 1-10
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## 迁移说明
|
||||
|
||||
如果您之前使用了 `training-plans` 的完成状态功能,现在需要:
|
||||
|
||||
1. 使用 `/workouts/sessions` 来创建每日训练
|
||||
2. 使用新的完成状态 API 来跟踪进度
|
||||
3. 原有的训练计划数据保持不变,作为模板使用
|
||||
|
||||
这样的架构分离使得系统更加清晰、可维护,也更符合健身应用的实际使用场景。
|
||||
Reference in New Issue
Block a user