476 lines
20 KiB
Markdown
476 lines
20 KiB
Markdown
|
|
---
|
|||
|
|
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/**` |
|
|||
|
|
|
|||
|
|
|