Ver Fonte

fix to cos

tsurumure há 1 mês atrás
pai
commit
0813f13ac7

+ 2 - 6
db/crt_generate_image.sql

@@ -9,15 +9,11 @@ CREATE TABLE `crt_generate_image` (
     PRIMARY KEY (`id`),
     `id` BIGINT AUTO_INCREMENT COMMENT 'ID',
     `user_id` BIGINT COMMENT '用户ID',
-    `name` VARCHAR(255) NOT NULL COMMENT '图片名称',
+    `drama_project_storyboard_id` BIGINT COMMENT '分镜ID',
+    `prompt_id` VARCHAR(255) COMMENT '任务ID',
     `url_origin` VARCHAR(2000) NOT NULL COMMENT '原图',
     `url` VARCHAR(2000) COMMENT '转存图',
     `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='短剧创作-生成图片记录表';
-
-# INSERT INTO crt_generate_image(id, name, lora_figure_name, lora_figure_path) VALUES
-#     (1, '柳王妃', 'liuwangfei-0012', '/etc/ComfyUI/custom_nodes/xxx/liuwangfei-0012.safetensors'),
-#     (2, '陆司明', 'lusiming-0001', '/etc/ComfyUI/custom_nodes/xxx/lusiming-0001.safetensors')
-# ;

+ 53 - 11
src/main/java/com/backendsys/modules/common/utils/CommonUtil.java

@@ -14,6 +14,7 @@ import java.io.File;
 import java.io.IOException;
 import java.net.MalformedURLException;
 import java.net.URL;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 
@@ -68,25 +69,66 @@ public class CommonUtil {
             throw new RuntimeException(e);
         }
     }
-    // 下载URL文件到本地
+
+    /**
+     * 从 URL 下载文件到本地临时目录(兼容 Windows & Linux)
+     * @param url 文件下载地址(如 "http://example.com/file.txt")
+     * @return 下载后的本地文件对象
+     * @throws RuntimeException 如果下载失败
+     */
     public static File downloadLocalFromUrl(String url) {
         try {
-            // 获取系统默认的临时目录
+            // 1. 获取系统临时目录(兼容 Win/Linux)
             Path tempDirPath = Paths.get(System.getProperty("java.io.tmpdir"));
-            // 下载文件到本地 (可能会有性能问题)
-            HttpUtil.downloadFile(url, tempDirPath.toFile());
-            // 获得远程 url
+            // 2. 解析 URL 获取文件名(处理编码和路径)
             URL urlObject = new URL(url);
-            // 获得文件名
-            String filename = Paths.get(urlObject.getPath()).getFileName().toString();
-            // 获得下载到本地的路径
-            File downloadFile = new File(tempDirPath + File.separator + filename);
-            return downloadFile;
+            String filename = extractValidFilename(urlObject);
+            // 3. 构建目标文件路径(自动处理路径分隔符)
+            Path targetPath = tempDirPath.resolve(filename);
+            // 4. 下载文件(示例使用 Hutool 的 HttpUtil)
+            HttpUtil.downloadFile(url, targetPath.toFile());
+            // 5. 检查文件是否下载成功
+            if (!targetPath.toFile().exists()) {
+                throw new RuntimeException("下载失败,文件不存在: " + targetPath);
+            }
+            return targetPath.toFile();
         } catch (MalformedURLException e) {
-            throw new RuntimeException(e);
+            throw new RuntimeException("URL 格式错误: " + url, e);
+        } catch (IOException e) {
+            throw new RuntimeException("下载文件失败: " + url, e);
         }
     }
 
+    /**
+     * 从 URL 提取有效文件名(移除非法字符)
+     */
+    private static String extractValidFilename(URL url) {
+        // 获取原始文件名(如 "/downloads/file.txt" → "file.txt")
+        String filename = Paths.get(url.getPath()).getFileName().toString();
+        // 移除 URL 编码(如 "%20" → " ")
+        filename = java.net.URLDecoder.decode(filename, StandardCharsets.UTF_8);
+        // 替换非法字符(兼容 Win/Linux)
+        return filename.replaceAll("[\\\\/:*?\"<>|]", "_");
+    }
+
+//    public static File downloadLocalFromUrl(String url) {
+//        try {
+//            // 获取系统默认的临时目录
+//            Path tempDirPath = Paths.get(System.getProperty("java.io.tmpdir"));
+//            // 下载文件到本地 (可能会有性能问题)
+//            HttpUtil.downloadFile(url, tempDirPath.toFile());
+//            // 获得远程 url
+//            URL urlObject = new URL(url);
+//            // 获得文件名
+//            String filename = Paths.get(urlObject.getPath()).getFileName().toString();
+//            // 获得下载到本地的路径
+//            File downloadFile = new File(tempDirPath + File.separator + filename);
+//            return downloadFile;
+//        } catch (MalformedURLException e) {
+//            throw new RuntimeException(e);
+//        }
+//    }
+
     // [文件名] 插入 '-thumb' 文件名后缀
     public static String insertThumbSuffix(String filePath) {
         int dotIndex = filePath.lastIndexOf('.');

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

@@ -16,6 +16,8 @@ public class CrtGenerateImage {
     @TableId(type = IdType.AUTO)
     private Long id;
     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;                          // 转存图

+ 11 - 1
src/main/java/com/backendsys/modules/crt/service/impl/CrtGenerateServiceImpl.java

@@ -5,11 +5,13 @@ 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.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.SamplerEnums;
 import com.backendsys.modules.crt.service.CrtGenerateService;
@@ -99,6 +101,11 @@ public class CrtGenerateServiceImpl implements CrtGenerateService {
             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);
 
         }
 
@@ -121,7 +128,9 @@ public class CrtGenerateServiceImpl implements CrtGenerateService {
         String client_id = Convert.toStr(UUID.randomUUID());
 
         // -- [ComfyUI] 创建 WebSocket 监听连接 --------------------------
-        comfyUISocketService.connectToSse(client_id, 8000).subscribe();
+        Map<String, Object> params = new LinkedHashMap<>();
+        params.put("drama_project_storyboard_id", drama_project_storyboard_id);
+        comfyUISocketService.connectToSse(client_id, 8000, params).subscribe();
 
         // -------------------------------------------------------------
         // [ComfyUI-基础生图]
@@ -515,6 +524,7 @@ public class CrtGenerateServiceImpl implements CrtGenerateService {
         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)
 
         Map<String, Object> resp = new LinkedHashMap<>();
         resp.put("drama_project_storyboard_id", drama_project_storyboard_id);

+ 13 - 1
src/main/java/com/backendsys/modules/sdk/comfyui/controller/ComfyUIDemoController.java

@@ -4,6 +4,8 @@ import com.backendsys.exception.CustException;
 import com.backendsys.modules.common.config.security.annotations.Anonymous;
 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;
@@ -22,8 +24,10 @@ import java.util.concurrent.Executors;
 public class ComfyUIDemoController {
 
     @Autowired
-    private ComfyUIService comfyUIService;
+    private TencentCosService tencentCosService;
 
+    @Autowired
+    private ComfyUIService comfyUIService;
     @Autowired
     private ComfyUISocketService comfyUISocketService;
 
@@ -47,4 +51,12 @@ public class ComfyUIDemoController {
         return "Disconnected: " + clientId;
     }
 
+//    /**
+//     * 转存测试
+//     */
+//    @GetMapping("/api/comfyui/testToCos")
+//    public SysFileResult testToCos(String url) {
+//        return tencentCosService.urlToCOS(url, "png");
+//    }
+
 }

+ 3 - 0
src/main/java/com/backendsys/modules/sdk/comfyui/service/ComfyUISocketService.java

@@ -2,6 +2,8 @@ package com.backendsys.modules.sdk.comfyui.service;
 
 import reactor.core.publisher.Mono;
 
+import java.util.Map;
+
 public interface ComfyUISocketService {
 
     // [ComfyUI] 创建 WebSocket 监听连接
@@ -9,6 +11,7 @@ public interface ComfyUISocketService {
 
     // [ComfyUI] 创建 WebSocket 监听连接 (转发到 SSE)
     Mono<Void> connectToSse(String clientId, Integer port);
+    Mono<Void> connectToSse(String clientId, Integer port, Map<String, Object> params);
 
     // [ComfyUI] 断开 WebSocket 监听连接
     Mono<Void> disconnect(String clientId);

+ 0 - 1
src/main/java/com/backendsys/modules/sdk/comfyui/service/impl/ComfyUIServiceImpl.java

@@ -32,7 +32,6 @@ public class ComfyUIServiceImpl implements ComfyUIService {
     private WebClient webClient;
     public WebClient getWebClient() {
         if (webClient == null) {
-//            webClient = WebClient.builder().baseUrl(COMFYUI_HOST + ":8000").filter(WebClientFilter.logFilter).build();
             webClient = WebClient.builder().filter(WebClientFilter.logFilter).build();
         }
         return webClient;

+ 52 - 1
src/main/java/com/backendsys/modules/sdk/comfyui/service/impl/ComfyUISocketServiceImpl.java

@@ -6,11 +6,17 @@ 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.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 org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
@@ -35,6 +41,14 @@ public class ComfyUISocketServiceImpl implements ComfyUISocketService {
 
     @Autowired
     private SseUtil sseUtil;
+    @Autowired
+    private SysCommonService sysCommonService;
+    @Autowired
+    private TencentCosService tencentCosService;
+    @Autowired
+    private DouyinTosService douyinTosService;
+    @Autowired
+    private CrtGenerateImageDao crtGenerateImageDao;
 
     @Value("${comfyui.host}")
     private String COMFYUI_HOST;
@@ -98,8 +112,15 @@ public class ComfyUISocketServiceImpl implements ComfyUISocketService {
      */
     @Override
     public Mono<Void> connectToSse(String clientId, Integer port) {
+        return connectToSse(clientId, port, null);
+    }
+    @Override
+    public Mono<Void> connectToSse(String clientId, Integer port, Map<String, Object> params) {
+
+        CrtGenerateImage entity = new CrtGenerateImage();
 
         Long user_id = SecurityUtil.getUserId();
+        entity.setUser_id(user_id);
 
         String wsUrl =  "ws://" + COMFYUI_HOST + ":" + port + "/ws";
         return Mono.defer(() -> {
@@ -132,6 +153,9 @@ public class ComfyUISocketServiceImpl implements ComfyUISocketService {
                                 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
+
                                 Object imagesObj = output.get("images");
                                 if (imagesObj != null) {
                                     JSONArray images = JSONUtil.parseArray(imagesObj);
@@ -144,12 +168,39 @@ public class ComfyUISocketServiceImpl implements ComfyUISocketService {
                                             JSONObject image = images.getJSONObject(i);
                                             String filename = image.getStr("filename");
                                             String filepath = "http://" + COMFYUI_HOST + ":" + port + "/api/view?filename=" + filename;
-                                            images_path.add(filepath);
+                                            String filepath_with_token = filepath + "&token=" + COMFYUI_TOKEN;
 
                                             // -- [图片转存储存桶] -------------------------------------
+                                            SysFileResult result = new SysFileResult();
+                                            Integer UPLOAD_TARGET = Convert.toInt(sysCommonService.getCommonByTag("UPLOAD_TARGET"));
 
+                                            // target: 上传目标 (-1:本地, 1:腾讯云, 2:阿里云, 3.抖音云)
+                                            if (UPLOAD_TARGET == 1) {
+                                                result = tencentCosService.urlToCOS(filepath_with_token);
+                                            }
+                                            if (UPLOAD_TARGET == 3) {
+                                                result = douyinTosService.urlToTOS(filepath_with_token);
+                                            }
+                                            System.out.println("filepath_with_token = " + filepath_with_token);
+                                            System.out.println("result = " + result);
+
+                                            // -- [记录到生成图片记录表] --------------------------------
+                                            if (params != null) {
+                                                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);
+                                                    System.out.println("crtGenerateImage entity = " + entity);
+                                                    crtGenerateImageDao.insert(entity);
+                                                }
+                                            }
                                             // ------------------------------------------------------
 
+                                            // 这里要改成 result 的返回地址拼接?
+                                            images_path.add(filepath);
+
                                         }
                                     }
                                     output.put("images_path", images_path);

+ 1 - 0
src/main/java/com/backendsys/modules/sdk/douyincloud/tos/service/DouyinTosService.java

@@ -37,5 +37,6 @@ public interface DouyinTosService {
 
     // [抖音云TOS] URL 转存
     SysFileResult urlToTOS(String origin_url);
+    SysFileResult urlToTOS(String origin_url, String input_suffix);
 
 }

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

@@ -153,6 +153,7 @@ public class DouyinTosServiceImpl implements DouyinTosService {
             result.setRequest_id(output.getRequestInfo().getRequestId());
             result.setE_tag(output.getEtag());
             result.setDomain(DOMAIN);
+            result.setUrl(DOMAIN + "/" + object_key);
             return result;
 
         } catch (TosClientException | TosServerException e) {
@@ -355,14 +356,19 @@ public class DouyinTosServiceImpl implements DouyinTosService {
     /**
      * [抖音云TOS] URL 转存
      */
+    @Override
     public SysFileResult urlToTOS(String origin_url) {
+        return urlToTOS(origin_url, null);
+    }
+    @Override
+    public SysFileResult urlToTOS(String origin_url, String input_suffix) {
 
         if (StrUtil.isEmpty(origin_url)) throw new CustException("url 不能为空");
 
         // 下载URL文件到本地
         File downloadFile = CommonUtil.downloadLocalFromUrl(origin_url);
         // 获得URL文件后缀名
-        String suffix = CommonUtil.getFileSuffixFromUrl(origin_url);
+        String suffix = StrUtil.isEmpty(input_suffix) ? CommonUtil.getFileSuffixFromUrl(origin_url) : input_suffix;
         // 获得文件大小
         Long size = downloadFile.length();
 
@@ -403,6 +409,7 @@ public class DouyinTosServiceImpl implements DouyinTosService {
                 result.setRequest_id(output.getRequestInfo().getRequestId());
                 result.setE_tag(output.getEtag());
                 result.setSize(size);
+                result.setUrl(DOMAIN + "/" + object_key);
                 return result;
 
             } catch (TosClientException | TosServerException e) {

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

@@ -42,4 +42,5 @@ public interface TencentCosService {
 
     // [腾讯云COS] URL 转存
     SysFileResult urlToCOS(String url);
+    SysFileResult urlToCOS(String url, String input_suffix);
 }

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

@@ -189,6 +189,7 @@ public class TencentCosServiceImpl implements TencentCosService {
             result.setRequest_id(uploadResult.getRequestId());
             result.setE_tag(uploadResult.getETag());
             result.setDomain(ACCESSIBLE_DOMAIN);
+            result.setUrl(ACCESSIBLE_DOMAIN + "/" + object_key);
             return result;
 
         } catch (IOException e) {
@@ -359,16 +360,21 @@ public class TencentCosServiceImpl implements TencentCosService {
     }
 
 
-    // [腾讯云COS] URL 转存
+    // [腾讯云COS] URL 转存@Override
     @Override
     public SysFileResult urlToCOS(String url) {
+        return urlToCOS(url, null);
+    }
+
+    @Override
+    public SysFileResult urlToCOS(String url, String input_suffix) {
 
         if (StrUtil.isEmpty(url)) throw new CustException("url 不能为空");
 
         // 下载URL文件到本地
         File downloadFile = CommonUtil.downloadLocalFromUrl(url);
         // 获得URL文件后缀名
-        String suffix = CommonUtil.getFileSuffixFromUrl(url);
+        String suffix = StrUtil.isEmpty(input_suffix) ? CommonUtil.getFileSuffixFromUrl(url) : input_suffix;
         // 获得文件大小
         Long size = downloadFile.length();
 
@@ -396,6 +402,7 @@ public class TencentCosServiceImpl implements TencentCosService {
             result.setRequest_id(uploadResult.getRequestId());
             result.setE_tag(uploadResult.getETag());
             result.setSize(size);
+            result.setUrl(ACCESSIBLE_DOMAIN + "/" + object_key);
             return result;
 
         } catch (CosClientException e) {

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

@@ -8,5 +8,6 @@ public class SysFileResult {
     private String key;
     private String e_tag;
     private String domain;
+    private String url;
     private Long size;
 }