Initial commit: MSH System\n\n- msh_single_uniapp: Vue 2 + UniApp 前端(微信小程序/H5/App/支付宝小程序)\n- msh_crmeb_22: Spring Boot 2.2 后端(C端API/管理端/业务逻辑)\n- models-integration: AI服务集成(Coze/KieAI/腾讯ASR)\n- docs: 产品文档与设计稿
This commit is contained in:
12
.cursor/debug.log
Normal file
12
.cursor/debug.log
Normal file
@@ -0,0 +1,12 @@
|
||||
{"location":"UploadServiceImpl.commonUpload","message":"before createFile","data":{"rootPath":"/Users/apple/.crmeb_upload/","webPath":"crmebimage/public/checkin/2026/02/14/","destPath":"/Users/apple/.crmeb_upload/crmebimage/public/checkin/2026/02/14/63e7b8e86e0147abafd057a1dd520732f69kuixvgh.jpg"},"timestamp":1771054614504,"hypothesisId":"H1,H2,H4","runId":"post-fix"}
|
||||
{"location":"UploadServiceImpl.commonUpload","message":"before createFile","data":{"rootPath":"/Users/apple/.crmeb_upload/","webPath":"crmebimage/public/audio/2026/02/14/","destPath":"/Users/apple/.crmeb_upload/crmebimage/public/audio/2026/02/14/92e75695b6934eef879a8067835bdf130gl7q8lus2.mp3"},"timestamp":1771054622634,"hypothesisId":"H1,H2,H4","runId":"post-fix"}
|
||||
{"data":{"rawValue":true,"setToUserSign":1,"parsedBoolean":true},"hypothesisId":"E","location":"ToolCheckinServiceImpl.java:135","id":"log_1771054634346_b","message":"enableAIVideo parsed","timestamp":1771054634346}
|
||||
{"location":"UploadServiceImpl.commonUpload","message":"before createFile","data":{"rootPath":"/Users/apple/.crmeb_upload/","webPath":"crmebimage/public/checkin/2026/02/14/","destPath":"/Users/apple/.crmeb_upload/crmebimage/public/checkin/2026/02/14/dd52bbd508044ac699acf0e1be8ab7dd77ns0z9rf1.jpg"},"timestamp":1771054891217,"hypothesisId":"H1,H2,H4","runId":"post-fix"}
|
||||
{"location":"UploadServiceImpl.commonUpload","message":"before createFile","data":{"rootPath":"/Users/apple/.crmeb_upload/","webPath":"crmebimage/public/audio/2026/02/14/","destPath":"/Users/apple/.crmeb_upload/crmebimage/public/audio/2026/02/14/a87b47ddf8fc4172963747c9b8a1e4105kshwyiubg.mp3"},"timestamp":1771054902191,"hypothesisId":"H1,H2,H4","runId":"post-fix"}
|
||||
{"data":{"rawValue":false,"setToUserSign":0,"parsedBoolean":false},"hypothesisId":"E","location":"ToolCheckinServiceImpl.java:135","id":"log_1771054906349_b","message":"enableAIVideo parsed","timestamp":1771054906349}
|
||||
{"location":"UploadServiceImpl.commonUpload","message":"before createFile","data":{"rootPath":"/Users/apple/.crmeb_upload/","webPath":"crmebimage/public/checkin/2026/02/14/","destPath":"/Users/apple/.crmeb_upload/crmebimage/public/checkin/2026/02/14/2728953d15ba4e26a7c1a799a4c15b49elt7yjci5x.png"},"timestamp":1771054919713,"hypothesisId":"H1,H2,H4","runId":"post-fix"}
|
||||
{"location":"UploadServiceImpl.commonUpload","message":"before createFile","data":{"rootPath":"/Users/apple/.crmeb_upload/","webPath":"crmebimage/public/audio/2026/02/14/","destPath":"/Users/apple/.crmeb_upload/crmebimage/public/audio/2026/02/14/73e142306c5b4f5ea5b93171b8ce54f370chfc8lz6.mp3"},"timestamp":1771054927494,"hypothesisId":"H1,H2,H4","runId":"post-fix"}
|
||||
{"data":{"rawValue":true,"setToUserSign":1,"parsedBoolean":true},"hypothesisId":"E","location":"ToolCheckinServiceImpl.java:135","id":"log_1771054935503_b","message":"enableAIVideo parsed","timestamp":1771054935503}
|
||||
{"location":"UploadServiceImpl.commonUpload","message":"before createFile","data":{"rootPath":"/Users/apple/.crmeb_upload/","webPath":"crmebimage/public/checkin/2026/02/14/","destPath":"/Users/apple/.crmeb_upload/crmebimage/public/checkin/2026/02/14/db082b45736e42e08c8cecd15798090eexut37l8lp.jpg"},"timestamp":1771055166731,"hypothesisId":"H1,H2,H4","runId":"post-fix"}
|
||||
{"location":"UploadServiceImpl.commonUpload","message":"before createFile","data":{"rootPath":"/Users/apple/.crmeb_upload/","webPath":"crmebimage/public/audio/2026/02/14/","destPath":"/Users/apple/.crmeb_upload/crmebimage/public/audio/2026/02/14/e75ddab2e25d40b492e2036e2f44301fcuri32duxq.mp3"},"timestamp":1771055173015,"hypothesisId":"H1,H2,H4","runId":"post-fix"}
|
||||
{"data":{"rawValue":true,"setToUserSign":1,"parsedBoolean":true},"hypothesisId":"E","location":"ToolCheckinServiceImpl.java:135","id":"log_1771055180778_b","message":"enableAIVideo parsed","timestamp":1771055180778}
|
||||
111
.cursor/plans/egfr_fix_and_video_gen_9021ff8f.plan.md
Normal file
111
.cursor/plans/egfr_fix_and_video_gen_9021ff8f.plan.md
Normal file
@@ -0,0 +1,111 @@
|
||||
---
|
||||
name: eGFR fix and video gen
|
||||
overview: Fix the eGFR field not appearing in calculator results due to a Lombok/Jackson serialization mismatch, and ensure the check-in page correctly calls the KieAI API to generate sora videos when the toggle is enabled.
|
||||
todos:
|
||||
- id: fix-egfr-serialization
|
||||
content: Add @JsonProperty("eGFR") to HealthData.eGFR field in NutritionCalculateResponse.java to fix JSON serialization mismatch
|
||||
status: completed
|
||||
- id: fix-nframes-hardcode
|
||||
content: Fix n_frames hardcoded value in ToolSora2ServiceImpl.java to use the request input value instead of always "15"
|
||||
status: completed
|
||||
- id: verify-checkin-video-flow
|
||||
content: Verify and fix the end-to-end video generation flow from checkin-publish.vue through KieAI API, ensuring taskId is properly stored in UserSign
|
||||
status: completed
|
||||
- id: improve-video-error-handling
|
||||
content: Improve error handling and user feedback in checkin-publish.vue for video generation failures
|
||||
status: completed
|
||||
isProject: false
|
||||
---
|
||||
|
||||
# Fix eGFR Display and Check-in Video Generation
|
||||
|
||||
## Issue 1: eGFR Not Showing in Calculator Results
|
||||
|
||||
### Root Cause
|
||||
|
||||
The field `private String eGFR` in `HealthData` uses Lombok `@Data`, which generates the getter `getEGFR()`. Jackson follows Java Beans naming convention: when the first two characters after stripping "get" are both uppercase ("EG"), the property name stays as-is: `"EGFR"`. The frontend expects `data.healthData.eGFR` but receives `data.healthData.EGFR`, resulting in `undefined` and displaying `--`.
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant FE as Frontend
|
||||
participant BE as Backend
|
||||
FE->>BE: GET /tool/calculator/result/{id}
|
||||
BE->>BE: buildResponse() sets healthData.setEGFR(...)
|
||||
BE-->>FE: JSON: {"healthData":{"EGFR":"7.9",...}}
|
||||
FE->>FE: healthData.eGFR is undefined
|
||||
FE->>FE: Displays "--"
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Fix
|
||||
|
||||
Add `@JsonProperty("eGFR")` annotation to the `eGFR` field in [NutritionCalculateResponse.java](msh_crmeb_22/crmeb-common/src/main/java/com/zbkj/common/response/NutritionCalculateResponse.java) (line 55):
|
||||
|
||||
```java
|
||||
@JsonProperty("eGFR")
|
||||
@ApiModelProperty(value = "eGFR数值(ml/min/1.73m²)", example = "7.9")
|
||||
private String eGFR;
|
||||
```
|
||||
|
||||
This forces Jackson to use the exact field name `"eGFR"` in JSON output, matching the frontend's expectation.
|
||||
|
||||
---
|
||||
|
||||
## Issue 2: Check-in Page Video Generation
|
||||
|
||||
### Current State
|
||||
|
||||
The implementation already exists in [checkin-publish.vue](msh_single_uniapp/pages/tool/checkin-publish.vue) (lines 590-627). It calls `api.createImageToVideoTask()` / `api.createTextToVideoTask()` from [models-api.js](msh_single_uniapp/api/models-api.js) which hit the backend [KieAIController.java](msh_crmeb_22/crmeb-front/src/main/java/com/zbkj/front/controller/KieAIController.java) endpoints at `/api/front/kieai/image-to-video` and `/api/front/kieai/text-to-video`.
|
||||
|
||||
Auth is not required (kieai endpoints are excluded from the interceptor in [WebConfig.java](msh_crmeb_22/crmeb-front/src/main/java/com/zbkj/front/config/WebConfig.java) line 98).
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant User
|
||||
participant CheckinPage as checkin-publish.vue
|
||||
participant ModelsAPI as models-api.js
|
||||
participant Backend as KieAIController
|
||||
participant Sora2 as ToolSora2ServiceImpl
|
||||
participant KieAI as KieAI API
|
||||
|
||||
User->>CheckinPage: Toggle "生成打卡视频" ON, click 发布
|
||||
CheckinPage->>ModelsAPI: createImageToVideoTask({imageUrl, prompt, uid})
|
||||
ModelsAPI->>Backend: POST /api/front/kieai/image-to-video
|
||||
Backend->>Sora2: createImageToVideoTask(...)
|
||||
Sora2->>KieAI: Upload image to KieAI
|
||||
Sora2->>KieAI: POST /api/v1/jobs/createTask (sora-2-image-to-video)
|
||||
KieAI-->>Sora2: {taskId: "..."}
|
||||
Sora2->>Sora2: Save Article record with taskId
|
||||
Sora2-->>Backend: taskId
|
||||
Backend-->>ModelsAPI: CommonResult{code:200, data: taskId}
|
||||
ModelsAPI-->>CheckinPage: response
|
||||
CheckinPage->>CheckinPage: submitCheckin({...taskId...})
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Issues to Fix
|
||||
|
||||
**a) Request parameter format bug in `models-api.js**`
|
||||
|
||||
The `createImageToVideoTask` function sends `image_urls` as `[params.imageUrl]` (single URL in array). However, the `Sora2Request.Input.image_urls` field is `String[]` -- this should work for deserialization, but the request body also sets `uid` as a potential number (from vuex). The `Sora2Request.uid` is `String` type. Jackson auto-coerces `number -> String`, so this is fine. No code change needed here.
|
||||
|
||||
**b) `n_frames` hardcoded in backend**
|
||||
|
||||
The backend [ToolSora2ServiceImpl.java](msh_crmeb_22/crmeb-service/src/main/java/com/zbkj/service/service/impl/tool/ToolSora2ServiceImpl.java) hardcodes `input.put("n_frames", "15")` at lines 44 and 109, ignoring the value sent by the frontend (`5`). For check-in videos, a shorter duration (5 frames ~ 5 seconds) is more appropriate. Change to use the value from the request input.
|
||||
|
||||
**c) Video task result not linked to check-in in backend**
|
||||
|
||||
Currently the video task creates an `Article` record but has no direct link to the `UserSign` (check-in) record. The frontend passes `taskId` in the `submitCheckin` request, but this linkage should be verified in the backend `ToolCheckinServiceImpl` to properly store and retrieve the video for the check-in.
|
||||
|
||||
**d) Error handling improvement in frontend**
|
||||
|
||||
The current error handling silently fails. Improve feedback to the user when video generation fails.
|
||||
|
||||
### Changes
|
||||
|
||||
- **[ToolSora2ServiceImpl.java](msh_crmeb_22/crmeb-service/src/main/java/com/zbkj/service/service/impl/tool/ToolSora2ServiceImpl.java)**: Use the `n_frames` value from the request input instead of hardcoding `"15"`. Both `createTextToVideoTask` (line 44) and `createImageToVideoTask` (line 109) need this fix.
|
||||
- **[checkin-publish.vue](msh_single_uniapp/pages/tool/checkin-publish.vue)**: Improve video generation error handling and ensure proper prompt construction for nutritional video content.
|
||||
- **Verify backend checkin service** stores the `taskId` from the video task in the `UserSign` record correctly.
|
||||
|
||||
475
.cursor/plans/mealplan_image_ai_generation_e8e123a0.plan.md
Normal file
475
.cursor/plans/mealplan_image_ai_generation_e8e123a0.plan.md
Normal file
@@ -0,0 +1,475 @@
|
||||
---
|
||||
name: MealPlan Image AI Generation
|
||||
overview: 将 models-integration 项目中 KieAI 图像/视频生成接口复刻到 crmeb-front,并在 mealPlan 菜品图片 URL 无效时,通过 KieAI NanoBanana API 生成图片、上传 OSS、返回有效 URL。
|
||||
todos:
|
||||
- id: replicate-kieai-config
|
||||
content: 在 crmeb-common 中新增 KieAIConfig 配置类,在 application-sophia.yml 中添加 kie-ai 配置
|
||||
status: completed
|
||||
- id: replicate-kieai-dtos
|
||||
content: 在 crmeb-common 中复刻 KieAI 相关 DTO(TextToImageInput, CreateTaskRequest, CreateTaskResponse, QueryTaskResponse, NanoBananaRequest, NanoBananaResponse 等)
|
||||
status: completed
|
||||
- id: replicate-kieai-helper
|
||||
content: 在 crmeb-service 中复刻 NanoBananaHelper(API 调用工具类)
|
||||
status: completed
|
||||
- id: replicate-kieai-service
|
||||
content: 在 crmeb-service 中复刻 KieAI 图像生成 Service 接口及实现(ToolKieAIService / ToolKieAIServiceImpl)
|
||||
status: completed
|
||||
- id: replicate-kieai-controller
|
||||
content: 在 crmeb-front 中新增 KieAIController,暴露文生图、图编辑、任务查询等 REST 接口
|
||||
status: completed
|
||||
- id: create-cache-table
|
||||
content: 创建 v2_dish_image_cache 数据库表及对应 MyBatis Entity/Dao/XML
|
||||
status: completed
|
||||
- id: extend-oss-service
|
||||
content: 扩展 OssService 支持 InputStream 上传(AI 生成的图片无需先落盘)
|
||||
status: completed
|
||||
- id: implement-dish-image-service
|
||||
content: 实现 DishImageService:URL 检测 + KieAI 文生图 + 轮询等待 + 下载图片 + OSS 上传 + 缓存写入
|
||||
status: completed
|
||||
- id: modify-calculator-service
|
||||
content: 修改 ToolCalculatorServiceImpl.generateMealPlan() 集成 DishImageService
|
||||
status: completed
|
||||
- id: error-handling
|
||||
content: 实现降级策略:KieAI 生成失败时使用默认占位图,不阻断主流程
|
||||
status: completed
|
||||
isProject: false
|
||||
---
|
||||
|
||||
# MealPlan 菜品图片 KieAI 生成 + OSS 上传方案
|
||||
|
||||
## 一、现状分析
|
||||
|
||||
### 1.1 问题
|
||||
|
||||
`[ToolCalculatorServiceImpl.generateMealPlan()](msh_crmeb_22/crmeb-service/src/main/java/com/zbkj/service/service/impl/tool/ToolCalculatorServiceImpl.java)` 中,11 道菜品的图片 URL 是硬编码的,指向 `https://uthink2025.oss-cn-shanghai.aliyuncs.com/recipes/xxx.jpg`。如果这些图片在 OSS 上不存在,前端会显示裂图。
|
||||
|
||||
### 1.2 已有基础设施
|
||||
|
||||
- **crmeb 项目**: Coze API SDK (v0.2.3)、阿里云 OSS 上传 (`[OssServiceImpl](msh_crmeb_22/crmeb-service/src/main/java/com/zbkj/service/service/impl/OssServiceImpl.java)`)、OSS 配置存储在 `system_config` 表中
|
||||
- **models-integration 项目**: 已有完整的 KieAI 集成,包括:
|
||||
- `NanoBanana` 文生图 API(`POST {baseUrl}/predictions`)
|
||||
- `Sora2` 文生视频 / 图生视频 API(`POST {baseUrl}/api/v1/jobs/createTask`)
|
||||
- 任务状态轮询、回调通知机制
|
||||
- 文件上传接口(`POST {uploadBaseUrl}/api/file-url-upload`)
|
||||
|
||||
### 1.3 KieAI API 核心信息
|
||||
|
||||
|
||||
| 项目 | 值 |
|
||||
| --------------- | ---------------------------------------------------- |
|
||||
| API Base URL | `https://api.kie.ai` |
|
||||
| Upload Base URL | `https://kieai.redpandaai.co` |
|
||||
| 文生图端点 | `POST /predictions` |
|
||||
| 查询任务端点 | `GET /predictions/{taskId}` |
|
||||
| 认证方式 | `Authorization: Bearer {apiToken}` |
|
||||
| 文生图模型 | `google/nano-banana` |
|
||||
| 图片编辑模型 | `google/nano-banana-edit` / `google/nano-banana-pro` |
|
||||
| 任务状态值 | `queuing` / `processing` / `completed` / `failed` |
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 二、整体架构
|
||||
|
||||
### 2.1 KieAI 接口复刻架构
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
subgraph crmebFront [crmeb-front]
|
||||
KieAICtrl[KieAIController]
|
||||
end
|
||||
subgraph crmebService [crmeb-service]
|
||||
ToolKieAISvc[ToolKieAIService]
|
||||
ToolKieAISvcImpl[ToolKieAIServiceImpl]
|
||||
KieAIHelper[KieAIHelper]
|
||||
end
|
||||
subgraph crmebCommon [crmeb-common]
|
||||
KieAICfg[KieAIConfig]
|
||||
DTOs[DTOs: TextToImageInput etc.]
|
||||
end
|
||||
subgraph external [External]
|
||||
KieAIAPI["KieAI API (api.kie.ai)"]
|
||||
end
|
||||
|
||||
KieAICtrl --> ToolKieAISvc
|
||||
ToolKieAISvc --> ToolKieAISvcImpl
|
||||
ToolKieAISvcImpl --> KieAIHelper
|
||||
KieAIHelper --> KieAICfg
|
||||
KieAIHelper --> DTOs
|
||||
KieAIHelper --> KieAIAPI
|
||||
```
|
||||
|
||||
|
||||
|
||||
### 2.2 菜品图片生成流程
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client
|
||||
participant CalculatorService
|
||||
participant DishImageService
|
||||
participant DB as v2_dish_image_cache
|
||||
participant KieAI as KieAI NanoBanana API
|
||||
participant OSS as 阿里云OSS
|
||||
|
||||
Client->>CalculatorService: POST /calculator/calculate
|
||||
CalculatorService->>CalculatorService: generateMealPlan()
|
||||
loop 每道菜品
|
||||
CalculatorService->>DishImageService: ensureValidImage(dishName, originalUrl)
|
||||
DishImageService->>DB: 查询缓存(dishName)
|
||||
alt 缓存命中
|
||||
DB-->>DishImageService: 返回缓存的 oss_url
|
||||
else 缓存未命中
|
||||
DishImageService->>DishImageService: HTTP HEAD 检查 originalUrl
|
||||
alt 原始URL有效
|
||||
DishImageService->>DB: 写入缓存
|
||||
else 原始URL无效(404)
|
||||
DishImageService->>KieAI: createTextToImageTask(prompt)
|
||||
KieAI-->>DishImageService: 返回 taskId
|
||||
DishImageService->>KieAI: waitForTaskCompletion(taskId)
|
||||
KieAI-->>DishImageService: 返回 output URLs
|
||||
DishImageService->>DishImageService: 下载图片 byte[]
|
||||
DishImageService->>OSS: 上传到 recipes/ 目录
|
||||
OSS-->>DishImageService: 返回 OSS URL
|
||||
DishImageService->>DB: 写入缓存
|
||||
end
|
||||
end
|
||||
DishImageService-->>CalculatorService: 返回有效 URL
|
||||
end
|
||||
CalculatorService-->>Client: NutritionCalculateResponse
|
||||
```
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 三、实现步骤
|
||||
|
||||
### 步骤 1:复刻 KieAI 配置到 crmeb-common
|
||||
|
||||
**新增文件:** `crmeb-common/src/main/java/com/zbkj/common/config/KieAIConfig.java`
|
||||
|
||||
从 `[models-integration KieAIConfig](models-integration/src/main/java/com/integration/api/config/KieAIConfig.java)` 复刻,保留核心字段:
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "kie-ai")
|
||||
public class KieAIConfig {
|
||||
private String baseUrl = "https://api.kie.ai";
|
||||
private String apiToken; // API Token
|
||||
private String apiUploadBaseUrl = "https://kieai.redpandaai.co";
|
||||
private String apiCallbackUrl; // 回调 URL
|
||||
private Integer connectTimeout = 30000;
|
||||
private Integer readTimeout = 60000;
|
||||
private Integer pollInterval = 2000; // 轮询间隔
|
||||
private Integer maxWaitTime = 300000; // 最大等待时间
|
||||
private String defaultOutputFormat = "png";
|
||||
private String defaultImageSize = "1:1"; // 菜品图默认正方形
|
||||
}
|
||||
```
|
||||
|
||||
**修改:** `application-sophia.yml` 新增配置段:
|
||||
|
||||
```yaml
|
||||
kie-ai:
|
||||
base-url: https://api.kie.ai
|
||||
api-token: 484661585fe62c5bcb77e6d392ba8ee8
|
||||
api-callback-url: https://sophia-shop.uj345.cc/api/front/kieai/callback
|
||||
api-upload-base-url: https://kieai.redpandaai.co
|
||||
connect-timeout: 30000
|
||||
read-timeout: 60000
|
||||
poll-interval: 2000
|
||||
max-wait-time: 300000
|
||||
default-output-format: png
|
||||
default-image-size: "1:1"
|
||||
```
|
||||
|
||||
### 步骤 2:复刻 KieAI DTOs 到 crmeb-common
|
||||
|
||||
**新增文件位置:** `crmeb-common/src/main/java/com/zbkj/common/request/kieai/` 和 `crmeb-common/src/main/java/com/zbkj/common/response/kieai/`
|
||||
|
||||
从 `[models-integration DTOs](models-integration/src/main/java/com/integration/api/dto/)` 复刻以下类:
|
||||
|
||||
|
||||
| 源文件 (models-integration) | 目标位置 (crmeb-common) | 说明 |
|
||||
| ------------------------- | --------------------------------------------- | --------- |
|
||||
| `TextToImageInput.java` | `request/kieai/KieAITextToImageInput.java` | 文生图输入参数 |
|
||||
| `ImageEditInput.java` | `request/kieai/KieAIImageEditInput.java` | 图编辑输入参数 |
|
||||
| `CreateTaskRequest.java` | `request/kieai/KieAICreateTaskRequest.java` | 创建任务请求 |
|
||||
| `NanoBananaRequest.java` | `request/kieai/KieAINanoBananaRequest.java` | 前端传入的完整请求 |
|
||||
| `CreateTaskResponse.java` | `response/kieai/KieAICreateTaskResponse.java` | 创建任务响应 |
|
||||
| `QueryTaskResponse.java` | `response/kieai/KieAIQueryTaskResponse.java` | 查询任务响应 |
|
||||
| `NanoBananaResponse.java` | `response/kieai/KieAINanoBananaResponse.java` | 通用响应包装 |
|
||||
|
||||
|
||||
核心 DTO 结构(`KieAIQueryTaskResponse`):
|
||||
|
||||
```java
|
||||
public class KieAIQueryTaskResponse {
|
||||
private String id; // 任务ID
|
||||
private String status; // queuing/processing/completed/failed
|
||||
private String created_at;
|
||||
private String finished_at;
|
||||
private String model;
|
||||
private Object input;
|
||||
private List<String> output; // 生成的图片URL列表
|
||||
private String error;
|
||||
}
|
||||
```
|
||||
|
||||
### 步骤 3:复刻 KieAI Helper 到 crmeb-service
|
||||
|
||||
**新增文件:** `crmeb-service/src/main/java/com/zbkj/service/helper/KieAIHelper.java`
|
||||
|
||||
从 `[NanoBananaHelper](models-integration/src/main/java/com/integration/api/helper/NanoBananaHelper.java)` 复刻,核心方法:
|
||||
|
||||
- `createHeaders()` -- 构建 `Authorization: Bearer {token}` 请求头
|
||||
- `createTask(KieAICreateTaskRequest)` -- POST `/predictions` 创建文生图任务
|
||||
- `queryTask(String taskId)` -- GET `/predictions/{taskId}` 查询任务
|
||||
- `buildTextToImageRequest(KieAITextToImageInput, callbackUrl)` -- 构建请求
|
||||
- `isApiTokenConfigured()` -- 验证 Token 配置
|
||||
|
||||
使用项目中已有的 `RestTemplate`(如果 crmeb 中没有 RestTemplate Bean,需要注册一个)。
|
||||
|
||||
### 步骤 4:复刻 KieAI Service 到 crmeb-service
|
||||
|
||||
**新增文件:**
|
||||
|
||||
- `crmeb-service/src/main/java/com/zbkj/service/service/tool/ToolKieAIService.java`(接口)
|
||||
- `crmeb-service/src/main/java/com/zbkj/service/service/impl/tool/ToolKieAIServiceImpl.java`(实现)
|
||||
|
||||
从 `[NanoBananaServiceImpl](models-integration/src/main/java/com/integration/api/service/impl/NanoBananaServiceImpl.java)` 复刻核心方法:
|
||||
|
||||
```java
|
||||
public interface ToolKieAIService {
|
||||
/** 文生图 - 创建任务 */
|
||||
KieAICreateTaskResponse createTextToImageTask(KieAINanoBananaRequest request);
|
||||
|
||||
/** 查询任务状态 */
|
||||
KieAIQueryTaskResponse queryTask(String taskId);
|
||||
|
||||
/** 同步等待任务完成(轮询) */
|
||||
KieAIQueryTaskResponse waitForTaskCompletion(String taskId, long maxWaitTime);
|
||||
|
||||
/** 图编辑 - 创建任务 */
|
||||
KieAICreateTaskResponse createImageEditTask(KieAINanoBananaRequest request);
|
||||
}
|
||||
```
|
||||
|
||||
**注意:** 复刻时简化设计 -- 不需要 `NanoBananaTask` 数据库表和 `Article` 表关联逻辑,因为 crmeb 中的主要使用场景是内部调用(菜品图片生成),任务记录可选。
|
||||
|
||||
### 步骤 5:复刻 KieAI Controller 到 crmeb-front
|
||||
|
||||
**新增文件:** `crmeb-front/src/main/java/com/zbkj/front/controller/KieAIController.java`
|
||||
|
||||
从 `[KieAI2ImageController](models-integration/src/main/java/com/integration/api/controller/KieAI2ImageController.java)` 复刻,暴露以下接口:
|
||||
|
||||
|
||||
| 接口 | 方法 | 说明 |
|
||||
| ------------------------------------- | ---- | ------ |
|
||||
| `/api/front/kieai/text-to-image` | POST | 文生图 |
|
||||
| `/api/front/kieai/image-edit` | POST | 图编辑 |
|
||||
| `/api/front/kieai/task/{taskId}` | GET | 查询任务 |
|
||||
| `/api/front/kieai/task/{taskId}/wait` | GET | 同步等待任务 |
|
||||
| `/api/front/kieai/callback` | POST | 回调通知 |
|
||||
|
||||
|
||||
同时需要在安全白名单中添加 `/api/front/kieai/**` 路径(参考 Coze 接口的白名单配置)。
|
||||
|
||||
### 步骤 6:新建菜品图片缓存表
|
||||
|
||||
```sql
|
||||
CREATE TABLE `v2_dish_image_cache` (
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT,
|
||||
`dish_name` VARCHAR(100) NOT NULL COMMENT '菜品名称',
|
||||
`original_url` VARCHAR(500) DEFAULT NULL COMMENT '原始图片URL',
|
||||
`oss_url` VARCHAR(500) NOT NULL COMMENT 'OSS有效图片URL',
|
||||
`ai_provider` VARCHAR(50) DEFAULT 'kieai' COMMENT 'AI生成来源',
|
||||
`task_id` VARCHAR(100) DEFAULT NULL COMMENT 'KieAI任务ID',
|
||||
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_dish_name` (`dish_name`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='菜品图片缓存表';
|
||||
```
|
||||
|
||||
对应新增:
|
||||
|
||||
- `crmeb-common/.../model/tool/V2DishImageCache.java`(实体)
|
||||
- `crmeb-service/.../dao/tool/V2DishImageCacheDao.java`(Mapper)
|
||||
- `resources/mapper/tool/V2DishImageCacheDao.xml`(MyBatis XML)
|
||||
|
||||
### 步骤 7:扩展 OssService 支持 InputStream 上传
|
||||
|
||||
当前 `[OssServiceImpl.upload()](msh_crmeb_22/crmeb-service/src/main/java/com/zbkj/service/service/impl/OssServiceImpl.java)` 只支持 `File` 参数。新增 InputStream 重载:
|
||||
|
||||
```java
|
||||
// OssService 接口新增
|
||||
void upload(CloudVo cloudVo, String objectKey, InputStream inputStream, long contentLength);
|
||||
|
||||
// OssServiceImpl 实现
|
||||
@Override
|
||||
public void upload(CloudVo cloudVo, String objectKey, InputStream inputStream, long contentLength) {
|
||||
OSS ossClient = new OSSClientBuilder().build(cloudVo.getRegion(), cloudVo.getAccessKey(), cloudVo.getSecretKey());
|
||||
try {
|
||||
ObjectMetadata metadata = new ObjectMetadata();
|
||||
metadata.setContentLength(contentLength);
|
||||
PutObjectRequest putRequest = new PutObjectRequest(cloudVo.getBucketName(), objectKey, inputStream, metadata);
|
||||
ossClient.putObject(putRequest);
|
||||
} finally {
|
||||
ossClient.shutdown();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 步骤 8:实现 DishImageService
|
||||
|
||||
**新增文件:**
|
||||
|
||||
- `crmeb-service/.../service/tool/DishImageService.java`
|
||||
- `crmeb-service/.../service/impl/tool/DishImageServiceImpl.java`
|
||||
|
||||
核心流程:
|
||||
|
||||
```java
|
||||
@Service
|
||||
public class DishImageServiceImpl implements DishImageService {
|
||||
|
||||
@Autowired private V2DishImageCacheDao cacheDao;
|
||||
@Autowired private ToolKieAIService kieAIService;
|
||||
@Autowired private OssService ossService;
|
||||
@Autowired private SystemConfigService systemConfigService;
|
||||
|
||||
@Override
|
||||
public String ensureValidImageUrl(String dishName, String originalUrl) {
|
||||
// 1. 查缓存
|
||||
V2DishImageCache cache = cacheDao.selectByDishName(dishName);
|
||||
if (cache != null) return cache.getOssUrl();
|
||||
|
||||
// 2. HTTP HEAD 检查原始 URL(3秒超时)
|
||||
if (checkUrlAccessible(originalUrl)) {
|
||||
saveCache(dishName, originalUrl, originalUrl, null);
|
||||
return originalUrl;
|
||||
}
|
||||
|
||||
// 3. KieAI 文生图
|
||||
try {
|
||||
String prompt = "一道精美的中式菜品照片:" + dishName
|
||||
+ ",高清美食摄影风格,白色餐盘,俯拍角度,自然光照";
|
||||
KieAICreateTaskResponse createResp = kieAIService.createTextToImageTask(buildRequest(prompt));
|
||||
KieAIQueryTaskResponse result = kieAIService.waitForTaskCompletion(createResp.getId(), 120000);
|
||||
|
||||
if ("completed".equals(result.getStatus()) && result.getOutput() != null) {
|
||||
String imageUrl = result.getOutput().get(0);
|
||||
// 4. 下载图片并上传 OSS
|
||||
byte[] imageBytes = downloadImage(imageUrl);
|
||||
String ossUrl = uploadToOss(imageBytes, dishName);
|
||||
// 5. 写缓存
|
||||
saveCache(dishName, originalUrl, ossUrl, createResp.getId());
|
||||
return ossUrl;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("菜品图片AI生成失败: {}", dishName, e);
|
||||
}
|
||||
|
||||
// 6. 降级:返回默认占位图
|
||||
return getDefaultPlaceholderUrl();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 步骤 9:修改 ToolCalculatorServiceImpl
|
||||
|
||||
在 `[generateMealPlan()](msh_crmeb_22/crmeb-service/src/main/java/com/zbkj/service/service/impl/tool/ToolCalculatorServiceImpl.java)` 方法末尾集成 DishImageService:
|
||||
|
||||
```java
|
||||
@Autowired
|
||||
private DishImageService dishImageService;
|
||||
|
||||
private MealPlan generateMealPlan(String ckdStage, Boolean dialysis) {
|
||||
MealPlan plan = new MealPlan();
|
||||
// ... 现有代码不变(创建早/午/晚餐 Dish 列表)...
|
||||
|
||||
// 确保所有菜品图片URL有效
|
||||
ensureMealPlanImages(plan);
|
||||
return plan;
|
||||
}
|
||||
|
||||
private void ensureMealPlanImages(MealPlan plan) {
|
||||
Stream.of(plan.getBreakfast(), plan.getLunch(), plan.getDinner())
|
||||
.filter(Objects::nonNull)
|
||||
.flatMap(List::stream)
|
||||
.forEach(dish -> {
|
||||
String validUrl = dishImageService.ensureValidImageUrl(dish.getName(), dish.getImage());
|
||||
dish.setImage(validUrl);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、异常处理与降级策略
|
||||
|
||||
- KieAI 生成失败 / 超时 -> 返回**默认占位图** URL(预先上传到 OSS 的通用食物图片)
|
||||
- HTTP URL 检测设置 **3 秒超时**,避免阻塞主流程
|
||||
- 整个图片处理过程用 try-catch 包裹,**失败不影响营养计算结果的返回**
|
||||
- 日志记录每次 AI 生成和上传操作,便于排查
|
||||
- KieAI API Token 未配置时跳过 AI 生成,直接使用原始 URL 或占位图
|
||||
|
||||
---
|
||||
|
||||
## 五、性能优化
|
||||
|
||||
- **缓存优先**: DB 缓存命中后直接返回,零网络开销
|
||||
- **仅 11 道固定菜品**: 缓存全部填充后,后续请求无 AI 调用
|
||||
- **首次预热**: 可提供管理后台接口或启动任务,手动触发所有菜品图片预生成
|
||||
- **并行处理**: 可用 `CompletableFuture` 并行检查/生成多道菜品图片(可选优化)
|
||||
|
||||
---
|
||||
|
||||
## 六、涉及文件清单
|
||||
|
||||
### 6.1 新增文件(KieAI 接口复刻)
|
||||
|
||||
|
||||
| 模块 | 文件路径 | 说明 |
|
||||
| ------------- | --------------------------------------------- | ------------- |
|
||||
| crmeb-common | `config/KieAIConfig.java` | KieAI 配置类 |
|
||||
| crmeb-common | `request/kieai/KieAITextToImageInput.java` | 文生图输入 DTO |
|
||||
| crmeb-common | `request/kieai/KieAIImageEditInput.java` | 图编辑输入 DTO |
|
||||
| crmeb-common | `request/kieai/KieAICreateTaskRequest.java` | 创建任务请求 DTO |
|
||||
| crmeb-common | `request/kieai/KieAINanoBananaRequest.java` | 前端完整请求 DTO |
|
||||
| crmeb-common | `response/kieai/KieAICreateTaskResponse.java` | 创建任务响应 DTO |
|
||||
| crmeb-common | `response/kieai/KieAIQueryTaskResponse.java` | 查询任务响应 DTO |
|
||||
| crmeb-common | `response/kieai/KieAINanoBananaResponse.java` | 通用响应包装 |
|
||||
| crmeb-service | `helper/KieAIHelper.java` | API 调用工具类 |
|
||||
| crmeb-service | `service/tool/ToolKieAIService.java` | KieAI 服务接口 |
|
||||
| crmeb-service | `service/impl/tool/ToolKieAIServiceImpl.java` | KieAI 服务实现 |
|
||||
| crmeb-front | `controller/KieAIController.java` | KieAI REST 接口 |
|
||||
|
||||
|
||||
### 6.2 新增文件(菜品图片缓存)
|
||||
|
||||
|
||||
| 模块 | 文件路径 | 说明 |
|
||||
| ------------- | ----------------------------------------------- | ------------------------- |
|
||||
| crmeb-common | `model/tool/V2DishImageCache.java` | 缓存实体 |
|
||||
| crmeb-service | `dao/tool/V2DishImageCacheDao.java` | MyBatis Mapper |
|
||||
| crmeb-service | `resources/mapper/tool/V2DishImageCacheDao.xml` | MyBatis XML |
|
||||
| crmeb-service | `service/tool/DishImageService.java` | 菜品图片服务接口 |
|
||||
| crmeb-service | `service/impl/tool/DishImageServiceImpl.java` | 菜品图片服务实现 |
|
||||
| - | SQL 迁移脚本 | 建 `v2_dish_image_cache` 表 |
|
||||
|
||||
|
||||
### 6.3 修改文件
|
||||
|
||||
|
||||
| 模块 | 文件路径 | 改动内容 |
|
||||
| ------------- | -------------------------------------------------- | ------------------------ |
|
||||
| crmeb-service | `service/OssService.java` | 新增 InputStream 上传重载方法 |
|
||||
| crmeb-service | `service/impl/OssServiceImpl.java` | 实现 InputStream 上传 |
|
||||
| crmeb-service | `service/impl/tool/ToolCalculatorServiceImpl.java` | 集成 DishImageService |
|
||||
| crmeb-front | `resources/application-sophia.yml` | 新增 `kie-ai` 配置段 |
|
||||
| crmeb-front | 安全白名单配置 | 添加 `/api/front/kieai/**` |
|
||||
|
||||
|
||||
193
.cursor/plans/migrate_apis_to_crmeb-front_e6251166.plan.md
Normal file
193
.cursor/plans/migrate_apis_to_crmeb-front_e6251166.plan.md
Normal file
@@ -0,0 +1,193 @@
|
||||
---
|
||||
name: Migrate APIs to crmeb-front
|
||||
overview: Migrate all backend API implementations that models-api.js currently calls from models-integration to crmeb-front, and update the frontend to route all requests through crmeb-front's /api/front/ prefix.
|
||||
todos:
|
||||
- id: module-1-video
|
||||
content: "Module 1: Copy KieAI Video (Sora2) -- DTO, Service, ServiceImpl, Controller endpoints (text-to-video, image-to-video, pro variants, watermark, file-upload, task query)"
|
||||
status: completed
|
||||
- id: module-2-asr
|
||||
content: "Module 2: Copy Tencent ASR -- add tencentcloud-sdk-java dependency, Config, DTO, Service, ServiceImpl, Controller (create-task, query-status, sentence-recognition)"
|
||||
status: completed
|
||||
- id: module-3-articles
|
||||
content: "Module 3: Copy Article Models -- Model/VO/Mapper, Service, Controller at /api/front/article-models (getById, list, search)"
|
||||
status: completed
|
||||
- id: coze-resume
|
||||
content: Add Coze workflow resume endpoint to existing CozeController and ToolCozeService
|
||||
status: completed
|
||||
- id: interceptor-config
|
||||
content: Update WebConfig/application.yml to exclude new routes from token interceptor
|
||||
status: completed
|
||||
- id: frontend-update
|
||||
content: "Update models-api.js: change API_BASE_URL, update all paths to /api/front/ prefix, move secrets to server-side config"
|
||||
status: completed
|
||||
isProject: false
|
||||
---
|
||||
|
||||
# Migrate models-api.js Backend to crmeb-front
|
||||
|
||||
## Current Architecture
|
||||
|
||||
The frontend `models-api.js` calls `API_BASE_URL = 'https://sophia-shop.uj345.cc/models'` (models-integration, port 5081). The goal is to migrate all these endpoints to crmeb-front (port 8081) so the frontend only needs to call `/api/front/` endpoints.
|
||||
|
||||
## Endpoint Mapping: models-api.js -> models-integration -> crmeb-front
|
||||
|
||||
### Already Ported (only need frontend URL change)
|
||||
|
||||
These endpoints already exist in crmeb-front and have working service implementations:
|
||||
|
||||
- **Coze Chat**: `/api/coze/chat` -> `/api/front/coze/chat` (CozeController)
|
||||
- **Coze Retrieve**: `/api/coze/chat/retrieve` -> `/api/front/coze/chat/retrieve`
|
||||
- **Coze Messages**: `/api/coze/chat/messages/list` -> `/api/front/coze/chat/messages/list`
|
||||
- **Coze Workflow Run**: `/api/coze/workflow/run` -> `/api/front/coze/workflow/run`
|
||||
- **Coze Workflow Stream**: `/api/coze/workflow/stream` -> `/api/front/coze/workflow/stream`
|
||||
- **Coze File Upload**: `/api/coze/file/upload` -> `/api/front/coze/file/upload`
|
||||
- **KieAI Image Edit**: `/api/kieai/image/image-edit` -> `/api/front/kieai/image-edit` (KieAIController)
|
||||
- **Upload File**: already calls `sophia-shop.uj345.cc/api/front/upload/imageOuter` (crmeb-front)
|
||||
|
||||
### Need to Add to crmeb-front (3 modules)
|
||||
|
||||
#### Module 1: KieAI Video (Sora2) -- 6 endpoints
|
||||
|
||||
Source: [KieAI2VideoController.java](models-integration/src/main/java/com/integration/api/controller/KieAI2VideoController.java) + [Sora2ServiceImpl.java](models-integration/src/main/java/com/integration/api/service/impl/Sora2ServiceImpl.java)
|
||||
|
||||
New endpoints to add to `KieAIController` or a new `KieAIVideoController`:
|
||||
|
||||
- `POST /api/front/kieai/text-to-video` (createTextToVideoTask)
|
||||
- `POST /api/front/kieai/image-to-video` (createImageToVideoTask)
|
||||
- `POST /api/front/kieai/pro/text-to-video` (createProTextToVideoTask)
|
||||
- `POST /api/front/kieai/pro/image-to-video` (createProImageToVideoTask)
|
||||
- `POST /api/front/kieai/remove-watermark` (removeWatermark)
|
||||
- `POST /api/front/kieai/file-url-upload` (uploadFileByUrl)
|
||||
- `GET /api/front/kieai/video/task/{taskId}` (getTaskStatus - video task query)
|
||||
|
||||
Files to create/copy:
|
||||
|
||||
- **DTO**: Copy `Sora2Request` -> `com.zbkj.common.request.kieai.Sora2Request`
|
||||
- **DTO**: Copy `TaskStatus` -> `com.zbkj.common.response.kieai.KieAIVideoTaskStatus`
|
||||
- **DTO**: Copy `CreateProTextToVideoRequest` -> `com.zbkj.common.request.kieai.CreateProTextToVideoRequest`
|
||||
- **Service**: Create `ToolSora2Service` interface in `crmeb-service`
|
||||
- **ServiceImpl**: Copy `Sora2ServiceImpl` logic -> `ToolSora2ServiceImpl` in `crmeb-service`
|
||||
- **Controller**: Add video endpoints to [KieAIController.java](msh_crmeb_22/crmeb-front/src/main/java/com/zbkj/front/controller/KieAIController.java) or new controller
|
||||
- **Helper**: Copy `HttpRequestUtils` -> `com.zbkj.common.utils.HttpRequestUtils` (or reuse existing crmeb HTTP utils)
|
||||
|
||||
Dependencies already available:
|
||||
|
||||
- Apache HttpClient (in crmeb-common pom.xml)
|
||||
- FastJSON (in crmeb parent pom.xml)
|
||||
- `KieAIConfig` already exists with `baseUrl`, `apiCallbackUrl` etc.
|
||||
|
||||
Config to add to `KieAIConfig`:
|
||||
|
||||
- `apiUploadBaseUrl` property (used by Sora2ServiceImpl for file uploads)
|
||||
|
||||
#### Module 2: Tencent ASR -- 3 endpoints
|
||||
|
||||
Source: [TencentAsrController.java](models-integration/src/main/java/com/integration/api/controller/TencentAsrController.java) + [TencentAsrServiceImpl.java](models-integration/src/main/java/com/integration/api/service/impl/TencentAsrServiceImpl.java)
|
||||
|
||||
New endpoints:
|
||||
|
||||
- `POST /api/front/tencent/asr/create-task`
|
||||
- `GET /api/front/tencent/asr/query-status/{taskId}`
|
||||
- `POST /api/front/tencent/asr/sentence-recognition`
|
||||
|
||||
Files to create/copy:
|
||||
|
||||
- **DTO**: Copy `TencentAsrRequest` -> `com.zbkj.common.request.tencent.TencentAsrRequest`
|
||||
- **DTO**: Copy `TencentAsrResponse` -> `com.zbkj.common.response.tencent.TencentAsrResponse`
|
||||
- **DTO**: Copy `TencentAsrTaskStatus` -> `com.zbkj.common.response.tencent.TencentAsrTaskStatus`
|
||||
- **Config**: Create `TencentAsrConfig` in `com.zbkj.common.config` (prefix `tencent-asr`, fields: secretId, secretKey, region, connectTimeout, readTimeout, defaultEngineModel, defaultResTextFormat, defaultChannelNum, enabled)
|
||||
- **Service**: Create `ToolTencentAsrService` interface
|
||||
- **ServiceImpl**: Copy `TencentAsrServiceImpl` logic -> `ToolTencentAsrServiceImpl`
|
||||
- **Controller**: Create `TencentAsrController` in crmeb-front at `/api/front/tencent/asr`
|
||||
|
||||
**New dependency needed** in `crmeb-common/pom.xml`:
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>com.tencentcloudapi</groupId>
|
||||
<artifactId>tencentcloud-sdk-java</artifactId>
|
||||
<version>3.1.880</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
**Config to add** to `application-sophia.yml`:
|
||||
|
||||
```yaml
|
||||
tencent-asr:
|
||||
secret-id: ${TENCENT_SECRET_ID:xxx}
|
||||
secret-key: ${TENCENT_SECRET_KEY:xxx}
|
||||
region: ap-shanghai
|
||||
enabled: true
|
||||
```
|
||||
|
||||
#### Module 3: Articles (models-integration style) -- 3 endpoints
|
||||
|
||||
Source: [ArticleController.java](models-integration/src/main/java/com/integration/api/controller/ArticleController.java)
|
||||
|
||||
Note: crmeb-front already has an `ArticleController` at `/api/front/article` for CMS articles, but that is a different data model (CRMEB's own articles). The models-integration `ArticleController` manages AI-generated content articles in a separate `eb_article` table with fields like `taskId`, `prompt`, `videoUrl`, `statusTask`.
|
||||
|
||||
Approach: Add these as new endpoints in a **new controller** `ArticleModelsController` at `/api/front/article-models` to avoid conflict with the existing crmeb ArticleController.
|
||||
|
||||
New endpoints:
|
||||
|
||||
- `GET /api/front/article-models/{id}` (getArticleById)
|
||||
- `GET /api/front/article-models` (getArticleList, paginated)
|
||||
- `GET /api/front/article-models/search` (searchArticles)
|
||||
|
||||
Files needed:
|
||||
|
||||
- The `Article` model, `ArticleVO`, `ArticleMapper`, `UserMapper` already exist in models-integration's DB tables. Need to ensure the crmeb service can access the same `eb_article` table or create equivalent mapper/model in crmeb-common.
|
||||
- **Service**: Create `ToolArticleModelsService` interface + impl
|
||||
- **Controller**: Create `ArticleModelsController`
|
||||
|
||||
### Callback Endpoints (remain in models-integration)
|
||||
|
||||
The following callbacks should **stay in models-integration** since they are called by external services (KieAI, Coze) and need a stable URL:
|
||||
|
||||
- `POST /api/kieai/callback`
|
||||
- `POST /api/kieai/callback20994`
|
||||
- `POST /api/diet-checkin/callback/video`
|
||||
- `POST /api/diet-checkin/callback/analysis`
|
||||
|
||||
These use `callBackUrl` that is configured in the task creation requests, so they can continue pointing to models-integration.
|
||||
|
||||
### Coze Workflow Resume
|
||||
|
||||
The frontend `cozeWorkflowResume` function calls `/api/coze/workflow/resume`, but this endpoint does **not exist** in either models-integration or crmeb-front. The Coze SDK does support workflow resume. Need to:
|
||||
|
||||
- Add `/api/front/coze/workflow/resume` endpoint to crmeb-front CozeController
|
||||
- Add `workflowResume()` method to `ToolCozeService` / `ToolCozeServiceImpl`
|
||||
|
||||
## Frontend Changes (models-api.js)
|
||||
|
||||
After backend is ready, update [models-api.js](msh_single_uniapp/utils/models-api.js):
|
||||
|
||||
1. Change `API_BASE_URL` from `'https://sophia-shop.uj345.cc/models'` to `'https://sophia-shop.uj345.cc'` (crmeb-front domain)
|
||||
2. Update all API paths to use `/api/front/` prefix:
|
||||
- `/api/articles/{id}` -> `/api/front/article-models/{id}`
|
||||
- `/api/articles` -> `/api/front/article-models`
|
||||
- `/api/articles/search` -> `/api/front/article-models/search`
|
||||
- `/api/kieai/text-to-video` -> `/api/front/kieai/text-to-video`
|
||||
- `/api/kieai/image-to-video` -> `/api/front/kieai/image-to-video`
|
||||
- `/api/kieai/image/image-edit` -> `/api/front/kieai/image-edit`
|
||||
- `/api/kieai/task/{taskId}` -> `/api/front/kieai/video/task/{taskId}`
|
||||
- `/api/kieai/file-url-upload` -> `/api/front/kieai/file-url-upload`
|
||||
- `/api/tencent/asr/create-task` -> `/api/front/tencent/asr/create-task`
|
||||
- `/api/tencent/asr/query-status/{id}` -> `/api/front/tencent/asr/query-status/{id}`
|
||||
- `/api/coze/chat` -> `/api/front/coze/chat`
|
||||
- `/api/coze/chat/retrieve` -> `/api/front/coze/chat/retrieve`
|
||||
- `/api/coze/chat/messages/list` -> `/api/front/coze/chat/messages/list`
|
||||
- `/api/coze/workflow/run` -> `/api/front/coze/workflow/run`
|
||||
- `/api/coze/workflow/stream` -> `/api/front/coze/workflow/stream`
|
||||
- `/api/coze/workflow/resume` -> `/api/front/coze/workflow/resume`
|
||||
- `/api/coze/file/upload` -> `/api/front/coze/file/upload` (in `cozeUploadFile`)
|
||||
3. Remove hardcoded `tenant_id`, `api_key`, callback URLs from frontend -- move these to server-side config
|
||||
|
||||
## Interceptor Config
|
||||
|
||||
Ensure the new routes are excluded from token interceptor in WebConfig (or application.yml `excludePathPatterns`):
|
||||
|
||||
- `/api/front/tencent/asr/**`
|
||||
- `/api/front/article-models/**`
|
||||
- KieAI video endpoints are under `/api/front/kieai/**` which is likely already excluded
|
||||
|
||||
559
.cursor/plans/tool_module_test_plan_4894ed78.plan.md
Normal file
559
.cursor/plans/tool_module_test_plan_4894ed78.plan.md
Normal file
@@ -0,0 +1,559 @@
|
||||
---
|
||||
name: Tool Module Test Plan
|
||||
overview: Create a comprehensive frontend functional test plan for the WeChat mini-program tool module, covering the hub page (tool_main/index.vue) and all 18 sub-pages under pages/tool/.
|
||||
todos:
|
||||
- id: test-hub
|
||||
content: "测试 Tool 首页 (tool_main/index.vue): 数据加载、四宫格导航、推荐内容、登录态"
|
||||
status: completed
|
||||
- id: test-calculator
|
||||
content: "测试食谱计算器流程: calculator.vue 表单校验提交 -> calculator-result.vue 结果展示与采纳"
|
||||
status: completed
|
||||
- id: test-ai
|
||||
content: "测试 AI 营养师: 文字对话、语音输入(ASR)、图片发送、Coze API 集成"
|
||||
status: completed
|
||||
- id: test-checkin
|
||||
content: "测试打卡流程: checkin.vue 积分/连续打卡 -> checkin-publish.vue 发布 -> checkin-detail.vue 详情 -> dietary-records.vue 列表 -> checkin-copy.vue 借鉴"
|
||||
status: completed
|
||||
- id: test-food
|
||||
content: "测试食物百科: food-encyclopedia.vue 搜索/分类/列表 -> food-detail.vue 详情"
|
||||
status: completed
|
||||
- id: test-knowledge
|
||||
content: "测试营养知识: nutrition-knowledge.vue Tab切换/列表 -> nutrient-detail.vue 详情"
|
||||
status: completed
|
||||
- id: test-community
|
||||
content: "测试社区帖子: post-detail.vue 展示/点赞/收藏/评论/关注"
|
||||
status: completed
|
||||
- id: test-profile
|
||||
content: "测试用户与积分: my-profile.vue / points-rules.vue / invite-rewards.vue / welcome-gift.vue"
|
||||
status: completed
|
||||
- id: test-common
|
||||
content: "通用测试: 登录态、网络异常、UI多机型适配、性能、页面标题与返回"
|
||||
status: completed
|
||||
isProject: false
|
||||
---
|
||||
|
||||
# Tool 模块前端功能测试计划
|
||||
|
||||
## 一、测试范围
|
||||
|
||||
本测试计划覆盖微信小程序 Tool 模块的全部 19 个页面:
|
||||
|
||||
- **入口首页**: [pages/tool_main/index.vue](msh_single_uniapp/pages/tool_main/index.vue)
|
||||
- **子页面 (18个)**: [pages/tool/](msh_single_uniapp/pages/tool/) 目录下全部 `.vue` 文件
|
||||
|
||||
### 页面与路由清单
|
||||
|
||||
|
||||
| 页面文件 | 路由路径 | 页面标题 | 分类 |
|
||||
| ---- | ---- | ---- | --- |
|
||||
|
||||
|
||||
> 注意:Markdown 表格不便渲染,以列表替代:
|
||||
|
||||
**食谱计算器**
|
||||
|
||||
- `calculator.vue` -> `/pages/tool/calculator` (食谱计算器)
|
||||
- `calculator-result.vue` -> `/pages/tool/calculator-result` (营养计算结果)
|
||||
|
||||
**AI 营养师**
|
||||
|
||||
- `ai-nutritionist.vue` -> `/pages/tool/ai-nutritionist` (AI营养师)
|
||||
|
||||
**食物百科**
|
||||
|
||||
- `food-encyclopedia.vue` -> `/pages/tool/food-encyclopedia` (食物百科)
|
||||
- `food-detail.vue` -> `/pages/tool/food-detail` (食物详情)
|
||||
|
||||
**营养知识**
|
||||
|
||||
- `nutrition-knowledge.vue` -> `/pages/tool/nutrition-knowledge` (营养知识)
|
||||
- `nutrient-detail.vue` -> `/pages/tool/nutrient-detail` (营养素详情)
|
||||
|
||||
**饮食打卡**
|
||||
|
||||
- `checkin.vue` -> `/pages/tool/checkin` (打卡)
|
||||
- `checkin-publish.vue` -> `/pages/tool/checkin-publish` (饮食打卡)
|
||||
- `checkin-detail.vue` -> `/pages/tool/checkin-detail` (打卡详情)
|
||||
- `checkin-copy.vue` -> `/pages/tool/checkin-copy` (一键借鉴打卡)
|
||||
- `dietary-records.vue` -> `/pages/tool/dietary-records` (饮食记录)
|
||||
|
||||
**社区帖子**
|
||||
|
||||
- `post-detail.vue` -> `/pages/tool/post-detail` (帖子详情)
|
||||
|
||||
**食谱**
|
||||
|
||||
- `recipe-detail.vue` -> `/pages/tool/recipe-detail` (食谱详情)
|
||||
|
||||
**用户与积分**
|
||||
|
||||
- `my-profile.vue` -> `/pages/tool/my-profile` (我的)
|
||||
- `points-rules.vue` -> `/pages/tool/points-rules` (积分规则)
|
||||
- `invite-rewards.vue` -> `/pages/tool/invite-rewards` (邀请有礼)
|
||||
- `welcome-gift.vue` -> `/pages/tool/welcome-gift` (会员福利)
|
||||
|
||||
---
|
||||
|
||||
## 二、测试环境与前置条件
|
||||
|
||||
- 测试平台:微信开发者工具 + 真机(iOS / Android)
|
||||
- 网络环境:WiFi 正常网络 + 弱网 (3G) + 断网
|
||||
- 账号准备:已登录账号 / 未登录访客
|
||||
- 后端接口:已部署测试环境 API(`tool/*` 全部接口可用)
|
||||
- 测试数据:至少 1 条已有计算结果、1 条打卡记录、1 条社区帖子
|
||||
|
||||
---
|
||||
|
||||
## 三、功能测试用例
|
||||
|
||||
### 3.1 Tool 首页 (tool_main/index.vue)
|
||||
|
||||
**页面加载**
|
||||
|
||||
- 页面打开后,并发调用 `getRecommendedRecipes`、`getRecommendedKnowledge`、`getUserHealthStatus` 三个接口,数据正常展示
|
||||
- 接口返回失败时(单个/全部),页面不白屏,其他模块正常展示
|
||||
- 弱网环境下 loading 状态正常
|
||||
|
||||
**用户信息区域**
|
||||
|
||||
- 已登录:显示用户头像、昵称、健康档案状态
|
||||
- 未登录:显示"请点击登录",点击触发登录弹窗
|
||||
- 点击头像跳转到 `/pages/tool/my-profile`
|
||||
- 点击"打卡"按钮跳转到 `/pages/tool/checkin`
|
||||
|
||||
**四宫格工具入口**
|
||||
|
||||
- "食谱计算器" 跳转到 `/pages/tool/calculator`
|
||||
- "AI营养师" 跳转到 `/pages/tool/ai-nutritionist`
|
||||
- "食物百科" 跳转到 `/pages/tool/food-encyclopedia`
|
||||
- "营养知识" 跳转到 `/pages/tool/nutrition-knowledge`
|
||||
|
||||
**推荐食谱区域**
|
||||
|
||||
- 展示 2 条推荐食谱(封面图、标题)
|
||||
- 点击食谱卡片跳转 `/pages/tool/recipe-detail?id=xxx`
|
||||
- 点击"更多"显示 toast "食谱列表功能开发中"
|
||||
- 无数据时不展示该区域 / 展示空态
|
||||
|
||||
**健康知识区域**
|
||||
|
||||
- 展示 2 条推荐知识(标题、摘要)
|
||||
- 点击知识条目跳转 `/pages/tool/nutrition-knowledge?id=xxx`
|
||||
- 点击"更多"跳转 `/pages/tool/nutrition-knowledge`
|
||||
|
||||
**推广卡片**
|
||||
|
||||
- "慢生活营养专家" 卡片正常展示
|
||||
- 点击跳转 `/pages/tool/welcome-gift`
|
||||
|
||||
---
|
||||
|
||||
### 3.2 食谱计算器 (calculator.vue)
|
||||
|
||||
**表单输入**
|
||||
|
||||
- 性别选择(男/女)单选正常
|
||||
- 年龄、身高、干体重、血肌酐输入框:只允许数字,限制合理范围
|
||||
- 透析状态切换(是/否),选"是"后透析类型选项出现
|
||||
- 透析类型(血液透析/腹膜透析)单选正常
|
||||
- 所有必填项为空时,点击"开始计算"给出校验提示
|
||||
|
||||
**计算提交**
|
||||
|
||||
- 填写完整后点击"开始计算",调用 `calculateNutrition` 接口(`POST tool/calculator/calculate`)
|
||||
- 接口返回成功后跳转到 `/pages/tool/calculator-result?id=xxx`
|
||||
- 接口返回失败时 toast 提示错误信息
|
||||
- 重复点击防抖(按钮 loading 状态)
|
||||
|
||||
---
|
||||
|
||||
### 3.3 营养计算结果 (calculator-result.vue)
|
||||
|
||||
**页面加载**
|
||||
|
||||
- 携带 `id` 参数进入,调用 `getCalculatorResult(id)` 获取数据
|
||||
- Loading 状态展示正常
|
||||
- 无结果 / id 无效时展示 error 状态
|
||||
|
||||
**健康概览 Tab**
|
||||
|
||||
- 展示 eGFR 值、体重、BMI、CKD 分期数据卡片
|
||||
- 营养目标(蛋白质、热量、钠、钾、磷等)正确展示
|
||||
- 食物份量列表正确展示
|
||||
|
||||
**膳食计划 Tab**
|
||||
|
||||
- Tab 切换正常(健康概览 / 膳食计划)
|
||||
- 早餐、午餐、晚餐 三餐方案展示正确
|
||||
- 每餐菜品名称、份量、图片展示
|
||||
- 菜品图片加载(含 AI 生成图片、OSS 压缩图片)正常
|
||||
- 温馨提示区域展示
|
||||
|
||||
**操作按钮**
|
||||
|
||||
- "采纳方案" 按钮调用 `adoptNutritionPlan(resultId)`(`POST tool/calculator/adopt`)
|
||||
- 采纳成功后提示并更新状态
|
||||
- "联系营养师" 按钮跳转 AI 营养师或显示联系方式
|
||||
|
||||
---
|
||||
|
||||
### 3.4 AI 营养师 (ai-nutritionist.vue)
|
||||
|
||||
**对话界面**
|
||||
|
||||
- 页面打开后展示推广 banner
|
||||
- 快捷问题列表展示,点击快捷问题自动发送
|
||||
- 消息列表(scroll-view)正常滚动,新消息自动滚到底部
|
||||
|
||||
**文字对话**
|
||||
|
||||
- 输入文字后点击发送按钮,消息气泡出现(用户侧)
|
||||
- AI 回复消息气泡出现(AI 侧),打字指示器展示
|
||||
- 使用 Coze API 流程:`cozeChat` -> `cozeRetrieveChat` -> `cozeMessageList`
|
||||
- 空消息不允许发送
|
||||
|
||||
**语音输入**
|
||||
|
||||
- 点击麦克风按钮开始录音
|
||||
- 录音完成后上传语音 (`uploadFile`) 并调用 ASR (`createAsrTask` -> `queryAsrStatus`)
|
||||
- 语音识别结果转为文字并发送
|
||||
- 录音授权拒绝时提示用户
|
||||
|
||||
**图片发送**
|
||||
|
||||
- 点击相机按钮选择/拍照图片
|
||||
- 图片上传后通过 Coze 接口发送给 AI
|
||||
- 图片预览功能正常
|
||||
|
||||
**异常处理**
|
||||
|
||||
- 网络断开时发送消息提示网络错误
|
||||
- AI 回复超时处理
|
||||
- 会话 ID 管理正常(多次对话保持上下文)
|
||||
|
||||
---
|
||||
|
||||
### 3.5 食物百科 (food-encyclopedia.vue)
|
||||
|
||||
- 页面加载调用 `getFoodList` 接口,展示食物列表
|
||||
- 顶部搜索框输入关键词,调用 `searchFood` 接口
|
||||
- 搜索防抖正常(不会每个字符触发请求)
|
||||
- 横向分类滑动切换(全部/谷物/蔬菜/水果/肉类/海鲜)
|
||||
- 切换分类后列表刷新
|
||||
- 食物卡片展示:图片、名称、安全标签、营养信息
|
||||
- 点击食物卡片跳转 `/pages/tool/food-detail`
|
||||
- 列表分页加载(上拉加载更多)
|
||||
- 空搜索结果展示空态
|
||||
|
||||
---
|
||||
|
||||
### 3.6 食物详情 (food-detail.vue)
|
||||
|
||||
- 页面加载展示食物图片、分类、安全标签
|
||||
- 关键营养素宫格展示
|
||||
- 营养成分表完整展示
|
||||
- "相似食物" 按钮功能
|
||||
- 分享按钮功能正常
|
||||
|
||||
---
|
||||
|
||||
### 3.7 营养知识 (nutrition-knowledge.vue)
|
||||
|
||||
- Tab 切换:营养素 / 饮食指导 / 文章
|
||||
- 各 Tab 调用 `getKnowledgeList` 接口(type 参数不同)
|
||||
- 营养素列表展示,点击跳转 `/pages/tool/nutrient-detail`
|
||||
- 饮食指导列表展示
|
||||
- 文章列表展示
|
||||
- 列表分页(上拉加载更多)
|
||||
- 携带 `id` 参数进入时自动定位到对应内容
|
||||
|
||||
---
|
||||
|
||||
### 3.8 营养素详情 (nutrient-detail.vue)
|
||||
|
||||
- 页面通过本地营养素数据 map 渲染
|
||||
- 展示:营养素头部信息、状态卡片、重要性、摄入建议、食物来源、风险提示、建议
|
||||
- 底部免责声明展示
|
||||
- 不存在的营养素名称进入时展示空态或提示
|
||||
|
||||
---
|
||||
|
||||
### 3.9 打卡首页 (checkin.vue)
|
||||
|
||||
- 积分展示区域,调用 `getUserPoints` 接口
|
||||
- 7 日连续打卡圈展示,调用 `getCheckinStreak` 接口
|
||||
- "积分规则"按钮跳转 `/pages/tool/points-rules`
|
||||
- "立即打卡"按钮跳转 `/pages/tool/checkin-publish`
|
||||
- 任务列表展示,调用 `getCheckinTasks` 接口
|
||||
- 兑换列表展示
|
||||
- 底部提示 banner 展示
|
||||
|
||||
---
|
||||
|
||||
### 3.10 饮食打卡发布 (checkin-publish.vue)
|
||||
|
||||
**图片上传**
|
||||
|
||||
- 点击添加照片,打开相册/相机
|
||||
- 最多上传 6 张图片
|
||||
- 图片预览和删除功能
|
||||
- 图片上传调用 `uploadFile` 接口
|
||||
|
||||
**餐次选择**
|
||||
|
||||
- 早餐/午餐/晚餐/加餐 选择器正常
|
||||
|
||||
**语音备注**
|
||||
|
||||
- 录音功能正常(同 AI 营养师)
|
||||
- 语音识别结果填入备注
|
||||
|
||||
**AI 功能开关**
|
||||
|
||||
- AI 视频生成开关切换
|
||||
- 开启后提交时调用 `createImageToVideoTask` 或 `createTextToVideoTask`
|
||||
|
||||
**提交**
|
||||
|
||||
- 点击"发布"调用 `submitCheckin` 接口(`POST tool/checkin/submit`)
|
||||
- 提交成功后跳转打卡详情或返回打卡首页
|
||||
- 无图片时提示必须上传
|
||||
- 防止重复提交
|
||||
|
||||
---
|
||||
|
||||
### 3.11 打卡详情 (checkin-detail.vue)
|
||||
|
||||
- 携带 `id` 参数进入,调用 `getCheckinDetail(id)`
|
||||
- 图片轮播(swiper)正常,底部图片计数器
|
||||
- 餐次标签展示
|
||||
- 作者信息、积分徽章展示
|
||||
- 描述文本展示
|
||||
- AI 营养分析卡片展示
|
||||
- "分享到社区"按钮功能
|
||||
|
||||
---
|
||||
|
||||
### 3.12 一键借鉴打卡 (checkin-copy.vue)
|
||||
|
||||
- 原始记录卡片展示
|
||||
- 照片网格选择(可勾选/取消勾选)
|
||||
- 备注文本编辑
|
||||
- AI 视频开关
|
||||
- 取消/确认按钮
|
||||
- 注意:当前 `loadOriginalRecord` 为 TODO,需确认数据来源是否已接通
|
||||
|
||||
---
|
||||
|
||||
### 3.13 饮食记录 (dietary-records.vue)
|
||||
|
||||
- 自定义导航栏展示(`navigationStyle: "custom"`)
|
||||
- Tab 切换:全部 / 早餐 / 午餐 / 晚餐
|
||||
- 调用 `getCheckinList` 接口,切换 Tab 传不同 `mealType`
|
||||
- 列表项展示:图片、餐次、备注、积分、AI 分析
|
||||
- 上拉加载更多
|
||||
- 空态展示(无记录时)
|
||||
- 下拉刷新
|
||||
|
||||
---
|
||||
|
||||
### 3.14 社区帖子详情 (post-detail.vue)
|
||||
|
||||
**内容展示**
|
||||
|
||||
- 调用 `getCommunityDetail(id)` 获取帖子
|
||||
- 图片轮播、餐次标签、标题、评分、描述、标签展示
|
||||
- 作者信息区、关注按钮
|
||||
- 营养统计数据展示
|
||||
- AI 评论展示
|
||||
|
||||
**互动功能**
|
||||
|
||||
- 点赞:调用 `toggleLike`,状态切换正常
|
||||
- 收藏:调用 `toggleCollect`,状态切换正常
|
||||
- 关注:调用 `toggleFollow`,状态切换正常
|
||||
- "一键借鉴打卡"按钮跳转 `/pages/tool/checkin-copy`
|
||||
|
||||
**评论功能**
|
||||
|
||||
- 调用 `getCommentList` 加载评论列表
|
||||
- 底部输入框发表评论,调用 `addComment`
|
||||
- 评论成功后列表刷新
|
||||
|
||||
**相关帖子**
|
||||
|
||||
- 调用 `getCommunityList` 展示相关帖子
|
||||
- 点击相关帖子跳转对应详情
|
||||
|
||||
---
|
||||
|
||||
### 3.15 食谱详情 (recipe-detail.vue)
|
||||
|
||||
- 封面图展示
|
||||
- 食谱信息(名称、难度徽章)展示
|
||||
- 营养团队卡片展示
|
||||
- 简介文本展示
|
||||
- 营养数据宫格展示
|
||||
- 膳食方案(早/午/晚)展示
|
||||
- 注意事项列表展示
|
||||
- 温馨提示展示
|
||||
- 底部操作栏:点赞、收藏、打卡按钮
|
||||
- 注意:当前 `loadRecipeData` 为 TODO,需确认数据加载是否已接通
|
||||
|
||||
---
|
||||
|
||||
### 3.16 我的 (my-profile.vue)
|
||||
|
||||
- 用户头像、昵称展示
|
||||
- 统计数据(打卡次数、积分、关注数)展示
|
||||
- "我的健康" 区域展示
|
||||
- "我的内容" 区域展示
|
||||
- "工具与服务" 区域展示
|
||||
- "设置" 区域展示
|
||||
- 退出登录按钮功能
|
||||
- 各导航项跳转正确
|
||||
|
||||
---
|
||||
|
||||
### 3.17 积分规则 (points-rules.vue)
|
||||
|
||||
- 页面为纯静态内容,scroll-view 滚动正常
|
||||
- 展示区域:赚取规则、使用方式、温馨提示、注意事项
|
||||
- 文字排版在不同机型上无溢出/截断
|
||||
|
||||
---
|
||||
|
||||
### 3.18 邀请有礼 (invite-rewards.vue)
|
||||
|
||||
- 邀请卡片展示
|
||||
- 奖励盒子列表展示
|
||||
- 邀请按钮功能(触发分享或复制链接)
|
||||
- 海报按钮功能
|
||||
- 权益说明列表展示
|
||||
- 邀请步骤展示
|
||||
- 注意:当前为静态占位数据,确认是否有接口接通
|
||||
|
||||
---
|
||||
|
||||
### 3.19 会员福利 (welcome-gift.vue)
|
||||
|
||||
- 礼品 banner 展示
|
||||
- 礼品项列表展示
|
||||
- 二维码展示
|
||||
- 领取步骤说明
|
||||
- "添加微信" 按钮功能(复制微信号或打开二维码)
|
||||
- 免责声明展示
|
||||
|
||||
---
|
||||
|
||||
## 四、通用测试项(每页必测)
|
||||
|
||||
### 4.1 页面基础
|
||||
|
||||
- 页面标题正确(与 pages.json 配置一致)
|
||||
- 返回按钮功能正常
|
||||
- 页面下拉不出现橡皮筋效果(或符合预期)
|
||||
|
||||
### 4.2 登录态
|
||||
|
||||
- 未登录访问需登录页面时,弹出登录引导
|
||||
- 登录后自动返回原页面并刷新数据
|
||||
|
||||
### 4.3 网络异常
|
||||
|
||||
- 断网时接口请求给出友好提示
|
||||
- 弱网环境下 loading 态不丢失
|
||||
- 恢复网络后可重试/刷新
|
||||
|
||||
### 4.4 UI 适配
|
||||
|
||||
- iPhone SE / iPhone 14 Pro Max / 小屏安卓 / 平板 屏幕适配
|
||||
- 安全区域(底部 Home Indicator)适配
|
||||
- 暗色模式适配(如支持)
|
||||
- 长文本不溢出、不截断
|
||||
|
||||
### 4.5 性能
|
||||
|
||||
- 页面首屏加载时间 < 2s
|
||||
- 列表页滚动流畅,无卡顿
|
||||
- 图片懒加载正常
|
||||
|
||||
---
|
||||
|
||||
## 五、关键 API 接口清单
|
||||
|
||||
以下接口在前端测试中需确认可用:
|
||||
|
||||
**首页**
|
||||
|
||||
- `GET tool/home/recipes`
|
||||
- `GET tool/home/knowledge`
|
||||
- `GET tool/home/health-status`
|
||||
|
||||
**食谱计算器**
|
||||
|
||||
- `POST tool/calculator/calculate`
|
||||
- `GET tool/calculator/result/{id}`
|
||||
- `POST tool/calculator/adopt`
|
||||
|
||||
**AI 营养师** (通过 models-api.js)
|
||||
|
||||
- Coze 系列接口 (cozeChat / cozeRetrieveChat / cozeMessageList)
|
||||
- ASR 接口 (createAsrTask / queryAsrStatus)
|
||||
- 文件上传 (uploadFile)
|
||||
|
||||
**打卡**
|
||||
|
||||
- `POST tool/checkin/submit`
|
||||
- `GET tool/checkin/list`
|
||||
- `GET tool/checkin/detail/{id}`
|
||||
- `GET tool/checkin/streak`
|
||||
- `GET tool/checkin/tasks`
|
||||
- `POST tool/checkin/copy`
|
||||
- `POST tool/checkin/learn`
|
||||
|
||||
**食物百科**
|
||||
|
||||
- `GET tool/food/search`
|
||||
- `GET tool/food/list`
|
||||
- `GET tool/food/detail/{id}`
|
||||
- `GET tool/food/similar/{id}`
|
||||
|
||||
**营养知识**
|
||||
|
||||
- `GET tool/knowledge/list`
|
||||
- `GET tool/knowledge/detail/{id}`
|
||||
- `GET tool/knowledge/nutrient/{name}`
|
||||
|
||||
**社区**
|
||||
|
||||
- `GET tool/community/detail/{id}`
|
||||
- `GET tool/community/list`
|
||||
- `POST tool/community/like`
|
||||
- `POST tool/community/collect`
|
||||
- `POST tool/community/follow`
|
||||
- `POST tool/community/comment`
|
||||
- `GET tool/community/comment/list/{postId}`
|
||||
|
||||
**积分**
|
||||
|
||||
- `GET tool/points/info`
|
||||
- `GET tool/points/rules`
|
||||
|
||||
**文件上传**
|
||||
|
||||
- `POST tool/upload/image`
|
||||
- `POST tool/upload/voice`
|
||||
|
||||
---
|
||||
|
||||
## 六、已知待完善项
|
||||
|
||||
以下页面有 TODO/未接通功能,测试时需标注为"受限测试":
|
||||
|
||||
- `checkin-copy.vue`: `loadOriginalRecord` 为 TODO,数据加载待实现
|
||||
- `food-detail.vue`: `loadFoodData` 为空实现,静态数据
|
||||
- `recipe-detail.vue`: `loadRecipeData` 为 TODO,静态数据
|
||||
- `tool_main/index.vue`: "食谱列表" 入口仅 toast 提示开发中
|
||||
- `invite-rewards.vue`: 邀请统计为静态占位数据
|
||||
|
||||
107
.cursor/plans/修复kieai回调反序列化问题_85d41db5.plan.md
Normal file
107
.cursor/plans/修复kieai回调反序列化问题_85d41db5.plan.md
Normal file
@@ -0,0 +1,107 @@
|
||||
---
|
||||
name: 修复KieAI回调反序列化问题
|
||||
overview: 修复 KieAI 回调接口的 JSON 反序列化错误,确保视频生成完成后能正确更新 article 表中的 video_url 和 status_task 字段
|
||||
todos: []
|
||||
isProject: false
|
||||
---
|
||||
|
||||
# 修复 KieAI 回调反序列化问题
|
||||
|
||||
## 问题分析
|
||||
|
||||
通过日志分析发现:
|
||||
|
||||
```
|
||||
Could not resolve parameter [0]... Problem deserializing 'setterless' property 'resultUrls':
|
||||
get method returned null
|
||||
```
|
||||
|
||||
KieAI 的回调请求已经到达后端 `/api/front/kieai/callback`,但是 Jackson 在反序列化 `KieAIQueryTaskResponse` 时失败,导致:
|
||||
|
||||
1. 回调处理方法 `handleCallback` 无法正常执行
|
||||
2. `handleTaskCallback` 中更新 article 表的逻辑从未运行
|
||||
3. 数据库中的 video_url 和 status_task 字段保持未更新状态
|
||||
|
||||
## 技术细节
|
||||
|
||||
**反序列化失败原因:**
|
||||
|
||||
- `KieAIQueryTaskResponse.TaskData` 类使用了 `@Data` 注解(Lombok)
|
||||
- `resultUrls` 字段虽然有 getter,但 Jackson 在反序列化嵌套的 List 类型时需要显式的 setter
|
||||
- 当前的便捷方法 `getResultUrls()` 定义在父类,不满足 Jackson 对 `TaskData.resultUrls` 字段的反序列化要求
|
||||
|
||||
## 修复方案
|
||||
|
||||
### 文件:`[KieAIQueryTaskResponse.java](msh_crmeb_22/crmeb-common/src/main/java/com/zbkj/common/response/kieai/KieAIQueryTaskResponse.java)`
|
||||
|
||||
**当前问题代码(第 73-113 行):**
|
||||
|
||||
```java
|
||||
@Data
|
||||
public static class TaskData implements Serializable {
|
||||
// ... other fields ...
|
||||
|
||||
@ApiModelProperty(value = "结果URL数组(视频/图片地址)")
|
||||
private java.util.List<String> resultUrls; // Lombok @Data 应该生成 setter,但可能被优化掉了
|
||||
|
||||
// ... other fields ...
|
||||
}
|
||||
```
|
||||
|
||||
**修复方法:**
|
||||
|
||||
添加显式的 setter 方法到 `TaskData` 类中:
|
||||
|
||||
```java
|
||||
@Data
|
||||
public static class TaskData implements Serializable {
|
||||
// ... existing fields ...
|
||||
|
||||
@ApiModelProperty(value = "结果URL数组(视频/图片地址)")
|
||||
private java.util.List<String> resultUrls;
|
||||
|
||||
// 显式添加 setter 方法以确保 Jackson 反序列化正常工作
|
||||
public void setResultUrls(java.util.List<String> resultUrls) {
|
||||
this.resultUrls = resultUrls;
|
||||
}
|
||||
|
||||
// ... other fields ...
|
||||
}
|
||||
```
|
||||
|
||||
**为什么这样修复:**
|
||||
|
||||
1. Lombok 的 `@Data` 注解理论上会自动生成 getter/setter,但在某些情况下(如嵌套泛型、继承关系等),Jackson 可能无法正确识别
|
||||
2. 显式声明 setter 方法可以确保 Jackson 在反序列化时能够正确填充 `resultUrls` 字段
|
||||
3. 这不会与 Lombok 冲突,显式方法优先级更高
|
||||
|
||||
## 验证方法
|
||||
|
||||
修复后,当 KieAI 发送回调时:
|
||||
|
||||
1. 查看日志,应该看到:
|
||||
```
|
||||
接收KieAI任务回调,任务ID: xxx
|
||||
处理KieAI任务回调,任务ID: xxx, 状态: success
|
||||
找到article记录,articleId: xxx, videoUrl: xxx, statusTask: 1
|
||||
更新article视频信息成功
|
||||
```
|
||||
2. 数据库验证:
|
||||
```sql
|
||||
SELECT id, task_id, video_url, status_task FROM eb_article WHERE task_id = 'xxx';
|
||||
```
|
||||
应该看到 `video_url` 被正确填充,`status_task` 为 1(成功)或 2(失败)
|
||||
3. 前端验证:
|
||||
- 访问 `http://127.0.0.1:20822/api/front/tool/community/detail/302`
|
||||
- 响应中应该包含 `videoUrl` 和 `hasVideo: true`
|
||||
|
||||
## 其他说明
|
||||
|
||||
**现有的回调处理逻辑是正确的:**
|
||||
|
||||
- `ToolKieAIServiceImpl.handleTaskCallback()` 方法(第 148-187 行)逻辑完整
|
||||
- 通过 `taskId` 查询 article 记录
|
||||
- 解析 `resultUrls` 并更新 `video_url` 和 `status_task`
|
||||
- 日志记录完善
|
||||
|
||||
问题仅在于回调数据无法被正确反序列化,导致方法未被调用。
|
||||
459
.cursor/plans/打卡视频生成与展示_12778af3.plan.md
Normal file
459
.cursor/plans/打卡视频生成与展示_12778af3.plan.md
Normal file
@@ -0,0 +1,459 @@
|
||||
---
|
||||
name: 打卡视频生成与展示
|
||||
overview: 实现打卡勾选生成视频后,在饮食记录页面定时查询视频任务状态并显示,在打卡详情支持视频播放,勾选生成视频的打卡帖子在社区最新tab显示
|
||||
todos:
|
||||
- id: backend-checkin-article
|
||||
content: 修改打卡提交逻辑,勾选视频时创建eb_article记录存储taskId
|
||||
status: completed
|
||||
- id: backend-list-api
|
||||
content: 更新打卡列表/详情接口,关联article表返回videoUrl和videoStatus
|
||||
status: completed
|
||||
- id: backend-callback
|
||||
content: 完善KieAI回调处理,更新article的video_url和status_task
|
||||
status: completed
|
||||
- id: backend-community
|
||||
content: 更新社区列表接口,关联打卡记录的视频信息
|
||||
status: completed
|
||||
- id: frontend-dietary-records
|
||||
content: 饮食记录页面增加视频状态显示和定时轮询逻辑
|
||||
status: completed
|
||||
- id: frontend-detail-video
|
||||
content: 打卡详情页增加视频播放器组件
|
||||
status: completed
|
||||
- id: frontend-community-badge
|
||||
content: 社区页面卡片增加视频标记
|
||||
status: completed
|
||||
- id: frontend-api
|
||||
content: 新增getVideoTaskStatus API方法
|
||||
status: completed
|
||||
isProject: false
|
||||
---
|
||||
|
||||
# 打卡视频生成与展示功能实现计划
|
||||
|
||||
## 背景分析
|
||||
|
||||
根据代码探索,当前系统已具备:
|
||||
|
||||
- **打卡发布**: `[checkin-publish.vue](msh_single_uniapp/pages/tool/checkin-publish.vue)` 支持勾选"生成打卡视频分享到社区",会调用KieAI创建视频任务并返回 `taskId`
|
||||
- **数据存储**: `eb_user_sign` 表存储 `task_id` 和 `enable_ai_video` 字段,`eb_article` 表存储视频URL和任务状态
|
||||
- **视频生成**: 通过 `[ToolSora2Service](msh_crmeb_22/crmeb-service/src/main/java/com/zbkj/service/service/impl/tool/ToolSora2ServiceImpl.java)` 调用KieAI API
|
||||
- **状态查询接口**: `GET /api/front/kieai/video/task/{taskId}` 已存在
|
||||
|
||||
**缺失功能**:
|
||||
|
||||
1. 打卡时未创建 `eb_article` 记录来存储视频
|
||||
2. 饮食记录页面不显示视频任务状态
|
||||
3. 打卡详情页不支持视频播放
|
||||
4. 社区帖子未关联视频信息
|
||||
|
||||
## 实现方案
|
||||
|
||||
### 数据库层面
|
||||
|
||||
#### 1. 修改表结构
|
||||
|
||||
**eb_user_sign表**: 已有 `task_id`, `enable_ai_video` 字段,无需修改
|
||||
|
||||
**v2_community_posts表**: 已有 `video_url` 字段(从SQL看到),需确认Java实体类是否包含
|
||||
|
||||
**eb_article表**: 已有 `video_url`, `task_id`, `status_task`, `check_in_record_id` 字段
|
||||
|
||||
#### 2. 数据流设计
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[用户勾选生成视频并发布打卡] --> B[前端:调用KieAI创建视频任务]
|
||||
B --> C[前端:获得taskId]
|
||||
C --> D[前端:提交打卡 带taskId+enableAIVideo]
|
||||
D --> E[后端:保存eb_user_sign记录]
|
||||
E --> F[后端:创建eb_article记录]
|
||||
F --> G[eb_article: taskId, statusTask=0, checkInRecordId]
|
||||
|
||||
H[饮食记录页面] --> I[加载打卡列表API]
|
||||
I --> J[返回 taskId, enableAIVideo, videoUrl]
|
||||
J --> K[前端:检测有taskId且无videoUrl]
|
||||
K --> L[启动定时轮询 /kieai/video/task/taskId]
|
||||
L --> M{任务状态?}
|
||||
M -->|进行中| N[显示生成中状态]
|
||||
M -->|完成| O[更新videoUrl到article]
|
||||
M -->|失败| P[显示失败状态]
|
||||
O --> Q[页面刷新后显示视频]
|
||||
|
||||
R[打卡详情页] --> S[查询详情API]
|
||||
S --> T[返回taskId+videoUrl]
|
||||
T --> U{有videoUrl?}
|
||||
U -->|是| V[显示video标签播放]
|
||||
U -->|否+有taskId| W[显示生成中状态]
|
||||
|
||||
X[社区最新tab] --> Y[查询v2_community_posts]
|
||||
Y --> Z[关联eb_user_sign.enableAIVideo]
|
||||
Z --> AA[显示有视频标记的帖子]
|
||||
```
|
||||
|
||||
|
||||
|
||||
### 后端改动
|
||||
|
||||
#### 1. 更新打卡提交逻辑
|
||||
|
||||
`[ToolCheckinServiceImpl.submit()](msh_crmeb_22/crmeb-service/src/main/java/com/zbkj/service/service/impl/tool/ToolCheckinServiceImpl.java)`
|
||||
|
||||
- 在打卡成功后,如果 `enableAIVideo=1` 且有 `taskId`,创建 `eb_article` 记录:
|
||||
- `type = 2` (视频类型)
|
||||
- `task_id = taskId`
|
||||
- `status_task = 0` (已创建)
|
||||
- `check_in_record_id = userSign.id`
|
||||
- `title` = 打卡备注或默认标题
|
||||
- 其他字段按现有逻辑填充
|
||||
|
||||
#### 2. 更新打卡列表接口
|
||||
|
||||
`[ToolCheckinServiceImpl.getList()](msh_crmeb_22/crmeb-service/src/main/java/com/zbkj/service/service/impl/tool/ToolCheckinServiceImpl.java)`
|
||||
|
||||
- 查询时关联 `eb_article` 表(通过 `eb_user_sign.task_id = eb_article.task_id`)
|
||||
- 返回字段增加:
|
||||
- `videoUrl`: 从 `eb_article.video_url` 获取
|
||||
- `videoStatus`: 从 `eb_article.status_task` 获取 (0=生成中, 1=完成, 2=失败)
|
||||
- `taskId`: 已有
|
||||
- `enableAIVideo`: 已有
|
||||
|
||||
#### 3. 更新打卡详情接口
|
||||
|
||||
`[ToolCheckinServiceImpl.getDetail()](msh_crmeb_22/crmeb-service/src/main/java/com/zbkj/service/service/impl/tool/ToolCheckinServiceImpl.java)`
|
||||
|
||||
- 增加返回 `videoUrl` 和 `videoStatus` 字段
|
||||
|
||||
#### 4. 完善KieAI回调处理
|
||||
|
||||
`[KieAIController.handleCallback()](msh_crmeb_22/crmeb-front/src/main/java/com/zbkj/front/controller/KieAIController.java)`
|
||||
|
||||
- 当前只记录日志,需改为:
|
||||
- 解析回调参数获取 `taskId`, `state`, `result_urls`
|
||||
- 根据 `taskId` 更新 `eb_article` 表:
|
||||
- `status_task = 1` (成功) 或 `2` (失败)
|
||||
- `video_url = result_urls[0]`
|
||||
|
||||
参考 `models-integration` 项目的实现:
|
||||
|
||||
```java
|
||||
articleMapper.updateVideoUrlAndTaskStatusByTaskId(taskId, videoUrl, statusTask);
|
||||
```
|
||||
|
||||
#### 5. 更新社区列表接口
|
||||
|
||||
`[ToolCommunityServiceImpl.getList()](msh_crmeb_22/crmeb-service/src/main/java/com/zbkj/service/service/impl/tool/ToolCommunityServiceImpl.java)`
|
||||
|
||||
- 当前已关联 `eb_user_sign` 获取 `mealType`
|
||||
- 同样方式关联获取 `enable_ai_video` 和 `task_id`
|
||||
- 通过 `task_id` 关联 `eb_article` 获取 `video_url`
|
||||
- 返回字段增加:
|
||||
- `hasVideo`: boolean (是否有视频)
|
||||
- `videoUrl`: 视频地址
|
||||
- `enableAIVideo`: 是否启用了视频生成
|
||||
|
||||
#### 6. 更新V2CommunityPost实体类
|
||||
|
||||
`[V2CommunityPost.java](msh_crmeb_22/crmeb-common/src/main/java/com/zbkj/common/model/tool/V2CommunityPost.java)`
|
||||
|
||||
- 确认是否已有 `videoUrl` 字段,若无则添加:
|
||||
|
||||
```java
|
||||
@ApiModelProperty(value = "视频地址")
|
||||
private String videoUrl;
|
||||
```
|
||||
|
||||
### 前端改动
|
||||
|
||||
#### 1. 饮食记录页面增加视频状态显示
|
||||
|
||||
`[dietary-records.vue](msh_single_uniapp/pages/tool/dietary-records.vue)`
|
||||
|
||||
**显示逻辑**:
|
||||
|
||||
- 列表项增加视频状态角标:
|
||||
- 有 `videoUrl`: 显示"📹视频"标记
|
||||
- 有 `taskId` 但无 `videoUrl`: 显示"⏳生成中"或进度条
|
||||
- `videoStatus = 2`: 显示"❌生成失败"
|
||||
|
||||
**轮询逻辑**:
|
||||
|
||||
```javascript
|
||||
data() {
|
||||
return {
|
||||
pollingTasks: [], // 需要轮询的任务列表
|
||||
pollingTimer: null
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 启动轮询
|
||||
startPolling() {
|
||||
if (this.pollingTimer) return;
|
||||
this.pollingTimer = setInterval(() => {
|
||||
this.pollVideoTasks();
|
||||
}, 5000); // 每5秒查询一次
|
||||
},
|
||||
|
||||
// 轮询视频任务状态
|
||||
async pollVideoTasks() {
|
||||
const tasksToCheck = this.recordList.filter(item =>
|
||||
item.enableAIVideo && item.taskId && !item.videoUrl
|
||||
);
|
||||
|
||||
if (tasksToCheck.length === 0) {
|
||||
this.stopPolling();
|
||||
return;
|
||||
}
|
||||
|
||||
for (const task of tasksToCheck) {
|
||||
try {
|
||||
const res = await getVideoTaskStatus(task.taskId);
|
||||
if (res.code === 200 && res.data) {
|
||||
const status = res.data.state;
|
||||
if (status === 'success') {
|
||||
// 刷新列表以获取最新视频URL
|
||||
this.loadRecordList();
|
||||
break;
|
||||
} else if (status === 'failed') {
|
||||
// 标记失败
|
||||
task.videoStatus = 2;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('查询视频任务失败:', error);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 停止轮询
|
||||
stopPolling() {
|
||||
if (this.pollingTimer) {
|
||||
clearInterval(this.pollingTimer);
|
||||
this.pollingTimer = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**生命周期**:
|
||||
|
||||
```javascript
|
||||
onLoad() {
|
||||
this.loadRecordList();
|
||||
this.startPolling();
|
||||
},
|
||||
onUnload() {
|
||||
this.stopPolling();
|
||||
},
|
||||
onShow() {
|
||||
// 从其他页面返回时重新检查
|
||||
this.loadRecordList();
|
||||
this.startPolling();
|
||||
}
|
||||
```
|
||||
|
||||
**模板增加视频状态标记**:
|
||||
|
||||
```vue
|
||||
<view class="video-status-badge" v-if="item.enableAIVideo">
|
||||
<text v-if="item.videoUrl" class="status-success">📹 有视频</text>
|
||||
<text v-else-if="item.videoStatus === 2" class="status-failed">❌ 生成失败</text>
|
||||
<text v-else class="status-pending">⏳ 生成中</text>
|
||||
</view>
|
||||
```
|
||||
|
||||
#### 2. 打卡详情页增加视频播放
|
||||
|
||||
`[checkin-detail.vue](msh_single_uniapp/pages/tool/checkin-detail.vue)`
|
||||
|
||||
**数据结构更新**:
|
||||
|
||||
```javascript
|
||||
data() {
|
||||
return {
|
||||
checkinData: {
|
||||
// ...现有字段
|
||||
videoUrl: '',
|
||||
taskId: '',
|
||||
enableAIVideo: false,
|
||||
videoStatus: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**模板增加视频播放器**:
|
||||
|
||||
```vue
|
||||
<!-- 在图片轮播区域下方或上方增加 -->
|
||||
<view class="video-section" v-if="checkinData.videoUrl">
|
||||
<view class="video-header">
|
||||
<text class="video-icon">🎬</text>
|
||||
<text class="video-title">打卡视频</text>
|
||||
</view>
|
||||
<video
|
||||
class="checkin-video"
|
||||
:src="checkinData.videoUrl"
|
||||
controls
|
||||
:show-center-play-btn="true"
|
||||
:enable-progress-gesture="true"
|
||||
object-fit="contain"
|
||||
></video>
|
||||
</view>
|
||||
|
||||
<!-- 视频生成中状态 -->
|
||||
<view class="video-generating" v-else-if="checkinData.enableAIVideo && checkinData.taskId">
|
||||
<text class="generating-icon">⏳</text>
|
||||
<text class="generating-text">视频生成中,请稍后...</text>
|
||||
</view>
|
||||
```
|
||||
|
||||
**formatCheckinData更新**:
|
||||
|
||||
```javascript
|
||||
formatCheckinData(item) {
|
||||
// ...现有逻辑
|
||||
this.checkinData = {
|
||||
// ...现有字段
|
||||
videoUrl: item.videoUrl || '',
|
||||
taskId: item.taskId || '',
|
||||
enableAIVideo: item.enableAIVideo || false,
|
||||
videoStatus: item.videoStatus || 0
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
#### 3. 社区页面显示视频标记
|
||||
|
||||
`[community.vue](msh_single_uniapp/pages/tool_main/community.vue)`
|
||||
|
||||
**formatPostList更新**:
|
||||
|
||||
```javascript
|
||||
formatPostList(list) {
|
||||
return list.map(item => {
|
||||
// ...现有逻辑
|
||||
return {
|
||||
// ...现有字段
|
||||
hasVideo: item.hasVideo || false,
|
||||
videoUrl: item.videoUrl || '',
|
||||
enableAIVideo: item.enableAIVideo || false
|
||||
};
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
**卡片增加视频标记**:
|
||||
|
||||
```vue
|
||||
<view class="post-card">
|
||||
<!-- 现有内容 -->
|
||||
|
||||
<!-- 视频标记 -->
|
||||
<view class="video-badge" v-if="item.hasVideo || item.videoUrl">
|
||||
<text class="badge-icon">🎬</text>
|
||||
<text class="badge-text">视频</text>
|
||||
</view>
|
||||
</view>
|
||||
```
|
||||
|
||||
**样式**:
|
||||
|
||||
```scss
|
||||
.video-badge {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
border-radius: 12px;
|
||||
padding: 4px 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
|
||||
.badge-icon {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.badge-text {
|
||||
font-size: 12px;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 4. 新增API方法
|
||||
|
||||
`[tool.js](msh_single_uniapp/api/tool.js)`
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* 查询视频任务状态
|
||||
* @param {String} taskId - 任务ID
|
||||
*/
|
||||
export function getVideoTaskStatus(taskId) {
|
||||
return request.get(`kieai/video/task/${taskId}`, {
|
||||
apiKey: 'your-api-key' // 从配置获取
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### 关键技术细节
|
||||
|
||||
#### 1. 轮询策略
|
||||
|
||||
- **触发条件**: 列表中存在 `enableAIVideo=true` 且 `taskId` 不为空但 `videoUrl` 为空的记录
|
||||
- **轮询间隔**: 5秒
|
||||
- **停止条件**:
|
||||
- 所有任务都有 `videoUrl` 或 `videoStatus=2`
|
||||
- 页面销毁(onUnload)
|
||||
- 超过最大轮询次数(如60次,即5分钟)
|
||||
- **优化**: 只轮询当前可见列表中的任务,避免全量查询
|
||||
|
||||
#### 2. 视频播放兼容性
|
||||
|
||||
- 使用uni-app的 `<video>` 组件,支持小程序、H5、APP
|
||||
- 视频格式要求: mp4 (KieAI返回格式)
|
||||
- 封面图: 可使用打卡的第一张照片作为视频封面
|
||||
|
||||
#### 3. 错误处理
|
||||
|
||||
- **视频生成失败**: 显示失败状态,允许用户重新生成或查看原因
|
||||
- **网络错误**: 轮询失败时静默处理,不影响用户浏览
|
||||
- **回调失败**: 前端轮询作为兜底方案
|
||||
|
||||
## 文件清单
|
||||
|
||||
### 后端Java文件
|
||||
|
||||
|
||||
| 文件 | 改动类型 | 说明 |
|
||||
| -------------------------------------------------------------------------------------------------------------------------------------------- | ----- | -------------------------------------------- |
|
||||
| `[ToolCheckinServiceImpl.java](msh_crmeb_22/crmeb-service/src/main/java/com/zbkj/service/service/impl/tool/ToolCheckinServiceImpl.java)` | 修改 | submit增加article创建,getList/getDetail增加video字段 |
|
||||
| `[KieAIController.java](msh_crmeb_22/crmeb-front/src/main/java/com/zbkj/front/controller/KieAIController.java)` | 修改 | 完善回调处理更新article表 |
|
||||
| `[ToolKieAIServiceImpl.java](msh_crmeb_22/crmeb-service/src/main/java/com/zbkj/service/service/impl/tool/ToolKieAIServiceImpl.java)` | 修改 | handleTaskCallback增加更新article逻辑 |
|
||||
| `[ToolCommunityServiceImpl.java](msh_crmeb_22/crmeb-service/src/main/java/com/zbkj/service/service/impl/tool/ToolCommunityServiceImpl.java)` | 修改 | getList关联video信息 |
|
||||
| `[V2CommunityPost.java](msh_crmeb_22/crmeb-common/src/main/java/com/zbkj/common/model/tool/V2CommunityPost.java)` | 检查/修改 | 确认有videoUrl字段 |
|
||||
| `[ArticleDao.java](msh_crmeb_22/crmeb-service/src/main/java/com/zbkj/service/dao/ArticleDao.java)` | 新增方法 | 增加updateVideoUrlByTaskId方法 |
|
||||
|
||||
|
||||
### 前端Vue文件
|
||||
|
||||
|
||||
| 文件 | 改动类型 | 说明 |
|
||||
| ------------------------------------------------------------------------- | ---- | ------------------------ |
|
||||
| `[dietary-records.vue](msh_single_uniapp/pages/tool/dietary-records.vue)` | 修改 | 增加视频状态显示和轮询逻辑 |
|
||||
| `[checkin-detail.vue](msh_single_uniapp/pages/tool/checkin-detail.vue)` | 修改 | 增加视频播放器 |
|
||||
| `[community.vue](msh_single_uniapp/pages/tool_main/community.vue)` | 修改 | 增加视频标记 |
|
||||
| `[tool.js](msh_single_uniapp/api/tool.js)` | 新增方法 | 增加getVideoTaskStatus API |
|
||||
|
||||
|
||||
## 测试要点
|
||||
|
||||
1. **打卡流程**: 勾选生成视频 → 发布 → 验证taskId保存 → 验证article记录创建
|
||||
2. **状态轮询**: 饮食记录页面显示"生成中" → 等待KieAI完成 → 自动更新显示"有视频"
|
||||
3. **视频播放**: 打卡详情查看视频 → 播放正常 → 控制条功能正常
|
||||
4. **社区显示**: 最新tab显示有视频标记的帖子 → 点击查看带视频
|
||||
5. **异常处理**: 视频生成失败 → 显示失败状态 → 不影响打卡记录查看
|
||||
6. **回调机制**: KieAI回调成功 → article表正确更新 → 前端轮询立即获取到结果
|
||||
|
||||
166
.cursor/plans/计算器结果保存食谱_f149b3dc.plan.md
Normal file
166
.cursor/plans/计算器结果保存食谱_f149b3dc.plan.md
Normal file
@@ -0,0 +1,166 @@
|
||||
---
|
||||
name: 计算器结果保存食谱
|
||||
overview: 在用户采纳营养计划时,将食谱计算器的配餐方案提取并保存到 v2_recipes 食谱表,并在首页"精选食谱"中混合展示用户自己的食谱(排在前面)。
|
||||
todos:
|
||||
- id: db-migration
|
||||
content: 新建 SQL 变更文件,v2_recipes 表新增 source 和 source_id 字段
|
||||
status: completed
|
||||
- id: entity-update
|
||||
content: V2Recipe.java 实体类新增 source 和 sourceId 属性
|
||||
status: completed
|
||||
- id: adopt-save-recipe
|
||||
content: ToolCalculatorServiceImpl.adopt() 中新增保存食谱到 v2_recipes 的逻辑(含幂等检查)
|
||||
status: completed
|
||||
- id: home-mix-recipes
|
||||
content: ToolHomeServiceImpl.getRecommendedRecipes() 混入当前用户的计算器食谱
|
||||
status: completed
|
||||
- id: home-return-fields
|
||||
content: 推荐食谱接口返回字段补充 description、source、totalProtein
|
||||
status: completed
|
||||
- id: frontend-recipe-card
|
||||
content: 首页 index.vue 食谱卡片展示适配(tag、desc 等字段映射)
|
||||
status: completed
|
||||
isProject: false
|
||||
---
|
||||
|
||||
# 食谱计算器结果保存到食谱表
|
||||
|
||||
## 整体流程
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant User as 用户
|
||||
participant FE as 前端
|
||||
participant Ctrl as ToolController
|
||||
participant CalcSvc as ToolCalculatorServiceImpl
|
||||
participant RecipeDao as V2RecipeDao
|
||||
participant HomeSvc as ToolHomeServiceImpl
|
||||
|
||||
User->>FE: 点击"采纳计划"
|
||||
FE->>Ctrl: POST /calculator/adopt
|
||||
Ctrl->>CalcSvc: adopt(resultId)
|
||||
CalcSvc->>CalcSvc: 创建营养计划 (已有逻辑)
|
||||
CalcSvc->>RecipeDao: 保存一条食谱到 v2_recipes
|
||||
CalcSvc-->>FE: 返回 adoptResponse
|
||||
Note over FE: 首页刷新时
|
||||
FE->>Ctrl: GET /home/recipes
|
||||
Ctrl->>HomeSvc: getRecommendedRecipes()
|
||||
HomeSvc->>RecipeDao: 查询推荐食谱 + 用户自己的食谱
|
||||
HomeSvc-->>FE: 返回混合列表
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 1. 数据库变更 - v2_recipes 新增字段
|
||||
|
||||
需要新增两个字段以追踪食谱来源:
|
||||
|
||||
```sql
|
||||
ALTER TABLE v2_recipes
|
||||
ADD COLUMN source VARCHAR(20) DEFAULT 'manual' COMMENT '来源:manual(手动)/calculator(计算器)/ai(AI生成)' AFTER sort_order,
|
||||
ADD COLUMN source_id BIGINT DEFAULT NULL COMMENT '来源ID(如计算器结果ID)' AFTER source;
|
||||
```
|
||||
|
||||
同时新增一个变更 SQL 文件记录此次 DDL。
|
||||
|
||||
## 2. 后端 - 实体类更新
|
||||
|
||||
**文件**: [V2Recipe.java](msh_crmeb_22/crmeb-common/src/main/java/com/zbkj/common/model/tool/V2Recipe.java)
|
||||
|
||||
新增两个字段:
|
||||
|
||||
```java
|
||||
@ApiModelProperty(value = "来源:manual/calculator/ai")
|
||||
private String source;
|
||||
|
||||
@ApiModelProperty(value = "来源ID(计算器结果ID等)")
|
||||
private Long sourceId;
|
||||
```
|
||||
|
||||
## 3. 后端 - 采纳时保存食谱
|
||||
|
||||
**文件**: [ToolCalculatorServiceImpl.java](msh_crmeb_22/crmeb-service/src/main/java/com/zbkj/service/service/impl/tool/ToolCalculatorServiceImpl.java)
|
||||
|
||||
在 `adopt()` 方法中,步骤 5(创建营养计划)之后,新增步骤"保存食谱到 v2_recipes":
|
||||
|
||||
- 注入 `V2RecipeDao`
|
||||
- 从 `V2CalculatorResult` 中解析 `mealPlanJson` 得到 MealPlan
|
||||
- 构建 `V2Recipe` 对象:
|
||||
- `userId` = 当前用户ID
|
||||
- `name` = "每日营养配餐 - {ckdStage}",如 "每日营养配餐 - CKD 5期"
|
||||
- `description` = "蛋白质 {proteinIntake}g/天 | 能量 {energyIntake}kcal/天"
|
||||
- `coverImage` = 取午餐第一道菜的图片 URL(最具代表性)
|
||||
- `mealType` = null(整日配餐,非单餐)
|
||||
- `category` = "营养配餐"
|
||||
- `tagsJson` = `["AI配餐", "{ckdStage}"]`
|
||||
- `ingredientsJson` = 聚合三餐所有食材
|
||||
- `stepsJson` = 存放完整 mealPlanJson(早/午/晚餐详情),复用此字段存储配餐详情
|
||||
- `totalProtein` = result.getProteinIntake()
|
||||
- `totalEnergy` = result.getEnergyIntake()
|
||||
- `suitableStagesJson` = `["{ckdStage}"]`
|
||||
- `suitableDialysis` = result.getHasDialysis()
|
||||
- `status` = "published"
|
||||
- `isRecommend` = 0(不进入官方推荐,通过 source + userId 查询)
|
||||
- `isOfficial` = 0
|
||||
- `source` = "calculator"
|
||||
- `sourceId` = resultId
|
||||
- 幂等处理:先检查是否已存在 `source='calculator' AND source_id=resultId` 的记录,避免重复
|
||||
|
||||
## 4. 后端 - 首页推荐食谱混入用户食谱
|
||||
|
||||
**文件**: [ToolHomeServiceImpl.java](msh_crmeb_22/crmeb-service/src/main/java/com/zbkj/service/service/impl/tool/ToolHomeServiceImpl.java)
|
||||
|
||||
修改 `getRecommendedRecipes()` 方法:
|
||||
|
||||
1. 如果用户已登录,先查询该用户的食谱(`user_id = currentUserId AND status = 'published' AND source = 'calculator'`,按 `created_at DESC`,取最新 1 条)
|
||||
2. 再查询官方推荐食谱(现有逻辑,`is_recommend = 1`)
|
||||
3. 将用户食谱排在前面,官方推荐排在后面,合并后返回
|
||||
4. 总数仍控制在 `limit` 范围内(如用户有 1 条自己的食谱,则官方推荐取 limit-1 条)
|
||||
5. 缓存 key 需要区分用户(登录用户的缓存 key 加上 userId)
|
||||
|
||||
## 5. 后端 - 推荐食谱接口返回字段补充
|
||||
|
||||
**文件**: [ToolHomeServiceImpl.java](msh_crmeb_22/crmeb-service/src/main/java/com/zbkj/service/service/impl/tool/ToolHomeServiceImpl.java)
|
||||
|
||||
在返回数据 map 中增加字段,使前端能够区分并展示更多信息:
|
||||
|
||||
```java
|
||||
map.put("id", recipe.getRecipeId());
|
||||
map.put("name", recipe.getName());
|
||||
map.put("coverImage", recipe.getCoverImage());
|
||||
map.put("totalEnergy", recipe.getTotalEnergy());
|
||||
map.put("description", recipe.getDescription()); // 新增
|
||||
map.put("source", recipe.getSource()); // 新增:标记来源
|
||||
map.put("totalProtein", recipe.getTotalProtein()); // 新增
|
||||
```
|
||||
|
||||
## 6. 前端 - 首页食谱展示适配
|
||||
|
||||
**文件**: [index.vue (首页)](msh_single_uniapp/pages/tool_main/index.vue)
|
||||
|
||||
调整食谱卡片展示逻辑:
|
||||
|
||||
- `item.tag`:如果 `source === 'calculator'` 显示 "我的配餐",否则显示 "推荐"
|
||||
- `item.tagClass`:根据来源使用不同样式(如用户食谱用不同颜色)
|
||||
- `item.desc`:使用 `description` 字段(如 "蛋白质 75.6g/天 | 能量 2205kcal/天")
|
||||
- `item.time`:可不显示或显示 "每日配餐"
|
||||
- `item.views`:使用 `viewCount` 或隐藏
|
||||
|
||||
修改 `loadData` 中对 `recipeList` 的处理逻辑,将后端返回的数据映射为前端需要的格式。
|
||||
|
||||
## 7. 前端 - 食谱详情页适配
|
||||
|
||||
点击首页食谱卡片跳转到 `recipe-detail` 页面,需要确认该页面能正确显示来自计算器的食谱内容(特别是 `stepsJson` 中存储的 mealPlan 数据)。如果需要特殊处理,在详情页根据 `source` 字段做渲染分支。
|
||||
|
||||
## 涉及文件清单
|
||||
|
||||
|
||||
| 层级 | 文件 | 改动 |
|
||||
| -------- | --------------------------------------------- | ------------------------------ |
|
||||
| SQL | `docs/sql/v2_recipes_add_source.sql`(新建) | 新增 source、source_id 字段 |
|
||||
| Entity | `V2Recipe.java` | 新增 source、sourceId 字段 |
|
||||
| Service | `ToolCalculatorServiceImpl.java` | adopt() 中新增保存食谱逻辑 |
|
||||
| Service | `ToolHomeServiceImpl.java` | getRecommendedRecipes() 混入用户食谱 |
|
||||
| Frontend | `msh_single_uniapp/pages/tool_main/index.vue` | 食谱卡片展示适配 |
|
||||
|
||||
|
||||
256
.cursor/plans/诊断article_status_task异常问题_6bd03fd4.plan.md
Normal file
256
.cursor/plans/诊断article_status_task异常问题_6bd03fd4.plan.md
Normal file
@@ -0,0 +1,256 @@
|
||||
---
|
||||
name: ""
|
||||
overview: ""
|
||||
todos: []
|
||||
isProject: false
|
||||
---
|
||||
|
||||
# 诊断article.status_task异常和video_url未更新问题
|
||||
|
||||
## 问题描述
|
||||
|
||||
**问题1**:提交饮食打卡记录时,article表中的status_task字段保持为0或变成2,而不是预期的1(完成)
|
||||
|
||||
**问题2**:article表中的video_url字段始终为NULL,即使视频已经生成完成
|
||||
|
||||
**关键信息**:
|
||||
|
||||
- KieAI回调一般需要10秒以上才到达,不会立即回调
|
||||
- 两个问题有关联,都指向**KieAI回调没有正常执行或执行失败**
|
||||
|
||||
## 根因分析
|
||||
|
||||
### 核心问题:KieAI回调未正常执行
|
||||
|
||||
根据分析,最可能的原因是:
|
||||
|
||||
**原因1:回调反序列化失败**(最可能)
|
||||
|
||||
根据[修复kieai回调反序列化问题计划](修复kieai回调反序列化问题_85d41db5.plan.md),KieAI回调到达后端,但Jackson反序列化失败:
|
||||
|
||||
```
|
||||
Could not resolve parameter [0]... Problem deserializing 'setterless' property 'resultUrls':
|
||||
get method returned null
|
||||
```
|
||||
|
||||
这导致:
|
||||
|
||||
1. `handleCallback` 方法无法执行
|
||||
2. `handleTaskCallback` 中更新article的逻辑从未运行
|
||||
3. `video_url` 和 `status_task` 字段保持未更新状态
|
||||
|
||||
**原因2:回调URL配置错误**
|
||||
|
||||
- `kieAIConfig.getApiCallbackUrl()` 可能未配置或配置错误
|
||||
- KieAI服务无法访问回调地址(网络问题、防火墙等)
|
||||
- 回调地址格式不正确
|
||||
|
||||
**原因3:回调数据格式问题**
|
||||
|
||||
- KieAI返回的数据结构与`KieAIQueryTaskResponse`不匹配
|
||||
- `resultUrls`字段名称或格式异常
|
||||
|
||||
## 执行流程分析
|
||||
|
||||
### 预期流程(正常情况)
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Frontend as 前端
|
||||
participant Sora2 as ToolSora2ServiceImpl
|
||||
participant KieAI as KieAI服务
|
||||
participant Callback as KieAIController
|
||||
participant Service as ToolKieAIServiceImpl
|
||||
participant DB as 数据库
|
||||
|
||||
Frontend->>Sora2: createImageToVideoTask
|
||||
Sora2->>KieAI: POST createTask含callBackUrl
|
||||
KieAI-->>Sora2: 返回taskId
|
||||
Sora2->>DB: INSERT article status_task=0
|
||||
Note over DB: status_task = 0
|
||||
|
||||
Note over KieAI: 10秒以上视频生成中
|
||||
|
||||
KieAI->>Callback: POST callback含resultUrls
|
||||
Callback->>Callback: 反序列化KieAIQueryTaskResponse
|
||||
Callback->>Service: handleTaskCallback
|
||||
Service->>DB: SELECT article WHERE task_id
|
||||
Service->>Service: extractVideoUrl
|
||||
Service->>DB: UPDATE video_url status_task=1
|
||||
Note over DB: video_url有值 status_task=1
|
||||
```
|
||||
|
||||
|
||||
|
||||
### 实际流程(异常情况)
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Frontend as 前端
|
||||
participant Sora2 as ToolSora2ServiceImpl
|
||||
participant KieAI as KieAI服务
|
||||
participant Callback as KieAIController
|
||||
participant DB as 数据库
|
||||
|
||||
Frontend->>Sora2: createImageToVideoTask
|
||||
Sora2->>KieAI: POST createTask
|
||||
KieAI-->>Sora2: 返回taskId
|
||||
Sora2->>DB: INSERT article status_task=0
|
||||
Note over DB: status_task = 0
|
||||
|
||||
Note over KieAI: 10秒以上...
|
||||
|
||||
KieAI->>Callback: POST callback
|
||||
Callback--xCallback: 反序列化失败
|
||||
Note over Callback: Jackson报错setterless property resultUrls
|
||||
Note over DB: video_url仍为NULL status_task仍为0
|
||||
|
||||
Note over Frontend,DB: 回调处理逻辑从未执行数据库未更新
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 解决方案
|
||||
|
||||
### 方案1:修复回调反序列化问题(主要修复)
|
||||
|
||||
**文件**:[KieAIQueryTaskResponse.java](msh_crmeb_22/crmeb-common/src/main/java/com/zbkj/common/response/kieai/KieAIQueryTaskResponse.java)
|
||||
|
||||
**问题代码**(TaskData类):
|
||||
|
||||
```java
|
||||
@Data
|
||||
public static class TaskData implements Serializable {
|
||||
@ApiModelProperty(value = "结果URL数组(视频/图片地址)")
|
||||
private java.util.List<String> resultUrls; // Lombok生成的setter可能被Jackson忽略
|
||||
}
|
||||
```
|
||||
|
||||
**修复方法**:显式添加setter方法
|
||||
|
||||
```java
|
||||
@Data
|
||||
public static class TaskData implements Serializable {
|
||||
@ApiModelProperty(value = "结果URL数组(视频/图片地址)")
|
||||
private java.util.List<String> resultUrls;
|
||||
|
||||
// 显式添加setter以确保Jackson反序列化正常
|
||||
public void setResultUrls(java.util.List<String> resultUrls) {
|
||||
this.resultUrls = resultUrls;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 方案2:验证回调URL配置
|
||||
|
||||
**检查配置文件**(application.yml或application.properties):
|
||||
|
||||
```yaml
|
||||
kie-ai:
|
||||
api-callback-url: http://your-domain.com/api/front/kieai/callback
|
||||
```
|
||||
|
||||
**验证要点**:
|
||||
|
||||
1. URL必须是KieAI服务可访问的公网地址
|
||||
2. 确保没有防火墙阻止
|
||||
3. 路径正确:`/api/front/kieai/callback`
|
||||
|
||||
### 方案3:添加详细日志(辅助调试)
|
||||
|
||||
在[KieAIController.java](msh_crmeb_22/crmeb-front/src/main/java/com/zbkj/front/controller/KieAIController.java)的回调方法开头添加:
|
||||
|
||||
```java
|
||||
@PostMapping("/callback")
|
||||
public KieAINanoBananaResponse<String> handleCallback(
|
||||
@RequestBody KieAIQueryTaskResponse callbackData) {
|
||||
try {
|
||||
logger.info("======== KieAI回调原始数据 ========");
|
||||
logger.info("完整对象: {}", JSON.toJSONString(callbackData));
|
||||
logger.info("taskId: {}", callbackData.getTaskId());
|
||||
logger.info("state: {}", callbackData.getState());
|
||||
logger.info("resultUrls: {}", callbackData.getResultUrls());
|
||||
|
||||
String taskId = callbackData.getTaskId();
|
||||
logger.info("接收KieAI任务回调,任务ID: {}", taskId);
|
||||
toolKieAIService.handleTaskCallback(taskId, callbackData);
|
||||
return KieAINanoBananaResponse.success("回调处理成功");
|
||||
} catch (Exception e) {
|
||||
logger.error("处理任务回调失败", e);
|
||||
return KieAINanoBananaResponse.fail("回调处理失败");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 诊断和修复步骤
|
||||
|
||||
### 步骤1:修复反序列化问题(优先级最高)
|
||||
|
||||
1. 修改`KieAIQueryTaskResponse.java`中的`TaskData`类,显式添加setter
|
||||
2. 重启后端服务
|
||||
3. 提交一次打卡测试
|
||||
|
||||
### 步骤2:检查后端日志
|
||||
|
||||
**如果回调仍未执行**,检查日志:
|
||||
|
||||
1. 搜索:`接收KieAI任务回调`
|
||||
- 找到:回调到达了,检查反序列化错误
|
||||
- 没找到:回调未到达,检查回调URL配置
|
||||
2. 搜索:`deserializing` 或 `Jackson` 或 `Could not resolve`
|
||||
- 找到:确认是反序列化问题
|
||||
3. 查看article创建日志:
|
||||
```
|
||||
文章新增成功, taskId: xxx
|
||||
```
|
||||
|
||||
### 步骤3:验证回调URL配置
|
||||
|
||||
1. 检查`application.yml`中的`kie-ai.api-callback-url`
|
||||
2. 确认URL可从外网访问
|
||||
3. 测试回调接口是否正常响应
|
||||
|
||||
### 步骤4:验证修复效果
|
||||
|
||||
提交打卡后,检查数据库:
|
||||
|
||||
```sql
|
||||
SELECT id, task_id, video_url, status_task, create_time, update_time
|
||||
FROM eb_article
|
||||
WHERE task_id = 'your_task_id'
|
||||
ORDER BY create_time DESC
|
||||
LIMIT 1;
|
||||
```
|
||||
|
||||
**预期结果**:
|
||||
|
||||
- `status_task = 1`(任务完成)
|
||||
- `video_url` 有值
|
||||
- `update_time` 在create_time的10秒以上后更新
|
||||
|
||||
## 问题总结
|
||||
|
||||
### 核心原因
|
||||
|
||||
KieAI回调无法被正确处理,导致:
|
||||
|
||||
1. `video_url`未更新(始终为NULL)
|
||||
2. `status_task`未更新(保持为0)
|
||||
|
||||
### 最可能的原因
|
||||
|
||||
根据[修复kieai回调反序列化问题计划](修复kieai回调反序列化问题_85d41db5.plan.md),Jackson反序列化失败导致回调处理方法无法执行。
|
||||
|
||||
### 修复优先级
|
||||
|
||||
1. **优先级1**:修复`KieAIQueryTaskResponse.TaskData`反序列化(添加显式setter)
|
||||
2. **优先级2**:验证回调URL配置
|
||||
3. **优先级3**:添加详细日志
|
||||
|
||||
## 涉及文件
|
||||
|
||||
- [KieAIQueryTaskResponse.java](msh_crmeb_22/crmeb-common/src/main/java/com/zbkj/common/response/kieai/KieAIQueryTaskResponse.java) - 主要修复
|
||||
- [KieAIController.java](msh_crmeb_22/crmeb-front/src/main/java/com/zbkj/front/controller/KieAIController.java) - 回调入口
|
||||
- [ToolKieAIServiceImpl.java](msh_crmeb_22/crmeb-service/src/main/java/com/zbkj/service/service/impl/tool/ToolKieAIServiceImpl.java) - 回调处理
|
||||
- [ToolSora2ServiceImpl.java](msh_crmeb_22/crmeb-service/src/main/java/com/zbkj/service/service/impl/tool/ToolSora2ServiceImpl.java) - article创建
|
||||
|
||||
91
.cursor/plans/食谱百科食物图ai生成与oss更新_b5228ab9.plan.md
Normal file
91
.cursor/plans/食谱百科食物图ai生成与oss更新_b5228ab9.plan.md
Normal file
@@ -0,0 +1,91 @@
|
||||
---
|
||||
name: 食谱百科食物图AI生成与OSS更新
|
||||
overview: 对 v2_foods 表中 image 非阿里云 OSS 的记录,根据 name 调用 KieAI 生成图片(压缩至不超过 100KB),上传到 OSS 并回写 v2_foods.image;复用现有 DishImageServiceImpl 的 KieAI + 压缩 + OSS 能力,扩展“食物”场景并增加更新数据库的入口与触发方式。
|
||||
todos: []
|
||||
isProject: false
|
||||
---
|
||||
|
||||
# 食谱百科食物图 AI 生成并更新 v2_foods.image
|
||||
|
||||
## 现状
|
||||
|
||||
- **食谱百科前端**:`[msh_single_uniapp/pages/tool/food-encyclopedia.vue](msh_single_uniapp/pages/tool/food-encyclopedia.vue)` 通过 `getFoodList` / `searchFood`(`[api/tool.js](msh_single_uniapp/api/tool.js)`)请求 `tool/food/list`、`tool/food/search`,列表项中的 `image` 直接来自后端。
|
||||
- **后端数据**:`[ToolFoodServiceImpl](msh_crmeb_22/crmeb-service/src/main/java/com/zbkj/service/service/impl/tool/ToolFoodServiceImpl.java)` 从 `v2_foods` 查数据,返回的 map 包含 `image`(`[V2Food](msh_crmeb_22/crmeb-common/src/main/java/com/zbkj/common/model/tool/V2Food.java)` 的 `image` 字段)。当前部分记录的 `image` 为 Figma/非 OSS 等非阿里云地址。
|
||||
- **可复用能力**:`[DishImageServiceImpl](msh_crmeb_22/crmeb-service/src/main/java/com/zbkj/service/service/impl/tool/DishImageServiceImpl.java)` 已实现:KieAI 文生图 → 下载图片 → **压缩至 100KB**(`MAX_IMAGE_BYTES = 100*1024`、`compressImageToMaxBytes`)→ 上传 OSS(`recipes/`)→ 写 `V2DishImageCache`。菜品 prompt 为“一道精美的中式菜品照片:{name}…”。
|
||||
|
||||
## 目标
|
||||
|
||||
- 对 **v2_foods** 中 **image 非阿里云 OSS** 的记录:根据 **name** 调用 AI 生成图片,生成图 **不超过 100KB**,上传到 OSS 后 **更新 v2_foods.image** 为 OSS 地址。
|
||||
- 判断“非 OSS”:`image` 为空或 `image` 不包含当前项目使用的 OSS 域名(如配置中的 `alUploadUrl` 或固定包含 `aliyuncs.com`)。
|
||||
|
||||
## 实现方案
|
||||
|
||||
### 1. 判断“是否为 OSS 地址”
|
||||
|
||||
- 在公共工具或 Service 内封装:若 `image` 为空/blank,视为非 OSS;若不为空,则判断是否包含 OSS 域名(可从 `SystemConfigService.getValueByKey(SysConfigConstants.CONFIG_AL_UPLOAD_URL)` 取域名,或简单用 `image.contains("aliyuncs.com")`)。满足则为 OSS,否则需要补图。
|
||||
|
||||
### 2. 后端:扩展“食物图片”能力并写回 v2_foods
|
||||
|
||||
**方案 A(推荐):在现有 DishImageServiceImpl 上扩展**
|
||||
|
||||
- 在 `[DishImageServiceImpl](msh_crmeb_22/crmeb-service/src/main/java/com/zbkj/service/service/impl/tool/DishImageServiceImpl.java)` 中:
|
||||
- 增加 **食物** 用 prompt 方法,例如:`buildFoodImagePrompt(String foodName)`,内容为“一份新鲜的食物/食材照片:{name},高清静物摄影,白色背景或餐盘,自然光,细节清晰”(与菜品区分开)。
|
||||
- 增加 **食物** 的 OSS 路径常量,如 `OSS_FOODS_PATH = "foods/"`,上传时使用 `foods/food-{sanitizedName}-{timestamp}.jpg`,与 `recipes/` 区分。
|
||||
- 新增 **公共流程**:`generateImageAndUploadToOss(String name, String prompt, String ossPathPrefix)`(或拆成:用现有 KieAI + 下载 + `compressImageToMaxBytes(imageBytes, 100*1024)` + 上传,仅参数为 name/prompt/路径前缀),返回 OSS 完整 URL。
|
||||
- 注入 `V2FoodDao`,新增方法:`ensureFoodImageAndUpdateDb(Long foodId)`:
|
||||
- 根据 foodId 查 `V2Food`;
|
||||
- 若 `image` 已是 OSS(用上述判断),直接返回当前 image;
|
||||
- 否则用 `buildFoodImagePrompt(food.getName())` 调用上述“生成并上传”流程,得到 ossUrl;
|
||||
- 更新 `food.setImage(ossUrl)` 并 `v2FoodDao.updateById(food)`;
|
||||
- 失败时可按现有逻辑降级为占位图并同样写回 DB,或只打日志不更新,视产品要求而定。
|
||||
- 在 `[DishImageService](msh_crmeb_22/crmeb-service/src/main/java/com/zbkj/service/service/tool/DishImageService.java)` 接口中声明 `ensureFoodImageAndUpdateDb(Long foodId)`(或返回 String 的 `ensureFoodImageUrl(Long foodId)` 由调用方更新 DB,二选一即可)。
|
||||
|
||||
**方案 B:新建 FoodImageService**
|
||||
|
||||
- 新建 `FoodImageService` + `FoodImageServiceImpl`,内部注入 `DishImageService` 或直接复用 KieAI、Oss、压缩逻辑(若将 DishImageServiceImpl 中下载/压缩/上传抽成 package 内可复用方法,或抽到公共工具类)。
|
||||
- `FoodImageServiceImpl.ensureFoodImageAndUpdateDb(Long foodId)` 查 v2_foods → 判断 image 非 OSS → 调 KieAI 生图 → 压缩 ≤100KB → 上传 OSS(路径 `foods/`)→ 更新 `v2_foods.image`。
|
||||
- 与方案 A 二选一即可;方案 A 复用更集中,改动面小。
|
||||
|
||||
### 3. 触发方式(可选其一或组合)
|
||||
|
||||
- **按需(列表/详情)**:在 `[ToolFoodServiceImpl](msh_crmeb_22/crmeb-service/src/main/java/com/zbkj/service/service/impl/tool/ToolFoodServiceImpl.java)` 的 `getList`/`search` 返回列表前,对每条记录的 `image` 判断;若非 OSS,可**异步**调用 `ensureFoodImageAndUpdateDb(food.getFoodId())`,本次仍返回原 image,下次请求得到 OSS 地址;或**同步**调用(会拉长接口耗时,需权衡)。详情 `getDetail` 同理,对当前条若 image 非 OSS 可同步/异步补图并更新。
|
||||
- **批量/管理端**:在 `[ToolController](msh_crmeb_22/crmeb-front/src/main/java/com/zbkj/front/controller/ToolController.java)` 或管理端增加接口,例如 `POST /api/front/tool/food/refresh-images` 或后台 `POST /api/admin/tool/food/refresh-images`:查询 `v2_foods` 中 `image` 为空或非 OSS 的记录,循环调用 `ensureFoodImageAndUpdateDb(foodId)`,可限制单次条数(如 20)并返回处理数量,避免一次性跑全表。
|
||||
|
||||
### 4. 技术细节摘要
|
||||
|
||||
- **图片大小**:沿用 `DishImageServiceImpl` 的 `compressImageToMaxBytes(bytes, 100*1024L)`,保证上传前 ≤100KB。
|
||||
- **OSS 路径**:使用 `foods/food-{sanitizedName}-{timestamp}.jpg`,与菜品 `recipes/dish-...` 区分,便于运维与排查。
|
||||
- **KieAI 配置**:与现有菜品一致,使用 `KieAIConfig`、`ToolKieAIService`;未配置 Token 时可按现有逻辑降级(占位图或跳过更新)。
|
||||
- **前端**:无需改;列表/详情接口返回的 `image` 更新为 OSS 后,`[food-encyclopedia.vue](msh_single_uniapp/pages/tool/food-encyclopedia.vue)` 和 `[food-detail.vue](msh_single_uniapp/pages/tool/food-detail.vue)` 会自然展示新图。
|
||||
|
||||
### 5. 可选说明(不阻塞本次需求)
|
||||
|
||||
- 食谱百科列表跳详情当前为 `id=${item.name}`,而后端 `getFoodDetail` 为 `Long id`;若实际数据以 id 为主,建议前端改为传 `id=${item.id}` 并在详情用 id 请求,避免按 name 查的兼容逻辑。
|
||||
|
||||
## 涉及文件(建议)
|
||||
|
||||
|
||||
| 类型 | 路径 |
|
||||
| -------- | ---------------------------------------------------------------------------------------------------------------------------- |
|
||||
| 接口 | `msh_crmeb_22/crmeb-service/.../tool/DishImageService.java` |
|
||||
| 实现 | `msh_crmeb_22/crmeb-service/.../tool/DishImageServiceImpl.java` |
|
||||
| 食物服务/控制器 | `msh_crmeb_22/crmeb-service/.../tool/ToolFoodServiceImpl.java`、`msh_crmeb_22/crmeb-front/.../ToolController.java`(若做按需或批量触发) |
|
||||
|
||||
|
||||
## 流程概览
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A[v2_foods 记录] --> B{image 是否 OSS?}
|
||||
B -->|是| C[直接使用]
|
||||
B -->|否| D[buildFoodImagePrompt]
|
||||
D --> E[KieAI 文生图]
|
||||
E --> F[下载图片]
|
||||
F --> G[压缩至 100KB]
|
||||
G --> H[上传 OSS foods/]
|
||||
H --> I[更新 v2_foods.image]
|
||||
I --> C
|
||||
```
|
||||
|
||||
|
||||
|
||||
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"java.configuration.updateBuildConfiguration": "interactive"
|
||||
}
|
||||
BIN
docs/.DS_Store
vendored
Normal file
BIN
docs/.DS_Store
vendored
Normal file
Binary file not shown.
5756
docs/PRD_慢生活智能营养专家_v2.0.md
Normal file
5756
docs/PRD_慢生活智能营养专家_v2.0.md
Normal file
File diff suppressed because it is too large
Load Diff
542
docs/Testing/Coze-API测试文档.md
Normal file
542
docs/Testing/Coze-API测试文档.md
Normal file
@@ -0,0 +1,542 @@
|
||||
# Coze API 测试文档
|
||||
|
||||
> **版本**:v1.0
|
||||
> **创建日期**:2026-02-08
|
||||
> **基础路径**:`/api/front/coze`
|
||||
> **服务端口**:20822
|
||||
> **认证方式**:免登录(已加入白名单)
|
||||
|
||||
---
|
||||
|
||||
## 一、配置信息
|
||||
|
||||
### 1.1 当前环境配置
|
||||
|
||||
| 配置项 | 值 | 说明 |
|
||||
|--------|-----|------|
|
||||
| Base URL | `https://api.coze.cn` | Coze 平台 API 地址 |
|
||||
| Auth Type | `pat` | Personal Access Token 模式 |
|
||||
| Default Bot ID | `7591133240535449654` | 食谱计算器 Bot |
|
||||
| Default User ID | `3243981400446844` | 测试用户 ID |
|
||||
|
||||
---
|
||||
|
||||
## 二、接口清单
|
||||
|
||||
| 序号 | 接口名称 | HTTP方法 | 路径 | 说明 |
|
||||
|------|----------|----------|------|------|
|
||||
| 1 | 发起对话 | `POST` | `/chat` | 与 Coze Bot 对话(支持流式/非流式) |
|
||||
| 2 | 流式对话 | `POST` | `/chat/stream` | SSE 流式对话 |
|
||||
| 3 | 执行工作流 | `POST` | `/workflow/run` | 触发预定义工作流 |
|
||||
| 4 | 流式执行工作流 | `POST` | `/workflow/stream` | SSE 流式执行工作流 |
|
||||
| 5 | 查看对话详情 | `POST` | `/chat/retrieve` | 查询对话状态 |
|
||||
| 6 | 查看对话消息列表 | `POST` | `/chat/messages/list` | 获取对话消息 |
|
||||
| 7 | 上传文件 | `POST` | `/file/upload` | 上传文件获取 file_id |
|
||||
|
||||
---
|
||||
|
||||
## 三、接口详细说明
|
||||
|
||||
### 3.1 发起对话
|
||||
|
||||
#### 基础信息
|
||||
|
||||
| 项目 | 值 |
|
||||
|------|------|
|
||||
| 请求路径 | `POST /api/front/coze/chat` |
|
||||
| Content-Type | `application/json` |
|
||||
| 是否鉴权 | **否**(白名单) |
|
||||
|
||||
#### 请求参数
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| `botId` | String | 是 | Coze Bot ID |
|
||||
| `userId` | String | 是 | 业务系统用户 ID |
|
||||
| `stream` | Boolean | 否 | 是否流式返回,默认 false |
|
||||
| `chatHistory` | Array | 否 | 历史对话上下文 |
|
||||
| `additionalMessages` | Array | 否 | 当前发送的消息 |
|
||||
|
||||
**chatHistory 结构**
|
||||
|
||||
| 参数名 | 类型 | 说明 |
|
||||
|--------|------|------|
|
||||
| `role` | String | 角色:`user` / `assistant` |
|
||||
| `content` | String | 消息内容 |
|
||||
| `contentType` | String | 内容类型:`text` / `object_string` / `card` |
|
||||
|
||||
#### 测试用例
|
||||
|
||||
##### TC-COZE-01: 基础对话测试
|
||||
|
||||
```bash
|
||||
curl -X POST 'http://localhost:20822/api/front/coze/chat' \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"botId": "7591133240535449654",
|
||||
"userId": "test_user_001",
|
||||
"stream": false,
|
||||
"additionalMessages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "你好,请介绍一下自己"
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
**预期响应**
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"id": "xxxxx",
|
||||
"conversationId": "xxxxx",
|
||||
"botId": "7591133240535449654",
|
||||
"status": "completed",
|
||||
"createdAt": 1707350400,
|
||||
"completedAt": 1707350405
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
##### TC-COZE-02: 食谱计算器测试(男性透析患者)
|
||||
|
||||
```bash
|
||||
curl -X POST 'http://localhost:20822/api/front/coze/chat' \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"botId": "7591133240535449654",
|
||||
"userId": "3243981400446844",
|
||||
"stream": false,
|
||||
"additionalMessages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "请帮我计算营养方案,我的信息如下:性别男,年龄55岁,身高170cm,正在进行血液透析,干体重65.5kg,血肌酐850μmol/L"
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
##### TC-COZE-03: 食谱计算器测试(女性非透析患者)
|
||||
|
||||
```bash
|
||||
curl -X POST 'http://localhost:20822/api/front/coze/chat' \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"botId": "7591133240535449654",
|
||||
"userId": "3243981400446844",
|
||||
"stream": false,
|
||||
"additionalMessages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "请帮我计算营养方案,我的信息如下:性别女,年龄48岁,身高160cm,未透析,体重52kg,血肌酐180μmol/L"
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
##### TC-COZE-04: 带历史上下文的对话
|
||||
|
||||
```bash
|
||||
curl -X POST 'http://localhost:20822/api/front/coze/chat' \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"botId": "7591133240535449654",
|
||||
"userId": "test_user_001",
|
||||
"stream": false,
|
||||
"chatHistory": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "我是一名55岁的男性透析患者",
|
||||
"contentType": "text"
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "好的,我了解了您的基本情况。请问您还有其他健康数据需要提供吗?",
|
||||
"contentType": "text"
|
||||
}
|
||||
],
|
||||
"additionalMessages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "我的身高是170cm,体重65.5kg,血肌酐850"
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3.2 流式对话
|
||||
|
||||
#### 基础信息
|
||||
|
||||
| 项目 | 值 |
|
||||
|------|------|
|
||||
| 请求路径 | `POST /api/front/coze/chat/stream` |
|
||||
| Content-Type | `application/json` |
|
||||
| Response Type | `text/event-stream` |
|
||||
|
||||
#### 测试用例
|
||||
|
||||
##### TC-COZE-05: 流式对话测试
|
||||
|
||||
```bash
|
||||
curl -X POST 'http://localhost:20822/api/front/coze/chat/stream' \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Accept: text/event-stream" \
|
||||
-d '{
|
||||
"botId": "7591133240535449654",
|
||||
"userId": "test_user_001",
|
||||
"additionalMessages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "请详细介绍一下肾病患者的饮食注意事项"
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
**预期响应(SSE 格式)**
|
||||
|
||||
```
|
||||
event: message
|
||||
data: {"event":"conversation.chat.created","chat":{"id":"xxx","conversation_id":"xxx"}}
|
||||
|
||||
event: message
|
||||
data: {"event":"conversation.message.delta","message":{"content":"肾病患者..."}}
|
||||
|
||||
event: message
|
||||
data: {"event":"conversation.message.delta","message":{"content":"需要注意..."}}
|
||||
|
||||
event: message
|
||||
data: {"event":"conversation.chat.completed","chat":{"status":"completed"}}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3.3 执行工作流
|
||||
|
||||
#### 基础信息
|
||||
|
||||
| 项目 | 值 |
|
||||
|------|------|
|
||||
| 请求路径 | `POST /api/front/coze/workflow/run` |
|
||||
| Content-Type | `application/json` |
|
||||
|
||||
#### 请求参数
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| `workflowId` | String | 是 | 工作流 ID |
|
||||
| `parameters` | Object | 否 | 工作流输入参数 |
|
||||
| `isAsync` | Boolean | 否 | 是否异步执行,默认 false |
|
||||
|
||||
#### 测试用例
|
||||
|
||||
##### TC-COZE-06: 同步执行工作流
|
||||
|
||||
```bash
|
||||
curl -X POST 'http://localhost:20822/api/front/coze/workflow/run' \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"workflowId": "1180790412263",
|
||||
"isAsync": false,
|
||||
"parameters": {
|
||||
"gender": "male",
|
||||
"age": 55,
|
||||
"height": 170,
|
||||
"dialysis": true,
|
||||
"dialysisType": "hemodialysis",
|
||||
"dryWeight": 65.5,
|
||||
"creatinine": 850
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
##### TC-COZE-07: 异步执行工作流
|
||||
|
||||
```bash
|
||||
curl -X POST 'http://localhost:20822/api/front/coze/workflow/run' \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"workflowId": "1180790412263",
|
||||
"isAsync": true,
|
||||
"parameters": {
|
||||
"userId": "test_user_001",
|
||||
"recordId": 12345
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3.4 流式执行工作流
|
||||
|
||||
#### 基础信息
|
||||
|
||||
| 项目 | 值 |
|
||||
|------|------|
|
||||
| 请求路径 | `POST /api/front/coze/workflow/stream` |
|
||||
| Content-Type | `application/json` |
|
||||
| Response Type | `text/event-stream` |
|
||||
|
||||
#### 测试用例
|
||||
|
||||
##### TC-COZE-08: 流式工作流测试
|
||||
|
||||
```bash
|
||||
curl -X POST 'http://localhost:20822/api/front/coze/workflow/stream' \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Accept: text/event-stream" \
|
||||
-d '{
|
||||
"workflowId": "1180790412263",
|
||||
"parameters": {
|
||||
"input": "分析用户的饮食记录"
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3.5 查看对话详情
|
||||
|
||||
#### 基础信息
|
||||
|
||||
| 项目 | 值 |
|
||||
|------|------|
|
||||
| 请求路径 | `POST /api/front/coze/chat/retrieve` |
|
||||
| Content-Type | `application/json` |
|
||||
|
||||
#### 请求参数
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| `conversationId` | String | 是 | 对话 ID |
|
||||
| `chatId` | String | 是 | Chat ID |
|
||||
|
||||
#### 测试用例
|
||||
|
||||
##### TC-COZE-09: 查询对话详情
|
||||
|
||||
```bash
|
||||
curl -X POST 'http://localhost:20822/api/front/coze/chat/retrieve' \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"conversationId": "7460461142355574825",
|
||||
"chatId": "7460461142355590000"
|
||||
}'
|
||||
```
|
||||
|
||||
**预期响应**
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"id": "7460461142355590000",
|
||||
"conversationId": "7460461142355574825",
|
||||
"botId": "7591133240535449654",
|
||||
"status": "completed",
|
||||
"createdAt": 1707350400,
|
||||
"completedAt": 1707350410,
|
||||
"usage": {
|
||||
"tokenCount": 1500,
|
||||
"outputCount": 800,
|
||||
"inputCount": 700
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3.6 查看对话消息列表
|
||||
|
||||
#### 基础信息
|
||||
|
||||
| 项目 | 值 |
|
||||
|------|------|
|
||||
| 请求路径 | `POST /api/front/coze/chat/messages/list` |
|
||||
| Content-Type | `application/json` |
|
||||
|
||||
#### 请求参数
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| `conversationId` | String | 是 | 对话 ID |
|
||||
| `chatId` | String | 是 | Chat ID |
|
||||
|
||||
#### 测试用例
|
||||
|
||||
##### TC-COZE-10: 获取对话消息列表
|
||||
|
||||
```bash
|
||||
curl -X POST 'http://localhost:20822/api/front/coze/chat/messages/list' \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"conversationId": "7460461142355574825",
|
||||
"chatId": "7460461142355590000"
|
||||
}'
|
||||
```
|
||||
|
||||
**预期响应**
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": [
|
||||
{
|
||||
"id": "msg_001",
|
||||
"role": "user",
|
||||
"type": "question",
|
||||
"content": "请帮我计算营养方案",
|
||||
"contentType": "text",
|
||||
"createdAt": 1707350400
|
||||
},
|
||||
{
|
||||
"id": "msg_002",
|
||||
"role": "assistant",
|
||||
"type": "answer",
|
||||
"content": "根据您提供的信息,我为您计算了以下营养方案...",
|
||||
"contentType": "text",
|
||||
"createdAt": 1707350410
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3.7 上传文件
|
||||
|
||||
#### 基础信息
|
||||
|
||||
| 项目 | 值 |
|
||||
|------|------|
|
||||
| 请求路径 | `POST /api/front/coze/file/upload` |
|
||||
| Content-Type | `multipart/form-data` |
|
||||
|
||||
#### 请求参数
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| `file` | File | 是 | 待上传的文件 |
|
||||
|
||||
#### 测试用例
|
||||
|
||||
##### TC-COZE-11: 上传图片文件
|
||||
|
||||
```bash
|
||||
curl -X POST 'http://localhost:20822/api/front/coze/file/upload' \
|
||||
-F "file=@/path/to/your/image.jpg"
|
||||
```
|
||||
|
||||
##### TC-COZE-12: 上传文档文件
|
||||
|
||||
```bash
|
||||
curl -X POST 'http://localhost:20822/api/front/coze/file/upload' \
|
||||
-F "file=@/path/to/your/document.pdf"
|
||||
```
|
||||
|
||||
**预期响应**
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"id": "file_xxxxx",
|
||||
"bytes": 102400,
|
||||
"createdAt": 1707350400,
|
||||
"fileName": "image.jpg"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、错误码说明
|
||||
|
||||
| code | message | 说明 |
|
||||
|------|---------|------|
|
||||
| 200 | success | 请求成功 |
|
||||
| 400 | Bad Request | 请求参数错误 |
|
||||
| 401 | Unauthorized | 认证失败(Token 无效或过期) |
|
||||
| 404 | Not Found | 资源不存在 |
|
||||
| 429 | Too Many Requests | 请求频率超限 |
|
||||
| 500 | Internal Server Error | 服务端内部错误 |
|
||||
|
||||
---
|
||||
|
||||
## 五、常见问题
|
||||
|
||||
### 5.1 Token 过期
|
||||
|
||||
当前使用 PAT 模式,Token 有效期 30 天。如遇 401 错误,请检查配置文件中的 `coze.api.token` 是否过期。
|
||||
|
||||
### 5.2 Bot ID 无效
|
||||
|
||||
确保使用正确的 Bot ID。默认 Bot ID `7591133240535449654` 为食谱计算器专用。
|
||||
|
||||
### 5.3 流式接口超时
|
||||
|
||||
流式接口默认超时时间为 60 秒。对于长时间运行的对话,请确保客户端保持连接。
|
||||
|
||||
---
|
||||
|
||||
## 六、Postman 测试集合
|
||||
|
||||
可导入以下 JSON 到 Postman 进行测试:
|
||||
|
||||
```json
|
||||
{
|
||||
"info": {
|
||||
"name": "Coze API Tests",
|
||||
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
|
||||
},
|
||||
"item": [
|
||||
{
|
||||
"name": "基础对话",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [{"key": "Content-Type", "value": "application/json"}],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\"botId\": \"7591133240535449654\", \"userId\": \"test_user_001\", \"stream\": false, \"additionalMessages\": [{\"role\": \"user\", \"content\": \"你好\"}]}"
|
||||
},
|
||||
"url": {"raw": "http://localhost:20822/api/front/coze/chat"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "食谱计算-男性透析",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [{"key": "Content-Type", "value": "application/json"}],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\"botId\": \"7591133240535449654\", \"userId\": \"3243981400446844\", \"stream\": false, \"additionalMessages\": [{\"role\": \"user\", \"content\": \"请帮我计算营养方案,我的信息如下:性别男,年龄55岁,身高170cm,正在进行血液透析,干体重65.5kg,血肌酐850μmol/L\"}]}"
|
||||
},
|
||||
"url": {"raw": "http://localhost:20822/api/front/coze/chat"}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 七、变更记录
|
||||
|
||||
| 版本 | 日期 | 作者 | 变更内容 |
|
||||
|------|------|------|----------|
|
||||
| v1.0 | 2026-02-08 | System | 初稿 |
|
||||
|
||||
---
|
||||
|
||||
*文档结束*
|
||||
808
docs/Testing/Tool模块-API测试文档.md
Normal file
808
docs/Testing/Tool模块-API测试文档.md
Normal file
@@ -0,0 +1,808 @@
|
||||
# Tool 模块 API 测试文档
|
||||
|
||||
> **版本**:v1.0
|
||||
> **创建日期**:2026-02-08
|
||||
> **基础路径**:`/api/front/tool`
|
||||
> **服务端口**:20822
|
||||
> **认证方式**:需登录(Header: `Authori-zation`)
|
||||
|
||||
---
|
||||
|
||||
## 一、接口总览
|
||||
|
||||
### 1.1 模块分类
|
||||
|
||||
| 模块 | 接口数量 | 说明 |
|
||||
|------|----------|------|
|
||||
| 食谱计算器 | 3 | 营养方案计算与采纳 |
|
||||
| AI营养师 | 4 | AI 对话问答 |
|
||||
| 饮食打卡 | 8 | 打卡记录管理 |
|
||||
| 食物百科 | 4 | 食物查询 |
|
||||
| 营养知识 | 3 | 知识库查询 |
|
||||
| 打卡社区 | 9 | 社区互动 |
|
||||
| 积分系统 | 5 | 积分管理 |
|
||||
| 首页数据 | 4 | 首页聚合数据 |
|
||||
| 食谱管理 | 3 | 食谱收藏 |
|
||||
| 文件上传 | 2 | 图片/语音上传 |
|
||||
|
||||
---
|
||||
|
||||
## 二、认证说明
|
||||
|
||||
所有接口(除特殊标注外)均需要登录认证:
|
||||
|
||||
```bash
|
||||
# Header 示例
|
||||
-H "Authori-zation: eyJhbGciOiJIUzI1NiJ9.xxxxx"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、食谱计算器
|
||||
|
||||
### 3.1 计算营养方案
|
||||
|
||||
#### 基础信息
|
||||
|
||||
| 项目 | 值 |
|
||||
|------|------|
|
||||
| 请求路径 | `POST /api/front/tool/calculator/calculate` |
|
||||
| Content-Type | `application/json` |
|
||||
| 是否鉴权 | **是** |
|
||||
|
||||
#### 请求参数
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 | 校验规则 |
|
||||
|--------|------|------|------|----------|
|
||||
| `gender` | String | 是 | 性别 | `male` / `female` |
|
||||
| `age` | Integer | 是 | 年龄(岁) | 1 ≤ age ≤ 150 |
|
||||
| `height` | Integer | 是 | 身高(cm) | 50 ≤ height ≤ 250 |
|
||||
| `dialysis` | Boolean | 是 | 是否透析 | true / false |
|
||||
| `dialysisType` | String | 否 | 透析类型 | `hemodialysis` / `peritoneal` |
|
||||
| `dryWeight` | Number | 是 | 干体重(kg) | 20 ≤ dryWeight ≤ 300 |
|
||||
| `creatinine` | Number | 是 | 血肌酐(μmol/L) | 0 < creatinine ≤ 2000 |
|
||||
|
||||
#### 测试用例
|
||||
|
||||
##### TC-CALC-01: 男性透析患者计算
|
||||
|
||||
```bash
|
||||
curl -X POST 'http://localhost:20822/api/front/tool/calculator/calculate' \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authori-zation: YOUR_TOKEN" \
|
||||
-d '{
|
||||
"gender": "male",
|
||||
"age": 55,
|
||||
"height": 170,
|
||||
"dialysis": true,
|
||||
"dialysisType": "hemodialysis",
|
||||
"dryWeight": 65.5,
|
||||
"creatinine": 850
|
||||
}'
|
||||
```
|
||||
|
||||
**预期响应**
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"id": 100234,
|
||||
"healthData": {
|
||||
"eGFR": "7.9",
|
||||
"standardWeight": "63.0",
|
||||
"bmi": "22.7",
|
||||
"bmiStatus": "正常",
|
||||
"ckdStage": "透析期"
|
||||
},
|
||||
"nutritionGoals": {
|
||||
"protein": "75.6",
|
||||
"energy": "2205"
|
||||
},
|
||||
"foodList": [
|
||||
{ "number": 1, "name": "谷薯50g", "portion": "5.7" },
|
||||
{ "number": 2, "name": "淀粉100g", "portion": "0.77" },
|
||||
{ "number": 3, "name": "绿叶蔬菜200g", "portion": "1" },
|
||||
{ "number": 4, "name": "瓜果蔬菜200g", "portion": "2" },
|
||||
{ "number": 5, "name": "奶类230g", "portion": "1" },
|
||||
{ "number": 6, "name": "肉蛋类50/60g", "portion": "7" },
|
||||
{ "number": 7, "name": "油脂类10g", "portion": "5.7" }
|
||||
],
|
||||
"mealPlan": {
|
||||
"breakfast": [...],
|
||||
"lunch": [...],
|
||||
"dinner": [...]
|
||||
},
|
||||
"importantTips": [
|
||||
"以上配餐由 AI 生成,仅适用于无其他并发症的单纯尿毒症人群",
|
||||
"透析患者需严格控制水分摄入"
|
||||
],
|
||||
"createdAt": "2026-02-08T10:30:00+08:00"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
##### TC-CALC-02: 女性非透析患者计算
|
||||
|
||||
```bash
|
||||
curl -X POST 'http://localhost:20822/api/front/tool/calculator/calculate' \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authori-zation: YOUR_TOKEN" \
|
||||
-d '{
|
||||
"gender": "female",
|
||||
"age": 48,
|
||||
"height": 160,
|
||||
"dialysis": false,
|
||||
"dryWeight": 52,
|
||||
"creatinine": 180
|
||||
}'
|
||||
```
|
||||
|
||||
##### TC-CALC-03: 参数校验-年龄超出范围
|
||||
|
||||
```bash
|
||||
curl -X POST 'http://localhost:20822/api/front/tool/calculator/calculate' \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authori-zation: YOUR_TOKEN" \
|
||||
-d '{
|
||||
"gender": "male",
|
||||
"age": 200,
|
||||
"height": 170,
|
||||
"dialysis": false,
|
||||
"dryWeight": 65,
|
||||
"creatinine": 100
|
||||
}'
|
||||
```
|
||||
|
||||
**预期响应**:`code=400, message 包含"年龄"`
|
||||
|
||||
---
|
||||
|
||||
### 3.2 获取计算结果详情
|
||||
|
||||
#### 基础信息
|
||||
|
||||
| 项目 | 值 |
|
||||
|------|------|
|
||||
| 请求路径 | `GET /api/front/tool/calculator/result/{id}` |
|
||||
| 是否鉴权 | **是** |
|
||||
|
||||
#### 测试用例
|
||||
|
||||
##### TC-CALC-04: 获取计算结果
|
||||
|
||||
```bash
|
||||
curl -X GET 'http://localhost:20822/api/front/tool/calculator/result/100234' \
|
||||
-H "Authori-zation: YOUR_TOKEN"
|
||||
```
|
||||
|
||||
##### TC-CALC-05: 查询不存在的结果
|
||||
|
||||
```bash
|
||||
curl -X GET 'http://localhost:20822/api/front/tool/calculator/result/999999' \
|
||||
-H "Authori-zation: YOUR_TOKEN"
|
||||
```
|
||||
|
||||
**预期响应**:`code=404`
|
||||
|
||||
---
|
||||
|
||||
### 3.3 采纳营养计划
|
||||
|
||||
#### 基础信息
|
||||
|
||||
| 项目 | 值 |
|
||||
|------|------|
|
||||
| 请求路径 | `POST /api/front/tool/calculator/adopt` |
|
||||
| Content-Type | `application/json` |
|
||||
| 是否鉴权 | **是** |
|
||||
|
||||
#### 请求参数
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| `resultId` | Long | 是 | 计算结果 ID |
|
||||
|
||||
#### 测试用例
|
||||
|
||||
##### TC-CALC-06: 采纳营养计划
|
||||
|
||||
```bash
|
||||
curl -X POST 'http://localhost:20822/api/front/tool/calculator/adopt' \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authori-zation: YOUR_TOKEN" \
|
||||
-d '{
|
||||
"resultId": 100234
|
||||
}'
|
||||
```
|
||||
|
||||
**预期响应**
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"planId": 56789,
|
||||
"startDate": "2026-02-08",
|
||||
"endDate": "2026-02-14"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
##### TC-CALC-07: 重复采纳(幂等测试)
|
||||
|
||||
```bash
|
||||
# 同一 resultId 调用两次
|
||||
curl -X POST 'http://localhost:20822/api/front/tool/calculator/adopt' \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authori-zation: YOUR_TOKEN" \
|
||||
-d '{"resultId": 100234}'
|
||||
```
|
||||
|
||||
**预期响应**:返回已存在的 planId
|
||||
|
||||
---
|
||||
|
||||
## 四、AI 营养师
|
||||
|
||||
### 4.1 发送消息给 AI 营养师
|
||||
|
||||
#### 基础信息
|
||||
|
||||
| 项目 | 值 |
|
||||
|------|------|
|
||||
| 请求路径 | `POST /api/front/tool/ai-nutritionist/message` |
|
||||
| Content-Type | `application/json` |
|
||||
|
||||
#### 测试用例
|
||||
|
||||
##### TC-AI-01: 发送消息
|
||||
|
||||
```bash
|
||||
curl -X POST 'http://localhost:20822/api/front/tool/ai-nutritionist/message' \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authori-zation: YOUR_TOKEN" \
|
||||
-d '{
|
||||
"conversationId": "conv_001",
|
||||
"content": "肾病患者可以吃香蕉吗?"
|
||||
}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4.2 获取 AI 回复
|
||||
|
||||
#### 基础信息
|
||||
|
||||
| 项目 | 值 |
|
||||
|------|------|
|
||||
| 请求路径 | `GET /api/front/tool/ai-nutritionist/response/{messageId}` |
|
||||
|
||||
#### 测试用例
|
||||
|
||||
##### TC-AI-02: 获取回复
|
||||
|
||||
```bash
|
||||
curl -X GET 'http://localhost:20822/api/front/tool/ai-nutritionist/response/12345' \
|
||||
-H "Authori-zation: YOUR_TOKEN"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4.3 获取对话历史
|
||||
|
||||
#### 基础信息
|
||||
|
||||
| 项目 | 值 |
|
||||
|------|------|
|
||||
| 请求路径 | `GET /api/front/tool/ai-nutritionist/history` |
|
||||
|
||||
#### 请求参数
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| `page` | Integer | 否 | 页码,默认 1 |
|
||||
| `limit` | Integer | 否 | 每页数量,默认 20 |
|
||||
| `conversationId` | Long | 否 | 对话 ID |
|
||||
|
||||
#### 测试用例
|
||||
|
||||
##### TC-AI-03: 获取对话历史
|
||||
|
||||
```bash
|
||||
curl -X GET 'http://localhost:20822/api/front/tool/ai-nutritionist/history?page=1&limit=20' \
|
||||
-H "Authori-zation: YOUR_TOKEN"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4.4 清空对话历史
|
||||
|
||||
#### 测试用例
|
||||
|
||||
##### TC-AI-04: 清空对话
|
||||
|
||||
```bash
|
||||
curl -X POST 'http://localhost:20822/api/front/tool/ai-nutritionist/clear' \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authori-zation: YOUR_TOKEN" \
|
||||
-d '{
|
||||
"conversationId": "conv_001"
|
||||
}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 五、饮食打卡
|
||||
|
||||
### 5.1 提交打卡记录
|
||||
|
||||
#### 基础信息
|
||||
|
||||
| 项目 | 值 |
|
||||
|------|------|
|
||||
| 请求路径 | `POST /api/front/tool/checkin/submit` |
|
||||
| Content-Type | `application/json` |
|
||||
|
||||
#### 测试用例
|
||||
|
||||
##### TC-CHECKIN-01: 提交打卡
|
||||
|
||||
```bash
|
||||
curl -X POST 'http://localhost:20822/api/front/tool/checkin/submit' \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authori-zation: YOUR_TOKEN" \
|
||||
-d '{
|
||||
"mealType": "breakfast",
|
||||
"date": "2026-02-08",
|
||||
"foods": [
|
||||
{"name": "牛奶", "amount": "250ml"},
|
||||
{"name": "全麦面包", "amount": "2片"}
|
||||
],
|
||||
"images": ["https://cdn.xxx.com/img1.jpg"],
|
||||
"remark": "早餐清淡"
|
||||
}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5.2 获取打卡记录列表
|
||||
|
||||
#### 请求参数
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| `page` | Integer | 否 | 页码 |
|
||||
| `limit` | Integer | 否 | 每页数量 |
|
||||
| `date` | String | 否 | 日期筛选 YYYY-MM-DD |
|
||||
| `mealType` | String | 否 | 餐次:breakfast/lunch/dinner |
|
||||
|
||||
#### 测试用例
|
||||
|
||||
##### TC-CHECKIN-02: 获取打卡列表
|
||||
|
||||
```bash
|
||||
curl -X GET 'http://localhost:20822/api/front/tool/checkin/list?page=1&limit=10&date=2026-02-08' \
|
||||
-H "Authori-zation: YOUR_TOKEN"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5.3 获取打卡记录详情
|
||||
|
||||
```bash
|
||||
curl -X GET 'http://localhost:20822/api/front/tool/checkin/detail/123' \
|
||||
-H "Authori-zation: YOUR_TOKEN"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5.4 获取连续打卡统计
|
||||
|
||||
```bash
|
||||
curl -X GET 'http://localhost:20822/api/front/tool/checkin/streak' \
|
||||
-H "Authori-zation: YOUR_TOKEN"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5.5 获取打卡日历数据
|
||||
|
||||
```bash
|
||||
curl -X GET 'http://localhost:20822/api/front/tool/checkin/calendar?yearMonth=2026-02' \
|
||||
-H "Authori-zation: YOUR_TOKEN"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5.6 获取打卡任务列表
|
||||
|
||||
```bash
|
||||
curl -X GET 'http://localhost:20822/api/front/tool/checkin/tasks' \
|
||||
-H "Authori-zation: YOUR_TOKEN"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5.7 一键复制打卡
|
||||
|
||||
```bash
|
||||
curl -X POST 'http://localhost:20822/api/front/tool/checkin/copy' \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authori-zation: YOUR_TOKEN" \
|
||||
-d '{
|
||||
"sourceRecordId": 12345,
|
||||
"targetDate": "2026-02-08",
|
||||
"mealType": "lunch"
|
||||
}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5.8 一键借鉴打卡
|
||||
|
||||
```bash
|
||||
curl -X POST 'http://localhost:20822/api/front/tool/checkin/learn' \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authori-zation: YOUR_TOKEN" \
|
||||
-d '{
|
||||
"sourcePostId": 67890,
|
||||
"targetDate": "2026-02-08",
|
||||
"mealType": "dinner"
|
||||
}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 六、食物百科
|
||||
|
||||
### 6.1 搜索食物
|
||||
|
||||
```bash
|
||||
curl -X GET 'http://localhost:20822/api/front/tool/food/search?keyword=鸡蛋&category=蛋类&page=1&limit=20' \
|
||||
-H "Authori-zation: YOUR_TOKEN"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 6.2 获取食物列表
|
||||
|
||||
```bash
|
||||
curl -X GET 'http://localhost:20822/api/front/tool/food/list?category=肉类&page=1&limit=20' \
|
||||
-H "Authori-zation: YOUR_TOKEN"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 6.3 获取食物详情
|
||||
|
||||
```bash
|
||||
curl -X GET 'http://localhost:20822/api/front/tool/food/detail/100' \
|
||||
-H "Authori-zation: YOUR_TOKEN"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 6.4 获取相似食物推荐
|
||||
|
||||
```bash
|
||||
curl -X GET 'http://localhost:20822/api/front/tool/food/similar/100' \
|
||||
-H "Authori-zation: YOUR_TOKEN"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 七、营养知识
|
||||
|
||||
### 7.1 获取营养知识列表
|
||||
|
||||
```bash
|
||||
curl -X GET 'http://localhost:20822/api/front/tool/knowledge/list?type=article&category=肾病饮食&page=1&limit=10' \
|
||||
-H "Authori-zation: YOUR_TOKEN"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.2 获取营养知识详情
|
||||
|
||||
```bash
|
||||
curl -X GET 'http://localhost:20822/api/front/tool/knowledge/detail/50' \
|
||||
-H "Authori-zation: YOUR_TOKEN"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7.3 获取营养素详情
|
||||
|
||||
```bash
|
||||
curl -X GET 'http://localhost:20822/api/front/tool/knowledge/nutrient/蛋白质' \
|
||||
-H "Authori-zation: YOUR_TOKEN"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 八、打卡社区
|
||||
|
||||
### 8.1 获取社区内容列表
|
||||
|
||||
```bash
|
||||
curl -X GET 'http://localhost:20822/api/front/tool/community/list?tab=recommend&page=1&limit=10' \
|
||||
-H "Authori-zation: YOUR_TOKEN"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 8.2 获取社区内容详情
|
||||
|
||||
```bash
|
||||
curl -X GET 'http://localhost:20822/api/front/tool/community/detail/200' \
|
||||
-H "Authori-zation: YOUR_TOKEN"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 8.3 发布社区内容
|
||||
|
||||
```bash
|
||||
curl -X POST 'http://localhost:20822/api/front/tool/community/publish' \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authori-zation: YOUR_TOKEN" \
|
||||
-d '{
|
||||
"content": "今天的早餐很健康!",
|
||||
"images": ["https://cdn.xxx.com/img1.jpg"],
|
||||
"checkinId": 12345
|
||||
}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 8.4 点赞/取消点赞
|
||||
|
||||
```bash
|
||||
curl -X POST 'http://localhost:20822/api/front/tool/community/like' \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authori-zation: YOUR_TOKEN" \
|
||||
-d '{
|
||||
"postId": 200,
|
||||
"isLike": true
|
||||
}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 8.5 收藏/取消收藏
|
||||
|
||||
```bash
|
||||
curl -X POST 'http://localhost:20822/api/front/tool/community/collect' \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authori-zation: YOUR_TOKEN" \
|
||||
-d '{
|
||||
"postId": 200,
|
||||
"isCollect": true
|
||||
}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 8.6 发表评论
|
||||
|
||||
```bash
|
||||
curl -X POST 'http://localhost:20822/api/front/tool/community/comment' \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authori-zation: YOUR_TOKEN" \
|
||||
-d '{
|
||||
"postId": 200,
|
||||
"content": "看起来很不错!",
|
||||
"parentId": null
|
||||
}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 8.7 获取评论列表
|
||||
|
||||
```bash
|
||||
curl -X GET 'http://localhost:20822/api/front/tool/community/comment/list/200?page=1&limit=20' \
|
||||
-H "Authori-zation: YOUR_TOKEN"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 8.8 关注/取消关注用户
|
||||
|
||||
```bash
|
||||
curl -X POST 'http://localhost:20822/api/front/tool/community/follow' \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authori-zation: YOUR_TOKEN" \
|
||||
-d '{
|
||||
"userId": 1001,
|
||||
"isFollow": true
|
||||
}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 8.9 分享内容
|
||||
|
||||
```bash
|
||||
curl -X POST 'http://localhost:20822/api/front/tool/community/share' \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authori-zation: YOUR_TOKEN" \
|
||||
-d '{
|
||||
"postId": 200
|
||||
}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 九、积分系统
|
||||
|
||||
### 9.1 获取用户积分信息
|
||||
|
||||
```bash
|
||||
curl -X GET 'http://localhost:20822/api/front/tool/points/info' \
|
||||
-H "Authori-zation: YOUR_TOKEN"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 9.2 获取积分规则
|
||||
|
||||
```bash
|
||||
curl -X GET 'http://localhost:20822/api/front/tool/points/rules' \
|
||||
-H "Authori-zation: YOUR_TOKEN"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 9.3 获取积分流水
|
||||
|
||||
```bash
|
||||
curl -X GET 'http://localhost:20822/api/front/tool/points/history?type=earn&page=1&limit=20' \
|
||||
-H "Authori-zation: YOUR_TOKEN"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 9.4 获取积分兑换列表
|
||||
|
||||
```bash
|
||||
curl -X GET 'http://localhost:20822/api/front/tool/points/exchange/list' \
|
||||
-H "Authori-zation: YOUR_TOKEN"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 9.5 积分兑换
|
||||
|
||||
```bash
|
||||
curl -X POST 'http://localhost:20822/api/front/tool/points/exchange' \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authori-zation: YOUR_TOKEN" \
|
||||
-d '{
|
||||
"exchangeId": 10
|
||||
}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 十、首页数据
|
||||
|
||||
### 10.1 获取首页数据
|
||||
|
||||
```bash
|
||||
curl -X GET 'http://localhost:20822/api/front/tool/home/data' \
|
||||
-H "Authori-zation: YOUR_TOKEN"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 10.2 获取推荐食谱列表
|
||||
|
||||
```bash
|
||||
curl -X GET 'http://localhost:20822/api/front/tool/home/recipes?limit=6' \
|
||||
-H "Authori-zation: YOUR_TOKEN"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 10.3 获取推荐营养知识
|
||||
|
||||
```bash
|
||||
curl -X GET 'http://localhost:20822/api/front/tool/home/knowledge?limit=4' \
|
||||
-H "Authori-zation: YOUR_TOKEN"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 10.4 获取用户健康档案状态
|
||||
|
||||
```bash
|
||||
curl -X GET 'http://localhost:20822/api/front/tool/home/health-status' \
|
||||
-H "Authori-zation: YOUR_TOKEN"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 十一、食谱管理
|
||||
|
||||
### 11.1 获取食谱列表
|
||||
|
||||
```bash
|
||||
curl -X GET 'http://localhost:20822/api/front/tool/recipe/list?mealType=breakfast&page=1&limit=10' \
|
||||
-H "Authori-zation: YOUR_TOKEN"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 11.2 获取食谱详情
|
||||
|
||||
```bash
|
||||
curl -X GET 'http://localhost:20822/api/front/tool/recipe/detail/50' \
|
||||
-H "Authori-zation: YOUR_TOKEN"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 11.3 收藏/取消收藏食谱
|
||||
|
||||
```bash
|
||||
curl -X POST 'http://localhost:20822/api/front/tool/recipe/favorite' \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authori-zation: YOUR_TOKEN" \
|
||||
-d '{
|
||||
"recipeId": 50,
|
||||
"isFavorite": true
|
||||
}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 十二、文件上传
|
||||
|
||||
### 12.1 上传图片
|
||||
|
||||
```bash
|
||||
curl -X POST 'http://localhost:20822/api/front/tool/upload/image?type=checkin' \
|
||||
-H "Authori-zation: YOUR_TOKEN" \
|
||||
-F "file=@/path/to/image.jpg"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 12.2 上传语音
|
||||
|
||||
```bash
|
||||
curl -X POST 'http://localhost:20822/api/front/tool/upload/voice' \
|
||||
-H "Authori-zation: YOUR_TOKEN" \
|
||||
-F "file=@/path/to/voice.mp3"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 十三、错误码说明
|
||||
|
||||
| code | message | 说明 |
|
||||
|------|---------|------|
|
||||
| 200 | success | 请求成功 |
|
||||
| 400 | 参数校验失败 | 请求参数不符合校验规则 |
|
||||
| 401 / 410000 | 未登录 | Token 缺失 |
|
||||
| 410001 | Token 无效 | Token 格式错误 |
|
||||
| 410002 | 登录过期 | Token 已过期 |
|
||||
| 403 | 无权限 | 访问他人数据 |
|
||||
| 404 | 资源不存在 | 数据不存在 |
|
||||
| 500 | 系统异常 | 服务端内部错误 |
|
||||
|
||||
---
|
||||
|
||||
## 十四、变更记录
|
||||
|
||||
| 版本 | 日期 | 作者 | 变更内容 |
|
||||
|------|------|------|----------|
|
||||
| v1.0 | 2026-02-08 | System | 初稿 |
|
||||
|
||||
---
|
||||
|
||||
*文档结束*
|
||||
25
docs/db/db_design.md
Normal file
25
docs/db/db_design.md
Normal file
@@ -0,0 +1,25 @@
|
||||
|
||||
## 数据库字典
|
||||
|
||||
### tool模块相关表
|
||||
- 用户表: eb_user
|
||||
- 用户积分记录表:eb_user_integral_record
|
||||
- 用户签到表:eb_user_sign
|
||||
- 文章内容表:eb_article
|
||||
|
||||
- 用户积分表: v2_user_points
|
||||
- 一键打卡记录表: v2_quick_checkin_records
|
||||
- AI营养师对话表: v2_ai_conversations
|
||||
- AI营养师消息表: v2_ai_messages
|
||||
- 营养师咨询记录表: v2_nutritionist_consultations
|
||||
- 食物百科表: v2_foods
|
||||
- 营养知识表: v2_knowledge
|
||||
- 食谱表: v2_recipes
|
||||
- 打卡社区表: v2_community_posts
|
||||
- 打卡社区互动表: v2_community_interactions
|
||||
- 打卡社区评论表: v2_community_comments
|
||||
- 打卡社区关注表: v2_community_follows
|
||||
- 营养计划表: v2_nutrition_plans
|
||||
- 食谱计算器结果表: v2_calculator_results
|
||||
|
||||
|
||||
2594
docs/db/shop-msh.sql
Normal file
2594
docs/db/shop-msh.sql
Normal file
File diff suppressed because it is too large
Load Diff
104
docs/sql/v2_calculator_results_alter.sql
Normal file
104
docs/sql/v2_calculator_results_alter.sql
Normal file
@@ -0,0 +1,104 @@
|
||||
-- =========================================================
|
||||
-- 食谱计算器表结构变更 SQL
|
||||
-- 版本:v1.0
|
||||
-- 日期:2026-02-01
|
||||
-- 说明:根据开发文档优化后,需要新增以下字段
|
||||
-- =========================================================
|
||||
|
||||
-- 检查并添加 bmi_status 字段
|
||||
ALTER TABLE `v2_calculator_results`
|
||||
ADD COLUMN IF NOT EXISTS `bmi_status` VARCHAR(20) DEFAULT NULL COMMENT 'BMI状态描述:体型过轻/正常/超重/肥胖'
|
||||
AFTER `bmi`;
|
||||
|
||||
-- 检查并添加 food_list_json 字段
|
||||
ALTER TABLE `v2_calculator_results`
|
||||
ADD COLUMN IF NOT EXISTS `food_list_json` TEXT DEFAULT NULL COMMENT '食物份数建议 JSON'
|
||||
AFTER `energy_intake`;
|
||||
|
||||
-- 检查并添加 tips_json 字段
|
||||
ALTER TABLE `v2_calculator_results`
|
||||
ADD COLUMN IF NOT EXISTS `tips_json` TEXT DEFAULT NULL COMMENT '重要提示 JSON'
|
||||
AFTER `meal_plan_json`;
|
||||
|
||||
-- 如果表不存在,创建完整表结构
|
||||
CREATE TABLE IF NOT EXISTS `v2_calculator_results` (
|
||||
`result_id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '计算结果ID',
|
||||
`user_id` BIGINT NOT NULL COMMENT '用户ID',
|
||||
|
||||
-- 输入参数
|
||||
`gender` VARCHAR(10) NOT NULL COMMENT '性别:male/female',
|
||||
`age` INT NOT NULL COMMENT '年龄(岁)',
|
||||
`height` INT NOT NULL COMMENT '身高(cm)',
|
||||
`weight` DECIMAL(5,2) DEFAULT NULL COMMENT '体重(kg)',
|
||||
`has_dialysis` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否透析:0-否 1-是',
|
||||
`dialysis_type` VARCHAR(20) DEFAULT NULL COMMENT '透析类型:hemodialysis/peritoneal',
|
||||
`dry_weight` DECIMAL(5,2) NOT NULL COMMENT '干体重(kg)',
|
||||
`creatinine` DECIMAL(8,2) NOT NULL COMMENT '血肌酐(μmol/L)',
|
||||
|
||||
-- 计算结果
|
||||
`egfr` DECIMAL(6,2) NOT NULL COMMENT 'eGFR(ml/min/1.73m²)',
|
||||
`standard_weight` DECIMAL(5,2) NOT NULL COMMENT '标准体重(kg)',
|
||||
`bmi` DECIMAL(4,1) NOT NULL COMMENT 'BMI',
|
||||
`bmi_status` VARCHAR(20) DEFAULT NULL COMMENT 'BMI状态描述',
|
||||
`ckd_stage` VARCHAR(50) NOT NULL COMMENT 'CKD分期',
|
||||
`protein_intake` DECIMAL(5,1) NOT NULL COMMENT '每日蛋白质目标(g)',
|
||||
`energy_intake` INT NOT NULL COMMENT '每日能量目标(kcal)',
|
||||
|
||||
-- JSON 数据
|
||||
`food_list_json` TEXT DEFAULT NULL COMMENT '食物份数建议 JSON',
|
||||
`meal_plan_json` TEXT DEFAULT NULL COMMENT '配餐方案 JSON',
|
||||
`tips_json` TEXT DEFAULT NULL COMMENT '重要提示 JSON',
|
||||
|
||||
-- 状态字段
|
||||
`is_adopted` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否已采纳:0-未采纳 1-已采纳',
|
||||
`adopted_at` DATETIME DEFAULT NULL COMMENT '采纳时间',
|
||||
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
|
||||
PRIMARY KEY (`result_id`),
|
||||
INDEX `idx_user_id` (`user_id`),
|
||||
INDEX `idx_created_at` (`created_at`),
|
||||
INDEX `idx_is_adopted` (`is_adopted`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='营养计算结果表';
|
||||
|
||||
-- 营养计划表(如果不存在则创建)
|
||||
CREATE TABLE IF NOT EXISTS `v2_nutrition_plans` (
|
||||
`plan_id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '营养计划ID',
|
||||
`user_id` BIGINT NOT NULL COMMENT '用户ID',
|
||||
`result_id` BIGINT DEFAULT NULL COMMENT '关联的计算结果ID',
|
||||
|
||||
-- 健康数据(冗余存储,便于查询)
|
||||
`gender` VARCHAR(10) DEFAULT NULL COMMENT '性别',
|
||||
`age` INT DEFAULT NULL COMMENT '年龄',
|
||||
`height` INT DEFAULT NULL COMMENT '身高(cm)',
|
||||
`weight` DECIMAL(5,2) DEFAULT NULL COMMENT '体重(kg)',
|
||||
`has_dialysis` TINYINT(1) DEFAULT 0 COMMENT '是否透析',
|
||||
`dialysis_type` VARCHAR(20) DEFAULT NULL COMMENT '透析类型',
|
||||
`creatinine` DECIMAL(8,2) DEFAULT NULL COMMENT '血肌酐',
|
||||
|
||||
-- 计算结果
|
||||
`egfr` DECIMAL(6,2) DEFAULT NULL COMMENT 'eGFR',
|
||||
`standard_weight` DECIMAL(5,2) DEFAULT NULL COMMENT '标准体重',
|
||||
`bmi` DECIMAL(4,1) DEFAULT NULL COMMENT 'BMI',
|
||||
`ckd_stage` VARCHAR(50) DEFAULT NULL COMMENT 'CKD分期',
|
||||
`protein_intake` DECIMAL(5,1) DEFAULT NULL COMMENT '蛋白质目标',
|
||||
`energy_intake` INT DEFAULT NULL COMMENT '能量目标',
|
||||
|
||||
-- 配餐方案
|
||||
`meal_plan_json` TEXT DEFAULT NULL COMMENT '配餐方案 JSON',
|
||||
|
||||
-- 计划状态
|
||||
`status` VARCHAR(20) NOT NULL DEFAULT 'active' COMMENT '状态:active/completed/abandoned',
|
||||
`start_date` DATE NOT NULL COMMENT '开始日期',
|
||||
`end_date` DATE NOT NULL COMMENT '结束日期',
|
||||
|
||||
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
|
||||
PRIMARY KEY (`plan_id`),
|
||||
INDEX `idx_user_id` (`user_id`),
|
||||
INDEX `idx_result_id` (`result_id`),
|
||||
INDEX `idx_status` (`status`),
|
||||
INDEX `idx_start_date` (`start_date`),
|
||||
UNIQUE INDEX `uk_user_result` (`user_id`, `result_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='营养计划表';
|
||||
15
docs/sql/v2_recipes_add_source.sql
Normal file
15
docs/sql/v2_recipes_add_source.sql
Normal file
@@ -0,0 +1,15 @@
|
||||
-- =============================================
|
||||
-- v2_recipes 表新增 source 相关字段
|
||||
-- 用于追踪食谱来源(手动/计算器/AI生成等)
|
||||
-- Author: ScottPan
|
||||
-- Date: 2026-02-14
|
||||
-- =============================================
|
||||
|
||||
ALTER TABLE v2_recipes
|
||||
ADD COLUMN `source` VARCHAR(20) DEFAULT 'manual' COMMENT '来源:manual(手动)/calculator(计算器)/ai(AI生成)' AFTER `sort_order`,
|
||||
ADD COLUMN `source_id` BIGINT DEFAULT NULL COMMENT '来源ID(如计算器结果ID)' AFTER `source`;
|
||||
|
||||
-- 新增索引,便于按来源查询
|
||||
ALTER TABLE v2_recipes
|
||||
ADD KEY `idx_source` (`source`),
|
||||
ADD KEY `idx_source_id` (`source_id`);
|
||||
BIN
docs/慢生活智能营养专家小程序用户界面交互设计文档.pdf
Normal file
BIN
docs/慢生活智能营养专家小程序用户界面交互设计文档.pdf
Normal file
Binary file not shown.
602
docs/打卡社区功能设计方案.md
Normal file
602
docs/打卡社区功能设计方案.md
Normal file
@@ -0,0 +1,602 @@
|
||||
# 打卡社区功能设计方案
|
||||
## 慢生活智能营养专家 - 类似小红书的UGC社区
|
||||
|
||||
---
|
||||
|
||||
## 📋 功能概述
|
||||
|
||||
**产品定位**:将个人打卡记录转化为可分享的UGC内容,打造肾病患者的饮食分享社区。
|
||||
|
||||
**对标产品**:小红书(内容社区)+ Keep运动社区(打卡分享)
|
||||
|
||||
**核心价值**:
|
||||
1. 📸 **降低UGC门槛** - 打卡记录一键转分享,无需重新创作
|
||||
2. 👥 **社交连接** - 患者之间互相学习、点赞、评论、关注
|
||||
3. 🎯 **激励增强** - 社交认同感大幅增强打卡动力
|
||||
4. 💰 **商业价值** - 优质内容吸引新用户,为KOL孵化和广告变现打基础
|
||||
|
||||
---
|
||||
|
||||
## 🎨 核心页面展示
|
||||
|
||||
### 1. 社区广场(瀑布流)
|
||||
|
||||
```
|
||||
┌────────────────────────────────┐
|
||||
│ 🏠 社区广场 [发布+] │
|
||||
└────────────────────────────────┘
|
||||
|
||||
【筛选Tab】 [推荐] [最新] [关注] [热门]
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
【瀑布流布局】(左右2列)
|
||||
|
||||
┌──────────┐ ┌──────────┐
|
||||
│ 图片 │ │ 图片 │
|
||||
│ │ │ │
|
||||
├──────────┤ ├──────────┤
|
||||
│ 🥗 早餐打 │ │ 🍱 午餐低 │
|
||||
│ 卡第7天│ │ 钾配餐 │
|
||||
│ │ │ │
|
||||
│ 👤 张小慢 │ │ 👤 李大康 │
|
||||
│ ❤️ 128 │ │ ❤️ 256 │
|
||||
└──────────┘ └──────────┘
|
||||
```
|
||||
|
||||
**关键特性**:
|
||||
- 瀑布流布局,视觉效果好
|
||||
- 封面图自动取打卡照片第一张
|
||||
- 显示点赞数,体现内容质量
|
||||
- 4个Tab满足不同浏览需求
|
||||
|
||||
### 2. 内容详情页
|
||||
|
||||
```
|
||||
┌────────────────────────────────┐
|
||||
│ 【用户信息】 │
|
||||
│ 👤 张小慢(透析3年) │
|
||||
│ 📅 2025-11-20 12:30 │
|
||||
│ [+ 关注] │
|
||||
└────────────────────────────────┘
|
||||
|
||||
┌────────────────────────────────┐
|
||||
│ 【图片轮播】1/3 │
|
||||
│ [滑动查看更多照片] │
|
||||
└────────────────────────────────┘
|
||||
|
||||
┌────────────────────────────────┐
|
||||
│ 🥗 早餐打卡第7天!终于坚持下来了 │
|
||||
│ #早餐 #低钾饮食 #透析期 │
|
||||
│ │
|
||||
│ 今天的早餐:牛奶+鸡蛋拌面+黄瓜 │
|
||||
│ 照片不是很好看但营养达标啦~ │
|
||||
└────────────────────────────────┘
|
||||
|
||||
┌────────────────────────────────┐
|
||||
│ 【营养数据】 │
|
||||
│ 蛋白质:18.8g (达标94%) │
|
||||
│ 能量:452kcal (达标90%) │
|
||||
│ 🎯 整体营养达标率:92% │
|
||||
└────────────────────────────────┘
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
┌────────────────────────────────┐
|
||||
│ ❤️ 128 💬 23 ⭐ 45 ➤ 分享 │
|
||||
└────────────────────────────────┘
|
||||
|
||||
┌────────────────────────────────┐
|
||||
│ 💬 评论区 │
|
||||
│ 太棒了!我也要学习这样吃 │
|
||||
│ 坚持得很好,营养搭配合理👍 │
|
||||
└────────────────────────────────┘
|
||||
```
|
||||
|
||||
**关键特性**:
|
||||
- 完整的营养数据展示(来自打卡记录)
|
||||
- 图片轮播支持1-3张
|
||||
- 互动栏:点赞/评论/收藏/分享
|
||||
- 评论区支持点赞和回复
|
||||
|
||||
### 3. 发布页面
|
||||
|
||||
```
|
||||
┌────────────────────────────────┐
|
||||
│ < 分享打卡记录 [发布] │
|
||||
└────────────────────────────────┘
|
||||
|
||||
【选择封面图】从打卡照片中选择
|
||||
|
||||
【标题】
|
||||
🥗 早餐打卡第7天!
|
||||
[AI帮你写标题 ✨]
|
||||
|
||||
【正文】
|
||||
分享一下你的打卡心得吧~
|
||||
[AI帮你写描述 ✨]
|
||||
|
||||
【添加话题】
|
||||
#早餐 #低钾饮食 #透析期
|
||||
[+ 添加话题]
|
||||
|
||||
【营养数据】(自动带入)
|
||||
蛋白质:18.8g 能量:452kcal
|
||||
|
||||
【隐私设置】
|
||||
○ 公开 ○ 仅关注可见 ○ 私密
|
||||
|
||||
💡 提示:分享到社区可获得20积分
|
||||
```
|
||||
|
||||
**关键特性**:
|
||||
- 从打卡记录一键分享(降低门槛)
|
||||
- AI辅助生成标题和描述(降低创作难度)
|
||||
- 营养数据自动带入(突出专业性)
|
||||
- 隐私设置(保护用户隐私)
|
||||
- 积分激励(促进分享)
|
||||
|
||||
### 4. 用户主页
|
||||
|
||||
```
|
||||
┌────────────────────────────────┐
|
||||
│ 【用户资料卡】 │
|
||||
│ 👤 张小慢 │
|
||||
│ 透析3年 | CKD 5期 │
|
||||
│ 简介:坚持健康饮食,享受慢生活 │
|
||||
│ [+ 关注] │
|
||||
└────────────────────────────────┘
|
||||
|
||||
┌────────────────────────────────┐
|
||||
│ 128 作品 | 256 获赞 | 89 粉丝 │
|
||||
└────────────────────────────────┘
|
||||
|
||||
┌────────────────────────────────┐
|
||||
│ 🏆 成就勋章 │
|
||||
│ 连续打卡7天 获赞100+ 营养达标王 │
|
||||
└────────────────────────────────┘
|
||||
|
||||
【作品网格】(3列)
|
||||
┌────┐ ┌────┐ ┌────┐
|
||||
│图片 │ │图片 │ │图片 │
|
||||
└────┘ └────┘ └────┘
|
||||
```
|
||||
|
||||
**关键特性**:
|
||||
- 显示个人标签(疾病状态)
|
||||
- 数据统计(作品/获赞/粉丝)
|
||||
- 成就勋章系统
|
||||
- 作品网格展示
|
||||
|
||||
---
|
||||
|
||||
## 🔄 用户使用流程
|
||||
|
||||
### 完整的内容生产和消费闭环
|
||||
|
||||
```
|
||||
【内容生产】
|
||||
打卡上传饮食记录
|
||||
↓
|
||||
打卡成功页点击"分享到社区"
|
||||
↓
|
||||
AI辅助编辑标题和描述
|
||||
↓
|
||||
添加话题标签
|
||||
↓
|
||||
发布成功 + 获得20积分
|
||||
↓
|
||||
内容进入推荐池
|
||||
|
||||
【内容消费】
|
||||
访问社区广场
|
||||
↓
|
||||
浏览瀑布流内容(推荐算法)
|
||||
↓
|
||||
点击感兴趣的卡片
|
||||
↓
|
||||
查看详情(图片/文字/营养数据)
|
||||
↓
|
||||
互动(点赞/评论/收藏)
|
||||
↓
|
||||
关注优质创作者
|
||||
↓
|
||||
形成社交关系
|
||||
|
||||
【社交连接】
|
||||
关注其他用户
|
||||
↓
|
||||
"关注"Tab看到关注用户的内容
|
||||
↓
|
||||
评论互动
|
||||
↓
|
||||
收到回复通知
|
||||
↓
|
||||
再次访问社区
|
||||
|
||||
【激励闭环】
|
||||
看到他人优质打卡内容
|
||||
↓
|
||||
受到激励,自己也想分享
|
||||
↓
|
||||
更认真地打卡(拍照、摆盘)
|
||||
↓
|
||||
分享到社区
|
||||
↓
|
||||
获得点赞和评论
|
||||
↓
|
||||
社交认同感增强
|
||||
↓
|
||||
继续坚持打卡和分享
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 💡 核心功能设计
|
||||
|
||||
### 1. 推荐算法
|
||||
|
||||
**推荐分数计算**:
|
||||
```javascript
|
||||
推荐分数 = 内容质量分 × 0.4
|
||||
+ 用户兴趣分 × 0.3
|
||||
+ 时效性分 × 0.2
|
||||
+ 多样性分 × 0.1
|
||||
```
|
||||
|
||||
**内容质量分**:
|
||||
- 有配图:+20分
|
||||
- 3张图:+10分
|
||||
- 有营养数据:+15分
|
||||
- 字数>50字:+10分
|
||||
- 点赞数×0.5分
|
||||
- 评论数×2分
|
||||
- 收藏数×3分
|
||||
|
||||
**用户兴趣分**:
|
||||
- 同疾病状态(透析/非透析):+30分
|
||||
- 同CKD分期:+20分
|
||||
- 同餐次(早中晚):+15分
|
||||
- 关注的用户:+50分
|
||||
- 历史互动过的标签:+10分
|
||||
|
||||
### 2. 互动系统
|
||||
|
||||
**点赞**:
|
||||
- 点击动画效果(心形放大+红色填充)
|
||||
- 实时更新点赞数
|
||||
- 发布者获得通知
|
||||
|
||||
**评论**:
|
||||
- 支持二级评论(回复评论)
|
||||
- 支持@用户
|
||||
- 支持Emoji
|
||||
- 评论可点赞
|
||||
- 按点赞数排序
|
||||
|
||||
**收藏**:
|
||||
- 一键收藏
|
||||
- 个人中心查看收藏列表
|
||||
- 取消收藏
|
||||
|
||||
**分享**:
|
||||
- 微信好友/朋友圈
|
||||
- 复制链接
|
||||
- 生成海报(带小程序码)
|
||||
|
||||
### 3. 积分奖励
|
||||
|
||||
| 行为 | 积分 | 说明 |
|
||||
|-----|------|------|
|
||||
| 发布内容到社区 | +20分 | 鼓励分享 |
|
||||
| 内容获得第1个赞 | +5分 | 正向反馈 |
|
||||
| 内容获得第10个赞 | +10分 | 里程碑奖励 |
|
||||
| 内容获得第100个赞 | +50分 | 优质内容奖励 |
|
||||
| 发布评论 | +2分 | 每日上限10条 |
|
||||
| 收到评论 | +3分 | 提升互动积极性 |
|
||||
| 评论被点赞 | +1分 | - |
|
||||
| 分享到朋友圈 | +5分 | 每日上限1次 |
|
||||
|
||||
### 4. 创作者等级
|
||||
|
||||
```
|
||||
Lv1 新手:发布1-10篇
|
||||
Lv2 达人:发布11-50篇
|
||||
Lv3 专家:发布51-100篇
|
||||
Lv4 大咖:发布100+篇 + 平均获赞50+
|
||||
```
|
||||
|
||||
**等级权益**:
|
||||
- Lv2:解锁话题创建权限
|
||||
- Lv3:内容优先推荐
|
||||
- Lv4:认证标识 + 优先审核 + 流量扶持
|
||||
|
||||
---
|
||||
|
||||
## 📊 数据库设计
|
||||
|
||||
### 社区内容表(community_posts)
|
||||
|
||||
```sql
|
||||
CREATE TABLE community_posts (
|
||||
post_id BIGINT PRIMARY KEY,
|
||||
user_id BIGINT NOT NULL,
|
||||
check_in_record_id BIGINT, -- 关联的打卡记录
|
||||
|
||||
title VARCHAR(100) NOT NULL,
|
||||
content TEXT,
|
||||
cover_image VARCHAR(255),
|
||||
images_json TEXT,
|
||||
nutrition_data_json TEXT,
|
||||
tags_json TEXT,
|
||||
|
||||
like_count INT DEFAULT 0,
|
||||
comment_count INT DEFAULT 0,
|
||||
collect_count INT DEFAULT 0,
|
||||
share_count INT DEFAULT 0,
|
||||
view_count INT DEFAULT 0,
|
||||
|
||||
recommend_score DECIMAL(10,2),
|
||||
hot_score DECIMAL(10,2),
|
||||
|
||||
status VARCHAR(20) DEFAULT 'published',
|
||||
privacy VARCHAR(20) DEFAULT 'public',
|
||||
|
||||
created_at TIMESTAMP
|
||||
);
|
||||
```
|
||||
|
||||
### 互动记录表(community_interactions)
|
||||
|
||||
```sql
|
||||
CREATE TABLE community_interactions (
|
||||
interaction_id BIGINT PRIMARY KEY,
|
||||
user_id BIGINT NOT NULL,
|
||||
post_id BIGINT NOT NULL,
|
||||
interaction_type VARCHAR(20), -- like, comment, collect, share, view
|
||||
created_at TIMESTAMP,
|
||||
|
||||
UNIQUE KEY (user_id, post_id, interaction_type)
|
||||
);
|
||||
```
|
||||
|
||||
### 评论表(community_comments)
|
||||
|
||||
```sql
|
||||
CREATE TABLE community_comments (
|
||||
comment_id BIGINT PRIMARY KEY,
|
||||
post_id BIGINT NOT NULL,
|
||||
user_id BIGINT NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
parent_comment_id BIGINT, -- NULL为一级评论
|
||||
reply_to_user_id BIGINT,
|
||||
like_count INT DEFAULT 0,
|
||||
status VARCHAR(20) DEFAULT 'published',
|
||||
created_at TIMESTAMP
|
||||
);
|
||||
```
|
||||
|
||||
### 关注关系表(community_follows)
|
||||
|
||||
```sql
|
||||
CREATE TABLE community_follows (
|
||||
follow_id BIGINT PRIMARY KEY,
|
||||
follower_id BIGINT NOT NULL, -- 关注者
|
||||
followee_id BIGINT NOT NULL, -- 被关注者
|
||||
created_at TIMESTAMP,
|
||||
|
||||
UNIQUE KEY (follower_id, followee_id)
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 核心KPI指标
|
||||
|
||||
### 内容生产指标
|
||||
|
||||
| 指标 | v2.0目标 | 说明 |
|
||||
|-----|---------|------|
|
||||
| 社区内容总量 | 500篇 | 3个月累计 |
|
||||
| 日均发布量 | 10篇 | 每日新增内容 |
|
||||
| 发布率 | 10% | 打卡用户中分享到社区的比例 |
|
||||
|
||||
### 内容消费指标
|
||||
|
||||
| 指标 | v2.0目标 | 说明 |
|
||||
|-----|---------|------|
|
||||
| 日均访问用户 | 200人 | 每日访问社区的用户数 |
|
||||
| 人均浏览数 | 5篇 | 每个用户平均浏览的内容数 |
|
||||
| 互动率 | 30% | 浏览用户中产生互动的比例 |
|
||||
|
||||
### 互动指标
|
||||
|
||||
| 指标 | v2.0目标 | 说明 |
|
||||
|-----|---------|------|
|
||||
| 日均点赞数 | 500 | 每日产生的点赞总数 |
|
||||
| 日均评论数 | 100 | 每日产生的评论总数 |
|
||||
| 日均收藏数 | 50 | 每日产生的收藏总数 |
|
||||
|
||||
### 社交指标
|
||||
|
||||
| 指标 | v2.0目标 | 说明 |
|
||||
|-----|---------|------|
|
||||
| 关注关系数 | 200对 | 用户之间的关注关系总数 |
|
||||
| 创作者数量 | 50人 | 发布过至少3篇内容的用户 |
|
||||
|
||||
### 留存指标
|
||||
|
||||
| 指标 | v2.0目标 | 说明 |
|
||||
|-----|---------|------|
|
||||
| 社区用户7日留存率 | 60% | 比普通用户高30% |
|
||||
|
||||
**北极星指标**:
|
||||
```
|
||||
社区健康度 = (日均发布量 × 10)
|
||||
+ (人均浏览数 × 5)
|
||||
+ (互动率 × 100)
|
||||
|
||||
v2.0目标:≥500分
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 开发计划
|
||||
|
||||
### 阶段1:MVP版本(2周)
|
||||
|
||||
**核心功能**:
|
||||
- [ ] 社区广场(瀑布流展示)
|
||||
- [ ] 内容详情页(图片/文字/营养数据)
|
||||
- [ ] 发布功能(从打卡记录分享)
|
||||
- [ ] 基础互动(点赞/评论)
|
||||
|
||||
**目标**:验证用户是否愿意分享和浏览内容
|
||||
|
||||
### 阶段2:完善版本(2周)
|
||||
|
||||
**新增功能**:
|
||||
- [ ] 推荐算法
|
||||
- [ ] 关注/粉丝系统
|
||||
- [ ] 用户主页
|
||||
- [ ] 话题标签
|
||||
- [ ] 收藏和分享
|
||||
- [ ] 评论二级回复
|
||||
|
||||
**目标**:完善社交体验,提升用户粘性
|
||||
|
||||
### 阶段3:运营版本(1周)
|
||||
|
||||
**新增功能**:
|
||||
- [ ] 内容审核机制
|
||||
- [ ] 创作者等级
|
||||
- [ ] 成就勋章
|
||||
- [ ] 举报功能
|
||||
- [ ] 运营后台
|
||||
|
||||
**目标**:确保内容质量,支持运营活动
|
||||
|
||||
---
|
||||
|
||||
## 💰 商业价值
|
||||
|
||||
### 短期价值(3-6个月)
|
||||
|
||||
1. **用户增长**
|
||||
- 优质内容吸引新用户注册
|
||||
- 预计带来20-30%的新用户增长
|
||||
|
||||
2. **用户留存**
|
||||
- 社区用户7日留存率60%(比普通用户高30%)
|
||||
- 社交连接增强用户粘性
|
||||
|
||||
3. **用户活跃**
|
||||
- 社区用户DAU/MAU是普通用户的3-4倍
|
||||
- 日均打开频次提升50%
|
||||
|
||||
### 中期价值(6-12个月)
|
||||
|
||||
4. **内容资产**
|
||||
- 积累10000+篇优质UGC内容
|
||||
- 降低运营成本(不需要大量PGC内容)
|
||||
|
||||
5. **KOL孵化**
|
||||
- 培养100+活跃创作者
|
||||
- 部分创作者成为KOL(认证营养师、资深患者)
|
||||
|
||||
6. **品牌影响力**
|
||||
- 打造"肾病患者专属社区"品牌认知
|
||||
- 建立行业壁垒
|
||||
|
||||
### 长期价值(12-24个月)
|
||||
|
||||
7. **商业变现**
|
||||
- **广告变现**:信息流广告、品牌合作内容
|
||||
- **电商导流**:低钾食品、营养补剂推荐
|
||||
- **会员体系**:优质内容专属查看
|
||||
- **KOL分成**:创作者激励计划
|
||||
|
||||
8. **数据价值**
|
||||
- 真实饮食数据+用户偏好数据
|
||||
- 优化AI推荐算法
|
||||
- 为医疗研究提供数据支持
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 风险和应对
|
||||
|
||||
### 风险1:冷启动问题
|
||||
|
||||
**风险**:初期内容少,用户不愿意访问社区
|
||||
|
||||
**应对**:
|
||||
- 官方账号提前准备100+优质内容
|
||||
- 种子用户激励(前100名发布用户额外200积分)
|
||||
- 首页推荐位展示优质社区内容
|
||||
|
||||
### 风险2:内容质量低
|
||||
|
||||
**风险**:用户分享的内容质量参差不齐
|
||||
|
||||
**应对**:
|
||||
- AI辅助生成标题和描述,降低创作难度
|
||||
- 推荐算法优先展示高质量内容
|
||||
- 创作者等级制度,优质创作者流量扶持
|
||||
|
||||
### 风险3:违规内容
|
||||
|
||||
**风险**:医疗广告、政治敏感内容
|
||||
|
||||
**应对**:
|
||||
- 敏感词过滤(实时拦截)
|
||||
- 图片审核(腾讯云内容安全API)
|
||||
- 人工审核(新用户首次发布、被举报内容)
|
||||
- 处罚机制(警告、封禁)
|
||||
|
||||
### 风险4:用户隐私
|
||||
|
||||
**风险**:患者不愿意公开疾病信息
|
||||
|
||||
**应对**:
|
||||
- 隐私设置(公开/仅关注可见/私密)
|
||||
- 匿名发布选项
|
||||
- 脱敏处理(可选择不显示个人标签)
|
||||
|
||||
---
|
||||
|
||||
## 📞 总结
|
||||
|
||||
### 为什么要做打卡社区?
|
||||
|
||||
1. **解决核心痛点**
|
||||
- 患者孤独感强,需要同伴支持
|
||||
- 打卡容易坚持不下来,需要社交激励
|
||||
- 不知道怎么吃得好看,需要优质内容参考
|
||||
|
||||
2. **提升产品价值**
|
||||
- 从工具型产品→社区型产品
|
||||
- 从单向服务→双向互动
|
||||
- 从个人使用→社交分享
|
||||
|
||||
3. **商业化基础**
|
||||
- UGC内容吸引新用户,降低获客成本
|
||||
- 社交连接增强用户粘性,提升LTV
|
||||
- 为广告、电商、会员变现打基础
|
||||
|
||||
### 成功关键
|
||||
|
||||
✅ **降低分享门槛** - 打卡记录一键转分享,AI辅助创作
|
||||
✅ **优质内容优先** - 推荐算法保证用户看到好内容
|
||||
✅ **社交激励充分** - 点赞/评论/关注形成正向反馈
|
||||
✅ **内容审核严格** - 确保社区氛围健康
|
||||
|
||||
---
|
||||
|
||||
**完整PRD章节**:第4.8章 - 打卡社区系统
|
||||
|
||||
**文档位置**:`/Users/a123/Documents/Works25/慢生活/爱肾/msh-tools/docs/PRD_慢生活智能营养专家_v2.0.md`
|
||||
|
||||
**开发排期**:3-4周(MVP→完善→运营,分三个阶段)
|
||||
|
||||
---
|
||||
|
||||
**下一步**:需要我生成小程序页面代码吗? 📱✨
|
||||
439
docs/打卡详情页_一键打卡入口设计.md
Normal file
439
docs/打卡详情页_一键打卡入口设计.md
Normal file
@@ -0,0 +1,439 @@
|
||||
# 打卡详情页 - 一键打卡入口设计
|
||||
|
||||
> **更新日期**:2025-11-20
|
||||
> **版本**:v2.2.1
|
||||
> **核心优化**:在打卡详情页增加"一键打卡"入口
|
||||
|
||||
---
|
||||
|
||||
## 🎯 优化目标
|
||||
|
||||
用户在查看打卡详情时,希望能快速复制该打卡内容,无需返回到列表页操作。
|
||||
|
||||
---
|
||||
|
||||
## 📱 三个主要入口
|
||||
|
||||
### 入口1:打卡历史页 → 直接复制 ⭐推荐快速打卡
|
||||
|
||||
```
|
||||
我的饮食记录
|
||||
┌──────────────────────────┐
|
||||
│ 11月19日 早餐 │
|
||||
│ ✅ 达标率92% ⭐高分 │
|
||||
│ [📊 查看详情] │
|
||||
│ [⚡一键复制打卡] ←入口1 │
|
||||
└──────────────────────────┘
|
||||
```
|
||||
|
||||
**特点**:
|
||||
- ✅ 最快捷的方式
|
||||
- ✅ 无需进入详情页
|
||||
- ✅ 适合确定要复制的情况
|
||||
- ✅ 30秒完成打卡
|
||||
|
||||
**流程**:
|
||||
```
|
||||
点击"⚡一键复制打卡"
|
||||
↓
|
||||
复制确认页(预览内容)
|
||||
↓
|
||||
打卡页(内容已填充)
|
||||
↓
|
||||
修改内容(可选)
|
||||
↓
|
||||
发布完成(+5积分)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 入口2:打卡详情页 → 详细查看后复制 ⭐⭐⭐本次新增
|
||||
|
||||
```
|
||||
我的饮食记录
|
||||
[📊 查看详情] ←先点这里
|
||||
↓
|
||||
打卡详情页
|
||||
┌──────────────────────────┐
|
||||
│ 📸 完整照片查看 │
|
||||
│ 📊 营养数据详情 │
|
||||
│ 📋 菜品清单 │
|
||||
│ ┌──────────────────────┐ │
|
||||
│ │ ⚡一键复制打卡 ←入口2 │ │
|
||||
│ └──────────────────────┘ │
|
||||
└──────────────────────────┘
|
||||
```
|
||||
|
||||
**特点**:
|
||||
- ✅ 查看完整信息后决定
|
||||
- ✅ 可以看到所有照片和数据
|
||||
- ✅ 更了解打卡内容
|
||||
- ✅ 适合需要详细查看的情况
|
||||
|
||||
**流程**:
|
||||
```
|
||||
点击"📊 查看详情"
|
||||
↓
|
||||
进入打卡详情页
|
||||
↓
|
||||
查看照片、营养数据、菜品清单
|
||||
↓
|
||||
点击"⚡一键复制打卡"
|
||||
↓
|
||||
复制确认页
|
||||
↓
|
||||
打卡页(内容已填充)
|
||||
↓
|
||||
发布完成(+5积分)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 入口3:社区打卡详情页 → 借鉴他人 ⭐学习优质内容
|
||||
|
||||
```
|
||||
社区广场 → 他人的打卡
|
||||
[点击帖子查看详情]
|
||||
↓
|
||||
打卡详情页(他人的)
|
||||
┌──────────────────────────┐
|
||||
│ 查看他人饮食方案 │
|
||||
│ 营养数据参考 │
|
||||
│ ┌──────────────────────┐ │
|
||||
│ │ 🎬一键借鉴打卡 ←入口3 │ │
|
||||
│ └──────────────────────┘ │
|
||||
└──────────────────────────┘
|
||||
```
|
||||
|
||||
**特点**:
|
||||
- ✅ 学习他人优质饮食方案
|
||||
- ✅ AI自动调整用量(根据你的情况)
|
||||
- ✅ 必须上传自己的照片
|
||||
- ✅ 原作者也会获得积分
|
||||
|
||||
**流程**:
|
||||
```
|
||||
浏览社区打卡内容
|
||||
↓
|
||||
点击感兴趣的帖子
|
||||
↓
|
||||
进入打卡详情页(他人的)
|
||||
↓
|
||||
点击"🎬一键借鉴打卡"
|
||||
↓
|
||||
借鉴确认页(AI调整用量)
|
||||
↓
|
||||
打卡页(需上传自己的照片)
|
||||
↓
|
||||
发布完成
|
||||
自己+5积分,原作者+2积分
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎨 UI设计对比
|
||||
|
||||
### 场景A:查看【自己的】打卡详情页
|
||||
|
||||
```
|
||||
┌────────────────────────────────┐
|
||||
│ 【快速操作】 │
|
||||
│ ┌──────────────────────────┐ │
|
||||
│ │ ⚡ 一键复制打卡 │ │
|
||||
│ │ 快速复用这份饮食方案 │ │
|
||||
│ └──────────────────────────┘ │
|
||||
│ │
|
||||
│ 点击后可以: │
|
||||
│ ✅ 复制照片和备注到新打卡 │
|
||||
│ ✅ 修改内容后重新发布 │
|
||||
│ ✅ 可选生成AI视频 │
|
||||
│ ✅ 获得5积分奖励 │
|
||||
│ │
|
||||
│ 💡 适用场景: │
|
||||
│ • 今天吃的和之前某天一样 │
|
||||
│ • 想复用高分打卡记录 │
|
||||
│ • 快速打卡不想重新拍照 │
|
||||
└────────────────────────────────┘
|
||||
```
|
||||
|
||||
**按钮样式**:
|
||||
- 颜色:绿色渐变(#4CAF50 → #45B649)
|
||||
- 大小:宽度100%,高度56px
|
||||
- 字体:18px,加粗
|
||||
- 图标:⚡(闪电,表示快速)
|
||||
- 圆角:12px
|
||||
- 阴影:0 4px 12px rgba(76, 175, 80, 0.3)
|
||||
|
||||
---
|
||||
|
||||
### 场景B:查看【他人的】打卡详情页
|
||||
|
||||
```
|
||||
┌────────────────────────────────┐
|
||||
│ 【一键借鉴】 │
|
||||
│ ┌──────────────────────────┐ │
|
||||
│ │ 🎬 一键借鉴打卡 │ │
|
||||
│ │ 学习这份营养方案 │ │
|
||||
│ └──────────────────────────┘ │
|
||||
│ │
|
||||
│ 点击后可以: │
|
||||
│ ✅ 复制菜品清单到我的打卡 │
|
||||
│ ✅ 根据我的情况调整用量 │
|
||||
│ ✅ AI生成适合我的食谱 │
|
||||
│ ✅ 获得5积分奖励 │
|
||||
│ │
|
||||
│ 💡 适用场景: │
|
||||
│ • 看到他人的优质打卡想学习 │
|
||||
│ • 发现适合自己的饮食方案 │
|
||||
│ • 需要新的饮食灵感 │
|
||||
└────────────────────────────────┘
|
||||
```
|
||||
|
||||
**按钮样式**:
|
||||
- 颜色:蓝色渐变(#2196F3 → #1E88E5)
|
||||
- 大小:宽度100%,高度56px
|
||||
- 字体:18px,加粗
|
||||
- 图标:🎬(摄像机,表示学习和记录)
|
||||
- 圆角:12px
|
||||
- 阴影:0 4px 12px rgba(33, 150, 243, 0.3)
|
||||
|
||||
---
|
||||
|
||||
## 💻 技术实现
|
||||
|
||||
### 1. 判断打卡详情页的场景
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* 判断打卡详情页的场景
|
||||
*/
|
||||
function getCheckInDetailScene(recordId, currentUserId) {
|
||||
const record = getCheckInRecordById(recordId);
|
||||
|
||||
if (record.user_id === currentUserId) {
|
||||
// 场景A:查看自己的打卡
|
||||
return {
|
||||
scene: 'self',
|
||||
buttonText: '⚡ 一键复制打卡',
|
||||
buttonColor: 'green',
|
||||
buttonClass: 'copy-btn-self',
|
||||
action: 'copyOwnCheckIn'
|
||||
};
|
||||
} else {
|
||||
// 场景B:查看他人的打卡
|
||||
return {
|
||||
scene: 'others',
|
||||
buttonText: '🎬 一键借鉴打卡',
|
||||
buttonColor: 'blue',
|
||||
buttonClass: 'copy-btn-others',
|
||||
action: 'learnFromOthers'
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 一键复制自己的打卡
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* 一键复制自己的打卡
|
||||
*/
|
||||
function copyOwnCheckIn(recordId) {
|
||||
const record = getCheckInRecordById(recordId);
|
||||
|
||||
// 复制数据
|
||||
const copiedData = {
|
||||
photos_json: record.photos_json, // 照片URL数组
|
||||
notes: record.notes, // 备注说明
|
||||
voice_url: record.voice_url, // 语音备注(如果有)
|
||||
copied_from_record_id: recordId, // 记录复制来源
|
||||
is_copied: 1 // 标记为复制的打卡
|
||||
};
|
||||
|
||||
// 保存到临时存储
|
||||
wx.setStorageSync('tempCheckInData', copiedData);
|
||||
|
||||
// 跳转到打卡页
|
||||
wx.navigateTo({
|
||||
url: '/pages/check-in/check-in?from=copy&recordId=' + recordId
|
||||
});
|
||||
|
||||
// 埋点统计
|
||||
trackEvent('copy_own_check_in', {
|
||||
record_id: recordId,
|
||||
source: 'detail_page'
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 一键借鉴他人的打卡
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* 一键借鉴他人的打卡
|
||||
*/
|
||||
function learnFromOthers(recordId, currentUserId) {
|
||||
const record = getCheckInRecordById(recordId);
|
||||
|
||||
// 获取用户自己的营养计划
|
||||
const myPlan = getNutritionPlan(currentUserId);
|
||||
|
||||
// AI自动调整菜品用量
|
||||
const adjustedDishes = adjustDishesForUser(
|
||||
record.actual_dishes_json,
|
||||
myPlan
|
||||
);
|
||||
|
||||
// 准备借鉴数据
|
||||
const learnedData = {
|
||||
dishes_json: adjustedDishes, // AI调整后的菜品清单
|
||||
reference_record_id: recordId, // 参考来源
|
||||
reference_user_id: record.user_id, // 原作者ID
|
||||
is_learned_from_others: 1 // 标记为借鉴的打卡
|
||||
};
|
||||
|
||||
// 保存到临时存储
|
||||
wx.setStorageSync('tempCheckInData', learnedData);
|
||||
|
||||
// 跳转到打卡页
|
||||
wx.navigateTo({
|
||||
url: '/pages/check-in/check-in?from=learn&recordId=' + recordId
|
||||
});
|
||||
|
||||
// 给原作者增加积分(+2分)
|
||||
addPointsToUser(record.user_id, 2, 'content_learned', recordId);
|
||||
|
||||
// 埋点统计
|
||||
trackEvent('learn_from_others', {
|
||||
record_id: recordId,
|
||||
original_user_id: record.user_id,
|
||||
source: 'detail_page'
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### 4. AI调整菜品用量
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* AI调整菜品用量(根据用户的营养计划)
|
||||
*/
|
||||
function adjustDishesForUser(originalDishes, userPlan) {
|
||||
// 计算原始菜品的总营养
|
||||
const originalNutrition = calculateTotalNutrition(originalDishes);
|
||||
|
||||
// 计算调整比例
|
||||
const proteinRatio = userPlan.target_protein / originalNutrition.protein;
|
||||
const energyRatio = userPlan.target_energy / originalNutrition.energy;
|
||||
|
||||
// 取平均比例
|
||||
const avgRatio = (proteinRatio + energyRatio) / 2;
|
||||
|
||||
// 调整每个菜品的用量
|
||||
return originalDishes.map(dish => {
|
||||
const newAmount = Math.round(dish.amount * avgRatio);
|
||||
|
||||
return {
|
||||
...dish,
|
||||
amount: newAmount, // 调整后的用量
|
||||
adjusted: true, // 标记为AI调整过的
|
||||
original_amount: dish.amount, // 保留原始用量供参考
|
||||
adjustment_ratio: avgRatio.toFixed(2) // 调整比例
|
||||
};
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 页面路径说明
|
||||
|
||||
| 页面 | 路径 | 参数 | 说明 |
|
||||
|-----|------|------|------|
|
||||
| 打卡历史页 | `/pages/check-in-history/history` | - | 显示用户的打卡记录列表 |
|
||||
| 打卡详情页(自己的) | `/pages/check-in-detail/detail` | `?id={recordId}&type=self` | 查看自己的打卡详情 |
|
||||
| 打卡详情页(他人的) | `/pages/check-in-detail/detail` | `?id={recordId}&type=others` | 查看他人的打卡详情 |
|
||||
| 复制确认页 | `/pages/copy-confirm/confirm` | `?from={recordId}` | 复制自己的打卡确认页 |
|
||||
| 借鉴确认页 | `/pages/learn-confirm/confirm` | `?from={recordId}` | 借鉴他人的打卡确认页 |
|
||||
| 打卡页 | `/pages/check-in/check-in` | `?from=copy&recordId={id}` 或 `?from=learn&recordId={id}` | 填写打卡内容 |
|
||||
|
||||
---
|
||||
|
||||
## 🎁 积分规则
|
||||
|
||||
| 行为 | 积分 | 说明 |
|
||||
|-----|------|------|
|
||||
| 一键复制打卡(自己的) | +5分 | 从历史记录快速复制 |
|
||||
| 一键借鉴打卡(他人的) | +5分 | 学习他人的饮食方案 |
|
||||
| 内容被借鉴(原作者) | +2分 | 自己的打卡被他人借鉴 |
|
||||
|
||||
---
|
||||
|
||||
## 📈 预期效果
|
||||
|
||||
| 指标 | 优化前 | 优化后 | 提升 |
|
||||
|-----|-------|-------|------|
|
||||
| **详情页停留时长** | 15秒 | 30秒 | +100% |
|
||||
| **从详情页发起的打卡** | 5% | 20% | +300% |
|
||||
| **一键复制打卡使用率** | 25% | 40% | +60% |
|
||||
| **打卡转化率** | 30% | 45% | +50% |
|
||||
|
||||
**数据支持**:
|
||||
- 用户在详情页查看完整信息后,打卡意愿更强
|
||||
- 详情页提供更多上下文,用户更愿意复制高质量打卡
|
||||
- 减少操作步骤,提升转化率
|
||||
|
||||
---
|
||||
|
||||
## 💡 设计亮点
|
||||
|
||||
### 1. **场景区分** ⭐⭐⭐
|
||||
- 智能判断是"自己的打卡"还是"他人的打卡"
|
||||
- 不同场景显示不同的按钮文案和颜色
|
||||
- 提供差异化的体验
|
||||
|
||||
### 2. **三重入口** ⭐⭐⭐
|
||||
- 历史列表页:快速直接复制
|
||||
- 详情页:查看后再复制
|
||||
- 社区详情页:学习他人
|
||||
|
||||
### 3. **AI智能调整** ⭐⭐⭐
|
||||
- 借鉴他人打卡时,AI自动调整菜品用量
|
||||
- 根据用户的营养计划个性化调整
|
||||
- 降低借鉴门槛,提升使用率
|
||||
|
||||
### 4. **积分激励** ⭐⭐⭐
|
||||
- 借鉴者获得+5分
|
||||
- 原作者获得+2分
|
||||
- 形成"创作-分享-学习"的正向循环
|
||||
|
||||
---
|
||||
|
||||
## 🚀 下一步优化
|
||||
|
||||
1. **智能推荐复制**
|
||||
- 在详情页根据历史数据推荐"您可能也想复制这几条"
|
||||
- 提供相似打卡记录推荐
|
||||
|
||||
2. **批量复制**
|
||||
- 支持一次性复制一天的三餐
|
||||
- 生成"一日食谱计划"
|
||||
|
||||
3. **收藏夹功能**
|
||||
- 收藏优质打卡记录
|
||||
- 从收藏夹快速复制
|
||||
|
||||
4. **打卡模板**
|
||||
- 将高分打卡保存为模板
|
||||
- 模板库供用户选择
|
||||
|
||||
---
|
||||
|
||||
**文档版本**:v2.2.1
|
||||
**更新日期**:2025-11-20
|
||||
**产品名称**:慢生活智能营养专家小程序
|
||||
|
||||
---
|
||||
|
||||
**让每个打卡详情页都成为下一次打卡的起点!** ⚡🎬
|
||||
|
||||
1310
docs/用户界面交互设计.md
Normal file
1310
docs/用户界面交互设计.md
Normal file
File diff suppressed because it is too large
Load Diff
197
docs/项目技术栈与架构分析.md
Normal file
197
docs/项目技术栈与架构分析.md
Normal file
@@ -0,0 +1,197 @@
|
||||
# 慢生活(MSH)系统 — 项目技术栈与架构分析
|
||||
|
||||
## 一、项目概述
|
||||
|
||||
**慢生活**(MSH System)是一套面向 C 端的**商城 + 营养/工具**一体化系统,包含移动端多端应用(UniApp)与基于 CRMEB 的 Java 后端服务。项目在 CRMEB Java 版 v2.2 基础上做了业务定制,并接入了 AI(Coze、KieAI、腾讯云 ASR 等)与文章/工具类能力,形成「慢生活营养专家」产品形态。
|
||||
|
||||
---
|
||||
|
||||
## 二、整体架构
|
||||
|
||||
系统采用**前后端分离 + 多端一体**架构:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ 客户端(多端) │
|
||||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
||||
│ │ 微信小程序 │ │ H5 商城 │ │ App(iOS/ │ │ 支付宝/ │ │
|
||||
│ │ (mp-weixin) │ │ │ │ Android) │ │ 头条等小程序│ │
|
||||
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
|
||||
│ │ │ │ │ │
|
||||
│ └────────────────┴────────────────┴────────────────┘ │
|
||||
│ │ │
|
||||
│ msh_single_uniapp (Vue 2 + UniApp) │
|
||||
└────────────────────────────────────┬────────────────────────────────────┘
|
||||
│ HTTPS / REST API
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ 后端服务(Spring Boot) │
|
||||
│ ┌─────────────────────────────┐ ┌─────────────────────────────┐ │
|
||||
│ │ crmeb-front (C 端 API) │ │ crmeb-admin (管理端 API) │ │
|
||||
│ │ 端口: 20822 (sophia) │ │ 后台管理、定时任务(Quartz) │ │
|
||||
│ │ /api/front/*, /api/public/* │ │ │ │
|
||||
│ └──────────────┬──────────────┘ └──────────────┬──────────────┘ │
|
||||
│ │ │ │
|
||||
│ └────────────┬───────────────────┘ │
|
||||
│ ▼ │
|
||||
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ crmeb-service (业务逻辑) │ crmeb-common (工具/实体/配置) │ │
|
||||
│ └─────────────────────────────────────────────────────────────────┘ │
|
||||
└────────────────────────────────────┬────────────────────────────────────┘
|
||||
│
|
||||
┌───────────────────────────┼───────────────────────────┐
|
||||
▼ ▼ ▼
|
||||
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
||||
│ MySQL 8.x │ │ Redis │ │ 对象存储/ │
|
||||
│ (Druid 连接池) │ │ (缓存/会话/ │ │ 支付/地图等 │
|
||||
│ │ │ Token/验证码) │ │ OSS/COS/七牛 │
|
||||
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
||||
```
|
||||
|
||||
- **前端**:一套 UniApp 源码,通过条件编译发布到微信小程序、H5、App、支付宝/头条等小程序。
|
||||
- **后端**:多模块 Maven 工程,C 端入口为 `crmeb-front`,管理端为 `crmeb-admin`,共用 `crmeb-service` 与 `crmeb-common`。
|
||||
- **外部**:MySQL 主库、Redis、阿里云 OSS/腾讯云 COS/七牛、微信/支付宝支付、高德地图、Coze/KieAI 等。
|
||||
|
||||
---
|
||||
|
||||
## 三、前端技术栈(msh_single_uniapp)
|
||||
|
||||
### 3.1 核心框架与语言
|
||||
|
||||
| 类别 | 技术选型 | 说明 |
|
||||
|------------|-----------------|------|
|
||||
| 跨端框架 | **UniApp** | 基于 Vue 2,一套代码多端运行(微信/支付宝/头条小程序、H5、App) |
|
||||
| 前端框架 | **Vue 2.x** | 选项式 API,`main.js` 中挂载全局工具与 Store |
|
||||
| 状态管理 | **Vuex** | `store/index.js` + `store/modules`(如 `app.js`) |
|
||||
| 构建/工程 | **Vue CLI** | `vue.config.js`:生产环境关闭 sourceMap、压缩时去掉 console |
|
||||
| 富文本 | **mp-html** | 用于商品/文章等富文本展示(`package.json` 中声明) |
|
||||
|
||||
### 3.2 工程结构要点
|
||||
|
||||
- **入口与全局**:`main.js`、`App.vue`;全局通过 `Vue.prototype` 挂载 `$util`、`$config`、`$Cache`、`$eventHub`、`$Order`、`$LoginAuth` 等。
|
||||
- **配置**:`config/app.js` 统一配置 `domain`(API 基地址)、`HTTP_REQUEST_URL`、`HEADER`、`TOKENNAME`(如 `Authori-zation`)等;H5 下可选 VConsole。
|
||||
- **请求层**:
|
||||
- 通用业务请求:`utils/request.js`,基于 `uni.request`,前缀 `/api/front/` 或 `/api/public/`,统一处理 401/403/410xxx 跳转登录。
|
||||
- AI/文章等:`api/models-api.js` 使用同一 `domain`,封装 Coze、KieAI、腾讯 ASR、文章等接口。
|
||||
- **API 模块化**:`api/api.js`、`api/user.js`、`api/order.js`、`api/store.js`、`api/tool.js`、`api/public.js`、`api/models-api.js` 等按业务拆分。
|
||||
- **路由与页面**:`pages.json` 配置首页、分包(subPackages)、tabBar、导航栏样式等;首屏为 `pages/tool_main/index`(慢生活营养专家)。
|
||||
|
||||
### 3.3 多端与发布
|
||||
|
||||
- **manifest.json**:应用名「慢生活」、版本 2.2.1、App 模块(支付、分享、地图、定位、直播等)、微信/支付宝/头条小程序 appid、高德 key、支付与 OAuth 等。
|
||||
- **发行**:H5 使用 history 路由;微信小程序开启 `lazyCodeLoading: requiredComponents` 控制包体;生产构建通过 `vue.config.js` 做压缩与去 console。
|
||||
|
||||
### 3.4 前端技术栈小结
|
||||
|
||||
- **语言**:JavaScript(ES6+)
|
||||
- **UI**:UniApp 内置组件 + 自定义组件(如 `skeleton`、`homeIndex`、`payment`、`jyf-parser` 等)
|
||||
- **特色**:多端统一、Token 鉴权、登录态校验、分包与懒加载、与 CRMEB 后端约定 `/api/front/`、`/api/public/` 前缀
|
||||
|
||||
---
|
||||
|
||||
## 四、后端技术栈(msh_crmeb_22)
|
||||
|
||||
### 4.1 核心框架与运行时
|
||||
|
||||
| 类别 | 技术选型 | 版本/说明 |
|
||||
|--------------|-----------------------------|-----------|
|
||||
| 基础框架 | **Spring Boot** | 2.2.6.RELEASE |
|
||||
| Java | **JDK 1.8** | 编译与运行 |
|
||||
| 构建 | **Maven** | 多模块 parent POM 管理依赖 |
|
||||
|
||||
### 4.2 模块划分
|
||||
|
||||
| 模块 | 职责 |
|
||||
|----------------|------|
|
||||
| **crmeb-common** | 公共实体、工具类、配置(如 CozeConfig)、通用组件(Druid、MyBatis-Plus、Redis、JWT、验证码等) |
|
||||
| **crmeb-service** | 业务逻辑层:商城、用户、订单、营销、工具、AI 对接(Coze、KieAI、腾讯 ASR)等 |
|
||||
| **crmeb-admin** | 管理端 Web API、Quartz 定时任务,打包为 `jxz-admin-2.2.jar` |
|
||||
| **crmeb-front** | C 端 Web API,打包为 `sophia-front-2.2.jar`,当前 sophia 配置端口 20822 |
|
||||
|
||||
### 4.3 数据访问与存储
|
||||
|
||||
| 类别 | 技术选型 | 说明 |
|
||||
|----------|-------------------------|------|
|
||||
| 关系库 | **MySQL 8.x** | 驱动 `mysql-connector-java` 8.0.33,Druid 连接池 |
|
||||
| ORM | **MyBatis-Plus** | 3.3.1,含 generator;mapper 扫描 `com.zbkj.**.dao` |
|
||||
| 分页 | **PageHelper** | 1.2.5,与 MyBatis-Plus 共存时排除 mybatis 避免冲突 |
|
||||
| 缓存 | **Redis + Jedis** | Spring Boot 2.2 Data Redis,独立 DB 存 accessToken 等 |
|
||||
| 文档 | **MongoDB** | 仅 driver-core 依赖,用于部分扩展能力 |
|
||||
|
||||
### 4.4 安全与认证
|
||||
|
||||
- **JWT**:`jjwt-api/impl/jackson` 0.11.5,Token 名称与前端约定(如 `Authori-zation`)。
|
||||
- **行为验证码**:`spring-boot-starter-captcha`(AJ),支持滑动/点选,缓存可走 Redis。
|
||||
- **白名单**:`application.yml` 中 `crmeb.ignored` 配置 Swagger、上传、Coze、KieAI 等路径免鉴权。
|
||||
|
||||
### 4.5 接口与文档
|
||||
|
||||
- **REST**:Spring MVC,C 端前缀 `/api/front/`、`/api/public/`。
|
||||
- **文档**:Springfox Swagger 2.9.2 + swagger-bootstrap-ui 1.9.3,配置在 `application*.yml` 的 `swagger.basic`。
|
||||
|
||||
### 4.6 第三方与中间件(根 POM 管理)
|
||||
|
||||
- **工具**:Hutool、Commons Lang3、Fastjson、HttpClient、Dom4j、XStream、Pinyin4j、ZXing(二维码)等。
|
||||
- **文件与图片**:Commons FileUpload/IO、Thumbnailator、阿里云 OSS、腾讯云 COS、七牛 SDK。
|
||||
- **支付**:支付宝 SDK、微信(通过 common 或 service 内封装)。
|
||||
- **AI/开放接口**:Coze API(coze-api 0.2.3)、OkHttp、RxJava(流式);KieAI、腾讯云 ASR 等通过 HTTP 在 service 层封装。
|
||||
|
||||
### 4.7 配置与运行
|
||||
|
||||
- **配置**:`application.yml` + `application-{profile}.yml`(如 `sophia`、`dev`、`prod`、`beta`)。
|
||||
- **数据源/Redis**:在 profile 中覆盖 URL、库名、端口、密码等。
|
||||
- **日志**:Logback(`logback-spring.xml`),按环境输出到 `./crmeb_log` 或 `./logs`。
|
||||
|
||||
---
|
||||
|
||||
## 五、models-integration 子项目
|
||||
|
||||
- **定位**:独立 Maven 工程(`com.integration:models-integration`),Java 8,Spring Boot 2.7.5。
|
||||
- **用途**:与「模型/AI」集成相关的独立服务或能力(如统一网关、模型路由等),通过 SpringDoc OpenAPI、Redis、MySQL、AOP 等搭建。
|
||||
- **与主系统关系**:与 msh_crmeb_22 无直接模块依赖,可单独部署,通过 HTTP 或消息与 front 协作。
|
||||
|
||||
---
|
||||
|
||||
## 六、部署与运行约定
|
||||
|
||||
### 6.1 前端
|
||||
|
||||
- **开发**:使用 HBuilderX 或 Vue CLI 运行/发行到各端;API 基地址在 `config/app.js` 的 `domain` 修改(如 `https://sophia-shop.uj345.cc`)。
|
||||
- **生产**:H5 可部署到任意静态服务器;小程序/App 需在对应平台上传与配置域名白名单。
|
||||
|
||||
### 6.2 后端
|
||||
|
||||
- **C 端**:以 `sophia` profile 启动 `CrmebFrontApplication`,默认端口 20822,需可访问的 MySQL、Redis。
|
||||
- **管理端**:启动 `CrmebAdminApplication`,按需配置端口与数据源。
|
||||
- **包名**:`com.zbkj`,front 的 `@ComponentScan` 包含 `com.zbkj` 与 `com.zbkj.front`,Mapper 扫描 `com.zbkj.**.dao`。
|
||||
|
||||
---
|
||||
|
||||
## 七、技术栈汇总表
|
||||
|
||||
| 层次 | 技术项 | 选型说明 |
|
||||
|----------|------------------|----------|
|
||||
| 前端框架 | Vue 2 + UniApp | 多端统一(小程序/H5/App) |
|
||||
| 状态管理 | Vuex | 登录态、购物车、全局配置等 |
|
||||
| 请求 | uni.request 封装 | 统一 domain、Token、错误与登录跳转 |
|
||||
| 后端框架 | Spring Boot 2.2 | 多模块(common/service/admin/front) |
|
||||
| 数据访问 | MyBatis-Plus + MySQL | Druid 连接池、PageHelper 分页 |
|
||||
| 缓存 | Redis (Jedis) | 会话、Token、验证码、配置缓存 |
|
||||
| 认证 | JWT + 行为验证码 | 前端 Header 传 Token |
|
||||
| 文档 | Swagger 2 + Bootstrap UI | 后端 API 文档 |
|
||||
| 构建 | Maven / npm | 后端 Maven;前端 Vue CLI / HBuilderX |
|
||||
| 对象存储 | 阿里云 OSS / 腾讯云 COS / 七牛 | 图片与文件 |
|
||||
| 支付 | 微信支付、支付宝 | 通过后端封装 |
|
||||
| AI/能力 | Coze、KieAI、腾讯云 ASR 等 | 在 crmeb-service 中封装,front 暴露 REST |
|
||||
|
||||
---
|
||||
|
||||
## 八、文档与资源
|
||||
|
||||
- **产品与设计**:`docs/` 下 PRD、打卡社区设计、食谱计算器接口文档、用户界面交互设计等。
|
||||
- **数据库**:`docs/sql`、`msh_crmeb_22/sql` 等目录可存放建表/迁移脚本。
|
||||
- **接口**:`documents/api` 可补充 C 端/管理端接口说明,与 Swagger 互补。
|
||||
|
||||
---
|
||||
|
||||
*文档基于当前仓库目录与配置文件整理,用于技术评审、新人上手与架构演进参考。具体版本号以各模块 `pom.xml`、`package.json` 及运行环境为准。*
|
||||
569
docs/食谱计算器后端接口开发文档.md
Normal file
569
docs/食谱计算器后端接口开发文档.md
Normal file
@@ -0,0 +1,569 @@
|
||||
# 食谱计算器后端接口开发文档
|
||||
|
||||
> **版本**:v1.0
|
||||
> **创建日期**:2026-02-01
|
||||
> **所属模块**:Tool(工具模块)
|
||||
> **配套前端**:`msh_single_uniapp/pages/tool/calculator.vue` / `calculator-result.vue`
|
||||
|
||||
---
|
||||
|
||||
## 一、功能概述
|
||||
|
||||
食谱计算器是**慢生活智能营养专家**的核心功能之一,面向肾病(CKD)及透析患者,根据用户输入的健康数据,自动计算:
|
||||
|
||||
1. **健康指标**:eGFR、标准体重、BMI、CKD分期
|
||||
2. **每日营养目标**:蛋白质摄入量(g/d)、能量摄入量(kcal/d)
|
||||
3. **食物份数建议**:7 类食物的每日推荐份数
|
||||
4. **一日三餐配餐方案**:早餐、午餐、晚餐的菜品清单(含图片、食材用量)
|
||||
5. **重要提示**:根据患者情况给出饮食注意事项
|
||||
|
||||
用户可进一步**采纳营养计划**,进入打卡流程(另单独接口)。
|
||||
|
||||
---
|
||||
|
||||
## 二、接口清单
|
||||
|
||||
| 序号 | 接口名称 | HTTP方法 | 路径 | 说明 |
|
||||
|------|----------|----------|------|------|
|
||||
| 1 | 计算营养方案 | `POST` | `/api/front/tool/calculator/calculate` | 核心接口,输入健康数据,返回计算结果 |
|
||||
| 2 | 获取计算结果详情 | `GET` | `/api/front/tool/calculator/result/{id}` | 通过结果ID获取完整结果(含配餐) |
|
||||
| 3 | 采纳营养计划 | `POST` | `/api/front/tool/calculator/adopt` | 用户采纳后创建营养计划,进入打卡流程 |
|
||||
|
||||
---
|
||||
|
||||
## 三、接口详细设计
|
||||
|
||||
### 3.1 计算营养方案
|
||||
|
||||
#### 基础信息
|
||||
|
||||
| 项目 | 值 |
|
||||
|------|------|
|
||||
| 请求路径 | `POST /api/front/tool/calculator/calculate` |
|
||||
| 是否鉴权 | **是**(需登录) |
|
||||
| Content-Type | `application/json` |
|
||||
|
||||
#### 请求参数(Request Body)
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 | 校验规则 |
|
||||
|--------|------|------|------|----------|
|
||||
| `gender` | String | 是 | 性别 | 可选值:`male` / `female` |
|
||||
| `age` | Integer | 是 | 年龄(岁) | 1 ≤ age ≤ 150 |
|
||||
| `height` | Integer | 是 | 身高(cm) | 50 ≤ height ≤ 250 |
|
||||
| `dialysis` | Boolean | 是 | 是否透析 | `true` / `false` |
|
||||
| `dialysisType` | String | 否 | 透析类型 | 当 `dialysis=true` 时有效;可选值:`hemodialysis`(血透)/ `peritoneal`(腹透) |
|
||||
| `dryWeight` | Number | 是 | 干体重(kg) | 20 ≤ dryWeight ≤ 300,支持1位小数 |
|
||||
| `creatinine` | Number | 是 | 血肌酐(μmol/L) | 0 < creatinine ≤ 2000,支持2位小数 |
|
||||
|
||||
**请求示例**
|
||||
|
||||
```json
|
||||
{
|
||||
"gender": "male",
|
||||
"age": 55,
|
||||
"height": 170,
|
||||
"dialysis": true,
|
||||
"dialysisType": "hemodialysis",
|
||||
"dryWeight": 65.5,
|
||||
"creatinine": 850
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应参数(Response Body)
|
||||
|
||||
| 参数名 | 类型 | 说明 |
|
||||
|--------|------|------|
|
||||
| `code` | Integer | 状态码,`200` 表示成功 |
|
||||
| `message` | String | 提示信息 |
|
||||
| `data` | Object | 计算结果对象 |
|
||||
|
||||
**`data` 结构**
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|--------|------|------|
|
||||
| `id` / `resultId` | Long | 计算结果唯一ID,用于后续查询/采纳 |
|
||||
| `healthData` | Object | 健康数据计算结果 |
|
||||
| `nutritionGoals` | Object | 每日营养目标 |
|
||||
| `foodList` | Array | 食物份数建议列表 |
|
||||
| `mealPlan` | Object | 一日三餐配餐方案 |
|
||||
| `importantTips` | Array\<String\> | 重要提示文案列表 |
|
||||
| `createdAt` | String | 创建时间 ISO8601 |
|
||||
|
||||
**`healthData` 结构**
|
||||
|
||||
| 字段名 | 类型 | 说明 | 计算公式 |
|
||||
|--------|------|------|----------|
|
||||
| `eGFR` | String | 肾小球滤过率(ml/min/1.73m²) | CKD-EPI 公式(见附录 A) |
|
||||
| `standardWeight` | String | 标准体重(kg) | 男:`(height - 80) × 0.7`;女:`(height - 70) × 0.6` |
|
||||
| `bmi` | String | 体重指数 | `dryWeight / (height/100)²` |
|
||||
| `bmiStatus` | String | BMI 状态描述 | 见附录 B |
|
||||
| `ckdStage` | String | CKD 分期 | 见附录 C |
|
||||
|
||||
**`nutritionGoals` 结构**
|
||||
|
||||
| 字段名 | 类型 | 说明 | 计算公式 |
|
||||
|--------|------|------|----------|
|
||||
| `protein` | String | 每日蛋白质目标(克) | 透析期:`standardWeight × 1.2`;非透析期:`standardWeight × 0.8` |
|
||||
| `energy` | String | 每日能量目标(千卡) | `standardWeight × 35` |
|
||||
|
||||
**`foodList` 结构**(数组,7 项)
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|--------|------|------|
|
||||
| `number` | Integer | 序号 1-7 |
|
||||
| `name` | String | 食物类别名称(如"谷薯50g") |
|
||||
| `portion` | String | 推荐份数 |
|
||||
|
||||
**`mealPlan` 结构**
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|--------|------|------|
|
||||
| `breakfast` | Array | 早餐菜品列表 |
|
||||
| `lunch` | Array | 午餐菜品列表 |
|
||||
| `dinner` | Array | 晚餐菜品列表 |
|
||||
|
||||
**菜品对象结构**
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|--------|------|------|
|
||||
| `name` | String | 菜品名称 |
|
||||
| `image` | String | 菜品图片 URL |
|
||||
| `ingredients` | Array\<String\> | 食材列表(如 `["牛奶 120g", "面条 90g"]`) |
|
||||
|
||||
**响应示例**
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"id": 100234,
|
||||
"healthData": {
|
||||
"eGFR": "7.9",
|
||||
"standardWeight": "63.0",
|
||||
"bmi": "22.7",
|
||||
"bmiStatus": "正常",
|
||||
"ckdStage": "CKD 5期"
|
||||
},
|
||||
"nutritionGoals": {
|
||||
"protein": "75.6",
|
||||
"energy": "2205"
|
||||
},
|
||||
"foodList": [
|
||||
{ "number": 1, "name": "谷薯50g", "portion": "5.7" },
|
||||
{ "number": 2, "name": "淀粉100g", "portion": "0.77" },
|
||||
{ "number": 3, "name": "绿叶蔬菜200g", "portion": "1" },
|
||||
{ "number": 4, "name": "瓜果蔬菜200g", "portion": "2" },
|
||||
{ "number": 5, "name": "奶类230g", "portion": "1" },
|
||||
{ "number": 6, "name": "肉蛋类50/60g", "portion": "7" },
|
||||
{ "number": 7, "name": "油脂类10g", "portion": "5.7" }
|
||||
],
|
||||
"mealPlan": {
|
||||
"breakfast": [
|
||||
{
|
||||
"name": "牛奶",
|
||||
"image": "https://cdn.xxx.com/images/milk.jpg",
|
||||
"ingredients": ["牛奶 120g"]
|
||||
},
|
||||
{
|
||||
"name": "鸡蛋拌面",
|
||||
"image": "https://cdn.xxx.com/images/egg-noodle.jpg",
|
||||
"ingredients": ["面条 90g", "鸡蛋 120g", "葱花 5g"]
|
||||
},
|
||||
{
|
||||
"name": "凉拌黄瓜",
|
||||
"image": "https://cdn.xxx.com/images/cucumber.jpg",
|
||||
"ingredients": ["黄瓜 100g"]
|
||||
}
|
||||
],
|
||||
"lunch": [
|
||||
{
|
||||
"name": "米饭",
|
||||
"image": "https://cdn.xxx.com/images/rice.jpg",
|
||||
"ingredients": ["大米 100g"]
|
||||
},
|
||||
{
|
||||
"name": "清蒸鲈鱼",
|
||||
"image": "https://cdn.xxx.com/images/bass.jpg",
|
||||
"ingredients": ["鲈鱼 120g", "生姜 5g", "葱 5g"]
|
||||
},
|
||||
{
|
||||
"name": "蒜蓉西兰花",
|
||||
"image": "https://cdn.xxx.com/images/broccoli.jpg",
|
||||
"ingredients": ["西兰花 150g", "大蒜 5g", "植物油 8g"]
|
||||
},
|
||||
{
|
||||
"name": "冬瓜汤",
|
||||
"image": "https://cdn.xxx.com/images/wax-gourd-soup.jpg",
|
||||
"ingredients": ["冬瓜 150g"]
|
||||
}
|
||||
],
|
||||
"dinner": [
|
||||
{
|
||||
"name": "杂粮饭",
|
||||
"image": "https://cdn.xxx.com/images/mixed-rice.jpg",
|
||||
"ingredients": ["大米 70g", "小米 30g"]
|
||||
},
|
||||
{
|
||||
"name": "香菇炒鸡丁",
|
||||
"image": "https://cdn.xxx.com/images/chicken-mushroom.jpg",
|
||||
"ingredients": ["鸡胸肉 100g", "香菇 50g", "植物油 8g"]
|
||||
},
|
||||
{
|
||||
"name": "清炒油菜",
|
||||
"image": "https://cdn.xxx.com/images/bok-choy.jpg",
|
||||
"ingredients": ["油菜 150g", "植物油 5g"]
|
||||
},
|
||||
{
|
||||
"name": "番茄蛋花汤",
|
||||
"image": "https://cdn.xxx.com/images/tomato-egg-soup.jpg",
|
||||
"ingredients": ["番茄 100g", "鸡蛋 60g"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"importantTips": [
|
||||
"以上配餐由 AI 生成,仅适用于无其他并发症的单纯尿毒症人群",
|
||||
"透析患者需严格控制水分摄入",
|
||||
"建议低盐饮食(每日少于 5g)",
|
||||
"注意限制高钾食物(香蕉、橙子、土豆等)",
|
||||
"限制高磷食物(坚果、动物内脏等)",
|
||||
"特别提醒:尿毒症合并其他并发症人群不适用此配餐,请务必咨询专业营养师调整方案"
|
||||
],
|
||||
"createdAt": "2026-02-01T10:30:00+08:00"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 错误码
|
||||
|
||||
| code | message | 说明 |
|
||||
|------|---------|------|
|
||||
| 400 | 参数校验失败 | 请求参数不符合校验规则 |
|
||||
| 401 / 410000-410002 | 未登录/登录过期 | 需重新登录 |
|
||||
| 500 | 系统异常 | 服务端内部错误 |
|
||||
|
||||
---
|
||||
|
||||
### 3.2 获取计算结果详情
|
||||
|
||||
#### 基础信息
|
||||
|
||||
| 项目 | 值 |
|
||||
|------|------|
|
||||
| 请求路径 | `GET /api/front/tool/calculator/result/{id}` |
|
||||
| 是否鉴权 | **是** |
|
||||
| 路径参数 | `id`:计算结果ID(Long) |
|
||||
|
||||
#### 响应
|
||||
|
||||
与 3.1 的 `data` 结构相同,用于结果页刷新或分享后重新加载。
|
||||
|
||||
---
|
||||
|
||||
### 3.3 采纳营养计划
|
||||
|
||||
#### 基础信息
|
||||
|
||||
| 项目 | 值 |
|
||||
|------|------|
|
||||
| 请求路径 | `POST /api/front/tool/calculator/adopt` |
|
||||
| 是否鉴权 | **是** |
|
||||
| Content-Type | `application/json` |
|
||||
|
||||
#### 请求参数
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| `resultId` | Long | 是 | 计算结果ID |
|
||||
|
||||
**请求示例**
|
||||
|
||||
```json
|
||||
{
|
||||
"resultId": 100234
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|--------|------|------|
|
||||
| `code` | Integer | 200 成功 |
|
||||
| `message` | String | 提示信息 |
|
||||
| `data.planId` | Long | 新创建的营养计划ID |
|
||||
| `data.startDate` | String | 计划开始日期 |
|
||||
| `data.endDate` | String | 计划结束日期(默认 +7 天) |
|
||||
|
||||
**响应示例**
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "采纳成功",
|
||||
"data": {
|
||||
"planId": 56789,
|
||||
"startDate": "2026-02-01",
|
||||
"endDate": "2026-02-07"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 业务规则
|
||||
|
||||
1. 同一用户同一时间只能有 **1 个激活状态(`status=active`)** 的营养计划;若已存在则自动将旧计划标记为 `abandoned`。
|
||||
2. 采纳成功后赠送 **+20 积分**(积分任务:采纳营养方案)。
|
||||
|
||||
---
|
||||
|
||||
## 四、数据库表设计
|
||||
|
||||
### 4.1 计算结果表(calculator_results)
|
||||
|
||||
> 存储每次计算的输入与输出,方便查询历史、分析
|
||||
|
||||
```sql
|
||||
CREATE TABLE calculator_results (
|
||||
result_id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
|
||||
user_id BIGINT NOT NULL COMMENT '用户ID',
|
||||
|
||||
-- 输入参数
|
||||
gender VARCHAR(10) NOT NULL COMMENT '性别:male/female',
|
||||
age INT NOT NULL COMMENT '年龄(岁)',
|
||||
height INT NOT NULL COMMENT '身高(cm)',
|
||||
has_dialysis TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否透析:0-否 1-是',
|
||||
dialysis_type VARCHAR(20) COMMENT '透析类型:hemodialysis/peritoneal',
|
||||
dry_weight DECIMAL(5,2) NOT NULL COMMENT '干体重(kg)',
|
||||
creatinine DECIMAL(8,2) NOT NULL COMMENT '血肌酐(μmol/L)',
|
||||
|
||||
-- 计算结果
|
||||
egfr DECIMAL(6,2) NOT NULL COMMENT 'eGFR(ml/min/1.73m²)',
|
||||
standard_weight DECIMAL(5,2) NOT NULL COMMENT '标准体重(kg)',
|
||||
bmi DECIMAL(4,1) NOT NULL COMMENT 'BMI',
|
||||
bmi_status VARCHAR(20) NOT NULL COMMENT 'BMI状态描述',
|
||||
ckd_stage VARCHAR(50) NOT NULL COMMENT 'CKD分期',
|
||||
protein_intake DECIMAL(5,1) NOT NULL COMMENT '每日蛋白质目标(g)',
|
||||
energy_intake INT NOT NULL COMMENT '每日能量目标(kcal)',
|
||||
|
||||
-- 配餐方案(JSON)
|
||||
food_list_json TEXT COMMENT '食物份数建议 JSON',
|
||||
meal_plan_json TEXT COMMENT '配餐方案 JSON',
|
||||
tips_json TEXT COMMENT '重要提示 JSON',
|
||||
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
|
||||
INDEX idx_user_id (user_id),
|
||||
INDEX idx_created_at (created_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='营养计算结果表';
|
||||
```
|
||||
|
||||
### 4.2 营养计划表(nutrition_plans)
|
||||
|
||||
> PRD 中已定义,补充关联
|
||||
|
||||
```sql
|
||||
-- 见 PRD 4.6.5 节
|
||||
-- 关键字段:
|
||||
-- plan_id, user_id, gender, age, height, weight, has_dialysis, dialysis_type, creatinine,
|
||||
-- egfr, standard_weight, bmi, ckd_stage, protein_intake, energy_intake,
|
||||
-- meal_plan_json, status, start_date, end_date, created_at, updated_at
|
||||
```
|
||||
|
||||
关联关系:`nutrition_plans.source_result_id → calculator_results.id`(可选字段,用于溯源)
|
||||
|
||||
---
|
||||
|
||||
## 五、核心算法
|
||||
|
||||
### 5.1 eGFR 计算(CKD-EPI 2021 公式)
|
||||
|
||||
```java
|
||||
/**
|
||||
* CKD-EPI 2021 公式(无种族系数)
|
||||
* @param creatinine 血肌酐 (μmol/L)
|
||||
* @param age 年龄 (岁)
|
||||
* @param isFemale 是否女性
|
||||
* @return eGFR (ml/min/1.73m²)
|
||||
*/
|
||||
public static double calculateEGFR(double creatinine, int age, boolean isFemale) {
|
||||
// 将 μmol/L 转换为 mg/dL
|
||||
double scr = creatinine / 88.4;
|
||||
|
||||
double kappa = isFemale ? 0.7 : 0.9;
|
||||
double alpha = isFemale ? -0.241 : -0.302;
|
||||
double multi = isFemale ? 1.012 : 1.0;
|
||||
|
||||
double minRatio = Math.min(scr / kappa, 1.0);
|
||||
double maxRatio = Math.max(scr / kappa, 1.0);
|
||||
|
||||
double eGFR = 142 * Math.pow(minRatio, alpha) * Math.pow(maxRatio, -1.200)
|
||||
* Math.pow(0.9938, age) * multi;
|
||||
return Math.round(eGFR * 100.0) / 100.0; // 保留2位小数
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 标准体重
|
||||
|
||||
```java
|
||||
public static double calculateStandardWeight(int height, boolean isFemale) {
|
||||
return isFemale
|
||||
? (height - 70) * 0.6
|
||||
: (height - 80) * 0.7;
|
||||
}
|
||||
```
|
||||
|
||||
### 5.3 BMI
|
||||
|
||||
```java
|
||||
public static double calculateBMI(double weight, int height) {
|
||||
double heightM = height / 100.0;
|
||||
return Math.round(weight / (heightM * heightM) * 10.0) / 10.0;
|
||||
}
|
||||
```
|
||||
|
||||
### 5.4 BMI 状态
|
||||
|
||||
| BMI 范围 | 状态描述 |
|
||||
|----------|----------|
|
||||
| < 18.5 | 体型过轻 |
|
||||
| 18.5 - 23.9 | 正常 |
|
||||
| 24.0 - 27.9 | 超重 |
|
||||
| ≥ 28.0 | 肥胖 |
|
||||
|
||||
### 5.5 CKD 分期
|
||||
|
||||
| eGFR 范围 | 分期 |
|
||||
|-----------|------|
|
||||
| ≥ 90 | CKD 1期 |
|
||||
| 60 - 89 | CKD 2期 |
|
||||
| 45 - 59 | CKD 3a期 |
|
||||
| 30 - 44 | CKD 3b期 |
|
||||
| 15 - 29 | CKD 4期 |
|
||||
| < 15(非透析) | CKD 5期 |
|
||||
| 透析患者 | 透析期 |
|
||||
|
||||
### 5.6 营养目标
|
||||
|
||||
```java
|
||||
public static double calculateProtein(double standardWeight, boolean hasDialysis) {
|
||||
return hasDialysis
|
||||
? standardWeight * 1.2 // 透析期
|
||||
: standardWeight * 0.8; // 非透析期
|
||||
}
|
||||
|
||||
public static int calculateEnergy(double standardWeight) {
|
||||
return (int) Math.round(standardWeight * 35);
|
||||
}
|
||||
```
|
||||
|
||||
### 5.7 食物份数
|
||||
|
||||
基于能量和蛋白质目标,按照《慢性肾脏病患者膳食指导(2017)》推荐比例分配,具体算法可参考专业营养软件或配置表。
|
||||
|
||||
### 5.8 配餐方案生成
|
||||
|
||||
1. **规则匹配**:基于 CKD 分期、透析类型、营养目标,从预置的 **配餐模板库(recipes)** 中匹配合适方案。
|
||||
2. **AI 生成(可选)**:调用 AI 模型根据营养目标动态生成菜品及食材用量。
|
||||
3. **输出**:早/午/晚三餐各 2-4 道菜,包含图片 URL 和食材列表。
|
||||
|
||||
---
|
||||
|
||||
## 六、性能与安全
|
||||
|
||||
### 6.1 缓存策略
|
||||
|
||||
| 数据 | 缓存方式 | 过期时间 | 说明 |
|
||||
|------|----------|----------|------|
|
||||
| 计算结果 | Redis Hash | 24 小时 | key = `calc_result:{id}` |
|
||||
| 配餐模板库 | 本地缓存 / Redis | 1 小时 | 减少 DB 查询 |
|
||||
|
||||
### 6.2 限流
|
||||
|
||||
| 接口 | QPS 限制 | 说明 |
|
||||
|------|----------|------|
|
||||
| calculate | 10/用户/分钟 | 防止恶意刷算 |
|
||||
| result/{id} | 60/用户/分钟 | 允许刷新 |
|
||||
| adopt | 5/用户/分钟 | 防重复提交 |
|
||||
|
||||
### 6.3 幂等性
|
||||
|
||||
- **calculate**:每次计算生成新记录,允许重复调用。
|
||||
- **adopt**:采纳接口需幂等设计,同一 `resultId` 多次调用只创建一个计划;可用 `user_id + result_id` 唯一索引或分布式锁。
|
||||
|
||||
### 6.4 数据安全
|
||||
|
||||
- 所有接口均需登录鉴权(Header: `Authori-zation`)。
|
||||
- 用户只能查询/采纳自己的计算结果(`user_id` 校验)。
|
||||
- 敏感健康数据传输全程 HTTPS。
|
||||
|
||||
---
|
||||
|
||||
## 七、测试用例
|
||||
|
||||
### 7.1 正常场景
|
||||
|
||||
| 用例编号 | 描述 | 输入 | 预期输出 |
|
||||
|----------|------|------|----------|
|
||||
| TC01 | 男性透析患者计算 | gender=male, age=55, height=170, dialysis=true, dryWeight=65.5, creatinine=850 | 返回 code=200,healthData/nutritionGoals/mealPlan 完整 |
|
||||
| TC02 | 女性非透析患者 | gender=female, age=48, height=160, dialysis=false, dryWeight=52, creatinine=180 | 返回 code=200,ckdStage 按 eGFR 判断 |
|
||||
| TC03 | 查询结果详情 | GET /result/100234 | 返回与 calculate 相同结构 |
|
||||
| TC04 | 采纳营养计划 | resultId=100234 | 返回 planId,计划 status=active |
|
||||
|
||||
### 7.2 异常场景
|
||||
|
||||
| 用例编号 | 描述 | 输入 | 预期输出 |
|
||||
|----------|------|------|----------|
|
||||
| TC11 | 年龄超出范围 | age=200 | code=400, message 包含"年龄" |
|
||||
| TC12 | 未登录调用 | 无 Token | code=401/410000 |
|
||||
| TC13 | 查询他人结果 | id 属于其他用户 | code=403 |
|
||||
| TC14 | 重复采纳 | 同一 resultId 调用两次 | 第二次返回已存在的 planId(幂等) |
|
||||
|
||||
---
|
||||
|
||||
## 八、附录
|
||||
|
||||
### 附录 A:CKD-EPI 2021 公式
|
||||
|
||||
> 无种族系数版本,适用于中国人群
|
||||
|
||||
```
|
||||
eGFR = 142 × min(Scr/κ, 1)^α × max(Scr/κ, 1)^(-1.200) × 0.9938^Age × (1.012 if female)
|
||||
|
||||
其中:
|
||||
- Scr = 血肌酐 (mg/dL) = μmol/L ÷ 88.4
|
||||
- κ = 0.7 (女) / 0.9 (男)
|
||||
- α = -0.241 (女) / -0.302 (男)
|
||||
```
|
||||
|
||||
### 附录 B:BMI 分类标准(中国)
|
||||
|
||||
| BMI | 分类 |
|
||||
|-----|------|
|
||||
| < 18.5 | 体型过轻 |
|
||||
| 18.5 - 23.9 | 正常 |
|
||||
| 24.0 - 27.9 | 超重 |
|
||||
| ≥ 28.0 | 肥胖 |
|
||||
|
||||
### 附录 C:CKD 分期标准
|
||||
|
||||
| 分期 | eGFR (ml/min/1.73m²) | 描述 |
|
||||
|------|----------------------|------|
|
||||
| 1期 | ≥ 90 | 肾功能正常或升高 |
|
||||
| 2期 | 60-89 | 轻度下降 |
|
||||
| 3a期 | 45-59 | 轻-中度下降 |
|
||||
| 3b期 | 30-44 | 中-重度下降 |
|
||||
| 4期 | 15-29 | 重度下降 |
|
||||
| 5期 | < 15 | 肾衰竭 |
|
||||
| 透析期 | — | 已接受透析治疗 |
|
||||
|
||||
---
|
||||
|
||||
## 九、变更记录
|
||||
|
||||
| 版本 | 日期 | 作者 | 变更内容 |
|
||||
|------|------|------|----------|
|
||||
| v1.0 | 2026-02-01 | — | 初稿 |
|
||||
|
||||
---
|
||||
|
||||
*文档结束*
|
||||
BIN
models-integration/.DS_Store
vendored
Normal file
BIN
models-integration/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
models-integration/.idea/.DS_Store
generated
vendored
Normal file
BIN
models-integration/.idea/.DS_Store
generated
vendored
Normal file
Binary file not shown.
8
models-integration/.idea/.gitignore
generated
vendored
Normal file
8
models-integration/.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
7
models-integration/.idea/MarsCodeWorkspaceAppSettings.xml
generated
Normal file
7
models-integration/.idea/MarsCodeWorkspaceAppSettings.xml
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="com.codeverse.userSettings.MarscodeWorkspaceAppSettingsState">
|
||||
<option name="chatAppRouterInfo" value="builder/68ec7201f33b556a21fc615e" />
|
||||
<option name="progress" value="1.0" />
|
||||
</component>
|
||||
</project>
|
||||
16
models-integration/.idea/compiler.xml
generated
Normal file
16
models-integration/.idea/compiler.xml
generated
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<option name="BUILD_PROCESS_HEAP_SIZE" value="8700" />
|
||||
<annotationProcessing>
|
||||
<profile default="true" name="Default" enabled="true" />
|
||||
<profile name="Maven default annotation processors profile" enabled="true">
|
||||
<sourceOutputDir name="target/generated-sources/annotations" />
|
||||
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
|
||||
<outputRelativeToContentRoot value="true" />
|
||||
<module name="model.integration" />
|
||||
<module name="models-integration" />
|
||||
</profile>
|
||||
</annotationProcessing>
|
||||
</component>
|
||||
</project>
|
||||
13
models-integration/.idea/easycode.ignore
generated
Normal file
13
models-integration/.idea/easycode.ignore
generated
Normal file
@@ -0,0 +1,13 @@
|
||||
.idea
|
||||
.vscode
|
||||
node_modules/
|
||||
dist/
|
||||
vendor/
|
||||
cache/
|
||||
.*/
|
||||
*.min.*
|
||||
*.test.*
|
||||
*.spec.*
|
||||
*.bundle.*
|
||||
*.bundle-min.*
|
||||
*.log
|
||||
6
models-integration/.idea/easycode/codebase-v2.xml
generated
Normal file
6
models-integration/.idea/easycode/codebase-v2.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="com.obiscr.chatgpt.settings.EasyCodeState">
|
||||
<option name="projectFiles" value="$PROJECT_DIR$/src/main/java/com/xbongbong/api/demo/communicate/CommunicateApi.java;/Users/a123/Documents/UthinkJava2025/xbb-api-demo/src/main/java/com/xbongbong/api/demo/contact/ContactApi.java;/Users/a123/Documents/UthinkJava2025/xbb-api-demo/src/main/java/com/xbongbong/api/demo/customer/CustomerApi.java;/Users/a123/Documents/UthinkJava2025/xbb-api-demo/src/main/java/com/xbongbong/api/demo/form/FormApi.java;/Users/a123/Documents/UthinkJava2025/xbb-api-demo/src/main/java/com/xbongbong/api/demo/helper/ConfigConstant.java;/Users/a123/Documents/UthinkJava2025/xbb-api-demo/src/main/java/com/xbongbong/api/demo/helper/DigestUtil.java;/Users/a123/Documents/UthinkJava2025/xbb-api-demo/src/main/java/com/xbongbong/api/demo/helper/HttpRequestUtils.java;/Users/a123/Documents/UthinkJava2025/xbb-api-demo/src/main/java/com/xbongbong/api/demo/helper/XbbException.java;/Users/a123/Documents/UthinkJava2025/xbb-api-demo/src/main/java/com/xbongbong/api/demo/Demo.java;/Users/a123/Documents/UthinkJava2025/xbb-api-demo/coding-tip.md;/Users/a123/Documents/UthinkJava2025/xbb-api-demo/readme-pom.md;/Users/a123/Documents/UthinkJava2025/xbb-api-demo/readme.md" />
|
||||
</component>
|
||||
</project>
|
||||
7
models-integration/.idea/encodings.xml
generated
Normal file
7
models-integration/.idea/encodings.xml
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding">
|
||||
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
|
||||
</component>
|
||||
</project>
|
||||
36
models-integration/.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
36
models-integration/.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@@ -0,0 +1,36 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="JavaDoc" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="TOP_LEVEL_CLASS_OPTIONS">
|
||||
<value>
|
||||
<option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" />
|
||||
<option name="REQUIRED_TAGS" value="" />
|
||||
</value>
|
||||
</option>
|
||||
<option name="INNER_CLASS_OPTIONS">
|
||||
<value>
|
||||
<option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" />
|
||||
<option name="REQUIRED_TAGS" value="" />
|
||||
</value>
|
||||
</option>
|
||||
<option name="METHOD_OPTIONS">
|
||||
<value>
|
||||
<option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" />
|
||||
<option name="REQUIRED_TAGS" value="@return@param@throws or @exception" />
|
||||
</value>
|
||||
</option>
|
||||
<option name="FIELD_OPTIONS">
|
||||
<value>
|
||||
<option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" />
|
||||
<option name="REQUIRED_TAGS" value="" />
|
||||
</value>
|
||||
</option>
|
||||
<option name="IGNORE_DEPRECATED" value="false" />
|
||||
<option name="IGNORE_JAVADOC_PERIOD" value="true" />
|
||||
<option name="IGNORE_DUPLICATED_THROWS" value="false" />
|
||||
<option name="IGNORE_POINT_TO_ITSELF" value="false" />
|
||||
<option name="myAdditionalJavadocTags" value="date" />
|
||||
</inspection_tool>
|
||||
</profile>
|
||||
</component>
|
||||
45
models-integration/.idea/jarRepositories.xml
generated
Normal file
45
models-integration/.idea/jarRepositories.xml
generated
Normal file
@@ -0,0 +1,45 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RemoteRepositoriesConfiguration">
|
||||
<remote-repository>
|
||||
<option name="id" value="nexus" />
|
||||
<option name="name" value="local private nexus" />
|
||||
<option name="url" value="https://maven.aliyun.com/nexus/content/groups/public/" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="central" />
|
||||
<option name="name" value="Central Repository" />
|
||||
<option name="url" value="https://repo.maven.apache.org/maven2" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="local-nexus" />
|
||||
<option name="name" value="local-nexus" />
|
||||
<option name="url" value="http://192.168.10.4:8081/nexus/content/groups/public/" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="central" />
|
||||
<option name="name" value="Maven Central repository" />
|
||||
<option name="url" value="https://repo1.maven.org/maven2" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="central" />
|
||||
<option name="name" value="Central Repository" />
|
||||
<option name="url" value="http://maven.oschina.net/content/groups/public/" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="jboss.community" />
|
||||
<option name="name" value="JBoss Community repository" />
|
||||
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="central" />
|
||||
<option name="name" value="Central Repository" />
|
||||
<option name="url" value="https://maven.aliyun.com/repository/public" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="central" />
|
||||
<option name="name" value="Central Repository" />
|
||||
<option name="url" value="https://maven.oschina.net/content/groups/public/" />
|
||||
</remote-repository>
|
||||
</component>
|
||||
</project>
|
||||
23
models-integration/.idea/misc.xml
generated
Normal file
23
models-integration/.idea/misc.xml
generated
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="FindBugsConfigurable">
|
||||
<option name="make" value="true" />
|
||||
<option name="effort" value="default" />
|
||||
<option name="priority" value="Medium" />
|
||||
<option name="excludeFilter" value="" />
|
||||
</component>
|
||||
<component name="MavenProjectsManager">
|
||||
<option name="originalFiles">
|
||||
<list>
|
||||
<option value="$PROJECT_DIR$/pom.xml" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
<component name="SuppressionsComponent">
|
||||
<option name="suppComments" value="[]" />
|
||||
</component>
|
||||
</project>
|
||||
465
models-integration/.idea/qaplug_profiles.xml
generated
Normal file
465
models-integration/.idea/qaplug_profiles.xml
generated
Normal file
@@ -0,0 +1,465 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="AnalysisProjectProfileManager">
|
||||
<option name="PROJECT_PROFILE" value="Project Default" />
|
||||
<option name="USE_PROJECT_LEVEL_SETTINGS" value="false" />
|
||||
<scopes />
|
||||
<profiles>
|
||||
<profile profile_name="Project Default" version="1.0" is_locked="false">
|
||||
<coding_rule class="AM_CREATES_EMPTY_JAR_FILE_ENTRY" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="AM_CREATES_EMPTY_ZIP_FILE_ENTRY" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="AT_OPERATION_SEQUENCE_ON_CONCURRENT_ABSTRACTION" level="CRITICAL" enabled="false" />
|
||||
<coding_rule class="BAC_BAD_APPLET_CONSTRUCTOR" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="BC_BAD_CAST_TO_ABSTRACT_COLLECTION" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="BC_BAD_CAST_TO_CONCRETE_COLLECTION" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="BC_EQUALS_METHOD_SHOULD_WORK_FOR_ALL_OBJECTS" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="BC_IMPOSSIBLE_CAST" level="BLOCKER" enabled="true" />
|
||||
<coding_rule class="BC_IMPOSSIBLE_DOWNCAST" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="BC_IMPOSSIBLE_DOWNCAST_OF_TOARRAY" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="BC_IMPOSSIBLE_INSTANCEOF" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="BC_UNCONFIRMED_CAST" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="BC_UNCONFIRMED_CAST_OF_RETURN_VALUE" level="CRITICAL" enabled="false" />
|
||||
<coding_rule class="BC_VACUOUS_INSTANCEOF" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="BIT_ADD_OF_SIGNED_BYTE" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="BIT_AND" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="BIT_AND_ZZ" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="BIT_IOR" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="BIT_IOR_OF_SIGNED_BYTE" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="BIT_SIGNED_CHECK" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="BIT_SIGNED_CHECK_HIGH_BIT" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="BOA_BADLY_OVERRIDDEN_ADAPTER" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="BSHIFT_WRONG_ADD_PRIORITY" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="BX_BOXING_IMMEDIATELY_UNBOXED" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="BX_BOXING_IMMEDIATELY_UNBOXED_TO_PERFORM_COERCION" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="BX_UNBOXED_AND_COERCED_FOR_TERNARY_OPERATOR" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="BX_UNBOXING_IMMEDIATELY_REBOXED" level="CRITICAL" enabled="false" />
|
||||
<coding_rule class="CAA_COVARIANT_ARRAY_ELEMENT_STORE" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="CAA_COVARIANT_ARRAY_FIELD" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="CAA_COVARIANT_ARRAY_LOCAL" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="CAA_COVARIANT_ARRAY_RETURN" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="CD_CIRCULAR_DEPENDENCY" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="CI_CONFUSED_INHERITANCE" level="MINOR" enabled="true" />
|
||||
<coding_rule class="CNT_ROUGH_CONSTANT_VALUE" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="CN_IDIOM" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="CN_IDIOM_NO_SUPER_CALL" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="CN_IMPLEMENTS_CLONE_BUT_NOT_CLONEABLE" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="CO_ABSTRACT_SELF" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="CO_COMPARETO_INCORRECT_FLOATING" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="CO_COMPARETO_RESULTS_MIN_VALUE" level="CRITICAL" enabled="false" />
|
||||
<coding_rule class="CO_SELF_NO_OBJECT" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="DB_DUPLICATE_BRANCHES" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="DB_DUPLICATE_SWITCH_CLAUSES" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="DC_DOUBLECHECK" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="DC_PARTIALLY_CONSTRUCTED" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="DE_MIGHT_DROP" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="DE_MIGHT_IGNORE" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="DLS_DEAD_LOCAL_INCREMENT_IN_RETURN" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="DLS_DEAD_LOCAL_STORE" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="DLS_DEAD_LOCAL_STORE_IN_RETURN" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="DLS_DEAD_LOCAL_STORE_OF_NULL" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="DLS_DEAD_LOCAL_STORE_SHADOWS_FIELD" level="CRITICAL" enabled="false" />
|
||||
<coding_rule class="DLS_DEAD_STORE_OF_CLASS_LITERAL" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="DLS_OVERWRITTEN_INCREMENT" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="DL_SYNCHRONIZATION_ON_BOOLEAN" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="DL_SYNCHRONIZATION_ON_BOXED_PRIMITIVE" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="DL_SYNCHRONIZATION_ON_SHARED_CONSTANT" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="DL_SYNCHRONIZATION_ON_UNSHARED_BOXED_PRIMITIVE" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="DMI_ANNOTATION_IS_NOT_VISIBLE_TO_REFLECTION" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="DMI_ARGUMENTS_WRONG_ORDER" level="CRITICAL" enabled="false" />
|
||||
<coding_rule class="DMI_BAD_MONTH" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="DMI_BIGDECIMAL_CONSTRUCTED_FROM_DOUBLE" level="CRITICAL" enabled="false" />
|
||||
<coding_rule class="DMI_BLOCKING_METHODS_ON_URL" level="BLOCKER" enabled="true" />
|
||||
<coding_rule class="DMI_CALLING_NEXT_FROM_HASNEXT" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="DMI_COLLECTIONS_SHOULD_NOT_CONTAIN_THEMSELVES" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="DMI_COLLECTION_OF_URLS" level="BLOCKER" enabled="true" />
|
||||
<coding_rule class="DMI_CONSTANT_DB_PASSWORD" level="BLOCKER" enabled="true" />
|
||||
<coding_rule class="DMI_DOH" level="CRITICAL" enabled="false" />
|
||||
<coding_rule class="DMI_EMPTY_DB_PASSWORD" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="DMI_ENTRY_SETS_MAY_REUSE_ENTRY_OBJECTS" level="CRITICAL" enabled="false" />
|
||||
<coding_rule class="DMI_FUTILE_ATTEMPT_TO_CHANGE_MAXPOOL_SIZE_OF_SCHEDULED_THREAD_POOL_EXECUTOR" level="MINOR" enabled="true" />
|
||||
<coding_rule class="DMI_HARDCODED_ABSOLUTE_FILENAME" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="DMI_INVOKING_HASHCODE_ON_ARRAY" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="DMI_INVOKING_TOSTRING_ON_ANONYMOUS_ARRAY" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="DMI_INVOKING_TOSTRING_ON_ARRAY" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="DMI_LONG_BITS_TO_DOUBLE_INVOKED_ON_INT" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="DMI_NONSERIALIZABLE_OBJECT_WRITTEN" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="DMI_RANDOM_USED_ONLY_ONCE" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="DMI_SCHEDULED_THREAD_POOL_EXECUTOR_WITH_ZERO_CORE_THREADS" level="MINOR" enabled="true" />
|
||||
<coding_rule class="DMI_THREAD_PASSED_WHERE_RUNNABLE_EXPECTED" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="DMI_UNSUPPORTED_METHOD" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="DMI_USELESS_SUBSTRING" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="DMI_USING_REMOVEALL_TO_CLEAR_COLLECTION" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="DMI_VACUOUS_CALL_TO_EASYMOCK_METHOD" level="MINOR" enabled="true" />
|
||||
<coding_rule class="DMI_VACUOUS_SELF_COLLECTION_CALL" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="DM_BOOLEAN_CTOR" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="DM_BOXED_PRIMITIVE_FOR_COMPARE" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="DM_BOXED_PRIMITIVE_FOR_PARSING" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="DM_BOXED_PRIMITIVE_TOSTRING" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="DM_CONVERT_CASE" level="INFO" enabled="true" />
|
||||
<coding_rule class="DM_DEFAULT_ENCODING" level="CRITICAL" enabled="false" />
|
||||
<coding_rule class="DM_EXIT" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="DM_FP_NUMBER_CTOR" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="DM_GC" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="DM_INVALID_MIN_MAX" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="DM_MONITOR_WAIT_ON_CONDITION" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="DM_NEW_FOR_GETCLASS" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="DM_NEXTINT_VIA_NEXTDOUBLE" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="DM_NUMBER_CTOR" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="DM_RUN_FINALIZERS_ON_EXIT" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="DM_STRING_CTOR" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="DM_STRING_TOSTRING" level="INFO" enabled="true" />
|
||||
<coding_rule class="DM_STRING_VOID_CTOR" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="DM_USELESS_THREAD" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="DP_DO_INSIDE_DO_PRIVILEGED" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="EC_ARRAY_AND_NONARRAY" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="EC_BAD_ARRAY_COMPARE" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="EC_INCOMPATIBLE_ARRAY_COMPARE" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="EC_NULL_ARG" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="EC_UNRELATED_CLASS_AND_INTERFACE" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="EC_UNRELATED_INTERFACES" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="EC_UNRELATED_TYPES" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="EC_UNRELATED_TYPES_USING_POINTER_EQUALITY" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="EI_EXPOSE_REP" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="EI_EXPOSE_REP2" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="EI_EXPOSE_STATIC_REP2" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="EQ_ABSTRACT_SELF" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="EQ_ALWAYS_FALSE" level="BLOCKER" enabled="true" />
|
||||
<coding_rule class="EQ_ALWAYS_TRUE" level="BLOCKER" enabled="true" />
|
||||
<coding_rule class="EQ_CHECK_FOR_OPERAND_NOT_COMPATIBLE_WITH_THIS" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="EQ_COMPARETO_USE_OBJECT_EQUALS" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="EQ_COMPARING_CLASS_NAMES" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="EQ_DOESNT_OVERRIDE_EQUALS" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="EQ_DONT_DEFINE_EQUALS_FOR_ENUM" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="EQ_GETCLASS_AND_CLASS_CONSTANT" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="EQ_OTHER_NO_OBJECT" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="EQ_OTHER_USE_OBJECT" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="EQ_OVERRIDING_EQUALS_NOT_SYMMETRIC" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="EQ_SELF_NO_OBJECT" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="EQ_SELF_USE_OBJECT" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="EQ_UNUSUAL" level="MINOR" enabled="true" />
|
||||
<coding_rule class="ES_COMPARING_PARAMETER_STRING_WITH_EQ" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="ES_COMPARING_STRINGS_WITH_EQ" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="ESync_EMPTY_SYNC" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="FB_MISSING_EXPECTED_WARNING" level="CRITICAL" enabled="false" />
|
||||
<coding_rule class="FB_UNEXPECTED_WARNING" level="CRITICAL" enabled="false" />
|
||||
<coding_rule class="FE_FLOATING_POINT_EQUALITY" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="FE_TEST_IF_EQUAL_TO_NOT_A_NUMBER" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="FI_EMPTY" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="FI_EXPLICIT_INVOCATION" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="FI_FINALIZER_NULLS_FIELDS" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="FI_FINALIZER_ONLY_NULLS_FIELDS" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="FI_MISSING_SUPER_CALL" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="FI_NULLIFY_SUPER" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="FI_PUBLIC_SHOULD_BE_PROTECTED" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="FI_USELESS" level="MINOR" enabled="true" />
|
||||
<coding_rule class="FL_MATH_USING_FLOAT_PRECISION" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="GC_UNCHECKED_TYPE_IN_GENERIC_CALL" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="GC_UNRELATED_TYPES" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="HE_EQUALS_NO_HASHCODE" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="HE_EQUALS_USE_HASHCODE" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="HE_HASHCODE_NO_EQUALS" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="HE_HASHCODE_USE_OBJECT_EQUALS" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="HE_INHERITS_EQUALS_USE_HASHCODE" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="HE_SIGNATURE_DECLARES_HASHING_OF_UNHASHABLE_CLASS" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="HE_USE_OF_UNHASHABLE_CLASS" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="HRS_REQUEST_PARAMETER_TO_COOKIE" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="HRS_REQUEST_PARAMETER_TO_HTTP_HEADER" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="HSC_HUGE_SHARED_STRING_CONSTANT" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="IA_AMBIGUOUS_INVOCATION_OF_INHERITED_OR_OUTER_METHOD" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="ICAST_BAD_SHIFT_AMOUNT" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="ICAST_IDIV_CAST_TO_DOUBLE" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="ICAST_INTEGER_MULTIPLY_CAST_TO_LONG" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="ICAST_INT_2_LONG_AS_INSTANT" level="CRITICAL" enabled="false" />
|
||||
<coding_rule class="ICAST_INT_CAST_TO_DOUBLE_PASSED_TO_CEIL" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="ICAST_INT_CAST_TO_FLOAT_PASSED_TO_ROUND" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="ICAST_QUESTIONABLE_UNSIGNED_RIGHT_SHIFT" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="IC_INIT_CIRCULARITY" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="IC_SUPERCLASS_USES_SUBCLASS_DURING_INITIALIZATION" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="IIL_ELEMENTS_GET_LENGTH_IN_LOOP" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="IIL_PATTERN_COMPILE_IN_LOOP" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="IIL_PATTERN_COMPILE_IN_LOOP_INDIRECT" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="IIL_PREPARE_STATEMENT_IN_LOOP" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="IIO_INEFFICIENT_INDEX_OF" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="IIO_INEFFICIENT_LAST_INDEX_OF" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="IJU_ASSERT_METHOD_INVOKED_FROM_RUN_METHOD" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="IJU_BAD_SUITE_METHOD" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="IJU_NO_TESTS" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="IJU_SETUP_NO_SUPER" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="IJU_SUITE_NOT_STATIC" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="IJU_TEARDOWN_NO_SUPER" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="IL_CONTAINER_ADDED_TO_ITSELF" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="IL_INFINITE_LOOP" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="IL_INFINITE_RECURSIVE_LOOP" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="IMA_INEFFICIENT_MEMBER_ACCESS" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="IMSE_DONT_CATCH_IMSE" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="IM_AVERAGE_COMPUTATION_COULD_OVERFLOW" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="IM_BAD_CHECK_FOR_ODD" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="IM_MULTIPLYING_RESULT_OF_IREM" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="INT_BAD_COMPARISON_WITH_INT_VALUE" level="CRITICAL" enabled="false" />
|
||||
<coding_rule class="INT_BAD_COMPARISON_WITH_NONNEGATIVE_VALUE" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="INT_BAD_COMPARISON_WITH_SIGNED_BYTE" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="INT_BAD_REM_BY_1" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="INT_VACUOUS_BIT_OPERATION" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="INT_VACUOUS_COMPARISON" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="IO_APPENDING_TO_OBJECT_OUTPUT_STREAM" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="IP_PARAMETER_IS_DEAD_BUT_OVERWRITTEN" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="IS2_INCONSISTENT_SYNC" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="ISC_INSTANTIATE_STATIC_CLASS" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="IS_FIELD_NOT_GUARDED" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="ITA_INEFFICIENT_TO_ARRAY" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="IT_NO_SUCH_ELEMENT" level="MINOR" enabled="true" />
|
||||
<coding_rule class="J2EE_STORE_OF_NON_SERIALIZABLE_OBJECT_INTO_SESSION" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="JCIP_FIELD_ISNT_FINAL_IN_IMMUTABLE_CLASS" level="MINOR" enabled="true" />
|
||||
<coding_rule class="JLM_JSR166_LOCK_MONITORENTER" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="JLM_JSR166_UTILCONCURRENT_MONITORENTER" level="CRITICAL" enabled="false" />
|
||||
<coding_rule class="JML_JSR166_CALLING_WAIT_RATHER_THAN_AWAIT" level="CRITICAL" enabled="false" />
|
||||
<coding_rule class="LG_LOST_LOGGER_DUE_TO_WEAK_REFERENCE" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="LI_LAZY_INIT_STATIC" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="LI_LAZY_INIT_UPDATE_STATIC" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="ME_ENUM_FIELD_SETTER" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="ME_MUTABLE_ENUM_FIELD" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="MF_CLASS_MASKS_FIELD" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="MF_METHOD_MASKS_FIELD" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="ML_SYNC_ON_FIELD_TO_GUARD_CHANGING_THAT_FIELD" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="ML_SYNC_ON_UPDATED_FIELD" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="MSF_MUTABLE_SERVLET_FIELD" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="MS_CANNOT_BE_FINAL" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="MS_EXPOSE_REP" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="MS_FINAL_PKGPROTECT" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="MS_MUTABLE_ARRAY" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="MS_MUTABLE_COLLECTION" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="MS_MUTABLE_COLLECTION_PKGPROTECT" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="MS_MUTABLE_HASHTABLE" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="MS_OOI_PKGPROTECT" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="MS_PKGPROTECT" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="MS_SHOULD_BE_FINAL" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="MS_SHOULD_BE_REFACTORED_TO_BE_FINAL" level="CRITICAL" enabled="false" />
|
||||
<coding_rule class="MTIA_SUSPECT_SERVLET_INSTANCE_FIELD" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="MTIA_SUSPECT_STRUTS_INSTANCE_FIELD" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="MWN_MISMATCHED_NOTIFY" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="MWN_MISMATCHED_WAIT" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="NM_BAD_EQUAL" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="NM_CLASS_NAMING_CONVENTION" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="NM_CLASS_NOT_EXCEPTION" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="NM_CONFUSING" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="NM_FIELD_NAMING_CONVENTION" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="NM_FUTURE_KEYWORD_USED_AS_IDENTIFIER" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="NM_FUTURE_KEYWORD_USED_AS_MEMBER_IDENTIFIER" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="NM_LCASE_HASHCODE" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="NM_LCASE_TOSTRING" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="NM_METHOD_CONSTRUCTOR_CONFUSION" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="NM_METHOD_NAMING_CONVENTION" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="NM_SAME_SIMPLE_NAME_AS_INTERFACE" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="NM_SAME_SIMPLE_NAME_AS_SUPERCLASS" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="NM_VERY_CONFUSING" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="NM_VERY_CONFUSING_INTENTIONAL" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="NM_WRONG_PACKAGE" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="NM_WRONG_PACKAGE_INTENTIONAL" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="NN_NAKED_NOTIFY" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="NOISE_FIELD_REFERENCE" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="NOISE_METHOD_CALL" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="NOISE_NULL_DEREFERENCE" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="NOISE_OPERATION" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="NO_NOTIFY_NOT_NOTIFYALL" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="NP_ALWAYS_NULL" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="NP_ALWAYS_NULL_EXCEPTION" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="NP_ARGUMENT_MIGHT_BE_NULL" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="NP_BOOLEAN_RETURN_NULL" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="NP_CLONE_COULD_RETURN_NULL" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="NP_CLOSING_NULL" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="NP_DEREFERENCE_OF_READLINE_VALUE" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="NP_EQUALS_SHOULD_HANDLE_NULL_ARGUMENT" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="NP_GUARANTEED_DEREF" level="BLOCKER" enabled="true" />
|
||||
<coding_rule class="NP_GUARANTEED_DEREF_ON_EXCEPTION_PATH" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="NP_IMMEDIATE_DEREFERENCE_OF_READLINE" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="NP_LOAD_OF_KNOWN_NULL_VALUE" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="NP_METHOD_PARAMETER_TIGHTENS_ANNOTATION" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="NP_METHOD_RETURN_RELAXING_ANNOTATION" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="NP_NONNULL_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR" level="CRITICAL" enabled="false" />
|
||||
<coding_rule class="NP_NONNULL_PARAM_VIOLATION" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="NP_NONNULL_RETURN_VIOLATION" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="NP_NULL_INSTANCEOF" level="BLOCKER" enabled="true" />
|
||||
<coding_rule class="NP_NULL_ON_SOME_PATH" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="NP_NULL_ON_SOME_PATH_EXCEPTION" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="NP_NULL_ON_SOME_PATH_MIGHT_BE_INFEASIBLE" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="NP_NULL_PARAM_DEREF" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="NP_NULL_PARAM_DEREF_ALL_TARGETS_DANGEROUS" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="NP_NULL_PARAM_DEREF_NONVIRTUAL" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="NP_OPTIONAL_RETURN_NULL" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="NP_PARAMETER_MUST_BE_NONNULL_BUT_MARKED_AS_NULLABLE" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="NP_STORE_INTO_NONNULL_FIELD" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="NP_SYNC_AND_NULL_CHECK_FIELD" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="NP_TOSTRING_COULD_RETURN_NULL" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="NP_UNWRITTEN_FIELD" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="NP_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD" level="CRITICAL" enabled="false" />
|
||||
<coding_rule class="NS_DANGEROUS_NON_SHORT_CIRCUIT" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="NS_NON_SHORT_CIRCUIT" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="OBL_UNSATISFIED_OBLIGATION" level="CRITICAL" enabled="false" />
|
||||
<coding_rule class="OBL_UNSATISFIED_OBLIGATION_EXCEPTION_EDGE" level="CRITICAL" enabled="false" />
|
||||
<coding_rule class="ODR_OPEN_DATABASE_RESOURCE" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="ODR_OPEN_DATABASE_RESOURCE_EXCEPTION_PATH" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="OS_OPEN_STREAM" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="OS_OPEN_STREAM_EXCEPTION_PATH" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="PS_PUBLIC_SEMAPHORES" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="PT_ABSOLUTE_PATH_TRAVERSAL" level="CRITICAL" enabled="false" />
|
||||
<coding_rule class="PT_RELATIVE_PATH_TRAVERSAL" level="CRITICAL" enabled="false" />
|
||||
<coding_rule class="PZLA_PREFER_ZERO_LENGTH_ARRAYS" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="PZ_DONT_REUSE_ENTRY_OBJECTS_IN_ITERATORS" level="CRITICAL" enabled="false" />
|
||||
<coding_rule class="QBA_QUESTIONABLE_BOOLEAN_ASSIGNMENT" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="QF_QUESTIONABLE_FOR_LOOP" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="RANGE_ARRAY_INDEX" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="RANGE_ARRAY_LENGTH" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="RANGE_ARRAY_OFFSET" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="RANGE_STRING_INDEX" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="RCN_REDUNDANT_COMPARISON_OF_NULL_AND_NONNULL_VALUE" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="RCN_REDUNDANT_COMPARISON_TWO_NULL_VALUES" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="RCN_REDUNDANT_NULLCHECK_WOULD_HAVE_BEEN_A_NPE" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="RC_REF_COMPARISON" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="RC_REF_COMPARISON_BAD_PRACTICE" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="RC_REF_COMPARISON_BAD_PRACTICE_BOOLEAN" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="REC_CATCH_EXCEPTION" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="RE_BAD_SYNTAX_FOR_REGULAR_EXPRESSION" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="RE_CANT_USE_FILE_SEPARATOR_AS_REGULAR_EXPRESSION" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="RE_POSSIBLE_UNINTENDED_PATTERN" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="RI_REDUNDANT_INTERFACES" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="RR_NOT_CHECKED" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="RS_READOBJECT_SYNC" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="RU_INVOKE_RUN" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="RV_01_TO_INT" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="RV_ABSOLUTE_VALUE_OF_HASHCODE" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="RV_ABSOLUTE_VALUE_OF_RANDOM_INT" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="RV_CHECK_COMPARETO_FOR_SPECIFIC_RETURN_VALUE" level="CRITICAL" enabled="false" />
|
||||
<coding_rule class="RV_CHECK_FOR_POSITIVE_INDEXOF" level="MINOR" enabled="true" />
|
||||
<coding_rule class="RV_DONT_JUST_NULL_CHECK_READLINE" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="RV_EXCEPTION_NOT_THROWN" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="RV_NEGATING_RESULT_OF_COMPARETO" level="CRITICAL" enabled="false" />
|
||||
<coding_rule class="RV_REM_OF_HASHCODE" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="RV_REM_OF_RANDOM_INT" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="RV_RETURN_VALUE_IGNORED" level="MINOR" enabled="true" />
|
||||
<coding_rule class="RV_RETURN_VALUE_IGNORED_BAD_PRACTICE" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="RV_RETURN_VALUE_IGNORED_INFERRED" level="CRITICAL" enabled="false" />
|
||||
<coding_rule class="RV_RETURN_VALUE_IGNORED_NO_SIDE_EFFECT" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="RV_RETURN_VALUE_OF_PUTIFABSENT_IGNORED" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="RpC_REPEATED_CONDITIONAL_TEST" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="SA_FIELD_DOUBLE_ASSIGNMENT" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="SA_FIELD_SELF_ASSIGNMENT" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="SA_FIELD_SELF_COMPARISON" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="SA_FIELD_SELF_COMPUTATION" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="SA_LOCAL_DOUBLE_ASSIGNMENT" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="SA_LOCAL_SELF_ASSIGNMENT" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="SA_LOCAL_SELF_ASSIGNMENT_INSTEAD_OF_FIELD" level="CRITICAL" enabled="false" />
|
||||
<coding_rule class="SA_LOCAL_SELF_COMPARISON" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="SA_LOCAL_SELF_COMPUTATION" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="SBSC_USE_STRINGBUFFER_CONCATENATION" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="SC_START_IN_CTOR" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="SE_BAD_FIELD" level="MINOR" enabled="false" />
|
||||
<coding_rule class="SE_BAD_FIELD_INNER_CLASS" level="MINOR" enabled="true" />
|
||||
<coding_rule class="SE_BAD_FIELD_STORE" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="SE_COMPARATOR_SHOULD_BE_SERIALIZABLE" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="SE_INNER_CLASS" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="SE_METHOD_MUST_BE_PRIVATE" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="SE_NONFINAL_SERIALVERSIONID" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="SE_NONLONG_SERIALVERSIONID" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="SE_NONSTATIC_SERIALVERSIONID" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="SE_NO_SERIALVERSIONID" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="SE_NO_SUITABLE_CONSTRUCTOR" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="SE_NO_SUITABLE_CONSTRUCTOR_FOR_EXTERNALIZATION" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="SE_PRIVATE_READ_RESOLVE_NOT_INHERITED" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="SE_READ_RESOLVE_IS_STATIC" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="SE_READ_RESOLVE_MUST_RETURN_OBJECT" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="SE_TRANSIENT_FIELD_NOT_RESTORED" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="SE_TRANSIENT_FIELD_OF_NONSERIALIZABLE_CLASS" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="SF_DEAD_STORE_DUE_TO_SWITCH_FALLTHROUGH" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="SF_DEAD_STORE_DUE_TO_SWITCH_FALLTHROUGH_TO_THROW" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="SF_SWITCH_FALLTHROUGH" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="SF_SWITCH_NO_DEFAULT" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="SIC_INNER_SHOULD_BE_STATIC" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="SIC_INNER_SHOULD_BE_STATIC_ANON" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="SIC_INNER_SHOULD_BE_STATIC_NEEDS_THIS" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="SIC_THREADLOCAL_DEADLY_EMBRACE" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="SIO_SUPERFLUOUS_INSTANCEOF" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="SI_INSTANCE_BEFORE_FINALS_ASSIGNED" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="SP_SPIN_ON_FIELD" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="SQL_BAD_PREPARED_STATEMENT_ACCESS" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="SQL_BAD_RESULTSET_ACCESS" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="SQL_NONCONSTANT_STRING_PASSED_TO_EXECUTE" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="SQL_PREPARED_STATEMENT_GENERATED_FROM_NONCONSTANT_STRING" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="SR_NOT_CHECKED" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="SS_SHOULD_BE_STATIC" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="STCAL_INVOKE_ON_STATIC_CALENDAR_INSTANCE" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="STCAL_INVOKE_ON_STATIC_DATE_FORMAT_INSTANCE" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="STCAL_STATIC_CALENDAR_INSTANCE" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="STCAL_STATIC_SIMPLE_DATE_FORMAT_INSTANCE" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="STI_INTERRUPTED_ON_CURRENTTHREAD" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="STI_INTERRUPTED_ON_UNKNOWNTHREAD" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="SWL_SLEEP_WITH_LOCK_HELD" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="SW_SWING_METHODS_INVOKED_IN_SWING_THREAD" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="TLW_TWO_LOCK_WAIT" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="TQ_ALWAYS_VALUE_USED_WHERE_NEVER_REQUIRED" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="TQ_COMPARING_VALUES_WITH_INCOMPATIBLE_TYPE_QUALIFIERS" level="CRITICAL" enabled="false" />
|
||||
<coding_rule class="TQ_EXPLICIT_UNKNOWN_SOURCE_VALUE_REACHES_ALWAYS_SINK" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="TQ_EXPLICIT_UNKNOWN_SOURCE_VALUE_REACHES_NEVER_SINK" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="TQ_MAYBE_SOURCE_VALUE_REACHES_ALWAYS_SINK" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="TQ_MAYBE_SOURCE_VALUE_REACHES_NEVER_SINK" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="TQ_NEVER_VALUE_USED_WHERE_ALWAYS_REQUIRED" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="TQ_UNKNOWN_VALUE_USED_WHERE_ALWAYS_STRICTLY_REQUIRED" level="CRITICAL" enabled="false" />
|
||||
<coding_rule class="UCF_USELESS_CONTROL_FLOW" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="UCF_USELESS_CONTROL_FLOW_NEXT_LINE" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="UC_USELESS_CONDITION" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="UC_USELESS_CONDITION_TYPE" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="UC_USELESS_OBJECT" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="UC_USELESS_OBJECT_STACK" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="UC_USELESS_VOID_METHOD" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="UG_SYNC_SET_UNSYNC_GET" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="UI_INHERITANCE_UNSAFE_GETRESOURCE" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="UL_UNRELEASED_LOCK" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="UL_UNRELEASED_LOCK_EXCEPTION_PATH" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="UMAC_UNCALLABLE_METHOD_OF_ANONYMOUS_CLASS" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="UM_UNNECESSARY_MATH" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="UPM_UNCALLED_PRIVATE_METHOD" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="URF_UNREAD_FIELD" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD" level="CRITICAL" enabled="false" />
|
||||
<coding_rule class="UR_UNINIT_READ" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="UR_UNINIT_READ_CALLED_FROM_SUPER_CONSTRUCTOR" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="USM_USELESS_ABSTRACT_METHOD" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="USM_USELESS_SUBCLASS_METHOD" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="UUF_UNUSED_FIELD" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="UUF_UNUSED_PUBLIC_OR_PROTECTED_FIELD" level="CRITICAL" enabled="false" />
|
||||
<coding_rule class="UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="UWF_NULL_FIELD" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="UWF_UNWRITTEN_FIELD" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD" level="CRITICAL" enabled="false" />
|
||||
<coding_rule class="UW_UNCOND_WAIT" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="VA_FORMAT_STRING_BAD_ARGUMENT" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="VA_FORMAT_STRING_BAD_CONVERSION" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="VA_FORMAT_STRING_BAD_CONVERSION_FROM_ARRAY" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="VA_FORMAT_STRING_BAD_CONVERSION_TO_BOOLEAN" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="VA_FORMAT_STRING_EXPECTED_MESSAGE_FORMAT_SUPPLIED" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="VA_FORMAT_STRING_EXTRA_ARGUMENTS_PASSED" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="VA_FORMAT_STRING_ILLEGAL" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="VA_FORMAT_STRING_MISSING_ARGUMENT" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="VA_FORMAT_STRING_NO_PREVIOUS_ARGUMENT" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="VA_FORMAT_STRING_USES_NEWLINE" level="CRITICAL" enabled="false" />
|
||||
<coding_rule class="VA_PRIMITIVE_ARRAY_PASSED_TO_OBJECT_VARARG" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="VO_VOLATILE_INCREMENT" level="CRITICAL" enabled="false" />
|
||||
<coding_rule class="VO_VOLATILE_REFERENCE_TO_ARRAY" level="MAJOR" enabled="true" />
|
||||
<coding_rule class="VR_UNRESOLVABLE_REFERENCE" level="MAJOR" enabled="false" />
|
||||
<coding_rule class="WA_AWAIT_NOT_IN_LOOP" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="WA_NOT_IN_LOOP" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="WL_USING_GETCLASS_RATHER_THAN_CLASS_LITERAL" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="WMI_WRONG_MAP_ITERATOR" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="WS_WRITEOBJECT_SYNC" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="XFB_XML_FACTORY_BYPASS" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="XSS_REQUEST_PARAMETER_TO_JSP_WRITER" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="XSS_REQUEST_PARAMETER_TO_SEND_ERROR" level="CRITICAL" enabled="true" />
|
||||
<coding_rule class="XSS_REQUEST_PARAMETER_TO_SERVLET_WRITER" level="CRITICAL" enabled="true" />
|
||||
</profile>
|
||||
</profiles>
|
||||
<list size="0" />
|
||||
</component>
|
||||
</project>
|
||||
10
models-integration/.idea/runConfigurations.xml
generated
Normal file
10
models-integration/.idea/runConfigurations.xml
generated
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RunConfigurationProducerService">
|
||||
<option name="ignoredProducers">
|
||||
<set>
|
||||
<option value="com.android.tools.idea.compose.preview.runconfiguration.ComposePreviewRunConfigurationProducer" />
|
||||
</set>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
124
models-integration/.idea/uiDesigner.xml
generated
Normal file
124
models-integration/.idea/uiDesigner.xml
generated
Normal file
@@ -0,0 +1,124 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Palette2">
|
||||
<group name="Swing">
|
||||
<item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||
<default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
|
||||
</item>
|
||||
<item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||
<default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
|
||||
</item>
|
||||
<item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
|
||||
</item>
|
||||
<item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.svg" removable="false" auto-create-binding="false" can-attach-label="true">
|
||||
<default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
|
||||
</item>
|
||||
<item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
|
||||
<initial-values>
|
||||
<property name="text" value="Button" />
|
||||
</initial-values>
|
||||
</item>
|
||||
<item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
|
||||
<initial-values>
|
||||
<property name="text" value="RadioButton" />
|
||||
</initial-values>
|
||||
</item>
|
||||
<item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
|
||||
<initial-values>
|
||||
<property name="text" value="CheckBox" />
|
||||
</initial-values>
|
||||
</item>
|
||||
<item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||
<default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
|
||||
<initial-values>
|
||||
<property name="text" value="Label" />
|
||||
</initial-values>
|
||||
</item>
|
||||
<item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
|
||||
<preferred-size width="150" height="-1" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
|
||||
<preferred-size width="150" height="-1" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
|
||||
<preferred-size width="150" height="-1" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
||||
<preferred-size width="150" height="50" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
||||
<preferred-size width="150" height="50" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
||||
<preferred-size width="150" height="50" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||
<default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
|
||||
</item>
|
||||
<item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
||||
<preferred-size width="150" height="50" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
|
||||
<preferred-size width="150" height="50" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
||||
<preferred-size width="150" height="50" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
|
||||
<preferred-size width="200" height="200" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
|
||||
<preferred-size width="200" height="200" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
|
||||
</item>
|
||||
<item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
|
||||
</item>
|
||||
<item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />
|
||||
</item>
|
||||
<item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />
|
||||
</item>
|
||||
<item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">
|
||||
<preferred-size width="-1" height="20" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||
<default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />
|
||||
</item>
|
||||
<item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />
|
||||
</item>
|
||||
</group>
|
||||
</component>
|
||||
</project>
|
||||
66
models-integration/.trae/rules/project_rules.md
Normal file
66
models-integration/.trae/rules/project_rules.md
Normal file
@@ -0,0 +1,66 @@
|
||||
# 项目对话规则
|
||||
|
||||
## 1. 项目上下文约束
|
||||
|
||||
- 所有对话必须围绕当前销帮帮Java API集成项目展开,不回应无关技术或业务问题
|
||||
- 始终默认使用销帮帮API最新稳定版本(当前为v2),如需使用旧版本需明确标注
|
||||
- 涉及代码实现时,必须遵循项目使用的Java版本(JDK 8+)及编码规范
|
||||
|
||||
## 2. 对话交互规范
|
||||
|
||||
- 对用户需求先进行业务场景识别,再提供技术实现方案,避免直接跳跃到代码层面
|
||||
- 当用户需求不明确时,按以下优先级提问澄清:
|
||||
1. 具体业务操作目标(如"是需要查询客户还是创建客户?")
|
||||
2. 必要参数信息(如"请提供客户的所属部门ID")
|
||||
3. 特殊处理要求(如"是否需要同步更新关联的商机信息?")
|
||||
- 技术方案回复需包含:业务对应API映射、核心参数说明、调用注意事项三部分
|
||||
|
||||
## 3. API调用规范
|
||||
|
||||
- 所有API调用示例必须包含完整请求结构:
|
||||
- 请求头(含Authorization、Content-Type)
|
||||
- 请求方法与路径
|
||||
- 请求体(JSON格式,标注必填字段)
|
||||
- 涉及认证相关操作时,必须提醒用户:
|
||||
- token的有效期(默认2小时)
|
||||
- 权限范围限制
|
||||
- 安全存储要求
|
||||
- 批量操作API调用必须提示分页处理建议,避免单次请求数据量过大
|
||||
|
||||
## 4. 代码实现规范
|
||||
|
||||
- 提供的Java代码必须:
|
||||
- 包含必要的异常处理
|
||||
- 使用项目指定的HTTP客户端(RestTemplate)
|
||||
- 遵循阿里巴巴Java开发手册规范
|
||||
- 包含关键业务注释(无需注释基础语法)
|
||||
- 涉及实体类定义时,需使用Lombok注解简化代码
|
||||
- 工具类调用需优先使用项目已封装的销帮帮API工具包
|
||||
|
||||
## 5. 数据安全规范
|
||||
|
||||
- 对话中禁止出现真实的敏感数据(如客户手机号、身份证号等)
|
||||
- 示例数据需使用明显的测试标识(如手机号使用13800000000)
|
||||
- 涉及数据权限问题时,必须提示用户检查当前账号的角色权限配置
|
||||
|
||||
## 6. 错误处理规范
|
||||
|
||||
- 收到API错误响应时,需按以下格式回复:
|
||||
1. 错误码与描述(直接引用销帮帮API文档)
|
||||
2. 可能的原因分析(列出2-3个最可能的原因)
|
||||
3. 具体解决方案(含操作步骤)
|
||||
- 对于未明确的错误,需建议用户提供完整的请求日志以便进一步分析
|
||||
|
||||
## 7. 版本兼容说明
|
||||
|
||||
- 当API版本存在差异时,需明确标注不同版本的区别:
|
||||
- 字段变更(新增/移除/重命名)
|
||||
- 路径变更
|
||||
- 功能逻辑调整
|
||||
- 推荐使用版本兼容的实现方式,避免锁定特定版本
|
||||
|
||||
## 8. 文档引用规范
|
||||
|
||||
- 涉及API细节时,需指明参考销帮帮开放平台文档的具体章节
|
||||
- 重要概念需提供官方定义,再补充项目实践说明
|
||||
- 第三方工具使用需参考其最新官方文档
|
||||
4
models-integration/.vscode/settings.json
vendored
Normal file
4
models-integration/.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"java.compile.nullAnalysis.mode": "automatic",
|
||||
"java.configuration.updateBuildConfiguration": "interactive"
|
||||
}
|
||||
BIN
models-integration/coze_oauth_java_jwt/.DS_Store
vendored
Normal file
BIN
models-integration/coze_oauth_java_jwt/.DS_Store
vendored
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
||||
›ssmgcqtsrbfxtc7jwtlnpxpwj4!Å]ürì”7§Kc©<.[‚ˆclasses·¿Â©Õ'î<>R36ó ‡4ˆsourcesr~öûqrRæÝÔ¤ƒÃ>
|
||||
@@ -0,0 +1,361 @@
|
||||
package org.gradle.accessors.dm;
|
||||
|
||||
import org.gradle.api.NonNullApi;
|
||||
import org.gradle.api.artifacts.MinimalExternalModuleDependency;
|
||||
import org.gradle.plugin.use.PluginDependency;
|
||||
import org.gradle.api.artifacts.ExternalModuleDependencyBundle;
|
||||
import org.gradle.api.artifacts.MutableVersionConstraint;
|
||||
import org.gradle.api.provider.Provider;
|
||||
import org.gradle.api.model.ObjectFactory;
|
||||
import org.gradle.api.provider.ProviderFactory;
|
||||
import org.gradle.api.internal.catalog.AbstractExternalDependencyFactory;
|
||||
import org.gradle.api.internal.catalog.DefaultVersionCatalog;
|
||||
import java.util.Map;
|
||||
import org.gradle.api.internal.attributes.ImmutableAttributesFactory;
|
||||
import org.gradle.api.internal.artifacts.dsl.CapabilityNotationParser;
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* A catalog of dependencies accessible via the {@code libs} extension.
|
||||
*/
|
||||
@NonNullApi
|
||||
public class LibrariesForLibs extends AbstractExternalDependencyFactory {
|
||||
|
||||
private final AbstractExternalDependencyFactory owner = this;
|
||||
private final CozeLibraryAccessors laccForCozeLibraryAccessors = new CozeLibraryAccessors(owner);
|
||||
private final JacksonLibraryAccessors laccForJacksonLibraryAccessors = new JacksonLibraryAccessors(owner);
|
||||
private final JunitLibraryAccessors laccForJunitLibraryAccessors = new JunitLibraryAccessors(owner);
|
||||
private final SpringLibraryAccessors laccForSpringLibraryAccessors = new SpringLibraryAccessors(owner);
|
||||
private final VersionAccessors vaccForVersionAccessors = new VersionAccessors(providers, config);
|
||||
private final BundleAccessors baccForBundleAccessors = new BundleAccessors(objects, providers, config, attributesFactory, capabilityNotationParser);
|
||||
private final PluginAccessors paccForPluginAccessors = new PluginAccessors(providers, config);
|
||||
|
||||
@Inject
|
||||
public LibrariesForLibs(DefaultVersionCatalog config, ProviderFactory providers, ObjectFactory objects, ImmutableAttributesFactory attributesFactory, CapabilityNotationParser capabilityNotationParser) {
|
||||
super(config, providers, objects, attributesFactory, capabilityNotationParser);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dependency provider for <b>lombok</b> with <b>org.projectlombok:lombok</b> coordinates and
|
||||
* with <b>no version specified</b>
|
||||
* <p>
|
||||
* This dependency was declared in catalog libs.versions.toml
|
||||
*/
|
||||
public Provider<MinimalExternalModuleDependency> getLombok() {
|
||||
return create("lombok");
|
||||
}
|
||||
|
||||
/**
|
||||
* Dependency provider for <b>snakeyaml</b> with <b>org.yaml:snakeyaml</b> coordinates and
|
||||
* with version reference <b>snakeyaml</b>
|
||||
* <p>
|
||||
* This dependency was declared in catalog libs.versions.toml
|
||||
*/
|
||||
public Provider<MinimalExternalModuleDependency> getSnakeyaml() {
|
||||
return create("snakeyaml");
|
||||
}
|
||||
|
||||
/**
|
||||
* Group of libraries at <b>coze</b>
|
||||
*/
|
||||
public CozeLibraryAccessors getCoze() {
|
||||
return laccForCozeLibraryAccessors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Group of libraries at <b>jackson</b>
|
||||
*/
|
||||
public JacksonLibraryAccessors getJackson() {
|
||||
return laccForJacksonLibraryAccessors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Group of libraries at <b>junit</b>
|
||||
*/
|
||||
public JunitLibraryAccessors getJunit() {
|
||||
return laccForJunitLibraryAccessors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Group of libraries at <b>spring</b>
|
||||
*/
|
||||
public SpringLibraryAccessors getSpring() {
|
||||
return laccForSpringLibraryAccessors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Group of versions at <b>versions</b>
|
||||
*/
|
||||
public VersionAccessors getVersions() {
|
||||
return vaccForVersionAccessors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Group of bundles at <b>bundles</b>
|
||||
*/
|
||||
public BundleAccessors getBundles() {
|
||||
return baccForBundleAccessors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Group of plugins at <b>plugins</b>
|
||||
*/
|
||||
public PluginAccessors getPlugins() {
|
||||
return paccForPluginAccessors;
|
||||
}
|
||||
|
||||
public static class CozeLibraryAccessors extends SubDependencyFactory {
|
||||
|
||||
public CozeLibraryAccessors(AbstractExternalDependencyFactory owner) { super(owner); }
|
||||
|
||||
/**
|
||||
* Dependency provider for <b>api</b> with <b>com.coze:coze-api</b> coordinates and
|
||||
* with version reference <b>coze</b>
|
||||
* <p>
|
||||
* This dependency was declared in catalog libs.versions.toml
|
||||
*/
|
||||
public Provider<MinimalExternalModuleDependency> getApi() {
|
||||
return create("coze.api");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class JacksonLibraryAccessors extends SubDependencyFactory {
|
||||
|
||||
public JacksonLibraryAccessors(AbstractExternalDependencyFactory owner) { super(owner); }
|
||||
|
||||
/**
|
||||
* Dependency provider for <b>databind</b> with <b>com.fasterxml.jackson.core:jackson-databind</b> coordinates and
|
||||
* with <b>no version specified</b>
|
||||
* <p>
|
||||
* This dependency was declared in catalog libs.versions.toml
|
||||
*/
|
||||
public Provider<MinimalExternalModuleDependency> getDatabind() {
|
||||
return create("jackson.databind");
|
||||
}
|
||||
|
||||
/**
|
||||
* Dependency provider for <b>yaml</b> with <b>com.fasterxml.jackson.dataformat:jackson-dataformat-yaml</b> coordinates and
|
||||
* with <b>no version specified</b>
|
||||
* <p>
|
||||
* This dependency was declared in catalog libs.versions.toml
|
||||
*/
|
||||
public Provider<MinimalExternalModuleDependency> getYaml() {
|
||||
return create("jackson.yaml");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class JunitLibraryAccessors extends SubDependencyFactory {
|
||||
|
||||
public JunitLibraryAccessors(AbstractExternalDependencyFactory owner) { super(owner); }
|
||||
|
||||
/**
|
||||
* Dependency provider for <b>bom</b> with <b>org.junit:junit-bom</b> coordinates and
|
||||
* with version reference <b>junit</b>
|
||||
* <p>
|
||||
* This dependency was declared in catalog libs.versions.toml
|
||||
*/
|
||||
public Provider<MinimalExternalModuleDependency> getBom() {
|
||||
return create("junit.bom");
|
||||
}
|
||||
|
||||
/**
|
||||
* Dependency provider for <b>jupiter</b> with <b>org.junit.jupiter:junit-jupiter</b> coordinates and
|
||||
* with <b>no version specified</b>
|
||||
* <p>
|
||||
* This dependency was declared in catalog libs.versions.toml
|
||||
*/
|
||||
public Provider<MinimalExternalModuleDependency> getJupiter() {
|
||||
return create("junit.jupiter");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class SpringLibraryAccessors extends SubDependencyFactory {
|
||||
private final SpringBootLibraryAccessors laccForSpringBootLibraryAccessors = new SpringBootLibraryAccessors(owner);
|
||||
|
||||
public SpringLibraryAccessors(AbstractExternalDependencyFactory owner) { super(owner); }
|
||||
|
||||
/**
|
||||
* Group of libraries at <b>spring.boot</b>
|
||||
*/
|
||||
public SpringBootLibraryAccessors getBoot() {
|
||||
return laccForSpringBootLibraryAccessors;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class SpringBootLibraryAccessors extends SubDependencyFactory {
|
||||
private final SpringBootStarterLibraryAccessors laccForSpringBootStarterLibraryAccessors = new SpringBootStarterLibraryAccessors(owner);
|
||||
|
||||
public SpringBootLibraryAccessors(AbstractExternalDependencyFactory owner) { super(owner); }
|
||||
|
||||
/**
|
||||
* Dependency provider for <b>configuration</b> with <b>org.springframework.boot:spring-boot-configuration-processor</b> coordinates and
|
||||
* with <b>no version specified</b>
|
||||
* <p>
|
||||
* This dependency was declared in catalog libs.versions.toml
|
||||
*/
|
||||
public Provider<MinimalExternalModuleDependency> getConfiguration() {
|
||||
return create("spring.boot.configuration");
|
||||
}
|
||||
|
||||
/**
|
||||
* Dependency provider for <b>test</b> with <b>org.springframework.boot:spring-boot-starter-test</b> coordinates and
|
||||
* with <b>no version specified</b>
|
||||
* <p>
|
||||
* This dependency was declared in catalog libs.versions.toml
|
||||
*/
|
||||
public Provider<MinimalExternalModuleDependency> getTest() {
|
||||
return create("spring.boot.test");
|
||||
}
|
||||
|
||||
/**
|
||||
* Group of libraries at <b>spring.boot.starter</b>
|
||||
*/
|
||||
public SpringBootStarterLibraryAccessors getStarter() {
|
||||
return laccForSpringBootStarterLibraryAccessors;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class SpringBootStarterLibraryAccessors extends SubDependencyFactory {
|
||||
|
||||
public SpringBootStarterLibraryAccessors(AbstractExternalDependencyFactory owner) { super(owner); }
|
||||
|
||||
/**
|
||||
* Dependency provider for <b>actuator</b> with <b>org.springframework.boot:spring-boot-starter-actuator</b> coordinates and
|
||||
* with <b>no version specified</b>
|
||||
* <p>
|
||||
* This dependency was declared in catalog libs.versions.toml
|
||||
*/
|
||||
public Provider<MinimalExternalModuleDependency> getActuator() {
|
||||
return create("spring.boot.starter.actuator");
|
||||
}
|
||||
|
||||
/**
|
||||
* Dependency provider for <b>web</b> with <b>org.springframework.boot:spring-boot-starter-web</b> coordinates and
|
||||
* with <b>no version specified</b>
|
||||
* <p>
|
||||
* This dependency was declared in catalog libs.versions.toml
|
||||
*/
|
||||
public Provider<MinimalExternalModuleDependency> getWeb() {
|
||||
return create("spring.boot.starter.web");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class VersionAccessors extends VersionFactory {
|
||||
|
||||
private final SpringVersionAccessors vaccForSpringVersionAccessors = new SpringVersionAccessors(providers, config);
|
||||
public VersionAccessors(ProviderFactory providers, DefaultVersionCatalog config) { super(providers, config); }
|
||||
|
||||
/**
|
||||
* Version alias <b>coze</b> with value <b>0.2.0</b>
|
||||
* <p>
|
||||
* If the version is a rich version and cannot be represented as a
|
||||
* single version string, an empty string is returned.
|
||||
* <p>
|
||||
* This version was declared in catalog libs.versions.toml
|
||||
*/
|
||||
public Provider<String> getCoze() { return getVersion("coze"); }
|
||||
|
||||
/**
|
||||
* Version alias <b>junit</b> with value <b>5.10.0</b>
|
||||
* <p>
|
||||
* If the version is a rich version and cannot be represented as a
|
||||
* single version string, an empty string is returned.
|
||||
* <p>
|
||||
* This version was declared in catalog libs.versions.toml
|
||||
*/
|
||||
public Provider<String> getJunit() { return getVersion("junit"); }
|
||||
|
||||
/**
|
||||
* Version alias <b>snakeyaml</b> with value <b>2.0</b>
|
||||
* <p>
|
||||
* If the version is a rich version and cannot be represented as a
|
||||
* single version string, an empty string is returned.
|
||||
* <p>
|
||||
* This version was declared in catalog libs.versions.toml
|
||||
*/
|
||||
public Provider<String> getSnakeyaml() { return getVersion("snakeyaml"); }
|
||||
|
||||
/**
|
||||
* Group of versions at <b>versions.spring</b>
|
||||
*/
|
||||
public SpringVersionAccessors getSpring() {
|
||||
return vaccForSpringVersionAccessors;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class SpringVersionAccessors extends VersionFactory {
|
||||
|
||||
public SpringVersionAccessors(ProviderFactory providers, DefaultVersionCatalog config) { super(providers, config); }
|
||||
|
||||
/**
|
||||
* Version alias <b>spring.boot</b> with value <b>3.2.3</b>
|
||||
* <p>
|
||||
* If the version is a rich version and cannot be represented as a
|
||||
* single version string, an empty string is returned.
|
||||
* <p>
|
||||
* This version was declared in catalog libs.versions.toml
|
||||
*/
|
||||
public Provider<String> getBoot() { return getVersion("spring.boot"); }
|
||||
|
||||
/**
|
||||
* Version alias <b>spring.dependency</b> with value <b>1.1.4</b>
|
||||
* <p>
|
||||
* If the version is a rich version and cannot be represented as a
|
||||
* single version string, an empty string is returned.
|
||||
* <p>
|
||||
* This version was declared in catalog libs.versions.toml
|
||||
*/
|
||||
public Provider<String> getDependency() { return getVersion("spring.dependency"); }
|
||||
|
||||
}
|
||||
|
||||
public static class BundleAccessors extends BundleFactory {
|
||||
|
||||
public BundleAccessors(ObjectFactory objects, ProviderFactory providers, DefaultVersionCatalog config, ImmutableAttributesFactory attributesFactory, CapabilityNotationParser capabilityNotationParser) { super(objects, providers, config, attributesFactory, capabilityNotationParser); }
|
||||
|
||||
}
|
||||
|
||||
public static class PluginAccessors extends PluginFactory {
|
||||
private final SpringPluginAccessors paccForSpringPluginAccessors = new SpringPluginAccessors(providers, config);
|
||||
|
||||
public PluginAccessors(ProviderFactory providers, DefaultVersionCatalog config) { super(providers, config); }
|
||||
|
||||
/**
|
||||
* Group of plugins at <b>plugins.spring</b>
|
||||
*/
|
||||
public SpringPluginAccessors getSpring() {
|
||||
return paccForSpringPluginAccessors;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class SpringPluginAccessors extends PluginFactory {
|
||||
|
||||
public SpringPluginAccessors(ProviderFactory providers, DefaultVersionCatalog config) { super(providers, config); }
|
||||
|
||||
/**
|
||||
* Plugin provider for <b>spring.boot</b> with plugin id <b>org.springframework.boot</b> and
|
||||
* with version reference <b>spring.boot</b>
|
||||
* <p>
|
||||
* This plugin was declared in catalog libs.versions.toml
|
||||
*/
|
||||
public Provider<PluginDependency> getBoot() { return createPlugin("spring.boot"); }
|
||||
|
||||
/**
|
||||
* Plugin provider for <b>spring.dependency</b> with plugin id <b>io.spring.dependency-management</b> and
|
||||
* with version reference <b>spring.dependency</b>
|
||||
* <p>
|
||||
* This plugin was declared in catalog libs.versions.toml
|
||||
*/
|
||||
public Provider<PluginDependency> getDependency() { return createPlugin("spring.dependency"); }
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,461 @@
|
||||
package org.gradle.accessors.dm;
|
||||
|
||||
import org.gradle.api.NonNullApi;
|
||||
import org.gradle.api.artifacts.MinimalExternalModuleDependency;
|
||||
import org.gradle.plugin.use.PluginDependency;
|
||||
import org.gradle.api.artifacts.ExternalModuleDependencyBundle;
|
||||
import org.gradle.api.artifacts.MutableVersionConstraint;
|
||||
import org.gradle.api.provider.Provider;
|
||||
import org.gradle.api.model.ObjectFactory;
|
||||
import org.gradle.api.provider.ProviderFactory;
|
||||
import org.gradle.api.internal.catalog.AbstractExternalDependencyFactory;
|
||||
import org.gradle.api.internal.catalog.DefaultVersionCatalog;
|
||||
import java.util.Map;
|
||||
import org.gradle.api.internal.attributes.ImmutableAttributesFactory;
|
||||
import org.gradle.api.internal.artifacts.dsl.CapabilityNotationParser;
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* A catalog of dependencies accessible via the {@code libs} extension.
|
||||
*/
|
||||
@NonNullApi
|
||||
public class LibrariesForLibsInPluginsBlock extends AbstractExternalDependencyFactory {
|
||||
|
||||
private final AbstractExternalDependencyFactory owner = this;
|
||||
private final CozeLibraryAccessors laccForCozeLibraryAccessors = new CozeLibraryAccessors(owner);
|
||||
private final JacksonLibraryAccessors laccForJacksonLibraryAccessors = new JacksonLibraryAccessors(owner);
|
||||
private final JunitLibraryAccessors laccForJunitLibraryAccessors = new JunitLibraryAccessors(owner);
|
||||
private final SpringLibraryAccessors laccForSpringLibraryAccessors = new SpringLibraryAccessors(owner);
|
||||
private final VersionAccessors vaccForVersionAccessors = new VersionAccessors(providers, config);
|
||||
private final BundleAccessors baccForBundleAccessors = new BundleAccessors(objects, providers, config, attributesFactory, capabilityNotationParser);
|
||||
private final PluginAccessors paccForPluginAccessors = new PluginAccessors(providers, config);
|
||||
|
||||
@Inject
|
||||
public LibrariesForLibsInPluginsBlock(DefaultVersionCatalog config, ProviderFactory providers, ObjectFactory objects, ImmutableAttributesFactory attributesFactory, CapabilityNotationParser capabilityNotationParser) {
|
||||
super(config, providers, objects, attributesFactory, capabilityNotationParser);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dependency provider for <b>lombok</b> with <b>org.projectlombok:lombok</b> coordinates and
|
||||
* with <b>no version specified</b>
|
||||
* <p>
|
||||
* This dependency was declared in catalog libs.versions.toml
|
||||
*
|
||||
* @deprecated Will be removed in Gradle 9.0.
|
||||
*/
|
||||
@Deprecated
|
||||
public Provider<MinimalExternalModuleDependency> getLombok() {
|
||||
org.gradle.internal.deprecation.DeprecationLogger.deprecateBehaviour("Accessing libraries or bundles from version catalogs in the plugins block.").withAdvice("Only use versions or plugins from catalogs in the plugins block.").willBeRemovedInGradle9().withUpgradeGuideSection(8, "kotlin_dsl_deprecated_catalogs_plugins_block").nagUser();
|
||||
return create("lombok");
|
||||
}
|
||||
|
||||
/**
|
||||
* Dependency provider for <b>snakeyaml</b> with <b>org.yaml:snakeyaml</b> coordinates and
|
||||
* with version reference <b>snakeyaml</b>
|
||||
* <p>
|
||||
* This dependency was declared in catalog libs.versions.toml
|
||||
*
|
||||
* @deprecated Will be removed in Gradle 9.0.
|
||||
*/
|
||||
@Deprecated
|
||||
public Provider<MinimalExternalModuleDependency> getSnakeyaml() {
|
||||
org.gradle.internal.deprecation.DeprecationLogger.deprecateBehaviour("Accessing libraries or bundles from version catalogs in the plugins block.").withAdvice("Only use versions or plugins from catalogs in the plugins block.").willBeRemovedInGradle9().withUpgradeGuideSection(8, "kotlin_dsl_deprecated_catalogs_plugins_block").nagUser();
|
||||
return create("snakeyaml");
|
||||
}
|
||||
|
||||
/**
|
||||
* Group of libraries at <b>coze</b>
|
||||
*
|
||||
* @deprecated Will be removed in Gradle 9.0.
|
||||
*/
|
||||
@Deprecated
|
||||
public CozeLibraryAccessors getCoze() {
|
||||
org.gradle.internal.deprecation.DeprecationLogger.deprecateBehaviour("Accessing libraries or bundles from version catalogs in the plugins block.").withAdvice("Only use versions or plugins from catalogs in the plugins block.").willBeRemovedInGradle9().withUpgradeGuideSection(8, "kotlin_dsl_deprecated_catalogs_plugins_block").nagUser();
|
||||
return laccForCozeLibraryAccessors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Group of libraries at <b>jackson</b>
|
||||
*
|
||||
* @deprecated Will be removed in Gradle 9.0.
|
||||
*/
|
||||
@Deprecated
|
||||
public JacksonLibraryAccessors getJackson() {
|
||||
org.gradle.internal.deprecation.DeprecationLogger.deprecateBehaviour("Accessing libraries or bundles from version catalogs in the plugins block.").withAdvice("Only use versions or plugins from catalogs in the plugins block.").willBeRemovedInGradle9().withUpgradeGuideSection(8, "kotlin_dsl_deprecated_catalogs_plugins_block").nagUser();
|
||||
return laccForJacksonLibraryAccessors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Group of libraries at <b>junit</b>
|
||||
*
|
||||
* @deprecated Will be removed in Gradle 9.0.
|
||||
*/
|
||||
@Deprecated
|
||||
public JunitLibraryAccessors getJunit() {
|
||||
org.gradle.internal.deprecation.DeprecationLogger.deprecateBehaviour("Accessing libraries or bundles from version catalogs in the plugins block.").withAdvice("Only use versions or plugins from catalogs in the plugins block.").willBeRemovedInGradle9().withUpgradeGuideSection(8, "kotlin_dsl_deprecated_catalogs_plugins_block").nagUser();
|
||||
return laccForJunitLibraryAccessors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Group of libraries at <b>spring</b>
|
||||
*
|
||||
* @deprecated Will be removed in Gradle 9.0.
|
||||
*/
|
||||
@Deprecated
|
||||
public SpringLibraryAccessors getSpring() {
|
||||
org.gradle.internal.deprecation.DeprecationLogger.deprecateBehaviour("Accessing libraries or bundles from version catalogs in the plugins block.").withAdvice("Only use versions or plugins from catalogs in the plugins block.").willBeRemovedInGradle9().withUpgradeGuideSection(8, "kotlin_dsl_deprecated_catalogs_plugins_block").nagUser();
|
||||
return laccForSpringLibraryAccessors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Group of versions at <b>versions</b>
|
||||
*/
|
||||
public VersionAccessors getVersions() {
|
||||
return vaccForVersionAccessors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Group of bundles at <b>bundles</b>
|
||||
*
|
||||
* @deprecated Will be removed in Gradle 9.0.
|
||||
*/
|
||||
@Deprecated
|
||||
public BundleAccessors getBundles() {
|
||||
org.gradle.internal.deprecation.DeprecationLogger.deprecateBehaviour("Accessing libraries or bundles from version catalogs in the plugins block.").withAdvice("Only use versions or plugins from catalogs in the plugins block.").willBeRemovedInGradle9().withUpgradeGuideSection(8, "kotlin_dsl_deprecated_catalogs_plugins_block").nagUser();
|
||||
return baccForBundleAccessors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Group of plugins at <b>plugins</b>
|
||||
*/
|
||||
public PluginAccessors getPlugins() {
|
||||
return paccForPluginAccessors;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Will be removed in Gradle 9.0.
|
||||
*/
|
||||
@Deprecated
|
||||
public static class CozeLibraryAccessors extends SubDependencyFactory {
|
||||
|
||||
public CozeLibraryAccessors(AbstractExternalDependencyFactory owner) { super(owner); }
|
||||
|
||||
/**
|
||||
* Dependency provider for <b>api</b> with <b>com.coze:coze-api</b> coordinates and
|
||||
* with version reference <b>coze</b>
|
||||
* <p>
|
||||
* This dependency was declared in catalog libs.versions.toml
|
||||
*
|
||||
* @deprecated Will be removed in Gradle 9.0.
|
||||
*/
|
||||
@Deprecated
|
||||
public Provider<MinimalExternalModuleDependency> getApi() {
|
||||
org.gradle.internal.deprecation.DeprecationLogger.deprecateBehaviour("Accessing libraries or bundles from version catalogs in the plugins block.").withAdvice("Only use versions or plugins from catalogs in the plugins block.").willBeRemovedInGradle9().withUpgradeGuideSection(8, "kotlin_dsl_deprecated_catalogs_plugins_block").nagUser();
|
||||
return create("coze.api");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Will be removed in Gradle 9.0.
|
||||
*/
|
||||
@Deprecated
|
||||
public static class JacksonLibraryAccessors extends SubDependencyFactory {
|
||||
|
||||
public JacksonLibraryAccessors(AbstractExternalDependencyFactory owner) { super(owner); }
|
||||
|
||||
/**
|
||||
* Dependency provider for <b>databind</b> with <b>com.fasterxml.jackson.core:jackson-databind</b> coordinates and
|
||||
* with <b>no version specified</b>
|
||||
* <p>
|
||||
* This dependency was declared in catalog libs.versions.toml
|
||||
*
|
||||
* @deprecated Will be removed in Gradle 9.0.
|
||||
*/
|
||||
@Deprecated
|
||||
public Provider<MinimalExternalModuleDependency> getDatabind() {
|
||||
org.gradle.internal.deprecation.DeprecationLogger.deprecateBehaviour("Accessing libraries or bundles from version catalogs in the plugins block.").withAdvice("Only use versions or plugins from catalogs in the plugins block.").willBeRemovedInGradle9().withUpgradeGuideSection(8, "kotlin_dsl_deprecated_catalogs_plugins_block").nagUser();
|
||||
return create("jackson.databind");
|
||||
}
|
||||
|
||||
/**
|
||||
* Dependency provider for <b>yaml</b> with <b>com.fasterxml.jackson.dataformat:jackson-dataformat-yaml</b> coordinates and
|
||||
* with <b>no version specified</b>
|
||||
* <p>
|
||||
* This dependency was declared in catalog libs.versions.toml
|
||||
*
|
||||
* @deprecated Will be removed in Gradle 9.0.
|
||||
*/
|
||||
@Deprecated
|
||||
public Provider<MinimalExternalModuleDependency> getYaml() {
|
||||
org.gradle.internal.deprecation.DeprecationLogger.deprecateBehaviour("Accessing libraries or bundles from version catalogs in the plugins block.").withAdvice("Only use versions or plugins from catalogs in the plugins block.").willBeRemovedInGradle9().withUpgradeGuideSection(8, "kotlin_dsl_deprecated_catalogs_plugins_block").nagUser();
|
||||
return create("jackson.yaml");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Will be removed in Gradle 9.0.
|
||||
*/
|
||||
@Deprecated
|
||||
public static class JunitLibraryAccessors extends SubDependencyFactory {
|
||||
|
||||
public JunitLibraryAccessors(AbstractExternalDependencyFactory owner) { super(owner); }
|
||||
|
||||
/**
|
||||
* Dependency provider for <b>bom</b> with <b>org.junit:junit-bom</b> coordinates and
|
||||
* with version reference <b>junit</b>
|
||||
* <p>
|
||||
* This dependency was declared in catalog libs.versions.toml
|
||||
*
|
||||
* @deprecated Will be removed in Gradle 9.0.
|
||||
*/
|
||||
@Deprecated
|
||||
public Provider<MinimalExternalModuleDependency> getBom() {
|
||||
org.gradle.internal.deprecation.DeprecationLogger.deprecateBehaviour("Accessing libraries or bundles from version catalogs in the plugins block.").withAdvice("Only use versions or plugins from catalogs in the plugins block.").willBeRemovedInGradle9().withUpgradeGuideSection(8, "kotlin_dsl_deprecated_catalogs_plugins_block").nagUser();
|
||||
return create("junit.bom");
|
||||
}
|
||||
|
||||
/**
|
||||
* Dependency provider for <b>jupiter</b> with <b>org.junit.jupiter:junit-jupiter</b> coordinates and
|
||||
* with <b>no version specified</b>
|
||||
* <p>
|
||||
* This dependency was declared in catalog libs.versions.toml
|
||||
*
|
||||
* @deprecated Will be removed in Gradle 9.0.
|
||||
*/
|
||||
@Deprecated
|
||||
public Provider<MinimalExternalModuleDependency> getJupiter() {
|
||||
org.gradle.internal.deprecation.DeprecationLogger.deprecateBehaviour("Accessing libraries or bundles from version catalogs in the plugins block.").withAdvice("Only use versions or plugins from catalogs in the plugins block.").willBeRemovedInGradle9().withUpgradeGuideSection(8, "kotlin_dsl_deprecated_catalogs_plugins_block").nagUser();
|
||||
return create("junit.jupiter");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Will be removed in Gradle 9.0.
|
||||
*/
|
||||
@Deprecated
|
||||
public static class SpringLibraryAccessors extends SubDependencyFactory {
|
||||
private final SpringBootLibraryAccessors laccForSpringBootLibraryAccessors = new SpringBootLibraryAccessors(owner);
|
||||
|
||||
public SpringLibraryAccessors(AbstractExternalDependencyFactory owner) { super(owner); }
|
||||
|
||||
/**
|
||||
* Group of libraries at <b>spring.boot</b>
|
||||
*
|
||||
* @deprecated Will be removed in Gradle 9.0.
|
||||
*/
|
||||
@Deprecated
|
||||
public SpringBootLibraryAccessors getBoot() {
|
||||
org.gradle.internal.deprecation.DeprecationLogger.deprecateBehaviour("Accessing libraries or bundles from version catalogs in the plugins block.").withAdvice("Only use versions or plugins from catalogs in the plugins block.").willBeRemovedInGradle9().withUpgradeGuideSection(8, "kotlin_dsl_deprecated_catalogs_plugins_block").nagUser();
|
||||
return laccForSpringBootLibraryAccessors;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Will be removed in Gradle 9.0.
|
||||
*/
|
||||
@Deprecated
|
||||
public static class SpringBootLibraryAccessors extends SubDependencyFactory {
|
||||
private final SpringBootStarterLibraryAccessors laccForSpringBootStarterLibraryAccessors = new SpringBootStarterLibraryAccessors(owner);
|
||||
|
||||
public SpringBootLibraryAccessors(AbstractExternalDependencyFactory owner) { super(owner); }
|
||||
|
||||
/**
|
||||
* Dependency provider for <b>configuration</b> with <b>org.springframework.boot:spring-boot-configuration-processor</b> coordinates and
|
||||
* with <b>no version specified</b>
|
||||
* <p>
|
||||
* This dependency was declared in catalog libs.versions.toml
|
||||
*
|
||||
* @deprecated Will be removed in Gradle 9.0.
|
||||
*/
|
||||
@Deprecated
|
||||
public Provider<MinimalExternalModuleDependency> getConfiguration() {
|
||||
org.gradle.internal.deprecation.DeprecationLogger.deprecateBehaviour("Accessing libraries or bundles from version catalogs in the plugins block.").withAdvice("Only use versions or plugins from catalogs in the plugins block.").willBeRemovedInGradle9().withUpgradeGuideSection(8, "kotlin_dsl_deprecated_catalogs_plugins_block").nagUser();
|
||||
return create("spring.boot.configuration");
|
||||
}
|
||||
|
||||
/**
|
||||
* Dependency provider for <b>test</b> with <b>org.springframework.boot:spring-boot-starter-test</b> coordinates and
|
||||
* with <b>no version specified</b>
|
||||
* <p>
|
||||
* This dependency was declared in catalog libs.versions.toml
|
||||
*
|
||||
* @deprecated Will be removed in Gradle 9.0.
|
||||
*/
|
||||
@Deprecated
|
||||
public Provider<MinimalExternalModuleDependency> getTest() {
|
||||
org.gradle.internal.deprecation.DeprecationLogger.deprecateBehaviour("Accessing libraries or bundles from version catalogs in the plugins block.").withAdvice("Only use versions or plugins from catalogs in the plugins block.").willBeRemovedInGradle9().withUpgradeGuideSection(8, "kotlin_dsl_deprecated_catalogs_plugins_block").nagUser();
|
||||
return create("spring.boot.test");
|
||||
}
|
||||
|
||||
/**
|
||||
* Group of libraries at <b>spring.boot.starter</b>
|
||||
*
|
||||
* @deprecated Will be removed in Gradle 9.0.
|
||||
*/
|
||||
@Deprecated
|
||||
public SpringBootStarterLibraryAccessors getStarter() {
|
||||
org.gradle.internal.deprecation.DeprecationLogger.deprecateBehaviour("Accessing libraries or bundles from version catalogs in the plugins block.").withAdvice("Only use versions or plugins from catalogs in the plugins block.").willBeRemovedInGradle9().withUpgradeGuideSection(8, "kotlin_dsl_deprecated_catalogs_plugins_block").nagUser();
|
||||
return laccForSpringBootStarterLibraryAccessors;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Will be removed in Gradle 9.0.
|
||||
*/
|
||||
@Deprecated
|
||||
public static class SpringBootStarterLibraryAccessors extends SubDependencyFactory {
|
||||
|
||||
public SpringBootStarterLibraryAccessors(AbstractExternalDependencyFactory owner) { super(owner); }
|
||||
|
||||
/**
|
||||
* Dependency provider for <b>actuator</b> with <b>org.springframework.boot:spring-boot-starter-actuator</b> coordinates and
|
||||
* with <b>no version specified</b>
|
||||
* <p>
|
||||
* This dependency was declared in catalog libs.versions.toml
|
||||
*
|
||||
* @deprecated Will be removed in Gradle 9.0.
|
||||
*/
|
||||
@Deprecated
|
||||
public Provider<MinimalExternalModuleDependency> getActuator() {
|
||||
org.gradle.internal.deprecation.DeprecationLogger.deprecateBehaviour("Accessing libraries or bundles from version catalogs in the plugins block.").withAdvice("Only use versions or plugins from catalogs in the plugins block.").willBeRemovedInGradle9().withUpgradeGuideSection(8, "kotlin_dsl_deprecated_catalogs_plugins_block").nagUser();
|
||||
return create("spring.boot.starter.actuator");
|
||||
}
|
||||
|
||||
/**
|
||||
* Dependency provider for <b>web</b> with <b>org.springframework.boot:spring-boot-starter-web</b> coordinates and
|
||||
* with <b>no version specified</b>
|
||||
* <p>
|
||||
* This dependency was declared in catalog libs.versions.toml
|
||||
*
|
||||
* @deprecated Will be removed in Gradle 9.0.
|
||||
*/
|
||||
@Deprecated
|
||||
public Provider<MinimalExternalModuleDependency> getWeb() {
|
||||
org.gradle.internal.deprecation.DeprecationLogger.deprecateBehaviour("Accessing libraries or bundles from version catalogs in the plugins block.").withAdvice("Only use versions or plugins from catalogs in the plugins block.").willBeRemovedInGradle9().withUpgradeGuideSection(8, "kotlin_dsl_deprecated_catalogs_plugins_block").nagUser();
|
||||
return create("spring.boot.starter.web");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class VersionAccessors extends VersionFactory {
|
||||
|
||||
private final SpringVersionAccessors vaccForSpringVersionAccessors = new SpringVersionAccessors(providers, config);
|
||||
public VersionAccessors(ProviderFactory providers, DefaultVersionCatalog config) { super(providers, config); }
|
||||
|
||||
/**
|
||||
* Version alias <b>coze</b> with value <b>0.2.0</b>
|
||||
* <p>
|
||||
* If the version is a rich version and cannot be represented as a
|
||||
* single version string, an empty string is returned.
|
||||
* <p>
|
||||
* This version was declared in catalog libs.versions.toml
|
||||
*/
|
||||
public Provider<String> getCoze() { return getVersion("coze"); }
|
||||
|
||||
/**
|
||||
* Version alias <b>junit</b> with value <b>5.10.0</b>
|
||||
* <p>
|
||||
* If the version is a rich version and cannot be represented as a
|
||||
* single version string, an empty string is returned.
|
||||
* <p>
|
||||
* This version was declared in catalog libs.versions.toml
|
||||
*/
|
||||
public Provider<String> getJunit() { return getVersion("junit"); }
|
||||
|
||||
/**
|
||||
* Version alias <b>snakeyaml</b> with value <b>2.0</b>
|
||||
* <p>
|
||||
* If the version is a rich version and cannot be represented as a
|
||||
* single version string, an empty string is returned.
|
||||
* <p>
|
||||
* This version was declared in catalog libs.versions.toml
|
||||
*/
|
||||
public Provider<String> getSnakeyaml() { return getVersion("snakeyaml"); }
|
||||
|
||||
/**
|
||||
* Group of versions at <b>versions.spring</b>
|
||||
*/
|
||||
public SpringVersionAccessors getSpring() {
|
||||
return vaccForSpringVersionAccessors;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class SpringVersionAccessors extends VersionFactory {
|
||||
|
||||
public SpringVersionAccessors(ProviderFactory providers, DefaultVersionCatalog config) { super(providers, config); }
|
||||
|
||||
/**
|
||||
* Version alias <b>spring.boot</b> with value <b>3.2.3</b>
|
||||
* <p>
|
||||
* If the version is a rich version and cannot be represented as a
|
||||
* single version string, an empty string is returned.
|
||||
* <p>
|
||||
* This version was declared in catalog libs.versions.toml
|
||||
*/
|
||||
public Provider<String> getBoot() { return getVersion("spring.boot"); }
|
||||
|
||||
/**
|
||||
* Version alias <b>spring.dependency</b> with value <b>1.1.4</b>
|
||||
* <p>
|
||||
* If the version is a rich version and cannot be represented as a
|
||||
* single version string, an empty string is returned.
|
||||
* <p>
|
||||
* This version was declared in catalog libs.versions.toml
|
||||
*/
|
||||
public Provider<String> getDependency() { return getVersion("spring.dependency"); }
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Will be removed in Gradle 9.0.
|
||||
*/
|
||||
@Deprecated
|
||||
public static class BundleAccessors extends BundleFactory {
|
||||
|
||||
public BundleAccessors(ObjectFactory objects, ProviderFactory providers, DefaultVersionCatalog config, ImmutableAttributesFactory attributesFactory, CapabilityNotationParser capabilityNotationParser) { super(objects, providers, config, attributesFactory, capabilityNotationParser); }
|
||||
|
||||
}
|
||||
|
||||
public static class PluginAccessors extends PluginFactory {
|
||||
private final SpringPluginAccessors paccForSpringPluginAccessors = new SpringPluginAccessors(providers, config);
|
||||
|
||||
public PluginAccessors(ProviderFactory providers, DefaultVersionCatalog config) { super(providers, config); }
|
||||
|
||||
/**
|
||||
* Group of plugins at <b>plugins.spring</b>
|
||||
*/
|
||||
public SpringPluginAccessors getSpring() {
|
||||
return paccForSpringPluginAccessors;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class SpringPluginAccessors extends PluginFactory {
|
||||
|
||||
public SpringPluginAccessors(ProviderFactory providers, DefaultVersionCatalog config) { super(providers, config); }
|
||||
|
||||
/**
|
||||
* Plugin provider for <b>spring.boot</b> with plugin id <b>org.springframework.boot</b> and
|
||||
* with version reference <b>spring.boot</b>
|
||||
* <p>
|
||||
* This plugin was declared in catalog libs.versions.toml
|
||||
*/
|
||||
public Provider<PluginDependency> getBoot() { return createPlugin("spring.boot"); }
|
||||
|
||||
/**
|
||||
* Plugin provider for <b>spring.dependency</b> with plugin id <b>io.spring.dependency-management</b> and
|
||||
* with version reference <b>spring.dependency</b>
|
||||
* <p>
|
||||
* This plugin was declared in catalog libs.versions.toml
|
||||
*/
|
||||
public Provider<PluginDependency> getDependency() { return createPlugin("spring.dependency"); }
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,2 @@
|
||||
#Sat Jan 10 11:40:33 CST 2026
|
||||
gradle.version=8.10
|
||||
BIN
models-integration/coze_oauth_java_jwt/.gradle/file-system.probe
Normal file
BIN
models-integration/coze_oauth_java_jwt/.gradle/file-system.probe
Normal file
Binary file not shown.
40
models-integration/coze_oauth_java_jwt/README.md
Normal file
40
models-integration/coze_oauth_java_jwt/README.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# Coze OAuth Examples
|
||||
|
||||
This repository contains examples of different OAuth flows for Coze API authentication.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Java 11 or higher
|
||||
- Gradle
|
||||
- A Coze API account with client credentials
|
||||
|
||||
## Configuration
|
||||
|
||||
Each example requires config file to be set with your Coze API credentials:
|
||||
|
||||
### JWT OAuth
|
||||
|
||||
### Set Environment Variables
|
||||
|
||||
To run the JWT OAuth example, set the following config file:
|
||||
|
||||
The configuration file should be a JSON file, named coze_oauth_config.json with the following format:
|
||||
```json
|
||||
{
|
||||
"client_type": "server",
|
||||
"client_id": "{client_id}",
|
||||
"private_key": "{private_key}",
|
||||
"public_key_id": "{public_key_id}",
|
||||
"coze_www_base": "https://www.coze.cn",
|
||||
"coze_api_base": "https://api.coze.cn"
|
||||
}
|
||||
```
|
||||
This file should be placed in the jwt-auth directory.
|
||||
|
||||
#### Running the Examples
|
||||
|
||||
After configuring the config file, you can run the JWT OAuth example using:
|
||||
|
||||
```bash
|
||||
sh bootstrap.sh
|
||||
```
|
||||
3
models-integration/coze_oauth_java_jwt/bootstrap.ps1
Normal file
3
models-integration/coze_oauth_java_jwt/bootstrap.ps1
Normal file
@@ -0,0 +1,3 @@
|
||||
# PowerShell script to run gradle project
|
||||
$CurrentPath = $PSScriptRoot
|
||||
Start-Process -FilePath "cmd.exe" -ArgumentList "/c pushd `"$CurrentPath`" && call gradlew.bat run && popd" -Wait -NoNewWindow
|
||||
4
models-integration/coze_oauth_java_jwt/bootstrap.sh
Normal file
4
models-integration/coze_oauth_java_jwt/bootstrap.sh
Normal file
@@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
|
||||
chmod +x ./gradlew
|
||||
./gradlew run
|
||||
92
models-integration/coze_oauth_java_jwt/build.gradle.kts
Normal file
92
models-integration/coze_oauth_java_jwt/build.gradle.kts
Normal file
@@ -0,0 +1,92 @@
|
||||
plugins {
|
||||
id("java")
|
||||
id("application")
|
||||
|
||||
id("com.diffplug.spotless") version "6.11.0"
|
||||
}
|
||||
|
||||
group = "com.coze"
|
||||
version = "1.0-SNAPSHOT"
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven {
|
||||
url = uri("https://maven.pkg.github.com/coze-dev/coze-api")
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// Javalin 和 Jetty 依赖
|
||||
implementation("io.javalin:javalin:4.6.8")
|
||||
implementation("org.eclipse.jetty:jetty-server:9.4.51.v20230217")
|
||||
implementation("org.eclipse.jetty:jetty-webapp:9.4.51.v20230217")
|
||||
implementation("org.eclipse.jetty:jetty-util:9.4.51.v20230217")
|
||||
implementation("org.eclipse.jetty:jetty-servlet:9.4.51.v20230217")
|
||||
implementation("org.eclipse.jetty:jetty-security:9.4.51.v20230217")
|
||||
implementation("org.eclipse.jetty:jetty-http:9.4.51.v20230217")
|
||||
implementation("org.eclipse.jetty:jetty-io:9.4.51.v20230217")
|
||||
implementation("org.slf4j:slf4j-simple:2.0.7")
|
||||
|
||||
// Lombok 支持 - 添加版本号
|
||||
compileOnly("org.projectlombok:lombok:1.18.30")
|
||||
annotationProcessor("org.projectlombok:lombok:1.18.30")
|
||||
|
||||
// YAML 支持
|
||||
implementation("org.yaml:snakeyaml")
|
||||
|
||||
// JSON 处理
|
||||
implementation("com.fasterxml.jackson.core:jackson-databind")
|
||||
implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml")
|
||||
|
||||
// coze api
|
||||
implementation("com.coze:coze-api:0.2.3")
|
||||
|
||||
implementation("commons-io:commons-io:2.11.0")
|
||||
|
||||
// 测试依赖
|
||||
testImplementation(platform("org.junit:junit-bom:5.10.0"))
|
||||
testImplementation("org.junit.jupiter:junit-jupiter")
|
||||
}
|
||||
|
||||
application {
|
||||
mainClass.set("com.coze.jwt.Main")
|
||||
}
|
||||
|
||||
tasks.register<Jar>("uberJar") {
|
||||
archiveClassifier.set("uber")
|
||||
from(sourceSets.main.get().output)
|
||||
dependsOn(configurations.runtimeClasspath)
|
||||
from({
|
||||
configurations.runtimeClasspath.get()
|
||||
.filter { it.name.endsWith("jar") }
|
||||
.map { zipTree(it) }
|
||||
})
|
||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||
}
|
||||
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion.set(JavaLanguageVersion.of(8))
|
||||
}
|
||||
}
|
||||
|
||||
tasks.named<Test>("test") {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
spotless {
|
||||
java {
|
||||
// 使用 Google Java 格式化规则
|
||||
googleJavaFormat()
|
||||
|
||||
// 移除未使用的 imports
|
||||
removeUnusedImports()
|
||||
|
||||
// 确保文件以新行结束
|
||||
endWithNewline()
|
||||
|
||||
// 自定义导入顺序
|
||||
importOrder("java", "javax", "org", "com", "")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"client_type": "jwt",
|
||||
"client_id": "1167741924603",
|
||||
"coze_www_base": "https://www.coze.cn",
|
||||
"coze_api_base": "https://api.coze.cn",
|
||||
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCqdnAKFZJ6lD5f\nzic42jpzHlabKGZoM1uhO+4qDwsEMtnCe4pdBPB0SDv5oK50atV3oDxrGbii2zPX\nCRABr7zEbFCoL6I2elVxDlQodFRsyQdOMJqJdROVu26uNTf5yCG1g2ZysKTSELk3\nPGzFJBExqcectjj67wojZltBbF3SMxYF7EK4bM4txAjOPr3k9+XlX4M8gDTlT6ZY\nwyZM6XFkUM3HX49yTtLqUORWdj29UEvtBG56Qwq+rDzXc1/u0OaL5DAwuWwn1nrU\ntIMUPvw6w7E3XbQsAatmoeuCaIsZSVX02qxNtjMktGwXT+YE/xWEQUM5c2ODRrMq\nOteW3frVAgMBAAECggEAE34sZ8Akb6deI/SIywPhj+KAmDOcKlRH09NCSUWjxod4\nxrhROpE4XK5vSqd+QPUqk13GgEorsVaMbLhY6pMfF1Mk52azD7RuQC8ZnpqsQNho\nNnUbvED1dreSpjyTDxmSCUC3yWu7yAMo5aZ08Gz5w7Uc4iXQXu77jiVupWJFWaZV\naR1AF6xnO/9iwTGHy+fDHYy5pCypVrEvg7UkaPw0bUC5x1nlFhLtXd2lnRLssY8l\nEH9HpckrkaqD3zYROh3nBysoZkkfJCj9+KGyVYAlAlGoLKbFv/KTfSIhXdlNlSg9\ngOQPeH8hnpJArTZGt2lb4+DN6P1FlGmEjzSjOj7eTQKBgQDX8Yc0kC2ET51Ims50\nEKy+dT1rp5n2DACBW+qzYKD8w67NTnViaZ2nc6NLu0x9/qwAarRm1X23U91HOEt9\nRa69PTrVAxmaYIXruDGAvu2h580IddmvUHjFVN8RWednFlltPgESErA+W81yTfKe\n82W5ZNnmSZh+MHnGSXDQJMO04wKBgQDKFStRvOzPCmOYwAclmw7UC36Q1rDh/gMn\n9pJrLQcbQGPR2pY+S/lGrDdmhuZTqPe2ircj1YWp8qr4tJNeht4N8YDS8qLsu12m\nHQNBTfrg7e6jQdSkxBz8iILmjBzHGwxUZApV0ERK6+iJ4HI4OF37IBjt8xUj9ZaX\nKNL2x7DW5wKBgHTH56ijeBofvB1xqsjV47W0TZ0UrIyFfRh4Dvsm/Kj1YmkhTxYD\nrADM5rij+AADZB1tl1YtiqlEL1y+swRyVMd+f3yHCqeUH5iUqiDIIFb4tscmhKzs\nxgNhnKkTh7MWQRJ0/7s4ZBu1Jev0/4q6cn7KbZS+pDtKF/EF8n5+A2ClAoGALzVO\nz2OBNsTIi5CgmspkZmjhAlkFLWY3uohBoEP+mwEp0IZt/tOkwjvNHWh6OiUI6V3y\ndq6U8SS9iCg3HSgIi91VKPKB2SfUMtAoSIM9DnrG+uCQGoWt99i0K8OjnWfWM4jD\nWVSz/4So6DzEshmI6veOm1fkImhm650f24K+7xUCgYBEH8EffQsbH/48HikJYWg2\naHVD9uxUT6n7bPHuKMRVfVTgAeXMn1/OBd23fyablmnQAARZFanx/smiUu3Bgyv3\nmV1IYfRo9s9ob+r+Kg7j3lyVy2GXTrojOT8O6ThwO/ImaEO+rImG0bpSgk/LzwWP\n9ZYdCpNH7uFPj/AIbwGizg==\n-----END PRIVATE KEY-----",
|
||||
"public_key_id": "aEFyH76ZupmgX46sNfh03MYcGscFpwPRSM4U3wOnYcM"
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
[versions]
|
||||
spring-boot = "3.2.3"
|
||||
spring-dependency = "1.1.4"
|
||||
junit = "5.10.0"
|
||||
snakeyaml = "2.0"
|
||||
coze = "0.2.0"
|
||||
|
||||
[libraries]
|
||||
spring-boot-starter-web = { group = "org.springframework.boot", name = "spring-boot-starter-web" }
|
||||
spring-boot-starter-actuator = { group = "org.springframework.boot", name = "spring-boot-starter-actuator" }
|
||||
spring-boot-configuration = { group = "org.springframework.boot", name = "spring-boot-configuration-processor" }
|
||||
lombok = { group = "org.projectlombok", name = "lombok" }
|
||||
snakeyaml = { group = "org.yaml", name = "snakeyaml", version.ref = "snakeyaml" }
|
||||
jackson-databind = { group = "com.fasterxml.jackson.core", name = "jackson-databind" }
|
||||
jackson-yaml = { group = "com.fasterxml.jackson.dataformat", name = "jackson-dataformat-yaml" }
|
||||
coze-api = { group = "com.coze", name = "coze-api", version.ref = "coze" }
|
||||
spring-boot-test = { group = "org.springframework.boot", name = "spring-boot-starter-test" }
|
||||
junit-bom = { group = "org.junit", name = "junit-bom", version.ref = "junit" }
|
||||
junit-jupiter = { group = "org.junit.jupiter", name = "junit-jupiter" }
|
||||
|
||||
[plugins]
|
||||
spring-boot = { id = "org.springframework.boot", version.ref = "spring-boot" }
|
||||
spring-dependency = { id = "io.spring.dependency-management", version.ref = "spring-dependency" }
|
||||
BIN
models-integration/coze_oauth_java_jwt/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
models-integration/coze_oauth_java_jwt/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
6
models-integration/coze_oauth_java_jwt/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
6
models-integration/coze_oauth_java_jwt/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
#Wed Jan 15 10:45:35 CST 2025
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
234
models-integration/coze_oauth_java_jwt/gradlew
vendored
Normal file
234
models-integration/coze_oauth_java_jwt/gradlew
vendored
Normal file
@@ -0,0 +1,234 @@
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=${0##*/}
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command;
|
||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||
# shell script including quotes and variable substitutions, so put them in
|
||||
# double quotes to make sure that they get re-expanded; and
|
||||
# * put everything else in single quotes, so that it's not re-expanded.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
89
models-integration/coze_oauth_java_jwt/gradlew.bat
vendored
Normal file
89
models-integration/coze_oauth_java_jwt/gradlew.bat
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
9
models-integration/coze_oauth_java_jwt/quickstart.md
Normal file
9
models-integration/coze_oauth_java_jwt/quickstart.md
Normal file
@@ -0,0 +1,9 @@
|
||||
Run this command for linux/macos:
|
||||
```bash
|
||||
bash bootstrap.sh
|
||||
```
|
||||
|
||||
Run this command for windows powershell:
|
||||
```powershell
|
||||
.\bootstrap.ps1
|
||||
```
|
||||
@@ -0,0 +1 @@
|
||||
rootProject.name = "jwt-oauth"
|
||||
BIN
models-integration/coze_oauth_java_jwt/src/.DS_Store
vendored
Normal file
BIN
models-integration/coze_oauth_java_jwt/src/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
models-integration/coze_oauth_java_jwt/src/main/.DS_Store
vendored
Normal file
BIN
models-integration/coze_oauth_java_jwt/src/main/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
models-integration/coze_oauth_java_jwt/src/main/java/.DS_Store
vendored
Normal file
BIN
models-integration/coze_oauth_java_jwt/src/main/java/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
models-integration/coze_oauth_java_jwt/src/main/java/com/.DS_Store
vendored
Normal file
BIN
models-integration/coze_oauth_java_jwt/src/main/java/com/.DS_Store
vendored
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user