From 5b89a077511f52ce69b35a7fcefb40d70b12cd29 Mon Sep 17 00:00:00 2001 From: richarjiang Date: Mon, 1 Dec 2025 17:58:59 +0800 Subject: [PATCH] stash --- docs/API-AI-HEALTH-REPORT.md | 456 ++++++++++++++++++ package-lock.json | 19 + package.json | 1 + src/ai-coach/ai-coach.module.ts | 16 +- src/ai-coach/services/ai-report.service.ts | 429 ++++++++++++++++ .../services/diet-analysis.service.ts | 3 +- src/challenges/challenges.module.ts | 4 +- src/diet-records/diet-records.module.ts | 2 +- src/medications/medications.module.ts | 6 +- src/mood-checkins/mood-checkins.module.ts | 4 +- .../push-notifications.module.ts | 6 +- src/users/cos.service.ts | 57 ++- src/users/users.controller.ts | 65 +++ src/users/users.module.ts | 2 + src/users/users.service.ts | 1 + src/workouts/workouts.module.ts | 6 +- yarn.lock | 60 ++- 17 files changed, 1094 insertions(+), 43 deletions(-) create mode 100644 docs/API-AI-HEALTH-REPORT.md create mode 100644 src/ai-coach/services/ai-report.service.ts diff --git a/docs/API-AI-HEALTH-REPORT.md b/docs/API-AI-HEALTH-REPORT.md new file mode 100644 index 0000000..afe19c8 --- /dev/null +++ b/docs/API-AI-HEALTH-REPORT.md @@ -0,0 +1,456 @@ +# AI 健康报告接口文档 + +## 接口概述 + +生成用户的 AI 健康报告图片接口,基于用户的健康数据(体重、饮食、运动等)生成可视化的健康分析报告图片。 + +--- + +## 接口信息 + +- **接口名称**: 生成 AI 健康报告 +- **接口路径**: `/users/ai-report` +- **请求方法**: `POST` +- **认证方式**: JWT Token (Bearer Token) +- **内容类型**: `application/json` + +--- + +## 请求说明 + +### 请求头 (Headers) + +| 参数名 | 类型 | 必填 | 说明 | 示例 | +| ------------- | ------ | ---- | ------------ | ------------------------------------------------ | +| Authorization | string | 是 | JWT 访问令牌 | `Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...` | +| Content-Type | string | 是 | 请求内容类型 | `application/json` | + +### 请求体 (Body) + +| 参数名 | 类型 | 必填 | 说明 | 示例 | +| ------ | ------ | ---- | ------------------------------------------------------------- | -------------- | +| date | string | 否 | 指定生成报告的日期,格式 YYYY-MM-DD。不传则默认生成今天的报告 | `"2024-01-15"` | + +### 请求示例 + +#### 示例 1: 生成今天的报告 + +```bash +curl -X POST 'https://api.example.com/users/ai-report' \ + -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...' \ + -H 'Content-Type: application/json' \ + -d '{}' +``` + +#### 示例 2: 生成指定日期的报告 + +```bash +curl -X POST 'https://api.example.com/users/ai-report' \ + -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...' \ + -H 'Content-Type: application/json' \ + -d '{ + "date": "2024-01-15" + }' +``` + +#### JavaScript/TypeScript 示例 + +```typescript +// 使用 fetch +async function generateHealthReport(date?: string) { + const response = await fetch("https://api.example.com/users/ai-report", { + method: "POST", + headers: { + Authorization: `Bearer ${accessToken}`, + "Content-Type": "application/json", + }, + body: JSON.stringify(date ? { date } : {}), + }); + + const result = await response.json(); + return result; +} + +// 使用 axios +import axios from "axios"; + +async function generateHealthReport(date?: string) { + const response = await axios.post( + "https://api.example.com/users/ai-report", + date ? { date } : {}, + { + headers: { + Authorization: `Bearer ${accessToken}`, + }, + } + ); + + return response.data; +} +``` + +#### Swift 示例 + +```swift +func generateHealthReport(date: String? = nil, completion: @escaping (Result) -> Void) { + let url = URL(string: "https://api.example.com/users/ai-report")! + var request = URLRequest(url: url) + request.httpMethod = "POST" + request.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization") + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + + var body: [String: Any] = [:] + if let date = date { + body["date"] = date + } + + if !body.isEmpty { + request.httpBody = try? JSONSerialization.data(withJSONObject: body) + } + + URLSession.shared.dataTask(with: request) { data, response, error in + if let error = error { + completion(.failure(error)) + return + } + + guard let data = data else { + completion(.failure(NSError(domain: "", code: -1, userInfo: nil))) + return + } + + do { + let decoder = JSONDecoder() + let result = try decoder.decode(HealthReportResponse.self, from: data) + completion(.success(result)) + } catch { + completion(.failure(error)) + } + }.resume() +} + +// 响应模型 +struct HealthReportResponse: Codable { + let code: Int + let message: String + let data: HealthReportData +} + +struct HealthReportData: Codable { + let imageUrl: String +} +``` + +--- + +## 响应说明 + +### 响应格式 + +所有响应都遵循统一的响应格式: + +```typescript +{ + code: number; // 响应码: 0-成功, 1-失败 + message: string; // 响应消息 + data: { + imageUrl: string; // 生成的报告图片 URL + } +} +``` + +### 成功响应 + +**HTTP 状态码**: `200 OK` + +**响应体示例**: + +```json +{ + "code": 0, + "message": "AI健康报告生成成功", + "data": { + "imageUrl": "https://pilates-1234567890.cos.ap-guangzhou.myqcloud.com/health-reports/user-123/2024-01-15/report-xxxxx.png" + } +} +``` + +**字段说明**: + +| 字段 | 类型 | 说明 | +| ------------- | ------ | -------------------------------------------- | +| code | number | 响应码,0 表示成功 | +| message | string | 响应消息,成功时为 "AI健康报告生成成功" | +| data.imageUrl | string | 生成的健康报告图片完整 URL,可直接访问和下载 | + +### 失败响应 + +**HTTP 状态码**: `200 OK` (业务失败也返回 200,通过 code 字段判断) + +**响应体示例**: + +```json +{ + "code": 1, + "message": "生成失败: 用户健康数据不足", + "data": { + "imageUrl": "" + } +} +``` + +**常见错误消息**: + +| 错误消息 | 说明 | 解决方案 | +| ----------------------------- | ------------------------------ | ---------------------------------------------- | +| `生成失败: 用户健康数据不足` | 用户没有足够的健康数据生成报告 | 引导用户添加更多健康数据(体重、饮食、运动等) | +| `生成失败: 日期格式不正确` | date 参数格式错误 | 确保日期格式为 YYYY-MM-DD | +| `生成失败: 未找到用户信息` | 用户不存在或 Token 无效 | 检查认证 Token 是否有效 | +| `生成失败: AI 服务暂时不可用` | AI 模型服务异常 | 稍后重试 | + +### 认证失败响应 + +**HTTP 状态码**: `401 Unauthorized` + +**响应体示例**: + +```json +{ + "statusCode": 401, + "message": "Unauthorized" +} +``` + +--- + +## 业务逻辑说明 + +### 报告内容 + +AI 健康报告会基于用户的以下数据生成: + +1. **体重数据**: 当天或最近的体重记录 +2. **饮食数据**: 当天的饮食记录和营养摄入 +3. **运动数据**: 当天的运动记录和卡路里消耗 +4. **围度数据**: 身体各部位的围度测量数据 +5. **目标进度**: 用户设定的健康目标完成情况 + +### 报告生成逻辑 + +- 如果不传 `date` 参数,默认生成今天的报告 +- 如果指定日期没有数据,会返回数据不足的提示 +- 报告图片为 PNG 格式,尺寸适配移动端展示 +- 图片会存储在腾讯云 COS,有效期永久(或根据策略定期清理) + +### 缓存策略 + +- 同一天同一用户的报告会缓存,重复请求会返回相同的图片 URL +- 如果用户更新了当天的数据,可以重新生成覆盖旧报告 + +--- + +## 错误处理 + +### 客户端错误处理示例 + +```typescript +async function fetchHealthReport(date?: string) { + try { + const response = await fetch("https://api.example.com/users/ai-report", { + method: "POST", + headers: { + Authorization: `Bearer ${accessToken}`, + "Content-Type": "application/json", + }, + body: JSON.stringify(date ? { date } : {}), + }); + + // 检查 HTTP 状态码 + if (response.status === 401) { + // Token 失效,需要重新登录 + throw new Error("认证失败,请重新登录"); + } + + const result = await response.json(); + + // 检查业务状态码 + if (result.code !== 0) { + // 业务失败 + throw new Error(result.message); + } + + // 成功,返回图片 URL + return result.data.imageUrl; + } catch (error) { + console.error("生成健康报告失败:", error); + throw error; + } +} + +// 使用示例 +try { + const imageUrl = await fetchHealthReport(); + console.log("报告生成成功:", imageUrl); + // 在 UI 中展示图片 +} catch (error) { + // 向用户展示错误提示 + alert(error.message); +} +``` + +--- + +## 使用建议 + +### 1. 前置检查 + +在调用接口前,建议先检查: + +- 用户是否已登录(有有效的 JWT Token) +- 用户是否有足够的健康数据(可通过其他接口查询) +- 网络连接是否正常 + +### 2. 加载提示 + +由于报告生成需要调用 AI 服务,可能需要几秒钟时间,建议: + +- 显示加载动画或进度提示 +- 设置合理的超时时间(建议 30-60 秒) +- 提供取消操作的选项 + +### 3. 图片展示 + +获取到图片 URL 后: + +- 可以直接在 Image 组件中使用该 URL +- 支持下载保存到本地相册 +- 支持分享到社交媒体 + +### 4. 错误处理 + +- 网络错误:提示用户检查网络连接 +- 数据不足:引导用户添加健康数据 +- Token 过期:自动刷新 Token 或引导重新登录 + +--- + +## 完整示例 + +### React Native 完整示例 + +```typescript +import React, { useState } from 'react'; +import { View, Image, Button, Text, ActivityIndicator } from 'react-native'; +import axios from 'axios'; + +const HealthReportScreen = () => { + const [loading, setLoading] = useState(false); + const [imageUrl, setImageUrl] = useState(null); + const [error, setError] = useState(null); + + const generateReport = async (date?: string) => { + setLoading(true); + setError(null); + + try { + const response = await axios.post( + 'https://api.example.com/users/ai-report', + date ? { date } : {}, + { + headers: { + 'Authorization': `Bearer ${accessToken}`, + }, + timeout: 60000, // 60秒超时 + } + ); + + if (response.data.code === 0) { + setImageUrl(response.data.data.imageUrl); + } else { + setError(response.data.message); + } + } catch (err) { + if (axios.isAxiosError(err)) { + if (err.response?.status === 401) { + setError('登录已过期,请重新登录'); + } else if (err.code === 'ECONNABORTED') { + setError('请求超时,请检查网络连接'); + } else { + setError(err.response?.data?.message || '生成报告失败'); + } + } else { + setError('未知错误'); + } + } finally { + setLoading(false); + } + }; + + return ( + +