Files
msh-system/.cursor/plans/mealplan_image_ai_generation_e8e123a0.plan.md

20 KiB
Raw Blame History

name, overview, todos, isProject
name overview todos isProject
MealPlan Image AI Generation 将 models-integration 项目中 KieAI 图像/视频生成接口复刻到 crmeb-front并在 mealPlan 菜品图片 URL 无效时,通过 KieAI NanoBanana API 生成图片、上传 OSS、返回有效 URL。
id content status
replicate-kieai-config 在 crmeb-common 中新增 KieAIConfig 配置类,在 application-sophia.yml 中添加 kie-ai 配置 completed
id content status
replicate-kieai-dtos 在 crmeb-common 中复刻 KieAI 相关 DTOTextToImageInput, CreateTaskRequest, CreateTaskResponse, QueryTaskResponse, NanoBananaRequest, NanoBananaResponse 等) completed
id content status
replicate-kieai-helper 在 crmeb-service 中复刻 NanoBananaHelperAPI 调用工具类) completed
id content status
replicate-kieai-service 在 crmeb-service 中复刻 KieAI 图像生成 Service 接口及实现ToolKieAIService / ToolKieAIServiceImpl completed
id content status
replicate-kieai-controller 在 crmeb-front 中新增 KieAIController暴露文生图、图编辑、任务查询等 REST 接口 completed
id content status
create-cache-table 创建 v2_dish_image_cache 数据库表及对应 MyBatis Entity/Dao/XML completed
id content status
extend-oss-service 扩展 OssService 支持 InputStream 上传AI 生成的图片无需先落盘) completed
id content status
implement-dish-image-service 实现 DishImageServiceURL 检测 + KieAI 文生图 + 轮询等待 + 下载图片 + OSS 上传 + 缓存写入 completed
id content status
modify-calculator-service 修改 ToolCalculatorServiceImpl.generateMealPlan() 集成 DishImageService completed
id content status
error-handling 实现降级策略KieAI 生成失败时使用默认占位图,不阻断主流程 completed
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 文生图 APIPOST {baseUrl}/predictions
    • Sora2 文生视频 / 图生视频 APIPOST {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 接口复刻架构

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 菜品图片生成流程

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) 复刻,保留核心字段:

@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 新增配置段:

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

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) 复刻核心方法:

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新建菜品图片缓存表

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.javaMapper
  • resources/mapper/tool/V2DishImageCacheDao.xmlMyBatis XML

步骤 7扩展 OssService 支持 InputStream 上传

当前 [OssServiceImpl.upload()](msh_crmeb_22/crmeb-service/src/main/java/com/zbkj/service/service/impl/OssServiceImpl.java) 只支持 File 参数。新增 InputStream 重载:

// 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

核心流程:

@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 检查原始 URL3秒超时
        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

@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/**