44 Commits 2cf02e0ac9 ... 400288eb28

Author SHA1 Message Date
  wangyunpeng 400288eb28 Merge branch 'refs/heads/master' into test 6 days ago
  wangyunpeng 0fbcf251de 公众号自动回复 裂变率改质量分 保留两位小数 6 days ago
  wangyunpeng a905531864 公众号自动回复 裂变率改质量分 6 days ago
  wangyunpeng f5cfea904a 自动回复数据清除 1 week ago
  wangyunpeng 8b54009c60 videoMultiCoverTitle retry 1 week ago
  wangyunpeng 2fceb2288f pageUrl上报标题封面 仅保留id 1 week ago
  wangyunpeng 432e6dc04d add log 1 week ago
  wangyunpeng e0f961bf2c pageUrl上报标题封面 1 week ago
  wangyunpeng 9713ca0956 pageUrl上报标题封面 1 week ago
  wangyunpeng 21795860d8 set rootSourceId 1 week ago
  wangyunpeng 4efe34e911 Merge branch '20260105-wyp-videoPath' of Server/growth-manager into master 1 week ago
  wangyunpeng 40abfaee04 pageUrl上报标题封面 1 week ago
  wangyunpeng 4a43fb3143 自动获取投流号增加开关 1 week ago
  wangyunpeng 241d25dade 自动替换的高消耗素材的小程序 自动获取服务号 2 weeks ago
  wangyunpeng 5fed6502dd 自动替换的高消耗素材的小程序 默认开启 2 weeks ago
  wangyunpeng 8a146ed4e7 自动替换的高消耗素材的小程序 关键词提取prompt修改 2 weeks ago
  wangyunpeng 28ee913325 自动替换的高消耗素材的小程序 兼容服务号 2 weeks ago
  wangyunpeng 23067b0704 自动替换的高消耗素材的小程序 兼容服务号 2 weeks ago
  wangyunpeng 450d5e5fb4 title dedumplicate 2 weeks ago
  wangyunpeng e69822121d pool size 2 weeks ago
  wangyunpeng 1728ed7372 Merge branch '20251225-wyp-GzhReplyVideoRefresh' of Server/growth-manager into master 2 weeks ago
  wangyunpeng f20a821a87 GzhReplyVideoRefreshJob title cover 2 weeks ago
  wangyunpeng 858b924a46 Merge branch 'master' into 20251225-wyp-GzhReplyVideoRefresh 2 weeks ago
  wangyunpeng 18ca0e7d0b GzhReplyVideoRefreshJob title cover 2 weeks ago
  wangyunpeng a75884f758 gemini timeout 2 weeks ago
  wangyunpeng 9fb79f66fe timeout 2 weeks ago
  wangyunpeng 559f1ff7ba add log 2 weeks ago
  wangyunpeng 6fa2d0e627 Merge branch '20251225-wyp-GzhReplyVideoRefresh' of Server/growth-manager into master 2 weeks ago
  wangyunpeng 42c12f3fbc 自动替换的高消耗素材的小程序 2 weeks ago
  wangyunpeng 39b5fd513a fix 2 weeks ago
  wangyunpeng c77a48c9b4 fix 3 weeks ago
  wangyunpeng a7fd6c4604 deepseek 3 weeks ago
  wangyunpeng 1d32379333 getCgiReplyBucketData withCustomTitleCover 3 weeks ago
  wangyunpeng a76ed29013 getCgiReplyBucketData withCustomTitleCover 3 weeks ago
  wangyunpeng 739ea15758 default watermark 3 weeks ago
  wangyunpeng 19e228c020 deleteVideo retry 1 month ago
  wangyunpeng 26671ac5ae cdnUploadImgLink retry 1 month ago
  wangyunpeng 5d90e70d71 WeComThirdPartyApiClient timeout 1 month ago
  wangyunpeng 4470d9a10a fix 1 month ago
  wangyunpeng 01e72dcd8f fix 1 month ago
  wangyunpeng f47ac0093f fix 1 month ago
  wangyunpeng 76d78aa4df 企微三方平台自动登录 1 month ago
  wangyunpeng 744a22e7fa fix 1 month ago
  wangyunpeng f33057592f Merge branch '20251211-wyp-gzhAutoReplyVideo' of Server/growth-manager into master 1 month ago
52 changed files with 2177 additions and 173 deletions
  1. 2 0
      api-module/src/main/java/com/tzld/piaoquan/api/common/enums/ExceptionEnum.java
  2. 21 0
      api-module/src/main/java/com/tzld/piaoquan/api/component/AdApiService.java
  3. 48 0
      api-module/src/main/java/com/tzld/piaoquan/api/component/AigcApiService.java
  4. 93 0
      api-module/src/main/java/com/tzld/piaoquan/api/component/DeepSeekApiService.java
  5. 62 0
      api-module/src/main/java/com/tzld/piaoquan/api/component/ManagerApiService.java
  6. 23 17
      api-module/src/main/java/com/tzld/piaoquan/api/component/VideoApiService.java
  7. 9 1
      api-module/src/main/java/com/tzld/piaoquan/api/component/WeComThirdPartyApiClient.java
  8. 11 2
      api-module/src/main/java/com/tzld/piaoquan/api/controller/AccountDetailController.java
  9. 6 0
      api-module/src/main/java/com/tzld/piaoquan/api/controller/wecom/thirdpart/WeComThirdPartyController.java
  10. 2 0
      api-module/src/main/java/com/tzld/piaoquan/api/dao/mapper/GhDetailMapperExt.java
  11. 349 0
      api-module/src/main/java/com/tzld/piaoquan/api/job/GzhReplyVideoRefreshJob.java
  12. 122 72
      api-module/src/main/java/com/tzld/piaoquan/api/job/contentplatform/ContentPlatformDatastatJob.java
  13. 7 0
      api-module/src/main/java/com/tzld/piaoquan/api/job/contentplatform/ContentPlatformIllegalVideoJob.java
  14. 7 1
      api-module/src/main/java/com/tzld/piaoquan/api/job/wecom/WeComMessageDataJob.java
  15. 4 0
      api-module/src/main/java/com/tzld/piaoquan/api/job/wecom/thirdpart/WeComSendMsgJob.java
  16. 2 1
      api-module/src/main/java/com/tzld/piaoquan/api/job/wecom/thirdpart/WeComUserDetailJob.java
  17. 36 0
      api-module/src/main/java/com/tzld/piaoquan/api/model/bo/AdPutCreativeComponentCostData.java
  18. 28 0
      api-module/src/main/java/com/tzld/piaoquan/api/model/bo/GoogleLLMResult.java
  19. 39 0
      api-module/src/main/java/com/tzld/piaoquan/api/model/dto/AIOfficialApiResponse.java
  20. 11 0
      api-module/src/main/java/com/tzld/piaoquan/api/model/dto/AIResult.java
  21. 22 0
      api-module/src/main/java/com/tzld/piaoquan/api/model/po/GhDetailExt.java
  22. 120 0
      api-module/src/main/java/com/tzld/piaoquan/api/model/po/GhDetailExtExample.java
  23. 22 0
      api-module/src/main/java/com/tzld/piaoquan/api/model/po/contentplatform/ContentPlatformIllegalMsg.java
  24. 140 0
      api-module/src/main/java/com/tzld/piaoquan/api/model/po/contentplatform/ContentPlatformIllegalMsgExample.java
  25. 1 1
      api-module/src/main/java/com/tzld/piaoquan/api/model/vo/contentplatform/GzhAccountVideoDatastatItemExportVO.java
  26. 1 1
      api-module/src/main/java/com/tzld/piaoquan/api/model/vo/contentplatform/GzhDatastatItemExportVO.java
  27. 1 1
      api-module/src/main/java/com/tzld/piaoquan/api/model/vo/contentplatform/GzhDatastatItemVO.java
  28. 1 1
      api-module/src/main/java/com/tzld/piaoquan/api/model/vo/contentplatform/GzhTotalDatastatItemExportVO.java
  29. 5 0
      api-module/src/main/java/com/tzld/piaoquan/api/service/GhDetailService.java
  30. 84 0
      api-module/src/main/java/com/tzld/piaoquan/api/service/VideoMultiService.java
  31. 2 9
      api-module/src/main/java/com/tzld/piaoquan/api/service/contentplatform/impl/ContentPlatformNoticeServiceImpl.java
  32. 60 9
      api-module/src/main/java/com/tzld/piaoquan/api/service/contentplatform/impl/ContentPlatformPlanServiceImpl.java
  33. 65 7
      api-module/src/main/java/com/tzld/piaoquan/api/service/impl/GhDetailServiceImpl.java
  34. 67 5
      api-module/src/main/java/com/tzld/piaoquan/api/service/strategy/impl/BuckStrategyV1.java
  35. 81 13
      api-module/src/main/java/com/tzld/piaoquan/api/service/strategy/impl/ThirdPartyPushMessageStrategyV1.java
  36. 30 9
      api-module/src/main/java/com/tzld/piaoquan/api/service/strategy/impl/WeComPushMessageStrategyV1.java
  37. 2 0
      api-module/src/main/java/com/tzld/piaoquan/api/service/wecom/thirdparty/WeComThirdPartyService.java
  38. 31 7
      api-module/src/main/java/com/tzld/piaoquan/api/service/wecom/thirdparty/impl/WeComThirdPartyServiceImpl.java
  39. 31 0
      api-module/src/main/java/com/tzld/piaoquan/api/util/AliOssFileTool.java
  40. 14 0
      api-module/src/main/java/com/tzld/piaoquan/api/util/CdnUtil.java
  41. 40 7
      api-module/src/main/resources/mapper/GhDetailExtMapper.xml
  42. 14 3
      api-module/src/main/resources/mapper/GhDetailMapperExt.xml
  43. 38 5
      api-module/src/main/resources/mapper/contentplatform/ContentPlatformIllegalMsgMapper.xml
  44. 133 0
      api-module/src/test/java/com/tzld/piaoquan/api/GhDetailTest.java
  45. 104 0
      api-module/src/test/java/com/tzld/piaoquan/api/WeComThirdPartTest.java
  46. 1 1
      common-module/src/main/java/com/tzld/piaoquan/growth/common/config/HttpClientConfig.java
  47. 8 0
      common-module/src/main/java/com/tzld/piaoquan/growth/common/dao/mapper/ext/CgiReplyBucketDataMapperExt.java
  48. 50 0
      common-module/src/main/java/com/tzld/piaoquan/growth/common/utils/MapBuilder.java
  49. 4 0
      common-module/src/main/java/com/tzld/piaoquan/growth/common/utils/MessageUtil.java
  50. 102 0
      common-module/src/main/java/com/tzld/piaoquan/growth/common/utils/TitleSimilarCheckUtil.java
  51. 15 0
      common-module/src/main/resources/mapper/ext/CgiReplyBucketDataMapperExt.xml
  52. 6 0
      pom.xml

+ 2 - 0
api-module/src/main/java/com/tzld/piaoquan/api/common/enums/ExceptionEnum.java

@@ -64,6 +64,8 @@ public enum ExceptionEnum {
     VIDEO_GET_FAILED(6008, "视频获取失败"),
     VIDEO_MULTI_TITLE_SAVE_FAILED(6009, "视频多标题保存失败"),
     VIDEO_MULTI_COVER_SAVE_FAILED(6010, "视频多封面保存失败"),
+    VIDEO_MULTI_COVER_LIST_FAILED(6011, "视频多封面列表获取失败"),
+    VIDEO_MULTI_TITLE_LIST_FAILED(6012, "视频多标题列表获取失败"),
 
     // 用户收藏视频
     VIDEO_NOT_EXIST(7001, "视频不存在"),

+ 21 - 0
api-module/src/main/java/com/tzld/piaoquan/api/component/AdApiService.java

@@ -2,6 +2,7 @@ package com.tzld.piaoquan.api.component;
 
 import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
+import com.tzld.piaoquan.api.model.bo.AdPutCreativeComponentCostData;
 import com.tzld.piaoquan.api.model.bo.AdPutFlowRecordTencent;
 import com.tzld.piaoquan.growth.common.component.HttpPoolClient;
 import lombok.extern.slf4j.Slf4j;
@@ -52,4 +53,24 @@ public class AdApiService {
         }
         return new ArrayList<>();
     }
+
+    /**
+     * 获取投放广告创意花费
+     * @param dt 2025-12-23
+     * @return
+     */
+    public List<AdPutCreativeComponentCostData> getCreativeComponentsCost(String dt) {
+        String url = "https://api.piaoquantv.com/ad" + "/put/tencent/getCreativeComponentsCost?dt=" + dt;
+        try {
+            String post = httpPoolClient.get(url);
+            JSONObject res = JSONObject.parseObject(post);
+            JSONArray data = res.getJSONArray("data");
+            if (Objects.nonNull(data) && !data.isEmpty()) {
+                return data.toJavaList(AdPutCreativeComponentCostData.class);
+            }
+        } catch (Exception e) {
+            log.error("getPutFlowListTencent error", e);
+        }
+        return new ArrayList<>();
+    }
 }

+ 48 - 0
api-module/src/main/java/com/tzld/piaoquan/api/component/AigcApiService.java

@@ -2,10 +2,13 @@ package com.tzld.piaoquan.api.component;
 
 import com.alibaba.fastjson.JSONObject;
 import com.tzld.piaoquan.api.common.exception.CommonException;
+import com.tzld.piaoquan.api.model.bo.GoogleLLMResult;
 import com.tzld.piaoquan.api.model.vo.WxAccountDatastatVO;
 import com.tzld.piaoquan.growth.common.component.HttpPoolClient;
 import com.tzld.piaoquan.growth.common.utils.DateUtil;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Component;
@@ -291,4 +294,49 @@ public class AigcApiService {
         }
         return false;
     }
+
+    public String gpt(String model, String prompt, JSONObject responseFormat, List<String> imageList) {
+        if (StringUtils.isEmpty(model) || StringUtils.isEmpty(prompt)) {
+            return null;
+        }
+        String url = "http://aigc-api.cybertogether.net/aigc" + "/dev/test/gpt";
+        JSONObject params = new JSONObject();
+        params.put("model", model);
+        params.put("prompt", prompt);
+        if (Objects.nonNull(responseFormat)) {
+            params.put("responseFormat", responseFormat);
+        }
+        if (CollectionUtils.isNotEmpty(imageList)) {
+            params.put("imageList", imageList);
+        }
+
+        try {
+            String post = httpPoolClient.post(url, params.toJSONString());
+            JSONObject res = JSONObject.parseObject(post);
+            return res.getString("data");
+        } catch (Exception e) {
+            log.error("gpt error", e);
+        }
+        return null;
+    }
+
+    public GoogleLLMResult gemini(String model, String prompt, List<String> imageList) {
+        if (StringUtils.isEmpty(model) || StringUtils.isEmpty(prompt)) {
+            return null;
+        }
+        String url = "http://aigc-api.cybertogether.net/aigc" + "/infrastructure/gemini/requestWithMedia?model=" + model;
+        JSONObject params = new JSONObject();
+        params.put("prompt", prompt);
+        if (CollectionUtils.isNotEmpty(imageList)) {
+            params.put("mediaList", imageList);
+        }
+
+        try {
+            String post = httpPoolClient.post(url, params.toJSONString());
+            return JSONObject.parseObject(post, GoogleLLMResult.class);
+        } catch (Exception e) {
+            log.error("gemini error", e);
+        }
+        return null;
+    }
 }

+ 93 - 0
api-module/src/main/java/com/tzld/piaoquan/api/component/DeepSeekApiService.java

@@ -0,0 +1,93 @@
+package com.tzld.piaoquan.api.component;
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.tzld.piaoquan.api.model.dto.AIOfficialApiResponse;
+import com.tzld.piaoquan.api.model.dto.AIResult;
+import com.tzld.piaoquan.growth.common.utils.MapBuilder;
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.*;
+import org.apache.commons.collections.CollectionUtils;
+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 AIResult requestOfficialApi(String prompt, String model, Double temperature, Boolean isJSON) {
+        AIResult result = new AIResult();
+        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(0.3))
+                    .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("Accept", "application/json")
+                    .addHeader("Authorization", "Bearer sk-62d7b2c37f824735aa4985852c919c1f")
+                    .build();
+            Response response = client.newCall(request).execute();
+
+            String responseContent = response.body().string();
+            result.setResponseStr(responseContent);
+            log.info("deepseek api responseContent = {}", responseContent);
+            if (response.isSuccessful()) {
+                AIOfficialApiResponse obj = JSONObject.parseObject(responseContent, AIOfficialApiResponse.class);
+                if (CollectionUtils.isNotEmpty(obj.getChoices())) {
+                    result.setSuccess(true);
+                    result.setResponse(obj);
+                } else {
+                    result.setFailReason("response empty");
+                }
+            } 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;
+    }
+}

+ 62 - 0
api-module/src/main/java/com/tzld/piaoquan/api/component/ManagerApiService.java

@@ -1,5 +1,6 @@
 package com.tzld.piaoquan.api.component;
 
+import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
 import com.tzld.piaoquan.api.common.enums.ExceptionEnum;
 import com.tzld.piaoquan.api.common.exception.CommonException;
@@ -73,4 +74,65 @@ public class ManagerApiService {
         return res.getJSONObject("content");
     }
 
+    public JSONArray videoMultiCoverListV2(Long videoId) {
+        String url = managerApiHost + "/video/multiCover/listV2";
+        JSONObject res = null;
+        int retry = 0;
+        while (retry < 3) {
+            try {
+                JSONObject param = new JSONObject();
+                param.put("videoId", videoId);
+                param.put("range", "2h");
+                String post = httpPoolClient.post(url, param.toJSONString());
+                res = JSONObject.parseObject(post);
+                break;
+            } catch (Exception e) {
+                log.error("ManagerApiService videoMultiCoverListV2 error, videoId={} range={}",
+                        videoId, "2h", e);
+                retry++;
+            }
+        }
+        if (Objects.isNull(res)) {
+            throw new CommonException(ExceptionEnum.VIDEO_MULTI_COVER_LIST_FAILED);
+        }
+        if (res.getInteger("code") != 0) {
+            log.error("ManagerApiService videoMultiCoverListV2 error, videoId={} range={} res={}",
+                    videoId, "2h", res);
+            throw new CommonException(ExceptionEnum.VIDEO_MULTI_COVER_LIST_FAILED.getCode(),
+                    ExceptionEnum.VIDEO_MULTI_COVER_LIST_FAILED.getMsg() + "," + res.getString("msg"));
+        }
+        return res.getJSONArray("content");
+    }
+
+    public JSONArray videoMultiTitleListV2(Long videoId) {
+        String url = managerApiHost + "/video/multiTitleV2/listV2";
+        JSONObject res = null;
+        int retry = 0;
+        while (retry < 3) {
+            try {
+                JSONObject param = new JSONObject();
+                param.put("videoId", videoId);
+                param.put("range", "4h");
+                String post = httpPoolClient.post(url, param.toJSONString());
+                res = JSONObject.parseObject(post);
+                break;
+            } catch (Exception e) {
+                log.error("ManagerApiService videoMultiCoverListV2 error, videoId={} range={}",
+                        videoId, "4h", e);
+                retry++;
+            }
+        }
+        if (Objects.isNull(res)) {
+            throw new CommonException(ExceptionEnum.VIDEO_MULTI_TITLE_LIST_FAILED);
+        }
+        if (res.getInteger("code") != 0) {
+            log.error("ManagerApiService videoMultiTitleListV2 error, videoId={} range={} res={}",
+                    videoId, "4h", res);
+            throw new CommonException(ExceptionEnum.VIDEO_MULTI_TITLE_LIST_FAILED.getCode(),
+                    ExceptionEnum.VIDEO_MULTI_TITLE_LIST_FAILED.getMsg() + "," + res.getString("msg"));
+        }
+        return res.getJSONArray("content");
+    }
+
+
 }

+ 23 - 17
api-module/src/main/java/com/tzld/piaoquan/api/component/VideoApiService.java

@@ -162,23 +162,29 @@ public class VideoApiService {
 
     public void deleteVideo(Long uid, Long videoId) {
         String url = videoApiHost + "/longvideoapi/openapi/video/delete";
-        JSONObject res = null;
-        try {
-            JSONObject param = new JSONObject();
-            param.put("loginUid", uid);
-            param.put("videoId", videoId);
-            String post = httpPoolClient.post(url, param.toJSONString());
-            res = JSONObject.parseObject(post);
-        } catch (Exception e) {
-            log.error("VideoApiService deleteVideo error", e);
-        }
-        if (Objects.isNull(res)) {
-            throw new CommonException(ExceptionEnum.VIDEO_DELETE_FAILED);
-        }
-        if (res.getInteger("code") != 0) {
-            log.error("VideoApiService deleteVideo error, videoId={} res={}", videoId, res);
-            throw new CommonException(ExceptionEnum.VIDEO_DELETE_FAILED.getCode(),
-                    ExceptionEnum.VIDEO_DELETE_FAILED.getMsg() + "," + res.getString("msg"));
+        int retryCount = 3;
+        while (retryCount > 0) {
+            try {
+                JSONObject param = new JSONObject();
+                param.put("loginUid", uid);
+                param.put("videoId", videoId);
+                String post = httpPoolClient.post(url, param.toJSONString());
+                JSONObject res = JSONObject.parseObject(post);
+                if (Objects.isNull(res)) {
+                    throw new CommonException(ExceptionEnum.VIDEO_DELETE_FAILED);
+                }
+                if (res.getInteger("code") != 0) {
+                    log.error("VideoApiService deleteVideo error, videoId={} res={}", videoId, res);
+                    throw new CommonException(ExceptionEnum.VIDEO_DELETE_FAILED.getCode(),
+                            ExceptionEnum.VIDEO_DELETE_FAILED.getMsg() + "," + res.getString("msg"));
+                } else {
+                    log.info("VideoApiService deleteVideo success, uid:{} videoId={}", uid, videoId);
+                    return;
+                }
+            } catch (Exception e) {
+                log.error("VideoApiService deleteVideo error", e);
+            }
+            retryCount--;
         }
     }
 

+ 9 - 1
api-module/src/main/java/com/tzld/piaoquan/api/component/WeComThirdPartyApiClient.java

@@ -8,13 +8,21 @@ import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Component;
 
 import java.io.IOException;
+import java.util.concurrent.TimeUnit;
 
 @Slf4j
 @Component
 public class WeComThirdPartyApiClient {
 
     private static final MediaType JSON = MediaType.get("application/json; charset=utf-8");
-    private final OkHttpClient client = new OkHttpClient();
+    private final OkHttpClient client;
+
+    {
+        client = new OkHttpClient().newBuilder()
+                .connectTimeout(3, TimeUnit.MINUTES)
+                .readTimeout(3, TimeUnit.MINUTES)
+                .writeTimeout(3, TimeUnit.MINUTES).build();
+    }
 
     @Value("${wecom.thirdpart.baseUrl:http://47.96.102.58:8083}")
     private String baseUrl;

+ 11 - 2
api-module/src/main/java/com/tzld/piaoquan/api/controller/AccountDetailController.java

@@ -1,12 +1,13 @@
 package com.tzld.piaoquan.api.controller;
 
-import com.tzld.piaoquan.growth.common.common.enums.GhTypeEnum;
-import com.tzld.piaoquan.growth.common.common.enums.StrategyStatusEnum;
+import com.tzld.piaoquan.api.job.GzhReplyVideoRefreshJob;
 import com.tzld.piaoquan.api.model.vo.GhDetailVo;
 import com.tzld.piaoquan.api.model.vo.GhTypeVo;
 import com.tzld.piaoquan.api.model.vo.StrategyStatusVo;
 import com.tzld.piaoquan.api.service.GhDetailService;
 import com.tzld.piaoquan.growth.common.common.base.CommonResponse;
+import com.tzld.piaoquan.growth.common.common.enums.GhTypeEnum;
+import com.tzld.piaoquan.growth.common.common.enums.StrategyStatusEnum;
 import com.tzld.piaoquan.growth.common.utils.page.Page;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -25,6 +26,8 @@ public class AccountDetailController {
 
     @Autowired
     private GhDetailService ghDetailService;
+    @Autowired
+    private GzhReplyVideoRefreshJob gzhReplyVideoRefreshJob;
 
     @GetMapping("/getList")
     public CommonResponse<Page<GhDetailVo>> getAccountDetailList(@RequestParam(defaultValue = "1") Integer pageNum,
@@ -63,4 +66,10 @@ public class AccountDetailController {
                 .map(strategyStatusEnum -> new StrategyStatusVo(strategyStatusEnum.status, strategyStatusEnum.name))
                 .collect(Collectors.toList()));
     }
+
+    @GetMapping("/job/gzhReplyVideoRefreshJob")
+    public CommonResponse<Void> gzhReplyVideoRefreshJob() {
+        gzhReplyVideoRefreshJob.gzhReplyVideoRefreshJob(null);
+        return CommonResponse.success();
+    }
 }

+ 6 - 0
api-module/src/main/java/com/tzld/piaoquan/api/controller/wecom/thirdpart/WeComThirdPartyController.java

@@ -96,4 +96,10 @@ public class WeComThirdPartyController {
         return CommonResponse.success();
     }
 
+    @PostMapping("/automaticLogin")
+    public CommonResponse<Void> automaticLogin(@RequestBody UuidRequest request) {
+        service.automaticLogin(request);
+        return CommonResponse.success();
+    }
+
 }

+ 2 - 0
api-module/src/main/java/com/tzld/piaoquan/api/dao/mapper/GhDetailMapperExt.java

@@ -14,4 +14,6 @@ public interface GhDetailMapperExt {
     void deleteOldGhDetailExt(@Param("ghId") String ghId, @Param("type") Integer type);
 
     void batchInsertGhDetailExt(@Param("records") List<GhDetailExt> records);
+
+    List<GhDetailExt> getGhDetailExtList(@Param("ghId") String ghId, @Param("type") Integer type);
 }

+ 349 - 0
api-module/src/main/java/com/tzld/piaoquan/api/job/GzhReplyVideoRefreshJob.java

@@ -0,0 +1,349 @@
+package com.tzld.piaoquan.api.job;
+
+import com.alibaba.fastjson.JSONObject;
+import com.aliyun.odps.data.Record;
+import com.ctrip.framework.apollo.spring.annotation.ApolloJsonValue;
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import com.tzld.piaoquan.api.component.AdApiService;
+import com.tzld.piaoquan.api.component.AigcApiService;
+import com.tzld.piaoquan.api.component.DeepSeekApiService;
+import com.tzld.piaoquan.api.model.bo.AdPutCreativeComponentCostData;
+import com.tzld.piaoquan.api.model.bo.GoogleLLMResult;
+import com.tzld.piaoquan.api.model.bo.VideoDetail;
+import com.tzld.piaoquan.api.model.dto.AIResult;
+import com.tzld.piaoquan.api.model.vo.GhDetailVo;
+import com.tzld.piaoquan.api.service.GhDetailService;
+import com.tzld.piaoquan.api.util.AliOssFileTool;
+import com.tzld.piaoquan.api.util.CdnUtil;
+import com.tzld.piaoquan.growth.common.model.po.GhDetail;
+import com.tzld.piaoquan.growth.common.utils.DateUtil;
+import com.tzld.piaoquan.growth.common.utils.OdpsUtil;
+import com.tzld.piaoquan.growth.common.utils.TitleSimilarCheckUtil;
+import com.xxl.job.core.biz.model.ReturnT;
+import com.xxl.job.core.handler.annotation.XxlJob;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import java.util.*;
+import java.util.concurrent.*;
+import java.util.stream.Collectors;
+
+@Slf4j
+@Component
+public class GzhReplyVideoRefreshJob {
+
+    @Autowired
+    GhDetailService ghDetailService;
+    @Autowired
+    AdApiService adApiService;
+    @Autowired
+    AigcApiService aigcApiService;
+    @Autowired
+    DeepSeekApiService deepSeekApiService;
+
+    @ApolloJsonValue("${gzh.reply.video.refresh.job.filter.wxIds:[]}")
+    private List<String> filterWxIds;
+    @Value("${gzh.reply.video.refresh.job.topNum:2}")
+    private Integer topNum;
+
+    private final static ExecutorService pool = new ThreadPoolExecutor(5, 5, 0L, TimeUnit.SECONDS,
+            new LinkedBlockingQueue<>(1000),
+            new ThreadFactoryBuilder().setNameFormat("GzhReplyVideoRefreshJob-%d").build(),
+            new ThreadPoolExecutor.AbortPolicy());
+
+    @XxlJob("gzhReplyVideoRefreshJob")
+    public ReturnT<String> gzhReplyVideoRefreshJob(String param) {
+        String dt =DateUtil.getBeforeDayDateString("yyyy-MM-dd");
+        if (StringUtils.isNotEmpty(param)) {
+            dt = param;
+        }
+        List<AdPutCreativeComponentCostData> list = adApiService.getCreativeComponentsCost(dt);
+        if (CollectionUtils.isEmpty(list)) {
+            return ReturnT.SUCCESS;
+        }
+        // 按公众号分组
+        Map<String, List<AdPutCreativeComponentCostData>> costDataMap = list.stream()
+                .filter(o ->CollectionUtils.isNotEmpty(o.getComponentIds()))
+                .collect(Collectors.groupingBy(AdPutCreativeComponentCostData::getWxId));
+        CountDownLatch cdl = new CountDownLatch(costDataMap.size());
+        // 获取所有投流账号信息
+        String accountMapSql = "SELECT account_id, account_name from loghubods.feishu_wechat_mp_account_base where account_status = '开';";
+        List<Record> accountList = OdpsUtil.getOdpsData(accountMapSql);
+        Map<String, String> accountMap = new HashMap<>();
+        if (CollectionUtils.isNotEmpty(accountList)) {
+            for (Record record : accountList) {
+                String accountId = record.getString(0);
+                String accountName = record.getString(1);
+                accountMap.put(accountId, accountName);
+            }
+        }
+        // 服务号投流账号
+        String fwhAccountMapSql = "SELECT account_id, account_name from loghubods.feishu_wechat_fwh_account_base where account_status = '开';";
+        List<Record> fwhAccountList = OdpsUtil.getOdpsData(fwhAccountMapSql);
+        if (CollectionUtils.isNotEmpty(fwhAccountList)) {
+            for (Record record : fwhAccountList) {
+                String accountId = record.getString(0);
+                String accountName = record.getString(1);
+                accountMap.put(accountId, accountName);
+            }
+        }
+
+        // 获取公众号信息
+        List<String> nameList = new ArrayList<>(accountMap.values());
+        List<GhDetail> ghDetailList = ghDetailService.getGhdetailByNames(nameList);
+        Map<String, GhDetail> ghDetailMap = ghDetailList.stream().collect(Collectors.toMap(GhDetail::getGhName, ghDetail -> ghDetail));
+        for (Map.Entry<String, List<AdPutCreativeComponentCostData>> entry : costDataMap.entrySet()) {
+            pool.submit(() -> {
+                try {
+                    replaceGZHReplyVideo(entry.getKey(), entry.getValue(), ghDetailMap, accountMap);
+                } finally {
+                    cdl.countDown();
+                }
+            });
+        }
+        try {
+            cdl.await();
+        } catch (InterruptedException e) {
+            log.error("GzhReplyVideoRefreshJob error", e);
+        }
+        return ReturnT.SUCCESS;
+    }
+
+    private void replaceGZHReplyVideo(String wxId, List<AdPutCreativeComponentCostData> costDataList,
+                                      Map<String, GhDetail> ghDetailMap,
+                                      Map<String, String> accountMap) {
+        if (filterWxIds.contains(wxId)) {
+            return;
+        }
+        String accountName = accountMap.get(wxId);
+        if (StringUtils.isEmpty(accountName)) {
+            return;
+        }
+        GhDetail ghDetail = ghDetailMap.get(accountName);
+        if (ghDetail == null) {
+            return;
+        }
+        Map<String, JSONObject> creativeIdTextMap = analysisImageText(costDataList);
+        log.info("GzhReplyVideoRefreshJob accountName:{} creativeIdTextMap:{}", accountName, JSONObject.toJSONString(creativeIdTextMap));
+        // 按cost排序 获取top2内容
+        List<JSONObject> sortedList = creativeIdTextMap.entrySet().stream()
+                .sorted((o1, o2) -> o2.getValue().getLong("cost").compareTo(o1.getValue().getLong("cost")))
+                .limit(topNum)
+                .map(Map.Entry::getValue)
+                .collect(Collectors.toList());
+        List<VideoDetail> searchVideos = new ArrayList<>();
+        List<String> existTitles = new ArrayList<>();
+        for (JSONObject obj : sortedList) {
+            String text = obj.getString("title");
+            String keywordPrompt = getKeyWordPrompt(text);
+            AIResult aiResult = deepSeekApiService.requestOfficialApi(keywordPrompt, null, null, false);
+            if (aiResult.isSuccess()) {
+                List<String> keywords = JSONObject.parseArray(aiResult.getResponse().getChoices().get(0).getMessage().getContent(), String.class);
+                log.info("GzhReplyVideoRefreshJob accountName:{} text:{} keywords:{}", accountName, text, keywords);
+                VideoDetail videoDetail = getVideoDetail(keywords, searchVideos, existTitles);
+                if (videoDetail != null) {
+                    existTitles.add(videoDetail.getTitle());
+                    existTitles.add(text);
+                    videoDetail.setTitle(text);
+                    videoDetail.setCover(obj.getString("coverImgUrl"));
+                    searchVideos.add(videoDetail);
+                }
+            }
+        }
+        if (searchVideos.size() == topNum) {
+            // 更新视频
+            updateVideoReply(ghDetail, searchVideos);
+        }
+    }
+
+    private String getKeyWordPrompt(String text) {
+        // 提取关键词
+        String keywordPrompt =
+                "# Role\n" +
+                        "你是一位世界顶级的短视频SEO专家。你擅长剥离标题中的修饰语和状态词,精准捕捉用户真正想搜索的“核心实体”或“核心话题”。\n" +
+                        "\n" +
+                        "# Task\n" +
+                        "分析给定的视频标题,提取 **1个 或 2个** 最具搜索价值的关键词。\n" +
+                        "\n" +
+                        "# Constraints (必须严格执行)\n" +
+                        "1. **字数限制**:每个关键词必须 **≤ 3个汉字**。\n" +
+                        "2. **数量限制**:输出 1 到 2 个关键词。如果只有一个核心词,不要强行凑数,只输出一个。\n" +
+                        "3. **选词优先级 (核心逻辑)**:\n" +
+                        "   - **最高优先级**:核心名词、实体(如:燕子、粮食、早餐)。\n" +
+                        "   - **次优先级**:具有明确分类属性的动词(如:剪辑、做菜)。\n" +
+                        "4. **负面清单 (严禁提取)**:\n" +
+                        "   - **剔除状态词/结果词**:如“过期”、“消失”、“变质”、“最好”、“几点”等描述物体状态或程度的词。\n" +
+                        "   - **剔除虚词**:如“的”、“了”、“吗”。\n" +
+                        "   - **剔除泛词**:如“今天”、“才知道”、“告诉”。\n" +
+                        "5. **输出格式**:仅输出JSON数组,无其他字符。\n" +
+                        "\n" +
+                        "# Few-Shot Examples (学习选词逻辑)\n" +
+                        "Input: 今天才知道5种粮食不怕过期!放越久越好!\n" +
+                        "Output: [\"粮食\"]\n" +
+                        "(解释:剔除“过期”,因为它只是粮食的一个状态,用户搜的是粮食)\n" +
+                        "\n" +
+                        "Input: 今年燕子消失了 你知道怎么回事吗?\n" +
+                        "Output: [\"燕子\"]\n" +
+                        "(解释:剔除“消失”,保留核心生物实体)\n" +
+                        "\n" +
+                        "Input: 新手如何快速学会剪映剪辑\n" +
+                        "Output: [\"剪映\",\"剪辑\"]\n" +
+                        "(解释:两个词都是核心技能点,且都在3字内)\n" +
+                        "\n" +
+                        "Input: 冰箱总是结冰怎么办\n" +
+                        "Output: [\"冰箱\",\"结冰\"]\n" +
+                        "(解释:“结冰”虽是状态,但“冰箱结冰”是常见故障搜索词,故保留;若无法判断,优先保实体)\n" +
+                        "\n" +
+                        "# Input\n" +
+                        "内容是: {{text}} \n" +
+                        "\n" +
+                        "# Output\n" +
+                        "请基于上述规则,输出最终的JSON:";
+        keywordPrompt = keywordPrompt.replace("text", text);
+        return keywordPrompt;
+    }
+
+    private Map<String, JSONObject> analysisImageText(List<AdPutCreativeComponentCostData> costDataList) {
+        Map<String, JSONObject> creativeIdTextMap = new HashMap<>();
+        for (AdPutCreativeComponentCostData costData : costDataList) {
+            for (AdPutCreativeComponentCostData.AdPutTencentComponent component : costData.getComponentList()) {
+                String imageUrl = component.getImageUrl();
+                try {
+                    // download & upload to oss
+                    String fileName = String.format("growth/image/%s_%d.jpg", costData.getCreativeId(), System.currentTimeMillis());
+                    String ossImageUrl = AliOssFileTool.downloadAndSaveInOSS(fileName, imageUrl, "image/jpeg");
+                    // 调用gemini-3-pro模型提取图片中的文字
+                    String prompt = "识别图片里的文字,直接输出图片里的文字标题,但是不要输出图片里右下角的字,比如“点开看看”“看这里”等,仅输出标题,不要有多余的文字输出";
+                    GoogleLLMResult result = aigcApiService.gemini("gemini-2.0-flash", prompt, Arrays.asList(ossImageUrl));
+                    String text = result.getResult();
+                    text = text.replaceAll("\\n", "");
+                    for (String existText : creativeIdTextMap.keySet()) {
+                        if (TitleSimilarCheckUtil.isSimilar(text, existText, TitleSimilarCheckUtil.SIMILARITY_THRESHOLD)) {
+                            text = existText;
+                            break;
+                        }
+                    }
+                    JSONObject obj = creativeIdTextMap.getOrDefault(text, new JSONObject());
+                    Long cost = obj.getLong("cost");
+                    if (Objects.isNull(cost)) {
+                        cost = costData.getCost();
+                        obj.put("cost", cost);
+                        obj.put("title", text);
+                        obj.put("coverImgUrl", ossImageUrl);
+                    } else {
+                        cost += costData.getCost();
+                        obj.put("cost", cost);
+                    }
+                    creativeIdTextMap.put(text, obj);
+                    break;
+                } catch (Exception e) {
+                    log.error("GzhReplyVideoRefreshJob analysisImageText error, imageUrl:{}", imageUrl, e);
+                }
+            }
+        }
+        return creativeIdTextMap;
+    }
+
+    private VideoDetail getVideoDetail(List<String> keywords, List<VideoDetail> searchVideos, List<String> existTitles) {
+        VideoDetail videoDetail = null;
+        int maxRetries = 3;
+        int retryCount = 0;
+        while (retryCount < maxRetries) {
+            try {
+                videoDetail = searchVideoByKeyword(keywords, searchVideos, existTitles);
+                break;
+            } catch (Exception e) {
+                retryCount++;
+                log.error("GzhReplyVideoRefreshJob searchVideoByKeyword error, retry count: {}/{}", retryCount, maxRetries, e);
+                if (retryCount >= maxRetries) {
+                    break;
+                }
+            }
+        }
+        return videoDetail;
+    }
+
+    private VideoDetail searchVideoByKeyword(List<String> keywords, List<VideoDetail> searchVideos, List<String> existTitles) {
+        if (CollectionUtils.isNotEmpty(keywords)) {
+            String searchVideoSql = "SELECT v.id, v.title, v.cover_img_path\n" +
+                    "FROM videoods.wx_video v\n" +
+                    "join videoods.wx_video_status vs on v.id = vs.video_id\n" +
+                    "where (v.title like '%" + String.join("%' or v.title like '%", keywords) + "%') and v.status = 1 and vs.audit_status = 5\n" +
+                    "order by v.play_count_total desc limit 10;";
+            List<Record> videoList = OdpsUtil.getOdpsData(searchVideoSql);
+            if (CollectionUtils.isNotEmpty(videoList)) {
+                for (Record record : videoList) {
+                    Long videoId = Long.parseLong(record.getString(0));
+                    String title = record.getString(1);
+                    // 过滤重复视频
+                    boolean filter = false;
+                    for (VideoDetail searchVideo : searchVideos) {
+                        if (searchVideo.getId().equals(videoId)) {
+                            filter = true;
+                            break;
+                        }
+                    }
+                    // 过滤重复标题
+                    if (!filter && CollectionUtils.isNotEmpty(existTitles)) {
+                        for (String existTitle : existTitles) {
+                            if (TitleSimilarCheckUtil.isSimilar(title, existTitle, TitleSimilarCheckUtil.SIMILARITY_THRESHOLD)) {
+                                filter = true;
+                                break;
+                            }
+                        }
+                    }
+                    if (filter) {
+                        continue;
+                    }
+                    String cover = record.getString(2);
+                    VideoDetail videoDetail = new VideoDetail();
+                    videoDetail.setId(videoId);
+                    videoDetail.setCover(CdnUtil.getOssHttpUrl(cover));
+                    videoDetail.setTitle(title);
+                    return videoDetail;
+                }
+            }
+        }
+        return null;
+    }
+
+    private void updateVideoReply(GhDetail ghDetail, List<VideoDetail> searchVideos) {
+        log.info("GzhReplyVideoRefreshJob accountName:{} updateVideoReply, oldVideoIds: {}, replaceVideos: {}",
+                ghDetail.getGhName(), ghDetail.getVideoIds(), JSONObject.toJSONString(searchVideos));
+        GhDetailVo ghDetailVo = new GhDetailVo();
+        ghDetailVo.setId(ghDetail.getId());
+        ghDetailVo.setAccountId(ghDetail.getGhId());
+        ghDetailVo.setAccountName(ghDetail.getGhName());
+        ghDetailVo.setCategory1(ghDetail.getCategory1());
+        ghDetailVo.setStrategyStatus(ghDetail.getStrategyStatus());
+        ghDetailVo.setType(ghDetail.getType());
+        ghDetailVo.setAutoreplySendMinigramNum(topNum);
+        ghDetailVo.setVideoIds(searchVideos.stream().map(VideoDetail::getId).collect(Collectors.toList()));
+        List<GhDetailVo.VideoDetail> videoDetailList = new ArrayList<>();
+        for (int i = 0; i < searchVideos.size(); i++) {
+            VideoDetail videoDetail = searchVideos.get(i);
+            GhDetailVo.VideoDetail videoDetailVo = new GhDetailVo.VideoDetail();
+            videoDetailVo.setVideoId(videoDetail.getId());
+            videoDetailVo.setSort(i + 1);
+            videoDetailVo.setTitle(dealTitleLength(videoDetail.getTitle()));
+            videoDetailVo.setCover(videoDetail.getCover());
+            videoDetailList.add(videoDetailVo);
+        }
+        ghDetailVo.setVideoList(videoDetailList);
+        ghDetailService.updateDetail(ghDetailVo);
+    }
+
+    private String dealTitleLength(String title) {
+        if (StringUtils.isBlank(title)) {
+            return title;
+        }
+        if (title.length() > 20) {
+            title = title.substring(0, 19) + "…";
+        }
+        return title;
+    }
+}

+ 122 - 72
api-module/src/main/java/com/tzld/piaoquan/api/job/contentplatform/ContentPlatformDatastatJob.java

@@ -74,12 +74,12 @@ public class ContentPlatformDatastatJob {
         Long now = System.currentTimeMillis();
         // 公众号自动回复数据统计
         String sql = String.format(
-                "SELECT first_level.channel_shortname, first_level.subchannel, first_level.first_uv, fission.split_uv " +
-                "FROM loghubods.out_channel_mid_first_total first_level " +
-                "left join loghubods.out_channel_mid_split_total fission " +
-                "on first_level.channel_shortname = fission.channel_shortname and first_level.subchannel = fission.subchannel " +
-                "and first_level.dt = fission.dt and first_level.type = fission.type and first_level.tag = fission.tag " +
-                "WHERE first_level.dt = %s and first_level.type = '公众号即时回复' and first_level.tag = '分投放渠道客户分账号去重';", dt);
+                "SELECT first_level.channel_shortname, first_level.subchannel, first_level.first_uv, fission.split_uv, fission.裂变arpu " +
+                        "FROM loghubods.out_channel_mid_first_total first_level " +
+                        "left join loghubods.out_channel_mid_split_total fission " +
+                        "on first_level.channel_shortname = fission.channel_shortname and first_level.subchannel = fission.subchannel " +
+                        "and first_level.dt = fission.dt and first_level.type = fission.type and first_level.tag = fission.tag " +
+                        "WHERE first_level.dt = %s and first_level.type = '公众号即时回复' and first_level.tag = '分投放渠道客户分账号去重';", dt);
         List<Record> dataList = OdpsUtil.getOdpsData(sql);
         // 所有公众号
         List<ContentPlatformGzhAccount> accountList = getAllGzhAccount();
@@ -94,16 +94,29 @@ public class ContentPlatformDatastatJob {
                 .collect(Collectors.toMap(WxAccountDatastatVO::getAccountId, wxAccountDatastatVO -> wxAccountDatastatVO));
         if (CollectionUtils.isNotEmpty(dataList)) {
             List<ContentPlatformGzhDataStat> saveList = new ArrayList<>();
+            BigDecimal sumScore = BigDecimal.ZERO;
             for (Record record : dataList) {
-                ContentPlatformGzhDataStat item = new ContentPlatformGzhDataStat();
+                String channel = (String) record.get(0);
                 String ghId = (String) record.get(1);
-                int firstLevelCount = Integer.parseInt((String) record.get(2));
+                Integer firstLevelCount = parseInteger(record.get(2));
                 Integer fissionCount = parseInteger(record.get(3));
-                item.setDateStr(dt);
+                Double fissionArpu = parseDouble(record.get(4));
+                if (fissionArpu > 0.3) {
+                    fissionArpu = 0.3;
+                }
+                if ("SUM".equals(channel)) {
+                    BigDecimal fissionRate = BigDecimal.valueOf(fissionCount.doubleValue() * 10 / firstLevelCount)
+                            .setScale(2, RoundingMode.HALF_UP);
+                    fissionArpu = BigDecimal.valueOf(fissionArpu).setScale(2, RoundingMode.HALF_UP).doubleValue();
+                    sumScore = fissionRate.add(new BigDecimal(fissionArpu));
+                    continue;
+                }
                 if (!ghIds.contains(ghId)) {
                     continue;
                 }
                 ContentPlatformGzhAccount gzhAccount = accountMap.get(ghId);
+                ContentPlatformGzhDataStat item = new ContentPlatformGzhDataStat();
+                item.setDateStr(dt);
                 item.setAccountId(gzhAccount.getId());
                 item.setFirstLevelCount(firstLevelCount);
                 WxAccountDatastatVO wxAccountDatastatVO = wxAccountDatastatMap.get(gzhAccount.getExternalId());
@@ -112,13 +125,25 @@ public class ContentPlatformDatastatJob {
                 }
 
                 if (fissionCount > 0 && firstLevelCount > 0) {
-                    BigDecimal num = BigDecimal.valueOf(fissionCount.doubleValue() * 10 / firstLevelCount);
-                    BigDecimal rounded = num.setScale(2, RoundingMode.HALF_UP);
-                    item.setScore(rounded.doubleValue());
+                    BigDecimal fissionRate = BigDecimal.valueOf(fissionCount.doubleValue() * 10 / firstLevelCount)
+                            .setScale(2, RoundingMode.HALF_UP);
+                    fissionArpu = BigDecimal.valueOf(fissionArpu).setScale(2, RoundingMode.HALF_UP).doubleValue();
+                    BigDecimal totalScore = fissionRate.add(new BigDecimal(fissionArpu));
+                    item.setScore(totalScore.doubleValue());
                 }
                 item.setCreateTimestamp(now);
                 saveList.add(item);
             }
+            for (ContentPlatformGzhDataStat item : saveList) {
+                if (item.getFirstLevelCount() < 10) {
+                    item.setScore(0.0);
+                    continue;
+                }
+                if (Objects.nonNull(item.getScore())) {
+                    BigDecimal score = BigDecimal.valueOf(item.getScore());
+                    item.setScore(score.divide(sumScore, 3, RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(10)).doubleValue());
+                }
+            }
             if (CollectionUtils.isNotEmpty(saveList)) {
                 dataStatMapperExt.deleteGzhDatastat(dt);
                 dataStatMapperExt.batchInsertGzhDatastat(saveList);
@@ -187,15 +212,15 @@ public class ContentPlatformDatastatJob {
         Long now = System.currentTimeMillis();
         // 公众号自动回复数据统计
         String sql = String.format(
-                "SELECT first_level.channel_shortname, first_level.first_uv, fission.split_uv, price.arpu " +
-                "FROM loghubods.out_channel_mid_first_total first_level " +
-                "left join loghubods.out_channel_mid_split_total fission " +
-                "on first_level.channel_shortname = fission.channel_shortname and first_level.dt = fission.dt " +
-                "and first_level.type = fission.type and first_level.tag = fission.tag " +
-                "left join loghubods.wecom_cooperation_dynamic_unit_price price " +
-                "on first_level.channel_shortname = price.channel_shortname and first_level.dt = price.dt " +
-                "and first_level.type = price.type and first_level.tag = price.tag " +
-                "WHERE first_level.dt = %s and first_level.type = '公众号即时回复' and first_level.tag = '投放渠道内去重' ;", dt);
+                "SELECT first_level.channel_shortname, first_level.first_uv, fission.split_uv, fission.裂变arpu, price.arpu " +
+                        "FROM loghubods.out_channel_mid_first_total first_level " +
+                        "left join loghubods.out_channel_mid_split_total fission " +
+                        "on first_level.channel_shortname = fission.channel_shortname and first_level.dt = fission.dt " +
+                        "and first_level.type = fission.type and first_level.tag = fission.tag " +
+                        "left join loghubods.wecom_cooperation_dynamic_unit_price price " +
+                        "on first_level.channel_shortname = price.channel_shortname and first_level.dt = price.dt " +
+                        "and first_level.type = price.type and first_level.tag = price.tag " +
+                        "WHERE first_level.dt = %s and first_level.type = '公众号即时回复' and first_level.tag = '投放渠道内去重' ;", dt);
         List<Record> dataList = OdpsUtil.getOdpsData(sql);
         // 所有公众号
         List<ContentPlatformAccount> accountList = getAllAccount();
@@ -211,16 +236,24 @@ public class ContentPlatformDatastatJob {
         Map<String, WxAccountDatastatVO> wxAccountDatastatMap = wxAccountDatastatVOList.stream()
                 .collect(Collectors.toMap(WxAccountDatastatVO::getAccountId, wxAccountDatastatVO -> wxAccountDatastatVO));
         if (CollectionUtils.isNotEmpty(dataList)) {
+            BigDecimal sumScore = BigDecimal.ZERO;
             List<ContentPlatformGzhDataStatTotal> saveList = new ArrayList<>();
             for (Record record : dataList) {
-                ContentPlatformGzhDataStatTotal item = new ContentPlatformGzhDataStatTotal();
                 String channel = record.getString(0);
-                int firstLevelCount = Integer.parseInt((String) record.get(1));
+                Integer firstLevelCount = parseInteger(record.get(1));
                 Integer fissionCount = parseInteger(record.get(2));
-                Double arpu = parseDouble(record.get(3));
-                item.setDateStr(dt);
-                item.setChannel(channel);
-                item.setFirstLevelCount(firstLevelCount);
+                Double fissionArpu = parseDouble(record.get(3));
+                if (fissionArpu > 0.3) {
+                    fissionArpu = 0.3;
+                }
+                Double arpu = parseDouble(record.get(4));
+                if ("SUM".equals(channel)) {
+                    BigDecimal fissionRate = BigDecimal.valueOf(fissionCount.doubleValue() * 10 / firstLevelCount)
+                            .setScale(2, RoundingMode.HALF_UP);
+                    fissionArpu = BigDecimal.valueOf(fissionArpu).setScale(2, RoundingMode.HALF_UP).doubleValue();
+                    sumScore = fissionRate.add(new BigDecimal(fissionArpu));
+                    continue;
+                }
                 ContentPlatformAccount account = accountMap.get(channel);
                 if (Objects.isNull(account)) {
                     continue;
@@ -235,12 +268,19 @@ public class ContentPlatformDatastatJob {
                         }
                     }
                 }
+                ContentPlatformGzhDataStatTotal item = new ContentPlatformGzhDataStatTotal();
+                item.setDateStr(dt);
+                item.setChannel(channel);
+                item.setFirstLevelCount(firstLevelCount);
                 item.setFansIncreaseCount(fansIncreaseCount);
 
                 if (fissionCount > 0 && firstLevelCount > 0) {
-                    BigDecimal fissionRate = BigDecimal.valueOf(fissionCount.doubleValue() / firstLevelCount);
-                    BigDecimal rounded = fissionRate.multiply(new BigDecimal(10)).setScale(2, RoundingMode.HALF_UP);
-                    item.setScore(rounded.doubleValue());
+                    BigDecimal fissionRate = BigDecimal.valueOf(fissionCount.doubleValue() * 10 / firstLevelCount)
+                            .setScale(2, RoundingMode.HALF_UP);
+                    fissionArpu = BigDecimal.valueOf(fissionArpu).setScale(2, RoundingMode.HALF_UP).doubleValue();
+                    BigDecimal totalScore = fissionRate.add(new BigDecimal(fissionArpu));
+                    item.setScore(totalScore.doubleValue());
+
                     BigDecimal unitPrice = getUnitPrice(account.getPrice(), fissionRate, arpu, BussinessTypeEnum.GZH_AUTO_REPLY);
                     if (Objects.nonNull(unitPrice)) {
                         item.setUnitPrice(unitPrice.doubleValue());
@@ -255,6 +295,16 @@ public class ContentPlatformDatastatJob {
                 item.setCreateTimestamp(now);
                 saveList.add(item);
             }
+            for (ContentPlatformGzhDataStatTotal item : saveList) {
+                if (item.getFirstLevelCount() < 25) {
+                    item.setScore(0.0);
+                    continue;
+                }
+                if (Objects.nonNull(item.getScore())) {
+                    BigDecimal score = BigDecimal.valueOf(item.getScore());
+                    item.setScore(score.divide(sumScore, 3, RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(10)).doubleValue());
+                }
+            }
             if (CollectionUtils.isNotEmpty(saveList)) {
                 dataStatMapperExt.deleteGzhDatastatTotal(dt);
                 dataStatMapperExt.batchInsertGzhDatastatTotal(saveList);
@@ -273,11 +323,11 @@ public class ContentPlatformDatastatJob {
         // 公众号自动回复数据统计
         String sql = String.format(
                 "SELECT first_level.subchannel, first_level.first_uv, fission.split_uv " +
-                "FROM loghubods.out_channel_mid_first_total first_level " +
-                "left join loghubods.out_channel_mid_split_total fission " +
-                "on first_level.subchannel = fission.subchannel and first_level.dt = fission.dt " +
-                "and first_level.type = fission.type and first_level.tag = fission.tag " +
-                "WHERE first_level.dt = %s and first_level.type = '服务号代运营' and first_level.tag = '分投放渠道客户分账号去重' ;", dt);
+                        "FROM loghubods.out_channel_mid_first_total first_level " +
+                        "left join loghubods.out_channel_mid_split_total fission " +
+                        "on first_level.subchannel = fission.subchannel and first_level.dt = fission.dt " +
+                        "and first_level.type = fission.type and first_level.tag = fission.tag " +
+                        "WHERE first_level.dt = %s and first_level.type = '服务号代运营' and first_level.tag = '分投放渠道客户分账号去重' ;", dt);
         List<Record> dataList = OdpsUtil.getOdpsData(sql);
         // 所有公众号
         List<ContentPlatformGzhAccount> accountList = getAllGzhAccount();
@@ -335,14 +385,14 @@ public class ContentPlatformDatastatJob {
         // 公众号自动回复数据统计
         String sql = String.format(
                 "SELECT first_level.channel_shortname, first_level.first_uv, fission.split_uv, price.arpu " +
-                "FROM loghubods.out_channel_mid_first_total first_level " +
-                "left join loghubods.out_channel_mid_split_total fission " +
-                "on first_level.channel_shortname = fission.channel_shortname and first_level.dt = fission.dt " +
-                "and first_level.type = fission.type and first_level.tag = fission.tag " +
-                "left join loghubods.wecom_cooperation_dynamic_unit_price price " +
-                "on first_level.channel_shortname = price.channel_shortname and first_level.dt = price.dt " +
-                "and first_level.type = price.type and first_level.tag = price.tag " +
-                "WHERE first_level.dt = %s and first_level.type = '服务号代运营' and first_level.tag = '投放渠道内去重' ;", dt);
+                        "FROM loghubods.out_channel_mid_first_total first_level " +
+                        "left join loghubods.out_channel_mid_split_total fission " +
+                        "on first_level.channel_shortname = fission.channel_shortname and first_level.dt = fission.dt " +
+                        "and first_level.type = fission.type and first_level.tag = fission.tag " +
+                        "left join loghubods.wecom_cooperation_dynamic_unit_price price " +
+                        "on first_level.channel_shortname = price.channel_shortname and first_level.dt = price.dt " +
+                        "and first_level.type = price.type and first_level.tag = price.tag " +
+                        "WHERE first_level.dt = %s and first_level.type = '服务号代运营' and first_level.tag = '投放渠道内去重' ;", dt);
         List<Record> dataList = OdpsUtil.getOdpsData(sql);
         // 所有公众号
         List<ContentPlatformAccount> accountList = getAllAccount();
@@ -420,11 +470,11 @@ public class ContentPlatformDatastatJob {
         // 公众号自动回复数据统计
         String sql = String.format(
                 "SELECT first_level.subchannel, first_level.first_uv, fission.split_uv " +
-                "FROM loghubods.out_channel_mid_first_total first_level " +
-                "left join loghubods.out_channel_mid_split_total fission " +
-                "on first_level.subchannel = fission.subchannel and first_level.dt = fission.dt " +
-                "and first_level.type = fission.type and first_level.tag = fission.tag " +
-                "WHERE first_level.dt = %s and first_level.type = '公众号推送' and first_level.tag = '分投放渠道客户分账号去重' ;", dt);
+                        "FROM loghubods.out_channel_mid_first_total first_level " +
+                        "left join loghubods.out_channel_mid_split_total fission " +
+                        "on first_level.subchannel = fission.subchannel and first_level.dt = fission.dt " +
+                        "and first_level.type = fission.type and first_level.tag = fission.tag " +
+                        "WHERE first_level.dt = %s and first_level.type = '公众号推送' and first_level.tag = '分投放渠道客户分账号去重' ;", dt);
         List<Record> dataList = OdpsUtil.getOdpsData(sql);
         // 所有公众号
         List<ContentPlatformGzhAccount> accountList = getAllGzhAccount();
@@ -482,14 +532,14 @@ public class ContentPlatformDatastatJob {
         // 公众号自动回复数据统计
         String sql = String.format(
                 "SELECT first_level.channel_shortname, first_level.first_uv, fission.split_uv, price.arpu " +
-                "FROM loghubods.out_channel_mid_first_total first_level " +
-                "left join loghubods.out_channel_mid_split_total fission " +
-                "on first_level.channel_shortname = fission.channel_shortname and first_level.dt = fission.dt " +
-                "and first_level.type = fission.type and first_level.tag = fission.tag " +
-                "left join loghubods.wecom_cooperation_dynamic_unit_price price " +
-                "on first_level.channel_shortname = price.channel_shortname and first_level.dt = price.dt " +
-                "and first_level.type = price.type and first_level.tag = price.tag " +
-                "WHERE first_level.dt = %s and first_level.type = '公众号推送' and first_level.tag = '投放渠道内去重' ;", dt);
+                        "FROM loghubods.out_channel_mid_first_total first_level " +
+                        "left join loghubods.out_channel_mid_split_total fission " +
+                        "on first_level.channel_shortname = fission.channel_shortname and first_level.dt = fission.dt " +
+                        "and first_level.type = fission.type and first_level.tag = fission.tag " +
+                        "left join loghubods.wecom_cooperation_dynamic_unit_price price " +
+                        "on first_level.channel_shortname = price.channel_shortname and first_level.dt = price.dt " +
+                        "and first_level.type = price.type and first_level.tag = price.tag " +
+                        "WHERE first_level.dt = %s and first_level.type = '公众号推送' and first_level.tag = '投放渠道内去重' ;", dt);
         List<Record> dataList = OdpsUtil.getOdpsData(sql);
         // 所有公众号
         List<ContentPlatformAccount> accountList = getAllAccount();
@@ -719,14 +769,14 @@ public class ContentPlatformDatastatJob {
         Map<String, ContentPlatformQwDataStatTotal> saveMap = new HashMap<>();
         String outSql = String.format(
                 "SELECT first_level.channel_shortname, first_level.first_uv, fission.split_uv, price.arpu " +
-                "FROM loghubods.out_channel_mid_first_total first_level " +
-                "left join loghubods.out_channel_mid_split_total fission " +
-                "on first_level.channel_shortname = fission.channel_shortname and first_level.dt = fission.dt " +
-                "and first_level.type = fission.type and first_level.tag = fission.tag " +
-                "left join loghubods.wecom_cooperation_dynamic_unit_price price " +
-                "on first_level.channel_shortname = price.channel_shortname and first_level.dt = price.dt " +
-                "and first_level.type = price.type and first_level.tag = price.tag " +
-                "WHERE first_level.dt = %s and first_level.type = '企微外部' and first_level.tag = '投放渠道内去重' ;", dt);
+                        "FROM loghubods.out_channel_mid_first_total first_level " +
+                        "left join loghubods.out_channel_mid_split_total fission " +
+                        "on first_level.channel_shortname = fission.channel_shortname and first_level.dt = fission.dt " +
+                        "and first_level.type = fission.type and first_level.tag = fission.tag " +
+                        "left join loghubods.wecom_cooperation_dynamic_unit_price price " +
+                        "on first_level.channel_shortname = price.channel_shortname and first_level.dt = price.dt " +
+                        "and first_level.type = price.type and first_level.tag = price.tag " +
+                        "WHERE first_level.dt = %s and first_level.type = '企微外部' and first_level.tag = '投放渠道内去重' ;", dt);
         List<Record> outDataList = OdpsUtil.getOdpsData(outSql);
         List<ContentPlatformAccount> accountList = getAllAccount();
         Map<String, ContentPlatformAccount> accountMap = accountList.stream()
@@ -871,14 +921,14 @@ public class ContentPlatformDatastatJob {
         List<ContentPlatformQwDataStatSubChannel> saveList = new ArrayList<>();
         String outSql = String.format(
                 "SELECT first_level.channel_shortname, first_level.subchannel, first_level.first_uv, fission.split_uv, price.arpu " +
-                "FROM loghubods.out_channel_mid_first_total first_level " +
-                "left join loghubods.out_channel_mid_split_total fission " +
-                "on first_level.channel_shortname = fission.channel_shortname and first_level.subchannel = fission.subchannel " +
-                "and first_level.dt = fission.dt and first_level.type = fission.type and first_level.tag = fission.tag " +
-                "left join loghubods.wecom_cooperation_dynamic_unit_price price " +
-                "on first_level.channel_shortname = price.channel_shortname and first_level.subchannel = price.subchannel " +
-                "and first_level.dt = price.dt and first_level.type = price.type and first_level.tag = price.tag " +
-                "WHERE first_level.dt = %s and first_level.type = '企微外部' and first_level.tag = '分投放渠道客户分账号去重' ;", dt);
+                        "FROM loghubods.out_channel_mid_first_total first_level " +
+                        "left join loghubods.out_channel_mid_split_total fission " +
+                        "on first_level.channel_shortname = fission.channel_shortname and first_level.subchannel = fission.subchannel " +
+                        "and first_level.dt = fission.dt and first_level.type = fission.type and first_level.tag = fission.tag " +
+                        "left join loghubods.wecom_cooperation_dynamic_unit_price price " +
+                        "on first_level.channel_shortname = price.channel_shortname and first_level.subchannel = price.subchannel " +
+                        "and first_level.dt = price.dt and first_level.type = price.type and first_level.tag = price.tag " +
+                        "WHERE first_level.dt = %s and first_level.type = '企微外部' and first_level.tag = '分投放渠道客户分账号去重' ;", dt);
         List<Record> outDataList = OdpsUtil.getOdpsData(outSql);
         List<ContentPlatformAccount> accountList = getAllAccount();
         Map<String, ContentPlatformAccount> accountMap = accountList.stream()

+ 7 - 0
api-module/src/main/java/com/tzld/piaoquan/api/job/contentplatform/ContentPlatformIllegalVideoJob.java

@@ -130,6 +130,13 @@ public class ContentPlatformIllegalVideoJob {
         illegalMsg.setVideoId(videoId);
         illegalMsg.setTitle(planList.get(0).getTitle());
         illegalMsg.setBusinessType(businessType);
+        String videoTitle = illegalMsg.getTitle();
+        if (videoTitle.length() >= 20) {
+            videoTitle = videoTitle.substring(0, 19) + "...";
+        }
+        illegalMsg.setIllegalTitle("视频“" + videoTitle + "”被封禁,请及时替换");
+        illegalMsg.setIllegalContent("您在" + illegalMsg.getBusinessType() + "栏目中使用的视频“" +
+                videoTitle + "”(视频ID:" + illegalMsg.getVideoId() + ")被微信封禁,请及时替换为其他视频。");
         illegalMsg.setStatus(0);
         illegalMsg.setCreateTimestamp(now);
         illegalMsg.setUpdateTimestamp(now);

+ 7 - 1
offline-module/src/main/java/com/tzld/piaoquan/offline/job/WeComMessageDataJob.java → api-module/src/main/java/com/tzld/piaoquan/api/job/wecom/WeComMessageDataJob.java

@@ -1,10 +1,11 @@
-package com.tzld.piaoquan.offline.job;
+package com.tzld.piaoquan.api.job.wecom;
 
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
 import com.aliyun.odps.data.Record;
 import com.google.common.collect.Lists;
+import com.tzld.piaoquan.api.service.VideoMultiService;
 import com.tzld.piaoquan.growth.common.common.base.CommonResponse;
 import com.tzld.piaoquan.growth.common.common.constant.MessageConstant;
 import com.tzld.piaoquan.growth.common.dao.mapper.*;
@@ -67,6 +68,9 @@ public class WeComMessageDataJob {
     @Autowired
     private WeComUserService weComUserService;
 
+    @Autowired
+    private VideoMultiService videoMultiService;
+
     //发送小程序标题限制字节数
     private static final int MAX_BYTES = 64;
 
@@ -551,8 +555,10 @@ public class WeComMessageDataJob {
             String key = staff.getCarrierId() + "_" + videoId;
             if (pageMap.containsKey(key)) {
                 page = pageMap.get(key);
+                page = videoMultiService.setVideoMultiTitleCoverPagePath(videoId, page, title, messageAttachment.getCover());
             } else {
                 page = messageAttachmentService.getPage("touliu", "tencentqw", staff, videoId, "企微", "日常推送");
+                page = videoMultiService.setVideoMultiTitleCoverPagePath(videoId, page, title, messageAttachment.getCover());
                 pageMap.put(key, page);
             }
             if (StringUtils.isEmpty(page)) {

+ 4 - 0
api-module/src/main/java/com/tzld/piaoquan/api/job/wecom/thirdpart/WeComSendMsgJob.java

@@ -14,6 +14,7 @@ import com.tzld.piaoquan.api.model.param.wecom.thirdpart.*;
 import com.tzld.piaoquan.api.model.po.contentplatform.ContentPlatformVideo;
 import com.tzld.piaoquan.api.model.po.wecom.thirdpart.*;
 import com.tzld.piaoquan.api.model.vo.contentplatform.WxVideoV2VO;
+import com.tzld.piaoquan.api.service.VideoMultiService;
 import com.tzld.piaoquan.api.service.wecom.thirdparty.WeComThirdPartyAccountService;
 import com.tzld.piaoquan.api.service.wecom.thirdparty.WeComThirdPartyRoomService;
 import com.tzld.piaoquan.api.service.wecom.thirdparty.WeComThirdPartyService;
@@ -64,6 +65,8 @@ public class WeComSendMsgJob {
     ThirdPartWeComRoomMapper roomMapper;
     @Autowired
     ThirdPartWeComMsgMapper msgMapper;
+    @Autowired
+    private VideoMultiService videoMultiService;
 
     @Autowired
     RedisUtils redisUtils;
@@ -297,6 +300,7 @@ public class WeComSendMsgJob {
         String putScene = StringUtils.isNotBlank(roomConfig.getPutScene()) ? roomConfig.getPutScene() : "touliu";
         String pageChannel = StringUtils.isNotBlank(roomConfig.getChannel()) ? roomConfig.getChannel() : "tencentqw";
         String page = messageAttachmentService.getPageNoCache(putScene, pageChannel, staff, video.getVideoId(), "企微", "社群");
+        page = videoMultiService.setVideoMultiTitleCoverPagePath(video.getVideoId(), page, video.getTitle(), video.getCover());
 
         CgiReplyBucketData cgiReplyBucketData = new CgiReplyBucketData();
         cgiReplyBucketData.setMiniVideoId(video.getVideoId());

+ 2 - 1
api-module/src/main/java/com/tzld/piaoquan/api/job/wecom/thirdpart/WeComUserDetailJob.java

@@ -67,7 +67,7 @@ public class WeComUserDetailJob {
     @Value("${create.room.auto.send.msg:false}")
     private Boolean autoSendMsg;
 
-    private final static ExecutorService pool = new ThreadPoolExecutor(10, 10, 0L, TimeUnit.SECONDS,
+    private final static ExecutorService pool = new ThreadPoolExecutor(5, 5, 0L, TimeUnit.SECONDS,
             new LinkedBlockingQueue<>(10000),
             new ThreadFactoryBuilder().setNameFormat("SyncUserDetailJob-%d").build(),
             new ThreadPoolExecutor.AbortPolicy());
@@ -416,6 +416,7 @@ public class WeComUserDetailJob {
         }
         // 删除不存在的用户
         List<Long> deleteList = existUserList.stream()
+                .filter(user -> Objects.isNull(user.getQuitTime()))
                 .map(ThirdPartWeComRoomUser::getUin)
                 .filter(uin -> !currentUniList.contains(uin))
                 .collect(Collectors.toList());

+ 36 - 0
api-module/src/main/java/com/tzld/piaoquan/api/model/bo/AdPutCreativeComponentCostData.java

@@ -0,0 +1,36 @@
+package com.tzld.piaoquan.api.model.bo;
+
+import lombok.Data;
+
+import java.util.Date;
+import java.util.List;
+
+@Data
+public class AdPutCreativeComponentCostData {
+    private Long accountId;
+    private Long adId;
+    private Long creativeId;
+    private String creativeName;
+    private Long cost;
+    private String wxId;
+    private List<Long> componentIds;
+    private List<AdPutTencentComponent> componentList;
+
+    @Data
+    public static class AdPutTencentComponent {
+        private Long id;
+        private Long accountId;
+        private Long componentId;
+        private String componentCustomName;
+        private String componentSubType;
+        private String generationType;
+        private Byte isDeleted;
+        private Long imageId;
+        private String imageUrl;
+        private Long videoId;
+        private Date createTime;
+        private Date updateTime;
+        private String videoCoverUrl;
+        private String videoUrl;
+    }
+}

+ 28 - 0
api-module/src/main/java/com/tzld/piaoquan/api/model/bo/GoogleLLMResult.java

@@ -0,0 +1,28 @@
+package com.tzld.piaoquan.api.model.bo;
+
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+@Data
+@Accessors(chain = true)
+public class GoogleLLMResult {
+
+    /**
+     * 生成结果
+     */
+    private String result;
+    /**
+     * 是否成功
+     */
+    private Boolean success;
+    /**
+     * 错误信息
+     */
+    private String errorMessage;
+    /**
+     * 是否可以重试
+     */
+    private Boolean enableRetry;
+    private String respBodyString;
+
+}

+ 39 - 0
api-module/src/main/java/com/tzld/piaoquan/api/model/dto/AIOfficialApiResponse.java

@@ -0,0 +1,39 @@
+package com.tzld.piaoquan.api.model.dto;
+
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class AIOfficialApiResponse {
+
+    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
api-module/src/main/java/com/tzld/piaoquan/api/model/dto/AIResult.java

@@ -0,0 +1,11 @@
+package com.tzld.piaoquan.api.model.dto;
+
+import lombok.Data;
+
+@Data
+public class AIResult {
+    private boolean success;
+    private AIOfficialApiResponse response;
+    private String failReason;
+    private String responseStr;
+}

+ 22 - 0
api-module/src/main/java/com/tzld/piaoquan/api/model/po/GhDetailExt.java

@@ -11,8 +11,12 @@ public class GhDetailExt {
 
     private String page;
 
+    private Long titleId;
+
     private String title;
 
+    private Long coverId;
+
     private String cover;
 
     private Integer sort;
@@ -55,6 +59,14 @@ public class GhDetailExt {
         this.page = page;
     }
 
+    public Long getTitleId() {
+        return titleId;
+    }
+
+    public void setTitleId(Long titleId) {
+        this.titleId = titleId;
+    }
+
     public String getTitle() {
         return title;
     }
@@ -63,6 +75,14 @@ public class GhDetailExt {
         this.title = title;
     }
 
+    public Long getCoverId() {
+        return coverId;
+    }
+
+    public void setCoverId(Long coverId) {
+        this.coverId = coverId;
+    }
+
     public String getCover() {
         return cover;
     }
@@ -113,7 +133,9 @@ public class GhDetailExt {
         sb.append(", ghDetailId=").append(ghDetailId);
         sb.append(", videoId=").append(videoId);
         sb.append(", page=").append(page);
+        sb.append(", titleId=").append(titleId);
         sb.append(", title=").append(title);
+        sb.append(", coverId=").append(coverId);
         sb.append(", cover=").append(cover);
         sb.append(", sort=").append(sort);
         sb.append(", isDelete=").append(isDelete);

+ 120 - 0
api-module/src/main/java/com/tzld/piaoquan/api/model/po/GhDetailExtExample.java

@@ -366,6 +366,66 @@ public class GhDetailExtExample {
             return (Criteria) this;
         }
 
+        public Criteria andTitleIdIsNull() {
+            addCriterion("title_id is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andTitleIdIsNotNull() {
+            addCriterion("title_id is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andTitleIdEqualTo(Long value) {
+            addCriterion("title_id =", value, "titleId");
+            return (Criteria) this;
+        }
+
+        public Criteria andTitleIdNotEqualTo(Long value) {
+            addCriterion("title_id <>", value, "titleId");
+            return (Criteria) this;
+        }
+
+        public Criteria andTitleIdGreaterThan(Long value) {
+            addCriterion("title_id >", value, "titleId");
+            return (Criteria) this;
+        }
+
+        public Criteria andTitleIdGreaterThanOrEqualTo(Long value) {
+            addCriterion("title_id >=", value, "titleId");
+            return (Criteria) this;
+        }
+
+        public Criteria andTitleIdLessThan(Long value) {
+            addCriterion("title_id <", value, "titleId");
+            return (Criteria) this;
+        }
+
+        public Criteria andTitleIdLessThanOrEqualTo(Long value) {
+            addCriterion("title_id <=", value, "titleId");
+            return (Criteria) this;
+        }
+
+        public Criteria andTitleIdIn(List<Long> values) {
+            addCriterion("title_id in", values, "titleId");
+            return (Criteria) this;
+        }
+
+        public Criteria andTitleIdNotIn(List<Long> values) {
+            addCriterion("title_id not in", values, "titleId");
+            return (Criteria) this;
+        }
+
+        public Criteria andTitleIdBetween(Long value1, Long value2) {
+            addCriterion("title_id between", value1, value2, "titleId");
+            return (Criteria) this;
+        }
+
+        public Criteria andTitleIdNotBetween(Long value1, Long value2) {
+            addCriterion("title_id not between", value1, value2, "titleId");
+            return (Criteria) this;
+        }
+
         public Criteria andTitleIsNull() {
             addCriterion("title is null");
             return (Criteria) this;
@@ -436,6 +496,66 @@ public class GhDetailExtExample {
             return (Criteria) this;
         }
 
+        public Criteria andCoverIdIsNull() {
+            addCriterion("cover_id is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andCoverIdIsNotNull() {
+            addCriterion("cover_id is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andCoverIdEqualTo(Long value) {
+            addCriterion("cover_id =", value, "coverId");
+            return (Criteria) this;
+        }
+
+        public Criteria andCoverIdNotEqualTo(Long value) {
+            addCriterion("cover_id <>", value, "coverId");
+            return (Criteria) this;
+        }
+
+        public Criteria andCoverIdGreaterThan(Long value) {
+            addCriterion("cover_id >", value, "coverId");
+            return (Criteria) this;
+        }
+
+        public Criteria andCoverIdGreaterThanOrEqualTo(Long value) {
+            addCriterion("cover_id >=", value, "coverId");
+            return (Criteria) this;
+        }
+
+        public Criteria andCoverIdLessThan(Long value) {
+            addCriterion("cover_id <", value, "coverId");
+            return (Criteria) this;
+        }
+
+        public Criteria andCoverIdLessThanOrEqualTo(Long value) {
+            addCriterion("cover_id <=", value, "coverId");
+            return (Criteria) this;
+        }
+
+        public Criteria andCoverIdIn(List<Long> values) {
+            addCriterion("cover_id in", values, "coverId");
+            return (Criteria) this;
+        }
+
+        public Criteria andCoverIdNotIn(List<Long> values) {
+            addCriterion("cover_id not in", values, "coverId");
+            return (Criteria) this;
+        }
+
+        public Criteria andCoverIdBetween(Long value1, Long value2) {
+            addCriterion("cover_id between", value1, value2, "coverId");
+            return (Criteria) this;
+        }
+
+        public Criteria andCoverIdNotBetween(Long value1, Long value2) {
+            addCriterion("cover_id not between", value1, value2, "coverId");
+            return (Criteria) this;
+        }
+
         public Criteria andCoverIsNull() {
             addCriterion("cover is null");
             return (Criteria) this;

+ 22 - 0
api-module/src/main/java/com/tzld/piaoquan/api/model/po/contentplatform/ContentPlatformIllegalMsg.java

@@ -11,6 +11,10 @@ public class ContentPlatformIllegalMsg {
 
     private String businessType;
 
+    private String illegalTitle;
+
+    private String illegalContent;
+
     private Integer status;
 
     private Long createTimestamp;
@@ -57,6 +61,22 @@ public class ContentPlatformIllegalMsg {
         this.businessType = businessType;
     }
 
+    public String getIllegalTitle() {
+        return illegalTitle;
+    }
+
+    public void setIllegalTitle(String illegalTitle) {
+        this.illegalTitle = illegalTitle;
+    }
+
+    public String getIllegalContent() {
+        return illegalContent;
+    }
+
+    public void setIllegalContent(String illegalContent) {
+        this.illegalContent = illegalContent;
+    }
+
     public Integer getStatus() {
         return status;
     }
@@ -92,6 +112,8 @@ public class ContentPlatformIllegalMsg {
         sb.append(", videoId=").append(videoId);
         sb.append(", title=").append(title);
         sb.append(", businessType=").append(businessType);
+        sb.append(", illegalTitle=").append(illegalTitle);
+        sb.append(", illegalContent=").append(illegalContent);
         sb.append(", status=").append(status);
         sb.append(", createTimestamp=").append(createTimestamp);
         sb.append(", updateTimestamp=").append(updateTimestamp);

+ 140 - 0
api-module/src/main/java/com/tzld/piaoquan/api/model/po/contentplatform/ContentPlatformIllegalMsgExample.java

@@ -435,6 +435,146 @@ public class ContentPlatformIllegalMsgExample {
             return (Criteria) this;
         }
 
+        public Criteria andIllegalTitleIsNull() {
+            addCriterion("illegal_title is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andIllegalTitleIsNotNull() {
+            addCriterion("illegal_title is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andIllegalTitleEqualTo(String value) {
+            addCriterion("illegal_title =", value, "illegalTitle");
+            return (Criteria) this;
+        }
+
+        public Criteria andIllegalTitleNotEqualTo(String value) {
+            addCriterion("illegal_title <>", value, "illegalTitle");
+            return (Criteria) this;
+        }
+
+        public Criteria andIllegalTitleGreaterThan(String value) {
+            addCriterion("illegal_title >", value, "illegalTitle");
+            return (Criteria) this;
+        }
+
+        public Criteria andIllegalTitleGreaterThanOrEqualTo(String value) {
+            addCriterion("illegal_title >=", value, "illegalTitle");
+            return (Criteria) this;
+        }
+
+        public Criteria andIllegalTitleLessThan(String value) {
+            addCriterion("illegal_title <", value, "illegalTitle");
+            return (Criteria) this;
+        }
+
+        public Criteria andIllegalTitleLessThanOrEqualTo(String value) {
+            addCriterion("illegal_title <=", value, "illegalTitle");
+            return (Criteria) this;
+        }
+
+        public Criteria andIllegalTitleLike(String value) {
+            addCriterion("illegal_title like", value, "illegalTitle");
+            return (Criteria) this;
+        }
+
+        public Criteria andIllegalTitleNotLike(String value) {
+            addCriterion("illegal_title not like", value, "illegalTitle");
+            return (Criteria) this;
+        }
+
+        public Criteria andIllegalTitleIn(List<String> values) {
+            addCriterion("illegal_title in", values, "illegalTitle");
+            return (Criteria) this;
+        }
+
+        public Criteria andIllegalTitleNotIn(List<String> values) {
+            addCriterion("illegal_title not in", values, "illegalTitle");
+            return (Criteria) this;
+        }
+
+        public Criteria andIllegalTitleBetween(String value1, String value2) {
+            addCriterion("illegal_title between", value1, value2, "illegalTitle");
+            return (Criteria) this;
+        }
+
+        public Criteria andIllegalTitleNotBetween(String value1, String value2) {
+            addCriterion("illegal_title not between", value1, value2, "illegalTitle");
+            return (Criteria) this;
+        }
+
+        public Criteria andIllegalContentIsNull() {
+            addCriterion("illegal_content is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andIllegalContentIsNotNull() {
+            addCriterion("illegal_content is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andIllegalContentEqualTo(String value) {
+            addCriterion("illegal_content =", value, "illegalContent");
+            return (Criteria) this;
+        }
+
+        public Criteria andIllegalContentNotEqualTo(String value) {
+            addCriterion("illegal_content <>", value, "illegalContent");
+            return (Criteria) this;
+        }
+
+        public Criteria andIllegalContentGreaterThan(String value) {
+            addCriterion("illegal_content >", value, "illegalContent");
+            return (Criteria) this;
+        }
+
+        public Criteria andIllegalContentGreaterThanOrEqualTo(String value) {
+            addCriterion("illegal_content >=", value, "illegalContent");
+            return (Criteria) this;
+        }
+
+        public Criteria andIllegalContentLessThan(String value) {
+            addCriterion("illegal_content <", value, "illegalContent");
+            return (Criteria) this;
+        }
+
+        public Criteria andIllegalContentLessThanOrEqualTo(String value) {
+            addCriterion("illegal_content <=", value, "illegalContent");
+            return (Criteria) this;
+        }
+
+        public Criteria andIllegalContentLike(String value) {
+            addCriterion("illegal_content like", value, "illegalContent");
+            return (Criteria) this;
+        }
+
+        public Criteria andIllegalContentNotLike(String value) {
+            addCriterion("illegal_content not like", value, "illegalContent");
+            return (Criteria) this;
+        }
+
+        public Criteria andIllegalContentIn(List<String> values) {
+            addCriterion("illegal_content in", values, "illegalContent");
+            return (Criteria) this;
+        }
+
+        public Criteria andIllegalContentNotIn(List<String> values) {
+            addCriterion("illegal_content not in", values, "illegalContent");
+            return (Criteria) this;
+        }
+
+        public Criteria andIllegalContentBetween(String value1, String value2) {
+            addCriterion("illegal_content between", value1, value2, "illegalContent");
+            return (Criteria) this;
+        }
+
+        public Criteria andIllegalContentNotBetween(String value1, String value2) {
+            addCriterion("illegal_content not between", value1, value2, "illegalContent");
+            return (Criteria) this;
+        }
+
         public Criteria andStatusIsNull() {
             addCriterion("`status` is null");
             return (Criteria) this;

+ 1 - 1
api-module/src/main/java/com/tzld/piaoquan/api/model/vo/contentplatform/GzhAccountVideoDatastatItemExportVO.java

@@ -31,6 +31,6 @@ public class GzhAccountVideoDatastatItemExportVO {
     @ExcelProperty("小程序访问人数")
     private Integer firstLevel;
 
-    @ExcelProperty("本渠道裂变率")
+    @ExcelProperty("本渠道质量分")
     private Double score;
 }

+ 1 - 1
api-module/src/main/java/com/tzld/piaoquan/api/model/vo/contentplatform/GzhDatastatItemExportVO.java

@@ -34,6 +34,6 @@ public class GzhDatastatItemExportVO {
     @ExcelProperty("打开率")
     private Double openRate;
 
-    @ExcelProperty("本渠道裂变率")
+    @ExcelProperty("本渠道质量分")
     private Double score;
 }

+ 1 - 1
api-module/src/main/java/com/tzld/piaoquan/api/model/vo/contentplatform/GzhDatastatItemVO.java

@@ -30,7 +30,7 @@ public class GzhDatastatItemVO {
     @ApiModelProperty(value = "打开率")
     private Double openRate;
 
-    @ApiModelProperty(value = "分")
+    @ApiModelProperty(value = "本渠道质量分")
     private Double score;
 
     @ApiModelProperty(value = "预估单价")

+ 1 - 1
api-module/src/main/java/com/tzld/piaoquan/api/model/vo/contentplatform/GzhTotalDatastatItemExportVO.java

@@ -28,7 +28,7 @@ public class GzhTotalDatastatItemExportVO {
     @ExcelProperty("打开率")
     private Double openRate;
 
-    @ExcelProperty("本渠道裂变率")
+    @ExcelProperty("本渠道质量分")
     private Double score;
 
     @ExcelProperty(value = "预估单价")

+ 5 - 0
api-module/src/main/java/com/tzld/piaoquan/api/service/GhDetailService.java

@@ -1,6 +1,7 @@
 package com.tzld.piaoquan.api.service;
 
 
+import com.tzld.piaoquan.api.model.po.GhDetailExt;
 import com.tzld.piaoquan.api.model.vo.GhDetailVo;
 import com.tzld.piaoquan.growth.common.common.base.CommonResponse;
 import com.tzld.piaoquan.growth.common.model.po.GhDetail;
@@ -23,4 +24,8 @@ public interface GhDetailService {
     GhDetail getGhDetailByGhId(String ghId);
 
     List<GhDetail> getByChannel(String channel);
+
+    List<GhDetail> getGhdetailByNames(List<String> nameList);
+
+    List<GhDetailExt> getGhDetailExtList(String ghId, Integer type);
 }

+ 84 - 0
api-module/src/main/java/com/tzld/piaoquan/api/service/VideoMultiService.java

@@ -0,0 +1,84 @@
+package com.tzld.piaoquan.api.service;
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.tzld.piaoquan.api.component.ManagerApiService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+
+import java.net.URLEncoder;
+import java.util.Objects;
+
+@Slf4j
+@Service
+public class VideoMultiService {
+
+    @Autowired
+    private ManagerApiService managerApiService;
+
+    public String setVideoMultiTitleCoverPagePath(Long videoId, String pageUrl, String title, String coverUrl) {
+        JSONArray multiTitleList = managerApiService.videoMultiTitleListV2(videoId);
+        JSONArray multiCoverList = managerApiService.videoMultiCoverListV2(videoId);
+        if (!CollectionUtils.isEmpty(multiTitleList)) {
+            Integer titleId = null;
+            for (int i = 0; i < multiTitleList.size(); i++) {
+                JSONObject item = multiTitleList.getJSONObject(i);
+                if (item.getInteger("source") == 0) {
+                    titleId = item.getInteger("id");
+                    break;
+                }
+            }
+            if (Objects.isNull(titleId)) {
+                for (int i = 0; i < multiTitleList.size(); i++) {
+                    JSONObject item = multiTitleList.getJSONObject(i);
+                    if (title.equals(item.getString("title"))) {
+                        titleId = item.getInteger("id");
+                        break;
+                    }
+                }
+            }
+            try {
+                //if (Objects.nonNull(title) && !pageUrl.contains("shareTitle")) {
+                //    pageUrl += URLEncoder.encode("&shareTitle=" + title, "UTF-8");
+                //}
+                if (Objects.nonNull(titleId) && !pageUrl.contains("shareTitleId")) {
+                    pageUrl += URLEncoder.encode("&shareTitleId=" + titleId, "UTF-8");
+                }
+            } catch (Exception e) {
+                log.error("ThirdPartyPushMessageStrategyV1 insertSmallData setCustomerCoverTitleId Error,data:", e);
+            }
+        }
+        if (!CollectionUtils.isEmpty(multiCoverList)) {
+            Integer coverId = null;
+            for (int i = 0; i < multiCoverList.size(); i++) {
+                JSONObject item = multiCoverList.getJSONObject(i);
+                if (item.getInteger("source") == 0) {
+                    coverId = item.getInteger("id");
+                    break;
+                }
+            }
+            if (Objects.isNull(coverId)) {
+                for (int i = 0; i < multiCoverList.size(); i++) {
+                    JSONObject item = multiCoverList.getJSONObject(i);
+                    if (coverUrl.equals(item.getString("coverUrl"))) {
+                        coverId = item.getInteger("id");
+                        break;
+                    }
+                }
+            }
+            try {
+                if (Objects.nonNull(coverId) && !pageUrl.contains("shareImageId")) {
+                    pageUrl += URLEncoder.encode("&shareImageId=" + coverId, "UTF-8");
+                }
+                //if (StringUtils.isNotEmpty(coverUrl) && !pageUrl.contains("shareImageUrl")) {
+                //    pageUrl += URLEncoder.encode("&shareImageUrl=" + URLEncoder.encode(coverUrl, "UTF-8"), "UTF-8");
+                //}
+            } catch (Exception e) {
+                log.error("ThirdPartyPushMessageStrategyV1 insertSmallData setCustomerCoverTitleId Error,data:", e);
+            }
+        }
+        return pageUrl;
+    }
+}

+ 2 - 9
api-module/src/main/java/com/tzld/piaoquan/api/service/contentplatform/impl/ContentPlatformNoticeServiceImpl.java

@@ -40,15 +40,8 @@ public class ContentPlatformNoticeServiceImpl implements ContentPlatformNoticeSe
         for (ContentPlatformIllegalMsg item : list) {
             NoticeItemVO vo = new NoticeItemVO();
             vo.setId(item.getId());
-            String videoTitle = item.getTitle();
-            if (videoTitle.length() >= 20) {
-                videoTitle = videoTitle.substring(0, 19) + "...";
-            }
-            String title = "视频“" + videoTitle + "”被封禁,请及时替换";
-            vo.setTitle(title);
-            String msg = "您在" + item.getBusinessType() + "栏目中使用的视频“" +
-                    videoTitle + "”(视频ID:" + item.getVideoId() + ")被微信封禁,请及时替换为其他视频。";
-            vo.setMsg(msg);
+            vo.setTitle(item.getIllegalTitle());
+            vo.setMsg(item.getIllegalContent());
             vo.setStatus(item.getStatus());
             vo.setCreateTimestamp(item.getCreateTimestamp());
             records.add(vo);

+ 60 - 9
api-module/src/main/java/com/tzld/piaoquan/api/service/contentplatform/impl/ContentPlatformPlanServiceImpl.java

@@ -21,6 +21,7 @@ import com.tzld.piaoquan.api.model.vo.contentplatform.QwPlanItemVO;
 import com.tzld.piaoquan.api.model.vo.contentplatform.VideoContentItemVO;
 import com.tzld.piaoquan.api.service.CgiReplyService;
 import com.tzld.piaoquan.api.service.GhDetailService;
+import com.tzld.piaoquan.api.service.VideoMultiService;
 import com.tzld.piaoquan.api.service.contentplatform.ContentPlatformAccountService;
 import com.tzld.piaoquan.api.service.contentplatform.ContentPlatformCollectContentService;
 import com.tzld.piaoquan.api.service.contentplatform.ContentPlatformCooperateAccountService;
@@ -99,6 +100,8 @@ public class ContentPlatformPlanServiceImpl implements ContentPlatformPlanServic
     ContentPlatformCollectContentService collectService;
     @Autowired
     private ManagerApiService managerApiService;
+    @Autowired
+    private VideoMultiService videoMultiService;
 
 
     @Value("${vlog.share.appType:11}")
@@ -143,7 +146,7 @@ public class ContentPlatformPlanServiceImpl implements ContentPlatformPlanServic
         List<CgiReplyBucketData> bucketDataList = cgiReplyService.getCgiReplyBucketDataListByGhIds(ghIds, "manual");
         Map<String, Map<Long, CgiReplyBucketData>> bucketDataMap = bucketDataList.stream()
                 .collect(Collectors.groupingBy(CgiReplyBucketData::getGhId,
-                        Collectors.toMap(CgiReplyBucketData::getMiniVideoId, Function.identity())));
+                        Collectors.toMap(CgiReplyBucketData::getMiniVideoId, Function.identity(), (a, b) -> b)));
         List<GzhPlanItemVO> result = new ArrayList<>();
         for (ContentPlatformGzhPlan gzhPlan : planList) {
             GzhPlanItemVO planItemVO = new GzhPlanItemVO();
@@ -197,11 +200,17 @@ public class ContentPlatformPlanServiceImpl implements ContentPlatformPlanServic
     private String buildCustomPageUrl(GzhPlanVideoContentItemVO videoItemVO, ContentPlatformGzhPlanVideo video) {
         String pageUrl = videoItemVO.getPageUrl();
         try {
-            if (Objects.nonNull(video.getCustomTitleId()) && StringUtils.hasText(videoItemVO.getCustomTitle())) {
-                pageUrl += URLEncoder.encode("&shareTitleId=" + video.getCustomTitleId() + "&shareTitle=" + video.getCustomTitle(), "UTF-8");
+            if (StringUtils.hasText(videoItemVO.getCustomTitle()) && !pageUrl.contains("shareTitleId")) {
+                if (Objects.nonNull(video.getCustomTitleId())) {
+                    pageUrl += URLEncoder.encode("&shareTitleId=" + video.getCustomTitleId(), "UTF-8");
+                }
+                //pageUrl += URLEncoder.encode("&shareTitle=" + video.getCustomTitle(), "UTF-8");
             }
-            if (Objects.nonNull(video.getCustomCoverId()) && StringUtils.hasText(videoItemVO.getCustomCover())) {
-                pageUrl += URLEncoder.encode("&shareImageId=" + video.getCustomCoverId() + "&shareImageUrl=" + URLEncoder.encode(video.getCustomCover(), "UTF-8"), "UTF-8");
+            if (StringUtils.hasText(videoItemVO.getCustomCover()) && !pageUrl.contains("shareImageId")) {
+                if (Objects.nonNull(video.getCustomCoverId())) {
+                    pageUrl += URLEncoder.encode("&shareImageId=" + video.getCustomCoverId(), "UTF-8");
+                }
+            //    pageUrl += URLEncoder.encode("&shareImageUrl=" +  URLEncoder.encode(video.getCustomCover(), "UTF-8"), "UTF-8");
             }
             return pageUrl;
         } catch (UnsupportedEncodingException e) {
@@ -331,7 +340,7 @@ public class ContentPlatformPlanServiceImpl implements ContentPlatformPlanServic
         // 调用aigc重新拉取视频
         if (param.getType() == ContentPlatformGzhPlanTypeEnum.AUTO_REPLY.getVal()) {
             if (PublishStageEnum.PLATFORM.getVal() == param.getPublishStage()) {
-                aigcApiService.refreshGzhAutoReplyMsgData(account.getGhId());
+                new Thread(() -> aigcApiService.refreshGzhAutoReplyMsgData(account.getGhId())).start();
             } else {
                 BucketDataParam bucketDataParam = new BucketDataParam();
                 bucketDataParam.setGhId(account.getGhId());
@@ -373,6 +382,8 @@ public class ContentPlatformPlanServiceImpl implements ContentPlatformPlanServic
                 setCustomCover(item, vo);
                 item.setCustomCoverType(vo.getCustomCoverType());
                 item.setStatus(VideoStatusEnum.NORMAL.getVal());
+                // 上报多标题封面
+                item.setPageUrl(setMultiTitleCoverPagePath(item));
                 gzhPlanVideoMapper.updateByPrimaryKey(item);
             } else {
                 ContentPlatformGzhPlanVideo item = new ContentPlatformGzhPlanVideo();
@@ -398,6 +409,8 @@ public class ContentPlatformPlanServiceImpl implements ContentPlatformPlanServic
                     item.setPageUrl(smallPageUrlDetail.getUrl());
                 }
                 item.setStatus(VideoStatusEnum.NORMAL.getVal());
+                // 上报多标题封面
+                item.setPageUrl(setMultiTitleCoverPagePath(item));
                 item.setCreateAccountId(loginAccount.getId());
                 item.setCreateTimestamp(System.currentTimeMillis());
                 gzhPlanVideoMapper.insert(item);
@@ -408,20 +421,56 @@ public class ContentPlatformPlanServiceImpl implements ContentPlatformPlanServic
         }
     }
 
+    private String setMultiTitleCoverPagePath(ContentPlatformGzhPlanVideo item) {
+        String pageUrl = item.getPageUrl();
+        if(StringUtils.hasText(pageUrl)){
+            String title = item.getTitle();
+            String cover = item.getCover();
+            try {
+                //if (!pageUrl.contains("shareTitle")) {
+                //    if (StringUtils.hasText(item.getCustomTitle())) {
+                //        pageUrl += URLEncoder.encode("&shareTitle=" + item.getCustomTitle(), "UTF-8");
+                //        title = item.getCustomTitle();
+                //    } else {
+                //        pageUrl += URLEncoder.encode("&shareTitle=" + item.getTitle(), "UTF-8");
+                //    }
+                //}
+                if (Objects.nonNull(item.getCustomTitleId()) && !pageUrl.contains("shareTitleId")) {
+                    pageUrl += URLEncoder.encode("&shareTitleId=" + item.getCustomTitleId(), "UTF-8");
+                }
+                if (Objects.nonNull(item.getCustomCoverId()) && !pageUrl.contains("shareImageId")) {
+                    pageUrl += URLEncoder.encode("&shareImageId=" + item.getCustomCoverId(), "UTF-8");
+                }
+                //if (!pageUrl.contains("shareImageUrl")) {
+                //    if (StringUtils.hasText(item.getCustomCover())) {
+                //        pageUrl += URLEncoder.encode("&shareImageUrl=" + URLEncoder.encode(item.getCustomCover(), "UTF-8"), "UTF-8");
+                //        cover = item.getCustomCover();
+                //    } else {
+                //        pageUrl += URLEncoder.encode("&shareImageUrl=" + URLEncoder.encode(item.getCover(), "UTF-8"), "UTF-8");
+                //    }
+                //}
+            } catch (Exception e) {
+                log.error("设置视频多标题封面失败,videoId={}", item.getVideoId(), e);
+            }
+            pageUrl = videoMultiService.setVideoMultiTitleCoverPagePath(item.getVideoId(), pageUrl, title, cover);
+        }
+        return pageUrl;
+    }
+
     private void setCustomTitle(ContentPlatformGzhPlanVideo item, GzhPlanVideoContentItemParam vo) {
         if (StringUtils.hasText(vo.getCustomTitle())) {
             List<ContentPlatformGzhPlanVideo> gzhPlanVideoList = gzhPlanVideoListByVideoTitle(vo.getVideoId());
-            boolean customCoverExist = false;
+            boolean customTitleExist = false;
             if (CollectionUtils.isNotEmpty(gzhPlanVideoList)) {
                 for (ContentPlatformGzhPlanVideo gzhPlanVideo : gzhPlanVideoList) {
                     if (gzhPlanVideo.getCustomTitle().equals(vo.getCustomTitle())) {
                         item.setCustomTitleId(gzhPlanVideo.getCustomTitleId());
-                        customCoverExist = true;
+                        customTitleExist = true;
                         break;
                     }
                 }
             }
-            if (!customCoverExist && vo.getCustomTitle().length() > 5) {
+            if (!customTitleExist && vo.getCustomTitle().length() > 5) {
                 JSONObject multiTitle = managerApiService.videoMultiTitleSave(vo.getVideoId(), vo.getCustomTitle());
                 item.setCustomTitleId(multiTitle.getLong("id"));
             }
@@ -927,6 +976,8 @@ public class ContentPlatformPlanServiceImpl implements ContentPlatformPlanServic
             String pageUrl = messageAttachmentService.getPage(loginUser.getChannel(), carrierId,
                     "dyyqw", "企微", QwPlanTypeEnum.from(param.getType()).getDescription(),
                     "位置1", videoParam.getVideoId());
+            pageUrl = videoMultiService.setVideoMultiTitleCoverPagePath(videoParam.getVideoId(), pageUrl,
+                    videoParam.getTitle(), videoParam.getCover());
             String rootSourceId = MessageUtil.getRootSourceId(pageUrl);
             qwPlan.setPageUrl(pageUrl);
             qwPlan.setRootSourceId(rootSourceId);

+ 65 - 7
api-module/src/main/java/com/tzld/piaoquan/api/service/impl/GhDetailServiceImpl.java

@@ -4,6 +4,7 @@ import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
 import com.tzld.piaoquan.api.component.AigcApiService;
+import com.tzld.piaoquan.api.component.ManagerApiService;
 import com.tzld.piaoquan.api.component.TouLiuHttpClient;
 import com.tzld.piaoquan.api.dao.mapper.GhDetailExtMapper;
 import com.tzld.piaoquan.api.dao.mapper.GhDetailMapperExt;
@@ -49,6 +50,8 @@ public class GhDetailServiceImpl implements GhDetailService {
     private TouLiuHttpClient touLiuHttpClient;
     @Autowired
     private CgiReplyBucketDataMapper cgiReplyBucketDataMapper;
+    @Autowired
+    private ManagerApiService managerApiService;
 
     @Value("${small_page_url}")
     private String GET_SMALL_PAGE_URL;
@@ -164,7 +167,7 @@ public class GhDetailServiceImpl implements GhDetailService {
             ghDetailMapper.insertSelective(ghDetail);
             ghDetailMapperExt.deleteOldGhDetailExt(ghDetailVo.getAccountId(), ghDetailVo.getType());
             batchSaveGhDetailExt(ghDetailVo);
-            aigcApiService.refreshGzhAutoReplyMsgData(ghDetailVo.getAccountId());
+            new Thread(() -> aigcApiService.refreshGzhAutoReplyMsgData(ghDetailVo.getAccountId())).start();
             return CommonResponse.success();
         } catch (Exception e) {
             log.error("addGhDetail error", e);
@@ -173,7 +176,7 @@ public class GhDetailServiceImpl implements GhDetailService {
     }
 
     private void batchSaveGhDetailExt(GhDetailVo ghDetailVo) {
-        if (CollectionUtils.isEmpty(ghDetailVo.getVideoIds())) {
+        if (CollectionUtils.isEmpty(ghDetailVo.getVideoList())) {
             return;
         }
         GhDetail ghDetail = getGhDetailByGhIdType(ghDetailVo.getAccountId(), ghDetailVo.getType());
@@ -187,8 +190,8 @@ public class GhDetailServiceImpl implements GhDetailService {
             ghDetailExt.setVideoId(videoDetail.getVideoId());
             ghDetailExt.setPage(getVideoPageUrl(videoDetail.getVideoId(), ghDetailVo.getChannel(),
                     ghDetailVo.getAccountId(), videoDetail.getSort(), ghDetailVo.getType()));
-            ghDetailExt.setTitle(videoDetail.getTitle());
-            ghDetailExt.setCover(videoDetail.getCover());
+            setCustomTitle(ghDetailExt, videoDetail);
+            setCustomCover(ghDetailExt, videoDetail);
             ghDetailExt.setSort(videoDetail.getSort());
             ghDetailExt.setIsDelete(0);
             ghDetailExts.add(ghDetailExt);
@@ -196,6 +199,50 @@ public class GhDetailServiceImpl implements GhDetailService {
         ghDetailMapperExt.batchInsertGhDetailExt(ghDetailExts);
     }
 
+    private void setCustomTitle(GhDetailExt ghDetailExt, GhDetailVo.VideoDetail videoDetail) {
+        if (StringUtils.isNotEmpty(videoDetail.getTitle())) {
+            JSONArray multiTitleList = managerApiService.videoMultiTitleListV2(videoDetail.getVideoId());
+            boolean titleExist = false;
+            if (!CollectionUtils.isEmpty(multiTitleList)) {
+                for (int i = 0; i < multiTitleList.size(); i++) {
+                    JSONObject item = multiTitleList.getJSONObject(i);
+                    if (item.getString("title").equals(videoDetail.getTitle())) {
+                        ghDetailExt.setTitleId(item.getLong("id"));
+                        titleExist = true;
+                        break;
+                    }
+                }
+            }
+            if (!titleExist) {
+                JSONObject multiTitle = managerApiService.videoMultiTitleSave(videoDetail.getVideoId(), videoDetail.getTitle());
+                ghDetailExt.setTitleId(multiTitle.getLong("id"));
+            }
+            ghDetailExt.setTitle(videoDetail.getTitle());
+        }
+    }
+
+    private void setCustomCover(GhDetailExt ghDetailExt, GhDetailVo.VideoDetail videoDetail) {
+        if (StringUtils.isNotEmpty(videoDetail.getCover())) {
+            JSONArray multiCoverList = managerApiService.videoMultiCoverListV2(videoDetail.getVideoId());
+            boolean customCoverExist = false;
+            if (!CollectionUtils.isEmpty(multiCoverList)) {
+                for (int i = 0; i < multiCoverList.size(); i++) {
+                    JSONObject item = multiCoverList.getJSONObject(i);
+                    if (item.getString("coverUrl").equals(videoDetail.getCover())) {
+                        ghDetailExt.setCoverId(item.getLong("id"));
+                        customCoverExist = true;
+                        break;
+                    }
+                }
+            }
+            if (!customCoverExist) {
+                JSONObject multiCover = managerApiService.videoMultiCoverSave(videoDetail.getVideoId(), videoDetail.getCover());
+                ghDetailExt.setCoverId(multiCover.getLong("id"));
+            }
+            ghDetailExt.setCover(videoDetail.getCover());
+        }
+    }
+
     private String getVideoPageUrl(Long videoId, String channel, String ghId, Integer sort, Integer accountType) {
         // 查询库里是否存在,如果存在即复用
         try {
@@ -203,7 +250,7 @@ public class GhDetailServiceImpl implements GhDetailService {
             cgiReplyBucketDataExample.createCriteria().andIsDeleteEqualTo(0).andMiniVideoIdEqualTo(videoId).andGhIdEqualTo(ghId);
             List<CgiReplyBucketData> cgiReplyBucketData = cgiReplyBucketDataMapper.selectByExample(cgiReplyBucketDataExample);
             if (!CollectionUtils.isEmpty(cgiReplyBucketData)) {
-                return cgiReplyBucketData.get(0).getPagePathUrlId().toString();
+                return cgiReplyBucketData.get(0).getMiniPagePath().toString();
             }
             String response;
             if (accountType.equals(GhTypeEnum.GH.type)) {
@@ -275,8 +322,7 @@ public class GhDetailServiceImpl implements GhDetailService {
                 updateGhDetail.setUpdateTime(new Date());
                 ghDetailMapper.updateByPrimaryKey(updateGhDetail);
             }
-            aigcApiService.refreshGzhAutoReplyMsgData(ghDetailVo.getAccountId());
-
+            new Thread(() -> aigcApiService.refreshGzhAutoReplyMsgData(ghDetailVo.getAccountId())).start();
             return CommonResponse.success();
         } catch (Exception e) {
             log.error("updateDetail error", e);
@@ -377,5 +423,17 @@ public class GhDetailServiceImpl implements GhDetailService {
         return ghDetailMapper.selectByExample(ghDetailExample);
     }
 
+    @Override
+    public List<GhDetail> getGhdetailByNames(List<String> nameList) {
+        GhDetailExample ghDetailExample = new GhDetailExample();
+        ghDetailExample.createCriteria().andGhNameIn(nameList).andIsDeleteEqualTo(0);
+        return ghDetailMapper.selectByExample(ghDetailExample);
+    }
+
+    @Override
+    public List<GhDetailExt> getGhDetailExtList(String ghId, Integer type) {
+        return ghDetailMapperExt.getGhDetailExtList(ghId, type);
+    }
+
 
 }

+ 67 - 5
api-module/src/main/java/com/tzld/piaoquan/api/service/strategy/impl/BuckStrategyV1.java

@@ -9,11 +9,16 @@ import com.tzld.piaoquan.api.dao.mapper.AlgGhAutoreplyVideoRankDataMapper;
 import com.tzld.piaoquan.api.model.bo.*;
 import com.tzld.piaoquan.api.model.po.AlgGhAutoreplyVideoRankData;
 import com.tzld.piaoquan.api.model.po.AlgGhAutoreplyVideoRankDataExample;
+import com.tzld.piaoquan.api.model.po.GhDetailExt;
 import com.tzld.piaoquan.api.model.vo.VideoCharacteristicVO;
+import com.tzld.piaoquan.api.service.GhDetailService;
+import com.tzld.piaoquan.api.service.VideoMultiService;
 import com.tzld.piaoquan.api.service.strategy.ReplyStrategyService;
+import com.tzld.piaoquan.growth.common.common.enums.GhTypeEnum;
 import com.tzld.piaoquan.growth.common.common.enums.StrategyStatusEnum;
 import com.tzld.piaoquan.growth.common.dao.mapper.CgiReplyBucketDataMapper;
 import com.tzld.piaoquan.growth.common.dao.mapper.GhDetailMapper;
+import com.tzld.piaoquan.growth.common.dao.mapper.ext.CgiReplyBucketDataMapperExt;
 import com.tzld.piaoquan.growth.common.model.po.CgiReplyBucketData;
 import com.tzld.piaoquan.growth.common.model.po.CgiReplyBucketDataExample;
 import com.tzld.piaoquan.growth.common.utils.MessageUtil;
@@ -26,7 +31,9 @@ import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
 import org.springframework.util.CollectionUtils;
 
+import java.net.URLEncoder;
 import java.util.*;
+import java.util.function.Function;
 import java.util.stream.Collectors;
 
 @Slf4j
@@ -73,6 +80,12 @@ public class BuckStrategyV1 implements ReplyStrategyService {
     private TouLiuHttpClient touLiuHttpClient;
     @Autowired
     private GhDetailMapper ghDetailMapper;
+    @Autowired
+    private CgiReplyBucketDataMapperExt cgiReplyBucketDataMapperExt;
+    @Autowired
+    private VideoMultiService videoMultiService;
+    @Autowired
+    private GhDetailService ghDetailService;
 
     @Autowired
     private RedisUtils redisUtils;
@@ -246,16 +259,59 @@ public class BuckStrategyV1 implements ReplyStrategyService {
                 log.error("insertSmallData 算法排序数据异常,data:" + JSON.toJSONString(smallDataCgiReplyList));
                 continue;
             }
-            // 清上个版本的策略数据
             CgiReplyBucketDataExample cgiReplyBucketDataExample = new CgiReplyBucketDataExample();
             cgiReplyBucketDataExample.createCriteria().andIsDeleteEqualTo(0).andMsgTypeEqualTo(1).andStrategyEqualTo(key).andGhIdEqualTo(bucketDataParam.getGhId());
             List<CgiReplyBucketData> cgiReplyBucketData1 = cgiReplyBucketDataMapper.selectByExample(cgiReplyBucketDataExample);
-            for (CgiReplyBucketData cgiReplyBucketData : cgiReplyBucketData1) {
-                cgiReplyBucketData.setIsDelete(1);
-                cgiReplyBucketDataMapper.updateByPrimaryKeySelective(cgiReplyBucketData);
-            }
+            Map<Long, CgiReplyBucketData> cgiReplyBucketDataMap = cgiReplyBucketData1.stream()
+                    .collect(Collectors.toMap(CgiReplyBucketData::getMiniVideoId, x -> x, (a, b) -> b));
+            // 清上个版本的策略数据
+            cgiReplyBucketDataMapperExt.updateDeleteStatus(bucketDataParam.getGhId(), key, 1, 1);
+            List<GhDetailExt> ghDetailExtList = ghDetailService.getGhDetailExtList(bucketDataParam.getGhId(), GhTypeEnum.GH.type);
+            Map<Long, GhDetailExt> ghDetailExtMap = ghDetailExtList.stream()
+                    .collect(Collectors.toMap(GhDetailExt::getVideoId, Function.identity(), (a, b) -> b));
             // 入库
             for (CgiReplyBucketData cgiReplyBucketData : collect) {
+                if (Objects.isNull(cgiReplyBucketData.getMiniVideoId())) {
+                    Long videoId = MessageUtil.getVideoId(cgiReplyBucketData.getMiniPagePath());
+                    cgiReplyBucketData.setMiniVideoId(videoId);
+                }
+                CgiReplyBucketData oldData = cgiReplyBucketDataMap.get(cgiReplyBucketData.getMiniVideoId());
+                if (Objects.isNull(oldData)) {
+                    oldData = cgiReplyBucketDataMapperExt.getOldCgiReplyData(cgiReplyBucketData.getMiniVideoId());
+                }
+                if (Objects.nonNull(oldData)) {
+                    if (StringUtils.isEmpty(cgiReplyBucketData.getTitle())) {
+                        cgiReplyBucketData.setTitle(oldData.getTitle());
+                    }
+                    if (StringUtils.isEmpty(cgiReplyBucketData.getCoverUrl())) {
+                        cgiReplyBucketData.setCoverUrl(oldData.getCoverUrl());
+                    }
+                }
+                String pageUrl = cgiReplyBucketData.getMiniPagePath();
+                GhDetailExt ghDetailExt = ghDetailExtMap.get(cgiReplyBucketData.getMiniVideoId());
+                if (Objects.nonNull(ghDetailExt)) {
+                    try {
+                        //if (!pageUrl.contains("shareTitle")) {
+                        //    pageUrl += URLEncoder.encode("&shareTitle=" + ghDetailExt.getTitle(), "UTF-8");
+                        //}
+                        if (Objects.nonNull(ghDetailExt.getTitleId()) && !pageUrl.contains("shareTitleId")) {
+                            pageUrl += URLEncoder.encode("&shareTitleId=" + ghDetailExt.getTitleId(), "UTF-8");
+                        }
+                        if (Objects.nonNull(ghDetailExt.getCoverId()) && !pageUrl.contains("shareImageId")) {
+                            pageUrl += URLEncoder.encode("&shareImageId=" + ghDetailExt.getCoverId(), "UTF-8");
+                        }
+                        //if (!pageUrl.contains("shareImageUrl")) {
+                        //    pageUrl += URLEncoder.encode("&shareImageUrl=" + URLEncoder.encode(ghDetailExt.getCover(), "UTF-8"), "UTF-8");
+                        //}
+                    } catch (Exception e) {
+                        log.error("BuckStrategyV1 insertSmallData setCoverTitleId Error,data:" + JSON.toJSONString(ghDetailExt), e);
+                    }
+                } else {
+                    pageUrl = videoMultiService.setVideoMultiTitleCoverPagePath(cgiReplyBucketData.getMiniVideoId(),
+                            cgiReplyBucketData.getMiniPagePath(), cgiReplyBucketData.getTitle(), cgiReplyBucketData.getCoverUrl());
+                }
+                cgiReplyBucketData.setMiniPagePath(pageUrl);
+                cgiReplyBucketData.setRootSourceId(MessageUtil.getRootSourceId(cgiReplyBucketData.getMiniPagePath()));
                 cgiReplyBucketDataMapper.insertSelective(cgiReplyBucketData);
                 String redisKey = "auto_reply_video_detail_" + cgiReplyBucketData.getRootSourceId();
                 VideoCharacteristicVO vo = new VideoCharacteristicVO();
@@ -430,6 +486,12 @@ public class BuckStrategyV1 implements ReplyStrategyService {
                     if (videoDetail.getCover().contains("?")) {
                         cgiReplyBucketData.setCoverUrl(miniPageData.getCover() + videoDetail.getCover().substring(videoDetail.getCover().indexOf("?")));
                     }
+                } else {
+                    // 视频封面为空时,使用默认裁剪+播放按钮水印
+                    if (miniPageData.getCover().contains("yishihui")) {
+                        cgiReplyBucketData.setCoverUrl(miniPageData.getCover() +
+                                "?x-oss-process=image/resize,m_fill,w_600,h_480,limit_0/format,jpg/watermark,image_eXNoL3BpYy93YXRlcm1hcmtlci9pY29uX3BsYXlfd2hpdGUucG5nP3gtb3NzLXByb2Nlc3M9aW1hZ2UvcmVzaXplLHdfMTQ0,g_center");
+                    }
                 }
                 cgiReplyBucketData.setTitle(miniPageData.getTitle());
                 cgiReplyBucketData.setMiniPagePath(miniPageData.getPage());

+ 81 - 13
api-module/src/main/java/com/tzld/piaoquan/api/service/strategy/impl/ThirdPartyPushMessageStrategyV1.java

@@ -2,21 +2,23 @@ package com.tzld.piaoquan.api.service.strategy.impl;
 
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
-
 import com.tzld.piaoquan.api.common.enums.ReplyStrategyServiceEnum;
-import com.tzld.piaoquan.api.model.po.contentplatform.ContentPlatformGzhPlanVideo;
-import com.tzld.piaoquan.api.model.vo.VideoCharacteristicVO;
-import com.tzld.piaoquan.api.service.contentplatform.ContentPlatformPlanService;
-import com.tzld.piaoquan.growth.common.common.enums.StrategyStatusEnum;
+import com.tzld.piaoquan.api.component.ManagerApiService;
 import com.tzld.piaoquan.api.component.TouLiuHttpClient;
 import com.tzld.piaoquan.api.dao.mapper.AlgGhAutoreplyVideoRankDataMapper;
-import com.tzld.piaoquan.growth.common.dao.mapper.CgiReplyBucketDataMapper;
 import com.tzld.piaoquan.api.model.bo.*;
 import com.tzld.piaoquan.api.model.po.AlgGhAutoreplyVideoRankData;
 import com.tzld.piaoquan.api.model.po.AlgGhAutoreplyVideoRankDataExample;
+import com.tzld.piaoquan.api.model.po.contentplatform.ContentPlatformGzhPlanVideo;
+import com.tzld.piaoquan.api.model.vo.VideoCharacteristicVO;
+import com.tzld.piaoquan.api.service.VideoMultiService;
+import com.tzld.piaoquan.api.service.contentplatform.ContentPlatformPlanService;
+import com.tzld.piaoquan.api.service.strategy.ReplyStrategyService;
+import com.tzld.piaoquan.growth.common.common.enums.StrategyStatusEnum;
+import com.tzld.piaoquan.growth.common.dao.mapper.CgiReplyBucketDataMapper;
+import com.tzld.piaoquan.growth.common.dao.mapper.ext.CgiReplyBucketDataMapperExt;
 import com.tzld.piaoquan.growth.common.model.po.CgiReplyBucketData;
 import com.tzld.piaoquan.growth.common.model.po.CgiReplyBucketDataExample;
-import com.tzld.piaoquan.api.service.strategy.ReplyStrategyService;
 import com.tzld.piaoquan.growth.common.utils.MessageUtil;
 import com.tzld.piaoquan.growth.common.utils.RedisUtils;
 import lombok.extern.slf4j.Slf4j;
@@ -27,6 +29,7 @@ import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
 import org.springframework.util.CollectionUtils;
 
+import java.net.URLEncoder;
 import java.util.*;
 import java.util.stream.Collectors;
 
@@ -67,6 +70,12 @@ public class ThirdPartyPushMessageStrategyV1 implements ReplyStrategyService {
     private TouLiuHttpClient touLiuHttpClient;
     @Autowired
     private ContentPlatformPlanService contentPlatformPlanService;
+    @Autowired
+    private CgiReplyBucketDataMapperExt cgiReplyBucketDataMapperExt;
+    @Autowired
+    private ManagerApiService managerApiService;
+    @Autowired
+    private VideoMultiService videoMultiService;
 
     @Autowired
     private RedisUtils redisUtils;
@@ -152,19 +161,72 @@ public class ThirdPartyPushMessageStrategyV1 implements ReplyStrategyService {
                     .filter(x -> x.getGhId().equals(bucketDataParam.getGhId()))
                     .collect(Collectors.toList());
             if (CollectionUtils.isEmpty(collect)) {
-                log.error("ThirdPartyPushMessageStrategyV1 insertSmallData 算法排序数据异常,data:" + JSON.toJSONString(smallDataCgiReplyList));
+                log.error("ThirdPartyPushMessageStrategyV1 insertSmallData 算法排序数据异常 ,key:" + key +
+                        ",data:" + JSON.toJSONString(smallDataCgiReplyList) + ",ghId:" + bucketDataParam.getGhId());
                 continue;
             }
-            // 清上个版本的策略数据
             CgiReplyBucketDataExample cgiReplyBucketDataExample = new CgiReplyBucketDataExample();
             cgiReplyBucketDataExample.createCriteria().andIsDeleteEqualTo(0).andMsgTypeEqualTo(1).andStrategyEqualTo(key).andGhIdEqualTo(bucketDataParam.getGhId());
             List<CgiReplyBucketData> cgiReplyBucketData1 = cgiReplyBucketDataMapper.selectByExample(cgiReplyBucketDataExample);
-            for (CgiReplyBucketData cgiReplyBucketData : cgiReplyBucketData1) {
-                cgiReplyBucketData.setIsDelete(1);
-                cgiReplyBucketDataMapper.updateByPrimaryKeySelective(cgiReplyBucketData);
-            }
+            Map<Long, CgiReplyBucketData> cgiReplyBucketDataMap = cgiReplyBucketData1.stream()
+                    .collect(Collectors.toMap(CgiReplyBucketData::getMiniVideoId, x -> x, (a, b) -> b));
+            // 清上个版本的策略数据
+            cgiReplyBucketDataMapperExt.updateDeleteStatus(bucketDataParam.getGhId(), key, 1, 1);
+            List<ContentPlatformGzhPlanVideo> gzhPlanVideoList = contentPlatformPlanService.getGzhPlanVideoListByCooperateAccountId(bucketDataParam.getGhId());
+            Map<Long, ContentPlatformGzhPlanVideo> gzhPlanVideoMap = gzhPlanVideoList.stream()
+                    .collect(Collectors.toMap(ContentPlatformGzhPlanVideo::getVideoId, x -> x, (a, b) -> b));
             // 入库
             for (CgiReplyBucketData cgiReplyBucketData : collect) {
+                if (Objects.isNull(cgiReplyBucketData.getMiniVideoId())) {
+                    Long videoId = MessageUtil.getVideoId(cgiReplyBucketData.getMiniPagePath());
+                    cgiReplyBucketData.setMiniVideoId(videoId);
+                }
+                CgiReplyBucketData oldData = cgiReplyBucketDataMap.get(cgiReplyBucketData.getMiniVideoId());
+                if (Objects.isNull(oldData)) {
+                    oldData = cgiReplyBucketDataMapperExt.getOldCgiReplyData(cgiReplyBucketData.getMiniVideoId());
+                }
+                if (Objects.nonNull(oldData)) {
+                    if (StringUtils.isEmpty(cgiReplyBucketData.getTitle())) {
+                        cgiReplyBucketData.setTitle(oldData.getTitle());
+                    }
+                    if (StringUtils.isEmpty(cgiReplyBucketData.getCoverUrl())) {
+                        cgiReplyBucketData.setCoverUrl(oldData.getCoverUrl());
+                    }
+                }
+                ContentPlatformGzhPlanVideo gzhPlanVideo = gzhPlanVideoMap.get(cgiReplyBucketData.getMiniVideoId());
+                String pageUrl = cgiReplyBucketData.getMiniPagePath();
+                if (Objects.nonNull(gzhPlanVideo)) {
+                    try {
+                        //if (!pageUrl.contains("shareTitle")) {
+                        //    if (StringUtils.isNotBlank(gzhPlanVideo.getCustomTitle())) {
+                        //        pageUrl += URLEncoder.encode("&shareTitle=" + gzhPlanVideo.getCustomTitle(), "UTF-8");
+                        //    } else {
+                        //        pageUrl += URLEncoder.encode("&shareTitle=" + gzhPlanVideo.getTitle(), "UTF-8");
+                        //    }
+                        //}
+                        if (Objects.nonNull(gzhPlanVideo.getCustomTitleId()) && !pageUrl.contains("shareTitleId")) {
+                            pageUrl += URLEncoder.encode("&shareTitleId=" + gzhPlanVideo.getCustomTitleId(), "UTF-8");
+                        }
+                        if (Objects.nonNull(gzhPlanVideo.getCustomCoverId()) && !pageUrl.contains("shareImageId")) {
+                            pageUrl += URLEncoder.encode("&shareImageId=" + gzhPlanVideo.getCustomCoverId(), "UTF-8");
+                        }
+                        //if (!pageUrl.contains("shareImageUrl")) {
+                        //    if (StringUtils.isNotEmpty(gzhPlanVideo.getCustomCover())) {
+                        //        pageUrl += URLEncoder.encode("&shareImageUrl=" + URLEncoder.encode(gzhPlanVideo.getCustomCover(), "UTF-8"), "UTF-8");
+                        //    } else {
+                        //        pageUrl += URLEncoder.encode("&shareImageUrl=" + URLEncoder.encode(gzhPlanVideo.getCover(), "UTF-8"), "UTF-8");
+                        //    }
+                        //}
+                        cgiReplyBucketData.setMiniPagePath(pageUrl);
+                    } catch (Exception e) {
+                        log.error("ThirdPartyPushMessageStrategyV1 insertSmallData setCustomerCoverTitleId Error,data:" + JSON.toJSONString(gzhPlanVideo), e);
+                    }
+                } else {
+                    pageUrl = videoMultiService.setVideoMultiTitleCoverPagePath(cgiReplyBucketData.getMiniVideoId(),
+                            pageUrl, cgiReplyBucketData.getTitle(), cgiReplyBucketData.getCoverUrl());
+                    cgiReplyBucketData.setMiniPagePath(pageUrl);
+                }
+                cgiReplyBucketData.setRootSourceId(MessageUtil.getRootSourceId(cgiReplyBucketData.getMiniPagePath()));
                 cgiReplyBucketDataMapper.insertSelective(cgiReplyBucketData);
                 String redisKey = "auto_reply_video_detail_" + cgiReplyBucketData.getRootSourceId();
                 VideoCharacteristicVO vo = new VideoCharacteristicVO();
@@ -288,6 +350,12 @@ public class ThirdPartyPushMessageStrategyV1 implements ReplyStrategyService {
                             if (videoDetail.getCover().contains("?")) {
                                 cgiReplyBucketData.setCoverUrl(miniPageData.getCover() + videoDetail.getCover().substring(videoDetail.getCover().indexOf("?")));
                             }
+                        } else {
+                            // 视频封面为空时,使用默认裁剪+播放按钮水印
+                            if (miniPageData.getCover().contains("yishihui")) {
+                                cgiReplyBucketData.setCoverUrl(miniPageData.getCover() +
+                                        "?x-oss-process=image/resize,m_fill,w_600,h_480,limit_0/format,jpg/watermark,image_eXNoL3BpYy93YXRlcm1hcmtlci9pY29uX3BsYXlfd2hpdGUucG5nP3gtb3NzLXByb2Nlc3M9aW1hZ2UvcmVzaXplLHdfMTQ0,g_center");
+                            }
                         }
                         cgiReplyBucketData.setTitle(miniPageData.getTitle());
                         cgiReplyBucketData.setMiniPagePath(miniPageData.getPage());

+ 30 - 9
api-module/src/main/java/com/tzld/piaoquan/api/service/strategy/impl/WeComPushMessageStrategyV1.java

@@ -5,15 +5,17 @@ import com.alibaba.fastjson.JSONObject;
 import com.tzld.piaoquan.api.common.enums.ReplyStrategyServiceEnum;
 import com.tzld.piaoquan.api.component.TouLiuHttpClient;
 import com.tzld.piaoquan.api.dao.mapper.AlgGhAutoreplyVideoRankDataMapper;
-import com.tzld.piaoquan.api.model.vo.VideoCharacteristicVO;
-import com.tzld.piaoquan.growth.common.common.enums.StrategyStatusEnum;
-import com.tzld.piaoquan.growth.common.dao.mapper.CgiReplyBucketDataMapper;
 import com.tzld.piaoquan.api.model.bo.*;
 import com.tzld.piaoquan.api.model.po.AlgGhAutoreplyVideoRankData;
 import com.tzld.piaoquan.api.model.po.AlgGhAutoreplyVideoRankDataExample;
+import com.tzld.piaoquan.api.model.vo.VideoCharacteristicVO;
+import com.tzld.piaoquan.api.service.VideoMultiService;
+import com.tzld.piaoquan.api.service.strategy.ReplyStrategyService;
+import com.tzld.piaoquan.growth.common.common.enums.StrategyStatusEnum;
+import com.tzld.piaoquan.growth.common.dao.mapper.CgiReplyBucketDataMapper;
+import com.tzld.piaoquan.growth.common.dao.mapper.ext.CgiReplyBucketDataMapperExt;
 import com.tzld.piaoquan.growth.common.model.po.CgiReplyBucketData;
 import com.tzld.piaoquan.growth.common.model.po.CgiReplyBucketDataExample;
-import com.tzld.piaoquan.api.service.strategy.ReplyStrategyService;
 import com.tzld.piaoquan.growth.common.utils.MessageUtil;
 import com.tzld.piaoquan.growth.common.utils.RedisUtils;
 import lombok.extern.slf4j.Slf4j;
@@ -60,7 +62,11 @@ public class WeComPushMessageStrategyV1 implements ReplyStrategyService {
     @Autowired
     private CgiReplyBucketDataMapper cgiReplyBucketDataMapper;
     @Autowired
+    private CgiReplyBucketDataMapperExt cgiReplyBucketDataMapperExt;
+    @Autowired
     private TouLiuHttpClient touLiuHttpClient;
+    @Autowired
+    private VideoMultiService videoMultiService;
 
     @Autowired
     private RedisUtils redisUtils;
@@ -149,16 +155,31 @@ public class WeComPushMessageStrategyV1 implements ReplyStrategyService {
                 log.error("ThirdPartyPushMessageStrategyV1 insertSmallData 算法排序数据异常,data:" + JSON.toJSONString(smallDataCgiReplyList));
                 continue;
             }
-            // 清上个版本的策略数据
             CgiReplyBucketDataExample cgiReplyBucketDataExample = new CgiReplyBucketDataExample();
             cgiReplyBucketDataExample.createCriteria().andIsDeleteEqualTo(0).andMsgTypeEqualTo(1).andStrategyEqualTo(key).andGhIdEqualTo(bucketDataParam.getGhId());
             List<CgiReplyBucketData> cgiReplyBucketData1 = cgiReplyBucketDataMapper.selectByExample(cgiReplyBucketDataExample);
-            for (CgiReplyBucketData cgiReplyBucketData : cgiReplyBucketData1) {
-                cgiReplyBucketData.setIsDelete(1);
-                cgiReplyBucketDataMapper.updateByPrimaryKeySelective(cgiReplyBucketData);
-            }
+            Map<Long, CgiReplyBucketData> cgiReplyBucketDataMap = cgiReplyBucketData1.stream()
+                    .collect(Collectors.toMap(CgiReplyBucketData::getMiniVideoId, x -> x, (a, b) -> b));
+            // 清上个版本的策略数据
+            cgiReplyBucketDataMapperExt.updateDeleteStatus(bucketDataParam.getGhId(), key, 1, 1);
             // 入库
             for (CgiReplyBucketData cgiReplyBucketData : collect) {
+                CgiReplyBucketData oldData = cgiReplyBucketDataMap.get(cgiReplyBucketData.getMiniVideoId());
+                if (Objects.isNull(oldData)) {
+                    oldData = cgiReplyBucketDataMapperExt.getOldCgiReplyData(cgiReplyBucketData.getMiniVideoId());
+                }
+                if (Objects.nonNull(oldData)) {
+                    if (StringUtils.isEmpty(cgiReplyBucketData.getTitle())) {
+                        cgiReplyBucketData.setTitle(oldData.getTitle());
+                    }
+                    if (StringUtils.isEmpty(cgiReplyBucketData.getCoverUrl())) {
+                        cgiReplyBucketData.setCoverUrl(oldData.getCoverUrl());
+                    }
+                }
+                String pageUrl = videoMultiService.setVideoMultiTitleCoverPagePath(cgiReplyBucketData.getMiniVideoId(),
+                        cgiReplyBucketData.getMiniPagePath(), cgiReplyBucketData.getTitle(), cgiReplyBucketData.getCoverUrl());
+                cgiReplyBucketData.setMiniPagePath(pageUrl);
+                cgiReplyBucketData.setRootSourceId(MessageUtil.getRootSourceId(cgiReplyBucketData.getMiniPagePath()));
                 cgiReplyBucketDataMapper.insertSelective(cgiReplyBucketData);
                 String redisKey = "auto_reply_video_detail_" + cgiReplyBucketData.getRootSourceId();
                 VideoCharacteristicVO vo = new VideoCharacteristicVO();

+ 2 - 0
api-module/src/main/java/com/tzld/piaoquan/api/service/wecom/thirdparty/WeComThirdPartyService.java

@@ -74,4 +74,6 @@ public interface WeComThirdPartyService {
     void updateAutoCreateRoomStatus(UpdateAutoCreateRoomStatusRequest request);
 
     ThirdPartWeComRoom getRoomById(Long roomId);
+
+    void automaticLogin(UuidRequest request);
 }

+ 31 - 7
api-module/src/main/java/com/tzld/piaoquan/api/service/wecom/thirdparty/impl/WeComThirdPartyServiceImpl.java

@@ -340,14 +340,19 @@ public class WeComThirdPartyServiceImpl implements WeComThirdPartyService {
 
     @Override
     public CdnUploadImgLinkResponse cdnUploadImgLink(CdnUploadImgLinkRequest request) {
-        String response = apiClient.cdnUploadImgLink(request);
-        CommonResponse<CdnUploadImgLinkResponse> commonResponse =
-                JSONObject.parseObject(response, new TypeReference<CommonResponse<CdnUploadImgLinkResponse>>() {});
-        if (commonResponse.getErrcode() != 0) {
-            log.error("WeComThirdPartyService cdn upload img link failed, request: {}, response: {}", request, response);
-            return null;
+        int retryCount = 3;
+        while (retryCount > 0) {
+            String response = apiClient.cdnUploadImgLink(request);
+            CommonResponse<CdnUploadImgLinkResponse> commonResponse =
+                    JSONObject.parseObject(response, new TypeReference<CommonResponse<CdnUploadImgLinkResponse>>() {});
+            if (commonResponse.getErrcode() == 0) {
+                return commonResponse.getData();
+            } else {
+                log.error("WeComThirdPartyService cdn upload img link failed, request: {}, response: {}", request, response);
+            }
+            retryCount--;
         }
-        return commonResponse.getData();
+        return null;
     }
 
     @Override
@@ -600,4 +605,23 @@ public class WeComThirdPartyServiceImpl implements WeComThirdPartyService {
         return thirdPartWeComRoomMapper.selectByPrimaryKey(roomId);
     }
 
+    @Override
+    public void automaticLogin(UuidRequest request) {
+        if (Objects.nonNull(request) && StringUtils.isNotEmpty(request.getUuid())) {
+            apiClient.automaticLogin(request);
+            return;
+        }
+        ThirdPartWeComStaffExample example = new ThirdPartWeComStaffExample();
+        example.createCriteria().andStatusIn(Arrays.asList(ThirdPartWeComStaffStatusEnum.NORMAL.getVal(),
+                ThirdPartWeComStaffStatusEnum.OFFLINE.getVal()));
+        List<ThirdPartWeComStaff> staffList = thirdPartWeComStaffMapper.selectByExample(example);
+        if (CollectionUtils.isNotEmpty(staffList)) {
+            for (ThirdPartWeComStaff staff : staffList) {
+                UuidRequest uuidRequest = new UuidRequest();
+                uuidRequest.setUuid(staff.getThirdUuid());
+                apiClient.automaticLogin(uuidRequest);
+            }
+        }
+    }
+
 }

+ 31 - 0
api-module/src/main/java/com/tzld/piaoquan/api/util/AliOssFileTool.java

@@ -1,5 +1,6 @@
 package com.tzld.piaoquan.api.util;
 
+import cn.hutool.http.HttpUtil;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
@@ -33,6 +34,7 @@ import com.tzld.piaoquan.api.model.vo.SignatureVO;
 import com.tzld.piaoquan.api.model.vo.StsTokenVO;
 import com.tzld.piaoquan.growth.common.utils.DateUtils;
 import com.tzld.piaoquan.growth.common.utils.RandomUtil;
+import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.collections4.CollectionUtils;
 import org.apache.commons.io.IOUtils;
 import org.apache.commons.lang3.StringUtils;
@@ -63,6 +65,7 @@ import java.util.*;
  * @version 1.0.0
  */
 @Component
+@Slf4j
 public class AliOssFileTool extends AliOssConfig {
 
     private static Logger logger = Logger.getLogger(AliOssFileTool.class);
@@ -100,6 +103,14 @@ public class AliOssFileTool extends AliOssConfig {
 
     }
 
+    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 String moveFile(EnumPublicBuckets sourceBucket, String sourceKey, EnumPublicBuckets targetBucket, String targetKey) {
         getOssClient().copyObject(getBucket(sourceBucket.getBucketName()), sourceKey,
                 getBucket(targetBucket.getBucketName()), targetKey);
@@ -1473,4 +1484,24 @@ public class AliOssFileTool extends AliOssConfig {
         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;
+        }
+    }
+
 }

+ 14 - 0
api-module/src/main/java/com/tzld/piaoquan/api/util/CdnUtil.java

@@ -1,5 +1,6 @@
 package com.tzld.piaoquan.api.util;
 
+import cn.hutool.core.net.URLEncodeUtil;
 import com.alibaba.fastjson.JSON;
 import com.aliyuncs.DefaultAcsClient;
 import com.aliyuncs.IAcsClient;
@@ -147,6 +148,19 @@ public class CdnUtil {
         return fileExtension;
     }
 
+
+    public static String getOssHttpUrl(String key) {
+        if (StringUtils.isBlank(key)) {
+            return key;
+        }
+        if (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) {
         String url = "ad/material_1564536000000.mp4";
         System.out.println(getFileExtensionFromUrl(url));

+ 40 - 7
api-module/src/main/resources/mapper/GhDetailExtMapper.xml

@@ -6,7 +6,9 @@
     <result column="gh_detail_id" jdbcType="BIGINT" property="ghDetailId" />
     <result column="video_id" jdbcType="BIGINT" property="videoId" />
     <result column="page" jdbcType="VARCHAR" property="page" />
+    <result column="title_id" jdbcType="BIGINT" property="titleId" />
     <result column="title" jdbcType="VARCHAR" property="title" />
+    <result column="cover_id" jdbcType="BIGINT" property="coverId" />
     <result column="cover" jdbcType="VARCHAR" property="cover" />
     <result column="sort" jdbcType="INTEGER" property="sort" />
     <result column="is_delete" jdbcType="INTEGER" property="isDelete" />
@@ -72,7 +74,8 @@
     </where>
   </sql>
   <sql id="Base_Column_List">
-    id, gh_detail_id, video_id, page, title, cover, sort, is_delete, create_time, update_time
+    id, gh_detail_id, video_id, page, title_id, title, cover_id, cover, sort, is_delete, 
+    create_time, update_time
   </sql>
   <select id="selectByExample" parameterType="com.tzld.piaoquan.api.model.po.GhDetailExtExample" resultMap="BaseResultMap">
     select
@@ -109,13 +112,15 @@
   </delete>
   <insert id="insert" parameterType="com.tzld.piaoquan.api.model.po.GhDetailExt">
     insert into gh_detail_ext (id, gh_detail_id, video_id, 
-      page, title, cover, 
-      sort, is_delete, create_time, 
-      update_time)
+      page, title_id, title, 
+      cover_id, cover, sort, 
+      is_delete, create_time, update_time
+      )
     values (#{id,jdbcType=BIGINT}, #{ghDetailId,jdbcType=BIGINT}, #{videoId,jdbcType=BIGINT}, 
-      #{page,jdbcType=VARCHAR}, #{title,jdbcType=VARCHAR}, #{cover,jdbcType=VARCHAR}, 
-      #{sort,jdbcType=INTEGER}, #{isDelete,jdbcType=INTEGER}, #{createTime,jdbcType=TIMESTAMP}, 
-      #{updateTime,jdbcType=TIMESTAMP})
+      #{page,jdbcType=VARCHAR}, #{titleId,jdbcType=BIGINT}, #{title,jdbcType=VARCHAR}, 
+      #{coverId,jdbcType=BIGINT}, #{cover,jdbcType=VARCHAR}, #{sort,jdbcType=INTEGER}, 
+      #{isDelete,jdbcType=INTEGER}, #{createTime,jdbcType=TIMESTAMP}, #{updateTime,jdbcType=TIMESTAMP}
+      )
   </insert>
   <insert id="insertSelective" parameterType="com.tzld.piaoquan.api.model.po.GhDetailExt">
     insert into gh_detail_ext
@@ -132,9 +137,15 @@
       <if test="page != null">
         page,
       </if>
+      <if test="titleId != null">
+        title_id,
+      </if>
       <if test="title != null">
         title,
       </if>
+      <if test="coverId != null">
+        cover_id,
+      </if>
       <if test="cover != null">
         cover,
       </if>
@@ -164,9 +175,15 @@
       <if test="page != null">
         #{page,jdbcType=VARCHAR},
       </if>
+      <if test="titleId != null">
+        #{titleId,jdbcType=BIGINT},
+      </if>
       <if test="title != null">
         #{title,jdbcType=VARCHAR},
       </if>
+      <if test="coverId != null">
+        #{coverId,jdbcType=BIGINT},
+      </if>
       <if test="cover != null">
         #{cover,jdbcType=VARCHAR},
       </if>
@@ -205,9 +222,15 @@
       <if test="record.page != null">
         page = #{record.page,jdbcType=VARCHAR},
       </if>
+      <if test="record.titleId != null">
+        title_id = #{record.titleId,jdbcType=BIGINT},
+      </if>
       <if test="record.title != null">
         title = #{record.title,jdbcType=VARCHAR},
       </if>
+      <if test="record.coverId != null">
+        cover_id = #{record.coverId,jdbcType=BIGINT},
+      </if>
       <if test="record.cover != null">
         cover = #{record.cover,jdbcType=VARCHAR},
       </if>
@@ -234,7 +257,9 @@
       gh_detail_id = #{record.ghDetailId,jdbcType=BIGINT},
       video_id = #{record.videoId,jdbcType=BIGINT},
       page = #{record.page,jdbcType=VARCHAR},
+      title_id = #{record.titleId,jdbcType=BIGINT},
       title = #{record.title,jdbcType=VARCHAR},
+      cover_id = #{record.coverId,jdbcType=BIGINT},
       cover = #{record.cover,jdbcType=VARCHAR},
       sort = #{record.sort,jdbcType=INTEGER},
       is_delete = #{record.isDelete,jdbcType=INTEGER},
@@ -256,9 +281,15 @@
       <if test="page != null">
         page = #{page,jdbcType=VARCHAR},
       </if>
+      <if test="titleId != null">
+        title_id = #{titleId,jdbcType=BIGINT},
+      </if>
       <if test="title != null">
         title = #{title,jdbcType=VARCHAR},
       </if>
+      <if test="coverId != null">
+        cover_id = #{coverId,jdbcType=BIGINT},
+      </if>
       <if test="cover != null">
         cover = #{cover,jdbcType=VARCHAR},
       </if>
@@ -282,7 +313,9 @@
     set gh_detail_id = #{ghDetailId,jdbcType=BIGINT},
       video_id = #{videoId,jdbcType=BIGINT},
       page = #{page,jdbcType=VARCHAR},
+      title_id = #{titleId,jdbcType=BIGINT},
       title = #{title,jdbcType=VARCHAR},
+      cover_id = #{coverId,jdbcType=BIGINT},
       cover = #{cover,jdbcType=VARCHAR},
       sort = #{sort,jdbcType=INTEGER},
       is_delete = #{isDelete,jdbcType=INTEGER},

+ 14 - 3
api-module/src/main/resources/mapper/GhDetailMapperExt.xml

@@ -3,11 +3,13 @@
 <mapper namespace="com.tzld.piaoquan.api.dao.mapper.GhDetailMapperExt">
 
     <insert id="batchInsertGhDetailExt">
-        insert into gh_detail_ext (gh_detail_id, video_id, page, title, cover, sort, is_delete, create_time, update_time)
+        insert into gh_detail_ext (gh_detail_id, video_id, page, title_id, title, cover_id, cover, sort,
+                                   is_delete, create_time, update_time)
         values
         <foreach collection="records" item="record" separator=",">
-            (#{record.ghDetailId}, #{record.videoId}, #{record.page}, #{record.title}, #{record.cover}, #{record.sort},
-             #{record.isDelete}, #{record.createTime}, #{record.updateTime})
+            (#{record.ghDetailId}, #{record.videoId}, #{record.page}, #{record.titleId}, #{record.title},
+             #{record.coverId}, #{record.cover}, #{record.sort}, #{record.isDelete},
+             #{record.createTime}, #{record.updateTime})
         </foreach>
     </insert>
 
@@ -19,4 +21,13 @@
         )
     </update>
 
+    <select id="getGhDetailExtList" resultType="com.tzld.piaoquan.api.model.po.GhDetailExt">
+        select gde.*
+        from gh_detail_ext gde
+        join gh_detail gd on gde.gh_detail_id = gd.id
+        where gd.gh_id = #{ghId}
+          and gd.type = #{type}
+          and gde.is_delete = 0
+    </select>
+
 </mapper>

+ 38 - 5
api-module/src/main/resources/mapper/contentplatform/ContentPlatformIllegalMsgMapper.xml

@@ -7,6 +7,8 @@
     <result column="video_id" jdbcType="BIGINT" property="videoId" />
     <result column="title" jdbcType="VARCHAR" property="title" />
     <result column="business_type" jdbcType="VARCHAR" property="businessType" />
+    <result column="illegal_title" jdbcType="VARCHAR" property="illegalTitle" />
+    <result column="illegal_content" jdbcType="VARCHAR" property="illegalContent" />
     <result column="status" jdbcType="INTEGER" property="status" />
     <result column="create_timestamp" jdbcType="BIGINT" property="createTimestamp" />
     <result column="update_timestamp" jdbcType="BIGINT" property="updateTimestamp" />
@@ -70,7 +72,8 @@
     </where>
   </sql>
   <sql id="Base_Column_List">
-    id, account_id, video_id, title, business_type, `status`, create_timestamp, update_timestamp
+    id, account_id, video_id, title, business_type, illegal_title, illegal_content, `status`, 
+    create_timestamp, update_timestamp
   </sql>
   <select id="selectByExample" parameterType="com.tzld.piaoquan.api.model.po.contentplatform.ContentPlatformIllegalMsgExample" resultMap="BaseResultMap">
     select
@@ -107,11 +110,13 @@
   </delete>
   <insert id="insert" parameterType="com.tzld.piaoquan.api.model.po.contentplatform.ContentPlatformIllegalMsg">
     insert into content_platform_illegal_msg (id, account_id, video_id, 
-      title, business_type, `status`, 
-      create_timestamp, update_timestamp)
+      title, business_type, illegal_title, 
+      illegal_content, `status`, create_timestamp, 
+      update_timestamp)
     values (#{id,jdbcType=BIGINT}, #{accountId,jdbcType=BIGINT}, #{videoId,jdbcType=BIGINT}, 
-      #{title,jdbcType=VARCHAR}, #{businessType,jdbcType=VARCHAR}, #{status,jdbcType=INTEGER}, 
-      #{createTimestamp,jdbcType=BIGINT}, #{updateTimestamp,jdbcType=BIGINT})
+      #{title,jdbcType=VARCHAR}, #{businessType,jdbcType=VARCHAR}, #{illegalTitle,jdbcType=VARCHAR}, 
+      #{illegalContent,jdbcType=VARCHAR}, #{status,jdbcType=INTEGER}, #{createTimestamp,jdbcType=BIGINT}, 
+      #{updateTimestamp,jdbcType=BIGINT})
   </insert>
   <insert id="insertSelective" parameterType="com.tzld.piaoquan.api.model.po.contentplatform.ContentPlatformIllegalMsg">
     insert into content_platform_illegal_msg
@@ -131,6 +136,12 @@
       <if test="businessType != null">
         business_type,
       </if>
+      <if test="illegalTitle != null">
+        illegal_title,
+      </if>
+      <if test="illegalContent != null">
+        illegal_content,
+      </if>
       <if test="status != null">
         `status`,
       </if>
@@ -157,6 +168,12 @@
       <if test="businessType != null">
         #{businessType,jdbcType=VARCHAR},
       </if>
+      <if test="illegalTitle != null">
+        #{illegalTitle,jdbcType=VARCHAR},
+      </if>
+      <if test="illegalContent != null">
+        #{illegalContent,jdbcType=VARCHAR},
+      </if>
       <if test="status != null">
         #{status,jdbcType=INTEGER},
       </if>
@@ -192,6 +209,12 @@
       <if test="record.businessType != null">
         business_type = #{record.businessType,jdbcType=VARCHAR},
       </if>
+      <if test="record.illegalTitle != null">
+        illegal_title = #{record.illegalTitle,jdbcType=VARCHAR},
+      </if>
+      <if test="record.illegalContent != null">
+        illegal_content = #{record.illegalContent,jdbcType=VARCHAR},
+      </if>
       <if test="record.status != null">
         `status` = #{record.status,jdbcType=INTEGER},
       </if>
@@ -213,6 +236,8 @@
       video_id = #{record.videoId,jdbcType=BIGINT},
       title = #{record.title,jdbcType=VARCHAR},
       business_type = #{record.businessType,jdbcType=VARCHAR},
+      illegal_title = #{record.illegalTitle,jdbcType=VARCHAR},
+      illegal_content = #{record.illegalContent,jdbcType=VARCHAR},
       `status` = #{record.status,jdbcType=INTEGER},
       create_timestamp = #{record.createTimestamp,jdbcType=BIGINT},
       update_timestamp = #{record.updateTimestamp,jdbcType=BIGINT}
@@ -235,6 +260,12 @@
       <if test="businessType != null">
         business_type = #{businessType,jdbcType=VARCHAR},
       </if>
+      <if test="illegalTitle != null">
+        illegal_title = #{illegalTitle,jdbcType=VARCHAR},
+      </if>
+      <if test="illegalContent != null">
+        illegal_content = #{illegalContent,jdbcType=VARCHAR},
+      </if>
       <if test="status != null">
         `status` = #{status,jdbcType=INTEGER},
       </if>
@@ -253,6 +284,8 @@
       video_id = #{videoId,jdbcType=BIGINT},
       title = #{title,jdbcType=VARCHAR},
       business_type = #{businessType,jdbcType=VARCHAR},
+      illegal_title = #{illegalTitle,jdbcType=VARCHAR},
+      illegal_content = #{illegalContent,jdbcType=VARCHAR},
       `status` = #{status,jdbcType=INTEGER},
       create_timestamp = #{createTimestamp,jdbcType=BIGINT},
       update_timestamp = #{updateTimestamp,jdbcType=BIGINT}

+ 133 - 0
api-module/src/test/java/com/tzld/piaoquan/api/GhDetailTest.java

@@ -0,0 +1,133 @@
+package com.tzld.piaoquan.api;
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.tzld.piaoquan.api.component.AigcApiService;
+import com.tzld.piaoquan.api.component.ManagerApiService;
+import com.tzld.piaoquan.api.dao.mapper.GhDetailExtMapper;
+import com.tzld.piaoquan.api.model.po.GhDetailExt;
+import com.tzld.piaoquan.api.model.po.GhDetailExtExample;
+import com.tzld.piaoquan.growth.common.dao.mapper.CgiReplyBucketDataMapper;
+import com.tzld.piaoquan.growth.common.dao.mapper.ext.CgiReplyBucketDataMapperExt;
+import com.tzld.piaoquan.growth.common.model.po.CgiReplyBucketData;
+import com.tzld.piaoquan.growth.common.model.po.CgiReplyBucketDataExample;
+import com.tzld.piaoquan.growth.common.utils.MessageUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.util.CollectionUtils;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+@SpringBootTest(classes = GrowthServerApplication.class)
+@Slf4j
+public class GhDetailTest {
+
+    @Autowired
+    GhDetailExtMapper ghDetailExtMapper;
+    @Autowired
+    ManagerApiService managerApiService;
+    @Autowired
+    CgiReplyBucketDataMapper cgiReplyBucketDataMapper;
+    @Autowired
+    CgiReplyBucketDataMapperExt cgiReplyBucketDataMapperExt;
+    @Autowired
+    AigcApiService aigcApiService;
+
+    @Test
+    public void testGhDetailExtId() {
+        GhDetailExtExample example = new GhDetailExtExample();
+        example.createCriteria().andIsDeleteEqualTo(0);
+        List<GhDetailExt> ghDetailExtList = ghDetailExtMapper.selectByExample(example);
+        for (GhDetailExt ghDetailExt : ghDetailExtList) {
+            setCustomTitle(ghDetailExt);
+            setCustomCover(ghDetailExt);
+            ghDetailExtMapper.updateByPrimaryKeySelective(ghDetailExt);
+        }
+    }
+
+    private void setCustomTitle(GhDetailExt ghDetailExt) {
+        if (StringUtils.isNotEmpty(ghDetailExt.getTitle())) {
+            JSONArray multiTitleList = managerApiService.videoMultiTitleListV2(ghDetailExt.getVideoId());
+            boolean titleExist = false;
+            if (!CollectionUtils.isEmpty(multiTitleList)) {
+                for (int i = 0; i < multiTitleList.size(); i++) {
+                    JSONObject item = multiTitleList.getJSONObject(i);
+                    if (item.getString("title").equals(ghDetailExt.getTitle())) {
+                        ghDetailExt.setTitleId(item.getLong("id"));
+                        titleExist = true;
+                        break;
+                    }
+                }
+            }
+            if (!titleExist) {
+                JSONObject multiTitle = managerApiService.videoMultiTitleSave(ghDetailExt.getVideoId(), ghDetailExt.getTitle());
+                ghDetailExt.setTitleId(multiTitle.getLong("id"));
+            }
+        }
+    }
+
+    private void setCustomCover(GhDetailExt ghDetailExt) {
+        if (StringUtils.isNotEmpty(ghDetailExt.getCover())) {
+            JSONArray multiCoverList = managerApiService.videoMultiCoverListV2(ghDetailExt.getVideoId());
+            boolean customCoverExist = false;
+            if (!CollectionUtils.isEmpty(multiCoverList)) {
+                for (int i = 0; i < multiCoverList.size(); i++) {
+                    JSONObject item = multiCoverList.getJSONObject(i);
+                    if (item.getString("coverUrl").equals(ghDetailExt.getCover())) {
+                        ghDetailExt.setCoverId(item.getLong("id"));
+                        customCoverExist = true;
+                        break;
+                    }
+                }
+            }
+            if (!customCoverExist) {
+                JSONObject multiCover = managerApiService.videoMultiCoverSave(ghDetailExt.getVideoId(), ghDetailExt.getCover());
+                ghDetailExt.setCoverId(multiCover.getLong("id"));
+            }
+        }
+    }
+
+    @Test
+    public void refreshCgiReplyBucketData() {
+        CgiReplyBucketDataExample example = new CgiReplyBucketDataExample();
+        example.createCriteria().andIsDeleteEqualTo(0).andTitleIsNull();
+        List<CgiReplyBucketData> list = cgiReplyBucketDataMapper.selectByExample(example);
+        if (!CollectionUtils.isEmpty(list)) {
+            for (CgiReplyBucketData item : list) {
+                CgiReplyBucketData oldItem = cgiReplyBucketDataMapperExt.getOldCgiReplyData(item.getMiniVideoId());
+                if (oldItem != null) {
+                    item.setTitle(oldItem.getTitle());
+                    item.setCoverUrl(oldItem.getCoverUrl());
+                    cgiReplyBucketDataMapper.updateByPrimaryKeySelective(item);
+                }
+            }
+        }
+    }
+
+    @Test
+    public void refreshGzhAutoReplyMsgData() {
+        CgiReplyBucketDataExample example = new CgiReplyBucketDataExample();
+        example.createCriteria().andIsDeleteEqualTo(0);
+        List<CgiReplyBucketData> list = cgiReplyBucketDataMapper.selectByExample(example);
+        List<String> ghIds = list.stream().map(CgiReplyBucketData::getGhId).distinct().collect(Collectors.toList());
+        for (String ghId : ghIds) {
+            aigcApiService.refreshGzhAutoReplyMsgData(ghId);
+        }
+    }
+
+    @Test
+    public void setRootSourceId() {
+        CgiReplyBucketDataExample example = new CgiReplyBucketDataExample();
+        example.createCriteria().andRootSourceIdIsNull();
+        List<CgiReplyBucketData> list = cgiReplyBucketDataMapper.selectByExample(example);
+        for (CgiReplyBucketData item : list) {
+            item.setRootSourceId(MessageUtil.getRootSourceId(item.getMiniPagePath()));
+            cgiReplyBucketDataMapper.updateByPrimaryKeySelective(item);
+        }
+    }
+
+}

+ 104 - 0
api-module/src/test/java/com/tzld/piaoquan/api/WeComThirdPartTest.java

@@ -1,11 +1,16 @@
 package com.tzld.piaoquan.api;
 
+import com.alibaba.fastjson.JSONObject;
+import com.tzld.piaoquan.api.component.AigcApiService;
+import com.tzld.piaoquan.api.component.DeepSeekApiService;
 import com.tzld.piaoquan.api.dao.mapper.wecom.thirdpart.ThirdPartWeComRoomMapper;
 import com.tzld.piaoquan.api.dao.mapper.wecom.thirdpart.ThirdPartWeComStaffMapper;
 import com.tzld.piaoquan.api.job.wecom.thirdpart.WeComAccountJob;
 import com.tzld.piaoquan.api.job.wecom.thirdpart.WeComCreateRoomJob;
 import com.tzld.piaoquan.api.job.wecom.thirdpart.WeComSendMsgJob;
 import com.tzld.piaoquan.api.job.wecom.thirdpart.WeComUserDetailJob;
+import com.tzld.piaoquan.api.model.bo.GoogleLLMResult;
+import com.tzld.piaoquan.api.model.dto.AIResult;
 import com.tzld.piaoquan.api.model.param.wecom.thirdpart.UpdateRoomNameRequest;
 import com.tzld.piaoquan.api.model.po.wecom.thirdpart.ThirdPartWeComRoom;
 import com.tzld.piaoquan.api.model.po.wecom.thirdpart.ThirdPartWeComStaff;
@@ -15,6 +20,9 @@ import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 
 @SpringBootTest(classes = GrowthServerApplication.class)
@@ -35,6 +43,10 @@ public class WeComThirdPartTest {
     WeComUserDetailJob weComUserDetailJob;
     @Autowired
     WeComCreateRoomJob weComCreateRoomJob;
+    @Autowired
+    AigcApiService aigcApiService;
+    @Autowired
+    DeepSeekApiService deepSeekApiService;
 
     @Test
     public void checkAccountOnline() {
@@ -97,4 +109,96 @@ public class WeComThirdPartTest {
         }
     }
 
+    @Test
+    public void tesGPT() {
+        //String imageUrl = "http://api.e.qq.com/image/70837126_29318969717_5e4c671a4e18aaad97d940a56aeb26bf";
+        //
+        //// download & upload to oss
+        //String fileName = String.format("supply/spider/image/%s_%d.jpg", "70837126", System.currentTimeMillis());
+        //String ossImageUrl = AliOssFileTool.downloadAndSaveInOSS(fileName, imageUrl, "image/jpeg");
+        //System.out.println("ossImageUrl: " + ossImageUrl);
+
+        String ossImageUrl = "https://rescdn.yishihui.com/supply/spider/image/70837126_1766470638729.jpg";
+
+        // 用户问题
+        String prompt = "请提取这张图片中的文字内容,仅仅提取图片中的文字,不要有其他的文字";
+
+        // 调用API
+        String result = aigcApiService.gpt("gpt-4o", prompt, null, Arrays.asList(ossImageUrl));
+
+        System.out.println(result);
+    }
+
+    @Test
+    public void tesGemini() {
+        //String imageUrl = "http://api.e.qq.com/image/70837126_29318969717_5e4c671a4e18aaad97d940a56aeb26bf";
+        //
+        //// download & upload to oss
+        //String fileName = String.format("supply/spider/image/%s_%d.jpg", "70837126", System.currentTimeMillis());
+        //String ossImageUrl = AliOssFileTool.downloadAndSaveInOSS(fileName, imageUrl, "image/jpeg");
+        //System.out.println("ossImageUrl: " + ossImageUrl);
+
+        String ossImageUrl = "https://rescdn.yishihui.com/supply/spider/image/70837126_1766470638729.jpg";
+
+        // 用户问题
+        String prompt = "识别图片里的文字,直接输出图片里的文字标题,但是不要输出图片里右下角的字,比如“点开看看”“看这里”等,仅输出标题,不要有多余的文字输出";
+
+        // 调用API
+        GoogleLLMResult result = aigcApiService.gemini("gemini-2.0-flash", prompt, new ArrayList<>(Collections.singleton(ossImageUrl)));
+
+        System.out.println(JSONObject.toJSONString(result));
+    }
+
+    @Test
+    public void testDeepSeek() {
+        List<String> titles = Arrays.asList(
+                "这首歌太好听了,听醉了别怪我!",
+                "2025年,一首老歌送给大家,好听极了",
+                "🔴致谢群主,群主辛苦了!感谢您的无私奉献!",
+                "狗屁股,笑死我了!",
+                "🔴菊花开了!太美了",
+                "🔴这首歌,献给所有苦命人!",
+                "102岁杨振宁月工资",
+                "🔴少年夫妻老来伴,珍惜身边人",
+                "⭕⭕⭕这才叫笑话!笑的肚子疼!哈哈哈哈!",
+                "🔴人人羡慕!这才是真正的人民公社!",
+                "▲80岁老大爷懂得真多,太有道理了!"
+        );
+        for (String title : titles) {
+            String keywordPrompt =
+                    "你是一位精通算法推荐逻辑的世界级短视频SEO专家。你擅长从标题中提炼出搜索量最大、用户意图最明确的“核心流量词”。\n" +
+                            "\n" +
+                            "# Task\n" +
+                            "分析用户提供的视频标题,提炼出 2 个核心搜索关键词。\n" +
+                            "\n" +
+                            "# Constraints (必须严格遵守)\n" +
+                            "1. **字数限制**:每个关键词严格控制在 **3个汉字以内**(包含3个字)。\n" +
+                            "2. **选词逻辑**:\n" +
+                            "   - 优先提取核心名词(人名/物名)或高频动词。\n" +
+                            "   - 剔除虚词(如“的”、“了”、“吗”)。\n" +
+                            "   - 必须与原标题强相关,能覆盖用户搜索意图。\n" +
+                            "3. **输出格式**:仅输出JSON数组,**严禁**包含任何解释、Markdown标记或其他文本。\n" +
+                            "\n" +
+                            "# Example\n" +
+                            "输入:新手如何快速学会剪映剪辑\n" +
+                            "输出:[\"剪映\",\"剪辑\"]\n" +
+                            "\n" +
+                            "输入:宝宝感冒流鼻涕怎么办\n" +
+                            "输出:[\"感冒\",\"流鼻涕\"]\n" +
+                            "\n" +
+                            "# Input\n" +
+                            "内容是: {{text}} \n" +
+                            "\n" +
+                            "# Output\n" +
+                            "请基于上述规则,输出最终的JSON:";
+            keywordPrompt = keywordPrompt.replace("text", title);
+            // 调用API
+            AIResult result = deepSeekApiService.requestOfficialApi(keywordPrompt, null, null, true);
+            List<String> keywords = JSONObject.parseArray(result.getResponse().getChoices().get(0).getMessage().getContent(), String.class);
+
+            System.out.println(String.format("title: %s, keywords: %s", title, keywords));
+        }
+
+    }
+
 }

+ 1 - 1
common-module/src/main/java/com/tzld/piaoquan/growth/common/config/HttpClientConfig.java

@@ -20,7 +20,7 @@ public class HttpClientConfig {
     /**
      * 响应超时时间 ms
      */
-    private static final int SOCKET_TIMEOUT = 30000;
+    private static final int SOCKET_TIMEOUT = 120000;
 
     /**
      * 每个路由的最大连接数

+ 8 - 0
common-module/src/main/java/com/tzld/piaoquan/growth/common/dao/mapper/ext/CgiReplyBucketDataMapperExt.java

@@ -1,6 +1,7 @@
 package com.tzld.piaoquan.growth.common.dao.mapper.ext;
 
 
+import com.tzld.piaoquan.growth.common.model.po.CgiReplyBucketData;
 import org.apache.ibatis.annotations.Mapper;
 import org.apache.ibatis.annotations.Param;
 
@@ -12,4 +13,11 @@ public interface CgiReplyBucketDataMapperExt {
                                           @Param("cover") String cover);
 
     void deleteBucketDataByGhId(@Param("ghId") String ghId);
+
+    CgiReplyBucketData getOldCgiReplyData(@Param("videoId") Long miniVideoId);
+
+    void updateDeleteStatus(@Param("ghId") String ghId,
+                            @Param("strategy") String strategy,
+                            @Param("msgType") int msgType,
+                            @Param("isDelete") int isDelete);
 }

+ 50 - 0
common-module/src/main/java/com/tzld/piaoquan/growth/common/utils/MapBuilder.java

@@ -0,0 +1,50 @@
+package com.tzld.piaoquan.growth.common.utils;
+
+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);
+    }
+}

+ 4 - 0
common-module/src/main/java/com/tzld/piaoquan/growth/common/utils/MessageUtil.java

@@ -38,4 +38,8 @@ public class MessageUtil {
         return String.format(defaultName, DateUtil.getDayDateString("MMdd"), remark);
     }
 
+    public static void main(String[] args) {
+        String url = "pages/category?jumpPage=pages%2Fuser-videos%3FfromGzh%3D1%26rootShareId%3D450ba747-3375-4cf3-a908-1b2772e181a7%26id%3D13889690%26shareId%3D450ba747-3375-4cf3-a908-1b2772e181a7%26rootSourceId%3Ddaitou_tencentgzh_20251212_13889690_f7221c51%26shareTitleId%3D14889690%26shareImageId%3D87473466%26shareImageUrl%3Dhttps%253A%252F%252Frescdn.yishihui.com%252Flongvideo%252Fpic%252F44a75a67b65244ef80a8dca6341584fc1765518383424";
+        System.out.println(getRootSourceId(url));
+    }
 }

+ 102 - 0
common-module/src/main/java/com/tzld/piaoquan/growth/common/utils/TitleSimilarCheckUtil.java

@@ -0,0 +1,102 @@
+package com.tzld.piaoquan.growth.common.utils;
+
+import org.springframework.util.CollectionUtils;
+
+import java.util.*;
+
+public class TitleSimilarCheckUtil {
+
+    public static final double SIMILARITY_THRESHOLD = 0.7;
+
+    public static Set<Character> makeCache(String title) {
+        title = title.trim().replace("\u200b", "");
+        Set<Character> cacheSet = new HashSet<>(title.length());
+        for (char c : title.toCharArray()) {
+            cacheSet.add(c);
+        }
+        return cacheSet;
+    }
+
+    public static List<Set<Character>> makeCache(List<String> titles) {
+        List<Set<Character>> cache = new ArrayList<>(titles.size());
+        for (String title : titles) {
+            cache.add(makeCache(title));
+        }
+        return cache;
+    }
+
+    public static boolean isDuplicateContentByCache(String title, List<Set<Character>> existsContentCache, double threshold) {
+        if (CollectionUtils.isEmpty(existsContentCache)) {
+            return false;
+        }
+        Set<Character> titleCache = makeCache(title);
+        for (Set<Character> existTitleCache : existsContentCache) {
+            if (isSimilar(titleCache, existTitleCache, threshold)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public static boolean isDuplicateContent(String title, List<String> existsContentTitle, double threshold) {
+        boolean result = false;
+        if (CollectionUtils.isEmpty(existsContentTitle)) {
+            return result;
+        }
+        title = title.trim().replace("\u200b", "");
+        for (String existsTitle : existsContentTitle) {
+            if (isSimilar(title, existsTitle, threshold)) {
+                result = true;
+                break;
+            }
+        }
+        return result;
+    }
+
+    public static boolean isSimilar(Set<Character> titleA, Set<Character> titleB, double threshold) {
+        if (titleA.isEmpty() || titleB.isEmpty()) {
+            return false;
+        }
+        // 确保遍历较小的集合以优化性能
+        Set<Character> smaller = titleA.size() < titleB.size() ? titleA : titleB;
+        Set<Character> larger = titleA.size() < titleB.size() ? titleB : titleA;
+
+        int intersectionCount = 0;
+        for (Character c : smaller) {
+            if (larger.contains(c)) {
+                intersectionCount++;
+            }
+        }
+        double rate = intersectionCount / (double) smaller.size();
+        return rate >= threshold;
+    }
+
+    public static boolean isSimilar(String titleA, Set<Character> titleB, double threshold) {
+        Set<Character> setA = makeCache(titleA);
+        return isSimilar(setA, titleB, threshold);
+    }
+
+    public static boolean isSimilar(String titleA, String titleB, double threshold) {
+        Set<Character> setA = makeCache(titleA);
+        Set<Character> setB = makeCache(titleB);
+        return isSimilar(setA, setB, threshold);
+    }
+
+    public static void main(String[] args) {
+        String title = "多子女家庭,老人大概率过得比独子家庭的要幸福";
+        List<String> existsContentTitle = Arrays.asList("以后买房,请记住7字真言:“买旧、买大、不买三!”",
+                "人到晚年才明白:多子女家庭,老人大概率过得比独子家庭的要幸福",
+                "可供中国使用3800年?山东意外发现巨大宝藏,西方当场酸了!",
+                "陕西女孩去医院体检后,发现左肾不见了,意外牵出8年前手术疑云");
+        boolean result = isDuplicateContent(title, existsContentTitle, SIMILARITY_THRESHOLD);
+        System.out.println(result);
+
+        List<Set<Character>> titlesCache = makeCache(existsContentTitle);
+        result = isDuplicateContentByCache(title, titlesCache, SIMILARITY_THRESHOLD);
+        System.out.println(result);
+
+        title = "江苏高考文科女状元,遭多所985名校拒绝录取,成为“最惨状元”";
+        result = isDuplicateContentByCache(title, titlesCache, SIMILARITY_THRESHOLD);
+        System.out.println(result);
+    }
+}

+ 15 - 0
common-module/src/main/resources/mapper/ext/CgiReplyBucketDataMapperExt.xml

@@ -18,4 +18,19 @@
         and is_delete = 0
   </update>
 
+    <select id="getOldCgiReplyData" resultType="com.tzld.piaoquan.growth.common.model.po.CgiReplyBucketData">
+        select *
+        from growth.cgi_reply_bucket_data
+        where mini_video_id = #{videoId} and title is not null and cover_url is not null
+        order by id desc limit 1
+    </select>
+
+    <update id="updateDeleteStatus">
+        update cgi_reply_bucket_data
+        set is_delete = #{isDelete}
+        where gh_id = #{ghId}
+        and strategy = #{strategy}
+        and msg_type = #{msgType}
+    </update>
+
 </mapper>

+ 6 - 0
pom.xml

@@ -264,6 +264,12 @@
             <artifactId>spring-cloud-starter-openfeign</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+            <version>5.8.18</version>
+        </dependency>
+
     </dependencies>
 
 </project>