瀏覽代碼

oss ffmpeg

wangyunpeng 3 周之前
父節點
當前提交
4ae9cf2c5e
共有 82 個文件被更改,包括 13292 次插入77 次删除
  1. 18 0
      core/src/main/java/com/tzld/supply/api/volcengine/MidiModel.java
  2. 18 0
      core/src/main/java/com/tzld/supply/api/volcengine/MidiPayloadModel.java
  3. 113 0
      core/src/main/java/com/tzld/supply/api/volcengine/SamiClient.java
  4. 203 0
      core/src/main/java/com/tzld/supply/api/volcengine/SamiTest.java
  5. 219 0
      core/src/main/java/com/tzld/supply/api/volcengine/TokenClient.java
  6. 182 0
      core/src/main/java/com/tzld/supply/api/volcengine/TokenDemo.java
  7. 209 0
      core/src/main/java/com/tzld/supply/api/volcengine/VolcengineMegattsClient.java
  8. 86 0
      core/src/main/java/com/tzld/supply/api/volcengine/VolcengineVoiceTransTextClient.java
  9. 30 0
      core/src/main/java/com/tzld/supply/common/enums/EnumUploadFileType.java
  10. 386 0
      core/src/main/java/com/tzld/supply/config/AliOssConfig.java
  11. 55 0
      core/src/main/java/com/tzld/supply/config/RedisConfig.java
  12. 0 63
      core/src/main/java/com/tzld/supply/config/RedisTemplateConfig.java
  13. 96 0
      core/src/main/java/com/tzld/supply/dao/mapper/supply/spider/SpiderContentMediaMapper.java
  14. 120 0
      core/src/main/java/com/tzld/supply/dao/mapper/supply/spider/ToolsAudioTransRecordMapper.java
  15. 8 0
      core/src/main/java/com/tzld/supply/exception/FFmpegExecuteException.java
  16. 8 0
      core/src/main/java/com/tzld/supply/exception/LayerException.java
  17. 8 0
      core/src/main/java/com/tzld/supply/exception/VaryImageException.java
  18. 12 0
      core/src/main/java/com/tzld/supply/model/dto/ali/AliVoiceResultData.java
  19. 14 0
      core/src/main/java/com/tzld/supply/model/dto/ali/AliVoiceResultSentenceData.java
  20. 11 0
      core/src/main/java/com/tzld/supply/model/dto/ali/AliVoiceResultWordData.java
  21. 17 0
      core/src/main/java/com/tzld/supply/model/param/FFmpeg/AudioSplitParam.java
  22. 13 0
      core/src/main/java/com/tzld/supply/model/param/FFmpeg/AudioVolumeParam.java
  23. 13 0
      core/src/main/java/com/tzld/supply/model/param/FFmpeg/CommandParam.java
  24. 14 0
      core/src/main/java/com/tzld/supply/model/param/FFmpeg/ExtraVideoSrtParam.java
  25. 15 0
      core/src/main/java/com/tzld/supply/model/param/FFmpeg/MediaSimpleMergeParam.java
  26. 14 0
      core/src/main/java/com/tzld/supply/model/param/FFmpeg/MergeBgVideoParam.java
  27. 14 0
      core/src/main/java/com/tzld/supply/model/param/FFmpeg/RemoveBgParam.java
  28. 11 0
      core/src/main/java/com/tzld/supply/model/param/FFmpeg/VideoAddAssSubtitleParam.java
  29. 19 0
      core/src/main/java/com/tzld/supply/model/param/FFmpeg/VideoAddAudioParam.java
  30. 61 0
      core/src/main/java/com/tzld/supply/model/param/FFmpeg/VideoAddTailParam.java
  31. 10 0
      core/src/main/java/com/tzld/supply/model/param/FFmpeg/VideoInfoParam.java
  32. 12 0
      core/src/main/java/com/tzld/supply/model/param/FFmpeg/VideoTextureParam.java
  33. 12 0
      core/src/main/java/com/tzld/supply/model/param/FFmpeg/VideoTimeCutParam.java
  34. 12 0
      core/src/main/java/com/tzld/supply/model/param/FFmpeg/VideoTimeExtraFrameParam.java
  35. 12 0
      core/src/main/java/com/tzld/supply/model/param/FFmpeg/VideoTranscodingParam.java
  36. 16 0
      core/src/main/java/com/tzld/supply/model/param/FFmpeg/VideoUrlsParam.java
  37. 18 0
      core/src/main/java/com/tzld/supply/model/param/FileUploadParam.java
  38. 15 0
      core/src/main/java/com/tzld/supply/model/param/OssUploadSignParam.java
  39. 19 0
      core/src/main/java/com/tzld/supply/model/param/StsTokenParam.java
  40. 196 0
      core/src/main/java/com/tzld/supply/model/param/VideoApiBaseParam.java
  41. 452 0
      core/src/main/java/com/tzld/supply/model/po/supply/spider/SpiderContentMedia.java
  42. 1072 0
      core/src/main/java/com/tzld/supply/model/po/supply/spider/SpiderContentMediaExample.java
  43. 382 0
      core/src/main/java/com/tzld/supply/model/po/supply/spider/ToolsAudioTransRecord.java
  44. 812 0
      core/src/main/java/com/tzld/supply/model/po/supply/spider/ToolsAudioTransRecordExample.java
  45. 25 0
      core/src/main/java/com/tzld/supply/model/vo/FileInfo.java
  46. 49 0
      core/src/main/java/com/tzld/supply/model/vo/PolicyDetailVO.java
  47. 13 0
      core/src/main/java/com/tzld/supply/model/vo/SignatureVO.java
  48. 20 0
      core/src/main/java/com/tzld/supply/model/vo/StsTokenVO.java
  49. 52 0
      core/src/main/java/com/tzld/supply/service/ffmpeg/FFmpegService.java
  50. 1617 0
      core/src/main/java/com/tzld/supply/service/ffmpeg/impl/FFmpegServiceImpl.java
  51. 62 0
      core/src/main/java/com/tzld/supply/service/image/ImageService.java
  52. 53 0
      core/src/main/java/com/tzld/supply/service/image/SplicingImageType.java
  53. 43 0
      core/src/main/java/com/tzld/supply/service/image/dto/DiverseSplicingImageParam.java
  54. 23 0
      core/src/main/java/com/tzld/supply/service/image/dto/SimpleSplicingImageParam.java
  55. 16 0
      core/src/main/java/com/tzld/supply/service/image/dto/SplicingImageDTO.java
  56. 8 0
      core/src/main/java/com/tzld/supply/service/image/exception/DownloadImageError.java
  57. 9 0
      core/src/main/java/com/tzld/supply/service/image/exception/SplicingImageError.java
  58. 421 0
      core/src/main/java/com/tzld/supply/service/image/impl/ImageServiceImpl.java
  59. 19 0
      core/src/main/java/com/tzld/supply/service/tools/ToolsAudioTransService.java
  60. 141 0
      core/src/main/java/com/tzld/supply/service/tools/impl/ToolsAudioTransServiceImpl.java
  61. 1492 0
      core/src/main/java/com/tzld/supply/util/AliOssFileTool.java
  62. 72 0
      core/src/main/java/com/tzld/supply/util/BasePropertiesUtils.java
  63. 139 0
      core/src/main/java/com/tzld/supply/util/BaseUtils.java
  64. 169 0
      core/src/main/java/com/tzld/supply/util/CdnUtil.java
  65. 15 0
      core/src/main/java/com/tzld/supply/util/DateUtils.java
  66. 30 0
      core/src/main/java/com/tzld/supply/util/FileUtils.java
  67. 347 0
      core/src/main/java/com/tzld/supply/util/ImageUtils.java
  68. 92 0
      core/src/main/java/com/tzld/supply/util/PropertiesUtils.java
  69. 64 0
      core/src/main/java/com/tzld/supply/util/RandomUtil.java
  70. 257 0
      core/src/main/java/com/tzld/supply/util/RedisUtils.java
  71. 55 0
      core/src/main/java/com/tzld/supply/util/TimelineUtils.java
  72. 1668 0
      core/src/main/java/com/tzld/supply/util/ffmpeg/FFmpegUtil.java
  73. 166 1
      core/src/main/java/com/tzld/supply/util/http/HttpClientUtils.java
  74. 3 1
      core/src/main/resources/generator/mybatis-spider-generator-config.xml
  75. 398 0
      core/src/main/resources/mapper/supply/spider/SpiderContentMediaMapper.xml
  76. 438 0
      core/src/main/resources/mapper/supply/spider/ToolsAudioTransRecordMapper.xml
  77. 142 0
      server/src/main/java/com/tzld/supply/controller/FFmpegController.java
  78. 102 0
      server/src/main/java/com/tzld/supply/controller/FileController.java
  79. 3 4
      server/src/main/resources/application-dev.yml
  80. 3 4
      server/src/main/resources/application-prod.yml
  81. 3 4
      server/src/main/resources/application-test.yml
  82. 36 0
      server/src/main/resources/application.yml

+ 18 - 0
core/src/main/java/com/tzld/supply/api/volcengine/MidiModel.java

@@ -0,0 +1,18 @@
+package com.tzld.supply.api.volcengine;
+
+import lombok.Getter;
+import lombok.Setter;
+import lombok.experimental.Accessors;
+
+import java.math.BigDecimal;
+
+@Getter
+@Setter
+@Accessors(chain = true)
+public class MidiModel {
+    private BigDecimal start;
+    private BigDecimal end;
+    private Integer pitch;
+    // 力度(音量)
+    private Integer velocity;
+}

+ 18 - 0
core/src/main/java/com/tzld/supply/api/volcengine/MidiPayloadModel.java

@@ -0,0 +1,18 @@
+package com.tzld.supply.api.volcengine;
+
+import lombok.Getter;
+import lombok.Setter;
+import lombok.experimental.Accessors;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+@Getter
+@Setter
+@Accessors(chain = true)
+public class MidiPayloadModel {
+    private String track;
+    private List<MidiModel> midiList;
+    // 置信度
+    private BigDecimal confidence;
+}

+ 113 - 0
core/src/main/java/com/tzld/supply/api/volcengine/SamiClient.java

@@ -0,0 +1,113 @@
+package com.tzld.supply.api.volcengine;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.tzld.supply.common.enums.ExceptionEnum;
+import com.tzld.supply.common.exception.CommonException;
+import com.tzld.supply.util.http.HttpClientUtils;
+import com.tzld.supply.util.http.HttpResponseContent;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.Base64;
+import java.util.List;
+
+@Slf4j
+@Component
+public class SamiClient {
+
+    private static final String appKey = "xPfFWHqBHG";
+
+    @Autowired
+    TokenClient tokenClient;
+
+    /**
+     * 音源分离
+     * @param musicUrl
+     * @param model 2track_vocal-提取人声,2track_acc-提取伴奏
+     * @return
+     */
+    public byte[] musicSourceSeparate(String musicUrl, String model) throws Exception {
+        String token = tokenClient.getToken(appKey, "cn-north-1", "sami", 3600);
+        JSONObject params = new JSONObject();
+        params.put("appkey", appKey);
+        params.put("token", token);
+        params.put("namespace", "MusicSourceSeparate");
+        JSONObject payloadJson = new JSONObject();
+        payloadJson.put("url", musicUrl);
+        payloadJson.put("model", model);
+        params.put("payload", payloadJson.toJSONString());
+        String apiUrl = "https://sami.bytedance.com/api/v1/invoke";
+        log.info("musicSourceSeparate,request:{}", params.toJSONString());
+        HttpResponseContent hrc = HttpClientUtils.postData(apiUrl, params.toJSONString(),
+                HttpClientUtils.contentTypeJson, 5000, 60000);
+        if (hrc == null) {
+            log.error("musicSourceSeparate,hrc is null");
+            throw new CommonException(ExceptionEnum.SYSTEM_ERROR.getCode(), "musicSourceSeparate,hrc is null,http请求超时");
+        }
+        log.info("musicSourceSeparate,request:{}, response:{}", params.toJSONString(), hrc.getBodyContent());
+        if (!hrc.isSuccessful()) {
+            log.error("musicSourceSeparate,httpCode is {},response:{}", hrc.getStatusCode(), hrc.getBodyContent());
+            throw new CommonException(ExceptionEnum.SYSTEM_ERROR.getCode(),
+                    "musicSourceSeparate,httpCode is " + hrc.getStatusCode() + ",response:" + hrc.getBodyContent());
+        }
+        String response = hrc.getBodyContent();
+        JSONObject jsonObject = JSON.parseObject(response);
+        if (jsonObject.getInteger("status_code") != null && jsonObject.getInteger("status_code") == 20000000) {
+            String data = jsonObject.getString("data");
+            Base64.Decoder decoder = Base64.getDecoder();
+            byte[] decodeBytes = decoder.decode(data);
+            return decodeBytes;
+        } else {
+            log.error("musicSourceSeparate,api返回失败,response:{}", response);
+            throw new CommonException(ExceptionEnum.SYSTEM_ERROR.getCode(),
+                    "musicSourceSeparate,api返回失败," + jsonObject.getInteger("status_code") + "," + jsonObject.getString("status_text"));
+        }
+    }
+
+    /**
+     * 获取音乐midi数据
+     *
+     * @param musicUrl
+     * @return
+     * @throws Exception
+     */
+    public List<MidiModel> getMusicMidi(String musicUrl) throws Exception {
+        String token = tokenClient.getToken(appKey, "cn-north-1", "sami", 3600);
+        JSONObject params = new JSONObject();
+        params.put("appkey", appKey);
+        params.put("token", token);
+        params.put("namespace", "MIDI");
+        JSONObject payloadJson = new JSONObject();
+        payloadJson.put("url", musicUrl);
+        JSONObject extra = new JSONObject();
+        extra.put("midi", false);
+        payloadJson.put("extra", extra);
+        params.put("payload", payloadJson.toJSONString());
+        String apiUrl = "https://sami.bytedance.com/api/v1/invoke";
+        log.info("getMusicMidi,request:{}", params.toJSONString());
+        HttpResponseContent hrc = HttpClientUtils.postData(apiUrl, params.toJSONString(),
+                HttpClientUtils.contentTypeJson, 5000, 120000);
+        if (hrc == null) {
+            log.error("getMusicMidi,hrc is null");
+            throw new CommonException(ExceptionEnum.SYSTEM_ERROR.getCode(), "getMusicMidi,hrc is null,http请求超时");
+        }
+        log.info("getMusicMidi,request:{}, response:{}", params.toJSONString(), hrc.getBodyContent());
+        if (!hrc.isSuccessful()) {
+            log.error("getMusicMidi,httpCode is {}", hrc.getStatusCode());
+            throw new CommonException(ExceptionEnum.SYSTEM_ERROR.getCode(),
+                    "getMusicMidi,httpCode is " + hrc.getStatusCode() + ",response:" + hrc.getBodyContent());
+        }
+        String response = hrc.getBodyContent();
+        JSONObject jsonObject = JSON.parseObject(response);
+        if (jsonObject.getInteger("status_code") != null && jsonObject.getInteger("status_code") == 20000000) {
+            String payload = jsonObject.getString("payload");
+            MidiPayloadModel payloadModel = JSON.parseObject(payload, MidiPayloadModel.class);
+            return payloadModel.getMidiList();
+        } else {
+            throw new CommonException(ExceptionEnum.SYSTEM_ERROR.getCode(),
+                    "getMusicMidi,api返回失败," + jsonObject.getInteger("status_code") + "," + jsonObject.getString("status_text"));
+        }
+    }
+}

+ 203 - 0
core/src/main/java/com/tzld/supply/api/volcengine/SamiTest.java

@@ -0,0 +1,203 @@
+package com.tzld.supply.api.volcengine;
+
+import cn.hutool.core.io.FileUtil;
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.List;
+
+public class SamiTest {
+    public static void main(String[] args) {
+        generateAcel();
+    }
+
+    public static void base64Decode() {
+        byte[] bytes = FileUtil.readBytes("/Users/liuzhiheng/Desktop/test2.txt");
+        Base64.Decoder decoder = Base64.getDecoder();
+        byte[] decodeBytes = decoder.decode(bytes);
+        FileUtil.writeBytes(decodeBytes, "/Users/liuzhiheng/Desktop/test2.mp3");
+    }
+
+    public static void generateAcel() {
+        String str = "{\n" +
+                "    \"status_code\": 20000000,\n" +
+                "    \"status_text\": \"OK\",\n" +
+                "    \"task_id\": \"9b33c17d-60c9-491b-bce6-ff4c804ccbc4\",\n" +
+                "    \"namespace\": \"MIDI\",\n" +
+                "    \"payload\": \"{\\\"track\\\":\\\"vocal\\\",\\\"midiList\\\":[{\\\"start\\\":1.08,\\\"end\\\":1" +
+                ".28,\\\"pitch\\\":55,\\\"velocity\\\":100},{\\\"start\\\":1.28,\\\"end\\\":1.36,\\\"pitch\\\":58," +
+                "\\\"velocity\\\":100},{\\\"start\\\":1.36,\\\"end\\\":1.52,\\\"pitch\\\":57,\\\"velocity\\\":100}," +
+                "{\\\"start\\\":1.52,\\\"end\\\":1.82,\\\"pitch\\\":58,\\\"velocity\\\":100},{\\\"start\\\":1.82," +
+                "\\\"end\\\":2.14,\\\"pitch\\\":65,\\\"velocity\\\":100},{\\\"start\\\":2.14,\\\"end\\\":2.46," +
+                "\\\"pitch\\\":62,\\\"velocity\\\":100},{\\\"start\\\":2.46,\\\"end\\\":2.86,\\\"pitch\\\":58," +
+                "\\\"velocity\\\":100},{\\\"start\\\":2.86,\\\"end\\\":3.62,\\\"pitch\\\":55,\\\"velocity\\\":100}," +
+                "{\\\"start\\\":4.56,\\\"end\\\":4.78,\\\"pitch\\\":57,\\\"velocity\\\":100},{\\\"start\\\":4.82," +
+                "\\\"end\\\":5.04,\\\"pitch\\\":57,\\\"velocity\\\":100},{\\\"start\\\":5.08,\\\"end\\\":5.28," +
+                "\\\"pitch\\\":57,\\\"velocity\\\":100},{\\\"start\\\":5.28,\\\"end\\\":5.54,\\\"pitch\\\":58," +
+                "\\\"velocity\\\":100},{\\\"start\\\":5.54,\\\"end\\\":5.98,\\\"pitch\\\":57,\\\"velocity\\\":100}," +
+                "{\\\"start\\\":5.98,\\\"end\\\":6.36,\\\"pitch\\\":53,\\\"velocity\\\":100},{\\\"start\\\":6.36," +
+                "\\\"end\\\":7.3,\\\"pitch\\\":55,\\\"velocity\\\":100},{\\\"start\\\":9.02,\\\"end\\\":9.28," +
+                "\\\"pitch\\\":55,\\\"velocity\\\":100},{\\\"start\\\":9.28,\\\"end\\\":9.54,\\\"pitch\\\":57," +
+                "\\\"velocity\\\":100},{\\\"start\\\":9.54,\\\"end\\\":9.82,\\\"pitch\\\":58,\\\"velocity\\\":100}," +
+                "{\\\"start\\\":9.82,\\\"end\\\":10.12,\\\"pitch\\\":65,\\\"velocity\\\":100},{\\\"start\\\":10.12," +
+                "\\\"end\\\":10.48,\\\"pitch\\\":62,\\\"velocity\\\":100},{\\\"start\\\":10.48,\\\"end\\\":10.92," +
+                "\\\"pitch\\\":58,\\\"velocity\\\":100},{\\\"start\\\":10.92,\\\"end\\\":11.78,\\\"pitch\\\":55," +
+                "\\\"velocity\\\":100},{\\\"start\\\":12.58,\\\"end\\\":12.78,\\\"pitch\\\":57,\\\"velocity\\\":100}," +
+                "{\\\"start\\\":12.82,\\\"end\\\":13.02,\\\"pitch\\\":57,\\\"velocity\\\":100},{\\\"start\\\":13.06," +
+                "\\\"end\\\":13.26,\\\"pitch\\\":57,\\\"velocity\\\":100},{\\\"start\\\":13.26,\\\"end\\\":13.54," +
+                "\\\"pitch\\\":58,\\\"velocity\\\":100},{\\\"start\\\":13.54,\\\"end\\\":13.82,\\\"pitch\\\":60," +
+                "\\\"velocity\\\":100},{\\\"start\\\":13.82,\\\"end\\\":14.06,\\\"pitch\\\":58,\\\"velocity\\\":100}," +
+                "{\\\"start\\\":14.06,\\\"end\\\":14.42,\\\"pitch\\\":57,\\\"velocity\\\":100},{\\\"start\\\":14.42," +
+                "\\\"end\\\":14.82,\\\"pitch\\\":53,\\\"velocity\\\":100},{\\\"start\\\":14.82,\\\"end\\\":15.88," +
+                "\\\"pitch\\\":55,\\\"velocity\\\":100},{\\\"start\\\":17.04,\\\"end\\\":17.28,\\\"pitch\\\":55," +
+                "\\\"velocity\\\":100},{\\\"start\\\":17.28,\\\"end\\\":17.5,\\\"pitch\\\":57,\\\"velocity\\\":100}," +
+                "{\\\"start\\\":17.5,\\\"end\\\":17.78,\\\"pitch\\\":58,\\\"velocity\\\":100},{\\\"start\\\":17.78," +
+                "\\\"end\\\":18.08,\\\"pitch\\\":65,\\\"velocity\\\":100},{\\\"start\\\":18.08,\\\"end\\\":18.46," +
+                "\\\"pitch\\\":62,\\\"velocity\\\":100},{\\\"start\\\":18.46,\\\"end\\\":18.82,\\\"pitch\\\":58," +
+                "\\\"velocity\\\":100},{\\\"start\\\":18.82,\\\"end\\\":19.68,\\\"pitch\\\":55,\\\"velocity\\\":100}," +
+                "{\\\"start\\\":20.56,\\\"end\\\":20.78,\\\"pitch\\\":57,\\\"velocity\\\":100},{\\\"start\\\":20.82," +
+                "\\\"end\\\":21.04,\\\"pitch\\\":57,\\\"velocity\\\":100},{\\\"start\\\":21.08,\\\"end\\\":21.28," +
+                "\\\"pitch\\\":57,\\\"velocity\\\":100},{\\\"start\\\":21.28,\\\"end\\\":21.5,\\\"pitch\\\":58," +
+                "\\\"velocity\\\":100},{\\\"start\\\":21.5,\\\"end\\\":22.06,\\\"pitch\\\":57,\\\"velocity\\\":100}," +
+                "{\\\"start\\\":22.06,\\\"end\\\":22.14,\\\"pitch\\\":55,\\\"velocity\\\":100},{\\\"start\\\":22.14," +
+                "\\\"end\\\":22.38,\\\"pitch\\\":53,\\\"velocity\\\":100},{\\\"start\\\":22.38,\\\"end\\\":22.78," +
+                "\\\"pitch\\\":55,\\\"velocity\\\":100},{\\\"start\\\":22.78,\\\"end\\\":23.08,\\\"pitch\\\":58," +
+                "\\\"velocity\\\":100},{\\\"start\\\":23.08,\\\"end\\\":23.9,\\\"pitch\\\":55,\\\"velocity\\\":100}," +
+                "{\\\"start\\\":25.08,\\\"end\\\":25.32,\\\"pitch\\\":55,\\\"velocity\\\":100},{\\\"start\\\":25.32," +
+                "\\\"end\\\":25.56,\\\"pitch\\\":57,\\\"velocity\\\":100},{\\\"start\\\":25.56,\\\"end\\\":25.78," +
+                "\\\"pitch\\\":58,\\\"velocity\\\":100},{\\\"start\\\":25.84,\\\"end\\\":26.16,\\\"pitch\\\":65," +
+                "\\\"velocity\\\":100},{\\\"start\\\":26.16,\\\"end\\\":26.46,\\\"pitch\\\":62,\\\"velocity\\\":100}," +
+                "{\\\"start\\\":26.46,\\\"end\\\":26.86,\\\"pitch\\\":58,\\\"velocity\\\":100},{\\\"start\\\":26.86," +
+                "\\\"end\\\":27.68,\\\"pitch\\\":55,\\\"velocity\\\":100},{\\\"start\\\":28.56,\\\"end\\\":29.34," +
+                "\\\"pitch\\\":57,\\\"velocity\\\":100},{\\\"start\\\":29.34,\\\"end\\\":29.54,\\\"pitch\\\":58," +
+                "\\\"velocity\\\":100},{\\\"start\\\":29.54,\\\"end\\\":29.88,\\\"pitch\\\":60,\\\"velocity\\\":100}," +
+                "{\\\"start\\\":29.88,\\\"end\\\":30,\\\"pitch\\\":58,\\\"velocity\\\":100}],\\\"confidence\\\":0" +
+                ".9155213}\"\n" +
+                "}";
+
+        String phoneStr = "f,eng\n" +
+                "d,ao\n" +
+                "zh,e\n" +
+                "l,i\n" +
+                "j,iou\n" +
+                "sh,iii\n" +
+                "n,ian\n" +
+                "n,ian\n" +
+                "zh,u\n" +
+                "g,uo\n" +
+                "k,e\n" +
+                "d,e\n" +
+                "s,ii\n" +
+                "n,ian\n" +
+                "y,v\n" +
+                "d,ao\n" +
+                "l,e\n" +
+                "zh,e\n" +
+                "l,i\n" +
+                "ch,an\n" +
+                "ch,eng\n" +
+                "x,ian\n" +
+                "ch,an\n" +
+                "zh,e\n" +
+                "w,uo\n" +
+                "m,en\n" +
+                "l,iou\n" +
+                "l,ian\n" +
+                "r,en\n" +
+                "sh,iii\n" +
+                "j,ian\n" +
+                "n,i\n" +
+                "z,ai\n" +
+                "sh,en\n" +
+                "b,ian\n" +
+                "j,iou\n" +
+                "sh,iii\n" +
+                "y,van\n" +
+                "y,van\n" +
+                "f,en\n" +
+                "x,ie\n" +
+                "z,ai\n" +
+                "s,an\n" +
+                "sh,eng\n" +
+                "sh,iii\n" +
+                "sh,ang\n" +
+                "m,ian\n" +
+                "ai\n" +
+                "y,iou\n" +
+                "w,uan\n" +
+                "f,en\n" +
+                "zh,iii\n" +
+                "y,i\n" +
+                "t,ian\n" +
+                "n,ing\n" +
+                "y,van\n" +
+                "w,uo\n" +
+                "j,iou\n" +
+                "z,ang\n" +
+                "z,ai\n" +
+                "zh,e\n" +
+                "y,i\n" +
+                "t,ian";
+
+        String[] phoneArray = phoneStr.split("\n");
+
+        System.out.println(str);
+        JSONObject jsonObject = JSON.parseObject(str);
+        JSONObject payload = jsonObject.getJSONObject("payload");
+        JSONArray midiList = payload.getJSONArray("midiList");
+
+        System.out.println("phoneArray size:" + phoneArray.length + ",midiList size:" + midiList.size());
+        List<JSONArray> notesList = new ArrayList<>();
+        JSONArray temps = new JSONArray();
+        float duration = 0f;
+        float firstStart = 0f;
+        for (int i = 0; i < midiList.size(); i++) {
+            JSONObject midiJson = (JSONObject) midiList.get(i);
+            Float start = midiJson.getFloat("start");
+            Float end = midiJson.getFloat("end");
+            Integer pitch = midiJson.getInteger("pitch");
+
+            JSONObject note = new JSONObject();
+            note.put("type", "general");
+            note.put("language", "ch");
+            note.put("start_time", start);
+            note.put("end_time", end);
+            note.put("pitch", pitch);
+            note.put("phone", phoneArray[i].split(","));
+
+            duration = end - firstStart;
+            if (duration >= 18) {
+                duration = 0f;
+                firstStart = start;
+                JSONArray notes = new JSONArray();
+                notes.addAll(temps);
+                notesList.add(notes);
+                temps.clear();
+            }
+            temps.add(note);
+        }
+        notesList.add(temps);
+
+        JSONArray acelArray = new JSONArray();
+        for (JSONArray jsonArray : notesList) {
+            JSONObject acelJson = new JSONObject();
+            JSONObject debug_info = new JSONObject();
+            debug_info.put("build", "252");
+            debug_info.put("device", "macOS(arm64)_zhaowenxiaodeMacBook-Pro.local");
+            debug_info.put("os", "23.1.0");
+            debug_info.put("platform", "pc");
+            debug_info.put("recordType", "create");
+            debug_info.put("version", "1.8.7");
+            acelJson.put("debug_info", debug_info);
+            acelJson.put("notes", jsonArray);
+            acelArray.add(acelJson);
+        }
+
+        System.out.println(acelArray.toJSONString());
+    }
+
+
+}

+ 219 - 0
core/src/main/java/com/tzld/supply/api/volcengine/TokenClient.java

@@ -0,0 +1,219 @@
+package com.tzld.supply.api.volcengine;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.*;
+import org.apache.commons.codec.binary.Hex;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.TimeZone;
+import java.util.concurrent.TimeUnit;
+
+@Slf4j
+@Component
+public class TokenClient {
+
+    private static final String TIME_FORMAT_V4 = "yyyyMMdd'T'HHmmss'Z'";
+    private static final TimeZone tz = TimeZone.getTimeZone("UTC");
+    private static String AK = "AKLTMDcxZTRkNjM2ODc5NGJjZWI1MjUwNThhNTAzMmM2N2E";
+    private static String SK = "TjJZeE5XVmpOR0V5WVRnd05ESTJOR0UxWkdJeVpUZ3hOVFpoWldVM04yWQ==";
+
+    @Autowired
+    RedisTemplate<String, String> stringRedisTemplate;
+
+
+    public String getToken(String appKey, String region, String service, int expiration) throws Exception {
+        String token = null;
+        String cacheKey = "volcengine_token_" + appKey + "_" + region + "_" + service;
+        log.info("getToken,cacheKey:{}", cacheKey);
+        token = stringRedisTemplate.opsForValue().get(cacheKey);
+        if (StringUtils.hasText(token)) {
+            return token;
+        }
+        token = getTokenFromApi(appKey, region, service, expiration);
+        if (StringUtils.hasText(token)) {
+            stringRedisTemplate.opsForValue().set(cacheKey, token, (expiration - 600), TimeUnit.SECONDS);
+        }
+        return token;
+    }
+
+    private static String getCurrentFormatDate() {
+        DateFormat df = new SimpleDateFormat(TIME_FORMAT_V4);
+        df.setTimeZone(tz);
+        return df.format(new Date());
+    }
+
+    private static String hashSHA256(byte[] content) throws Exception {
+        try {
+            MessageDigest md = MessageDigest.getInstance("SHA-256");
+            byte[] result = md.digest(content);
+            return Hex.encodeHexString(result);
+        } catch (Exception e) {
+            throw new Exception(
+                    "Unable to compute hash while signing request: "
+                            + e.getMessage(), e);
+        }
+    }
+
+    private static byte[] hmacSHA256(byte[] key, String content) throws Exception {
+        try {
+            Mac mac = Mac.getInstance("HmacSHA256");
+            mac.init(new SecretKeySpec(key, "HmacSHA256"));
+            return mac.doFinal(content.getBytes());
+        } catch (Exception e) {
+            throw new Exception(
+                    "Unable to calculate a request signature: "
+                            + e.getMessage(), e);
+        }
+    }
+
+    private static String getTokenFromApi(String appKey, String region, String service, int expiration) throws Exception {
+        String authVersion = "volc-auth-v1";
+        String request = "request";
+        String algorithm = "HMAC-SHA256";
+        String action = "GetToken";
+        String version = "2021-07-27";
+
+        String method = "POST";
+        String contentType = "application/json; charset=utf-8";
+        String host = "open.volcengineapi.com";
+        String query = "Action=" + action + "&Version=" + version;
+        String path = "/";
+        String url = "http://" + host + path + "?" + query;
+        System.out.println("url: " + url);
+
+        String format_date = getCurrentFormatDate();
+        String date = format_date.substring(0, 8);
+
+        HashMap<String, String> headers = new HashMap<>();
+
+        JSONObject bodyObj = new JSONObject();
+        bodyObj.put("appkey", appKey);
+        bodyObj.put("token_version", authVersion);
+        bodyObj.put("expiration", expiration);
+        String bodyStr = bodyObj.toJSONString();
+        System.out.println("body: " + bodyStr);
+        String bodyHash256 = hashSHA256(bodyStr.getBytes(StandardCharsets.UTF_8));
+        System.out.println("body hash sha256: " + bodyHash256);
+
+        headers.put("Host", host);
+        headers.put("Content-Type", contentType);
+        headers.put("X-Date", format_date);
+        headers.put("X-Content-Sha256", bodyHash256);
+
+        String[] sortedKeys = headers.keySet().toArray(new String[] {});
+        Arrays.sort(sortedKeys);
+
+        StringBuilder canonicalizedQueryString = new StringBuilder();
+        for (String key : sortedKeys) {
+            canonicalizedQueryString.append(key.toLowerCase())
+                    .append(":")
+                    .append(headers.get(key)).append("\n");
+        }
+
+        String signed_str = canonicalizedQueryString.toString();
+        System.out.println("signed_str: " + signed_str);
+
+
+        StringBuilder signedHeadersBuilder = new StringBuilder();
+        for (String key : sortedKeys) {
+            signedHeadersBuilder.append(";").append(key.toLowerCase());
+        }
+        String signed_headersStr = signedHeadersBuilder.toString().substring(1);
+        System.out.println("signed_headersStr: " + signed_headersStr);
+
+        StringBuilder canoncialRequestBulder = new StringBuilder();
+        canoncialRequestBulder.append(method).append("\n");
+        canoncialRequestBulder.append(path).append("\n");
+        canoncialRequestBulder.append(query).append("\n");
+        canoncialRequestBulder.append(signed_str).append("\n");
+        canoncialRequestBulder.append(signed_headersStr).append("\n");
+        canoncialRequestBulder.append(bodyHash256);
+        String canoncial_request = canoncialRequestBulder.toString();
+        System.out.println("canoncial_request: " + canoncial_request);
+        String hashed_canon_req = hashSHA256(canoncial_request.getBytes(StandardCharsets.UTF_8));
+        System.out.println("hashed_canon_req: " + hashed_canon_req);
+
+        StringBuilder credential_scopeBuilder = new StringBuilder();
+        credential_scopeBuilder.append(date).append("/");
+        credential_scopeBuilder.append(region).append("/");
+        credential_scopeBuilder.append(service).append("/");
+        credential_scopeBuilder.append(request);
+        String credential_scope = credential_scopeBuilder.toString();
+        System.out.println("credential_scope: " + credential_scope);
+
+        StringBuilder signingStrBuilder = new StringBuilder();
+        signingStrBuilder.append(algorithm).append("\n");
+        signingStrBuilder.append(format_date).append("\n");
+        signingStrBuilder.append(credential_scope).append("\n");
+        signingStrBuilder.append(hashed_canon_req);
+        String signing_str = signingStrBuilder.toString();
+        System.out.println("signing_str: " + signing_str);
+
+        byte[] kDate = hmacSHA256(SK.getBytes(StandardCharsets.UTF_8), date);
+        byte[] kRegion = hmacSHA256(kDate, region);
+        byte[] kService = hmacSHA256(kRegion, service);
+        byte[] signing_key = hmacSHA256(kService, request);
+        System.out.println("signing_key: " + signing_key);
+
+        String sign = Hex.encodeHexString(hmacSHA256(signing_key, signing_str));
+        System.out.println("sign: " + sign);
+
+        String credential = AK + "/" + credential_scope;
+        System.out.println("credential: " + credential);
+
+        String Authorization = algorithm + " Credential=" + credential +
+                ", SignedHeaders=" + signed_headersStr +
+                ", Signature=" + sign;
+
+        System.out.println("Authorization: " + Authorization);
+
+        RequestBody reqBody = RequestBody.create(MediaType.parse(contentType),
+                bodyStr);
+        Request request1 = new Request.Builder().url(url)
+                .addHeader("Host", host)
+                .addHeader("Content-Type", contentType)
+                .addHeader("X-Date", format_date)
+                .addHeader("X-Content-Sha256", bodyHash256)
+                .addHeader("Authorization", Authorization)
+                .post(reqBody)
+                .build();
+
+        try {
+            OkHttpClient client = new OkHttpClient();
+            Response response = client.newCall(request1).execute();
+
+            String body = response.body().string();
+            System.out.println(body);
+            JSONObject jsonObject = JSON.parseObject(body);
+            if (jsonObject.getInteger("status_code") != null && jsonObject.getInteger("status_code") == 20000000) {
+                return jsonObject.getString("token");
+            }
+        } catch (Exception e) {
+            log.error("getTokenFromApi error", e);
+        }
+        return null;
+    }
+
+    public static void main(String args[]) throws Exception {
+        String appKey = "xPfFWHqBHG";
+        String region = "cn-north-1";
+        String service = "sami";
+        int expiration = 864000;
+        String token = getTokenFromApi(appKey, region, service, expiration);
+        System.out.println(token);
+    }
+}

+ 182 - 0
core/src/main/java/com/tzld/supply/api/volcengine/TokenDemo.java

@@ -0,0 +1,182 @@
+package com.tzld.supply.api.volcengine;
+
+import com.alibaba.fastjson.JSONObject;
+import okhttp3.*;
+import org.apache.commons.codec.binary.Hex;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.TimeZone;
+
+public class TokenDemo {
+
+    private static final String TIME_FORMAT_V4 = "yyyyMMdd'T'HHmmss'Z'";
+    private static final TimeZone tz = TimeZone.getTimeZone("UTC");
+
+    private static String getCurrentFormatDate() {
+        DateFormat df = new SimpleDateFormat(TIME_FORMAT_V4);
+        df.setTimeZone(tz);
+        return df.format(new Date());
+    }
+
+    public static String hashSHA256(byte[] content) throws Exception {
+        try {
+            MessageDigest md = MessageDigest.getInstance("SHA-256");
+            byte[] result = md.digest(content);
+            return Hex.encodeHexString(result);
+        } catch (Exception e) {
+            throw new Exception(
+                    "Unable to compute hash while signing request: "
+                            + e.getMessage(), e);
+        }
+    }
+
+    public static byte[] hmacSHA256(byte[] key, String content) throws Exception {
+        try {
+            Mac mac = Mac.getInstance("HmacSHA256");
+            mac.init(new SecretKeySpec(key, "HmacSHA256"));
+            return mac.doFinal(content.getBytes());
+        } catch (Exception e) {
+            throw new Exception(
+                    "Unable to calculate a request signature: "
+                            + e.getMessage(), e);
+        }
+    }
+
+    public static void main(String args[]) throws Exception {
+        String ak = "AKLTOWNiNjI0ZGZjNWU4NGNjMGEyYzJlOGNkMjg1OGMyZDM";
+        String sk = "TUdWbE9HTTJaR0ZsTWpRME5EY3lNMkUyWXpZMk9EYzBaak16TnpBek5ERQ==";
+        String appKey = "xPfFWHqBHG";
+        String authVersion = "volc-auth-v1";
+        String region = "cn-north-1";
+        String service = "sami";
+        int expiration = 864000;
+        String request = "request";
+        String algorithm = "HMAC-SHA256";
+        String action = "GetToken";
+        String version = "2021-07-27";
+
+        String method = "POST";
+        String contentType = "application/json; charset=utf-8";
+        String host = "open.volcengineapi.com";
+        String query = "Action=" + action + "&Version=" + version;
+        String path = "/";
+        String url = "http://" + host + path + "?" + query;
+        System.out.println("url: " + url);
+
+        String format_date = getCurrentFormatDate();
+        String date = format_date.substring(0, 8);
+
+        HashMap<String, String> headers = new HashMap<>();
+
+        JSONObject bodyObj = new JSONObject();
+        bodyObj.put("appkey", appKey);
+        bodyObj.put("token_version", authVersion);
+        bodyObj.put("expiration", expiration);
+        String bodyStr = bodyObj.toJSONString();
+        System.out.println("body: " + bodyStr);
+        String bodyHash256 = hashSHA256(bodyStr.getBytes(StandardCharsets.UTF_8));
+        System.out.println("body hash sha256: " + bodyHash256);
+
+        headers.put("Host", host);
+        headers.put("Content-Type", contentType);
+        headers.put("X-Date", format_date);
+        headers.put("X-Content-Sha256", bodyHash256);
+
+        String[] sortedKeys = headers.keySet().toArray(new String[] {});
+        Arrays.sort(sortedKeys);
+
+        StringBuilder canonicalizedQueryString = new StringBuilder();
+        for (String key : sortedKeys) {
+            canonicalizedQueryString.append(key.toLowerCase())
+                    .append(":")
+                    .append(headers.get(key)).append("\n");
+        }
+
+        String signed_str = canonicalizedQueryString.toString();
+        System.out.println("signed_str: " + signed_str);
+
+
+        StringBuilder signedHeadersBuilder = new StringBuilder();
+        for (String key : sortedKeys) {
+            signedHeadersBuilder.append(";").append(key.toLowerCase());
+        }
+        String signed_headersStr = signedHeadersBuilder.toString().substring(1);
+        System.out.println("signed_headersStr: " + signed_headersStr);
+
+        StringBuilder canoncialRequestBulder = new StringBuilder();
+        canoncialRequestBulder.append(method).append("\n");
+        canoncialRequestBulder.append(path).append("\n");
+        canoncialRequestBulder.append(query).append("\n");
+        canoncialRequestBulder.append(signed_str).append("\n");
+        canoncialRequestBulder.append(signed_headersStr).append("\n");
+        canoncialRequestBulder.append(bodyHash256);
+        String canoncial_request = canoncialRequestBulder.toString();
+        System.out.println("canoncial_request: " + canoncial_request);
+        String hashed_canon_req = hashSHA256(canoncial_request.getBytes(StandardCharsets.UTF_8));
+        System.out.println("hashed_canon_req: " + hashed_canon_req);
+
+        StringBuilder credential_scopeBuilder = new StringBuilder();
+        credential_scopeBuilder.append(date).append("/");
+        credential_scopeBuilder.append(region).append("/");
+        credential_scopeBuilder.append(service).append("/");
+        credential_scopeBuilder.append(request);
+        String credential_scope = credential_scopeBuilder.toString();
+        System.out.println("credential_scope: " + credential_scope);
+
+        StringBuilder signingStrBuilder = new StringBuilder();
+        signingStrBuilder.append(algorithm).append("\n");
+        signingStrBuilder.append(format_date).append("\n");
+        signingStrBuilder.append(credential_scope).append("\n");
+        signingStrBuilder.append(hashed_canon_req);
+        String signing_str = signingStrBuilder.toString();
+        System.out.println("signing_str: " + signing_str);
+
+        byte[] kDate = hmacSHA256(sk.getBytes(StandardCharsets.UTF_8), date);
+        byte[] kRegion = hmacSHA256(kDate, region);
+        byte[] kService = hmacSHA256(kRegion, service);
+        byte[] signing_key = hmacSHA256(kService, request);
+        System.out.println("signing_key: " + signing_key);
+
+        String sign = Hex.encodeHexString(hmacSHA256(signing_key, signing_str));
+        System.out.println("sign: " + sign);
+
+        String credential = ak + "/" + credential_scope;
+        System.out.println("credential: " + credential);
+
+        String Authorization = algorithm + " Credential=" + credential +
+                ", SignedHeaders=" + signed_headersStr +
+                ", Signature=" + sign;
+
+        System.out.println("Authorization: " + Authorization);
+
+        RequestBody reqBody = RequestBody.create(MediaType.parse(contentType),
+                bodyStr);
+        Request request1 = new Request.Builder().url(url)
+                .addHeader("Host", host)
+                .addHeader("Content-Type", contentType)
+                .addHeader("X-Date", format_date)
+                .addHeader("X-Content-Sha256", bodyHash256)
+                .addHeader("Authorization", Authorization)
+                .post(reqBody)
+                .build();
+
+        try {
+            OkHttpClient client = new OkHttpClient();
+            Response response = client.newCall(request1).execute();
+
+            String body = response.body().string();
+            System.out.println(body);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+    }
+}

+ 209 - 0
core/src/main/java/com/tzld/supply/api/volcengine/VolcengineMegattsClient.java

@@ -0,0 +1,209 @@
+package com.tzld.supply.api.volcengine;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.tzld.supply.common.enums.ExceptionEnum;
+import com.tzld.supply.common.exception.CommonException;
+import com.tzld.supply.util.BaseUtils;
+import com.tzld.supply.util.http.HttpClientUtils;
+import com.tzld.supply.util.http.HttpResponseContent;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.data.util.Pair;
+import org.springframework.stereotype.Component;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+@Slf4j
+@Component
+public class VolcengineMegattsClient {
+    private static final String baseApiUrl = "https://openspeech.bytedance.com";
+    private static final String APPID = "1526776025";
+    private static final String TOKEN = "O_bdH6rUbl2BcyuwBPOCddNEDIVssUlR";
+
+    public String submitTrain(byte[] audioBytes, String speakerId) {
+        String apiUrl = baseApiUrl + "/api/v1/mega_tts/audio/upload";
+
+        Map<String, String> headerMap = new HashMap<>();
+        headerMap.put("Authorization", "Bearer; " + TOKEN);
+        headerMap.put("Resource-Id", "volc.megatts.voiceclone");
+
+        JSONObject params = new JSONObject();
+        params.put("appid", APPID);
+        params.put("speaker_id", speakerId);
+        params.put("source", 2);
+        params.put("model_type", 1);
+
+        JSONArray audios = new JSONArray();
+        JSONObject audio = new JSONObject();
+        audio.put("audio_bytes", audioBytes);
+        audios.add(audio);
+        params.put("audios", audios);
+
+        HttpResponseContent hrc = HttpClientUtils.postDataAddHeader(apiUrl, params.toJSONString(),
+                headerMap, 3000, 15000);
+        if (hrc == null) {
+            throw new CommonException(ExceptionEnum.SYSTEM_ERROR.getCode(), "火山引擎-音色训练提交接口请求超时");
+        }
+        if (!hrc.isSuccessful()) {
+            throw new CommonException(ExceptionEnum.SYSTEM_ERROR.getCode(),
+                    "火山引擎-音色训练提交接口,httpCode=" + hrc.getStatusCode() + ",response=" + hrc.getBodyContent());
+        }
+        String response = hrc.getBodyContent();
+        log.info("submitTrain,speakerId:{},response:{}", speakerId, response);
+        JSONObject responseJson = JSON.parseObject(response);
+        JSONObject baseRespJson = responseJson.getJSONObject("BaseResp");
+        if (Objects.nonNull(baseRespJson)) {
+            Integer statusCode = baseRespJson.getInteger("StatusCode");
+            if (Objects.nonNull(statusCode) && statusCode != 0) {
+                throw new CommonException(ExceptionEnum.SYSTEM_ERROR.getCode(), "火山引擎-音色训练提交接口返回失败,response:" + response);
+            }
+        }
+        return response;
+    }
+
+    // NotFound = 0 Training = 1 Success = 2 Failed = 3 Active = 4
+    private Pair<Integer, String> queryTrainStatus(String speakerId) {
+        String apiUrl = baseApiUrl + "/api/v1/mega_tts/status";
+
+        Map<String, String> headerMap = new HashMap<>();
+        headerMap.put("Authorization", "Bearer; " + TOKEN);
+        headerMap.put("Resource-Id", "volc.megatts.voiceclone");
+
+        JSONObject params = new JSONObject();
+        params.put("appid", APPID);
+        params.put("speaker_id", speakerId);
+
+        HttpResponseContent hrc = HttpClientUtils.postDataAddHeader(apiUrl, params.toJSONString(),
+                headerMap, 3000, 15000);
+        if (hrc == null) {
+            throw new CommonException(ExceptionEnum.SYSTEM_ERROR.getCode(), "火山引擎-查询音色训练状态接口请求超时");
+        }
+        if (!hrc.isSuccessful()) {
+            throw new CommonException(ExceptionEnum.SYSTEM_ERROR.getCode(),
+                    "火山引擎-查询音色训练状态接口,httpCode=" + hrc.getStatusCode() + ",response=" + hrc.getBodyContent());
+        }
+        String response = hrc.getBodyContent();
+        log.info("queryTrainStatus,speakerId:{},response:{}", speakerId, response);
+        JSONObject responseJson = JSON.parseObject(response);
+        JSONObject baseRespJson = responseJson.getJSONObject("BaseResp");
+        if (Objects.nonNull(baseRespJson)) {
+            Integer statusCode = baseRespJson.getInteger("StatusCode");
+            if (Objects.nonNull(statusCode) && statusCode != 0) {
+                throw new CommonException(ExceptionEnum.SYSTEM_ERROR.getCode(), "火山引擎-查询音色训练状态接口返回失败,response:" + response);
+            }
+        }
+        Integer status = responseJson.getInteger("status");
+        String demoAudio = responseJson.getString("demo_audio");
+        if (Objects.isNull(demoAudio)) {
+            demoAudio = "";
+        }
+        return Pair.of(status, demoAudio);
+    }
+
+    public String train(byte[] audioBytes, String speakerId) {
+        submitTrain(audioBytes, speakerId);
+        String demoAudio = "";
+        for (int i = 0; i < 200; i++) {
+            Pair<Integer, String> pair = queryTrainStatus(speakerId);
+            Integer status = pair.getFirst();
+            demoAudio = pair.getSecond();
+            // NotFound = 0 Training = 1 Success = 2 Failed = 3 Active = 4
+            if (status == 2 || status == 4) {
+                break;
+            } else if (status == 3) {
+                throw new CommonException(ExceptionEnum.SYSTEM_ERROR.getCode(), "火山引擎音色训练失败");
+            }
+            try {
+                Thread.sleep(5000);
+            } catch (InterruptedException e) {
+            }
+        }
+        log.info("train success,{},{}", speakerId, demoAudio);
+        return demoAudio;
+    }
+
+    public String tts(String speakerId, String text, String bizId) {
+        String apiUrl = baseApiUrl + "/api/v1/tts";
+
+        Map<String, String> headerMap = new HashMap<>();
+        headerMap.put("Authorization", "Bearer; " + TOKEN);
+
+        JSONObject params = new JSONObject();
+
+        JSONObject app = new JSONObject();
+        app.put("appid", APPID);
+        app.put("token", TOKEN);
+        // volcano_tts 内置音色, volcano_icl 自定义音色
+        app.put("cluster", "volcano_icl");
+        params.put("app", app);
+
+        JSONObject user = new JSONObject();
+        user.put("uid", bizId);
+        params.put("user", user);
+
+        JSONObject audio = new JSONObject();
+        audio.put("voice_type", speakerId);
+        audio.put("encoding", "mp3");
+        params.put("audio", audio);
+
+        JSONObject request = new JSONObject();
+        request.put("reqid", BaseUtils.getUUIDStr());
+        request.put("text", text);
+        request.put("operation", "query");
+        params.put("request", request);
+
+        HttpResponseContent hrc = HttpClientUtils.postDataAddHeader(apiUrl, params.toJSONString(),
+                headerMap, 3000, 1200000);
+        if (hrc == null) {
+            throw new CommonException(ExceptionEnum.SYSTEM_ERROR.getCode(), "火山引擎-tts接口请求超时,bizId:" + bizId);
+        }
+        if (!hrc.isSuccessful()) {
+            throw new CommonException(ExceptionEnum.SYSTEM_ERROR.getCode(),
+                    "火山引擎-tts接口接口,httpCode=" + hrc.getStatusCode() + ",response=" + hrc.getBodyContent() + ",bizId:" + bizId);
+        }
+        String response = hrc.getBodyContent();
+        JSONObject responseJson = JSON.parseObject(response);
+        Integer code = responseJson.getInteger("code");
+        if (Objects.nonNull(code) && code != 3000) {
+            throw new CommonException(ExceptionEnum.SYSTEM_ERROR.getCode(),
+                    "火山引擎-tts接口接口返回失败,response:" + response + "bizId:" + bizId);
+        }
+        String data = responseJson.getString("data");
+        return data;
+    }
+
+    public static void main(String[] args) {
+        VolcengineMegattsClient client = new VolcengineMegattsClient();
+        // 训练
+//        String filePath = "/Users/liuzhiheng/Downloads/4.15语音包/人生忠告-老年男性-急迫.MP3";
+//        byte[] audioBytes = FileUtil.readBytes(filePath);
+//        client.train(audioBytes, "S_xMxwsuSw1");
+
+        // tts
+
+//        String text = "好的,请看下面文案:\n" +
+//                "\n" +
+//                "“各位老哥哥、老姐姐,这个视频呀,真是让人心里不是滋味,养老院本该是咱们老年人安享晚年的地方,可视频里的事情真是太让人气愤了!想想咱们老了,最怕的就是生病没人照顾,遇到不讲良心的人。这个视频里的老人遭遇真是太可怜了,看得我这心里堵得慌。\n" +
+//                "\n" +
+//                "咱们老年人,辛辛苦苦一辈子,到老了更要懂得保护自己。养老防骗很重要,别轻易相信那些花言巧语,捂紧自己的钱袋子。\n" +
+//                "\n" +
+//                "为了让更多老年朋友们提高警惕,避免上当受骗,我恳请大家把这个视频转发到您们所在的各个群里,让大家都看看,别让这样的悲剧再发生了!您的每一次转发,都可能帮助一个家庭避免遭受损失。动动手指,传递爱心,让咱们老年人的晚年生活更安心、更幸福!记得转发到三个以上的群里哦,让更多人看到,一起守护咱们老年人的权益!”";
+
+//        String textJson = "{\"ttsId\":\"tts_20250418195851200663860\"," +
+//                "\"text\":\"早上好,今天是6月1日开门红,6月的第一天,第一声祝福送给你,愿你6月每天都开心,愿你6月每步都平安,愿你6月每秒都健康,愿你6月吉祥如意,好运连连,一切安好。\\n\\n" +
+//                "大家早上好,我是你们的老朋友,今天这美好的祝福送给大家,希望大家在新的一月里,每天都有好心情,身体健康,万事如意。看完这个视频,如果您也喜欢这份祝福,觉得对您身边的朋友也有用,那就动动手指,把它分享出去吧,点击下面的黄色按钮,把这份美好的祝福分享到咱们的微信群里,让更多的朋友都能收到这份暖心的问候。\\n\\n当然了,如果您觉得这份祝福特别好,想单独送给您特别在乎的亲朋好友,那就点击下面的绿色按钮,把祝福私发给他们,让他们感受到您的一份心意。我相信,收到这份祝福的朋友,一定会非常高兴,因为这代表着您对他们的关心和祝福。让我们一起把这份爱传递下去,让更多的人感受到生活的美好和温暖。别忘了,分享的时候可以加上一句您自己的祝福语,这样会显得更加真诚和用心。\\n\\n希望大家在新的一月,每天都能充满活力,积极面对生活中的一切挑战,祝愿大家生活幸福,健康快乐!\"}";
+//        JSONObject json = JSON.parseObject(textJson);
+//        String text = json.getString("text");
+
+        // S_DBiWzipv1
+//        String speakerId = "zh_male_yuanboxiaoshu_moon_bigtts";
+//        String bizId = DistributedIdGenerator.generate();
+//        String ttsBase64 = client.tts(speakerId, text, bizId);
+//        byte[] ttsBytes = Base64Utils.decodeFromString(ttsBase64);
+//        FileUtil.writeBytes(ttsBytes, "/Users/liuzhiheng/Downloads/tts/" + bizId + ".mp3");
+//        System.out.println(text);
+    }
+}

+ 86 - 0
core/src/main/java/com/tzld/supply/api/volcengine/VolcengineVoiceTransTextClient.java

@@ -0,0 +1,86 @@
+package com.tzld.supply.api.volcengine;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.tzld.supply.common.enums.ExceptionEnum;
+import com.tzld.supply.common.exception.CommonException;
+import com.tzld.supply.util.http.HttpClientUtils;
+import com.tzld.supply.util.http.HttpResponseContent;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Slf4j
+@Component
+public class VolcengineVoiceTransTextClient {
+
+    private static final String SUBMIT_URL = "https://openspeech.bytedance.com/api/v1/vc/submit?appid={appid}";
+    private static final String QUERY_URL = "https://openspeech.bytedance.com/api/v1/vc/query?appid={appid}&id={id}";
+    private static final String APPID = "1526776025";
+    private static final String TOKEN = "O_bdH6rUbl2BcyuwBPOCddNEDIVssUlR";
+
+    public String requestVoiceTransText(String audioUrl) {
+        String submitUrl = SUBMIT_URL.replace("{appid}", APPID);
+        JSONObject submitParam = new JSONObject();
+        submitParam.put("url", audioUrl);
+        Map<String, String> submitHeaderMap = new HashMap<>();
+        submitHeaderMap.put("Authorization", "Bearer; " + TOKEN);
+        submitHeaderMap.put("Content-Type", "application/json");
+        HttpResponseContent submitHrc = HttpClientUtils.postDataAddHeader(submitUrl,
+                submitParam.toJSONString(), submitHeaderMap, 5000, 300000);
+        if (submitHrc == null) {
+            throw new CommonException(ExceptionEnum.SYSTEM_ERROR.getCode(), "火山引擎提交录音文件识别-http请求超时返回空");
+        }
+        String submitResponse = submitHrc.getBodyContent();
+        log.info("requestVoiceTransText,submit request:{},httpCode:{}, response:{}", submitParam.toJSONString(), submitHrc.getStatusCode(), submitResponse);
+        if (!submitHrc.isSuccessful()) {
+            throw new CommonException(ExceptionEnum.SYSTEM_ERROR.getCode(), "火山引擎提交录音文件识别-http请求返回code=" + submitHrc.getStatusCode() + ",response=" + submitResponse);
+        }
+        JSONObject submitResponseJson = JSON.parseObject(submitResponse);
+        if (submitResponseJson.getIntValue("code") != 0) {
+            throw new CommonException(ExceptionEnum.SYSTEM_ERROR.getCode(),
+                    "火山引擎提交录音文件识别-返回错误-" + submitResponseJson.getString("message"));
+        }
+        String id = submitResponseJson.getString("id");
+
+        // 查询结果
+        String queryUrl = QUERY_URL.replace("{appid}", APPID).replace("{id}", id);
+        Map<String, String> queryHeaderMap = new HashMap<>();
+        queryHeaderMap.put("Authorization", "Bearer; " + TOKEN);
+        for (int i = 0; i < 100; i++) {
+            HttpResponseContent queryHrc = HttpClientUtils.get(queryUrl, HttpClientUtils.contentTypeJson, 5000, 30000
+                    , queryHeaderMap);
+            if (queryHrc == null) {
+                log.error("火山引擎查询录音文件识别结果-http请求超时返回空");
+                continue;
+            }
+            if (!queryHrc.isSuccessful()) {
+                log.error("火山引擎查询录音文件识别结果-http请求返回code=" + queryHrc.getStatusCode());
+                continue;
+            }
+            String queryResponse = queryHrc.getBodyContent();
+            log.info("requestVoiceTransText,query request:{},response:{}", id, queryResponse);
+            JSONObject queryResponseJson = JSON.parseObject(queryResponse);
+            if (queryResponseJson.getIntValue("code") == 0) {
+                return queryResponse;
+            } else if (queryResponseJson.getIntValue("code") == 2000) {
+                try {
+                    Thread.sleep(5000);
+                } catch (InterruptedException e) {
+                }
+            } else {
+                throw new CommonException(ExceptionEnum.SYSTEM_ERROR.getCode(),
+                        "火山引擎查询录音文件识别结果-返回错误-" + queryResponseJson.getString("message"));
+            }
+        }
+        return null;
+    }
+
+    public static void main(String[] args) {
+        VolcengineVoiceTransTextClient client = new VolcengineVoiceTransTextClient();
+        System.out.println(client.requestVoiceTransText("http://res.cybertogether.net/audio/fish/tts/db15cfdf6e1140989a8161da9aae9862.mp3"));
+    }
+
+}

+ 30 - 0
core/src/main/java/com/tzld/supply/common/enums/EnumUploadFileType.java

@@ -0,0 +1,30 @@
+package com.tzld.supply.common.enums;
+
+public enum EnumUploadFileType {
+	PICTURE(1,"1"),
+	VIDEO(2,"2"),
+	VOICE(3,"3"),
+	FILE(4,"4"),
+	GIF(5,"5"),
+	SUBTITLE(6,"6"),
+	;
+
+	private Integer intType;
+	private String strType;
+	EnumUploadFileType(Integer intType, String strType){
+		this.intType = intType;
+		this.strType = strType;
+	}
+	public Integer getIntType() {
+		return intType;
+	}
+	public void setIntType(Integer intType) {
+		this.intType = intType;
+	}
+	public String getStrType() {
+		return strType;
+	}
+	public void setStrType(String strType) {
+		this.strType = strType;
+	}
+}

+ 386 - 0
core/src/main/java/com/tzld/supply/config/AliOssConfig.java

@@ -0,0 +1,386 @@
+package com.tzld.supply.config;
+
+import com.aliyun.oss.ClientConfiguration;
+import com.aliyun.oss.OSSClient;
+import com.aliyuncs.DefaultAcsClient;
+import com.aliyuncs.IAcsClient;
+import com.aliyuncs.profile.DefaultProfile;
+import com.tzld.supply.model.vo.PolicyDetailVO;
+import com.tzld.supply.util.AliOssFileTool;
+import com.tzld.supply.util.PropertiesUtils;
+import org.apache.commons.lang.StringUtils;
+import org.apache.log4j.Logger;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Configuration;
+
+import javax.annotation.PostConstruct;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+
+/**
+ * @author zhaoyuchun
+ */
+@Configuration
+public class AliOssConfig {
+	/** 图片服务域名 **/
+	// public String picEndPoint;
+	/** 对象存储域名 **/
+	private static String ossEndPoint;
+	/** 阿里云accesskeyId **/
+	private static String accessKeyId;
+	/** 过期时间,单位秒 **/
+	private static long expiration;
+	/** 阿里云accessKeySecret **/
+	private static String accessKeySecret;
+	/** 文件系统配置文件地址 **/
+	public String configPath;
+	private static Boolean needPress;
+	private static String projectName;
+	private static String videoEndPoint;
+	private static String internalEndPoint;
+	private static OSSClient ossClient;
+	private static OSSClient priClient;
+	private static OSSClient videoClient;
+	private static OSSClient internalOssClient;
+
+	private static String snapshotLocation;
+	private static OSSClient ossReadOnlyClient;
+	private static IAcsClient mAcsClient;
+
+	private static DefaultAcsClient defaultAcsClient;
+	public static final String PIPELINEID = "abe6a0b9b9334858913eb416974485d2";
+	public static final String MTS_REGION = "cn-hangzhou";
+	public static final String OSS_REGION = "oss-cn-hangzhou";
+	private static PolicyDetailVO policyDetail;
+	static {
+		policyDetail = new PolicyDetailVO();
+		policyDetail.setVersion("1");
+		List<PolicyDetailVO.PolicyVO> statement = new ArrayList<PolicyDetailVO.PolicyVO>();
+		PolicyDetailVO.PolicyVO policy = new PolicyDetailVO.PolicyVO();
+		List<String> action = new ArrayList<String>();
+		List<String> resource = new ArrayList<String>();
+		action.add("oss:PutObject");
+		action.add("oss:AbortMultipartUpload");
+		action.add("oss:ListParts");
+		action.add("oss:AbortMultipartUpload");
+		action.add("oss:ListObjects");
+		resource.add("*");
+		policy.setEffect("Allow");
+		policy.setAction(action);
+		policy.setResource(resource);
+		statement.add(policy);
+		policyDetail.setStatement(statement);
+
+	}
+
+
+	public static OSSClient getVideoClient() {
+		return videoClient;
+	}
+
+	public void setVideoClient(OSSClient videoClient) {
+		AliOssConfig.videoClient = videoClient;
+	}
+
+	public static String cdnDomain;
+	public static String imgDomain;
+	public static String videoDomain;
+	public static String uploadDomain;
+	public static String lvvideoDomain;
+	public static String pubBucket;
+	public static String priBucket;
+	public static String priEndPoint;
+	public static HashMap<String, String> bucketes = new HashMap<String, String>();
+	private static Logger logger = Logger.getLogger(AliOssFileTool.class);
+	public static String SUFFIX = "https://";
+	private String tmpStr;
+	@PostConstruct
+	public void init() throws Exception {
+		logger.info("初始化oss参数");
+		try {
+			setOssEndPoint(ossEndPoint);
+			setProjectName(projectName);
+			setExpiration(Long.valueOf(expiration));
+			setVideoEndPoint(videoEndPoint);
+			setAccessKeyId(accessKeyId);
+			setAccessKeySecret(accessKeySecret);
+			setCdnDomain(cdnDomain);
+			setImgDomain(imgDomain);
+			setVideoDomain(videoDomain);
+			setUploadDomain(uploadDomain);
+			setInternalEndPoint(internalEndPoint);
+			setPubBucket(pubBucket);
+			try{
+				String[] buckets = pubBucket.split(",");
+				for (String bucket : buckets) {
+					String[] bucketParms = bucket.split(":");
+					String bucketKey = bucketParms[0];
+					String bucketName = bucketParms[1];
+					AliOssConfig.bucketes.put(bucketKey, bucketName);
+				}
+			}catch(Exception e){
+				e.printStackTrace();
+			}
+			setPriBucket(priBucket);
+			String[] buckets = priBucket.split(",");
+			for (String bucket : buckets) {
+				String[] bucketParms = bucket.split(":");
+				String bucketKey = bucketParms[0];
+				String bucketName = bucketParms[1];
+				AliOssConfig.bucketes.put(bucketKey, bucketName);
+			}
+			setNeedPress(needPress);
+			setLvvideoDomain(lvvideoDomain);
+			setpriEndPoint(priEndPoint);
+			setSnapshotLocation("");
+
+			setOssClient(new OSSClient(SUFFIX + ossEndPoint, accessKeyId, accessKeySecret));
+			setInternalOssClient(new OSSClient(SUFFIX + internalEndPoint, accessKeyId, accessKeySecret));
+			setPriClient(new OSSClient(SUFFIX + priEndPoint, accessKeyId, accessKeySecret));
+			setVideoClient(new OSSClient(SUFFIX + videoEndPoint, accessKeyId, accessKeySecret));
+			DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou", accessKeyId, accessKeySecret);
+			setDefaultAcsClient(new DefaultAcsClient(profile));
+		} catch (Exception e) {
+			logger.error("配置文件读取失败");
+			throw e;
+		}
+
+	}
+	@Value("${oss.supply.priEndPoint}")
+	public void setpriEndPoint(String priEndPoint) {
+		AliOssConfig.priEndPoint = priEndPoint;
+	}
+
+	public static OSSClient getOssReadOnlyClient() {
+		if (AliOssConfig.ossReadOnlyClient == null) {
+			ClientConfiguration conf = new ClientConfiguration();
+			// 设置OSSClient允许打开的最大HTTP连接数,默认为1024个。
+			conf.setMaxConnections(500);
+			// 设置Socket层传输数据的超时时间,默认为50000毫秒。
+			conf.setSocketTimeout(40000);
+			// 设置建立连接的超时时间,默认为50000毫秒。
+			conf.setConnectionTimeout(10000);
+			// 设置从连接池中获取连接的超时时间(单位:毫秒),默认不超时。
+			conf.setConnectionRequestTimeout(1000);
+			// 设置连接空闲超时时间。超时则关闭连接,默认为60000毫秒。
+			conf.setIdleConnectionTime(10000);
+			// 设置失败请求重试次数,默认为3次。
+			conf.setMaxErrorRetry(5);
+			AliOssConfig.ossReadOnlyClient = new OSSClient(SUFFIX + getOssEndPoint(), 
+					PropertiesUtils.getReadOnlyAccessKeyId(),
+					PropertiesUtils.getReadOnlyAccessKeySecret(),
+					conf );
+		}
+		return AliOssConfig.ossReadOnlyClient;
+	}
+	@Value("${oss.supply.internal.endPoint}")
+	public void setInternalEndPoint(String internalEndPoint) {
+		AliOssConfig.internalEndPoint = internalEndPoint;
+	}
+
+	public static String getInternalEndPoint() {
+		return internalEndPoint;
+	}
+	public static String getBucket(String key) {
+		String bucket = AliOssConfig.bucketes.get(key);
+		if (StringUtils.isEmpty(bucket)) {
+			logger.error(String.format("bucket key[%s]不存在", key));
+		}
+		return bucket;
+	}
+
+	public static String getSnapshotLocation() {
+		return snapshotLocation;
+	}
+
+	public void setSnapshotLocation(String snapshotLocation ) {
+		AliOssConfig.snapshotLocation = snapshotLocation;
+	}
+	
+	public static String getProjectName() {
+		return projectName;
+	}
+
+	@Value("${oss.supply.projectName}")
+	public void setProjectName(String projectName) {
+		AliOssConfig.projectName = projectName;
+	}
+
+
+	public static OSSClient getOssClient() {
+		return ossClient;
+	}
+
+	public void setOssClient(OSSClient ossClient) {
+		AliOssConfig.ossClient = ossClient;
+	}
+
+	public static OSSClient getInternalOssClient() {
+		return internalOssClient;
+	}
+
+	public void setInternalOssClient(OSSClient internalOssClient) {
+		AliOssConfig.internalOssClient = internalOssClient;
+	}
+
+	public static String getOssEndPoint() {
+		return ossEndPoint;
+	}
+
+	@Value("${oss.supply.ossEndPoint}")
+	public void setOssEndPoint(String ossEndPoint) {
+		AliOssConfig.ossEndPoint = ossEndPoint;
+	}
+
+	public static String getAccessKeyId() {
+		return accessKeyId;
+	}
+	@Value("${oss.supply.accessKey}")
+	public void setAccessKeyId(String accessKeyId) {
+		AliOssConfig.accessKeyId = accessKeyId;
+	}
+
+
+
+	public static String getAccessKeySecret() {
+		return accessKeySecret;
+	}
+	@Value("${oss.supply.secretKey}")
+	public void setAccessKeySecret(String accessKeySecret) {
+		AliOssConfig.accessKeySecret = accessKeySecret;
+	}
+
+	public String getConfigPath() {
+		return configPath;
+	}
+
+	public void setConfigPath(String configPath) {
+		this.configPath = configPath;
+	}
+
+	public static String getCdnDomain() {
+		return cdnDomain;
+	}
+	@Value("${oss.supply.cdnDomain}")
+	public void setCdnDomain(String cdnDomain) {
+		AliOssConfig.cdnDomain = cdnDomain;
+	}
+
+	public static String getImgDomain() {
+		return imgDomain;
+	}
+
+	@Value("${oss.supply.imgDomain}")
+	public void setImgDomain(String imgDomain) {
+		AliOssConfig.imgDomain = imgDomain;
+	}
+	@Value("${oss.supply.pubBucket}")
+	public void setPubBucket(String pubBucket) {
+		AliOssConfig.pubBucket = pubBucket;
+	}
+	@Value("${oss.supply.priBucket}")
+	public void setPriBucket(String priBucket) {
+		AliOssConfig.priBucket = priBucket;
+	}
+
+	public static HashMap<String, String> getBucketes() {
+		return bucketes;
+	}
+
+	public void setBucketes(HashMap<String, String> bucketes) {
+		AliOssConfig.bucketes = bucketes;
+	}
+
+
+	public static Boolean getNeedPress() {
+		return needPress;
+	}
+	@Value("${oss.supply.needPress}")
+	public void setNeedPress(Boolean needPress) {
+		AliOssConfig.needPress = needPress;
+	}
+
+	public static String getVideoDomain() {
+		return videoDomain;
+	}
+	@Value("${oss.supply.videoDomain}")
+	public void setVideoDomain(String videoDomain) {
+		AliOssConfig.videoDomain = videoDomain;
+	}
+
+	public static String getVideoEndPoint() {
+		return videoEndPoint;
+	}
+	@Value("${oss.supply.videoEndPoint}")
+	public void setVideoEndPoint(String videoEndPoint) {
+		AliOssConfig.videoEndPoint = videoEndPoint;
+	}
+
+
+	public static long getExpiration() {
+		return expiration;
+	}
+	@Value("${oss.supply.expiration}")
+	public void setExpiration(long expiration) {
+		AliOssConfig.expiration = expiration;
+	}
+
+	public static String getUploadDomain() {
+		return uploadDomain;
+	}
+	@Value("${cdn.upload.domain}")
+	public void setUploadDomain(String uploadDomain) {
+		AliOssConfig.uploadDomain = uploadDomain;
+	}
+
+	public static PolicyDetailVO getPolicyDetail() {
+		return policyDetail;
+	}
+
+	public void setPolicyDetail(PolicyDetailVO policyDetail) {
+		AliOssConfig.policyDetail = policyDetail;
+	}
+
+	public static OSSClient getPriClient() {
+		return priClient;
+	}
+
+	public void setPriClient(OSSClient priClient) {
+		AliOssConfig.priClient = priClient;
+	}
+
+	public static DefaultAcsClient getDefaultAcsClient() {
+		return defaultAcsClient;
+	}
+
+	public void setDefaultAcsClient(DefaultAcsClient defaultAcsClient) {
+		AliOssConfig.defaultAcsClient = defaultAcsClient;
+	}
+
+	public static String getLvvideoDomain() {
+		return lvvideoDomain;
+	}
+	@Value("${oss.supply.lvvideoDomain}")
+	public void setLvvideoDomain(String lvvideoDomain) {
+		AliOssConfig.lvvideoDomain = lvvideoDomain;
+	}
+	
+	public static OSSClient getShangHaiInternalOssClient() {
+		return new OSSClient(SUFFIX + "oss-cn-shanghai.aliyuncs.com", accessKeyId, accessKeySecret);
+	} 
+	
+	public static IAcsClient getIAcsClient() {
+		if (AliOssConfig.mAcsClient == null) {
+			DefaultProfile profile = DefaultProfile.getProfile(getMpsRegionId(), // 地域ID
+					getAccessKeyId(), // RAM账号的AccessKey ID
+					getAccessKeySecret()); // RAM账号Access Key Secret
+			mAcsClient = new DefaultAcsClient(profile);
+		}
+		return AliOssConfig.mAcsClient;
+	}
+	public static String getMpsRegionId() {
+		return PropertiesUtils.getValue("oss.longvideo.transcode.mpsRegionId");
+	}
+}

+ 55 - 0
core/src/main/java/com/tzld/supply/config/RedisConfig.java

@@ -0,0 +1,55 @@
+package com.tzld.supply.config;
+
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.PropertyAccessor;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
+import org.springframework.data.redis.serializer.RedisSerializer;
+import org.springframework.data.redis.serializer.StringRedisSerializer;
+
+@Configuration
+public class RedisConfig {
+
+    @Bean
+    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
+        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
+        redisTemplate.setConnectionFactory(redisConnectionFactory);
+        //设置value的序列化方式json
+        redisTemplate.setValueSerializer(redisSerializer());
+        //设置key序列化方式String
+        redisTemplate.setKeySerializer(new StringRedisSerializer());
+        //设置hash key序列化方式String
+        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
+        //设置hash value序列化json
+        redisTemplate.setHashValueSerializer(redisSerializer());
+        redisTemplate.afterPropertiesSet();
+        return redisTemplate;
+    }
+
+    @Bean
+    public RedisTemplate<String, String> stringRedisTemplate(RedisConnectionFactory redisConnectionFactory){
+        RedisTemplate<String, String> redisTemplate = new RedisTemplate<>();
+        redisTemplate.setConnectionFactory(redisConnectionFactory);
+        redisTemplate.setKeySerializer(new StringRedisSerializer());
+        redisTemplate.setValueSerializer(new StringRedisSerializer());
+        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
+        redisTemplate.setHashValueSerializer(new StringRedisSerializer());
+        redisTemplate.afterPropertiesSet();
+        return redisTemplate;
+    }
+
+    public RedisSerializer<Object> redisSerializer() {
+        //创建JSON序列化器
+        ObjectMapper objectMapper = new ObjectMapper();
+        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
+        //必须设置,否则无法序列化实体类对象
+        objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
+        return new GenericJackson2JsonRedisSerializer(objectMapper);
+    }
+
+}

+ 0 - 63
core/src/main/java/com/tzld/supply/config/RedisTemplateConfig.java

@@ -1,63 +0,0 @@
-package com.tzld.supply.config;
-
-import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
-import org.springframework.beans.factory.annotation.Qualifier;
-import org.springframework.boot.context.properties.ConfigurationProperties;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.Primary;
-import org.springframework.data.redis.connection.RedisConnectionFactory;
-import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
-import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
-import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
-import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
-import org.springframework.data.redis.core.RedisTemplate;
-import org.springframework.data.redis.serializer.StringRedisSerializer;
-
-@Configuration
-public class RedisTemplateConfig {
-
-    @Bean("redisPool")
-    @ConfigurationProperties(prefix = "spring.redis.lettuce.pool")
-    public GenericObjectPoolConfig<LettucePoolingClientConfiguration> redisPool() {
-        return new GenericObjectPoolConfig<>();
-    }
-
-    @Bean("redisConfig")
-    @ConfigurationProperties(prefix = "spring.redis")
-    public RedisStandaloneConfiguration tairConfig() {
-        return new RedisStandaloneConfiguration();
-    }
-
-    @Bean("redisFactory")
-    @Primary
-    public LettuceConnectionFactory factory(@Qualifier("redisPool") GenericObjectPoolConfig<LettucePoolingClientConfiguration> redisPool,
-                                            @Qualifier("redisConfig") RedisStandaloneConfiguration redisConfig) {
-        LettuceClientConfiguration lettuceClientConfiguration =
-                LettucePoolingClientConfiguration.builder().poolConfig(redisPool).build();
-        return new LettuceConnectionFactory(redisConfig, lettuceClientConfiguration);
-    }
-
-    @Bean(name = "redisTemplate")
-    public RedisTemplate<String, String> getRedisTemplate(@Qualifier("redisFactory") RedisConnectionFactory factory) {
-        return buildRedisTemplateByString(factory);
-    }
-
-    /**
-     * 构建redisTemplate 使用string序列化
-     *
-     * @param factory
-     * @return
-     */
-    public RedisTemplate<String, String> buildRedisTemplateByString(RedisConnectionFactory factory) {
-        RedisTemplate<String, String> redisTemplate = new RedisTemplate<>();
-        redisTemplate.setConnectionFactory(factory);
-        // key的序列化类型 保证可读性
-        redisTemplate.setKeySerializer(new StringRedisSerializer());
-        redisTemplate.setValueSerializer(new StringRedisSerializer());
-        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
-        redisTemplate.setHashValueSerializer(new StringRedisSerializer());
-        return redisTemplate;
-    }
-
-}

+ 96 - 0
core/src/main/java/com/tzld/supply/dao/mapper/supply/spider/SpiderContentMediaMapper.java

@@ -0,0 +1,96 @@
+package com.tzld.supply.dao.mapper.supply.spider;
+
+import com.tzld.supply.model.po.supply.spider.SpiderContentMedia;
+import com.tzld.supply.model.po.supply.spider.SpiderContentMediaExample;
+import java.util.List;
+import org.apache.ibatis.annotations.Param;
+
+public interface SpiderContentMediaMapper {
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table spider_content_media
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    long countByExample(SpiderContentMediaExample example);
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table spider_content_media
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    int deleteByExample(SpiderContentMediaExample example);
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table spider_content_media
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    int deleteByPrimaryKey(Long id);
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table spider_content_media
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    int insert(SpiderContentMedia record);
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table spider_content_media
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    int insertSelective(SpiderContentMedia record);
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table spider_content_media
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    List<SpiderContentMedia> selectByExample(SpiderContentMediaExample example);
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table spider_content_media
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    SpiderContentMedia selectByPrimaryKey(Long id);
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table spider_content_media
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    int updateByExampleSelective(@Param("record") SpiderContentMedia record, @Param("example") SpiderContentMediaExample example);
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table spider_content_media
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    int updateByExample(@Param("record") SpiderContentMedia record, @Param("example") SpiderContentMediaExample example);
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table spider_content_media
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    int updateByPrimaryKeySelective(SpiderContentMedia record);
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table spider_content_media
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    int updateByPrimaryKey(SpiderContentMedia record);
+}

+ 120 - 0
core/src/main/java/com/tzld/supply/dao/mapper/supply/spider/ToolsAudioTransRecordMapper.java

@@ -0,0 +1,120 @@
+package com.tzld.supply.dao.mapper.supply.spider;
+
+import com.tzld.supply.model.po.supply.spider.ToolsAudioTransRecord;
+import com.tzld.supply.model.po.supply.spider.ToolsAudioTransRecordExample;
+import java.util.List;
+import org.apache.ibatis.annotations.Param;
+
+public interface ToolsAudioTransRecordMapper {
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table tools_audio_trans_record
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    long countByExample(ToolsAudioTransRecordExample example);
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table tools_audio_trans_record
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    int deleteByExample(ToolsAudioTransRecordExample example);
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table tools_audio_trans_record
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    int deleteByPrimaryKey(Long id);
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table tools_audio_trans_record
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    int insert(ToolsAudioTransRecord record);
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table tools_audio_trans_record
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    int insertSelective(ToolsAudioTransRecord record);
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table tools_audio_trans_record
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    List<ToolsAudioTransRecord> selectByExampleWithBLOBs(ToolsAudioTransRecordExample example);
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table tools_audio_trans_record
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    List<ToolsAudioTransRecord> selectByExample(ToolsAudioTransRecordExample example);
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table tools_audio_trans_record
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    ToolsAudioTransRecord selectByPrimaryKey(Long id);
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table tools_audio_trans_record
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    int updateByExampleSelective(@Param("record") ToolsAudioTransRecord record, @Param("example") ToolsAudioTransRecordExample example);
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table tools_audio_trans_record
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    int updateByExampleWithBLOBs(@Param("record") ToolsAudioTransRecord record, @Param("example") ToolsAudioTransRecordExample example);
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table tools_audio_trans_record
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    int updateByExample(@Param("record") ToolsAudioTransRecord record, @Param("example") ToolsAudioTransRecordExample example);
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table tools_audio_trans_record
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    int updateByPrimaryKeySelective(ToolsAudioTransRecord record);
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table tools_audio_trans_record
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    int updateByPrimaryKeyWithBLOBs(ToolsAudioTransRecord record);
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table tools_audio_trans_record
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    int updateByPrimaryKey(ToolsAudioTransRecord record);
+}

+ 8 - 0
core/src/main/java/com/tzld/supply/exception/FFmpegExecuteException.java

@@ -0,0 +1,8 @@
+package com.tzld.supply.exception;
+
+public class FFmpegExecuteException extends RuntimeException {
+
+    public FFmpegExecuteException(String message) {
+        super(message);
+    }
+}

+ 8 - 0
core/src/main/java/com/tzld/supply/exception/LayerException.java

@@ -0,0 +1,8 @@
+package com.tzld.supply.exception;
+
+public class LayerException extends Exception{
+
+    public LayerException(String message) {
+        super(message);
+    }
+}

+ 8 - 0
core/src/main/java/com/tzld/supply/exception/VaryImageException.java

@@ -0,0 +1,8 @@
+package com.tzld.supply.exception;
+
+public class VaryImageException extends Exception{
+
+    public VaryImageException(String message) {
+        super(message);
+    }
+}

+ 12 - 0
core/src/main/java/com/tzld/supply/model/dto/ali/AliVoiceResultData.java

@@ -0,0 +1,12 @@
+package com.tzld.supply.model.dto.ali;
+
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class AliVoiceResultData {
+    private String taskId;
+    private List<AliVoiceResultSentenceData> sentences;
+    private List<AliVoiceResultWordData> words;
+}

+ 14 - 0
core/src/main/java/com/tzld/supply/model/dto/ali/AliVoiceResultSentenceData.java

@@ -0,0 +1,14 @@
+package com.tzld.supply.model.dto.ali;
+
+import lombok.Data;
+
+@Data
+public class AliVoiceResultSentenceData {
+    private Integer ChannelId;
+    private Integer BeginTime;
+    private Integer EndTime;
+    private String Text;
+    private Integer SilenceDuration;
+    private Integer SpeechRate;
+    private Double EmotionValue;
+}

+ 11 - 0
core/src/main/java/com/tzld/supply/model/dto/ali/AliVoiceResultWordData.java

@@ -0,0 +1,11 @@
+package com.tzld.supply.model.dto.ali;
+
+import lombok.Data;
+
+@Data
+public class AliVoiceResultWordData {
+    private Integer ChannelId;
+    private Integer BeginTime;
+    private Integer EndTime;
+    private String Word;
+}

+ 17 - 0
core/src/main/java/com/tzld/supply/model/param/FFmpeg/AudioSplitParam.java

@@ -0,0 +1,17 @@
+package com.tzld.supply.model.param.FFmpeg;
+
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+@Data
+@Accessors(chain = true)
+public class AudioSplitParam {
+
+    private String audioUrl;
+
+    /**
+     * 间隔时长,按照间隔时长切分时需要
+     */
+    private Integer segmentTime;
+
+}

+ 13 - 0
core/src/main/java/com/tzld/supply/model/param/FFmpeg/AudioVolumeParam.java

@@ -0,0 +1,13 @@
+package com.tzld.supply.model.param.FFmpeg;
+
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+@Data
+@Accessors(chain = true)
+public class AudioVolumeParam {
+
+    private String audioUrl;
+
+    private Double volume;
+}

+ 13 - 0
core/src/main/java/com/tzld/supply/model/param/FFmpeg/CommandParam.java

@@ -0,0 +1,13 @@
+package com.tzld.supply.model.param.FFmpeg;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class CommandParam {
+    private String ffmpegCommand;
+    private String outputFile;
+    // video audio image text unknown
+    private String outputType;
+}

+ 14 - 0
core/src/main/java/com/tzld/supply/model/param/FFmpeg/ExtraVideoSrtParam.java

@@ -0,0 +1,14 @@
+package com.tzld.supply.model.param.FFmpeg;
+
+import lombok.Getter;
+import lombok.Setter;
+import lombok.experimental.Accessors;
+
+
+@Getter
+@Setter
+@Accessors(chain = true)
+public class ExtraVideoSrtParam {
+    private String videoUrl;
+
+}

+ 15 - 0
core/src/main/java/com/tzld/supply/model/param/FFmpeg/MediaSimpleMergeParam.java

@@ -0,0 +1,15 @@
+package com.tzld.supply.model.param.FFmpeg;
+
+import lombok.Getter;
+import lombok.Setter;
+import lombok.experimental.Accessors;
+
+import java.util.List;
+
+@Getter
+@Setter
+@Accessors(chain = true)
+public class MediaSimpleMergeParam {
+    private List<String> videoUrls;
+    private List<String> audioUrls;
+}

+ 14 - 0
core/src/main/java/com/tzld/supply/model/param/FFmpeg/MergeBgVideoParam.java

@@ -0,0 +1,14 @@
+package com.tzld.supply.model.param.FFmpeg;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class MergeBgVideoParam {
+    private String mainVideo;
+    private String type;
+    private String bgMedia;
+    // 0x00FF00
+    private String color;
+}

+ 14 - 0
core/src/main/java/com/tzld/supply/model/param/FFmpeg/RemoveBgParam.java

@@ -0,0 +1,14 @@
+package com.tzld.supply.model.param.FFmpeg;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class RemoveBgParam {
+    // video/image
+    private String mediaType;
+    private String mediaUrl;
+    // 0x00FF00
+    private String color;
+}

+ 11 - 0
core/src/main/java/com/tzld/supply/model/param/FFmpeg/VideoAddAssSubtitleParam.java

@@ -0,0 +1,11 @@
+package com.tzld.supply.model.param.FFmpeg;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class VideoAddAssSubtitleParam {
+    private String videoUrl;
+    private String assSubtitle;
+}

+ 19 - 0
core/src/main/java/com/tzld/supply/model/param/FFmpeg/VideoAddAudioParam.java

@@ -0,0 +1,19 @@
+package com.tzld.supply.model.param.FFmpeg;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class VideoAddAudioParam {
+    private String videoUrl;
+    private String audioUrl;
+    // 主时间轴,video/audio
+    private String mainTimeline;
+    // 是否保留视频原声
+    private Boolean keepVideoOriginalSound;
+    // 视频音量 1-100
+    private Integer videoVolume;
+    // 音频音量 1-100
+    private Integer audioVolume;
+}

+ 61 - 0
core/src/main/java/com/tzld/supply/model/param/FFmpeg/VideoAddTailParam.java

@@ -0,0 +1,61 @@
+package com.tzld.supply.model.param.FFmpeg;
+
+import com.alibaba.fastjson.JSON;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class VideoAddTailParam {
+    private String bizId;
+    private String originVideoUrl;
+    private String tailType;
+    private String tailVideoUrl;
+    private String tailAudioUrl;
+    private String assSubtitle;
+    private Integer srtMillisDuration;
+    // 原视频音量 1-100
+    private Integer originVideoVolume;
+    // 片尾音量 1-100
+    private Integer tailVolume;
+
+    public static void main(String[] args) {
+        VideoAddTailParam param = new VideoAddTailParam();
+        param.setOriginVideoUrl("http://testres.cybertogether.net/test/2.mp4");
+        param.setTailType("lastFrame");
+        param.setTailVideoUrl("http://testres.cybertogether.net/test/7.mp4");
+        param.setTailAudioUrl("http://testres.cybertogether.net/test/2.mp3");
+        param.setAssSubtitle("[Script Info]\n" +
+                "; Script generated by Aegisub 3.2.2\n" +
+                "; http://www.aegisub.org/\n" +
+                "Title: Default Aegisub file\n" +
+                "ScriptType: v4.00+\n" +
+                "WrapStyle: 0\n" +
+                "ScaledBorderAndShadow: yes\n" +
+                "YCbCr Matrix: None\n" +
+                "PlayResX: 100\n" +
+                "PlayResY: 100\n" +
+                "\n" +
+                "[Aegisub Project Garbage]\n" +
+                "Last Style Storage: Default\n" +
+                "Active Line: 1\n" +
+                "\n" +
+                "[V4+ Styles]\n" +
+                "Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, " +
+                "Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, " +
+                "Alignment, MarginL, MarginR, MarginV, Encoding\n" +
+                "Style: Default,PingFang SC,6,&H00000000,&H00000000,&H0000DEFF,&HFF00DEFF,-1,0,0,0,100,100,0,0,3,0.1," +
+                "2,2,10,10,10,134\n" +
+                "\n" +
+                "\n" +
+                "[Events]\n" +
+                "Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text\n" +
+                "Dialogue: 0,00:00:00.04,00:00:04.06,Default,,0,0,0,,叠个千纸鹤再系个红飘带\n" +
+                "Dialogue: 0,00:00:04.12,00:00:08.24,Default,,0,0,0,,愿善良的人们天天好运来\n" +
+                "Dialogue: 0,00:00:08.24,00:00:12.76,Default,,0,0,0,,你勤劳生活美你健康春常\\N在\n" +
+                "Dialogue: 0,00:00:12.76,00:00:17.26,Default,,0,0,0,,你一生的忙碌为了笑逐颜\\N开\n" +
+                "Dialogue: 0,00:00:18.12,00:00:22.44,Default,,0,0,0,,打个中国结请春风剪个彩\n");
+        param.setSrtMillisDuration(22044);
+        System.out.println(JSON.toJSONString(param));
+    }
+}

+ 10 - 0
core/src/main/java/com/tzld/supply/model/param/FFmpeg/VideoInfoParam.java

@@ -0,0 +1,10 @@
+package com.tzld.supply.model.param.FFmpeg;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class VideoInfoParam {
+    private String videoUrl;
+}

+ 12 - 0
core/src/main/java/com/tzld/supply/model/param/FFmpeg/VideoTextureParam.java

@@ -0,0 +1,12 @@
+package com.tzld.supply.model.param.FFmpeg;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class VideoTextureParam {
+    private String videoUrl;
+    private String imageUrl;
+    private Integer border;
+}

+ 12 - 0
core/src/main/java/com/tzld/supply/model/param/FFmpeg/VideoTimeCutParam.java

@@ -0,0 +1,12 @@
+package com.tzld.supply.model.param.FFmpeg;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class VideoTimeCutParam {
+    private String videoUrl;
+    private String startTime;
+    private String endTime;
+}

+ 12 - 0
core/src/main/java/com/tzld/supply/model/param/FFmpeg/VideoTimeExtraFrameParam.java

@@ -0,0 +1,12 @@
+package com.tzld.supply.model.param.FFmpeg;
+
+import lombok.Getter;
+import lombok.Setter;
+
+
+@Getter
+@Setter
+public class VideoTimeExtraFrameParam {
+    private String videoUrl;
+    private String time;
+}

+ 12 - 0
core/src/main/java/com/tzld/supply/model/param/FFmpeg/VideoTranscodingParam.java

@@ -0,0 +1,12 @@
+package com.tzld.supply.model.param.FFmpeg;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class VideoTranscodingParam {
+    private String videoUrl;
+    private String coding;
+    private String bitrate;
+}

+ 16 - 0
core/src/main/java/com/tzld/supply/model/param/FFmpeg/VideoUrlsParam.java

@@ -0,0 +1,16 @@
+package com.tzld.supply.model.param.FFmpeg;
+
+import lombok.Getter;
+import lombok.Setter;
+import lombok.experimental.Accessors;
+
+import java.util.List;
+
+@Getter
+@Setter
+@Accessors(chain = true)
+public class VideoUrlsParam {
+    private List<String> videoUrls;
+
+    private Integer pretreatmentSwitch;
+}

+ 18 - 0
core/src/main/java/com/tzld/supply/model/param/FileUploadParam.java

@@ -0,0 +1,18 @@
+package com.tzld.supply.model.param;
+
+import com.stuuudy.commons.external.filestorage.enums.EnumFileType;
+import lombok.Data;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.validation.constraints.NotNull;
+
+@Data
+public class FileUploadParam {
+    @NotNull(message = "文件不能为空")
+    private MultipartFile file;
+
+    private String fileUri;
+
+    @NotNull(message = "文件类型不能为空")
+    private EnumFileType fileType;
+}

+ 15 - 0
core/src/main/java/com/tzld/supply/model/param/OssUploadSignParam.java

@@ -0,0 +1,15 @@
+package com.tzld.supply.model.param;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+
+@Data
+public class OssUploadSignParam extends VideoApiBaseParam {
+
+	@ApiModelProperty(value = "文件类型1:picture 2:视频 3:声音 4:文件  5:gif 6:字幕", required = true)
+	@NotNull
+	private Integer fileType;
+
+}

+ 19 - 0
core/src/main/java/com/tzld/supply/model/param/StsTokenParam.java

@@ -0,0 +1,19 @@
+package com.tzld.supply.model.param;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.hibernate.validator.constraints.NotEmpty;
+
+import javax.validation.constraints.NotNull;
+
+@Data
+public class StsTokenParam extends VideoApiBaseParam {
+	
+	@NotEmpty(message = "fileType不能为空")
+	@NotNull(message = "fileType不能为空")
+	@ApiModelProperty(value = "文件类型:(1:PICTURE, 2:VIDEO, 3:VOICE, 4:FILE, 5:GIF)")
+	private Integer fileType;
+	@ApiModelProperty(value = "第一次获取token时返回的唯一标识字符串,用来在上传大文件时若签名快失效时重新获取对应文件的token")
+	private String uploadId;
+
+}

+ 196 - 0
core/src/main/java/com/tzld/supply/model/param/VideoApiBaseParam.java

@@ -0,0 +1,196 @@
+package com.tzld.supply.model.param;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+@Data
+public class VideoApiBaseParam {
+
+	//用户登陆相关的参数
+	@ApiModelProperty(value = "公共参数-登录用户ID")
+	private Long loginUid;
+	@ApiModelProperty(value = "公共参数-token值")
+	private String token;
+
+	//APP相关的参数
+	@ApiModelProperty(value = "公共参数-版本号")
+	private Integer versionCode;
+	@ApiModelProperty(value = "公共参数-app发布版本")
+	private String versionName;
+	@ApiModelProperty(value = "公共参数-产品类型:0-VLOG,1-轻趣视频,2-搞笑视频,3-爱电影,4-爱生活,5-长视频,6-短视频,7-惊奇视频,8-PC端,9-票圈长视频APP,10-票圈长视频lite,11-票圈相册,12-H5,13-票圈视频APP,14-小程序极速版,15-闪音卡点APP,16-社区APP")
+	private Integer appType;
+	@ApiModelProperty(value = "公共参数-appid")
+	private String appId;
+
+	//手机设备信息相关的参数
+	@ApiModelProperty(value = "公共参数-手机设备的唯一码")
+	private String machineCode;
+	@ApiModelProperty(value = "公共参数-ios,android")
+	private String platform;
+	@ApiModelProperty(value = "公共参数-精确到ios,android的哪个版本")
+	private String system;
+	@ApiModelProperty(value = "公共参数-手机信息")
+	private String machineInfo;
+	@ApiModelProperty(value = "公共参数-网络类型")
+	private String networkType;
+	@ApiModelProperty(value = "公共参数-客户端ip")
+	private String clientIp;
+
+	//pageSource相关的参数
+	@ApiModelProperty(value = "公共参数-页面来源")
+	private String pageSource;
+	@ApiModelProperty(value = "公共参数-页面分类ID,只在首页分发列表中才有")
+	private Long pageCategoryId;
+	@ApiModelProperty(value = "公共参数-root页面来源")
+	private String rootPageSource;
+	@ApiModelProperty(value = "公共参数-root页面分类ID")
+	private Long rootPageCategoryId;
+	@ApiModelProperty(value = "公共参数-root页面时间戳")
+	private Long rootPageTimestamp;
+
+	//某次操作相关的参数
+	@ApiModelProperty(value = "公共参数-小程序打开类型")
+	private String openType;
+	@ApiModelProperty(value = "公共参数-前端请求时间")
+	private Long clientTimestamp;
+	@ApiModelProperty(value = "公共参数-sessionId")
+	private String sessionId;
+
+	//视频相关的参数
+	@ApiModelProperty(value = "公共参数-视频加载时长,毫秒")
+	private Long videoLoadTime;
+	@ApiModelProperty(value = "公共参数-视频事件发生的时间点,相对于视频时长,秒")
+	private Long videoEventTime;
+	@ApiModelProperty(value = "公共参数-videoPlayError事件,播放器返回的错误信息")
+	private String errorMsg;
+
+	//分享相关的参数
+	@ApiModelProperty(value = "公共参数-分享的深度")
+	private Integer shareDepth;
+	@ApiModelProperty(value = "公共参数-分享者手机设备的唯一码,只有在分享页才有")
+	private String shareMachineCode;
+	@ApiModelProperty(value = "公共参数-分享者uid,只有在分享页才有")
+	private Long shareUid;
+	@ApiModelProperty(value = "公共参数-是否为分享页标识,0:不是分享页,1:是分享页")
+	private Integer sharePageFlag;
+	@ApiModelProperty(value = "公共参数-分享按钮类型")
+	private Integer shareButtonType;
+	@ApiModelProperty(value = "公共参数-从哪个产品分享出来的")
+	private Integer shareAppType;
+
+	@ApiModelProperty(value = "公共参数-0 综合模块 1 feed流 ")
+	private String sharePageType;
+	@ApiModelProperty(value = "公共参数-分享页跳转")
+	private String videoShareJumpModel;
+	//推荐相关的参数
+	@ApiModelProperty(value = "公共参数-推荐来源 0 默认")
+	private Integer recommendSource = 0;
+
+	@ApiModelProperty(value = "公共参数-视频上报数据,使用后端返回的原样数据")
+	private String videoReportMeta;
+
+	@ApiModelProperty(value = "公共参数-事件ID")
+	private String eventId;
+	@ApiModelProperty(value = "公共参数-root事件ID")
+	private String rootEventId;
+
+	@ApiModelProperty(value = "公共参数-root分享页面类型")
+	private String rootSharePageType;
+	@ApiModelProperty(value = "公共参数-自动标志位类型")
+	private String autoType;
+	@ApiModelProperty(value = "公共参数-动作发生的页面位置")
+	private String actionPosition;
+
+	@ApiModelProperty(value = "公共参数-运营消息ID")
+	private String operationMsgId;
+	@ApiModelProperty(value = "公共参数-playId")
+	private String playId;
+	@ApiModelProperty(value = "公共参数-活动id")
+	private String activityId;
+
+	@ApiModelProperty(value = "公共参数-AB信息")
+	private String abInfoData;
+
+	// h5
+	@ApiModelProperty(value = "公共参数-h5分享id")
+	private String h5ShareId;
+	@ApiModelProperty(value = "公共参数-h5打开场景")
+	private String h5OpenFrom;
+	@ApiModelProperty(value = "公共参数-h5分享场景")
+	private String h5ShareFrom;
+	@ApiModelProperty(value = "公共参数-h5页面-小程序分享根源rootPageSource")
+	private String h5WxRootPageSource;
+
+	@ApiModelProperty(value = "公共参数-搜索上报")
+	private String searchActionCode;
+
+	@ApiModelProperty(value = "公共参数-AB的根实验集合")
+	private String rootEventIds;
+
+	@ApiModelProperty(value = "公共参数-returnId")
+	private String returnId;
+	@ApiModelProperty(value = "公共参数-viewId")
+	private String viewId;
+	@ApiModelProperty(value = "公共参数-shareId")
+	private String shareId;
+	@ApiModelProperty(value = "公共参数-subSessionId")
+	private String subSessionId;
+
+	@ApiModelProperty(value = "公共参数-headVideoId")
+	private String headVideoId;
+    @ApiModelProperty(value = "公共参数-推荐链路ID")
+    private String recommendId;
+	@ApiModelProperty(value = "公共参数-推荐日志对象")
+	private String recommendLogVO;
+
+	@ApiModelProperty(value = "公共参数-分享ID")
+	private String jumpHomeVideoId;
+	@ApiModelProperty(value = "公共参数-根分享ID")
+	private String rootJumpHomeVideoId;
+
+	@ApiModelProperty(value = "公共参数-小程序打开场景")
+	private Integer senceType=-1;
+	@ApiModelProperty(value = "公共参数-小程序打开场景 热启动")
+	private Integer hotSenceType;
+	@ApiModelProperty(value = "公共参数-rootMid")
+	private String rootMid;
+	@ApiModelProperty(value = "公共参数-渠道:yybstore xmstore hwstore oppostore vivostore sougou bdstore mzstore store360 douyin kuaishou")
+	private String appChannel;
+	//	@ApiModelProperty(value = "微信AB实验参数 例如:22031801")
+//	private String wechatAbcode;
+	@ApiModelProperty(value = "通过微信实验分流获取到的对应数据")
+    private String abExpInfo; //Map<String, List<AbExpItemDTO>>
+    @ApiModelProperty(value = "H5")
+    private Long adBlockVideoId;
+
+    private Boolean showDistribute;
+
+    private Boolean showShare;
+
+    private Boolean showFocusExp;
+
+    private Boolean isFirstPageOrFirstPageFeed;
+
+	private String newExpGroup;
+
+	private Boolean isTest;
+	/**
+	 * 代表用户来自哪个外部来源,包括:公众号文章、投流等等,
+	 */
+	private String rootSourceId;
+	/**
+	 * 代表一个通过分享卡片进入的用户,他最初来自哪个用户mid的分享
+	 */
+	private String rootShareMid;
+	/**
+	 * 代表一个通过分享卡片进入的用户,他最初来自哪个用户mid的哪次冷启动sessionId的分享
+	 */
+	private String rootSessionId;
+
+	private String rootShareId;
+	/**
+	 * 微信群ID
+	 */
+	private String openGId;
+
+}

+ 452 - 0
core/src/main/java/com/tzld/supply/model/po/supply/spider/SpiderContentMedia.java

@@ -0,0 +1,452 @@
+package com.tzld.supply.model.po.supply.spider;
+
+/**
+ *
+ * This class was generated by MyBatis Generator.
+ * This class corresponds to the database table spider_content_media
+ */
+public class SpiderContentMedia {
+    /**
+     * Database Column Remarks:
+     *   主键ID
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column spider_content_media.id
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    private Long id;
+
+    /**
+     * Database Column Remarks:
+     *   内容ID
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column spider_content_media.content_id
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    private Long contentId;
+
+    /**
+     * Database Column Remarks:
+     *   类型(video/image)
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column spider_content_media.media_type
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    private String mediaType;
+
+    /**
+     * Database Column Remarks:
+     *   来源网站
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column spider_content_media.source_site
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    private String sourceSite;
+
+    /**
+     * Database Column Remarks:
+     *   原始URL
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column spider_content_media.url
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    private String url;
+
+    /**
+     * Database Column Remarks:
+     *   标题
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column spider_content_media.title
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    private String title;
+
+    /**
+     * Database Column Remarks:
+     *   视频时长
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column spider_content_media.duration
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    private Integer duration;
+
+    /**
+     * Database Column Remarks:
+     *   转存oss路径
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column spider_content_media.oss_key
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    private String ossKey;
+
+    /**
+     * Database Column Remarks:
+     *   相关性分值
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column spider_content_media.relevance_score
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    private Double relevanceScore;
+
+    /**
+     * Database Column Remarks:
+     *   状态(1-有效/0-废弃)
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column spider_content_media.status
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    private Integer status;
+
+    /**
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column spider_content_media.create_time
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    private Long createTime;
+
+    /**
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column spider_content_media.update_time
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    private Long updateTime;
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column spider_content_media.id
+     *
+     * @return the value of spider_content_media.id
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    public Long getId() {
+        return id;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column spider_content_media.id
+     *
+     * @param id the value for spider_content_media.id
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column spider_content_media.content_id
+     *
+     * @return the value of spider_content_media.content_id
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    public Long getContentId() {
+        return contentId;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column spider_content_media.content_id
+     *
+     * @param contentId the value for spider_content_media.content_id
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    public void setContentId(Long contentId) {
+        this.contentId = contentId;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column spider_content_media.media_type
+     *
+     * @return the value of spider_content_media.media_type
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    public String getMediaType() {
+        return mediaType;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column spider_content_media.media_type
+     *
+     * @param mediaType the value for spider_content_media.media_type
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    public void setMediaType(String mediaType) {
+        this.mediaType = mediaType;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column spider_content_media.source_site
+     *
+     * @return the value of spider_content_media.source_site
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    public String getSourceSite() {
+        return sourceSite;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column spider_content_media.source_site
+     *
+     * @param sourceSite the value for spider_content_media.source_site
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    public void setSourceSite(String sourceSite) {
+        this.sourceSite = sourceSite;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column spider_content_media.url
+     *
+     * @return the value of spider_content_media.url
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    public String getUrl() {
+        return url;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column spider_content_media.url
+     *
+     * @param url the value for spider_content_media.url
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    public void setUrl(String url) {
+        this.url = url;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column spider_content_media.title
+     *
+     * @return the value of spider_content_media.title
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    public String getTitle() {
+        return title;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column spider_content_media.title
+     *
+     * @param title the value for spider_content_media.title
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    public void setTitle(String title) {
+        this.title = title;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column spider_content_media.duration
+     *
+     * @return the value of spider_content_media.duration
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    public Integer getDuration() {
+        return duration;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column spider_content_media.duration
+     *
+     * @param duration the value for spider_content_media.duration
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    public void setDuration(Integer duration) {
+        this.duration = duration;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column spider_content_media.oss_key
+     *
+     * @return the value of spider_content_media.oss_key
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    public String getOssKey() {
+        return ossKey;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column spider_content_media.oss_key
+     *
+     * @param ossKey the value for spider_content_media.oss_key
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    public void setOssKey(String ossKey) {
+        this.ossKey = ossKey;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column spider_content_media.relevance_score
+     *
+     * @return the value of spider_content_media.relevance_score
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    public Double getRelevanceScore() {
+        return relevanceScore;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column spider_content_media.relevance_score
+     *
+     * @param relevanceScore the value for spider_content_media.relevance_score
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    public void setRelevanceScore(Double relevanceScore) {
+        this.relevanceScore = relevanceScore;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column spider_content_media.status
+     *
+     * @return the value of spider_content_media.status
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    public Integer getStatus() {
+        return status;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column spider_content_media.status
+     *
+     * @param status the value for spider_content_media.status
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    public void setStatus(Integer status) {
+        this.status = status;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column spider_content_media.create_time
+     *
+     * @return the value of spider_content_media.create_time
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    public Long getCreateTime() {
+        return createTime;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column spider_content_media.create_time
+     *
+     * @param createTime the value for spider_content_media.create_time
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    public void setCreateTime(Long createTime) {
+        this.createTime = createTime;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column spider_content_media.update_time
+     *
+     * @return the value of spider_content_media.update_time
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    public Long getUpdateTime() {
+        return updateTime;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column spider_content_media.update_time
+     *
+     * @param updateTime the value for spider_content_media.update_time
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    public void setUpdateTime(Long updateTime) {
+        this.updateTime = updateTime;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table spider_content_media
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(getClass().getSimpleName());
+        sb.append(" [");
+        sb.append("Hash = ").append(hashCode());
+        sb.append(", id=").append(id);
+        sb.append(", contentId=").append(contentId);
+        sb.append(", mediaType=").append(mediaType);
+        sb.append(", sourceSite=").append(sourceSite);
+        sb.append(", url=").append(url);
+        sb.append(", title=").append(title);
+        sb.append(", duration=").append(duration);
+        sb.append(", ossKey=").append(ossKey);
+        sb.append(", relevanceScore=").append(relevanceScore);
+        sb.append(", status=").append(status);
+        sb.append(", createTime=").append(createTime);
+        sb.append(", updateTime=").append(updateTime);
+        sb.append("]");
+        return sb.toString();
+    }
+}

+ 1072 - 0
core/src/main/java/com/tzld/supply/model/po/supply/spider/SpiderContentMediaExample.java

@@ -0,0 +1,1072 @@
+package com.tzld.supply.model.po.supply.spider;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class SpiderContentMediaExample {
+    /**
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database table spider_content_media
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    protected String orderByClause;
+
+    /**
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database table spider_content_media
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    protected boolean distinct;
+
+    /**
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database table spider_content_media
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    protected List<Criteria> oredCriteria;
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table spider_content_media
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    public SpiderContentMediaExample() {
+        oredCriteria = new ArrayList<Criteria>();
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table spider_content_media
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    public void setOrderByClause(String orderByClause) {
+        this.orderByClause = orderByClause;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table spider_content_media
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    public String getOrderByClause() {
+        return orderByClause;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table spider_content_media
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    public void setDistinct(boolean distinct) {
+        this.distinct = distinct;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table spider_content_media
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    public boolean isDistinct() {
+        return distinct;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table spider_content_media
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    public List<Criteria> getOredCriteria() {
+        return oredCriteria;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table spider_content_media
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    public void or(Criteria criteria) {
+        oredCriteria.add(criteria);
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table spider_content_media
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    public Criteria or() {
+        Criteria criteria = createCriteriaInternal();
+        oredCriteria.add(criteria);
+        return criteria;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table spider_content_media
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    public Criteria createCriteria() {
+        Criteria criteria = createCriteriaInternal();
+        if (oredCriteria.size() == 0) {
+            oredCriteria.add(criteria);
+        }
+        return criteria;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table spider_content_media
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    protected Criteria createCriteriaInternal() {
+        Criteria criteria = new Criteria();
+        return criteria;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table spider_content_media
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    public void clear() {
+        oredCriteria.clear();
+        orderByClause = null;
+        distinct = false;
+    }
+
+    /**
+     * This class was generated by MyBatis Generator.
+     * This class corresponds to the database table spider_content_media
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    protected abstract static class GeneratedCriteria {
+        protected List<Criterion> criteria;
+
+        protected GeneratedCriteria() {
+            super();
+            criteria = new ArrayList<Criterion>();
+        }
+
+        public boolean isValid() {
+            return criteria.size() > 0;
+        }
+
+        public List<Criterion> getAllCriteria() {
+            return criteria;
+        }
+
+        public List<Criterion> getCriteria() {
+            return criteria;
+        }
+
+        protected void addCriterion(String condition) {
+            if (condition == null) {
+                throw new RuntimeException("Value for condition cannot be null");
+            }
+            criteria.add(new Criterion(condition));
+        }
+
+        protected void addCriterion(String condition, Object value, String property) {
+            if (value == null) {
+                throw new RuntimeException("Value for " + property + " cannot be null");
+            }
+            criteria.add(new Criterion(condition, value));
+        }
+
+        protected void addCriterion(String condition, Object value1, Object value2, String property) {
+            if (value1 == null || value2 == null) {
+                throw new RuntimeException("Between values for " + property + " cannot be null");
+            }
+            criteria.add(new Criterion(condition, value1, value2));
+        }
+
+        public Criteria andIdIsNull() {
+            addCriterion("id is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andIdIsNotNull() {
+            addCriterion("id is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andIdEqualTo(Long value) {
+            addCriterion("id =", value, "id");
+            return (Criteria) this;
+        }
+
+        public Criteria andIdNotEqualTo(Long value) {
+            addCriterion("id <>", value, "id");
+            return (Criteria) this;
+        }
+
+        public Criteria andIdGreaterThan(Long value) {
+            addCriterion("id >", value, "id");
+            return (Criteria) this;
+        }
+
+        public Criteria andIdGreaterThanOrEqualTo(Long value) {
+            addCriterion("id >=", value, "id");
+            return (Criteria) this;
+        }
+
+        public Criteria andIdLessThan(Long value) {
+            addCriterion("id <", value, "id");
+            return (Criteria) this;
+        }
+
+        public Criteria andIdLessThanOrEqualTo(Long value) {
+            addCriterion("id <=", value, "id");
+            return (Criteria) this;
+        }
+
+        public Criteria andIdIn(List<Long> values) {
+            addCriterion("id in", values, "id");
+            return (Criteria) this;
+        }
+
+        public Criteria andIdNotIn(List<Long> values) {
+            addCriterion("id not in", values, "id");
+            return (Criteria) this;
+        }
+
+        public Criteria andIdBetween(Long value1, Long value2) {
+            addCriterion("id between", value1, value2, "id");
+            return (Criteria) this;
+        }
+
+        public Criteria andIdNotBetween(Long value1, Long value2) {
+            addCriterion("id not between", value1, value2, "id");
+            return (Criteria) this;
+        }
+
+        public Criteria andContentIdIsNull() {
+            addCriterion("content_id is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andContentIdIsNotNull() {
+            addCriterion("content_id is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andContentIdEqualTo(Long value) {
+            addCriterion("content_id =", value, "contentId");
+            return (Criteria) this;
+        }
+
+        public Criteria andContentIdNotEqualTo(Long value) {
+            addCriterion("content_id <>", value, "contentId");
+            return (Criteria) this;
+        }
+
+        public Criteria andContentIdGreaterThan(Long value) {
+            addCriterion("content_id >", value, "contentId");
+            return (Criteria) this;
+        }
+
+        public Criteria andContentIdGreaterThanOrEqualTo(Long value) {
+            addCriterion("content_id >=", value, "contentId");
+            return (Criteria) this;
+        }
+
+        public Criteria andContentIdLessThan(Long value) {
+            addCriterion("content_id <", value, "contentId");
+            return (Criteria) this;
+        }
+
+        public Criteria andContentIdLessThanOrEqualTo(Long value) {
+            addCriterion("content_id <=", value, "contentId");
+            return (Criteria) this;
+        }
+
+        public Criteria andContentIdIn(List<Long> values) {
+            addCriterion("content_id in", values, "contentId");
+            return (Criteria) this;
+        }
+
+        public Criteria andContentIdNotIn(List<Long> values) {
+            addCriterion("content_id not in", values, "contentId");
+            return (Criteria) this;
+        }
+
+        public Criteria andContentIdBetween(Long value1, Long value2) {
+            addCriterion("content_id between", value1, value2, "contentId");
+            return (Criteria) this;
+        }
+
+        public Criteria andContentIdNotBetween(Long value1, Long value2) {
+            addCriterion("content_id not between", value1, value2, "contentId");
+            return (Criteria) this;
+        }
+
+        public Criteria andMediaTypeIsNull() {
+            addCriterion("media_type is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andMediaTypeIsNotNull() {
+            addCriterion("media_type is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andMediaTypeEqualTo(String value) {
+            addCriterion("media_type =", value, "mediaType");
+            return (Criteria) this;
+        }
+
+        public Criteria andMediaTypeNotEqualTo(String value) {
+            addCriterion("media_type <>", value, "mediaType");
+            return (Criteria) this;
+        }
+
+        public Criteria andMediaTypeGreaterThan(String value) {
+            addCriterion("media_type >", value, "mediaType");
+            return (Criteria) this;
+        }
+
+        public Criteria andMediaTypeGreaterThanOrEqualTo(String value) {
+            addCriterion("media_type >=", value, "mediaType");
+            return (Criteria) this;
+        }
+
+        public Criteria andMediaTypeLessThan(String value) {
+            addCriterion("media_type <", value, "mediaType");
+            return (Criteria) this;
+        }
+
+        public Criteria andMediaTypeLessThanOrEqualTo(String value) {
+            addCriterion("media_type <=", value, "mediaType");
+            return (Criteria) this;
+        }
+
+        public Criteria andMediaTypeLike(String value) {
+            addCriterion("media_type like", value, "mediaType");
+            return (Criteria) this;
+        }
+
+        public Criteria andMediaTypeNotLike(String value) {
+            addCriterion("media_type not like", value, "mediaType");
+            return (Criteria) this;
+        }
+
+        public Criteria andMediaTypeIn(List<String> values) {
+            addCriterion("media_type in", values, "mediaType");
+            return (Criteria) this;
+        }
+
+        public Criteria andMediaTypeNotIn(List<String> values) {
+            addCriterion("media_type not in", values, "mediaType");
+            return (Criteria) this;
+        }
+
+        public Criteria andMediaTypeBetween(String value1, String value2) {
+            addCriterion("media_type between", value1, value2, "mediaType");
+            return (Criteria) this;
+        }
+
+        public Criteria andMediaTypeNotBetween(String value1, String value2) {
+            addCriterion("media_type not between", value1, value2, "mediaType");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceSiteIsNull() {
+            addCriterion("source_site is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceSiteIsNotNull() {
+            addCriterion("source_site is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceSiteEqualTo(String value) {
+            addCriterion("source_site =", value, "sourceSite");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceSiteNotEqualTo(String value) {
+            addCriterion("source_site <>", value, "sourceSite");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceSiteGreaterThan(String value) {
+            addCriterion("source_site >", value, "sourceSite");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceSiteGreaterThanOrEqualTo(String value) {
+            addCriterion("source_site >=", value, "sourceSite");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceSiteLessThan(String value) {
+            addCriterion("source_site <", value, "sourceSite");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceSiteLessThanOrEqualTo(String value) {
+            addCriterion("source_site <=", value, "sourceSite");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceSiteLike(String value) {
+            addCriterion("source_site like", value, "sourceSite");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceSiteNotLike(String value) {
+            addCriterion("source_site not like", value, "sourceSite");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceSiteIn(List<String> values) {
+            addCriterion("source_site in", values, "sourceSite");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceSiteNotIn(List<String> values) {
+            addCriterion("source_site not in", values, "sourceSite");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceSiteBetween(String value1, String value2) {
+            addCriterion("source_site between", value1, value2, "sourceSite");
+            return (Criteria) this;
+        }
+
+        public Criteria andSourceSiteNotBetween(String value1, String value2) {
+            addCriterion("source_site not between", value1, value2, "sourceSite");
+            return (Criteria) this;
+        }
+
+        public Criteria andUrlIsNull() {
+            addCriterion("url is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andUrlIsNotNull() {
+            addCriterion("url is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andUrlEqualTo(String value) {
+            addCriterion("url =", value, "url");
+            return (Criteria) this;
+        }
+
+        public Criteria andUrlNotEqualTo(String value) {
+            addCriterion("url <>", value, "url");
+            return (Criteria) this;
+        }
+
+        public Criteria andUrlGreaterThan(String value) {
+            addCriterion("url >", value, "url");
+            return (Criteria) this;
+        }
+
+        public Criteria andUrlGreaterThanOrEqualTo(String value) {
+            addCriterion("url >=", value, "url");
+            return (Criteria) this;
+        }
+
+        public Criteria andUrlLessThan(String value) {
+            addCriterion("url <", value, "url");
+            return (Criteria) this;
+        }
+
+        public Criteria andUrlLessThanOrEqualTo(String value) {
+            addCriterion("url <=", value, "url");
+            return (Criteria) this;
+        }
+
+        public Criteria andUrlLike(String value) {
+            addCriterion("url like", value, "url");
+            return (Criteria) this;
+        }
+
+        public Criteria andUrlNotLike(String value) {
+            addCriterion("url not like", value, "url");
+            return (Criteria) this;
+        }
+
+        public Criteria andUrlIn(List<String> values) {
+            addCriterion("url in", values, "url");
+            return (Criteria) this;
+        }
+
+        public Criteria andUrlNotIn(List<String> values) {
+            addCriterion("url not in", values, "url");
+            return (Criteria) this;
+        }
+
+        public Criteria andUrlBetween(String value1, String value2) {
+            addCriterion("url between", value1, value2, "url");
+            return (Criteria) this;
+        }
+
+        public Criteria andUrlNotBetween(String value1, String value2) {
+            addCriterion("url not between", value1, value2, "url");
+            return (Criteria) this;
+        }
+
+        public Criteria andTitleIsNull() {
+            addCriterion("title is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andTitleIsNotNull() {
+            addCriterion("title is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andTitleEqualTo(String value) {
+            addCriterion("title =", value, "title");
+            return (Criteria) this;
+        }
+
+        public Criteria andTitleNotEqualTo(String value) {
+            addCriterion("title <>", value, "title");
+            return (Criteria) this;
+        }
+
+        public Criteria andTitleGreaterThan(String value) {
+            addCriterion("title >", value, "title");
+            return (Criteria) this;
+        }
+
+        public Criteria andTitleGreaterThanOrEqualTo(String value) {
+            addCriterion("title >=", value, "title");
+            return (Criteria) this;
+        }
+
+        public Criteria andTitleLessThan(String value) {
+            addCriterion("title <", value, "title");
+            return (Criteria) this;
+        }
+
+        public Criteria andTitleLessThanOrEqualTo(String value) {
+            addCriterion("title <=", value, "title");
+            return (Criteria) this;
+        }
+
+        public Criteria andTitleLike(String value) {
+            addCriterion("title like", value, "title");
+            return (Criteria) this;
+        }
+
+        public Criteria andTitleNotLike(String value) {
+            addCriterion("title not like", value, "title");
+            return (Criteria) this;
+        }
+
+        public Criteria andTitleIn(List<String> values) {
+            addCriterion("title in", values, "title");
+            return (Criteria) this;
+        }
+
+        public Criteria andTitleNotIn(List<String> values) {
+            addCriterion("title not in", values, "title");
+            return (Criteria) this;
+        }
+
+        public Criteria andTitleBetween(String value1, String value2) {
+            addCriterion("title between", value1, value2, "title");
+            return (Criteria) this;
+        }
+
+        public Criteria andTitleNotBetween(String value1, String value2) {
+            addCriterion("title not between", value1, value2, "title");
+            return (Criteria) this;
+        }
+
+        public Criteria andDurationIsNull() {
+            addCriterion("duration is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andDurationIsNotNull() {
+            addCriterion("duration is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andDurationEqualTo(Integer value) {
+            addCriterion("duration =", value, "duration");
+            return (Criteria) this;
+        }
+
+        public Criteria andDurationNotEqualTo(Integer value) {
+            addCriterion("duration <>", value, "duration");
+            return (Criteria) this;
+        }
+
+        public Criteria andDurationGreaterThan(Integer value) {
+            addCriterion("duration >", value, "duration");
+            return (Criteria) this;
+        }
+
+        public Criteria andDurationGreaterThanOrEqualTo(Integer value) {
+            addCriterion("duration >=", value, "duration");
+            return (Criteria) this;
+        }
+
+        public Criteria andDurationLessThan(Integer value) {
+            addCriterion("duration <", value, "duration");
+            return (Criteria) this;
+        }
+
+        public Criteria andDurationLessThanOrEqualTo(Integer value) {
+            addCriterion("duration <=", value, "duration");
+            return (Criteria) this;
+        }
+
+        public Criteria andDurationIn(List<Integer> values) {
+            addCriterion("duration in", values, "duration");
+            return (Criteria) this;
+        }
+
+        public Criteria andDurationNotIn(List<Integer> values) {
+            addCriterion("duration not in", values, "duration");
+            return (Criteria) this;
+        }
+
+        public Criteria andDurationBetween(Integer value1, Integer value2) {
+            addCriterion("duration between", value1, value2, "duration");
+            return (Criteria) this;
+        }
+
+        public Criteria andDurationNotBetween(Integer value1, Integer value2) {
+            addCriterion("duration not between", value1, value2, "duration");
+            return (Criteria) this;
+        }
+
+        public Criteria andOssKeyIsNull() {
+            addCriterion("oss_key is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andOssKeyIsNotNull() {
+            addCriterion("oss_key is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andOssKeyEqualTo(String value) {
+            addCriterion("oss_key =", value, "ossKey");
+            return (Criteria) this;
+        }
+
+        public Criteria andOssKeyNotEqualTo(String value) {
+            addCriterion("oss_key <>", value, "ossKey");
+            return (Criteria) this;
+        }
+
+        public Criteria andOssKeyGreaterThan(String value) {
+            addCriterion("oss_key >", value, "ossKey");
+            return (Criteria) this;
+        }
+
+        public Criteria andOssKeyGreaterThanOrEqualTo(String value) {
+            addCriterion("oss_key >=", value, "ossKey");
+            return (Criteria) this;
+        }
+
+        public Criteria andOssKeyLessThan(String value) {
+            addCriterion("oss_key <", value, "ossKey");
+            return (Criteria) this;
+        }
+
+        public Criteria andOssKeyLessThanOrEqualTo(String value) {
+            addCriterion("oss_key <=", value, "ossKey");
+            return (Criteria) this;
+        }
+
+        public Criteria andOssKeyLike(String value) {
+            addCriterion("oss_key like", value, "ossKey");
+            return (Criteria) this;
+        }
+
+        public Criteria andOssKeyNotLike(String value) {
+            addCriterion("oss_key not like", value, "ossKey");
+            return (Criteria) this;
+        }
+
+        public Criteria andOssKeyIn(List<String> values) {
+            addCriterion("oss_key in", values, "ossKey");
+            return (Criteria) this;
+        }
+
+        public Criteria andOssKeyNotIn(List<String> values) {
+            addCriterion("oss_key not in", values, "ossKey");
+            return (Criteria) this;
+        }
+
+        public Criteria andOssKeyBetween(String value1, String value2) {
+            addCriterion("oss_key between", value1, value2, "ossKey");
+            return (Criteria) this;
+        }
+
+        public Criteria andOssKeyNotBetween(String value1, String value2) {
+            addCriterion("oss_key not between", value1, value2, "ossKey");
+            return (Criteria) this;
+        }
+
+        public Criteria andRelevanceScoreIsNull() {
+            addCriterion("relevance_score is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andRelevanceScoreIsNotNull() {
+            addCriterion("relevance_score is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andRelevanceScoreEqualTo(Double value) {
+            addCriterion("relevance_score =", value, "relevanceScore");
+            return (Criteria) this;
+        }
+
+        public Criteria andRelevanceScoreNotEqualTo(Double value) {
+            addCriterion("relevance_score <>", value, "relevanceScore");
+            return (Criteria) this;
+        }
+
+        public Criteria andRelevanceScoreGreaterThan(Double value) {
+            addCriterion("relevance_score >", value, "relevanceScore");
+            return (Criteria) this;
+        }
+
+        public Criteria andRelevanceScoreGreaterThanOrEqualTo(Double value) {
+            addCriterion("relevance_score >=", value, "relevanceScore");
+            return (Criteria) this;
+        }
+
+        public Criteria andRelevanceScoreLessThan(Double value) {
+            addCriterion("relevance_score <", value, "relevanceScore");
+            return (Criteria) this;
+        }
+
+        public Criteria andRelevanceScoreLessThanOrEqualTo(Double value) {
+            addCriterion("relevance_score <=", value, "relevanceScore");
+            return (Criteria) this;
+        }
+
+        public Criteria andRelevanceScoreIn(List<Double> values) {
+            addCriterion("relevance_score in", values, "relevanceScore");
+            return (Criteria) this;
+        }
+
+        public Criteria andRelevanceScoreNotIn(List<Double> values) {
+            addCriterion("relevance_score not in", values, "relevanceScore");
+            return (Criteria) this;
+        }
+
+        public Criteria andRelevanceScoreBetween(Double value1, Double value2) {
+            addCriterion("relevance_score between", value1, value2, "relevanceScore");
+            return (Criteria) this;
+        }
+
+        public Criteria andRelevanceScoreNotBetween(Double value1, Double value2) {
+            addCriterion("relevance_score not between", value1, value2, "relevanceScore");
+            return (Criteria) this;
+        }
+
+        public Criteria andStatusIsNull() {
+            addCriterion("`status` is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andStatusIsNotNull() {
+            addCriterion("`status` is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andStatusEqualTo(Integer value) {
+            addCriterion("`status` =", value, "status");
+            return (Criteria) this;
+        }
+
+        public Criteria andStatusNotEqualTo(Integer value) {
+            addCriterion("`status` <>", value, "status");
+            return (Criteria) this;
+        }
+
+        public Criteria andStatusGreaterThan(Integer value) {
+            addCriterion("`status` >", value, "status");
+            return (Criteria) this;
+        }
+
+        public Criteria andStatusGreaterThanOrEqualTo(Integer value) {
+            addCriterion("`status` >=", value, "status");
+            return (Criteria) this;
+        }
+
+        public Criteria andStatusLessThan(Integer value) {
+            addCriterion("`status` <", value, "status");
+            return (Criteria) this;
+        }
+
+        public Criteria andStatusLessThanOrEqualTo(Integer value) {
+            addCriterion("`status` <=", value, "status");
+            return (Criteria) this;
+        }
+
+        public Criteria andStatusIn(List<Integer> values) {
+            addCriterion("`status` in", values, "status");
+            return (Criteria) this;
+        }
+
+        public Criteria andStatusNotIn(List<Integer> values) {
+            addCriterion("`status` not in", values, "status");
+            return (Criteria) this;
+        }
+
+        public Criteria andStatusBetween(Integer value1, Integer value2) {
+            addCriterion("`status` between", value1, value2, "status");
+            return (Criteria) this;
+        }
+
+        public Criteria andStatusNotBetween(Integer value1, Integer value2) {
+            addCriterion("`status` not between", value1, value2, "status");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreateTimeIsNull() {
+            addCriterion("create_time is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreateTimeIsNotNull() {
+            addCriterion("create_time is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreateTimeEqualTo(Long value) {
+            addCriterion("create_time =", value, "createTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreateTimeNotEqualTo(Long value) {
+            addCriterion("create_time <>", value, "createTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreateTimeGreaterThan(Long value) {
+            addCriterion("create_time >", value, "createTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreateTimeGreaterThanOrEqualTo(Long value) {
+            addCriterion("create_time >=", value, "createTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreateTimeLessThan(Long value) {
+            addCriterion("create_time <", value, "createTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreateTimeLessThanOrEqualTo(Long value) {
+            addCriterion("create_time <=", value, "createTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreateTimeIn(List<Long> values) {
+            addCriterion("create_time in", values, "createTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreateTimeNotIn(List<Long> values) {
+            addCriterion("create_time not in", values, "createTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreateTimeBetween(Long value1, Long value2) {
+            addCriterion("create_time between", value1, value2, "createTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreateTimeNotBetween(Long value1, Long value2) {
+            addCriterion("create_time not between", value1, value2, "createTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andUpdateTimeIsNull() {
+            addCriterion("update_time is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andUpdateTimeIsNotNull() {
+            addCriterion("update_time is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andUpdateTimeEqualTo(Long value) {
+            addCriterion("update_time =", value, "updateTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andUpdateTimeNotEqualTo(Long value) {
+            addCriterion("update_time <>", value, "updateTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andUpdateTimeGreaterThan(Long value) {
+            addCriterion("update_time >", value, "updateTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andUpdateTimeGreaterThanOrEqualTo(Long value) {
+            addCriterion("update_time >=", value, "updateTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andUpdateTimeLessThan(Long value) {
+            addCriterion("update_time <", value, "updateTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andUpdateTimeLessThanOrEqualTo(Long value) {
+            addCriterion("update_time <=", value, "updateTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andUpdateTimeIn(List<Long> values) {
+            addCriterion("update_time in", values, "updateTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andUpdateTimeNotIn(List<Long> values) {
+            addCriterion("update_time not in", values, "updateTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andUpdateTimeBetween(Long value1, Long value2) {
+            addCriterion("update_time between", value1, value2, "updateTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andUpdateTimeNotBetween(Long value1, Long value2) {
+            addCriterion("update_time not between", value1, value2, "updateTime");
+            return (Criteria) this;
+        }
+    }
+
+    /**
+     * This class was generated by MyBatis Generator.
+     * This class corresponds to the database table spider_content_media
+     *
+     * @mbg.generated do_not_delete_during_merge Thu Oct 23 19:21:42 CST 2025
+     */
+    public static class Criteria extends GeneratedCriteria {
+
+        protected Criteria() {
+            super();
+        }
+    }
+
+    /**
+     * This class was generated by MyBatis Generator.
+     * This class corresponds to the database table spider_content_media
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    public static class Criterion {
+        private String condition;
+
+        private Object value;
+
+        private Object secondValue;
+
+        private boolean noValue;
+
+        private boolean singleValue;
+
+        private boolean betweenValue;
+
+        private boolean listValue;
+
+        private String typeHandler;
+
+        public String getCondition() {
+            return condition;
+        }
+
+        public Object getValue() {
+            return value;
+        }
+
+        public Object getSecondValue() {
+            return secondValue;
+        }
+
+        public boolean isNoValue() {
+            return noValue;
+        }
+
+        public boolean isSingleValue() {
+            return singleValue;
+        }
+
+        public boolean isBetweenValue() {
+            return betweenValue;
+        }
+
+        public boolean isListValue() {
+            return listValue;
+        }
+
+        public String getTypeHandler() {
+            return typeHandler;
+        }
+
+        protected Criterion(String condition) {
+            super();
+            this.condition = condition;
+            this.typeHandler = null;
+            this.noValue = true;
+        }
+
+        protected Criterion(String condition, Object value, String typeHandler) {
+            super();
+            this.condition = condition;
+            this.value = value;
+            this.typeHandler = typeHandler;
+            if (value instanceof List<?>) {
+                this.listValue = true;
+            } else {
+                this.singleValue = true;
+            }
+        }
+
+        protected Criterion(String condition, Object value) {
+            this(condition, value, null);
+        }
+
+        protected Criterion(String condition, Object value, Object secondValue, String typeHandler) {
+            super();
+            this.condition = condition;
+            this.value = value;
+            this.secondValue = secondValue;
+            this.typeHandler = typeHandler;
+            this.betweenValue = true;
+        }
+
+        protected Criterion(String condition, Object value, Object secondValue) {
+            this(condition, value, secondValue, null);
+        }
+    }
+}

+ 382 - 0
core/src/main/java/com/tzld/supply/model/po/supply/spider/ToolsAudioTransRecord.java

@@ -0,0 +1,382 @@
+package com.tzld.supply.model.po.supply.spider;
+
+/**
+ *
+ * This class was generated by MyBatis Generator.
+ * This class corresponds to the database table tools_audio_trans_record
+ */
+public class ToolsAudioTransRecord {
+    /**
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column tools_audio_trans_record.id
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    private Long id;
+
+    /**
+     * Database Column Remarks:
+     *   文件url
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column tools_audio_trans_record.file_url
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    private String fileUrl;
+
+    /**
+     * Database Column Remarks:
+     *   文件url md5值
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column tools_audio_trans_record.file_url_md5
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    private String fileUrlMd5;
+
+    /**
+     * Database Column Remarks:
+     *   任务ID(阿里云服务)
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column tools_audio_trans_record.task_id
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    private String taskId;
+
+    /**
+     * Database Column Remarks:
+     *   状态(0-待处理,1-执行中,2-成功,3-失败)
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column tools_audio_trans_record.status
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    private Integer status;
+
+    /**
+     * Database Column Remarks:
+     *   创建时间戳
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column tools_audio_trans_record.create_timestamp
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    private Long createTimestamp;
+
+    /**
+     * Database Column Remarks:
+     *   执行时间戳
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column tools_audio_trans_record.exe_timestamp
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    private Long exeTimestamp;
+
+    /**
+     * Database Column Remarks:
+     *   完成时间戳
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column tools_audio_trans_record.finish_timestamp
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    private Long finishTimestamp;
+
+    /**
+     * Database Column Remarks:
+     *   识别结果
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column tools_audio_trans_record.result
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    private String result;
+
+    /**
+     * Database Column Remarks:
+     *   返回的词信息
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column tools_audio_trans_record.words
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    private String words;
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column tools_audio_trans_record.id
+     *
+     * @return the value of tools_audio_trans_record.id
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    public Long getId() {
+        return id;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column tools_audio_trans_record.id
+     *
+     * @param id the value for tools_audio_trans_record.id
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column tools_audio_trans_record.file_url
+     *
+     * @return the value of tools_audio_trans_record.file_url
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    public String getFileUrl() {
+        return fileUrl;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column tools_audio_trans_record.file_url
+     *
+     * @param fileUrl the value for tools_audio_trans_record.file_url
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    public void setFileUrl(String fileUrl) {
+        this.fileUrl = fileUrl;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column tools_audio_trans_record.file_url_md5
+     *
+     * @return the value of tools_audio_trans_record.file_url_md5
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    public String getFileUrlMd5() {
+        return fileUrlMd5;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column tools_audio_trans_record.file_url_md5
+     *
+     * @param fileUrlMd5 the value for tools_audio_trans_record.file_url_md5
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    public void setFileUrlMd5(String fileUrlMd5) {
+        this.fileUrlMd5 = fileUrlMd5;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column tools_audio_trans_record.task_id
+     *
+     * @return the value of tools_audio_trans_record.task_id
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    public String getTaskId() {
+        return taskId;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column tools_audio_trans_record.task_id
+     *
+     * @param taskId the value for tools_audio_trans_record.task_id
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    public void setTaskId(String taskId) {
+        this.taskId = taskId;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column tools_audio_trans_record.status
+     *
+     * @return the value of tools_audio_trans_record.status
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    public Integer getStatus() {
+        return status;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column tools_audio_trans_record.status
+     *
+     * @param status the value for tools_audio_trans_record.status
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    public void setStatus(Integer status) {
+        this.status = status;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column tools_audio_trans_record.create_timestamp
+     *
+     * @return the value of tools_audio_trans_record.create_timestamp
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    public Long getCreateTimestamp() {
+        return createTimestamp;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column tools_audio_trans_record.create_timestamp
+     *
+     * @param createTimestamp the value for tools_audio_trans_record.create_timestamp
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    public void setCreateTimestamp(Long createTimestamp) {
+        this.createTimestamp = createTimestamp;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column tools_audio_trans_record.exe_timestamp
+     *
+     * @return the value of tools_audio_trans_record.exe_timestamp
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    public Long getExeTimestamp() {
+        return exeTimestamp;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column tools_audio_trans_record.exe_timestamp
+     *
+     * @param exeTimestamp the value for tools_audio_trans_record.exe_timestamp
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    public void setExeTimestamp(Long exeTimestamp) {
+        this.exeTimestamp = exeTimestamp;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column tools_audio_trans_record.finish_timestamp
+     *
+     * @return the value of tools_audio_trans_record.finish_timestamp
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    public Long getFinishTimestamp() {
+        return finishTimestamp;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column tools_audio_trans_record.finish_timestamp
+     *
+     * @param finishTimestamp the value for tools_audio_trans_record.finish_timestamp
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    public void setFinishTimestamp(Long finishTimestamp) {
+        this.finishTimestamp = finishTimestamp;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column tools_audio_trans_record.result
+     *
+     * @return the value of tools_audio_trans_record.result
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    public String getResult() {
+        return result;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column tools_audio_trans_record.result
+     *
+     * @param result the value for tools_audio_trans_record.result
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    public void setResult(String result) {
+        this.result = result;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column tools_audio_trans_record.words
+     *
+     * @return the value of tools_audio_trans_record.words
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    public String getWords() {
+        return words;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column tools_audio_trans_record.words
+     *
+     * @param words the value for tools_audio_trans_record.words
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    public void setWords(String words) {
+        this.words = words;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table tools_audio_trans_record
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(getClass().getSimpleName());
+        sb.append(" [");
+        sb.append("Hash = ").append(hashCode());
+        sb.append(", id=").append(id);
+        sb.append(", fileUrl=").append(fileUrl);
+        sb.append(", fileUrlMd5=").append(fileUrlMd5);
+        sb.append(", taskId=").append(taskId);
+        sb.append(", status=").append(status);
+        sb.append(", createTimestamp=").append(createTimestamp);
+        sb.append(", exeTimestamp=").append(exeTimestamp);
+        sb.append(", finishTimestamp=").append(finishTimestamp);
+        sb.append(", result=").append(result);
+        sb.append(", words=").append(words);
+        sb.append("]");
+        return sb.toString();
+    }
+}

+ 812 - 0
core/src/main/java/com/tzld/supply/model/po/supply/spider/ToolsAudioTransRecordExample.java

@@ -0,0 +1,812 @@
+package com.tzld.supply.model.po.supply.spider;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ToolsAudioTransRecordExample {
+    /**
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database table tools_audio_trans_record
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    protected String orderByClause;
+
+    /**
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database table tools_audio_trans_record
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    protected boolean distinct;
+
+    /**
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database table tools_audio_trans_record
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    protected List<Criteria> oredCriteria;
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table tools_audio_trans_record
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    public ToolsAudioTransRecordExample() {
+        oredCriteria = new ArrayList<Criteria>();
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table tools_audio_trans_record
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    public void setOrderByClause(String orderByClause) {
+        this.orderByClause = orderByClause;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table tools_audio_trans_record
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    public String getOrderByClause() {
+        return orderByClause;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table tools_audio_trans_record
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    public void setDistinct(boolean distinct) {
+        this.distinct = distinct;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table tools_audio_trans_record
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    public boolean isDistinct() {
+        return distinct;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table tools_audio_trans_record
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    public List<Criteria> getOredCriteria() {
+        return oredCriteria;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table tools_audio_trans_record
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    public void or(Criteria criteria) {
+        oredCriteria.add(criteria);
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table tools_audio_trans_record
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    public Criteria or() {
+        Criteria criteria = createCriteriaInternal();
+        oredCriteria.add(criteria);
+        return criteria;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table tools_audio_trans_record
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    public Criteria createCriteria() {
+        Criteria criteria = createCriteriaInternal();
+        if (oredCriteria.size() == 0) {
+            oredCriteria.add(criteria);
+        }
+        return criteria;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table tools_audio_trans_record
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    protected Criteria createCriteriaInternal() {
+        Criteria criteria = new Criteria();
+        return criteria;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table tools_audio_trans_record
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    public void clear() {
+        oredCriteria.clear();
+        orderByClause = null;
+        distinct = false;
+    }
+
+    /**
+     * This class was generated by MyBatis Generator.
+     * This class corresponds to the database table tools_audio_trans_record
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    protected abstract static class GeneratedCriteria {
+        protected List<Criterion> criteria;
+
+        protected GeneratedCriteria() {
+            super();
+            criteria = new ArrayList<Criterion>();
+        }
+
+        public boolean isValid() {
+            return criteria.size() > 0;
+        }
+
+        public List<Criterion> getAllCriteria() {
+            return criteria;
+        }
+
+        public List<Criterion> getCriteria() {
+            return criteria;
+        }
+
+        protected void addCriterion(String condition) {
+            if (condition == null) {
+                throw new RuntimeException("Value for condition cannot be null");
+            }
+            criteria.add(new Criterion(condition));
+        }
+
+        protected void addCriterion(String condition, Object value, String property) {
+            if (value == null) {
+                throw new RuntimeException("Value for " + property + " cannot be null");
+            }
+            criteria.add(new Criterion(condition, value));
+        }
+
+        protected void addCriterion(String condition, Object value1, Object value2, String property) {
+            if (value1 == null || value2 == null) {
+                throw new RuntimeException("Between values for " + property + " cannot be null");
+            }
+            criteria.add(new Criterion(condition, value1, value2));
+        }
+
+        public Criteria andIdIsNull() {
+            addCriterion("id is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andIdIsNotNull() {
+            addCriterion("id is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andIdEqualTo(Long value) {
+            addCriterion("id =", value, "id");
+            return (Criteria) this;
+        }
+
+        public Criteria andIdNotEqualTo(Long value) {
+            addCriterion("id <>", value, "id");
+            return (Criteria) this;
+        }
+
+        public Criteria andIdGreaterThan(Long value) {
+            addCriterion("id >", value, "id");
+            return (Criteria) this;
+        }
+
+        public Criteria andIdGreaterThanOrEqualTo(Long value) {
+            addCriterion("id >=", value, "id");
+            return (Criteria) this;
+        }
+
+        public Criteria andIdLessThan(Long value) {
+            addCriterion("id <", value, "id");
+            return (Criteria) this;
+        }
+
+        public Criteria andIdLessThanOrEqualTo(Long value) {
+            addCriterion("id <=", value, "id");
+            return (Criteria) this;
+        }
+
+        public Criteria andIdIn(List<Long> values) {
+            addCriterion("id in", values, "id");
+            return (Criteria) this;
+        }
+
+        public Criteria andIdNotIn(List<Long> values) {
+            addCriterion("id not in", values, "id");
+            return (Criteria) this;
+        }
+
+        public Criteria andIdBetween(Long value1, Long value2) {
+            addCriterion("id between", value1, value2, "id");
+            return (Criteria) this;
+        }
+
+        public Criteria andIdNotBetween(Long value1, Long value2) {
+            addCriterion("id not between", value1, value2, "id");
+            return (Criteria) this;
+        }
+
+        public Criteria andFileUrlIsNull() {
+            addCriterion("file_url is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andFileUrlIsNotNull() {
+            addCriterion("file_url is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andFileUrlEqualTo(String value) {
+            addCriterion("file_url =", value, "fileUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andFileUrlNotEqualTo(String value) {
+            addCriterion("file_url <>", value, "fileUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andFileUrlGreaterThan(String value) {
+            addCriterion("file_url >", value, "fileUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andFileUrlGreaterThanOrEqualTo(String value) {
+            addCriterion("file_url >=", value, "fileUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andFileUrlLessThan(String value) {
+            addCriterion("file_url <", value, "fileUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andFileUrlLessThanOrEqualTo(String value) {
+            addCriterion("file_url <=", value, "fileUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andFileUrlLike(String value) {
+            addCriterion("file_url like", value, "fileUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andFileUrlNotLike(String value) {
+            addCriterion("file_url not like", value, "fileUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andFileUrlIn(List<String> values) {
+            addCriterion("file_url in", values, "fileUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andFileUrlNotIn(List<String> values) {
+            addCriterion("file_url not in", values, "fileUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andFileUrlBetween(String value1, String value2) {
+            addCriterion("file_url between", value1, value2, "fileUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andFileUrlNotBetween(String value1, String value2) {
+            addCriterion("file_url not between", value1, value2, "fileUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andFileUrlMd5IsNull() {
+            addCriterion("file_url_md5 is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andFileUrlMd5IsNotNull() {
+            addCriterion("file_url_md5 is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andFileUrlMd5EqualTo(String value) {
+            addCriterion("file_url_md5 =", value, "fileUrlMd5");
+            return (Criteria) this;
+        }
+
+        public Criteria andFileUrlMd5NotEqualTo(String value) {
+            addCriterion("file_url_md5 <>", value, "fileUrlMd5");
+            return (Criteria) this;
+        }
+
+        public Criteria andFileUrlMd5GreaterThan(String value) {
+            addCriterion("file_url_md5 >", value, "fileUrlMd5");
+            return (Criteria) this;
+        }
+
+        public Criteria andFileUrlMd5GreaterThanOrEqualTo(String value) {
+            addCriterion("file_url_md5 >=", value, "fileUrlMd5");
+            return (Criteria) this;
+        }
+
+        public Criteria andFileUrlMd5LessThan(String value) {
+            addCriterion("file_url_md5 <", value, "fileUrlMd5");
+            return (Criteria) this;
+        }
+
+        public Criteria andFileUrlMd5LessThanOrEqualTo(String value) {
+            addCriterion("file_url_md5 <=", value, "fileUrlMd5");
+            return (Criteria) this;
+        }
+
+        public Criteria andFileUrlMd5Like(String value) {
+            addCriterion("file_url_md5 like", value, "fileUrlMd5");
+            return (Criteria) this;
+        }
+
+        public Criteria andFileUrlMd5NotLike(String value) {
+            addCriterion("file_url_md5 not like", value, "fileUrlMd5");
+            return (Criteria) this;
+        }
+
+        public Criteria andFileUrlMd5In(List<String> values) {
+            addCriterion("file_url_md5 in", values, "fileUrlMd5");
+            return (Criteria) this;
+        }
+
+        public Criteria andFileUrlMd5NotIn(List<String> values) {
+            addCriterion("file_url_md5 not in", values, "fileUrlMd5");
+            return (Criteria) this;
+        }
+
+        public Criteria andFileUrlMd5Between(String value1, String value2) {
+            addCriterion("file_url_md5 between", value1, value2, "fileUrlMd5");
+            return (Criteria) this;
+        }
+
+        public Criteria andFileUrlMd5NotBetween(String value1, String value2) {
+            addCriterion("file_url_md5 not between", value1, value2, "fileUrlMd5");
+            return (Criteria) this;
+        }
+
+        public Criteria andTaskIdIsNull() {
+            addCriterion("task_id is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andTaskIdIsNotNull() {
+            addCriterion("task_id is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andTaskIdEqualTo(String value) {
+            addCriterion("task_id =", value, "taskId");
+            return (Criteria) this;
+        }
+
+        public Criteria andTaskIdNotEqualTo(String value) {
+            addCriterion("task_id <>", value, "taskId");
+            return (Criteria) this;
+        }
+
+        public Criteria andTaskIdGreaterThan(String value) {
+            addCriterion("task_id >", value, "taskId");
+            return (Criteria) this;
+        }
+
+        public Criteria andTaskIdGreaterThanOrEqualTo(String value) {
+            addCriterion("task_id >=", value, "taskId");
+            return (Criteria) this;
+        }
+
+        public Criteria andTaskIdLessThan(String value) {
+            addCriterion("task_id <", value, "taskId");
+            return (Criteria) this;
+        }
+
+        public Criteria andTaskIdLessThanOrEqualTo(String value) {
+            addCriterion("task_id <=", value, "taskId");
+            return (Criteria) this;
+        }
+
+        public Criteria andTaskIdLike(String value) {
+            addCriterion("task_id like", value, "taskId");
+            return (Criteria) this;
+        }
+
+        public Criteria andTaskIdNotLike(String value) {
+            addCriterion("task_id not like", value, "taskId");
+            return (Criteria) this;
+        }
+
+        public Criteria andTaskIdIn(List<String> values) {
+            addCriterion("task_id in", values, "taskId");
+            return (Criteria) this;
+        }
+
+        public Criteria andTaskIdNotIn(List<String> values) {
+            addCriterion("task_id not in", values, "taskId");
+            return (Criteria) this;
+        }
+
+        public Criteria andTaskIdBetween(String value1, String value2) {
+            addCriterion("task_id between", value1, value2, "taskId");
+            return (Criteria) this;
+        }
+
+        public Criteria andTaskIdNotBetween(String value1, String value2) {
+            addCriterion("task_id not between", value1, value2, "taskId");
+            return (Criteria) this;
+        }
+
+        public Criteria andStatusIsNull() {
+            addCriterion("`status` is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andStatusIsNotNull() {
+            addCriterion("`status` is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andStatusEqualTo(Integer value) {
+            addCriterion("`status` =", value, "status");
+            return (Criteria) this;
+        }
+
+        public Criteria andStatusNotEqualTo(Integer value) {
+            addCriterion("`status` <>", value, "status");
+            return (Criteria) this;
+        }
+
+        public Criteria andStatusGreaterThan(Integer value) {
+            addCriterion("`status` >", value, "status");
+            return (Criteria) this;
+        }
+
+        public Criteria andStatusGreaterThanOrEqualTo(Integer value) {
+            addCriterion("`status` >=", value, "status");
+            return (Criteria) this;
+        }
+
+        public Criteria andStatusLessThan(Integer value) {
+            addCriterion("`status` <", value, "status");
+            return (Criteria) this;
+        }
+
+        public Criteria andStatusLessThanOrEqualTo(Integer value) {
+            addCriterion("`status` <=", value, "status");
+            return (Criteria) this;
+        }
+
+        public Criteria andStatusIn(List<Integer> values) {
+            addCriterion("`status` in", values, "status");
+            return (Criteria) this;
+        }
+
+        public Criteria andStatusNotIn(List<Integer> values) {
+            addCriterion("`status` not in", values, "status");
+            return (Criteria) this;
+        }
+
+        public Criteria andStatusBetween(Integer value1, Integer value2) {
+            addCriterion("`status` between", value1, value2, "status");
+            return (Criteria) this;
+        }
+
+        public Criteria andStatusNotBetween(Integer value1, Integer value2) {
+            addCriterion("`status` not between", value1, value2, "status");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreateTimestampIsNull() {
+            addCriterion("create_timestamp is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreateTimestampIsNotNull() {
+            addCriterion("create_timestamp is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreateTimestampEqualTo(Long value) {
+            addCriterion("create_timestamp =", value, "createTimestamp");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreateTimestampNotEqualTo(Long value) {
+            addCriterion("create_timestamp <>", value, "createTimestamp");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreateTimestampGreaterThan(Long value) {
+            addCriterion("create_timestamp >", value, "createTimestamp");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreateTimestampGreaterThanOrEqualTo(Long value) {
+            addCriterion("create_timestamp >=", value, "createTimestamp");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreateTimestampLessThan(Long value) {
+            addCriterion("create_timestamp <", value, "createTimestamp");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreateTimestampLessThanOrEqualTo(Long value) {
+            addCriterion("create_timestamp <=", value, "createTimestamp");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreateTimestampIn(List<Long> values) {
+            addCriterion("create_timestamp in", values, "createTimestamp");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreateTimestampNotIn(List<Long> values) {
+            addCriterion("create_timestamp not in", values, "createTimestamp");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreateTimestampBetween(Long value1, Long value2) {
+            addCriterion("create_timestamp between", value1, value2, "createTimestamp");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreateTimestampNotBetween(Long value1, Long value2) {
+            addCriterion("create_timestamp not between", value1, value2, "createTimestamp");
+            return (Criteria) this;
+        }
+
+        public Criteria andExeTimestampIsNull() {
+            addCriterion("exe_timestamp is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andExeTimestampIsNotNull() {
+            addCriterion("exe_timestamp is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andExeTimestampEqualTo(Long value) {
+            addCriterion("exe_timestamp =", value, "exeTimestamp");
+            return (Criteria) this;
+        }
+
+        public Criteria andExeTimestampNotEqualTo(Long value) {
+            addCriterion("exe_timestamp <>", value, "exeTimestamp");
+            return (Criteria) this;
+        }
+
+        public Criteria andExeTimestampGreaterThan(Long value) {
+            addCriterion("exe_timestamp >", value, "exeTimestamp");
+            return (Criteria) this;
+        }
+
+        public Criteria andExeTimestampGreaterThanOrEqualTo(Long value) {
+            addCriterion("exe_timestamp >=", value, "exeTimestamp");
+            return (Criteria) this;
+        }
+
+        public Criteria andExeTimestampLessThan(Long value) {
+            addCriterion("exe_timestamp <", value, "exeTimestamp");
+            return (Criteria) this;
+        }
+
+        public Criteria andExeTimestampLessThanOrEqualTo(Long value) {
+            addCriterion("exe_timestamp <=", value, "exeTimestamp");
+            return (Criteria) this;
+        }
+
+        public Criteria andExeTimestampIn(List<Long> values) {
+            addCriterion("exe_timestamp in", values, "exeTimestamp");
+            return (Criteria) this;
+        }
+
+        public Criteria andExeTimestampNotIn(List<Long> values) {
+            addCriterion("exe_timestamp not in", values, "exeTimestamp");
+            return (Criteria) this;
+        }
+
+        public Criteria andExeTimestampBetween(Long value1, Long value2) {
+            addCriterion("exe_timestamp between", value1, value2, "exeTimestamp");
+            return (Criteria) this;
+        }
+
+        public Criteria andExeTimestampNotBetween(Long value1, Long value2) {
+            addCriterion("exe_timestamp not between", value1, value2, "exeTimestamp");
+            return (Criteria) this;
+        }
+
+        public Criteria andFinishTimestampIsNull() {
+            addCriterion("finish_timestamp is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andFinishTimestampIsNotNull() {
+            addCriterion("finish_timestamp is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andFinishTimestampEqualTo(Long value) {
+            addCriterion("finish_timestamp =", value, "finishTimestamp");
+            return (Criteria) this;
+        }
+
+        public Criteria andFinishTimestampNotEqualTo(Long value) {
+            addCriterion("finish_timestamp <>", value, "finishTimestamp");
+            return (Criteria) this;
+        }
+
+        public Criteria andFinishTimestampGreaterThan(Long value) {
+            addCriterion("finish_timestamp >", value, "finishTimestamp");
+            return (Criteria) this;
+        }
+
+        public Criteria andFinishTimestampGreaterThanOrEqualTo(Long value) {
+            addCriterion("finish_timestamp >=", value, "finishTimestamp");
+            return (Criteria) this;
+        }
+
+        public Criteria andFinishTimestampLessThan(Long value) {
+            addCriterion("finish_timestamp <", value, "finishTimestamp");
+            return (Criteria) this;
+        }
+
+        public Criteria andFinishTimestampLessThanOrEqualTo(Long value) {
+            addCriterion("finish_timestamp <=", value, "finishTimestamp");
+            return (Criteria) this;
+        }
+
+        public Criteria andFinishTimestampIn(List<Long> values) {
+            addCriterion("finish_timestamp in", values, "finishTimestamp");
+            return (Criteria) this;
+        }
+
+        public Criteria andFinishTimestampNotIn(List<Long> values) {
+            addCriterion("finish_timestamp not in", values, "finishTimestamp");
+            return (Criteria) this;
+        }
+
+        public Criteria andFinishTimestampBetween(Long value1, Long value2) {
+            addCriterion("finish_timestamp between", value1, value2, "finishTimestamp");
+            return (Criteria) this;
+        }
+
+        public Criteria andFinishTimestampNotBetween(Long value1, Long value2) {
+            addCriterion("finish_timestamp not between", value1, value2, "finishTimestamp");
+            return (Criteria) this;
+        }
+    }
+
+    /**
+     * This class was generated by MyBatis Generator.
+     * This class corresponds to the database table tools_audio_trans_record
+     *
+     * @mbg.generated do_not_delete_during_merge Thu Oct 23 19:21:42 CST 2025
+     */
+    public static class Criteria extends GeneratedCriteria {
+
+        protected Criteria() {
+            super();
+        }
+    }
+
+    /**
+     * This class was generated by MyBatis Generator.
+     * This class corresponds to the database table tools_audio_trans_record
+     *
+     * @mbg.generated Thu Oct 23 19:21:42 CST 2025
+     */
+    public static class Criterion {
+        private String condition;
+
+        private Object value;
+
+        private Object secondValue;
+
+        private boolean noValue;
+
+        private boolean singleValue;
+
+        private boolean betweenValue;
+
+        private boolean listValue;
+
+        private String typeHandler;
+
+        public String getCondition() {
+            return condition;
+        }
+
+        public Object getValue() {
+            return value;
+        }
+
+        public Object getSecondValue() {
+            return secondValue;
+        }
+
+        public boolean isNoValue() {
+            return noValue;
+        }
+
+        public boolean isSingleValue() {
+            return singleValue;
+        }
+
+        public boolean isBetweenValue() {
+            return betweenValue;
+        }
+
+        public boolean isListValue() {
+            return listValue;
+        }
+
+        public String getTypeHandler() {
+            return typeHandler;
+        }
+
+        protected Criterion(String condition) {
+            super();
+            this.condition = condition;
+            this.typeHandler = null;
+            this.noValue = true;
+        }
+
+        protected Criterion(String condition, Object value, String typeHandler) {
+            super();
+            this.condition = condition;
+            this.value = value;
+            this.typeHandler = typeHandler;
+            if (value instanceof List<?>) {
+                this.listValue = true;
+            } else {
+                this.singleValue = true;
+            }
+        }
+
+        protected Criterion(String condition, Object value) {
+            this(condition, value, null);
+        }
+
+        protected Criterion(String condition, Object value, Object secondValue, String typeHandler) {
+            super();
+            this.condition = condition;
+            this.value = value;
+            this.secondValue = secondValue;
+            this.typeHandler = typeHandler;
+            this.betweenValue = true;
+        }
+
+        protected Criterion(String condition, Object value, Object secondValue) {
+            this(condition, value, secondValue, null);
+        }
+    }
+}

+ 25 - 0
core/src/main/java/com/tzld/supply/model/vo/FileInfo.java

@@ -0,0 +1,25 @@
+package com.tzld.supply.model.vo;
+
+import lombok.Data;
+
+/**
+ * @author sunxy
+ */
+@Data
+public class FileInfo {
+
+    /**
+     * 文件名
+     */
+    private String fileName;
+
+    /**
+     * 文件URL
+     */
+    private String fileUrl;
+
+    /**
+     * 文件Size,单位KB
+     */
+    private Double fileSize;
+}

+ 49 - 0
core/src/main/java/com/tzld/supply/model/vo/PolicyDetailVO.java

@@ -0,0 +1,49 @@
+package com.tzld.supply.model.vo;
+
+import java.util.List;
+
+public class PolicyDetailVO {
+	public List<PolicyVO> Statement;
+	public String Version;
+
+	public List<PolicyVO> getStatement() {
+		return Statement;
+	}
+
+	public void setStatement(List<PolicyVO> statement) {
+		Statement = statement;
+	}
+
+	public String getVersion() {
+		return Version;
+	}
+
+	public void setVersion(String version) {
+		Version = version;
+	}
+
+	public static class PolicyVO {
+		public List<String> Action;
+		public String Effect;
+		public List<String> Resource;
+		public List<String> getAction() {
+			return Action;
+		}
+		public void setAction(List<String> action) {
+			Action = action;
+		}
+		public String getEffect() {
+			return Effect;
+		}
+		public void setEffect(String effect) {
+			Effect = effect;
+		}
+		public List<String> getResource() {
+			return Resource;
+		}
+		public void setResource(List<String> resource) {
+			Resource = resource;
+		}
+		
+	}
+}

+ 13 - 0
core/src/main/java/com/tzld/supply/model/vo/SignatureVO.java

@@ -0,0 +1,13 @@
+package com.tzld.supply.model.vo;
+
+import lombok.Data;
+
+@Data
+public class SignatureVO {
+    private String accessId;
+    private String policy;
+    private String signature;
+    private String fileName;
+    private String host;
+    private String expire;
+}

+ 20 - 0
core/src/main/java/com/tzld/supply/model/vo/StsTokenVO.java

@@ -0,0 +1,20 @@
+package com.tzld.supply.model.vo;
+
+import lombok.Data;
+
+@Data
+public class StsTokenVO {
+    private String Expiration;
+    private String AccessKeyId;
+    private String AccessKeySecret;
+    private String SecurityToken;
+    private String RequestId;
+    private String FileName;
+    private String Host;
+    private String[] Hosts;
+    private String Bucket;
+    private String Region;
+    private Boolean Cname;
+    private Long serverTimestamp;
+
+}

+ 52 - 0
core/src/main/java/com/tzld/supply/service/ffmpeg/FFmpegService.java

@@ -0,0 +1,52 @@
+package com.tzld.supply.service.ffmpeg;
+
+import com.tzld.supply.model.param.FFmpeg.*;
+
+import java.util.List;
+
+public interface FFmpegService {
+
+    String getFFmpegApiBaseUrl();
+
+    String mergeBgVideo(MergeBgVideoParam param);
+
+    String removeBg(RemoveBgParam param);
+
+    String videoAddAssSubtitle(VideoAddAssSubtitleParam param);
+
+    List<String> videoExtraAudio(VideoUrlsParam param);
+
+    List<String> videoExtraText(VideoUrlsParam param);
+
+    String videoExtraText(String videoUrl);
+
+    List<String> videoExtraFrame(VideoUrlsParam param);
+
+    String mediaSimpleMerge(MediaSimpleMergeParam param);
+
+    String videoTexture(VideoTextureParam param);
+
+    String videoInfo(String videoUrl);
+
+    String videoConcat(VideoUrlsParam param);
+
+    String videoAddTail(VideoAddTailParam param);
+
+    String videoTranscoding(VideoTranscodingParam param);
+
+    String videoAddAudio(VideoAddAudioParam param);
+
+    String videoTimeExtraFrame(VideoTimeExtraFrameParam param);
+
+    String timeCutVideo(VideoTimeCutParam param);
+
+    String timeExtraAudio(VideoTimeCutParam param);
+
+    String command(CommandParam param);
+
+    String extraVideoSrt(ExtraVideoSrtParam param);
+
+    String audioVolume(AudioVolumeParam param);
+
+    String audioSplit(AudioSplitParam param);
+}

+ 1617 - 0
core/src/main/java/com/tzld/supply/service/ffmpeg/impl/FFmpegServiceImpl.java

@@ -0,0 +1,1617 @@
+package com.tzld.supply.service.ffmpeg.impl;
+
+import cn.hutool.core.collection.CollectionUtil;
+import com.alibaba.fastjson.JSON;
+import com.stuuudy.commons.external.filestorage.enums.EnumPublicBuckets;
+import com.tzld.supply.common.enums.ExceptionEnum;
+import com.tzld.supply.common.exception.CommonException;
+import com.tzld.supply.model.dto.ali.AliVoiceResultSentenceData;
+import com.tzld.supply.model.param.FFmpeg.*;
+import com.tzld.supply.service.ffmpeg.FFmpegService;
+import com.tzld.supply.service.image.ImageService;
+import com.tzld.supply.service.image.exception.DownloadImageError;
+import com.tzld.supply.service.tools.ToolsAudioTransService;
+import com.tzld.supply.util.*;
+import com.tzld.supply.util.ffmpeg.FFmpegUtil;
+import com.tzld.supply.util.http.HttpClientUtils;
+import com.tzld.supply.util.http.HttpResponseContent;
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.Call;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Response;
+import org.apache.http.Header;
+import org.apache.http.util.TextUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+
+import javax.imageio.ImageIO;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.*;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+@Slf4j
+@Service
+public class FFmpegServiceImpl implements FFmpegService {
+
+    @Autowired
+    ToolsAudioTransService toolsAudioTransService;
+    @Autowired
+    ImageService imageService;
+    
+    @Value("${ffmpeg.api.url:http://ffmpeg.aiddit.com}")
+    private String ffmpegApiUrl;
+
+    public static OkHttpClient client = new OkHttpClient().newBuilder()
+            .connectTimeout(15, TimeUnit.SECONDS)
+            .readTimeout(5, TimeUnit.MINUTES)
+            .writeTimeout(5, TimeUnit.MINUTES)
+            .retryOnConnectionFailure(true)
+            .build();
+
+    private void downloadFile(String url, String filePath) throws Exception {
+        for (int i = 0; i < 3; i++) {
+            try {
+                Request request = new Request.Builder()
+                        .url(url)
+                        .build();
+
+                Call call = client.newCall(request);
+                Response response = call.execute();
+                InputStream is = response.body().byteStream();
+
+                Path path = Paths.get(filePath);
+                Path parentDir = path.getParent();
+                if (!Files.exists(parentDir)) {
+                    Files.createDirectories(parentDir);
+                }
+                File file = new File(filePath);
+                FileOutputStream outputStream = new FileOutputStream(file);
+                byte[] buffer = new byte[4096];
+                int bytesRead;
+                while ((bytesRead = is.read(buffer)) != -1) {
+                    outputStream.write(buffer, 0, bytesRead);
+                }
+                is.close();
+                break;
+            } catch (Exception e) {
+                if (i == 2) {
+                    throw e;
+                } else {
+                    log.error("downloadFile error,{}", url, e);
+                }
+            }
+        }
+    }
+
+    @Override
+    public String getFFmpegApiBaseUrl() {
+        List<String> apiUrlList = new ArrayList<>();
+        for (String apiUrl : ffmpegApiUrl.split(",")) {
+            apiUrlList.add(apiUrl);
+        }
+        String result = apiUrlList.get(new Random().nextInt(apiUrlList.size()));
+        log.info("getFFmpegApiBaseUrl,result:{}", result);
+        return result;
+    }
+
+    @Override
+    public String mergeBgVideo(MergeBgVideoParam param) {
+        Set<String> allLocalFilePathList = new HashSet<>();
+        String mainVideoSuffix = ".mp4";
+        if (param.getMainVideo().contains(".mov")) {
+            mainVideoSuffix = ".mov";
+        }
+        String mainVideoFilePath = "/datalog/videoComposite/video/" + BaseUtils.getUUIDStr() + mainVideoSuffix;
+        String bgMediaFilePath = "/datalog/videoComposite/video/" + BaseUtils.getUUIDStr() + ".mp4";
+        if ("image".equals(param.getType())) {
+            bgMediaFilePath = "/datalog/videoComposite/video/" + BaseUtils.getUUIDStr() + ".png";
+        }
+        String adjustBgMediaFilePath = bgMediaFilePath;
+        String outputFilePath = "/datalog/videoComposite/video/" + BaseUtils.getUUIDStr() + ".mp4";
+        String ossKey = "ffmpeg/mergeBgVideo/" + BaseUtils.getUUIDStr() + ".mp4";
+
+        allLocalFilePathList.add(mainVideoFilePath);
+        allLocalFilePathList.add(bgMediaFilePath);
+        allLocalFilePathList.add(adjustBgMediaFilePath);
+        allLocalFilePathList.add(outputFilePath);
+        try {
+            downloadFile(CdnUtil.getOssHttpUrl(param.getMainVideo()), mainVideoFilePath);
+            downloadFile(CdnUtil.getOssHttpUrl(param.getBgMedia()), bgMediaFilePath);
+            // 调整背景视频/图片的尺寸、时长
+            adjustBgMediaFilePath = adjustBgMediaSizeAndDuration(mainVideoFilePath, bgMediaFilePath, param.getType(), allLocalFilePathList);
+            FFmpegUtil.mergeBgVideo(mainVideoFilePath, adjustBgMediaFilePath, outputFilePath);
+            // 上传到OSS
+            InputStream inputStream = new FileInputStream(outputFilePath);
+            AliOssFileTool.saveInPublic(EnumPublicBuckets.PUBBUCKET.getBucketName(), ossKey, inputStream, "video/mp4");
+        } catch (Exception e) {
+            ossKey = null;
+            log.error("putObject error,{}", outputFilePath, e);
+        } finally {
+            if (CollectionUtil.isNotEmpty(allLocalFilePathList)) {
+                for (String filePath : allLocalFilePathList) {
+                    try {
+                        File file = new File(filePath);
+                        file.delete();
+                    } catch (Exception e) {
+                        log.error("delete file error,", filePath);
+                    }
+                }
+            }
+        }
+        return CdnUtil.getOssHttpUrl(ossKey);
+    }
+
+    private String adjustBgMediaSizeAndDuration(String mainVideoFilePath, String bgMediaFilePath, String type,
+                                                Set<String> allLocalFilePathList) throws IOException {
+        // 背景视频/图片 按短边缩放再居中裁剪
+        FFmpegUtil.MediaInfo mainVideoMediaInfo = FFmpegUtil.getMediaInfo(mainVideoFilePath);
+        int width = mainVideoMediaInfo.getWidth();
+        int height = mainVideoMediaInfo.getHeight();
+        int duration = mainVideoMediaInfo.getDuration();
+        if ("image".equals(type)) {
+            BufferedImage bgImage = ImageIO.read(new File(bgMediaFilePath));
+            if (bgImage.getWidth() == width && bgImage.getHeight() == height) {
+                return bgMediaFilePath;
+            }
+            String resizeImageFilePath = "/datalog/videoComposite/video/" + BaseUtils.getUUIDStr() + ".png";
+            allLocalFilePathList.add(resizeImageFilePath);
+            BigDecimal widthRate = new BigDecimal(width).divide(new BigDecimal(bgImage.getWidth()), 2,
+                    RoundingMode.UP);
+            BigDecimal heightRate = new BigDecimal(height).divide(new BigDecimal(bgImage.getHeight()), 2,
+                    RoundingMode.UP);
+            if (widthRate.compareTo(heightRate) > 0) {
+                int resizeHeight = widthRate.multiply(new BigDecimal(bgImage.getHeight())).intValue();
+                BufferedImage resizeImage = resizeImage(bgImage, width, resizeHeight);
+                if (resizeHeight > height) {
+                    resizeImage = resizeImage.getSubimage(0, (resizeHeight - height) / 2, width, height);
+                }
+                ImageIO.write(resizeImage, "png", new File(resizeImageFilePath));
+            } else {
+                int resizeWidth = heightRate.multiply(new BigDecimal(bgImage.getWidth())).intValue();
+                BufferedImage resizeImage = resizeImage(bgImage, resizeWidth, height);
+                if (resizeWidth > width) {
+                    resizeImage = resizeImage.getSubimage((resizeWidth - width) / 2, 0, width, height);
+                }
+                ImageIO.write(resizeImage, "png", new File(resizeImageFilePath));
+            }
+            return resizeImageFilePath;
+        } else {
+            FFmpegUtil.MediaInfo bgVideoMediaInfo = FFmpegUtil.getMediaInfo(bgMediaFilePath);
+            String resizeBgVideoFilePath = "/datalog/videoComposite/video/" + BaseUtils.getUUIDStr() + ".mp4";
+            allLocalFilePathList.add(resizeBgVideoFilePath);
+            if (bgVideoMediaInfo.getWidth() == width && bgVideoMediaInfo.getHeight() == height) {
+                resizeBgVideoFilePath = bgMediaFilePath;
+            } else {
+                BigDecimal widthRate = new BigDecimal(width).divide(new BigDecimal(bgVideoMediaInfo.getWidth()), 2,
+                        RoundingMode.UP);
+                BigDecimal heightRate = new BigDecimal(height).divide(new BigDecimal(bgVideoMediaInfo.getHeight()), 2,
+                        RoundingMode.UP);
+                String clipParams;
+                if (widthRate.compareTo(heightRate) > 0) {
+                    int cropX = 0;
+                    int resizeHeight = widthRate.multiply(new BigDecimal(bgVideoMediaInfo.getHeight())).intValue();
+                    int cropY = (resizeHeight - height) / 2;
+                    clipParams = "scale=" + width + ":-1,crop=" + width + ":" + height + ":" + cropX + ":" + cropY;
+                } else {
+                    int resizeWidth = heightRate.multiply(new BigDecimal(bgVideoMediaInfo.getWidth())).intValue();
+                    int cropX = (resizeWidth - width) / 2;
+                    int cropY = 0;
+                    clipParams = "scale=-1:" + height + ",crop=" + width + ":" + height + ":" + cropX + ":" + cropY;
+                }
+                FFmpegUtil.clipVideo(bgMediaFilePath, clipParams, resizeBgVideoFilePath);
+            }
+            if (bgVideoMediaInfo.getDuration() < duration) {
+                // 循环播放
+                int count = duration / bgVideoMediaInfo.getDuration();
+                int mod = duration % bgVideoMediaInfo.getDuration();
+                int loop = count - 1;
+                if (mod > 0) {
+                    loop = loop + 1;
+                }
+                String loopBgVideoFilePath = "/datalog/videoComposite/video/" + BaseUtils.getUUIDStr() + ".mp4";
+                allLocalFilePathList.add(loopBgVideoFilePath);
+                FFmpegUtil.loopVideo(resizeBgVideoFilePath, loop, loopBgVideoFilePath);
+                FFmpegUtil.MediaInfo loopMediaInfo = FFmpegUtil.getMediaInfo(loopBgVideoFilePath);
+                if (loopMediaInfo.getDuration() > duration) {
+                    // 截断
+                    String cutBgVideoFilePath = "/datalog/videoComposite/video/" + BaseUtils.getUUIDStr() + ".mp4";
+                    allLocalFilePathList.add(cutBgVideoFilePath);
+                    String startTime = "00:00:00.000";
+                    String cutTime = covertFFmpegCutTime(duration);
+                    FFmpegUtil.cutVideo(loopBgVideoFilePath, startTime, cutTime, cutBgVideoFilePath);
+                    return cutBgVideoFilePath;
+                } else {
+                    return loopBgVideoFilePath;
+                }
+            } else if (bgVideoMediaInfo.getDuration() > duration) {
+                // 截断
+                String cutBgVideoFilePath = "/datalog/videoComposite/video/" + BaseUtils.getUUIDStr() + ".mp4";
+                allLocalFilePathList.add(cutBgVideoFilePath);
+                String startTime = "00:00:00.000";
+                String cutTime = covertFFmpegCutTime(duration);
+                FFmpegUtil.cutVideo(resizeBgVideoFilePath, startTime, cutTime, cutBgVideoFilePath);
+                return cutBgVideoFilePath;
+            } else {
+                return resizeBgVideoFilePath;
+            }
+        }
+    }
+
+    private String covertFFmpegCutTime(int millis) {
+        int second = millis / 1000;
+        String mod = millis % 1000 + "";
+        // 补0
+        if (mod.length() == 0) {
+            mod = "000";
+        } else if (mod.length() == 1) {
+            mod = "00" + mod;
+        } else if (mod.length() == 2) {
+            mod = "0" + mod;
+        }
+        return second + "." + mod;
+    }
+
+    private static BufferedImage resizeImage(BufferedImage originalImage, int width, int height) {
+        Image scaledImage = originalImage.getScaledInstance(width, height, Image.SCALE_SMOOTH);
+        BufferedImage resizedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
+
+        Graphics2D g2d = resizedImage.createGraphics();
+        g2d.drawImage(scaledImage, 0, 0, null);
+        g2d.dispose();
+
+        return resizedImage;
+    }
+
+    @Override
+    public String removeBg(RemoveBgParam param) {
+        String inputFilePath = "/datalog/videoComposite/video/" + BaseUtils.getUUIDStr() + ".mp4";
+        String outputFilePath = "/datalog/videoComposite/video/" + BaseUtils.getUUIDStr() + ".mov";
+        String ossKey = "ffmpeg/removeBg/" + BaseUtils.getUUIDStr() + ".mov";
+        String contentType = "video/mov";
+        if ("image".endsWith(param.getMediaType())) {
+            inputFilePath = "/datalog/videoComposite/video/" + BaseUtils.getUUIDStr() + ".png";
+            outputFilePath = "/datalog/videoComposite/video/" + BaseUtils.getUUIDStr() + ".png";
+            ossKey = "ffmpeg/removeBg/" + BaseUtils.getUUIDStr() + ".png";
+            contentType = "image/png";
+        }
+
+        String webmOutputFilePath = "";
+
+        try {
+            downloadFile(CdnUtil.getOssHttpUrl(param.getMediaUrl()), inputFilePath);
+            String colorAndSF = param.getColor();
+            if ("0xFFFFFF".equals(colorAndSF)) {
+                // 去除白幕,相似度和混合度需要特殊设置
+                colorAndSF = colorAndSF + ":0.01:0.01";
+            } else if ("0x00FF00".equals(colorAndSF)) {
+                colorAndSF = colorAndSF + ":0.2:0.1";
+            }
+            FFmpegUtil.removeBg(inputFilePath, colorAndSF, outputFilePath);
+            // 上传到OSS
+            InputStream inputStream = new FileInputStream(outputFilePath);
+            AliOssFileTool.saveInPublic(EnumPublicBuckets.PUBBUCKET.getBucketName(), ossKey, inputStream, contentType);
+
+            // 如果是mov视频,还需要多转一份webm格式的视频,才能在浏览器中显示
+            if (outputFilePath.contains(".mov")) {
+                webmOutputFilePath = outputFilePath.replace(".mov", ".webm");
+                FFmpegUtil.movToWebm(outputFilePath, webmOutputFilePath);
+                String webmOssKey = ossKey.replace(".mov", ".webm");
+                InputStream inputStreamWebm = new FileInputStream(webmOutputFilePath);
+                AliOssFileTool.saveInPublic(EnumPublicBuckets.PUBBUCKET.getBucketName(), webmOssKey, inputStreamWebm, "video/webm");
+            }
+        } catch (Exception e) {
+            ossKey = null;
+            log.error("putObject error,{}", outputFilePath, e);
+        } finally {
+            try {
+                File fileMain = new File(inputFilePath);
+                fileMain.delete();
+
+                File fileOut = new File(outputFilePath);
+                fileOut.delete();
+
+                if (StringUtils.hasText(webmOutputFilePath)) {
+                    File fileWebmOut = new File(webmOutputFilePath);
+                    fileWebmOut.delete();
+                }
+            } catch (Exception e) {
+                log.error("delete file error {}", outputFilePath);
+            }
+        }
+        return CdnUtil.getOssHttpUrl(ossKey);
+    }
+
+    @Override
+    public String videoAddAssSubtitle(VideoAddAssSubtitleParam param) {
+        String inputFilePath = "/datalog/videoComposite/video/" + BaseUtils.getUUIDStr() + ".mp4";
+        String outputFilePath = "/datalog/videoComposite/video/" + BaseUtils.getUUIDStr() + ".mp4";
+        String assSubtitleFilePath = "/datalog/videoComposite/subtitle/" + BaseUtils.getUUIDStr() + ".ass";
+        String ossKey = "ffmpeg/videoAddAssSubtitle/" + BaseUtils.getUUIDStr() + ".mp4";
+        try {
+            downloadFile(CdnUtil.getOssHttpUrl(param.getVideoUrl()), inputFilePath);
+            FileUtils.saveFileContent(assSubtitleFilePath, param.getAssSubtitle().getBytes());
+            String inputSubtitleAndStyle = "subtitles=" + assSubtitleFilePath;
+            FFmpegUtil.addSubtitle(inputFilePath, inputSubtitleAndStyle, outputFilePath);
+            // 上传到OSS
+            InputStream inputStream = new FileInputStream(outputFilePath);
+            AliOssFileTool.saveInPublic(EnumPublicBuckets.PUBBUCKET.getBucketName(), ossKey, inputStream, "video/mp4");
+        } catch (Exception e) {
+            ossKey = null;
+            log.error("videoAddAssSubtitle error,{}", outputFilePath, e);
+        } finally {
+            try {
+                File fileIn = new File(inputFilePath);
+                fileIn.delete();
+
+                File fileOut = new File(outputFilePath);
+                fileOut.delete();
+
+                File fileAss = new File(assSubtitleFilePath);
+                fileAss.delete();
+            } catch (Exception e) {
+                log.error("delete file error {}", outputFilePath);
+            }
+        }
+        return CdnUtil.getOssHttpUrl(ossKey);
+    }
+
+    @Override
+    public List<String> videoExtraAudio(VideoUrlsParam param) {
+        if (CollectionUtil.isEmpty(param.getVideoUrls())) {
+            return new ArrayList<>();
+        }
+
+        List<String> result = new ArrayList<>();
+        for (String videoUrl : param.getVideoUrls()) {
+            String outputPath = null;
+            try {
+                outputPath = FFmpegUtil.getVideoVoice(videoUrl,
+                        FFmpegUtil.pathCreate("/datalog/videoExtractAudio/voice/" + BaseUtils.getUUIDStr() + ".mp3"));
+
+                if (StringUtils.hasText(outputPath)) {
+                    String ossKey = "video/voice/" + BaseUtils.getUUIDStr() + ".mp3";
+                    AliOssFileTool.saveInPublic(EnumPublicBuckets.PUBBUCKET.getBucketName(), ossKey, Files.newInputStream(Paths.get(outputPath)), "audio/mp3");
+                    result.add(CdnUtil.getOssHttpUrl(ossKey));
+                }
+            } catch (Exception e) {
+                log.error("ffmpeg videoExtraAudio error, videoUrl = {}", videoUrl, e);
+            } finally {
+                try {
+                    if (StringUtils.hasText(outputPath)) {
+                        File file = new File(outputPath);
+                        boolean delete = file.delete();
+                    }
+                } catch (Exception e) {
+                    log.error("delete file error {}", outputPath);
+                }
+            }
+        }
+
+        return result;
+    }
+
+    @Override
+    public List<String> videoExtraText(VideoUrlsParam param) {
+        if (CollectionUtil.isEmpty(param.getVideoUrls())) {
+            return new ArrayList<>();
+        }
+
+        List<String> result = new ArrayList<>();
+
+        for (String videoUrl : param.getVideoUrls()) {
+            try {
+                result.add(videoExtraText(videoUrl));
+            } catch (Exception e) {
+                log.error("ffmpeg videoExtraText error, videoUrl = {}", videoUrl, e);
+            }
+        }
+
+        return result;
+    }
+
+    @Override
+    public String extraVideoSrt(ExtraVideoSrtParam param) {
+        // 1,提取音频
+        List<String> videoUrls = videoExtraAudio(new VideoUrlsParam().setVideoUrls(Collections.singletonList(param.getVideoUrl())));
+        if (CollectionUtil.isEmpty(videoUrls)) {
+            throw new RuntimeException("提取音频步骤失败");
+        }
+
+        // 2,录音文件识别
+        String audioUrl = videoUrls.get(0);
+        List<AliVoiceResultSentenceData> sentences = toolsAudioTransService.getAudioTransResults(audioUrl, false).getSentences();
+        if (CollectionUtil.isEmpty(sentences)) {
+            throw new RuntimeException("录音文件识别步骤失败");
+        }
+
+        // 3,提取srt
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < sentences.size(); i++) {
+            AliVoiceResultSentenceData sentenceData = sentences.get(i);
+            if (StringUtils.hasText(sentenceData.getText())) {
+                sb.append(i + 1).append("\n");
+                sb.append(TimelineUtils.convertMillisToStringTime(sentenceData.getBeginTime(), ",")).append(" --> ")
+                        .append(TimelineUtils.convertMillisToStringTime(sentenceData.getEndTime(), ",")).append("\n");
+                sb.append(sentenceData.getText()).append("\n");
+                sb.append("\n");
+            }
+        }
+        return sb.toString().trim();
+    }
+
+    @Override
+    public String audioVolume(AudioVolumeParam param) {
+        if (!StringUtils.hasText(param.getAudioUrl())) {
+            return "";
+        }
+
+        double volume = Optional.ofNullable(param.getVolume()).orElse(1d);
+
+        String url = param.getAudioUrl();
+        List<String> allLocalFilePathList = new ArrayList<>();
+        try {
+            String inputFilePath = FFmpegUtil.pathCreate("/datalog/audioVolume/audio/" + BaseUtils.getUUIDStr() + ".mp3");
+            downloadFile(url, inputFilePath);
+            allLocalFilePathList.add(inputFilePath);
+
+            // 音频降噪
+            String outputFilePath = FFmpegUtil.pathCreate("/datalog/audioVolume/audio/" + BaseUtils.getUUIDStr() + ".mp3");
+            allLocalFilePathList.add(outputFilePath);
+
+            String afParam = String.format("afftdn=nf=-20,volume=%s", volume);
+            FFmpegUtil.audioVolume(inputFilePath, outputFilePath, afParam);
+
+            String ossKey = String.format("audioVolume/%s.mp3", BaseUtils.getUUIDStr());
+            AliOssFileTool.saveInPublic(EnumPublicBuckets.PUBBUCKET.getBucketName(), ossKey, Files.newInputStream(Paths.get(outputFilePath)));
+            return CdnUtil.getOssHttpUrl(ossKey);
+        } catch (Exception e) {
+            log.error("ffmpeg audioVolume error, url = {}", url, e);
+        } finally {
+            for (String localFilePath : allLocalFilePathList) {
+                try {
+                    new File(localFilePath).delete();
+                } catch (Exception e) {
+                    log.error("delete file error, localFilePath = {}", localFilePath, e);
+                }
+            }
+        }
+        return "";
+    }
+
+    @Override
+    public String audioSplit(AudioSplitParam param) {
+        log.info("audioSplit param = {}", param);
+        if (!StringUtils.hasText(param.getAudioUrl()) || Objects.isNull(param.getSegmentTime()) || param.getSegmentTime() <= 0) {
+            return "";
+        }
+
+        List<String> allLocalFilePathList = new ArrayList<>();
+
+        try {
+            String inputFile = FFmpegUtil.pathCreate("/datalog/audioSplit/audio/" + BaseUtils.getUUIDStr() + ".mp3");
+            downloadFile(param.getAudioUrl(), inputFile);
+            allLocalFilePathList.add(inputFile);
+
+            String outputPath = "/datalog/audioSplit/split/" + BaseUtils.getUUIDStr();
+            String outputFilePath = FFmpegUtil.pathCreate(outputPath + "/" + BaseUtils.getUUIDStr() + "_%03d.mp3");
+            FFmpegUtil.audioSplit(inputFile, param.getSegmentTime(), outputFilePath);
+
+            List<String> files = FFmpegUtil.ls(outputPath);
+            if (CollectionUtil.isEmpty(files)) {
+                return "";
+            }
+            allLocalFilePathList.addAll(files);
+
+            // 按照文件名的后几位数字排序
+            files = files.stream().sorted((f1, f2) -> {
+                String filename1 = Paths.get(f1).getFileName().toString();
+                String filename2 = Paths.get(f2).getFileName().toString();
+
+                // 提取数字部分
+                int num1 = extractNumber(filename1);
+                int num2 = extractNumber(filename2);
+
+                // 如果都能提取到数字,按数字排序
+                if (num1 != Integer.MAX_VALUE && num2 != Integer.MAX_VALUE) {
+                    return Integer.compare(num1, num2);
+                }
+
+                // 否则按文件名自然排序
+                return filename1.compareTo(filename2);
+            }).collect(Collectors.toList());
+
+            List<String> ossUrls = new ArrayList<>(files.size());
+            for (String file : files) {
+                Path path = Paths.get(file);
+                String ossKey = "audio/split/" + path.getFileName().toString();
+                AliOssFileTool.saveInPublic(EnumPublicBuckets.PUBBUCKET.getBucketName(), ossKey, Files.newInputStream(path));
+                ossUrls.add(CdnUtil.getOssHttpUrl(ossKey));
+            }
+            if (ossUrls.size() != files.size()) {
+                return "";
+            }
+            return String.join(",", ossUrls);
+        } catch (Exception e) {
+            log.error("ffmpeg audioSplit error, url = {}", param.getAudioUrl(), e);
+        } finally {
+            for (String localFilePath : allLocalFilePathList) {
+                try {
+                    new File(localFilePath).delete();
+                } catch (Exception e) {
+                    log.error("delete file error, localFilePath = {}", localFilePath, e);
+                }
+            }
+        }
+
+        return "";
+    }
+
+    public String videoExtraText(String videoUrl) {
+        // 1,提取音频
+        List<String> videoUrls = videoExtraAudio(new VideoUrlsParam().setVideoUrls(Collections.singletonList(videoUrl)));
+        if (CollectionUtil.isEmpty(videoUrls)) {
+            throw new RuntimeException("提取音频步骤失败");
+        }
+
+        // 2,录音文件识别
+        String audioUrl = videoUrls.get(0);
+        List<AliVoiceResultSentenceData> sentences = toolsAudioTransService.getAudioTransResults(audioUrl, false).getSentences();
+        if (CollectionUtil.isEmpty(sentences)) {
+            throw new RuntimeException("录音文件识别步骤失败");
+        }
+
+        // 3,提取文字
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < sentences.size(); i++) {
+            AliVoiceResultSentenceData sentenceData = sentences.get(i);
+            if (StringUtils.hasText(sentenceData.getText())) {
+                sb.append(sentenceData.getText()).append("\n");
+            }
+        }
+        return sb.toString().trim();
+    }
+
+    @Override
+    public List<String> videoExtraFrame(VideoUrlsParam param) {
+        if (CollectionUtil.isEmpty(param.getVideoUrls())) {
+            return new ArrayList<>();
+        }
+
+        List<String> result = new ArrayList<>();
+
+        for (String videoUrl : param.getVideoUrls()) {
+            try {
+                result.addAll(videoExtraFrame(videoUrl));
+            } catch (Exception e) {
+                log.error("ffmpeg videoExtraFrame error, videoUrl = {}", videoUrl, e);
+            }
+        }
+
+        return result;
+    }
+
+    @Override
+    public String videoTimeExtraFrame(VideoTimeExtraFrameParam param) {
+        if (!StringUtils.hasText(param.getVideoUrl())
+                || !StringUtils.hasText(param.getTime())) {
+            return "";
+        }
+
+        String imageLocalPath = null;
+        try {
+            imageLocalPath = FFmpegUtil.getTimeImage(param.getVideoUrl(), param.getTime(),
+                    FFmpegUtil.pathCreate("/datalog/videoExtractKeyframe/image/" + BaseUtils.getUUIDStr() + ".jpg"), 0);
+            if (StringUtils.hasText(imageLocalPath)) {
+                String ossKey = "video/timeFrame/" + BaseUtils.getUUIDStr() + ".jpg";
+                AliOssFileTool.saveInPublic(EnumPublicBuckets.PUBBUCKET.getBucketName(), ossKey,
+                        Files.newInputStream(Paths.get(imageLocalPath)), "image/jpeg");
+                return CdnUtil.getOssHttpUrl(ossKey);
+            }
+        } catch (Exception e) {
+            log.error("ffmpeg videoTimeExtraFrame error, params = {}, ", JSON.toJSONString(param), e);
+        } finally {
+            if (StringUtils.hasText(imageLocalPath)) {
+                File file = new File(imageLocalPath);
+                file.delete();
+            }
+        }
+
+        return null;
+    }
+
+    @Override
+    public String timeCutVideo(VideoTimeCutParam param) {
+        if (!StringUtils.hasText(param.getVideoUrl())
+                || !StringUtils.hasText(param.getStartTime())
+                || !StringUtils.hasText(param.getEndTime())) {
+            return "";
+        }
+
+        String outputPath = "";
+        try {
+            outputPath = FFmpegUtil.pathCreate("/datalog/videoComposite/video/" + BaseUtils.getUUIDStr() + ".mp4");
+            FFmpegUtil.cutVideoTime(param.getVideoUrl(), param.getStartTime(), param.getEndTime(), outputPath);
+            String ossKey = "video/video/" + BaseUtils.getUUIDStr() + ".mp4";
+            AliOssFileTool.saveInPublic(EnumPublicBuckets.PUBBUCKET.getBucketName(), ossKey,
+                    Files.newInputStream(Paths.get(outputPath)), "video/mp4");
+            return CdnUtil.getOssHttpUrl(ossKey);
+        } catch (Exception e) {
+            log.error("ffmpeg timeCutVideo error, params = {}, ", JSON.toJSONString(param), e);
+        } finally {
+            if (StringUtils.hasText(outputPath)) {
+                File file = new File(outputPath);
+                file.delete();
+            }
+        }
+
+        return null;
+    }
+
+    @Override
+    public String timeExtraAudio(VideoTimeCutParam param) {
+        if (!StringUtils.hasText(param.getVideoUrl())
+                || !StringUtils.hasText(param.getStartTime())
+                || !StringUtils.hasText(param.getEndTime())) {
+            return "";
+        }
+
+        String outputPath = null;
+        String inputVideoPath = null;
+        try {
+            inputVideoPath = FFmpegUtil.pathCreate("/datalog/videoComposite/video/" + BaseUtils.getUUIDStr() + ".mp4");
+            FileUtils.saveFileContent(inputVideoPath, imageService.imageDownloadV2(param.getVideoUrl()));
+            outputPath = FFmpegUtil.getVideoVoice(inputVideoPath, param.getStartTime(), param.getEndTime(),
+                    FFmpegUtil.pathCreate("/datalog/videoExtractAudio/voice/" + BaseUtils.getUUIDStr() + ".mp3"));
+            if (StringUtils.hasText(outputPath)) {
+                String ossKey = "video/voice/" + BaseUtils.getUUIDStr() + ".mp3";
+                AliOssFileTool.saveInPublic(EnumPublicBuckets.PUBBUCKET.getBucketName(), ossKey, Files.newInputStream(Paths.get(outputPath)), "audio/mp3");
+                return CdnUtil.getOssHttpUrl(ossKey);
+            }
+        } catch (Exception e) {
+            log.error("ffmpeg timeExtraAudio error, params = {}", JSON.toJSONString(param), e);
+        } finally {
+            try {
+                if (StringUtils.hasText(outputPath)) {
+                    File file = new File(outputPath);
+                    boolean delete = file.delete();
+                }
+                if (StringUtils.hasText(inputVideoPath)) {
+                    File file = new File(inputVideoPath);
+                    boolean delete = file.delete();
+                }
+            } catch (Exception e) {
+                log.error("delete file error {}, {}", outputPath, inputVideoPath);
+            }
+        }
+
+        return "";
+    }
+
+    @Override
+    public String command(CommandParam param) {
+        String ffmpegCommand = param.getFfmpegCommand();
+        List<String> allLocalFilePathList = new ArrayList<>();
+
+        try {
+            String outputFile = FFmpegUtil.pathCreate("/datalog/ffmpeg/command/result/" + param.getOutputFile());
+            allLocalFilePathList.add(outputFile);
+            ffmpegCommand = ffmpegCommand.replace(param.getOutputFile(), outputFile);
+            ffmpegCommand = ffmpegCommand.replace("\n", " ").replace("\\", "").trim();
+            List<String> commands = splitCommands(ffmpegCommand);
+            FFmpegUtil.FFmpegCommandResult fFmpegCommandResult = FFmpegUtil.executeFFmpegCommand(commands);
+            if (!fFmpegCommandResult.isSuccessful()) {
+                throw new CommonException(ExceptionEnum.SYSTEM_ERROR.getCode(),
+                        "执行出错," + JSON.toJSONString(fFmpegCommandResult)
+                );
+            }
+
+            if ("video".equals(param.getOutputType())) {
+                String ossKey = "videoComposite/" + BaseUtils.getUUIDStr() + ".mp4";
+                AliOssFileTool.saveInPublic(EnumPublicBuckets.PUBBUCKET.getBucketName(), ossKey, Files.newInputStream(Paths.get(outputFile)), "video/mp4");
+                return CdnUtil.getOssHttpUrl(ossKey);
+            } else if ("audio".equals(param.getOutputType())) {
+                String ossKey = "videoComposite/" + BaseUtils.getUUIDStr() + ".mp3";
+                AliOssFileTool.saveInPublic(EnumPublicBuckets.PUBBUCKET.getBucketName(), ossKey, Files.newInputStream(Paths.get(outputFile)), "audio/mpeg");
+                return CdnUtil.getOssHttpUrl(ossKey);
+            } else if ("image".equals(param.getOutputType())) {
+                String ossKey = "videoComposite/" + BaseUtils.getUUIDStr() + ".jpg";
+                AliOssFileTool.saveInPublic(EnumPublicBuckets.PUBBUCKET.getBucketName(), ossKey, Files.newInputStream(Paths.get(outputFile)), "image/jpeg");
+                return CdnUtil.getOssHttpUrl(ossKey);
+            } else {
+                String ossKey = "videoComposite/" + BaseUtils.getUUIDStr() + ".txt";
+                AliOssFileTool.saveInPublic(EnumPublicBuckets.PUBBUCKET.getBucketName(), ossKey, Files.newInputStream(Paths.get(outputFile)), "text/plain");
+                return CdnUtil.getOssHttpUrl(ossKey);
+            }
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        } finally {
+            for (String s : allLocalFilePathList) {
+                File file = new File(s);
+                boolean delete = file.delete();
+            }
+        }
+
+
+    }
+
+    private List<String> splitCommands(String ffmpegCommand) {
+        List<String> parts = new ArrayList<>();
+        boolean inQuotes = false;
+        StringBuilder current = new StringBuilder();
+
+        for (char c : ffmpegCommand.toCharArray()) {
+            if (c == '"') {
+                inQuotes = !inQuotes;
+            } else if (c == ' ' && !inQuotes) {
+                if (current.length() > 0) {
+                    parts.add(current.toString());
+                    current.setLength(0);
+                }
+            } else {
+                current.append(c);
+            }
+        }
+
+        if (current.length() > 0) {
+            parts.add(current.toString());
+        }
+        return parts.stream().filter(StringUtils::hasText).collect(Collectors.toList());
+    }
+
+    @Override
+    public String mediaSimpleMerge(MediaSimpleMergeParam param) {
+        if (CollectionUtil.isEmpty(param.getVideoUrls())) {
+            return null;
+        }
+
+        try {
+            // 视频拼接
+            String videoUrl = videoTogether(param.getVideoUrls());
+
+            // 音频拼接
+            if (CollectionUtil.isEmpty(param.getAudioUrls())) {
+                return videoUrl;
+            }
+            String audioUrl = audioTogether(param.getAudioUrls());
+
+            // 音视频合并
+            if (TextUtils.isBlank(audioUrl)) {
+                return videoUrl;
+            }
+            String result = mediaMerge(videoUrl, audioUrl);
+
+            return result;
+        } catch (Exception e) {
+            log.error("ffmpeg mediaSimpleMerge error", e);
+            return null;
+        }
+    }
+
+    @Override
+    public String videoTexture(VideoTextureParam param) {
+        if (TextUtils.isBlank(param.getImageUrl()) || TextUtils.isBlank(param.getVideoUrl())) {
+            log.error("videoTexture missing necessary parameters, param = {}", JSON.toJSONString(param));
+            throw new CommonException(ExceptionEnum.PARAM_ERROR.getCode(), "videoTexture missing necessary " +
+                    "parameters, param:" + JSON.toJSONString(param));
+        }
+
+        List<String> allLocalFilePathList = new ArrayList<>();
+        long beginTime = System.currentTimeMillis();
+        try {
+            String videoPath = FFmpegUtil.pathCreate("/datalog/videoComposite/video/" + BaseUtils.getUUIDStr() + ".mp4");
+            FileUtils.saveFileContent(videoPath, imageService.imageDownloadV2(param.getVideoUrl()));
+            allLocalFilePathList.add(videoPath);
+            long time1 = System.currentTimeMillis();
+            log.info("videoTexture,time1:{},{}", (time1 - beginTime), param.getVideoUrl());
+            // 获取视频宽高
+            FFmpegUtil.MediaInfo videoMediaInfo = FFmpegUtil.getMediaInfo(videoPath);
+            long time2 = System.currentTimeMillis();
+            log.info("videoTexture,time2:{},{}", (time2 - time1), param.getVideoUrl());
+
+            String imagePath = FFmpegUtil.pathCreate("/datalog/videoComposite/image/" + BaseUtils.getUUIDStr() + ".png");
+            FileUtils.saveFileContent(imagePath, imageService.imageDownloadV2(param.getImageUrl()));
+            allLocalFilePathList.add(imagePath);
+            long time3 = System.currentTimeMillis();
+            log.info("videoTexture,time3:{},{}", (time3 - time2), param.getVideoUrl());
+
+            BufferedImage originImage = ImageIO.read(new File(imagePath));
+            boolean needCrop = false;
+            // 图片尺寸过大,需要裁剪
+            if (originImage.getWidth() > videoMediaInfo.getWidth().intValue()
+                    || originImage.getHeight() > videoMediaInfo.getHeight().intValue()) {
+                needCrop = true;
+            } else {
+                BigDecimal widthMultiply =
+                        new BigDecimal(originImage.getWidth()).divide(new BigDecimal(videoMediaInfo.getWidth()), 2, RoundingMode.HALF_UP);
+                BigDecimal heightMultiply =
+                        new BigDecimal(originImage.getHeight()).divide(new BigDecimal(videoMediaInfo.getHeight()), 2, RoundingMode.HALF_UP);
+                if (widthMultiply.compareTo(new BigDecimal("0.4")) > 0 || heightMultiply.compareTo(new BigDecimal("0.4")) > 0) {
+                    needCrop = true;
+                }
+            }
+            // 图片尺寸过大,需要裁剪
+            if (needCrop) {
+                originImage = ImageUtils.cropTransparentImageByShort(originImage, videoMediaInfo.getWidth(), videoMediaInfo.getHeight());
+                ImageIO.write(originImage, "PNG", new File(imagePath));
+                long time4 = System.currentTimeMillis();
+                log.info("videoTexture,time4:{},{}", (time4 - time3), param.getVideoUrl());
+            }
+
+            String outputPath = FFmpegUtil.pathCreate("/datalog/videoComposite/video/" + BaseUtils.getUUIDStr() + ".mp4");
+            allLocalFilePathList.add(outputPath);
+
+            // 贴图操作
+            FFmpegUtil.videoTexture(videoPath, imagePath,
+                    videoMediaInfo.getWidth() - (originImage.getWidth() + Optional.ofNullable(param.getBorder()).orElse(0)),
+                    videoMediaInfo.getHeight() - (originImage.getHeight() + Optional.ofNullable(param.getBorder()).orElse(0)),
+                    outputPath);
+            long time5 = System.currentTimeMillis();
+            log.info("videoTexture,time5:{},{}", (time5 - time3), param.getVideoUrl());
+            // 转存
+            String ossKey = "videoComposite/" + BaseUtils.getUUIDStr() + ".mp4";
+            AliOssFileTool.saveInPublic(EnumPublicBuckets.PUBBUCKET.getBucketName(), ossKey, Files.newInputStream(Paths.get(outputPath)), "video/mp4");
+            long time6 = System.currentTimeMillis();
+            log.info("videoTexture,time6:{},{}", (time6 - time5), param.getVideoUrl());
+            return CdnUtil.getOssHttpUrl(ossKey);
+        } catch (Exception e) {
+            log.error("videoTexture error, request param = {}", JSON.toJSONString(param), e);
+            throw new CommonException(ExceptionEnum.SYSTEM_ERROR.getCode(), "videoTexture error," + e.getMessage());
+        } finally {
+            if (CollectionUtil.isNotEmpty(allLocalFilePathList)) {
+                for (String filePath : allLocalFilePathList) {
+                    try {
+                        File file = new File(filePath);
+                        file.delete();
+                    } catch (Exception e) {
+                        log.error("delete file error,", filePath);
+                    }
+                }
+            }
+        }
+    }
+
+    @Override
+    public String videoInfo(String videoUrl) {
+        FFmpegUtil.MediaInfo mediaInfo = FFmpegUtil.getMediaInfo(videoUrl);
+        return JSON.toJSONString(mediaInfo);
+    }
+
+    private String mediaMerge(String videoUrl, String audioUrl) throws IOException, DownloadImageError {
+        if (TextUtils.isBlank(videoUrl) || TextUtils.isBlank(audioUrl)) {
+            return null;
+        }
+        List<String> allLocalFilePathList = new ArrayList<>();
+
+        String videoPath = FFmpegUtil.pathCreate("/datalog/videoComposite/video/" + BaseUtils.getUUIDStr() + ".mp4");
+        FileUtils.saveFileContent(videoPath, imageService.imageDownloadV2(videoUrl));
+        allLocalFilePathList.add(videoPath);
+        String audioPath = FFmpegUtil.pathCreate("/datalog/videoComposite/filelist/" + BaseUtils.getUUIDStr() + ".mp3");
+        FileUtils.saveFileContent(audioPath, imageService.imageDownloadV2(audioUrl));
+        allLocalFilePathList.add(audioPath);
+
+        String outputPath = FFmpegUtil.pathCreate("/datalog/videoComposite/video/" + BaseUtils.getUUIDStr() + ".mp4");
+        allLocalFilePathList.add(outputPath);
+        FFmpegUtil.addAudioSelfAdaption(videoPath, audioPath, outputPath);
+        String ossKey = "videoComposite/" + BaseUtils.getUUIDStr() + ".mp4";
+        AliOssFileTool.saveInPublic(EnumPublicBuckets.PUBBUCKET.getBucketName(), ossKey, Files.newInputStream(Paths.get(outputPath)), "video/mp4");
+        for (String s : allLocalFilePathList) {
+            File file = new File(s);
+            boolean delete = file.delete();
+        }
+
+        return CdnUtil.getOssHttpUrl(ossKey);
+    }
+
+    private String audioTogether(List<String> audioUrls) throws DownloadImageError, IOException {
+        List<String> allLocalFilePathList = new ArrayList<>();
+        StringBuilder concatContent = new StringBuilder();
+        for (String audioUrl : audioUrls) {
+            String audioPath = FFmpegUtil.pathCreate("/datalog/videoComposite/filelist/" + BaseUtils.getUUIDStr() + ".mp3");
+            FileUtils.saveFileContent(audioPath, imageService.imageDownloadV2(audioUrl));
+            allLocalFilePathList.add(audioPath);
+            concatContent.append("file '").append(audioPath).append("'").append("\n");
+        }
+        String concatFilePath = FFmpegUtil.pathCreate("/datalog/videoComposite/ace/" + BaseUtils.getUUIDStr() + ".txt");
+        FileUtils.saveFileContent(concatFilePath, concatContent.toString().trim().getBytes());
+        allLocalFilePathList.add(concatFilePath);
+        String concatOutputFilePath = FFmpegUtil.pathCreate("/datalog/videoComposite/ace/" + BaseUtils.getUUIDStr() + ".mp3");
+        FFmpegUtil.concatAudios(concatFilePath, concatOutputFilePath);
+        allLocalFilePathList.add(concatOutputFilePath);
+        String ossKey = "composeAceAudio/" + BaseUtils.getUUIDStr() + ".mp3";
+        AliOssFileTool.saveInPublic(EnumPublicBuckets.PUBBUCKET.getBucketName(), ossKey, Files.newInputStream(Paths.get(concatOutputFilePath)), "audio/mp3");
+        for (String s : allLocalFilePathList) {
+            File file = new File(s);
+            boolean delete = file.delete();
+        }
+
+        return CdnUtil.getOssHttpUrl(ossKey);
+    }
+
+    private String videoTogether(List<String> videoUrls) throws DownloadImageError, IOException {
+        List<String> allLocalFilePathList = new ArrayList<>();
+        // 视频片段拼接
+        StringBuilder concatContent = new StringBuilder();
+        for (String videoUrl : videoUrls) {
+            String videoPath = FFmpegUtil.pathCreate("/datalog/videoComposite/filelist/" + BaseUtils.getUUIDStr() + ".mp4");
+            FileUtils.saveFileContent(videoPath, imageService.imageDownloadV2(videoUrl));
+            concatContent.append("file '").append(videoPath).append("'").append("\n");
+            allLocalFilePathList.add(videoPath);
+        }
+        String concatFilePath = FFmpegUtil.pathCreate("/datalog/videoComposite/filelist/" + BaseUtils.getUUIDStr() + ".txt");
+        FileUtils.saveFileContent(concatFilePath, concatContent.toString().trim().getBytes());
+        allLocalFilePathList.add(concatFilePath);
+        String concatVideoPath = FFmpegUtil.pathCreate("/datalog/videoComposite/video/" + BaseUtils.getUUIDStr() + ".mp4");
+        FFmpegUtil.concatVideos(concatFilePath, concatVideoPath);
+        allLocalFilePathList.add(concatVideoPath);
+        String ossKey = "videoComposite/" + BaseUtils.getUUIDStr() + ".mp4";
+        AliOssFileTool.saveInPublic(EnumPublicBuckets.PUBBUCKET.getBucketName(), ossKey, Files.newInputStream(Paths.get(concatVideoPath)), "video/mp4");
+        for (String s : allLocalFilePathList) {
+            File file = new File(s);
+            boolean delete = file.delete();
+        }
+
+        return CdnUtil.getOssHttpUrl(ossKey);
+    }
+
+    public List<String> videoExtraFrame(String videoUrl) throws IOException {
+        // 1,提取音频
+        List<String> videoUrls = videoExtraAudio(new VideoUrlsParam().setVideoUrls(Collections.singletonList(videoUrl)));
+        if (CollectionUtil.isEmpty(videoUrls)) {
+            throw new RuntimeException("提取音频步骤失败");
+        }
+        // 2,录音文件识别
+        String audioUrl = videoUrls.get(0);
+        List<AliVoiceResultSentenceData> sentences = toolsAudioTransService.getAudioTransResults(audioUrl, false).getSentences();
+        if (CollectionUtil.isEmpty(sentences)) {
+            throw new RuntimeException("录音文件识别步骤失败");
+        }
+        // 3,提取关键帧
+        List<String> keyframeUrlList = new ArrayList<>();
+        for (AliVoiceResultSentenceData sentenceData : sentences) {
+            int keyTime = (sentenceData.getBeginTime() + sentenceData.getEndTime()) / 2;
+            String cutTime = getCutTime(keyTime);
+            String imageLocalPath = FFmpegUtil.getTimeImage(videoUrl, cutTime,
+                    FFmpegUtil.pathCreate("/datalog/videoExtractKeyframe/image/" + BaseUtils.getUUIDStr() + ".jpg"), 0);
+            if (StringUtils.hasText(imageLocalPath)) {
+                String ossKey = "video/keyFrame/" + BaseUtils.getUUIDStr() + ".jpg";
+                AliOssFileTool.saveInPublic(EnumPublicBuckets.PUBBUCKET.getBucketName(), ossKey,
+                        Files.newInputStream(Paths.get(imageLocalPath)), "image/jpeg");
+                keyframeUrlList.add(CdnUtil.getOssHttpUrl(ossKey));
+                File file = new File(imageLocalPath);
+                boolean delete = file.delete();
+            }
+        }
+
+        return keyframeUrlList;
+    }
+
+    @Override
+    public String videoConcat(VideoUrlsParam param) {
+        List<String> allLocalFilePathList = new ArrayList<>();
+        try {
+            String concatFileContent = buildConcatFileContentV2(param.getVideoUrls(), allLocalFilePathList, param.getPretreatmentSwitch());
+            String concatFilePath = FFmpegUtil.pathCreate("/datalog/videoComposite/filelist/" + BaseUtils.getUUIDStr() + ".txt");
+            FileUtils.saveFileContent(concatFilePath, concatFileContent.getBytes());
+            allLocalFilePathList.add(concatFilePath);
+
+            String outputFilePath = "/datalog/videoComposite/video/" + BaseUtils.getUUIDStr() + ".mp4";
+            allLocalFilePathList.add(outputFilePath);
+
+            FFmpegUtil.concatVideos(concatFilePath, outputFilePath);
+
+            // 上传到oss中
+            String ossKey = "videoComposite/" + BaseUtils.getUUIDStr() + ".mp4";
+            InputStream inputStream = new FileInputStream(outputFilePath);
+            AliOssFileTool.saveInPublic(EnumPublicBuckets.PUBBUCKET.getBucketName(), ossKey, inputStream, "video/mp4");
+            return CdnUtil.getOssHttpUrl(ossKey);
+        } catch (Exception e) {
+            log.error("videoConcat error,param:{}", JSON.toJSONString(param), e);
+            throw new CommonException(ExceptionEnum.SYSTEM_ERROR.getCode(), e.getMessage());
+        } finally {
+            if (CollectionUtil.isNotEmpty(allLocalFilePathList)) {
+                for (String filePath : allLocalFilePathList) {
+                    try {
+                        File file = new File(filePath);
+                        file.delete();
+                    } catch (Exception e) {
+                        log.error("delete file error,", filePath);
+                    }
+                }
+            }
+        }
+    }
+
+    @Deprecated
+    private String buildConcatFileContent(List<String> clipVideoUrls, List<String> allLocalFilePathList) throws Exception {
+        List<String> scaleVideoFilePathList = new ArrayList<>();
+        // 以第一个视频的宽高为准,其他视频宽高不一致,缩放裁剪处理
+        int finalWidth = 720;
+        int finalHeight = 1280;
+        for (int i = 0; i < clipVideoUrls.size(); i++) {
+            String clipVideoUrl = clipVideoUrls.get(i);
+            String clipVideoFilePath = FFmpegUtil.pathCreate("/datalog/videoComposite/video/" + BaseUtils.getUUIDStr() + ".mp4");
+            downloadFile(clipVideoUrl, clipVideoFilePath);
+            allLocalFilePathList.add(clipVideoFilePath);
+            FFmpegUtil.MediaInfo mediaInfo = FFmpegUtil.getMediaInfo(clipVideoFilePath);
+            if (i == 0) {
+                finalWidth = mediaInfo.getWidth();
+                finalHeight = mediaInfo.getHeight();
+                // 确保宽高可以被2整除
+                if (finalWidth % 2 != 0) {
+                    finalWidth = finalWidth + 1;
+                }
+                if (finalHeight % 2 != 0) {
+                    finalHeight = finalHeight + 1;
+                }
+            }
+            if (mediaInfo.getWidth() == finalWidth && mediaInfo.getHeight() == finalHeight) {
+                scaleVideoFilePathList.add(clipVideoFilePath);
+            } else {
+                int width = mediaInfo.getWidth();
+                int height = mediaInfo.getHeight();
+                double wRate =
+                        new BigDecimal(finalWidth).divide(new BigDecimal(width), 4, RoundingMode.HALF_UP).doubleValue();
+                double hRate =
+                        new BigDecimal(finalHeight).divide(new BigDecimal(height), 4, RoundingMode.HALF_UP).doubleValue();
+                String clipParams;
+                if (wRate >= hRate) {
+                    int cropX = 0;
+                    int targetHeight = (finalWidth * height) / width;
+                    if (targetHeight % 2 != 0) {
+                        targetHeight = targetHeight + 1;
+                    }
+                    int cropY = (targetHeight - finalHeight) / 2;
+                    if (cropY < 0) {
+                        cropY = 0;
+                    }
+                    clipParams =
+                            "scale=" + finalWidth + ":" + targetHeight + ",crop=" + finalWidth + ":" + finalHeight + ":" + cropX +
+                                    ":" + cropY;
+                } else {
+                    int targetWidth = (finalHeight * width) / height;
+                    if (targetWidth % 2 != 0) {
+                        targetWidth = targetWidth + 1;
+                    }
+                    int cropX = (targetWidth - finalWidth) / 2;
+                    if (cropX < 0) {
+                        cropX = 0;
+                    }
+                    int cropY = 0;
+                    clipParams =
+                            "scale=" + targetWidth + ":" + finalHeight + ",crop=" + finalWidth + ":" + finalHeight + ":" + cropX +
+                                    ":" + cropY;
+                }
+                String scaleClipVideoPath = FFmpegUtil.pathCreate("/datalog/videoComposite/video/" + BaseUtils.getUUIDStr() + ".mp4");
+//                System.out.println("clipVideoUrl:" + clipVideoUrl);
+                FFmpegUtil.clipVideo(clipVideoFilePath, clipParams, scaleClipVideoPath);
+                allLocalFilePathList.add(scaleClipVideoPath);
+
+                scaleVideoFilePathList.add(scaleClipVideoPath);
+            }
+        }
+        // 处理视频时间戳,避免拼接后时长不对问题
+        StringBuilder concatContent = new StringBuilder();
+        for (String scaleVideoFilePath : scaleVideoFilePathList) {
+            String timeScaleVideoPath = FFmpegUtil.pathCreate("/datalog/videoComposite/video/" + BaseUtils.getUUIDStr() + ".mp4");
+            FFmpegUtil.fixVideoTime(scaleVideoFilePath, timeScaleVideoPath);
+            allLocalFilePathList.add(timeScaleVideoPath);
+            concatContent.append("file '" + timeScaleVideoPath + "'").append("\n");
+        }
+        return concatContent.toString().trim();
+    }
+
+    private String buildConcatFileContentV2(List<String> clipVideoUrls, List<String> allLocalFilePathList, Integer pretreatmentSwitch) throws Exception {
+        List<String> scaleVideoFilePathList = new ArrayList<>();
+        // 以第一个视频的宽高为准,其他视频宽高不一致,缩放裁剪处理
+        int finalWidth = 720;
+        int finalHeight = 1280;
+        Integer finalAvgFrameRate = null;
+        Integer finalTimeBase = null;
+        for (int i = 0; i < clipVideoUrls.size(); i++) {
+            String clipVideoUrl = clipVideoUrls.get(i);
+            String clipVideoFilePath = FFmpegUtil.pathCreate("/datalog/videoComposite/video/" + BaseUtils.getUUIDStr() + ".mp4");
+            downloadFile(clipVideoUrl, clipVideoFilePath);
+            allLocalFilePathList.add(clipVideoFilePath);
+            FFmpegUtil.MediaInfo mediaInfo = FFmpegUtil.getMediaInfo(clipVideoFilePath);
+            if (i == 0) {
+                finalWidth = mediaInfo.getWidth();
+                finalHeight = mediaInfo.getHeight();
+                finalAvgFrameRate = mediaInfo.getAvgFrameRate();
+                finalTimeBase = mediaInfo.getTimeBase();
+            }
+            String clipParams = "scale={finalWidth}:{finalHeight}:force_original_aspect_ratio=decrease,pad={finalWidth}:{finalHeight}:(ow-iw)/2:(oh-ih)/2";
+            clipParams = clipParams.replace("{finalWidth}", finalWidth + "").replace("{finalHeight}", finalHeight + "");
+
+            String scaleClipVideoPath = FFmpegUtil.pathCreate("/datalog/videoComposite/video/" + BaseUtils.getUUIDStr() + ".mp4");
+
+            // 如果开启预处理,且第一个视频的帧率和时间基都有值,则进行预处理
+            if (Objects.equals(pretreatmentSwitch, 1)
+                    && Objects.nonNull(finalAvgFrameRate) && Objects.nonNull(finalTimeBase)) {
+                clipParams = clipParams + ",fps=" + finalAvgFrameRate;
+                FFmpegUtil.clipAndTranscodeVideo(clipVideoFilePath, clipParams, scaleClipVideoPath, finalAvgFrameRate, finalTimeBase);
+            } else {
+                FFmpegUtil.clipAndTranscodeVideo(clipVideoFilePath, clipParams, scaleClipVideoPath, null, null);
+            }
+
+            allLocalFilePathList.add(scaleClipVideoPath);
+
+            scaleVideoFilePathList.add(scaleClipVideoPath);
+
+        }
+        StringBuilder concatContent = new StringBuilder();
+        for (String scaleVideoFilePath : scaleVideoFilePathList) {
+            concatContent.append("file '" + scaleVideoFilePath + "'").append("\n");
+        }
+        return concatContent.toString().trim();
+    }
+
+    @Override
+    public String videoAddTail(VideoAddTailParam param) {
+        long beginTime = System.currentTimeMillis();
+        List<String> allLocalFilePathList = new ArrayList<>();
+        try {
+            String originVideoFilePath = FFmpegUtil.pathCreate("/datalog/videoComposite/video/" + BaseUtils.getUUIDStr() + ".mp4");
+            downloadFile(param.getOriginVideoUrl(), originVideoFilePath);
+            long time1 = System.currentTimeMillis();
+            log.info("videoAddTail,bizId:{},time1:{}", param.getBizId(), time1 - beginTime);
+            allLocalFilePathList.add(originVideoFilePath);
+            String volumeOriginVideoFilePath = originVideoFilePath;
+            if (Objects.nonNull(param.getOriginVideoVolume())) {
+                long timea = System.currentTimeMillis();
+                String lufsOriginVideoFilePath = FFmpegUtil.pathCreate("/datalog/videoComposite/video/" + BaseUtils.getUUIDStr() + ".mp4");
+                allLocalFilePathList.add(lufsOriginVideoFilePath);
+                FFmpegUtil.changeVideoVolumeByLoudnorm(originVideoFilePath, "-16", lufsOriginVideoFilePath);
+                long time2 = System.currentTimeMillis();
+                log.info("videoAddTail,bizId:{},time2:{}", param.getBizId(), time2 - timea);
+
+                String volumeMultiple = new BigDecimal(param.getOriginVideoVolume()).divide(new BigDecimal("50"), 2,
+                        RoundingMode.HALF_UP).toString();
+                if (new BigDecimal(volumeMultiple).subtract(new BigDecimal("1.00")).abs().compareTo(new BigDecimal("0.1")) > 0) {
+                    volumeOriginVideoFilePath = FFmpegUtil.pathCreate("/datalog/videoComposite/video/" + BaseUtils.getUUIDStr() + ".mp4");
+                    allLocalFilePathList.add(volumeOriginVideoFilePath);
+                    FFmpegUtil.changeVideoVolume(lufsOriginVideoFilePath, volumeMultiple, volumeOriginVideoFilePath);
+                } else {
+                    volumeOriginVideoFilePath = lufsOriginVideoFilePath;
+                }
+                long time3 = System.currentTimeMillis();
+                log.info("videoAddTail,bizId:{},time3:{}", param.getBizId(), time3 - time2);
+            }
+            FFmpegUtil.MediaInfo originVideoMediaInfo = FFmpegUtil.getMediaInfo(volumeOriginVideoFilePath);
+
+            List<String> concatFilePathList = new ArrayList<>();
+            concatFilePathList.add(volumeOriginVideoFilePath);
+            if ("video".equals(param.getTailType())) {
+                long timea = System.currentTimeMillis();
+                int finalWidth = originVideoMediaInfo.getWidth();
+                int finalHeight = originVideoMediaInfo.getHeight();
+
+                String tailVideoFilePath = FFmpegUtil.pathCreate("/datalog/videoComposite/video/" + BaseUtils.getUUIDStr() + ".mp4");
+                allLocalFilePathList.add(tailVideoFilePath);
+                downloadFile(param.getTailVideoUrl(), tailVideoFilePath);
+                String volumeTailVideoFilePath = tailVideoFilePath;
+                if (Objects.nonNull(param.getTailVolume())) {
+                    String lufsTailVideoFilePath = FFmpegUtil.pathCreate("/datalog/videoComposite/video/" + BaseUtils.getUUIDStr() + ".mp4");
+                    allLocalFilePathList.add(lufsTailVideoFilePath);
+                    FFmpegUtil.changeVideoVolumeByLoudnorm(tailVideoFilePath, "-16", lufsTailVideoFilePath);
+
+                    String volumeMultiple = new BigDecimal(param.getTailVolume()).divide(new BigDecimal("50"), 2,
+                            RoundingMode.HALF_UP).toString();
+                    if (new BigDecimal(volumeMultiple).subtract(new BigDecimal("1.00")).abs().compareTo(new BigDecimal("0.1")) > 0) {
+                        volumeTailVideoFilePath = FFmpegUtil.pathCreate("/datalog/videoComposite/video/" + BaseUtils.getUUIDStr() + ".mp4");
+                        allLocalFilePathList.add(volumeTailVideoFilePath);
+                        FFmpegUtil.changeVideoVolume(lufsTailVideoFilePath, volumeMultiple, volumeTailVideoFilePath);
+                    } else {
+                        volumeTailVideoFilePath = lufsTailVideoFilePath;
+                    }
+                }
+
+                FFmpegUtil.MediaInfo tailVideoMediaInfo = FFmpegUtil.getMediaInfo(volumeTailVideoFilePath);
+                if (tailVideoMediaInfo.getWidth() == finalWidth && tailVideoMediaInfo.getHeight() == finalHeight) {
+                    concatFilePathList.add(volumeTailVideoFilePath);
+                } else {
+                    String clipParams = buildScaleClipParams(finalWidth, finalHeight, tailVideoMediaInfo.getWidth(),
+                            tailVideoMediaInfo.getHeight());
+                    String scaleClipVideoPath = FFmpegUtil.pathCreate("/datalog/videoComposite/video/" + BaseUtils.getUUIDStr() + ".mp4");
+                    allLocalFilePathList.add(scaleClipVideoPath);
+                    FFmpegUtil.clipVideo(volumeTailVideoFilePath, clipParams, scaleClipVideoPath);
+                    concatFilePathList.add(scaleClipVideoPath);
+                }
+                long time4 = System.currentTimeMillis();
+                log.info("videoAddTail,bizId:{},time4:{}", param.getBizId(), time4 - timea);
+            } else if ("lastFrame".equals(param.getTailType())) {
+                long timea = System.currentTimeMillis();
+                // 最后一帧图片
+                String lastFrameFilePath = FFmpegUtil.pathCreate("/datalog/videoComposite/image/" + BaseUtils.getUUIDStr() + ".png");
+                allLocalFilePathList.add(lastFrameFilePath);
+                int lastTime = 1;
+                int durationSecond = originVideoMediaInfo.getDuration() / 1000;
+                for (int i = 0; i < 100; i++) {
+                    FFmpegUtil.getVideoLastFrame(volumeOriginVideoFilePath, lastTime, lastFrameFilePath);
+                    File lastFrameFile = new File(lastFrameFilePath);
+                    if (lastFrameFile.exists()) {
+                        break;
+                    }
+                    lastTime = lastTime + 3;
+                    if (lastTime >= durationSecond) {
+                        break;
+                    }
+                }
+                long time5 = System.currentTimeMillis();
+                log.info("videoAddTail,bizId:{},time5:{}", param.getBizId(), time5 - timea);
+                File lastFrameFile = new File(lastFrameFilePath);
+                if (!lastFrameFile.exists()) {
+                    throw new CommonException(ExceptionEnum.SYSTEM_ERROR.getCode(), "提取尾帧图片失败");
+                }
+
+                // 确定片尾视频时长
+                Integer audioMillisDuration = 0;
+                String tailAudioFilePath = FFmpegUtil.pathCreate("/datalog/videoComposite/audio/" + BaseUtils.getUUIDStr() + ".mp3");
+                String volumeTailAudioFilePath = tailAudioFilePath;
+                FFmpegUtil.MediaInfo tailAudioMediaInfo = null;
+                if (StringUtils.hasText(param.getTailAudioUrl())) {
+                    allLocalFilePathList.add(tailAudioFilePath);
+                    downloadFile(param.getTailAudioUrl(), tailAudioFilePath);
+
+                    if (Objects.nonNull(param.getTailVolume())) {
+                        String lufsTailAudioFilePath = FFmpegUtil.pathCreate("/datalog/videoComposite/audio/" + BaseUtils.getUUIDStr() + ".mp3");
+                        allLocalFilePathList.add(lufsTailAudioFilePath);
+                        FFmpegUtil.changeMediaVolumeByLoudnorm(tailAudioFilePath, "-16", lufsTailAudioFilePath);
+
+                        String volumeMultiple = new BigDecimal(param.getTailVolume()).divide(new BigDecimal("50"), 2,
+                                RoundingMode.HALF_UP).toString();
+                        if (new BigDecimal(volumeMultiple).subtract(new BigDecimal("1.00")).abs().compareTo(new BigDecimal("0.1")) > 0) {
+                            volumeTailAudioFilePath = FFmpegUtil.pathCreate("/datalog/videoComposite/audio/" + BaseUtils.getUUIDStr() + ".mp3");
+                            allLocalFilePathList.add(volumeTailAudioFilePath);
+                            FFmpegUtil.changeMediaVolume(lufsTailAudioFilePath, volumeMultiple, volumeTailAudioFilePath);
+                        } else {
+                            volumeTailAudioFilePath = lufsTailAudioFilePath;
+                        }
+                    }
+
+                    tailAudioMediaInfo = FFmpegUtil.getMediaInfo(volumeTailAudioFilePath);
+                    audioMillisDuration = tailAudioMediaInfo.getDuration();
+                }
+                long time6 = System.currentTimeMillis();
+                log.info("videoAddTail,bizId:{},time6:{}", param.getBizId(), time6 - time5);
+                Integer srtMillisDuration = param.getSrtMillisDuration();
+                if (Objects.isNull(srtMillisDuration)) {
+                    srtMillisDuration = 0;
+                }
+                Integer tailMillisDuration = Math.max(audioMillisDuration, srtMillisDuration);
+                if (tailMillisDuration == 0) {
+                    tailMillisDuration = 3000;
+                }
+
+                String tailSecondDuration = new BigDecimal(tailMillisDuration).divide(new BigDecimal(1000), 3,
+                        RoundingMode.HALF_UP).toString();
+                // 生成片尾视频
+                String tailVideoFilePath = FFmpegUtil.pathCreate("/datalog/videoComposite/video/" + BaseUtils.getUUIDStr() + ".mp4");
+                allLocalFilePathList.add(tailVideoFilePath);
+                FFmpegUtil.imageToVideo(lastFrameFilePath, originVideoMediaInfo.getWidth(),
+                        originVideoMediaInfo.getHeight(), tailSecondDuration, tailVideoFilePath);
+                long time7 = System.currentTimeMillis();
+                log.info("videoAddTail,bizId:{},time7:{}", param.getBizId(), time7 - time6);
+
+                // 片尾视频加音频
+                String lastTailVideoFilePath = tailVideoFilePath;
+                if (StringUtils.hasText(param.getTailAudioUrl())) {
+                    String addAudioFilePath = FFmpegUtil.pathCreate("/datalog/videoComposite/video/" + BaseUtils.getUUIDStr() + ".mp4");
+                    allLocalFilePathList.add(addAudioFilePath);
+                    FFmpegUtil.addAudio(tailVideoFilePath, volumeTailAudioFilePath, addAudioFilePath);
+                    lastTailVideoFilePath = addAudioFilePath;
+                }
+                long time8 = System.currentTimeMillis();
+                log.info("videoAddTail,bizId:{},time8:{}", param.getBizId(), time8 - time7);
+
+                // 片尾视频加字幕
+                if (StringUtils.hasText(param.getAssSubtitle())) {
+                    String assSubtitleFilePath = FFmpegUtil.pathCreate("/datalog/videoComposite/subtitle/" + BaseUtils.getUUIDStr() + ".ass");
+                    FileUtils.saveFileContent(assSubtitleFilePath, param.getAssSubtitle().getBytes());
+                    allLocalFilePathList.add(assSubtitleFilePath);
+                    String inputSubtitleAndStyle = "subtitles=" + assSubtitleFilePath;
+                    String addSubtitleFilePath = FFmpegUtil.pathCreate("/datalog/videoComposite/video/" + BaseUtils.getUUIDStr() + ".mp4");
+                    allLocalFilePathList.add(addSubtitleFilePath);
+                    FFmpegUtil.addSubtitle(lastTailVideoFilePath, inputSubtitleAndStyle, addSubtitleFilePath);
+                    lastTailVideoFilePath = addSubtitleFilePath;
+                }
+                concatFilePathList.add(lastTailVideoFilePath);
+                long time9 = System.currentTimeMillis();
+                log.info("videoAddTail,bizId:{},time9:{}", param.getBizId(), time9 - time8);
+            }
+
+            long timea = System.currentTimeMillis();
+            // 处理视频时间戳,避免拼接后时长不对问题
+            StringBuilder concatContent = new StringBuilder();
+            for (String concatFilePath : concatFilePathList) {
+                String timeFixVideoPath = FFmpegUtil.pathCreate("/datalog/videoComposite/video/" + BaseUtils.getUUIDStr() + ".mp4");
+                allLocalFilePathList.add(timeFixVideoPath);
+                FFmpegUtil.encodingEquivalence(concatFilePath, timeFixVideoPath);
+                concatContent.append("file '" + timeFixVideoPath + "'").append("\n");
+//                concatContent.append("file '" + concatFilePath + "'").append("\n");
+            }
+            long time10 = System.currentTimeMillis();
+            log.info("videoAddTail,bizId:{},time10:{}", param.getBizId(), time10 - timea);
+            String concatFileListPath = FFmpegUtil.pathCreate("/datalog/videoComposite/filelist/" + BaseUtils.getUUIDStr() + ".txt");
+            FileUtils.saveFileContent(concatFileListPath, concatContent.toString().trim().getBytes());
+            allLocalFilePathList.add(concatFileListPath);
+
+            String concatVideoFilePath = FFmpegUtil.pathCreate("/datalog/videoComposite/video/" + BaseUtils.getUUIDStr() + ".mp4");
+            allLocalFilePathList.add(concatVideoFilePath);
+            // 拼接视频
+            FFmpegUtil.concatVideos(concatFileListPath, concatVideoFilePath);
+            long time11 = System.currentTimeMillis();
+            log.info("videoAddTail,bizId:{},time11:{}", param.getBizId(), time11 - time10);
+
+            // 拼接完成后再进行一次编码
+//            String outputFilePath = FFmpegUtil.pathCreate("/datalog/videoComposite/video/" + BaseUtils.getUUIDStr() + ".mp4");
+//            FFmpegUtil.videoTranscoding(concatVideoFilePath, "libx264", outputFilePath);
+//            allLocalFilePathList.add(outputFilePath);
+
+            // 上传到oss中
+            String ossKey = "videoComposite/" + BaseUtils.getUUIDStr() + ".mp4";
+            InputStream inputStream = new FileInputStream(concatVideoFilePath);
+            AliOssFileTool.saveInPublic(EnumPublicBuckets.PUBBUCKET.getBucketName(), ossKey, inputStream, "video/mp4");
+            long time12 = System.currentTimeMillis();
+            log.info("videoAddTail,bizId:{},time12:{}", param.getBizId(), time12 - time11);
+            String result = CdnUtil.getOssHttpUrl(ossKey);
+            log.info("videoAddTail,bizId:{},result:{}", param.getBizId(), result);
+            return result;
+        } catch (Exception e) {
+            log.error("videoAddTail error,param:{}", JSON.toJSONString(param), e);
+            throw new CommonException(ExceptionEnum.SYSTEM_ERROR.getCode(), e.getMessage());
+        } finally {
+            if (CollectionUtil.isNotEmpty(allLocalFilePathList)) {
+                for (String filePath : allLocalFilePathList) {
+                    try {
+                        File file = new File(filePath);
+                        file.delete();
+                    } catch (Exception e) {
+                        log.error("delete file error,", filePath);
+                    }
+                }
+            }
+        }
+    }
+
+    private String buildScaleClipParams(int finalWidth, int finalHeight, int width, int height) {
+        double wRate =
+                new BigDecimal(finalWidth).divide(new BigDecimal(width), 4, RoundingMode.HALF_UP).doubleValue();
+        double hRate =
+                new BigDecimal(finalHeight).divide(new BigDecimal(height), 4, RoundingMode.HALF_UP).doubleValue();
+        String clipParams;
+        if (wRate >= hRate) {
+            int cropX = 0;
+            int targetHeight = (finalWidth * height) / width;
+            if (targetHeight % 2 != 0) {
+                targetHeight = targetHeight + 1;
+            }
+            int cropY = (targetHeight - finalHeight) / 2;
+            if (cropY < 0) {
+                cropY = 0;
+            }
+            clipParams =
+                    "scale=" + finalWidth + ":" + targetHeight + ",crop=" + finalWidth + ":" + finalHeight + ":" + cropX +
+                            ":" + cropY;
+        } else {
+            int targetWidth = (finalHeight * width) / height;
+            if (targetWidth % 2 != 0) {
+                targetWidth = targetWidth + 1;
+            }
+            int cropX = (targetWidth - finalWidth) / 2;
+            if (cropX < 0) {
+                cropX = 0;
+            }
+            int cropY = 0;
+            clipParams =
+                    "scale=" + targetWidth + ":" + finalHeight + ",crop=" + finalWidth + ":" + finalHeight + ":" + cropX +
+                            ":" + cropY;
+        }
+        return clipParams;
+    }
+
+    private String getCutTime(Integer keyFrameTime) {
+        Integer second = (keyFrameTime / 1000) % 60;
+        Integer minute = (keyFrameTime / (1000 * 60)) % 60;
+        Integer hour = (keyFrameTime / (1000 * 60 * 60));
+        return hour + ":" + (minute < 10 ? "0" + minute : minute) + ":" + (second < 10 ? "0" + second : second);
+    }
+
+    @Override
+    public String videoTranscoding(VideoTranscodingParam param) {
+        List<String> allLocalFilePathList = new ArrayList<>();
+        try {
+
+            String originVideoFilePath = FFmpegUtil.pathCreate("/datalog/videoComposite/video/" + BaseUtils.getUUIDStr() + ".mp4");
+            downloadFile(param.getVideoUrl(), originVideoFilePath);
+            allLocalFilePathList.add(originVideoFilePath);
+
+            String outputFilePath = FFmpegUtil.pathCreate("/datalog/videoComposite/video/" + BaseUtils.getUUIDStr() + ".mp4");
+            allLocalFilePathList.add(outputFilePath);
+
+            FFmpegUtil.videoTranscoding(originVideoFilePath, param.getCoding(), param.getBitrate(), outputFilePath);
+
+            // 上传到oss中
+            String ossKey = "videoComposite/" + BaseUtils.getUUIDStr() + ".mp4";
+            InputStream inputStream = new FileInputStream(outputFilePath);
+            AliOssFileTool.saveInPublic(EnumPublicBuckets.PUBBUCKET.getBucketName(), ossKey, inputStream, "video/mp4");
+            return CdnUtil.getOssHttpUrl(ossKey);
+        } catch (Exception e) {
+            log.error("videoTranscoding error,param:{}", JSON.toJSONString(param), e);
+            throw new CommonException(ExceptionEnum.SYSTEM_ERROR.getCode(), e.getMessage());
+        } finally {
+            if (CollectionUtil.isNotEmpty(allLocalFilePathList)) {
+                for (String filePath : allLocalFilePathList) {
+                    try {
+                        File file = new File(filePath);
+                        file.delete();
+                    } catch (Exception e) {
+                        log.error("delete file error,", filePath);
+                    }
+                }
+            }
+        }
+    }
+
+    @Override
+    public String videoAddAudio(VideoAddAudioParam param) {
+        List<String> allLocalFilePathList = new ArrayList<>();
+        try {
+            // 先判断下视频文件大小,文件太大可能会导致OOM
+            long videoSize = getVideoSize(param.getVideoUrl());
+            if (videoSize > 500 * 1024 * 1024) {
+                throw new CommonException(ExceptionEnum.SYSTEM_ERROR.getCode(), "视频文件太大,不能超过500M");
+            }
+            String videoFilePath = "/datalog/videoComposite/video/" + BaseUtils.getUUIDStr() + ".mp4";
+            downloadFile(param.getVideoUrl(), videoFilePath);
+            allLocalFilePathList.add(videoFilePath);
+            String volumeVideoFilePath = videoFilePath;
+            if (Objects.nonNull(param.getVideoVolume()) && Boolean.TRUE.equals(param.getKeepVideoOriginalSound())) {
+                String lufsVideoFilePath = "/datalog/videoComposite/video/" + BaseUtils.getUUIDStr() + ".mp4";
+                allLocalFilePathList.add(lufsVideoFilePath);
+                FFmpegUtil.changeVideoVolumeByLoudnorm(videoFilePath, "-16", lufsVideoFilePath);
+
+                String volumeMultiple = new BigDecimal(param.getVideoVolume()).divide(new BigDecimal("50"), 2,
+                        RoundingMode.HALF_UP).toString();
+                if (new BigDecimal(volumeMultiple).subtract(new BigDecimal("1.00")).abs().compareTo(new BigDecimal("0.1")) > 0) {
+                    volumeVideoFilePath = "/datalog/videoComposite/video/" + BaseUtils.getUUIDStr() + ".mp4";
+                    allLocalFilePathList.add(volumeVideoFilePath);
+                    FFmpegUtil.changeVideoVolume(lufsVideoFilePath, volumeMultiple, volumeVideoFilePath);
+                } else {
+                    volumeVideoFilePath = lufsVideoFilePath;
+                }
+            }
+
+            String audioFilePath = "/datalog/videoComposite/audio/" + BaseUtils.getUUIDStr() + ".mp3";
+            downloadFile(param.getAudioUrl(), audioFilePath);
+            allLocalFilePathList.add(audioFilePath);
+            String volumeAudioFilePath = audioFilePath;
+            if (Objects.nonNull(param.getAudioVolume())) {
+                String lufsAudioFilePath = "/datalog/videoComposite/audio/" + BaseUtils.getUUIDStr() + ".mp3";
+                allLocalFilePathList.add(lufsAudioFilePath);
+                FFmpegUtil.changeMediaVolumeByLoudnorm(audioFilePath, "-16", lufsAudioFilePath);
+
+                String volumeMultiple = new BigDecimal(param.getAudioVolume()).divide(new BigDecimal("50"), 2,
+                        RoundingMode.HALF_UP).toString();
+                if (new BigDecimal(volumeMultiple).subtract(new BigDecimal("1.00")).abs().compareTo(new BigDecimal("0.1")) > 0) {
+                    volumeAudioFilePath = "/datalog/videoComposite/audio/" + BaseUtils.getUUIDStr() + ".mp3";
+                    allLocalFilePathList.add(volumeAudioFilePath);
+                    FFmpegUtil.changeMediaVolume(lufsAudioFilePath, volumeMultiple, volumeAudioFilePath);
+                } else {
+                    volumeAudioFilePath = lufsAudioFilePath;
+                }
+            }
+
+            String outputFilePath = "/datalog/videoComposite/video/" + BaseUtils.getUUIDStr() + ".mp4";
+
+            if (StringUtils.hasText(param.getMainTimeline()) && "audio".equals(param.getMainTimeline())) {
+                FFmpegUtil.MediaInfo videoMediaInfo = FFmpegUtil.getMediaInfo(volumeVideoFilePath);
+                FFmpegUtil.MediaInfo audioMediaInfo = FFmpegUtil.getMediaInfo(volumeAudioFilePath);
+                int loopCount = audioMediaInfo.getDuration() / videoMediaInfo.getDuration() - 1;
+                if (loopCount >= 0) {
+                    int mod = audioMediaInfo.getDuration() % videoMediaInfo.getDuration();
+                    if (mod > 0) {
+                        loopCount++;
+                    }
+                }
+                if (loopCount < 0) {
+                    loopCount = 0;
+                }
+                if (Boolean.TRUE.equals(param.getKeepVideoOriginalSound())) {
+                    FFmpegUtil.videoAmixAudioLoopVideoAndShortest(volumeVideoFilePath, volumeAudioFilePath, outputFilePath, loopCount);
+                } else {
+                    FFmpegUtil.addAudioLoopVideoAndShortest(volumeVideoFilePath, volumeAudioFilePath, outputFilePath, loopCount);
+                }
+            } else if (StringUtils.hasText(param.getMainTimeline()) && "video".equals(param.getMainTimeline())) {
+                FFmpegUtil.MediaInfo videoMediaInfo = FFmpegUtil.getMediaInfo(volumeVideoFilePath);
+                FFmpegUtil.MediaInfo audioMediaInfo = FFmpegUtil.getMediaInfo(volumeAudioFilePath);
+                int loopCount = videoMediaInfo.getDuration() / audioMediaInfo.getDuration() - 1;
+                if (loopCount >= 0) {
+                    int mod = videoMediaInfo.getDuration() % audioMediaInfo.getDuration();
+                    if (mod > 0) {
+                        loopCount++;
+                    }
+                }
+                if (loopCount < 0) {
+                    loopCount = 0;
+                }
+                if (Boolean.TRUE.equals(param.getKeepVideoOriginalSound())) {
+                    FFmpegUtil.videoAmixAudioLoopAudioAndShortest(volumeVideoFilePath, volumeAudioFilePath, outputFilePath, loopCount);
+                } else {
+                    FFmpegUtil.addAudioLoopAudioAndShortest(volumeVideoFilePath, volumeAudioFilePath, outputFilePath, loopCount);
+                }
+            } else {
+                if (Boolean.TRUE.equals(param.getKeepVideoOriginalSound())) {
+                    FFmpegUtil.videoAmixAudio(volumeVideoFilePath, volumeAudioFilePath, outputFilePath);
+                } else {
+                    FFmpegUtil.addAudio(volumeVideoFilePath, volumeAudioFilePath, outputFilePath);
+                }
+            }
+            allLocalFilePathList.add(outputFilePath);
+            // 上传到oss中
+            String ossKey = "videoComposite/" + BaseUtils.getUUIDStr() + ".mp4";
+            InputStream inputStream = new FileInputStream(outputFilePath);
+            AliOssFileTool.saveInPublic(EnumPublicBuckets.PUBBUCKET.getBucketName(), ossKey, inputStream, "video/mp4");
+            return CdnUtil.getOssHttpUrl(ossKey);
+        } catch (Exception e) {
+            log.error("videoAddAudio error,param:{}", JSON.toJSONString(param), e);
+            throw new CommonException(ExceptionEnum.SYSTEM_ERROR.getCode(), e.getMessage());
+        } finally {
+            if (CollectionUtil.isNotEmpty(allLocalFilePathList)) {
+                for (String filePath : allLocalFilePathList) {
+                    try {
+                        File file = new File(filePath);
+                        file.delete();
+                    } catch (Exception e) {
+                        log.error("delete file error,", filePath);
+                    }
+                }
+            }
+        }
+    }
+
+    private long getVideoSize(String url) {
+        HttpResponseContent hrc = HttpClientUtils.head(url);
+        long videoSize = 0;
+        for (Header header : hrc.getHeaders()) {
+            if ("content-length".equals(header.getName().toLowerCase())) {
+                videoSize = Long.valueOf(header.getValue());
+                break;
+            }
+        }
+        return videoSize;
+    }
+
+    private int extractNumber(String filename) {
+        int lastUnderscore = filename.lastIndexOf('_');
+        if (lastUnderscore != -1 && lastUnderscore < filename.length() - 1) {
+            try {
+                String numberStr = filename.substring(lastUnderscore + 1);
+                return Integer.parseInt(numberStr);
+            } catch (NumberFormatException e) {
+                // 忽略解析错误
+            }
+        }
+        return Integer.MAX_VALUE;
+    }
+
+
+    public static void main(String[] args) throws Exception {
+        FFmpegServiceImpl impl = new FFmpegServiceImpl();
+        String url = "http://res.cybertogether.net/crawler/video/24d4055041c1625f31ac464428c4d324.mp4";
+        String mainVideoFilePath = "/datalog/videoComposite/video/111.mp4";
+        impl.downloadFile(url, mainVideoFilePath);
+        System.out.println("finish");
+    }
+}

+ 62 - 0
core/src/main/java/com/tzld/supply/service/image/ImageService.java

@@ -0,0 +1,62 @@
+package com.tzld.supply.service.image;
+
+import com.tzld.supply.service.image.dto.DiverseSplicingImageParam;
+import com.tzld.supply.service.image.dto.SimpleSplicingImageParam;
+import com.tzld.supply.service.image.exception.DownloadImageError;
+import com.tzld.supply.service.image.exception.SplicingImageError;
+import org.springframework.data.util.Pair;
+import org.springframework.lang.Nullable;
+
+import java.io.IOException;
+import java.util.List;
+
+public interface ImageService {
+    /**
+     * 为图片加蒙层
+     * @param imageUrl 图片链接
+     * @param alpha 蒙层透明度,例如0.01,越大越不透明
+     */
+    String imageLayer(String imageUrl, @Nullable Float alpha);
+    /**
+     * 为图片加蒙层
+     * @param imageUrl 图片链接
+     * @param alpha 蒙层透明度,例如0.01,越大越不透明
+     */
+    String imageLayer(String imageUrl, @Nullable String imageType, @Nullable Float alpha);
+
+    /**
+     * 下载图片
+     */
+    byte[] imageDownload(String imageUrl) throws DownloadImageError;
+
+    /**
+     *  下载图片
+     *
+     * @param imageUrl
+     * @return
+     * @throws DownloadImageError
+     */
+    byte[] imageDownloadV2(String imageUrl) throws DownloadImageError;
+
+    /**
+     * 多图片拼接
+     * 多少行、多少列
+     * 外层List定义行数,内层List定义每一行的列数
+     */
+    String imageSplicing(List<List<SimpleSplicingImageParam>> inputs) throws SplicingImageError;
+    /**
+     * 递归拼接图片
+     */
+    String imageSplicing(DiverseSplicingImageParam input) throws SplicingImageError;
+
+    /**
+     * 压缩图片
+     */
+    byte[] compress(String imageUrl, Integer targetHeight) throws DownloadImageError, IOException;
+
+    Pair<Integer, Integer> imageSize(String image) throws DownloadImageError, IOException;
+
+    boolean resourceIsAccessible(String url);
+
+    String transferAndStore(String imageUrl) throws DownloadImageError;;
+}

+ 53 - 0
core/src/main/java/com/tzld/supply/service/image/SplicingImageType.java

@@ -0,0 +1,53 @@
+package com.tzld.supply.service.image;
+
+public class SplicingImageType {
+    /**
+     * 横向
+     */
+    public static final SplicingImageType transverse = new SplicingImageType(1);
+    /**
+     * 纵向
+     */
+    public static final SplicingImageType vertical = new SplicingImageType(2);
+    /**
+     * 宫格
+     */
+    public static final SplicingImageType grid = new SplicingImageType(3);
+    private final Integer type;
+    private Integer rows;
+    private Integer columns;
+
+    public SplicingImageType(Integer type) {
+        this.type = type;
+    }
+
+    public SplicingImageType rows(Integer rows) {
+        this.rows = rows;
+        return this;
+    }
+
+    public SplicingImageType columns(Integer columns) {
+        this.columns = columns;
+        return this;
+    }
+
+    public boolean isTransverse() {
+        return type == 1;
+    }
+
+    public boolean isVertical() {
+        return type == 2;
+    }
+
+    public boolean isGrid() {
+        return type == 3;
+    }
+
+    public Integer getRows() {
+        return rows;
+    }
+
+    public Integer getColumns() {
+        return columns;
+    }
+}

+ 43 - 0
core/src/main/java/com/tzld/supply/service/image/dto/DiverseSplicingImageParam.java

@@ -0,0 +1,43 @@
+package com.tzld.supply.service.image.dto;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.experimental.Accessors;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+@Getter
+@Setter
+@Accessors(chain = true)
+public class DiverseSplicingImageParam {
+    @ApiModelProperty(value = "direction。row、column")
+    private String direction;
+    @ApiModelProperty(value = "链接")
+    private String img;
+    @ApiModelProperty(value = "尺寸")
+    private Size size;
+    @ApiModelProperty(value = "间距")
+    private Integer gap;
+    @ApiModelProperty(value = "裁切位置")
+    private Position position;
+    @ApiModelProperty(value = "children")
+    private List<DiverseSplicingImageParam> children;
+
+    @Getter
+    @Setter
+    public static class Size {
+        private Integer width;
+        private Integer height;
+    }
+
+    @Getter
+    @Setter
+    public static class Position {
+        private BigDecimal top;
+        private BigDecimal bottom;
+        private BigDecimal left;
+        private BigDecimal right;
+    }
+}

+ 23 - 0
core/src/main/java/com/tzld/supply/service/image/dto/SimpleSplicingImageParam.java

@@ -0,0 +1,23 @@
+package com.tzld.supply.service.image.dto;
+
+import lombok.Getter;
+import lombok.Setter;
+import lombok.experimental.Accessors;
+
+@Getter
+@Setter
+@Accessors(chain = true)
+public class SimpleSplicingImageParam {
+    /**
+     * 图片链接
+     */
+    private String url;
+    /**
+     * 自定义图片宽度,默认为原始宽度
+     */
+    private Integer width;
+    /**
+     * 自定义图片高度,默认为原始高度
+     */
+    private Integer height;
+}

+ 16 - 0
core/src/main/java/com/tzld/supply/service/image/dto/SplicingImageDTO.java

@@ -0,0 +1,16 @@
+package com.tzld.supply.service.image.dto;
+
+import lombok.Getter;
+import lombok.Setter;
+import lombok.experimental.Accessors;
+
+import java.awt.image.BufferedImage;
+
+@Getter
+@Setter
+@Accessors(chain = true)
+public class SplicingImageDTO {
+    private BufferedImage image;
+    private Integer width;
+    private Integer height;
+}

+ 8 - 0
core/src/main/java/com/tzld/supply/service/image/exception/DownloadImageError.java

@@ -0,0 +1,8 @@
+package com.tzld.supply.service.image.exception;
+
+public class DownloadImageError extends Exception{
+
+    public DownloadImageError(String message) {
+        super(message);
+    }
+}

+ 9 - 0
core/src/main/java/com/tzld/supply/service/image/exception/SplicingImageError.java

@@ -0,0 +1,9 @@
+package com.tzld.supply.service.image.exception;
+
+
+public class SplicingImageError extends Exception{
+
+    public SplicingImageError(String message) {
+        super(message);
+    }
+}

+ 421 - 0
core/src/main/java/com/tzld/supply/service/image/impl/ImageServiceImpl.java

@@ -0,0 +1,421 @@
+package com.tzld.supply.service.image.impl;
+
+import cn.hutool.core.collection.CollectionUtil;
+import com.stuuudy.commons.external.filestorage.enums.EnumPublicBuckets;
+import com.tzld.supply.exception.LayerException;
+import com.tzld.supply.service.image.ImageService;
+import com.tzld.supply.service.image.dto.DiverseSplicingImageParam;
+import com.tzld.supply.service.image.dto.SimpleSplicingImageParam;
+import com.tzld.supply.service.image.dto.SplicingImageDTO;
+import com.tzld.supply.service.image.exception.DownloadImageError;
+import com.tzld.supply.service.image.exception.SplicingImageError;
+import com.tzld.supply.util.*;
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Response;
+import org.apache.http.util.TextUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.util.Pair;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+
+import javax.imageio.ImageIO;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.TimeUnit;
+
+@Component
+@Slf4j
+public class ImageServiceImpl implements ImageService {
+    public static final String OSS_LAYER_KEY_PREFIX = "layer/";
+    public static final String OSS_SPLICING_KEY_PREFIX = "splicing/";
+
+    @Autowired
+    RedisUtils redisUtils;
+
+    private static final OkHttpClient okHttpClient = new OkHttpClient.Builder()
+            .connectTimeout(10, TimeUnit.SECONDS)
+            .readTimeout(60, TimeUnit.SECONDS)
+            .writeTimeout(15, TimeUnit.SECONDS)
+            .build();
+
+    @Override
+    public String imageLayer(String imageUrl, Float alpha) {
+        return imageLayer(imageUrl, null, alpha);
+    }
+
+    @Override
+    public String imageLayer(String imageUrl, String imageType, Float alpha) {
+        final String cacheKeyPrefix = "layer";
+        try {
+            // 检查是否有处理过的缓存
+            String url = redisUtils.get(cacheKeyPrefix + imageUrl);
+            if (StringUtils.hasText(url)) {
+                return url;
+            }
+
+            byte[] bytes = ImageUtils.layer(
+                    imageUrl,
+                    imageType,
+                    Optional.ofNullable(alpha).orElse(0.01f)
+            );
+            String key = OSS_LAYER_KEY_PREFIX + BaseUtils.getUUIDStr() + ".jpg";
+            AliOssFileTool.saveInPublic(EnumPublicBuckets.PUBBUCKET.getBucketName(), key, new ByteArrayInputStream(bytes));
+            url = CdnUtil.getOssHttpUrl(key);
+            redisUtils.set(cacheKeyPrefix + imageUrl, url, 60 * 60 * 10);
+            return url;
+        } catch (LayerException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public byte[] imageDownload(String imageUrl) throws DownloadImageError {
+        try {
+            OkHttpClient client = new OkHttpClient();
+            Request request = new Request.Builder()
+                    .url(CdnUtil.getOssHttpUrl(imageUrl))
+                    .build();
+            try (Response response = client.newCall(request).execute()) {
+                if (response.isSuccessful()) {
+                    return response.body().bytes();
+                }else {
+                    throw new DownloadImageError("图片下载失败,code" + response.code());
+                }
+            }
+        } catch (Exception e) {
+            throw new DownloadImageError(e.getMessage());
+        }
+    }
+
+    @Override
+    public byte[] imageDownloadV2(String imageUrl) throws DownloadImageError {
+        try {
+            Request request = new Request.Builder()
+                    .url(CdnUtil.getOssHttpUrl(imageUrl))
+                    .build();
+            try (Response response = okHttpClient.newCall(request).execute()) {
+                if (response.isSuccessful()) {
+                    return response.body().bytes();
+                }else {
+                    throw new DownloadImageError("图片下载失败,code" + response.code());
+                }
+            }
+        } catch (Exception e) {
+            throw new DownloadImageError(e.getMessage());
+        }
+    }
+
+    @Override
+    public String imageSplicing(List<List<SimpleSplicingImageParam>> inputs) throws SplicingImageError {
+        if (CollectionUtil.isEmpty(inputs)) {
+            throw new SplicingImageError("未输入有效图片");
+        }
+        if (inputs.stream().anyMatch(CollectionUtil::isEmpty)) {
+            throw new SplicingImageError("输入有错误,有行不存在有效图片");
+        }
+
+        // 下载图片
+        List<List<SplicingImageDTO>> images = new ArrayList<>();
+        for (List<SimpleSplicingImageParam> input : inputs) {
+            List<SplicingImageDTO> objects = new ArrayList<>();
+            for (SimpleSplicingImageParam image : input) {
+                try {
+                    byte[] bytes = imageDownload(image.getUrl());
+                    BufferedImage bufferedImage = ImageIO.read(new ByteArrayInputStream(bytes));
+                    objects.add(
+                            new SplicingImageDTO()
+                                    .setImage(bufferedImage)
+                                    .setHeight(Optional.ofNullable(image.getHeight()).orElse(bufferedImage.getHeight()))
+                                    .setWidth(Optional.ofNullable(image.getWidth()).orElse(bufferedImage.getWidth()))
+                    );
+                } catch (DownloadImageError e) {
+                    throw new SplicingImageError("拼接失败,图片下载失败。" + e.getMessage());
+                } catch (IOException e) {
+                    throw new SplicingImageError("拼接失败,创建BufferedImage失败。" + e.getMessage());
+                }
+            }
+            images.add(objects);
+        }
+
+        // 开始拼接
+        byte[] bytes;
+        try {
+            // 计算画布的整体宽、高
+            int width = images
+                    .stream()
+                    .mapToInt(obj -> obj.stream().mapToInt(SplicingImageDTO::getWidth).sum())
+                    .max().getAsInt();
+            int height = 0;
+            int colNum = images
+                    .stream()
+                    .mapToInt(List::size)
+                    .max().getAsInt();
+            for (int i = 0; i < colNum; i++) {
+                int count = 0;
+                for (List<SplicingImageDTO> dtos : images) {
+                    if (dtos.size() > i) {
+                        Integer num = dtos.get(i).getHeight();
+                        count += num;
+                    }
+                }
+                if (count > height) {
+                    height = count;
+                }
+            }
+
+            // 开始画
+            BufferedImage newImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
+            Graphics2D g2d = newImage.createGraphics();
+            int yStart = 0;
+            for (List<SplicingImageDTO> dtos : images) {
+                int x = 0;
+                for (SplicingImageDTO dto : dtos) {
+                    g2d.drawImage(dto.getImage(), x, yStart, dto.getWidth(), dto.getHeight(), null);
+                    x = x + dto.getWidth();
+                }
+                int maxHeight = dtos.stream().mapToInt(SplicingImageDTO::getHeight).max().getAsInt();
+                yStart = yStart + maxHeight;
+            }
+            g2d.dispose();
+            ByteArrayOutputStream os = new ByteArrayOutputStream();
+            ImageIO.write(newImage, "PNG", os);
+            bytes = os.toByteArray();
+        }catch (Exception e) {
+            throw new SplicingImageError("拼接失败。" + e.getMessage());
+        }
+
+        String key = OSS_SPLICING_KEY_PREFIX + BaseUtils.getUUIDStr() + ".jpg";
+        AliOssFileTool.saveInPublic(EnumPublicBuckets.PUBBUCKET.getBucketName(), key, new ByteArrayInputStream(bytes));
+        return CdnUtil.getOssHttpUrl(key);
+    }
+
+    @Override
+    public String imageSplicing(DiverseSplicingImageParam input) throws SplicingImageError {
+        if (Objects.isNull(input) || CollectionUtil.isEmpty(input.getChildren())) {
+            throw new SplicingImageError("未输入有效图片");
+        }
+        if (Objects.isNull(input.getSize()) || Objects.isNull(input.getSize().getWidth())) {
+            throw new SplicingImageError("未设置拼接后图像大小");
+        }
+
+        // 创建画布
+        BufferedImage newImage = new BufferedImage(input.getSize().getWidth(), input.getSize().getHeight(), BufferedImage.TYPE_INT_ARGB);
+        Graphics2D g2d = newImage.createGraphics();
+        // 设置背景色为白色
+        g2d.setColor(Color.white);
+        g2d.fillRect(0, 0, newImage.getWidth(), newImage.getHeight());
+        // 开始递归画
+        graph(g2d, input, Pair.of(0, 0));
+        g2d.dispose();
+        byte[] bytes;
+        try {
+            ByteArrayOutputStream os = new ByteArrayOutputStream();
+            ImageIO.write(newImage, "PNG", os);
+            bytes = os.toByteArray();
+        }catch (Exception e) {
+            throw new SplicingImageError("拼接失败。" + e.getMessage());
+        }
+        String key = OSS_SPLICING_KEY_PREFIX + BaseUtils.getUUIDStr() + ".jpg";
+        AliOssFileTool.saveInPublic(EnumPublicBuckets.PUBBUCKET.getBucketName(), key, new ByteArrayInputStream(bytes));
+        return CdnUtil.getOssHttpUrl(key);
+    }
+
+    @Override
+    public byte[] compress(String imageUrl, Integer targetHeight) throws DownloadImageError, IOException {
+        byte[] bytes = imageDownload(imageUrl);
+        BufferedImage bufferedImage = ImageIO.read(new ByteArrayInputStream(bytes));
+        int originalWidth = bufferedImage.getWidth();
+        int originalHeight = bufferedImage.getHeight();
+        if (originalHeight <= targetHeight) {
+            return bytes;
+        }
+
+        int newHeight = targetHeight;
+        int newWidth = (int) (originalWidth * (newHeight / (double) originalHeight));
+
+        BufferedImage outputImage = new BufferedImage(newWidth, newHeight, bufferedImage.getType());
+        Graphics2D g2d = outputImage.createGraphics();
+        g2d.drawImage(bufferedImage, 0, 0, newWidth, newHeight, null);
+        g2d.dispose();
+
+        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+        ImageIO.write(outputImage, "jpg", byteArrayOutputStream);
+        return byteArrayOutputStream.toByteArray();
+    }
+
+    @Override
+    public Pair<Integer, Integer> imageSize(String image) throws DownloadImageError, IOException {
+        byte[] bytes = this.imageDownload(image);
+        BufferedImage bufferedImage = ImageIO.read(new ByteArrayInputStream(bytes));
+        int width = bufferedImage.getWidth();
+        int height = bufferedImage.getHeight();
+        return Pair.of(width, height);
+    }
+
+    @Override
+    public boolean resourceIsAccessible(String url) {
+        OkHttpClient client = new OkHttpClient().newBuilder()
+                .connectTimeout(20, TimeUnit.SECONDS)
+                .readTimeout(40, TimeUnit.SECONDS)
+                .writeTimeout(20, TimeUnit.SECONDS)
+                .build();
+        Request request = new Request.Builder()
+                .url(url)
+                .method("HEAD", null)
+                .build();
+        try {
+            Response response = client.newCall(request).execute();
+            if (response.isSuccessful()) {
+                return true;
+            }
+            return false;
+        } catch (Exception e) {
+            return false;
+        }
+    }
+
+    private Pair<Integer,Integer> graph(Graphics2D g2d, DiverseSplicingImageParam input, Pair<Integer, Integer> nextPosition) throws SplicingImageError{
+        // 对象为布局
+        if (TextUtils.isBlank(input.getImg())) {
+            // row、column
+            String direction = input.getDirection();
+            Integer gap = input.getGap();
+            List<DiverseSplicingImageParam> children = input.getChildren();
+            for (int i = 0; i < children.size(); i++) {
+                Pair<Integer,Integer> latetstPositionPair = graph(g2d, children.get(i), nextPosition);
+                if (i == children.size() - 1) {
+                    return latetstPositionPair;
+                }else {
+                    if (direction.equals("row")) {
+                        nextPosition = Pair.of(nextPosition.getFirst(), latetstPositionPair.getSecond() + gap);
+                    }else {
+                        nextPosition = Pair.of(latetstPositionPair.getFirst() + gap, nextPosition.getSecond());
+                    }
+                }
+            }
+
+            return nextPosition;
+        }else {
+            try {
+                byte[] bytes = imageDownload(input.getImg());
+                BufferedImage bufferedImage = ImageIO.read(new ByteArrayInputStream(bytes));
+                // 先按Position裁切 update 20240805
+                DiverseSplicingImageParam.Position position = input.getPosition();
+                // 裁切区域左上角的X坐标
+                int cropX = position.getLeft().setScale(0, RoundingMode.UP).intValue();
+                // 裁切区域左上角的Y坐标
+                int cropY = position.getTop().setScale(0, RoundingMode.UP).intValue();
+                // 裁切区域的宽度
+                int cropWidth =
+                        bufferedImage.getWidth() - cropX - position.getRight().setScale(0, RoundingMode.UP).intValue();
+                // 裁切区域的高度
+                int cropHeight =
+                        bufferedImage.getHeight() - cropY - position.getBottom().setScale(0, RoundingMode.UP).intValue();
+                // 执行裁切操作
+                bufferedImage = bufferedImage.getSubimage(cropX, cropY, cropWidth, cropHeight);
+
+                int dx1 = nextPosition.getFirst();
+                int dy1 = nextPosition.getSecond();
+                int dx2 = nextPosition.getFirst() + input.getSize().getWidth();
+                int dy2 = nextPosition.getSecond() + input.getSize().getHeight();
+                int sx1 = 0;
+                int sx2 = bufferedImage.getWidth();
+                int sy1 = 0;
+                int sy2 = bufferedImage.getHeight();
+
+                // 等比放大/缩小,切掉多余的高度/宽度,居中切割
+                Image image = bufferedImage;
+                BigDecimal canvasRatio = new BigDecimal(input.getSize().getWidth()).divide(new BigDecimal(input.getSize().getHeight()), 2, RoundingMode.UP);
+                BigDecimal originalRatio = new BigDecimal(image.getWidth(null)).divide(new BigDecimal(image.getHeight(null)), 2, RoundingMode.UP);
+
+                // 这里加步图片缩放,目的是防止单位画布内挤压过多像素点导致图像不清晰
+                if (canvasRatio.compareTo(originalRatio) < 0 && image.getHeight(null) > input.getSize().getHeight()) {
+                    // 以高度为准等比缩放
+                    image = bufferedImage.getScaledInstance(
+                            new BigDecimal(image.getWidth(null))
+                                    .multiply(
+                                            new BigDecimal(input.getSize().getHeight())
+                                                    .divide(new BigDecimal(image.getHeight(null)), 2, RoundingMode.UP)
+                                    ).intValue(),
+                            input.getSize().getHeight(), Image.SCALE_SMOOTH);
+                    sx2 = image.getWidth(null);
+                    sy2 = image.getHeight(null);
+                }else if (canvasRatio.compareTo(originalRatio) > 0 && image.getWidth(null) > input.getSize().getWidth()) {
+                    // 以宽度为准等比缩放
+                    image = bufferedImage.getScaledInstance(
+                            input.getSize().getWidth(),
+                            new BigDecimal(image.getHeight(null))
+                                    .multiply(
+                                            new BigDecimal(input.getSize().getWidth())
+                                                    .divide(new BigDecimal(image.getWidth(null)), 2, RoundingMode.UP)
+                                    ).intValue(), Image.SCALE_SMOOTH);
+                    sx2 = image.getWidth(null);
+                    sy2 = image.getHeight(null);
+                }
+
+                // 要画的宽高比小于原始宽高比,宽度需要截取
+                if (canvasRatio.compareTo(originalRatio) < 0) {
+                    BigDecimal d = new BigDecimal(input.getSize().getHeight())
+                            .divide(new BigDecimal(image.getHeight(null)), 2, RoundingMode.UP);
+                    BigDecimal width = new BigDecimal(input.getSize().getWidth())
+                            .divide(d, 2, RoundingMode.UP);
+                    sx1 = sx1 + (
+                            new BigDecimal(image.getWidth(null)).subtract(width)
+                                    .divide(new BigDecimal(2))
+                                    .intValue()
+                            );
+                    sx2 = sx2 - (
+                            new BigDecimal(image.getWidth(null)).subtract(width)
+                                    .divide(new BigDecimal(2))
+                                    .intValue()
+                            );
+                }else if (canvasRatio.compareTo(originalRatio) > 0) {
+                    // 要画的宽高比小于原始宽高比,高度需要截取
+                    BigDecimal d = new BigDecimal(input.getSize().getWidth())
+                            .divide(new BigDecimal(image.getWidth(null)), 2, RoundingMode.UP);
+                    BigDecimal height = new BigDecimal(input.getSize().getHeight())
+                            .divide(d, 2, RoundingMode.UP);
+                    sy1 = sy1 + (
+                            new BigDecimal(image.getHeight(null)).subtract(height)
+                                    .divide(new BigDecimal(2))
+                                    .intValue()
+                    );
+                    sy2 = sy2 - (
+                            new BigDecimal(image.getHeight(null)).subtract(height)
+                                    .divide(new BigDecimal(2))
+                                    .intValue()
+                    );
+                }
+
+                g2d.drawImage(image,
+                        dx1, dy1,
+                        dx2, dy2,
+                        sx1, sy1, sx2, sy2, null);
+                return Pair.of(nextPosition.getFirst() + input.getSize().getWidth(), nextPosition.getSecond() + input.getSize().getHeight());
+            } catch (DownloadImageError e) {
+                throw new SplicingImageError("拼接失败,图片下载失败。" + e.getMessage());
+            } catch (Exception e) {
+                throw new SplicingImageError("拼接失败,创建BufferedImage失败。" + e.getMessage());
+            }
+        }
+    }
+
+    @Override
+    public String transferAndStore(String imageUrl) throws DownloadImageError {
+        byte[] bytes = this.imageDownload(imageUrl);
+        String key = OSS_LAYER_KEY_PREFIX + BaseUtils.getUUIDStr() + ".jpg";
+        AliOssFileTool.saveInPublic(EnumPublicBuckets.PUBBUCKET.getBucketName(), key, new ByteArrayInputStream(bytes));
+        return CdnUtil.getOssHttpUrl(key);
+    }
+}

+ 19 - 0
core/src/main/java/com/tzld/supply/service/tools/ToolsAudioTransService.java

@@ -0,0 +1,19 @@
+package com.tzld.supply.service.tools;
+
+
+import com.tzld.supply.model.dto.ali.AliVoiceResultData;
+
+public interface ToolsAudioTransService {
+
+    AliVoiceResultData getAudioTransResults(String audioUrl, boolean needWords);
+
+    /**
+     *
+     * @param audioUrl
+     * @param needWords
+     * @param provider aliyun / volcengine
+     * @return
+     */
+    AliVoiceResultData getAudioTransResults(String audioUrl, boolean needWords, String provider);
+
+}

+ 141 - 0
core/src/main/java/com/tzld/supply/service/tools/impl/ToolsAudioTransServiceImpl.java

@@ -0,0 +1,141 @@
+package com.tzld.supply.service.tools.impl;
+
+import cn.hutool.core.collection.CollectionUtil;
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.tzld.supply.api.volcengine.VolcengineVoiceTransTextClient;
+import com.tzld.supply.dao.mapper.supply.spider.ToolsAudioTransRecordMapper;
+import com.tzld.supply.model.dto.ali.AliVoiceResultData;
+import com.tzld.supply.model.dto.ali.AliVoiceResultSentenceData;
+import com.tzld.supply.model.dto.ali.AliVoiceResultWordData;
+import com.tzld.supply.model.po.supply.spider.ToolsAudioTransRecord;
+import com.tzld.supply.model.po.supply.spider.ToolsAudioTransRecordExample;
+import com.tzld.supply.service.tools.ToolsAudioTransService;
+import com.tzld.supply.util.Md5Util;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+@Slf4j
+@Service
+public class ToolsAudioTransServiceImpl implements ToolsAudioTransService {
+
+    @Autowired
+    ToolsAudioTransRecordMapper toolsAudioTransRecordMapper;
+    @Autowired
+    VolcengineVoiceTransTextClient volcengineVoiceTransTextClient;
+
+    @Override
+    public AliVoiceResultData getAudioTransResults(String audioUrl, boolean needWords) {
+        return getAudioTransResults(audioUrl, needWords, "volcengine");
+    }
+
+    @Override
+    public AliVoiceResultData getAudioTransResults(String audioUrl, boolean needWords, String provider) {
+        AliVoiceResultData resultData = new AliVoiceResultData();
+        if (!StringUtils.hasText(audioUrl)) {
+            return resultData;
+        }
+        ToolsAudioTransRecord existRecord = getExistToolsAudioTransRecord(audioUrl);
+        if (Objects.nonNull(existRecord)) {
+            List<AliVoiceResultSentenceData> sentences = JSON.parseArray(existRecord.getResult(),
+                    AliVoiceResultSentenceData.class);
+            resultData.setSentences(sentences);
+            if (!needWords) {
+                return resultData;
+            } else {
+                if (StringUtils.hasText(existRecord.getWords())) {
+                    List<AliVoiceResultWordData> words = JSON.parseArray(existRecord.getWords(),
+                            AliVoiceResultWordData.class);
+                    resultData.setWords(words);
+                    return resultData;
+                }
+            }
+        }
+        resultData = getAudioTransResultsByVolcengine(audioUrl);
+        if (CollectionUtil.isNotEmpty(resultData.getSentences())) {
+            long ctime = System.currentTimeMillis();
+            String taskId = resultData.getTaskId();
+            List<AliVoiceResultSentenceData> sentences = resultData.getSentences();
+            List<AliVoiceResultWordData> words = resultData.getWords();
+            // 保存记录
+            try {
+                if (Objects.isNull(existRecord)) {
+                    ToolsAudioTransRecord record = new ToolsAudioTransRecord();
+                    record.setFileUrl(audioUrl);
+                    record.setFileUrlMd5(Md5Util.encoderByMd5(audioUrl));
+                    record.setTaskId(taskId);
+                    record.setStatus(2);
+                    record.setResult(JSON.toJSONString(sentences));
+                    if (Objects.nonNull(words)) {
+                        record.setWords(JSON.toJSONString(words));
+                    }
+                    record.setCreateTimestamp(ctime);
+                    record.setExeTimestamp(ctime);
+                    record.setFinishTimestamp(System.currentTimeMillis());
+                    toolsAudioTransRecordMapper.insertSelective(record);
+                } else {
+                    ToolsAudioTransRecord update = new ToolsAudioTransRecord();
+                    update.setId(existRecord.getId());
+                    update.setResult(JSON.toJSONString(sentences));
+                    if (Objects.nonNull(words)) {
+                        update.setWords(JSON.toJSONString(words));
+                    }
+                    update.setFinishTimestamp(System.currentTimeMillis());
+                    toolsAudioTransRecordMapper.updateByPrimaryKeySelective(update);
+                }
+
+            } catch (Exception e) {
+                log.error("save ToolsAudioTransRecord error,{}", audioUrl, e);
+            }
+        }
+        return resultData;
+    }
+
+    private AliVoiceResultData getAudioTransResultsByVolcengine(String audioUrl) {
+        AliVoiceResultData resultData = new AliVoiceResultData();
+        String response = volcengineVoiceTransTextClient.requestVoiceTransText(audioUrl);
+        JSONObject queryResponseJson = JSON.parseObject(response);
+        String taskId = queryResponseJson.getString("id");
+        resultData.setTaskId(taskId);
+        List<AliVoiceResultSentenceData> sentences = new ArrayList<>();
+        List<AliVoiceResultWordData> words = new ArrayList<>();
+        resultData.setSentences(sentences);
+        resultData.setWords(words);
+        JSONArray utterances = queryResponseJson.getJSONArray("utterances");
+        for (Object utterance : utterances) {
+            JSONObject utteranceJson = (JSONObject) utterance;
+            AliVoiceResultSentenceData sentenceData = new AliVoiceResultSentenceData();
+            sentenceData.setBeginTime(utteranceJson.getInteger("start_time"));
+            sentenceData.setEndTime(utteranceJson.getInteger("end_time"));
+            sentenceData.setText(utteranceJson.getString("text"));
+            sentences.add(sentenceData);
+            JSONArray wordsArray = utteranceJson.getJSONArray("words");
+            for (Object word : wordsArray) {
+                JSONObject wordJson = (JSONObject) word;
+                AliVoiceResultWordData wordData = new AliVoiceResultWordData();
+                wordData.setBeginTime(wordJson.getInteger("start_time"));
+                wordData.setEndTime(wordJson.getInteger("end_time"));
+                wordData.setWord(wordJson.getString("text"));
+                words.add(wordData);
+            }
+        }
+        return resultData;
+    }
+
+    private ToolsAudioTransRecord getExistToolsAudioTransRecord(String audioUrl) {
+        ToolsAudioTransRecordExample example = new ToolsAudioTransRecordExample();
+        example.createCriteria().andFileUrlEqualTo(audioUrl);
+        List<ToolsAudioTransRecord> list = toolsAudioTransRecordMapper.selectByExampleWithBLOBs(example);
+        if (CollectionUtil.isNotEmpty(list)) {
+            return list.get(0);
+        }
+        return null;
+    }
+}

+ 1492 - 0
core/src/main/java/com/tzld/supply/util/AliOssFileTool.java

@@ -0,0 +1,1492 @@
+package com.tzld.supply.util;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.aliyun.opensearch.sdk.dependencies.org.apache.commons.codec.binary.Base64;
+import com.aliyun.oss.*;
+import com.aliyun.oss.common.utils.BinaryUtil;
+import com.aliyun.oss.model.*;
+import com.aliyuncs.DefaultAcsClient;
+import com.aliyuncs.exceptions.ServerException;
+import com.aliyuncs.http.MethodType;
+import com.aliyuncs.http.ProtocolType;
+import com.aliyuncs.mts.model.v20140618.SubmitJobsRequest;
+import com.aliyuncs.mts.model.v20140618.SubmitJobsResponse;
+import com.aliyuncs.mts.model.v20140618.SubmitMediaInfoJobRequest;
+import com.aliyuncs.mts.model.v20140618.SubmitMediaInfoJobResponse;
+import com.aliyuncs.mts.model.v20140618.SubmitMediaInfoJobResponse.MediaInfoJob.Properties;
+import com.aliyuncs.profile.DefaultProfile;
+import com.aliyuncs.profile.IClientProfile;
+import com.aliyuncs.sts.model.v20150401.AssumeRoleRequest;
+import com.aliyuncs.sts.model.v20150401.AssumeRoleResponse;
+import com.stuuudy.commons.external.filestorage.alibaba.OSSFileDO;
+import com.stuuudy.commons.external.filestorage.enums.*;
+import com.stuuudy.commons.util.BlankUtil;
+import com.stuuudy.commons.util.CompressImage;
+import com.stuuudy.commons.util.MD5.Md5Util;
+import com.stuuudy.commons.util.exception.CommonsException;
+import com.stuuudy.commons.util.exception.EnumErrorException;
+import com.tzld.supply.common.enums.EnumUploadFileType;
+import com.tzld.supply.config.AliOssConfig;
+import com.tzld.supply.model.vo.SignatureVO;
+import com.tzld.supply.model.vo.StsTokenVO;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.log4j.Logger;
+import org.springframework.stereotype.Component;
+
+import javax.crypto.Mac;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+import javax.imageio.ImageIO;
+import javax.xml.crypto.dsig.SignatureMethod;
+import java.awt.image.BufferedImage;
+import java.io.*;
+import java.net.URL;
+import java.net.URLEncoder;
+import java.security.DigestInputStream;
+import java.security.InvalidKeyException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.*;
+
+/**
+ * (阿里对象存储工具类)<BR>
+ * AliOssFileTool<BR>
+ * 创建人:陈海 <BR>
+ * 时间:2016年7月18日-下午12:47:52 <BR>
+ *
+ * @version 1.0.0
+ */
+@Component
+public class AliOssFileTool extends AliOssConfig {
+
+    private static Logger logger = Logger.getLogger(AliOssFileTool.class);
+
+    private static final String ENCODE_TYPE = "UTF-8";
+    private static final String ALGORITHM = "HmacSHA1";
+    private static final String HTTP_METHOD = "GET";
+    private static final String SEPARATOR = "&";
+    private static final String EQUAL = "=";
+
+    public static String saveInPublicWithPress(InputStream inputStream, EnumPublicBuckets bucket, String key, EnumFileType type,
+                                               EnumFileSuffix fileSuffix) throws OSSException, ClientException {
+        inputStream = compressImg(inputStream, fileSuffix, type);
+        return saveInPublicNoPress(inputStream, bucket, key, type);
+    }
+
+    public static String saveInPublicReturnHost(InputStream inputStream, EnumPublicBuckets bucket, String fullKey,
+                                                EnumFileType type) {
+        String path = saveInPublic(inputStream, bucket, fullKey, type);
+        // 保存路径
+        return CdnUtil.getCdnUrlHost(type) + path;
+    }
+
+    public static String saveInPublic(InputStream inputStream, EnumPublicBuckets bucket, String fullKey, EnumFileType type) {
+        String bucketName = getBucket(bucket.getBucketName());
+        if (EnumFileType.VOICE.equals(type)) {
+            ObjectMetadata om = new ObjectMetadata();
+            om.setContentType("audio/mpeg");
+            getOssClient().putObject(bucketName, fullKey, inputStream, om);
+        } else if (EnumFileType.PICTURE.equals(type)) {
+            ObjectMetadata om = new ObjectMetadata();
+            om.setContentType("image/jpeg");
+            getOssClient().putObject(bucketName, fullKey, inputStream, om);
+        } else if (EnumFileType.VIDEO.equals(type)) {
+            ObjectMetadata om = new ObjectMetadata();
+            om.setContentType("video/mp4");
+            getOssClient().putObject(bucketName, fullKey, inputStream, om);
+        } else {
+            getOssClient().putObject(bucketName, fullKey, inputStream);
+        }
+        return fullKey;
+    }
+
+    public static String saveInPublic(String bucketName, String ossKey, InputStream inputStream, String contentType) {
+        bucketName = getBucket(bucketName);
+        ObjectMetadata om = new ObjectMetadata();
+        om.setContentType(contentType);
+        getOssClient().putObject(bucketName, ossKey, inputStream, om);
+        return ossKey;
+    }
+
+    public static void saveInPublic(String bucketName, String key, InputStream input) {
+        getOssClient().putObject(bucketName, key, input);
+    }
+
+    public static String moveFile(EnumPublicBuckets sourceBucket, String sourceKey, EnumPublicBuckets targetBucket, String targetKey) {
+        getOssClient().copyObject(getBucket(sourceBucket.getBucketName()), sourceKey,
+                getBucket(targetBucket.getBucketName()), targetKey);
+        if (!StringUtils.equals(sourceKey, targetKey)) {
+            getOssClient().deleteObject(getBucket(sourceBucket.getBucketName()), sourceKey);
+        }
+        return targetKey;
+    }
+
+    public static void deleteFile(EnumPublicBuckets bucket, String key) {
+        getOssClient().deleteObject(getBucket(bucket.getBucketName()), key);
+    }
+
+    public static String saveInPublicNoPress(InputStream inputStream, EnumPublicBuckets bucket, String key,
+                                             EnumFileType type) throws OSSException, ClientException {
+        String fullKey = getProjectName() + "/" + type.getLocation() + key;
+        saveInPublic(inputStream, bucket, fullKey, type);
+        return fullKey;
+    }
+
+    /**
+     * (网络资源上传) 方法名saveURLInPublicNoPress
+     *
+     * @param url        资源地址
+     * @param key
+     * @param type
+     * @param fileSuffix
+     * @return
+     * @throws OSSException
+     * @throws ClientException
+     * @throws IOException     String
+     * @throws @version        1.0
+     * @author 创建人:何振斌
+     * @time 创建日期:2018年3月12日 下午7:10:00
+     */
+    public static String saveURLInPublicNoPress(String url, String bucketName, String key, EnumFileType type,
+                                                EnumFileSuffix fileSuffix) throws OSSException, ClientException, IOException {
+        String fullKey = getProjectName() + "/" + type.getLocation() + key;
+        InputStream inputStream = null;
+        if (!BlankUtil.isBlank(url)) {
+            inputStream = new URL(url).openStream();
+        }
+        if (EnumFileType.VOICE.equals(type)) {
+            ObjectMetadata om = new ObjectMetadata();
+            om.setContentType("audio/mpeg");
+            getOssClient().putObject(bucketName, fullKey, inputStream, om);
+        } else {
+            getOssClient().putObject(bucketName, fullKey, inputStream);
+        }
+        return getProjectName() + "/" + type.getLocation() + key;
+    }
+
+    /**
+     * (根据名称和类型保存文件,返回是否成功)<BR>
+     * 方法名:saveWiteKeyAndType<BR>
+     * 创建人:陈海 <BR>
+     * 时间:2016年7月18日-下午1:02:10 <BR>
+     *
+     * @param inputStream 文件流
+     * @param bucket      空间名称
+     * @param key         文件保存在服务器上的另存名
+     * @param type        枚举文件类型,声音或图片等
+     * @return boolean<BR>
+     * @throws IOException
+     * @throws ClientException
+     * @throws OSSException
+     * @throws <BR>
+     * @since 1.0.0
+     */
+    public static boolean saveInPrivate(InputStream inputStream, EnumPrivateBuckets bucket, String key,
+                                        EnumFileType type, EnumFileSuffix suffix) throws OSSException, ClientException, IOException {
+        inputStream = compressImg(inputStream, suffix, type);
+        String bucketName = getBucket(bucket.getBucketName());
+        String fullKey = getProjectName() + "/" + type.getLocation() + key;
+        getOssClient().putObject(bucketName, fullKey, inputStream);
+        return true;
+    }
+
+    /**
+     * (获取前缀为prefix的全部文件)<BR>
+     * 方法名:listFileInFolder<BR>
+     * 创建人:陈海 <BR>
+     * 时间:2017年1月20日-下午4:59:25 <BR>
+     *
+     * @param bucket
+     * @param prefix
+     * @return List<OSSObjectSummary><BR>
+     * @throws <BR>
+     * @since 1.0.0
+     */
+    public static List<OSSObjectSummary> listFileInFolder(EnumPublicBuckets bucket, String prefix) {
+        final int maxKeys = 200;
+        String nextMarker = null;
+        ObjectListing objectListing;
+        List<OSSObjectSummary> sums = new ArrayList<OSSObjectSummary>();
+        // 递归列出fun目录下的所有文件
+        do {
+            ListObjectsRequest listObjectsRequest = new ListObjectsRequest(getBucket(bucket.getBucketName()));
+            listObjectsRequest.setPrefix(prefix);
+            listObjectsRequest.withMarker(nextMarker).withMaxKeys(maxKeys);
+            objectListing = getOssClient().listObjects(listObjectsRequest);
+            List<OSSObjectSummary> temp = objectListing.getObjectSummaries();
+            if (temp != null) {
+                sums.addAll(temp);
+            }
+            nextMarker = objectListing.getNextMarker();
+        } while (objectListing.isTruncated());
+        return sums;
+    }
+
+    /**
+     * (判断是否存在文件)<BR>
+     * 方法名:isExist<BR>
+     * 创建人:陈海 <BR>
+     * 时间:2016年11月21日-下午3:54:36 <BR>
+     *
+     * @param bucket
+     * @param key
+     * @param type
+     * @return boolean<BR>
+     * @throws <BR>
+     * @since 1.0.0
+     */
+    public static boolean isExist(EnumPrivateBuckets bucket, String key, EnumFileType type) {
+        String fullKey = getProjectName() + "/" + type.getLocation() + key;
+        return getOssClient().doesObjectExist(getBucket(bucket.getBucketName()), fullKey);
+    }
+
+    public static final String REGION_CN_HANGZHOU = "cn-hangzhou";
+    public static final String STS_API_VERSION = "2015-04-01";
+
+    public static AssumeRoleResponse getTempUserToken(long durationSeconds, Integer uid)
+            throws com.aliyuncs.exceptions.ClientException {
+
+        // 只有 RAM用户(子账号)才能调用 AssumeRole 接口
+        // 阿里云主账号的AccessKeys不能用于发起AssumeRole请求
+        // 请首先在RAM控制台创建一个RAM用户,并为这个用户创建AccessKeys
+        String accessKeyId = "LTAIQLkVvDFXRO7j";
+        String accessKeySecret = "aOnxO0xyoa0lDmTxTI1cNmVLNotF15";
+
+        // RoleArn 需要在 RAM 控制台上获取
+        String roleArn = "acs:ram::1894469520484605:role/aliyunosstokengeneratorrole";
+        String policy = JSONObject.toJSONString(AliOssFileTool.getPolicyDetail());
+        // RoleSessionName 是临时Token的会话名称,自己指定用于标识你的用户,主要用于审计,或者用于区分Token颁发给谁
+        // 但是注意RoleSessionName的长度和规则,不要有空格,只能有'-' '_' 字母和数字等字符
+        // 具体规则请参考API文档中的格式要求
+        String roleSessionName = "uid_" + uid;
+
+        // 此处必须为 HTTPS
+        ProtocolType protocolType = ProtocolType.HTTPS;
+
+        try {
+            final AssumeRoleResponse stsResponse = assumeRole(accessKeyId, accessKeySecret, roleArn, roleSessionName,
+                    policy, protocolType, durationSeconds);
+            return stsResponse;
+            /*
+             * Map<String, String> respMap = new LinkedHashMap<String, String>();
+             * respMap.put("status", "200"); respMap.put("AccessKeyId",
+             * stsResponse.getCredentials().getAccessKeyId());
+             * respMap.put("AccessKeySecret",
+             * stsResponse.getCredentials().getAccessKeySecret());
+             * respMap.put("SecurityToken",
+             * stsResponse.getCredentials().getSecurityToken()); respMap.put("Expiration",
+             * stsResponse.getCredentials().getExpiration());
+             */
+
+        } catch (ClientException e) {
+            throw e;
+        }
+
+    }
+
+    protected static AssumeRoleResponse assumeRole(String accessKeyId, String accessKeySecret, String roleArn,
+                                                   String roleSessionName, String policy, ProtocolType protocolType, long durationSeconds)
+            throws com.aliyuncs.exceptions.ClientException {
+        try {
+            // 创建一个 Aliyun Acs Client, 用于发起 OpenAPI 请求
+            IClientProfile profile = DefaultProfile.getProfile(REGION_CN_HANGZHOU, accessKeyId, accessKeySecret);
+            DefaultAcsClient client = new DefaultAcsClient(profile);
+
+            // 创建一个 AssumeRoleRequest 并设置请求参数
+            final AssumeRoleRequest request = new AssumeRoleRequest();
+            request.setVersion(STS_API_VERSION);
+            request.setMethod(MethodType.POST);
+            request.setProtocol(protocolType);
+
+            request.setRoleArn(roleArn);
+            request.setRoleSessionName(roleSessionName);
+            request.setPolicy(policy);
+            request.setDurationSeconds(durationSeconds);
+
+            // 发起请求,并得到response
+            final AssumeRoleResponse response = client.getAcsResponse(request);
+
+            return response;
+        } catch (com.aliyuncs.exceptions.ClientException e) {
+            throw e;
+        }
+    }
+
+    /**
+     * (判断是否存在文件)<BR>
+     * 方法名:isExist<BR>
+     * 创建人:陈海 <BR>
+     * 时间:2016年11月21日-下午3:54:36 <BR>
+     *
+     * @param bucket
+     * @param key
+     * @param type
+     * @return boolean<BR>
+     * @throws <BR>
+     * @since 1.0.0
+     */
+    @Deprecated
+    public boolean isExist(EnumPublicBuckets bucket, String key, EnumFileType type) {
+        String fullKey = getProjectName() + "/" + type.getLocation() + key;
+        return getOssClient().doesObjectExist(getBucket(bucket.getBucketName()), fullKey);
+    }
+
+    /**
+     * (用bucket名称和完整key 判断文件是否存在)<BR>
+     * 方法名:isExist<BR>
+     * 创建人:陈海 <BR>
+     * 时间:2017年5月12日-下午6:38:49 <BR>
+     *
+     * @param bucket
+     * @param fullKey
+     * @return boolean<BR>
+     * @throws <BR>
+     * @since 1.0.0
+     */
+    public static boolean isExist(String bucket, String fullKey) {
+        return getOssClient().doesObjectExist(bucket, fullKey);
+    }
+
+    /**
+     * 文件是否存在 内网
+     *
+     * @param bucket
+     * @param fullKey
+     * @return
+     */
+    public static boolean isExistOfInternal(String bucket, String fullKey) {
+        return getInternalOssClient().doesObjectExist(bucket, fullKey);
+    }
+
+    /**
+     * (删除oss文件)<BR>
+     * 方法名:delFile<BR>
+     * 创建人:陈海 <BR>
+     * 时间:2016年7月20日-上午10:44:52 <BR>
+     *
+     * @param bucket 存储空间名称
+     * @param key    文件保存在服务器上的另存名
+     * @param type   枚举文件类型,声音或图片等
+     * @return boolean<BR>
+     * @throws <BR>
+     * @since 1.0.0
+     */
+    public static boolean delFile(EnumAllBuckets bucket, String key, EnumFileType type) {
+        getOssClient().deleteObject(getBucket(bucket.getBucketName()),
+                getProjectName() + "/" + type.getLocation() + key);
+        return true;
+    }
+
+    public static boolean deleteObject(String bucketName, String objectKey) {
+        getInternalOssClient().deleteObject(bucketName, objectKey);
+        return true;
+    }
+
+    public static boolean deleteObjects(String bucketName, List<String> objectKeys) {
+        if (objectKeys == null || objectKeys.size() == 0) {
+            return false;
+        }
+        DeleteObjectsRequest deleteObjectsRequest = new DeleteObjectsRequest(bucketName);
+        deleteObjectsRequest.setKeys(objectKeys);
+        deleteObjectsRequest.setQuiet(true);
+        getInternalOssClient().deleteObjects(deleteObjectsRequest);
+        return true;
+    }
+
+    /**
+     * (获取经处理的图片签名url)<BR>
+     * 方法名:getPicUrlWithSign<BR>
+     * 创建人:陈海 <BR>
+     * 时间:2016年7月18日-下午1:07:23 <BR>
+     *
+     * @param bucket 空间名称
+     * @param key    文件保存在服务器上的名称
+     * @param type   枚举文件类型,声音或图片等
+     * @return String<BR>
+     * @throws <BR>
+     * @since 1.0.0
+     */
+    public static String getPicUrl(EnumPrivateBuckets bucket, String key, EnumFileType type, Long expiresSec) {
+        if (expiresSec == null) {
+            expiresSec = getExpiration();
+        }
+        Date expires = new Date(new Date().getTime() + getExpiration() * 1000);
+        String picUrl = getPriClient().generatePresignedUrl(getBucket(bucket.getBucketName()),
+                getProjectName() + "/" + type.getLocation() + key, expires).toString();
+        return picUrl;
+    }
+
+    /**
+     * (获取文件对象的签名url)<BR>
+     * 方法名:getUrlWithSign<BR>
+     * 创建人:陈海 <BR>
+     * 时间:2016年7月18日-下午1:05:50 <BR>
+     *
+     * @param bucket 空间名称
+     * @param key    文件保存在服务器上的名称
+     * @param type   枚举文件类型,声音或图片等
+     * @return String<BR>
+     * @throws <BR>
+     * @since 1.0.0
+     */
+    public static String getUrl(EnumPrivateBuckets bucket, String key, EnumFileType type, Long expiresSec) {
+        if (expiresSec == null) {
+            expiresSec = getExpiration();
+        }
+        Date expires = new Date(new Date().getTime() + expiresSec * 1000);
+        String url = "";
+        if (type.equals(EnumFileType.VIDEO)) {
+            url = getVideoClient().generatePresignedUrl(getBucket(bucket.getBucketName()),
+                    getProjectName() + "/" + type.getLocation() + key, expires).toString();
+        } else {
+            url = getPriClient().generatePresignedUrl(getBucket(bucket.getBucketName()),
+                    getProjectName() + "/" + type.getLocation() + key, expires).toString();
+        }
+        return url;
+    }
+
+    public static String getUrlWithOssEndPoint(EnumPrivateBuckets bucket, String key, EnumFileType type,
+                                               Long expiresSec) {
+        if (expiresSec == null) {
+            expiresSec = getExpiration();
+        }
+        Date expires = new Date(new Date().getTime() + expiresSec * 1000);
+        return getOssClient().generatePresignedUrl(getBucket(bucket.getBucketName()),
+                getProjectName() + "/" + type.getLocation() + key, expires).toString();
+    }
+
+    /**
+     * (获取object)<BR>
+     * 方法名:getObject<BR>
+     * 创建人:陈海 <BR>
+     * 时间:2016年12月28日-上午10:55:43 <BR>
+     *
+     * @param bucket
+     * @param key
+     * @param type
+     * @return OSSObject<BR>
+     * @throws <BR>
+     * @since 1.0.0
+     */
+    public static OSSObject getObject(EnumPrivateBuckets bucket, String key, EnumFileType type) {
+        OSSObject ossObject = getOssClient().getObject(getBucket(bucket.getBucketName()),
+                getProjectName() + "/" + type.getLocation() + key);
+        return ossObject;
+    }
+
+    public static OSSObject getPubObject(EnumAllBuckets bucket, String key, EnumFileType type) {
+        OSSObject ossObject = getOssClient().getObject(getBucket(bucket.getBucketName()),
+                getProjectName() + "/" + type.getLocation() + key);
+        return ossObject;
+    }
+
+    public static OSSObject getPubObject(String bucketName, String fullKey) {
+        OSSObject ossObject = getOssClient().getObject(bucketName, fullKey);
+        return ossObject;
+    }
+
+    public static OSSObject getInternalPubObject(String bucketName, String fullKey) {
+        OSSObject ossObject = getInternalOssClient().getObject(bucketName, fullKey);
+        return ossObject;
+    }
+
+    public static ObjectMetadata getPubObjectMedeta(EnumAllBuckets bucket, String key, EnumFileType type) {
+        ObjectMetadata ossObjectMeta = getOssClient().getObjectMetadata(getBucket(bucket.getBucketName()),
+                getProjectName() + "/" + type.getLocation() + key);
+        return ossObjectMeta;
+    }
+
+    public static String calcFileMD5(String bucketName, String objectKey) {
+        try {
+            OSSObject ossObj = getOssClient().getObject(bucketName, objectKey);
+            MessageDigest md = MessageDigest.getInstance("MD5");
+            try (DigestInputStream digestInputStream = new DigestInputStream(ossObj.getObjectContent(), md)) {
+                // 读取数据流并更新 MD5
+                byte[] buffer = new byte[8192];
+                while (digestInputStream.read(buffer) != -1) {
+                    // DigestInputStream 会自动更新 MessageDigest
+                }
+            } catch (Exception e) {
+                logger.info("读取oss文件异常: ", e);
+                return "";
+            }
+
+            // 获取最终的 MD5 值
+            byte[] md5Bytes = md.digest();
+            // 转换为十六进制字符串
+            StringBuilder sb = new StringBuilder();
+            for (byte b : md5Bytes) {
+                sb.append(String.format("%02x", b));
+            }
+
+            String md5 = sb.toString();
+            logger.info(String.format("buketName: %s, objectKey: %s, MD5: %s", bucketName, objectKey, md5));
+
+            return md5;
+        } catch (Exception e) {
+            logger.error("计算文件MD5异常: ", e);
+        }
+        return "";
+    }
+
+    /**
+     * (重命名文件,实现是先拷贝文件,再删掉旧的文件)<BR>
+     * 方法名:renameFile<BR>
+     * 创建人:陈海 <BR>
+     * 时间:2016年8月1日-下午4:03:09 <BR>
+     *
+     * @param srcBucket
+     * @param srckey
+     * @param destkey
+     * @return boolean<BR>
+     * @throws <BR>
+     * @since 1.0.0
+     */
+    @Deprecated
+    public boolean renameFile(EnumAllBuckets srcBucket, String srckey, String destkey, EnumFileType srcType,
+                              EnumFileType destType) {
+
+        // 拷贝Object
+        getOssClient().copyObject(getBucket(srcBucket.getBucketName()),
+                getProjectName() + "/" + srcType.getLocation() + srckey, getBucket(srcBucket.getBucketName()),
+                getProjectName() + "/" + destType.getLocation() + destkey);
+        delFile(srcBucket, srckey, srcType);
+        return true;
+    }
+
+    /**
+     * (将oss srckey 命名为 destkey)<BR>
+     * 方法名:renameFileWithFullKey<BR>
+     * 创建人:陈海 <BR>
+     * 时间:2017年9月16日-下午5:24:39 <BR>
+     *
+     * @param srcBucket
+     * @param srckey
+     * @param destkey
+     * @return boolean<BR>
+     * @throws <BR>
+     * @since 1.0.0
+     */
+    public static boolean renameFileWithFullKey(EnumAllBuckets srcBucket, String srckey, String destkey) {
+
+        // 拷贝Object
+        getOssClient().copyObject(getBucket(srcBucket.getBucketName()), srckey, getBucket(srcBucket.getBucketName()),
+                destkey);
+        getOssClient().deleteObject(getBucket(srcBucket.getBucketName()), srckey);
+        return true;
+    }
+
+    public static boolean renameFileWithFullKeyFromInternal(EnumAllBuckets srcBucket, String srckey, String destkey, long size) {
+        // 拷贝Object
+        if (size < 1024 * 1024 * 1024) {
+            getInternalOssClient().copyObject(getBucket(srcBucket.getBucketName()), srckey,
+                    getBucket(srcBucket.getBucketName()), destkey);
+        } else {
+            multipartCopyObjectFromInternal(getBucket(srcBucket.getBucketName()), srckey,
+                    getBucket(srcBucket.getBucketName()), destkey);
+        }
+        getInternalOssClient().deleteObject(getBucket(srcBucket.getBucketName()), srckey);
+        return true;
+    }
+
+    // 分片拷贝object,大于1G的文件需要分片拷贝
+    public static void multipartCopyObjectFromInternal(String sourceBucketName, String sourceObjectName,
+                                                       String destinationBucketName, String destinationObjectName) {
+        OSSClient ossClient = getInternalOssClient();
+        ObjectMetadata objectMetadata = ossClient.getObjectMetadata(sourceBucketName, sourceObjectName);
+        // 获取被拷贝文件的大小。
+        long contentLength = objectMetadata.getContentLength();
+
+        // 设置分片大小为10MB。
+        long partSize = 1024 * 1024 * 10;
+
+        // 计算分片总数。
+        int partCount = (int) (contentLength / partSize);
+        if (contentLength % partSize != 0) {
+            partCount++;
+        }
+        logger.info("oss 分片拷贝开始,sourceObjectName:" + sourceObjectName + ",size:" + contentLength + ",partCount:" + partCount);
+        // 初始化拷贝任务。可以通过InitiateMultipartUploadRequest指定目标文件元信息。
+        InitiateMultipartUploadRequest initiateMultipartUploadRequest = new InitiateMultipartUploadRequest(
+                destinationBucketName, destinationObjectName);
+        InitiateMultipartUploadResult initiateMultipartUploadResult = ossClient
+                .initiateMultipartUpload(initiateMultipartUploadRequest);
+        String uploadId = initiateMultipartUploadResult.getUploadId();
+
+        // 分片拷贝。
+        List<PartETag> partETags = new ArrayList<PartETag>();
+        for (int i = 0; i < partCount; i++) {
+            // 计算每个分片的大小。
+            long skipBytes = partSize * i;
+            long size = partSize < contentLength - skipBytes ? partSize : contentLength - skipBytes;
+
+            // 创建UploadPartCopyRequest。可以通过UploadPartCopyRequest指定限定条件。
+            UploadPartCopyRequest uploadPartCopyRequest = new UploadPartCopyRequest(sourceBucketName, sourceObjectName,
+                    destinationBucketName, destinationObjectName);
+            uploadPartCopyRequest.setUploadId(uploadId);
+            uploadPartCopyRequest.setPartSize(size);
+            uploadPartCopyRequest.setBeginIndex(skipBytes);
+            uploadPartCopyRequest.setPartNumber(i + 1);
+            UploadPartCopyResult uploadPartCopyResult = ossClient.uploadPartCopy(uploadPartCopyRequest);
+
+            // 将返回的分片ETag保存到partETags中。
+            partETags.add(uploadPartCopyResult.getPartETag());
+        }
+
+        // 提交分片拷贝任务。
+        CompleteMultipartUploadRequest completeMultipartUploadRequest = new CompleteMultipartUploadRequest(
+                destinationBucketName, destinationObjectName, uploadId, partETags);
+        ossClient.completeMultipartUpload(completeMultipartUploadRequest);
+        logger.info("oss 分片拷贝结束,sourceObjectName:" + sourceObjectName + ",size:" + contentLength + ",partCount:" + partCount);
+    }
+
+    public static void copyObject(String srcBucketName, String srcObjectKey,
+                                  String destBucketName, String destObjectKey) {
+        getOssClient().copyObject(srcBucketName, srcObjectKey, destBucketName, destObjectKey);
+    }
+
+    public static void copyObjectFromInternal(String srcBucketName, String srcObjectKey,
+                                              String destBucketName, String destObjectKey) {
+        getInternalOssClient().copyObject(srcBucketName, srcObjectKey, destBucketName, destObjectKey);
+    }
+
+    private static InputStream compressImg(InputStream stream, EnumFileSuffix suffix, EnumFileType fileType) {
+        if (!getNeedPress()) {
+            return stream;
+        }
+        if (suffix == EnumFileSuffix.JPG || suffix == EnumFileSuffix.PNG || suffix == EnumFileSuffix.JPEG
+                || suffix == EnumFileSuffix.BMP) {
+            try {
+                byte[] file = CompressImage.compress(stream, 1080);
+                if (file != null) {
+                    stream = new ByteArrayInputStream(file);
+                } else {
+                    logger.warn("图片上传有误");
+                    return null;
+                }
+            } catch (Exception e) {
+                logger.error("图片压缩失败", e);
+                if (null != stream) {
+                    try {
+                        stream.close();
+                    } catch (IOException e1) {
+                    }
+                }
+                throw new CommonsException(EnumErrorException.PIC_FORMAT_ERROR);
+            }
+        }
+        return stream;
+    }
+
+    public static String submitPubTranscodeJob(EnumPublicBuckets inputBucket, EnumAllBuckets outputBucket,
+                                               String fullKey, String outPutKey, String templateId) {
+        OSSFileDO inputFile = new OSSFileDO(OSS_REGION, getBucket(EnumAllBuckets.PUBVIDEOBUCKET.getBucketName()),
+                fullKey);
+        JSONArray outputs = new JSONArray();
+        JSONObject outPutConfig = new JSONObject();
+        outPutConfig.put("OutputObject", outPutKey);
+        outPutConfig.put("TemplateId", templateId);
+        outputs.add(outPutConfig);
+        SubmitJobsRequest request = new SubmitJobsRequest();
+        request.setInput(inputFile.toJsonString());
+        request.setOutputs(outputs.toJSONString());
+        request.setPipelineId(PIPELINEID);
+        request.setOutputBucket(getBucket(outputBucket.getBucketName()));
+        request.setOutputLocation(OSS_REGION);
+        Integer outputJobCount = 1;
+
+        SubmitJobsResponse response = null;
+        try {
+            response = getDefaultAcsClient().getAcsResponse(request);
+            if (response.getJobResultList().size() != outputJobCount) {
+                throw new RuntimeException("SubmitJobsRequest Size failed");
+            }
+            return response.getJobResultList().get(0).getJob().getJobId();
+        } catch (ServerException e) {
+            logger.error("submitTranscodeJob Server failed", e);
+            throw new CommonsException(EnumErrorException.TRANS_CODE_SERVER_ERROR);
+        } catch (com.aliyuncs.exceptions.ClientException e) {
+            logger.error("submitTranscodeJob Client failed", e);
+            throw new CommonsException(EnumErrorException.TRANS_CODE_CLIENT_ERROR);
+        }
+    }
+
+    public static String getPubPicAverageColor(EnumAllBuckets bucket, String key) {
+        String style = "image/average-hue";
+        try {
+            GetObjectRequest request = new GetObjectRequest(getBucket(bucket.getBucketName()), key);
+            request.setProcess(style);
+            OSSObject obj = getOssClient().getObject(request);
+            if (obj != null) {
+                String a = IOUtils.toString(obj.getObjectContent());
+                JSONObject jo = (JSONObject) JSONObject.parse(a);
+                String rgb = jo.getString("RGB");
+                if (rgb != null) {
+                    rgb = rgb.replace("0x", "#");
+                }
+                return rgb;
+            }
+        } catch (Exception e) {
+            throw new CommonsException(EnumErrorException.TRANS_CODE_CLIENT_ERROR);
+        }
+        return null;
+    }
+
+    public static Properties getPubObjMediaInfoData(String bucketName, String fullKey) {
+        DefaultAcsClient client = getDefaultAcsClient();
+        SubmitMediaInfoJobRequest request = new SubmitMediaInfoJobRequest();
+        request.setReadTimeout(Integer.MAX_VALUE);
+        SubmitMediaInfoJobResponse response = new SubmitMediaInfoJobResponse();
+        JSONObject json = new JSONObject();
+        json.put("Location", "oss-cn-hangzhou");
+        json.put("Bucket", bucketName);
+        json.put("Object", fullKey);
+        request.setInput(json.toJSONString());
+        try {
+            response = client.getAcsResponse(request);
+            // String width = response.getMediaInfoJob().getProperties().getWidth();
+            // String height = response.getMediaInfoJob().getProperties().getHeight();
+            return response.getMediaInfoJob().getProperties();
+
+        } catch (Exception e) {
+            throw new CommonsException(EnumErrorException.TRANS_CODE_CLIENT_ERROR, e);
+        }
+    }
+
+    public static SignatureVO getUploadPolicy(Integer fileType) throws Exception {
+        // 后台随机生成唯一文件名
+        String env = PropertiesUtils.getProjectEnv();
+        if (StringUtils.isEmpty(env)) {
+            env = "qa";
+        }
+        String fileName = RandomUtil.generate18String();
+        String contentType = "";
+        if (EnumUploadFileType.VIDEO.getIntType().equals(fileType)) {
+            fileName = "/video/" + env + "/" + DateUtils.dateToStringyyyyMMdd(new Date()) + "/" + fileName;
+        } else if (EnumUploadFileType.VOICE.getIntType().equals(fileType)) {
+            fileName = "/voice/" + env + "/" + DateUtils.dateToStringyyyyMMdd(new Date()) + "/" + fileName;
+        } else {
+            fileName = "/pic/" + env + "/" + DateUtils.dateToStringyyyyMMdd(new Date()) + "/" + fileName;
+        }
+        long expireTime = 60 * 60 * 4;
+        long expireEndTime = System.currentTimeMillis() + expireTime * 1000;
+        Date expiration = new Date(expireEndTime);
+        PolicyConditions policyConds = new PolicyConditions();
+        //4.95G  上限是5个G
+        policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 5261334938l);
+        policyConds.addConditionItem(MatchMode.Exact, PolicyConditions.COND_KEY,
+                AliOssConfig.getProjectName() + fileName);
+        if (!StringUtils.isEmpty(contentType)) {
+            policyConds.addConditionItem(PolicyConditions.COND_CONTENT_TYPE, contentType);
+        }
+
+        String postPolicy = AliOssConfig.getOssClient().generatePostPolicy(expiration, policyConds);
+        byte[] binaryData = postPolicy.getBytes("utf-8");
+        String encodedPolicy = BinaryUtil.toBase64String(binaryData);
+        String postSignature = AliOssConfig.getOssClient().calculatePostSignature(postPolicy);
+        SignatureVO signatureVO = new SignatureVO();
+        signatureVO.setAccessId(AliOssConfig.getAccessKeyId());
+        signatureVO.setPolicy(encodedPolicy);
+        signatureVO.setSignature(postSignature);
+        signatureVO.setFileName(AliOssConfig.getProjectName() + fileName);
+        signatureVO.setHost(AliOssConfig.getUploadDomain());
+        signatureVO.setExpire(String.valueOf(expireEndTime / 1000));
+        return signatureVO;
+    }
+
+    public static String getSignaturedUrl(String fileName, Boolean isPrivate) {
+
+        if (StringUtils.isBlank(fileName)) {
+            return fileName;
+        }
+        if (StringUtils.startsWith(fileName, "https")) {
+            return fileName;
+        }
+        String host = AliOssConfig.getVideoDomain();
+        fileName = host + fileName;
+        return fileName;
+    }
+
+    public static String getSignaturedInternalUrl(String fileName, Boolean isPrivate) throws Exception {
+        if (StringUtils.isBlank(fileName)) {
+            return fileName;
+        }
+        if (StringUtils.startsWith(fileName, "https")) {
+            return fileName;
+        }
+        String bucket = getBucket(isPrivate);
+        String host = "http://" + bucket + "." + AliOssConfig.getInternalEndPoint();
+        fileName = host + "/" + fileName;
+        return fileName;
+    }
+
+    public static String saveVideoCoverImagFromInternal(File file, Integer fileType, Boolean isPrivate, Long videoId)
+            throws Exception {
+        Date begin = new Date();
+        logger.info(String.format("开始往阿里云OSS上传封面图片,当前时间为:%s", begin.getTime()));
+        String bucketName = getBucket(isPrivate);
+
+        String fullKey = getVideoCoverImagPath(fileType, isPrivate, videoId.toString(), null);
+        AliOssConfig.getInternalOssClient().putObject(bucketName, fullKey, file);
+        Date end = new Date();
+        logger.info(String.format("阿里云OSS上传封面图片完成,总耗时为 %s 毫秒。图片地址为:%s, 正在执行下一步。。", end.getTime() - begin.getTime(),
+                fullKey));
+        return fullKey;
+    }
+
+    public static String saveVideoCoverImagFromInternal(InputStream input, Integer fileType, Boolean isPrivate,
+                                                        String filePrex, String dic) throws Exception {
+        Date begin = new Date();
+        logger.info(String.format("开始往阿里云OSS上传图片,当前时间为:%s", begin.getTime()));
+        String bucketName = getBucket(isPrivate);
+
+        String fullKey = getVideoCoverImagPath(fileType, isPrivate, filePrex, dic);
+        // AliOssConfig.getOssClient().putObject(bucketName, fullKey, file);
+        AliOssConfig.getInternalOssClient().putObject(bucketName, fullKey, input);
+        Date end = new Date();
+        logger.info(
+                String.format("阿里云OSS上传图片完成,总耗时为 %s 毫秒。图片地址为:%s, 正在执行下一步。。", end.getTime() - begin.getTime(), fullKey));
+        return fullKey;
+    }
+
+    public static String getVideoCoverImagPath(Integer fileType, Boolean isPrivate, String filePrex, String dic)
+            throws Exception {
+        String fileName = UUID.randomUUID().toString().replaceAll("-", "") + Calendar.getInstance().getTimeInMillis()
+                + "_" + filePrex;
+        if (StringUtils.isNotBlank(dic)) {
+            fileName = (dic + "/" + fileName);
+        }
+        if (EnumUploadFileType.VIDEO.getIntType().equals(fileType)) {
+            fileName = "/video/" + fileName;
+        } else if (EnumUploadFileType.VOICE.getIntType().equals(fileType)) {
+            fileName = "/voice/" + fileName;
+        } else {
+            fileName = "/pic/" + fileName;
+        }
+        String fullKey = AliOssConfig.getProjectName() + fileName;
+        return fullKey;
+    }
+
+    public static String getBucket(boolean isPrivate) {
+        if (isPrivate) {
+            return AliOssFileTool.getBucket(EnumAllBuckets.PRIVIDEOBUCKET.getBucketName());
+        } else {
+            return AliOssFileTool.getBucket(EnumAllBuckets.PUBVIDEOBUCKET.getBucketName());
+        }
+    }
+
+    public static ObjectMetadata getObjectMetadata(String fileName) throws Exception {
+
+        // Endpoint以杭州为例,其它Region请按实际情况填写。
+        // 创建OSSClient实例。
+        OSSClient ossClient = new OSSClient(AliOssConfig.getInternalEndPoint(), AliOssConfig.getAccessKeyId(),
+                AliOssConfig.getAccessKeySecret());
+        // // 获取文件的部分元信息。
+        // SimplifiedObjectMeta objectMeta = ossClient.getSimplifiedObjectMeta(
+        // getBucket(false), fileName);
+        // logger.info( "SimplifiedObjectMeta==="+ JSON.toJSONString(objectMeta));
+        // 获取文件的全部元信息。
+        ObjectMetadata metadata = ossClient.getObjectMetadata(getBucket(false), fileName);
+        // logger.info( "metadata==="+ JSON.toJSONString(metadata));
+        return metadata;
+    }
+
+    /**
+     * 获取部分元信息
+     *
+     * @param bucketName
+     * @param fileName
+     * @return
+     * @throws Exception
+     */
+    public static SimplifiedObjectMeta getSimpleObjectMetadata(String bucketName, String fileName) throws Exception {
+        OSS ossClient = new OSSClientBuilder().build(AliOssConfig.getInternalEndPoint(), AliOssConfig.getAccessKeyId(),
+                AliOssConfig.getAccessKeySecret());
+        SimplifiedObjectMeta metadata = ossClient.getSimplifiedObjectMeta(bucketName, fileName);
+        ossClient.shutdown();
+        return metadata;
+    }
+
+    /**
+     * 获取全部元信息
+     *
+     * @param bucketName
+     * @param fileName
+     * @return
+     * @throws Exception
+     */
+    public static ObjectMetadata getObjectMetadata(String bucketName, String fileName) throws Exception {
+//		OSS ossClient = new OSSClientBuilder().build(AliOssConfig.getInternalEndPoint(), AliOssConfig.getAccessKeyId(),
+//				AliOssConfig.getAccessKeySecret());
+//		// 获取文件的全部元信息
+//		ObjectMetadata metadata = ossClient.getObjectMetadata(bucketName, fileName);
+//		ossClient.shutdown();
+//		return metadata;
+        ObjectMetadata metadata = getInternalOssClient().getObjectMetadata(bucketName, fileName);
+        return metadata;
+    }
+
+//    public static void main(String[] args) throws Exception {
+////		String a = JSONObject.toJSONString(AliOssFileTool.getDefaultAcsClient());
+////		System.out.println(a);
+//    }
+
+    public static Map<String, String> buildCommonParameters() {
+        Map<String, String> parameterMap = new HashMap<>();
+        // 请求公共参数
+        parameterMap.put("Version", "2014-06-18");
+        parameterMap.put("AccessKeyId", AliOssConfig.getAccessKeyId()); // 此处请替换成您自己的AccessKeyId
+        // parameterMap.put("Timestamp",
+        // "2018-07-27T13:03:45Z");//此处将时间戳固定只是测试需要,这样此示例中生成的签名值就不会变,方便您对比验证,可变时间戳的生成需要用下边这句替换
+        parameterMap.put("Timestamp", DateUtils.formatIso8601Date(new Date()));
+        parameterMap.put("SignatureMethod", "HMAC-SHA1");
+        parameterMap.put("SignatureVersion", "1.0");
+        // parameterMap.put("SignatureNonce", "4902260a-516a-4b6a-a455-45b653cf6150");
+        // //此处将唯一随机数固定只是测试需要,这样此示例中生成的签名值就不会变,方便您对比验证,可变唯一随机数的生成需要用下边这句替换
+        parameterMap.put("SignatureNonce", UUID.randomUUID().toString());
+        parameterMap.put("Format", "JSON"); // 另外支持JSON格式
+        return parameterMap;
+    }
+
+    /**
+     * @Title: sendAsyncSnapshotJob @Description: 获取发送视频批量截图的任务的URL,此接口为异步任务 @param
+     * fileName 要截图的视频的路径,如 longvideo/video/XXXXX,其中XXXXX为视频名称 @param
+     * snapshotPath 截图后的图片输出路径,如
+     * longvideo/snapshot/XXXXX,截图时会自动在文件名后面加序号,输出路径为空时,自动指定为longvideo/snapshot/XXXXX @param
+     * startMillseconds 截图时间,单位毫秒 @param interval
+     * 截图间隔时间,若指定则表示异步模式序列截图,Interval必须大于等于0,默认10,单位秒。其中Interval=0表示根据视频时长平均截图。 @param
+     * snapshotNumber
+     * 截图数量,若指定则表示异步模式序列截图,且必须大于0,当Time+Interval*Num的截取点超过视频时长时,后续截图自动失效,截图完成时返回实际截取的个数;当Num=1,忽略Interval参数,表示异步单张截图 @return @throws
+     * Exception @author 王树杞 @date 2018年7月27日 下午5:17:31 @version 2.0 @throws
+     */
+    public static String sendAsyncSnapshotJob(String fileName, String snapshotPath, Integer startMillseconds,
+                                              Integer interval, Integer snapshotNumber) throws Exception {
+
+        // 首先获取公众参数
+        Map<String, String> parameterMap = buildCommonParameters();
+        parameterMap.put("Action", "SubmitSnapshotJob");
+
+        Map<String, String> inputParamsMap = new HashMap<>();
+        inputParamsMap.put("Bucket", AliOssFileTool.getBucket(false));
+        inputParamsMap.put("Location", AliOssFileTool.getSnapshotLocation());
+        inputParamsMap.put("Object", URLEncoder.encode(fileName, "UTF-8"));
+        parameterMap.put("Input", JSON.toJSONString(inputParamsMap));
+        Map<String, String> snapshotConfig = new HashMap<>();
+        Map<String, String> outPutFile = new HashMap<>();
+        outPutFile.put("Bucket", AliOssFileTool.getBucket(false));
+        outPutFile.put("Location", AliOssFileTool.getSnapshotLocation());
+        if (StringUtils.isBlank(snapshotPath)) {
+            snapshotPath = fileName.replace("/video/", "/snapshot/");
+        }
+        outPutFile.put("Object", URLEncoder.encode(snapshotPath + "_{Count}", "UTF-8"));
+        snapshotConfig.put("Interval", interval.toString());
+        snapshotConfig.put("Num", snapshotNumber.toString());
+        parameterMap.put("PipelineId", "abe6a0b9b9334858913eb416974485d2");
+
+        snapshotConfig.put("OutputFile", JSON.toJSONString(outPutFile));
+        parameterMap.put("SnapshotConfig", JSON.toJSONString(snapshotConfig));
+
+        String canonicalizedQueryString = buildCanonicalizedQueryString(parameterMap);
+        String stringToSign = buildStringToSign(canonicalizedQueryString);
+        String signature = buildSignature(AliOssConfig.getAccessKeySecret(), stringToSign);
+
+        return buildRequestURL(signature, parameterMap);
+
+    }
+
+    /**
+     * @Title: sendSyncSnapshotJob @Description: 获取发送某个视频的同步截图的任务URL地址 @param
+     * fileName 视频路径 @param snapshotPath 输出的截图路径 @param startMillseconds
+     * 截图的时间戳 @return 某个视频的同步截图的任务URL地址 @throws Exception @author 王树杞 @date
+     * 2018年7月27日 下午8:07:47 @version 2.0 @throws
+     */
+    public static String sendSyncSnapshotJob(String fileName, String snapshotPath, Integer startMillseconds)
+            throws Exception {
+
+        // 首先获取公众参数
+        Map<String, String> parameterMap = buildCommonParameters();
+        parameterMap.put("Action", "SubmitSnapshotJob");
+
+        Map<String, String> inputParamsMap = new HashMap<>();
+        inputParamsMap.put("Bucket", AliOssFileTool.getBucket(false));
+        inputParamsMap.put("Location", AliOssFileTool.getSnapshotLocation());
+        inputParamsMap.put("Object", URLEncoder.encode(fileName, "UTF-8"));
+        parameterMap.put("Input", JSON.toJSONString(inputParamsMap));
+        Map<String, String> snapshotConfig = new HashMap<>();
+        Map<String, String> outPutFile = new HashMap<>();
+        outPutFile.put("Bucket", AliOssFileTool.getBucket(false));
+        outPutFile.put("Location", AliOssFileTool.getSnapshotLocation());
+        if (StringUtils.isBlank(snapshotPath)) {
+            snapshotPath = fileName.replace("/video/", "/snapshot/");
+        }
+        outPutFile.put("Object", URLEncoder.encode(snapshotPath, "UTF-8"));
+        snapshotConfig.put("Time", startMillseconds.toString());
+        snapshotConfig.put("OutputFile", JSON.toJSONString(outPutFile));
+        parameterMap.put("SnapshotConfig", JSON.toJSONString(snapshotConfig));
+
+        String canonicalizedQueryString = buildCanonicalizedQueryString(parameterMap);
+        String stringToSign = buildStringToSign(canonicalizedQueryString);
+        String signature = buildSignature(AliOssConfig.getAccessKeySecret(), stringToSign);
+
+        return buildRequestURL(signature, parameterMap);
+
+    }
+
+    private static String percentEncode(String value) throws UnsupportedEncodingException {
+        return URLEncoder.encode(value, ENCODE_TYPE).replace("+", "%20").replace("*", "%2A").replace("%7E", "~");
+    }
+
+    private static String buildCanonicalizedQueryString(Map<String, String> parameterMap)
+            throws UnsupportedEncodingException {
+        // 对参数进行排序
+        List<String> sortedKeys = new ArrayList<String>(parameterMap.keySet());
+        Collections.sort(sortedKeys);
+        StringBuilder temp = new StringBuilder();
+        for (String key : sortedKeys) {
+            // 此处需要对key和value进行编码
+            String value = parameterMap.get(key);
+            temp.append(SEPARATOR).append(percentEncode(key)).append(EQUAL).append(percentEncode(value));
+        }
+        return temp.toString().substring(1);
+    }
+
+    private static String buildStringToSign(String canonicalizedQueryString) throws UnsupportedEncodingException {
+        // 生成stringToSign字符
+        StringBuilder temp = new StringBuilder();
+        temp.append(HTTP_METHOD).append(SEPARATOR);
+        temp.append(percentEncode("/")).append(SEPARATOR);
+        // 此处需要对canonicalizedQueryString进行编码
+        temp.append(percentEncode(canonicalizedQueryString));
+        return temp.toString();
+    }
+
+    private static String buildSignature(String keySecret, String stringToSign)
+            throws UnsupportedEncodingException, InvalidKeyException, NoSuchAlgorithmException {
+        SecretKey key = new SecretKeySpec((keySecret + SEPARATOR).getBytes(ENCODE_TYPE), SignatureMethod.HMAC_SHA1);
+        Mac mac = Mac.getInstance(ALGORITHM);
+        mac.init(key);
+        byte[] hashBytes = mac.doFinal(stringToSign.toString().getBytes(ENCODE_TYPE));
+        byte[] base64Bytes = new Base64().encode(hashBytes);
+        String base64UTF8String = new String(base64Bytes, "utf-8");
+        return URLEncoder.encode(base64UTF8String, ENCODE_TYPE);
+    }
+
+    private static String buildRequestURL(String signature, Map<String, String> parameterMap)
+            throws UnsupportedEncodingException {
+        // 生成请求URL
+        StringBuilder temp = new StringBuilder("http://mts.cn-hangzhou.aliyuncs.com?");
+        temp.append(URLEncoder.encode("Signature", ENCODE_TYPE)).append("=").append(signature);
+        for (Map.Entry<String, String> e : parameterMap.entrySet()) {
+            temp.append("&").append(percentEncode(e.getKey())).append("=").append(percentEncode(e.getValue()));
+        }
+        return temp.toString();
+    }
+
+    public static String saveInputStreamPic(InputStream inputStream, String bucket, String project, String dir,
+                                            String key) throws OSSException, ClientException, IOException {
+        String fullKey = project + "/" + dir + "/" + key;
+        ObjectMetadata objectMetadata = new ObjectMetadata();
+        objectMetadata.setContentType("image/jpg");
+        getOssClient().putObject(bucket, fullKey, inputStream, objectMetadata);
+        return fullKey;
+    }
+
+    public static String sendImageToAliOss(BufferedImage image) throws Exception {
+
+        ByteArrayOutputStream os = new ByteArrayOutputStream();
+        ImageIO.write(image, "jpg", os);
+        InputStream is = new ByteArrayInputStream(os.toByteArray());
+        // 将生成的图片往OSS里面上传
+        String random = UUID.randomUUID().toString().replaceAll("-", "") + new Random().nextLong();
+        String key = Md5Util.encoderByMd5(random);
+        String coverImgPath = AliOssFileTool.saveVideoCoverImagFromInternal(is, key);
+        return coverImgPath;
+    }
+
+    public static String saveVideoCoverImagFromInternal(InputStream inputStream, String key)
+            throws OSSException, ClientException, IOException {
+        String bucketName = AliOssConfig.getBucket(EnumAllBuckets.PUBVIDEOBUCKET.getBucketName());
+        String fullKey = getProjectName() + "/pic/video/cover/" + DateUtils.dateToStringyyyyMMdd(new Date()) + "/"
+                + key;
+        ObjectMetadata objectMetadata = new ObjectMetadata();
+        objectMetadata.setContentType("image/jpg");
+        getInternalOssClient().putObject(bucketName, fullKey, inputStream, objectMetadata);
+        return fullKey;
+
+    }
+
+    /**
+     * @param duration   时长,如果是图片则传0,视频则传视频时长
+     * @param isInternal 是否内网访问
+     * @return
+     * @return String
+     * @Title: generatePresignedUrl
+     * @Description:得到对象的临时访问URL
+     * @author 王树杞
+     * @date 2019年2月25日 上午11:04:22
+     * @version 2.0
+     */
+    public static String generatePresignedUrl(String filePath, Integer duration, boolean isInternal) {
+
+        if (StringUtils.isEmpty(filePath)) {
+            return null;
+        }
+
+        if (filePath.startsWith("http")) {
+            filePath = filePath.replace(PropertiesUtils.getDownloadDomain(), "");
+            filePath = filePath.replace("https://weappupload.piaoquantv.com/", "");
+            filePath = filePath.replace("https://testweappupload.yishihui.com/", "");
+            filePath = filePath.replace("https://lvupload.piaoquantv.com/", "");
+        }
+
+        if (filePath.contains("Signature") && filePath.contains("OSSAccessKeyId")) {
+            return filePath;
+        }
+
+        if (duration == null) {
+            duration = 0;
+        }
+        // 设置URL过期时间为视频时长+3天。
+        // 单位为秒;
+        Date expiration = new Date(new Date().getTime() + (duration + (3 * 24 * 60 * 60)) * 1000);
+//		//一分钟
+//		Date expiration = new Date(new Date().getTime() +( 60) * 1000);
+        // 生成以GET方法访问的签名URL,访客可以直接通过浏览器访问相关内容。
+
+        URL url = getOssReadOnlyClient().generatePresignedUrl(BasePropertiesUtils.getVideoBucket(),
+                filePath, expiration, HttpMethod.GET);
+        String path = url.getFile();
+        if (StringUtils.startsWith(path, "/")) {
+            return StringUtils.substring(path, 1);
+        }
+        return path;
+
+    }
+
+    public static String saveObjectFromInternal(InputStream inputStream, String fullKey)
+            throws OSSException, ClientException, IOException {
+        String bucketName = BasePropertiesUtils.getVideoBucket();
+        getInternalOssClient().putObject(bucketName, fullKey, inputStream);
+        return fullKey;
+    }
+
+    public static String saveObjectFromInternal(String bucketName, String url, String fullKey)
+            throws Exception {
+        InputStream inputStream = new URL(url).openStream();
+        getInternalOssClient().putObject(bucketName, fullKey, inputStream);
+        if (inputStream != null) {
+            inputStream.close();
+        }
+        return fullKey;
+    }
+
+    public static boolean checkObjectExistFromInternal(String bucketName, String fullKey) {
+        return getInternalOssClient().doesObjectExist(bucketName, fullKey);
+    }
+
+    public static String uploadShangHaiVideo(InputStream inputStream, String key, String bucketName) {
+        ObjectMetadata objectMetadata = new ObjectMetadata();
+        objectMetadata.setContentType("video/mp4");
+        getShangHaiInternalOssClient().putObject(bucketName, key, inputStream, objectMetadata);
+        return key;
+    }
+
+    public static boolean isShangHaiExist(String bucket, String fullKey) {
+        return getShangHaiInternalOssClient().doesObjectExist(bucket, fullKey);
+    }
+
+    public static Boolean isStorageClassArchive(String bucket, String key) throws OSSException, ClientException {
+        ObjectMetadata objectMetadata = getInternalOssClient().getObjectMetadata(bucket, key);
+        StorageClass storageClass = objectMetadata.getObjectStorageClass();
+        return storageClass == StorageClass.Archive;
+    }
+
+    public static boolean isStorageClassColdArchive(String bucket, String key) throws OSSException, ClientException {
+        ObjectMetadata objectMetadata = getInternalOssClient().getObjectMetadata(bucket, key);
+        StorageClass storageClass = objectMetadata.getObjectStorageClass();
+        return storageClass == StorageClass.ColdArchive;
+    }
+
+    public static void setStorageClassToArchive(String bucket, String key) throws OSSException, ClientException {
+        // 创建CopyObjectRequest对象。
+        CopyObjectRequest request = new CopyObjectRequest(bucket, key, bucket, key);
+
+        // 创建ObjectMetadata对象。
+        ObjectMetadata objectMetadata = new ObjectMetadata();
+
+        // 封装header,此处以设置存储类型为归档类型为例。
+        objectMetadata.setHeader("x-oss-storage-class", StorageClass.Archive);
+        request.setNewObjectMetadata(objectMetadata);
+
+        // 更改文件存储类型。
+        CopyObjectResult result = getInternalOssClient().copyObject(request);
+    }
+
+    public static void recoverVideoAsync(String bucket, String srckey) {
+        OSSClient ossInternalClient = getInternalOssClient();
+        ObjectMetadata objectMetadata = ossInternalClient.getObjectMetadata(bucket, srckey);
+        // 校验文件是否为归档文件。
+        StorageClass storageClass = objectMetadata.getObjectStorageClass();
+        if (storageClass == StorageClass.Archive) {
+            // 解冻文件。
+            try {
+                ossInternalClient.restoreObject(bucket, srckey);
+            } catch (Exception e) {
+                // TODO OSS Auto-generated catch blockvideo-common/src/main/java/com/weiqu/video/common/enums/ExceptionCodeEnum.java
+//				e.printStackTrace();
+                logger.error("视频可能在解冻中");
+            }
+        } else if (storageClass == StorageClass.ColdArchive) {
+            // 解冻文件。
+            try {
+                RestoreJobParameters parameters = new RestoreJobParameters(RestoreTier.RESTORE_TIER_EXPEDITED);
+                RestoreConfiguration configuration = new RestoreConfiguration(3, parameters);
+                ossInternalClient.restoreObject(bucket, srckey, configuration);
+            } catch (Exception ex) {
+                logger.error("视频可能在解冻中", ex);
+            }
+        }
+    }
+
+    public static Boolean isRestoreCompleted(String bucket, String key) throws OSSException, ClientException {
+        ObjectMetadata objectMetadata = getInternalOssClient().getObjectMetadata(bucket, key);
+        Boolean isRestoreCompleted = false;
+        try {
+            isRestoreCompleted = objectMetadata.isRestoreCompleted();
+        } catch (Exception e) {
+            // TODO OSS Auto-generated catch block
+//			e.printStackTrace();
+            logger.error("视频可能在解冻中");
+        }
+
+        return isRestoreCompleted;
+    }
+
+    /**
+     * @param path
+     * @param path
+     * @return
+     * @ApiModelProperty(value="冻结状态: -1:无须解冻  0:待解冻  1:解冻中  2:解冻完成")
+     */
+    public static Integer getFrozenStatus(String path) {
+        if (path.startsWith("http") && !path.startsWith(PropertiesUtils.getDownloadDomain())) {
+            logger.info("getFrozenStatus-outer size video, path :" + path);
+            return -1;
+        }
+        String realPath = path.replace(PropertiesUtils.getDownloadDomain(), "");
+        String bucketName = BasePropertiesUtils.getVideoBucket();
+        ObjectMetadata objectMetadata = null;
+        try {
+            objectMetadata = getInternalOssClient().getObjectMetadata(bucketName, realPath);
+        } catch (Exception ex) {
+            logger.error("getFrozenStatus-error", ex);
+            return -1;
+        }
+
+        if (Objects.isNull(objectMetadata)) {
+            logger.error("getFrozenStatus-error, path :" + path + " do not has metaData");
+            return -1;
+        }
+        StorageClass storageClass = objectMetadata.getObjectStorageClass();
+        if (storageClass != StorageClass.Archive && storageClass != StorageClass.ColdArchive) {
+            logger.info("getFrozenStatus, path :" + path + " do not in Archive status");
+            return -1;
+        }
+        try {
+            // 解冻完成
+            boolean isRestoreCompleted = objectMetadata.isRestoreCompleted();
+            if (isRestoreCompleted) {
+                return 2;
+            } else {
+                return 1;
+            }
+        } catch (NullPointerException e) {
+            logger.info("getFrozenStatus, path :" + path + " waiting for forzed!");
+            return 0;
+        }
+    }
+
+
+    public static Map<String, Object> getMediaAllInfo(String inPath) {
+        return getMediaAllInfo(inPath, BasePropertiesUtils.getVideoBucket());
+    }
+
+    public static Map<String, Object> getMediaAllInfo(String inPath, String bucketName) {
+        Map<String, Object> map = new HashMap<String, Object>();
+        String encodedInput;
+        try {
+            encodedInput = URLEncoder.encode(inPath, "utf-8");
+        } catch (UnsupportedEncodingException e) {
+            logger.error("获取元数据输入流解析错误", e);
+            map.put("status", "1");
+            return map;
+        }
+        SubmitMediaInfoJobRequest request = new SubmitMediaInfoJobRequest();
+        JSONObject input = new JSONObject();
+        input.put("Bucket", bucketName);
+        input.put("Location", PropertiesUtils.getTranscodeLocation());
+        input.put("Object", encodedInput);
+        request.setInput(input.toJSONString());
+        request.setPipelineId(PropertiesUtils.getTranscodePipelineId());
+        try {
+            SubmitMediaInfoJobResponse response = new SubmitMediaInfoJobResponse();
+            response = AliOssConfig.getIAcsClient().getAcsResponse(request);
+            if (response.getMediaInfoJob().getState() != null
+                    && response.getMediaInfoJob().getState().equalsIgnoreCase("Fail")) {
+                logger.error("获取元数据错误:" + JSONObject.toJSONString(response.getMediaInfoJob())
+                        + ",requestId:" + response.getRequestId());
+                map.put("status", "2");
+                return map;
+            }
+            Properties properties = response.getMediaInfoJob().getProperties();
+            Properties.Streams.VideoStream videoStream = null;
+            if (CollectionUtils.isNotEmpty(properties.getStreams().getVideoStreamList())) {
+                videoStream = properties.getStreams().getVideoStreamList().get(0);
+            }
+            Properties.Format format = properties.getFormat();
+            logger.info("videoStream:" + JSONObject.toJSONString(videoStream));
+            logger.info("format:" + JSONObject.toJSONString(format));
+            map.put("status", "0");
+            if (properties.getWidth() != null) {
+                map.put("width", Integer.parseInt(properties.getWidth()) + "");
+            }
+            if (properties.getHeight() != null) {
+                map.put("height", Integer.parseInt(properties.getHeight()) + "");
+            }
+            if (videoStream != null) {
+                map.put("rotate", videoStream.getRotate());
+                map.put("codeName", videoStream.getCodecName());
+            }
+            if (format != null) {
+                map.put("size", format.getSize());
+                //设置成long类型的 大于0少于1的设置成1
+                if (Double.parseDouble(format.getDuration()) > 0 && Double.parseDouble(format.getDuration()) < 1) {
+                    map.put("duration", new Double(1).longValue());
+                } else {
+                    map.put("duration", StringUtils.isEmpty(format.getDuration()) ? 0 : new Double(Double.parseDouble(format.getDuration())).longValue());
+                }
+                map.put("bitRate", StringUtils.isEmpty(format.getBitrate()) ? 0 : new Double(Double.parseDouble(format.getBitrate())).longValue() * 1000);
+            }
+        } catch (ServerException e) {
+            logger.error("获取元数据服务器异常", e);
+            map.put("status", "3");
+        } catch (ClientException e) {
+            logger.error("获取元数据客户端异常", e);
+            map.put("status", "4");
+        } catch (Exception e) {
+            logger.error("获取元数据异常", e);
+            map.put("status", "5");
+        }
+        return map;
+    }
+
+    public static CopyObjectResult setStorageClassToColdArchive(String bucket, String key) {
+        CopyObjectRequest request = new CopyObjectRequest(bucket, key, bucket, key);
+        ObjectMetadata objectMetadata = new ObjectMetadata();
+
+        // 封装header,此处以设置存储类型为归档类型为例。
+        objectMetadata.setHeader("x-oss-storage-class", StorageClass.ColdArchive);
+        request.setNewObjectMetadata(objectMetadata);
+
+        // 更改文件存储类型。
+        return getInternalOssClient().copyObject(request);
+    }
+
+    public static String getRandomObjectKey(Integer fileType) {
+        String env = PropertiesUtils.getProjectEnv();
+        if (StringUtils.isEmpty(env)) {
+            env = "qa";
+        }
+        String fileName = RandomUtil.generate18String();
+        if (EnumUploadFileType.VIDEO.getIntType().equals(fileType)) {
+            fileName = "/video/" + env + "/" + DateUtils.dateToStringyyyyMMdd(new Date()) + "/" + fileName;
+        } else if (EnumUploadFileType.VOICE.getIntType().equals(fileType)) {
+            fileName = "/voice/" + env + "/" + DateUtils.dateToStringyyyyMMdd(new Date()) + "/" + fileName;
+        } else {
+            fileName = "/pic/" + env + "/" + DateUtils.dateToStringyyyyMMdd(new Date()) + "/" + fileName;
+        }
+        return getProjectName() + fileName;
+    }
+
+    public static StsTokenVO getStsToken(String fileName, Long durationSeconds) {
+
+        String endpoint = BasePropertiesUtils.getLongvideoStsEndpoint();
+        String accessKeyId = BasePropertiesUtils.getLongvideoStsAccessKeyId();
+        String accessKeySecret = BasePropertiesUtils.getLongvideoStsAccessKeySecret();
+        String roleArn = BasePropertiesUtils.getLongvideoStsRoleArn();
+        String roleSessionName = BasePropertiesUtils.getLongvideoStsSessionName();
+        StsTokenVO result = new StsTokenVO();
+        try {
+            String region = "cn-hangzhou";
+            DefaultProfile.addEndpoint("", "", "Sts", endpoint);
+            IClientProfile profile = DefaultProfile.getProfile("", accessKeyId, accessKeySecret);
+            DefaultAcsClient client = new DefaultAcsClient(profile);
+            final AssumeRoleRequest request = new AssumeRoleRequest();
+            request.setMethod(MethodType.POST);
+            request.setRoleArn(roleArn);
+            request.setRoleSessionName(roleSessionName);
+            //设置过期时间 临时访问凭证的有效时间,单位为秒。最小值为900,最大值以当前角色设定的最大会话时间为准。角色最大会话时间取值范围为3600秒~43200秒
+            if (Objects.nonNull(durationSeconds)) {
+                request.setDurationSeconds(durationSeconds);
+            }
+            JSONObject policy = getUploadFileStsPolicy(BasePropertiesUtils.getVideoBucket(), fileName);
+            request.setPolicy(policy.toJSONString());
+
+            final AssumeRoleResponse response = client.getAcsResponse(request);
+            result.setExpiration(response.getCredentials().getExpiration());
+            result.setAccessKeyId(response.getCredentials().getAccessKeyId());
+            result.setAccessKeySecret(response.getCredentials().getAccessKeySecret());
+            result.setSecurityToken(response.getCredentials().getSecurityToken());
+            result.setRequestId(response.getRequestId());
+            result.setFileName(fileName);
+            result.setHost(AliOssConfig.getUploadDomain());
+
+            String host0 = BasePropertiesUtils.getOssaccelerateUploadDomain();
+            String host1 = "https://" + AliOssConfig.getBucket(EnumPublicBuckets.PUBBUCKET.getBucketName()) + "." + AliOssConfig.getOssEndPoint() + "/";
+            result.setHosts(new String[]{host0, host1});
+
+            result.setBucket(BasePropertiesUtils.getVideoBucket());
+            result.setRegion(region);
+            result.setCname(true);
+
+            // 返回服务器当前时间
+            result.setServerTimestamp(System.currentTimeMillis());
+
+        } catch (com.aliyuncs.exceptions.ClientException e) {
+            logger.error(String.format("获取临时令牌失败,errorCode:%s;errorMessage:%s;requestId:%s",
+                    e.getErrCode(), e.getErrMsg(), e.getRequestId()));
+        } catch (Exception e) {
+            logger.error("获取临时令牌失败", e);
+        }
+        return result;
+    }
+
+    private static JSONObject getUploadFileStsPolicy(String bucketName, String fileName) {
+
+        JSONObject policy = new JSONObject();
+        policy.put("Version", "1");
+
+        JSONArray statements = new JSONArray();
+        JSONObject statement = new JSONObject();
+        statement.put("Effect", "Allow");
+
+        JSONArray action = new JSONArray();
+        action.add("oss:PutObject");
+
+        statement.put("Action", action);
+        JSONArray resource = new JSONArray();
+        resource.add("acs:oss:*:1894469520484605:" + bucketName + "/" + fileName);
+        statement.put("Resource", resource);
+
+        statements.add(statement);
+        policy.put("Statement", statements);
+
+        logger.info(String.format("Policy:%s", JSON.toJSONString(policy)));
+        return policy;
+    }
+
+}

+ 72 - 0
core/src/main/java/com/tzld/supply/util/BasePropertiesUtils.java

@@ -0,0 +1,72 @@
+package com.tzld.supply.util;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Properties;
+
+@Slf4j
+public class BasePropertiesUtils {
+
+    public static Properties properties;
+
+    static {
+        Properties mainProperties = new Properties();
+        properties = new Properties();
+        try {
+            InputStream mainStream = BasePropertiesUtils.class.getClassLoader().getResourceAsStream("application.properties");
+            mainProperties.load(mainStream);
+            // 先通过虚拟机参数 -Denv=value去取配置文件的值
+            // 如果没有设置-Denv的虚拟机参数则说明是本地开发环境,则读取application.properties文件中spring.profiles.active的值
+            String env = System.getProperty("env");
+            if (StringUtils.isEmpty(env)) {
+                env = mainProperties.getProperty("spring.profiles.active");
+            }
+            if (StringUtils.isEmpty(env)) {
+                log.error("设置spring.profiles.active or 设置虚拟机启动参数 -Denv错误!!!");
+                System.exit(1);
+            }
+            InputStream activeStream = BasePropertiesUtils.class.getClassLoader().getResourceAsStream("application.properties");
+            properties.load(activeStream);
+            log.info("开发环境为: " + env);
+        } catch (IOException e) {
+            log.error(e.getMessage());
+            System.exit(1);
+        }
+    }
+
+    public static Properties getProperties() {
+        return properties;
+    }
+
+    public static String getVideoBucket() {
+        return getProperties().getProperty("oss.video.bucket");
+    }
+
+    public static String getLongvideoStsEndpoint() {
+        return getProperties().getProperty("oss.longvideo.video.sts.endpoint");
+    }
+
+    public static String getLongvideoStsAccessKeyId() {
+        return getProperties().getProperty("oss.longvideo.video.sts.accessKeyId");
+    }
+
+    public static String getLongvideoStsAccessKeySecret() {
+        return getProperties().getProperty("oss.longvideo.video.sts.accessKeySecret");
+    }
+
+    public static String getLongvideoStsRoleArn() {
+        return getProperties().getProperty("oss.longvideo.video.sts.roleArn");
+    }
+
+    public static String getLongvideoStsSessionName() {
+        return getProperties().getProperty("oss.longvideo.video.sts.roleSessionName");
+    }
+
+    public static String getOssaccelerateUploadDomain() {
+        return getProperties().getProperty("oss.accelerate.upload.domain");
+    }
+
+}

+ 139 - 0
core/src/main/java/com/tzld/supply/util/BaseUtils.java

@@ -0,0 +1,139 @@
+package com.tzld.supply.util;
+
+import org.apache.http.util.TextUtils;
+import org.springframework.beans.BeanUtils;
+import org.springframework.util.StringUtils;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.Objects;
+import java.util.UUID;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class BaseUtils {
+
+    public static void copyProperties(Object orig, Object dest) {
+        // spring的BeanUtils性能较好
+        BeanUtils.copyProperties(orig, dest);
+    }
+
+    public static String getUUID() {
+        return UUID.randomUUID().toString();
+    }
+
+    public static String getUUIDStr() {
+        return UUID.randomUUID().toString().replaceAll("-", "");
+    }
+
+    private static String[] chars = new String[]{"a", "b", "c", "d", "e", "f",
+            "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s",
+            "t", "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5",
+            "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I",
+            "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V",
+            "W", "X", "Y", "Z"};
+
+    public static String getShortUUIDStr() {
+        StringBuffer shortBuffer = new StringBuffer();
+        String uuid = UUID.randomUUID().toString().replace("-", "");
+        for (int i = 0; i < 8; i++) {
+            String str = uuid.substring(i * 4, i * 4 + 4);
+            int x = Integer.parseInt(str, 16);
+            shortBuffer.append(chars[x % 0x3E]);
+        }
+        return shortBuffer.toString();
+    }
+
+    public static String getShortUUIDStr_6() {
+        StringBuffer shortBuffer = new StringBuffer();
+        String uuid = UUID.randomUUID().toString().replace("-", "");
+        System.out.println("uuid = " + uuid.length() + " , chars = " + chars.length);
+        for (int i = 0; i < 6; i++) {
+            String str = uuid.substring(i * 5, i * 5 + 5);
+            int x = Integer.parseInt(str, 16);
+            shortBuffer.append(chars[x % 0x3E]);
+        }
+        return shortBuffer.toString();
+    }
+
+    /**
+     * 带时间戳的uuid
+     *
+     * @return
+     */
+    public static String getTimestampUUIDStr() {
+        return UUID.randomUUID().toString().replaceAll("-", "") + System.currentTimeMillis();
+    }
+
+    public static boolean isUidAvailable(Long uid) {
+        return Objects.nonNull(uid) && uid >= 0;
+    }
+
+    public static boolean checkEmailAddressAvailable(String email) {
+        String check = "^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$";
+        Pattern regex = Pattern.compile(check);
+        Matcher matcher = regex.matcher(email);
+        return matcher.matches();
+    }
+
+    public static int getInt(String intValue, int defaultValue) {
+        try {
+            return Integer.parseInt(intValue);
+        } catch (NumberFormatException e) {
+            e.printStackTrace();
+        }
+        return defaultValue;
+    }
+
+    public static boolean isContainChinese(String str) {
+        if (TextUtils.isEmpty(str)) {
+            return false;
+        }
+        try {
+            Pattern p = Pattern.compile("[\u4E00-\u9FA5|\\!|\\,|\\。|\\(|\\)|\\《|\\》|\\“|\\”|\\?|\\:|\\;|\\【|\\】]");
+            Matcher m = p.matcher(str);
+            if (m.find()) {
+                return true;
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return false;
+    }
+
+    public static String calcRate(Integer a, Integer b) {
+        if (a == null || b == null || b == 0) {
+            return "0";
+        }
+        return new BigDecimal(a).divide(new BigDecimal(b), 2, RoundingMode.HALF_UP).toString();
+    }
+
+    public static boolean isNumberStr(String str){
+        Pattern pattern = Pattern.compile("[0-9]*");
+        return pattern.matcher(str).matches();
+    }
+
+    public static String getFirstGroupPlanExeId(String planExeId) {
+        if (StringUtils.hasText(planExeId) && planExeId.contains("_")) {
+            return planExeId.substring(0, planExeId.indexOf("_"));
+        }
+        return planExeId;
+    }
+
+    public static boolean isFirstGroupPlanExeId(String planExeId) {
+        if (StringUtils.hasText(planExeId) && planExeId.contains("_")) {
+            return false;
+        }
+        return true;
+    }
+
+    public static void main(String[] args) {
+        System.out.println(checkEmailAddressAvailable("a-a_a@q-q.A"));
+        System.out.println(getUUIDStr());
+        System.out.println(getShortUUIDStr());
+        System.out.println(getShortUUIDStr_6());
+        System.out.println(isNumberStr("20240201052729691500183"));
+        System.out.println(getFirstGroupPlanExeId("20240314125002672235936_2"));
+        System.out.println("20240314125002672235936_2".substring("20240314125002672235936_2".indexOf("_") + 1));
+    }
+}

+ 169 - 0
core/src/main/java/com/tzld/supply/util/CdnUtil.java

@@ -0,0 +1,169 @@
+package com.tzld.supply.util;
+
+import cn.hutool.core.net.URLEncodeUtil;
+import com.alibaba.fastjson.JSON;
+import com.aliyuncs.DefaultAcsClient;
+import com.aliyuncs.IAcsClient;
+import com.aliyuncs.cdn.model.v20141111.DescribeDomainBpsDataRequest;
+import com.aliyuncs.cdn.model.v20141111.DescribeDomainBpsDataResponse;
+import com.aliyuncs.exceptions.ClientException;
+import com.aliyuncs.exceptions.ServerException;
+import com.aliyuncs.profile.DefaultProfile;
+import com.stuuudy.commons.external.filestorage.enums.EnumFileType;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.Objects;
+
+public class CdnUtil {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(CdnUtil.class);
+
+//    public static final String VIDEO_CDN_URL_HOST = "https://xycdn.yishihui.com/";
+    public static final String VIDEO_CDN_URL_HOST = "http://rescdn.yishihui.com/";
+
+    public static final String PICTURE_CDN_URL_HOST_PICTURE = "http://rescdn.yishihui.com/";
+    public static final String DOWNLOAD_CDN_URL_HOST_PICTURE = "https://rescdn.yishihui.com/";
+
+
+    private static IAcsClient client;
+
+    static {
+        if (Objects.isNull(client)) {
+            DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou", "LTAInlg8tWvBeiJe", "hDu5v2nrOmdY0AjlqGnZkW57mxCOLO");
+            /** use STS Token
+             DefaultProfile profile = DefaultProfile.getProfile(
+             "<your-region-id>",           // The region ID
+             "<your-access-key-id>",       // The AccessKey ID of the RAM account
+             "<your-access-key-secret>",   // The AccessKey Secret of the RAM account
+             "<your-sts-token>");          // STS Token
+             **/
+            client = new DefaultAcsClient(profile);
+        }
+    }
+
+
+    public static Double getDescribeDomainBpsData() {
+        DescribeDomainBpsDataRequest request = new DescribeDomainBpsDataRequest();
+        //需要时区为GMT
+        LocalDateTime endTime = LocalDateTime.now().plusHours(-8);
+        LocalDateTime startTime = endTime.plusMinutes(-10);
+        DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+        String[] starts = startTime.format(df).split(" ");
+        String[] ends = endTime.format(df).split(" ");
+        String start = starts[0] + "T" + starts[1] + "Z";
+        String end = ends[0] + "T" + ends[1] + "Z";
+        request.setStartTime(start);
+        request.setEndTime(end);
+        LOGGER.info("getDescribeDomainBpsData request = {}", JSON.toJSONString(request));
+        try {
+            DescribeDomainBpsDataResponse response = client.getAcsResponse(request);
+            LOGGER.info("getDescribeDomainBpsData response = {}", JSON.toJSONString(response));
+            if (Objects.isNull(response) || Objects.isNull(response.getBpsDataPerInterval())
+                    || response.getBpsDataPerInterval().isEmpty() || response.getBpsDataPerInterval().size() <= 1) {
+                //只有1条可能不准确
+                return null;
+            }
+            DescribeDomainBpsDataResponse.DataModule module1 = response.getBpsDataPerInterval().get(1);
+            if (Objects.nonNull(module1) && Objects.equals("0", module1.getValue())) {
+                return null;
+            }
+            DescribeDomainBpsDataResponse.DataModule module = response.getBpsDataPerInterval().get(0);
+            if (Objects.nonNull(module) && Objects.nonNull(module.getValue())) {
+                return Double.parseDouble(module.getValue()) / 1000 / 1000 / 1000;
+            }
+        } catch (ServerException e) {
+            LOGGER.error("getDescribeDomainBpsData ServerException", e);
+        } catch (ClientException e) {
+            LOGGER.error("getDescribeDomainBpsData ClientException", e);
+        }
+        return null;
+    }
+
+    public static String getCdnUrlHost(EnumFileType fileType) {
+        if (Objects.isNull(fileType)) {
+            return null;
+        }
+        switch (fileType) {
+            case VIDEO:
+                return VIDEO_CDN_URL_HOST;
+            case PICTURE:
+                return PICTURE_CDN_URL_HOST_PICTURE;
+        }
+        return null;
+    }
+
+
+    public static String getOssHttpUrl(String key) {
+        if (StringUtils.isBlank(key)) {
+            return key;
+        }
+        if (org.springframework.util.StringUtils.startsWithIgnoreCase(key, "http")) {
+            return key;
+        } else {
+            String url = DOWNLOAD_CDN_URL_HOST_PICTURE + key;
+            return URLEncodeUtil.encode(url);
+        }
+    }
+
+//    public static void main(String[] args_) throws Exception {
+//        DescribeDomainBpsDataRequest request = new DescribeDomainBpsDataRequest();
+//        //需要时区为GMT
+//        LocalDateTime endTime = LocalDateTime.now().plusHours(-8);
+//        LocalDateTime startTime = endTime.plusMinutes(-10);
+//        DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+//        String[] starts = startTime.format(df).split(" ");
+//        String[] ends = endTime.format(df).split(" ");
+//        String start = starts[0] + "T" + starts[1] + "Z";
+//        String end = ends[0] + "T" + ends[1] + "Z";
+//        request.setStartTime(start);
+//        request.setEndTime(end);
+//        System.out.println("---" + JSON.toJSONString(request));
+//        try {
+//            DescribeDomainBpsDataResponse response = client.getAcsResponse(request);
+//
+//            System.out.println("---" + JSON.toJSONString(response));
+//            DescribeDomainBpsDataResponse.DataModule module = response.getBpsDataPerInterval().get(0);
+//
+//            System.out.println("---" + module.getValue());
+//            double d = Double.valueOf(module.getValue()) / 1000 / 1000 / 1000;
+//            System.out.println(d);
+//
+////            System.out.println(new Gson().toJson(response));
+//        } catch (ServerException e) {
+//            e.printStackTrace();
+//        } catch (ClientException e) {
+//            System.out.println("ErrCode:" + e.getErrCode());
+//            System.out.println("ErrMsg:" + e.getErrMsg());
+//            System.out.println("RequestId:" + e.getRequestId());
+//        }
+//
+//    }
+
+    public static String getFileExtensionFromUrl(String uri) {
+        if (StringUtils.isBlank(uri)) {
+            return "";
+        }
+        String fileExtension = "";
+        try {
+            if (uri.contains(".")) {
+                int lastDotIndex = uri.lastIndexOf('.');
+                if (lastDotIndex > 0) {
+                    fileExtension = uri.substring(lastDotIndex + 1);
+                }
+            }
+        } catch (Exception e) {
+            LOGGER.error("getFileExtensionFromURL error", e);
+        }
+        return fileExtension;
+    }
+
+    public static void main(String[] args) {
+        String url = "ad/material_1564536000000.mp4";
+        System.out.println(getFileExtensionFromUrl(url));
+    }
+
+}

+ 15 - 0
core/src/main/java/com/tzld/supply/util/DateUtils.java

@@ -315,4 +315,19 @@ public final class DateUtils {
         ZoneId zone = ZoneId.of("Asia/Shanghai");
         return Date.from(localDate.atStartOfDay(zone).toInstant());
     }
+
+    /**
+     * 日期型转化成字符串类型
+     * @param date
+     * @return
+     */
+    public static String dateToStringyyyyMMdd(Date date) {
+        return new SimpleDateFormat("yyyyMMdd").format(date);
+    }
+
+    public static String formatIso8601Date(Date date) {
+        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
+        df.setTimeZone(new SimpleTimeZone(8, "GMT"));
+        return df.format(date);
+    }
 }

+ 30 - 0
core/src/main/java/com/tzld/supply/util/FileUtils.java

@@ -0,0 +1,30 @@
+package com.tzld.supply.util;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+public class FileUtils {
+
+    public static void deleteFileByIO(String filePath) {
+        File file = new File(filePath);
+        File[] list = file.listFiles();
+        if (list != null) {
+            for (File temp : list) {
+                deleteFileByIO(temp.getAbsolutePath());
+            }
+        }
+        file.delete();
+    }
+
+    public static void saveFileContent(String filePath, byte[] contentBytes) throws IOException {
+        Path path = Paths.get(filePath);
+        Path parentDir = path.getParent();
+        if (!Files.exists(parentDir)) {
+            Files.createDirectories(parentDir);
+        }
+        Files.write(path, contentBytes);
+    }
+}

文件差異過大導致無法顯示
+ 347 - 0
core/src/main/java/com/tzld/supply/util/ImageUtils.java


+ 92 - 0
core/src/main/java/com/tzld/supply/util/PropertiesUtils.java

@@ -0,0 +1,92 @@
+package com.tzld.supply.util;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Properties;
+
+@Slf4j
+public class PropertiesUtils {
+
+    public static Properties properties;
+
+    static {
+        Properties mainProperties = new Properties();
+        properties = new Properties();
+        try {
+            InputStream mainStream = PropertiesUtils.class.getClassLoader().getResourceAsStream("application.properties");
+            mainProperties.load(mainStream);
+            // 先通过虚拟机参数 -Denv=value去取配置文件的值
+            // 如果没有设置-Denv的虚拟机参数则说明是本地开发环境,则读取application.properties文件中spring.profiles.active的值
+            String env = System.getProperty("env");
+            if (StringUtils.isEmpty(env)) {
+                env = mainProperties.getProperty("spring.profiles.active");
+            }
+            if (StringUtils.isEmpty(env)) {
+                log.error("设置spring.profiles.active or 设置虚拟机启动参数 -Denv错误!!!");
+                System.exit(1);
+            }
+            InputStream activeStream = PropertiesUtils.class.getClassLoader().getResourceAsStream("application-" + env + ".properties");
+            properties.load(activeStream);
+            properties.put("spring.profiles.active", env);
+            log.info("开发环境为: " + env);
+        } catch (IOException e) {
+            log.error(e.getMessage());
+            System.exit(1);
+        }
+    }
+
+    public static Properties getProperties() {
+        return properties;
+    }
+
+    public static String getProjectEnv() {
+        return getProperties().getProperty("spring.profiles.active");
+    }
+
+    public static String getValue(String key) {
+        return PropertiesUtils.getProperties().getProperty(key);
+    }
+
+
+    public static boolean getSwaggerEnabled() {
+        return Boolean.parseBoolean(getProperties().getProperty("swagger.enabled"));
+    }
+
+    public static String getSwaggerBasePath() {
+        return getProperties().getProperty("swagger.basePath");
+    }
+
+
+    public static String getSwaggerProtocols() {
+        return getProperties().getProperty("swagger.protocols");
+    }
+
+
+    public static String getVideoBucket() {
+        return getProperties().getProperty("oss.longvideo.bucket");
+    }
+
+    public static String getDownloadDomain() {
+        return getProperties().getProperty("oss.longvideo.cdnDomain");
+    }
+
+    public static String getTranscodeLocation() {
+        return getProperties().getProperty("oss.longvideo.transcode.location");
+    }
+
+    public static String getTranscodePipelineId() {
+        return getProperties().getProperty("oss.longvideo.transcode.PipelineId");
+    }
+
+    public static String getReadOnlyAccessKeyId() {
+        return getProperties().getProperty("oss.video.readonly.accessKey");
+    }
+
+    public static String getReadOnlyAccessKeySecret() {
+        return getProperties().getProperty("oss.video.readonly.secretKey");
+    }
+
+}

+ 64 - 0
core/src/main/java/com/tzld/supply/util/RandomUtil.java

@@ -0,0 +1,64 @@
+package com.tzld.supply.util;
+
+import java.math.BigDecimal;
+import java.util.Calendar;
+import java.util.Random;
+import java.util.UUID;
+
+public class RandomUtil {
+
+	public static final String ALLNUMBER = "0123456789";  
+	public static final String ALLCHAR = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";  
+	public static final Integer FIXLENG=18;
+    /** 
+     * 返回一个定长的随机字符串(只包含大小写字母、数字)
+     *  随机字符串长度
+     * @return 随机字符串 
+     */  
+    public static String generate18String() {  
+        StringBuffer sb = new StringBuffer();  
+        Random random = new Random();  
+        for (int i = 0; i < FIXLENG; i++) {  
+            sb.append(ALLCHAR.charAt(random.nextInt(ALLCHAR.length())));  
+        }  
+        return sb.toString();  
+    } 
+    
+    public static void main(String[] args) {
+		//System.out.println(generate18String())
+        String proportion="0.01";
+        Long price=150l;
+        Double incomeMoney=price*(Double.valueOf(proportion));
+        System.out.println(incomeMoney);
+
+
+        BigDecimal b = new BigDecimal(incomeMoney);
+        Long t = b.setScale(0,BigDecimal.ROUND_HALF_UP).longValue();
+        System.out.println(t);
+
+    	
+	}
+    
+    public static String generateString(int length) {  
+        StringBuffer sb = new StringBuffer();  
+        Random random = new Random();  
+        for (int i = 0; i < length; i++) {  
+            sb.append(ALLCHAR.charAt(random.nextInt(ALLCHAR.length())));  
+        }  
+        return sb.toString();  
+    }
+    
+    public static String generateNumber(int length) {  
+        StringBuffer sb = new StringBuffer();  
+        Random random = new Random();  
+        for (int i = 0; i < length; i++) {  
+            sb.append(ALLNUMBER.charAt(random.nextInt(ALLNUMBER.length())));  
+        }  
+        return sb.toString();  
+    } 
+    
+    public static String getRandomUUIDStr() {
+		return UUID.randomUUID().toString().replaceAll("-", "")+Calendar.getInstance().getTimeInMillis();
+	}
+
+}

+ 257 - 0
core/src/main/java/com/tzld/supply/util/RedisUtils.java

@@ -0,0 +1,257 @@
+package com.tzld.supply.util;
+
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.RedisCallback;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.serializer.RedisSerializer;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+@Service
+public class RedisUtils {
+
+    private final static Logger log = LoggerFactory.getLogger(RedisUtils.class);
+
+    public final static Long DEFAULT_EXPIRE_TIME = 7L * 24 * 60 * 60; // 默认过期7天,单位秒
+
+    @Autowired
+    private RedisTemplate<String, String> redisTemplate;
+
+
+    public boolean containsKey(String key) {
+        if (StringUtils.isBlank(key)) {
+            log.error("containsKey is empty key:" + key);
+            return Boolean.FALSE;
+        }
+        return Boolean.TRUE.equals(redisTemplate.hasKey(key));
+    }
+
+    public String getString(String key) {
+        Object obj = redisTemplate.opsForValue().get(key);
+        return Objects.isNull(obj) ? null : obj.toString();
+    }
+
+    public Long getLong(String key) {
+        long longVal = 0L;
+        Object obj = redisTemplate.opsForValue().get(key);
+        if (Objects.nonNull(obj)) {
+            try {
+                longVal = Long.parseLong(obj.toString());
+            } catch (Exception e) {
+                e.printStackTrace();
+                return longVal;
+            }
+
+        }
+        return longVal;
+    }
+
+    public void setValueWithExpire(String key, String value, Long expireTime) {
+        redisTemplate.opsForValue().set(key, value, expireTime, TimeUnit.SECONDS);
+    }
+
+    public void setIncrementValue(String key, long value, Date date) {
+        Long expireTime;
+        if (date != null) {
+            expireTime = (date.getTime() - System.currentTimeMillis()) / 1000;
+            if (expireTime < 0) {
+                expireTime = DEFAULT_EXPIRE_TIME;
+            }
+        } else { // date为null
+            expireTime = DEFAULT_EXPIRE_TIME;
+        }
+        // 只在第一次进行设置过期时间
+        if (!containsKey(key)) {
+            redisTemplate.opsForValue().increment(key, value);
+            redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);
+        } else {
+            redisTemplate.opsForValue().increment(key, value);
+        }
+    }
+
+    public void setIncrementValue(String key, Integer value, Long expireTime) {
+        // 只在第一次进行设置过期时间
+        if (!containsKey(key)) {
+            redisTemplate.opsForValue().increment(key, value);
+            redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);
+        } else {
+            redisTemplate.opsForValue().increment(key, value);
+        }
+    }
+
+    public void setDecrementValue(String key, double value) {
+        redisTemplate.opsForValue().increment(key, -value);
+    }
+
+    public void setIncrementValue(String key, double value, Date date) {
+        Long expireTime;
+        if (date != null) {
+            expireTime = (date.getTime() - System.currentTimeMillis()) / 1000;
+            if (expireTime < 0) {
+                expireTime = DEFAULT_EXPIRE_TIME;
+            }
+        } else { // date为null
+            expireTime = DEFAULT_EXPIRE_TIME;
+        }
+        // 只在第一次进行设置过期时间
+        if (!containsKey(key)) {
+            redisTemplate.opsForValue().increment(key, value);
+            redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);
+        } else {
+            redisTemplate.opsForValue().increment(key, value);
+        }
+    }
+
+    public int getInteger(String key) {
+        Object obj = redisTemplate.opsForValue().get(key);
+        if (Objects.isNull(obj)) {
+            return 0;
+        }
+        return Integer.valueOf(obj.toString());
+    }
+
+    public Integer getIntegerO(String key) {
+        Object obj = redisTemplate.opsForValue().get(key);
+        if (Objects.isNull(obj)) {
+            return null;
+        }
+        return Integer.valueOf(obj.toString());
+    }
+
+    public void putDealyQueueMsg(String key, String value) {
+        redisTemplate.opsForZSet().add(key, value, System.currentTimeMillis() + DEFAULT_EXPIRE_TIME);
+    }
+
+    public Set<String> processDelayQueue(String key) {
+        long currentTimeMillis = System.currentTimeMillis();
+        Set<String> values = redisTemplate.opsForZSet().rangeByScore(key, 0, currentTimeMillis);
+        if (!CollectionUtils.isEmpty(values)) {
+            redisTemplate.opsForZSet().removeRangeByScore(key, 0, currentTimeMillis);
+        }
+        return values;
+    }
+
+    public void addVal(String key, String val) {
+        redisTemplate.opsForValue().set(key, val);
+    }
+
+    public Long getKeyExpire(String key) {
+        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
+    }
+
+    public void expire(String key, Long time) {
+        redisTemplate.expire(key, time, TimeUnit.SECONDS);
+    }
+
+    public Long countExistingKeys(List<String> keys) {
+        return redisTemplate.countExistingKeys(keys);
+    }
+
+    public boolean del(String key) {
+        return redisTemplate.delete(key);
+    }
+
+    public boolean set(String key, String value, long time) {
+        try {
+            if (time > 0) {
+                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
+            } else {
+                redisTemplate.opsForValue().set(key, value);
+            }
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * Redis 分布式锁
+     *
+     * @param key        锁键
+     * @param value      锁值,可以为随机数或者 UUID 等唯一标识符
+     * @param expireTime 锁过期时间,单位为秒
+     * @return true:获取锁成功,false:获取锁失败
+     */
+    public boolean tryLock(String key, String value, long expireTime) {
+        Boolean result = redisTemplate.opsForValue().setIfAbsent(key, value, expireTime, TimeUnit.SECONDS);
+        if (result != null && result) {
+            // 获取锁成功
+            return true;
+        }
+        return false;
+    }
+
+    public void listLeftPush(String key, String value) {
+        redisTemplate.opsForList().leftPush(key, value);
+
+    }
+
+    public String listRightPop(String key) {
+        return redisTemplate.opsForList().rightPop(key);
+    }
+
+    public Double getDouble(String key) {
+        try {
+            String val = redisTemplate.opsForValue().get(key);
+            if (StringUtils.isNotBlank(val)) {
+                return Double.valueOf(val);
+            }
+        } catch (Exception e) {
+            log.error("getDouble error redis key:" + key + "----" + e);
+        }
+        return null;
+    }
+
+    public String get(String key) {
+        String val = redisTemplate.opsForValue().get(key);
+        if (StringUtils.isNotBlank(val)) {
+            return val;
+        }
+        return null;
+    }
+
+    public void sAdd(String key, String val) {
+        redisTemplate.opsForSet().add(key, val);
+    }
+
+    public Set<String> sMembers(String key) {
+        return redisTemplate.opsForSet().members(key);
+    }
+
+
+    public Boolean sIsMember(String key, String val) {
+        return redisTemplate.opsForSet().isMember(key, val);
+    }
+
+    public void setBit(String key, long val, boolean flag) {
+        redisTemplate.opsForValue().setBit(key, val, flag);
+    }
+
+    public void setBitList(String key, Set<Long> indexs, boolean flag) {
+        RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
+        redisTemplate.executePipelined((RedisCallback<Object>) redisConnection -> {
+            indexs.forEach(x -> {
+                redisConnection.setBit(key.getBytes(), x, flag);
+            });
+            return null;
+        }, serializer);
+    }
+
+    public Boolean getBitMap(String key, long offset) {
+        return redisTemplate.opsForValue().getBit(key, offset);
+    }
+
+    public List<String> mGet(List<String> keys) {
+        return redisTemplate.opsForValue().multiGet(keys);
+    }
+}

+ 55 - 0
core/src/main/java/com/tzld/supply/util/TimelineUtils.java

@@ -0,0 +1,55 @@
+package com.tzld.supply.util;
+
+public class TimelineUtils {
+
+    public static void main(String[] args) {
+        System.out.println(coverSrtTimeToMillis("00:00:00.000", "\\."));
+    }
+
+    // 00:01:57,230 时间格式转成毫秒
+    public static int coverSrtTimeToMillis(String srtTime, String splitMs) {
+        String[] array = srtTime.split(splitMs);
+        String[] hhmmss = array[0].split(":");
+        return Integer.valueOf(hhmmss[0]) * 60 * 60 * 1000 + Integer.valueOf(hhmmss[1]) * 60 * 1000
+                + Integer.valueOf(hhmmss[2]) * 1000 + Integer.valueOf(array[1]);
+    }
+
+    public static String convertMillisToStringTime(int millis, String splitSSS) {
+        String hh = "";
+        int hhInt = 0;
+        String mm = "";
+        int mmInt = 0;
+        String ss = "";
+        int ssInt = millis / 1000;
+        String SSS = millis % 1000 + "";
+        if (ssInt >= 60) {
+            mmInt = ssInt / 60;
+            ss = ssInt % 60 + "";
+        } else {
+            ss = ssInt + "";
+        }
+        if (mmInt >= 60) {
+            hhInt = mmInt / 60;
+            mm = mmInt % 60 + "";
+        } else {
+            mm = mmInt + "";
+        }
+        hh = hhInt + "";
+        // 补0
+        if (SSS.length() == 1) {
+            SSS = "00" + SSS;
+        } else if (SSS.length() == 2) {
+            SSS = "0" + SSS;
+        }
+        if (ss.length() == 1) {
+            ss = "0" + ss;
+        }
+        if (mm.length() == 1) {
+            mm = "0" + mm;
+        }
+        if (hh.length() == 1) {
+            hh = "0" + hh;
+        }
+        return hh + ":" + mm + ":" + ss + splitSSS + SSS;
+    }
+}

+ 1668 - 0
core/src/main/java/com/tzld/supply/util/ffmpeg/FFmpegUtil.java

@@ -0,0 +1,1668 @@
+package com.tzld.supply.util.ffmpeg;
+
+import cn.hutool.core.collection.CollectionUtil;
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.tzld.supply.exception.FFmpegExecuteException;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.experimental.Accessors;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.io.IOUtils;
+import org.springframework.util.StringUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+@Slf4j
+public class FFmpegUtil {
+
+    /**
+     * 获取媒体信息(宽、高、时长)
+     *
+     * @param mediaUrl 媒体url,可以是网络url或者文件路径
+     * @return
+     */
+    public static MediaInfo getMediaInfo(String mediaUrl) {
+        MediaInfo mediaInfo = new MediaInfo();
+        // ffprobe -v quiet -print_format json -show_format -show_streams -i 1.mp4
+        List<String> commands = new ArrayList<>();
+        commands.add("ffprobe");
+        commands.add("-v");
+        commands.add("quiet");
+        commands.add("-print_format");
+        commands.add("json");
+        commands.add("-show_format");
+        commands.add("-show_streams");
+        commands.add("-i");
+        commands.add(mediaUrl);
+
+        FFmpegCommandResult ffmpegResult = executeFFmpegCommand(commands);
+        if (!ffmpegResult.isSuccessful()) {
+            throw new FFmpegExecuteException("getMediaInfo,ffmpeg执行失败");
+        }
+        if (ffmpegResult.isSuccessful()) {
+            JSONObject jsonObject = JSON.parseObject(ffmpegResult.getResultContent());
+            JSONObject formatJson = jsonObject.getJSONObject("format");
+            if (Objects.nonNull(formatJson)) {
+                String durationStr = formatJson.getString("duration");
+                if (StringUtils.hasText(durationStr)) {
+                    String[] split = durationStr.split("\\.");
+                    mediaInfo.setDuration(Integer.valueOf(split[0]) * 1000 + Integer.valueOf(split[1]) / 1000);
+                }
+                String sizeStr = formatJson.getString("size");
+                if (StringUtils.hasText(sizeStr)) {
+                    mediaInfo.setSize(Integer.valueOf(sizeStr));
+                }
+            }
+            JSONArray streams = jsonObject.getJSONArray("streams");
+            if (CollectionUtil.isNotEmpty(streams)) {
+                for (Object stream : streams) {
+                    JSONObject streamJson = (JSONObject) stream;
+                    if ("video".equals(streamJson.getString("codec_type"))) {
+                        mediaInfo.setWidth(streamJson.getInteger("width"));
+                        mediaInfo.setHeight(streamJson.getInteger("height"));
+                        mediaInfo.setAvgFrameRate(FFmpegUtil.parseAndCalcAvgFrameRate(streamJson.getString("avg_frame_rate")));
+                        mediaInfo.setTimeBase(FFmpegUtil.parseAndCalcTimeBase(streamJson.getString("time_base")));
+                    } else if ("audio".equals(streamJson.getString("codec_type"))) {
+                        mediaInfo.setSampleRate(streamJson.getInteger("sample_rate"));
+                    }
+                }
+            }
+        }
+        return mediaInfo;
+    }
+
+    /**
+     * 截取视频片段
+     *
+     * @param inputVideo
+     * @param startTime
+     * @param cutTime
+     * @param outputFilePath
+     * @return
+     */
+    public static void cutVideo(String inputVideo, String startTime, String cutTime, String outputFilePath) {
+        // ffmpeg -ss 00:00:00.000 -i 1.mp4 -t 5.100 -c:v copy -c:a copy -y output.mp4
+        List<String> commands = new ArrayList<>();
+        commands.add("ffmpeg");
+        commands.add("-ss");
+        commands.add(startTime);
+        commands.add("-i");
+        commands.add(inputVideo);
+        commands.add("-t");
+        commands.add(cutTime);
+        commands.add("-c:v");
+        commands.add("copy");
+        commands.add("-c:a");
+        commands.add("copy");
+        commands.add("-y");
+        commands.add(outputFilePath);
+        FFmpegCommandResult ffmpegResult = executeFFmpegCommand(commands);
+        if (!ffmpegResult.isSuccessful()) {
+            throw new FFmpegExecuteException("cutVideo,ffmpeg执行失败");
+        }
+    }
+
+    /**
+     * 截取视频片段,根据时间范围
+     */
+    public static void cutVideoTime(String inputVideo, String startTime, String endTime, String outputFilePath) {
+        // ffmpeg -ss 00:00:00.000 -i 1.mp4 -t 5.100 -c:v copy -c:a copy -y output.mp4
+        List<String> commands = new ArrayList<>();
+        commands.add("ffmpeg");
+        commands.add("-i");
+        commands.add(inputVideo);
+        commands.add("-ss");
+        commands.add(startTime);
+        commands.add("-to");
+        commands.add(endTime);
+        commands.add("-c:v");
+        commands.add("copy");
+        commands.add("-c:a");
+        commands.add("copy");
+        commands.add("-y");
+        commands.add(outputFilePath);
+        FFmpegCommandResult ffmpegResult = executeFFmpegCommand(commands);
+        if (!ffmpegResult.isSuccessful()) {
+            throw new FFmpegExecuteException("cutVideo,ffmpeg执行失败");
+        }
+    }
+
+    /**
+     * 精确截取视频片段(需重新编码,速度慢)
+     *
+     * @param inputVideo
+     * @param startTime
+     * @param cutTime
+     * @param outputFilePath
+     * @return
+     */
+    public static void accurateCutVideo(String inputVideo, String startTime, String cutTime, String outputFilePath) {
+        // ffmpeg -ss 00:00:00.000 -i 1.mp4 -t 5.100 -y output.mp4
+        List<String> commands = new ArrayList<>();
+        commands.add("ffmpeg");
+        commands.add("-ss");
+        commands.add(startTime);
+        commands.add("-i");
+        commands.add(inputVideo);
+        commands.add("-t");
+        commands.add(cutTime);
+        commands.add("-y");
+        commands.add(outputFilePath);
+        FFmpegCommandResult ffmpegResult = executeFFmpegCommand(commands);
+        if (!ffmpegResult.isSuccessful()) {
+            throw new FFmpegExecuteException("cutVideo,ffmpeg执行失败");
+        }
+    }
+
+    /**
+     * 截取音频片段
+     *
+     * @param inputAudio
+     * @param startTime
+     * @param cutTime
+     * @param outputFilePath
+     * @return
+     */
+    public static void cutAudio(String inputAudio, String startTime, String cutTime, String outputFilePath) {
+        // ffmpeg -ss 00:00:00.000 -i 1.mp3 -t 5.100 -c copy -y output.mp3
+        List<String> commands = new ArrayList<>();
+        commands.add("ffmpeg");
+        commands.add("-ss");
+        commands.add(startTime);
+        commands.add("-i");
+        commands.add(inputAudio);
+        commands.add("-t");
+        commands.add(cutTime);
+        commands.add("-c");
+        commands.add("copy");
+        commands.add("-y");
+        commands.add(outputFilePath);
+        FFmpegCommandResult ffmpegResult = executeFFmpegCommand(commands);
+        if (!ffmpegResult.isSuccessful()) {
+            throw new FFmpegExecuteException("cutAudio,ffmpeg执行失败");
+        }
+    }
+
+    /**
+     * 音频切分
+     *
+     * @param inputAudio
+     * @param segmentTime
+     * @param outputFilePath
+     */
+    public static void audioSplit(String inputAudio, int segmentTime, String outputFilePath) {
+        List<String> commands = new ArrayList<>();
+        commands.add("ffmpeg");
+        commands.add("-i");
+        commands.add(inputAudio);
+        commands.add("-f");
+        commands.add("segment");
+        commands.add("-segment_time");
+        commands.add(String.valueOf(segmentTime));
+        commands.add(outputFilePath);
+        FFmpegCommandResult ffmpegResult = executeFFmpegCommand(commands);
+        if (!ffmpegResult.isSuccessful()) {
+            throw new FFmpegExecuteException("audioSplit,ffmpeg执行失败");
+        }
+    }
+
+    /**
+     * 精确截取音频片段(需重新编码,速度慢)
+     *
+     * @param inputAudio
+     * @param startTime
+     * @param cutTime
+     * @param outputFilePath
+     * @return
+     */
+    public static void accurateCutAudio(String inputAudio, String startTime, String cutTime, String outputFilePath) {
+        // ffmpeg -ss 00:00:00.000 -i 1.mp3 -t 5.100 -c copy -y output.mp3
+        List<String> commands = new ArrayList<>();
+        commands.add("ffmpeg");
+        commands.add("-ss");
+        commands.add(startTime);
+        commands.add("-i");
+        commands.add(inputAudio);
+        commands.add("-t");
+        commands.add(cutTime);
+        commands.add("-y");
+        commands.add(outputFilePath);
+        FFmpegCommandResult ffmpegResult = executeFFmpegCommand(commands);
+        if (!ffmpegResult.isSuccessful()) {
+            throw new FFmpegExecuteException("cutAudio,ffmpeg执行失败");
+        }
+    }
+
+    /**
+     * 视频倍数
+     *
+     * @param inputVideo
+     * @param multiple       倍数,取值范围 [0.01 - 100],小于1表示放慢,大于1表示加速,仅支持两位小数
+     * @param outputFilePath
+     * @return
+     */
+    public static void multipleVideo(String inputVideo, double multiple, String outputFilePath) {
+        // 4倍数
+        // ffmpeg -i 1.mp4 -filter_complex "[0:v]setpts=1/4*PTS[v];[0:a]atempo=4[a]" -map "[v]" -map "[a]" -y
+        // output4.mp4
+        // 0.25倍数
+        // ffmpeg -i output4.mp4 -filter_complex "[0:v]setpts=4*PTS[v];[0:a]atempo=1/2,atempo=1/2[a]" -map "[v]" -map
+        // "[a]" -y output5.mp4
+        // atempo [0.5, 100.0] range
+        // 仅视频轨道倍数 ffmpeg -i test.mp4 -filter:v "setpts=0.5*PTS" test-0.5.mp4
+        String multipleParams = "";
+//        if (multiple >= 1) {
+//            multipleParams = "[0:v]setpts=1/" + multiple + "*PTS[v];[0:a]atempo=" + multiple + "[a]";
+//        } else {
+//            StringBuilder sb = new StringBuilder();
+//            String atempoParams = buildMultipleAtempoParams(sb, multiple);
+//            multipleParams = "[0:v]setpts=1/" + multiple + "*PTS[v];[0:a]" + atempoParams + "[a]";
+//        }
+        multipleParams = "setpts=1/" + multiple + "*PTS";
+        List<String> commands = new ArrayList<>();
+        commands.add("ffmpeg");
+        commands.add("-i");
+        commands.add(inputVideo);
+//        commands.add("-filter_complex");
+        commands.add("-filter:v");
+        commands.add(multipleParams);
+//        commands.add("-map");
+//        commands.add("[v]");
+//        commands.add("-map");
+//        commands.add("[a]");
+        commands.add("-y");
+        commands.add(outputFilePath);
+        FFmpegCommandResult ffmpegResult = executeFFmpegCommand(commands);
+        if (!ffmpegResult.isSuccessful()) {
+            throw new FFmpegExecuteException("multipleVideo,ffmpeg执行失败");
+        }
+    }
+
+    private static String buildMultipleAtempoParams(StringBuilder sb, double multiple) {
+        double minLimit = 0.5;
+        if (multiple >= minLimit) {
+            return "atempo=" + multiple;
+        } else {
+            sb.append("atempo=").append(minLimit);
+            sb.append(",");
+            BigDecimal a = new BigDecimal(multiple);
+            BigDecimal b = new BigDecimal(minLimit);
+            double newMultiple = a.divide(b, 2, RoundingMode.HALF_UP).doubleValue();
+            if (newMultiple >= minLimit) {
+                sb.append("atempo=").append(newMultiple);
+                return sb.toString();
+            } else {
+                return buildMultipleAtempoParams(sb, newMultiple);
+            }
+        }
+    }
+
+    /**
+     * 视频倒放
+     *
+     * @param inputVideo
+     * @param outputFilePath
+     * @return
+     */
+    public static void reverseVideo(String inputVideo, String outputFilePath) {
+        // ffmpeg -i input.mp4 -vf reverse reversed.mp4
+        List<String> commands = new ArrayList<>();
+        commands.add("ffmpeg");
+        commands.add("-i");
+        commands.add(inputVideo);
+        commands.add("-vf");
+        commands.add("reverse");
+        commands.add("-y");
+        commands.add(outputFilePath);
+        FFmpegCommandResult ffmpegResult = executeFFmpegCommand(commands);
+        if (!ffmpegResult.isSuccessful()) {
+            throw new FFmpegExecuteException("reverseVideo,ffmpeg执行失败");
+        }
+    }
+
+    /**
+     * 视频循环
+     *
+     * @param inputVideo
+     * @param loop
+     * @param outputFilePath
+     * @return
+     */
+    public static void loopVideo(String inputVideo, int loop, String outputFilePath) {
+        // ffmpeg -stream_loop 2 -i 7.mp4 -c copy out5.mp4
+        List<String> commands = new ArrayList<>();
+        commands.add("ffmpeg");
+        commands.add("-stream_loop");
+        commands.add(loop + "");
+        commands.add("-i");
+        commands.add(inputVideo);
+        commands.add("-c");
+        commands.add("copy");
+        commands.add("-y");
+        commands.add(outputFilePath);
+        FFmpegCommandResult ffmpegResult = executeFFmpegCommand(commands);
+        if (!ffmpegResult.isSuccessful()) {
+            throw new FFmpegExecuteException("loopVideo,ffmpeg执行失败");
+        }
+    }
+
+    /**
+     * 多个视频拼接
+     *
+     * @param concatVideosFilePath
+     * @param outputFilePath
+     * @return
+     */
+    public static void concatVideos(String concatVideosFilePath, String outputFilePath) {
+        // ffmpeg -f concat -safe 0 -i filelist.txt -c copy output1.mp4
+        List<String> commands = new ArrayList<>();
+        commands.add("ffmpeg");
+        commands.add("-f");
+        commands.add("concat");
+        commands.add("-safe");
+        commands.add("0");
+        commands.add("-i");
+        commands.add(concatVideosFilePath);
+        commands.add("-c");
+        commands.add("copy");
+        commands.add("-y");
+        commands.add(outputFilePath);
+        FFmpegCommandResult ffmpegResult = executeFFmpegCommand(commands);
+        if (!ffmpegResult.isSuccessful()) {
+            throw new FFmpegExecuteException("concatVideos,ffmpeg执行失败");
+        }
+    }
+
+    /**
+     * 多个视频拼接并重新编码
+     *
+     * @param concatVideosFilePath
+     * @param outputFilePath
+     * @return
+     */
+    public static void concatVideosAndTranscode(String concatVideosFilePath, String outputFilePath) {
+        // ffmpeg -f concat -safe 0 -i filelist.txt -c copy output1.mp4
+        List<String> commands = new ArrayList<>();
+        commands.add("ffmpeg");
+        commands.add("-f");
+        commands.add("concat");
+        commands.add("-safe");
+        commands.add("0");
+        commands.add("-i");
+        commands.add(concatVideosFilePath);
+        commands.add("-c:v");
+        commands.add("libx264");
+        commands.add("-c:a");
+        commands.add("aac");
+        commands.add("-y");
+        commands.add(outputFilePath);
+        FFmpegCommandResult ffmpegResult = executeFFmpegCommand(commands);
+        if (!ffmpegResult.isSuccessful()) {
+            throw new FFmpegExecuteException("concatVideosAndTranscode,ffmpeg执行失败");
+        }
+    }
+
+    /**
+     * 多个音频拼接
+     *
+     * @param concatAudiosFilePath
+     * @param outputFilePath
+     * @return
+     */
+    public static void concatAudios(String concatAudiosFilePath, String outputFilePath) {
+        // ffmpeg -f concat -safe 0 -i filelist.txt -codec libmp3lame output1.mp3
+        List<String> commands = new ArrayList<>();
+        commands.add("ffmpeg");
+        commands.add("-f");
+        commands.add("concat");
+        commands.add("-safe");
+        commands.add("0");
+        commands.add("-i");
+        commands.add(concatAudiosFilePath);
+        commands.add("-codec");
+        commands.add("libmp3lame");
+        commands.add("-y");
+        commands.add(outputFilePath);
+        FFmpegCommandResult ffmpegResult = executeFFmpegCommand(commands);
+        if (!ffmpegResult.isSuccessful()) {
+            throw new FFmpegExecuteException("concatAudios,ffmpeg执行失败");
+        }
+    }
+
+    /**
+     * 添加音频
+     *
+     * @param inputVideo
+     * @param inputAudio
+     * @param outputFilePath
+     * @return
+     */
+    public static void addAudio(String inputVideo, String inputAudio, String outputFilePath) {
+        // ffmpeg -i output.mp4 -i 1.mp3 -c copy -map 0:v:0 -map 1:a:0 -y output2.mp4
+        List<String> commands = new ArrayList<>();
+        commands.add("ffmpeg");
+        commands.add("-i");
+        commands.add(inputVideo);
+        commands.add("-i");
+        commands.add(inputAudio);
+        commands.add("-c");
+        commands.add("copy");
+        commands.add("-map");
+        commands.add("0:v:0");
+        commands.add("-map");
+        commands.add("1:a:0");
+        commands.add("-y");
+        commands.add(outputFilePath);
+        FFmpegCommandResult ffmpegResult = executeFFmpegCommand(commands);
+        if (!ffmpegResult.isSuccessful()) {
+            throw new FFmpegExecuteException("addAudio,ffmpeg执行失败");
+        }
+    }
+
+    /**
+     * 添加音频,以短时长为准
+     */
+    public static void addAudioSelfAdaption(String inputVideo, String inputAudio, String outputFilePath) {
+        // ffmpeg -i output.mp4 -i 1.mp3 -c copy -map 0:v:0 -map 1:a:0 -y output2.mp4
+        List<String> commands = new ArrayList<>();
+        commands.add("ffmpeg");
+        commands.add("-i");
+        commands.add(inputVideo);
+        commands.add("-i");
+        commands.add(inputAudio);
+        commands.add("-c");
+        commands.add("copy");
+        commands.add("-map");
+        commands.add("0:v:0");
+        commands.add("-map");
+        commands.add("1:a:0");
+        commands.add("-shortest");
+        commands.add("-y");
+        commands.add(outputFilePath);
+        FFmpegCommandResult ffmpegResult = executeFFmpegCommand(commands);
+        if (!ffmpegResult.isSuccessful()) {
+            throw new FFmpegExecuteException("addAudioSelfAdaption,ffmpeg执行失败");
+        }
+    }
+
+    /**
+     * 添加音频,循环视频并且以短时长为准
+     */
+    public static void addAudioLoopVideoAndShortest(String inputVideo, String inputAudio, String outputFilePath,
+                                                    int loopCount) {
+        // ffmpeg -i output.mp4 -i 1.mp3 -c copy -map 0:v:0 -map 1:a:0 -y output2.mp4
+        List<String> commands = new ArrayList<>();
+        commands.add("ffmpeg");
+        commands.add("-stream_loop");
+        commands.add(loopCount + "");
+        commands.add("-i");
+        commands.add(inputVideo);
+        commands.add("-i");
+        commands.add(inputAudio);
+        commands.add("-c");
+        commands.add("copy");
+        commands.add("-map");
+        commands.add("0:v:0");
+        commands.add("-map");
+        commands.add("1:a:0");
+        commands.add("-shortest");
+        commands.add("-y");
+        commands.add(outputFilePath);
+        FFmpegCommandResult ffmpegResult = executeFFmpegCommand(commands);
+        if (!ffmpegResult.isSuccessful()) {
+            throw new FFmpegExecuteException("addAudioLoopVideoAndShortest,ffmpeg执行失败");
+        }
+    }
+
+    /**
+     * 添加音频,循环音频并且以短时长为准
+     */
+    public static void addAudioLoopAudioAndShortest(String inputVideo, String inputAudio, String outputFilePath,
+                                                    int loopCount) {
+        // ffmpeg -i output.mp4 -i 1.mp3 -c copy -map 0:v:0 -map 1:a:0 -y output2.mp4
+        List<String> commands = new ArrayList<>();
+        commands.add("ffmpeg");
+        commands.add("-i");
+        commands.add(inputVideo);
+        commands.add("-stream_loop");
+        commands.add(loopCount + "");
+        commands.add("-i");
+        commands.add(inputAudio);
+        commands.add("-c");
+        commands.add("copy");
+        commands.add("-map");
+        commands.add("0:v:0");
+        commands.add("-map");
+        commands.add("1:a:0");
+        commands.add("-shortest");
+        commands.add("-y");
+        commands.add(outputFilePath);
+        FFmpegCommandResult ffmpegResult = executeFFmpegCommand(commands);
+        if (!ffmpegResult.isSuccessful()) {
+            throw new FFmpegExecuteException("addAudioLoopVideoAndShortest,ffmpeg执行失败");
+        }
+    }
+
+    /**
+     * 视频混合音频(保留视频原声)
+     *
+     * @param inputVideo
+     * @param inputAudio
+     * @param outputFilePath
+     * @return
+     */
+    public static void videoAmixAudio(String inputVideo, String inputAudio, String outputFilePath) {
+        // ffmpeg -i 7.mp4 -i 3.mp3 -filter_complex "[0:a] [1:a] amix=inputs=2:duration=first [aout]" -map 0:v -map "[aout]" -y output.mp4
+        List<String> commands = new ArrayList<>();
+        commands.add("ffmpeg");
+        commands.add("-i");
+        commands.add(inputVideo);
+        commands.add("-i");
+        commands.add(inputAudio);
+        commands.add("-filter_complex");
+        commands.add("[0:a] [1:a] amix=inputs=2:duration=first [aout]");
+        commands.add("-map");
+        commands.add("0:v");
+        commands.add("-map");
+        commands.add("[aout]");
+        commands.add("-y");
+        commands.add(outputFilePath);
+        FFmpegCommandResult ffmpegResult = executeFFmpegCommand(commands);
+        if (!ffmpegResult.isSuccessful()) {
+            throw new FFmpegExecuteException("videoAmixAudio,ffmpeg执行失败");
+        }
+    }
+
+    /**
+     * 视频混合音频(保留视频原声),循环视频并且以短时长为准
+     *
+     * @param inputVideo
+     * @param inputAudio
+     * @param outputFilePath
+     * @return
+     */
+    public static void videoAmixAudioLoopVideoAndShortest(String inputVideo, String inputAudio, String outputFilePath, int loopCount) {
+        // ffmpeg -stream_loop 1 -i 7.mp4 -i 3.mp3 -filter_complex "[0:a] [1:a] amix=inputs=2:duration=shortest [aout]" -map 0:v -map "[aout]" -y output.mp4
+        List<String> commands = new ArrayList<>();
+        commands.add("ffmpeg");
+        commands.add("-stream_loop");
+        commands.add(loopCount + "");
+        commands.add("-i");
+        commands.add(inputVideo);
+        commands.add("-i");
+        commands.add(inputAudio);
+        commands.add("-filter_complex");
+        commands.add("[0:a] [1:a] amix=inputs=2:duration=shortest [aout]");
+        commands.add("-map");
+        commands.add("0:v");
+        commands.add("-map");
+        commands.add("[aout]");
+        commands.add("-shortest");
+        commands.add("-y");
+        commands.add(outputFilePath);
+        FFmpegCommandResult ffmpegResult = executeFFmpegCommand(commands);
+        if (!ffmpegResult.isSuccessful()) {
+            throw new FFmpegExecuteException("videoAmixAudioLoopVideoAndShortest,ffmpeg执行失败");
+        }
+    }
+
+    /**
+     * 视频混合音频(保留视频原声),循环音频并且以短时长为准
+     *
+     * @param inputVideo
+     * @param inputAudio
+     * @param outputFilePath
+     * @return
+     */
+    public static void videoAmixAudioLoopAudioAndShortest(String inputVideo, String inputAudio, String outputFilePath, int loopCount) {
+        // ffmpeg -stream_loop 1 -i 7.mp4 -i 3.mp3 -filter_complex "[0:a] [1:a] amix=inputs=2:duration=shortest [aout]" -map 0:v -map "[aout]" -y output.mp4
+        List<String> commands = new ArrayList<>();
+        commands.add("ffmpeg");
+        commands.add("-i");
+        commands.add(inputVideo);
+        commands.add("-stream_loop");
+        commands.add(loopCount + "");
+        commands.add("-i");
+        commands.add(inputAudio);
+        commands.add("-filter_complex");
+        commands.add("[0:a] [1:a] amix=inputs=2:duration=shortest [aout]");
+        commands.add("-map");
+        commands.add("0:v");
+        commands.add("-map");
+        commands.add("[aout]");
+        commands.add("-shortest");
+        commands.add("-y");
+        commands.add(outputFilePath);
+        FFmpegCommandResult ffmpegResult = executeFFmpegCommand(commands);
+        if (!ffmpegResult.isSuccessful()) {
+            throw new FFmpegExecuteException("videoAmixAudioLoopAudioAndShortest,ffmpeg执行失败");
+        }
+    }
+
+    public static void delogo(String inputImage, String outputImage, String delogoPosition) {
+        // ffmpeg -i 1.jpeg -vf delogo=x=50:y=1100:w=120:h=120:show=0 dologo1.jpeg
+        List<String> commands = new ArrayList<>();
+        commands.add("ffmpeg");
+        commands.add("-i");
+        commands.add(inputImage);
+        commands.add("-vf");
+        commands.add("delogo=" + delogoPosition + ":show=0");
+        commands.add(outputImage);
+        FFmpegCommandResult ffmpegResult = executeFFmpegCommand(commands);
+        if (!ffmpegResult.isSuccessful()) {
+            throw new FFmpegExecuteException("delogo,ffmpeg执行失败:" + ffmpegResult.getResultContent());
+        }
+    }
+
+    /**
+     * 添加字幕
+     *
+     * @param inputVideo
+     * @param inputSubtitleAndStyle
+     * @param outputFilePath
+     * @return
+     */
+    public static void addSubtitle(String inputVideo, String inputSubtitleAndStyle, String outputFilePath) {
+        // ffmpeg -i 1.mp4 -vf subtitles=1.srt:force_style='Fontname=DejaVu Serif,PrimaryColour=&HCCFF0000' -y
+        // output6.mp4
+        List<String> commands = new ArrayList<>();
+        commands.add("ffmpeg");
+        commands.add("-i");
+        commands.add(inputVideo);
+        commands.add("-vf");
+        commands.add(inputSubtitleAndStyle);
+        commands.add("-preset");
+        commands.add("superfast");
+        commands.add("-c:a");
+        commands.add("copy");
+        commands.add("-y");
+        commands.add(outputFilePath);
+        FFmpegCommandResult ffmpegResult = executeFFmpegCommand(commands);
+        if (!ffmpegResult.isSuccessful()) {
+            throw new FFmpegExecuteException("addSubtitle,ffmpeg执行失败");
+        }
+    }
+
+    /**
+     * 剪辑视频(设置宽高、填充黑边、裁剪)
+     *
+     * @param inputVideo
+     * @param clipParams
+     * @param outputFilePath
+     * @return
+     */
+    public static void clipVideo(String inputVideo, String clipParams, String outputFilePath) {
+        // 视频压缩填充黑边
+        // ffmpeg -i 2.mp4 -vf 'scale=900:900,pad=900:1600:0:350:black' -y output7.mp4
+        // 视频放大裁剪
+        // ffmpeg -i 4.mp4 -vf 'scale=1080:-1,crop=1080:1080:0:420' -y output10.mp4
+        List<String> commands = new ArrayList<>();
+        commands.add("ffmpeg");
+        commands.add("-i");
+        commands.add(inputVideo);
+        commands.add("-vf");
+        commands.add(clipParams);
+        commands.add("-y");
+        commands.add(outputFilePath);
+        FFmpegCommandResult ffmpegResult = executeFFmpegCommand(commands);
+        if (!ffmpegResult.isSuccessful()) {
+            throw new FFmpegExecuteException("clipVideo,ffmpeg执行失败");
+        }
+    }
+
+    /**
+     * 剪辑并重新编码视频(设置宽高、填充黑边、裁剪)
+     *
+     * @param inputVideo
+     * @param clipParams
+     * @param outputFilePath
+     * @return
+     */
+    public static void clipAndTranscodeVideo(String inputVideo, String clipParams, String outputFilePath, Integer avgFrameRate, Integer timeBase) {
+        // ffmpeg -i input.mp4 -c:v libx264 -preset slow -crf 23 -c:a aac -b:a 128k -ar 44100 -vf "scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2" output.mp4
+        List<String> commands = new ArrayList<>();
+        commands.add("ffmpeg");
+        commands.add("-i");
+        commands.add(inputVideo);
+        commands.add("-c:v");
+        commands.add("libx264");
+        commands.add("-preset");
+        commands.add("superfast");
+        commands.add("-crf");
+        commands.add("28");
+        commands.add("-c:a");
+        commands.add("aac");
+        commands.add("-b:a");
+        commands.add("128k");
+        commands.add("-ar");
+        commands.add("44100");
+        if (Objects.nonNull(avgFrameRate)) {
+            commands.add("-r");
+            commands.add(avgFrameRate.toString());
+        }
+        if (Objects.nonNull(timeBase)) {
+            commands.add("-video_track_timescale");
+            commands.add(timeBase.toString());
+        }
+
+        commands.add("-vf");
+        commands.add(clipParams);
+        commands.add("-y");
+        commands.add(outputFilePath);
+        FFmpegCommandResult ffmpegResult = executeFFmpegCommand(commands);
+        if (!ffmpegResult.isSuccessful()) {
+            throw new FFmpegExecuteException("clipAndTranscodeVideo,ffmpeg执行失败");
+        }
+    }
+
+    public static void generateBlackVideo(int width, int height, String duration, String outputFilePath) {
+        // ffmpeg -f lavfi -i color=size=1920x1080:rate=25:color=black:duration=5 -y black.mp4
+        List<String> commands = new ArrayList<>();
+        commands.add("ffmpeg");
+        commands.add("-f");
+        commands.add("lavfi");
+        commands.add("-i");
+        String params = "color=size=" + width + "x" + height + ":rate=25:color=black:duration=" + duration;
+        commands.add(params);
+        commands.add("-y");
+        commands.add(outputFilePath);
+        FFmpegCommandResult ffmpegResult = executeFFmpegCommand(commands);
+        if (!ffmpegResult.isSuccessful()) {
+            throw new FFmpegExecuteException("generateBlackVideo,ffmpeg执行失败");
+        }
+    }
+
+    /**
+     * 视频指定时间截图
+     *
+     * @param mediaUrl 媒体url,可以是网络url或者文件路径
+     * @return
+     */
+    public static String getTimeImage(String mediaUrl, String time, String outputFilePath, int retryTimes) {
+        // ffmpeg -ss 00:50:00 -i RevolutionOS.rmvb -frames:v 1 outPut.jpg
+        List<String> commands = new ArrayList<>();
+        commands.add("ffmpeg");
+        commands.add("-i");
+        commands.add(mediaUrl);
+        commands.add("-ss");
+        commands.add(time);
+        commands.add("-frames:v");
+        commands.add("1");
+        commands.add("-y");
+        commands.add(outputFilePath);
+
+        FFmpegCommandResult ffmpegResult = executeFFmpegCommand(commands);
+        if (ffmpegResult.isSuccessful()) {
+            return outputFilePath;
+        } else {
+            if (retryTimes > 10) {
+                return null;
+            }
+            retryTimes++;
+            return getTimeImage(mediaUrl, time, outputFilePath, retryTimes);
+        }
+    }
+
+    /**
+     * 获取视频音频
+     *
+     * @param mediaUrl 媒体url,可以是网络url或者文件路径
+     * @return
+     */
+    public static String getVideoVoice(String mediaUrl, String outputFilePath) {
+        // ffmpeg -i inputVideo.mp4 -vn -ar 44100 -ac 2 -b:a 192k output.mp3
+        List<String> commands = new ArrayList<>();
+        commands.add("ffmpeg");
+        commands.add("-i");
+        commands.add(mediaUrl);
+        commands.add("-vn");
+        commands.add("-ar");
+        commands.add("44100");
+        commands.add("-ac");
+        commands.add("2");
+        commands.add("-b:a");
+        commands.add("192k");
+        commands.add(outputFilePath);
+
+        FFmpegCommandResult ffmpegResult = executeFFmpegCommand(commands);
+        if (ffmpegResult.isSuccessful()) {
+            return outputFilePath;
+        }
+        return null;
+    }
+
+    /**
+     * 获取视频音频
+     *
+     * @param mediaUrl 媒体url,可以是网络url或者文件路径
+     * @return
+     */
+    public static String getVideoVoice(String mediaUrl, String startTime, String endTime, String outputFilePath) {
+        // ffmpeg -i inputVideo.mp4 -vn -ar 44100 -ac 2 -b:a 192k output.mp3
+        List<String> commands = new ArrayList<>();
+        commands.add("ffmpeg");
+        commands.add("-i");
+        commands.add(mediaUrl);
+        commands.add("-ss");
+        commands.add(startTime);
+        commands.add("-to");
+        commands.add(endTime);
+        commands.add("-vn");
+        commands.add("-ar");
+        commands.add("44100");
+        commands.add("-ac");
+        commands.add("2");
+        commands.add("-b:a");
+        commands.add("192k");
+        commands.add(outputFilePath);
+
+        FFmpegCommandResult ffmpegResult = executeFFmpegCommand(commands);
+        if (ffmpegResult.isSuccessful()) {
+            return outputFilePath;
+        }
+        return null;
+    }
+
+    /**
+     * 去除背景并合并视频
+     *
+     * @param mainVideo
+     * @param bgVideo
+     * @param color
+     * @param outputFilePath
+     * @return
+     */
+    public static void removeColorAndMergeBgVideo(String mainVideo, String bgVideo, String color, String outputFilePath) {
+        // ffmpeg -i 6.mp4 -i green.mp4 -filter_complex "[1:v]chromakey=0x00FF00:0.2:0.1[ckout];[0:v][ckout]overlay[out]" -map 0:a -map "[out]" -y out3.mp4
+        List<String> commands = new ArrayList<>();
+        commands.add("ffmpeg");
+        commands.add("-i");
+        commands.add(bgVideo);
+        commands.add("-i");
+        commands.add(mainVideo);
+        commands.add("-filter_complex");
+        String param = "[1:v]chromakey={color}:0.2:0.1[ckout];[0:v][ckout]overlay[out]";
+        param = param.replace("{color}", color);
+        commands.add(param);
+        commands.add("-map");
+        commands.add("0:a");
+        commands.add("-map");
+        commands.add("[out]");
+        commands.add("-y");
+        commands.add(outputFilePath);
+        FFmpegCommandResult ffmpegResult = executeFFmpegCommand(commands);
+        if (!ffmpegResult.isSuccessful()) {
+            throw new FFmpegExecuteException("removeColorAndMergeBgVideo,ffmpeg执行失败");
+        }
+    }
+
+    /**
+     * 合并背景视频
+     *
+     * @param mainVideo
+     * @param bgVideo
+     * @param outputFilePath
+     */
+    public static void mergeBgVideo(String mainVideo, String bgVideo, String outputFilePath) {
+        // ffmpeg -i 7.mp4 -i out1.mov -filter_complex "[0:v][1:v]overlay=0:0" -c:a copy out5.mp4
+        List<String> commands = new ArrayList<>();
+        commands.add("ffmpeg");
+        commands.add("-i");
+        commands.add(mainVideo);
+        commands.add("-i");
+        commands.add(bgVideo);
+        commands.add("-filter_complex");
+        String param = "[1:v][0:v]overlay=0:0";
+        commands.add(param);
+//        commands.add("-map 1:a");
+        commands.add("-c:a");
+        commands.add("copy");
+        commands.add("-y");
+        commands.add(outputFilePath);
+        FFmpegCommandResult ffmpegResult = executeFFmpegCommand(commands);
+        if (!ffmpegResult.isSuccessful()) {
+            throw new FFmpegExecuteException("mergeBgVideo,ffmpeg执行失败");
+        }
+    }
+
+    /**
+     * 去除背景
+     *
+     * @param inputFilePath
+     * @param colorAndSF
+     * @param outputFilePath
+     * @return
+     */
+    public static void removeBg(String inputFilePath, String colorAndSF, String outputFilePath) {
+        // ffmpeg -i green.mp4 -vf "chromakey=0x00FF00:0.2:0.1" -c copy -c:v png out1.mov
+        List<String> commands = new ArrayList<>();
+        commands.add("ffmpeg");
+        commands.add("-i");
+        commands.add(inputFilePath);
+        commands.add("-vf");
+        String param = "chromakey={color}";
+        param = param.replace("{color}", colorAndSF);
+        commands.add(param);
+        commands.add("-c");
+        commands.add("copy");
+        commands.add("-c:v");
+        commands.add("png");
+        commands.add("-y");
+        commands.add(outputFilePath);
+        FFmpegCommandResult ffmpegResult = executeFFmpegCommand(commands);
+        if (!ffmpegResult.isSuccessful()) {
+            throw new FFmpegExecuteException("removeBg,ffmpeg执行失败");
+        }
+    }
+
+    /**
+     * mov视频转webm
+     *
+     * @param inputFilePath
+     * @param outputFilePath
+     */
+    public static void movToWebm(String inputFilePath, String outputFilePath) {
+        // ffmpeg -i out1.mov -c:v libvpx-vp9 -vf "format=yuva420p" -auto-alt-ref 0 -y out1.webm
+        List<String> commands = new ArrayList<>();
+        commands.add("ffmpeg");
+        commands.add("-i");
+        commands.add(inputFilePath);
+        commands.add("-c:v");
+        commands.add("libvpx-vp9");
+        commands.add("-vf");
+        commands.add("format=yuva420p");
+        commands.add("-auto-alt-ref");
+        commands.add("0");
+        commands.add("-y");
+        commands.add(outputFilePath);
+        FFmpegCommandResult ffmpegResult = executeFFmpegCommand(commands);
+        if (!ffmpegResult.isSuccessful()) {
+            throw new FFmpegExecuteException("movToWebm,ffmpeg执行失败");
+        }
+    }
+
+    /**
+     * 图片转视频
+     *
+     * @param inputImage
+     * @param width
+     * @param height
+     * @param duration       单位:秒
+     * @param outputFilePath
+     */
+    public static void imageToVideo(String inputImage, Integer width, Integer height, String duration, String outputFilePath) {
+        // ffmpeg -r 25 -f image2 -loop 1 -i base_1.png -vcodec libx264 -pix_fmt yuv420p -s 1334x700 -r 25 -t 4 -y out4.mp4
+        List<String> commands = new ArrayList<>();
+        commands.add("ffmpeg");
+        commands.add("-r");
+        commands.add("25");
+        commands.add("-f");
+        commands.add("image2");
+        commands.add("-loop");
+        commands.add("1");
+        commands.add("-i");
+        commands.add(inputImage);
+        commands.add("-vcodec");
+        commands.add("libx264");
+        commands.add("-pix_fmt");
+        commands.add("yuv420p");
+        commands.add("-s");
+        commands.add(width + "x" + height);
+        commands.add("-r");
+        commands.add("25");
+        commands.add("-t");
+        commands.add(duration);
+        commands.add("-y");
+        commands.add(outputFilePath);
+        FFmpegCommandResult ffmpegResult = executeFFmpegCommand(commands);
+        if (!ffmpegResult.isSuccessful()) {
+            throw new FFmpegExecuteException("imageToVideo,ffmpeg执行失败");
+        }
+    }
+
+    /**
+     * 两个音频混合
+     *
+     * @param audio1
+     * @param audio2
+     * @param outputFilePath
+     * @return
+     */
+    public static void mixTwoAudio(String audio1, String audio2, String outputFilePath) {
+        // ffmpeg -i 江南_伴奏_片段.mp3 -i generate.wav -filter_complex amix=inputs=2:duration=longest -y merge.mp3
+        List<String> commands = new ArrayList<>();
+        commands.add("ffmpeg");
+        commands.add("-i");
+        commands.add(audio1);
+        commands.add("-i");
+        commands.add(audio2);
+        commands.add("-filter_complex");
+        commands.add("amix=inputs=2:duration=longest");
+        commands.add("-y");
+        commands.add(outputFilePath);
+        FFmpegCommandResult ffmpegResult = executeFFmpegCommand(commands);
+        if (!ffmpegResult.isSuccessful()) {
+            throw new FFmpegExecuteException("mixTwoAudio,ffmpeg执行失败");
+        }
+    }
+
+    /**
+     * 修改音量
+     *
+     * @param inputMedia
+     * @param volumeMultiple
+     * @param outputFilePath
+     * @return
+     */
+    public static void changeMediaVolume(String inputMedia, String volumeMultiple, String outputFilePath) {
+        // ffmpeg -i out1.mp3 -af "volume=1.5" -y out2.mp3
+        List<String> commands = new ArrayList<>();
+        commands.add("ffmpeg");
+        commands.add("-i");
+        commands.add(inputMedia);
+        commands.add("-af");
+        commands.add("volume=" + volumeMultiple);
+        commands.add("-y");
+        commands.add(outputFilePath);
+        FFmpegCommandResult ffmpegResult = executeFFmpegCommand(commands);
+        if (!ffmpegResult.isSuccessful()) {
+            throw new FFmpegExecuteException("changeMediaVolume,ffmpeg执行失败");
+        }
+    }
+
+    /**
+     * 修改视频音量(视频不编解码)
+     *
+     * @param inputMedia
+     * @param volumeMultiple
+     * @param outputFilePath
+     * @return
+     */
+    public static void changeVideoVolume(String inputMedia, String volumeMultiple, String outputFilePath) {
+        // ffmpeg -i out1.mp3 -af "volume=1.5" -y out2.mp3
+        List<String> commands = new ArrayList<>();
+        commands.add("ffmpeg");
+        commands.add("-i");
+        commands.add(inputMedia);
+        commands.add("-c:v");
+        commands.add("copy");
+        commands.add("-af");
+        commands.add("volume=" + volumeMultiple);
+        commands.add("-y");
+        commands.add(outputFilePath);
+        FFmpegCommandResult ffmpegResult = executeFFmpegCommand(commands);
+        if (!ffmpegResult.isSuccessful()) {
+            throw new FFmpegExecuteException("changeVideoVolume,ffmpeg执行失败");
+        }
+    }
+
+    /**
+     * 修改音量,使用loudnorm过滤器
+     *
+     * @param inputMedia
+     * @param volumeLUFS
+     * @param outputFilePath
+     * @return
+     */
+    public static void changeMediaVolumeByLoudnorm(String inputMedia, String volumeLUFS, String outputFilePath) {
+        // ffmpeg -i 1.mp3 -af loudnorm=I=-16:TP=-1.5:LRA=11 -y output5.mp3
+        List<String> commands = new ArrayList<>();
+        commands.add("ffmpeg");
+        commands.add("-i");
+        commands.add(inputMedia);
+        commands.add("-af");
+        commands.add("loudnorm=I=" + volumeLUFS + ":TP=-1.5:LRA=11");
+        commands.add("-y");
+        commands.add(outputFilePath);
+        FFmpegCommandResult ffmpegResult = executeFFmpegCommand(commands);
+        if (!ffmpegResult.isSuccessful()) {
+            throw new FFmpegExecuteException("changeMediaVolumeByLoudnorm,ffmpeg执行失败");
+        }
+    }
+
+    /**
+     * 修改视频音量,使用loudnorm过滤器(视频不编解码)
+     *
+     * @param inputMedia
+     * @param volumeLUFS
+     * @param outputFilePath
+     * @return
+     */
+    public static void changeVideoVolumeByLoudnorm(String inputMedia, String volumeLUFS, String outputFilePath) {
+        // ffmpeg -i 1.mp3 -c:v -copy -af loudnorm=I=-16:TP=-1.5:LRA=11 -y output5.mp3
+        List<String> commands = new ArrayList<>();
+        commands.add("ffmpeg");
+        commands.add("-i");
+        commands.add(inputMedia);
+        commands.add("-c:v");
+        commands.add("copy");
+        commands.add("-af");
+        commands.add("loudnorm=I=" + volumeLUFS + ":TP=-1.5:LRA=11");
+        commands.add("-y");
+        commands.add(outputFilePath);
+        FFmpegCommandResult ffmpegResult = executeFFmpegCommand(commands);
+        if (!ffmpegResult.isSuccessful()) {
+            throw new FFmpegExecuteException("changeVideoVolumeByLoudnorm,ffmpeg执行失败");
+        }
+    }
+
+
+    /**
+     * 生成静音音频
+     *
+     * @param duration
+     * @param sampleRate
+     * @param coding
+     * @param outputFilePath
+     * @return
+     */
+    public static void generateSilenceAudio(String duration, String sampleRate, String coding, String outputFilePath) {
+        // ffmpeg -f lavfi -t 5 -i anullsrc=r=44100:cl=mono -c:a libopus -y silence.ogg
+        List<String> commands = new ArrayList<>();
+        commands.add("ffmpeg");
+        commands.add("-f");
+        commands.add("lavfi");
+        commands.add("-t");
+        commands.add(duration);
+        commands.add("-i");
+        String params = "anullsrc=r=" + sampleRate + ":cl=mono";
+        commands.add(params);
+        commands.add("-c:a");
+        commands.add(coding);
+        commands.add("-y");
+        commands.add(outputFilePath);
+        FFmpegCommandResult ffmpegResult = executeFFmpegCommand(commands);
+        if (!ffmpegResult.isSuccessful()) {
+            throw new FFmpegExecuteException("generateSilenceAudio,ffmpeg执行失败");
+        }
+    }
+
+    /**
+     * 在音频开头添加静音时长
+     *
+     * @param duration
+     * @param sampleRate
+     * @param inputAudio
+     * @param outputFilePath
+     */
+    public static void addHeadSilenceToAudio(String duration, String sampleRate, String inputAudio, String outputFilePath) {
+        // ffmpeg -f lavfi -t 10 -i anullsrc=r=44100:cl=mono -i bd464824ae9343538e06eaeddabf3f86.ogg -filter_complex "[0:0][1:0]concat=n=2:v=0:a=1[out]" -map "[out]" -y out5.ogg
+        List<String> commands = new ArrayList<>();
+        commands.add("ffmpeg");
+        commands.add("-f");
+        commands.add("lavfi");
+        commands.add("-t");
+        commands.add(duration);
+        commands.add("-i");
+        String params1 = "anullsrc=r=" + sampleRate + ":cl=mono";
+        commands.add(params1);
+        commands.add("-i");
+        commands.add(inputAudio);
+        commands.add("-filter_complex");
+        commands.add("[0:0][1:0]concat=n=2:v=0:a=1[out]");
+        commands.add("-map");
+        commands.add("[out]");
+        commands.add("-y");
+        commands.add(outputFilePath);
+        FFmpegCommandResult ffmpegResult = executeFFmpegCommand(commands);
+        if (!ffmpegResult.isSuccessful()) {
+            throw new FFmpegExecuteException("addHeadSilenceToAudio,ffmpeg执行失败");
+        }
+    }
+
+    /**
+     * @param inputAudio
+     * @param coding
+     * @param outputFilePath
+     */
+    public static void audioTranscoding(String inputAudio, String coding, String outputFilePath) {
+        // ffmpeg -i input.ogg -c:a libopus output.opus
+        List<String> commands = new ArrayList<>();
+        commands.add("ffmpeg");
+        commands.add("-i");
+        commands.add(inputAudio);
+        commands.add("-c:a");
+        commands.add(coding);
+        commands.add("-y");
+        commands.add(outputFilePath);
+        FFmpegCommandResult ffmpegResult = executeFFmpegCommand(commands);
+        if (!ffmpegResult.isSuccessful()) {
+            throw new FFmpegExecuteException("audioTranscoding,ffmpeg执行失败");
+        }
+    }
+
+    /**
+     * 视频转码
+     *
+     * @param inputVideo
+     * @param coding
+     * @param outputFilePath
+     */
+    public static void videoTranscoding(String inputVideo, String coding, String bitrate, String outputFilePath) {
+        // ffmpeg -i 13.mp4 -c:v libx264 -b:v 2000k -y out13.mp4
+        List<String> commands = new ArrayList<>();
+        commands.add("ffmpeg");
+        commands.add("-i");
+        commands.add(inputVideo);
+        if (StringUtils.hasText(coding)) {
+            commands.add("-c:v");
+            commands.add(coding);
+        }
+        if (StringUtils.hasText(bitrate)) {
+            commands.add("-b:v");
+            commands.add(bitrate);
+        }
+        commands.add("-y");
+        commands.add(outputFilePath);
+        FFmpegCommandResult ffmpegResult = executeFFmpegCommand(commands);
+        if (!ffmpegResult.isSuccessful()) {
+            throw new FFmpegExecuteException("videoTranscoding,ffmpeg执行失败");
+        }
+    }
+
+    /**
+     * 视频贴图
+     */
+    public static void videoTexture(String videoPath, String imagePath,
+                                    Integer x, Integer y, String outputFilePath) {
+        // ffmpeg -i 25.mp4 -i 2.png -filter_complex "[0:v][1:v]overlay=0:0[out]" -map "[out]" -map 0:a -c:v libx264 -preset ultrafast -crf 28 -c:a copy -y output_3.mp4
+        List<String> commands = new ArrayList<>();
+        commands.add("ffmpeg");
+        commands.add("-i");
+        commands.add(videoPath);
+        commands.add("-i");
+        commands.add(imagePath);
+        commands.add("-filter_complex");
+        commands.add(String.format("[0:v][1:v]overlay=%s:%s[out]", x, y));
+        commands.add("-map");
+        commands.add("[out]");
+        commands.add("-map");
+        commands.add("0:a");
+        commands.add("-c:v");
+        commands.add("libx264");
+        commands.add("-preset");
+        commands.add("superfast");
+        commands.add("-crf");
+        commands.add("28");
+        commands.add("-c:a");
+        commands.add("copy");
+        commands.add("-y");
+        commands.add(outputFilePath);
+        FFmpegCommandResult ffmpegResult = executeFFmpegCommand(commands);
+        if (!ffmpegResult.isSuccessful()) {
+            throw new FFmpegExecuteException("videoTexture,ffmpeg执行失败");
+        }
+    }
+
+    /**
+     * 修复视频时间戳
+     *
+     * @param inputVideo
+     * @param outputFilePath
+     * @return
+     */
+    public static void fixVideoTime(String inputVideo, String outputFilePath) {
+        // ffmpeg -i input.mp4 -c copy -video_track_timescale 30k -avoid_negative_ts make_zero -y output.mp4
+        List<String> commands = new ArrayList<>();
+        commands.add("ffmpeg");
+        commands.add("-i");
+        commands.add(inputVideo);
+        commands.add("-c");
+        commands.add("copy");
+        commands.add("-video_track_timescale");
+        commands.add("30k");
+        commands.add("-avoid_negative_ts");
+        commands.add("make_zero");
+        commands.add("-y");
+        commands.add(outputFilePath);
+        FFmpegCommandResult ffmpegResult = executeFFmpegCommand(commands);
+        if (!ffmpegResult.isSuccessful()) {
+            throw new FFmpegExecuteException("fixVideoTime,ffmpeg执行失败");
+        }
+    }
+
+    /**
+     * 重新编码
+     *
+     * @param inputVideo
+     * @param outputFilePath
+     * @return
+     */
+    public static void encodingEquivalence(String inputVideo, String outputFilePath) {
+        List<String> commands = new ArrayList<>();
+        commands.add("ffmpeg");
+        commands.add("-i");
+        commands.add(inputVideo);
+        commands.add("-r");
+        commands.add("30");
+//        commands.add("-s");
+//        commands.add("720x1280");
+        commands.add("-c:v");
+        commands.add("libx264");
+        commands.add("-preset");
+        commands.add("superfast");
+        commands.add("-c:a");
+        commands.add("aac");
+        commands.add("-ar");
+        commands.add("44100");
+        commands.add("-ac");
+        commands.add("2");
+        commands.add(outputFilePath);
+        FFmpegCommandResult ffmpegResult = executeFFmpegCommand(commands);
+        if (!ffmpegResult.isSuccessful()) {
+            throw new FFmpegExecuteException("encodingEquivalence,ffmpeg执行失败");
+        }
+    }
+
+    /**
+     * 取视频最后一帧
+     *
+     * @param inputVideo
+     * @param outputFilePath
+     * @return
+     */
+    public static void getVideoLastFrame(String inputVideo, int lastTime, String outputFilePath) {
+        // ffmpeg -sseof -2 -i input.mp4 -update 1 -q:v 1 -y last_frame.jpg
+        List<String> commands = new ArrayList<>();
+        commands.add("ffmpeg");
+        commands.add("-sseof");
+        commands.add("-" + lastTime);
+        commands.add("-i");
+        commands.add(inputVideo);
+        commands.add("-update");
+        commands.add("1");
+        commands.add("-q:v");
+        commands.add("1");
+        commands.add("-y");
+        commands.add(outputFilePath);
+        FFmpegCommandResult ffmpegResult = executeFFmpegCommand(commands);
+        if (!ffmpegResult.isSuccessful()) {
+            throw new FFmpegExecuteException("getVideoLastFrame,ffmpeg执行失败");
+        }
+    }
+
+    // ffmpeg -i input.mp3 -af "afftdn=nf=-20,volume=2" -codec:a libmp3lame -b:a 128k -ar 44100 -ac 2 output.mp3
+    public static void audioVolume(String inputFilePath, String outputFilePath, String afParam) {
+        List<String> commands = new ArrayList<>();
+        commands.add("ffmpeg");
+        commands.add("-i");
+        commands.add(inputFilePath);
+        commands.add("-af");
+        commands.add(afParam);
+        commands.add("-codec:a");
+        commands.add("libmp3lame");
+        commands.add("-b:a");
+        commands.add("128k");
+        commands.add("-ar");
+        commands.add("44100");
+        commands.add("-ac");
+        commands.add("2");
+        commands.add(outputFilePath);
+        FFmpegCommandResult ffmpegResult = executeFFmpegCommand(commands);
+        if (!ffmpegResult.isSuccessful()) {
+            throw new FFmpegExecuteException("audioVolume,ffmpeg执行失败");
+        }
+    }
+
+    /**
+     * 执行ffmpeg命令
+     *
+     * @param commands
+     * @return
+     */
+    public static FFmpegCommandResult executeFFmpegCommand(List<String> commands) {
+        long beginTime = System.currentTimeMillis();
+        FFmpegCommandResult result = new FFmpegCommandResult();
+        String commandStr = "";
+        for (String command : commands) {
+            commandStr = commandStr + command + " ";
+        }
+        commandStr = commandStr.trim();
+        Runtime runtime = Runtime.getRuntime();
+        Process process = null;
+        try {
+            ProcessBuilder processBuilder = new ProcessBuilder();
+            processBuilder.command(commands);
+            processBuilder.redirectErrorStream(true);
+            process = processBuilder.start();
+            log.info("开始执行ffmpeg命令,command:{},线程名:{}", commandStr, process.toString());
+            String resultContent = IOUtils.toString(process.getInputStream(), "utf-8");
+            int exitCode = process.waitFor();
+            result.setExitCode(exitCode);
+            result.setResultContent(resultContent);
+            log.info("ffmpeg命令执行结束,command:\n{}\nexpendTime:{},exitCode:{},result:\n{}", commandStr,
+                    (System.currentTimeMillis() - beginTime), exitCode, resultContent);
+        } catch (Exception e) {
+            log.info("ffmpeg命令执行异常,command:{}", commandStr, e);
+            result.setExitCode(-999);
+        } finally {
+            if (Objects.nonNull(process)) {
+                process.destroy();
+                log.info("已销毁FFmpeg进程进程名:{}", process.toString());
+//                ProcessKiller processKiller = new ProcessKiller(process);
+//                // JVM退出时,先通过钩子关闭FFmepg进程
+//                runtime.addShutdownHook(processKiller);
+            }
+        }
+        return result;
+    }
+
+    public static String pathCreate(String path) {
+        File file = new File(path);
+        if (!file.getParentFile().exists()) {
+            file.getParentFile().mkdirs();
+        }
+        return path;
+    }
+
+    public static List<String> ls(String path) {
+        if (!StringUtils.hasText(path)) {
+            return new ArrayList<>();
+        }
+
+        try (Stream<Path> paths = Files.walk(Paths.get(path))) {
+            return paths.filter(Files::isRegularFile) // 只要文件
+                    .map(Path::toAbsolutePath)    // 转绝对路径
+                    .map(Path::toString)          // 转成字符串
+                    .collect(Collectors.toList());
+        } catch (IOException e) {
+            log.error("FFmpegUtil.ls error. path: {},\n", path, e);
+        }
+
+        return new ArrayList<>();
+    }
+
+    /**
+     * 在程序退出前结束已有的FFmpeg进程
+     */
+    private static class ProcessKiller extends Thread {
+        private Process process;
+
+        public ProcessKiller(Process process) {
+            this.process = process;
+        }
+
+        @Override
+        public void run() {
+            this.process.destroy();
+            log.info("已销毁FFmpeg进程进程名:{}", process.toString());
+        }
+    }
+
+    private static Integer parseAndCalcAvgFrameRate(String avgFrameRateStr) {
+        try {
+            String[] split = avgFrameRateStr.split("/");
+            if (split.length != 2) {
+                return null;
+            }
+            double d0 = Double.parseDouble(split[0].trim());
+            double d1 = Double.parseDouble(split[1].trim());
+            return (int) Math.ceil(d0 / d1);
+        } catch (Exception e) {
+            log.error("parseAndCalcAvgFrameRate error: {} \n", avgFrameRateStr, e);
+        }
+        return null;
+    }
+
+    private static Integer parseAndCalcTimeBase(String timeBaseStr) {
+        try {
+            String[] split = timeBaseStr.split("/");
+            if (split.length != 2) {
+                return null;
+            }
+            return Integer.parseInt(split[1].trim());
+        } catch (Exception e) {
+            log.error("parseAndCalcTimeBase error: {} \n", timeBaseStr, e);
+        }
+        return null;
+    }
+
+    @Getter
+    @Setter
+    @Accessors(chain = true)
+    public static class MediaInfo {
+        private Integer width;
+        private Integer height;
+        // 毫秒
+        private Integer duration;
+        // Byte
+        private Integer size;
+        // 音频采样率
+        private Integer sampleRate;
+        // 视频平均帧率
+        private Integer avgFrameRate;
+        // 时间基
+        private Integer timeBase;
+    }
+
+    @Getter
+    @Setter
+    @Accessors(chain = true)
+    public static class FFmpegCommandResult {
+        // 0 表示命令正常结束
+        private int exitCode = -1;
+        private String resultContent;
+
+        public boolean isSuccessful() {
+            return exitCode == 0;
+        }
+    }
+
+    public static void main(String[] args) throws Exception {
+//        String mediaUrl = "/Users/liuzhiheng/test/video/out6.mp4";
+//        MediaInfo mediaInfo = getMediaInfo(mediaUrl);
+//        System.out.println(JSON.toJSONString(mediaInfo));
+
+//        String inputVideo = "/Users/liuzhiheng/test/video/6.mp4";
+//        String outputFilePath = "/Users/liuzhiheng/test/video/7.mp4";
+//        String startTime = "00:00:02.000";
+//        String endTime = "3.900";
+//        cutVideo(inputVideo, startTime, endTime, outputFilePath);
+
+//        String inputAudio = "/Users/liuzhiheng/test/video/2.mp3";
+//        String outputFilePath = "/Users/liuzhiheng/test/video/output3.mp3";
+//        String startTime = "00:00:04.766";
+//        String endTime = "2.967";
+//        cutAudio(inputAudio, startTime, endTime, outputFilePath);
+
+//        System.out.println(buildMultipleAtempoParams(new StringBuilder(), 0.61));
+
+//        String inputVideo = "/Users/liuzhiheng/test/video/1.mp4";
+//        String outputFilePath = "/Users/liuzhiheng/test/video/output.mp4";
+//        multipleVideo(inputVideo, 3.24, outputFilePath);
+
+//        String inputVideo = "/Users/liuzhiheng/test/video/2.mp4";
+//        String outputFilePath = "/Users/liuzhiheng/test/video/output.mp4";
+//        reverseVideo(inputVideo, outputFilePath);
+
+//        String concatVideosFilePath = "/Users/liuzhiheng/test/video/filelist.txt";
+//        String outputFilePath = "/Users/liuzhiheng/test/video/output.mp4";
+//        concatVideos(concatVideosFilePath, outputFilePath);
+
+//        String inputVideo = "/Users/liuzhiheng/test/video/7.mp4";
+//        String inputAudio = "/Users/liuzhiheng/test/video/3.mp3";
+//        String outputFilePath = "/Users/liuzhiheng/test/video/output.mp4";
+//        videoAmixAudioLoopVideoAndShortest(inputVideo, inputAudio, outputFilePath, 1);
+
+//        String inputVideo = "/Users/liuzhiheng/test/video/6.mp4";
+////        String clipParams = "scale=900:900,pad=900:1600:0:350:black";
+//        String clipParams = "scale=1080:-1,crop=1080:1080:0:420";
+////        String clipParams = "scale=755:-1";
+//        String outputFilePath = "/Users/liuzhiheng/test/video/output.mp4";
+//        clipVideo(inputVideo, clipParams, outputFilePath);
+
+//        String inputVideo = "/Users/liuzhiheng/test/video/6.mp4";
+//        String inputSubtitleAndStyle = "subtitles=/Users/liuzhiheng/test/video/2.ass";
+////                + ":force_style='Fontsize=12,Alignment=2,MarginV=68'";
+//        String outputFilePath = "/Users/liuzhiheng/test/video/output2.mp4";
+//        addSubtitle(inputVideo, inputSubtitleAndStyle, outputFilePath);
+
+//        generateBlackVideo(1920, 1080, "10.333", "/Users/liuzhiheng/test/video/output5.mp4");
+
+//        String bgVideo = "/Users/liuzhiheng/test/video/bg.mp4";
+//        String mainVideo = "/Users/liuzhiheng/test/video/out1.mov";
+//        mergeBgVideo(mainVideo, bgVideo, "/Users/liuzhiheng/test/video/out1.mp4");
+
+//        String inputImage = "/Users/liuzhiheng/test/video/111.mp4";
+//        imageToVideo(inputImage, 1334, 700, 5, "/Users/liuzhiheng/test/video/out7.mp4");
+
+//        String inputFilePath = "/Users/liuzhiheng/test/video/green.mp4";
+//        removeBg(inputFilePath, "0x00FF00", "/Users/liuzhiheng/test/video/out5.mov");
+
+//        String inputMedia = "/Users/liuzhiheng/test/video/6.mp4";
+//        changeVideoVolume(inputMedia, "0.6", "/Users/liuzhiheng/test/video/out4.mp4");
+
+//        generateSilenceAudio("10.521", "44100", "/Users/liuzhiheng/Downloads/out1.ogg");
+
+//        addHeadSilenceToAudio("3.566", "44100", "/Users/liuzhiheng/Downloads/1.ogg", "/Users/liuzhiheng/Downloads/out2.ogg");
+
+//        String inputVideo = "/Users/liuzhiheng/test/video/out1.mov";
+//        movToWebm(inputVideo, "/Users/liuzhiheng/test/video/out1-3.webm");
+
+//        String videoUrl = "http://res.cybertogether.net/crawler/video/c8adfa2f63943d6f69a484120dfb798b.mp4";
+//        String time = "10";
+//        String outputFilePath = "/Users/liuzhiheng/test/video/out_image_6.jpg";
+//        getTimeImage(videoUrl, time, outputFilePath, 1);
+
+//        String inputVideo = "/Users/liuzhiheng/test/video/concat/01.mp4";
+//        String outputFilePath = "/Users/liuzhiheng/test/video/concat/01_time.mp4";
+//        fixVideoTime(inputVideo, outputFilePath);
+
+//        String inputVideo = "/Users/liuzhiheng/test/video/6.mp4";
+//        String outputFilePath = "/Users/liuzhiheng/test/video/output2.mp4";
+//        videoTranscoding(inputVideo, "libx264","1000k", outputFilePath);
+
+//        String inputMedia = "/Users/liuzhiheng/test/video/6.mp4";
+//        String volumeLUFS = "-16";
+//        String outputFilePath = "/Users/liuzhiheng/test/video/out2.mp4";
+//        changeVideoVolumeByLoudnorm(inputMedia, volumeLUFS, outputFilePath);
+
+//        String videoPath = "/Users/liuzhiheng/test/video/25.mp4";
+//        String imagePath = "/Users/liuzhiheng/test/video/2.png";
+//        Integer x = 0;
+//        Integer y = 0;
+//        String outputFilePath = "/Users/liuzhiheng/test/video/output4.mp4";
+//        videoTexture(videoPath, imagePath, x, y, outputFilePath);
+
+//        String inputMedia = "/Users/liuzhiheng/test/video/6.mp4";
+//        encodingEquivalence(inputMedia, "/Users/liuzhiheng/test/video/out7.mp4");
+
+        String inputVideo = "/Users/liuzhiheng/test/video/concat/4.mp4";
+        String clipParams = "scale=1080:1920:force_original_aspect_ratio=decrease,pad=1080:1920:(ow-iw)/2:(oh-ih)/2";
+        String outputFilePath = "/Users/liuzhiheng/test/video/concat/4_clip.mp4";
+        clipAndTranscodeVideo(inputVideo, clipParams, outputFilePath, null, null);
+    }
+}

+ 166 - 1
core/src/main/java/com/tzld/supply/util/http/HttpClientUtils.java

@@ -14,8 +14,11 @@ import org.apache.http.config.*;
 import org.apache.http.conn.socket.ConnectionSocketFactory;
 import org.apache.http.conn.socket.PlainConnectionSocketFactory;
 import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import org.apache.http.entity.ByteArrayEntity;
 import org.apache.http.entity.ContentType;
 import org.apache.http.entity.StringEntity;
+import org.apache.http.entity.mime.HttpMultipartMode;
+import org.apache.http.entity.mime.MultipartEntityBuilder;
 import org.apache.http.impl.client.CloseableHttpClient;
 import org.apache.http.impl.client.HttpClients;
 import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
@@ -26,6 +29,7 @@ import org.apache.http.util.EntityUtils;
 import javax.net.ssl.SSLContext;
 import javax.net.ssl.TrustManager;
 import javax.net.ssl.X509TrustManager;
+import java.io.File;
 import java.io.IOException;
 import java.io.UnsupportedEncodingException;
 import java.net.URLEncoder;
@@ -38,6 +42,10 @@ import java.util.Map.Entry;
 
 /**
  * 基于apache httpclient4.5.5 的HTTP工具类
+ *
+ * @author liuzhiheng
+ * @date 2017-4-7 下午12:10:29
+ * @since 1.0
  */
 public class HttpClientUtils {
 
@@ -105,6 +113,13 @@ public class HttpClientUtils {
 
     }
 
+
+    public static HttpResponseContent head(String url) {
+        HttpRequestBase request = new HttpHead(url);
+        HttpResponseContent hrc = executeHeadHttpRequest(request, connectTimeoutDefault, readTimeoutDefault);
+        return hrc;
+    }
+
     /**
      * get请求
      *
@@ -333,6 +348,36 @@ public class HttpClientUtils {
 
     }
 
+    /**
+     * post请求,请求数据在data中
+     *
+     * @param url            请求的url
+     * @param msgpackBytes   请求数据
+     * @param headerMap      http请求头,key-value放在map中
+     * @param connectTimeout 连接超时设置,毫秒
+     * @param readTimeout    读取超时设置,毫秒
+     * @return HttpResponseContent对象,如果http请求出现异常,返回null
+     */
+    public static HttpResponseContent postMsgpackDataAddHeader(String url, byte[] msgpackBytes, Map<String, String> headerMap,
+                                                        int connectTimeout, int readTimeout) {
+        HttpRequestBase request = null;
+        HttpResponseContent hrc = null;
+        HttpPost httpPost = new HttpPost(url);
+        ByteArrayEntity entity = new ByteArrayEntity(msgpackBytes);
+        entity.setContentType("application/msgpack");
+        httpPost.setEntity(entity);
+        request = httpPost;
+        // 设置header
+        if (headerMap != null) {
+            for (Entry<String, String> entry : headerMap.entrySet()) {
+                request.addHeader(entry.getKey(), entry.getValue());
+            }
+        }
+        hrc = executeHttpRequest(request, connectTimeout, readTimeout);
+        return hrc;
+
+    }
+
     /**
      * post请求,form表单,不能包含二进制数据
      *
@@ -373,7 +418,7 @@ public class HttpClientUtils {
      * post请求,form表单,不能包含二进制数据
      *
      * @param url            请求的url
-     * @param nvps           form表单参数
+     * @param nvps      form表单参数
      * @param connectTimeout 连接超时设置,毫秒
      * @param readTimeout    读取超时设置,毫秒
      * @param headerMap      form表单header
@@ -400,6 +445,62 @@ public class HttpClientUtils {
         return hrc;
     }
 
+    /**
+     * post请求,multipart,支持File,byte[]这两种二进制数据
+     *
+     * @param url       请求的url
+     * @param paramsMap 请求参数map
+     * @return HttpResponseContent对象,如果http请求出现异常,返回null
+     */
+    public static HttpResponseContent postMultipart(String url, Map<String, Object> paramsMap) {
+        return postMultipart(url, paramsMap, connectTimeoutDefault, readTimeoutDefault, null);
+    }
+
+    public static HttpResponseContent postMultipart(String url, Map<String, Object> paramsMap, Map<String, String> headerMap) {
+        return postMultipart(url, paramsMap, connectTimeoutDefault, readTimeoutDefault, headerMap);
+    }
+
+    /**
+     * post请求,multipart,支持File,byte[]这两种二进制数据
+     *
+     * @param url            请求的url
+     * @param paramsMap      请求参数map
+     * @param connectTimeout 连接超时设置,毫秒
+     * @param readTimeout    读取超时设置,毫秒
+     * @return HttpResponseContent对象,如果http请求出现异常,返回null
+     */
+    public static HttpResponseContent postMultipart(String url, Map<String, Object> paramsMap, int connectTimeout,
+                                                    int readTimeout, Map<String, String> headerMap) {
+        HttpRequestBase request = null;
+        HttpResponseContent hrc = null;
+        HttpPost httpPost = new HttpPost(url);
+        MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create();
+        multipartEntityBuilder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
+        Iterator<String> iterator = paramsMap.keySet().iterator();
+        while (iterator.hasNext()) {
+            String key = iterator.next();
+            Object value = paramsMap.get(key);
+            if (value instanceof File) {
+                multipartEntityBuilder.addBinaryBody(key, (File) value);
+            } else if (value instanceof byte[]) {
+                multipartEntityBuilder.addBinaryBody(key, (byte[]) value);
+            } else {
+                multipartEntityBuilder.addTextBody(key, String.valueOf(value),
+                        ContentType.create("text/plain", charsetDefault));
+            }
+        }
+        httpPost.setEntity(multipartEntityBuilder.build());
+        // 设置header
+        if (headerMap != null) {
+            for (Entry<String, String> entry : headerMap.entrySet()) {
+                httpPost.addHeader(entry.getKey(), entry.getValue());
+            }
+        }
+        request = httpPost;
+        hrc = executeHttpRequest(request, connectTimeout, readTimeout);
+        return hrc;
+    }
+
     public static HttpResponseContent postRequestBody(String url, Object s, Map<String, String> headerMap) {
         HttpRequestBase request = null;
         HttpResponseContent hrc = null;
@@ -545,6 +646,70 @@ public class HttpClientUtils {
 
     }
 
+    /**
+     * 执行Http请求
+     *
+     * @param request
+     * @param connectTimeout
+     * @param readTimeout
+     * @return HttpResponseContent对象,如果http请求出现异常,返回null
+     */
+    private static HttpResponseContent executeHeadHttpRequest(HttpRequestBase request, int connectTimeout,
+                                                          int readTimeout) {
+        CloseableHttpResponse response = null;
+        HttpResponseContent hrc = null;
+        try {
+            // 设置请求配置
+            RequestConfig.Builder configBuilder = RequestConfig.custom();
+            // 设置连接超时
+            configBuilder.setConnectTimeout(connectTimeout);
+            // 设置读取超时
+            configBuilder.setSocketTimeout(readTimeout);
+            // 设置从连接池获取连接实例的超时
+            configBuilder.setConnectionRequestTimeout(getConnectionTimeoutDefault);
+            RequestConfig requestConfig = configBuilder.build();
+            request.setConfig(requestConfig);
+            log.debug("开始执行Http请求, uri:" + request.getURI());
+            response = client.execute(request);
+            hrc = getHeadHttpResponseContent(response);
+            return hrc;
+        } catch (Exception e) {
+            log.error("执行Http请求异常, uri:" + request.getURI(), e);
+        } finally {
+            close(request, response);
+        }
+        return hrc;
+
+    }
+
+    /**
+     * 封装HTTP响应报文
+     *
+     * @param response
+     * @return
+     */
+    private static HttpResponseContent getHeadHttpResponseContent(CloseableHttpResponse response) {
+        HttpResponseContent hrc = new HttpResponseContent();
+        hrc.setHeaders(response.getAllHeaders());
+        hrc.setStatusCode(response.getStatusLine().getStatusCode());
+        HttpEntity entity = response.getEntity();
+        if (entity != null) {
+            ContentType contentType = ContentType.getOrDefault(entity);
+            hrc.setMimeType(contentType.getMimeType());
+            if (contentType.getCharset() != null) {
+                hrc.setCharsetName(contentType.getCharset().name());
+            }
+            try {
+                hrc.setContentBytes(EntityUtils.toByteArray(entity));
+            } catch (IOException e) {
+                log.error("封装HTTP响应报文异常", e);
+            }
+        }
+
+        return hrc;
+
+    }
+
     /**
      * 关闭资源
      *

+ 3 - 1
core/src/main/resources/generator/mybatis-spider-generator-config.xml

@@ -47,7 +47,9 @@
         </javaClientGenerator>
 
 <!--        <table tableName="spider_task" domainObjectName="" alias=""/>-->
-        <table tableName="spider_content" domainObjectName="" alias=""/>
+<!--        <table tableName="spider_content" domainObjectName="" alias=""/>-->
+        <table tableName="spider_content_media" domainObjectName="" alias=""/>
+        <table tableName="tools_audio_trans_record" domainObjectName="" alias=""/>
     </context>
 
 </generatorConfiguration>

+ 398 - 0
core/src/main/resources/mapper/supply/spider/SpiderContentMediaMapper.xml

@@ -0,0 +1,398 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.tzld.supply.dao.mapper.supply.spider.SpiderContentMediaMapper">
+  <resultMap id="BaseResultMap" type="com.tzld.supply.model.po.supply.spider.SpiderContentMedia">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Thu Oct 23 19:21:42 CST 2025.
+    -->
+    <id column="id" jdbcType="BIGINT" property="id" />
+    <result column="content_id" jdbcType="BIGINT" property="contentId" />
+    <result column="media_type" jdbcType="VARCHAR" property="mediaType" />
+    <result column="source_site" jdbcType="VARCHAR" property="sourceSite" />
+    <result column="url" jdbcType="VARCHAR" property="url" />
+    <result column="title" jdbcType="VARCHAR" property="title" />
+    <result column="duration" jdbcType="INTEGER" property="duration" />
+    <result column="oss_key" jdbcType="VARCHAR" property="ossKey" />
+    <result column="relevance_score" jdbcType="DOUBLE" property="relevanceScore" />
+    <result column="status" jdbcType="INTEGER" property="status" />
+    <result column="create_time" jdbcType="BIGINT" property="createTime" />
+    <result column="update_time" jdbcType="BIGINT" property="updateTime" />
+  </resultMap>
+  <sql id="Example_Where_Clause">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Thu Oct 23 19:21:42 CST 2025.
+    -->
+    <where>
+      <foreach collection="oredCriteria" item="criteria" separator="or">
+        <if test="criteria.valid">
+          <trim prefix="(" prefixOverrides="and" suffix=")">
+            <foreach collection="criteria.criteria" item="criterion">
+              <choose>
+                <when test="criterion.noValue">
+                  and ${criterion.condition}
+                </when>
+                <when test="criterion.singleValue">
+                  and ${criterion.condition} #{criterion.value}
+                </when>
+                <when test="criterion.betweenValue">
+                  and ${criterion.condition} #{criterion.value} and #{criterion.secondValue}
+                </when>
+                <when test="criterion.listValue">
+                  and ${criterion.condition}
+                  <foreach close=")" collection="criterion.value" item="listItem" open="(" separator=",">
+                    #{listItem}
+                  </foreach>
+                </when>
+              </choose>
+            </foreach>
+          </trim>
+        </if>
+      </foreach>
+    </where>
+  </sql>
+  <sql id="Update_By_Example_Where_Clause">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Thu Oct 23 19:21:42 CST 2025.
+    -->
+    <where>
+      <foreach collection="example.oredCriteria" item="criteria" separator="or">
+        <if test="criteria.valid">
+          <trim prefix="(" prefixOverrides="and" suffix=")">
+            <foreach collection="criteria.criteria" item="criterion">
+              <choose>
+                <when test="criterion.noValue">
+                  and ${criterion.condition}
+                </when>
+                <when test="criterion.singleValue">
+                  and ${criterion.condition} #{criterion.value}
+                </when>
+                <when test="criterion.betweenValue">
+                  and ${criterion.condition} #{criterion.value} and #{criterion.secondValue}
+                </when>
+                <when test="criterion.listValue">
+                  and ${criterion.condition}
+                  <foreach close=")" collection="criterion.value" item="listItem" open="(" separator=",">
+                    #{listItem}
+                  </foreach>
+                </when>
+              </choose>
+            </foreach>
+          </trim>
+        </if>
+      </foreach>
+    </where>
+  </sql>
+  <sql id="Base_Column_List">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Thu Oct 23 19:21:42 CST 2025.
+    -->
+    id, content_id, media_type, source_site, url, title, duration, oss_key, relevance_score, 
+    `status`, create_time, update_time
+  </sql>
+  <select id="selectByExample" parameterType="com.tzld.supply.model.po.supply.spider.SpiderContentMediaExample" resultMap="BaseResultMap">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Thu Oct 23 19:21:42 CST 2025.
+    -->
+    select
+    <if test="distinct">
+      distinct
+    </if>
+    <include refid="Base_Column_List" />
+    from spider_content_media
+    <if test="_parameter != null">
+      <include refid="Example_Where_Clause" />
+    </if>
+    <if test="orderByClause != null">
+      order by ${orderByClause}
+    </if>
+  </select>
+  <select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Thu Oct 23 19:21:42 CST 2025.
+    -->
+    select 
+    <include refid="Base_Column_List" />
+    from spider_content_media
+    where id = #{id,jdbcType=BIGINT}
+  </select>
+  <delete id="deleteByPrimaryKey" parameterType="java.lang.Long">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Thu Oct 23 19:21:42 CST 2025.
+    -->
+    delete from spider_content_media
+    where id = #{id,jdbcType=BIGINT}
+  </delete>
+  <delete id="deleteByExample" parameterType="com.tzld.supply.model.po.supply.spider.SpiderContentMediaExample">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Thu Oct 23 19:21:42 CST 2025.
+    -->
+    delete from spider_content_media
+    <if test="_parameter != null">
+      <include refid="Example_Where_Clause" />
+    </if>
+  </delete>
+  <insert id="insert" parameterType="com.tzld.supply.model.po.supply.spider.SpiderContentMedia">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Thu Oct 23 19:21:42 CST 2025.
+    -->
+    insert into spider_content_media (id, content_id, media_type, 
+      source_site, url, title, 
+      duration, oss_key, relevance_score, 
+      `status`, create_time, update_time
+      )
+    values (#{id,jdbcType=BIGINT}, #{contentId,jdbcType=BIGINT}, #{mediaType,jdbcType=VARCHAR}, 
+      #{sourceSite,jdbcType=VARCHAR}, #{url,jdbcType=VARCHAR}, #{title,jdbcType=VARCHAR}, 
+      #{duration,jdbcType=INTEGER}, #{ossKey,jdbcType=VARCHAR}, #{relevanceScore,jdbcType=DOUBLE}, 
+      #{status,jdbcType=INTEGER}, #{createTime,jdbcType=BIGINT}, #{updateTime,jdbcType=BIGINT}
+      )
+  </insert>
+  <insert id="insertSelective" parameterType="com.tzld.supply.model.po.supply.spider.SpiderContentMedia">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Thu Oct 23 19:21:42 CST 2025.
+    -->
+    insert into spider_content_media
+    <trim prefix="(" suffix=")" suffixOverrides=",">
+      <if test="id != null">
+        id,
+      </if>
+      <if test="contentId != null">
+        content_id,
+      </if>
+      <if test="mediaType != null">
+        media_type,
+      </if>
+      <if test="sourceSite != null">
+        source_site,
+      </if>
+      <if test="url != null">
+        url,
+      </if>
+      <if test="title != null">
+        title,
+      </if>
+      <if test="duration != null">
+        duration,
+      </if>
+      <if test="ossKey != null">
+        oss_key,
+      </if>
+      <if test="relevanceScore != null">
+        relevance_score,
+      </if>
+      <if test="status != null">
+        `status`,
+      </if>
+      <if test="createTime != null">
+        create_time,
+      </if>
+      <if test="updateTime != null">
+        update_time,
+      </if>
+    </trim>
+    <trim prefix="values (" suffix=")" suffixOverrides=",">
+      <if test="id != null">
+        #{id,jdbcType=BIGINT},
+      </if>
+      <if test="contentId != null">
+        #{contentId,jdbcType=BIGINT},
+      </if>
+      <if test="mediaType != null">
+        #{mediaType,jdbcType=VARCHAR},
+      </if>
+      <if test="sourceSite != null">
+        #{sourceSite,jdbcType=VARCHAR},
+      </if>
+      <if test="url != null">
+        #{url,jdbcType=VARCHAR},
+      </if>
+      <if test="title != null">
+        #{title,jdbcType=VARCHAR},
+      </if>
+      <if test="duration != null">
+        #{duration,jdbcType=INTEGER},
+      </if>
+      <if test="ossKey != null">
+        #{ossKey,jdbcType=VARCHAR},
+      </if>
+      <if test="relevanceScore != null">
+        #{relevanceScore,jdbcType=DOUBLE},
+      </if>
+      <if test="status != null">
+        #{status,jdbcType=INTEGER},
+      </if>
+      <if test="createTime != null">
+        #{createTime,jdbcType=BIGINT},
+      </if>
+      <if test="updateTime != null">
+        #{updateTime,jdbcType=BIGINT},
+      </if>
+    </trim>
+  </insert>
+  <select id="countByExample" parameterType="com.tzld.supply.model.po.supply.spider.SpiderContentMediaExample" resultType="java.lang.Long">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Thu Oct 23 19:21:42 CST 2025.
+    -->
+    select count(*) from spider_content_media
+    <if test="_parameter != null">
+      <include refid="Example_Where_Clause" />
+    </if>
+  </select>
+  <update id="updateByExampleSelective" parameterType="map">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Thu Oct 23 19:21:42 CST 2025.
+    -->
+    update spider_content_media
+    <set>
+      <if test="record.id != null">
+        id = #{record.id,jdbcType=BIGINT},
+      </if>
+      <if test="record.contentId != null">
+        content_id = #{record.contentId,jdbcType=BIGINT},
+      </if>
+      <if test="record.mediaType != null">
+        media_type = #{record.mediaType,jdbcType=VARCHAR},
+      </if>
+      <if test="record.sourceSite != null">
+        source_site = #{record.sourceSite,jdbcType=VARCHAR},
+      </if>
+      <if test="record.url != null">
+        url = #{record.url,jdbcType=VARCHAR},
+      </if>
+      <if test="record.title != null">
+        title = #{record.title,jdbcType=VARCHAR},
+      </if>
+      <if test="record.duration != null">
+        duration = #{record.duration,jdbcType=INTEGER},
+      </if>
+      <if test="record.ossKey != null">
+        oss_key = #{record.ossKey,jdbcType=VARCHAR},
+      </if>
+      <if test="record.relevanceScore != null">
+        relevance_score = #{record.relevanceScore,jdbcType=DOUBLE},
+      </if>
+      <if test="record.status != null">
+        `status` = #{record.status,jdbcType=INTEGER},
+      </if>
+      <if test="record.createTime != null">
+        create_time = #{record.createTime,jdbcType=BIGINT},
+      </if>
+      <if test="record.updateTime != null">
+        update_time = #{record.updateTime,jdbcType=BIGINT},
+      </if>
+    </set>
+    <if test="_parameter != null">
+      <include refid="Update_By_Example_Where_Clause" />
+    </if>
+  </update>
+  <update id="updateByExample" parameterType="map">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Thu Oct 23 19:21:42 CST 2025.
+    -->
+    update spider_content_media
+    set id = #{record.id,jdbcType=BIGINT},
+      content_id = #{record.contentId,jdbcType=BIGINT},
+      media_type = #{record.mediaType,jdbcType=VARCHAR},
+      source_site = #{record.sourceSite,jdbcType=VARCHAR},
+      url = #{record.url,jdbcType=VARCHAR},
+      title = #{record.title,jdbcType=VARCHAR},
+      duration = #{record.duration,jdbcType=INTEGER},
+      oss_key = #{record.ossKey,jdbcType=VARCHAR},
+      relevance_score = #{record.relevanceScore,jdbcType=DOUBLE},
+      `status` = #{record.status,jdbcType=INTEGER},
+      create_time = #{record.createTime,jdbcType=BIGINT},
+      update_time = #{record.updateTime,jdbcType=BIGINT}
+    <if test="_parameter != null">
+      <include refid="Update_By_Example_Where_Clause" />
+    </if>
+  </update>
+  <update id="updateByPrimaryKeySelective" parameterType="com.tzld.supply.model.po.supply.spider.SpiderContentMedia">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Thu Oct 23 19:21:42 CST 2025.
+    -->
+    update spider_content_media
+    <set>
+      <if test="contentId != null">
+        content_id = #{contentId,jdbcType=BIGINT},
+      </if>
+      <if test="mediaType != null">
+        media_type = #{mediaType,jdbcType=VARCHAR},
+      </if>
+      <if test="sourceSite != null">
+        source_site = #{sourceSite,jdbcType=VARCHAR},
+      </if>
+      <if test="url != null">
+        url = #{url,jdbcType=VARCHAR},
+      </if>
+      <if test="title != null">
+        title = #{title,jdbcType=VARCHAR},
+      </if>
+      <if test="duration != null">
+        duration = #{duration,jdbcType=INTEGER},
+      </if>
+      <if test="ossKey != null">
+        oss_key = #{ossKey,jdbcType=VARCHAR},
+      </if>
+      <if test="relevanceScore != null">
+        relevance_score = #{relevanceScore,jdbcType=DOUBLE},
+      </if>
+      <if test="status != null">
+        `status` = #{status,jdbcType=INTEGER},
+      </if>
+      <if test="createTime != null">
+        create_time = #{createTime,jdbcType=BIGINT},
+      </if>
+      <if test="updateTime != null">
+        update_time = #{updateTime,jdbcType=BIGINT},
+      </if>
+    </set>
+    where id = #{id,jdbcType=BIGINT}
+  </update>
+  <update id="updateByPrimaryKey" parameterType="com.tzld.supply.model.po.supply.spider.SpiderContentMedia">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Thu Oct 23 19:21:42 CST 2025.
+    -->
+    update spider_content_media
+    set content_id = #{contentId,jdbcType=BIGINT},
+      media_type = #{mediaType,jdbcType=VARCHAR},
+      source_site = #{sourceSite,jdbcType=VARCHAR},
+      url = #{url,jdbcType=VARCHAR},
+      title = #{title,jdbcType=VARCHAR},
+      duration = #{duration,jdbcType=INTEGER},
+      oss_key = #{ossKey,jdbcType=VARCHAR},
+      relevance_score = #{relevanceScore,jdbcType=DOUBLE},
+      `status` = #{status,jdbcType=INTEGER},
+      create_time = #{createTime,jdbcType=BIGINT},
+      update_time = #{updateTime,jdbcType=BIGINT}
+    where id = #{id,jdbcType=BIGINT}
+  </update>
+</mapper>

+ 438 - 0
core/src/main/resources/mapper/supply/spider/ToolsAudioTransRecordMapper.xml

@@ -0,0 +1,438 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.tzld.supply.dao.mapper.supply.spider.ToolsAudioTransRecordMapper">
+  <resultMap id="BaseResultMap" type="com.tzld.supply.model.po.supply.spider.ToolsAudioTransRecord">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Thu Oct 23 19:21:42 CST 2025.
+    -->
+    <id column="id" jdbcType="BIGINT" property="id" />
+    <result column="file_url" jdbcType="VARCHAR" property="fileUrl" />
+    <result column="file_url_md5" jdbcType="VARCHAR" property="fileUrlMd5" />
+    <result column="task_id" jdbcType="VARCHAR" property="taskId" />
+    <result column="status" jdbcType="INTEGER" property="status" />
+    <result column="create_timestamp" jdbcType="BIGINT" property="createTimestamp" />
+    <result column="exe_timestamp" jdbcType="BIGINT" property="exeTimestamp" />
+    <result column="finish_timestamp" jdbcType="BIGINT" property="finishTimestamp" />
+  </resultMap>
+  <resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="com.tzld.supply.model.po.supply.spider.ToolsAudioTransRecord">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Thu Oct 23 19:21:42 CST 2025.
+    -->
+    <result column="result" jdbcType="LONGVARCHAR" property="result" />
+    <result column="words" jdbcType="LONGVARCHAR" property="words" />
+  </resultMap>
+  <sql id="Example_Where_Clause">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Thu Oct 23 19:21:42 CST 2025.
+    -->
+    <where>
+      <foreach collection="oredCriteria" item="criteria" separator="or">
+        <if test="criteria.valid">
+          <trim prefix="(" prefixOverrides="and" suffix=")">
+            <foreach collection="criteria.criteria" item="criterion">
+              <choose>
+                <when test="criterion.noValue">
+                  and ${criterion.condition}
+                </when>
+                <when test="criterion.singleValue">
+                  and ${criterion.condition} #{criterion.value}
+                </when>
+                <when test="criterion.betweenValue">
+                  and ${criterion.condition} #{criterion.value} and #{criterion.secondValue}
+                </when>
+                <when test="criterion.listValue">
+                  and ${criterion.condition}
+                  <foreach close=")" collection="criterion.value" item="listItem" open="(" separator=",">
+                    #{listItem}
+                  </foreach>
+                </when>
+              </choose>
+            </foreach>
+          </trim>
+        </if>
+      </foreach>
+    </where>
+  </sql>
+  <sql id="Update_By_Example_Where_Clause">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Thu Oct 23 19:21:42 CST 2025.
+    -->
+    <where>
+      <foreach collection="example.oredCriteria" item="criteria" separator="or">
+        <if test="criteria.valid">
+          <trim prefix="(" prefixOverrides="and" suffix=")">
+            <foreach collection="criteria.criteria" item="criterion">
+              <choose>
+                <when test="criterion.noValue">
+                  and ${criterion.condition}
+                </when>
+                <when test="criterion.singleValue">
+                  and ${criterion.condition} #{criterion.value}
+                </when>
+                <when test="criterion.betweenValue">
+                  and ${criterion.condition} #{criterion.value} and #{criterion.secondValue}
+                </when>
+                <when test="criterion.listValue">
+                  and ${criterion.condition}
+                  <foreach close=")" collection="criterion.value" item="listItem" open="(" separator=",">
+                    #{listItem}
+                  </foreach>
+                </when>
+              </choose>
+            </foreach>
+          </trim>
+        </if>
+      </foreach>
+    </where>
+  </sql>
+  <sql id="Base_Column_List">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Thu Oct 23 19:21:42 CST 2025.
+    -->
+    id, file_url, file_url_md5, task_id, `status`, create_timestamp, exe_timestamp, finish_timestamp
+  </sql>
+  <sql id="Blob_Column_List">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Thu Oct 23 19:21:42 CST 2025.
+    -->
+    `result`, words
+  </sql>
+  <select id="selectByExampleWithBLOBs" parameterType="com.tzld.supply.model.po.supply.spider.ToolsAudioTransRecordExample" resultMap="ResultMapWithBLOBs">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Thu Oct 23 19:21:42 CST 2025.
+    -->
+    select
+    <if test="distinct">
+      distinct
+    </if>
+    <include refid="Base_Column_List" />
+    ,
+    <include refid="Blob_Column_List" />
+    from tools_audio_trans_record
+    <if test="_parameter != null">
+      <include refid="Example_Where_Clause" />
+    </if>
+    <if test="orderByClause != null">
+      order by ${orderByClause}
+    </if>
+  </select>
+  <select id="selectByExample" parameterType="com.tzld.supply.model.po.supply.spider.ToolsAudioTransRecordExample" resultMap="BaseResultMap">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Thu Oct 23 19:21:42 CST 2025.
+    -->
+    select
+    <if test="distinct">
+      distinct
+    </if>
+    <include refid="Base_Column_List" />
+    from tools_audio_trans_record
+    <if test="_parameter != null">
+      <include refid="Example_Where_Clause" />
+    </if>
+    <if test="orderByClause != null">
+      order by ${orderByClause}
+    </if>
+  </select>
+  <select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="ResultMapWithBLOBs">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Thu Oct 23 19:21:42 CST 2025.
+    -->
+    select 
+    <include refid="Base_Column_List" />
+    ,
+    <include refid="Blob_Column_List" />
+    from tools_audio_trans_record
+    where id = #{id,jdbcType=BIGINT}
+  </select>
+  <delete id="deleteByPrimaryKey" parameterType="java.lang.Long">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Thu Oct 23 19:21:42 CST 2025.
+    -->
+    delete from tools_audio_trans_record
+    where id = #{id,jdbcType=BIGINT}
+  </delete>
+  <delete id="deleteByExample" parameterType="com.tzld.supply.model.po.supply.spider.ToolsAudioTransRecordExample">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Thu Oct 23 19:21:42 CST 2025.
+    -->
+    delete from tools_audio_trans_record
+    <if test="_parameter != null">
+      <include refid="Example_Where_Clause" />
+    </if>
+  </delete>
+  <insert id="insert" parameterType="com.tzld.supply.model.po.supply.spider.ToolsAudioTransRecord">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Thu Oct 23 19:21:42 CST 2025.
+    -->
+    insert into tools_audio_trans_record (id, file_url, file_url_md5, 
+      task_id, `status`, create_timestamp, 
+      exe_timestamp, finish_timestamp, `result`, 
+      words)
+    values (#{id,jdbcType=BIGINT}, #{fileUrl,jdbcType=VARCHAR}, #{fileUrlMd5,jdbcType=VARCHAR}, 
+      #{taskId,jdbcType=VARCHAR}, #{status,jdbcType=INTEGER}, #{createTimestamp,jdbcType=BIGINT}, 
+      #{exeTimestamp,jdbcType=BIGINT}, #{finishTimestamp,jdbcType=BIGINT}, #{result,jdbcType=LONGVARCHAR}, 
+      #{words,jdbcType=LONGVARCHAR})
+  </insert>
+  <insert id="insertSelective" parameterType="com.tzld.supply.model.po.supply.spider.ToolsAudioTransRecord">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Thu Oct 23 19:21:42 CST 2025.
+    -->
+    insert into tools_audio_trans_record
+    <trim prefix="(" suffix=")" suffixOverrides=",">
+      <if test="id != null">
+        id,
+      </if>
+      <if test="fileUrl != null">
+        file_url,
+      </if>
+      <if test="fileUrlMd5 != null">
+        file_url_md5,
+      </if>
+      <if test="taskId != null">
+        task_id,
+      </if>
+      <if test="status != null">
+        `status`,
+      </if>
+      <if test="createTimestamp != null">
+        create_timestamp,
+      </if>
+      <if test="exeTimestamp != null">
+        exe_timestamp,
+      </if>
+      <if test="finishTimestamp != null">
+        finish_timestamp,
+      </if>
+      <if test="result != null">
+        `result`,
+      </if>
+      <if test="words != null">
+        words,
+      </if>
+    </trim>
+    <trim prefix="values (" suffix=")" suffixOverrides=",">
+      <if test="id != null">
+        #{id,jdbcType=BIGINT},
+      </if>
+      <if test="fileUrl != null">
+        #{fileUrl,jdbcType=VARCHAR},
+      </if>
+      <if test="fileUrlMd5 != null">
+        #{fileUrlMd5,jdbcType=VARCHAR},
+      </if>
+      <if test="taskId != null">
+        #{taskId,jdbcType=VARCHAR},
+      </if>
+      <if test="status != null">
+        #{status,jdbcType=INTEGER},
+      </if>
+      <if test="createTimestamp != null">
+        #{createTimestamp,jdbcType=BIGINT},
+      </if>
+      <if test="exeTimestamp != null">
+        #{exeTimestamp,jdbcType=BIGINT},
+      </if>
+      <if test="finishTimestamp != null">
+        #{finishTimestamp,jdbcType=BIGINT},
+      </if>
+      <if test="result != null">
+        #{result,jdbcType=LONGVARCHAR},
+      </if>
+      <if test="words != null">
+        #{words,jdbcType=LONGVARCHAR},
+      </if>
+    </trim>
+  </insert>
+  <select id="countByExample" parameterType="com.tzld.supply.model.po.supply.spider.ToolsAudioTransRecordExample" resultType="java.lang.Long">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Thu Oct 23 19:21:42 CST 2025.
+    -->
+    select count(*) from tools_audio_trans_record
+    <if test="_parameter != null">
+      <include refid="Example_Where_Clause" />
+    </if>
+  </select>
+  <update id="updateByExampleSelective" parameterType="map">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Thu Oct 23 19:21:42 CST 2025.
+    -->
+    update tools_audio_trans_record
+    <set>
+      <if test="record.id != null">
+        id = #{record.id,jdbcType=BIGINT},
+      </if>
+      <if test="record.fileUrl != null">
+        file_url = #{record.fileUrl,jdbcType=VARCHAR},
+      </if>
+      <if test="record.fileUrlMd5 != null">
+        file_url_md5 = #{record.fileUrlMd5,jdbcType=VARCHAR},
+      </if>
+      <if test="record.taskId != null">
+        task_id = #{record.taskId,jdbcType=VARCHAR},
+      </if>
+      <if test="record.status != null">
+        `status` = #{record.status,jdbcType=INTEGER},
+      </if>
+      <if test="record.createTimestamp != null">
+        create_timestamp = #{record.createTimestamp,jdbcType=BIGINT},
+      </if>
+      <if test="record.exeTimestamp != null">
+        exe_timestamp = #{record.exeTimestamp,jdbcType=BIGINT},
+      </if>
+      <if test="record.finishTimestamp != null">
+        finish_timestamp = #{record.finishTimestamp,jdbcType=BIGINT},
+      </if>
+      <if test="record.result != null">
+        `result` = #{record.result,jdbcType=LONGVARCHAR},
+      </if>
+      <if test="record.words != null">
+        words = #{record.words,jdbcType=LONGVARCHAR},
+      </if>
+    </set>
+    <if test="_parameter != null">
+      <include refid="Update_By_Example_Where_Clause" />
+    </if>
+  </update>
+  <update id="updateByExampleWithBLOBs" parameterType="map">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Thu Oct 23 19:21:42 CST 2025.
+    -->
+    update tools_audio_trans_record
+    set id = #{record.id,jdbcType=BIGINT},
+      file_url = #{record.fileUrl,jdbcType=VARCHAR},
+      file_url_md5 = #{record.fileUrlMd5,jdbcType=VARCHAR},
+      task_id = #{record.taskId,jdbcType=VARCHAR},
+      `status` = #{record.status,jdbcType=INTEGER},
+      create_timestamp = #{record.createTimestamp,jdbcType=BIGINT},
+      exe_timestamp = #{record.exeTimestamp,jdbcType=BIGINT},
+      finish_timestamp = #{record.finishTimestamp,jdbcType=BIGINT},
+      `result` = #{record.result,jdbcType=LONGVARCHAR},
+      words = #{record.words,jdbcType=LONGVARCHAR}
+    <if test="_parameter != null">
+      <include refid="Update_By_Example_Where_Clause" />
+    </if>
+  </update>
+  <update id="updateByExample" parameterType="map">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Thu Oct 23 19:21:42 CST 2025.
+    -->
+    update tools_audio_trans_record
+    set id = #{record.id,jdbcType=BIGINT},
+      file_url = #{record.fileUrl,jdbcType=VARCHAR},
+      file_url_md5 = #{record.fileUrlMd5,jdbcType=VARCHAR},
+      task_id = #{record.taskId,jdbcType=VARCHAR},
+      `status` = #{record.status,jdbcType=INTEGER},
+      create_timestamp = #{record.createTimestamp,jdbcType=BIGINT},
+      exe_timestamp = #{record.exeTimestamp,jdbcType=BIGINT},
+      finish_timestamp = #{record.finishTimestamp,jdbcType=BIGINT}
+    <if test="_parameter != null">
+      <include refid="Update_By_Example_Where_Clause" />
+    </if>
+  </update>
+  <update id="updateByPrimaryKeySelective" parameterType="com.tzld.supply.model.po.supply.spider.ToolsAudioTransRecord">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Thu Oct 23 19:21:42 CST 2025.
+    -->
+    update tools_audio_trans_record
+    <set>
+      <if test="fileUrl != null">
+        file_url = #{fileUrl,jdbcType=VARCHAR},
+      </if>
+      <if test="fileUrlMd5 != null">
+        file_url_md5 = #{fileUrlMd5,jdbcType=VARCHAR},
+      </if>
+      <if test="taskId != null">
+        task_id = #{taskId,jdbcType=VARCHAR},
+      </if>
+      <if test="status != null">
+        `status` = #{status,jdbcType=INTEGER},
+      </if>
+      <if test="createTimestamp != null">
+        create_timestamp = #{createTimestamp,jdbcType=BIGINT},
+      </if>
+      <if test="exeTimestamp != null">
+        exe_timestamp = #{exeTimestamp,jdbcType=BIGINT},
+      </if>
+      <if test="finishTimestamp != null">
+        finish_timestamp = #{finishTimestamp,jdbcType=BIGINT},
+      </if>
+      <if test="result != null">
+        `result` = #{result,jdbcType=LONGVARCHAR},
+      </if>
+      <if test="words != null">
+        words = #{words,jdbcType=LONGVARCHAR},
+      </if>
+    </set>
+    where id = #{id,jdbcType=BIGINT}
+  </update>
+  <update id="updateByPrimaryKeyWithBLOBs" parameterType="com.tzld.supply.model.po.supply.spider.ToolsAudioTransRecord">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Thu Oct 23 19:21:42 CST 2025.
+    -->
+    update tools_audio_trans_record
+    set file_url = #{fileUrl,jdbcType=VARCHAR},
+      file_url_md5 = #{fileUrlMd5,jdbcType=VARCHAR},
+      task_id = #{taskId,jdbcType=VARCHAR},
+      `status` = #{status,jdbcType=INTEGER},
+      create_timestamp = #{createTimestamp,jdbcType=BIGINT},
+      exe_timestamp = #{exeTimestamp,jdbcType=BIGINT},
+      finish_timestamp = #{finishTimestamp,jdbcType=BIGINT},
+      `result` = #{result,jdbcType=LONGVARCHAR},
+      words = #{words,jdbcType=LONGVARCHAR}
+    where id = #{id,jdbcType=BIGINT}
+  </update>
+  <update id="updateByPrimaryKey" parameterType="com.tzld.supply.model.po.supply.spider.ToolsAudioTransRecord">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Thu Oct 23 19:21:42 CST 2025.
+    -->
+    update tools_audio_trans_record
+    set file_url = #{fileUrl,jdbcType=VARCHAR},
+      file_url_md5 = #{fileUrlMd5,jdbcType=VARCHAR},
+      task_id = #{taskId,jdbcType=VARCHAR},
+      `status` = #{status,jdbcType=INTEGER},
+      create_timestamp = #{createTimestamp,jdbcType=BIGINT},
+      exe_timestamp = #{exeTimestamp,jdbcType=BIGINT},
+      finish_timestamp = #{finishTimestamp,jdbcType=BIGINT}
+    where id = #{id,jdbcType=BIGINT}
+  </update>
+</mapper>

+ 142 - 0
server/src/main/java/com/tzld/supply/controller/FFmpegController.java

@@ -0,0 +1,142 @@
+package com.tzld.supply.controller;
+
+import com.tzld.supply.common.base.CommonResponse;
+import com.tzld.supply.model.param.FFmpeg.*;
+import com.tzld.supply.service.ffmpeg.FFmpegService;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/ffmpeg")
+public class FFmpegController {
+
+    @Autowired
+    FFmpegService fFmpegService;
+
+    @ApiOperation(value = "融合背景视频")
+    @PostMapping("/mergeBgVideo")
+    public CommonResponse<String> mergeBgVideo(@RequestBody MergeBgVideoParam param) {
+        return CommonResponse.success(fFmpegService.mergeBgVideo(param));
+    }
+
+    @ApiOperation(value = "去除背景")
+    @PostMapping("/removeBg")
+    public CommonResponse<String> mergeBgVideo(@RequestBody RemoveBgParam param) {
+        return CommonResponse.success(fFmpegService.removeBg(param));
+    }
+
+    @ApiOperation(value = "视频添加ass字幕")
+    @PostMapping("/videoAddAssSubtitle")
+    public CommonResponse<String> videoAddAssSubtitle(@RequestBody VideoAddAssSubtitleParam param) {
+        return CommonResponse.success(fFmpegService.videoAddAssSubtitle(param));
+    }
+
+    @ApiOperation(value = "视频提取音频")
+    @PostMapping("/extraAudio")
+    public CommonResponse<List<String>> videoExtraAudio(@RequestBody VideoUrlsParam param) {
+        return CommonResponse.success(fFmpegService.videoExtraAudio(param));
+    }
+
+    @ApiOperation(value = "视频提取文本")
+    @PostMapping("/extraText")
+    public CommonResponse<List<String>> videoExtraText(@RequestBody VideoUrlsParam param) {
+        return CommonResponse.success(fFmpegService.videoExtraText(param));
+    }
+
+    @ApiOperation(value = "视频提取srt")
+    @PostMapping("/extraVideoSrt")
+    public CommonResponse<String> extraVideoSrt(@RequestBody ExtraVideoSrtParam param) {
+        return CommonResponse.success(fFmpegService.extraVideoSrt(param));
+    }
+
+    @ApiOperation(value = "视频提取关键帧")
+    @PostMapping("/extraFrame")
+    public CommonResponse<List<String>> videoExtraFrame(@RequestBody VideoUrlsParam param) {
+        return CommonResponse.success(fFmpegService.videoExtraFrame(param));
+    }
+
+    @ApiOperation(value = "视频截帧-根据时间戳")
+    @PostMapping("/timeExtraFrame")
+    public CommonResponse<String> videoTimeExtraFrame(@RequestBody VideoTimeExtraFrameParam param) {
+        return CommonResponse.success(fFmpegService.videoTimeExtraFrame(param));
+    }
+
+    @ApiOperation(value = "视频片段截取-根据时间范围")
+    @PostMapping("/timeCutVideo")
+    public CommonResponse<String> timeCutVideo(@RequestBody VideoTimeCutParam param) {
+        return CommonResponse.success(fFmpegService.timeCutVideo(param));
+    }
+
+    @ApiOperation(value = "视频提取音频-根据时间范围")
+    @PostMapping("/timeExtraAudio")
+    public CommonResponse<String> timeExtraAudio(@RequestBody VideoTimeCutParam param) {
+        return CommonResponse.success(fFmpegService.timeExtraAudio(param));
+    }
+
+    @ApiOperation(value = "视频音频简单合并")
+    @PostMapping("/media/merge/simple")
+    public CommonResponse<String> mediaSimpleMerge(@RequestBody MediaSimpleMergeParam param) {
+        return CommonResponse.success(fFmpegService.mediaSimpleMerge(param));
+    }
+
+    @ApiOperation(value = "视频贴图")
+    @PostMapping("/videoTexture")
+    public CommonResponse<String> videoTexture(@RequestBody VideoTextureParam param) {
+        return CommonResponse.success(fFmpegService.videoTexture(param));
+    }
+
+    @ApiOperation(value = "获取视频宽高等信息")
+    @PostMapping("/video/info")
+    public CommonResponse<String> videoInfo(@RequestBody VideoInfoParam param) {
+        return CommonResponse.success(fFmpegService.videoInfo(param.getVideoUrl()));
+    }
+
+    @ApiOperation(value = "视频拼接")
+    @PostMapping("/videoConcat")
+    public CommonResponse<String> videoConcat(@RequestBody VideoUrlsParam param) {
+        return CommonResponse.success(fFmpegService.videoConcat(param));
+    }
+
+    @ApiOperation(value = "视频增加片尾")
+    @PostMapping("/videoAddTail")
+    public CommonResponse<String> videoAddTail(@RequestBody VideoAddTailParam param) {
+        return CommonResponse.success(fFmpegService.videoAddTail(param));
+    }
+
+    @ApiOperation(value = "视频转码")
+    @PostMapping("/videoTranscoding")
+    public CommonResponse<String> videoTranscoding(@RequestBody VideoTranscodingParam param) {
+        return CommonResponse.success(fFmpegService.videoTranscoding(param));
+    }
+
+    @ApiOperation(value = "视频添加音频")
+    @PostMapping("/videoAddAudio")
+    public CommonResponse<String> videoAddAudio(@RequestBody VideoAddAudioParam param) {
+        return CommonResponse.success(fFmpegService.videoAddAudio(param));
+    }
+
+    @ApiOperation(value = "音频降噪")
+    @PostMapping("/audioVolume")
+    public CommonResponse<String> audioVolume(@RequestBody AudioVolumeParam param) {
+        return CommonResponse.create(fFmpegService.audioVolume(param));
+    }
+
+    @ApiOperation(value = "音频切割")
+    @PostMapping("/audio/split")
+    public CommonResponse<String> audioSplit(@RequestBody AudioSplitParam param) {
+        return CommonResponse.create(fFmpegService.audioSplit(param));
+    }
+
+    @ApiOperation(value = "命令行执行")
+    @PostMapping("/command")
+    public CommonResponse<String> command(@RequestBody CommandParam param) {
+        return CommonResponse.success(fFmpegService.command(param));
+    }
+
+}

+ 102 - 0
server/src/main/java/com/tzld/supply/controller/FileController.java

@@ -0,0 +1,102 @@
+package com.tzld.supply.controller;
+
+import com.alibaba.fastjson.JSON;
+import com.stuuudy.commons.external.filestorage.enums.EnumPublicBuckets;
+import com.tzld.supply.common.base.CommonResponse;
+import com.tzld.supply.common.enums.ExceptionEnum;
+import com.tzld.supply.common.exception.CommonException;
+import com.tzld.supply.config.AliOssConfig;
+import com.tzld.supply.model.param.FileUploadParam;
+import com.tzld.supply.model.param.OssUploadSignParam;
+import com.tzld.supply.model.param.StsTokenParam;
+import com.tzld.supply.model.vo.FileInfo;
+import com.tzld.supply.model.vo.SignatureVO;
+import com.tzld.supply.model.vo.StsTokenVO;
+import com.tzld.supply.util.AliOssFileTool;
+import com.tzld.supply.util.RandomUtil;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.validation.BindingResult;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.CrossOrigin;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.servlet.http.HttpServletRequest;
+import java.io.IOException;
+
+@RestController
+@RequestMapping("/file")
+@Slf4j
+public class FileController {
+
+    @CrossOrigin(origins = "*")
+    @PostMapping("/upload")
+    public CommonResponse<FileInfo> fileUpload(@Validated FileUploadParam param, BindingResult bindingResult) throws IOException {
+        if (bindingResult.hasErrors()) {
+            return CommonResponse.create(ExceptionEnum.PARAM_ERROR.getCode(), bindingResult.getAllErrors().get(0).getDefaultMessage());
+        }
+        if (StringUtils.isBlank(param.getFileUri())) {
+            String fileName = param.getFile().getOriginalFilename();
+            // 获取文件名中的文件类型
+            if (StringUtils.isBlank(fileName)) {
+                param.setFileUri("temp/" + System.currentTimeMillis() + "_" + RandomUtil.generate18String());
+            } else {
+                String fileType = fileName.substring(fileName.lastIndexOf("."));
+                param.setFileUri("temp/" + System.currentTimeMillis() + "_" + RandomUtil.generate18String() + fileType);
+            }
+        }
+        String fileUrl = AliOssFileTool.saveInPublicReturnHost(param.getFile().getInputStream(),
+                EnumPublicBuckets.PUBBUCKET, param.getFileUri(), param.getFileType());
+        FileInfo fileInfo = new FileInfo();
+        fileInfo.setFileUrl(fileUrl);
+        String bucketName = AliOssConfig.getBucket(EnumPublicBuckets.PUBBUCKET.getBucketName());
+        boolean isExistFile = AliOssFileTool.getOssClient().doesObjectExist(bucketName, param.getFileUri());
+        if (!isExistFile) {
+            throw new CommonException(ExceptionEnum.PARAM_ERROR.getCode(), "上传文件不存在!!");
+        }
+        return CommonResponse.create(fileInfo);
+    }
+
+    @CrossOrigin(origins = "*")
+    @PostMapping("/signature")
+    @ApiOperation(value = "获取签名")
+    public CommonResponse<SignatureVO> signature(@Validated OssUploadSignParam ossUploadSignParam) {
+        log.info("获取OSS签名失败 param" + JSON.toJSONString(ossUploadSignParam));
+        SignatureVO signatureVO;
+        try {
+            signatureVO = AliOssFileTool.getUploadPolicy(ossUploadSignParam.getFileType());
+        } catch (Exception e) {
+            log.error("获取OSS签名失败", e);
+            return CommonResponse.create(ExceptionEnum.SYSTEM_ERROR.getCode(), "获取OSS签名失败");
+        }
+        return CommonResponse.success(signatureVO);
+    }
+
+    /**
+     * 尽量控制前端获取oss权限,降低安全风险
+     * 有效期为15分钟,getStsToken接口为1小时
+     *
+     * @param stsTokenParam
+     * @param request
+     * @return
+     * @throws Exception
+     */
+    @CrossOrigin(origins = "*")
+    @PostMapping("/getTempStsToken")
+    @ApiOperation(value = "获取STS临时令牌")
+    public CommonResponse<StsTokenVO> getTempStsToken(StsTokenParam stsTokenParam, HttpServletRequest request) throws Exception {
+        log.info("获取STS临时令牌 param" + JSON.toJSONString(stsTokenParam));
+        StsTokenVO stsTokenVO;
+        try {
+            String fileName = AliOssFileTool.getRandomObjectKey(stsTokenParam.getFileType());
+            stsTokenVO = AliOssFileTool.getStsToken(fileName, 15 * 60L);
+        } catch (Exception e) {
+            log.error("获取STS临时令牌", e);
+            return CommonResponse.create(ExceptionEnum.SYSTEM_ERROR.getCode(), "获取STS临时令牌失败");
+        }
+        return CommonResponse.success(stsTokenVO);
+    }
+}

+ 3 - 4
server/src/main/resources/application-dev.yml

@@ -15,10 +15,9 @@ spring:
         connection-test-query: SELECT 1
 
   redis:
-    hostName: r-t4n023zec9wyjeer0spd.redis.singapore.rds.aliyuncs.com
+    hostName: r-bp1ps6my7lzg8rdhwxpd.redis.rds.aliyuncs.com
     port: 6379
-    username: denet_crawler
-    password: denet_crawler2023
+    password: Wqsd@2019
     connect-timeout: 1000
     timeout: 3000
     lettuce:
@@ -27,7 +26,7 @@ spring:
         max-wait: -1
         max-idle: 8
         min-idle: 0
-    host: r-t4n023zec9wyjeer0spd.redis.singapore.rds.aliyuncs.com
+    host: r-bp1ps6my7lzg8rdhwxpd.redis.rds.aliyuncs.com
 
 xxl:
   job:

+ 3 - 4
server/src/main/resources/application-prod.yml

@@ -15,10 +15,9 @@ spring:
         connection-test-query: SELECT 1
 
   redis:
-    hostName: r-t4n1ff4vv1j0u6xm2y.redis.singapore.rds.aliyuncs.com
+    hostName: r-bp1rbmomeunfo3b3vppd.redis.rds.aliyuncs.com
     port: 6379
-    username: denet_crawler
-    password: denet_crawler2023
+    password: wqsd@2025
     connect-timeout: 1000
     timeout: 3000
     lettuce:
@@ -27,7 +26,7 @@ spring:
         max-wait: -1
         max-idle: 8
         min-idle: 0
-    host: r-t4n1ff4vv1j0u6xm2y.redis.singapore.rds.aliyuncs.com
+    host: r-bp1rbmomeunfo3b3vppd.redis.rds.aliyuncs.com
 
 xxl:
   job:

+ 3 - 4
server/src/main/resources/application-test.yml

@@ -15,10 +15,9 @@ spring:
         connection-test-query: SELECT 1
 
   redis:
-    hostName: r-t4n023zec9wyjeer0s.redis.singapore.rds.aliyuncs.com
+    hostName: r-bp1ps6my7lzg8rdhwxpd.redis.rds.aliyuncs.com
     port: 6379
-    username: denet_crawler
-    password: denet_crawler2023
+    password: Wqsd@2019
     connect-timeout: 1000
     timeout: 3000
     lettuce:
@@ -27,7 +26,7 @@ spring:
         max-wait: -1
         max-idle: 8
         min-idle: 0
-    host: r-t4n023zec9wyjeer0s.redis.singapore.rds.aliyuncs.com
+    host: r-bp1ps6my7lzg8rdhwxpd.redis.rds.aliyuncs.com
 
 xxl:
   job:

+ 36 - 0
server/src/main/resources/application.yml

@@ -62,3 +62,39 @@ logging:
     com.tzld.supply.dao.mapper.supply.spider: DEBUG  # 输出该包下所有 Mapper 的 SQL 日志
     # 可选:HikariCP 连接池日志(调试连接问题时开启)
     com.zaxxer.hikari: INFO  # 连接池基本信息(避免 DEBUG 级别日志过多)
+
+oss:
+  supply:
+    accessKey: LTAIP6x1l3DXfSxm
+    secretKey: KbTaM9ars4OX3PMS6Xm7rtxGr1FLon
+    ossEndPoint: oss-cn-hangzhou.aliyuncs.com
+    videoEndPoint: slicevideo.yishihui.com
+    expiration: 3600
+    projectName: supply
+    cdnDomain: http://rescdn.yishihui.com/
+    imgDomain: http://rescdn.yishihui.com/
+    videoDomain: http://rescdn.yishihui.com/
+    lvvideoDomain: https://lvupload.piaoquantv.com/
+    pubBucket: public:art-pubbucket,publicVideo:art-pubbucket
+    priBucket: private:art-pribucket,privateVideo:art-privideo,privateVideoIn:art-privideo-in
+    priEndPoint: pricdn.yishihui.com
+    needPress: true
+    internal:
+      endPoint: oss-cn-hangzhou.aliyuncs.com
+  longvideo:
+    video:
+      sts:
+        endpoint: sts.cn-hangzhou.aliyuncs.com
+        accessKeyId: LTAIfZYdxeQpq3YI
+        accessKeySecret: 1yISVWe5Gws2VAdTMc9XWIHpjPd7ja
+        roleArn: acs:ram::1894469520484605:role/oss-sts
+        roleSessionName: session-name
+  accelerate:
+    upload:
+      domain: https://ossaccelerateupload.piaoquantv.com/
+  video:
+    bucket: art-pubbucket
+
+cdn:
+  upload:
+    domain: https://weappupload.piaoquantv.com/

部分文件因文件數量過多而無法顯示