From d138d40869d5c5740c88c41e2bde1bede01dbc89 Mon Sep 17 00:00:00 2001 From: glx <783262171@qq.com> Date: Fri, 15 May 2026 17:37:08 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=8E=A5=E5=8F=A3=E5=8F=82?= =?UTF-8?q?=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/AiGenerationController.java | 27 +- .../AiCallbackImage.java} | 10 +- .../model/form/AiFourPanelCallbackForm.java | 35 ++ .../model/form/AiFourPanelGenerateForm.java | 12 +- .../AiSingleImageCallbackForm.java} | 13 +- .../model/form/AiSingleImageGenerateForm.java | 12 +- .../mini/model/form/AiVideoGenerateForm.java | 5 +- .../youlai/boot/mini/model/form/MediaUrl.java | 19 ++ .../mini/model/form/VideoContentItem.java | 34 ++ .../mini/model/vo/AiTaskCallbackResult.java | 38 --- .../mini/model/vo/AiTaskCallbackUsage.java | 31 -- .../mini/service/AiGenerationService.java | 7 +- .../service/impl/AiGenerationServiceImpl.java | 323 ++++++++++++------ src/main/resources/application-dev.yml | 34 +- 14 files changed, 380 insertions(+), 220 deletions(-) rename src/main/java/com/youlai/boot/mini/model/{vo/AiTaskCallbackImage.java => form/AiCallbackImage.java} (63%) create mode 100644 src/main/java/com/youlai/boot/mini/model/form/AiFourPanelCallbackForm.java rename src/main/java/com/youlai/boot/mini/model/{vo/AiTaskCallbackVO.java => form/AiSingleImageCallbackForm.java} (70%) create mode 100644 src/main/java/com/youlai/boot/mini/model/form/MediaUrl.java create mode 100644 src/main/java/com/youlai/boot/mini/model/form/VideoContentItem.java delete mode 100644 src/main/java/com/youlai/boot/mini/model/vo/AiTaskCallbackResult.java delete mode 100644 src/main/java/com/youlai/boot/mini/model/vo/AiTaskCallbackUsage.java diff --git a/src/main/java/com/youlai/boot/mini/controller/AiGenerationController.java b/src/main/java/com/youlai/boot/mini/controller/AiGenerationController.java index 26f9544..0b69114 100644 --- a/src/main/java/com/youlai/boot/mini/controller/AiGenerationController.java +++ b/src/main/java/com/youlai/boot/mini/controller/AiGenerationController.java @@ -7,15 +7,13 @@ import com.youlai.boot.common.enums.LogModuleEnum; import com.youlai.boot.common.result.Result; import com.youlai.boot.framework.security.util.SecurityUtils; import com.youlai.boot.mini.model.form.AiFourPanelGenerateForm; +import com.youlai.boot.mini.model.form.AiSingleImageCallbackForm; import com.youlai.boot.mini.model.form.AiSingleImageGenerateForm; import com.youlai.boot.mini.model.form.AiVideoGenerateForm; -import com.youlai.boot.mini.model.vo.AiTaskCallbackVO; +import com.youlai.boot.mini.model.form.AiFourPanelCallbackForm; import com.youlai.boot.mini.model.vo.AiVideoCallbackVO; import com.youlai.boot.mini.service.AiGenerationService; import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.enums.ParameterIn; -import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; @@ -23,7 +21,6 @@ import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; -import java.util.Collections; import java.util.List; @Tag(name = "AI生成图片视频相关接口") @@ -48,6 +45,10 @@ public class AiGenerationController { return Result.success(urlList); } + //TODO: 查询用户最近上传的图片视频接口 + + //TODO:用户删除上次的图片视频接口 + @Operation(summary = "提交单图生成任务") @PostMapping("/generate-single-image") @Log(module = LogModuleEnum.AI_GENERATION_TASK, value = ActionTypeEnum.INSERT) @@ -57,12 +58,11 @@ public class AiGenerationController { return Result.success(taskUuid); } - //TODO: 后续增加用户小程序订阅通知 @Operation(summary = "单图任务回调接口") @PostMapping("/single-image/task/callback") @Log(module = LogModuleEnum.AI_GENERATION_TASK, value = ActionTypeEnum.UPDATE) - public Result taskCallback(@Valid @RequestBody AiTaskCallbackVO vo) { - boolean success = aiGenerationService.handleTaskCallback(vo); + public Result singleImageTaskCallback(@Valid @RequestBody AiSingleImageCallbackForm form) { + boolean success = aiGenerationService.handleTaskCallback(form); return Result.success(success); } @@ -75,6 +75,14 @@ public class AiGenerationController { return Result.success(taskUuid); } + @Operation(summary = "四宫格漫画任务回调接口") + @PostMapping("/four-panel/task/callback") + @Log(module = LogModuleEnum.AI_GENERATION_TASK, value = ActionTypeEnum.UPDATE) + public Result fourPanelTaskCallback(@Valid @RequestBody AiFourPanelCallbackForm form) { + boolean success = aiGenerationService.handleFourPanelCallback(form); + return Result.success(success); + } + @Operation(summary = "提交视频生成任务") @PostMapping("/generate-video") @Log(module = LogModuleEnum.AI_GENERATION_TASK, value = ActionTypeEnum.INSERT) @@ -84,7 +92,6 @@ public class AiGenerationController { return Result.success(taskUuid); } - //TODO: 后续增加用户小程序订阅通知 @Operation(summary = "视频任务回调接口") @PostMapping("/video/task/callback") @Log(module = LogModuleEnum.AI_GENERATION_TASK, value = ActionTypeEnum.UPDATE) @@ -93,4 +100,6 @@ public class AiGenerationController { return Result.success(success); } + //TODO: 后续在回调中 增加用户小程序订阅通知 + } diff --git a/src/main/java/com/youlai/boot/mini/model/vo/AiTaskCallbackImage.java b/src/main/java/com/youlai/boot/mini/model/form/AiCallbackImage.java similarity index 63% rename from src/main/java/com/youlai/boot/mini/model/vo/AiTaskCallbackImage.java rename to src/main/java/com/youlai/boot/mini/model/form/AiCallbackImage.java index 9e6a916..e080aa9 100644 --- a/src/main/java/com/youlai/boot/mini/model/vo/AiTaskCallbackImage.java +++ b/src/main/java/com/youlai/boot/mini/model/form/AiCallbackImage.java @@ -1,17 +1,17 @@ -package com.youlai.boot.mini.model.vo; +package com.youlai.boot.mini.model.form; import com.fasterxml.jackson.annotation.JsonProperty; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; /** - * AI生成图片信息 + * AI回调图片通用结构 * * @author youlai */ @Data -@Schema(description = "AI生成图片信息") -public class AiTaskCallbackImage { +@Schema(description = "AI回调图片通用结构") +public class AiCallbackImage { @Schema(description = "图片URL地址") private String url; @@ -20,6 +20,6 @@ public class AiTaskCallbackImage { @Schema(description = "Base64编码的图片JSON") private String b64Json; - @Schema(description = "图片尺寸,格式如 1664x2496") + @Schema(description = "图片尺寸,格式如 2048x2048") private String size; } diff --git a/src/main/java/com/youlai/boot/mini/model/form/AiFourPanelCallbackForm.java b/src/main/java/com/youlai/boot/mini/model/form/AiFourPanelCallbackForm.java new file mode 100644 index 0000000..15d0131 --- /dev/null +++ b/src/main/java/com/youlai/boot/mini/model/form/AiFourPanelCallbackForm.java @@ -0,0 +1,35 @@ +package com.youlai.boot.mini.model.form; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; +import java.util.List; + +/** + * 四宫格漫画任务回调请求表单 + * + * @author youlai + */ +@Data +@Schema(description = "四宫格漫画任务回调请求表单") +public class AiFourPanelCallbackForm { + + @NotBlank(message = "任务UUID不能为空") + @Schema(description = "任务唯一标识UUID", requiredMode = Schema.RequiredMode.REQUIRED) + private String uuid; + + @Schema(description = "任务状态:succeeded=成功,failed=失败") + private String status; + + @Schema(description = "生成的图片列表") + private List result; + + @JsonProperty("created_at") + @Schema(description = "创建时间戳") + private Long createdAt; + + @JsonProperty("updated_at") + @Schema(description = "更新时间戳") + private Long updatedAt; +} diff --git a/src/main/java/com/youlai/boot/mini/model/form/AiFourPanelGenerateForm.java b/src/main/java/com/youlai/boot/mini/model/form/AiFourPanelGenerateForm.java index 3f6f0ad..d8cf303 100644 --- a/src/main/java/com/youlai/boot/mini/model/form/AiFourPanelGenerateForm.java +++ b/src/main/java/com/youlai/boot/mini/model/form/AiFourPanelGenerateForm.java @@ -20,16 +20,16 @@ public class AiFourPanelGenerateForm { private String model; @Schema(description = "画风:1=治愈系水彩,2=Q版卡通,3=日式漫画,4=吉卜力风,5=3D毛绒,6=搞笑夸张", defaultValue = "1") - private Integer style = 1; + private Integer style; @Schema(description = "比例:1=1:1,2=2:3,3=3:4,4=4:3,5=5:4", defaultValue = "1") - private Integer ratio = 1; + private Integer ratio; @Schema(description = "图片尺寸:1=2K,2=4K,3=8K", defaultValue = "1") - private Integer imgSize = 1; + private Integer imgSize; @Schema(description = "图片格式:1=png,2=jpeg", defaultValue = "1") - private Integer imgType = 1; + private Integer imgType; @Schema(description = "宠物物种:如cat、dog") private String species; @@ -47,8 +47,8 @@ public class AiFourPanelGenerateForm { private String bodyType; @Schema(description = "特殊特征:如flat face、thick coat", defaultValue = "") - private String distinctiveFeatures = ""; + private String distinctiveFeatures; @Schema(description = "故事梗概:AI会据此生成四格漫画脚本") - private String storyOutline; + private String description; } diff --git a/src/main/java/com/youlai/boot/mini/model/vo/AiTaskCallbackVO.java b/src/main/java/com/youlai/boot/mini/model/form/AiSingleImageCallbackForm.java similarity index 70% rename from src/main/java/com/youlai/boot/mini/model/vo/AiTaskCallbackVO.java rename to src/main/java/com/youlai/boot/mini/model/form/AiSingleImageCallbackForm.java index bd93853..9fe2d06 100644 --- a/src/main/java/com/youlai/boot/mini/model/vo/AiTaskCallbackVO.java +++ b/src/main/java/com/youlai/boot/mini/model/form/AiSingleImageCallbackForm.java @@ -1,18 +1,19 @@ -package com.youlai.boot.mini.model.vo; +package com.youlai.boot.mini.model.form; import com.fasterxml.jackson.annotation.JsonProperty; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import lombok.Data; +import java.util.List; /** - * AI生成任务回调请求VO + * 单图生成任务回调请求表单 * * @author youlai */ @Data -@Schema(description = "AI生成任务回调请求") -public class AiTaskCallbackVO { +@Schema(description = "单图生成任务回调请求表单") +public class AiSingleImageCallbackForm { @NotBlank(message = "任务UUID不能为空") @Schema(description = "任务唯一标识UUID", requiredMode = Schema.RequiredMode.REQUIRED) @@ -21,8 +22,8 @@ public class AiTaskCallbackVO { @Schema(description = "任务状态:succeeded=成功,failed=失败") private String status; - @Schema(description = "生成结果") - private AiTaskCallbackResult result; + @Schema(description = "生成的图片列表") + private List result; @JsonProperty("created_at") @Schema(description = "创建时间戳") diff --git a/src/main/java/com/youlai/boot/mini/model/form/AiSingleImageGenerateForm.java b/src/main/java/com/youlai/boot/mini/model/form/AiSingleImageGenerateForm.java index ecaed29..2d09d56 100644 --- a/src/main/java/com/youlai/boot/mini/model/form/AiSingleImageGenerateForm.java +++ b/src/main/java/com/youlai/boot/mini/model/form/AiSingleImageGenerateForm.java @@ -20,16 +20,16 @@ public class AiSingleImageGenerateForm { private String model; @Schema(description = "画风:1=治愈系水彩,2=Q版卡通,3=日式漫画,4=吉卜力风,5=3D毛绒,6=搞笑夸张", defaultValue = "1") - private Integer style = 1; + private Integer style; @Schema(description = "比例:1=1:1,2=2:3,3=3:4,4=4:3,5=5:4", defaultValue = "1") - private Integer ratio = 1; + private Integer ratio; @Schema(description = "图片尺寸:1=2K,2=4K,3=8K", defaultValue = "1") - private Integer imgSize = 1; + private Integer imgSize; @Schema(description = "图片格式:1=png,2=jpeg", defaultValue = "1") - private Integer imgType = 1; + private Integer imgType; @Schema(description = "宠物物种:如cat、dog") private String species; @@ -47,8 +47,8 @@ public class AiSingleImageGenerateForm { private String bodyType; @Schema(description = "特殊特征:如flat face、thick coat", defaultValue = "") - private String distinctiveFeatures = ""; + private String distinctiveFeatures; @Schema(description = "场景描述:用户自定义生成场景") - private String sceneDescription; + private String description; } diff --git a/src/main/java/com/youlai/boot/mini/model/form/AiVideoGenerateForm.java b/src/main/java/com/youlai/boot/mini/model/form/AiVideoGenerateForm.java index c21bc1a..5ad08ec 100644 --- a/src/main/java/com/youlai/boot/mini/model/form/AiVideoGenerateForm.java +++ b/src/main/java/com/youlai/boot/mini/model/form/AiVideoGenerateForm.java @@ -3,7 +3,6 @@ package com.youlai.boot.mini.model.form; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import java.util.List; -import java.util.Map; /** * AI视频生成请求表单 @@ -17,8 +16,8 @@ public class AiVideoGenerateForm { @Schema(description = "使用的模型ID", defaultValue = "doubao-seedance-2-0-260128") private String model; - @Schema(description = "内容数组,包含text、image_url等类型") - private List> content; + @Schema(description = "内容数组,包含text、image_url、video_url、audio_url类型") + private List content; @Schema(description = "分辨率: 480p, 720p, 1080p, 2K", defaultValue = "720p") private String resolution = "720p"; diff --git a/src/main/java/com/youlai/boot/mini/model/form/MediaUrl.java b/src/main/java/com/youlai/boot/mini/model/form/MediaUrl.java new file mode 100644 index 0000000..9b028a1 --- /dev/null +++ b/src/main/java/com/youlai/boot/mini/model/form/MediaUrl.java @@ -0,0 +1,19 @@ +package com.youlai.boot.mini.model.form; + +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 媒体URL通用结构 + * + * @author youlai + */ +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(description = "媒体URL通用结构") +public class MediaUrl { + + @Schema(description = "资源URL地址") + private String url; +} diff --git a/src/main/java/com/youlai/boot/mini/model/form/VideoContentItem.java b/src/main/java/com/youlai/boot/mini/model/form/VideoContentItem.java new file mode 100644 index 0000000..cddb30a --- /dev/null +++ b/src/main/java/com/youlai/boot/mini/model/form/VideoContentItem.java @@ -0,0 +1,34 @@ +package com.youlai.boot.mini.model.form; + +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 视频生成内容项 + * + * @author youlai + */ +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(description = "视频生成内容项") +public class VideoContentItem { + + @Schema(description = "内容类型:text/image_url/video_url/audio_url", allowableValues = {"text", "image_url", "video_url", "audio_url"}) + private String type; + + @Schema(description = "文本内容,type=text时必填") + private String text; + + @Schema(description = "图片URL结构,type=image_url时必填") + private MediaUrl image_url; + + @Schema(description = "视频URL结构,type=video_url时必填") + private MediaUrl video_url; + + @Schema(description = "音频URL结构,type=audio_url时必填") + private MediaUrl audio_url; + + @Schema(description = "内容角色标识,比如reference_image/reference_video/reference_audio") + private String role; +} diff --git a/src/main/java/com/youlai/boot/mini/model/vo/AiTaskCallbackResult.java b/src/main/java/com/youlai/boot/mini/model/vo/AiTaskCallbackResult.java deleted file mode 100644 index 0f0b1c0..0000000 --- a/src/main/java/com/youlai/boot/mini/model/vo/AiTaskCallbackResult.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.youlai.boot.mini.model.vo; - -import com.fasterxml.jackson.annotation.JsonProperty; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import java.util.List; - -/** - * AI任务生成结果 - * - * @author youlai - */ -@Data -@Schema(description = "AI任务生成结果") -public class AiTaskCallbackResult { - - @Schema(description = "使用的模型ID") - private String model; - - @Schema(description = "生成的图片列表") - private List data; - - @Schema(description = "错误信息,无错误则为null") - private Object error; - - @Schema(description = "Token使用统计") - private AiTaskCallbackUsage usage; - - @JsonProperty("created_at") - @Schema(description = "创建时间戳") - private Long createdAt; - - @Schema(description = "使用的工具") - private String tool; - - @Schema(description = "创建时间戳") - private Long created; -} diff --git a/src/main/java/com/youlai/boot/mini/model/vo/AiTaskCallbackUsage.java b/src/main/java/com/youlai/boot/mini/model/vo/AiTaskCallbackUsage.java deleted file mode 100644 index 8fe587d..0000000 --- a/src/main/java/com/youlai/boot/mini/model/vo/AiTaskCallbackUsage.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.youlai.boot.mini.model.vo; - -import com.fasterxml.jackson.annotation.JsonProperty; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -/** - * AI生成资源消耗统计 - * - * @author youlai - */ -@Data -@Schema(description = "AI生成资源消耗统计") -public class AiTaskCallbackUsage { - - @JsonProperty("generated_images") - @Schema(description = "生成的图片数量") - private Integer generatedImages; - - @JsonProperty("output_tokens") - @Schema(description = "输出token数") - private Integer outputTokens; - - @JsonProperty("total_tokens") - @Schema(description = "总token数") - private Integer totalTokens; - - @JsonProperty("tool_usage") - @Schema(description = "工具使用信息") - private Object toolUsage; -} diff --git a/src/main/java/com/youlai/boot/mini/service/AiGenerationService.java b/src/main/java/com/youlai/boot/mini/service/AiGenerationService.java index 3837d54..316f3ef 100644 --- a/src/main/java/com/youlai/boot/mini/service/AiGenerationService.java +++ b/src/main/java/com/youlai/boot/mini/service/AiGenerationService.java @@ -1,10 +1,11 @@ package com.youlai.boot.mini.service; import com.youlai.boot.mini.model.form.AiFourPanelGenerateForm; +import com.youlai.boot.mini.model.form.AiSingleImageCallbackForm; import com.youlai.boot.mini.model.form.AiSingleImageGenerateForm; import com.youlai.boot.mini.model.form.AiVideoGenerateForm; import com.youlai.boot.mini.model.entity.MiniAiGenerationTask; -import com.youlai.boot.mini.model.vo.AiTaskCallbackVO; +import com.youlai.boot.mini.model.form.AiFourPanelCallbackForm; import com.youlai.boot.mini.model.vo.AiVideoCallbackVO; import org.springframework.web.multipart.MultipartFile; @@ -18,12 +19,14 @@ public interface AiGenerationService { String createAndGenerateFourPanel(AiFourPanelGenerateForm form, Long userId); - boolean handleTaskCallback(AiTaskCallbackVO vo); + boolean handleTaskCallback(AiSingleImageCallbackForm form); String createAndGenerateVideo(AiVideoGenerateForm form, Long userId); boolean handleVideoTaskCallback(AiVideoCallbackVO vo); + boolean handleFourPanelCallback(AiFourPanelCallbackForm form); + MiniAiGenerationTask getTaskByUuid(String uuid); } diff --git a/src/main/java/com/youlai/boot/mini/service/impl/AiGenerationServiceImpl.java b/src/main/java/com/youlai/boot/mini/service/impl/AiGenerationServiceImpl.java index 6e1ff5c..0309e49 100644 --- a/src/main/java/com/youlai/boot/mini/service/impl/AiGenerationServiceImpl.java +++ b/src/main/java/com/youlai/boot/mini/service/impl/AiGenerationServiceImpl.java @@ -25,11 +25,13 @@ import com.youlai.boot.mini.mapper.MiniAiTaskMediaMapper; import com.youlai.boot.mini.model.entity.MiniAiGenerationTask; import com.youlai.boot.mini.model.entity.MiniAiTaskMedia; import com.youlai.boot.mini.model.form.AiFourPanelGenerateForm; +import com.youlai.boot.mini.model.form.AiSingleImageCallbackForm; import com.youlai.boot.mini.model.form.AiSingleImageGenerateForm; import com.youlai.boot.mini.model.form.AiVideoGenerateForm; import com.youlai.boot.mini.model.form.MiniDeductPointForm; -import com.youlai.boot.mini.model.vo.AiTaskCallbackVO; import com.youlai.boot.mini.model.vo.AiVideoCallbackVO; +import com.youlai.boot.mini.model.form.AiCallbackImage; +import com.youlai.boot.mini.model.form.AiFourPanelCallbackForm; import com.youlai.boot.mini.model.vo.VideoCallbackData; import com.youlai.boot.common.exception.BusinessException; import com.youlai.boot.mini.service.AiGenerationService; @@ -76,6 +78,9 @@ public class AiGenerationServiceImpl implements AiGenerationService { //AI单图/四宫格图片生成任务回调地址 @Value("${ai.callback.single-image-callback-url:http://127.0.0.1:30101/backend/api/v1/mini/ai/generation/task/callback}") private String aiSingleImageCallbackUrl; + //四宫格图片生成任务回调地址 + @Value("${ai.callback.four-panel-callback-url:http://192.168.31.197:30101/backend/api/v1/mini/ai/generation/four-panel/task/callback}") + private String aiFourPanelCallbackUrl; //视频生成服务地址 @Value("${ai.generate.video-server-url:http://127.0.0.1:8001/api/v1/video/submit}") private String aiVideoServerUrl; @@ -108,10 +113,10 @@ public class AiGenerationServiceImpl implements AiGenerationService { // 校验用户当前未关联任务的上传文件数量,最多5个 Long existCount = aiTaskMediaMapper.selectCount(new LambdaQueryWrapper() - .eq(MiniAiTaskMedia::getCreateBy, userId) - .isNull(MiniAiTaskMedia::getTaskId) - .eq(MiniAiTaskMedia::getFileSource, "user_upload") - .eq(MiniAiTaskMedia::getDeleted, false)); + .eq(MiniAiTaskMedia::getCreateBy, userId) + .isNull(MiniAiTaskMedia::getTaskId) + .eq(MiniAiTaskMedia::getFileSource, "user_upload") + .eq(MiniAiTaskMedia::getDeleted, false)); if (existCount + uploadCount > 5) { throw new BusinessException("最多只能上传5个待生成的参考文件"); @@ -129,22 +134,23 @@ public class AiGenerationServiceImpl implements AiGenerationService { String objectName = OSS_IMAGE_DIR + fileName + "." + ext; String url = aliyunFileService.uploadFile(objectName, image.getInputStream()); - // 获取图片信息 - BufferedImage imageInfo = ImageIO.read(image.getInputStream()); - // 保存媒体记录 MiniAiTaskMedia media = new MiniAiTaskMedia(); String uuid = UUID.randomUUID().toString().replace("-", ""); media.setUuid(uuid) - .setFileSource("user_upload")//TODO 待整理成枚举,参考项目已有内容 - .setMediaType("image") - .setSourceUrl(url) - .setStorageKey(objectName) - .setWidth(imageInfo.getWidth()) - .setHeight(imageInfo.getHeight()) - .setCreateBy(userId) - .setCreateTimestamp(timestamp) - .setCreateTime(new Date(timestamp)); + .setMiniUserId(userId) + .setFileSource("user_upload") + .setMediaType("image") + .setSourceUrl(url) + .setStorageKey(objectName) + .setCreateBy(userId) + .setCreateTimestamp(timestamp) + .setCreateTime(new Date(timestamp)); + + // 获取图片信息 + BufferedImage imageInfo = ImageIO.read(image.getInputStream()); + media.setWidth(imageInfo.getWidth()) + .setHeight(imageInfo.getHeight()); int result = aiTaskMediaMapper.insert(media); if (result > 0) { @@ -178,13 +184,14 @@ public class AiGenerationServiceImpl implements AiGenerationService { String uuid = UUID.randomUUID().toString().replace("-", ""); media.setUuid(uuid) - .setFileSource("user_upload") - .setMediaType("video") - .setSourceUrl(url) - .setStorageKey(objectName) - .setCreateBy(userId) - .setCreateTimestamp(timestamp) - .setCreateTime(new Date(timestamp)); + .setMiniUserId(userId) + .setFileSource("user_upload") + .setMediaType("video") + .setSourceUrl(url) + .setStorageKey(objectName) + .setCreateBy(userId) + .setCreateTimestamp(timestamp) + .setCreateTime(new Date(timestamp)); // 获取视频时长 FileUtils.saveFile(video, tmpPath, fileName); @@ -197,7 +204,7 @@ public class AiGenerationServiceImpl implements AiGenerationService { String thumbnailFileName = timestamp + RandomNumberUtils.createRandomLowerLetterAndNumber(8); String thumbnailObjectName = OSS_THUMBNAIL_DIR + thumbnailFileName + ".png"; String thumbnailUrl = aliyunFileService.uploadFile(thumbnailObjectName, - FileUtils.bufferedImageToInputStream(thumbnail, "png")); + FileUtils.bufferedImageToInputStream(thumbnail, "png")); media.setThumbnailUrl(thumbnailUrl); int result = aiTaskMediaMapper.insert(media); @@ -234,17 +241,17 @@ public class AiGenerationServiceImpl implements AiGenerationService { // 创建生成任务(自动加入当前事务,失败会一起回滚) MiniAiGenerationTask task = new MiniAiGenerationTask(); task.setUuid(taskUuid) - .setMiniUserId(userId) - .setType("img_single") // 单图 - .setGenerateParams(JSONUtil.toJsonStr(form)) - .setPointsConsumed(Math.abs(deductPoint)) - .setStatus(0) // 生成中 - .setCreateBy(userId) - .setCreateTime(now) - .setCreateTimestamp(timestamp) - .setUpdateTime(now) // 补全update相关字段 - .setUpdateTimestamp(timestamp) - .setDeleted(false); + .setMiniUserId(userId) + .setType("img_single") // 单图 + .setGenerateParams(JSONUtil.toJsonStr(form)) + .setPointsConsumed(Math.abs(deductPoint)) + .setStatus(0) // 生成中 + .setCreateBy(userId) + .setCreateTime(now) + .setCreateTimestamp(timestamp) + .setUpdateTime(now) // 补全update相关字段 + .setUpdateTimestamp(timestamp) + .setDeleted(false); aiGenerationTaskMapper.insert(task); @@ -262,7 +269,7 @@ public class AiGenerationServiceImpl implements AiGenerationService { aiRequest.put("eye_color", form.getEyeColor()); aiRequest.put("body_type", form.getBodyType()); aiRequest.put("distinctive_features", form.getDistinctiveFeatures()); - aiRequest.put("scene_description", form.getSceneDescription()); + aiRequest.put("description", form.getDescription()); // 传递任务UUID和回调地址 aiRequest.put("uuid", taskUuid); aiRequest.put("callback_url", aiSingleImageCallbackUrl); @@ -271,10 +278,10 @@ public class AiGenerationServiceImpl implements AiGenerationService { log.info("提交AI生成任务,任务UUID:{},请求参数:{}", taskUuid, JSONUtil.toJsonStr(aiRequest)); // 同步调用AI接口,超时1秒 HttpResponse response = HttpRequest.post(aiSingleImageServerUrl) - .header("Content-Type", "application/json") - .body(JSONUtil.toJsonStr(aiRequest)) - .timeout(1000) - .execute(); + .header("Content-Type", "application/json") + .body(JSONUtil.toJsonStr(aiRequest)) + .timeout(1000) + .execute(); // 先判断HTTP状态码 if (!response.isOk()) { @@ -290,7 +297,7 @@ public class AiGenerationServiceImpl implements AiGenerationService { String errMsg = resJson.getStr("msg", "服务调用失败"); log.error("AI生成任务提交失败,业务错误码:{},错误信息:{},request_id:{},完整响应:{}", - code, errMsg, resJson.getStr("request_id"), responseBody); + code, errMsg, resJson.getStr("request_id"), responseBody); throw new MsgException("AI生成失败:" + errMsg); } } catch (JSONException e) { @@ -322,14 +329,14 @@ public class AiGenerationServiceImpl implements AiGenerationService { // 创建生成任务(自动加入当前事务,失败会一起回滚) MiniAiGenerationTask task = new MiniAiGenerationTask(); task.setUuid(taskUuid) - .setMiniUserId(userId) - .setType("img_grid_4") // 四宫格漫画 - .setGenerateParams(JSONUtil.toJsonStr(form)) - .setPointsConsumed(Math.abs(deductPoint)) - .setStatus(0) // 生成中 - .setCreateBy(userId) - .setCreateTime(now) - .setCreateTimestamp(timestamp); + .setMiniUserId(userId) + .setType("img_grid_4") // 四宫格漫画 + .setGenerateParams(JSONUtil.toJsonStr(form)) + .setPointsConsumed(Math.abs(deductPoint)) + .setStatus(0) // 生成中 + .setCreateBy(userId) + .setCreateTime(now) + .setCreateTimestamp(timestamp); aiGenerationTaskMapper.insert(task); @@ -347,19 +354,19 @@ public class AiGenerationServiceImpl implements AiGenerationService { aiRequest.put("eye_color", form.getEyeColor()); aiRequest.put("body_type", form.getBodyType()); aiRequest.put("distinctive_features", form.getDistinctiveFeatures()); - aiRequest.put("story_outline", form.getStoryOutline()); + aiRequest.put("description", form.getDescription()); // 传递任务UUID和回调地址 aiRequest.put("uuid", taskUuid); - aiRequest.put("callback_url", aiSingleImageCallbackUrl); + aiRequest.put("callback_url", aiFourPanelCallbackUrl); try { log.info("提交四宫格漫画生成任务,任务UUID:{},请求参数:{}", taskUuid, JSONUtil.toJsonStr(aiRequest)); // 同步调用AI接口,超时1秒 HttpResponse response = HttpRequest.post(aiFourPanelServerUrl) - .header("Content-Type", "application/json") - .body(JSONUtil.toJsonStr(aiRequest)) - .timeout(1000) - .execute(); + .header("Content-Type", "application/json") + .body(JSONUtil.toJsonStr(aiRequest)) + .timeout(1000) + .execute(); // 先判断HTTP状态码 if (!response.isOk()) { @@ -375,7 +382,7 @@ public class AiGenerationServiceImpl implements AiGenerationService { String errMsg = resJson.getStr("msg", "服务调用失败"); log.error("四宫格漫画生成任务提交失败,业务错误码:{},错误信息:{},request_id:{},完整响应:{}", - code, errMsg, resJson.getStr("request_id"), responseBody); + code, errMsg, resJson.getStr("request_id"), responseBody); throw new MsgException("AI生成失败:" + errMsg); } } catch (JSONException e) { @@ -407,14 +414,14 @@ public class AiGenerationServiceImpl implements AiGenerationService { // 创建生成任务 MiniAiGenerationTask task = new MiniAiGenerationTask(); task.setUuid(taskUuid) - .setMiniUserId(userId) - .setType("video") - .setGenerateParams(JSONUtil.toJsonStr(form)) - .setPointsConsumed(Math.abs(deductPoint)) - .setStatus(0) - .setCreateBy(userId) - .setCreateTime(now) - .setCreateTimestamp(timestamp); + .setMiniUserId(userId) + .setType("video") + .setGenerateParams(JSONUtil.toJsonStr(form)) + .setPointsConsumed(Math.abs(deductPoint)) + .setStatus(0) + .setCreateBy(userId) + .setCreateTime(now) + .setCreateTimestamp(timestamp); aiGenerationTaskMapper.insert(task); @@ -425,15 +432,15 @@ public class AiGenerationServiceImpl implements AiGenerationService { aiRequest.put("resolution", form.getResolution()); aiRequest.put("duration", form.getDuration()); aiRequest.put("uuid", taskUuid); - aiRequest.put("callback_url", aiVideoCallbackUrl); //TODO 后续独立回调地址 + aiRequest.put("callback_url", aiVideoCallbackUrl); try { log.info("提交视频生成任务,任务UUID:{},请求参数:{}", taskUuid, JSONUtil.toJsonStr(aiRequest)); HttpResponse response = HttpRequest.post(aiVideoServerUrl) - .header("Content-Type", "application/json") - .body(JSONUtil.toJsonStr(aiRequest)) - .timeout(1000) - .execute(); + .header("Content-Type", "application/json") + .body(JSONUtil.toJsonStr(aiRequest)) + .timeout(30000) //超时30秒 + .execute(); if (!response.isOk()) { log.error("视频生成任务提交失败,HTTP状态码:{},响应内容:{}", response.getStatus(), response.body()); @@ -488,8 +495,8 @@ public class AiGenerationServiceImpl implements AiGenerationService { String videoTaskUuid = data.getId(); // 根据第三方返回的视频任务uuid查询对应的任务 MiniAiGenerationTask task = aiGenerationTaskMapper.selectOne(new LambdaQueryWrapper() - .eq(MiniAiGenerationTask::getVideoTaskUuid, videoTaskUuid) - .eq(MiniAiGenerationTask::getDeleted, false)); + .eq(MiniAiGenerationTask::getVideoTaskUuid, videoTaskUuid) + .eq(MiniAiGenerationTask::getDeleted, false)); if (task == null) { log.error("视频回调任务不存在,第三方视频任务UUID:{}", videoTaskUuid); return false; @@ -525,6 +532,7 @@ public class AiGenerationServiceImpl implements AiGenerationService { String mediaUuid = UUID.randomUUID().toString().replace("-", ""); media.setUuid(mediaUuid) .setTaskId(task.getId()) + .setMiniUserId(task.getMiniUserId()) .setFileSource("ai_generate") .setMediaType("video") .setSourceUrl(ossUrl) @@ -544,59 +552,176 @@ public class AiGenerationServiceImpl implements AiGenerationService { } @Override - public boolean handleTaskCallback(AiTaskCallbackVO vo) { - log.info("处理AI生成任务回调,任务UUID:{}", vo.getUuid()); + @Transactional(rollbackFor = Exception.class) + public boolean handleFourPanelCallback(AiFourPanelCallbackForm form) { try { - String taskUuid = vo.getUuid(); + log.info("处理四宫格漫画生成任务回调,任务UUID:{}", form.getUuid()); + String taskUuid = form.getUuid(); // 查询任务是否存在 MiniAiGenerationTask task = getTaskByUuid(taskUuid); if (task == null) { - log.error("回调任务不存在,UUID:{}", taskUuid); + log.error("四宫格回调任务不存在,UUID:{}", taskUuid); return false; } // 转换任务状态 Integer status; - if ("succeeded".equals(vo.getStatus())) { + if ("succeeded".equals(form.getStatus())) { status = 1; // 成功 - } else if ("failed".equals(vo.getStatus())) { + } else if ("failed".equals(form.getStatus())) { status = 2; // 失败 } else { - log.error("回调任务状态非法,UUID:{},status:{}", taskUuid, vo.getStatus()); - return false; + log.info("四宫格任务{}处于中间状态:{},不处理", taskUuid, form.getStatus()); + return true; // 中间状态直接返回成功,不更新任务 } + // 更新任务状态 + task.setStatus(status); + task.setUpdateTime(new Date()); + task.setUpdateTimestamp(System.currentTimeMillis()); + aiGenerationTaskMapper.updateById(task); + // 如果生成成功,下载外部URL到OSS - String ossUrl = null; - if (status == 1 && vo.getResult() != null && !vo.getResult().getData().isEmpty()) { - String externalResultUrl = vo.getResult().getData().get(0).getUrl(); - ossUrl = downloadExternalUrlToOss(externalResultUrl); + if (status == 1 && form.getResult() != null && !form.getResult().isEmpty()) { + String externalImageUrl = form.getResult().get(0).getUrl(); + // 调用下载方法,存储到图片目录 + String ossUrl = downloadExternalUrlToOss(externalImageUrl); + task.setResultResourceUrl(ossUrl); + aiGenerationTaskMapper.updateById(task); // 保存生成的媒体记录 MiniAiTaskMedia media = new MiniAiTaskMedia(); String mediaUuid = UUID.randomUUID().toString().replace("-", ""); + AiCallbackImage image = form.getResult().get(0); + // 解析尺寸 + Integer width = null; + Integer height = null; + if (image.getSize() != null && image.getSize().contains("x")) { + String[] sizeArr = image.getSize().split("x"); + try { + width = Integer.parseInt(sizeArr[0]); + height = Integer.parseInt(sizeArr[1]); + } catch (Exception e) { + log.warn("解析四宫格图片尺寸失败,size:{}", image.getSize(), e); + } + } media.setUuid(mediaUuid) - .setMiniUserId(task.getMiniUserId()) .setTaskId(task.getId()) - .setFileSource("ai_generated") + .setMiniUserId(task.getMiniUserId()) + .setFileSource("ai_generate") .setMediaType("image") .setSourceUrl(ossUrl) + .setWidth(width) + .setHeight(height) .setCreateBy(task.getCreateBy()) - .setCreateTime(new Date()) .setCreateTimestamp(System.currentTimeMillis()) - .setUpdateTime(new Date()) - .setUpdateTimestamp(System.currentTimeMillis()) - .setDeleted(false); + .setCreateTime(new Date()); aiTaskMediaMapper.insert(media); + + // 更新用户上传的参考文件:关联当前任务ID并软删除 + LambdaUpdateWrapper updateMediaWrapper = new LambdaUpdateWrapper<>(); + updateMediaWrapper.eq(MiniAiTaskMedia::getMiniUserId, task.getMiniUserId()) + .isNull(MiniAiTaskMedia::getTaskId) + .eq(MiniAiTaskMedia::getFileSource, "user_upload") + .eq(MiniAiTaskMedia::getDeleted, false) + .set(MiniAiTaskMedia::getTaskId, task.getId()) + .set(MiniAiTaskMedia::getDeleted, true) + .set(MiniAiTaskMedia::getUpdateTime, new Date()) + .set(MiniAiTaskMedia::getUpdateTimestamp, System.currentTimeMillis()); + aiTaskMediaMapper.update(null, updateMediaWrapper); + } + + log.info("四宫格任务{}回调处理完成,状态:{}", taskUuid, status); + return true; + } catch (Exception e) { + log.error("四宫格任务回调处理异常,任务UUID:{},异常信息:{}", form.getUuid(), e.getMessage(), e); + return false; + } + } + + @Override + public boolean handleTaskCallback(AiSingleImageCallbackForm form) { + log.info("处理单图生成任务回调,任务UUID:{}", form.getUuid()); + try { + String taskUuid = form.getUuid(); + // 查询任务是否存在 + MiniAiGenerationTask task = getTaskByUuid(taskUuid); + if (task == null) { + log.error("单图回调任务不存在,UUID:{}", taskUuid); + return false; + } + + // 转换任务状态 + Integer status; + if ("succeeded".equals(form.getStatus())) { + status = 1; // 成功 + } else if ("failed".equals(form.getStatus())) { + status = 2; // 失败 + } else { + log.error("单图回调任务状态非法,UUID:{},status:{}", taskUuid, form.getStatus()); + return false; + } + + // 如果生成成功,下载外部URL到OSS + String ossUrl = null; + if (status == 1 && form.getResult() != null && !form.getResult().isEmpty()) { + String externalResultUrl = form.getResult().get(0).getUrl(); + ossUrl = downloadExternalUrlToOss(externalResultUrl); + + // 保存生成的媒体记录 + MiniAiTaskMedia media = new MiniAiTaskMedia(); + String mediaUuid = UUID.randomUUID().toString().replace("-", ""); + AiCallbackImage image = form.getResult().get(0); + // 解析尺寸 + Integer width = null; + Integer height = null; + if (image.getSize() != null && image.getSize().contains("x")) { + String[] sizeArr = image.getSize().split("x"); + try { + width = Integer.parseInt(sizeArr[0]); + height = Integer.parseInt(sizeArr[1]); + } catch (Exception e) { + log.warn("解析单图图片尺寸失败,size:{}", image.getSize(), e); + } + } + + media.setUuid(mediaUuid) + .setMiniUserId(task.getMiniUserId()) + .setTaskId(task.getId()) + .setFileSource("ai_generated") + .setMediaType("image") + .setSourceUrl(ossUrl) + .setWidth(width) + .setHeight(height) + .setCreateBy(task.getCreateBy()) + .setCreateTime(new Date()) + .setCreateTimestamp(System.currentTimeMillis()) + .setUpdateTime(new Date()) + .setUpdateTimestamp(System.currentTimeMillis()) + .setDeleted(false); + + aiTaskMediaMapper.insert(media); + + // 更新用户上传的参考文件:关联当前任务ID并软删除 + LambdaUpdateWrapper updateMediaWrapper = new LambdaUpdateWrapper<>(); + updateMediaWrapper.eq(MiniAiTaskMedia::getMiniUserId, task.getMiniUserId()) + .isNull(MiniAiTaskMedia::getTaskId) + .eq(MiniAiTaskMedia::getFileSource, "user_upload") + .eq(MiniAiTaskMedia::getDeleted, false) + .set(MiniAiTaskMedia::getTaskId, task.getId()) + .set(MiniAiTaskMedia::getDeleted, true) + .set(MiniAiTaskMedia::getUpdateTime, new Date()) + .set(MiniAiTaskMedia::getUpdateTimestamp, System.currentTimeMillis()); + aiTaskMediaMapper.update(null, updateMediaWrapper); } // 更新任务状态 return updateTaskStatus(taskUuid, status, ossUrl); } catch (Exception e) { - log.error("处理AI任务回调异常,UUID:{},异常信息:{}", vo.getUuid(), e.getMessage(), e); + log.error("处理AI任务回调异常,UUID:{},异常信息:{}", form.getUuid(), e.getMessage(), e); return false; } } @@ -604,8 +729,8 @@ public class AiGenerationServiceImpl implements AiGenerationService { @Override public MiniAiGenerationTask getTaskByUuid(String uuid) { return aiGenerationTaskMapper.selectOne(new LambdaQueryWrapper() - .eq(MiniAiGenerationTask::getUuid, uuid) - .eq(MiniAiGenerationTask::getDeleted, false)); + .eq(MiniAiGenerationTask::getUuid, uuid) + .eq(MiniAiGenerationTask::getDeleted, false)); } /** @@ -614,11 +739,11 @@ public class AiGenerationServiceImpl implements AiGenerationService { private boolean updateTaskStatus(String uuid, Integer status, String resultUrl) { LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>(); updateWrapper.eq(MiniAiGenerationTask::getUuid, uuid) - .eq(MiniAiGenerationTask::getDeleted, false) - .set(MiniAiGenerationTask::getStatus, status) - .set(resultUrl != null, MiniAiGenerationTask::getResultResourceUrl, resultUrl) - .set(MiniAiGenerationTask::getUpdateTime, new Date()) - .set(MiniAiGenerationTask::getUpdateTimestamp, System.currentTimeMillis()); + .eq(MiniAiGenerationTask::getDeleted, false) + .set(MiniAiGenerationTask::getStatus, status) + .set(resultUrl != null, MiniAiGenerationTask::getResultResourceUrl, resultUrl) + .set(MiniAiGenerationTask::getUpdateTime, new Date()) + .set(MiniAiGenerationTask::getUpdateTimestamp, System.currentTimeMillis()); return aiGenerationTaskMapper.update(null, updateWrapper) > 0; } @@ -655,7 +780,7 @@ public class AiGenerationServiceImpl implements AiGenerationService { if (lastDotIndex > 0 && lastDotIndex < path.length() - 1) { ext = path.substring(lastDotIndex + 1); } - if (ext == null){ + if (ext == null) { log.error("无法从URL中提取文件后缀,URL:{}", externalUrl); ext = "jpg"; } diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index 59b78c3..51667a9 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -82,17 +82,18 @@ security: allow-multi-login: true # 是否允许多设备登录 # 安全白名单路径,仅跳过 AuthorizationFilter 过滤器,还是会走 Spring Security 的其他过滤器(CSRF、CORS等) ignore-urls: - - /api/v1/auth/login/** # 登录接口(账号密码登录、手机验证码登录和微信登录) - - /api/v1/auth/captcha # 验证码获取接口 - - /api/v1/auth/sms/code # 发送登录短信验证码 - - /api/v1/auth/refresh-token # 刷新令牌接口 - - /api/v1/wxma/auth/** # 微信小程序认证接口(静默登录/手机号快捷登录/绑定手机号) - - /api/v1/logs/** # 日志接口(访问日志列表) - - /api/v1/mini/public/** - - /api/v1/mini/homePage/listByBounds - - /healthcheck - - /api/v1/mini/ai/generation/single-image/task/callback # AIGeneration 单图任务回调 - - /api/v1/mini/ai/generation/video/task/callback # AIGeneration 单图任务回调 + - /api/v1/auth/login/** # 登录接口(账号密码登录、手机验证码登录和微信登录) + - /api/v1/auth/captcha # 验证码获取接口 + - /api/v1/auth/sms/code # 发送登录短信验证码 + - /api/v1/auth/refresh-token # 刷新令牌接口 + - /api/v1/wxma/auth/** # 微信小程序认证接口(静默登录/手机号快捷登录/绑定手机号) + - /api/v1/logs/** # 日志接口(访问日志列表) + - /api/v1/mini/public/** + - /api/v1/mini/homePage/listByBounds + - /healthcheck + - /api/v1/mini/ai/generation/single-image/task/callback # AIGeneration 单图任务回调 + - /api/v1/mini/ai/generation/video/task/callback # AIGeneration 视频任务回调 + - /api/v1/mini/ai/generation/four-panel/task/callback # AIGeneration 四宫格任务回调 # 非安全端点路径,完全绕过 Spring Security 的过滤器 unsecured-urls: - ${springdoc.swagger-ui.path} @@ -231,12 +232,15 @@ wx: # AIGeneration 配置 ai: generate: - single-image-server-url: http://192.168.31.91:8001/api/v1/photo-to-comic - four-panel-server-url: http://192.168.31.91:8001/api/v1/four-panel-comic - video-server-url: http://192.168.31.91:8001/api/v1/video/submit + single-image-server-url: http://192.168.31.93:8001/api/v1/photo-to-comic + four-panel-server-url: http://192.168.31.93:8001/api/v1/four-panel-comic + video-server-url: http://192.168.31.93:8001/api/v1/video/submit callback: - single-image-callback-url: http://192.168.31.197:30101/backend/api/v1/mini/ai/generation/task/callback + single-image-callback-url: http://192.168.31.197:30101/backend/api/v1/mini/ai/generation/single-image/task/callback + four-panel-callback-url: http://192.168.31.197:30101/backend/api/v1/mini/ai/generation/four-panel/task/callback + #需要内网穿透工具 由火山方舟 api 回调,ngrok http 30101 替换为内网穿透工具地址 video-url: http://192.168.31.197:30101/backend/api/v1/mini/ai/generation/video/task/callback default: image-model: doubao-seedream-5-0-260128 video-model: doubao-seedance-2-0-260128 +