Sfoglia il codice sorgente

Merge branch 'dev-yhq' into develop

tsurumure 1 mese fa
parent
commit
1847e46e82
71 ha cambiato i file con 2917 aggiunte e 828 eliminazioni
  1. 69 0
      configuration/comfyui/clean-output.sh
  2. 11 0
      configuration/comfyui/sh_link.md
  3. 19 0
      db/comfyui_task.sql
  4. 26 0
      db/comfyui_task_execute.sql
  5. 5 3
      db/crt_drama_project_storyboard.sql
  6. 0 47
      db/crt_drama_task.sql
  7. 5 1
      db/crt_generate_image.sql
  8. 1 1
      db/sys_user_info.sql
  9. 6 0
      pom.xml
  10. 94 15
      src/main/java/com/backendsys/modules/TestController.java
  11. 1 1
      src/main/java/com/backendsys/modules/app/user/service/impl/AppAuthServiceImpl.java
  12. 3 3
      src/main/java/com/backendsys/modules/cms/article/service/impl/ArticleServiceImpl.java
  13. 19 0
      src/main/java/com/backendsys/modules/common/config/rabbitmq/RabbitConfig.java
  14. 62 0
      src/main/java/com/backendsys/modules/common/config/rabbitmq/RabbitListener.java
  15. 41 0
      src/main/java/com/backendsys/modules/common/config/rabbitmq/RabbitListenerRunner.java
  16. 47 0
      src/main/java/com/backendsys/modules/common/config/rabbitmq/queue/QueueDemoConfig.java
  17. 30 0
      src/main/java/com/backendsys/modules/common/config/rabbitmq/queue/QueueDlxConfig.java
  18. 16 12
      src/main/java/com/backendsys/modules/crt/controller/CrtDramaProjectStoryboardController.java
  19. 7 1
      src/main/java/com/backendsys/modules/crt/controller/CrtGenerateController.java
  20. 5 0
      src/main/java/com/backendsys/modules/crt/dao/CrtDramaProjectStoryboardDao.java
  21. 11 4
      src/main/java/com/backendsys/modules/crt/entity/CrtDramaProjectStoryboard.java
  22. 0 24
      src/main/java/com/backendsys/modules/crt/entity/CrtDramaTask.java
  23. 6 2
      src/main/java/com/backendsys/modules/crt/entity/CrtGenerateImage.java
  24. 40 8
      src/main/java/com/backendsys/modules/crt/enums/AspectRatioEnums.java
  25. 5 1
      src/main/java/com/backendsys/modules/crt/service/CrtDramaProjectStoryboardService.java
  26. 4 0
      src/main/java/com/backendsys/modules/crt/service/CrtGenerateService.java
  27. 109 26
      src/main/java/com/backendsys/modules/crt/service/impl/CrtDramaProjectStoryboardServiceImpl.java
  28. 115 438
      src/main/java/com/backendsys/modules/crt/service/impl/CrtGenerateServiceImpl.java
  29. 51 51
      src/main/java/com/backendsys/modules/queue/controller/QueueController.java
  30. 95 0
      src/main/java/com/backendsys/modules/queue/controller/TaskStatusController.java
  31. 9 0
      src/main/java/com/backendsys/modules/queue/entity/Entire.java
  32. 10 0
      src/main/java/com/backendsys/modules/queue/entity/GenerateRequest.java
  33. 17 14
      src/main/java/com/backendsys/modules/queue/service/QueueService.java
  34. 35 0
      src/main/java/com/backendsys/modules/queue/service/TaskService.java
  35. 199 0
      src/main/java/com/backendsys/modules/queue/service/impl/TaskServiceImpl_bak.java
  36. 12 23
      src/main/java/com/backendsys/modules/sdk/comfyui/controller/ComfyuiDemoController.java
  37. 9 0
      src/main/java/com/backendsys/modules/sdk/comfyui/dao/ComfyuiTaskDao.java
  38. 9 0
      src/main/java/com/backendsys/modules/sdk/comfyui/dao/ComfyuiTaskExecuteDao.java
  39. 1 1
      src/main/java/com/backendsys/modules/sdk/comfyui/entity/ComfyuiQueue.java
  40. 1 1
      src/main/java/com/backendsys/modules/sdk/comfyui/entity/ComfyuiRequest.java
  41. 1 1
      src/main/java/com/backendsys/modules/sdk/comfyui/entity/ComfyuiResponse.java
  42. 22 0
      src/main/java/com/backendsys/modules/sdk/comfyui/entity/ComfyuiTask.java
  43. 29 0
      src/main/java/com/backendsys/modules/sdk/comfyui/entity/ComfyuiTaskExecute.java
  44. 16 0
      src/main/java/com/backendsys/modules/sdk/comfyui/entity/ComfyuiText2Image.java
  45. 3 2
      src/main/java/com/backendsys/modules/sdk/comfyui/enums/TaskStatusEnums.java
  46. 21 0
      src/main/java/com/backendsys/modules/sdk/comfyui/enums/TaskTypeEnums.java
  47. 0 16
      src/main/java/com/backendsys/modules/sdk/comfyui/service/ComfyUIService.java
  48. 16 0
      src/main/java/com/backendsys/modules/sdk/comfyui/service/ComfyuiService.java
  49. 3 2
      src/main/java/com/backendsys/modules/sdk/comfyui/service/ComfyuiSocketService.java
  50. 14 0
      src/main/java/com/backendsys/modules/sdk/comfyui/service/ComfyuiText2ImageService.java
  51. 11 17
      src/main/java/com/backendsys/modules/sdk/comfyui/service/impl/ComfyuiServiceImpl.java
  52. 126 92
      src/main/java/com/backendsys/modules/sdk/comfyui/service/impl/ComfyuiSocketServiceImpl.java
  53. 446 0
      src/main/java/com/backendsys/modules/sdk/comfyui/service/impl/ComfyuiText2ImageServiceImpl.java
  54. 305 0
      src/main/java/com/backendsys/modules/sdk/comfyui/service/json/7.16生图 (1)-API.json
  55. 286 0
      src/main/java/com/backendsys/modules/sdk/comfyui/service/json/7.19_图片高清(输出2K).json
  56. 51 0
      src/main/java/com/backendsys/modules/sdk/comfyui/utils/ComfyUtil.java
  57. 5 2
      src/main/java/com/backendsys/modules/sdk/douyincloud/tos/service/DouyinTosService.java
  58. 50 0
      src/main/java/com/backendsys/modules/sdk/douyincloud/tos/service/impl/DouyinTosServiceImpl.java
  59. 2 2
      src/main/java/com/backendsys/modules/sdk/tencentcloud/cos/service/TencentCosService.java
  60. 33 5
      src/main/java/com/backendsys/modules/sdk/tencentcloud/cos/service/impl/TencentCosServiceImpl.java
  61. 0 2
      src/main/java/com/backendsys/modules/sdk/tencentcloud/cos/utils/TencentCosUtil.java
  62. 2 1
      src/main/java/com/backendsys/modules/sse/entity/SseResponseEnum.java
  63. 11 0
      src/main/java/com/backendsys/modules/upload/entity/ObjectKey.java
  64. 1 0
      src/main/java/com/backendsys/modules/upload/entity/SysFileResult.java
  65. 1 5
      src/main/java/com/backendsys/modules/upload/service/impl/SysFileServiceImpl.java
  66. 59 0
      src/main/java/com/backendsys/modules/upload/utils/ObjectKeyUtil.java
  67. 36 0
      src/main/java/com/backendsys/modules/upload/utils/UploadUtil.java
  68. 16 1
      src/main/resources/application-dev.yml
  69. 18 1
      src/main/resources/application-local.yml
  70. 16 1
      src/main/resources/application-prod.yml
  71. 112 1
      src/main/resources/mapper/crt/drama/CrtDramaProjectStoryboardDao.xml

+ 69 - 0
configuration/comfyui/clean-output.sh

@@ -0,0 +1,69 @@
+#!/bin/bash
+
+# 需求:
+# 1.把 ComfyUI_x/output 中的文件打包到 bak/YYYY-MM-DD.tar.gz
+# 2.成功后删除原文件
+
+# 环境: Ubuntu 22.04
+# 执行:
+# chmod +x ./clean-output.sh
+# dos2unix ./clean-output.sh
+# sh ./clean-output.sh
+
+# 定时任务
+# crontab -e
+# 0 0 */3 * * /mnt/nvme0n1/ComfyUI/clean-output.sh >> /var/log/clean-output.log 2>&1
+# crontab -l
+
+set -euo pipefail
+
+# 根目录列表
+COMFY_ROOTS=(
+	/mnt/nvme0n1/ComfyUI/ComfyUI_0
+	# /mnt/nvme0n1/ComfyUI/ComfyUI_1
+	# /mnt/nvme0n1/ComfyUI/ComfyUI_2
+	# /mnt/nvme0n1/ComfyUI/ComfyUI_3
+	# /mnt/nvme0n1/ComfyUI/ComfyUI_4
+	# /mnt/nvme0n1/ComfyUI/ComfyUI_5
+	# /mnt/nvme0n1/ComfyUI/ComfyUI_6
+	# /mnt/nvme0n1/ComfyUI/ComfyUI_7
+)
+
+DATE=$(date +%F)                # 2025-07-21
+LOG="/var/log/clean-output.log"
+
+# 写日志函数
+log() {
+	echo "[$(date '+%F %T')] $*" | tee -a "$LOG"
+}
+
+log "===== Starting backup ====="
+
+for r in "${COMFY_ROOTS[@]}"; do
+	SRC_DIR="${r}/output"
+	BAK_DIR="${SRC_DIR}/bak"
+
+	# 如果 output 为空则跳过
+	[[ -d "$SRC_DIR" ]] || { log "$SRC_DIR 不存在,跳过"; continue; }
+	[[ -n "$(ls -A "$SRC_DIR")" ]] || { log "$SRC_DIR 为空,跳过"; continue; }
+
+	mkdir -p "$BAK_DIR"
+	TAR_FILE="${BAK_DIR}/${DATE}.tar.gz"
+
+	log "开始打包 $SRC_DIR -> $TAR_FILE"
+
+	tar -czf "$TAR_FILE" -C "$SRC_DIR" --exclude='bak' . && {
+	# tar -cJf "${TAR_FILE%.tar.gz}.tar.xz" -C "$SRC_DIR" --exclude='bak' . && {
+		log "打包完成,开始清理源文件"
+		# 删除除 bak 以外的文件/目录
+		find "$SRC_DIR" -mindepth 1 ! -path "${BAK_DIR}*" -delete
+		log "$SRC_DIR 已清理"
+	} || {
+		log "打包失败,保留源文件"
+	}
+done
+
+log "===== Backup finished ====="
+
+# 重新解压出来
+# tar -xzf 2025-07-21.tar.gz -C /mnt/nvme0n1/ComfyUI/ComfyUI_0/output/

+ 11 - 0
configuration/comfyui/sh_link.md

@@ -0,0 +1,11 @@
+
+
+# 创建符号链接
+$ ln -s /mnt/nvme0n1/ComfyUI/share_custom_nodes /mnt/nvme0n1/ComfyUI/ComfyUI_0/custom_nodes
+$ ln -s /mnt/nvme0n1/ComfyUI/share_models /mnt/nvme0n1/ComfyUI/ComfyUI_0/models
+$ ln -s /mnt/nvme0n1/ComfyUI/share_user /mnt/nvme0n1/ComfyUI/ComfyUI_0/user
+
+# 删除符号链接
+$ unlink /mnt/nvme0n1/ComfyUI/ComfyUI_0/custom_nodes
+$ unlink /mnt/nvme0n1/ComfyUI/ComfyUI_0/models
+$ unlink /mnt/nvme0n1/ComfyUI/ComfyUI_0/user

+ 19 - 0
db/comfyui_task.sql

@@ -0,0 +1,19 @@
+/**
+Source Server Version: 8.0.31
+Source Database: backendsys
+Date: 2025/06/03 10:09:22
+*/
+
+DROP TABLE IF EXISTS `comfyui_task`;
+CREATE TABLE `comfyui_task` (
+    PRIMARY KEY (`id`),
+    `id` BIGINT AUTO_INCREMENT COMMENT 'ID',
+    `user_id` BIGINT NOT NULL COMMENT '用户ID',
+    `client_id` VARCHAR(255) COMMENT 'Client ID',
+    `task_type` VARCHAR(255) NOT NULL COMMENT '任务类型 (Text2Image, ..)',
+    `task_status` TINYINT DEFAULT '-1' COMMENT '任务状态 (-1:未提交, 1:已提交)',
+    `generate_request` TEXT COMMENT '任务请求原始参数 (JSONString)',
+    `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    INDEX `idx_user_id` (`user_id`)
+) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='任务表';

+ 26 - 0
db/comfyui_task_execute.sql

@@ -0,0 +1,26 @@
+/**
+Source Server Version: 8.0.31
+Source Database: backendsys
+Date: 2025/06/03 10:09:22
+*/
+
+DROP TABLE IF EXISTS `comfyui_task_execute`;
+CREATE TABLE `comfyui_task_execute` (
+    PRIMARY KEY (`id`),
+    `id` BIGINT AUTO_INCREMENT COMMENT 'ID',
+    `task_id` BIGINT NOT NULL COMMENT '任务ID',
+    `user_id` BIGINT NOT NULL COMMENT '用户ID',
+    `client_id` VARCHAR(255) COMMENT 'Client ID',
+    `task_type` VARCHAR(255) NOT NULL COMMENT '任务类型 (Text2Image, ..)',
+    `execute_prompt_id` VARCHAR(255) NOT NULL COMMENT '任务提交ID',
+    `execute_status` TINYINT DEFAULT '-1' COMMENT '任务执行状态 (-1:未开始, 1:进行中, 2:成功, 3:失败)',
+    `execute_url` VARCHAR(2000) COMMENT '任务执行URL',
+    `execute_url_port` INT COMMENT '任务执行URL端口',
+    `generate_request` TEXT COMMENT '任务请求原始参数 (JSONString)',
+    `generate_response` TEXT COMMENT '任务生成原始结果 (JSONString)',
+    `reason` TEXT COMMENT '当任务失败时展示失败原因(如触发平台的内容风控等)',
+    `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    INDEX `idx_execute_prompt_id` (`execute_prompt_id`),
+    INDEX `idx_user_id` (`user_id`)
+) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='任务执行表';

+ 5 - 3
db/crt_drama_project_storyboard.sql

@@ -24,8 +24,8 @@ CREATE TABLE `crt_drama_project_storyboard` (
     `param_prompt_flux_guidance` FLOAT DEFAULT '3.5' COMMENT '提示词引导系数 (默认值:3.5,小数点后一位,范围:1~30)',
     `param_sampler` VARCHAR(20) DEFAULT 'Euler' COMMENT '采样方法 (枚举)(Euler: euler, DPM++2M: dpmpp_2m)',
     `param_step` INT DEFAULT '20' COMMENT '步数 (默认值:20,整数范围:1~30)',
-    `param_seed` TINYINT DEFAULT '1' COMMENT '随机种子 (默认值:1,范围:(1:随机, 2:自定义))',
-    `param_seed_custom` VARCHAR(255) COMMENT '随机种子自定义值 (长度: 0~64位整数)',
+    `param_seed_type` TINYINT DEFAULT '1' COMMENT '随机种子 (默认值:1,范围:(1:随机, 2:自定义))',
+    `param_seed` VARCHAR(255) COMMENT '随机种子自定义值 (长度: 0~64位整数)',
     `text_to_image_prompt` VARCHAR(2000) COMMENT '文生图提示词 (生图时必填)',
 
     `param_video_reference_type` TINYINT DEFAULT '1' COMMENT '生视频模式 (1:首尾帧模式, 2:多图参考模式)',
@@ -37,7 +37,9 @@ CREATE TABLE `crt_drama_project_storyboard` (
 
     `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
     `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
-    INDEX `idx_user_id` (`user_id`)
+    INDEX `idx_user_id` (`user_id`),
+    INDEX `idx_drama_project_id` (`drama_project_id`),
+    INDEX `idx_episode_num` (`episode_num`)
 ) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='短剧创作-分镜表';
 
 INSERT INTO crt_drama_project_storyboard(user_id, drama_project_id, episode_num, sort, story_prompt, story_framing, story_scene, story_weather_time,

+ 0 - 47
db/crt_drama_task.sql

@@ -1,47 +0,0 @@
-/**
-Source Server Version: 8.0.31
-Source Database: backendsys
-Date: 2025/06/03 10:09:22
-*/
-
-DROP TABLE IF EXISTS `crt_drama_task`;
-CREATE TABLE `crt_drama_task` (
-    PRIMARY KEY (`id`),
-    `id` BIGINT AUTO_INCREMENT COMMENT 'ID',
-    `user_id` BIGINT NOT NULL COMMENT '用户ID',
-    `drama_project_id` BIGINT NOT NULL COMMENT '项目ID',
-    `task_id` VARCHAR(255) NOT NULL COMMENT '任务ID',
-    `task_type` TINYINT NOT NULL COMMENT '任务类型 (1:图像, 2:视频)',
-    `task_status` TINYINT DEFAULT '-1' COMMENT '任务状态 (-1:未开始, 1:进行中, 2:成功, 3:失败)',
-    `task_status_msg` VARCHAR(500) COMMENT '任务状态信息,当任务失败时展示失败原因(如触发平台的内容风控等)',
-    `task_result` TEXT COMMENT '任务生成结果 (JSONString)(成功时,返回生成结果)',
-    `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
-    `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
-    INDEX `idx_user_id` (`user_id`),
-    INDEX `idx_drama_project_id` (`drama_project_id`),
-    INDEX `idx_task_id` (`task_id`)
-) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='短剧创作-生图任务表';
-
-/*
-    目前打算生图使用ComfyUI,生视频使用可灵。
-
-    可灵并发:5
-    ComfyUI并发:15
- */
-
-/*
-    [Comfyui] 生图结果
-    task_result:{
-
-    }
-    [可灵] 生视频结果
-    task_result:{
-        "videos":[
-            {
-                "id": "string", //生成的视频ID;全局唯一
-                "url": "string", //生成视频的URL,例如https://p1.a.kwimgs.com/bs2/upload-ylab-stunt/special-effect/output/HB1_PROD_ai_web_46554461/-2878350957757294165/output.mp4(请注意,为保障信息安全,生成的图片/视频会在30天后被清理,请及时转存)
-                "duration": "string" //视频总时长,单位s
-            }
-          ]
-    }
- */

+ 5 - 1
db/crt_generate_image.sql

@@ -13,7 +13,11 @@ CREATE TABLE `crt_generate_image` (
     `prompt_id` VARCHAR(255) COMMENT '任务ID',
     `url_origin` VARCHAR(2000) NOT NULL COMMENT '原图',
     `url` VARCHAR(2000) COMMENT '转存图',
+    `object_key` VARCHAR(500) COMMENT 'ObjectKey',
+    `target` INT COMMENT '上传目标 (-1:本地, 1:腾讯云, 2:阿里云, 3.抖音云)',
     `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
     `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
-    INDEX `idx_user_id` (`user_id`)
+    INDEX `idx_user_id` (`user_id`),
+    INDEX `idx_prompt_id` (`prompt_id`),
+    INDEX `idx_drama_project_storyboard_id` (`drama_project_storyboard_id`)
 ) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='短剧创作-生成图片记录表';

+ 1 - 1
db/sys_user_info.sql

@@ -52,7 +52,7 @@ CREATE TABLE `sys_user_info` (
 
 
 INSERT INTO sys_user_info(user_id, nickname, email, gender, is_super, audit_status, audit_note, status, avatar, invite_code, create_time) VALUES
-    (1, '超人', 'admin@qq.com', '1', '-1', '2', '同意通过备注', '1', null, '12c9dd17-b7f4-4483-a513-fbcc36512d8d', '2023-07-19 10:45:00'),
+    (1, '超人-local', 'admin@qq.com', '1', '-1', '2', '同意通过备注', '1', null, '12c9dd17-b7f4-4483-a513-fbcc36512d8d', '2023-07-19 10:45:00'),
     (2, '普通用户A', '1111@qq.com', '1', '-1', '2', '同意通过备注', '1', null, '12c9dd17-b7f4-4483-a513-fbcc36512d8d', '2023-07-19 10:45:01'),
     (3, '内容运营', '2222@qq.com', '2', '1', '2', '同意通过备注', '1', null, '12c9dd17-b7f4-4483-a513-fbcc36512d8d', '2023-07-19 10:45:02'),
     (4, 'aaa', 'aaa@qq.com', '2', '-1', '1', '', '1', null, '12c9dd17-b7f4-4483-a513-fbcc36512d8d', '2023-07-19 10:45:03'),

+ 6 - 0
pom.xml

@@ -39,6 +39,12 @@
             </exclusions>
         </dependency>
 
+        <!-- RabbitMQ -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-amqp</artifactId>
+        </dependency>
+
         <!-- Nacos -->
 
         <!-- logback / log4j / slf4j -->

+ 94 - 15
src/main/java/com/backendsys/modules/TestController.java

@@ -13,6 +13,7 @@ import com.backendsys.modules.sdk.baidu.yunapp.entity.ExecuteScriptParams;
 import com.backendsys.modules.sdk.baidu.yunapp.service.YunappService;
 import com.backendsys.modules.sdk.volcengine.entity.VisualFaceSwapV2;
 import com.backendsys.modules.sdk.volcengine.service.VolcengineService;
+import com.backendsys.modules.system.entity.SysUser;
 import com.backendsys.service.TestService;
 import com.backendsys.utils.ResourceUtil;
 import com.fasterxml.jackson.core.JsonProcessingException;
@@ -21,15 +22,24 @@ import com.fasterxml.jackson.databind.ObjectMapper;
 import cn.afterturn.easypoi.word.WordExportUtil;
 import com.backendsys.utils.MD5Util;
 
+import com.rabbitmq.client.Channel;
+import com.rabbitmq.client.GetResponse;
 import com.tencentcloudapi.common.Credential;
 //import io.github.pigmesh.ai.deepseek.core.DeepSeekClientImpl;
 //import io.github.pigmesh.ai.deepseek.core.OpenAiHttpException;
 //import io.github.pigmesh.ai.deepseek.core.chat.ChatCompletionResponse;
 import com.volcengine.service.visual.IVisualService;
 import com.volcengine.service.visual.impl.VisualServiceImpl;
+import jakarta.annotation.PostConstruct;
 import jakarta.servlet.ServletContext;
 import org.apache.poi.xwpf.usermodel.XWPFDocument;
 import org.redisson.api.*;
+import org.springframework.amqp.core.*;
+import org.springframework.amqp.core.Queue;
+import org.springframework.amqp.rabbit.annotation.RabbitListener;
+import org.springframework.amqp.rabbit.connection.Connection;
+import org.springframework.amqp.rabbit.core.RabbitAdmin;
+import org.springframework.amqp.rabbit.core.RabbitTemplate;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.context.annotation.Lazy;
@@ -39,6 +49,7 @@ import org.springframework.web.bind.annotation.*;
 
 import java.awt.*;
 import java.io.*;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.security.NoSuchAlgorithmException;
@@ -47,21 +58,6 @@ import java.util.List;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
-
-//import com.tencentcloudapi.facefusion.v20181201.FacefusionClient;
-//import com.tencentcloudapi.facefusion.v20181201.models.*;
-
-//import com.tencentcloudapi.facefusion.v20220927.FacefusionClient;
-//import com.tencentcloudapi.facefusion.v20220927.models.*;
-
-//import com.tencentcloudapi.common.Credential;
-//import com.tencentcloudapi.common.profile.ClientProfile;
-//import com.tencentcloudapi.common.profile.HttpProfile;
-//import com.tencentcloudapi.common.exception.TencentCloudSDKException;
-//import com.tencentcloudapi.facefusion.v20181201.FacefusionClient;
-//import com.tencentcloudapi.facefusion.v20181201.models.*;
-
-
 import com.tencentcloudapi.common.CommonClient;
 import com.tencentcloudapi.common.exception.TencentCloudSDKException;
 
@@ -77,6 +73,89 @@ public class TestController {
     @Value("${tencent.facefusion.secret-key}")
     private String SECRET_KEY;
 
+//    @Autowired
+//    private AmqpAdmin amqpAdmin;
+    @Autowired
+    private RabbitTemplate rabbitTemplate;
+
+//    private void initRabbitMQ() {
+//        System.out.println("-- initRabbitMQ. --");
+//
+//        // 交换机
+//        amqpAdmin.declareExchange(new DirectExchange("demo.exchange", true, false));
+//
+//        // 队列 (TTL + 死信)
+//        Map<String, Object> args = new HashMap<>();
+//        args.put("x-message-ttl", 5000);                // 消息 TTL:60 秒(单位毫秒)
+//        amqpAdmin.declareQueue(new Queue("demo.queue", true, false, false, args));
+//
+////        // 队列
+////        amqpAdmin.declareQueue(new Queue("demo.queue", true));
+//
+//        // 把队列 demo.queue 绑定到交换机 demo.exchange,路由键设置为 order.create
+//        amqpAdmin.declareBinding(
+//                BindingBuilder.bind(new Queue("demo.queue"))
+//                        .to(new DirectExchange("demo.exchange"))
+//                        .with("order.create")
+//        );
+
+//        // 单队列 (无死信)
+//        amqpAdmin.declareQueue(new Queue("demo.queue", true));
+//    }
+
+    @GetMapping("/testRabbitMQ/send")
+    public String send() {
+
+//        initRabbitMQ();
+//        rabbitTemplate.convertAndSend("demo.exchange", "order.create", "Hello RabbitMQ!");
+//        rabbitTemplate.convertAndSend("", "demo.queue", "Hello RabbitMQ!");
+
+        SysUser sysUser = new SysUser();
+        sysUser.setUsername(UUID.randomUUID().toString());
+        System.out.println("【RabbitMQ-发送】:" + sysUser);
+//        rabbitTemplate.convertAndSend("", "demo.queue", sysUser);
+        rabbitTemplate.convertAndSend("order.exchange", "order.create", sysUser);
+
+        return "ok";
+    }
+
+    @GetMapping("/testRabbitMQ/poll")
+    public String poll() throws Exception {
+
+        // 1. 拿到连接和 channel(不要每次 new)
+        Connection connection = rabbitTemplate.getConnectionFactory().createConnection();
+        Channel channel = connection.createChannel(false);
+
+        // 2. 手动拉消息,第二个参数传 false 表示“不要自动 ack”
+        GetResponse resp = channel.basicGet("order.queue", false);
+        if (resp == null) {
+            channel.close();
+            connection.close();
+            return "no message";
+        }
+
+        String body = new String(resp.getBody(), StandardCharsets.UTF_8);
+        System.out.println("【RabbitMQ-手动处理】:" + body);
+
+        // 3. 用同一个 channel 去 ack
+        channel.basicAck(resp.getEnvelope().getDeliveryTag(), false);
+        channel.close();
+        connection.close();
+        return "acked: " + body;
+
+
+//        try {
+//            channel.basicAck(msg.getMessageProperties().getDeliveryTag(), false);
+//        } finally {
+//            channel.close();
+//        }
+//        return "acked: " + body;
+    }
+
+
+
+
+
 
 //    @Autowired
 //    private DeepSeekClientImpl deepSeekClient;

+ 1 - 1
src/main/java/com/backendsys/modules/app/user/service/impl/AppAuthServiceImpl.java

@@ -155,7 +155,7 @@ public class AppAuthServiceImpl implements AppAuthService {
 //                .toString("yyyy-MM-dd HH:mm:ss");
 //        appUser.setLast_login_time(utcTimeStr);
 
-        // [db] 更新前台用户信息
+        // [DB] 更新前台用户信息
         appUserDao.updateById(appUser);
 
 

+ 3 - 3
src/main/java/com/backendsys/modules/cms/article/service/impl/ArticleServiceImpl.java

@@ -43,8 +43,8 @@ public class ArticleServiceImpl implements ArticleService {
 //        List<Article> list = articleDao.selectList(new LambdaQueryWrapper<>());
         List<Map<String, Object>> list = articleDao.selectArticleList(article);
 
-//        // 1) 完成分页实体渲染
-//        PageEntity pageEntity = new PageInfoResult(list).toEntity();
+        // 1) 完成分页实体渲染
+        PageEntity pageEntity = new PageInfoResult(list).toEntity();
 //
 //        // 2) 分页列表格式化
 //        list = list.stream().map(item -> {
@@ -56,7 +56,7 @@ public class ArticleServiceImpl implements ArticleService {
 //        List<Object> objectList = list.stream().map(item -> (Object) item).collect(Collectors.toList());
 //        pageEntity.setList(objectList);
 
-        return new PageInfoResult(list).toEntity();
+        return pageEntity;
     }
 
     /**

+ 19 - 0
src/main/java/com/backendsys/modules/common/config/rabbitmq/RabbitConfig.java

@@ -0,0 +1,19 @@
+package com.backendsys.modules.common.config.rabbitmq;
+
+import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
+import org.springframework.amqp.support.converter.MessageConverter;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * RabbitMQ 通用配置
+ */
+@Configuration
+public class RabbitConfig {
+
+    // 允许 [实体类] 可以通过 Jackson 序列化 / 反序列化 传参
+    @Bean
+    public MessageConverter messageConverter() {
+        return new Jackson2JsonMessageConverter();
+    }
+}

+ 62 - 0
src/main/java/com/backendsys/modules/common/config/rabbitmq/RabbitListener.java

@@ -0,0 +1,62 @@
+package com.backendsys.modules.common.config.rabbitmq;
+
+import com.rabbitmq.client.Channel;
+import org.springframework.amqp.core.*;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Component;
+
+import java.io.IOException;
+
+/**
+ * 自定义监听器
+ */
+@Component
+@Lazy(false)
+public class RabbitListener {
+
+//    // [监听] 自动 ACK
+//    @RabbitListener(id = "demoContainer", queues = "demo.queue", autoStartup = "false" )
+//    public void receive(SysUser sysUser) {
+//        System.out.println("收到消息: " + sysUser);
+//    }
+
+
+//    // [监听] 手动 ACK
+//    @RabbitListener(id = "demoContainer", queues = "demo.queue", ackMode = "MANUAL")
+//    public void receive(Message message, Channel channel) throws IOException, InterruptedException {
+//        try {
+//            // 1. 模拟耗时业务
+//            Thread.sleep(8000);
+//
+//            // 2. 业务处理
+//            String body = new String(message.getBody(), StandardCharsets.UTF_8);
+//            System.out.println("处理消息 (8s): " + body);
+//
+//            // 3. 手动确认(deliveryTag + 是否批量)
+//            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
+//        } catch (Exception e) {
+//            System.err.println("处理失败 (10s): " + e.getMessage());
+//            // 拒绝并重新入队
+//            channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
+//        }
+//    }
+
+    // [监听] 死信队列
+    @org.springframework.amqp.rabbit.annotation.RabbitListener(id = "dlxContainer", queues = "dlx.queue", ackMode = "MANUAL")
+    public void handleDlx(Message message, Channel channel) throws IOException {
+        System.out.println("【RabbitMQ-死信】收到:" + new String(message.getBody()));
+
+        // 业务:记录日志 / 重发 / 报警 / 人工补偿
+        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
+    }
+
+
+    // 4. 并发消费(一条队列多线程)
+    /*
+    @RabbitListener(queues = "demo.queue", concurrency = "5-10")
+    public void receive(String msg) { ... }
+     */
+
+
+
+}

+ 41 - 0
src/main/java/com/backendsys/modules/common/config/rabbitmq/RabbitListenerRunner.java

@@ -0,0 +1,41 @@
+package com.backendsys.modules.common.config.rabbitmq;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.amqp.rabbit.listener.MessageListenerContainer;
+import org.springframework.amqp.rabbit.listener.RabbitListenerEndpointRegistry;
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.stereotype.Component;
+
+/**
+ * 手动启动监听器
+ * 由于 RabbitMQ 的监听器默认是懒加载,所以需要手动启动监听器
+ */
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class RabbitListenerRunner implements CommandLineRunner {
+
+    private final RabbitListenerEndpointRegistry registry;
+
+//    @Override
+//    public void run(String... args) {
+//        List<String> MANUAL_START_IDS = List.of("demoContainer", "dlxContainer");
+//        MANUAL_START_IDS.forEach(id -> {
+//            MessageListenerContainer c = registry.getListenerContainer(id);
+//            if (c != null && !c.isRunning()) {
+//                c.start();
+//                System.out.printf("-- RabbitListener '{}' 已手动启动 --", id);
+//            }
+//        });
+//    }
+
+    public void run(String... args) {
+        MessageListenerContainer container = registry.getListenerContainer("dlxContainer");
+        if (container != null && !container.isRunning()) {
+            container.start();     // 关键:真正启动监听
+            System.out.println("-- dlxContainer Listener 已手动启动 --");
+        }
+    }
+
+}

+ 47 - 0
src/main/java/com/backendsys/modules/common/config/rabbitmq/queue/QueueDemoConfig.java

@@ -0,0 +1,47 @@
+package com.backendsys.modules.common.config.rabbitmq.queue;
+
+import org.springframework.amqp.core.*;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Lazy;
+
+/**
+ * 非懒加载,在项目启动时创建 RabbitMQ 队列、交换机、绑定关系
+ */
+@Configuration
+@Lazy(false)
+public class QueueDemoConfig {
+
+    @Autowired
+    private QueueDlxConfig queueDlxConfig;
+
+    public static final String EXCHANGE = "order.exchange";
+    public static final String QUEUE = "order.queue";
+    public static final String ROUTING_KEY = "order.create";
+
+    // 交换机
+    @Bean
+    public DirectExchange demoExchange() {
+        return ExchangeBuilder.directExchange(EXCHANGE).durable(true).build();
+    }
+
+    // 队列 (5s后过期,1000条后过期)
+    @Bean
+    public Queue demoQueue() {
+        return QueueBuilder.durable(QUEUE)
+                .ttl(5000)                    // 5s
+                .maxLength(1000)        // 1000条
+                .deadLetterExchange(queueDlxConfig.EXCHANGE)
+                .deadLetterRoutingKey(queueDlxConfig.ROUTING_KEY)
+                .build();
+    }
+
+    // 队列绑定交换机
+    @Bean
+    public Binding demoBinding() {
+        return BindingBuilder.bind(demoQueue()).to(demoExchange()).with(ROUTING_KEY);
+    }
+
+
+}

+ 30 - 0
src/main/java/com/backendsys/modules/common/config/rabbitmq/queue/QueueDlxConfig.java

@@ -0,0 +1,30 @@
+package com.backendsys.modules.common.config.rabbitmq.queue;
+
+import org.springframework.amqp.core.*;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Lazy;
+
+/**
+ * 死信队列
+ * 在项目启动时创建 (RabbitMQ) 队列、交换机、绑定关系
+ */
+@Configuration
+@Lazy(false)
+public class QueueDlxConfig {
+
+    /* === 死信交换机/队列 === */
+    public static final String EXCHANGE = "dlx";
+    public static final String QUEUE = "dlx.queue";
+    public static final String ROUTING_KEY = "dlx.routekey";
+
+    @Bean
+    public DirectExchange dlxExchange() { return ExchangeBuilder.directExchange(EXCHANGE).durable(true).build(); }
+
+    @Bean
+    public Queue dlxQueue() { return QueueBuilder.durable(QUEUE).build(); }
+
+    @Bean
+    public Binding dlxBinding() { return BindingBuilder.bind(dlxQueue()).to(dlxExchange()).with(ROUTING_KEY); }
+
+}

+ 16 - 12
src/main/java/com/backendsys/modules/crt/controller/CrtDramaProjectStoryboardController.java

@@ -23,7 +23,6 @@ public class CrtDramaProjectStoryboardController {
     @Autowired
     private CrtDramaProjectStoryboardService crtDramaProjectStoryboardService;
 
-
     @Operation(summary = "获取分集信息/分镜列表")
     @PreAuthorize("@sr.hasPermission('36.2.1')")
     @GetMapping("/api/crt/drama/getStoryboard")
@@ -40,15 +39,6 @@ public class CrtDramaProjectStoryboardController {
         return Result.success().put("data", crtDramaProjectStoryboardService.createStoryboard(crtDramaProjectStoryboard));
     }
 
-    @SysLog("删除分集")
-    @Operation(summary = "删除分集")
-    @DeleteMapping("/api/crt/drama/deleteStoryboard")
-    public Result deleteStoryboard(@Validated(CrtDramaProjectStoryboard.Delete.class) @RequestBody CrtDramaProjectStoryboard crtDramaProjectStoryboard) {
-        return Result.success().put("data", crtDramaProjectStoryboardService.deleteStoryboard(crtDramaProjectStoryboard));
-    }
-
-
-
     @SysLog("编辑分镜")
     @Operation(summary = "编辑分镜")
     @PutMapping("/api/crt/drama/updateStoryboard")
@@ -56,11 +46,25 @@ public class CrtDramaProjectStoryboardController {
         return Result.success().put("data", crtDramaProjectStoryboardService.updateStoryboard(crtDramaProjectStoryboard));
     }
 
-    @SysLog("清除分镜")
-    @Operation(summary = "清除分镜")
+    @SysLog("清空全部分镜 (仅保留一个空分镜)")
+    @Operation(summary = "清空全部分镜 (仅保留一个空分镜)")
     @DeleteMapping("/api/crt/drama/clearStoryboard")
     public Result clearStoryboard(@Validated(CrtDramaProjectStoryboard.Clear.class) @RequestBody CrtDramaProjectStoryboard crtDramaProjectStoryboard) {
         return Result.success().put("data", crtDramaProjectStoryboardService.clearStoryboard(crtDramaProjectStoryboard));
     }
 
+    @SysLog("删除分集")
+    @Operation(summary = "删除分集")
+    @DeleteMapping("/api/crt/drama/deleteStoryboard")
+    public Result deleteStoryboard(@Validated(CrtDramaProjectStoryboard.Delete.class) @RequestBody CrtDramaProjectStoryboard crtDramaProjectStoryboard) {
+        return Result.success().put("data", crtDramaProjectStoryboardService.deleteStoryboard(crtDramaProjectStoryboard));
+    }
+
+    @SysLog("删除单个分镜")
+    @Operation(summary = "删除单个分镜")
+    @DeleteMapping("/api/crt/drama/deleteStoryboardBySort")
+    public Result deleteStoryboardBySort(@Validated(CrtDramaProjectStoryboard.DeleteBySort.class) @RequestBody CrtDramaProjectStoryboard crtDramaProjectStoryboard) {
+        return Result.success().put("data", crtDramaProjectStoryboardService.deleteStoryboardBySort(crtDramaProjectStoryboard));
+    }
+
 }

+ 7 - 1
src/main/java/com/backendsys/modules/crt/controller/CrtGenerateController.java

@@ -29,7 +29,6 @@ public class CrtGenerateController {
         return Result.success().put("data", crtGenerateService.getQueue());
     }
 
-    // 生成图片
     @PreAuthorize("@sr.hasPermission('36.3')")
     @Operation(summary = "生成图片")
     @PostMapping("/api/crt/generate/image")
@@ -37,6 +36,13 @@ public class CrtGenerateController {
         return Result.success().put("data", crtGenerateService.generateImage(crtDramaProjectStoryboard));
     }
 
+    @PreAuthorize("@sr.hasPermission('36.3')")
+    @Operation(summary = "获取分集生图记录")
+    @GetMapping("/api/crt/getGenerateImage")
+    public Result getGenerateImage(@Validated(CrtDramaProjectStoryboard.GenerateImageList.class) CrtDramaProjectStoryboard crtDramaProjectStoryboard) {
+        return Result.success().put("data", crtGenerateService.selectGenerateImage(crtDramaProjectStoryboard));
+    }
+
     // 生成视频
 
 }

+ 5 - 0
src/main/java/com/backendsys/modules/crt/dao/CrtDramaProjectStoryboardDao.java

@@ -4,9 +4,14 @@ import com.backendsys.modules.crt.entity.CrtDramaProjectStoryboard;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import org.apache.ibatis.annotations.Mapper;
 
+import java.util.List;
+import java.util.Map;
+
 @Mapper
 public interface CrtDramaProjectStoryboardDao extends BaseMapper<CrtDramaProjectStoryboard> {
 
+    List<CrtDramaProjectStoryboard> selectStoryboardList(CrtDramaProjectStoryboard crtDramaProjectStoryboard);
+
     // 编辑分镜
     int updateStoryboard(CrtDramaProjectStoryboard crtDramaProjectStoryboard);
 

+ 11 - 4
src/main/java/com/backendsys/modules/crt/entity/CrtDramaProjectStoryboard.java

@@ -16,6 +16,8 @@ import lombok.Data;
 import org.hibernate.validator.constraints.Range;
 
 import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Map;
 
 @Data
 @TableName("crt_drama_project_storyboard")
@@ -27,13 +29,15 @@ public class CrtDramaProjectStoryboard {
     public static interface Update{}
     public static interface Clear{}
     public static interface Delete{}
+    public static interface DeleteBySort{}
     public static interface GenerateImage{}
+    public static interface GenerateImageList{}
 
     @TableId(type = IdType.AUTO)
     private Long id;
 
-    @TableField(exist = false)
-    @NotNull(message = "分镜ID不能为空", groups = { Update.class, GenerateImage.class })
+    @TableField("id")
+    @NotNull(message = "分镜ID不能为空", groups = { Update.class, GenerateImage.class, GenerateImageList.class, DeleteBySort.class })
     private Long drama_project_storyboard_id;
 
     private Long user_id;
@@ -79,11 +83,11 @@ public class CrtDramaProjectStoryboard {
 
     // 随机种子 (默认值:1,范围:(1:随机, 2:自定义))
     @RangeArray(message="随机种子取值有误,范围应是(1, 2)", value = { "1", "2" }, groups = { Update.class })
-    private Integer param_seed;
+    private Integer param_seed_type;
 
     // 随机种子自定义值 (长度: 0~64位整数)
     @Size(max = 64, message = "随机种子自定义值长度不超过 {max} 个字符", groups = { Update.class })
-    private String param_seed_custom;
+    private String param_seed;
 
     // 文生图提示词 (生图时必填)
     @Size(max = 2000, message = "文生图提示词 长度不超过 {max} 个字符", groups = { Update.class })
@@ -102,6 +106,9 @@ public class CrtDramaProjectStoryboard {
     // 生图状态 (-1:未生图, 1:生成中, 2:已生图)
     private Integer generate_image_status;
 
+    @TableField(exist = false)
+    private List<CrtGenerateImage> generate_images;
+
     // 生视频状态 (-1:未生视频, 1:生成中, 2:已生视频)
     private Integer generate_video_status;
 

+ 0 - 24
src/main/java/com/backendsys/modules/crt/entity/CrtDramaTask.java

@@ -1,24 +0,0 @@
-package com.backendsys.modules.crt.entity;
-
-import com.baomidou.mybatisplus.annotation.IdType;
-import com.baomidou.mybatisplus.annotation.TableId;
-import com.baomidou.mybatisplus.annotation.TableName;
-import lombok.Data;
-
-@Data
-@TableName("crt_drama_task")
-public class CrtDramaTask {
-
-    @TableId(type = IdType.AUTO)
-    private Long id;
-    private Long user_id;                       // 用户ID
-    private Long drama_project_id;              // 项目ID
-    private String task_id;                     // 任务ID
-    private Integer task_type;                  // 任务类型 (1:图像, 2:视频)
-    private Integer task_status;                // 任务状态 (-1:未开始, 1:进行中, 2:成功, 3:失败)
-    private String task_status_msg;             // 任务状态信息,当任务失败时展示失败原因(如触发平台的内容风控等)
-    private String task_result;                 // 任务生成结果 (JSONString)(成功时,返回生成结果)
-    private String create_time;
-    private String update_time;
-
-}

+ 6 - 2
src/main/java/com/backendsys/modules/crt/entity/CrtGenerateImage.java

@@ -2,6 +2,7 @@ package com.backendsys.modules.crt.entity;
 
 import com.backendsys.config.Mybatis.handler.timezone.LocalDateTimeAdapter;
 import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
 import com.google.gson.annotations.JsonAdapter;
@@ -18,9 +19,12 @@ public class CrtGenerateImage {
     private Long user_id;                       // 用户ID
     private Long drama_project_storyboard_id;   // 分镜ID
     private String prompt_id;                   // 任务ID
-    private String name;                        // 图片名称
     private String url_origin;                  // 原图
-    private String url;                          // 转存图
+    private String url;                         // 转存图
+    @TableField(exist = false)
+    private String url_thumb;                   // 转存图-缩略图
+    private Integer target;                     // 上传目标 (-1:本地, 1:腾讯云, 2:阿里云, 3.抖音云)
+    private String object_key;
 
     @JsonAdapter(LocalDateTimeAdapter.class)
     private LocalDateTime create_time;

+ 40 - 8
src/main/java/com/backendsys/modules/crt/enums/AspectRatioEnums.java

@@ -2,24 +2,56 @@ package com.backendsys.modules.crt.enums;
 
 public enum AspectRatioEnums {
 
-    RATIO_16_9("16:9", "1280*720"),
-    RATIO_9_16("9:16", "720*1280"),
-    RATIO_1_1("1:1", "1024*1024")
+    RATIO_16_9("16:9", 1280, 720),
+    RATIO_9_16("9:16", 720, 1280),
+    RATIO_1_1("1:1", 1024, 1024)
     ;
 
     private final String key;
-    private final String value;
+    private final Integer width;
+    private final Integer height;
 
-    AspectRatioEnums(String key, String value) {
+    AspectRatioEnums(String key, Integer width, Integer height) {
         this.key = key;
-        this.value = value;
+        this.width = width;
+        this.height = height;
     }
 
-    public String getValue() {
-        return this.value;
+    public Integer getWidth() {
+        return this.width;
+    }
+    public Integer getHeight() {
+        return this.height;
     }
     public String getKey() {
         return this.key;
     }
 
+    /* 根据 key 返回枚举,找不到返回 null */
+    public static AspectRatioEnums of(String key) {
+        for (AspectRatioEnums e : values()) {
+            if (e.key.equals(key)) {
+                return e;
+            }
+        }
+        return null;
+    }
+
+    /*
+         int[] size = AspectRatioEnums.getAspectRatio("16:9");
+         // [1280, 720]
+     */
+    public static int[] getAspectRatio(String key) {
+        AspectRatioEnums e = of(key);
+        return e == null ? null : new int[]{e.width, e.height};
+    }
+    /*
+        String sizeStr = AspectRatioEnums.getAspectRatioString("16:9");
+        // "1280*720"
+    */
+    public static String getAspectRatioString(String key) {
+        AspectRatioEnums e = of(key);
+        return e == null ? null : e.width + "*" + e.height;
+    }
+
 }

+ 5 - 1
src/main/java/com/backendsys/modules/crt/service/CrtDramaProjectStoryboardService.java

@@ -19,9 +19,13 @@ public interface CrtDramaProjectStoryboardService {
     // 编辑分镜
     Map<String, Object> updateStoryboard(CrtDramaProjectStoryboard crtDramaProjectStoryboard);
 
-    // 清空分镜
+    // 清空全部分镜 (仅保留一个空分镜)
     Map<String, Object> clearStoryboard(CrtDramaProjectStoryboard crtDramaProjectStoryboard);
 
     // 删除分集
     Map<String, Object> deleteStoryboard(CrtDramaProjectStoryboard crtDramaProjectStoryboard);
+
+    // 删除单个分镜
+    Map<String, Object> deleteStoryboardBySort(CrtDramaProjectStoryboard crtDramaProjectStoryboard);
+
 }

+ 4 - 0
src/main/java/com/backendsys/modules/crt/service/CrtGenerateService.java

@@ -1,6 +1,7 @@
 package com.backendsys.modules.crt.service;
 
 import com.backendsys.modules.crt.entity.CrtDramaProjectStoryboard;
+import com.backendsys.utils.response.PageEntity;
 
 import java.util.Map;
 
@@ -12,4 +13,7 @@ public interface CrtGenerateService {
     // 短剧创作-生成图片
     Map<String, Object> generateImage(CrtDramaProjectStoryboard crtDramaProjectStoryboard);
 
+    // 短剧创作-获取分集生图记录
+    PageEntity selectGenerateImage(CrtDramaProjectStoryboard crtDramaProjectStoryboard);
+
 }

+ 109 - 26
src/main/java/com/backendsys/modules/crt/service/impl/CrtDramaProjectStoryboardServiceImpl.java

@@ -1,5 +1,6 @@
 package com.backendsys.modules.crt.service.impl;
 
+import cn.hutool.core.convert.Convert;
 import com.backendsys.exception.CustException;
 import com.backendsys.modules.common.config.security.enums.SecurityEnum;
 import com.backendsys.modules.common.config.security.utils.SecurityUtil;
@@ -7,30 +8,34 @@ import com.backendsys.modules.common.enums.MatchType;
 import com.backendsys.modules.crt.dao.CrtDramaProjectDao;
 import com.backendsys.modules.crt.dao.CrtDramaProjectSettingsDao;
 import com.backendsys.modules.crt.dao.CrtDramaProjectStoryboardDao;
-import com.backendsys.modules.crt.entity.CrtDramaProject;
-import com.backendsys.modules.crt.entity.CrtDramaProjectSettings;
-import com.backendsys.modules.crt.entity.CrtDramaProjectStoryboard;
-import com.backendsys.modules.crt.entity.StoryboardResponse;
+import com.backendsys.modules.crt.dao.CrtGenerateImageDao;
+import com.backendsys.modules.crt.entity.*;
 import com.backendsys.modules.crt.service.CrtDramaProjectStoryboardService;
+import com.backendsys.modules.upload.entity.ObjectKey;
+import com.backendsys.modules.upload.enums.StyleEnums;
+import com.backendsys.modules.upload.utils.ObjectKeyUtil;
+import com.backendsys.modules.upload.utils.UploadUtil;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
-import java.util.Arrays;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
+import java.util.concurrent.CompletableFuture;
 import java.util.stream.Collectors;
 
 @Service
 public class CrtDramaProjectStoryboardServiceImpl implements CrtDramaProjectStoryboardService {
 
+    @Autowired
+    private ObjectKeyUtil objectKeyUtil;
     @Autowired
     private SecurityUtil securityUtil;
     @Autowired
     private CrtDramaProjectDao crtDramaProjectDao;
     @Autowired
+    private CrtGenerateImageDao crtGenerateImageDao;
+    @Autowired
     private CrtDramaProjectSettingsDao crtDramaProjectSettingsDao;
     @Autowired
     private CrtDramaProjectStoryboardDao crtDramaProjectStoryboardDao;
@@ -41,17 +46,29 @@ public class CrtDramaProjectStoryboardServiceImpl implements CrtDramaProjectStor
     @Override
     public StoryboardResponse selectCrtDramaProjectStoryboardDetail(CrtDramaProjectStoryboard crtDramaProjectStoryboard) {
 
-        Integer episode_num = crtDramaProjectStoryboard.getEpisode_num();
         Long project_id = crtDramaProjectStoryboard.getDrama_project_id();
 
         CrtDramaProject crtDramaProject = crtDramaProjectDao.selectById(project_id);
         if (crtDramaProject == null) throw new CustException("项目不存在");
 
-        LambdaQueryWrapper<CrtDramaProjectStoryboard> wrapperStoryboard = new LambdaQueryWrapper<>();
-        wrapperStoryboard.eq(CrtDramaProjectStoryboard::getDrama_project_id, project_id);
-        wrapperStoryboard.eq(CrtDramaProjectStoryboard::getEpisode_num, episode_num);
-        List<CrtDramaProjectStoryboard> storyboardList = crtDramaProjectStoryboardDao.selectList(wrapperStoryboard);
-        if (storyboardList.isEmpty()) throw new CustException("分镜不存在");
+        List<CrtDramaProjectStoryboard> storyboardList = crtDramaProjectStoryboardDao.selectStoryboardList(crtDramaProjectStoryboard);
+        if (storyboardList != null && storyboardList.isEmpty()) throw new CustException("分镜不存在");
+
+        // 获取分镜列表
+        storyboardList.stream().forEach(storyboard -> {
+            // 获取分镜生图结果
+            if (storyboard.getGenerate_images() != null && !storyboard.getGenerate_images().isEmpty()) {
+                // 设置生图结果缩略略
+                List<CrtGenerateImage> generate_image_list = storyboard.getGenerate_images().stream().map(item -> {
+                    String url = item.getUrl();
+                    Integer target = item.getTarget();
+                    String url_thumb = UploadUtil.getImageThumbUrl(url, target, 100, null, StyleEnums.THUMB_BACKGROUND.getValue());
+                    item.setUrl_thumb(url_thumb);
+                    return item;
+                }).collect(Collectors.toList());
+                storyboard.setGenerate_images(generate_image_list);
+            }
+        });
 
         // 查询项目设置
         LambdaQueryWrapper<CrtDramaProjectSettings> wrapperSettings = new LambdaQueryWrapper<>();
@@ -62,12 +79,6 @@ public class CrtDramaProjectStoryboardServiceImpl implements CrtDramaProjectStor
         StoryboardResponse storyboardResponse = new StoryboardResponse();
         storyboardResponse.setDrama_project_id(project_id);
         storyboardResponse.setProject_name(crtDramaProject.getProject_name());
-
-        // 字段格式化
-        storyboardList = storyboardList.stream().map(storyboard -> {
-            storyboard.setDrama_project_storyboard_id(storyboard.getId());
-            return storyboard;
-        }).collect(Collectors.toList());
         storyboardResponse.setStoryboard_list(storyboardList);
 
         if (settingsList.size() > 0) {
@@ -104,11 +115,11 @@ public class CrtDramaProjectStoryboardServiceImpl implements CrtDramaProjectStor
         wrapperStoryboard.eq(CrtDramaProjectStoryboard::getEpisode_num, episode_num);
         if (sort == null) {
             Boolean is_exist_storyboard = crtDramaProjectStoryboardDao.exists(wrapperStoryboard);
-            if (is_exist_storyboard) throw new CustException("该项目集数已存在,请勿重复创建");
+            if (is_exist_storyboard) throw new CustException("集数或分镜已存在,请勿重复创建");
         } else {
             wrapperStoryboard.eq(CrtDramaProjectStoryboard::getSort, sort);
             Boolean is_exist_storyboard = crtDramaProjectStoryboardDao.exists(wrapperStoryboard);
-            if (is_exist_storyboard) throw new CustException("该项目集数已存在,请勿重复创建");
+            if (is_exist_storyboard) throw new CustException("集数或分镜已存在,请勿重复创建");
         }
 
         // [DB] 创建分镜
@@ -161,13 +172,16 @@ public class CrtDramaProjectStoryboardServiceImpl implements CrtDramaProjectStor
 
         // [DB] 查询分镜
         List<CrtDramaProjectStoryboard> storyboardList = crtDramaProjectStoryboardDao.selectList(wrapper);
-        if (storyboardList == null || storyboardList.size() == 0) throw new CustException("分镜不存在");
-        CrtDramaProjectStoryboard storyboardDetail = storyboardList.get(0);
+        if (storyboardList == null || storyboardList.isEmpty()) throw new CustException("分镜不存在");
+
+        // 删除或清空全部,需要有项目的所有者权限
+        CrtDramaProject dramaProjectDetail = crtDramaProjectDao.selectById(drama_project_id);
+        if (dramaProjectDetail == null) throw new CustException("项目不存在");
 
         // 是否 [分镜] 拥有者,权限:
         // - 编辑自己的 (需 36.2.6.1 权限)
         // - 编辑自己及他人的 (需要 36.2.6 权限或超级管理员)
-        Boolean isOwner = storyboardDetail.getUser_id() == SecurityUtil.getUserId();
+        Boolean isOwner = dramaProjectDetail.getUser_id() == SecurityUtil.getUserId();
         if (isOwner && !securityUtil.hasPermissions(Arrays.asList("36.2.6", "36.2.6.1"), MatchType.OR)) {
             throw new CustException(SecurityEnum.NOAUTH);
         }
@@ -178,10 +192,16 @@ public class CrtDramaProjectStoryboardServiceImpl implements CrtDramaProjectStor
 
         // [DB] 清空当前分集的所有分镜
         crtDramaProjectStoryboardDao.delete(wrapper);
+
+        // [DB/OBJ] 删除分镜下的生图内容 (批量删除 cos/tos)
+        storyboardList.forEach(storyboard -> {
+            deleteStoryboardImage(storyboard.getDrama_project_storyboard_id());
+        });
+
     }
 
     /**
-     * 清空分镜
+     * 清空全部分镜 (仅保留一个空分镜)
      */
     @Override
     @Transactional(rollbackFor = Exception.class)
@@ -225,4 +245,67 @@ public class CrtDramaProjectStoryboardServiceImpl implements CrtDramaProjectStor
         return resp;
     }
 
+    /**
+     * 删除单个分镜
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Map<String, Object> deleteStoryboardBySort(CrtDramaProjectStoryboard crtDramaProjectStoryboard) {
+
+        Long drama_project_storyboard_id = crtDramaProjectStoryboard.getDrama_project_storyboard_id();
+
+        LambdaQueryWrapper<CrtDramaProjectStoryboard> wrapperStoryboard = new LambdaQueryWrapper<>();
+        wrapperStoryboard.eq(CrtDramaProjectStoryboard::getDrama_project_storyboard_id, drama_project_storyboard_id);
+        CrtDramaProjectStoryboard storyboardDetail = crtDramaProjectStoryboardDao.selectOne(wrapperStoryboard);
+        if (storyboardDetail == null) throw new CustException("分镜不存在");
+
+        // 是否 [分镜] 拥有者,权限:
+        // - 编辑自己的 (需 36.2.6.1 权限)
+        // - 编辑自己及他人的 (需要 36.2.6 权限或超级管理员)
+        Boolean isOwner = storyboardDetail.getUser_id() == SecurityUtil.getUserId();
+        if (isOwner && !securityUtil.hasPermissions(Arrays.asList("36.2.6", "36.2.6.1"), MatchType.OR)) {
+            throw new CustException(SecurityEnum.NOAUTH);
+        }
+        if (!isOwner && !securityUtil.hasPermission("36.2.6")) {
+            throw new CustException(SecurityEnum.NOAUTH);
+        }
+        // ---------------------------------------------------------------------------
+
+        // [DB] 删除分镜
+        crtDramaProjectStoryboardDao.deleteById(drama_project_storyboard_id);
+
+        // [DB/OBJ] 删除分镜下的生图内容 (批量删除 cos/tos)
+        deleteStoryboardImage(drama_project_storyboard_id);
+
+        return Map.of("drama_project_storyboard_id", drama_project_storyboard_id);
+    }
+
+    /**
+     * [DB/OBJ] 删除分镜下的生图内容 (批量删除 cos/tos)
+     */
+    private void deleteStoryboardImage(Long drama_project_storyboard_id) {
+        LambdaQueryWrapper<CrtGenerateImage> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(CrtGenerateImage::getDrama_project_storyboard_id, drama_project_storyboard_id);
+        List<CrtGenerateImage> generateImageList = crtGenerateImageDao.selectList(wrapper);
+        if (generateImageList != null && !generateImageList.isEmpty()) {
+
+            // 获得生图内容的 object_keys
+            List<ObjectKey> object_keys = new ArrayList<>();
+            generateImageList.stream().forEach(item -> {
+                ObjectKey objectKey = new ObjectKey();
+                objectKey.setObject_key(item.getObject_key());
+                objectKey.setTarget(item.getTarget());
+                object_keys.add(objectKey);
+            });
+
+            CompletableFuture.runAsync(() -> {
+                // 批量删除生图记录
+                crtGenerateImageDao.delete(wrapper);
+                // 批量删除 ObjectKeys (By target)
+                objectKeyUtil.deleteObjects(object_keys);
+            });
+
+        }
+    }
+
 }

+ 115 - 438
src/main/java/com/backendsys/modules/crt/service/impl/CrtGenerateServiceImpl.java

@@ -2,25 +2,32 @@ package com.backendsys.modules.crt.service.impl;
 
 import cn.hutool.core.convert.Convert;
 import cn.hutool.core.date.DateUtil;
-import cn.hutool.json.JSONObject;
-import cn.hutool.json.JSONUtil;
 import com.backendsys.exception.CustException;
-import com.backendsys.modules.common.config.security.utils.SecurityUtil;
 import com.backendsys.modules.crt.dao.CrtDramaProjectSettingsDao;
 import com.backendsys.modules.crt.dao.CrtDramaProjectStoryboardDao;
+import com.backendsys.modules.crt.dao.CrtGenerateImageDao;
 import com.backendsys.modules.crt.dao.CrtModelDao;
 import com.backendsys.modules.crt.entity.CrtDramaProjectSettings;
 import com.backendsys.modules.crt.entity.CrtDramaProjectStoryboard;
 import com.backendsys.modules.crt.entity.CrtGenerateImage;
 import com.backendsys.modules.crt.entity.CrtModel;
+import com.backendsys.modules.crt.enums.AspectRatioEnums;
 import com.backendsys.modules.crt.enums.SamplerEnums;
 import com.backendsys.modules.crt.service.CrtGenerateService;
-import com.backendsys.modules.sdk.comfyui.entity.CFPromptResponse;
-import com.backendsys.modules.sdk.comfyui.entity.CFQueue;
-import com.backendsys.modules.sdk.comfyui.service.ComfyUIService;
-import com.backendsys.modules.sdk.comfyui.service.ComfyUISocketService;
+import com.backendsys.modules.sdk.comfyui.entity.ComfyuiResponse;
+import com.backendsys.modules.sdk.comfyui.entity.ComfyuiQueue;
+import com.backendsys.modules.sdk.comfyui.entity.ComfyuiText2Image;
+import com.backendsys.modules.sdk.comfyui.service.ComfyuiService;
+import com.backendsys.modules.sdk.comfyui.service.ComfyuiSocketService;
+import com.backendsys.modules.sdk.comfyui.service.ComfyuiText2ImageService;
+import com.backendsys.modules.upload.enums.StyleEnums;
+import com.backendsys.modules.upload.utils.UploadUtil;
+import com.backendsys.utils.response.PageEntity;
+import com.backendsys.utils.response.PageInfoResult;
+import com.backendsys.utils.v2.PageUtils;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
 import reactor.core.publisher.Mono;
 
@@ -28,14 +35,21 @@ import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.UUID;
+import java.util.stream.Collectors;
 
 @Service
 public class CrtGenerateServiceImpl implements CrtGenerateService {
 
+    @Value("${comfyui.is-save}")
+    private Boolean IS_SAVE;
+
+    @Autowired
+    private ComfyuiService comfyUIService;
     @Autowired
-    private ComfyUIService comfyUIService;
+    private ComfyuiSocketService comfyUISocketService;
+
     @Autowired
-    private ComfyUISocketService comfyUISocketService;
+    private ComfyuiText2ImageService comfyuiText2ImageService;
 
     @Autowired
     private CrtModelDao crtModelDao;
@@ -43,6 +57,8 @@ public class CrtGenerateServiceImpl implements CrtGenerateService {
     private CrtDramaProjectSettingsDao crtDramaProjectSettingsDao;
     @Autowired
     private CrtDramaProjectStoryboardDao crtDramaProjectStoryboardDao;
+    @Autowired
+    private CrtGenerateImageDao crtGenerateImageDao;
 
     /**
      * [ComfyUI] 查询任务队列
@@ -50,8 +66,8 @@ public class CrtGenerateServiceImpl implements CrtGenerateService {
     @Override
     public Map<String, Object> getQueue() {
         // [ComfyUI] 执行任务
-        Mono<CFQueue> cfQueueMono = comfyUIService.getQueue();
-        CFQueue response = cfQueueMono.block();
+        Mono<ComfyuiQueue> cfQueueMono = comfyUIService.getQueue();
+        ComfyuiQueue response = cfQueueMono.block();
         System.out.println("结果: " + response);
 
         Map<String, Object> resp = new LinkedHashMap<>();
@@ -70,469 +86,130 @@ public class CrtGenerateServiceImpl implements CrtGenerateService {
 
         CrtDramaProjectStoryboard storyboardDetail = crtDramaProjectStoryboardDao.selectById(drama_project_storyboard_id);
         if (storyboardDetail == null) throw new CustException("分镜不存在");
-        System.out.println("分镜详情: " + JSONUtil.toJsonStr(storyboardDetail));
 
         Long drama_project_id = storyboardDetail.getDrama_project_id();
         LambdaQueryWrapper<CrtDramaProjectSettings> wrapperSettings = new LambdaQueryWrapper<>();
         wrapperSettings.eq(CrtDramaProjectSettings::getDrama_project_id, drama_project_id);
         List<CrtDramaProjectSettings> settingsDetail = crtDramaProjectSettingsDao.selectList(wrapperSettings);
         if (settingsDetail == null) throw new CustException("项目配置不存在");
-        System.out.println("项目配置: " + JSONUtil.toJsonStr(settingsDetail));
 
         // 项目配置类型 (1:生图配置, 2:生视频配置)
         CrtDramaProjectSettings settings_image = settingsDetail.stream().filter(item -> item.getDrama_project_setting_type() == 1).findFirst().orElse(null);
-        CrtDramaProjectSettings settings_video = settingsDetail.stream().filter(item -> item.getDrama_project_setting_type() == 2).findFirst().orElse(null);
-        System.out.println("- 生图配置: " + JSONUtil.toJsonStr(settings_image));
-        System.out.println("- 生视频配置: " + JSONUtil.toJsonStr(settings_video));
+        // CrtDramaProjectSettings settings_video = settingsDetail.stream().filter(item -> item.getDrama_project_setting_type() == 2).findFirst().orElse(null);
 
 
-        // 文生图提示词
-        String text_to_image_prompt = crtDramaProjectStoryboard.getText_to_image_prompt();
-
-        // == 从 storboard detail 获取参数 =============================================================================
+        // == 从生图参数初始化 ===========================================================================================
+        String prompt_text = crtDramaProjectStoryboard.getText_to_image_prompt();                 // 文生图提示词
         Integer param_batch_size = storyboardDetail.getParam_batch_size();                        // 生成图片数量
         Float param_prompt_flux_guidance = storyboardDetail.getParam_prompt_flux_guidance();      // 提示词引导系数
         String param_sampler = SamplerEnums.getValueByKey(storyboardDetail.getParam_sampler());   // 采样器
-        Integer param_step = storyboardDetail.getParam_step();
+        Integer param_step = storyboardDetail.getParam_step();                                    // 步数
 
         // 随机种子 (默认值:1,范围:(1:随机, 2:自定义))
-        Integer param_seed = storyboardDetail.getParam_seed();
-        String param_seed_custom = storyboardDetail.getParam_seed_custom();
-        if (param_seed == 1) {
-            param_seed_custom = Convert.toStr(DateUtil.current());   // 生成一个随机值 (毫秒时间戳)
-
-            // 【随机值,要保存到数据库】
-            CrtDramaProjectStoryboard entity = new CrtDramaProjectStoryboard();
-            entity.setParam_seed_custom(param_seed_custom);
-            LambdaQueryWrapper<CrtDramaProjectStoryboard> wrapper = new LambdaQueryWrapper<>();
-            wrapper.eq(CrtDramaProjectStoryboard::getId, drama_project_storyboard_id);
-            crtDramaProjectStoryboardDao.update(entity, wrapper);
-
-        }
-
+        Integer param_seed_type = storyboardDetail.getParam_seed_type();
+        String param_seed = (param_seed_type == 1) ?
+                Convert.toStr(DateUtil.current()) :
+                storyboardDetail.getParam_seed();
 
+        // 画面比例 (从项目配置)(16:9 - 1280*720, 9:16 - 720*1280, 1:1 - 1024*1024)
+        AspectRatioEnums aspect_ratio_option = AspectRatioEnums.of(settings_image.getAspect_ratio());
+        Integer width = aspect_ratio_option.getWidth();     // 1280
+        Integer height = aspect_ratio_option.getHeight();   // 720
 
-        // [db] 获取基础模型 (从项目配置)
+        // 获取基础模型 (从项目配置)
         Long model_id = settings_image.getModel_id();
         CrtModel modelDetail = crtModelDao.selectById(model_id);
         String model_name = modelDetail.getModel_name();
+        // =========================================================================================================
 
 
-        // 风格 Lora?
-        // 风格 Lora (阙值)?
+        // [DB] 更新分集详情记录 (更新种子值、提示词)
+        CrtDramaProjectStoryboard entity = new CrtDramaProjectStoryboard();
+        entity.setParam_seed(param_seed);
+        entity.setText_to_image_prompt(prompt_text);
+        LambdaQueryWrapper<CrtDramaProjectStoryboard> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(CrtDramaProjectStoryboard::getId, drama_project_storyboard_id);
+        crtDramaProjectStoryboardDao.update(entity, wrapper);
+
+        // 风格 Lora 待定
+        // 风格 Lora (阙值)待定
 
 
         // ===========================================================================================================
 
+        // -- [ComfyUI] 生图参数 ---------------------------------------------------
+        ComfyuiText2Image comfyuiText2Image = new ComfyuiText2Image();
+        comfyuiText2Image.setBatch_size(param_batch_size);
+        comfyuiText2Image.setPrompt_flux_guidance(param_prompt_flux_guidance);
+        comfyuiText2Image.setSampler(param_sampler);
+        comfyuiText2Image.setStep(param_step);
+        comfyuiText2Image.setWidth(width);
+        comfyuiText2Image.setHeight(height);
+        comfyuiText2Image.setModel_name(model_name);
+        comfyuiText2Image.setPrompt_text(prompt_text);
+        comfyuiText2Image.setSeed(param_seed);
 
+        String client_id = Convert.toStr(UUID.randomUUID());
 
+        // -- [ComfyUI] 发起生图任务 -----------------------------------------------
+        Long task_id = comfyuiText2ImageService.generateText2Image(client_id, comfyuiText2Image);
 
-        // -- 前端生成的UUID ---------------------------------------------
-        String client_id = Convert.toStr(UUID.randomUUID());
 
-        // -- [ComfyUI] 创建 WebSocket 监听连接 --------------------------
-        Map<String, Object> params = new LinkedHashMap<>();
-        params.put("drama_project_storyboard_id", drama_project_storyboard_id);
-        comfyUISocketService.connectToSse(client_id, 8000, params).subscribe();
-
-        // -------------------------------------------------------------
-        // [ComfyUI-基础生图]
-//        String prompt = "{" +
-//                    "\"3\": {" +
-//                        "\"inputs\": {" +
-//                            "\"seed\": 449753344472378," +
-//                            "\"steps\": 20," +
-//                            "\"cfg\": 8," +
-//                            "\"sampler_name\": \"euler\"," +
-//                            "\"scheduler\": \"normal\"," +
-//                            "\"denoise\": 1," +
-//                            "\"model\": [\"4\", 0]," +
-//                            "\"positive\": [\"6\", 0]," +
-//                            "\"negative\": [\"7\", 0]," +
-//                            "\"latent_image\": [\"5\", 0]" +
-//                        "}," +
-//                        "\"class_type\": \"KSampler\"," +
-//                        "\"_meta\": { \"title\": \"K采样器\" }" +
-//                    "}," +
-//                    "\"4\": {" +
-//                        "\"inputs\": {" +
-//                            "\"ckpt_name\": \"v1-5-pruned-emaonly-fp16.safetensors\"" +
-//                        "}," +
-//                        "\"class_type\": \"CheckpointLoaderSimple\"," +
-//                        "\"_meta\": { \"title\": \"Checkpoint加载器(简易)\" }" +
-//                    "}," +
-//                    "\"5\": {" +
-//                        "\"inputs\": {" +
-//                            "\"width\": 512, \"height\": 512, \"batch_size\": 1" +
-//                        "}," +
-//                        "\"class_type\": \"EmptyLatentImage\"," +
-//                        "\"_meta\": { \"title\": \"空Latent图像\" }" +
-//                    "}," +
-//                    "\"6\": {" +
-//                        "\"inputs\": {" +
-//                            "\"text\": \"beautiful scenery nature glass bottle landscape, , purple galaxy bottle,\"," +
-//                            "\"speak_and_recognation\": {" +
-//                                "\"__value__\": [false, true]" +
-//                            "}," +
-//                            "\"clip\": [\"4\", 1]" +
-//                        "}," +
-//                        "\"class_type\": \"CLIPTextEncode\"," +
-//                        "\"_meta\": { \"title\": \"CLIP文本编码\" }" +
-//                    "}," +
-//                    "\"7\": {" +
-//                        "\"inputs\": {" +
-//                            "\"text\": \"text, watermark\"," +
-//                            "\"speak_and_recognation\": {" +
-//                                "\"__value__\": [false, true]" +
-//                            "}," +
-//                            "\"clip\": [\"4\", 1]" +
-//                        "}," +
-//                        "\"class_type\": \"CLIPTextEncode\"," +
-//                        "\"_meta\": { \"title\": \"CLIP文本编码\" }" +
-//                    "}," +
-//                    "\"8\": {" +
-//                        "\"inputs\": {" +
-//                            "\"samples\": [\"3\", 0]," +
-//                            "\"vae\": [\"4\", 2]," +
-//                        "}," +
-//                        "\"class_type\": \"VAEDecode\"," +
-//                        "\"_meta\": { \"title\": \"VAE解码\" }" +
-//                    "}," +
-//                    "\"9\": {" +
-//                        "\"inputs\": {" +
-//                            "\"filename_prefix\": \"ComfyUI\"," +
-//                            "\"images\": [\"8\", 0]" +
-//                        "}," +
-//                        "\"class_type\": \"SaveImage\"," +
-//                        "\"_meta\": { \"title\": \"保存图像\" }" +
-//                    "}," +
-//                "}";
-
-        // ComfyUI - 7.4生图.json
-        String prompt = "{"+
-                "  \"3\": {"+
-                "    \"inputs\": {"+
-                "      \"text\": ["+
-                "        \"120\","+
-                "        0"+
-                "      ],"+
-                "      \"speak_and_recognation\": {"+
-                "        \"__value__\": ["+
-                "          false,"+
-                "          true"+
-                "        ]"+
-                "      },"+
-                "      \"clip\": ["+
-                "        \"151\","+
-                "        1"+
-                "      ]"+
-                "    },"+
-                "    \"class_type\": \"CLIPTextEncode\","+
-                "    \"_meta\": {"+
-                "      \"title\": \"CLIP文本编码\""+
-                "    }"+
-                "  },"+
-                "  \"13\": {"+
-                "    \"inputs\": {"+
-                "      \"guidance\": " + param_prompt_flux_guidance + ","+
-                "      \"conditioning\": ["+
-                "        \"3\","+
-                "        0"+
-                "      ]"+
-                "    },"+
-                "    \"class_type\": \"FluxGuidance\","+
-                "    \"_meta\": {"+
-                "      \"title\": \"Flux引导\""+
-                "    }"+
-                "  },"+
-                "  \"15\": {"+
-                "    \"inputs\": {"+
-                "      \"model\": ["+
-                "        \"152\","+
-                "        0"+
-                "      ],"+
-                "      \"conditioning\": ["+
-                "        \"13\","+
-                "        0"+
-                "      ]"+
-                "    },"+
-                "    \"class_type\": \"BasicGuider\","+
-                "    \"_meta\": {"+
-                "      \"title\": \"基本引导器\""+
-                "    }"+
-                "  },"+
-                "  \"31\": {"+
-                "    \"inputs\": {"+
-                "      \"samples\": ["+
-                "        \"32\","+
-                "        0"+
-                "      ],"+
-                "      \"vae\": ["+
-                "        \"151\","+
-                "        2"+
-                "      ]"+
-                "    },"+
-                "    \"class_type\": \"VAEDecode\","+
-                "    \"_meta\": {"+
-                "      \"title\": \"VAE解码\""+
-                "    }"+
-                "  },"+
-                "  \"32\": {"+
-                "    \"inputs\": {"+
-                "      \"noise\": ["+
-                "        \"37\","+
-                "        0"+
-                "      ],"+
-                "      \"guider\": ["+
-                "        \"15\","+
-                "        0"+
-                "      ],"+
-                "      \"sampler\": ["+
-                "        \"84\","+
-                "        0"+
-                "      ],"+
-                "      \"sigmas\": ["+
-                "        \"150\","+
-                "        0"+
-                "      ],"+
-                "      \"latent_image\": ["+
-                "        \"90\","+
-                "        0"+
-                "      ]"+
-                "    },"+
-                "    \"class_type\": \"SamplerCustomAdvanced\","+
-                "    \"_meta\": {"+
-                "      \"title\": \"自定义采样器(高级)\""+
-                "    }"+
-                "  },"+
-                "  \"37\": {"+
-                "    \"inputs\": {"+
-                "      \"noise_seed\": " + param_seed_custom +
-                "    },"+
-                "    \"class_type\": \"RandomNoise\","+
-                "    \"_meta\": {"+
-                "      \"title\": \"随机噪波\""+
-                "    }"+
-                "  },"+
-                "  \"38\": {"+
-                "    \"inputs\": {"+
-                "      \"text\": \"\","+
-                "      \"speak_and_recognation\": {"+
-                "        \"__value__\": ["+
-                "          false,"+
-                "          true"+
-                "        ]"+
-                "      }"+
-                "    },"+
-                "    \"class_type\": \"TextInput_\","+
-                "    \"_meta\": {"+
-                "      \"title\": \"触发词\""+
-                "    }"+
-                "  },"+
-                "  \"51\": {"+
-                "    \"inputs\": {"+
-                "      \"text\": \"fenjing_" + drama_project_storyboard_id + "\","+
-                "      \"speak_and_recognation\": {"+
-                "        \"__value__\": ["+
-                "          false,"+
-                "          true"+
-                "        ]"+
-                "      }"+
-                "    },"+
-                "    \"class_type\": \"TextInput_\","+
-                "    \"_meta\": {"+
-                "      \"title\": \"分镜号\""+
-                "    }"+
-                "  },"+
-                "  \"84\": {"+
-                "    \"inputs\": {"+
-                "      \"sampler_name\": \"" + param_sampler + "\""+
-                "    },"+
-                "    \"class_type\": \"KSamplerSelect\","+
-                "    \"_meta\": {"+
-                "      \"title\": \"K采样器选择\""+
-                "    }"+
-                "  },"+
-                "  \"90\": {"+
-                "    \"inputs\": {"+
-                "      \"width\": ["+
-                "        \"121\","+
-                "        0"+
-                "      ],"+
-                "      \"height\": ["+
-                "        \"122\","+
-                "        0"+
-                "      ],"+
-                "      \"batch_size\": " + param_batch_size +
-                "    },"+
-                "    \"class_type\": \"EmptySD3LatentImage\","+
-                "    \"_meta\": {"+
-                "      \"title\": \"空Latent图像(SD3)\""+
-                "    }"+
-                "  },"+
-                "  \"91\": {"+
-                "    \"inputs\": {"+
-                "      \"filename_prefix\": ["+
-                "        \"51\","+
-                "        0"+
-                "      ],"+
-                "      \"images\": ["+
-                "        \"31\","+
-                "        0"+
-                "      ]"+
-                "    },"+
-                "    \"class_type\": \"SaveImage\","+
-                "    \"_meta\": {"+
-                "      \"title\": \"保存图像\""+
-                "    }"+
-                "  },"+
-                "  \"120\": {"+
-                "    \"inputs\": {"+
-                "      \"text_0\": \", a pig\","+
-                "      \"text\": ["+
-                "        \"148\","+
-                "        0"+
-                "      ]"+
-                "    },"+
-                "    \"class_type\": \"PandasShowText\","+
-                "    \"_meta\": {"+
-                "      \"title\": \"Pandas Show Text\""+
-                "    }"+
-                "  },"+
-                "  \"121\": {"+
-                "    \"inputs\": {"+
-                "      \"value\": 1280"+
-                "    },"+
-                "    \"class_type\": \"easy int\","+
-                "    \"_meta\": {"+
-                "      \"title\": \"整数\""+
-                "    }"+
-                "  },"+
-                "  \"122\": {"+
-                "    \"inputs\": {"+
-                "      \"value\": 720"+
-                "    },"+
-                "    \"class_type\": \"easy int\","+
-                "    \"_meta\": {"+
-                "      \"title\": \"整数\""+
-                "    }"+
-                "  },"+
-                "  \"146\": {"+
-                "    \"inputs\": {"+
-                "      \"from_translate\": \"auto\","+
-                "      \"to_translate\": \"english\","+
-                "      \"add_proxies\": false,"+
-                "      \"proxies\": \"\","+
-                "      \"auth_data\": \"\","+
-                "      \"service\": \"BaiduTranslator [appid and appkey]\","+
-                "      \"text\": ["+
-                "        \"153\","+
-                "        0"+
-                "      ],"+
-                "      \"Show proxy\": \"proxy_hide\","+
-                "      \"Show authorization\": \"authorization_hide\","+
-                "      \"speak_and_recognation\": {"+
-                "        \"__value__\": ["+
-                "          false,"+
-                "          true"+
-                "        ]"+
-                "      }"+
-                "    },"+
-                "    \"class_type\": \"DeepTranslatorTextNode\","+
-                "    \"_meta\": {"+
-                "      \"title\": \"Deep Translator Text Node\""+
-                "    }"+
-                "  },"+
-                "  \"148\": {"+
-                "    \"inputs\": {"+
-                "      \"delimiter\": \", \","+
-                "      \"string1\": ["+
-                "        \"38\","+
-                "        0"+
-                "      ],"+
-                "      \"string2\": ["+
-                "        \"146\","+
-                "        0"+
-                "      ]"+
-                "    },"+
-                "    \"class_type\": \"JoinStrings\","+
-                "    \"_meta\": {"+
-                "      \"title\": \"Join Strings\""+
-                "    }"+
-                "  },"+
-                "  \"150\": {"+
-                "    \"inputs\": {"+
-                "      \"scheduler\": \"normal\","+
-                "      \"steps\": " + param_step + ","+
-                "      \"denoise\": 1,"+
-                "      \"model\": ["+
-                "        \"152\","+
-                "        0"+
-                "      ]"+
-                "    },"+
-                "    \"class_type\": \"BasicScheduler\","+
-                "    \"_meta\": {"+
-                "      \"title\": \"基本调度器\""+
-                "    }"+
-                "  },"+
-                "  \"151\": {"+
-                "    \"inputs\": {"+
-                "      \"ckpt_name\": \"" + model_name + "\""+
-                "    },"+
-                "    \"class_type\": \"CheckpointLoaderSimple\","+
-                "    \"_meta\": {"+
-                "      \"title\": \"Checkpoint加载器(简易)\""+
-                "    }"+
-                "  },"+
-                "  \"152\": {"+
-                "    \"inputs\": {"+
-                "      \"lora_name\": \"【FLUX】都市高武推文 _ 异能 法术 战斗_v1.0.safetensors\","+
-                "      \"strength_model\": 0,"+
-                "      \"model\": ["+
-                "        \"151\","+
-                "        0"+
-                "      ]"+
-                "    },"+
-                "    \"class_type\": \"LoraLoaderModelOnly\","+
-                "    \"_meta\": {"+
-                "      \"title\": \"LoRA加载器(仅模型)\""+
-                "    }"+
-                "  },"+
-                "  \"153\": {"+
-                "    \"inputs\": {"+
-                "      \"text\": \"" + text_to_image_prompt + "\","+
-                "      \"speak_and_recognation\": {"+
-                "        \"__value__\": ["+
-                "          false,"+
-                "          true"+
-                "        ]"+
-                "      }"+
-                "    },"+
-                "    \"class_type\": \"TextInput_\","+
-                "    \"_meta\": {"+
-                "      \"title\": \"Text Input ♾️Mixlab\""+
-                "    }"+
-                "  }"+
-                "}"
-        ;
-
-
-        // -------------------------------------------------------------
-
-        JSONObject prompt_object = JSONUtil.parseObj(prompt);
-        System.out.println("prompt_object: " + prompt_object);
 
-        // [ComfyUI] 执行任务
-        Mono<CFPromptResponse> cfPromptResponseMono = comfyUIService.prompt(client_id, prompt_object);
-        CFPromptResponse response = cfPromptResponseMono.block();
-        response.setClient_id(client_id);
-        System.out.println("结果: " + response);
-        // 结果: CFPromptResponse(client_id=1a8a2d01-5500-437f-bb11-7a986130da48, prompt_id=c74501ed-6755-48f8-a440-aef3474b523c, number=47, node_errors={}, error=null)
+
+//        // -- [ComfyUI] 创建 WebSocket 监听进度 --------------------------
+//        Map<String, Object> params = new LinkedHashMap<>();
+//        params.put("drama_project_storyboard_id", drama_project_storyboard_id);
+//        // - is_save: 是否转存到 cos/tos
+//        comfyUISocketService.connectToSse(client_id, 8000, IS_SAVE, params).subscribe();
+
+        // -- [ComfyUI] 执行/排队生图任务 --------------------------------------------
+
+
+
+
+
+//        ComfyuiResponse response = null; // comfyuiText2ImageService.generateText2Image(client_id, comfyuiText2Image);
+//        System.out.println("结果: " + response);
+//        // 结果: ComfyuiResponse(client_id=1a8a2d01-5500-437f-bb11-7a986130da48, prompt_id=c74501ed-6755-48f8-a440-aef3474b523c, number=47, node_errors={}, error=null)
 
         Map<String, Object> resp = new LinkedHashMap<>();
         resp.put("drama_project_storyboard_id", drama_project_storyboard_id);
-        resp.put("response", response);
+        resp.put("client_id", client_id);
+        resp.put("task_id", task_id);
+//        resp.put("response", response);
         return resp;
     }
 
+    // 短剧创作-获取分集生图记录
+    @Override
+    public PageEntity selectGenerateImage(CrtDramaProjectStoryboard crtDramaProjectStoryboard) {
+        PageUtils.startPage();  // 分页
+
+        Long drama_project_storyboard_id = crtDramaProjectStoryboard.getDrama_project_storyboard_id();
+
+        LambdaQueryWrapper<CrtGenerateImage> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(CrtGenerateImage::getDrama_project_storyboard_id, drama_project_storyboard_id);
+        wrapper.orderByDesc(CrtGenerateImage::getCreate_time);
+        List<CrtGenerateImage> list = crtGenerateImageDao.selectList(wrapper);
+
+        // 1) 完成分页实体渲染
+        PageEntity pageEntity = new PageInfoResult(list).toEntity();
+
+        // 加入缩略图
+        if (!list.isEmpty()) {
+            // 2) 分页列表格式化
+            list = list.stream().map(item -> {
+                item.setUrl_thumb(UploadUtil.getImageThumbUrl(item.getUrl(), item.getTarget(), 100, null, StyleEnums.THUMB_BACKGROUND.getValue()));
+                return item;
+            }).collect(Collectors.toList());
+
+            // 3) 分页实体重新赋值
+            List<Object> objectList = list.stream().map(item -> (Object) item).collect(Collectors.toList());
+            pageEntity.setList(objectList);
+        }
+
+        return pageEntity;
+    }
+
 }

+ 51 - 51
src/main/java/com/backendsys/modules/queue/controller/QueueController.java

@@ -1,51 +1,51 @@
-package com.backendsys.modules.queue.controller;
-
-import com.backendsys.modules.common.config.security.annotations.Anonymous;
-import com.backendsys.modules.queue.entity.QueuePosition;
-import com.backendsys.modules.queue.entity.QueueRequest;
-import com.backendsys.modules.queue.service.QueueService;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.*;
-
-@RestController
-public class QueueController {
-
-    @Autowired
-    private QueueService queueService;
-
-    /**
-     * 提交请求并加入队列
-     */
-    @Anonymous
-    @PostMapping("/api/queue/submit")
-    public String submitRequest() {
-        QueueRequest request = new QueueRequest();
-        int position = queueService.enqueue("taskQueue", request);
-        return "Your request has been submitted. You are at position " + position + " in the queue. request id: " + request.getId();
-    }
-
-    /**
-     * 启动处理队列中的请求
-     */
-    @Anonymous
-    @GetMapping("/api/queue/startProcessing")
-    public String startProcessing() {
-        queueService.startProcessing("taskQueue");
-        return "Processing has started.";
-    }
-
-    /**
-     * 查询队列
-     */
-    @Anonymous
-    @GetMapping("/api/queue/position")
-    public String getPosition(@RequestParam String requestId) {
-        QueuePosition positionInfo = queueService.getPosition("taskQueue", requestId);
-        if (positionInfo.getPosition() == -1) {
-            return "Request not found.";
-        } else {
-            return "Your request is at position " + positionInfo.getPosition() + " out of " + positionInfo.getTotal() + " in the queue.";
-        }
-    }
-
-}
+//package com.backendsys.modules.queue.controller;
+//
+//import com.backendsys.modules.common.config.security.annotations.Anonymous;
+//import com.backendsys.modules.queue.entity.QueuePosition;
+//import com.backendsys.modules.queue.entity.QueueRequest;
+//import com.backendsys.modules.queue.service.QueueService;
+//import org.springframework.beans.factory.annotation.Autowired;
+//import org.springframework.web.bind.annotation.*;
+//
+//@RestController
+//public class QueueController {
+//
+//    @Autowired
+//    private QueueService queueService;
+//
+//    /**
+//     * 提交请求并加入队列
+//     */
+//    @Anonymous
+//    @PostMapping("/api/queue/submit")
+//    public String submitRequest() {
+//        QueueRequest request = new QueueRequest();
+//        int position = queueService.enqueue("taskQueue", request);
+//        return "Your request has been submitted. You are at position " + position + " in the queue. request id: " + request.getId();
+//    }
+//
+//    /**
+//     * 启动处理队列中的请求
+//     */
+//    @Anonymous
+//    @GetMapping("/api/queue/startProcessing")
+//    public String startProcessing() {
+//        queueService.startProcessing("taskQueue");
+//        return "Processing has started.";
+//    }
+//
+//    /**
+//     * 查询队列
+//     */
+//    @Anonymous
+//    @GetMapping("/api/queue/position")
+//    public String getPosition(@RequestParam String requestId) {
+//        QueuePosition positionInfo = queueService.getPosition("taskQueue", requestId);
+//        if (positionInfo.getPosition() == -1) {
+//            return "Request not found.";
+//        } else {
+//            return "Your request is at position " + positionInfo.getPosition() + " out of " + positionInfo.getTotal() + " in the queue.";
+//        }
+//    }
+//
+//}

+ 95 - 0
src/main/java/com/backendsys/modules/queue/controller/TaskStatusController.java

@@ -0,0 +1,95 @@
+//package com.backendsys.modules.queue.controller;
+//
+//import cn.hutool.core.convert.Convert;
+//import com.backendsys.modules.common.config.security.annotations.Anonymous;
+//import com.backendsys.modules.common.utils.Result;
+//import com.backendsys.modules.queue.entity.Entire;
+//import com.backendsys.modules.queue.service.TaskService;
+//import com.backendsys.modules.sdk.comfyui.enums.TaskStatusEnums;
+//import io.swagger.v3.oas.annotations.Operation;
+//import jakarta.annotation.PostConstruct;
+//import org.springframework.beans.factory.annotation.Autowired;
+//import org.springframework.beans.factory.annotation.Value;
+//import org.springframework.data.redis.connection.stream.MapRecord;
+//import org.springframework.data.redis.connection.stream.PendingMessages;
+//import org.springframework.data.redis.connection.stream.RecordId;
+//import org.springframework.web.bind.annotation.GetMapping;
+//import org.springframework.web.bind.annotation.RestController;
+//
+//import java.util.LinkedHashMap;
+//import java.util.List;
+//import java.util.Map;
+//import java.util.UUID;
+//
+//@RestController
+//public class TaskStatusController {
+//
+//    @Autowired
+//    private TaskService taskService;
+//
+//    @Value("${comfyui.queue-key}")
+//    private String QUEUE_KEY;
+//    @Value("${spring.application.name}")
+//    private String APPLICATION_NAME;
+//
+//
+//    @PostConstruct
+//    @Operation(summary = "初始化消费者组")
+//    public void initConsumerGroup() {
+//        String stream_key = APPLICATION_NAME + ":" + QUEUE_KEY;
+//        String group_name = stream_key + ":group";
+//        taskService.initConsumerGroup(stream_key, group_name);
+//    }
+//
+//    @Operation(summary = "添加任务")
+//    @GetMapping("/api/tasks/addTask")
+//    public Result addTask() {
+//        Map<String, Object> data = new LinkedHashMap<>();
+//        data.put("progress", 0);
+//        data.put("status", TaskStatusEnums.EXECUTION_START.getValue());
+//
+//        // 加入队列
+//        String stream_key = APPLICATION_NAME + ":" + QUEUE_KEY;
+//        RecordId record_id = taskService.addTask(stream_key, data);
+//        data.put("task_id", record_id.getValue());
+//
+//        return Result.success().put("data", data);
+//    }
+//
+//    @Anonymous
+//    @Operation(summary = "消费任务")
+//    @GetMapping("/api/tasks/consumeTask")
+//    public Result consumeTask(String task_id) {
+//        String stream_key = APPLICATION_NAME + ":" + QUEUE_KEY;
+//        taskService.consumeTask(stream_key, task_id);
+//        return Result.success();
+//    }
+//
+//    @Anonymous
+//    @Operation(summary = "移除任务")
+//    @GetMapping("/api/tasks/removeTask")
+//    public Result removeTask(String task_id) {
+//        String stream_key = APPLICATION_NAME + ":" + QUEUE_KEY;
+//        taskService.removeTask(stream_key, task_id);
+//        return Result.success();
+//    }
+//
+//    @Anonymous
+//    @Operation(summary = "获取任务列表")
+//    @GetMapping("/api/tasks/getTaskList")
+//    public Result getTaskList() {
+//        String stream_key = APPLICATION_NAME + ":" + QUEUE_KEY;
+//        List<MapRecord<String, String, String>> task_list = taskService.getTaskList(stream_key);
+//        return Result.success().put("data", task_list);
+//    }
+//
+//    @Anonymous
+//    @Operation(summary = "获取任务列表(未消费)")
+//    @GetMapping("/api/tasks/getTaskPaddingList")
+//    public Result getTaskPaddingList() {
+//        String stream_key = APPLICATION_NAME + ":" + QUEUE_KEY;
+//        List<MapRecord<String, String, String>> task_list = taskService.getTaskPaddingList(stream_key);
+//        return Result.success().put("data", task_list);
+//    }
+//
+//}

+ 9 - 0
src/main/java/com/backendsys/modules/queue/entity/Entire.java

@@ -0,0 +1,9 @@
+package com.backendsys.modules.queue.entity;
+
+import lombok.Data;
+
+@Data
+public class Entire {
+    String key;
+    Object value;
+}

+ 10 - 0
src/main/java/com/backendsys/modules/queue/entity/GenerateRequest.java

@@ -0,0 +1,10 @@
+package com.backendsys.modules.queue.entity;
+
+import lombok.Data;
+
+@Data
+public class GenerateRequest {
+    private String prompt;
+    private String userId;
+    private String model = "stable-diffusion-v2.1";              // 默认模型
+}

+ 17 - 14
src/main/java/com/backendsys/modules/queue/service/QueueService.java

@@ -14,6 +14,7 @@ public class QueueService {
     private final StringRedisTemplate redisTemplate;
     private final AtomicInteger counter = new AtomicInteger(0);
 
+
     @Autowired
     public QueueService(StringRedisTemplate redisTemplate) {
         this.redisTemplate = redisTemplate;
@@ -39,20 +40,22 @@ public class QueueService {
      * 开始排队
      */
     public void startProcessing(String queueKey) {
-        new Thread(() -> {
-            while (true) {
-                // 从有序集合中取出第一个请求
-                String requestId = Convert.toStr(redisTemplate.opsForZSet().popMin(queueKey));
-                if (requestId != null) {
-                    QueueRequest queueRequest = getRequestById(requestId);
-                    int position = queueRequest.getPosition();
-                    // 处理请求
-                    processRequest(queueRequest);
-                    // 可以通知用户处理完成
-                    notifyUser(queueRequest, position);
-                }
-            }
-        }).start();
+//        new Thread(() -> {
+//            while (true) {
+//                // 从有序集合中取出第一个请求
+//                String requestId = Convert.toStr(redisTemplate.opsForZSet().popMin(queueKey));
+//                if (requestId != null) {
+//                    QueueRequest queueRequest = getRequestById(requestId);
+//                    int position = queueRequest.getPosition();
+//                    // 处理请求
+//                    processRequest(queueRequest);
+//                    // 可以通知用户处理完成
+//                    notifyUser(queueRequest, position);
+//                }
+//            }
+//        }).start();
+        String requestId = Convert.toStr(redisTemplate.opsForZSet().popMin(queueKey));
+        System.out.println("requestId = " + requestId);
     }
 
     private void processRequest(QueueRequest queueRequest) {

+ 35 - 0
src/main/java/com/backendsys/modules/queue/service/TaskService.java

@@ -0,0 +1,35 @@
+package com.backendsys.modules.queue.service;
+
+import com.backendsys.modules.queue.entity.Entire;
+import org.springframework.data.redis.connection.stream.MapRecord;
+import org.springframework.data.redis.connection.stream.PendingMessages;
+import org.springframework.data.redis.connection.stream.RecordId;
+
+import java.util.List;
+import java.util.Map;
+
+public interface TaskService {
+
+    // 初始化消费者组
+    void initConsumerGroup(String stream_key, String group_name);
+
+    // 获取任务队列
+    List<MapRecord<String, String, String>> getTaskList(String stream_key);
+    // 获取任务队列 (未消费)
+    List<MapRecord<String, String, String>> getTaskPaddingList(String stream_key);
+
+    // 添加任务
+    RecordId addTask(String stream_key, Map<String, Object> task_data);
+    // 移除任务
+    void removeTask(String stream_key, String task_id);
+
+    // 消费任务
+    void consumeTask(String stream_key, String task_id);
+
+//    // 消费任务
+//    void consumeTask(String stream_key);
+//    // 获取任务队列
+//    List<MapRecord<String, String, String>> getTaskList(String stream_key);
+//    List<MapRecord<String, String, String>> getTaskList(String stream_key, Entire entire);
+
+}

+ 199 - 0
src/main/java/com/backendsys/modules/queue/service/impl/TaskServiceImpl_bak.java

@@ -0,0 +1,199 @@
+package com.backendsys.modules.queue.service.impl;
+
+import com.backendsys.modules.queue.service.TaskService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Range;
+import org.springframework.data.redis.RedisSystemException;
+import org.springframework.data.redis.connection.Limit;
+import org.springframework.data.redis.connection.stream.*;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 任务队列服务
+ */
+@Service
+public class TaskServiceImpl_bak implements TaskService {
+
+    @Autowired
+    private RedisTemplate redisTemplate;
+
+    /**
+     * 初始化消费者组
+     */
+    public void initConsumerGroup(String stream_key, String group_name) {
+        System.out.println("initConsumerGroup: [stream_key = " + stream_key + ", group_name = " + group_name + "]");
+        try {
+            redisTemplate.opsForStream().createGroup(stream_key, group_name);
+        } catch (RedisSystemException e) {
+            if (!e.getCause().getMessage().contains("BUSYGROUP")) {
+                throw e; // 忽略"组已存在"错误
+            }
+        }
+    }
+
+    /**
+     * 获取任务队列 (显示前5条)
+     */
+    @Override
+    public List<MapRecord<String, String, String>> getTaskList(String stream_key) {
+        return redisTemplate.opsForStream()
+                .reverseRange(stream_key, Range.unbounded(), Limit.limit().count(5));
+    }
+
+
+
+
+
+    /**
+     * 获取任务队列 (未消费)
+     */
+    @Override
+    public List<MapRecord<String, String, String>> getTaskPaddingList(String stream_key) {
+        String group_name = stream_key + ":group";
+
+//        /* 1. 已读未 ACK 的 Pending 消息 */
+//        List<MapRecord<String, String, String>> pendingRecords = Collections.emptyList();
+//        PendingMessagesSummary summary = redisTemplate.opsForStream().pending(stream_key, group_name);
+//        System.out.println("total = " + summary.getTotalPendingMessages());
+//        if (summary.getTotalPendingMessages() > 0) {
+//            Range<String> idRange = summary.getIdRange();
+//            System.out.println("idRange = " + idRange);
+//            pendingRecords = redisTemplate.opsForStream().range(stream_key, idRange);
+//            System.out.println("pendingRecords = " + pendingRecords);
+//        }
+//        return pendingRecords;
+
+        StreamInfo.XInfoGroups groups = redisTemplate.opsForStream().groups(stream_key);
+        System.out.println("groups = " + groups);
+
+        String lastDelivered = groups.stream()
+                .filter(g -> group_name.equals(g.groupName()))
+                .findFirst()
+                .map(StreamInfo.XInfoGroup::lastDeliveredId)
+                .orElse("0-0");
+        System.out.println("lastDelivered = " + lastDelivered);
+
+        List<MapRecord<String, String, String>> unread =
+                redisTemplate.opsForStream()
+                        .read(Consumer.from(group_name, "my-consumer"),
+                                StreamOffset.create(stream_key,
+                                        ReadOffset.from(lastDelivered)));
+        System.out.println("unread = " + unread);
+        return unread;
+
+//        PendingMessagesSummary summary = redisTemplate.opsForStream().pending(stream_key, group_name);
+//        Range<String> idRange = summary.getIdRange();
+//
+//        long total = summary.getTotalPendingMessages();
+//        String minId = idRange.getLowerBound().getValue().orElse(null);
+//        String maxId = idRange.getUpperBound().getValue().orElse(null);
+//        System.out.println("total = " + total + ", minId: " + minId + ", maxId: " + maxId);
+//
+//        // 用得到的范围再去查询完整消息
+//        List<MapRecord<String, String, String>> records = redisTemplate.opsForStream().range(stream_key, idRange);
+//        return records;
+
+    }
+
+    /**
+     * 消费任务 (不移除)
+     */
+    @Override
+    public void consumeTask(String stream_key, String task_id) {
+        String group_name = stream_key + ":group";
+
+        // 用组里任意消费者读一条(不 ACK)
+        List<MapRecord<String, String, String>> records = redisTemplate.opsForStream()
+                .read(Consumer.from(group_name, "my-consumer"), StreamOffset.create(stream_key, ReadOffset.from(">")));
+        System.out.println("records = " + records);
+
+
+//        Long acknowledged = redisTemplate.opsForStream().acknowledge(stream_key, stream_key + ":group", task_id);
+//        System.out.println("acknowledged = " + acknowledged);
+    }
+
+
+
+
+
+
+    /**
+     * 添加任务
+     */
+    @Override
+    public RecordId addTask(String stream_key, Map<String, Object> task_data) {
+        ObjectRecord<String, Map<String, Object>> task = StreamRecords.newRecord()
+            .ofObject(task_data)
+            .withStreamKey(stream_key);
+        // 添加任务
+        return redisTemplate.opsForStream().add(task);
+    }
+
+    /**
+     * 移除任务
+     */
+    @Override
+    public void removeTask(String stream_key, String task_id) {
+        redisTemplate.opsForStream().delete(stream_key, task_id);
+    }
+
+
+
+//        // 1. 安全获取消息
+//        List<MapRecord<String, String, String>> task_list = redisTemplate.opsForStream()
+//                .read(Consumer.from(stream_key + ":group", "consumer-1"),
+//                        StreamReadOptions.empty().count(1),
+//                        StreamOffset.create(stream_key, ReadOffset.lastConsumed()));
+
+//        System.out.println("(consumeTask) task_list = " + task_list);
+
+//        if (task_list != null && !task_list.isEmpty()) {
+//            // 2. 处理消息
+//            MapRecord<String, String, String> record = task_list.get(0);
+//            try {
+                // 实际业务处理(如调用文生图API)
+                // processTask(record.getValue());
+
+//                // 3. 确认消息(ACK)
+//                redisTemplate.opsForStream().acknowledge(stream_key, stream_key + ":group", record.getId());
+
+                // 在ACK后追加删除操作
+//                redisTemplate.opsForStream().delete(stream_key, record.getId());
+
+//            } catch (Exception e) {
+//                System.out.println("任务处理失败: " + record.getId() + ", " + e.getMessage());
+//            }
+//        }
+
+//    }
+
+    /**
+     * 获取任务队列
+     */
+//    @Override
+//    public List<MapRecord<String, String, String>> getTaskList(String stream_key) {
+//        return getTaskList(stream_key, null);
+//    }
+//    @Override
+//    public List<MapRecord<String, String, String>> getTaskList(String stream_key, Entire entire) {
+//
+//        // 1. 读取全部数据
+//        List<MapRecord<String, String, String>> task_list = redisTemplate.opsForStream().read(StreamOffset.fromStart(stream_key));
+//
+//        // 2. 根据 entire 过滤
+//        if (entire != null) {
+//            if (StrUtil.isNotEmpty(entire.getKey()) && ObjectUtil.isNotEmpty(entire.getValue())) {
+//                task_list.stream().filter(task -> {
+//                    return entire.getValue().equals(task.getValue().get(entire.getKey()));
+//                }).collect(Collectors.toList());
+//            }
+//        }
+//        return task_list;
+//    }
+
+
+}

+ 12 - 23
src/main/java/com/backendsys/modules/sdk/comfyui/controller/ComfyUIDemoController.java → src/main/java/com/backendsys/modules/sdk/comfyui/controller/ComfyuiDemoController.java

@@ -1,36 +1,25 @@
 package com.backendsys.modules.sdk.comfyui.controller;
 
-import com.backendsys.exception.CustException;
 import com.backendsys.modules.common.config.security.annotations.Anonymous;
-import com.backendsys.modules.common.config.security.utils.SecurityUtil;
-import com.backendsys.modules.sdk.comfyui.service.ComfyUIService;
-import com.backendsys.modules.sdk.comfyui.service.ComfyUISocketService;
+import com.backendsys.modules.sdk.comfyui.service.ComfyuiService;
+import com.backendsys.modules.sdk.comfyui.service.ComfyuiSocketService;
 import com.backendsys.modules.sdk.tencentcloud.cos.service.TencentCosService;
 import com.backendsys.modules.upload.entity.SysFileResult;
-import com.tencentcloudapi.tione.v20211111.models.ChatCompletionResponse;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.http.MediaType;
 import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RestController;
-import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
-import reactor.core.publisher.Flux;
-
-import java.io.IOException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
 
 @RestController
-public class ComfyUIDemoController {
+public class ComfyuiDemoController {
 
     @Autowired
     private TencentCosService tencentCosService;
 
     @Autowired
-    private ComfyUIService comfyUIService;
+    private ComfyuiService comfyUIService;
     @Autowired
-    private ComfyUISocketService comfyUISocketService;
+    private ComfyuiSocketService comfyUISocketService;
 
     /**
      * [ComfyUI] 创建 WebSocket 监听连接
@@ -52,12 +41,12 @@ public class ComfyUIDemoController {
         return "Disconnected: " + clientId;
     }
 
-    /**
-     * 转存测试
-     */
-    @GetMapping("/api/comfyui/testToCos")
-    public SysFileResult testToCos(String url) {
-        return tencentCosService.urlToCOS(url, "png");
-    }
+//    /**
+//     * 转存测试
+//     */
+//    @GetMapping("/api/comfyui/testToCos")
+//    public SysFileResult testToCos(String url) {
+//        return tencentCosService.urlToCOS(url, "png");
+//    }
 
 }

+ 9 - 0
src/main/java/com/backendsys/modules/sdk/comfyui/dao/ComfyuiTaskDao.java

@@ -0,0 +1,9 @@
+package com.backendsys.modules.sdk.comfyui.dao;
+
+import com.backendsys.modules.sdk.comfyui.entity.ComfyuiTask;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface ComfyuiTaskDao extends BaseMapper<ComfyuiTask> {
+}

+ 9 - 0
src/main/java/com/backendsys/modules/sdk/comfyui/dao/ComfyuiTaskExecuteDao.java

@@ -0,0 +1,9 @@
+package com.backendsys.modules.sdk.comfyui.dao;
+
+import com.backendsys.modules.sdk.comfyui.entity.ComfyuiTaskExecute;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface ComfyuiTaskExecuteDao extends BaseMapper<ComfyuiTaskExecute> {
+}

+ 1 - 1
src/main/java/com/backendsys/modules/sdk/comfyui/entity/CFQueue.java → src/main/java/com/backendsys/modules/sdk/comfyui/entity/ComfyuiQueue.java

@@ -4,7 +4,7 @@ import cn.hutool.json.JSONArray;
 import lombok.Data;
 
 @Data
-public class CFQueue {
+public class ComfyuiQueue {
     private JSONArray queue_running;
     private JSONArray queue_pending;
 }

+ 1 - 1
src/main/java/com/backendsys/modules/sdk/comfyui/entity/CFPromptRequest.java → src/main/java/com/backendsys/modules/sdk/comfyui/entity/ComfyuiRequest.java

@@ -4,7 +4,7 @@ import cn.hutool.json.JSONObject;
 import lombok.Data;
 
 @Data
-public class CFPromptRequest {
+public class ComfyuiRequest {
 
     private String client_id;
     private JSONObject prompt;

+ 1 - 1
src/main/java/com/backendsys/modules/sdk/comfyui/entity/CFPromptResponse.java → src/main/java/com/backendsys/modules/sdk/comfyui/entity/ComfyuiResponse.java

@@ -3,7 +3,7 @@ package com.backendsys.modules.sdk.comfyui.entity;
 import lombok.Data;
 
 @Data
-public class CFPromptResponse {
+public class ComfyuiResponse {
     private String client_id;       // 任务ID
     private String prompt_id;       // 任务ID
     private Integer number;         // 当前任务序号,可用于后续获取需要等待任务数的计算

+ 22 - 0
src/main/java/com/backendsys/modules/sdk/comfyui/entity/ComfyuiTask.java

@@ -0,0 +1,22 @@
+package com.backendsys.modules.sdk.comfyui.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+@Data
+@TableName("comfyui_task")
+public class ComfyuiTask {
+
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private Long user_id;                 // 用户ID
+    private String client_id;             // Client ID
+    private String task_type;             // 任务类型 (Text2Image, ..)
+    private Integer task_status;          // 任务状态 (-1:未开始, 1:进行中, 2:成功, 3:失败)
+    private String generate_request;      // 任务请求原始参数 (JSONString)
+    private String create_time;
+    private String update_time;
+
+}

+ 29 - 0
src/main/java/com/backendsys/modules/sdk/comfyui/entity/ComfyuiTaskExecute.java

@@ -0,0 +1,29 @@
+package com.backendsys.modules.sdk.comfyui.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+@Data
+@TableName("comfyui_task_execute")
+public class ComfyuiTaskExecute {
+
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private Long task_id;
+    private Long user_id;                 // 用户ID
+    private String client_id;             // Client ID
+    private String task_type;             // 任务类型 (Text2Image, ..)
+    private String execute_prompt_id;     // 任务ID
+    private Integer execute_status;       // 任务状态 (-1:未开始, 1:进行中, 2:成功, 3:失败)
+    private String execute_url;           // 任务执行URL
+    private Integer execute_url_port;     // 任务执行URL端口
+
+    private String generate_request;      // 任务请求原始参数 (JSONString)
+    private String generate_response;     // 任务生成原始结果 (JSONString)
+    private String reason;                // 任务状态信息,当任务失败时展示失败原因(如触发平台的内容风控等)
+    private String create_time;
+    private String update_time;
+
+}

+ 16 - 0
src/main/java/com/backendsys/modules/sdk/comfyui/entity/ComfyuiText2Image.java

@@ -0,0 +1,16 @@
+package com.backendsys.modules.sdk.comfyui.entity;
+
+import lombok.Data;
+
+@Data
+public class ComfyuiText2Image {
+    private Integer batch_size;           // 生成图片数量
+    private Float prompt_flux_guidance;   // 提示词引导系数
+    private String sampler;               // 采样器
+    private Integer step;                 // 步数
+    private Integer width;                // 图片宽度
+    private Integer height;               // 图片高度
+    private String model_name;            // 模型名称
+    private String prompt_text;           // 提示词
+    private String seed;                  // 种子值
+}

+ 3 - 2
src/main/java/com/backendsys/modules/sdk/comfyui/enums/TypeEnums.java → src/main/java/com/backendsys/modules/sdk/comfyui/enums/TaskStatusEnums.java

@@ -1,6 +1,6 @@
 package com.backendsys.modules.sdk.comfyui.enums;
 
-public enum TypeEnums {
+public enum TaskStatusEnums {
 
     STATUS("status", "队列状态"),
     EXECUTION_START("execution_start", "任务开始执行"),
@@ -11,7 +11,7 @@ public enum TypeEnums {
 
     private final String value;
     private final String label;
-    TypeEnums(String value, String label) {
+    TaskStatusEnums(String value, String label) {
         this.value = value;
         this.label = label;
     }
@@ -21,4 +21,5 @@ public enum TypeEnums {
     public String getValue() {
         return this.value;
     }
+
 }

+ 21 - 0
src/main/java/com/backendsys/modules/sdk/comfyui/enums/TaskTypeEnums.java

@@ -0,0 +1,21 @@
+package com.backendsys.modules.sdk.comfyui.enums;
+
+public enum TaskTypeEnums {
+    TEXT_2_IMAGE("Text2Image", "文生图"),
+    TEXT_2_VIDEO("Text2Video", "文生视频")
+    ;
+    private final String value;
+    private final String label;
+
+    TaskTypeEnums(String value, String label) {
+        this.value = value;
+        this.label = label;
+    }
+    public String getLabel() {
+        return this.label;
+    }
+    public String getValue() {
+        return this.value;
+    }
+
+}

+ 0 - 16
src/main/java/com/backendsys/modules/sdk/comfyui/service/ComfyUIService.java

@@ -1,16 +0,0 @@
-package com.backendsys.modules.sdk.comfyui.service;
-
-import cn.hutool.json.JSONObject;
-import com.backendsys.modules.sdk.comfyui.entity.CFPromptResponse;
-import com.backendsys.modules.sdk.comfyui.entity.CFQueue;
-import reactor.core.publisher.Mono;
-
-public interface ComfyUIService {
-
-    // [ComfyUI] 查询任务队列
-    Mono<CFQueue> getQueue();
-
-    // [ComfyUI] 执行任务
-    Mono<CFPromptResponse> prompt(String client_id, JSONObject prompt);
-
-}

+ 16 - 0
src/main/java/com/backendsys/modules/sdk/comfyui/service/ComfyuiService.java

@@ -0,0 +1,16 @@
+package com.backendsys.modules.sdk.comfyui.service;
+
+import cn.hutool.json.JSONObject;
+import com.backendsys.modules.sdk.comfyui.entity.ComfyuiResponse;
+import com.backendsys.modules.sdk.comfyui.entity.ComfyuiQueue;
+import reactor.core.publisher.Mono;
+
+public interface ComfyuiService {
+
+    // [ComfyUI] 查询任务队列
+    Mono<ComfyuiQueue> getQueue();
+
+    // [ComfyUI] 执行任务 (通用)
+    Mono<ComfyuiResponse> prompt(String client_id, JSONObject prompt);
+
+}

+ 3 - 2
src/main/java/com/backendsys/modules/sdk/comfyui/service/ComfyUISocketService.java → src/main/java/com/backendsys/modules/sdk/comfyui/service/ComfyuiSocketService.java

@@ -4,14 +4,15 @@ import reactor.core.publisher.Mono;
 
 import java.util.Map;
 
-public interface ComfyUISocketService {
+public interface ComfyuiSocketService {
 
     // [ComfyUI] 创建 WebSocket 监听连接
     Mono<Void> connect(String clientId, Integer port);
 
     // [ComfyUI] 创建 WebSocket 监听连接 (转发到 SSE)
     Mono<Void> connectToSse(String clientId, Integer port);
-    Mono<Void> connectToSse(String clientId, Integer port, Map<String, Object> params);
+    Mono<Void> connectToSse(String clientId, Integer port, Boolean is_save);
+    Mono<Void> connectToSse(String clientId, Integer port, Boolean is_save, Map<String, Object> params);
 
     // [ComfyUI] 断开 WebSocket 监听连接
     Mono<Void> disconnect(String clientId);

+ 14 - 0
src/main/java/com/backendsys/modules/sdk/comfyui/service/ComfyuiText2ImageService.java

@@ -0,0 +1,14 @@
+package com.backendsys.modules.sdk.comfyui.service;
+
+import com.backendsys.modules.sdk.comfyui.entity.ComfyuiResponse;
+import com.backendsys.modules.sdk.comfyui.entity.ComfyuiText2Image;
+
+public interface ComfyuiText2ImageService {
+
+    // [ComfyUI] 文生图 (7.16生图.json)
+    Long generateText2Image(String client_id, ComfyuiText2Image comfyuiText2Image);
+
+    // [ComfyUI] 文生图 (基础生图)
+    Long generateText2ImageSimple(String client_id, ComfyuiText2Image comfyuiText2Image);
+
+}

+ 11 - 17
src/main/java/com/backendsys/modules/sdk/comfyui/service/impl/ComfyUIServiceImpl.java → src/main/java/com/backendsys/modules/sdk/comfyui/service/impl/ComfyuiServiceImpl.java

@@ -1,28 +1,22 @@
 package com.backendsys.modules.sdk.comfyui.service.impl;
 
-import cn.hutool.core.convert.Convert;
 import cn.hutool.json.JSONObject;
 import com.backendsys.modules.common.Filter.WebClientFilter;
-import com.backendsys.modules.sdk.comfyui.entity.CFPromptRequest;
-import com.backendsys.modules.sdk.comfyui.entity.CFPromptResponse;
-import com.backendsys.modules.sdk.comfyui.entity.CFQueue;
-import com.backendsys.modules.sdk.comfyui.service.ComfyUIService;
+import com.backendsys.modules.sdk.comfyui.entity.ComfyuiRequest;
+import com.backendsys.modules.sdk.comfyui.entity.ComfyuiResponse;
+import com.backendsys.modules.sdk.comfyui.entity.ComfyuiQueue;
+import com.backendsys.modules.sdk.comfyui.service.ComfyuiService;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.http.HttpHeaders;
 import org.springframework.http.MediaType;
 import org.springframework.stereotype.Service;
-import org.springframework.util.LinkedMultiValueMap;
-import org.springframework.util.MultiValueMap;
 import org.springframework.web.reactive.function.client.WebClient;
-import org.springframework.web.util.UriComponentsBuilder;
 import reactor.core.publisher.Mono;
 
-import java.util.Map;
-import java.util.UUID;
 import java.util.function.Consumer;
 
 @Service
-public class ComfyUIServiceImpl implements ComfyUIService {
+public class ComfyuiServiceImpl implements ComfyuiService {
 
     @Value("${comfyui.host}")
     private String COMFYUI_HOST;
@@ -51,23 +45,23 @@ public class ComfyUIServiceImpl implements ComfyUIService {
     /**
      * [ComfyUI] 查询任务队列
      */
-    public Mono<CFQueue> getQueue() {
+    public Mono<ComfyuiQueue> getQueue() {
         String url = "http://" + COMFYUI_HOST + ":8000/queue";
         WebClient webClient = getWebClient();
         return webClient.get()
                 .uri(url)
                 .headers(getHttpHeaders())
                 .accept(MediaType.APPLICATION_JSON)
-                .exchangeToMono(response -> response.bodyToMono(CFQueue.class));
+                .exchangeToMono(response -> response.bodyToMono(ComfyuiQueue.class));
     }
 
     /**
      * [ComfyUI] 执行任务
      */
     @Override
-    public Mono<CFPromptResponse> prompt(String client_id, JSONObject prompt) {
+    public Mono<ComfyuiResponse> prompt(String client_id, JSONObject prompt) {
 
-        CFPromptRequest bodyValue = new CFPromptRequest();
+        ComfyuiRequest bodyValue = new ComfyuiRequest();
         bodyValue.setClient_id(client_id);
         bodyValue.setPrompt(prompt);
 
@@ -79,10 +73,10 @@ public class ComfyUIServiceImpl implements ComfyUIService {
                 .headers(getHttpHeaders())
                 .accept(MediaType.APPLICATION_JSON)
                 .bodyValue(bodyValue)
-                .exchangeToMono(response -> response.bodyToMono(CFPromptResponse.class))
+                .exchangeToMono(response -> response.bodyToMono(ComfyuiResponse.class))
                 .onErrorResume(e -> {
                     // 捕获所有异常(包括上面抛出的 RuntimeException)
-                    CFPromptResponse response = new CFPromptResponse();
+                    ComfyuiResponse response = new ComfyuiResponse();
                     response.setNode_errors(e.getMessage());
                     return Mono.just(response);
                 });

+ 126 - 92
src/main/java/com/backendsys/modules/sdk/comfyui/service/impl/ComfyUISocketServiceImpl.java → src/main/java/com/backendsys/modules/sdk/comfyui/service/impl/ComfyuiSocketServiceImpl.java

@@ -4,18 +4,15 @@ import cn.hutool.core.convert.Convert;
 import cn.hutool.json.JSONArray;
 import cn.hutool.json.JSONObject;
 import cn.hutool.json.JSONUtil;
-import com.backendsys.modules.ai.chat.entity.ChatSseMessage;
 import com.backendsys.modules.common.config.security.utils.SecurityUtil;
 import com.backendsys.modules.crt.dao.CrtGenerateImageDao;
 import com.backendsys.modules.crt.entity.CrtGenerateImage;
-import com.backendsys.modules.sdk.comfyui.enums.TypeEnums;
-import com.backendsys.modules.sdk.comfyui.service.ComfyUISocketService;
-import com.backendsys.modules.sdk.douyincloud.tos.service.DouyinTosService;
-import com.backendsys.modules.sdk.tencentcloud.cos.service.TencentCosService;
+import com.backendsys.modules.sdk.comfyui.enums.TaskStatusEnums;
+import com.backendsys.modules.sdk.comfyui.service.ComfyuiSocketService;
+import com.backendsys.modules.sdk.comfyui.utils.ComfyUtil;
 import com.backendsys.modules.sse.entity.SseResponse;
 import com.backendsys.modules.sse.entity.SseResponseEnum;
 import com.backendsys.modules.sse.utils.SseUtil;
-import com.backendsys.modules.system.service.SysCommonService;
 import com.backendsys.modules.upload.entity.SysFileResult;
 import com.backendsys.modules.upload.service.SysFileService;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -32,17 +29,19 @@ import reactor.netty.http.client.HttpClient;
 import java.net.URI;
 import java.time.Duration;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ConcurrentHashMap;
 
 @Service
-public class ComfyUISocketServiceImpl implements ComfyUISocketService {
+public class ComfyuiSocketServiceImpl implements ComfyuiSocketService {
 
     @Autowired
     private SseUtil sseUtil;
+
+    @Autowired
+    private ComfyUtil comfyUtil;
     @Autowired
     private SysFileService sysFileService;
     @Autowired
@@ -74,30 +73,30 @@ public class ComfyUISocketServiceImpl implements ComfyUISocketService {
      * [ComfyUI] 创建 WebSocket 监听连接
      */
     @Override
-    public Mono<Void> connect(String clientId, Integer port) {
+    public Mono<Void> connect(String client_id, Integer port) {
 
         String wsUrl =  "ws://" + COMFYUI_HOST + ":" + port + "/ws";
         return Mono.defer(() -> {
-            if (sessions.containsKey(clientId)) {
-                return Mono.error(new IllegalStateException("Connection already exists for client: " + clientId));
+            if (sessions.containsKey(client_id)) {
+                return Mono.error(new IllegalStateException("Connection already exists for client: " + client_id));
             }
             // 动态创建带有认证头的客户端
             WebSocketClient clientWithAuth = createWebSocketClientWithToken(COMFYUI_TOKEN);
-            return clientWithAuth.execute(URI.create(wsUrl + "?clientId=" + clientId), session -> {
+            return clientWithAuth.execute(URI.create(wsUrl + "?clientId=" + client_id), session -> {
                 // 保存会话
-                sessions.put(clientId, session);
+                sessions.put(client_id, session);
                 // 接收消息
                 Flux<String> incomingMessages = session.receive()
                     .map(WebSocketMessage::getPayloadAsText)
                     .doOnNext(message -> {
-                        System.out.println("(doOnNext) Received from " + clientId + ": " + message);
+                        System.out.println("(doOnNext) Received from " + client_id + ": " + message);
                     })
                     .doOnError(e -> {
-                        System.err.println("(doOnError) Error for " + clientId + ": " + e.getMessage());
+                        System.err.println("(doOnError) Error for " + client_id + ": " + e.getMessage());
                     })
                     .doFinally(signal -> {
-                        System.out.println("(doFinally) Connection closed for " + clientId + ": " + signal);
-                        sessions.remove(clientId);
+                        System.out.println("(doFinally) Connection closed for " + client_id + ": " + signal);
+                        sessions.remove(client_id);
                     });
                 // 需要返回一个Mono<Void>来表示处理完成
                 return incomingMessages.then();
@@ -107,120 +106,106 @@ public class ComfyUISocketServiceImpl implements ComfyUISocketService {
 
     /**
      * [ComfyUI] 创建 WebSocket 监听连接 (转发到 SSE)
+     * - client_id: -
+     * - port:      端口 (comfyui)
+     * - is_save:   是否转存到 (cos/tos)
+     * - params:    额外参数
      */
     @Override
-    public Mono<Void> connectToSse(String clientId, Integer port) {
-        return connectToSse(clientId, port, null);
+    public Mono<Void> connectToSse(String client_id, Integer port) {
+        return connectToSse(client_id, port, false, null);
     }
     @Override
-    public Mono<Void> connectToSse(String clientId, Integer port, Map<String, Object> params) {
-
-        CrtGenerateImage entity = new CrtGenerateImage();
+    public Mono<Void> connectToSse(String client_id, Integer port, Boolean is_save) {
+        return connectToSse(client_id, port, is_save, null);
+    }
+    @Override
+    public Mono<Void> connectToSse(String client_id, Integer port, Boolean is_save, Map<String, Object> params) {
 
-        // Websocket 获取不到上下文信息,所以 user_id 需要从外部传递
+        // 由于 Websocket 获取不到上下文信息,所以 user_id 不能在 socket 周期获取
         Long user_id = SecurityUtil.getUserId();
-        entity.setUser_id(user_id);
 
         String wsUrl =  "ws://" + COMFYUI_HOST + ":" + port + "/ws";
         return Mono.defer(() -> {
-            if (sessions.containsKey(clientId)) {
-                return Mono.error(new IllegalStateException("Connection already exists for client: " + clientId));
+            if (sessions.containsKey(client_id)) {
+                return Mono.error(new IllegalStateException("Connection already exists for client: " + client_id));
             }
             // 动态创建带有认证头的客户端
             WebSocketClient clientWithAuth = createWebSocketClientWithToken(COMFYUI_TOKEN);
-            return clientWithAuth.execute(URI.create(wsUrl + "?clientId=" + clientId), session -> {
+            return clientWithAuth.execute(URI.create(wsUrl + "?clientId=" + client_id), session -> {
                 // 保存会话
-                sessions.put(clientId, session);
+                sessions.put(client_id, session);
                 // 接收消息
-
                 System.out.println("------ wsUrl: " + wsUrl + " ------");
-                System.out.println("------ connectToSse clientId: " + clientId + ", user_id: " + user_id + " ------");
+                System.out.println("------ connectToSse client_id: " + client_id + ", user_id: " + user_id + " ------");
 
                 Flux<String> incomingMessages = session.receive()
                         .map(WebSocketMessage::getPayloadAsText)
                         .doOnNext(message -> {
-                            System.out.println("(doOnNext) Received from " + clientId + ": " + message);
+                            System.out.println("(doOnNext) Received from " + client_id + ": " + message);
 
                             JSONObject data = JSONUtil.parseObj(message);
-                            String type = Convert.toStr(data.get("type"));
+                            JSONObject dataChildren = JSONUtil.parseObj(data.get("data"));
 
-                            // == [任务执行成功] =======================================================
-                            // { "type": "executed", "data": { "output": { "images": [{ "filename": "ComfyUI_00117_.png" }] } } }
-                            if (TypeEnums.EXECUTED.getValue().equals(type)) {
+                            Long drama_project_storyboard_id = Convert.toLong(params.get("drama_project_storyboard_id"));
+                            dataChildren.put("drama_project_storyboard_id", drama_project_storyboard_id);
+                            data.put("data", dataChildren);
+
+                            // == [任务执行完成] =======================================================
+                            // { "type": "executed", .. }
+                            String type = Convert.toStr(data.get("type"));
+                            if (TaskStatusEnums.EXECUTED.getValue().equals(type)) {
 
-                                JSONObject dataChildren = JSONUtil.parseObj(data.get("data"));
                                 JSONObject output = JSONUtil.parseObj(dataChildren.get("output"));
 
-                                // -- [生成图片] ------------------------------------------------------
+                                // -- [生成图片 -> 转存图片 -> 新增记录] ------------------------------------------------------
                                 // 由于图片地址不是公开的,需要加 Token 访问,因此不能公开返回原始图片地址,比如:
                                 // http://43.128.1.201:8000/api/view?filename=fenjing_1_00012_.png&token=$2b$12$.MR4qGaFetN1FPQzbfyIrehsyjnPJ12xAZhR/l7KZpLkUPQTCG4gy
-
+                                // 因此,直接在 ComfyUI 的物理目录上构建 nginx 静态资源访问目录
                                 Object imagesObj = output.get("images");
                                 if (imagesObj != null) {
-                                    JSONArray images = JSONUtil.parseArray(imagesObj);
-                                    // [{"filename": "ComfyUI_00122_.png", "subfolder": "", "type": "output"}]
-                                    // http://43.128.1.201:8001/api/view?filename=ComfyUI_00117_.png
-                                    // http://43.128.1.201:8001/api/view?filename=ComfyUI_00117_.png&preview=1
-                                    List<String> images_path = new ArrayList<>();
-                                    if (images.size() > 0) {
-                                        for (int i = 0; i < images.size(); i++) {
-                                            JSONObject image = images.getJSONObject(i);
-                                            String filename = image.getStr("filename");
-
-                                            // ComfyUI + Nginx 输出域名转发
-//                                            String filepath = "http://" + COMFYUI_HOST + ":" + port + "/api/view?filename=" + filename;
-//                                            String filepath_with_token = filepath + "&token=" + COMFYUI_TOKEN;
-
-                                            String filepath = "http://o.daogu.ai/" + port + "/" + filename;
-
-                                            // -- [图片转存储存桶] -------------------------------------
-                                            SysFileResult result = sysFileService.urlToUploadFile(filepath, user_id);
-                                            System.out.println("urlToUploadFile (result) = " + result);
-
-                                            // -- [记录到生成图片记录表] --------------------------------
-                                            if (params != null) {
-
-                                                // 创建一个 CompletableFuture 来执行异步任务
-                                                CompletableFuture.runAsync(() -> {
-
-                                                    Long drama_project_storyboard_id = Convert.toLong(params.get("drama_project_storyboard_id"));
-                                                    String prompt_id = Convert.toStr(dataChildren.get("prompt_id"));
-                                                    if (drama_project_storyboard_id != null) {
-                                                        entity.setDrama_project_storyboard_id(drama_project_storyboard_id);
-                                                        entity.setPrompt_id(prompt_id);
-                                                        entity.setUrl_origin(filepath);
-                                                        entity.setUrl(result.getUrl());
-                                                        crtGenerateImageDao.insert(entity);
-                                                    }
-
-                                                });
-
-                                            }
-                                            // ------------------------------------------------------
+                                    String prompt_id = Convert.toStr(dataChildren.get("prompt_id"));
 
-                                            images_path.add(filepath);
 
-                                        }
+                                    // 临时图片相对路径
+                                    JSONArray images = JSONUtil.parseArray(imagesObj);
+                                    // 返回完整的临时图片路径
+                                    List<String> images_temp_path_full = imagesToFullpath(images, port);
+                                    System.out.println("images_temp_path_full = " + images_temp_path_full);
+                                    output.put("images_temp_path", images_temp_path_full);
+
+                                    if (is_save) {
+                                        // 创建一个 CompletableFuture 来执行异步任务
+                                        CompletableFuture.runAsync(() -> {
+                                            // 返回转存后的图片路径
+                                            List<String> images_path = imagesToStoryboard(user_id, prompt_id, images_temp_path_full, drama_project_storyboard_id);
+                                            output.put("images_path", images_path);
+                                        });
                                     }
-                                    output.put("images_path", images_path);
+
                                     dataChildren.put("output", output);
                                     data.put("data", dataChildren);
+
+                                    // [DB] 执行任务
+                                    CompletableFuture.runAsync(() -> {
+                                        comfyUtil.executeComfyuiTask(prompt_id, JSONUtil.toJsonStr(dataChildren), 2);
+                                    });
+
                                 }
-                                // ------------------------------------------------------------------
 
                             }
-                            // ======================================================================
+                            // ========================================================================
 
                             sseUtil.send(user_id, new SseResponse(SseResponseEnum.COMFYUI, data).toJsonStr());
                         })
                         .doOnError(e -> {
-                            System.err.println("(doOnError) Error for " + clientId + ": " + e.getMessage());
+                            System.err.println("(doOnError) Error for " + client_id + ": " + e.getMessage());
                             sseUtil.send(user_id, new SseResponse(SseResponseEnum.COMFYUI, e.getMessage()).toJsonStr());
                         })
                         .doFinally(signal -> {
-                            System.out.println("(doFinally) Connection closed for " + clientId + ": " + signal);
+                            System.out.println("(doFinally) Connection closed for " + client_id + ": " + signal);
                             sseUtil.send(user_id, new SseResponse(SseResponseEnum.COMFYUI, signal).toJsonStr());
-                            sessions.remove(clientId);
+                            sessions.remove(client_id);
                             System.out.println("---------------------------------------------------------");
                         });
                 // 需要返回一个Mono<Void>来表示处理完成
@@ -231,23 +216,23 @@ public class ComfyUISocketServiceImpl implements ComfyUISocketService {
 
     /**
      * [ComfyUI] 断开 WebSocket 监听连接
-     * @param clientId 客户端ID
+     * @param client_id 客户端ID
      * @return Mono<Void> 表示断开操作
      */
     @Override
-    public Mono<Void> disconnect(String clientId) {
+    public Mono<Void> disconnect(String client_id) {
         return Mono.fromRunnable(() -> {
-            WebSocketSession session = sessions.get(clientId);
+            WebSocketSession session = sessions.get(client_id);
             if (session != null) {
-                System.out.println("disconnect success! clientId: " + clientId);
+                System.out.println("disconnect success! client_id: " + client_id);
 
                 Long user_id = SecurityUtil.getUserId();
                 if (user_id != null) {
-                    sseUtil.send(user_id, new SseResponse(SseResponseEnum.COMFYUI, "disconnect success! clientId: " + clientId).toJsonStr());
+                    sseUtil.send(user_id, new SseResponse(SseResponseEnum.COMFYUI, "disconnect success! client_id: " + client_id).toJsonStr());
                 }
 
                 session.close().subscribe();
-                sessions.remove(clientId);
+                sessions.remove(client_id);
             }
         });
     }
@@ -260,4 +245,53 @@ public class ComfyUISocketServiceImpl implements ComfyUISocketService {
         return Flux.fromIterable(sessions.keySet());
     }
 
+    // 返回临时图片完整路径
+    private List<String> imagesToFullpath(JSONArray images, Integer port) {
+        List<String> images_path = new ArrayList<>();
+        if (images.size() > 0) {
+            for (int i = 0; i < images.size(); i++) {
+                JSONObject image = images.getJSONObject(i);
+                String filename = image.getStr("filename");
+                String filepath = "http://o.daogu.ai/" + port + "/" + filename;
+                images_path.add(filepath);
+            }
+        }
+        return images_path;
+    }
+
+    // 转存图片
+    private List<String> imagesToStoryboard(Long user_id, String prompt_id, List<String> images_temp_path_full, Long drama_project_storyboard_id) {
+
+        List<String> images_path = new ArrayList<>();
+        if (images_temp_path_full.size() > 0) {
+
+            images_temp_path_full.stream().forEach(temp_image -> {
+
+                // -- [图片转存储存桶] -------------------------------------
+                SysFileResult result = sysFileService.urlToUploadFile(temp_image, user_id);
+                System.out.println("urlToUploadFile (result) = " + result);
+
+                String image = result.getUrl();
+                images_path.add(image);
+
+                // -- [记录到生成图片记录表] --------------------------------
+                if (drama_project_storyboard_id != null) {
+                    CrtGenerateImage entity = new CrtGenerateImage();
+                    entity.setUser_id(user_id);
+                    entity.setDrama_project_storyboard_id(drama_project_storyboard_id);
+                    entity.setPrompt_id(prompt_id);
+                    entity.setUrl_origin(temp_image);
+                    entity.setUrl(image);
+                    entity.setTarget(result.getTarget());
+                    entity.setObject_key(result.getKey());
+                    crtGenerateImageDao.insert(entity);
+                }
+                // ------------------------------------------------------
+
+            });
+
+        }
+        return images_path;
+    }
+
 }

+ 446 - 0
src/main/java/com/backendsys/modules/sdk/comfyui/service/impl/ComfyuiText2ImageServiceImpl.java

@@ -0,0 +1,446 @@
+package com.backendsys.modules.sdk.comfyui.service.impl;
+
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import com.backendsys.modules.sdk.comfyui.dao.ComfyuiTaskDao;
+import com.backendsys.modules.sdk.comfyui.entity.ComfyuiResponse;
+import com.backendsys.modules.sdk.comfyui.entity.ComfyuiText2Image;
+import com.backendsys.modules.sdk.comfyui.enums.TaskTypeEnums;
+import com.backendsys.modules.sdk.comfyui.service.ComfyuiService;
+import com.backendsys.modules.sdk.comfyui.service.ComfyuiText2ImageService;
+import com.backendsys.modules.sdk.comfyui.utils.ComfyUtil;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import reactor.core.publisher.Mono;
+
+import java.util.concurrent.CompletableFuture;
+
+@Service
+public class ComfyuiText2ImageServiceImpl implements ComfyuiText2ImageService {
+
+    @Autowired
+    private ComfyuiService comfyUIService;
+
+    @Autowired
+    private ComfyUtil comfyUtil;
+
+    /**
+     * [ComfyUI] 文生图 (7.16生图.json)
+     * // ComfyuiResponse -> void
+     */
+    @Override
+    public Long generateText2Image(String client_id, ComfyuiText2Image comfyuiText2Image) {
+
+        String prompt = "{"+
+                "  \"3\": {"+
+                "    \"inputs\": {"+
+                "      \"text\": ["+
+                "        \"120\","+
+                "        0"+
+                "      ],"+
+                "      \"speak_and_recognation\": {"+
+                "        \"__value__\": ["+
+                "          false,"+
+                "          true"+
+                "        ]"+
+                "      },"+
+                "      \"clip\": ["+
+                "        \"151\","+
+                "        1"+
+                "      ]"+
+                "    },"+
+                "    \"class_type\": \"CLIPTextEncode\","+
+                "    \"_meta\": {"+
+                "      \"title\": \"CLIP文本编码\""+
+                "    }"+
+                "  },"+
+                "  \"13\": {"+
+                "    \"inputs\": {"+
+                "      \"guidance\": " + comfyuiText2Image.getPrompt_flux_guidance() + ","+
+                "      \"conditioning\": ["+
+                "        \"3\","+
+                "        0"+
+                "      ]"+
+                "    },"+
+                "    \"class_type\": \"FluxGuidance\","+
+                "    \"_meta\": {"+
+                "      \"title\": \"Flux引导\""+
+                "    }"+
+                "  },"+
+                "  \"15\": {"+
+                "    \"inputs\": {"+
+                "      \"model\": ["+
+                "        \"152\","+
+                "        0"+
+                "      ],"+
+                "      \"conditioning\": ["+
+                "        \"13\","+
+                "        0"+
+                "      ]"+
+                "    },"+
+                "    \"class_type\": \"BasicGuider\","+
+                "    \"_meta\": {"+
+                "      \"title\": \"基本引导器\""+
+                "    }"+
+                "  },"+
+                "  \"31\": {"+
+                "    \"inputs\": {"+
+                "      \"samples\": ["+
+                "        \"32\","+
+                "        0"+
+                "      ],"+
+                "      \"vae\": ["+
+                "        \"151\","+
+                "        2"+
+                "      ]"+
+                "    },"+
+                "    \"class_type\": \"VAEDecode\","+
+                "    \"_meta\": {"+
+                "      \"title\": \"VAE解码\""+
+                "    }"+
+                "  },"+
+                "  \"32\": {"+
+                "    \"inputs\": {"+
+                "      \"noise\": ["+
+                "        \"37\","+
+                "        0"+
+                "      ],"+
+                "      \"guider\": ["+
+                "        \"15\","+
+                "        0"+
+                "      ],"+
+                "      \"sampler\": ["+
+                "        \"84\","+
+                "        0"+
+                "      ],"+
+                "      \"sigmas\": ["+
+                "        \"150\","+
+                "        0"+
+                "      ],"+
+                "      \"latent_image\": ["+
+                "        \"90\","+
+                "        0"+
+                "      ]"+
+                "    },"+
+                "    \"class_type\": \"SamplerCustomAdvanced\","+
+                "    \"_meta\": {"+
+                "      \"title\": \"自定义采样器(高级)\""+
+                "    }"+
+                "  },"+
+                "  \"37\": {"+
+                "    \"inputs\": {"+
+                "      \"noise_seed\": " + comfyuiText2Image.getSeed() +
+                "    },"+
+                "    \"class_type\": \"RandomNoise\","+
+                "    \"_meta\": {"+
+                "      \"title\": \"随机噪波\""+
+                "    }"+
+                "  },"+
+                "  \"38\": {"+
+                "    \"inputs\": {"+
+                "      \"text\": \"\","+
+                "      \"speak_and_recognation\": {"+
+                "        \"__value__\": ["+
+                "          false,"+
+                "          true"+
+                "        ]"+
+                "      }"+
+                "    },"+
+                "    \"class_type\": \"TextInput_\","+
+                "    \"_meta\": {"+
+                "      \"title\": \"触发词\""+
+                "    }"+
+                "  },"+
+                "  \"51\": {"+
+                "    \"inputs\": {"+
+                "      \"text\": \"fenjing\","+
+                "      \"speak_and_recognation\": {"+
+                "        \"__value__\": ["+
+                "          false,"+
+                "          true"+
+                "        ]"+
+                "      }"+
+                "    },"+
+                "    \"class_type\": \"TextInput_\","+
+                "    \"_meta\": {"+
+                "      \"title\": \"分镜号\""+
+                "    }"+
+                "  },"+
+                "  \"84\": {"+
+                "    \"inputs\": {"+
+                "      \"sampler_name\": \"" + comfyuiText2Image.getSampler() + "\""+
+                "    },"+
+                "    \"class_type\": \"KSamplerSelect\","+
+                "    \"_meta\": {"+
+                "      \"title\": \"K采样器选择\""+
+                "    }"+
+                "  },"+
+                "  \"90\": {"+
+                "    \"inputs\": {"+
+                "      \"width\": ["+
+                "        \"121\","+
+                "        0"+
+                "      ],"+
+                "      \"height\": ["+
+                "        \"122\","+
+                "        0"+
+                "      ],"+
+                "      \"batch_size\": " + comfyuiText2Image.getBatch_size() +
+                "    },"+
+                "    \"class_type\": \"EmptySD3LatentImage\","+
+                "    \"_meta\": {"+
+                "      \"title\": \"空Latent图像(SD3)\""+
+                "    }"+
+                "  },"+
+                "  \"91\": {"+
+                "    \"inputs\": {"+
+                "      \"filename_prefix\": ["+
+                "        \"51\","+
+                "        0"+
+                "      ],"+
+                "      \"images\": ["+
+                "        \"31\","+
+                "        0"+
+                "      ]"+
+                "    },"+
+                "    \"class_type\": \"SaveImage\","+
+                "    \"_meta\": {"+
+                "      \"title\": \"保存图像\""+
+                "    }"+
+                "  },"+
+                "  \"120\": {"+
+                "    \"inputs\": {"+
+                "      \"text_0\": \", a pig\","+
+                "      \"text\": ["+
+                "        \"148\","+
+                "        0"+
+                "      ]"+
+                "    },"+
+                "    \"class_type\": \"PandasShowText\","+
+                "    \"_meta\": {"+
+                "      \"title\": \"Pandas Show Text\""+
+                "    }"+
+                "  },"+
+                "  \"121\": {"+
+                "    \"inputs\": {"+
+                "      \"value\": " + comfyuiText2Image.getWidth() +
+                "    },"+
+                "    \"class_type\": \"easy int\","+
+                "    \"_meta\": {"+
+                "      \"title\": \"整数\""+
+                "    }"+
+                "  },"+
+                "  \"122\": {"+
+                "    \"inputs\": {"+
+                "      \"value\": " + comfyuiText2Image.getHeight() +
+                "    },"+
+                "    \"class_type\": \"easy int\","+
+                "    \"_meta\": {"+
+                "      \"title\": \"整数\""+
+                "    }"+
+                "  },"+
+                "  \"146\": {"+
+                "    \"inputs\": {"+
+                "      \"from_translate\": \"auto\","+
+                "      \"to_translate\": \"english\","+
+                "      \"add_proxies\": false,"+
+                "      \"proxies\": \"\","+
+                "      \"auth_data\": \"\","+
+                "      \"service\": \"BaiduTranslator [appid and appkey]\","+
+                "      \"text\": ["+
+                "        \"153\","+
+                "        0"+
+                "      ],"+
+                "      \"Show proxy\": \"proxy_hide\","+
+                "      \"Show authorization\": \"authorization_hide\","+
+                "      \"speak_and_recognation\": {"+
+                "        \"__value__\": ["+
+                "          false,"+
+                "          true"+
+                "        ]"+
+                "      }"+
+                "    },"+
+                "    \"class_type\": \"DeepTranslatorTextNode\","+
+                "    \"_meta\": {"+
+                "      \"title\": \"Deep Translator Text Node\""+
+                "    }"+
+                "  },"+
+                "  \"148\": {"+
+                "    \"inputs\": {"+
+                "      \"delimiter\": \", \","+
+                "      \"string1\": ["+
+                "        \"38\","+
+                "        0"+
+                "      ],"+
+                "      \"string2\": ["+
+                "        \"146\","+
+                "        0"+
+                "      ]"+
+                "    },"+
+                "    \"class_type\": \"JoinStrings\","+
+                "    \"_meta\": {"+
+                "      \"title\": \"Join Strings\""+
+                "    }"+
+                "  },"+
+                "  \"150\": {"+
+                "    \"inputs\": {"+
+                "      \"scheduler\": \"normal\","+
+                "      \"steps\": " + comfyuiText2Image.getStep() + ","+
+                "      \"denoise\": 1,"+
+                "      \"model\": ["+
+                "        \"152\","+
+                "        0"+
+                "      ]"+
+                "    },"+
+                "    \"class_type\": \"BasicScheduler\","+
+                "    \"_meta\": {"+
+                "      \"title\": \"基本调度器\""+
+                "    }"+
+                "  },"+
+                "  \"151\": {"+
+                "    \"inputs\": {"+
+                "      \"ckpt_name\": \"" + comfyuiText2Image.getModel_name() + "\""+
+                "    },"+
+                "    \"class_type\": \"CheckpointLoaderSimple\","+
+                "    \"_meta\": {"+
+                "      \"title\": \"Checkpoint加载器(简易)\""+
+                "    }"+
+                "  },"+
+                "  \"152\": {"+
+                "    \"inputs\": {"+
+                "      \"lora_name\": \"【FLUX】都市高武推文 _ 异能 法术 战斗_v1.0.safetensors\","+
+                "      \"strength_model\": 0,"+
+                "      \"model\": ["+
+                "        \"151\","+
+                "        0"+
+                "      ]"+
+                "    },"+
+                "    \"class_type\": \"LoraLoaderModelOnly\","+
+                "    \"_meta\": {"+
+                "      \"title\": \"LoRA加载器(仅模型)\""+
+                "    }"+
+                "  },"+
+                "  \"153\": {"+
+                "    \"inputs\": {"+
+                "      \"text\": \"" + comfyuiText2Image.getPrompt_text() + "\","+
+                "      \"speak_and_recognation\": {"+
+                "        \"__value__\": ["+
+                "          false,"+
+                "          true"+
+                "        ]"+
+                "      }"+
+                "    },"+
+                "    \"class_type\": \"TextInput_\","+
+                "    \"_meta\": {"+
+                "      \"title\": \"Text Input ♾️Mixlab\""+
+                "    }"+
+                "  }"+
+                "}"
+        ;
+
+
+        // [DB] 初始化任务
+        Long task_id = comfyUtil.initComfyuiTask(client_id, prompt, TaskTypeEnums.TEXT_2_IMAGE.getValue());
+        return task_id;
+
+//        // [ComfyUI] 执行任务
+//        JSONObject prompt_object = JSONUtil.parseObj(prompt);
+//        Mono<ComfyuiResponse> cfPromptResponseMono = comfyUIService.prompt(client_id, prompt_object);
+//        ComfyuiResponse response = cfPromptResponseMono.block();
+//        response.setClient_id(client_id);
+
+//        return response;
+    }
+
+
+    /**
+     * [ComfyUI] 文生图 (基础生图)
+     * // ComfyuiResponse -> void
+     */
+    @Override
+    public Long generateText2ImageSimple(String client_id, ComfyuiText2Image comfyuiText2Image) {
+
+        // [ComfyUI-基础生图]
+        String prompt = "{" +
+                    "\"3\": {" +
+                        "\"inputs\": {" +
+//                            "\"seed\": 449753344472378," +
+                            "\"seed\": " + comfyuiText2Image.getSeed() + "," +
+//                            "\"steps\": 20," +
+                            "\"steps\": " + comfyuiText2Image.getStep() + "," +
+                            "\"cfg\": 8," +
+//                            "\"sampler_name\": \"euler\"," +
+                            "\"sampler_name\": \"" + comfyuiText2Image.getSampler() + "\"," +
+                            "\"scheduler\": \"normal\", \"denoise\": 1," +
+                            "\"model\": [\"4\", 0], \"positive\": [\"6\", 0]," +
+                            "\"negative\": [\"7\", 0], \"latent_image\": [\"5\", 0]" +
+                        "}," +
+                        "\"class_type\": \"KSampler\", \"_meta\": { \"title\": \"K采样器\" }" +
+                    "}," +
+                    "\"4\": {" +
+                        "\"inputs\": {" +
+//                            "\"ckpt_name\": \"v1-5-pruned-emaonly-fp16.safetensors\"" +
+                            "\"ckpt_name\": \"" + comfyuiText2Image.getModel_name() + "\"" +
+                        "}," +
+                        "\"class_type\": \"CheckpointLoaderSimple\"," +
+                        "\"_meta\": { \"title\": \"Checkpoint加载器(简易)\" }" +
+                    "}," +
+                    "\"5\": {" +
+                        "\"inputs\": {" +
+//                            "\"width\": 512," +
+//                            "\"height\": 512," +
+//                            "\"batch_size\": 1" +
+                            "\"width\": " + comfyuiText2Image.getWidth() + "," +
+                            "\"height\": " + comfyuiText2Image.getHeight() + "," +
+                            "\"batch_size\": " + comfyuiText2Image.getBatch_size() +
+                        "}," +
+                        "\"class_type\": \"EmptyLatentImage\"," +
+                        "\"_meta\": { \"title\": \"空Latent图像\" }" +
+                    "}," +
+                    "\"6\": {" +
+                        "\"inputs\": {" +
+//                            "\"text\": \"beautiful scenery nature glass bottle landscape, , purple galaxy bottle,\"," +
+                            "\"text\": \"" + comfyuiText2Image.getPrompt_text() + "\"," +
+                            "\"speak_and_recognation\": { \"__value__\": [false, true] }," +
+                            "\"clip\": [\"4\", 1]" +
+                        "}," +
+                        "\"class_type\": \"CLIPTextEncode\", \"_meta\": { \"title\": \"CLIP文本编码\" }" +
+                    "}," +
+                    "\"7\": {" +
+                        "\"inputs\": {" +
+                            "\"text\": \"text, watermark\"," +
+                            "\"speak_and_recognation\": { \"__value__\": [false, true] }," +
+                            "\"clip\": [\"4\", 1]" +
+                        "}," +
+                        "\"class_type\": \"CLIPTextEncode\", \"_meta\": { \"title\": \"CLIP文本编码\" }" +
+                    "}," +
+                    "\"8\": {" +
+                        "\"inputs\": {" +
+                            "\"samples\": [\"3\", 0], \"vae\": [\"4\", 2]," +
+                        "}," +
+                        "\"class_type\": \"VAEDecode\", \"_meta\": { \"title\": \"VAE解码\" }" +
+                    "}," +
+                    "\"9\": {" +
+                        "\"inputs\": {" +
+                            "\"filename_prefix\": \"ComfyUI\", \"images\": [\"8\", 0]" +
+                        "}," +
+                        "\"class_type\": \"SaveImage\", \"_meta\": { \"title\": \"保存图像\" }" +
+                    "}," +
+                "}"
+        ;
+
+
+        // [DB] 初始化任务
+        Long task_id = comfyUtil.initComfyuiTask(client_id, prompt, TaskTypeEnums.TEXT_2_IMAGE.getValue());
+        return task_id;
+
+//        // [ComfyUI] 执行任务
+//        JSONObject prompt_object = JSONUtil.parseObj(prompt);
+//        Mono<ComfyuiResponse> cfPromptResponseMono = comfyUIService.prompt(client_id, prompt_object);
+//        ComfyuiResponse response = cfPromptResponseMono.block();
+//        response.setClient_id(client_id);
+
+//        return response;
+    }
+
+}

+ 305 - 0
src/main/java/com/backendsys/modules/sdk/comfyui/service/json/7.16生图 (1)-API.json

@@ -0,0 +1,305 @@
+{
+  "3": {
+    "inputs": {
+      "text": [
+        "120",
+        0
+      ],
+      "speak_and_recognation": {
+        "__value__": [
+          false,
+          true
+        ]
+      },
+      "clip": [
+        "151",
+        1
+      ]
+    },
+    "class_type": "CLIPTextEncode",
+    "_meta": {
+      "title": "CLIP文本编码"
+    }
+  },
+  "13": {
+    "inputs": {
+      "guidance": 3.5,
+      "conditioning": [
+        "3",
+        0
+      ]
+    },
+    "class_type": "FluxGuidance",
+    "_meta": {
+      "title": "Flux引导"
+    }
+  },
+  "15": {
+    "inputs": {
+      "model": [
+        "152",
+        0
+      ],
+      "conditioning": [
+        "13",
+        0
+      ]
+    },
+    "class_type": "BasicGuider",
+    "_meta": {
+      "title": "基本引导器"
+    }
+  },
+  "31": {
+    "inputs": {
+      "samples": [
+        "32",
+        0
+      ],
+      "vae": [
+        "151",
+        2
+      ]
+    },
+    "class_type": "VAEDecode",
+    "_meta": {
+      "title": "VAE解码"
+    }
+  },
+  "32": {
+    "inputs": {
+      "noise": [
+        "37",
+        0
+      ],
+      "guider": [
+        "15",
+        0
+      ],
+      "sampler": [
+        "84",
+        0
+      ],
+      "sigmas": [
+        "150",
+        0
+      ],
+      "latent_image": [
+        "90",
+        0
+      ]
+    },
+    "class_type": "SamplerCustomAdvanced",
+    "_meta": {
+      "title": "自定义采样器(高级)"
+    }
+  },
+  "37": {
+    "inputs": {
+      "noise_seed": 229229841277714
+    },
+    "class_type": "RandomNoise",
+    "_meta": {
+      "title": "随机噪波"
+    }
+  },
+  "38": {
+    "inputs": {
+      "text": "",
+      "speak_and_recognation": {
+        "__value__": [
+          false,
+          true
+        ]
+      }
+    },
+    "class_type": "TextInput_",
+    "_meta": {
+      "title": "触发词"
+    }
+  },
+  "51": {
+    "inputs": {
+      "text": "分镜1",
+      "speak_and_recognation": {
+        "__value__": [
+          false,
+          true
+        ]
+      }
+    },
+    "class_type": "TextInput_",
+    "_meta": {
+      "title": "分镜号"
+    }
+  },
+  "84": {
+    "inputs": {
+      "sampler_name": "euler"
+    },
+    "class_type": "KSamplerSelect",
+    "_meta": {
+      "title": "K采样器选择"
+    }
+  },
+  "90": {
+    "inputs": {
+      "width": [
+        "121",
+        0
+      ],
+      "height": [
+        "122",
+        0
+      ],
+      "batch_size": 1
+    },
+    "class_type": "EmptySD3LatentImage",
+    "_meta": {
+      "title": "空Latent图像(SD3)"
+    }
+  },
+  "91": {
+    "inputs": {
+      "filename_prefix": [
+        "51",
+        0
+      ],
+      "images": [
+        "31",
+        0
+      ]
+    },
+    "class_type": "SaveImage",
+    "_meta": {
+      "title": "保存图像"
+    }
+  },
+  "120": {
+    "inputs": {
+      "text_0": ", a pig",
+      "text": [
+        "148",
+        0
+      ]
+    },
+    "class_type": "PandasShowText",
+    "_meta": {
+      "title": "Pandas Show Text"
+    }
+  },
+  "121": {
+    "inputs": {
+      "value": 1280
+    },
+    "class_type": "easy int",
+    "_meta": {
+      "title": "整数"
+    }
+  },
+  "122": {
+    "inputs": {
+      "value": 720
+    },
+    "class_type": "easy int",
+    "_meta": {
+      "title": "整数"
+    }
+  },
+  "146": {
+    "inputs": {
+      "from_translate": "auto",
+      "to_translate": "english",
+      "add_proxies": false,
+      "proxies": "",
+      "auth_data": "",
+      "service": "BaiduTranslator [appid and appkey]",
+      "text": [
+        "153",
+        0
+      ],
+      "Show proxy": "proxy_hide",
+      "Show authorization": "authorization_hide",
+      "speak_and_recognation": {
+        "__value__": [
+          false,
+          true
+        ]
+      }
+    },
+    "class_type": "DeepTranslatorTextNode",
+    "_meta": {
+      "title": "Deep Translator Text Node"
+    }
+  },
+  "148": {
+    "inputs": {
+      "delimiter": ", ",
+      "string1": [
+        "38",
+        0
+      ],
+      "string2": [
+        "146",
+        0
+      ]
+    },
+    "class_type": "JoinStrings",
+    "_meta": {
+      "title": "Join Strings"
+    }
+  },
+  "150": {
+    "inputs": {
+      "scheduler": "normal",
+      "steps": 25,
+      "denoise": 1,
+      "model": [
+        "152",
+        0
+      ]
+    },
+    "class_type": "BasicScheduler",
+    "_meta": {
+      "title": "基本调度器"
+    }
+  },
+  "151": {
+    "inputs": {
+      "ckpt_name": "FLUX1/flux1-dev-fp8.safetensors"
+    },
+    "class_type": "CheckpointLoaderSimple",
+    "_meta": {
+      "title": "Checkpoint加载器(简易)"
+    }
+  },
+  "152": {
+    "inputs": {
+      "lora_name": "【FLUX】都市高武推文 _ 异能 法术 战斗_v1.0.safetensors",
+      "strength_model": 0,
+      "model": [
+        "151",
+        0
+      ]
+    },
+    "class_type": "LoraLoaderModelOnly",
+    "_meta": {
+      "title": "LoRA加载器(仅模型)"
+    }
+  },
+  "153": {
+    "inputs": {
+      "text": "一只猪",
+      "speak_and_recognation": {
+        "__value__": [
+          false,
+          true
+        ]
+      }
+    },
+    "class_type": "TextInput_",
+    "_meta": {
+      "title": "Text Input ♾️Mixlab"
+    }
+  }
+}

+ 286 - 0
src/main/java/com/backendsys/modules/sdk/comfyui/service/json/7.19_图片高清(输出2K).json

@@ -0,0 +1,286 @@
+{
+  "id": "4e70780b-0f80-47b8-a2dc-50d1cd0e33c0",
+  "revision": 0,
+  "last_node_id": 11,
+  "last_link_id": 15,
+  "nodes": [
+    {
+      "id": 1,
+      "type": "LoadImage",
+      "pos": [
+        -1460,
+        950
+      ],
+      "size": [
+        270,
+        314
+      ],
+      "flags": {},
+      "order": 0,
+      "mode": 0,
+      "inputs": [],
+      "outputs": [
+        {
+          "name": "IMAGE",
+          "type": "IMAGE",
+          "links": [
+            8
+          ]
+        },
+        {
+          "name": "MASK",
+          "type": "MASK",
+          "links": null
+        }
+      ],
+      "properties": {
+        "cnr_id": "comfy-core",
+        "ver": "0.3.43",
+        "Node name for S&R": "LoadImage",
+        "widget_ue_connectable": {}
+      },
+      "widgets_values": [
+        "01_03_16_参考_01_伸出手释放技能.png",
+        "image"
+      ]
+    },
+    {
+      "id": 8,
+      "type": "UpscaleModelLoader",
+      "pos": [
+        -1160,
+        950
+      ],
+      "size": [
+        270,
+        58
+      ],
+      "flags": {},
+      "order": 1,
+      "mode": 0,
+      "inputs": [],
+      "outputs": [
+        {
+          "name": "UPSCALE_MODEL",
+          "type": "UPSCALE_MODEL",
+          "links": [
+            7
+          ]
+        }
+      ],
+      "properties": {
+        "cnr_id": "comfy-core",
+        "ver": "0.3.43",
+        "Node name for S&R": "UpscaleModelLoader",
+        "widget_ue_connectable": {}
+      },
+      "widgets_values": [
+        "4x-AnimeSharp.pth"
+      ]
+    },
+    {
+      "id": 9,
+      "type": "ImageUpscaleWithModel",
+      "pos": [
+        -1160,
+        1050
+      ],
+      "size": [
+        151.89999389648438,
+        46
+      ],
+      "flags": {},
+      "order": 2,
+      "mode": 0,
+      "inputs": [
+        {
+          "name": "upscale_model",
+          "type": "UPSCALE_MODEL",
+          "link": 7
+        },
+        {
+          "name": "image",
+          "type": "IMAGE",
+          "link": 8
+        }
+      ],
+      "outputs": [
+        {
+          "name": "IMAGE",
+          "type": "IMAGE",
+          "links": [
+            14
+          ]
+        }
+      ],
+      "properties": {
+        "cnr_id": "comfy-core",
+        "ver": "0.3.43",
+        "Node name for S&R": "ImageUpscaleWithModel",
+        "widget_ue_connectable": {}
+      },
+      "widgets_values": []
+    },
+    {
+      "id": 4,
+      "type": "SaveImage",
+      "pos": [
+        -560,
+        950
+      ],
+      "size": [
+        460,
+        310
+      ],
+      "flags": {},
+      "order": 4,
+      "mode": 0,
+      "inputs": [
+        {
+          "name": "images",
+          "type": "IMAGE",
+          "link": 15
+        }
+      ],
+      "outputs": [],
+      "properties": {
+        "cnr_id": "comfy-core",
+        "ver": "0.3.43",
+        "Node name for S&R": "SaveImage",
+        "widget_ue_connectable": {}
+      },
+      "widgets_values": [
+        "ComfyUI"
+      ]
+    },
+    {
+      "id": 11,
+      "type": "LayerUtility: ImageScaleByAspectRatio V2",
+      "pos": [
+        -860,
+        950
+      ],
+      "size": [
+        274.9136657714844,
+        330
+      ],
+      "flags": {},
+      "order": 3,
+      "mode": 0,
+      "inputs": [
+        {
+          "name": "image",
+          "shape": 7,
+          "type": "IMAGE",
+          "link": 14
+        },
+        {
+          "name": "mask",
+          "shape": 7,
+          "type": "MASK",
+          "link": null
+        }
+      ],
+      "outputs": [
+        {
+          "name": "image",
+          "type": "IMAGE",
+          "links": [
+            15
+          ]
+        },
+        {
+          "name": "mask",
+          "type": "MASK",
+          "links": null
+        },
+        {
+          "name": "original_size",
+          "type": "BOX",
+          "links": null
+        },
+        {
+          "name": "width",
+          "type": "INT",
+          "links": null
+        },
+        {
+          "name": "height",
+          "type": "INT",
+          "links": null
+        }
+      ],
+      "properties": {
+        "cnr_id": "comfyui_layerstyle",
+        "ver": "a46b1e6d26d45be9784c49f7065ba44700ef2b63",
+        "Node name for S&R": "LayerUtility: ImageScaleByAspectRatio V2",
+        "widget_ue_connectable": {}
+      },
+      "widgets_values": [
+        "original",
+        1,
+        1,
+        "letterbox",
+        "lanczos",
+        "8",
+        "longest",
+        2160,
+        "#000000"
+      ],
+      "color": "rgba(38, 73, 116, 0.7)"
+    }
+  ],
+  "links": [
+    [
+      7,
+      8,
+      0,
+      9,
+      0,
+      "UPSCALE_MODEL"
+    ],
+    [
+      8,
+      1,
+      0,
+      9,
+      1,
+      "IMAGE"
+    ],
+    [
+      14,
+      9,
+      0,
+      11,
+      0,
+      "IMAGE"
+    ],
+    [
+      15,
+      11,
+      0,
+      4,
+      0,
+      "IMAGE"
+    ]
+  ],
+  "groups": [],
+  "config": {},
+  "extra": {
+    "ue_links": [],
+    "links_added_by_ue": [],
+    "ds": {
+      "scale": 0.8390545288824024,
+      "offset": [
+        1965.7694737112463,
+        -562.0755511845969
+      ]
+    },
+    "frontendVersion": "1.23.4",
+    "VHS_latentpreview": false,
+    "VHS_latentpreviewrate": 0,
+    "VHS_MetadataImage": true,
+    "VHS_KeepIntermediate": true
+  },
+  "version": 0.4
+}

+ 51 - 0
src/main/java/com/backendsys/modules/sdk/comfyui/utils/ComfyUtil.java

@@ -1,8 +1,59 @@
 package com.backendsys.modules.sdk.comfyui.utils;
 
+import com.backendsys.modules.common.config.security.utils.SecurityUtil;
+import com.backendsys.modules.sdk.comfyui.dao.ComfyuiTaskDao;
+import com.backendsys.modules.sdk.comfyui.dao.ComfyuiTaskExecuteDao;
+import com.backendsys.modules.sdk.comfyui.entity.ComfyuiTask;
+import com.backendsys.modules.sdk.comfyui.entity.ComfyuiTaskExecute;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
 @Component
 public class ComfyUtil {
 
+    @Autowired
+    private ComfyuiTaskDao comfyuiTaskDao;
+    
+    @Autowired
+    private ComfyuiTaskExecuteDao comfyuiTaskExecuteDao;
+
+    /**
+     * 初始化任务
+     */
+    public Long initComfyuiTask(String client_id, String prompt, String type) {
+        Long user_id = SecurityUtil.getUserId();
+        try {
+            ComfyuiTask comfyuiTask = new ComfyuiTask();
+            comfyuiTask.setUser_id(user_id);
+            comfyuiTask.setClient_id(client_id);
+            comfyuiTask.setTask_type(type);
+            comfyuiTask.setGenerate_request(prompt);
+            comfyuiTaskDao.insert(comfyuiTask);
+            return comfyuiTask.getId();
+        } catch (Exception e) {
+            System.out.println(e.getMessage());
+        }
+        return -1L;
+    }
+    /**
+     * [更新] 执行任务
+     * - status: 任务状态 (-1:未开始, 1:进行中, 2:成功, 3:失败)
+     */
+    public void executeComfyuiTask(String prompt_id, String response, Integer status) {
+        try {
+            LambdaQueryWrapper<ComfyuiTaskExecute> wrapper = new LambdaQueryWrapper<>();
+            wrapper.eq(ComfyuiTaskExecute::getExecute_prompt_id, prompt_id);
+
+            ComfyuiTaskExecute comfyuiTaskExecute = new ComfyuiTaskExecute();
+            comfyuiTaskExecute.setExecute_status(status);
+            comfyuiTaskExecute.setExecute_prompt_id(prompt_id);
+            comfyuiTaskExecute.setGenerate_response(response);
+
+            comfyuiTaskExecuteDao.update(comfyuiTaskExecute, wrapper);
+        } catch (Exception e) {
+            System.out.println(e.getMessage());
+        }
+    }
+
 }

+ 5 - 2
src/main/java/com/backendsys/modules/sdk/douyincloud/tos/service/DouyinTosService.java

@@ -11,6 +11,8 @@ import java.io.IOException;
 import java.util.List;
 
 /**
+ * 抖音云 - 对象储存
+ * https://cloud.douyin.com/overview
  * 文档首页/对象存储/SDK参考/Java/对象接口/上传对象/普通上传(Java SDK)
  * https://www.volcengine.com/docs/6349/79898
  * 分片上传
@@ -21,8 +23,9 @@ public interface DouyinTosService {
     // [抖音云TOS] 上传对象
     SysFileResult putObject(MultipartFile multipartFile, String object_key) throws IOException;
     // [抖音云TOS] 删除对象
-    void deleteObject(String object_key) throws IOException;
-
+    void deleteObject(String object_key);
+    // [抖音云TOS] 批量删除对象
+    void deleteObjects(List<String> object_keys);
 
     // [抖音云TOS] 初始化分块上传
     CreateMultipartUploadOutput initiateMultipartUpload(String object_name);

+ 50 - 0
src/main/java/com/backendsys/modules/sdk/douyincloud/tos/service/impl/DouyinTosServiceImpl.java

@@ -34,6 +34,7 @@ import java.io.InputStream;
 import java.math.BigDecimal;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.stream.Collectors;
 
 // 对象接口 -> 上传对象
 // https://www.volcengine.com/docs/6349/79898
@@ -157,6 +158,7 @@ public class DouyinTosServiceImpl implements DouyinTosService {
             result.setE_tag(output.getEtag());
             result.setDomain(DOMAIN);
             result.setUrl(DOMAIN + "/" + object_key);
+            result.setTarget(3);
             return result;
 
         } catch (TosClientException | TosServerException e) {
@@ -201,6 +203,53 @@ public class DouyinTosServiceImpl implements DouyinTosService {
         }
     }
 
+    // [抖音云TOS] 批量删除对象
+    @Override
+    public void deleteObjects(List<String> object_keys) {
+        try {
+
+            // 初始化 TosClient
+            TOSV2 tos = new TOSV2ClientBuilder().build(REGION, ENDPOINT, ACCESS_KEY_ID, SECRET_ACCESS_KEY);
+
+            List<ObjectTobeDeleted> objs = new ArrayList<>();
+            object_keys.stream().forEach(object_key -> {
+                ObjectTobeDeleted obj = new ObjectTobeDeleted().setKey(object_key);
+                objs.add(obj);
+            });
+
+            System.out.println("[抖音云TOS][即将删除对象]: " +
+                    object_keys.stream().collect(Collectors.joining(", ", "[", "]")));
+
+            DeleteMultiObjectsV2Input input = new DeleteMultiObjectsV2Input().setBucket(BUCKET_NAME).setObjects(objs);
+            DeleteMultiObjectsV2Output output = tos.deleteMultiObjects(input);
+            System.out.println("[抖音云TOS][批量删除对象成功].");
+
+            if (output.getDeleteds() != null) {
+                for (int i = 0; i < output.getDeleteds().size(); i++){
+                    Deleted deleted = output.getDeleteds().get(i);
+                    System.out.println("deleted object is " + deleted);
+                }
+            }
+            if (output.getErrors() != null) {
+                for (int i = 0; i < output.getErrors().size(); i++){
+                    DeleteError error = output.getErrors().get(i);
+                    System.out.println("delete error is " + error);
+                }
+            }
+
+            tos.close();
+
+        } catch (TosClientException e) {
+            System.out.println("deleteMultiObjects failed (TosClientException): " + e.getMessage());
+        } catch (TosServerException e) {
+            System.out.println("deleteMultiObjects failed (TosServerException): " + e.getMessage());
+        } catch (Throwable t) {
+            // 作为兜底捕获其他异常,一般不会执行到这里
+            System.out.println("deleteMultiObjects failed");
+            System.out.println("unexpected exception, message: " + t.getMessage());
+        }
+    }
+
 
     /**
      * [抖音云TOS] 初始化分块上传
@@ -417,6 +466,7 @@ public class DouyinTosServiceImpl implements DouyinTosService {
                 result.setE_tag(output.getEtag());
                 result.setSize(size);
                 result.setUrl(DOMAIN + "/" + object_key);
+                result.setTarget(3);
                 return result;
 
             } catch (TosClientException | TosServerException e) {

+ 2 - 2
src/main/java/com/backendsys/modules/sdk/tencentcloud/cos/service/TencentCosService.java

@@ -20,8 +20,8 @@ public interface TencentCosService {
     // [腾讯云COS] 删除对象
     void deleteObject(String object_key);
 
-//    // [腾讯云COS] 批量删除对象
-//    void deleteObjects(String object_keys);
+    // [腾讯云COS] 批量删除对象
+    void deleteObjects(List<String> object_keys);
 
     // [腾讯云COS] 查询对象是否存在
     boolean doesObjectExist(String object_key);

+ 33 - 5
src/main/java/com/backendsys/modules/sdk/tencentcloud/cos/service/impl/TencentCosServiceImpl.java

@@ -19,6 +19,7 @@ import com.qcloud.cos.auth.BasicCOSCredentials;
 import com.qcloud.cos.auth.COSCredentials;
 import com.qcloud.cos.exception.CosClientException;
 import com.qcloud.cos.exception.CosServiceException;
+import com.qcloud.cos.exception.MultiObjectDeleteException;
 import com.qcloud.cos.http.HttpMethodName;
 import com.qcloud.cos.model.*;
 import com.qcloud.cos.region.Region;
@@ -35,9 +36,11 @@ import org.springframework.web.multipart.MultipartFile;
 import java.io.*;
 import java.math.BigDecimal;
 import java.net.URL;
+import java.util.ArrayList;
 import java.util.Date;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.stream.Collectors;
 
 // 图片处理概述
 // https://cloud.tencent.com/document/product/436/42215
@@ -185,6 +188,7 @@ public class TencentCosServiceImpl implements TencentCosService {
             result.setE_tag(uploadResult.getETag());
             result.setDomain(ACCESSIBLE_DOMAIN);
             result.setUrl(ACCESSIBLE_DOMAIN + "/" + object_key);
+            result.setTarget(1);
             return result;
 
         } catch (IOException e) {
@@ -221,11 +225,34 @@ public class TencentCosServiceImpl implements TencentCosService {
         cosClient.shutdown();
     }
 
-//    // [腾讯云COS] 批量删除对象
-//    // https://cloud.tencent.com/document/product/436/65939#841fe310-bdf8-4789-9bc0-26ea844e316d
-//    @Override
-//    public void deleteObjects(String object_keys) {
-//    }
+    // [腾讯云COS] 批量删除对象
+    // https://cloud.tencent.com/document/product/436/65939#841fe310-bdf8-4789-9bc0-26ea844e316d
+    @Override
+    public void deleteObjects(List<String> object_keys) {
+        COSClient cosClient = getClient();
+
+        DeleteObjectsRequest deleteObjectsRequest = new DeleteObjectsRequest(BUCKET_NAME);
+        List<DeleteObjectsRequest.KeyVersion> keyList = new ArrayList<>();
+        object_keys.stream().forEach(object_key -> {
+            keyList.add(new DeleteObjectsRequest.KeyVersion(object_key));
+        });
+        deleteObjectsRequest.setKeys(keyList);
+
+        System.out.println("[腾讯云Cos][即将删除对象]: " +
+                keyList.stream()
+                .map(obj -> obj.getKey())
+                .collect(Collectors.joining(", ", "[", "]")));
+
+        DeleteObjectsResult deleteObjectsResult = cosClient.deleteObjects(deleteObjectsRequest);
+        List<DeleteObjectsResult.DeletedObject> deleteObjectResultArray = deleteObjectsResult.getDeletedObjects();
+
+        System.out.println("[腾讯云Cos][批量删除对象成功]: " +
+                deleteObjectResultArray.stream()
+                .map(obj -> obj.getKey())
+                .collect(Collectors.joining(", ", "[", "]")));
+
+        cosClient.shutdown();
+    }
 
 
     // [腾讯云COS] 生成临时图片地址
@@ -401,6 +428,7 @@ public class TencentCosServiceImpl implements TencentCosService {
             result.setE_tag(uploadResult.getETag());
             result.setSize(size);
             result.setUrl(ACCESSIBLE_DOMAIN + "/" + object_key);
+            result.setTarget(1);
             return result;
 
         } catch (CosClientException e) {

+ 0 - 2
src/main/java/com/backendsys/modules/sdk/tencentcloud/cos/utils/TencentCosUtil.java

@@ -37,6 +37,4 @@ public class TencentCosUtil {
         return file;
     }
 
-
-
 }

+ 2 - 1
src/main/java/com/backendsys/modules/sse/entity/SseResponseEnum.java

@@ -7,7 +7,8 @@ public enum SseResponseEnum {
     LOGOUT("logout", "退出登录"),
     NOTICE("notice", "通知"),
     UPLOAD("upload", "上传"),
-    COMFYUI("comfyui", "ComfyUI"),
+    COMFYUI("comfyui", "Comfyui"),
+    COMFYUI_TASK("comfyui_task", "Comfyui 任务队列"),
     OLLAMA("ollama", "Ollama"),
     DEEPSEEK("deepseek", "Deepseek"),
     HUNYUAN("hunyuan", "Hunyuan");

+ 11 - 0
src/main/java/com/backendsys/modules/upload/entity/ObjectKey.java

@@ -0,0 +1,11 @@
+package com.backendsys.modules.upload.entity;
+
+import lombok.Data;
+
+@Data
+public class ObjectKey {
+    String object_key;
+    Integer target;
+
+
+}

+ 1 - 0
src/main/java/com/backendsys/modules/upload/entity/SysFileResult.java

@@ -10,4 +10,5 @@ public class SysFileResult {
     private String domain;
     private String url;
     private Long size;
+    private Integer target;
 }

+ 1 - 5
src/main/java/com/backendsys/modules/upload/service/impl/SysFileServiceImpl.java

@@ -337,11 +337,7 @@ public class SysFileServiceImpl extends ServiceImpl<SysFileDao, SysFile> impleme
         }
         if (target == 3) {
             // [抖音云] 删除对象
-            try {
-                douyinTosService.deleteObject(object_key);
-            } catch (IOException e) {
-                throw new RuntimeException(e);
-            }
+            douyinTosService.deleteObject(object_key);
             System.out.println("Delete douyin tos object: " + object_key);
         }
     }

+ 59 - 0
src/main/java/com/backendsys/modules/upload/utils/ObjectKeyUtil.java

@@ -0,0 +1,59 @@
+package com.backendsys.modules.upload.utils;
+
+import com.backendsys.modules.sdk.douyincloud.tos.service.DouyinTosService;
+import com.backendsys.modules.sdk.tencentcloud.cos.service.TencentCosService;
+import com.backendsys.modules.upload.entity.ObjectKey;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+@Component
+public class ObjectKeyUtil {
+
+    @Autowired
+    private TencentCosService tencentCosService;
+    @Autowired
+    private DouyinTosService douyinTosService;
+
+    /**
+     * 按 target 分组
+     */
+    public Map<Integer, List<ObjectKey>> groupByTarget(List<ObjectKey> objectKeys) {
+        return objectKeys.stream().collect(Collectors.groupingBy(ObjectKey::getTarget));
+    }
+
+    /**
+     * 批量删除 ObjectKeys (By target)
+     */
+    public void deleteObjects(List<ObjectKey> objectKeys) {
+
+        // 按 Target 分组 (-1:本地, 1:腾讯云, 2:阿里云, 3.抖音云)
+        Map<Integer, List<ObjectKey>> groups = groupByTarget(objectKeys);
+
+        // [腾讯云]
+        List<ObjectKey> tencent_object_keys = groups.get(1);
+        if (tencent_object_keys != null && !tencent_object_keys.isEmpty()) {
+            List<String> object_keys = tencent_object_keys.stream().map(ObjectKey::getObject_key).collect(Collectors.toList());
+            tencentCosService.deleteObjects(object_keys);
+        }
+
+        // [阿里云] (未开发)
+        List<ObjectKey> ali_object_keys = groups.get(2);
+        if (ali_object_keys != null && !ali_object_keys.isEmpty()) {
+            List<String> object_keys = ali_object_keys.stream().map(ObjectKey::getObject_key).collect(Collectors.toList());
+            System.out.println("(ali) object_keys (未开发) = " + object_keys);
+        }
+
+        // [抖音云]
+        List<ObjectKey> douyin_object_keys = groups.get(3);
+        if (douyin_object_keys != null && !douyin_object_keys.isEmpty()) {
+            List<String> object_keys = douyin_object_keys.stream().map(ObjectKey::getObject_key).collect(Collectors.toList());
+            douyinTosService.deleteObjects(object_keys);
+        }
+
+    }
+
+}

+ 36 - 0
src/main/java/com/backendsys/modules/upload/utils/UploadUtil.java

@@ -1,5 +1,7 @@
 package com.backendsys.modules.upload.utils;
 
+import cn.hutool.core.codec.Base64;
+import cn.hutool.core.convert.Convert;
 import com.backendsys.exception.CustException;
 import com.backendsys.modules.common.utils.CommonUtil;
 import com.backendsys.modules.upload.entity.SysFileResult;
@@ -9,6 +11,7 @@ import net.coobird.thumbnailator.Thumbnails;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Component;
 import org.springframework.web.multipart.MultipartFile;
+import org.springframework.web.util.UriComponentsBuilder;
 
 import java.io.File;
 import java.io.IOException;
@@ -105,4 +108,37 @@ public class UploadUtil {
         }
     }
 
+    // 不同的云环境 (target),缩略图配置也不一样
+    //   -1:本地:
+    //   1:腾讯云: https://cloud.tencent.com/document/product/436/113295
+    //   2:阿里云:
+    //   3:抖音云: https://www.volcengine.com/docs/6349/153626
+    public static String getImageThumbUrl(String url, Integer target, Integer width, Integer height, String backgroundColor) {
+        if (target == -1) {
+            UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url);
+            if (width != null) builder.queryParam("w", width);
+            if (height != null) builder.queryParam("h", height);
+            return builder.toUriString();
+//            return url + "?" + "w=" + width + "&h=" + height;
+        }
+        // 腾讯云 (color值通过base64加密, #f8f8f8)
+        if (target == 1) {
+            backgroundColor = "#" + backgroundColor;
+            String w = (width != null) ? Convert.toStr(width) : "";
+            String h = (height != null) ? Convert.toStr(height) : "";
+            return url + "?imageMogr2/thumbnail/" + w + "x" + h + "/pad/1/color/" + Base64.encode(backgroundColor);
+        }
+        // 抖音云
+        if (target == 3) {
+            String w = (width != null) ? (",w_" + width) : "";
+            String h = (height != null) ? (",h_" + height) : "";
+//            return url + "?x-tos-process=image/resize,w_" + width + ",h_" + height + ",m_pad,color_" + backgroundColor;
+            return url + "?x-tos-process=image/resize" + w + h + ",color_" + backgroundColor;
+        }
+        return url;
+    }
+
+
+
+
 }

+ 16 - 1
src/main/resources/application-dev.yml

@@ -47,6 +47,19 @@ spring:
       host: 172.19.0.7
       port: 6388
       password: p1FM!fkfPdBQ%@5o
+  rabbitmq:
+    host: localhost
+    port: 5672
+    username: guest
+    password: guest
+    publisher-confirm-type: correlated   # 开启发布确认
+    publisher-returns: true              # 开启发布返回
+    listener:
+      simple:
+        concurrency: 8
+        max-concurrency: 16
+        prefetch: 32
+
 #    cache:
 #      type: redis
 #      redis:
@@ -198,4 +211,6 @@ klingai:
 
 comfyui:
   host: 127.0.0.1
-  token: $2b$12$.MR4qGaFetN1FPQzbfyIrehsyjnPJ12xAZhR/l7KZpLkUPQTCG4gy
+  token: $2b$12$.MR4qGaFetN1FPQzbfyIrehsyjnPJ12xAZhR/l7KZpLkUPQTCG4gy
+  is-save: true
+  # queue-key: comfyui:queue

+ 18 - 1
src/main/resources/application-local.yml

@@ -47,6 +47,21 @@ spring:
       host: 127.0.0.1
       port: 6388
       password: 123456
+  rabbitmq:
+    host: localhost
+    port: 5672
+    username: guest
+    password: guest
+    publisher-confirm-type: correlated   # 开启发布确认
+    publisher-returns: true              # 开启发布返回
+    template:
+      mandatory: true         # 强制检查消息是否路由成功(配合publisher-returns
+    listener:
+      simple:
+        concurrency: 8
+        max-concurrency: 16
+        prefetch: 32
+
 #    cache:
 #      type: redis
 #      redis:
@@ -211,4 +226,6 @@ klingai:
 
 comfyui:
   host: 43.128.1.201
-  token: $2b$12$.MR4qGaFetN1FPQzbfyIrehsyjnPJ12xAZhR/l7KZpLkUPQTCG4gy
+  token: $2b$12$.MR4qGaFetN1FPQzbfyIrehsyjnPJ12xAZhR/l7KZpLkUPQTCG4gy
+  is-save: true
+  # queue-key: comfyui:queue

+ 16 - 1
src/main/resources/application-prod.yml

@@ -47,6 +47,19 @@ spring:
       host: 172.19.0.6
       port: 6388
       password: stI2gmsq$Y9z3vdT
+  rabbitmq:
+    host: localhost
+    port: 5672
+    username: guest
+    password: guest
+    publisher-confirm-type: correlated   # 开启发布确认
+    publisher-returns: true              # 开启发布返回
+    listener:
+      simple:
+        concurrency: 8
+        max-concurrency: 16
+        prefetch: 32
+
 #    cache:
 #      type: redis
 #      redis:
@@ -199,4 +212,6 @@ klingai:
 
 comfyui:
   host: 127.0.0.1
-  token: $2b$12$.MR4qGaFetN1FPQzbfyIrehsyjnPJ12xAZhR/l7KZpLkUPQTCG4gy
+  token: $2b$12$.MR4qGaFetN1FPQzbfyIrehsyjnPJ12xAZhR/l7KZpLkUPQTCG4gy
+  is-save: true
+  # queue-key: comfyui:queue

+ 112 - 1
src/main/resources/mapper/crt/drama/CrtDramaProjectStoryboardDao.xml

@@ -2,6 +2,117 @@
 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
 <mapper namespace="com.backendsys.modules.crt.dao.CrtDramaProjectStoryboardDao">
 
+    <sql id="includeStoryboard">
+        id,
+        id drama_project_storyboard_id,
+        user_id,
+        drama_project_id,
+        episode_num,
+        sort,
+        COALESCE(story_prompt, '') story_prompt,
+        COALESCE(story_framing, '') story_framing,
+        COALESCE(story_scene, '') story_scene,
+        COALESCE(story_weather_time, '') story_weather_time,
+        COALESCE(lora_figure_ids, '') lora_figure_ids,
+
+        param_batch_size,
+        param_prompt_flux_guidance,
+        param_sampler,
+        param_step,
+        param_seed_type,
+        COALESCE(param_seed, 0) param_seed,
+        COALESCE(text_to_image_prompt, '') text_to_image_prompt,
+
+        param_video_reference_type,
+        COALESCE(param_video_reference_images, '') param_video_reference_images,
+        COALESCE(image_to_video_prompt, '') image_to_video_prompt,
+
+        generate_image_status,
+        generate_video_status,
+        create_time,
+        update_time
+    </sql>
+
+    <resultMap id="resultMapStoryboard" type="com.backendsys.modules.crt.entity.CrtDramaProjectStoryboard">
+        <id property="id" column="id" jdbcType="BIGINT" />
+        <result property="user_id" column="user_id" javaType="java.lang.Long" />
+        <result property="drama_project_id" column="drama_project_id" javaType="java.lang.Long" />
+        <result property="episode_num" column="episode_num" javaType="java.lang.Integer" />
+        <result property="sort" column="sort" javaType="java.lang.Integer" />
+        <result property="story_prompt" column="story_prompt" />
+        <result property="story_framing" column="story_framing" />
+        <result property="story_scene" column="story_scene" />
+        <result property="story_weather_time" column="story_weather_time" />
+        <result property="lora_figure_ids" column="lora_figure_ids" />
+        <result property="param_batch_size" column="param_batch_size" javaType="java.lang.Integer" />
+        <result property="param_prompt_flux_guidance" column="param_prompt_flux_guidance" javaType="java.lang.Float" />
+        <result property="param_sampler" column="param_sampler" />
+        <result property="param_step" column="param_step" javaType="java.lang.Integer" />
+        <result property="param_seed_type" column="param_seed_type" javaType="java.lang.Integer" />
+        <result property="param_seed" column="param_seed" />
+        <result property="text_to_image_prompt" column="text_to_image_prompt" />
+        <result property="param_video_reference_type" column="param_video_reference_type" javaType="java.lang.Integer" />
+        <result property="param_video_reference_images" column="param_video_reference_images" />
+        <result property="image_to_video_prompt" column="image_to_video_prompt" />
+        <result property="generate_image_status" column="generate_image_status" javaType="java.lang.Integer" />
+        <result property="generate_video_status" column="generate_video_status" javaType="java.lang.Integer" />
+        <result property="create_time" column="create_time" />
+        <result property="update_time" column="update_time" />
+        <collection property="generate_images" javaType="java.util.List"
+            select="queryGenerateImageById" column="id">
+            <id property="id" column="id" jdbcType="BIGINT" />
+            <result property="user_id" column="user_id" javaType="java.lang.Long" />
+            <result property="drama_project_storyboard_id" column="drama_project_storyboard_id" javaType="java.lang.Long" />
+            <result property="prompt_id" column="prompt_id" />
+            <result property="url_origin" column="url_origin" />
+            <result property="url" column="url" />
+            <result property="url_thumb" column="url_thumb" />
+            <result property="object_key" column="object_key" />
+            <result property="target" column="target" javaType="java.lang.Integer" />
+            <result property="create_time" column="create_time" />
+            <result property="update_time" column="update_time" />
+        </collection>
+    </resultMap>
+
+    <!-- 子查询 (生图结果) -->
+    <sql id="includeGenerateImage">
+        id,
+        user_id,
+        drama_project_storyboard_id,
+        COALESCE(prompt_id, "") prompt_id,
+        COALESCE(url_origin, "") url_origin,
+        COALESCE(url, "") url,
+        COALESCE(object_key, "") object_key,
+        COALESCE(target, -1) target,
+        create_time,
+        update_time
+    </sql>
+    <select id="queryGenerateImageById" resultType="com.backendsys.modules.crt.entity.CrtGenerateImage">
+        SELECT <include refid="includeGenerateImage" />
+        FROM crt_generate_image
+        WHERE drama_project_storyboard_id = #{id}
+        ORDER BY create_time DESC
+        LIMIT 8
+    </select>
+    <!--  -->
+
+    <select id="selectStoryboardList" resultMap="resultMapStoryboard">
+        SELECT <include refid="includeStoryboard" />
+        FROM crt_drama_project_storyboard
+        <where>
+            <if test="user_id != null and user_id != ''">
+                AND user_id = #{user_id}
+            </if>
+            <if test="drama_project_id != null and drama_project_id != ''">
+                AND drama_project_id = #{drama_project_id}
+            </if>
+            <if test="episode_num != null and episode_num != ''">
+                AND episode_num = #{episode_num}
+            </if>
+        </where>
+        ORDER BY sort ASC
+    </select>
+
     <!-- 编辑分镜 -->
     <!-- { user_id } 用户ID不能改 -->
     <!-- { drama_project_id, episode_num } 项目ID、集数 不能改 -->
@@ -19,8 +130,8 @@
             <if test="param_prompt_flux_guidance != null and param_prompt_flux_guidance != ''">param_prompt_flux_guidance = #{param_prompt_flux_guidance},</if>
             <if test="param_sampler != null and param_sampler != ''">param_sampler = #{param_sampler},</if>
             <if test="param_step != null and param_step != ''">param_step = #{param_step},</if>
+            <if test="param_seed_type != null and param_seed_type != ''">param_seed_type = #{param_seed_type},</if>
             <if test="param_seed != null and param_seed != ''">param_seed = #{param_seed},</if>
-            <if test="param_seed_custom != null and param_seed_custom != ''">param_seed_custom = #{param_seed_custom},</if>
             <if test="text_to_image_prompt != null and text_to_image_prompt != ''">text_to_image_prompt = #{text_to_image_prompt},</if>
             <if test="param_video_reference_type != null and param_video_reference_type != ''">param_video_reference_type = #{param_video_reference_type},</if>
             <if test="param_video_reference_images != null and param_video_reference_images != ''">param_video_reference_images = #{param_video_reference_images},</if>