From 02f21f08581b874d7247291e4c7aa06ec5484e1e Mon Sep 17 00:00:00 2001 From: richarjiang Date: Tue, 2 Sep 2025 15:27:21 +0800 Subject: [PATCH] =?UTF-8?q?feat(water-records):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E5=88=9B=E5=BB=BA=E5=96=9D=E6=B0=B4=E8=AE=B0=E5=BD=95=E5=92=8C?= =?UTF-8?q?=E8=8E=B7=E5=8F=96=E8=AE=B0=E5=BD=95=E5=88=97=E8=A1=A8=E7=9A=84?= =?UTF-8?q?=E6=97=A5=E6=9C=9F=E5=A4=84=E7=90=86=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/water-records-api.md | 473 +++++++++++++++++++++ src/water-records/water-records.service.ts | 12 +- 2 files changed, 479 insertions(+), 6 deletions(-) create mode 100644 docs/water-records-api.md diff --git a/docs/water-records-api.md b/docs/water-records-api.md new file mode 100644 index 0000000..ea37d65 --- /dev/null +++ b/docs/water-records-api.md @@ -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 +``` + +## 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封装提供了完整的喝水记录管理功能,可以直接在客户端项目中使用。 \ No newline at end of file diff --git a/src/water-records/water-records.service.ts b/src/water-records/water-records.service.ts index a207b41..d802a19 100644 --- a/src/water-records/water-records.service.ts +++ b/src/water-records/water-records.service.ts @@ -13,6 +13,7 @@ import { WaterStatsResponseDto } from './dto/water-record.dto'; import { Op } from 'sequelize'; +import * as dayjs from 'dayjs'; @Injectable() export class WaterRecordsService { @@ -30,14 +31,15 @@ export class WaterRecordsService { */ async createWaterRecord(userId: string, createDto: CreateWaterRecordDto): Promise { try { + this.logger.log(`createWaterRecord userId: ${userId}, createDto: ${JSON.stringify(createDto, null, 2)}`); const waterRecord = await this.userWaterHistoryModel.create({ userId, amount: createDto.amount, - recordedAt: createDto.recordedAt || new Date(), + recordedAt: new Date(), note: createDto.note, }); - this.logger.log(`用户 ${userId} 创建喝水记录成功,记录ID: ${waterRecord.id}`); + this.logger.log(`createWaterRecord 用户 ${userId} 创建喝水记录成功,记录ID: ${waterRecord.id}`); return { success: true, @@ -71,12 +73,10 @@ export class WaterRecordsService { if (startDate || endDate) { whereCondition.recordedAt = {}; if (startDate) { - whereCondition.recordedAt[Op.gte] = new Date(startDate); + whereCondition.recordedAt[Op.gte] = dayjs(startDate).startOf('day').toDate(); } if (endDate) { - const endDateTime = new Date(endDate); - endDateTime.setHours(23, 59, 59, 999); - whereCondition.recordedAt[Op.lte] = endDateTime; + whereCondition.recordedAt[Op.lte] = dayjs(endDate).endOf('day').toDate(); } }