wangyunpeng 2 dagen geleden
bovenliggende
commit
4323ecf9c4
28 gewijzigde bestanden met toevoegingen van 5082 en 5 verwijderingen
  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. 120 0
      core/src/main/java/com/tzld/supply/dao/mapper/supply/spider/SubtitleStyleMapper.java
  10. 120 0
      core/src/main/java/com/tzld/supply/dao/mapper/supply/spider/ToolsAudioTransRecordMapper.java
  11. 57 4
      core/src/main/java/com/tzld/supply/job/VideoGenerateJob.java
  12. 12 0
      core/src/main/java/com/tzld/supply/model/entity/ali/AliVoiceResultData.java
  13. 14 0
      core/src/main/java/com/tzld/supply/model/entity/ali/AliVoiceResultSentenceData.java
  14. 11 0
      core/src/main/java/com/tzld/supply/model/entity/ali/AliVoiceResultWordData.java
  15. 2 0
      core/src/main/java/com/tzld/supply/model/param/FFmpeg/VideoAddAssSubtitleParam.java
  16. 314 0
      core/src/main/java/com/tzld/supply/model/po/supply/spider/SubtitleStyle.java
  17. 763 0
      core/src/main/java/com/tzld/supply/model/po/supply/spider/SubtitleStyleExample.java
  18. 382 0
      core/src/main/java/com/tzld/supply/model/po/supply/spider/ToolsAudioTransRecord.java
  19. 812 0
      core/src/main/java/com/tzld/supply/model/po/supply/spider/ToolsAudioTransRecordExample.java
  20. 21 0
      core/src/main/java/com/tzld/supply/service/ToolsAudioTransService.java
  21. 142 0
      core/src/main/java/com/tzld/supply/service/impl/ToolsAudioTransServiceImpl.java
  22. 125 0
      core/src/main/java/com/tzld/supply/util/AssSubtitleUtil.java
  23. 139 0
      core/src/main/java/com/tzld/supply/util/BaseUtils.java
  24. 55 0
      core/src/main/java/com/tzld/supply/util/TimelineUtils.java
  25. 100 0
      core/src/main/java/com/tzld/supply/util/VideoInputUtils.java
  26. 3 1
      core/src/main/resources/generator/mybatis-spider-generator-config.xml
  27. 404 0
      core/src/main/resources/mapper/supply/spider/SubtitleStyleMapper.xml
  28. 438 0
      core/src/main/resources/mapper/supply/spider/ToolsAudioTransRecordMapper.xml

+ 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> redisTemplate;
+
+
+    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 = redisTemplate.opsForValue().get(cacheKey);
+        if (StringUtils.hasText(token)) {
+            return token;
+        }
+        token = getTokenFromApi(appKey, region, service, expiration);
+        if (StringUtils.hasText(token)) {
+            redisTemplate.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"));
+    }
+
+}

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

@@ -0,0 +1,120 @@
+package com.tzld.supply.dao.mapper.supply.spider;
+
+import com.tzld.supply.model.po.supply.spider.SubtitleStyle;
+import com.tzld.supply.model.po.supply.spider.SubtitleStyleExample;
+import java.util.List;
+import org.apache.ibatis.annotations.Param;
+
+public interface SubtitleStyleMapper {
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table subtitle_style
+     *
+     * @mbg.generated Thu Nov 13 19:51:58 CST 2025
+     */
+    long countByExample(SubtitleStyleExample example);
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table subtitle_style
+     *
+     * @mbg.generated Thu Nov 13 19:51:58 CST 2025
+     */
+    int deleteByExample(SubtitleStyleExample example);
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table subtitle_style
+     *
+     * @mbg.generated Thu Nov 13 19:51:58 CST 2025
+     */
+    int deleteByPrimaryKey(String id);
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table subtitle_style
+     *
+     * @mbg.generated Thu Nov 13 19:51:58 CST 2025
+     */
+    int insert(SubtitleStyle record);
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table subtitle_style
+     *
+     * @mbg.generated Thu Nov 13 19:51:58 CST 2025
+     */
+    int insertSelective(SubtitleStyle record);
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table subtitle_style
+     *
+     * @mbg.generated Thu Nov 13 19:51:58 CST 2025
+     */
+    List<SubtitleStyle> selectByExampleWithBLOBs(SubtitleStyleExample example);
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table subtitle_style
+     *
+     * @mbg.generated Thu Nov 13 19:51:58 CST 2025
+     */
+    List<SubtitleStyle> selectByExample(SubtitleStyleExample example);
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table subtitle_style
+     *
+     * @mbg.generated Thu Nov 13 19:51:58 CST 2025
+     */
+    SubtitleStyle selectByPrimaryKey(String id);
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table subtitle_style
+     *
+     * @mbg.generated Thu Nov 13 19:51:58 CST 2025
+     */
+    int updateByExampleSelective(@Param("record") SubtitleStyle record, @Param("example") SubtitleStyleExample example);
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table subtitle_style
+     *
+     * @mbg.generated Thu Nov 13 19:51:58 CST 2025
+     */
+    int updateByExampleWithBLOBs(@Param("record") SubtitleStyle record, @Param("example") SubtitleStyleExample example);
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table subtitle_style
+     *
+     * @mbg.generated Thu Nov 13 19:51:58 CST 2025
+     */
+    int updateByExample(@Param("record") SubtitleStyle record, @Param("example") SubtitleStyleExample example);
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table subtitle_style
+     *
+     * @mbg.generated Thu Nov 13 19:51:58 CST 2025
+     */
+    int updateByPrimaryKeySelective(SubtitleStyle record);
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table subtitle_style
+     *
+     * @mbg.generated Thu Nov 13 19:51:58 CST 2025
+     */
+    int updateByPrimaryKeyWithBLOBs(SubtitleStyle record);
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table subtitle_style
+     *
+     * @mbg.generated Thu Nov 13 19:51:58 CST 2025
+     */
+    int updateByPrimaryKey(SubtitleStyle 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 CST 2025
+     */
+    int updateByPrimaryKey(ToolsAudioTransRecord record);
+}

+ 57 - 4
core/src/main/java/com/tzld/supply/job/VideoGenerateJob.java

@@ -11,11 +11,11 @@ import com.tzld.supply.common.enums.SpiderContentMediaTypeEnum;
 import com.tzld.supply.common.enums.SpiderContentStatusEnum;
 import com.tzld.supply.dao.mapper.supply.spider.*;
 import com.tzld.supply.dao.mapper.supply.spider.ext.SpiderMapperExt;
+import com.tzld.supply.model.entity.ali.AliVoiceResultSentenceData;
 import com.tzld.supply.model.param.FFmpeg.*;
 import com.tzld.supply.model.po.supply.spider.*;
-import com.tzld.supply.util.AliOssFileTool;
-import com.tzld.supply.util.DateUtils;
-import com.tzld.supply.util.DistributedIdGenerator;
+import com.tzld.supply.service.ToolsAudioTransService;
+import com.tzld.supply.util.*;
 import com.xxl.job.core.biz.model.ReturnT;
 import com.xxl.job.core.handler.annotation.XxlJob;
 import lombok.Getter;
@@ -52,7 +52,11 @@ public class VideoGenerateJob {
     @Autowired
     private ProduceVideoMaterialMapper produceVideoMaterialMapper;
     @Autowired
+    private SubtitleStyleMapper subtitleStyleMapper;
+    @Autowired
     private FFmpegApiService ffmpegApiService;
+    @Autowired
+    private ToolsAudioTransService toolsAudioTransService;
 
     @Value("${produce.video.max.size:10}")
     private Integer maxProduceSize;
@@ -133,6 +137,9 @@ public class VideoGenerateJob {
             audio.setCreateTime(now);
             audio.setUpdateTime(now);
             produceVideoAudioMapper.insertSelective(audio);
+
+            // 生成srt
+            toolsAudioTransService.getAudioTransResults(voiceUrl, true);
         }
         return ReturnT.SUCCESS;
     }
@@ -434,6 +441,10 @@ public class VideoGenerateJob {
             if (audio == null) {
                 continue;
             }
+            ToolsAudioTransRecord audioTransRecord = toolsAudioTransService.getExistToolsAudioTransRecord(audio.getUrl());
+            if (audioTransRecord == null) {
+                continue;
+            }
             audio.setDuration(audio.getDuration() + 500);
             // short video to long video
             // 视频素材拼接长视频 随机排序
@@ -474,9 +485,25 @@ public class VideoGenerateJob {
                     .border(0)
                     .build();
             String watermarkVideoUrl = ffmpegApiService.videoTexture(textureParam);
+            // 添加字幕
+            SubtitleStyle subtitleStyle = subtitleStyleMapper.selectByPrimaryKey("subtitle_style_109");
+            int lineWidth = 80;
+            MediaInfo mediaInfo = getMediaInfo(watermarkVideoUrl);
+            if (mediaInfo.getWidth() > mediaInfo.getHeight()) {
+                lineWidth = 220;
+            }
+            String srt = generateSrtSubtitleContent(audioTransRecord);
+            String assSubtitleData = AssSubtitleUtil.buildAssSubtitleData(subtitleStyle.getAssStyleData(),
+                    "下", "中", subtitleStyle.getFontName(),
+                    srt, lineWidth);
+            VideoAddAssSubtitleParam addAssSubtitleParam = VideoAddAssSubtitleParam.builder()
+                    .videoUrl(watermarkVideoUrl)
+                    .assSubtitle(assSubtitleData)
+                    .build();
+            String addAssSubtitleVideoUrl = ffmpegApiService.videoAddAssSubtitle(addAssSubtitleParam);
             // 上传到OSS
             String fileName = String.format("supply/produce/video/%s", videoName);
-            String finalVideoUrl = AliOssFileTool.downloadAndSaveInOSS(fileName, watermarkVideoUrl, "video/mp4");
+            String finalVideoUrl = AliOssFileTool.downloadAndSaveInOSS(fileName, addAssSubtitleVideoUrl, "video/mp4");
             // 更新视频地址
             if (StringUtils.isNotBlank(finalVideoUrl)) {
                 produceVideo.setUrl(finalVideoUrl);
@@ -487,6 +514,32 @@ public class VideoGenerateJob {
         return ReturnT.SUCCESS;
     }
 
+    private String generateSrtSubtitleContent(ToolsAudioTransRecord audioTransRecord) {
+        String srt = null;
+        // 提取SRT
+        if (StringUtils.isNotBlank(audioTransRecord.getResult())) {
+            List<AliVoiceResultSentenceData> sentences = JSONObject.parseArray(audioTransRecord.getResult(), AliVoiceResultSentenceData.class);
+            StringBuilder sb = new StringBuilder();
+            for (int i = 0; i < sentences.size(); i++) {
+                AliVoiceResultSentenceData sentenceData = sentences.get(i);
+                if (org.springframework.util.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");
+                }
+            }
+            srt = sb.toString();
+        }
+        return srt;
+    }
+
+    // 毫秒转换成 00:00:00.000 或者 00:01:57,230格式
+    private String convertMillisToStringTime(int millis, String splitSSS) {
+        return TimelineUtils.convertMillisToStringTime(millis, splitSSS);
+    }
+
     private List<SpiderContentMedia> getSpiderContentMediaByContentId(Long contentId) {
         SpiderContentMediaExample example = new SpiderContentMediaExample();
         example.createCriteria().andContentIdEqualTo(contentId).andStatusEqualTo(1);

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

@@ -0,0 +1,12 @@
+package com.tzld.supply.model.entity.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/entity/ali/AliVoiceResultSentenceData.java

@@ -0,0 +1,14 @@
+package com.tzld.supply.model.entity.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/entity/ali/AliVoiceResultWordData.java

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

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

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

+ 314 - 0
core/src/main/java/com/tzld/supply/model/po/supply/spider/SubtitleStyle.java

@@ -0,0 +1,314 @@
+package com.tzld.supply.model.po.supply.spider;
+
+import java.util.Date;
+
+/**
+ *
+ * This class was generated by MyBatis Generator.
+ * This class corresponds to the database table subtitle_style
+ */
+public class SubtitleStyle {
+    /**
+     * Database Column Remarks:
+     *   ID,以 subtitle_style_ 开头
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column subtitle_style.id
+     *
+     * @mbg.generated Thu Nov 13 19:51:58 CST 2025
+     */
+    private String id;
+
+    /**
+     * Database Column Remarks:
+     *   名称
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column subtitle_style.name
+     *
+     * @mbg.generated Thu Nov 13 19:51:58 CST 2025
+     */
+    private String name;
+
+    /**
+     * Database Column Remarks:
+     *   图标
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column subtitle_style.icon_url
+     *
+     * @mbg.generated Thu Nov 13 19:51:58 CST 2025
+     */
+    private String iconUrl;
+
+    /**
+     * Database Column Remarks:
+     *   字体名称
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column subtitle_style.font_name
+     *
+     * @mbg.generated Thu Nov 13 19:51:58 CST 2025
+     */
+    private String fontName;
+
+    /**
+     * Database Column Remarks:
+     *   数据状态(1-正常,2-已删除)
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column subtitle_style.data_status
+     *
+     * @mbg.generated Thu Nov 13 19:51:58 CST 2025
+     */
+    private Integer dataStatus;
+
+    /**
+     * Database Column Remarks:
+     *   创建时间
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column subtitle_style.create_time
+     *
+     * @mbg.generated Thu Nov 13 19:51:58 CST 2025
+     */
+    private Date createTime;
+
+    /**
+     * Database Column Remarks:
+     *   更新时间
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column subtitle_style.update_time
+     *
+     * @mbg.generated Thu Nov 13 19:51:58 CST 2025
+     */
+    private Date updateTime;
+
+    /**
+     * Database Column Remarks:
+     *   ass样式数据
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column subtitle_style.ass_style_data
+     *
+     * @mbg.generated Thu Nov 13 19:51:58 CST 2025
+     */
+    private String assStyleData;
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column subtitle_style.id
+     *
+     * @return the value of subtitle_style.id
+     *
+     * @mbg.generated Thu Nov 13 19:51:58 CST 2025
+     */
+    public String getId() {
+        return id;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column subtitle_style.id
+     *
+     * @param id the value for subtitle_style.id
+     *
+     * @mbg.generated Thu Nov 13 19:51:58 CST 2025
+     */
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column subtitle_style.name
+     *
+     * @return the value of subtitle_style.name
+     *
+     * @mbg.generated Thu Nov 13 19:51:58 CST 2025
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column subtitle_style.name
+     *
+     * @param name the value for subtitle_style.name
+     *
+     * @mbg.generated Thu Nov 13 19:51:58 CST 2025
+     */
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column subtitle_style.icon_url
+     *
+     * @return the value of subtitle_style.icon_url
+     *
+     * @mbg.generated Thu Nov 13 19:51:58 CST 2025
+     */
+    public String getIconUrl() {
+        return iconUrl;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column subtitle_style.icon_url
+     *
+     * @param iconUrl the value for subtitle_style.icon_url
+     *
+     * @mbg.generated Thu Nov 13 19:51:58 CST 2025
+     */
+    public void setIconUrl(String iconUrl) {
+        this.iconUrl = iconUrl;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column subtitle_style.font_name
+     *
+     * @return the value of subtitle_style.font_name
+     *
+     * @mbg.generated Thu Nov 13 19:51:58 CST 2025
+     */
+    public String getFontName() {
+        return fontName;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column subtitle_style.font_name
+     *
+     * @param fontName the value for subtitle_style.font_name
+     *
+     * @mbg.generated Thu Nov 13 19:51:58 CST 2025
+     */
+    public void setFontName(String fontName) {
+        this.fontName = fontName;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column subtitle_style.data_status
+     *
+     * @return the value of subtitle_style.data_status
+     *
+     * @mbg.generated Thu Nov 13 19:51:58 CST 2025
+     */
+    public Integer getDataStatus() {
+        return dataStatus;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column subtitle_style.data_status
+     *
+     * @param dataStatus the value for subtitle_style.data_status
+     *
+     * @mbg.generated Thu Nov 13 19:51:58 CST 2025
+     */
+    public void setDataStatus(Integer dataStatus) {
+        this.dataStatus = dataStatus;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column subtitle_style.create_time
+     *
+     * @return the value of subtitle_style.create_time
+     *
+     * @mbg.generated Thu Nov 13 19:51:58 CST 2025
+     */
+    public Date getCreateTime() {
+        return createTime;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column subtitle_style.create_time
+     *
+     * @param createTime the value for subtitle_style.create_time
+     *
+     * @mbg.generated Thu Nov 13 19:51:58 CST 2025
+     */
+    public void setCreateTime(Date createTime) {
+        this.createTime = createTime;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column subtitle_style.update_time
+     *
+     * @return the value of subtitle_style.update_time
+     *
+     * @mbg.generated Thu Nov 13 19:51:58 CST 2025
+     */
+    public Date getUpdateTime() {
+        return updateTime;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column subtitle_style.update_time
+     *
+     * @param updateTime the value for subtitle_style.update_time
+     *
+     * @mbg.generated Thu Nov 13 19:51:58 CST 2025
+     */
+    public void setUpdateTime(Date updateTime) {
+        this.updateTime = updateTime;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column subtitle_style.ass_style_data
+     *
+     * @return the value of subtitle_style.ass_style_data
+     *
+     * @mbg.generated Thu Nov 13 19:51:58 CST 2025
+     */
+    public String getAssStyleData() {
+        return assStyleData;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column subtitle_style.ass_style_data
+     *
+     * @param assStyleData the value for subtitle_style.ass_style_data
+     *
+     * @mbg.generated Thu Nov 13 19:51:58 CST 2025
+     */
+    public void setAssStyleData(String assStyleData) {
+        this.assStyleData = assStyleData;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table subtitle_style
+     *
+     * @mbg.generated Thu Nov 13 19:51:58 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(", name=").append(name);
+        sb.append(", iconUrl=").append(iconUrl);
+        sb.append(", fontName=").append(fontName);
+        sb.append(", dataStatus=").append(dataStatus);
+        sb.append(", createTime=").append(createTime);
+        sb.append(", updateTime=").append(updateTime);
+        sb.append(", assStyleData=").append(assStyleData);
+        sb.append("]");
+        return sb.toString();
+    }
+}

+ 763 - 0
core/src/main/java/com/tzld/supply/model/po/supply/spider/SubtitleStyleExample.java

@@ -0,0 +1,763 @@
+package com.tzld.supply.model.po.supply.spider;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+public class SubtitleStyleExample {
+    /**
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database table subtitle_style
+     *
+     * @mbg.generated Thu Nov 13 19:51:58 CST 2025
+     */
+    protected String orderByClause;
+
+    /**
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database table subtitle_style
+     *
+     * @mbg.generated Thu Nov 13 19:51:58 CST 2025
+     */
+    protected boolean distinct;
+
+    /**
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database table subtitle_style
+     *
+     * @mbg.generated Thu Nov 13 19:51:58 CST 2025
+     */
+    protected List<Criteria> oredCriteria;
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table subtitle_style
+     *
+     * @mbg.generated Thu Nov 13 19:51:58 CST 2025
+     */
+    public SubtitleStyleExample() {
+        oredCriteria = new ArrayList<Criteria>();
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table subtitle_style
+     *
+     * @mbg.generated Thu Nov 13 19:51:58 CST 2025
+     */
+    public void setOrderByClause(String orderByClause) {
+        this.orderByClause = orderByClause;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table subtitle_style
+     *
+     * @mbg.generated Thu Nov 13 19:51:58 CST 2025
+     */
+    public String getOrderByClause() {
+        return orderByClause;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table subtitle_style
+     *
+     * @mbg.generated Thu Nov 13 19:51:58 CST 2025
+     */
+    public void setDistinct(boolean distinct) {
+        this.distinct = distinct;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table subtitle_style
+     *
+     * @mbg.generated Thu Nov 13 19:51:58 CST 2025
+     */
+    public boolean isDistinct() {
+        return distinct;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table subtitle_style
+     *
+     * @mbg.generated Thu Nov 13 19:51:58 CST 2025
+     */
+    public List<Criteria> getOredCriteria() {
+        return oredCriteria;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table subtitle_style
+     *
+     * @mbg.generated Thu Nov 13 19:51:58 CST 2025
+     */
+    public void or(Criteria criteria) {
+        oredCriteria.add(criteria);
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method corresponds to the database table subtitle_style
+     *
+     * @mbg.generated Thu Nov 13 19:51:58 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 subtitle_style
+     *
+     * @mbg.generated Thu Nov 13 19:51:58 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 subtitle_style
+     *
+     * @mbg.generated Thu Nov 13 19:51:58 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 subtitle_style
+     *
+     * @mbg.generated Thu Nov 13 19:51:58 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 subtitle_style
+     *
+     * @mbg.generated Thu Nov 13 19:51:58 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(String value) {
+            addCriterion("id =", value, "id");
+            return (Criteria) this;
+        }
+
+        public Criteria andIdNotEqualTo(String value) {
+            addCriterion("id <>", value, "id");
+            return (Criteria) this;
+        }
+
+        public Criteria andIdGreaterThan(String value) {
+            addCriterion("id >", value, "id");
+            return (Criteria) this;
+        }
+
+        public Criteria andIdGreaterThanOrEqualTo(String value) {
+            addCriterion("id >=", value, "id");
+            return (Criteria) this;
+        }
+
+        public Criteria andIdLessThan(String value) {
+            addCriterion("id <", value, "id");
+            return (Criteria) this;
+        }
+
+        public Criteria andIdLessThanOrEqualTo(String value) {
+            addCriterion("id <=", value, "id");
+            return (Criteria) this;
+        }
+
+        public Criteria andIdLike(String value) {
+            addCriterion("id like", value, "id");
+            return (Criteria) this;
+        }
+
+        public Criteria andIdNotLike(String value) {
+            addCriterion("id not like", value, "id");
+            return (Criteria) this;
+        }
+
+        public Criteria andIdIn(List<String> values) {
+            addCriterion("id in", values, "id");
+            return (Criteria) this;
+        }
+
+        public Criteria andIdNotIn(List<String> values) {
+            addCriterion("id not in", values, "id");
+            return (Criteria) this;
+        }
+
+        public Criteria andIdBetween(String value1, String value2) {
+            addCriterion("id between", value1, value2, "id");
+            return (Criteria) this;
+        }
+
+        public Criteria andIdNotBetween(String value1, String value2) {
+            addCriterion("id not between", value1, value2, "id");
+            return (Criteria) this;
+        }
+
+        public Criteria andNameIsNull() {
+            addCriterion("`name` is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andNameIsNotNull() {
+            addCriterion("`name` is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andNameEqualTo(String value) {
+            addCriterion("`name` =", value, "name");
+            return (Criteria) this;
+        }
+
+        public Criteria andNameNotEqualTo(String value) {
+            addCriterion("`name` <>", value, "name");
+            return (Criteria) this;
+        }
+
+        public Criteria andNameGreaterThan(String value) {
+            addCriterion("`name` >", value, "name");
+            return (Criteria) this;
+        }
+
+        public Criteria andNameGreaterThanOrEqualTo(String value) {
+            addCriterion("`name` >=", value, "name");
+            return (Criteria) this;
+        }
+
+        public Criteria andNameLessThan(String value) {
+            addCriterion("`name` <", value, "name");
+            return (Criteria) this;
+        }
+
+        public Criteria andNameLessThanOrEqualTo(String value) {
+            addCriterion("`name` <=", value, "name");
+            return (Criteria) this;
+        }
+
+        public Criteria andNameLike(String value) {
+            addCriterion("`name` like", value, "name");
+            return (Criteria) this;
+        }
+
+        public Criteria andNameNotLike(String value) {
+            addCriterion("`name` not like", value, "name");
+            return (Criteria) this;
+        }
+
+        public Criteria andNameIn(List<String> values) {
+            addCriterion("`name` in", values, "name");
+            return (Criteria) this;
+        }
+
+        public Criteria andNameNotIn(List<String> values) {
+            addCriterion("`name` not in", values, "name");
+            return (Criteria) this;
+        }
+
+        public Criteria andNameBetween(String value1, String value2) {
+            addCriterion("`name` between", value1, value2, "name");
+            return (Criteria) this;
+        }
+
+        public Criteria andNameNotBetween(String value1, String value2) {
+            addCriterion("`name` not between", value1, value2, "name");
+            return (Criteria) this;
+        }
+
+        public Criteria andIconUrlIsNull() {
+            addCriterion("icon_url is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andIconUrlIsNotNull() {
+            addCriterion("icon_url is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andIconUrlEqualTo(String value) {
+            addCriterion("icon_url =", value, "iconUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andIconUrlNotEqualTo(String value) {
+            addCriterion("icon_url <>", value, "iconUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andIconUrlGreaterThan(String value) {
+            addCriterion("icon_url >", value, "iconUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andIconUrlGreaterThanOrEqualTo(String value) {
+            addCriterion("icon_url >=", value, "iconUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andIconUrlLessThan(String value) {
+            addCriterion("icon_url <", value, "iconUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andIconUrlLessThanOrEqualTo(String value) {
+            addCriterion("icon_url <=", value, "iconUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andIconUrlLike(String value) {
+            addCriterion("icon_url like", value, "iconUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andIconUrlNotLike(String value) {
+            addCriterion("icon_url not like", value, "iconUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andIconUrlIn(List<String> values) {
+            addCriterion("icon_url in", values, "iconUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andIconUrlNotIn(List<String> values) {
+            addCriterion("icon_url not in", values, "iconUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andIconUrlBetween(String value1, String value2) {
+            addCriterion("icon_url between", value1, value2, "iconUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andIconUrlNotBetween(String value1, String value2) {
+            addCriterion("icon_url not between", value1, value2, "iconUrl");
+            return (Criteria) this;
+        }
+
+        public Criteria andFontNameIsNull() {
+            addCriterion("font_name is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andFontNameIsNotNull() {
+            addCriterion("font_name is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andFontNameEqualTo(String value) {
+            addCriterion("font_name =", value, "fontName");
+            return (Criteria) this;
+        }
+
+        public Criteria andFontNameNotEqualTo(String value) {
+            addCriterion("font_name <>", value, "fontName");
+            return (Criteria) this;
+        }
+
+        public Criteria andFontNameGreaterThan(String value) {
+            addCriterion("font_name >", value, "fontName");
+            return (Criteria) this;
+        }
+
+        public Criteria andFontNameGreaterThanOrEqualTo(String value) {
+            addCriterion("font_name >=", value, "fontName");
+            return (Criteria) this;
+        }
+
+        public Criteria andFontNameLessThan(String value) {
+            addCriterion("font_name <", value, "fontName");
+            return (Criteria) this;
+        }
+
+        public Criteria andFontNameLessThanOrEqualTo(String value) {
+            addCriterion("font_name <=", value, "fontName");
+            return (Criteria) this;
+        }
+
+        public Criteria andFontNameLike(String value) {
+            addCriterion("font_name like", value, "fontName");
+            return (Criteria) this;
+        }
+
+        public Criteria andFontNameNotLike(String value) {
+            addCriterion("font_name not like", value, "fontName");
+            return (Criteria) this;
+        }
+
+        public Criteria andFontNameIn(List<String> values) {
+            addCriterion("font_name in", values, "fontName");
+            return (Criteria) this;
+        }
+
+        public Criteria andFontNameNotIn(List<String> values) {
+            addCriterion("font_name not in", values, "fontName");
+            return (Criteria) this;
+        }
+
+        public Criteria andFontNameBetween(String value1, String value2) {
+            addCriterion("font_name between", value1, value2, "fontName");
+            return (Criteria) this;
+        }
+
+        public Criteria andFontNameNotBetween(String value1, String value2) {
+            addCriterion("font_name not between", value1, value2, "fontName");
+            return (Criteria) this;
+        }
+
+        public Criteria andDataStatusIsNull() {
+            addCriterion("data_status is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andDataStatusIsNotNull() {
+            addCriterion("data_status is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andDataStatusEqualTo(Integer value) {
+            addCriterion("data_status =", value, "dataStatus");
+            return (Criteria) this;
+        }
+
+        public Criteria andDataStatusNotEqualTo(Integer value) {
+            addCriterion("data_status <>", value, "dataStatus");
+            return (Criteria) this;
+        }
+
+        public Criteria andDataStatusGreaterThan(Integer value) {
+            addCriterion("data_status >", value, "dataStatus");
+            return (Criteria) this;
+        }
+
+        public Criteria andDataStatusGreaterThanOrEqualTo(Integer value) {
+            addCriterion("data_status >=", value, "dataStatus");
+            return (Criteria) this;
+        }
+
+        public Criteria andDataStatusLessThan(Integer value) {
+            addCriterion("data_status <", value, "dataStatus");
+            return (Criteria) this;
+        }
+
+        public Criteria andDataStatusLessThanOrEqualTo(Integer value) {
+            addCriterion("data_status <=", value, "dataStatus");
+            return (Criteria) this;
+        }
+
+        public Criteria andDataStatusIn(List<Integer> values) {
+            addCriterion("data_status in", values, "dataStatus");
+            return (Criteria) this;
+        }
+
+        public Criteria andDataStatusNotIn(List<Integer> values) {
+            addCriterion("data_status not in", values, "dataStatus");
+            return (Criteria) this;
+        }
+
+        public Criteria andDataStatusBetween(Integer value1, Integer value2) {
+            addCriterion("data_status between", value1, value2, "dataStatus");
+            return (Criteria) this;
+        }
+
+        public Criteria andDataStatusNotBetween(Integer value1, Integer value2) {
+            addCriterion("data_status not between", value1, value2, "dataStatus");
+            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(Date value) {
+            addCriterion("create_time =", value, "createTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreateTimeNotEqualTo(Date value) {
+            addCriterion("create_time <>", value, "createTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreateTimeGreaterThan(Date value) {
+            addCriterion("create_time >", value, "createTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreateTimeGreaterThanOrEqualTo(Date value) {
+            addCriterion("create_time >=", value, "createTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreateTimeLessThan(Date value) {
+            addCriterion("create_time <", value, "createTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreateTimeLessThanOrEqualTo(Date value) {
+            addCriterion("create_time <=", value, "createTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreateTimeIn(List<Date> values) {
+            addCriterion("create_time in", values, "createTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreateTimeNotIn(List<Date> values) {
+            addCriterion("create_time not in", values, "createTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreateTimeBetween(Date value1, Date value2) {
+            addCriterion("create_time between", value1, value2, "createTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreateTimeNotBetween(Date value1, Date 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(Date value) {
+            addCriterion("update_time =", value, "updateTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andUpdateTimeNotEqualTo(Date value) {
+            addCriterion("update_time <>", value, "updateTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andUpdateTimeGreaterThan(Date value) {
+            addCriterion("update_time >", value, "updateTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andUpdateTimeGreaterThanOrEqualTo(Date value) {
+            addCriterion("update_time >=", value, "updateTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andUpdateTimeLessThan(Date value) {
+            addCriterion("update_time <", value, "updateTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andUpdateTimeLessThanOrEqualTo(Date value) {
+            addCriterion("update_time <=", value, "updateTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andUpdateTimeIn(List<Date> values) {
+            addCriterion("update_time in", values, "updateTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andUpdateTimeNotIn(List<Date> values) {
+            addCriterion("update_time not in", values, "updateTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andUpdateTimeBetween(Date value1, Date value2) {
+            addCriterion("update_time between", value1, value2, "updateTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andUpdateTimeNotBetween(Date value1, Date 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 subtitle_style
+     *
+     * @mbg.generated do_not_delete_during_merge Thu Nov 13 19:51:58 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 subtitle_style
+     *
+     * @mbg.generated Thu Nov 13 19:51:58 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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);
+        }
+    }
+}

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

@@ -0,0 +1,21 @@
+package com.tzld.supply.service;
+
+
+import com.tzld.supply.model.entity.ali.AliVoiceResultData;
+import com.tzld.supply.model.po.supply.spider.ToolsAudioTransRecord;
+
+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);
+
+    ToolsAudioTransRecord getExistToolsAudioTransRecord(String audioUrl);
+}

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

@@ -0,0 +1,142 @@
+package com.tzld.supply.service.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.entity.ali.AliVoiceResultData;
+import com.tzld.supply.model.entity.ali.AliVoiceResultSentenceData;
+import com.tzld.supply.model.entity.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.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;
+    }
+
+    @Override
+    public 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;
+    }
+}

+ 125 - 0
core/src/main/java/com/tzld/supply/util/AssSubtitleUtil.java

@@ -0,0 +1,125 @@
+package com.tzld.supply.util;
+
+import cn.hutool.core.collection.CollectionUtil;
+import com.tzld.supply.common.enums.ExceptionEnum;
+import com.tzld.supply.common.exception.CommonException;
+import org.springframework.util.StringUtils;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.util.List;
+
+public class AssSubtitleUtil {
+
+    public static String buildAssSubtitleData(String assStyleData, String subtitlePosition, String fontSizeStr,
+                                              String fontName, String srt, int lineWidth) {
+        if ("上".equals(subtitlePosition)) {
+            assStyleData = assStyleData.replace("{Alignment}", "8").replace("{MarginV}", "7");
+        } else if ("中".equals(subtitlePosition)) {
+            assStyleData = assStyleData.replace("{Alignment}", "5").replace("{MarginV}", "0");
+        } else {
+            assStyleData = assStyleData.replace("{Alignment}", "2").replace("{MarginV}", "10");
+        }
+        int fontSize;
+        if ("超大".equals(fontSizeStr)) {
+            fontSize = 20;
+            assStyleData = assStyleData.replace("{Fontsize}", "20");
+
+        } else if ("大".equals(fontSizeStr)) {
+            fontSize = 9;
+            assStyleData = assStyleData.replace("{Fontsize}", "9");
+
+        } else if ("中".equals(fontSizeStr)) {
+            fontSize = 6;
+            assStyleData = assStyleData.replace("{Fontsize}", "6");
+        } else {
+            fontSize = 3;
+            assStyleData = assStyleData.replace("{Fontsize}", "3");
+        }
+        assStyleData = assStyleData.replace("{Fontsize}", fontSize + "");
+
+        if (!StringUtils.hasText(fontName)) {
+            fontName = "PingFang SC";
+        }
+        assStyleData = assStyleData.replace("{Fontname}", fontName);
+
+        String dialogues = covertDialoguesFromSrt(srt, fontName, fontSize, lineWidth);
+        assStyleData = assStyleData.replace("{Dialogues}", dialogues);
+
+        return assStyleData;
+    }
+
+    public static String covertDialoguesFromSrt(String srt, String fontName, int fontSize, int lineWidth) {
+        // start 00:00:00.16 end 00:00:01.26
+        String dialogueTemplate = "Dialogue: 0,{start},{end},Default,,0,0,0,,{text}";
+        StringBuilder sb = new StringBuilder();
+        List<VideoInputUtils.Subtitle> subtitleList;
+        try {
+            subtitleList = VideoInputUtils.parseSRT(srt);
+        } catch (IOException e) {
+            throw new CommonException(ExceptionEnum.DATA_ERROR.getCode(), "SRT文件解析失败");
+        }
+        if (CollectionUtil.isEmpty(subtitleList) && StringUtils.hasText(srt)) {
+            VideoInputUtils.Subtitle subtitle = new VideoInputUtils.Subtitle("00:00:00,00", "00:00:60,00", srt);
+            subtitleList.add(subtitle);
+        }
+        Font font = new Font(fontName, Font.PLAIN, fontSize + 1);
+        for (VideoInputUtils.Subtitle subtitle : subtitleList) {
+            String start = covertSrtTimeToAssTime(subtitle.getStartTime());
+            String end = covertSrtTimeToAssTime(subtitle.getEndTime());
+            String text = autoLineBreak(subtitle.getText(), font, lineWidth);
+            String dialogue = dialogueTemplate.replace("{start}", start).replace("{end}", end).replace("{text}", text);
+            sb.append(dialogue).append("\n");
+        }
+        return sb.toString().trim();
+    }
+
+    public static String covertSrtTimeToAssTime(String srtTime) {
+        String[] array = srtTime.split(",");
+        String millis = array[1];
+        if (millis.length() == 3) {
+            millis = millis.substring(0, 2);
+        }
+        return array[0] + "." + millis;
+    }
+
+    public static String autoLineBreak(String text, Font font, int lineWidth) {
+        StringBuilder sb = new StringBuilder();
+        int currentWidth = 0;
+        for (char c : text.toCharArray()) {
+            String word = String.valueOf(c);
+            int wordWidth = getTextWidth(word, font);
+            currentWidth = currentWidth + wordWidth;
+            if (currentWidth > lineWidth) {
+                sb.append("\\N");
+                currentWidth = wordWidth;
+            }
+            sb.append(word);
+        }
+        return sb.toString();
+    }
+
+    public static int getTextWidth(String text, Font font) {
+        BufferedImage img = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
+        Graphics g = img.getGraphics();
+        g.setFont(font);
+        FontMetrics fm = g.getFontMetrics();
+        return fm.stringWidth(text);
+    }
+
+    public static void main(String[] args) {
+        Font font = new Font("PingFang SC", Font.PLAIN, 20);
+        String str = "清凉夏日的快乐时光";
+        int count = 0;
+        for (char c : str.toCharArray()) {
+            int textWidth = getTextWidth(String.valueOf(c), font);
+            System.out.println(c + "," + textWidth);
+            count = count + textWidth;
+        }
+        System.out.println("count:" + count);
+        System.out.println(autoLineBreak(str, font, 220));
+
+//        System.out.println(executor.covertSrtTimeToAssTime("00:01:57,231"));
+    }
+}

+ 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));
+    }
+}

+ 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;
+    }
+}

+ 100 - 0
core/src/main/java/com/tzld/supply/util/VideoInputUtils.java

@@ -0,0 +1,100 @@
+package com.tzld.supply.util;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class VideoInputUtils {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(VideoInputUtils.class);
+
+    public static List<String> getSrtLines(String srtText) {
+
+        try {
+            return parseSRT(srtText).stream().map(Subtitle::getText).collect(Collectors.toList());
+        } catch (Exception e) {
+            LOGGER.error("parseSRT error {}", srtText, e);
+        }
+        return new ArrayList<>();
+    }
+
+    public static void main(String[] args) {
+        try {
+            String srtText = "5\n" +
+                    "00:01:57,230 --> 00:02:03,230\n" +
+                    "我們透過歌聲來回憶,每一顆串珠都代表著一個故事\n" +
+                    "\n" +
+                    "6\n" +
+                    "00:02:05,465 --> 00:02:08,455\n" +
+                    "我們兒子出生時的串珠\n" +
+                    "\n";
+
+            List<Subtitle> subtitles = parseSRT(srtText);
+            for (Subtitle subtitle : subtitles) {
+                System.out.println("时间戳: " + subtitle.getStartTime() + " - " + subtitle.getEndTime());
+                System.out.println("歌词: " + subtitle.getText());
+                System.out.println();
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    public static List<Subtitle> parseSRT(String srtText) throws IOException {
+        List<Subtitle> subtitles = new ArrayList<>();
+        StringReader stringReader = new StringReader(srtText);
+        try (BufferedReader reader = new BufferedReader(stringReader)) {
+            String line;
+            while ((line = reader.readLine()) != null) {
+                if (line.matches("\\d+") || line.isEmpty()) {
+                    // 此行是字幕编号,跳过;字幕间多个换行符
+                    continue;
+                }
+                String timeLine = line; // 时间戳行
+                String text = "";
+                while ((line = reader.readLine()) != null && !line.isEmpty()) {
+                    text += line + "\n";
+                }
+                text = text.trim();
+                String[] timeParts = timeLine.split(" --> ");
+                if (timeParts.length == 2) {
+                    String startTime = timeParts[0];
+                    String endTime = timeParts[1];
+                    subtitles.add(new Subtitle(startTime, endTime, text));
+                }
+            }
+        }
+        return subtitles;
+    }
+
+    public static class Subtitle {
+        private String startTime;
+        private String endTime;
+        private String text;
+
+        public Subtitle(String startTime, String endTime, String text) {
+            this.startTime = startTime;
+            this.endTime = endTime;
+            this.text = text;
+        }
+
+        public String getStartTime() {
+            return startTime;
+        }
+
+        public String getEndTime() {
+            return endTime;
+        }
+
+        public String getText() {
+            return text;
+        }
+    }
+
+}

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

@@ -53,7 +53,9 @@
 <!--        <table tableName="fish_api_record" domainObjectName="" alias=""/>-->
 <!--        <table tableName="produce_video" domainObjectName="" alias=""/>-->
 <!--        <table tableName="produce_video_audio" domainObjectName="" alias=""/>-->
-        <table tableName="produce_video_material" domainObjectName="" alias=""/>
+<!--        <table tableName="produce_video_material" domainObjectName="" alias=""/>-->
+<!--        <table tableName="tools_audio_trans_record" domainObjectName="" alias=""/>-->
+        <table tableName="subtitle_style" domainObjectName="" alias=""/>
     </context>
 
 </generatorConfiguration>

+ 404 - 0
core/src/main/resources/mapper/supply/spider/SubtitleStyleMapper.xml

@@ -0,0 +1,404 @@
+<?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.SubtitleStyleMapper">
+  <resultMap id="BaseResultMap" type="com.tzld.supply.model.po.supply.spider.SubtitleStyle">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Thu Nov 13 19:51:58 CST 2025.
+    -->
+    <id column="id" jdbcType="VARCHAR" property="id" />
+    <result column="name" jdbcType="VARCHAR" property="name" />
+    <result column="icon_url" jdbcType="VARCHAR" property="iconUrl" />
+    <result column="font_name" jdbcType="VARCHAR" property="fontName" />
+    <result column="data_status" jdbcType="INTEGER" property="dataStatus" />
+    <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
+    <result column="update_time" jdbcType="TIMESTAMP" property="updateTime" />
+  </resultMap>
+  <resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="com.tzld.supply.model.po.supply.spider.SubtitleStyle">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Thu Nov 13 19:51:58 CST 2025.
+    -->
+    <result column="ass_style_data" jdbcType="LONGVARCHAR" property="assStyleData" />
+  </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 Nov 13 19:51:58 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 Nov 13 19:51:58 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 Nov 13 19:51:58 CST 2025.
+    -->
+    id, `name`, icon_url, font_name, data_status, create_time, update_time
+  </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 Nov 13 19:51:58 CST 2025.
+    -->
+    ass_style_data
+  </sql>
+  <select id="selectByExampleWithBLOBs" parameterType="com.tzld.supply.model.po.supply.spider.SubtitleStyleExample" resultMap="ResultMapWithBLOBs">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Thu Nov 13 19:51:58 CST 2025.
+    -->
+    select
+    <if test="distinct">
+      distinct
+    </if>
+    <include refid="Base_Column_List" />
+    ,
+    <include refid="Blob_Column_List" />
+    from subtitle_style
+    <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.SubtitleStyleExample" resultMap="BaseResultMap">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Thu Nov 13 19:51:58 CST 2025.
+    -->
+    select
+    <if test="distinct">
+      distinct
+    </if>
+    <include refid="Base_Column_List" />
+    from subtitle_style
+    <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.String" resultMap="ResultMapWithBLOBs">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Thu Nov 13 19:51:58 CST 2025.
+    -->
+    select 
+    <include refid="Base_Column_List" />
+    ,
+    <include refid="Blob_Column_List" />
+    from subtitle_style
+    where id = #{id,jdbcType=VARCHAR}
+  </select>
+  <delete id="deleteByPrimaryKey" parameterType="java.lang.String">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Thu Nov 13 19:51:58 CST 2025.
+    -->
+    delete from subtitle_style
+    where id = #{id,jdbcType=VARCHAR}
+  </delete>
+  <delete id="deleteByExample" parameterType="com.tzld.supply.model.po.supply.spider.SubtitleStyleExample">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Thu Nov 13 19:51:58 CST 2025.
+    -->
+    delete from subtitle_style
+    <if test="_parameter != null">
+      <include refid="Example_Where_Clause" />
+    </if>
+  </delete>
+  <insert id="insert" parameterType="com.tzld.supply.model.po.supply.spider.SubtitleStyle">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Thu Nov 13 19:51:58 CST 2025.
+    -->
+    insert into subtitle_style (id, `name`, icon_url, 
+      font_name, data_status, create_time, 
+      update_time, ass_style_data)
+    values (#{id,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR}, #{iconUrl,jdbcType=VARCHAR}, 
+      #{fontName,jdbcType=VARCHAR}, #{dataStatus,jdbcType=INTEGER}, #{createTime,jdbcType=TIMESTAMP}, 
+      #{updateTime,jdbcType=TIMESTAMP}, #{assStyleData,jdbcType=LONGVARCHAR})
+  </insert>
+  <insert id="insertSelective" parameterType="com.tzld.supply.model.po.supply.spider.SubtitleStyle">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Thu Nov 13 19:51:58 CST 2025.
+    -->
+    insert into subtitle_style
+    <trim prefix="(" suffix=")" suffixOverrides=",">
+      <if test="id != null">
+        id,
+      </if>
+      <if test="name != null">
+        `name`,
+      </if>
+      <if test="iconUrl != null">
+        icon_url,
+      </if>
+      <if test="fontName != null">
+        font_name,
+      </if>
+      <if test="dataStatus != null">
+        data_status,
+      </if>
+      <if test="createTime != null">
+        create_time,
+      </if>
+      <if test="updateTime != null">
+        update_time,
+      </if>
+      <if test="assStyleData != null">
+        ass_style_data,
+      </if>
+    </trim>
+    <trim prefix="values (" suffix=")" suffixOverrides=",">
+      <if test="id != null">
+        #{id,jdbcType=VARCHAR},
+      </if>
+      <if test="name != null">
+        #{name,jdbcType=VARCHAR},
+      </if>
+      <if test="iconUrl != null">
+        #{iconUrl,jdbcType=VARCHAR},
+      </if>
+      <if test="fontName != null">
+        #{fontName,jdbcType=VARCHAR},
+      </if>
+      <if test="dataStatus != null">
+        #{dataStatus,jdbcType=INTEGER},
+      </if>
+      <if test="createTime != null">
+        #{createTime,jdbcType=TIMESTAMP},
+      </if>
+      <if test="updateTime != null">
+        #{updateTime,jdbcType=TIMESTAMP},
+      </if>
+      <if test="assStyleData != null">
+        #{assStyleData,jdbcType=LONGVARCHAR},
+      </if>
+    </trim>
+  </insert>
+  <select id="countByExample" parameterType="com.tzld.supply.model.po.supply.spider.SubtitleStyleExample" resultType="java.lang.Long">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Thu Nov 13 19:51:58 CST 2025.
+    -->
+    select count(*) from subtitle_style
+    <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 Nov 13 19:51:58 CST 2025.
+    -->
+    update subtitle_style
+    <set>
+      <if test="record.id != null">
+        id = #{record.id,jdbcType=VARCHAR},
+      </if>
+      <if test="record.name != null">
+        `name` = #{record.name,jdbcType=VARCHAR},
+      </if>
+      <if test="record.iconUrl != null">
+        icon_url = #{record.iconUrl,jdbcType=VARCHAR},
+      </if>
+      <if test="record.fontName != null">
+        font_name = #{record.fontName,jdbcType=VARCHAR},
+      </if>
+      <if test="record.dataStatus != null">
+        data_status = #{record.dataStatus,jdbcType=INTEGER},
+      </if>
+      <if test="record.createTime != null">
+        create_time = #{record.createTime,jdbcType=TIMESTAMP},
+      </if>
+      <if test="record.updateTime != null">
+        update_time = #{record.updateTime,jdbcType=TIMESTAMP},
+      </if>
+      <if test="record.assStyleData != null">
+        ass_style_data = #{record.assStyleData,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 Nov 13 19:51:58 CST 2025.
+    -->
+    update subtitle_style
+    set id = #{record.id,jdbcType=VARCHAR},
+      `name` = #{record.name,jdbcType=VARCHAR},
+      icon_url = #{record.iconUrl,jdbcType=VARCHAR},
+      font_name = #{record.fontName,jdbcType=VARCHAR},
+      data_status = #{record.dataStatus,jdbcType=INTEGER},
+      create_time = #{record.createTime,jdbcType=TIMESTAMP},
+      update_time = #{record.updateTime,jdbcType=TIMESTAMP},
+      ass_style_data = #{record.assStyleData,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 Nov 13 19:51:58 CST 2025.
+    -->
+    update subtitle_style
+    set id = #{record.id,jdbcType=VARCHAR},
+      `name` = #{record.name,jdbcType=VARCHAR},
+      icon_url = #{record.iconUrl,jdbcType=VARCHAR},
+      font_name = #{record.fontName,jdbcType=VARCHAR},
+      data_status = #{record.dataStatus,jdbcType=INTEGER},
+      create_time = #{record.createTime,jdbcType=TIMESTAMP},
+      update_time = #{record.updateTime,jdbcType=TIMESTAMP}
+    <if test="_parameter != null">
+      <include refid="Update_By_Example_Where_Clause" />
+    </if>
+  </update>
+  <update id="updateByPrimaryKeySelective" parameterType="com.tzld.supply.model.po.supply.spider.SubtitleStyle">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Thu Nov 13 19:51:58 CST 2025.
+    -->
+    update subtitle_style
+    <set>
+      <if test="name != null">
+        `name` = #{name,jdbcType=VARCHAR},
+      </if>
+      <if test="iconUrl != null">
+        icon_url = #{iconUrl,jdbcType=VARCHAR},
+      </if>
+      <if test="fontName != null">
+        font_name = #{fontName,jdbcType=VARCHAR},
+      </if>
+      <if test="dataStatus != null">
+        data_status = #{dataStatus,jdbcType=INTEGER},
+      </if>
+      <if test="createTime != null">
+        create_time = #{createTime,jdbcType=TIMESTAMP},
+      </if>
+      <if test="updateTime != null">
+        update_time = #{updateTime,jdbcType=TIMESTAMP},
+      </if>
+      <if test="assStyleData != null">
+        ass_style_data = #{assStyleData,jdbcType=LONGVARCHAR},
+      </if>
+    </set>
+    where id = #{id,jdbcType=VARCHAR}
+  </update>
+  <update id="updateByPrimaryKeyWithBLOBs" parameterType="com.tzld.supply.model.po.supply.spider.SubtitleStyle">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Thu Nov 13 19:51:58 CST 2025.
+    -->
+    update subtitle_style
+    set `name` = #{name,jdbcType=VARCHAR},
+      icon_url = #{iconUrl,jdbcType=VARCHAR},
+      font_name = #{fontName,jdbcType=VARCHAR},
+      data_status = #{dataStatus,jdbcType=INTEGER},
+      create_time = #{createTime,jdbcType=TIMESTAMP},
+      update_time = #{updateTime,jdbcType=TIMESTAMP},
+      ass_style_data = #{assStyleData,jdbcType=LONGVARCHAR}
+    where id = #{id,jdbcType=VARCHAR}
+  </update>
+  <update id="updateByPrimaryKey" parameterType="com.tzld.supply.model.po.supply.spider.SubtitleStyle">
+    <!--
+      WARNING - @mbg.generated
+      This element is automatically generated by MyBatis Generator, do not modify.
+      This element was generated on Thu Nov 13 19:51:58 CST 2025.
+    -->
+    update subtitle_style
+    set `name` = #{name,jdbcType=VARCHAR},
+      icon_url = #{iconUrl,jdbcType=VARCHAR},
+      font_name = #{fontName,jdbcType=VARCHAR},
+      data_status = #{dataStatus,jdbcType=INTEGER},
+      create_time = #{createTime,jdbcType=TIMESTAMP},
+      update_time = #{updateTime,jdbcType=TIMESTAMP}
+    where id = #{id,jdbcType=VARCHAR}
+  </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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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 Nov 13 19:18:13 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>