feat(water-records): 优化创建喝水记录和获取记录列表的日期处理逻辑
This commit is contained in:
473
docs/water-records-api.md
Normal file
473
docs/water-records-api.md
Normal file
@@ -0,0 +1,473 @@
|
|||||||
|
# 喝水记录 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封装提供了完整的喝水记录管理功能,可以直接在客户端项目中使用。
|
||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
WaterStatsResponseDto
|
WaterStatsResponseDto
|
||||||
} from './dto/water-record.dto';
|
} from './dto/water-record.dto';
|
||||||
import { Op } from 'sequelize';
|
import { Op } from 'sequelize';
|
||||||
|
import * as dayjs from 'dayjs';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class WaterRecordsService {
|
export class WaterRecordsService {
|
||||||
@@ -30,14 +31,15 @@ export class WaterRecordsService {
|
|||||||
*/
|
*/
|
||||||
async createWaterRecord(userId: string, createDto: CreateWaterRecordDto): Promise<WaterRecordResponseDto> {
|
async createWaterRecord(userId: string, createDto: CreateWaterRecordDto): Promise<WaterRecordResponseDto> {
|
||||||
try {
|
try {
|
||||||
|
this.logger.log(`createWaterRecord userId: ${userId}, createDto: ${JSON.stringify(createDto, null, 2)}`);
|
||||||
const waterRecord = await this.userWaterHistoryModel.create({
|
const waterRecord = await this.userWaterHistoryModel.create({
|
||||||
userId,
|
userId,
|
||||||
amount: createDto.amount,
|
amount: createDto.amount,
|
||||||
recordedAt: createDto.recordedAt || new Date(),
|
recordedAt: new Date(),
|
||||||
note: createDto.note,
|
note: createDto.note,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.logger.log(`用户 ${userId} 创建喝水记录成功,记录ID: ${waterRecord.id}`);
|
this.logger.log(`createWaterRecord 用户 ${userId} 创建喝水记录成功,记录ID: ${waterRecord.id}`);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
@@ -71,12 +73,10 @@ export class WaterRecordsService {
|
|||||||
if (startDate || endDate) {
|
if (startDate || endDate) {
|
||||||
whereCondition.recordedAt = {};
|
whereCondition.recordedAt = {};
|
||||||
if (startDate) {
|
if (startDate) {
|
||||||
whereCondition.recordedAt[Op.gte] = new Date(startDate);
|
whereCondition.recordedAt[Op.gte] = dayjs(startDate).startOf('day').toDate();
|
||||||
}
|
}
|
||||||
if (endDate) {
|
if (endDate) {
|
||||||
const endDateTime = new Date(endDate);
|
whereCondition.recordedAt[Op.lte] = dayjs(endDate).endOf('day').toDate();
|
||||||
endDateTime.setHours(23, 59, 59, 999);
|
|
||||||
whereCondition.recordedAt[Op.lte] = endDateTime;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user