소스 검색

Initial commit

wangyunpeng 4 일 전
커밋
d0b462f03a
87개의 변경된 파일8366개의 추가작업 그리고 0개의 파일을 삭제
  1. 37 0
      .gitignore
  2. 20 0
      core/pom.xml
  3. 15 0
      core/src/main/java/com/tzld/videoVector/annotation/NoRequestLog.java
  4. 94 0
      core/src/main/java/com/tzld/videoVector/api/DeepSeekApiService.java
  5. 152 0
      core/src/main/java/com/tzld/videoVector/api/google/GeminiApiService.java
  6. 16 0
      core/src/main/java/com/tzld/videoVector/api/google/ModelEnum.java
  7. 361 0
      core/src/main/java/com/tzld/videoVector/common/LoghubAppender.java
  8. 34 0
      core/src/main/java/com/tzld/videoVector/common/LoghubAppenderCallback.java
  9. 81 0
      core/src/main/java/com/tzld/videoVector/common/base/CommonResponse.java
  10. 13 0
      core/src/main/java/com/tzld/videoVector/common/constant/ResponseConstant.java
  11. 30 0
      core/src/main/java/com/tzld/videoVector/common/enums/EnumUploadFileType.java
  12. 32 0
      core/src/main/java/com/tzld/videoVector/common/enums/ExceptionEnum.java
  13. 87 0
      core/src/main/java/com/tzld/videoVector/common/exception/CommonException.java
  14. 386 0
      core/src/main/java/com/tzld/videoVector/config/AliOssConfig.java
  15. 86 0
      core/src/main/java/com/tzld/videoVector/config/HttpClientConfig.java
  16. 43 0
      core/src/main/java/com/tzld/videoVector/config/RedisConfig.java
  17. 11 0
      core/src/main/java/com/tzld/videoVector/config/SchedulingConfig.java
  18. 56 0
      core/src/main/java/com/tzld/videoVector/config/SwaggerConfig.java
  19. 49 0
      core/src/main/java/com/tzld/videoVector/config/XxlJobConfig.java
  20. 57 0
      core/src/main/java/com/tzld/videoVector/config/db/VideoVectorDBConfig.java
  21. 19 0
      core/src/main/java/com/tzld/videoVector/config/mybatis/VideoVectorMybatisConfig.java
  22. 31 0
      core/src/main/java/com/tzld/videoVector/dao/generator/MybatisGeneratorMain.java
  23. 73 0
      core/src/main/java/com/tzld/videoVector/handle/GlobalExceptionHandle.java
  24. 38 0
      core/src/main/java/com/tzld/videoVector/interceptor/CrosDomainAllowInterceptor.java
  25. 32 0
      core/src/main/java/com/tzld/videoVector/job/SpiderJob.java
  26. 39 0
      core/src/main/java/com/tzld/videoVector/model/entity/DeepSeekOfficialApiResponse.java
  27. 11 0
      core/src/main/java/com/tzld/videoVector/model/entity/DeepSeekResult.java
  28. 15 0
      core/src/main/java/com/tzld/videoVector/model/entity/PQResponse.java
  29. 12 0
      core/src/main/java/com/tzld/videoVector/model/entity/ali/AliVoiceResultData.java
  30. 14 0
      core/src/main/java/com/tzld/videoVector/model/entity/ali/AliVoiceResultSentenceData.java
  31. 11 0
      core/src/main/java/com/tzld/videoVector/model/entity/ali/AliVoiceResultWordData.java
  32. 18 0
      core/src/main/java/com/tzld/videoVector/model/param/FileUploadParam.java
  33. 15 0
      core/src/main/java/com/tzld/videoVector/model/param/OssUploadSignParam.java
  34. 19 0
      core/src/main/java/com/tzld/videoVector/model/param/StsTokenParam.java
  35. 196 0
      core/src/main/java/com/tzld/videoVector/model/param/VideoApiBaseParam.java
  36. 25 0
      core/src/main/java/com/tzld/videoVector/model/vo/FileInfo.java
  37. 49 0
      core/src/main/java/com/tzld/videoVector/model/vo/PolicyDetailVO.java
  38. 13 0
      core/src/main/java/com/tzld/videoVector/model/vo/SignatureVO.java
  39. 20 0
      core/src/main/java/com/tzld/videoVector/model/vo/StsTokenVO.java
  40. 8 0
      core/src/main/java/com/tzld/videoVector/service/SpiderTaskService.java
  41. 10 0
      core/src/main/java/com/tzld/videoVector/service/impl/SpiderTaskServiceImpl.java
  42. 1514 0
      core/src/main/java/com/tzld/videoVector/util/AliOssFileTool.java
  43. 139 0
      core/src/main/java/com/tzld/videoVector/util/BaseUtils.java
  44. 169 0
      core/src/main/java/com/tzld/videoVector/util/CdnUtil.java
  45. 333 0
      core/src/main/java/com/tzld/videoVector/util/DateUtils.java
  46. 23 0
      core/src/main/java/com/tzld/videoVector/util/DistributedIdGenerator.java
  47. 96 0
      core/src/main/java/com/tzld/videoVector/util/HttpClientFactory.java
  48. 69 0
      core/src/main/java/com/tzld/videoVector/util/IpUtil.java
  49. 50 0
      core/src/main/java/com/tzld/videoVector/util/MapBuilder.java
  50. 72 0
      core/src/main/java/com/tzld/videoVector/util/Md5Util.java
  51. 175 0
      core/src/main/java/com/tzld/videoVector/util/PropertiesUtils.java
  52. 64 0
      core/src/main/java/com/tzld/videoVector/util/RandomUtil.java
  53. 257 0
      core/src/main/java/com/tzld/videoVector/util/RedisUtils.java
  54. 55 0
      core/src/main/java/com/tzld/videoVector/util/TimelineUtils.java
  55. 76 0
      core/src/main/java/com/tzld/videoVector/util/feishu/FeiShu.java
  56. 190 0
      core/src/main/java/com/tzld/videoVector/util/feishu/FeishuExcelUtil.java
  57. 146 0
      core/src/main/java/com/tzld/videoVector/util/feishu/FeishuMessageSender.java
  58. 37 0
      core/src/main/java/com/tzld/videoVector/util/feishu/model/FeishuChatListResult.java
  59. 62 0
      core/src/main/java/com/tzld/videoVector/util/feishu/model/FeishuGroup.java
  60. 48 0
      core/src/main/java/com/tzld/videoVector/util/feishu/model/Message.java
  61. 50 0
      core/src/main/java/com/tzld/videoVector/util/feishu/model/MessageParams.java
  62. 26 0
      core/src/main/java/com/tzld/videoVector/util/feishu/model/config/MessageConfig.java
  63. 17 0
      core/src/main/java/com/tzld/videoVector/util/feishu/model/element/BaseElement.java
  64. 24 0
      core/src/main/java/com/tzld/videoVector/util/feishu/model/element/DivElement.java
  65. 30 0
      core/src/main/java/com/tzld/videoVector/util/feishu/model/element/Filed.java
  66. 31 0
      core/src/main/java/com/tzld/videoVector/util/feishu/model/element/MarkDownText.java
  67. 14 0
      core/src/main/java/com/tzld/videoVector/util/feishu/model/header/HeadTitle.java
  68. 17 0
      core/src/main/java/com/tzld/videoVector/util/feishu/model/header/MessageHeader.java
  69. 764 0
      core/src/main/java/com/tzld/videoVector/util/http/HttpClientUtils.java
  70. 24 0
      core/src/main/java/com/tzld/videoVector/util/http/HttpDeleteExpand.java
  71. 97 0
      core/src/main/java/com/tzld/videoVector/util/http/HttpResponseContent.java
  72. 61 0
      core/src/main/resources/generator/mybatis-spider-generator-config.xml
  73. 282 0
      pom.xml
  74. 51 0
      server/pom.xml
  75. 33 0
      server/src/main/java/com/tzld/videoVector/Application.java
  76. 159 0
      server/src/main/java/com/tzld/videoVector/aop/LogRequestAop.java
  77. 102 0
      server/src/main/java/com/tzld/videoVector/controller/FileController.java
  78. 21 0
      server/src/main/java/com/tzld/videoVector/controller/HealthController.java
  79. 24 0
      server/src/main/java/com/tzld/videoVector/controller/XxlJobController.java
  80. 40 0
      server/src/main/java/com/tzld/videoVector/filter/CustomFilter.java
  81. 57 0
      server/src/main/resources/application-dev.yml
  82. 53 0
      server/src/main/resources/application-prod.yml
  83. 52 0
      server/src/main/resources/application-test.yml
  84. 100 0
      server/src/main/resources/application.yml
  85. 166 0
      server/src/main/resources/logback-spring.xml
  86. 42 0
      server/src/test/java/FeishuTest.java
  87. 126 0
      server/src/test/java/GeminiTest.java

+ 37 - 0
.gitignore

@@ -0,0 +1,37 @@
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### Eclipse ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
+
+### Mac OS ###
+.DS_Store
+
+*.log

+ 20 - 0
core/pom.xml

@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>com.tzld.piaoquan</groupId>
+        <artifactId>video-vector-server</artifactId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>core</artifactId>
+
+    <properties>
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+
+</project>

+ 15 - 0
core/src/main/java/com/tzld/videoVector/annotation/NoRequestLog.java

@@ -0,0 +1,15 @@
+package com.tzld.videoVector.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 请求日志排除注解
+ *
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface NoRequestLog {
+}

+ 94 - 0
core/src/main/java/com/tzld/videoVector/api/DeepSeekApiService.java

@@ -0,0 +1,94 @@
+package com.tzld.videoVector.api;
+
+import cn.hutool.core.collection.CollectionUtil;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.tzld.videoVector.model.entity.DeepSeekOfficialApiResponse;
+import com.tzld.videoVector.model.entity.DeepSeekResult;
+import com.tzld.videoVector.util.MapBuilder;
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.*;
+import org.apache.http.util.TextUtils;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.PostConstruct;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.TimeUnit;
+
+@Service
+@Slf4j
+public class DeepSeekApiService {
+
+    private OkHttpClient client;
+
+    @PostConstruct
+    public void init() {
+        client = new OkHttpClient().newBuilder()
+                .connectTimeout(5, TimeUnit.MINUTES)
+                .readTimeout(5, TimeUnit.MINUTES)
+                .writeTimeout(5, TimeUnit.MINUTES)
+                .build();
+    }
+
+    public DeepSeekResult requestOfficialApi(String prompt, String model, Double temperature, Boolean isJSON) {
+        DeepSeekResult result = new DeepSeekResult();
+        result.setSuccess(false);
+        if (TextUtils.isBlank(prompt) || TextUtils.isBlank(prompt.trim())) {
+            result.setFailReason("prompt is empty");
+            return result;
+        }
+
+        try {
+            JSONArray jsonArray = new JSONArray();
+            JSONObject message = new JSONObject();
+            message.put("role", "user");
+            message.put("content", prompt);
+            jsonArray.add(message);
+
+            Map<Object, Object> bodyParam = MapBuilder
+                    .builder()
+                    .put("model", Optional.ofNullable(model).orElse("deepseek-chat"))
+                    .put("temperature", Optional.ofNullable(temperature).orElse(1.0))
+                    .put("messages", jsonArray)
+                    .build();
+            if (isJSON) {
+                JSONObject formatJSON = new JSONObject();
+                formatJSON.put("type", "json_object");
+                bodyParam.put("response_format", formatJSON);
+            }
+
+            MediaType mediaType = MediaType.parse("application/json");
+            RequestBody body = RequestBody.create(mediaType, JSONObject.toJSONString(bodyParam));
+            Request request = new Request.Builder()
+                    .url("https://api.deepseek.com/chat/completions")
+                    .method("POST", body)
+                    .addHeader("Content-Type", "application/json")
+                    .addHeader("Authorization", "Bearer sk-d0c7bd4e16454db19c1451810f496399")
+                    .build();
+            Response response = client.newCall(request).execute();
+
+            String responseContent = response.body().string();
+            result.setResponseStr(responseContent);
+            log.info("deepseek api responseContent = {}", responseContent);
+            if (response.isSuccessful()) {
+                DeepSeekOfficialApiResponse obj = JSONObject.parseObject(responseContent, DeepSeekOfficialApiResponse.class);
+                if (CollectionUtil.isNotEmpty(obj.getChoices())) {
+                    result.setSuccess(true);
+                    result.setResponse(obj);
+                } else {
+                    JSONObject json = JSONObject.parseObject(responseContent);
+                    JSONObject errorObj = json.getJSONObject("error");
+                    result.setFailReason("response error, errorMsg: " + errorObj.getString("message"));
+                }
+            } else {
+                JSONObject json = JSONObject.parseObject(responseContent);
+                result.setFailReason("request error code:" + response.code() + " message:" + json.getString("error"));
+            }
+        } catch (Exception e) {
+            log.error("deepseek official api fail: " + e.getMessage());
+            result.setFailReason(e.getMessage());
+        }
+        return result;
+    }
+}

+ 152 - 0
core/src/main/java/com/tzld/videoVector/api/google/GeminiApiService.java

@@ -0,0 +1,152 @@
+
+package com.tzld.videoVector.api.google;
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.*;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.PostConstruct;
+import java.io.IOException;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+
+@Slf4j
+@Service
+public class GeminiApiService {
+
+    private OkHttpClient client;
+    @Value("${airouter.gemini.apikey:}")
+    private String aiRouterApiKey;
+    /**
+     * AIRouter url
+     */
+    private static final String URL = "https://airouter.piaoquantv.com/v1beta/models/%s:generateContent?key=%s";
+
+    private static final MediaType JSON_MEDIA_TYPE = MediaType.get("application/json; charset=utf-8");
+
+    @PostConstruct
+    public void init() {
+        // 初始化 OkHttpClient (设置超时时间为 60 秒)
+        OkHttpClient.Builder builder = new OkHttpClient.Builder()
+                .connectTimeout(60, TimeUnit.SECONDS)
+                .readTimeout(600, TimeUnit.SECONDS)
+                .writeTimeout(600, TimeUnit.SECONDS);
+//        Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("127.0.0.1", 7890));
+//        builder.proxy(proxy);
+        client = builder.build();
+    }
+
+    /**
+     * 调用 Gemini
+     *
+     * @param systemPrompt 你的 Prompt(定义角色、规则、输出格式)
+     * @param input  输入内容
+     * @return 解析后的 JSONObject 结果,解析失败返回 null
+     */
+    public JSONObject callGemini(ModelEnum model, String systemPrompt, String input) throws IOException {
+        if (Objects.isNull(model)) {
+            model = ModelEnum.GEMINI_2_FLASH;
+        }
+        JSONObject requestBodyJson = new JSONObject();
+
+        // ==========================================
+        // 使用 System Instruction 隔离规则与输入
+        // 这会让大模型更严格地遵守你设定的“红灯标准”和“评分流程”
+        // ==========================================
+        JSONObject systemInstruction = new JSONObject();
+        JSONArray sysParts = new JSONArray();
+        JSONObject sysText = new JSONObject();
+        sysText.put("text", systemPrompt);
+        sysParts.add(sysText);
+        systemInstruction.put("parts", sysParts);
+        requestBodyJson.put("system_instruction", systemInstruction);
+
+        // ==========================================
+        // User Content 仅放入待处理的目标内容
+        // ==========================================
+        JSONArray contentsArray = new JSONArray();
+        JSONObject userContent = new JSONObject();
+        userContent.put("role", "user");
+        JSONArray userParts = new JSONArray();
+        JSONObject userText = new JSONObject();
+        userText.put("text", input != null ? input : "");
+        userParts.add(userText);
+        userContent.put("parts", userParts);
+        contentsArray.add(userContent);
+        requestBodyJson.put("contents", contentsArray);
+
+        // ==========================================
+        // 强制开启 JSON 模式 (JSON Mode)
+        // Gemini 官方特性,强制返回标准的 JSON 格式
+        // ==========================================
+        JSONObject generationConfig = new JSONObject();
+        generationConfig.put("responseMimeType", "application/json");
+        requestBodyJson.put("generationConfig", generationConfig);
+
+        log.info(requestBodyJson.toJSONString());
+        // 构建并发送 HTTP 请求
+        String url = String.format(URL, model.getModel(), aiRouterApiKey);
+        RequestBody body = RequestBody.create(JSON_MEDIA_TYPE, requestBodyJson.toJSONString());
+        Request request = new Request.Builder()
+                .url(url)
+                .post(body)
+                .build();
+
+        try (Response response = client.newCall(request).execute()) {
+            if (!response.isSuccessful()) {
+                String errorBody = response.body() != null ? response.body().string() : "无";
+                throw new IOException("请求失败!HTTP: " + response.code() + " | 详情: " + errorBody);
+            }
+
+            String responseBodyString = response.body().string();
+            log.info(responseBodyString);
+            // 提取大模型的回复文本
+            JSONObject responseJson = JSONObject.parseObject(responseBodyString);
+            JSONArray candidates = responseJson.getJSONArray("candidates");
+
+            if (candidates != null && !candidates.isEmpty()) {
+                JSONObject firstCandidate = candidates.getJSONObject(0);
+                JSONObject content = firstCandidate.getJSONObject("content");
+                JSONArray parts = content.getJSONArray("parts");
+
+                if (parts != null && !parts.isEmpty()) {
+                    String rawLlmOutput = parts.getJSONObject(0).getString("text");
+                    // ==========================================
+                    // 防御性清洗并转化为 JSONObject
+                    // 应对极少数情况下大模型仍然返回 Markdown 格式的情况
+                    // ==========================================
+                    String cleanJsonStr = cleanMarkdown(rawLlmOutput);
+                    try {
+                        return JSONObject.parseObject(cleanJsonStr);
+                    } catch (Exception e) {
+                        System.err.println("大模型返回的内容不是有效的 JSON: " + rawLlmOutput);
+                        return null;
+                    }
+                }
+            }
+            throw new IOException("未在响应中找到有效的内容 (candidates)");
+        }
+    }
+
+    /**
+     * 兜底工具:移除大模型喜欢附带的 ```json 和 ``` 标记
+     */
+    private String cleanMarkdown(String raw) {
+        if (raw == null) {
+            return null;
+        }
+        String cleaned = raw.trim();
+        if (cleaned.startsWith("```json")) {
+            cleaned = cleaned.substring(7);
+        } else if (cleaned.startsWith("```")) {
+            cleaned = cleaned.substring(3);
+        }
+        if (cleaned.endsWith("```")) {
+            cleaned = cleaned.substring(0, cleaned.length() - 3);
+        }
+        return cleaned.trim();
+    }
+}

+ 16 - 0
core/src/main/java/com/tzld/videoVector/api/google/ModelEnum.java

@@ -0,0 +1,16 @@
+package com.tzld.videoVector.api.google;
+
+import lombok.Getter;
+
+@Getter
+public enum ModelEnum {
+    GEMINI_2_FLASH("gemini-2.0-flash"),
+    GEMINI_25_FLASH("gemini-2.5-flash"),
+    GEMINI_3_FLASH_PREVIEW("gemini-3-flash-preview"),
+    ;
+
+    private final String model;
+    ModelEnum(String model) {
+        this.model = model;
+    }
+}

+ 361 - 0
core/src/main/java/com/tzld/videoVector/common/LoghubAppender.java

@@ -0,0 +1,361 @@
+package com.tzld.videoVector.common;
+
+import ch.qos.logback.classic.spi.IThrowableProxy;
+import ch.qos.logback.classic.spi.LoggingEvent;
+import ch.qos.logback.classic.spi.StackTraceElementProxy;
+import ch.qos.logback.classic.spi.ThrowableProxyUtil;
+import ch.qos.logback.core.CoreConstants;
+import ch.qos.logback.core.UnsynchronizedAppenderBase;
+import ch.qos.logback.core.encoder.Encoder;
+import com.aliyun.openservices.aliyun.log.producer.LogProducer;
+import com.aliyun.openservices.aliyun.log.producer.Producer;
+import com.aliyun.openservices.aliyun.log.producer.ProducerConfig;
+import com.aliyun.openservices.aliyun.log.producer.ProjectConfig;
+import com.aliyun.openservices.aliyun.log.producer.errors.ProducerException;
+import com.aliyun.openservices.log.common.LogItem;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.joda.time.format.DateTimeFormat;
+import org.joda.time.format.DateTimeFormatter;
+
+import java.time.Instant;
+import java.time.ZoneId;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+
+public class LoghubAppender<E> extends UnsynchronizedAppenderBase<E> {
+    private String project;
+    private String endpoint;
+    private String accessKeyId;
+    private String accessKeySecret;
+    private String userAgent = "logback";
+    protected Encoder<E> encoder;
+    protected ProducerConfig producerConfig = new ProducerConfig();
+    protected ProjectConfig projectConfig;
+    protected Producer producer;
+    protected String logStore;
+    protected String topic = "";
+    protected String source = "";
+    protected String timeZone = "UTC";
+    protected String timeFormat = "yyyy-MM-dd'T'HH:mmZ";
+    protected DateTimeFormatter formatter;
+    protected java.time.format.DateTimeFormatter formatter1;
+    private String mdcFields;
+
+    public LoghubAppender() {
+    }
+
+    public void start() {
+        try {
+            this.doStart();
+        } catch (Exception var2) {
+            this.addError("Failed to start LoghubAppender.", var2);
+        }
+
+    }
+
+    private void doStart() {
+        try {
+            this.formatter = DateTimeFormat.forPattern(this.timeFormat).withZone(DateTimeZone.forID(this.timeZone));
+        } catch (Exception var2) {
+            this.formatter1 = java.time.format.DateTimeFormatter.ofPattern(this.timeFormat).withZone(ZoneId.of(this.timeZone));
+        }
+
+        this.producer = this.createProducer();
+        super.start();
+    }
+
+    public Producer createProducer() {
+        this.projectConfig = this.buildProjectConfig();
+        Producer producer = new LogProducer(this.producerConfig);
+        producer.putProjectConfig(this.projectConfig);
+        return producer;
+    }
+
+    private ProjectConfig buildProjectConfig() {
+        return new ProjectConfig(this.project, this.endpoint, this.accessKeyId, this.accessKeySecret, (String) null, this.userAgent);
+    }
+
+    public void stop() {
+        try {
+            this.doStop();
+        } catch (Exception var2) {
+            this.addError("Failed to stop LoghubAppender.", var2);
+        }
+
+    }
+
+    private void doStop() throws InterruptedException, ProducerException {
+        if (this.isStarted()) {
+            super.stop();
+            this.producer.close();
+        }
+    }
+
+    public void append(E eventObject) {
+        try {
+            this.appendEvent(eventObject);
+        } catch (Exception var3) {
+            this.addError("Failed to append event.", var3);
+        }
+    }
+
+    private void appendEvent(E eventObject) {
+        if (!(eventObject instanceof LoggingEvent)) {
+            return;
+        }
+        LoggingEvent event = (LoggingEvent) eventObject;
+        if (event.getMarker() == null) {
+            appendPlainEvent(eventObject);
+        }
+    }
+
+    private void appendPlainEvent(E eventObject) {
+        LoggingEvent event = (LoggingEvent) eventObject;
+        List<LogItem> logItems = new ArrayList();
+        LogItem item = new LogItem();
+        logItems.add(item);
+        item.SetTime((int) (event.getTimeStamp() / 1000L));
+        if (this.formatter != null) {
+            DateTime dateTime = new DateTime(event.getTimeStamp());
+            item.PushBack("time", dateTime.toString(this.formatter));
+        } else {
+            Instant instant = Instant.ofEpochMilli(event.getTimeStamp());
+            item.PushBack("time", this.formatter1.format(instant));
+        }
+
+        item.PushBack("level", event.getLevel().toString());
+        item.PushBack("thread", event.getThreadName());
+        StackTraceElement[] caller = event.getCallerData();
+        if (caller != null && caller.length > 0) {
+            item.PushBack("location", caller[0].toString());
+        }
+
+        String message = event.getFormattedMessage();
+        item.PushBack("message", message);
+        IThrowableProxy iThrowableProxy = event.getThrowableProxy();
+        if (iThrowableProxy != null) {
+            String throwable = this.getExceptionInfo(iThrowableProxy);
+            throwable = throwable + this.fullDump(event.getThrowableProxy().getStackTraceElementProxyArray());
+            item.PushBack("throwable", throwable);
+        }
+
+        if (this.encoder != null) {
+            item.PushBack("log", new String(this.encoder.encode(eventObject)));
+        }
+
+        Optional.ofNullable(this.mdcFields).ifPresent((f) -> {
+            event.getMDCPropertyMap().entrySet().stream().filter((v) -> {
+                return Arrays.stream(f.split(",")).anyMatch((i) -> {
+                    return i.equals(v.getKey());
+                });
+            }).forEach((map) -> {
+                item.PushBack((String) map.getKey(), (String) map.getValue());
+            });
+        });
+
+        try {
+            this.producer.send(this.projectConfig.getProject(), this.logStore, this.topic, this.source, logItems,
+                    new LoghubAppenderCallback(this, this.projectConfig.getProject(), this.logStore,
+                            this.topic, this.source, logItems));
+        } catch (Exception var9) {
+            this.addError("Failed to send log, project=" + this.project + ", logStore=" + this.logStore + ", topic=" + this.topic + ", source=" + this.source + ", logItem=" + logItems, var9);
+        }
+    }
+
+    public String getTimeFormat() {
+        return this.timeFormat;
+    }
+
+    public void setTimeFormat(String timeFormat) {
+        this.timeFormat = timeFormat;
+    }
+
+    private String getExceptionInfo(IThrowableProxy iThrowableProxy) {
+        String s = iThrowableProxy.getClassName();
+        String message = iThrowableProxy.getMessage();
+        return message != null ? s + ": " + message : s;
+    }
+
+    private String fullDump(StackTraceElementProxy[] stackTraceElementProxyArray) {
+        StringBuilder builder = new StringBuilder();
+        StackTraceElementProxy[] var3 = stackTraceElementProxyArray;
+        int var4 = stackTraceElementProxyArray.length;
+
+        for (int var5 = 0; var5 < var4; ++var5) {
+            StackTraceElementProxy step = var3[var5];
+            builder.append(CoreConstants.LINE_SEPARATOR);
+            String string = step.toString();
+            builder.append('\t').append(string);
+            ThrowableProxyUtil.subjoinPackagingData(builder, step);
+        }
+
+        return builder.toString();
+    }
+
+    public String getLogStore() {
+        return this.logStore;
+    }
+
+    public void setLogStore(String logStore) {
+        this.logStore = logStore;
+    }
+
+    public String getTopic() {
+        return this.topic;
+    }
+
+    public void setTopic(String topic) {
+        this.topic = topic;
+    }
+
+    public String getSource() {
+        return this.source;
+    }
+
+    public void setSource(String source) {
+        this.source = source;
+    }
+
+    public String getTimeZone() {
+        return this.timeZone;
+    }
+
+    public void setTimeZone(String timeZone) {
+        this.timeZone = timeZone;
+    }
+
+    public String getProject() {
+        return this.project;
+    }
+
+    public void setProject(String project) {
+        this.project = project;
+    }
+
+    public String getEndpoint() {
+        return this.endpoint;
+    }
+
+    public void setEndpoint(String endpoint) {
+        this.endpoint = endpoint;
+    }
+
+    public String getAccessKeyId() {
+        return this.accessKeyId;
+    }
+
+    public void setAccessKeyId(String accessKeyId) {
+        this.accessKeyId = accessKeyId;
+    }
+
+    public String getAccessKeySecret() {
+        return this.accessKeySecret;
+    }
+
+    public void setAccessKeySecret(String accessKeySecret) {
+        this.accessKeySecret = accessKeySecret;
+    }
+
+    public String getUserAgent() {
+        return this.userAgent;
+    }
+
+    public void setUserAgent(String userAgent) {
+        this.userAgent = userAgent;
+    }
+
+    public int getTotalSizeInBytes() {
+        return this.producerConfig.getTotalSizeInBytes();
+    }
+
+    public void setTotalSizeInBytes(int totalSizeInBytes) {
+        this.producerConfig.setTotalSizeInBytes(totalSizeInBytes);
+    }
+
+    public long getMaxBlockMs() {
+        return this.producerConfig.getMaxBlockMs();
+    }
+
+    public void setMaxBlockMs(long maxBlockMs) {
+        this.producerConfig.setMaxBlockMs(maxBlockMs);
+    }
+
+    public int getIoThreadCount() {
+        return this.producerConfig.getIoThreadCount();
+    }
+
+    public void setIoThreadCount(int ioThreadCount) {
+        this.producerConfig.setIoThreadCount(ioThreadCount);
+    }
+
+    public int getBatchSizeThresholdInBytes() {
+        return this.producerConfig.getBatchSizeThresholdInBytes();
+    }
+
+    public void setBatchSizeThresholdInBytes(int batchSizeThresholdInBytes) {
+        this.producerConfig.setBatchSizeThresholdInBytes(batchSizeThresholdInBytes);
+    }
+
+    public int getBatchCountThreshold() {
+        return this.producerConfig.getBatchCountThreshold();
+    }
+
+    public void setBatchCountThreshold(int batchCountThreshold) {
+        this.producerConfig.setBatchCountThreshold(batchCountThreshold);
+    }
+
+    public int getLingerMs() {
+        return this.producerConfig.getLingerMs();
+    }
+
+    public void setLingerMs(int lingerMs) {
+        this.producerConfig.setLingerMs(lingerMs);
+    }
+
+    public int getRetries() {
+        return this.producerConfig.getRetries();
+    }
+
+    public void setRetries(int retries) {
+        this.producerConfig.setRetries(retries);
+    }
+
+    public int getMaxReservedAttempts() {
+        return this.producerConfig.getMaxReservedAttempts();
+    }
+
+    public void setMaxReservedAttempts(int maxReservedAttempts) {
+        this.producerConfig.setMaxReservedAttempts(maxReservedAttempts);
+    }
+
+    public long getBaseRetryBackoffMs() {
+        return this.producerConfig.getBaseRetryBackoffMs();
+    }
+
+    public void setBaseRetryBackoffMs(long baseRetryBackoffMs) {
+        this.producerConfig.setBaseRetryBackoffMs(baseRetryBackoffMs);
+    }
+
+    public long getMaxRetryBackoffMs() {
+        return this.producerConfig.getMaxRetryBackoffMs();
+    }
+
+    public void setMaxRetryBackoffMs(long maxRetryBackoffMs) {
+        this.producerConfig.setMaxRetryBackoffMs(maxRetryBackoffMs);
+    }
+
+    public Encoder<E> getEncoder() {
+        return this.encoder;
+    }
+
+    public void setEncoder(Encoder<E> encoder) {
+        this.encoder = encoder;
+    }
+
+    public void setMdcFields(String mdcFields) {
+        this.mdcFields = mdcFields;
+    }
+}

+ 34 - 0
core/src/main/java/com/tzld/videoVector/common/LoghubAppenderCallback.java

@@ -0,0 +1,34 @@
+package com.tzld.videoVector.common;
+
+
+import com.aliyun.openservices.aliyun.log.producer.Callback;
+import com.aliyun.openservices.aliyun.log.producer.Result;
+import com.aliyun.openservices.log.common.LogItem;
+
+import java.util.List;
+
+public class LoghubAppenderCallback<E> implements Callback {
+    protected LoghubAppender<E> loghubAppender;
+    protected String project;
+    protected String logstore;
+    protected String topic;
+    protected String source;
+    protected List<LogItem> logItems;
+
+    public LoghubAppenderCallback(LoghubAppender<E> loghubAppender, String project, String logstore,
+                                  String topic, String source, List<LogItem> logItems) {
+        this.loghubAppender = loghubAppender;
+        this.project = project;
+        this.logstore = logstore;
+        this.topic = topic;
+        this.source = source;
+        this.logItems = logItems;
+    }
+
+    public void onCompletion(Result result) {
+        if (!result.isSuccessful()) {
+            this.loghubAppender.addError("Failed to send log, project=" + this.project + ", logStore=" + this.logstore + ", topic=" + this.topic + ", source=" + this.source + ", logItem=" + this.logItems + ", errorCode=" + result.getErrorCode() + ", errorMessage=" + result.getErrorMessage());
+        }
+
+    }
+}

+ 81 - 0
core/src/main/java/com/tzld/videoVector/common/base/CommonResponse.java

@@ -0,0 +1,81 @@
+package com.tzld.videoVector.common.base;
+
+import com.tzld.videoVector.common.enums.ExceptionEnum;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
+
+import static com.tzld.videoVector.common.constant.ResponseConstant.SUCCESS_CODE;
+import static com.tzld.videoVector.common.constant.ResponseConstant.SUCCESS_MSG;
+
+
+/**
+ * Common Response
+ *
+ * @author xueyiming
+ */
+@Setter
+@Getter
+@ToString
+public class CommonResponse<T> {
+    /**
+     * 返回状态码,0 表示业务成功
+     */
+    private int code = 0;
+    /**
+     * 返回消息
+     */
+    private String msg = "success";
+    /**
+     * 业务成功时返回数据
+     */
+    private T data;
+
+    public static <T> CommonResponse<T> success() {
+        CommonResponse<T> commonResponse = new CommonResponse<>();
+        commonResponse.setCode(SUCCESS_CODE);
+        commonResponse.setMsg(SUCCESS_MSG);
+        return commonResponse;
+    }
+
+    public static <T> CommonResponse<T> success(T data) {
+        CommonResponse<T> commonResponse = new CommonResponse<>();
+        commonResponse.setCode(SUCCESS_CODE);
+        commonResponse.setMsg(SUCCESS_MSG);
+        commonResponse.setData(data);
+        return commonResponse;
+    }
+
+    public static <T> CommonResponse<T> create() {
+        return create(SUCCESS_CODE, SUCCESS_MSG, null);
+    }
+
+    public static <T> CommonResponse<T> create(T data) {
+        return create(SUCCESS_CODE, SUCCESS_MSG, data);
+    }
+
+    public static <T> CommonResponse<T> create(ExceptionEnum exceptionEnum) {
+        return create(exceptionEnum.getCode(), exceptionEnum.getMsg(), null);
+    }
+
+    public static <T> CommonResponse<T> create(ExceptionEnum exceptionEnum, String msg) {
+        return create(exceptionEnum.getCode(), msg, null);
+    }
+
+    public static <T> CommonResponse<T> create(int code, String msg) {
+        return create(code, msg, null);
+    }
+
+    public static <T> CommonResponse<T> create(int code, String msg, T data) {
+        CommonResponse<T> commonResponse = new CommonResponse<>();
+        commonResponse.setCode(code);
+        commonResponse.setMsg(msg);
+        commonResponse.setData(data);
+        return commonResponse;
+    }
+
+    public boolean isSuccess() {
+        return this.code == SUCCESS_CODE;
+    }
+
+}

+ 13 - 0
core/src/main/java/com/tzld/videoVector/common/constant/ResponseConstant.java

@@ -0,0 +1,13 @@
+package com.tzld.videoVector.common.constant;
+
+/**
+ * 响应常量
+ *
+ * @author xueyiming
+ */
+public interface ResponseConstant {
+
+    int SUCCESS_CODE = 0;
+
+    String SUCCESS_MSG = "success";
+}

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

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

+ 32 - 0
core/src/main/java/com/tzld/videoVector/common/enums/ExceptionEnum.java

@@ -0,0 +1,32 @@
+package com.tzld.videoVector.common.enums;
+
+/**
+ * 异常枚举
+ * 通用 code三位负整数
+ * 业务 code四位正整数,其中第一位代表功能模块
+ */
+
+public enum ExceptionEnum {
+
+    SYSTEM_ERROR(-101, "系统错误"),
+    DATA_ERROR(-102, "数据异常,请联系管理员"),
+    PARAM_ERROR(-103, "参数异常"),
+
+    ;
+    private int code;
+    private String msg;
+
+    public int getCode() {
+        return code;
+    }
+
+
+    public String getMsg() {
+        return msg;
+    }
+
+    ExceptionEnum(int code, String msg) {
+        this.code = code;
+        this.msg = msg;
+    }
+}

+ 87 - 0
core/src/main/java/com/tzld/videoVector/common/exception/CommonException.java

@@ -0,0 +1,87 @@
+package com.tzld.videoVector.common.exception;
+
+import com.tzld.videoVector.common.enums.ExceptionEnum;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * 通用异常
+ */
+public class CommonException extends RuntimeException {
+
+    /**
+     *
+     */
+    private static final long serialVersionUID = 1L;
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(CommonException.class);
+    /**
+     * 异常
+     */
+    private ExceptionEnum exceptionEnum;
+    /**
+     * 错误码
+     */
+    private int code;
+    /**
+     * 异常信息
+     */
+    private String msg;
+
+
+    public int getCode() {
+        return code;
+    }
+
+    public void setCode(int code) {
+        this.code = code;
+    }
+
+    public String getMsg() {
+        return msg;
+    }
+
+    public void setMsg(String msg) {
+        this.msg = msg;
+    }
+
+    public CommonException(Throwable throwable) {
+        super(throwable);
+    }
+
+    public CommonException(int code, String msg) {
+        super(msg);
+        this.code = code;
+        this.msg = msg;
+    }
+
+    public CommonException(ExceptionEnum exceptionEnum) {
+        super(exceptionEnum.getMsg());
+        this.exceptionEnum = exceptionEnum;
+        this.code = exceptionEnum.getCode();
+        this.msg = exceptionEnum.getMsg();
+    }
+
+
+    public CommonException(int code, String msg, Throwable throwable) {
+        super(msg, throwable);
+        this.code = code;
+        this.msg = msg;
+    }
+
+    public CommonException(ExceptionEnum exceptionEnum, Throwable throwable) {
+        super(exceptionEnum.getMsg(), throwable);
+        this.exceptionEnum = exceptionEnum;
+        this.code = exceptionEnum.getCode();
+        this.msg = exceptionEnum.getMsg();
+    }
+
+
+    @Override
+    public void printStackTrace() {
+        if (exceptionEnum != null) {
+            LOGGER.info("exception code = {}, msg = {}", exceptionEnum.getCode(), exceptionEnum.getMsg());
+        }
+        LOGGER.info("exception code = {}, msg = {}", code, msg);
+    }
+}

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

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

+ 86 - 0
core/src/main/java/com/tzld/videoVector/config/HttpClientConfig.java

@@ -0,0 +1,86 @@
+package com.tzld.videoVector.config;
+
+import org.apache.http.HttpHost;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class HttpClientConfig {
+
+
+    /**
+     * 链接建立的超时时间 ms
+     */
+    private static final int CONNECTION_TIMEOUT = 5000;
+    /**
+     * 响应超时时间 ms
+     */
+    private static final int SOCKET_TIMEOUT = 30000;
+
+    /**
+     * 每个路由的最大连接数
+     */
+    private static final int MAX_PER_ROUTE = 100;
+
+    /**
+     * 最大连接数
+     */
+    private static final int MAX_TOTAL = 500;
+
+    /**
+     * 重试次数,默认0
+     */
+    private static final int RETRY_COUNT = 3;
+
+    /**
+     * 从connection pool中获得一个connection的超时时间 ms
+     */
+    private static final int CONNECTION_WAIT_TIMEOUT = 5000;
+
+
+    @Bean(name = "httpClient")
+    public CloseableHttpClient httpClient() {
+
+        RequestConfig requestConfig = RequestConfig.custom()
+                .setConnectTimeout(CONNECTION_TIMEOUT)
+                .setSocketTimeout(SOCKET_TIMEOUT)
+                .setConnectionRequestTimeout(CONNECTION_WAIT_TIMEOUT)
+                .build();
+
+        PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
+        connectionManager.setMaxTotal(MAX_TOTAL); // 设置最大连接数
+        connectionManager.setDefaultMaxPerRoute(MAX_PER_ROUTE); // 每个路由的最大连接数
+
+        return HttpClientBuilder.create()
+                .setDefaultRequestConfig(requestConfig)
+                .setConnectionManager(connectionManager)
+                .setRetryHandler(new DefaultHttpRequestRetryHandler(RETRY_COUNT, false))
+                .build();
+    }
+
+    @Bean(name = "proxyHttpClient")
+    public CloseableHttpClient proxyHttpClient() {
+        HttpHost proxy = new HttpHost("121.43.150.231", 3128, "http");
+        RequestConfig requestConfig = RequestConfig.custom()
+                .setConnectTimeout(CONNECTION_TIMEOUT)
+                .setSocketTimeout(SOCKET_TIMEOUT)
+                .setConnectionRequestTimeout(CONNECTION_WAIT_TIMEOUT)
+                .build();
+
+        PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
+        connectionManager.setMaxTotal(MAX_TOTAL); // 设置最大连接数
+        connectionManager.setDefaultMaxPerRoute(MAX_PER_ROUTE); // 每个路由的最大连接数
+
+        return HttpClientBuilder.create()
+                .setProxy(proxy)
+                .setDefaultRequestConfig(requestConfig)
+                .setConnectionManager(connectionManager)
+                .setRetryHandler(new DefaultHttpRequestRetryHandler(RETRY_COUNT, false))
+                .build();
+    }
+}

+ 43 - 0
core/src/main/java/com/tzld/videoVector/config/RedisConfig.java

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

+ 11 - 0
core/src/main/java/com/tzld/videoVector/config/SchedulingConfig.java

@@ -0,0 +1,11 @@
+package com.tzld.videoVector.config;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Profile;
+import org.springframework.scheduling.annotation.EnableScheduling;
+
+@Configuration(proxyBeanMethods = false)
+@EnableScheduling
+@Profile("prod")
+public class SchedulingConfig {
+}

+ 56 - 0
core/src/main/java/com/tzld/videoVector/config/SwaggerConfig.java

@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.tzld.videoVector.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Profile;
+import springfox.documentation.builders.ApiInfoBuilder;
+import springfox.documentation.builders.PathSelectors;
+import springfox.documentation.builders.RequestHandlerSelectors;
+import springfox.documentation.service.ApiInfo;
+import springfox.documentation.spi.DocumentationType;
+import springfox.documentation.spring.web.plugins.Docket;
+import springfox.documentation.swagger2.annotations.EnableSwagger2;
+
+/**
+ * swagger2 配置
+ */
+@Configuration
+@Profile({"dev","test","pre"})
+@EnableSwagger2
+public class SwaggerConfig {
+
+    @Bean
+    public Docket createRestApi() {
+        return new Docket(DocumentationType.SWAGGER_2)
+                .apiInfo(apiInfo())
+                .select()
+                .apis(RequestHandlerSelectors.basePackage("com.tzld.videoVector.controller"))
+                .paths(PathSelectors.any())
+                .build();
+    }
+
+    private ApiInfo apiInfo() {
+        return new ApiInfoBuilder()
+                .title("videoVector server swagger api")
+                .description("videoVector server swagger api")
+                .version("1.0")
+                .build();
+    }
+}

+ 49 - 0
core/src/main/java/com/tzld/videoVector/config/XxlJobConfig.java

@@ -0,0 +1,49 @@
+package com.tzld.videoVector.config;
+
+import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class XxlJobConfig {
+
+
+    @Value("${xxl.job.admin.addresses}")
+    private String adminAddresses;
+
+    @Value("${xxl.job.accessToken}")
+    private String accessToken;
+
+    @Value("${xxl.job.executor.appname}")
+    private String appName;
+
+    @Value("${xxl.job.executor.address}")
+    private String address;
+
+    @Value("${xxl.job.executor.ip}")
+    private String ip;
+
+    @Value("${xxl.job.executor.port}")
+    private int port;
+
+    @Value("${xxl.job.executor.logpath}")
+    private String logPath;
+
+    @Value("${xxl.job.executor.logretentiondays}")
+    private int logRetentionDays;
+
+    @Bean
+    public XxlJobSpringExecutor xxlJobExecutor() {
+        XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
+        xxlJobSpringExecutor.setAdminAddresses(adminAddresses);
+        xxlJobSpringExecutor.setAppname(appName);
+        xxlJobSpringExecutor.setAddress(address);
+        xxlJobSpringExecutor.setIp(ip);
+        xxlJobSpringExecutor.setPort(port);
+        xxlJobSpringExecutor.setAccessToken(accessToken);
+        xxlJobSpringExecutor.setLogPath(logPath);
+        xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays);
+        return xxlJobSpringExecutor;
+    }
+}

+ 57 - 0
core/src/main/java/com/tzld/videoVector/config/db/VideoVectorDBConfig.java

@@ -0,0 +1,57 @@
+package com.tzld.videoVector.config.db;
+
+import com.zaxxer.hikari.HikariDataSource;
+import org.apache.ibatis.session.SqlSessionFactory;
+import org.mybatis.spring.SqlSessionFactoryBean;
+import org.mybatis.spring.boot.autoconfigure.MybatisProperties;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Primary;
+import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
+import org.springframework.jdbc.datasource.DataSourceTransactionManager;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
+
+import javax.sql.DataSource;
+
+@Configuration
+@EnableTransactionManagement
+public class VideoVectorDBConfig {
+
+    // 1. 配置 videoVector 数据源
+    @Primary
+    @Bean(name = "videoVectorDataSource")
+    @ConfigurationProperties(prefix = "spring.datasource.videoVector")
+    public DataSource videoVectorDataSource() {
+        return new HikariDataSource(); // 使用 HikariCP 连接池
+    }
+
+    // 2. 配置 videoVector 专属 SqlSessionFactory
+    @Primary
+    @Bean(name = "videoVectorSqlSessionFactory")
+    public SqlSessionFactory videoVectorSqlSessionFactory(
+            @Qualifier("videoVectorDataSource") DataSource videoVectorDataSource,
+            MybatisProperties properties) throws Exception {
+        SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
+        sessionFactory.setDataSource(videoVectorDataSource); // 关联 videoVector 数据源
+        // 关键:指定 videoVector 模块的 mapper 文件路径(隔离其他数据源)
+        sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver()
+                .getResources("classpath:mapper/videoVector/**/*.xml"));
+        sessionFactory.setTypeAliasesPackage("com.tzld.videoVector");
+        sessionFactory.setConfiguration(properties.getConfiguration());
+        return sessionFactory.getObject();
+    }
+
+
+    // 3. 配置 videoVector 事务管理器
+    @Primary
+    @Bean(name = "videoVectorTransactionManager")
+    public PlatformTransactionManager videoVectorTransactionManager(
+            @Qualifier("videoVectorDataSource") DataSource videoVectorDataSource) {
+        return new DataSourceTransactionManager(videoVectorDataSource);
+    }
+
+}
+

+ 19 - 0
core/src/main/java/com/tzld/videoVector/config/mybatis/VideoVectorMybatisConfig.java

@@ -0,0 +1,19 @@
+package com.tzld.videoVector.config.mybatis;
+
+import org.apache.ibatis.session.SqlSessionFactory;
+import org.mybatis.spring.SqlSessionTemplate;
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@MapperScan(basePackages = "com.tzld.videoVector.dao.mapper.videoVector",
+        sqlSessionFactoryRef = "videoVectorSqlSessionFactory")
+public class VideoVectorMybatisConfig {
+
+    @Bean(name = "videoVectorSqlSessionTemplate")
+    public SqlSessionTemplate videoVectorSqlSessionTemplate(@Qualifier("videoVectorSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
+        return new SqlSessionTemplate(sqlSessionFactory);
+    }
+}

+ 31 - 0
core/src/main/java/com/tzld/videoVector/dao/generator/MybatisGeneratorMain.java

@@ -0,0 +1,31 @@
+package com.tzld.videoVector.dao.generator;
+
+import org.mybatis.generator.api.MyBatisGenerator;
+import org.mybatis.generator.config.Configuration;
+import org.mybatis.generator.config.xml.ConfigurationParser;
+import org.mybatis.generator.exception.InvalidConfigurationException;
+import org.mybatis.generator.exception.XMLParserException;
+import org.mybatis.generator.internal.DefaultShellCallback;
+
+import java.io.File;
+import java.io.IOException;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+
+public class MybatisGeneratorMain {
+
+	public static void main(String[] args)
+			throws SQLException, IOException, InterruptedException, InvalidConfigurationException, XMLParserException {
+		List<String> warnings = new ArrayList<String>();
+		boolean overwrite = true;
+		File configFile = new File(MybatisGeneratorMain.class.getResource("/generator/mybatis-spider-generator-config.xml").getFile());
+
+		ConfigurationParser cp = new ConfigurationParser(warnings);
+		Configuration config = cp.parseConfiguration(configFile);
+		DefaultShellCallback callback = new DefaultShellCallback(overwrite);
+		MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings);
+		myBatisGenerator.generate(null);
+		System.out.println("generate finish");
+	}
+}

+ 73 - 0
core/src/main/java/com/tzld/videoVector/handle/GlobalExceptionHandle.java

@@ -0,0 +1,73 @@
+package com.tzld.videoVector.handle;
+
+import com.tzld.videoVector.common.base.CommonResponse;
+import com.tzld.videoVector.common.enums.ExceptionEnum;
+import com.tzld.videoVector.common.exception.CommonException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.util.CollectionUtils;
+import org.springframework.validation.BindException;
+import org.springframework.validation.ObjectError;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.List;
+
+/**
+ * 全局异常处理器
+ */
+
+@RestControllerAdvice
+public class GlobalExceptionHandle {
+
+    private static Logger LOGGER = LoggerFactory.getLogger(GlobalExceptionHandle.class);
+
+    @ExceptionHandler
+    public Object handleException(HttpServletRequest req, Exception exception) throws Exception {
+        String uri = req.getRequestURI();
+        CommonResponse<Object> response = new CommonResponse<Object>();
+        // 业务异常
+        if (exception instanceof CommonException) {
+            CommonException e = (CommonException) exception;
+            response.setCode(e.getCode());
+            response.setMsg(e.getMsg());
+            LOGGER.info("uri:" + uri + "\n" + "CustomException log.", exception, exception.getMessage());
+        } else if (exception instanceof MethodArgumentNotValidException) {
+            // 参数校验异常
+            MethodArgumentNotValidException e = (MethodArgumentNotValidException) exception;
+            List<ObjectError> errorList = e.getBindingResult().getAllErrors();
+            StringBuilder errorMsg = new StringBuilder();
+            errorMsg.append("|");
+            if (!CollectionUtils.isEmpty(errorList)) {
+                for (ObjectError objectError : errorList) {
+                    errorMsg.append(objectError.getDefaultMessage()).append("|");
+                }
+            }
+            response.setCode(ExceptionEnum.PARAM_ERROR.getCode());
+            response.setMsg(errorMsg.toString());
+            LOGGER.error("uri:" + uri + "\n" + "MethodArgumentNotValidException log.", exception, exception.getMessage());
+        } else if (exception instanceof BindException) {
+            // 参数绑定异常
+            BindException e = (BindException) exception;
+            List<ObjectError> errorList = e.getBindingResult().getAllErrors();
+            StringBuilder errorMsg = new StringBuilder();
+            errorMsg.append("|");
+            if (!CollectionUtils.isEmpty(errorList)) {
+                for (ObjectError objectError : errorList) {
+                    errorMsg.append(objectError.getDefaultMessage()).append("|");
+                }
+            }
+            response.setCode(ExceptionEnum.PARAM_ERROR.getCode());
+            response.setMsg(errorMsg.toString());
+            LOGGER.error("uri:" + uri + "\n" + "BindException log.", exception, exception.getMessage());
+        } else {
+            response.setCode(ExceptionEnum.SYSTEM_ERROR.getCode());
+            response.setMsg(ExceptionEnum.SYSTEM_ERROR.getMsg());
+            LOGGER.error("uri:" + uri + "\n" + "unknownException log.", exception, exception.getMessage());
+        }
+        return response;
+    }
+
+}

+ 38 - 0
core/src/main/java/com/tzld/videoVector/interceptor/CrosDomainAllowInterceptor.java

@@ -0,0 +1,38 @@
+package com.tzld.videoVector.interceptor;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.stereotype.Component;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.servlet.HandlerInterceptor;
+import org.springframework.web.servlet.ModelAndView;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+@Component
+public class CrosDomainAllowInterceptor implements HandlerInterceptor {
+
+    @Override
+    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
+            throws Exception {
+        response.addHeader("Access-Control-Allow-Origin", "*");
+        response.addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
+        response.addHeader("Access-Control-Allow-Headers", "*");
+        if (request.getMethod().equals(RequestMethod.OPTIONS.name())) {
+            response.setStatus(HttpStatus.OK.value());
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
+                           ModelAndView modelAndView) throws Exception {
+    }
+
+    @Override
+    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
+            throws Exception {
+    }
+
+}

+ 32 - 0
core/src/main/java/com/tzld/videoVector/job/SpiderJob.java

@@ -0,0 +1,32 @@
+package com.tzld.videoVector.job;
+
+import com.ctrip.framework.apollo.spring.annotation.ApolloJsonValue;
+import com.xxl.job.core.biz.model.ReturnT;
+import com.xxl.job.core.handler.annotation.XxlJob;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+
+@Slf4j
+@Component
+public class SpiderJob {
+
+    @Value("${get.rank.page.size:5}")
+    private Integer getRankPageSize;
+    @ApolloJsonValue("${spider.filter.source.list:[\"知乎\",\"知乎日报\",\"果壳\",\"壹心理\",\"China Daily\",\"NASA \uD83C\uDF0D\",\"wikiHow 中文\"]}")
+    private List<String> filterSourceList;
+
+    /**
+     * 热榜爬取
+     * @param param
+     * @return
+     */
+    @XxlJob("spiderTaskJob")
+    public ReturnT<String> spiderTaskJob(String param) {
+
+        return ReturnT.SUCCESS;
+    }
+
+}

+ 39 - 0
core/src/main/java/com/tzld/videoVector/model/entity/DeepSeekOfficialApiResponse.java

@@ -0,0 +1,39 @@
+package com.tzld.videoVector.model.entity;
+
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class DeepSeekOfficialApiResponse {
+
+    private String id;
+    private String object;
+    private long created;
+    private String model;
+    private List<Choice> choices;
+    private Usage usage;
+
+
+    @Data
+    public static class Choice {
+        private long index;
+        private Message message;
+        private String finishReason;
+    }
+
+
+    @Data
+    public static class Message {
+        private String role;
+        private String content;
+    }
+
+    @Data
+    public static class Usage {
+        private long promptTokens;
+        private long completionTokens;
+        private long totalTokens;
+    }
+
+}

+ 11 - 0
core/src/main/java/com/tzld/videoVector/model/entity/DeepSeekResult.java

@@ -0,0 +1,11 @@
+package com.tzld.videoVector.model.entity;
+
+import lombok.Data;
+
+@Data
+public class DeepSeekResult {
+    private boolean success;
+    private DeepSeekOfficialApiResponse response;
+    private String failReason;
+    private String responseStr;
+}

+ 15 - 0
core/src/main/java/com/tzld/videoVector/model/entity/PQResponse.java

@@ -0,0 +1,15 @@
+package com.tzld.videoVector.model.entity;
+
+import lombok.Data;
+
+@Data
+public class PQResponse<T> {
+
+    private long code;
+    private String msg;
+    private T data;
+
+}
+
+
+

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

+ 8 - 0
core/src/main/java/com/tzld/videoVector/service/SpiderTaskService.java

@@ -0,0 +1,8 @@
+package com.tzld.videoVector.service;
+
+
+
+public interface SpiderTaskService {
+
+
+}

+ 10 - 0
core/src/main/java/com/tzld/videoVector/service/impl/SpiderTaskServiceImpl.java

@@ -0,0 +1,10 @@
+package com.tzld.videoVector.service.impl;
+
+import com.tzld.videoVector.service.SpiderTaskService;
+import org.springframework.stereotype.Service;
+
+@Service
+public class SpiderTaskServiceImpl implements SpiderTaskService {
+
+
+}

+ 1514 - 0
core/src/main/java/com/tzld/videoVector/util/AliOssFileTool.java

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

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

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

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

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

+ 333 - 0
core/src/main/java/com/tzld/videoVector/util/DateUtils.java

@@ -0,0 +1,333 @@
+package com.tzld.videoVector.util;
+
+import org.springframework.util.StringUtils;
+
+import java.text.SimpleDateFormat;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.time.temporal.ChronoUnit;
+import java.util.*;
+
+/**
+ * @author dyp
+ */
+public final class DateUtils {
+    public static String getCurrentDateStr(String format) {
+        SimpleDateFormat dateFormat = new SimpleDateFormat(format);
+        Date currentDate = new Date();
+        return dateFormat.format(currentDate);
+    }
+
+    public static int getCurrentHour() {
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
+        return calendar.get(Calendar.HOUR_OF_DAY);
+    }
+
+    public static String getBeforeDaysDateStr(String format, int d) {
+        SimpleDateFormat dateFormat = new SimpleDateFormat(format);
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
+        calendar.setTime(new Date());
+        calendar.add(Calendar.DAY_OF_MONTH, -d);
+        Date previousDate = calendar.getTime();
+        return dateFormat.format(previousDate);
+    }
+
+    public static String getBeforeDaysDateStr(String dateStr, String format, int d) {
+        SimpleDateFormat dateFormat = new SimpleDateFormat(format);
+        Date date = new Date();
+        try {
+            date = dateFormat.parse(dateStr);
+        } catch (Exception ignore) {}
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
+        calendar.setTime(date);
+        calendar.add(Calendar.DAY_OF_MONTH, -d);
+        Date previousDate = calendar.getTime();
+        return dateFormat.format(previousDate);
+    }
+
+
+    public static boolean ifTimeRangeInNow(String timeRange) {
+        try {
+
+            String[] split = timeRange.split("-");
+            if (split.length != 2) {
+                return false;
+            }
+            String startTime = split[0];
+            String endTime = split[1];
+            if (startTime.length() != 10 || endTime.length() != 10) {
+                return false;
+            }
+            LocalDateTime now = LocalDateTime.now();
+            LocalDateTime start = LocalDateTime.parse(startTime, DateTimeFormatter.ofPattern("yyyyMMddHH"));
+            LocalDateTime end = LocalDateTime.parse(endTime, DateTimeFormatter.ofPattern("yyyyMMddHH"));
+            return now.equals(start) || (now.isAfter(start) && now.isBefore(end));
+        } catch (Exception e) {
+//            e.printStackTrace();
+            return false;
+        }
+    }
+
+    public static long dateStrToTimestamp(String dateStr, String format) {
+        // 定义日期格式
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format);
+
+        // 解析输入的日期字符串为LocalDate对象
+        LocalDate date = LocalDate.parse(dateStr, formatter);
+
+        // 将LocalDate转换为LocalDateTime,时间部分设为00:00:00
+        LocalDateTime dateTime = date.atStartOfDay();
+
+        // 定义北京时区
+        ZoneId zone = ZoneId.of("Asia/Shanghai");
+
+        // 将LocalDateTime转换为时间戳(秒数),使用北京时区
+        return dateTime.toEpochSecond(zone.getRules().getOffset(dateTime));
+    }
+
+    public static boolean isValidDate(String dateString) {
+        // 定义日期格式
+        DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+
+        try {
+            // 尝试解析日期字符串
+            LocalDate date = LocalDate.parse(dateString, dateFormatter);
+            return true; // 如果解析成功,返回 true
+        } catch (DateTimeParseException e) {
+            return false; // 如果解析失败,返回 false
+        }
+    }
+
+    public static String timestampToYMDStr(Long timestamp, String format) {
+
+        // 定义北京时区
+        ZoneId zone = ZoneId.of("Asia/Shanghai");
+
+        // 将时间戳转换为LocalDateTime
+        LocalDateTime dateTime = LocalDateTime.ofInstant(Instant.ofEpochSecond(timestamp), zone);
+
+        // 定义日期格式为"yyyyMMddHHmmss"
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format);
+
+        // 将LocalDateTime格式化为字符串
+        return dateTime.format(formatter);
+    }
+
+    public static String findNearestDate(List<String> dateList, String targetDateStr, String format) {
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format);
+        // 将目标日期字符串转换为 LocalDate
+        LocalDate targetDate = LocalDate.parse(targetDateStr, formatter);
+
+        // 初始化最近的日期和最小的天数差值
+        String nearestDateStr = null;
+        long minDiff = Long.MAX_VALUE;
+
+        // 遍历日期列表,找到最近的日期
+        for (String dateStr : dateList) {
+            LocalDate currentDate = LocalDate.parse(dateStr, formatter);
+            long diff = Math.abs(ChronoUnit.DAYS.between(currentDate, targetDate));  // 计算天数差异
+
+            // 如果找到更小的差异,更新最近的日期
+            if (diff < minDiff) {
+                minDiff = diff;
+                nearestDateStr = dateStr;
+            }
+        }
+
+        return nearestDateStr;  // 返回最近的日期字符串
+    }
+
+    public static List<String> getBeforeDays(String beginDate, String endDate, int days) {
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd");
+        // 获取今天的日期
+        LocalDate today = LocalDate.now();
+        if (!StringUtils.hasText(beginDate)) {
+            beginDate = today.format(formatter);
+        }
+        if (!StringUtils.hasText(endDate)) {
+            endDate = today.format(formatter);
+        }
+        // 解析输入的日期字符串为LocalDate对象
+        LocalDate date = LocalDate.parse(beginDate, formatter);
+        LocalDate end = LocalDate.parse(endDate, formatter);
+        // 获取从输入日期前三天的日期
+        LocalDate startDate = date.minusDays(days);
+        // 存储所有日期的列表
+        List<String> datesList = new ArrayList<>();
+        // 从startDate到today遍历日期
+        while (startDate.isBefore(today) && !startDate.isAfter(end)) {
+            // 将当前日期格式化为"yyyyMMdd"并添加到列表中
+            datesList.add(startDate.format(formatter));
+            // 日期加1天
+            startDate = startDate.plusDays(1);
+        }
+        return datesList;
+    }
+
+    // 获取每日最小秒数 (当天零点)
+    public static Long getStartOfDay(String dateStr, String formatter) {
+        try {
+            SimpleDateFormat sdf = new SimpleDateFormat(formatter);
+            Date date = sdf.parse(dateStr);
+
+            Calendar calendar = Calendar.getInstance();
+            calendar.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
+            calendar.setTime(date);
+            calendar.set(Calendar.HOUR_OF_DAY, 0);
+            calendar.set(Calendar.MINUTE, 0);
+            calendar.set(Calendar.SECOND, 0);
+            calendar.set(Calendar.MILLISECOND, 0);
+
+            return calendar.getTimeInMillis();
+        } catch (Exception e) {
+            Date date = new Date();
+            return date.getTime();
+        }
+    }
+
+    public static Date getStartDateOfDay(Long timestamp) {
+        try {
+            Calendar calendar = Calendar.getInstance();
+            // 设置时区为UTC(你可以根据需要更改时区)
+            calendar.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
+            // 将时间戳转换为毫秒并设置时间
+            calendar.setTimeInMillis(timestamp);
+            calendar.set(Calendar.HOUR_OF_DAY, 0);
+            calendar.set(Calendar.MINUTE, 0);
+            calendar.set(Calendar.SECOND, 0);
+            calendar.set(Calendar.MILLISECOND, 0);
+
+            return calendar.getTime();
+        } catch (Exception e) {
+            return new Date();
+        }
+    }
+
+    // 获取每日最大秒数 (当天的23:59:59)
+    public static Long getEndOfDay(String dateStr, String formatter) {
+        try {
+            SimpleDateFormat sdf = new SimpleDateFormat(formatter);
+            Date date = sdf.parse(dateStr);
+
+            Calendar calendar = Calendar.getInstance();
+            calendar.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
+            calendar.setTime(date);
+            calendar.set(Calendar.HOUR_OF_DAY, 23);
+            calendar.set(Calendar.MINUTE, 59);
+            calendar.set(Calendar.SECOND, 59);
+            calendar.set(Calendar.MILLISECOND, 999);
+
+            return calendar.getTimeInMillis();
+        } catch (Exception e) {
+            Date date = new Date();
+            return date.getTime();
+        }
+    }
+
+    public static Integer getHourByTimestamp(Long timestamp) {
+        Calendar calendar = Calendar.getInstance();
+        // 设置时区为UTC(你可以根据需要更改时区)
+        calendar.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
+        // 将时间戳转换为毫秒并设置时间
+        calendar.setTimeInMillis(timestamp);
+        // 获取小时
+        return calendar.get(Calendar.HOUR_OF_DAY);
+    }
+
+    public static int getMinuteByTimestamp(long timestamp) {
+        Calendar calendar = Calendar.getInstance();
+        // 设置时区为UTC(你可以根据需要更改时区)
+        calendar.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
+        // 将时间戳转换为毫秒并设置时间
+        calendar.setTimeInMillis(timestamp);
+        // 获取小时
+        return calendar.get(Calendar.MINUTE);
+    }
+
+
+    public static void main(String[] args) {
+        Calendar calendar = Calendar.getInstance();
+
+        System.out.println((calendar.get(Calendar.DAY_OF_WEEK) + 6) % 7);
+        System.out.println(new SimpleDateFormat("yyyyMMdd").format(calendar.getTime()));
+        System.out.println(new SimpleDateFormat("HH").format(calendar.getTime()));
+
+    }
+
+    public static Long getTodayStart() {
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
+        calendar.set(Calendar.HOUR_OF_DAY, 0);
+        calendar.set(Calendar.MINUTE, 0);
+        calendar.set(Calendar.SECOND, 0);
+        calendar.set(Calendar.MILLISECOND, 0);
+        return calendar.getTime().getTime();
+    }
+
+    public static Long getBeforeDayStart(int days) {
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
+        calendar.set(Calendar.HOUR_OF_DAY, 0);
+        calendar.set(Calendar.MINUTE, 0);
+        calendar.set(Calendar.SECOND, 0);
+        calendar.set(Calendar.MILLISECOND, 0);
+        calendar.add(Calendar.DAY_OF_MONTH, -days);
+        return calendar.getTime().getTime();
+    }
+
+    public static String getBeforeDayStr(String dateStr, String format, int days) {
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format);
+        LocalDate date = LocalDate.parse(dateStr, formatter);
+        date = date.minusDays(days);
+        return date.format(formatter);
+    }
+
+    public static String getDateString(Long timestamp) {
+        // 创建日期时间格式化器
+        DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("yyyyMMdd");
+        // 将时间戳转换为 LocalDateTime
+        ZoneId zone = ZoneId.of("Asia/Shanghai");
+        LocalDateTime dateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), zone);
+        // 格式化日期时间并返回
+        return dateTime.format(dateFormat);
+    }
+
+    public static String getDateString(Long timestamp, String pattern) {
+        // 创建日期时间格式化器
+        DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern(pattern);
+        // 将时间戳转换为 LocalDateTime
+        ZoneId zone = ZoneId.of("Asia/Shanghai");
+        LocalDateTime dateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), zone);
+        // 格式化日期时间并返回
+        return dateTime.format(dateFormat);
+    }
+
+    public static Date getDate(String dateString) {
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+        LocalDate localDate = LocalDate.parse(dateString, formatter);
+        ZoneId zone = ZoneId.of("Asia/Shanghai");
+        return Date.from(localDate.atStartOfDay(zone).toInstant());
+    }
+
+    /**
+     * 日期型转化成字符串类型
+     * @param date
+     * @return
+     */
+    public static String dateToStringyyyyMMdd(Date date) {
+        return new SimpleDateFormat("yyyyMMdd").format(date);
+    }
+
+    public static String formatIso8601Date(Date date) {
+        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
+        df.setTimeZone(new SimpleTimeZone(8, "GMT"));
+        return df.format(date);
+    }
+}

+ 23 - 0
core/src/main/java/com/tzld/videoVector/util/DistributedIdGenerator.java

@@ -0,0 +1,23 @@
+package com.tzld.videoVector.util;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Random;
+
+public abstract class DistributedIdGenerator {
+    /**
+     * 生成一个
+     *
+     * @return
+     */
+    public static String generate() {
+        StringBuilder builder = new StringBuilder();
+        builder.append(new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date()));
+        builder.append(new Random().nextInt(899999) + 100000);
+        return builder.toString();
+    }
+
+    public static void main(String[] args) {
+        System.out.println(generate());
+    }
+}

+ 96 - 0
core/src/main/java/com/tzld/videoVector/util/HttpClientFactory.java

@@ -0,0 +1,96 @@
+package com.tzld.videoVector.util;
+
+import com.google.common.collect.Lists;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.concurrent.BasicThreadFactory;
+import org.apache.http.HttpRequestInterceptor;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.config.Registry;
+import org.apache.http.config.RegistryBuilder;
+import org.apache.http.conn.HttpClientConnectionManager;
+import org.apache.http.conn.socket.ConnectionSocketFactory;
+import org.apache.http.conn.socket.PlainConnectionSocketFactory;
+import org.apache.http.conn.ssl.NoopHostnameVerifier;
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
+import org.apache.http.ssl.SSLContexts;
+import org.slf4j.MDC;
+
+import javax.net.ssl.SSLContext;
+import java.security.KeyManagementException;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.util.List;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+@Slf4j
+public class HttpClientFactory {
+
+    private static final ScheduledExecutorService SCHEDULED_CLOSED_EXECUTOR = new ScheduledThreadPoolExecutor(1,
+            new BasicThreadFactory.Builder().namingPattern("http conn-closed-thread-%s").priority(Thread.NORM_PRIORITY).daemon(false).build(), (r, e) -> log.error(" monitor push reject task error={}", e.toString()));
+
+    private static final List<HttpClientConnectionManager> HTTP_CLIENT_CONNECTION_MANAGERS = Lists.newArrayList();
+
+    static {
+        SCHEDULED_CLOSED_EXECUTOR.schedule(() -> HTTP_CLIENT_CONNECTION_MANAGERS.forEach(HttpClientConnectionManager::closeExpiredConnections), 5, TimeUnit.SECONDS);
+    }
+
+    private static HttpRequestInterceptor getInterceptor() {
+        HttpRequestInterceptor requestInterceptor = (request, context) -> {
+            try {
+                String missSpanId = MDC.get("missSpanId");
+                String missTraceId = MDC.get("request-id");
+                if (missTraceId != null && !"".equals(missTraceId.trim())) {
+                    request.setHeader("request-id", missTraceId);
+                }
+                if (missSpanId != null && !"".equals(missSpanId.trim())) {
+                    request.setHeader("missSpanId", missSpanId);
+                }
+            } catch (Exception e) {
+                log.error(e.getMessage(), e);
+            }
+        };
+        return requestInterceptor;
+    }
+
+    /**
+     * @param connectTimeout 连接超时时间 ms
+     * @param socketTimeout  读超时时间(等待数据超时时间)ms
+     * @param maxPerRoute    每个路由的最大连接数
+     * @param maxTotal       最大连接数
+     * @param retryCount     重试次数
+     * @return httpclient instance
+     */
+    public static CloseableHttpClient create(int connectTimeout, int socketTimeout, int maxPerRoute, int maxTotal,
+                                             int retryCount, int connectionWaitTimeout) {
+        try {
+            RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(connectTimeout).setSocketTimeout(socketTimeout).setConnectionRequestTimeout(connectionWaitTimeout).build();
+            CloseableHttpClient client = HttpClientBuilder.create()
+                    .setDefaultRequestConfig(requestConfig)
+                    .setConnectionManager(createConnectionManager(maxPerRoute, maxTotal))
+                    .setRetryHandler(new DefaultHttpRequestRetryHandler(retryCount, false)).addInterceptorFirst(getInterceptor()).build();
+            return client;
+        } catch (Throwable e) {
+            log.error("create HttpPoolClient exception", e);
+            throw new RuntimeException("create HttpPoolClient exception");
+        }
+    }
+
+    private static PoolingHttpClientConnectionManager createConnectionManager(int maxPerRoute, int maxTotal) throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
+        SSLContext sslContext = SSLContexts.custom().loadTrustMaterial((chain, authType) -> true).build();
+        Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
+                .register("http", PlainConnectionSocketFactory.getSocketFactory())
+                .register("https", new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE)).build();
+        PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(registry);
+        cm.setDefaultMaxPerRoute(maxPerRoute);
+        cm.setMaxTotal(maxTotal);
+        HTTP_CLIENT_CONNECTION_MANAGERS.add(cm);
+        return cm;
+    }
+
+}

+ 69 - 0
core/src/main/java/com/tzld/videoVector/util/IpUtil.java

@@ -0,0 +1,69 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright © 2021 xrv <xrg@live.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package com.tzld.videoVector.util;
+
+import javax.servlet.http.HttpServletRequest;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+/**
+ * @author ehlxr
+ * @since 2021-12-14 15:27.
+ */
+public class IpUtil {
+    public static String getIpAddr(HttpServletRequest request) {
+        String ipAddress;
+        ipAddress = request.getHeader("x-forwarded-for");
+        if (ipAddress == null || ipAddress.length() == 0
+                || "unknown".equalsIgnoreCase(ipAddress)) {
+            ipAddress = request.getHeader("Proxy-Client-IP");
+        }
+        if (ipAddress == null || ipAddress.length() == 0
+                || "unknown".equalsIgnoreCase(ipAddress)) {
+            ipAddress = request.getHeader("WL-Proxy-Client-IP");
+        }
+        if (ipAddress == null || ipAddress.length() == 0
+                || "unknown".equalsIgnoreCase(ipAddress)) {
+            ipAddress = request.getRemoteAddr();
+            if ("127.0.0.1".equals(ipAddress)) {
+                // 根据网卡取本机配置的IP
+                InetAddress inet;
+                try {
+                    inet = InetAddress.getLocalHost();
+                    ipAddress = inet.getHostAddress();
+                } catch (UnknownHostException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+        // 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
+        if (ipAddress != null && ipAddress.length() > 15) { // "***.***.***.***".length()
+            if (ipAddress.indexOf(",") > 0) {
+                ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
+            }
+        }
+        return ipAddress;
+    }
+}

+ 50 - 0
core/src/main/java/com/tzld/videoVector/util/MapBuilder.java

@@ -0,0 +1,50 @@
+package com.tzld.videoVector.util;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * @author: TanJingyu
+ * @create:2023-06-12 13:28:18
+ **/
+public class MapBuilder {
+
+    private MapBuilder() {
+    }
+
+    public static <K, V> Builder<K, V> builder() {
+        return new Builder<>(null);
+    }
+
+    public static <K, V> Builder<K, V> builder(Map<K, V> map) {
+        return new Builder<>(map);
+    }
+
+    public static class Builder<K, V> {
+        private Map<K, V> resultMap = new HashMap<>();
+
+        private Builder(Map<K, V> map) {
+            if (Objects.nonNull(map)) {
+                resultMap = map;
+            }
+        }
+
+        public Builder<K, V> put(K k, V v) {
+            resultMap.put(k, v);
+            return this;
+        }
+
+        public Map<K, V> build() {
+            return resultMap;
+        }
+    }
+
+
+    public static void main(String[] args) {
+        Map<String, Object> build = MapBuilder.<String, Object>builder().put("name", "忘记")
+                .put("age", 23)
+                .build();
+        System.out.println(build);
+    }
+}

+ 72 - 0
core/src/main/java/com/tzld/videoVector/util/Md5Util.java

@@ -0,0 +1,72 @@
+package com.tzld.videoVector.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.MessageDigest;
+
+public class Md5Util {
+	static char hexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };  
+	
+	public static final String encoderByMd5(String msg) {
+		try {
+			byte[] strTemp = msg.getBytes();
+            //如果输入“SHA”,就是实现SHA加密。
+			MessageDigest mdTemp = MessageDigest.getInstance("MD5"); 
+			mdTemp.update(strTemp);
+			byte[] md = mdTemp.digest();
+			int j = md.length;
+			char str[] = new char[j * 2];
+			int k = 0;
+			for (int i = 0; i < j; i++) {
+				byte byte0 = md[i];
+				str[k++] = hexDigits[byte0 >>> 4 & 0xf];
+				str[k++] = hexDigits[byte0 & 0xf];
+			}
+			return new String(str);
+		} catch (Exception e) {
+			return null;
+		}
+	}
+
+	public static final String encoderFileByMd5(InputStream is){
+        try {  
+            MessageDigest md = MessageDigest.getInstance("MD5");  
+            byte[] buffer = new byte[2048];  
+            int length = -1;  
+            while ((length = is.read(buffer)) != -1) {  
+                md.update(buffer, 0, length);  
+            }  
+            byte[] b = md.digest();  
+            return byteToHexString(b);  
+        } catch (Exception e) {  
+            e.printStackTrace();  
+            return null;  
+        } finally {  
+            try {  
+                is.close();  
+            } catch (IOException e) {  
+                e.printStackTrace();  
+            }  
+        }  
+	}
+	
+	private static String byteToHexString(byte[] tmp) {  
+        String s;  
+        // 用字节表示就是 16 个字节  
+        // 每个字节用 16 进制表示的话,使用两个字符,所以表示成 16 进制需要 32 个字符  
+        // 比如一个字节为01011011,用十六进制字符来表示就是“5b”  
+        char str[] = new char[16 * 2];  
+        int k = 0; // 表示转换结果中对应的字符位置  
+        for (int i = 0; i < 16; i++) { // 从第一个字节开始,对 MD5 的每一个字节转换成 16 进制字符的转换  
+            byte byte0 = tmp[i]; // 取第 i 个字节  
+            str[k++] = hexDigits[byte0 >>> 4 & 0xf]; // 取字节中高 4 位的数字转换, >>> 为逻辑右移,将符号位一起右移  
+            str[k++] = hexDigits[byte0 & 0xf]; // 取字节中低 4 位的数字转换  
+        }  
+        s = new String(str); // 换后的结果转换为字符串  
+        return s;  
+    }  
+	
+	public static void main(String[] args) {
+		System.out.println(encoderByMd5("test1002"));
+	}
+}

+ 175 - 0
core/src/main/java/com/tzld/videoVector/util/PropertiesUtils.java

@@ -0,0 +1,175 @@
+package com.tzld.videoVector.util;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.yaml.snakeyaml.Yaml;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Map;
+import java.util.Properties;
+
+@Slf4j
+public class PropertiesUtils {
+    public static Properties properties;
+    public static Properties mainProperties;
+
+    static {
+        mainProperties = new Properties();
+        properties = new Properties();
+        try {
+            // 加载主配置文件 application.yml
+            InputStream mainStream = PropertiesUtils.class.getClassLoader().getResourceAsStream("application.yml");
+            if (mainStream == null) {
+                throw new IOException("application.yml 配置文件不存在");
+            }
+            // 解析 YAML 并转换为 Properties 格式(扁平 key-value)
+            Map<String, Object> mainYamlMap = new Yaml().load(mainStream);
+            mainProperties = convertYamlToProperties(mainYamlMap);
+
+            // 获取环境变量(优先取 -Denv,其次取 application.yml 中的 spring.profiles.active)
+            String env = System.getProperty("env");
+            if (StringUtils.isEmpty(env)) {
+                env = mainProperties.getProperty("spring.profiles.active");
+            }
+            if (StringUtils.isEmpty(env)) {
+                log.error("请设置 spring.profiles.active 或启动参数 -Denv");
+                System.exit(1);
+            }
+
+            // 加载环境配置文件 application-{env}.yml
+            InputStream activeStream = PropertiesUtils.class.getClassLoader().getResourceAsStream("application-" + env + ".yml");
+            if (activeStream == null) {
+                throw new IOException("application-" + env + ".yml 配置文件不存在");
+            }
+            Map<String, Object> activeYamlMap = new Yaml().load(activeStream);
+            Properties activeProperties = convertYamlToProperties(activeYamlMap);
+
+            // 合并配置(环境配置覆盖主配置)
+            properties.putAll(mainProperties);
+            properties.putAll(activeProperties);
+            properties.setProperty("spring.profiles.active", env); // 确保环境变量生效
+            log.info("开发环境为: " + env);
+
+        } catch (IOException e) {
+            log.error("配置文件加载失败: " + e.getMessage(), e);
+            System.exit(1);
+        }
+    }
+
+    /**
+     * 将 YAML 解析后的嵌套 Map 转换为扁平的 Properties(键名用点分隔层级)
+     * 例如: {oss: {videoVector: {accessKey: "xxx"}}} → "oss.videoVector.accessKey" = "xxx"
+     */
+    private static Properties convertYamlToProperties(Map<String, Object> yamlMap) {
+        Properties properties = new Properties();
+        flattenYamlMap(yamlMap, "", properties);
+        return properties;
+    }
+
+    /**
+     * 递归展平 YAML Map
+     */
+    private static void flattenYamlMap(Map<String, Object> yamlMap, String parentKey, Properties properties) {
+        for (Map.Entry<String, Object> entry : yamlMap.entrySet()) {
+            String key = parentKey.isEmpty() ? entry.getKey() : parentKey + "." + entry.getKey();
+            Object value = entry.getValue();
+
+            if (value instanceof Map<?, ?>) {
+                // 递归处理嵌套 Map
+                flattenYamlMap((Map<String, Object>) value, key, properties);
+            } else if (value instanceof Iterable<?>) {
+                // 处理列表(转换为逗号分隔的字符串)
+                StringBuilder sb = new StringBuilder();
+                for (Object item : (Iterable<?>) value) {
+                    if (sb.length() > 0) sb.append(",");
+                    sb.append(item);
+                }
+                properties.setProperty(key, sb.toString());
+            } else {
+                // 基本类型直接设置
+                properties.setProperty(key, value != null ? value.toString() : "");
+            }
+        }
+    }
+
+    public static Properties getProperties() {
+        return properties;
+    }
+
+    public static String getProjectEnv() {
+        return getProperties().getProperty("spring.profiles.active");
+    }
+
+    public static String getValue(String key) {
+        return PropertiesUtils.getProperties().getProperty(key);
+    }
+
+    public static boolean getSwaggerEnabled() {
+        return Boolean.parseBoolean(getProperties().getProperty("swagger.enabled"));
+    }
+
+    public static String getSwaggerBasePath() {
+        return getProperties().getProperty("swagger.basePath");
+    }
+
+    public static String getSwaggerProtocols() {
+        return getProperties().getProperty("swagger.protocols");
+    }
+
+    public static String getDownloadDomain() {
+        return getProperties().getProperty("oss.longvideo.cdnDomain");
+    }
+
+    public static String getTranscodeLocation() {
+        return getProperties().getProperty("oss.longvideo.transcode.location");
+    }
+
+    public static String getTranscodePipelineId() {
+        return getProperties().getProperty("oss.longvideo.transcode.PipelineId");
+    }
+
+    public static String getReadOnlyAccessKeyId() {
+        return getProperties().getProperty("oss.video.readonly.accessKey");
+    }
+
+    public static String getReadOnlyAccessKeySecret() {
+        return getProperties().getProperty("oss.video.readonly.secretKey");
+    }
+
+    public static Properties getMainProperties() {
+        return mainProperties;
+    }
+
+    public static String getMainPropertiesValue(String key) {
+        return getMainProperties().getProperty(key);
+    }
+
+    public static String getVideoBucket() {
+        return getMainProperties().getProperty("oss.video.bucket");
+    }
+
+    public static String getLongvideoStsEndpoint() {
+        return getMainProperties().getProperty("oss.longvideo.video.sts.endpoint");
+    }
+
+    public static String getLongvideoStsAccessKeyId() {
+        return getMainProperties().getProperty("oss.longvideo.video.sts.accessKeyId");
+    }
+
+    public static String getLongvideoStsAccessKeySecret() {
+        return getMainProperties().getProperty("oss.longvideo.video.sts.accessKeySecret");
+    }
+
+    public static String getLongvideoStsRoleArn() {
+        return getMainProperties().getProperty("oss.longvideo.video.sts.roleArn");
+    }
+
+    public static String getLongvideoStsSessionName() {
+        return getMainProperties().getProperty("oss.longvideo.video.sts.roleSessionName");
+    }
+
+    public static String getOssaccelerateUploadDomain() {
+        return getMainProperties().getProperty("oss.accelerate.upload.domain");
+    }
+}

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

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

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

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

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

@@ -0,0 +1,55 @@
+package com.tzld.videoVector.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;
+    }
+}

+ 76 - 0
core/src/main/java/com/tzld/videoVector/util/feishu/FeiShu.java

@@ -0,0 +1,76 @@
+package com.tzld.videoVector.util.feishu;
+
+import com.alibaba.fastjson.JSONObject;
+import com.tzld.videoVector.util.feishu.model.FeishuChatListResult;
+import com.tzld.videoVector.util.feishu.model.FeishuGroup;
+import com.tzld.videoVector.util.http.HttpClientUtils;
+import com.tzld.videoVector.util.http.HttpResponseContent;
+import org.slf4j.LoggerFactory;
+import org.springframework.data.util.Pair;
+
+import java.util.HashMap;
+
+public class FeiShu {
+
+    private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(FeiShu.class);
+
+    public static final String URL_FEISHU_ACCESSTOKEN = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal";
+    public static final String URL_FEISHU_CHAT_LIST = "https://open.feishu.cn/open-apis/chat/v4/list?page_size=200";
+    public static final String URL_FEISHU_SEND_MESSAGE = "https://open.feishu.cn/open-apis/message/v4/send";
+    public static final String URL_FEISHU_GROUP_MEMBERS = "https://open.feishu.cn/open-apis/im/v1/chats/:chat_id/members?member_id_type=open_id";
+    public static final String URL_FEISHU_REPLY_MESSAGE = "https://open.feishu.cn/open-apis/im/v1/messages/:message_id/reply";
+    public static final String URL_FEISHU_CARD_MESSAGE_UPDATE = "https://open.feishu.cn/open-apis/im/v1/messages/:message_id";
+    public static final String APPID = "cli_a51114cf8bf8d00c";
+    public static final String APPSECRET = "cNoTAqMpsAm7mPBcpCAXFfvOzCNL27fe";
+
+    public static Pair<String, Integer> requestAccessToken() {
+        Pair<String, Integer> result = Pair.of("", 0);
+        long startTime = System.currentTimeMillis();
+        int retryCount = 0;
+
+        while (retryCount < 3) {
+            try {
+                HashMap<String, String> params = new HashMap<>();
+                params.put("app_id", APPID);
+                params.put("app_secret", APPSECRET);
+                HttpResponseContent hrc = HttpClientUtils.postForm(URL_FEISHU_ACCESSTOKEN, params, new HashMap<>());
+                LOGGER.info("feishu accessToken response = {}", hrc.getBodyContent());
+                JSONObject response = JSONObject.parseObject(hrc.getBodyContent());
+                String accessToken = response.getString("tenant_access_token");
+                int expireSeconds = response.getIntValue("expire");
+                return Pair.of(accessToken, expireSeconds);
+            } catch (Exception e) {
+                LOGGER.error("requestFeishuAccessToken, error ,retryCount = {}, cost = {}", retryCount, System.currentTimeMillis() - startTime, e);
+            }
+            retryCount++;
+        }
+
+        return result;
+
+    }
+
+    public static void main(String[] args) {
+        HashMap<String, String> params = new HashMap<>();
+        params.put("app_id", APPID);
+        params.put("app_secret", APPSECRET);
+        HttpResponseContent hrc = HttpClientUtils.postForm(URL_FEISHU_ACCESSTOKEN, params, new HashMap<>());
+        System.out.println(hrc.getBodyContent());
+        JSONObject response = JSONObject.parseObject(hrc.getBodyContent());
+        String accessToken = response.getString("tenant_access_token");
+
+        HashMap<String, String> header = new HashMap<>();
+        header.put("Authorization", "Bearer " + accessToken);
+        header.put("content-type", "application/json; charset=utf-8");
+
+        //获取群聊ID
+        HttpResponseContent chatHrc = HttpClientUtils.get(URL_FEISHU_CHAT_LIST, header);
+        JSONObject responseJSON = JSONObject.parseObject(chatHrc.getBodyContent());
+        String chatData = responseJSON.getString("data");
+        System.out.println(chatData);
+        System.out.println("-------------------");
+        FeishuChatListResult feishuChatListResult = JSONObject.parseObject(chatData, FeishuChatListResult.class);
+        for (FeishuGroup group : feishuChatListResult.getGroups()) {
+            System.out.println(JSONObject.toJSONString(group));
+        }
+    }
+}

+ 190 - 0
core/src/main/java/com/tzld/videoVector/util/feishu/FeishuExcelUtil.java

@@ -0,0 +1,190 @@
+package com.tzld.videoVector.util.feishu;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.tzld.videoVector.util.MapBuilder;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.data.util.Pair;
+import org.springframework.http.*;
+import org.springframework.web.client.RestTemplate;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+@Slf4j
+public class FeishuExcelUtil {
+
+    public static void feishuSheetDelete(String sheetToken,
+                                   String sheetId,
+                                   int rowNum,
+                                   int startRowIndex,
+                                   HttpHeaders httpHeaders,
+                                   RestTemplate restTemplate,
+                                   List<String> dateStrList) {
+        HttpEntity<Object> queryEntity = new HttpEntity<>(httpHeaders);
+        int deleteRowNum = rowNum < 20 ? startRowIndex + dateStrList.size() * rowNum : startRowIndex + (rowNum * 2);
+        ResponseEntity<String> queryResponseEntity = restTemplate.exchange(
+                String.format("https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/%s/values/%s!A"
+                        + startRowIndex + ":A" + deleteRowNum, sheetToken, sheetId),
+                HttpMethod.GET, queryEntity, String.class);
+        JSONArray values = JSON.parseObject(queryResponseEntity.getBody())
+                .getJSONObject("data")
+                .getJSONObject("valueRange")
+                .getJSONArray("values");
+        int count = 0;
+        if (!values.isEmpty() && Objects.nonNull(values.get(0)) && !((JSONArray) values.get(0)).isEmpty()
+                && Objects.nonNull(((JSONArray) values.get(0)).get(0))
+                && dateStrList.contains(((JSONArray) values.get(0)).get(0).toString())) {
+            for (Object value : values) {
+                if (((JSONArray) value).get(0) != null) {
+                    List<String> dates = ((JSONArray) value).stream().map(Object::toString).collect(Collectors.toList());
+                    if (dateStrList.contains(dates.get(0))) {
+                        count++;
+                        continue;
+                    }
+                }
+                break;
+            }
+        }
+        if (count > 0) {
+            int delNum = 0;
+            do {
+                // 删除当前日期已存在的旧数据
+                httpHeaders.setContentType(MediaType.APPLICATION_JSON);
+                HttpEntity<Object> deleteEntity = new HttpEntity<>(
+                        String.format("{\n" +
+                                "    \"dimension\": {\n" +
+                                "        \"sheetId\": \"%s\",\n" +
+                                "        \"majorDimension\": \"ROWS\",\n" +
+                                "        \"startIndex\": %s,\n" +
+                                "        \"endIndex\": %s\n" +
+                                "    }\n" +
+                                "}", sheetId, startRowIndex, Math.min(startRowIndex + 4000, count - delNum + startRowIndex) - 1),
+                        httpHeaders);
+                restTemplate.exchange(String.format("https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/%s/dimension_range", sheetToken),
+                        HttpMethod.DELETE, deleteEntity, String.class);
+                delNum = Math.min(delNum + 4000, count);
+            } while (delNum < count);
+        }
+    }
+
+    public static void feishuSheetInsert(String sheetToken,
+                                   String sheetId,
+                                   Integer startRowIndex,
+                                   HttpHeaders httpHeaders,
+                                   RestTemplate restTemplate,
+                                   List<List<List<Object>>> partitions) {
+        int startRow = startRowIndex;
+        for (List<List<Object>> partition : partitions) {
+            // 插入数据
+            HttpEntity<Object> postEntity = new HttpEntity<>(MapBuilder
+                    .builder()
+                    .put("valueRange", MapBuilder
+                            .builder()
+                            .put("range", String.format("%s!A" + startRow + ":CI", sheetId) + (partition.size() + startRow - 1))
+                            .put("values", partition)
+                            .build())
+                    .build(), httpHeaders);
+            ResponseEntity<String> response = restTemplate.exchange(String.format("https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/%s/values_prepend",
+                            sheetToken),
+                    HttpMethod.POST, postEntity, String.class);
+            JSONObject responseJSON = JSONObject.parseObject(response.getBody());
+            if (0 != responseJSON.getInteger("code")) {
+                log.error("doSendFeishuSheet write error :{}", responseJSON.getString("msg"));
+            }
+            // 设置行高
+            httpHeaders.setContentType(MediaType.APPLICATION_JSON);
+            HttpEntity<Object> putEntity = new HttpEntity<>(
+                    String.format("{\n" +
+                            "    \"dimension\": {\n" +
+                            "        \"sheetId\": \"%s\",\n" +
+                            "        \"majorDimension\": \"ROWS\",\n" +
+                            "        \"startIndex\": %s,\n" +
+                            "        \"endIndex\": %s\n" +
+                            "    },\n" +
+                            "    \"dimensionProperties\":{\n" +
+                            "        \"fixedSize\":27\n" +
+                            "    }\n" +
+                            "}", sheetId, startRow, Math.min(startRow + 4000, startRow + partition.size()) - 1),
+                    httpHeaders);
+            restTemplate.exchange(String.format("https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/%s/dimension_range", sheetToken),
+                    HttpMethod.PUT, putEntity, String.class);
+            startRow += partition.size();
+        }
+    }
+
+    public static void feishuSheetStyle(String sheetToken,
+                                  String sheetId,
+                                  int rowNum,
+                                  Integer startRowIndex,
+                                  HttpHeaders httpHeaders,
+                                  RestTemplate restTemplate,
+                                  List<Pair<String, Map<Object, Object>>> styles) {
+        Integer startRow = startRowIndex;
+        do {
+            for (Pair<String, Map<Object, Object>> style : styles) {
+                HttpEntity<Map<Object, Object>> styleEntity = new HttpEntity<>(MapBuilder
+                        .builder()
+                        .put("appendStyle",
+                                MapBuilder
+                                        .builder()
+                                        .put("range", String.format("%s!%s" + startRow + ":%s", sheetId,
+                                                style.getFirst(), style.getFirst())
+                                                + (Math.min(startRow + 4000, rowNum + startRowIndex) - 1))
+                                        .put("style", style.getSecond())
+                                        .build()
+                        )
+                        .build(), httpHeaders);
+                restTemplate.exchange(
+                        String.format("https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/%s/style",
+                                sheetToken),
+                        HttpMethod.PUT,
+                        styleEntity,
+                        String.class
+                );
+            }
+            startRow += 4000;
+        } while (startRow < rowNum);
+    }
+
+    public static void feishuSheetThanks(String sheetToken,
+                                   String sheetId,
+                                   int rowNum,
+                                   Integer startRowIndex,
+                                   HttpHeaders httpHeaders,
+                                   RestTemplate restTemplate,
+                                   List<Pair<String, List<Pair<String, String>>>> thanks) {
+        Integer startRow = startRowIndex;
+        do {
+            for (Pair<String, List<Pair<String, String>>> thank : thanks) {
+                List<String> keyList = thank.getSecond().stream().map(Pair::getFirst).collect(Collectors.toList());
+                List<String> colorList = thank.getSecond().stream().map(Pair::getSecond).collect(Collectors.toList());
+                HttpEntity<Map<Object, Object>> styleEntity = new HttpEntity<>(MapBuilder
+                        .builder()
+                        .put("range", String.format("%s!%s" + startRow + ":%s", sheetId,
+                                thank.getFirst(), thank.getFirst())
+                                + (Math.min(startRow + 4000, rowNum + startRowIndex) - 1))
+                        .put("dataValidationType", "list")
+                        .put("dataValidation", MapBuilder.builder()
+                                .put("conditionValues", keyList)
+                                .put("options", MapBuilder.builder()
+                                        .put("highlightValidData", true)
+                                        .put("colors", colorList)
+                                        .build()
+                                ).build())
+                        .build(), httpHeaders);
+                restTemplate.exchange(
+                        String.format("https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/%s/dataValidation",
+                                sheetToken),
+                        HttpMethod.POST,
+                        styleEntity,
+                        String.class
+                );
+            }
+            startRow += 4000;
+        } while (startRow < rowNum);
+    }
+}

+ 146 - 0
core/src/main/java/com/tzld/videoVector/util/feishu/FeishuMessageSender.java

@@ -0,0 +1,146 @@
+package com.tzld.videoVector.util.feishu;
+
+import com.alibaba.fastjson.JSONObject;
+import com.tzld.videoVector.util.HttpClientFactory;
+import com.tzld.videoVector.util.MapBuilder;
+import com.tzld.videoVector.util.feishu.model.Message;
+import com.tzld.videoVector.util.feishu.model.MessageParams;
+import com.tzld.videoVector.util.feishu.model.config.MessageConfig;
+import com.tzld.videoVector.util.feishu.model.element.BaseElement;
+import com.tzld.videoVector.util.feishu.model.element.DivElement;
+import com.tzld.videoVector.util.feishu.model.element.Filed;
+import com.tzld.videoVector.util.feishu.model.element.MarkDownText;
+import com.tzld.videoVector.util.feishu.model.header.HeadTitle;
+import com.tzld.videoVector.util.feishu.model.header.MessageHeader;
+import com.tzld.videoVector.util.http.HttpClientUtils;
+import com.tzld.videoVector.util.http.HttpResponseContent;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.http.HttpEntity;
+import org.apache.http.StatusLine;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.util.EntityUtils;
+import org.apache.http.util.TextUtils;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.data.util.Pair;
+import org.springframework.stereotype.Component;
+
+import java.nio.charset.StandardCharsets;
+import java.util.*;
+
+@Component
+@Slf4j
+public class FeishuMessageSender implements InitializingBean {
+
+    private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(FeishuMessageSender.class);
+
+    private static final String webHookUrl = "https://open.feishu.cn/open-apis/bot/v2/hook/";
+
+    private static final CloseableHttpClient client = HttpClientFactory.create(
+            1000, 1000, 200, 200, 0, 1000);
+
+    private static String staticEnv;
+
+    @Value("${spring.profiles.active:prod}")
+    private String env;
+
+    @Override
+    public void afterPropertiesSet() throws Exception {
+        FeishuMessageSender.staticEnv = env;
+    }
+
+    public void sendChat(String chatId, String title, Map<String, String> infos) {
+        sendChat(chatId, null, title, infos);
+    }
+
+    public void sendChat(String chatId, String userOpenId, String title, Map<String, String> infos) {
+        try {
+            Pair<String, Integer> token = FeiShu.requestAccessToken();
+            HashMap<String, String> header = new HashMap<>();
+            header.put("Authorization", "Bearer " + token.getFirst());
+            header.put("content-type", "application/json; charset=utf-8");
+
+            MessageConfig messageConfig = new MessageConfig();
+            MessageHeader messageHeader = new MessageHeader();
+            messageHeader.title = new HeadTitle(title);
+            List<BaseElement> elements = new ArrayList<>();
+            DivElement textElement = new DivElement();
+            for (Map.Entry<String, String> stringStringEntry : infos.entrySet()) {
+                if (TextUtils.isBlank(stringStringEntry.getValue())) {
+                    textElement.fields.add(new Filed(new MarkDownText(String.format("%s", stringStringEntry.getKey()))));
+                } else {
+                    textElement.fields.add(new Filed(new MarkDownText(String.format("**%s**:%s", stringStringEntry.getKey(), stringStringEntry.getValue()))));
+                }
+            }
+            elements.add(textElement);
+            MessageParams messageParams = new MessageParams(chatId, userOpenId, new Message(messageConfig, messageHeader, elements));
+            HttpResponseContent hrc = HttpClientUtils.postRequestBody(FeiShu.URL_FEISHU_SEND_MESSAGE, messageParams, header);
+        } catch (Exception e) {
+            LOGGER.error("FeishuMessageSender", e);
+        }
+    }
+
+    public static void sendWebHookMessage(String robotId, String msg) {
+        // 使用自定义群机器人webhook方式,支持外部群
+        if (!"prod".equals(staticEnv)) {
+            return;
+        }
+        JSONObject content = new JSONObject();
+        content.put("text", msg);
+        JSONObject bodyParam = new JSONObject();
+        bodyParam.put("msg_type", "text");
+        bodyParam.put("content", content);
+        String webhookUrl = webHookUrl + robotId;
+        String hrc = request(bodyParam, webhookUrl);
+        if (hrc == null) {
+            // 重试一次
+            hrc = request(bodyParam, webhookUrl);
+        }
+        if (hrc == null) {
+            log.error("sendWebHookMessage,hrc is null");
+        }
+    }
+
+    public static void sendWebHookMessage(String robotId, JSONObject bodyParam) {
+        // 使用自定义群机器人webhook方式,支持外部群
+        if (!"prod".equals(staticEnv)) {
+            return;
+        }
+        String webhookUrl = webHookUrl + robotId;
+        String hrc = request(bodyParam, webhookUrl);
+        if (hrc == null) {
+            // 重试一次
+            hrc = request(bodyParam, webhookUrl);
+        }
+        if (hrc == null) {
+            log.error("sendWebHookMessage,hrc is null");
+        }
+    }
+
+    public static String request(JSONObject bodyParam, String webhookUrl) {
+        long start = System.currentTimeMillis();
+        try {
+            HttpPost httpPost = new HttpPost(webhookUrl);
+            StringEntity stringEntity = new StringEntity(bodyParam.toJSONString(), StandardCharsets.UTF_8);
+            httpPost.setHeader("Content-Type", "application/json");
+            httpPost.setEntity(stringEntity);
+            CloseableHttpResponse response = client.execute(httpPost);
+            StatusLine statusLine = response.getStatusLine();
+            if (statusLine.getStatusCode() == 200) {
+                HttpEntity responseEntity = response.getEntity();
+                if (Objects.nonNull(responseEntity)) {
+                    return EntityUtils.toString(responseEntity, "UTF-8");
+                }
+            }
+        } catch (Exception e) {
+            log.error("request error", e);
+        }
+        log.info("score耗时:{}", System.currentTimeMillis() - start);
+        return null;
+    }
+
+}

+ 37 - 0
core/src/main/java/com/tzld/videoVector/util/feishu/model/FeishuChatListResult.java

@@ -0,0 +1,37 @@
+package com.tzld.videoVector.util.feishu.model;
+
+import java.util.List;
+
+/**
+ * Create by nieqi on 2021/7/8
+ */
+public class FeishuChatListResult {
+
+    private boolean has_more;
+    private String page_token;
+    private List<FeishuGroup> groups;
+
+    public boolean isHas_more() {
+        return has_more;
+    }
+
+    public void setHas_more(boolean has_more) {
+        this.has_more = has_more;
+    }
+
+    public String getPage_token() {
+        return page_token;
+    }
+
+    public void setPage_token(String page_token) {
+        this.page_token = page_token;
+    }
+
+    public List<FeishuGroup> getGroups() {
+        return groups;
+    }
+
+    public void setGroups(List<FeishuGroup> groups) {
+        this.groups = groups;
+    }
+}

+ 62 - 0
core/src/main/java/com/tzld/videoVector/util/feishu/model/FeishuGroup.java

@@ -0,0 +1,62 @@
+package com.tzld.videoVector.util.feishu.model;
+
+/**
+ * Create by nieqi on 2021/7/8
+ */
+public class FeishuGroup {
+
+    private String avatar;
+    private String description;
+    private String chat_id;
+    private String name;
+    private String owner_open_id;
+    private String owner_user_id;
+
+    public String getAvatar() {
+        return avatar;
+    }
+
+    public void setAvatar(String avatar) {
+        this.avatar = avatar;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public String getChat_id() {
+        return chat_id;
+    }
+
+    public void setChat_id(String chat_id) {
+        this.chat_id = chat_id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getOwner_open_id() {
+        return owner_open_id;
+    }
+
+    public void setOwner_open_id(String owner_open_id) {
+        this.owner_open_id = owner_open_id;
+    }
+
+    public String getOwner_user_id() {
+        return owner_user_id;
+    }
+
+    public void setOwner_user_id(String owner_user_id) {
+        this.owner_user_id = owner_user_id;
+    }
+}

+ 48 - 0
core/src/main/java/com/tzld/videoVector/util/feishu/model/Message.java

@@ -0,0 +1,48 @@
+package com.tzld.videoVector.util.feishu.model;
+
+
+import com.tzld.videoVector.util.feishu.model.config.MessageConfig;
+import com.tzld.videoVector.util.feishu.model.element.BaseElement;
+import com.tzld.videoVector.util.feishu.model.header.MessageHeader;
+
+import java.util.List;
+
+/**
+ * Create by nieqi on 2021/7/9
+ */
+public class Message {
+
+    private MessageConfig config;
+    private MessageHeader header;
+    private List<BaseElement> elements;
+
+    public Message(MessageConfig config, MessageHeader header, List<BaseElement> elements) {
+        this.config = config;
+        this.header = header;
+        this.elements = elements;
+    }
+
+    public MessageConfig getConfig() {
+        return config;
+    }
+
+    public void setConfig(MessageConfig config) {
+        this.config = config;
+    }
+
+    public MessageHeader getHeader() {
+        return header;
+    }
+
+    public void setHeader(MessageHeader header) {
+        this.header = header;
+    }
+
+    public List<BaseElement> getElements() {
+        return elements;
+    }
+
+    public void setElements(List<BaseElement> elements) {
+        this.elements = elements;
+    }
+}

+ 50 - 0
core/src/main/java/com/tzld/videoVector/util/feishu/model/MessageParams.java

@@ -0,0 +1,50 @@
+package com.tzld.videoVector.util.feishu.model;
+
+/**
+ * Create by nieqi on 2021/7/9
+ */
+public class MessageParams {
+
+    private String chat_id;
+    private String open_id;
+    private String msg_type = "interactive";
+    private Message card;
+
+    public MessageParams(String chat_id, String open_id, Message card) {
+        this.chat_id = chat_id;
+        this.open_id = open_id;
+        this.card = card;
+    }
+
+    public String getOpen_id() {
+        return open_id;
+    }
+
+    public void setOpen_id(String open_id) {
+        this.open_id = open_id;
+    }
+
+    public void setChat_id(String chat_id) {
+        this.chat_id = chat_id;
+    }
+
+    public void setMsg_type(String msg_type) {
+        this.msg_type = msg_type;
+    }
+
+    public void setCard(Message card) {
+        this.card = card;
+    }
+
+    public String getChat_id() {
+        return chat_id;
+    }
+
+    public String getMsg_type() {
+        return msg_type;
+    }
+
+    public Message getCard() {
+        return card;
+    }
+}

+ 26 - 0
core/src/main/java/com/tzld/videoVector/util/feishu/model/config/MessageConfig.java

@@ -0,0 +1,26 @@
+package com.tzld.videoVector.util.feishu.model.config;
+
+/**
+ * Create by nieqi on 2021/7/9
+ */
+public class MessageConfig {
+
+    boolean wide_screen_mode = true;
+    boolean enable_forward = true;
+
+    public boolean isWide_screen_mode() {
+        return wide_screen_mode;
+    }
+
+    public void setWide_screen_mode(boolean wide_screen_mode) {
+        this.wide_screen_mode = wide_screen_mode;
+    }
+
+    public boolean isEnable_forward() {
+        return enable_forward;
+    }
+
+    public void setEnable_forward(boolean enable_forward) {
+        this.enable_forward = enable_forward;
+    }
+}

+ 17 - 0
core/src/main/java/com/tzld/videoVector/util/feishu/model/element/BaseElement.java

@@ -0,0 +1,17 @@
+package com.tzld.videoVector.util.feishu.model.element;
+
+/**
+ * Create by nieqi on 2021/7/9
+ */
+public class BaseElement {
+
+    public String tag = "";
+
+    public String getTag() {
+        return tag;
+    }
+
+    public void setTag(String tag) {
+        this.tag = tag;
+    }
+}

+ 24 - 0
core/src/main/java/com/tzld/videoVector/util/feishu/model/element/DivElement.java

@@ -0,0 +1,24 @@
+package com.tzld.videoVector.util.feishu.model.element;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Create by nieqi on 2021/7/9
+ */
+public class DivElement extends BaseElement {
+
+    public DivElement() {
+        this.tag = "div";
+    }
+
+    public List<Filed> fields = new ArrayList<>();
+
+    public List<Filed> getFields() {
+        return fields;
+    }
+
+    public void setFields(List<Filed> fields) {
+        this.fields = fields;
+    }
+}

+ 30 - 0
core/src/main/java/com/tzld/videoVector/util/feishu/model/element/Filed.java

@@ -0,0 +1,30 @@
+package com.tzld.videoVector.util.feishu.model.element;
+
+/**
+ * Create by nieqi on 2021/7/9
+ */
+public class Filed {
+
+    boolean is_short = false;
+    MarkDownText text;
+
+    public Filed(MarkDownText text) {
+        this.text = text;
+    }
+
+    public boolean isIs_short() {
+        return is_short;
+    }
+
+    public void setIs_short(boolean is_short) {
+        this.is_short = is_short;
+    }
+
+    public MarkDownText getText() {
+        return text;
+    }
+
+    public void setText(MarkDownText text) {
+        this.text = text;
+    }
+}

+ 31 - 0
core/src/main/java/com/tzld/videoVector/util/feishu/model/element/MarkDownText.java

@@ -0,0 +1,31 @@
+package com.tzld.videoVector.util.feishu.model.element;
+
+/**
+ * Create by nieqi on 2021/7/9
+ */
+public class MarkDownText {
+
+    private String tag = "lark_md";
+
+    String content = "";
+
+    public MarkDownText(String content) {
+        this.content = content;
+    }
+
+    public String getTag() {
+        return tag;
+    }
+
+    public void setTag(String tag) {
+        this.tag = tag;
+    }
+
+    public String getContent() {
+        return content;
+    }
+
+    public void setContent(String content) {
+        this.content = content;
+    }
+}

+ 14 - 0
core/src/main/java/com/tzld/videoVector/util/feishu/model/header/HeadTitle.java

@@ -0,0 +1,14 @@
+package com.tzld.videoVector.util.feishu.model.header;
+
+/**
+ * Create by nieqi on 2021/7/9
+ */
+public class HeadTitle {
+
+    public String tag = "plain_text";
+    public String content;
+
+    public HeadTitle(String content) {
+        this.content = content;
+    }
+}

+ 17 - 0
core/src/main/java/com/tzld/videoVector/util/feishu/model/header/MessageHeader.java

@@ -0,0 +1,17 @@
+package com.tzld.videoVector.util.feishu.model.header;
+
+/**
+ * Create by nieqi on 2021/7/9
+ */
+public class MessageHeader {
+
+    public HeadTitle title;
+
+    public HeadTitle getTitle() {
+        return title;
+    }
+
+    public void setTitle(HeadTitle title) {
+        this.title = title;
+    }
+}

+ 764 - 0
core/src/main/java/com/tzld/videoVector/util/http/HttpClientUtils.java

@@ -0,0 +1,764 @@
+package com.tzld.videoVector.util.http;
+
+import com.alibaba.fastjson.JSONObject;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.http.Consts;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpHeaders;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.*;
+import org.apache.http.config.*;
+import org.apache.http.conn.socket.ConnectionSocketFactory;
+import org.apache.http.conn.socket.PlainConnectionSocketFactory;
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import org.apache.http.entity.ByteArrayEntity;
+import org.apache.http.entity.ContentType;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.entity.mime.HttpMultipartMode;
+import org.apache.http.entity.mime.MultipartEntityBuilder;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
+import org.apache.http.message.BasicNameValuePair;
+import org.apache.http.ssl.SSLContexts;
+import org.apache.http.util.EntityUtils;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+import java.io.File;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.nio.charset.CodingErrorAction;
+import java.security.KeyManagementException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.*;
+import java.util.Map.Entry;
+
+/**
+ * 基于apache httpclient4.5.5 的HTTP工具类
+ */
+public class HttpClientUtils {
+
+    static Log log = LogFactory.getLog(HttpClientUtils.class);
+
+    private static CloseableHttpClient client;
+    private static RequestConfig requestConfigDefault;
+    private static PoolingHttpClientConnectionManager connManager = null;
+    // 默认请求获取数据的超时时间
+    private static Integer readTimeoutDefault = 150000;
+    // 默认连接超时时间
+    private static Integer connectTimeoutDefault = 50000;
+    // 默认从connectManager获取Connection超时时间
+    private static Integer getConnectionTimeoutDefault = 1000;
+    // 默认的字符集
+    private static String charsetDefault = "UTF-8";
+
+    public static final String contentTypeJson = "application/json";
+    public static final String contentTypeXml = "application/xml";
+
+    private enum MethodType {
+        GET, POST
+    }
+
+    static {
+        try {
+            SSLContext sslContext = SSLContexts.createDefault();
+            sslContext.init(null, new TrustManager[]{new X509TrustManager() {
+
+                public void checkClientTrusted(X509Certificate[] arg0, String arg1)
+                        throws CertificateException {
+                }
+
+                public void checkServerTrusted(X509Certificate[] arg0, String arg1)
+                        throws CertificateException {
+                }
+
+                public X509Certificate[] getAcceptedIssuers() {
+                    return null;
+                }
+            }}, null);
+            Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
+                    .register("http", PlainConnectionSocketFactory.INSTANCE)
+                    .register("https", new SSLConnectionSocketFactory(sslContext)).build();
+            connManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
+            SocketConfig socketConfig = SocketConfig.custom().setTcpNoDelay(true).build();
+            connManager.setDefaultSocketConfig(socketConfig);
+            MessageConstraints messageConstraints = MessageConstraints.custom().build();
+            ConnectionConfig connectionConfig = ConnectionConfig.custom()
+                    .setMalformedInputAction(CodingErrorAction.IGNORE)
+                    .setUnmappableInputAction(CodingErrorAction.IGNORE).setCharset(Consts.UTF_8)
+                    .setMessageConstraints(messageConstraints).build();
+            connManager.setDefaultConnectionConfig(connectionConfig);
+            connManager.setMaxTotal(1000);
+            connManager.setDefaultMaxPerRoute(500);
+            connManager.setValidateAfterInactivity(3000);
+        } catch (KeyManagementException e) {
+            log.error(e);
+        }
+
+        requestConfigDefault = RequestConfig.custom().setConnectionRequestTimeout(getConnectionTimeoutDefault)
+                .setConnectTimeout(connectTimeoutDefault).setSocketTimeout(readTimeoutDefault).build();
+        client = HttpClients.custom().useSystemProperties().setConnectionManager(connManager).setDefaultRequestConfig(requestConfigDefault)
+                .build();
+
+    }
+
+
+    public static HttpResponseContent head(String url) {
+        HttpRequestBase request = new HttpHead(url);
+        HttpResponseContent hrc = executeHeadHttpRequest(request, connectTimeoutDefault, readTimeoutDefault);
+        return hrc;
+    }
+
+    /**
+     * get请求
+     *
+     * @param url 请求的url
+     * @return HttpResponseContent对象,如果http请求出现异常,返回null
+     */
+    public static HttpResponseContent get(String url) {
+        return get(url, null, connectTimeoutDefault, readTimeoutDefault, null);
+    }
+
+    /**
+     * @param url     请求的url
+     * @param headers 请求需要携带的header信息
+     * @return HttpResponseContent对象,如果http请求出现异常,返回null
+     */
+    public static HttpResponseContent get(String url, Map<String, String> headers) {
+        return get(url, null, connectTimeoutDefault, readTimeoutDefault, headers);
+    }
+
+    /**
+     * get请求
+     *
+     * @param url         请求的url
+     * @param contentType contentType,例如:text/plain
+     * @return HttpResponseContent对象,如果http请求出现异常,返回null
+     */
+    public static HttpResponseContent get(String url, String contentType) {
+        return get(url, contentType, connectTimeoutDefault, readTimeoutDefault, null);
+    }
+
+    /**
+     * get请求
+     *
+     * @param url            请求的url
+     * @param connectTimeout 连接超时设置,毫秒
+     * @param readTimeout    读取超时设置,毫秒
+     * @return HttpResponseContent对象,如果http请求出现异常,返回null
+     */
+    public static HttpResponseContent get(String url, int connectTimeout, int readTimeout) {
+        return get(url, null, connectTimeout, readTimeout, null);
+    }
+
+    /**
+     * get请求
+     *
+     * @param url            请求的url
+     * @param contentType    contentType,例如:text/plain
+     * @param connectTimeout 连接超时设置,毫秒
+     * @param readTimeout    读取超时设置,毫秒
+     * @param headers        请求需要携带的header信息
+     * @return HttpResponseContent对象,如果http请求出现异常,返回null
+     */
+    public static HttpResponseContent get(String url, String contentType, int connectTimeout, int readTimeout, Map<String, String> headers) {
+        return getOrPostUrl(MethodType.GET, url, contentType, connectTimeout, readTimeout, headers);
+    }
+
+    /**
+     * post请求,参数在url中
+     *
+     * @param url 请求的url
+     * @return HttpResponseContent对象,如果http请求出现异常,返回null
+     */
+    public static HttpResponseContent postUrl(String url) {
+        return postUrl(url, null, connectTimeoutDefault, readTimeoutDefault);
+    }
+
+    /**
+     * post请求,参数在url中
+     *
+     * @param url         请求的url
+     * @param contentType contentType,例如:text/plain
+     * @return HttpResponseContent对象,如果http请求出现异常,返回null
+     */
+    public static HttpResponseContent postUrl(String url, String contentType) {
+        return postUrl(url, contentType, connectTimeoutDefault, readTimeoutDefault);
+    }
+
+    /**
+     * post请求,参数在url中
+     *
+     * @param url            请求的url
+     * @param connectTimeout 连接超时设置,毫秒
+     * @param readTimeout    读取超时设置,毫秒
+     * @return HttpResponseContent对象,如果http请求出现异常,返回null
+     */
+    public static HttpResponseContent postUrl(String url, int connectTimeout, int readTimeout) {
+        return postUrl(url, null, connectTimeout, readTimeout);
+    }
+
+    /**
+     * post请求,参数在url中
+     *
+     * @param url            请求的url
+     * @param contentType    contentType,例如:text/plain
+     * @param connectTimeout 连接超时设置,毫秒
+     * @param readTimeout    读取超时设置,毫秒
+     * @return HttpResponseContent对象,如果http请求出现异常,返回null
+     */
+    public static HttpResponseContent postUrl(String url, String contentType, int connectTimeout, int readTimeout) {
+        return getOrPostUrl(MethodType.POST, url, contentType, connectTimeout, readTimeout);
+    }
+
+    /**
+     * get或者post请求,参数在url中
+     *
+     * @param methodType     GET/POST
+     * @param url            请求的url
+     * @param contentType    contentType,例如:text/plain
+     * @param connectTimeout 连接超时设置,毫秒
+     * @param readTimeout    读取超时设置,毫秒
+     * @return HttpResponseContent对象,如果http请求出现异常,返回null
+     */
+    private static HttpResponseContent getOrPostUrl(MethodType methodType, String url, String contentType,
+                                                    int connectTimeout, int readTimeout) {
+        return getOrPostUrl(methodType, url, contentType, connectTimeout, readTimeout, null);
+    }
+
+    private static HttpResponseContent getOrPostUrl(MethodType methodType, String url, String contentType,
+                                                    int connectTimeout, int readTimeout, Map<String, String> extHeaders) {
+        HttpRequestBase request = null;
+        HttpResponseContent hrc = null;
+        if (methodType == MethodType.GET) {
+            request = new HttpGet(url);
+        } else {
+            request = new HttpPost(url);
+        }
+        // 设置contentType
+        if (contentType != null) {
+            request.addHeader(HttpHeaders.CONTENT_TYPE, contentType);
+        }
+        //add by nieqi since 2022-3-23
+        if (Objects.nonNull(extHeaders)) {
+            for (String s : extHeaders.keySet()) {
+                if (Objects.nonNull(s)) {
+                    String headerValue = extHeaders.get(s);
+                    if (Objects.nonNull(headerValue)) {
+                        request.addHeader(s, headerValue);
+                    }
+                }
+            }
+        }
+        hrc = executeHttpRequest(request, connectTimeout, readTimeout);
+        return hrc;
+    }
+
+    /**
+     * post请求,请求数据在data中
+     *
+     * @param url  请求的url
+     * @param data 请求数据
+     * @return HttpResponseContent对象,如果http请求出现异常,返回null
+     */
+    public static HttpResponseContent postData(String url, String data) {
+        return postData(url, data, null, connectTimeoutDefault, readTimeoutDefault);
+
+    }
+
+    /**
+     * post请求,请求数据在data中
+     *
+     * @param url         请求的url
+     * @param data        请求数据
+     * @param contentType contentType,例如:text/plain
+     * @return HttpResponseContent对象,如果http请求出现异常,返回null
+     */
+    public static HttpResponseContent postData(String url, String data, String contentType) {
+        return postData(url, data, contentType, connectTimeoutDefault, readTimeoutDefault);
+    }
+
+    /**
+     * post请求,请求数据在data中
+     *
+     * @param url            请求的url
+     * @param data           请求数据
+     * @param connectTimeout 连接超时设置,毫秒
+     * @param readTimeout    读取超时设置,毫秒
+     * @return HttpResponseContent对象,如果http请求出现异常,返回null
+     */
+    public static HttpResponseContent postData(String url, String data, int connectTimeout, int readTimeout) {
+        return postData(url, data, null, connectTimeout, readTimeout);
+    }
+
+    /**
+     * post请求,请求数据在data中
+     *
+     * @param url            请求的url
+     * @param data           请求数据
+     * @param contentType    contentType,例如:text/plain
+     * @param connectTimeout 连接超时设置,毫秒
+     * @param readTimeout    读取超时设置,毫秒
+     * @return HttpResponseContent对象,如果http请求出现异常,返回null
+     */
+    public static HttpResponseContent postData(String url, String data, String contentType, int connectTimeout,
+                                               int readTimeout) {
+        Map<String, String> headerMap = new HashMap<String, String>();
+        headerMap.put(HttpHeaders.CONTENT_TYPE, contentType);
+        return postDataAddHeader(url, data, headerMap, connectTimeout, readTimeout);
+
+    }
+
+    /**
+     * post请求,请求数据在data中
+     *
+     * @param url            请求的url
+     * @param data           请求数据
+     * @param headerMap      http请求头,key-value放在map中
+     * @param connectTimeout 连接超时设置,毫秒
+     * @param readTimeout    读取超时设置,毫秒
+     * @return HttpResponseContent对象,如果http请求出现异常,返回null
+     */
+    public static HttpResponseContent postDataAddHeader(String url, String data, Map<String, String> headerMap,
+                                                        int connectTimeout, int readTimeout) {
+        HttpRequestBase request = null;
+        HttpResponseContent hrc = null;
+        HttpPost httpPost = new HttpPost(url);
+        httpPost.setEntity(new StringEntity(data, charsetDefault));
+        request = httpPost;
+        // 设置header
+        if (headerMap != null) {
+            for (Entry<String, String> entry : headerMap.entrySet()) {
+                request.addHeader(entry.getKey(), entry.getValue());
+            }
+        }
+        hrc = executeHttpRequest(request, connectTimeout, readTimeout);
+        return hrc;
+
+    }
+
+    /**
+     * post请求,请求数据在data中
+     *
+     * @param url            请求的url
+     * @param msgpackBytes   请求数据
+     * @param headerMap      http请求头,key-value放在map中
+     * @param connectTimeout 连接超时设置,毫秒
+     * @param readTimeout    读取超时设置,毫秒
+     * @return HttpResponseContent对象,如果http请求出现异常,返回null
+     */
+    public static HttpResponseContent postMsgpackDataAddHeader(String url, byte[] msgpackBytes, Map<String, String> headerMap,
+                                                        int connectTimeout, int readTimeout) {
+        HttpRequestBase request = null;
+        HttpResponseContent hrc = null;
+        HttpPost httpPost = new HttpPost(url);
+        ByteArrayEntity entity = new ByteArrayEntity(msgpackBytes);
+        entity.setContentType("application/msgpack");
+        httpPost.setEntity(entity);
+        request = httpPost;
+        // 设置header
+        if (headerMap != null) {
+            for (Entry<String, String> entry : headerMap.entrySet()) {
+                request.addHeader(entry.getKey(), entry.getValue());
+            }
+        }
+        hrc = executeHttpRequest(request, connectTimeout, readTimeout);
+        return hrc;
+
+    }
+
+    /**
+     * post请求,form表单,不能包含二进制数据
+     *
+     * @param url       请求的url
+     * @param paramsMap form表单参数map
+     * @param headerMap form表单header
+     * @return HttpResponseContent对象,如果http请求出现异常,返回null
+     */
+    public static HttpResponseContent postForm(String url, Map<String, String> paramsMap, Map<String, String> headerMap) {
+        return postForm(url, paramsMap, connectTimeoutDefault, readTimeoutDefault, headerMap);
+    }
+
+    /**
+     * post请求,form表单,不能包含二进制数据
+     *
+     * @param url            请求的url
+     * @param paramsMap      form表单参数map
+     * @param connectTimeout 连接超时设置,毫秒
+     * @param readTimeout    读取超时设置,毫秒
+     * @param headerMap      form表单header
+     * @return HttpResponseContent对象,如果http请求出现异常,返回null
+     */
+    public static HttpResponseContent postForm(String url, Map<String, String> paramsMap, int connectTimeout,
+                                               int readTimeout, Map<String, String> headerMap) {
+        List<NameValuePair> nvps = new ArrayList<NameValuePair>();
+        if (Objects.nonNull(paramsMap)) {
+            Iterator<String> iterator = paramsMap.keySet().iterator();
+            while (iterator.hasNext()) {
+                String key = iterator.next();
+                Object value = paramsMap.get(key);
+                nvps.add(new BasicNameValuePair(key, String.valueOf(value)));
+            }
+        }
+        return postForm(url, nvps, connectTimeout, readTimeout, headerMap);
+    }
+
+    /**
+     * post请求,form表单,不能包含二进制数据
+     *
+     * @param url            请求的url
+     * @param nvps           form表单参数
+     * @param connectTimeout 连接超时设置,毫秒
+     * @param readTimeout    读取超时设置,毫秒
+     * @param headerMap      form表单header
+     * @return HttpResponseContent对象,如果http请求出现异常,返回null
+     */
+    public static HttpResponseContent postForm(String url, List<NameValuePair> nvps, int connectTimeout,
+                                               int readTimeout, Map<String, String> headerMap) {
+        HttpRequestBase request = null;
+        HttpResponseContent hrc = null;
+        HttpPost httpPost = new HttpPost(url);
+        // 设置header
+        if (headerMap != null) {
+            for (Entry<String, String> entry : headerMap.entrySet()) {
+                httpPost.addHeader(entry.getKey(), entry.getValue());
+            }
+        }
+        try {
+            httpPost.setEntity(new UrlEncodedFormEntity(nvps, charsetDefault));
+        } catch (UnsupportedEncodingException e) {
+            log.error(e);
+        }
+        request = httpPost;
+        hrc = executeHttpRequest(request, connectTimeout, readTimeout);
+        return hrc;
+    }
+
+    /**
+     * post请求,multipart,支持File,byte[]这两种二进制数据
+     *
+     * @param url       请求的url
+     * @param paramsMap 请求参数map
+     * @return HttpResponseContent对象,如果http请求出现异常,返回null
+     */
+    public static HttpResponseContent postMultipart(String url, Map<String, Object> paramsMap) {
+        return postMultipart(url, paramsMap, connectTimeoutDefault, readTimeoutDefault, null);
+    }
+
+    public static HttpResponseContent postMultipart(String url, Map<String, Object> paramsMap, Map<String, String> headerMap) {
+        return postMultipart(url, paramsMap, connectTimeoutDefault, readTimeoutDefault, headerMap);
+    }
+
+    /**
+     * post请求,multipart,支持File,byte[]这两种二进制数据
+     *
+     * @param url            请求的url
+     * @param paramsMap      请求参数map
+     * @param connectTimeout 连接超时设置,毫秒
+     * @param readTimeout    读取超时设置,毫秒
+     * @return HttpResponseContent对象,如果http请求出现异常,返回null
+     */
+    public static HttpResponseContent postMultipart(String url, Map<String, Object> paramsMap, int connectTimeout,
+                                                    int readTimeout, Map<String, String> headerMap) {
+        HttpRequestBase request = null;
+        HttpResponseContent hrc = null;
+        HttpPost httpPost = new HttpPost(url);
+        MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create();
+        multipartEntityBuilder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
+        Iterator<String> iterator = paramsMap.keySet().iterator();
+        while (iterator.hasNext()) {
+            String key = iterator.next();
+            Object value = paramsMap.get(key);
+            if (value instanceof File) {
+                multipartEntityBuilder.addBinaryBody(key, (File) value);
+            } else if (value instanceof byte[]) {
+                multipartEntityBuilder.addBinaryBody(key, (byte[]) value);
+            } else {
+                multipartEntityBuilder.addTextBody(key, String.valueOf(value),
+                        ContentType.create("text/plain", charsetDefault));
+            }
+        }
+        httpPost.setEntity(multipartEntityBuilder.build());
+        // 设置header
+        if (headerMap != null) {
+            for (Entry<String, String> entry : headerMap.entrySet()) {
+                httpPost.addHeader(entry.getKey(), entry.getValue());
+            }
+        }
+        request = httpPost;
+        hrc = executeHttpRequest(request, connectTimeout, readTimeout);
+        return hrc;
+    }
+
+    public static HttpResponseContent postRequestBody(String url, Object s, Map<String, String> headerMap) {
+        HttpRequestBase request = null;
+        HttpResponseContent hrc = null;
+        HttpPost httpPost = new HttpPost(url);
+
+        StringEntity stringEntity = new StringEntity((s instanceof String) ? (String) s : JSONObject.toJSONString(s), Consts.UTF_8);
+        stringEntity.setContentEncoding(Consts.UTF_8.name());
+        stringEntity.setContentType("application/json");
+        httpPost.setEntity(stringEntity);
+        // 设置header
+        if (headerMap != null) {
+            for (Entry<String, String> entry : headerMap.entrySet()) {
+                httpPost.addHeader(entry.getKey(), entry.getValue());
+            }
+        }
+        request = httpPost;
+        hrc = executeHttpRequest(request, connectTimeoutDefault, readTimeoutDefault);
+        return hrc;
+    }
+
+    public static HttpResponseContent patchRequestBody(String url, Object s, Map<String, String> headerMap) {
+        HttpRequestBase request = null;
+        HttpResponseContent hrc = null;
+        HttpPatch httpPost = new HttpPatch(url);
+
+        StringEntity stringEntity = new StringEntity((s instanceof String) ? (String) s : JSONObject.toJSONString(s), Consts.UTF_8);
+        stringEntity.setContentEncoding(Consts.UTF_8.name());
+        stringEntity.setContentType("application/json");
+        httpPost.setEntity(stringEntity);
+        // 设置header
+        if (headerMap != null) {
+            for (Entry<String, String> entry : headerMap.entrySet()) {
+                httpPost.addHeader(entry.getKey(), entry.getValue());
+            }
+        }
+        request = httpPost;
+        hrc = executeHttpRequest(request, connectTimeoutDefault, readTimeoutDefault);
+        return hrc;
+    }
+
+    public static HttpResponseContent deleteRequestBody(String url, Object s, HashMap<String, String> headerMap) {
+        HttpRequestBase request = null;
+        HttpResponseContent hrc = null;
+        HttpDeleteExpand httpDelete = new HttpDeleteExpand(url);
+
+        StringEntity stringEntity = new StringEntity(JSONObject.toJSONString(s), Consts.UTF_8);
+        stringEntity.setContentEncoding(Consts.UTF_8.name());
+        stringEntity.setContentType("application/json");
+        httpDelete.setEntity(stringEntity);
+        // 设置header
+        if (headerMap != null) {
+            for (Entry<String, String> entry : headerMap.entrySet()) {
+                httpDelete.addHeader(entry.getKey(), entry.getValue());
+            }
+        }
+        request = httpDelete;
+        hrc = executeHttpRequest(request, connectTimeoutDefault, readTimeoutDefault);
+        return hrc;
+    }
+
+    public static HttpResponseContent putRequestBody(String url, Object s, HashMap<String, String> headerMap) {
+        HttpRequestBase request = null;
+        HttpResponseContent hrc = null;
+        HttpPut httpPut = new HttpPut(url);
+
+        StringEntity stringEntity = new StringEntity(JSONObject.toJSONString(s), Consts.UTF_8);
+        stringEntity.setContentEncoding(Consts.UTF_8.name());
+        stringEntity.setContentType("application/json");
+        httpPut.setEntity(stringEntity);
+        // 设置header
+        if (headerMap != null) {
+            for (Entry<String, String> entry : headerMap.entrySet()) {
+                httpPut.addHeader(entry.getKey(), entry.getValue());
+            }
+        }
+        request = httpPut;
+        hrc = executeHttpRequest(request, connectTimeoutDefault, readTimeoutDefault);
+        return hrc;
+    }
+
+    /**
+     * 执行Http请求
+     *
+     * @param request
+     * @param connectTimeout
+     * @param readTimeout
+     * @return HttpResponseContent对象,如果http请求出现异常,返回null
+     */
+    private static HttpResponseContent executeHttpRequest(HttpRequestBase request, int connectTimeout,
+                                                          int readTimeout) {
+        CloseableHttpResponse response = null;
+        HttpResponseContent hrc = null;
+        try {
+            // 设置请求配置
+            RequestConfig.Builder configBuilder = RequestConfig.custom();
+            // 设置连接超时
+            configBuilder.setConnectTimeout(connectTimeout);
+            // 设置读取超时
+            configBuilder.setSocketTimeout(readTimeout);
+            // 设置从连接池获取连接实例的超时
+            configBuilder.setConnectionRequestTimeout(getConnectionTimeoutDefault);
+            RequestConfig requestConfig = configBuilder.build();
+            request.setConfig(requestConfig);
+            log.debug("开始执行Http请求, uri:" + request.getURI());
+            response = client.execute(request);
+            hrc = getHttpResponseContent(response);
+            return hrc;
+        } catch (Exception e) {
+            log.error("执行Http请求异常, uri:" + request.getURI(), e);
+        } finally {
+            close(request, response);
+        }
+        return hrc;
+
+    }
+
+    /**
+     * 封装HTTP响应报文
+     *
+     * @param response
+     * @return
+     */
+    private static HttpResponseContent getHttpResponseContent(CloseableHttpResponse response) {
+        // 获取响应实体
+        HttpEntity entity = response.getEntity();
+        if (entity == null) {
+            return null;
+        }
+        HttpResponseContent hrc = new HttpResponseContent();
+        hrc.setHeaders(response.getAllHeaders());
+        hrc.setStatusCode(response.getStatusLine().getStatusCode());
+        ContentType contentType = ContentType.getOrDefault(entity);
+        hrc.setMimeType(contentType.getMimeType());
+        if (contentType.getCharset() != null) {
+            hrc.setCharsetName(contentType.getCharset().name());
+        }
+        try {
+            hrc.setContentBytes(EntityUtils.toByteArray(entity));
+        } catch (IOException e) {
+            log.error("封装HTTP响应报文异常", e);
+        }
+        return hrc;
+
+    }
+
+    /**
+     * 执行Http请求
+     *
+     * @param request
+     * @param connectTimeout
+     * @param readTimeout
+     * @return HttpResponseContent对象,如果http请求出现异常,返回null
+     */
+    private static HttpResponseContent executeHeadHttpRequest(HttpRequestBase request, int connectTimeout,
+                                                          int readTimeout) {
+        CloseableHttpResponse response = null;
+        HttpResponseContent hrc = null;
+        try {
+            // 设置请求配置
+            RequestConfig.Builder configBuilder = RequestConfig.custom();
+            // 设置连接超时
+            configBuilder.setConnectTimeout(connectTimeout);
+            // 设置读取超时
+            configBuilder.setSocketTimeout(readTimeout);
+            // 设置从连接池获取连接实例的超时
+            configBuilder.setConnectionRequestTimeout(getConnectionTimeoutDefault);
+            RequestConfig requestConfig = configBuilder.build();
+            request.setConfig(requestConfig);
+            log.debug("开始执行Http请求, uri:" + request.getURI());
+            response = client.execute(request);
+            hrc = getHeadHttpResponseContent(response);
+            return hrc;
+        } catch (Exception e) {
+            log.error("执行Http请求异常, uri:" + request.getURI(), e);
+        } finally {
+            close(request, response);
+        }
+        return hrc;
+
+    }
+
+    /**
+     * 封装HTTP响应报文
+     *
+     * @param response
+     * @return
+     */
+    private static HttpResponseContent getHeadHttpResponseContent(CloseableHttpResponse response) {
+        HttpResponseContent hrc = new HttpResponseContent();
+        hrc.setHeaders(response.getAllHeaders());
+        hrc.setStatusCode(response.getStatusLine().getStatusCode());
+        HttpEntity entity = response.getEntity();
+        if (entity != null) {
+            ContentType contentType = ContentType.getOrDefault(entity);
+            hrc.setMimeType(contentType.getMimeType());
+            if (contentType.getCharset() != null) {
+                hrc.setCharsetName(contentType.getCharset().name());
+            }
+            try {
+                hrc.setContentBytes(EntityUtils.toByteArray(entity));
+            } catch (IOException e) {
+                log.error("封装HTTP响应报文异常", e);
+            }
+        }
+
+        return hrc;
+
+    }
+
+    /**
+     * 关闭资源
+     *
+     * @param request
+     * @param response
+     */
+    private static void close(HttpRequestBase request, CloseableHttpResponse response) {
+        try {
+            if (request != null)
+                request.releaseConnection();
+            if (response != null)
+                response.close();
+        } catch (Exception e) {
+            log.error("关闭资源异常", e);
+        }
+    }
+
+    /**
+     * url编码
+     *
+     * @param url
+     * @param charset
+     * @return
+     */
+    public static String encodeURL(String url, String charset) {
+        if (url == null || charset == null) {
+            return null;
+        }
+        int p = url.indexOf("?");
+        if (p < 0) {
+            return url;
+        }
+        StringBuilder sb = new StringBuilder();
+        String preStr = url.substring(0, p + 1);
+        sb.append(preStr);
+        String queryStr = url.substring(p + 1, url.length());
+        String[] array = queryStr.split("&");
+        for (int i = 0; i < array.length; i++) {
+            String str = array[i];
+            int pp = str.indexOf("=");
+            if (pp > -1) {
+                sb.append(str.substring(0, pp + 1));
+                try {
+                    sb.append(URLEncoder.encode(str.substring(pp + 1, str.length()), charset));
+                } catch (UnsupportedEncodingException e) {
+                    e.printStackTrace();
+                }
+                if (i < array.length - 1) {
+                    sb.append("&");
+                }
+            }
+        }
+        return sb.toString();
+    }
+
+}

+ 24 - 0
core/src/main/java/com/tzld/videoVector/util/http/HttpDeleteExpand.java

@@ -0,0 +1,24 @@
+package com.tzld.videoVector.util.http;
+
+import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
+
+import java.net.URI;
+
+public class HttpDeleteExpand extends HttpEntityEnclosingRequestBase {
+    public static final String METHOD_NAME = "DELETE";
+
+    public HttpDeleteExpand() {
+    }
+
+    public HttpDeleteExpand(URI uri) {
+        this.setURI(uri);
+    }
+
+    public HttpDeleteExpand(String uri) {
+        this.setURI(URI.create(uri));
+    }
+
+    public String getMethod() {
+        return "DELETE";
+    }
+}

+ 97 - 0
core/src/main/java/com/tzld/videoVector/util/http/HttpResponseContent.java

@@ -0,0 +1,97 @@
+package com.tzld.videoVector.util.http;
+
+import org.apache.http.Header;
+
+import java.io.UnsupportedEncodingException;
+
+/**
+ * HTTP请求响应报文封装类
+ */
+public class HttpResponseContent {
+
+    private int statusCode; // 响应状态码
+    private String mimeType; // mime类型
+    private String charsetName; // 字符集
+    private byte[] contentBytes; // 响应报文主体内容的二进制数据
+    private Header[] headers; // 响应头
+
+    /**
+     * 获取响应报文主体内容,字符串,使用响应报文中的字符集编码
+     *
+     * @return
+     */
+    public String getBodyContent() {
+        return this.getBodyContent(charsetName);
+    }
+
+    /**
+     * 获取响应报文主体内容,字符串
+     *
+     * @param charsetName 指定的字符编码
+     * @return
+     */
+    public String getBodyContent(String charsetName) {
+        String str = null;
+        if (charsetName == null) {
+            charsetName = "UTF-8";
+        }
+        try {
+            str = new String(contentBytes, charsetName);
+        } catch (UnsupportedEncodingException e) {
+            // 忽略异常
+            e.printStackTrace();
+        }
+        return str;
+    }
+
+    public boolean isSuccessful() {
+        return statusCode == 200;
+    }
+
+    public int getStatusCode() {
+        return statusCode;
+    }
+
+    public void setStatusCode(int statusCode) {
+        this.statusCode = statusCode;
+    }
+
+    public String getMimeType() {
+        return mimeType;
+    }
+
+    public void setMimeType(String mimeType) {
+        this.mimeType = mimeType;
+    }
+
+    public String getCharsetName() {
+        return charsetName;
+    }
+
+    public void setCharsetName(String charsetName) {
+        this.charsetName = charsetName;
+    }
+
+    public byte[] getContentBytes() {
+        return contentBytes;
+    }
+
+    public void setContentBytes(byte[] contentBytes) {
+        this.contentBytes = contentBytes;
+    }
+
+    public Header[] getHeaders() {
+        return headers;
+    }
+
+    public void setHeaders(Header[] headers) {
+        this.headers = headers;
+    }
+
+    @Override
+    public String toString() {
+        return "HttpResponseContent [statusCode=" + statusCode + ", mimeType=" + mimeType + ", charsetName=" + charsetName
+                + ", bodyContent=" + getBodyContent() + "]";
+    }
+
+}

+ 61 - 0
core/src/main/resources/generator/mybatis-spider-generator-config.xml

@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE generatorConfiguration
+        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
+        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
+<!-- 配置生成器 -->
+<generatorConfiguration>
+    <context id="mysql" defaultModelType="flat">
+        <property name="autoDelimitKeywords" value="true"/>
+        <!-- 生成的Java文件的编码 -->
+        <property name="javaFileEncoding" value="UTF-8"/>
+        <!-- 格式化java代码 -->
+        <property name="javaFormatter" value="org.mybatis.generator.api.dom.DefaultJavaFormatter"/>
+        <!-- 格式化XML代码 -->
+        <property name="xmlFormatter" value="org.mybatis.generator.api.dom.DefaultXmlFormatter"/>
+        <!-- beginningDelimiter和endingDelimiter:指明数据库的用于标记数据库对象名的符号,比如ORACLE就是双引号,MYSQL默认是`反引号; -->
+        <property name="beginningDelimiter" value="`"/>
+        <property name="endingDelimiter" value="`"/>
+
+        <plugin type="org.mybatis.generator.plugins.ToStringPlugin" />
+        <plugin type="org.mybatis.generator.plugins.UnmergeableXmlMappersPlugin" />
+
+        <commentGenerator>
+            <property name="addRemarkComments" value="true"/>
+        </commentGenerator>
+
+        <jdbcConnection driverClass="com.mysql.jdbc.Driver"
+                        connectionURL="jdbc:mysql://rm-bp13g3ra2f59q49xs.mysql.rds.aliyuncs.com:3306/ai_supply?useUnicode=true&amp;characterEncoding=utf-8&amp;zeroDateTimeBehavior=convertToNull&amp;useSSL=false"
+                        userId="wqsd" password="wqsd@2025">
+        </jdbcConnection>
+
+        <javaTypeResolver type="org.mybatis.generator.internal.types.JavaTypeResolverDefaultImpl">
+            <property name="forceBigDecimals" value="false"/>
+        </javaTypeResolver>
+
+        <javaModelGenerator targetPackage="com.tzld.videoVector.model.po.videoVector.spider" targetProject="core/src/main/java">
+            <property name="constructorBased" value="false"/>
+            <property name="enableSubPackages" value="true"/>
+            <property name="immutable" value="false"/>
+        </javaModelGenerator>
+
+        <sqlMapGenerator targetPackage="mapper.videoVector.spider" targetProject="core/src/main/resources">
+            <property name="enableSubPackages" value="true"/>
+        </sqlMapGenerator>
+
+        <javaClientGenerator targetPackage="com.tzld.videoVector.dao.mapper.videoVector.spider" type="XMLMAPPER" targetProject="core/src/main/java">
+            <property name="enableSubPackages" value="true"/>
+        </javaClientGenerator>
+
+<!--        <table tableName="spider_task" domainObjectName="" alias=""/>-->
+<!--        <table tableName="spider_content" domainObjectName="" alias=""/>-->
+<!--        <table tableName="spider_content_media" domainObjectName="" alias=""/>-->
+<!--        <table tableName="ai_model_tts" domainObjectName="" alias=""/>-->
+<!--        <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="tools_audio_trans_record" domainObjectName="" alias=""/>-->
+        <table tableName="subtitle_style" domainObjectName="" alias=""/>
+    </context>
+
+</generatorConfiguration>

+ 282 - 0
pom.xml

@@ -0,0 +1,282 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>com.tzld.piaoquan</groupId>
+    <artifactId>video-vector-server</artifactId>
+    <version>1.0-SNAPSHOT</version>
+    <packaging>pom</packaging>
+    <modules>
+        <module>core</module>
+        <module>server</module>
+    </modules>
+
+    <parent>
+        <groupId>com.tzld.commons</groupId>
+        <artifactId>supom</artifactId>
+        <version>1.0.6</version>
+    </parent>
+
+    <properties>
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.mybatis.spring.boot</groupId>
+            <artifactId>mybatis-spring-boot-starter</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.github.pagehelper</groupId>
+            <artifactId>pagehelper-spring-boot-starter</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-redis</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-aop</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>io.lettuce</groupId>
+            <artifactId>lettuce-core</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-pool2</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-collections4</artifactId>
+            <version>4.1</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.mybatis.generator</groupId>
+            <artifactId>mybatis-generator-core</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+        </dependency>
+
+        <!-- druid数据源 -->
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>druid</artifactId>
+            <version>1.1.23</version>
+        </dependency>
+        <!--easyexcel-->
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>easyexcel</artifactId>
+            <version>3.0.5</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-boot-starter</artifactId>
+            <version>3.5.3.2</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <version>1.18.12</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+            <version>4.5.14</version> <!-- 使用最新版本 -->
+        </dependency>
+
+        <!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpmime -->
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpmime</artifactId>
+            <version>4.5.14</version>
+        </dependency>
+
+        <!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpcore -->
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpcore</artifactId>
+            <version>4.4.16</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+            <version>31.1-jre</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>fastjson</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.aliyun.odps</groupId>
+            <artifactId>odps-sdk-core</artifactId>
+            <version>0.27.2-public</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.aliyun.oss</groupId>
+            <artifactId>aliyun-sdk-oss</artifactId>
+            <version>3.17.4</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.xuxueli</groupId>
+            <artifactId>xxl-job-core</artifactId>
+        </dependency>
+
+        <!-- https://mvnrepository.com/artifact/commons-codec/commons-codec -->
+        <dependency>
+            <groupId>commons-codec</groupId>
+            <artifactId>commons-codec</artifactId>
+            <version>1.17.1</version>
+        </dependency>
+
+        <!--XML 解析包-->
+        <dependency>
+            <groupId>org.jdom</groupId>
+            <artifactId>jdom2</artifactId>
+            <version>2.0.6</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.aliyun.openservices</groupId>
+            <artifactId>ons-client</artifactId>
+            <version>1.8.4.Final</version>
+        </dependency>
+
+        <!-- https://mvnrepository.com/artifact/com.ctrip.framework.apollo/apollo-client -->
+        <dependency>
+            <groupId>com.ctrip.framework.apollo</groupId>
+            <artifactId>apollo-client</artifactId>
+            <version>2.0.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.aliyun.openservices</groupId>
+            <artifactId>aliyun-log-producer</artifactId>
+            <version>0.3.10</version>
+        </dependency>
+        <dependency>
+            <groupId>com.aliyun.openservices</groupId>
+            <artifactId>aliyun-log-logback-appender</artifactId>
+            <version>0.1.18</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>com.google.protobuf</groupId>
+                    <artifactId>protobuf-java</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <!-- 强制使用兼容的 Protobuf 版本 -->
+        <dependency>
+            <groupId>com.google.protobuf</groupId>
+            <artifactId>protobuf-java</artifactId>
+            <version>3.20.1</version> <!-- 使用与阿里云 SDK 兼容的版本 -->
+        </dependency>
+
+        <dependency>
+            <groupId>io.springfox</groupId>
+            <artifactId>springfox-swagger2</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.springfox</groupId>
+            <artifactId>springfox-swagger-ui</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.aliyun</groupId>
+            <artifactId>alibabacloud-dysmsapi20170525</artifactId>
+            <version>2.0.23</version>
+            <scope>compile</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>com.tzld.commons</groupId>
+            <artifactId>aliyun-log-spring-boot-starter</artifactId>
+            <version>2.0.0</version>
+            <exclusions>
+                <exclusion>
+                    <artifactId>aliyun-log</artifactId>
+                    <groupId>com.aliyun.openservices</groupId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>com.stuuudy.commons</groupId>
+            <artifactId>commons-external</artifactId>
+            <version>1.0-SNAPSHOT</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-validation</artifactId>
+        </dependency>
+
+        <!-- Eureka 客户端 -->
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
+        </dependency>
+
+        <!-- OpenFeign -->
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-openfeign</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+            <version>5.8.18</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.msgpack</groupId>
+            <artifactId>msgpack-core</artifactId>
+            <version>0.9.9</version>
+        </dependency>
+        <dependency>
+            <groupId>org.msgpack</groupId>
+            <artifactId>jackson-dataformat-msgpack</artifactId>
+            <version>0.9.9</version>
+        </dependency>
+
+    </dependencies>
+
+</project>

+ 51 - 0
server/pom.xml

@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>com.tzld.piaoquan</groupId>
+        <artifactId>video-vector-server</artifactId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>server</artifactId>
+
+    <properties>
+        <java.version>1.8</java.version>
+        <maven.build.timestamp.format>yyyy-MM-dd HH:mm:ss.SSS</maven.build.timestamp.format>
+        <mavenBuildTime>${maven.build.timestamp}</mavenBuildTime>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.tzld.piaoquan</groupId>
+            <artifactId>core</artifactId>
+            <version>1.0-SNAPSHOT</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <!-- 固定包名 避免随着版本变动 -->
+        <finalName>video-vector-server</finalName>
+        <resources>
+            <resource>
+                <directory>src/main/resources</directory>
+                <filtering>true</filtering>
+                <includes>
+                    <include>**/*.yml</include>
+                    <include>**/*.yaml</include>
+                    <include>**/*.xml</include>
+                    <include>**/*.properties</include>
+                </includes>
+            </resource>
+        </resources>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+            </plugin>
+        </plugins>
+
+    </build>
+</project>

+ 33 - 0
server/src/main/java/com/tzld/videoVector/Application.java

@@ -0,0 +1,33 @@
+package com.tzld.videoVector;
+
+import com.tzld.videoVector.interceptor.CrosDomainAllowInterceptor;
+import org.mybatis.spring.annotation.MapperScan;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.web.servlet.ServletComponentScan;
+import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
+import org.springframework.cloud.openfeign.EnableFeignClients;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+@SpringBootApplication
+@MapperScan("com.tzld.videoVector.dao")
+@ServletComponentScan("com.tzld.videoVector.controller")
+@EnableDiscoveryClient
+@EnableFeignClients
+public class Application implements WebMvcConfigurer {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(Application.class);
+
+    public static void main(String[] args) {
+        SpringApplication.run(Application.class, args);
+        LOGGER.info("video-vector-server Start Success");
+    }
+
+    @Override
+    public void addInterceptors(InterceptorRegistry registry) {
+        registry.addInterceptor(new CrosDomainAllowInterceptor()).addPathPatterns("/**");
+    }
+}

+ 159 - 0
server/src/main/java/com/tzld/videoVector/aop/LogRequestAop.java

@@ -0,0 +1,159 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright © 2021 xrv <xrg@live.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package com.tzld.videoVector.aop;
+
+import com.alibaba.fastjson.JSON;
+import com.google.common.base.Strings;
+import com.tzld.commons.aliyun.log.AliyunLogManager;
+import com.tzld.videoVector.util.IpUtil;
+import org.apache.commons.lang3.StringUtils;
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.annotation.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.slf4j.MDC;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+/**
+ * 请求日志记录
+ *
+ * @author ehlxr
+ * @since 2021-12-14 14:03.
+ */
+@Aspect
+@Component
+public class LogRequestAop {
+    private static final Logger log = LoggerFactory.getLogger(LogRequestAop.class);
+
+    private final AliyunLogManager aliyunLogManager;
+    /**
+     * aliyun log 配置
+     */
+    @Value("${aliyun.log.project:}")
+    private String projcet;
+    @Value("${aliyun.log.logstore.request:request-log}")
+    private String logStore;
+    @Value("${aliyun.log.topic:}")
+    private String topic;
+
+    public static String LOG_TRACE_ID = "logTraceId";
+    private String REQUEST_START_TIME = "request_start_time";
+
+    @Autowired
+    public LogRequestAop(AliyunLogManager aliyunLogManager) {
+        this.aliyunLogManager = aliyunLogManager;
+    }
+
+    /**
+     * 切入点
+     */
+    @Pointcut("execution(public * com.tzld.videoVector.controller..*(..)) " +
+            "&& !@annotation(com.tzld.videoVector.annotation.NoRequestLog)")
+    public void requestLog() {
+    }
+
+    /**
+     * 前置操作
+     */
+    @Before("requestLog()")
+    public void beforeLog() {
+        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
+        HttpServletRequest request = Objects.requireNonNull(attributes).getRequest();
+
+        request.setAttribute(REQUEST_START_TIME, System.currentTimeMillis());
+    }
+
+    /**
+     * 后置操作
+     */
+    @AfterReturning(pointcut = "requestLog()", returning = "returnValue")
+    public void afterReturning(JoinPoint point, Object returnValue) {
+        logRecord(point, JSON.toJSONString(returnValue));
+    }
+
+    @AfterThrowing(pointcut = "requestLog()", throwing = "ex")
+    public void afterThrowing(JoinPoint point, Exception ex) {
+        logRecord(point, ex.toString());
+    }
+
+    private void logRecord(JoinPoint point, String message) {
+        try {
+            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
+            HttpServletRequest request = Objects.requireNonNull(attributes).getRequest();
+
+            Long startTime = (Long) request.getAttribute(REQUEST_START_TIME);
+            if (Objects.isNull(startTime)) {
+                return;
+            }
+
+            String query = request.getQueryString();
+            String uri = request.getRequestURI();
+
+            Map<String, Object> logMap = new HashMap<>(8);
+            String url = Strings.isNullOrEmpty(query) ? uri : uri + "?" + query;
+            if (StringUtils.isNotBlank(url) && url.contains("/test")) {
+                return;
+            }
+            logMap.put("url", url);
+            logMap.put("method", request.getMethod());
+            logMap.put("header", getHeaders(request));
+            logMap.put("elapsedTime", String.valueOf(System.currentTimeMillis() - startTime));
+            logMap.put("clientIp", IpUtil.getIpAddr(request));
+            logMap.put("requestBody", getRequestBody(point.getArgs()));
+            logMap.put(LOG_TRACE_ID, Strings.nullToEmpty(MDC.get(LOG_TRACE_ID)));
+            logMap.put("responseBody", message);
+            aliyunLogManager.sendLog(projcet, logStore, topic, logMap);
+        } catch (Exception e) {
+            log.error("log report request error", e);
+        }
+    }
+
+    private Object getRequestBody(Object[] args) {
+        try {
+            return JSON.toJSONString(args[0]);
+        } catch (Exception e) {
+            return args;
+        }
+    }
+
+    private String getHeaders(HttpServletRequest request) {
+        return Collections.list(request.getHeaderNames())
+                .stream()
+                .collect(Collectors.toMap(
+                        name -> name,
+                        request::getHeader)).toString();
+    }
+}

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

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

+ 21 - 0
server/src/main/java/com/tzld/videoVector/controller/HealthController.java

@@ -0,0 +1,21 @@
+package com.tzld.videoVector.controller;
+
+import com.tzld.videoVector.annotation.NoRequestLog;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/")
+public class HealthController {
+
+    /**
+     * 探活
+     * @return
+     */
+    @GetMapping("/healthcheck")
+    @NoRequestLog
+    public String healthcheck() {
+        return "ok";
+    }
+}

+ 24 - 0
server/src/main/java/com/tzld/videoVector/controller/XxlJobController.java

@@ -0,0 +1,24 @@
+package com.tzld.videoVector.controller;
+
+import com.tzld.videoVector.common.base.CommonResponse;
+import com.tzld.videoVector.job.*;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/job")
+public class XxlJobController {
+
+    @Autowired
+    private SpiderJob spiderJob;
+
+    @GetMapping("/spiderTaskJob")
+    public CommonResponse<Void> spiderTaskJob() {
+        spiderJob.spiderTaskJob(null);
+        return CommonResponse.success();
+    }
+
+
+}

+ 40 - 0
server/src/main/java/com/tzld/videoVector/filter/CustomFilter.java

@@ -0,0 +1,40 @@
+package com.tzld.videoVector.filter;
+
+import com.tzld.videoVector.aop.LogRequestAop;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.slf4j.MDC;
+import org.springframework.core.annotation.Order;
+
+import javax.servlet.*;
+import javax.servlet.annotation.WebFilter;
+import java.io.IOException;
+import java.util.UUID;
+
+/**
+ * auth filter
+ *
+ */
+@Order(value = 1)
+@WebFilter(filterName = "customFilter", urlPatterns = {"/*"})
+public class CustomFilter implements Filter {
+    private static final Logger log = LoggerFactory.getLogger(CustomFilter.class);
+
+    @Override
+    public void init(FilterConfig filterConfig) {
+        log.info("customFilter init");
+    }
+
+    @Override
+    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
+            throws IOException, ServletException {
+        MDC.put(LogRequestAop.LOG_TRACE_ID, UUID.randomUUID().toString().replaceAll("-", ""));
+        filterChain.doFilter(servletRequest, servletResponse);
+        MDC.remove(LogRequestAop.LOG_TRACE_ID);
+    }
+
+    @Override
+    public void destroy() {
+        log.info("customFilter destroy");
+    }
+}

+ 57 - 0
server/src/main/resources/application-dev.yml

@@ -0,0 +1,57 @@
+server:
+  port: 8080
+
+spring:
+  datasource:
+    videoVector:
+      driver-class-name: com.mysql.jdbc.Driver
+      jdbc-url: jdbc:mysql://rm-bp13g3ra2f59q49xs.mysql.rds.aliyuncs.com:3306/ai_supply_test?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false
+      username: wqsd
+      password: wqsd@2025
+      type: com.zaxxer.hikari.HikariDataSource
+      hikari:
+        minimum-idle: 10
+        maximum-pool-size: 20
+        connection-test-query: SELECT 1
+
+  redis:
+    host: r-bp1ps6my7lzg8rdhwxpd.redis.rds.aliyuncs.com
+    port: 6379
+    password: Wqsd@2019
+
+xxl:
+  job:
+    admin:
+      addresses: http://xxl-job-test-internal.piaoquantv.com/xxl-job-admin
+
+aliyun:
+  log:
+    endpoint: cn-hangzhou.log.aliyuncs.com
+    accessKeyId: LTAIP6x1l3DXfSxm
+    accessKeySecret: KbTaM9ars4OX3PMS6Xm7rtxGr1FLon
+    project: video-vector-server-test
+    requestLogstore: request-log
+    logstore:
+      info: info-log
+      error: error-log
+
+apollo:
+  meta: http://devapolloconfig-internal.piaoquantv.com
+
+feign:
+  client:
+    config:
+      default:  # 默认 Feign 客户端配置
+        logger-level: NONE  # 日志级别(NONE/BASE/HEADERS/FULL)
+        connect-timeout: 20000  # 连接超时时间(毫秒)
+        read-timeout: 20000  # 读取超时时间(毫秒)
+
+eureka:
+  client:
+    service-url:  # Eureka 服务注册中心地址
+      defaultZone: http://deveureka-internal.piaoquantv.com/eureka/  # 注册中心 URL
+
+danger:
+  face:
+    recognize:
+      host: http://47.111.230.160:5000

+ 53 - 0
server/src/main/resources/application-prod.yml

@@ -0,0 +1,53 @@
+server:
+  port: 8080
+
+spring:
+  datasource:
+    videoVector:
+      driver-class-name: com.mysql.jdbc.Driver
+      jdbc-url: jdbc:mysql://rm-bp13g3ra2f59q49xs.mysql.rds.aliyuncs.com:3306/ai_supply?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false
+      username: wqsd
+      password: wqsd@2025
+      type: com.zaxxer.hikari.HikariDataSource
+      hikari:
+        minimum-idle: 10
+        maximum-pool-size: 20
+        connection-test-query: SELECT 1
+
+  redis:
+    host: r-bp1rbmomeunfo3b3vppd.redis.rds.aliyuncs.com
+    port: 6379
+    password: wqsd@2025
+
+xxl:
+  job:
+    admin:
+      addresses: http://xxl-job-internal.piaoquantv.com/xxl-job-admin
+
+aliyun:
+  log:
+    endpoint: cn-hangzhou.log.aliyuncs.com
+    accessKeyId: LTAIP6x1l3DXfSxm
+    accessKeySecret: KbTaM9ars4OX3PMS6Xm7rtxGr1FLon
+    project: video-vector-server-prod
+    requestLogstore: request-log
+    logstore:
+      info: info-log
+      error: error-log
+
+apollo:
+  meta: http://apolloconfig-internal.piaoquantv.com
+
+feign:
+  client:
+    config:
+      default:  # 默认 Feign 客户端配置
+        logger-level: NONE  # 日志级别(NONE/BASE/HEADERS/FULL)
+        connect-timeout: 20000  # 连接超时时间(毫秒)
+        read-timeout: 20000  # 读取超时时间(毫秒)
+
+eureka:
+  client:
+    service-url:  # Eureka 服务注册中心地址
+      defaultZone: http://eureka-internal.piaoquantv.com/eureka/  # 注册中心 URL
+

+ 52 - 0
server/src/main/resources/application-test.yml

@@ -0,0 +1,52 @@
+server:
+  port: 8080
+
+spring:
+  datasource:
+    videoVector:
+      driver-class-name: com.mysql.jdbc.Driver
+      jdbc-url: jdbc:mysql://rm-bp13g3ra2f59q49xs.mysql.rds.aliyuncs.com:3306/ai_supply_test?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false
+      username: wqsd
+      password: wqsd@2025
+      type: com.zaxxer.hikari.HikariDataSource
+      hikari:
+        minimum-idle: 10
+        maximum-pool-size: 20
+        connection-test-query: SELECT 1
+
+  redis:
+    host: r-bp1ps6my7lzg8rdhwxpd.redis.rds.aliyuncs.com
+    port: 6379
+    password: Wqsd@2019
+
+xxl:
+  job:
+    admin:
+      addresses: http://xxl-job-test-internal.piaoquantv.com/xxl-job-admin
+
+aliyun:
+  log:
+    endpoint: cn-hangzhou.log.aliyuncs.com
+    accessKeyId: LTAIP6x1l3DXfSxm
+    accessKeySecret: KbTaM9ars4OX3PMS6Xm7rtxGr1FLon
+    project: video-vector-server-test
+    requestLogstore: request-log
+    logstore:
+      info: info-log
+      error: error-log
+
+apollo:
+  meta: http://testapolloconfig-internal.piaoquantv.com
+
+feign:
+  client:
+    config:
+      default:  # 默认 Feign 客户端配置
+        logger-level: NONE  # 日志级别(NONE/BASE/HEADERS/FULL)
+        connect-timeout: 20000  # 连接超时时间(毫秒)
+        read-timeout: 20000  # 读取超时时间(毫秒)
+
+eureka:
+  client:
+    service-url:  # Eureka 服务注册中心地址
+      defaultZone: http://testeureka-internal.piaoquantv.com/eureka/  # 注册中心 URL

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

@@ -0,0 +1,100 @@
+spring:
+  profiles:
+    active: dev
+  application:
+    name: video-vector-server
+
+server:
+  tomcat:
+    threads:
+      max: 1000
+    uri-encoding: UTF-8
+    accept-count: 1000
+    connection-timeout: 30000
+  servlet:
+    context-path: /videoVector
+    session:
+      timeout: 60
+pagehelper:
+  helper-dialect: mysql
+
+mybatis:
+  mapper-locations: classpath:mapper/**/*.xml
+  configuration:
+    use-generated-keys: true
+    map-underscore-to-camel-case: true
+    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl  # 直接输出到控制台(简单调试用)
+
+app:
+  id: video-vector-server  # 应用标识
+  build:
+    time: @mavenBuildTime@
+
+xxl:
+  job:
+    accessToken:
+    executor:
+      appname: ${spring.application.name}
+      address:
+      ip:
+      port: 9999
+      logpath: /datalog/weblog/xxl-job/jobhandler
+      logretentiondays: 7
+
+apollo:
+  bootstrap:
+    enabled: true  # 启用 Apollo 配置预加载
+    namespaces: application  # 指定加载的命名空间
+  cacheDir: /datalog/apollo-cache-dir  # Apollo 本地缓存目录
+
+eureka:
+  instance:
+    prefer-ip-address: true  # 优先使用 IP 地址注册
+    instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}  # 实例 ID 格式
+    lease-renewal-interval-in-seconds: 30  # 心跳续约间隔(秒)
+    lease-expiration-duration-in-seconds: 90  # 心跳过期时间(秒)
+  client:
+    registry-fetch-interval-seconds: 5  # 拉取服务注册列表间隔(秒)
+
+logging:
+  level:
+    # 替换为你的 Mapper 接口所在包路径(根据错误日志中的包名调整)
+    com.tzld.videoVector.dao.mapper.videoVector.spider: DEBUG  # 输出该包下所有 Mapper 的 SQL 日志
+    # 可选:HikariCP 连接池日志(调试连接问题时开启)
+    com.zaxxer.hikari: INFO  # 连接池基本信息(避免 DEBUG 级别日志过多)
+
+oss:
+  videoVector:
+    accessKey: LTAIP6x1l3DXfSxm
+    secretKey: KbTaM9ars4OX3PMS6Xm7rtxGr1FLon
+    ossEndPoint: oss-cn-hangzhou.aliyuncs.com
+    videoEndPoint: slicevideo.yishihui.com
+    expiration: 3600
+    projectName: videoVector
+    cdnDomain: http://rescdn.yishihui.com/
+    imgDomain: http://rescdn.yishihui.com/
+    videoDomain: http://rescdn.yishihui.com/
+    lvvideoDomain: https://lvupload.piaoquantv.com/
+    pubBucket: public:art-pubbucket,publicVideo:art-pubbucket
+    priBucket: private:art-pribucket,privateVideo:art-privideo,privateVideoIn:art-privideo-in
+    priEndPoint: pricdn.yishihui.com
+    needPress: true
+    internal:
+      endPoint: oss-cn-hangzhou.aliyuncs.com
+  longvideo:
+    video:
+      sts:
+        endpoint: sts.cn-hangzhou.aliyuncs.com
+        accessKeyId: LTAIfZYdxeQpq3YI
+        accessKeySecret: 1yISVWe5Gws2VAdTMc9XWIHpjPd7ja
+        roleArn: acs:ram::1894469520484605:role/oss-sts
+        roleSessionName: session-name
+  accelerate:
+    upload:
+      domain: https://ossaccelerateupload.piaoquantv.com/
+  video:
+    bucket: art-pubbucket
+
+cdn:
+  upload:
+    domain: https://weappupload.piaoquantv.com/

+ 166 - 0
server/src/main/resources/logback-spring.xml

@@ -0,0 +1,166 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出 -->
+<!-- scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true -->
+<!-- scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。 -->
+<!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->
+<configuration scan="true" scanPeriod="10 seconds">
+    <!--<include resource="org/springframework/boot/logging/logback/base.xml"/>-->
+
+    <contextName>logback</contextName>
+    <!-- name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义变量后,可以使“${}”来使用变量。 -->
+    <!-- <property name="LOG_PATH"  value="${logging.file.path}" />-->
+
+    <springProperty name="LOG_PATH" source="logging.file.path"/>
+    <springProperty name="ALIYUN_LOG_ENDPOINT" source="aliyun.log.endpoint"/>
+    <springProperty name="ALIYUN_LOG_ACCESSKEYID" source="aliyun.log.accessKeyId"/>
+    <springProperty name="ALIYUN_LOG_ACCESSKEYSECRET" source="aliyun.log.accessKeySecret"/>
+    <springProperty name="ALIYUN_LOG_PROJECT" source="aliyun.log.project"/>
+    <springProperty name="ALIYUN_LOG_LOGSTORE_INFO" source="aliyun.log.logstore.info"/>
+    <springProperty name="ALIYUN_LOG_LOGSTORE_ERROR" source="aliyun.log.logstore.error"/>
+
+    <!-- 彩色日志 -->
+    <!-- 彩色日志依赖的渲染类 -->
+    <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/>
+    <conversionRule conversionWord="wex"
+                    converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>
+    <conversionRule conversionWord="wEx"
+                    converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
+    <!-- 彩色日志格式 -->
+    <property name="CONSOLE_LOG_PATTERN"
+              value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr([%X{logTraceId}]){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
+    <!--<property name="CONSOLE_LOG_PATTERN"-->
+    <!--          value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr([%X{√logTraceId}]){magenta} %clr(-&#45;&#45;){faint} %clr([%15.15t]){faint} %clr(at %class.%method){cyan} \\(%file:%line\\) %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>-->
+
+    <!--输出到控制台-->
+    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
+        <!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息-->
+        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
+            <level>debug</level>
+        </filter>
+        <encoder>
+            <Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
+            <!-- 设置字符集 -->
+            <charset>UTF-8</charset>
+        </encoder>
+    </appender>
+
+
+    <appender name="ALIYUN_LOG_INFO" class="com.tzld.videoVector.common.LoghubAppender">
+        <endpoint>${ALIYUN_LOG_ENDPOINT}</endpoint>
+        <accessKeyId>${ALIYUN_LOG_ACCESSKEYID}</accessKeyId>
+        <accessKeySecret>${ALIYUN_LOG_ACCESSKEYSECRET}</accessKeySecret>
+        <project>${ALIYUN_LOG_PROJECT}</project>
+        <logStore>${ALIYUN_LOG_LOGSTORE_INFO}</logStore>
+
+        <totalSizeInBytes>104857600</totalSizeInBytes>
+        <maxBlockMs>0</maxBlockMs>
+        <ioThreadCount>8</ioThreadCount>
+        <batchSizeThresholdInBytes>524288</batchSizeThresholdInBytes>
+        <batchCountThreshold>4096</batchCountThreshold>
+        <lingerMs>2000</lingerMs>
+        <retries>10</retries>
+        <baseRetryBackoffMs>100</baseRetryBackoffMs>
+        <maxRetryBackoffMs>50000</maxRetryBackoffMs>
+
+        <encoder>
+            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%X{logTraceId}] %logger{50} [%L] - %msg%n</pattern>
+            <charset>UTF-8</charset>
+        </encoder>
+
+        <timeFormat>yyyy-MM-dd'T'HH:mmZ</timeFormat>
+        <timeZone>Asia/Shanghai</timeZone>
+
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <level>INFO</level>
+        </filter>
+
+        <mdcFields>logTraceId</mdcFields>
+    </appender>
+
+    <appender name="ALIYUN_LOG_ERROR" class="com.tzld.videoVector.common.LoghubAppender">
+        <endpoint>${ALIYUN_LOG_ENDPOINT}</endpoint>
+        <accessKeyId>${ALIYUN_LOG_ACCESSKEYID}</accessKeyId>
+        <accessKeySecret>${ALIYUN_LOG_ACCESSKEYSECRET}</accessKeySecret>
+        <project>${ALIYUN_LOG_PROJECT}</project>
+        <logStore>${ALIYUN_LOG_LOGSTORE_ERROR}</logStore>
+
+        <totalSizeInBytes>104857600</totalSizeInBytes>
+        <maxBlockMs>0</maxBlockMs>
+        <ioThreadCount>8</ioThreadCount>
+        <batchSizeThresholdInBytes>524288</batchSizeThresholdInBytes>
+        <batchCountThreshold>4096</batchCountThreshold>
+        <lingerMs>2000</lingerMs>
+        <retries>10</retries>
+        <baseRetryBackoffMs>100</baseRetryBackoffMs>
+        <maxRetryBackoffMs>50000</maxRetryBackoffMs>
+
+        <encoder>
+            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%X{logTraceId}] %logger{50} [%L] - %msg%n</pattern>
+            <charset>UTF-8</charset>
+        </encoder>
+
+        <timeFormat>yyyy-MM-dd'T'HH:mmZ</timeFormat>
+        <timeZone>Asia/Shanghai</timeZone>
+
+        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
+            <level>ERROR</level>
+        </filter>
+
+        <mdcFields>logTraceId</mdcFields>
+    </appender>
+
+    <!--
+        <logger>用来设置某一个包或者具体的某一个类的日志打印级别、以及指定<appender>。
+        <logger>仅有一个name属性,一个可选的level和一个可选的addtivity属性。
+        name:用来指定受此logger约束的某一个包或者具体的某一个类。
+        level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
+              还有一个特俗值INHERITED或者同义词NULL,代表强制执行上级的级别。
+              如果未设置此属性,那么当前logger将会继承上级的级别。
+        addtivity:是否向上级logger传递打印信息。默认是true。
+    -->
+    <!--<logger name="org.springframework.web" level="info"/>-->
+    <!--<logger name="org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor" level="INFO"/>-->
+
+    <!--
+        使用mybatis的时候,sql语句是debug下才会打印,而这里我们只配置了info,所以想要查看sql语句的话,有以下两种操作:
+        第一种把<root level="info">改成<root level="DEBUG">这样就会打印sql,不过这样日志那边会出现很多其他消息
+        第二种就是单独给dao下目录配置 <logger> 指定 level 属性为 debug,这样配置sql语句会打印,其他还是正常info级别:
+     -->
+
+    <!--<springProfile name="dev">-->
+    <!--    <logger name="com.tzld.piaoquan.ad" level="debug"/>-->
+    <!--    <logger name="com.ctrip.framework.apollo.internals.RemoteConfigLongPollService" level="error"/>-->
+    <!--</springProfile>-->
+    <!--<springProfile name="test">-->
+    <!--    <logger name="com.tzld.piaoquan.ad" level="info"/>-->
+    <!--</springProfile>-->
+    <!--<springProfile name="pre">-->
+    <!--    <logger name="com.tzld.piaoquan.ad" level="info"/>-->
+    <!--</springProfile>-->
+    <!--<springProfile name="stress">-->
+    <!--    <logger name="com.tzld.piaoquan.ad" level="info"/>-->
+    <!--</springProfile>-->
+    <!--<springProfile name="prod">-->
+    <!--    <logger name="com.tzld.piaoquan.ad" level="info"/>-->
+    <!--</springProfile>-->
+    <!--<logger name="com.netflix.discovery.shared.resolver.aws.ConfigClusterResolver" level="warn"/>-->
+
+    <!-- 可用来获取 StatusManager 中的状态 -->
+    <!--<statusListener class="ch.qos.logback.core.status.OnConsoleStatusListener"/>-->
+    <!-- 解决 aliyun loghub debug模式下循环发送的问题 -->
+    <logger name="org.apache.http.impl.conn.Wire" level="WARN"/>
+    <!--aliyun loghub 为了防止进程退出时,内存中的数据丢失,请加上此选项-->
+    <shutdownHook class="ch.qos.logback.core.hook.DelayingShutdownHook"/>
+
+    <!--
+        root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性
+        level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
+        不能设置为INHERITED或者同义词NULL。默认是DEBUG
+        可以包含零个或多个元素,标识这个appender将会添加到这个logger。
+    -->
+    <root level="info">
+        <appender-ref ref="CONSOLE"/>
+        <appender-ref ref="ALIYUN_LOG_INFO"/>
+        <appender-ref ref="ALIYUN_LOG_ERROR"/>
+    </root>
+</configuration>

+ 42 - 0
server/src/test/java/FeishuTest.java

@@ -0,0 +1,42 @@
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.tzld.videoVector.Application;
+import com.tzld.videoVector.util.feishu.FeiShu;
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.data.util.Pair;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.client.RestTemplate;
+
+@SpringBootTest(classes = Application.class)
+@Slf4j
+public class FeishuTest {
+
+    @Test
+    public void test() {
+        String sheetToken = "ZSIVw4IQiiuYVwkBrC9c1uMpnKc";
+        String sheetId = "ecec8a";
+        int rowNum = 2;
+        Integer startRowIndex = 2;
+        Pair<String, Integer> token = FeiShu.requestAccessToken();
+        RestTemplate restTemplate = new RestTemplate();
+        HttpHeaders httpHeaders = new HttpHeaders();
+        httpHeaders.add("Authorization", "Bearer " + token.getFirst());
+        HttpEntity<Object> queryEntity = new HttpEntity<>(httpHeaders);
+        int deleteRowNum = rowNum < 20 ? startRowIndex + 20 * rowNum : startRowIndex + (rowNum * 2);
+        ResponseEntity<String> queryResponseEntity = restTemplate.exchange(
+                String.format("https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/%s/values/%s!A"
+                        + startRowIndex + ":A" + deleteRowNum, sheetToken, sheetId),
+                HttpMethod.GET, queryEntity, String.class);
+        JSONArray values = JSON.parseObject(queryResponseEntity.getBody())
+                .getJSONObject("data")
+                .getJSONObject("valueRange")
+                .getJSONArray("values");
+        System.out.println(JSONObject.toJSONString(values));
+    }
+}

+ 126 - 0
server/src/test/java/GeminiTest.java

@@ -0,0 +1,126 @@
+import com.alibaba.fastjson.JSONObject;
+import com.tzld.videoVector.Application;
+import com.tzld.videoVector.api.google.GeminiApiService;
+import com.tzld.videoVector.api.google.ModelEnum;
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+
+import java.io.IOException;
+
+@SpringBootTest(classes = Application.class)
+@Slf4j
+public class GeminiTest {
+    @Autowired
+    private GeminiApiService geminiApiService;
+
+//    @Value("title.analyse.systemprompt:")
+//    private String systemPrompt;
+
+    @Test
+    public void testGemini() {
+        String systemPrompt = "# 角色\n" +
+                "你是一名严格的,为中国50岁以上中老年群体做热点内容筛选的内容专家。你非常严格的判断内容是否“适老”,你需要逐个分析给到的标题信息,根据目标用户画像的特征,剔除所有属于城市年轻群体、中产阶级焦虑、高认知门槛、语义模糊或与老年人生活脱节的“噪音”,保留符合「中国 50 岁以上中老年人」用户画像的内容。\n" +
+                "\n" +
+                "# 一、基础定义(严格遵守,不可修改)\n" +
+                "## 用户画像:中国50岁以上老年人\n" +
+                "1. **认知特点**: 追求“确定性”和“安全感”。偏好简单直白的内容,拒绝烧脑、逻辑复杂或需要推理的内容。拒绝一切“盲盒式”标题(如“这件事千万别做”但没说什么事)。不关注新事物,不关注抽象的宏观经济指标、复杂的金融博弈、枯燥的行政程序\n" +
+                "2. **文化背景**: 成长于上世纪50~70年代,传统观念根深蒂固。对网络梗、亚文化、职场黑话、微短剧逻辑不敏感甚至反感。深受儒家文化影响,拥有强烈的孝道观念和集体主义倾向,对投资及房产等与年轻人生活相关度高的信息不感兴趣, 处于“安享期”而非“奋斗期”。关注“保命”(三高、心脏、防骗)而非“塑形”(减肥、发际线);关注“存量财产安全”而非“增量资产博弈”。更倾向于追求身心安宁及正能量内容,不喜欢高强度、信息量大的娱乐内容。\n" +
+                "3. **情感需求**: “安逸”、“从容”、“被尊重”。倾向于追求身心安宁、正能量、民族自豪感。反感贩卖焦虑、激烈的矛盾冲突、血腥暴力或过于悲惨的负面新闻。偏好娱乐放松、激发民族自豪感的国家大事、与中国相关的重大国际形式、民生生活、弘扬社会正能量、家庭生活、传统文化、时事政务、同龄人出镜的内容,需要被“认同感”及“群体认同感”,偏好接地气、贴近生活、贴近自身的内容,叙事风格\n" +
+                "4. **场景偏好**: 菜市场、公园、家庭、医院、老友聚会等怀旧场景。排斥写字楼、夜店、高端滑雪场、极限运动场所。\n" +
+                "\n" +
+                "## 适老品类库(白名单)\n" +
+                "只有核心内容属于以下分类,才具备“品类适老系数=1”的基础条件:\n" +
+                "*   **国家力量/统一**: 阅兵、基建狂魔、外交胜利、撤侨、领土主权、两岸统一(不含晦涩的地缘政治分析),以非具体正能量人物、非科学、技术为主要内容;反映中国人文、文化、基建、人民生活、国内外对比中强大的内容\n" +
+                "*   **知识科普**:非生活技巧、非科技自然 的 文化、历史、人文、健康等社科知识类科普视\n" +
+                "*   **惠民/民生政策**: 养老金调整、医保报销、现金支付保障、菜篮子物价、天气预警(需具体利民,非枯燥公文)。\n" +
+                "*   **人财诈骗/防骗**: 电诈案例、新型毒品伪装、保健品骗局(极高优先级)。\n" +
+                "*   **老年健康**: 三高管理、心脑血管、养生食疗、长寿知识(**严格剔除**减肥、塑形、医美、脱发焦虑)。\n" +
+                "*   **怀念时光**: 70年代及以前的老照片、老电影切片(非影评)、经典红歌。\n" +
+                "*   **家庭/亲子**: 隔辈亲、孝道故事、家庭邻里互助(**严格剔除**婆媳恶斗、剧烈伦理冲突)。\n" +
+                "*   **传统文化/习俗**: 节气、民俗、戏曲、国学、非遗。\n" +
+                "*   **正能量/社会风气**: 见义勇为、拾金不昧、反腐倡廉、平凡人的善举,以具体中国当代正能量人物为主要描述对象的内容\n" +
+                "*   **惊奇/罕见画面**: 自然奇观、动物趣闻(**严格剔除**血腥、恐怖、猎奇阴暗面)。\n" +
+                "\n" +
+                "# 二、红灯拦截标准(触犯即死,优先级最高)\n" +
+                "**不仅要检查负面词汇,更要检查“逻辑不适老”,凡触犯以下任意一条,分值直接归零,:**\n" +
+                "\n" +
+                "1.  **信源模糊/标题党拦截**:\n" +
+                "    *   标题缺乏具体的**人名、地名、机构名**,无法作为搜索关键词的(如“最爱发钱的老板招工了”、“我看这地儿不错”)。\n" +
+                "    *   主语不明、指代不清的(如“注意!这东西不能吃”、“他竟然这样做”)。\n" +
+                "    *   个人Vlog式、无公共信息价值的感悟(如“如果我在冬天失业了”)。\n" +
+                "2.  **生命阶段错位拦截**:\n" +
+                "    *   **职场焦虑**:涉及“月薪、大厂、内卷、求职(非银发专岗)、降薪、裁员、年终奖”。\n" +
+                "    *   **中年健康焦虑**:涉及“减肥、瘦身、代谢变慢、发际线、抗初老、精力管理”。\n" +
+                "    *   **高危活动**:涉及“滑雪、潜水、马拉松、蹦极、赛车”等不适合老年人生理机能的活动(即使是免费票也要拦截)。\n" +
+                "3.  **低质娱乐/亚文化拦截**:\n" +
+                "    *   **微短剧/网剧**:标题含“微短剧、网剧、霸总、逆袭、重生”等,此类内容多为虚构浮夸且含诱导付费。\n" +
+                "    *   **网络热梗**:含“破防、yyds、绝绝子、CP感、谐音梗营销”,网络热梗段子、恶搞视频、二次元的游戏黑话等\n" +
+                "    *   **专业/小众体育**:NBA交易、欧洲足球战术、球星转会费、网球/斯诺克小众赛事(除非是**国家队/为国争光**)。\n" +
+                "    *   **长辈无关娱乐圈**:年轻流量偶像八卦、饭圈互撕、国外网红动态。\n" +
+                "4.  **金融与商业噪音拦截**:\n" +
+                "    *   涉及“板块、指数、美联储、加息、IPO、资产配置、套利”的金融博弈。\n" +
+                "    *   涉及“金银价格剧烈波动分析”(如“K线、点位、抄底”),只保留单纯的实物金价涨跌。\n" +
+                "    *   明显的商业软文、带货广告、陌生APP公测。\n" +
+                "5.  **负面与恐慌拦截**:\n" +
+                "    *   过于血腥、暴力、违背伦理的案件细节(如“强奸、分尸、虐待”)。\n" +
+                "    *   纯粹贩卖焦虑而无解决方案的内容。\n" +
+                "6.行政公文拦截:没有具体利民事件的,一律不通过。\n" +
+                "7.宏观自豪感拦截: 只有国家领导人会面而无具体实惠或震撼性视觉成果(如大型阅兵、卫星发射成功)的,一律不通过。\n" +
+                "\n" +
+                "# 三、核心评分与计算逻辑\n" +
+                "## 第一步:红灯熔断审查\n" +
+                "检查标题是否触犯【二、红灯拦截标准】。\n" +
+                "*   **若触犯任意一条**:最终综合得分直接为 0.00,判定为“不通过”,reason中明确指出触犯了哪条红灯。\n" +
+                "*   **若未触犯**:进入第二步。\n" +
+                "\n" +
+                "## 第二步:用户画像维度打分(0.00 - 1.00)\n" +
+                "请根据【一、用户画像】对以下四个维度进行最严格的独立打分,如果信息模糊则全部用最低分值判断:\n" +
+                "1.  **S1 认知特点 (权重 20%)**:对应用户画像进行判断(模糊不清/专业术语/黑话=0分)\n" +
+                "2.  **S2 文化背景 (权重 30%)**:对应用户画像进行判断(个人主义/挑战传统/崇洋媚外=0分)\n" +
+                "3.  **S3 情感需求 (权重 30%)**:对应用户画像进行判断(焦虑/恐惧/悲惨/激烈冲突=0分)\n" +
+                "4.  **S4 场景偏好 (权重 20%)**:对应用户画像进行判断(CBD/夜店/极限运动/国外陌生场景=0分)\n" +
+                "\n" +
+                "## 第三步:短板效应与画像分计算\n" +
+                "*   **公式**:\n" +
+                "    IF (S1 < 0.6 OR S2 < 0.6 OR S3 < 0.6):\n" +
+                "        Persona_Score = Min(S1, S2, S3)  (任意核心维度不及格,则整体不及格)\n" +
+                "    ELSE:\n" +
+                "        Persona_Score = (S1 * 0.2) + (S2 * 0.3) + (S3 * 0.3) + (S4 * 0.2)\n" +
+                "\n" +
+                "## 第四步:最终判定\n" +
+                "*   **品类系数 K**:\n" +
+                "    *   内容核心属于白名单品类:K = 1\n" +
+                "    *   内容核心不属于白名单(如科技、游戏、职场、二次元等):K = 0\n" +
+                "*   **最终综合得分** = Persona_Score * K\n" +
+                "\n" +
+                "\n" +
+                "# 四、输出格式\n" +
+                "仅输出 JSON 格式,无多余解释。\n" +
+                "\n" +
+                "{\n" +
+                "  \"标题内容\": \"原标题\",\n" +
+                "  \"最终综合得分\": 0.00,\n" +
+                "  \"画像维度得分\": {\n" +
+                "    \"S1_认知\": 0.00,\n" +
+                "    \"S2_文化\": 0.00,\n" +
+                "    \"S3_情感\": 0.00,\n" +
+                "    \"S4_场景\": 0.00\n" +
+                "  },\n" +
+                "  \"用户画像匹配分(Persona_Score)\": 0.00,\n" +
+                "  \"品类系数(K)\": 1或0,\n" +
+                "  \"匹配品类\": \"最匹配的白名单分类或'无'\",\n" +
+                "  \"reason\": \"简要说明理由,若拦截需指出具体原因(如:信源模糊、生命阶段错位、红灯拦截等)\"\n" +
+                "}";
+        String content = "郑建成:中国现代文学评论中的金岳霖";
+
+        try {
+            JSONObject jsonObject = geminiApiService.callGemini(ModelEnum.GEMINI_3_FLASH_PREVIEW, systemPrompt,content);
+            log.info(jsonObject.toString());
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+
+    }
+}