ソースを参照

小程序匹配流程基础工具开发

xueyiming 6 ヶ月 前
コミット
e513254d98
18 ファイル変更1581 行追加2 行削除
  1. 26 0
      long-article-server/src/main/java/com/tzld/piaoquan/longarticle/service/local/impl/CrawlerVideoServiceImpl.java
  2. 56 1
      long-article-server/src/main/java/com/tzld/piaoquan/longarticle/service/local/impl/KimiServiceImpl.java
  3. 256 0
      long-article-server/src/main/java/com/tzld/piaoquan/longarticle/service/local/impl/MatchVideoServiceImpl.java
  4. 1 1
      long-article-server/src/main/java/com/tzld/piaoquan/longarticle/utils/HttpPoolClientUtil.java
  5. 85 0
      long-article-server/src/main/java/com/tzld/piaoquan/longarticle/utils/other/DeepSeekAPI.java
  6. 88 0
      long-article-server/src/main/java/com/tzld/piaoquan/longarticle/utils/other/DouyinSearch.java
  7. 85 0
      long-article-server/src/main/java/com/tzld/piaoquan/longarticle/utils/other/GetContentUtil.java
  8. 90 0
      long-article-server/src/main/java/com/tzld/piaoquan/longarticle/utils/other/GptAPI.java
  9. 151 0
      long-article-server/src/main/java/com/tzld/piaoquan/longarticle/utils/other/HkspSearch.java
  10. 92 0
      long-article-server/src/main/java/com/tzld/piaoquan/longarticle/utils/other/HttpClientUtil.java
  11. 193 0
      long-article-server/src/main/java/com/tzld/piaoquan/longarticle/utils/other/HttpPoolClientUtil.java
  12. 56 0
      long-article-server/src/main/java/com/tzld/piaoquan/longarticle/utils/other/KimiAPI.java
  13. 37 0
      long-article-server/src/main/java/com/tzld/piaoquan/longarticle/utils/other/OSSUploader.java
  14. 108 0
      long-article-server/src/main/java/com/tzld/piaoquan/longarticle/utils/other/OpenAIUtils.java
  15. 32 0
      long-article-server/src/main/java/com/tzld/piaoquan/longarticle/utils/other/SearchUtils.java
  16. 55 0
      long-article-server/src/main/java/com/tzld/piaoquan/longarticle/utils/other/SortUtil.java
  17. 161 0
      long-article-server/src/main/java/com/tzld/piaoquan/longarticle/utils/other/VideoDownloader.java
  18. 9 0
      pom.xml

+ 26 - 0
long-article-server/src/main/java/com/tzld/piaoquan/longarticle/service/local/impl/CrawlerVideoServiceImpl.java

@@ -0,0 +1,26 @@
+package com.tzld.piaoquan.longarticle.service.local.impl;
+
+import com.alibaba.fastjson.JSONArray;
+import com.tzld.piaoquan.longarticle.model.po.LongArticlesText;
+import org.springframework.stereotype.Service;
+
+import java.util.stream.Collectors;
+
+@Service
+public class CrawlerVideoServiceImpl {
+
+    private static final String default_account_id = "69637498";
+
+
+    public void searchVideosFromWeb(LongArticlesText longArticlesText) {
+        String articleSummary = longArticlesText.getKimiSummary().substring(0, 15);
+        String oriTitle = longArticlesText.getKimiTitle();
+        String kimiKeys = longArticlesText.getKimiKeys();
+        JSONArray jsonArray = JSONArray.parseArray(kimiKeys);
+        String newKimiKeys = jsonArray.stream()
+                .map(Object::toString)
+                .collect(Collectors.joining(","));
+
+
+    }
+}

+ 56 - 1
long-article-server/src/main/java/com/tzld/piaoquan/longarticle/service/local/impl/KimiServiceImpl.java

@@ -1,9 +1,12 @@
 package com.tzld.piaoquan.longarticle.service.local.impl;
 
+import com.alibaba.fastjson.JSONObject;
 import com.tzld.piaoquan.longarticle.dao.mapper.LongArticlesTextMapper;
 import com.tzld.piaoquan.longarticle.model.po.LongArticlesText;
 import com.tzld.piaoquan.longarticle.model.po.LongArticlesTextExample;
 import com.tzld.piaoquan.longarticle.service.local.KimiService;
+import com.tzld.piaoquan.longarticle.utils.other.OpenAIUtils;
+import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.util.CollectionUtils;
@@ -13,6 +16,8 @@ import java.util.List;
 @Service
 public class KimiServiceImpl implements KimiService {
 
+    private static final Integer SAFE_SCORE = 7;
+
     @Autowired
     LongArticlesTextMapper longArticlesTextMapper;
 
@@ -21,9 +26,59 @@ public class KimiServiceImpl implements KimiService {
         LongArticlesTextExample example = new LongArticlesTextExample();
         example.createCriteria().andContentIdEqualTo(contentId);
         List<LongArticlesText> longArticlesTexts = longArticlesTextMapper.selectByExample(example);
-        if(CollectionUtils.isEmpty(longArticlesTexts)){
+        if (CollectionUtils.isEmpty(longArticlesTexts)) {
             return null;
         }
         return longArticlesTexts.get(0);
     }
+
+    public void addContent(String contentId, String articleTitle, String articleText) {
+        LongArticlesTextExample example = new LongArticlesTextExample();
+        example.createCriteria().andContentIdEqualTo(contentId);
+        long l = longArticlesTextMapper.countByExample(example);
+        if (l == 0) {
+            LongArticlesText longArticlesText = new LongArticlesText();
+            longArticlesText.setContentId(contentId);
+            longArticlesText.setArticleTitle(articleTitle);
+            longArticlesText.setArticleText(articleText);
+            longArticlesText.setKimiStatus(0);
+            longArticlesTextMapper.insertSelective(longArticlesText);
+        }
+        longArticlesTextMapper.selectByExample(example);
+    }
+
+    public LongArticlesText getAndUpdateContent(String contentId) {
+        LongArticlesTextExample example = new LongArticlesTextExample();
+        example.createCriteria().andContentIdEqualTo(contentId);
+        List<LongArticlesText> longArticlesTexts = longArticlesTextMapper.selectByExampleWithBLOBs(example);
+        if (CollectionUtils.isEmpty(longArticlesTexts)) {
+            return null;
+        }
+        LongArticlesText longArticlesText = longArticlesTexts.get(0);
+        if (longArticlesText.getKimiStatus() == 1) {
+            return longArticlesText;
+        }
+        String kimiTitle = OpenAIUtils.getKimiTitle(longArticlesText.getArticleTitle());
+        Integer score = OpenAIUtils.getKimiTitleSafeScore(kimiTitle);
+        if (score == null || score > SAFE_SCORE) {
+            kimiTitle = OpenAIUtils.makeKimiTitleSafer(longArticlesText.getArticleTitle());
+        }
+        kimiTitle = kimiTitle.replace("'", "").replace("\"", "").replace("\\", "");
+
+        JSONObject kimiInfo = OpenAIUtils.kimiMining(longArticlesText.getArticleText());
+        String kimiSummary = kimiInfo.getString("content_title").replace("'", "").replace("\"", "");
+        String kimiKeys = kimiInfo.getJSONArray("content_keys").toJSONString();
+
+        if (StringUtils.isNotEmpty(kimiTitle) && StringUtils.isNotEmpty(kimiSummary) && StringUtils.isNotEmpty(kimiKeys)) {
+            longArticlesText.setKimiTitle(kimiTitle);
+            longArticlesText.setKimiSummary(kimiSummary);
+            longArticlesText.setKimiKeys(kimiKeys);
+            longArticlesText.setKimiStatus(1);
+            longArticlesTextMapper.updateByPrimaryKeySelective(longArticlesText);
+            return longArticlesText;
+        }
+        return null;
+
+    }
+
 }

+ 256 - 0
long-article-server/src/main/java/com/tzld/piaoquan/longarticle/service/local/impl/MatchVideoServiceImpl.java

@@ -0,0 +1,256 @@
+package com.tzld.piaoquan.longarticle.service.local.impl;
+
+import cn.hutool.json.JSONObject;
+import com.alibaba.fastjson.JSONArray;
+import com.tzld.piaoquan.longarticle.dao.mapper.CrawlerVideoMapper;
+import com.tzld.piaoquan.longarticle.dao.mapper.MatchVideoMapper;
+import com.tzld.piaoquan.longarticle.model.po.*;
+import com.tzld.piaoquan.longarticle.model.vo.MatchVideoVo;
+import com.tzld.piaoquan.longarticle.service.local.KimiService;
+import com.tzld.piaoquan.longarticle.utils.*;
+import com.tzld.piaoquan.longarticle.utils.other.*;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+
+import java.util.*;
+
+@Service
+public class MatchVideoServiceImpl {
+
+    private static final int MAX_NUM = 3;
+
+    @Autowired
+    KimiService kimiService;
+
+    @Autowired
+    private MatchVideoMapper matchVideoMapper;
+
+    @Autowired
+    private CrawlerVideoMapper crawlerVideoMapper;
+
+    public void addMatchVideo(MatchVideoVo matchVideoVo) {
+        MatchVideoExample example = new MatchVideoExample();
+        example.createCriteria().andContentIdEqualTo(matchVideoVo.getArticleId()).andGhIdEqualTo(matchVideoVo.getGhId());
+        long l = matchVideoMapper.countByExample(example);
+        if (l > 0) {
+            return;
+        }
+        String traceId = "search-" + UUID.randomUUID();
+        MatchVideo matchVideo = new MatchVideo();
+        matchVideo.setTraceId(traceId);
+        matchVideo.setContentId(matchVideoVo.getArticleId());
+        matchVideo.setAccountName(matchVideoVo.getAccountName());
+        matchVideo.setFlowPoolLevel(matchVideo.getFlowPoolLevel());
+        matchVideo.setProcessTimes(1);
+        matchVideo.setContentStatus(0);
+        long timestamp = System.currentTimeMillis() / 1000;
+        matchVideo.setContentStatusUpdateTime(Long.valueOf(timestamp).intValue());
+        matchVideoMapper.insertSelective(matchVideo);
+    }
+
+    public void matchContent() {
+        CrawlerVideoExample example = new CrawlerVideoExample();
+        example.createCriteria().andDownloadStatusEqualTo(0);
+        crawlerVideoMapper.selectByExample(example);
+    }
+
+    public Boolean existHistoryContent(String contentId) {
+        CrawlerVideoExample example = new CrawlerVideoExample();
+        example.createCriteria().andContentIdEqualTo(contentId).andDownloadStatusEqualTo(2);
+        long l = crawlerVideoMapper.countByExample(example);
+        return l >= MAX_NUM;
+    }
+
+    public void pushOss(String traceId) {
+        CrawlerVideoExample example = new CrawlerVideoExample();
+        example.createCriteria().andTraceIdEqualTo(traceId).andDownloadStatusEqualTo(0);
+        List<CrawlerVideo> crawlerVideoList = crawlerVideoMapper.selectByExampleWithBLOBs(example);
+        if (CollectionUtils.isEmpty(crawlerVideoList)) {
+            return;
+        }
+        for (CrawlerVideo crawlerVideo : crawlerVideoList) {
+            String platform = crawlerVideo.getPlatform();
+            String outVideoId = crawlerVideo.getOutVideoId();
+            String videoPath = VideoDownloader.generateVideoPath(platform, outVideoId);
+            String coverPath = VideoDownloader.generateCoverPath(platform, outVideoId);
+            videoPath = VideoDownloader.downloadVideo(videoPath, platform, crawlerVideo.getVideoUrl(), "video");
+            coverPath = VideoDownloader.downloadCover(coverPath, platform, crawlerVideo.getCoverUrl());
+            if (StringUtils.isNotEmpty(videoPath) && StringUtils.isNotEmpty(coverPath)) {
+                String videoOssPath = OSSUploader.uploadToOSS(videoPath, "video");
+                String coverOssPath = OSSUploader.uploadToOSS(coverPath, "image");
+                if (StringUtils.isNotEmpty(videoOssPath) && StringUtils.isNotEmpty(coverOssPath)) {
+                    CrawlerVideo udpateCrawlerVideo = new CrawlerVideo();
+                    udpateCrawlerVideo.setVideoOssPath(videoOssPath);
+                    udpateCrawlerVideo.setCoverOssPath(coverOssPath);
+                    udpateCrawlerVideo.setId(crawlerVideo.getId());
+                    udpateCrawlerVideo.setDownloadStatus(2);
+                    crawlerVideoMapper.updateByPrimaryKeySelective(udpateCrawlerVideo);
+                }
+            }
+        }
+    }
+
+    public boolean addVideo(String contentId, String traceId) {
+        LongArticlesText kimiText = kimiService.getKimiText(contentId);
+        if (kimiText == null) {
+            //TODO 报警  KIMI查询不到
+            return false;
+        }
+        List<JSONObject> video = getVideo(contentId, kimiText);
+        List<JSONObject> jsonObjects = SortUtil.titleSimilarityRank(kimiText.getKimiTitle(), video);
+        List<CrawlerVideo> crawlerVideoList = getCrawlerVideoList(jsonObjects, traceId, contentId);
+        if (!CollectionUtils.isEmpty(crawlerVideoList)) {
+            for (CrawlerVideo crawlerVideo : crawlerVideoList) {
+                crawlerVideoMapper.insertSelective(crawlerVideo);
+            }
+        }
+        System.out.println();
+        System.out.println();
+        System.out.println(crawlerVideoList);
+        return false;
+    }
+
+    private List<CrawlerVideo> getCrawlerVideoList(List<JSONObject> jsonObjects, String traceId, String contentId) {
+        List<CrawlerVideo> crawlerVideoList = new ArrayList<>();
+        for (JSONObject jsonObject : jsonObjects) {
+            String platform = jsonObject.getStr("platform");
+            CrawlerVideo crawlerVideo = new CrawlerVideo();
+            crawlerVideo.setContentId(contentId);
+            crawlerVideo.setTraceId(traceId);
+            crawlerVideo.setPlatform(platform);
+            if (Objects.equals(platform, "dy_search")) {
+                crawlerVideo.setOutVideoId(jsonObject.getStr("channel_content_id"));
+                crawlerVideo.setVideoTitle(jsonObject.getStr("title"));
+                crawlerVideo.setPublishTime(jsonObject.getDate("publish_timestamp"));
+                List<JSONObject> videoUrlList = jsonObject.get("video_url_list", List.class);
+                if (!CollectionUtils.isEmpty(videoUrlList)) {
+                    crawlerVideo.setVideoUrl(videoUrlList.get(0).getStr("video_url"));
+                    crawlerVideo.setDuration(videoUrlList.get(0).getInt("video_duration"));
+                }
+                List<JSONObject> imageUrlList = jsonObject.get("image_url_list", List.class);
+                if (!CollectionUtils.isEmpty(imageUrlList)) {
+                    crawlerVideo.setCoverUrl(imageUrlList.get(0).getStr("image_url"));
+                }
+                crawlerVideo.setPlayCount(jsonObject.getInt("play_count"));
+                crawlerVideo.setLikeCount(jsonObject.getInt("like_count"));
+                crawlerVideo.setScore(jsonObject.getFloat("score"));
+                if (cheakCrawlerVideo(crawlerVideo)) {
+                    crawlerVideoList.add(crawlerVideo);
+                }
+
+                continue;
+            }
+            if (Objects.equals(platform, "baidu_search")) {
+                crawlerVideo.setOutVideoId(jsonObject.getStr("id"));
+                crawlerVideo.setVideoTitle(jsonObject.getStr("title"));
+                crawlerVideo.setPublishTime(jsonObject.getDate("publish_timestamp"));
+                crawlerVideo.setVideoUrl(jsonObject.getStr("playurl"));
+                crawlerVideo.setCoverUrl(jsonObject.getStr("poster"));
+                crawlerVideo.setPlayCount(jsonObject.getInt("play_cnt"));
+                crawlerVideo.setLikeCount(jsonObject.getInt("like_count") == null ? 0 : jsonObject.getInt("like_count"));
+                crawlerVideo.setDuration(jsonObject.getInt("duration"));
+                crawlerVideo.setScore(jsonObject.getFloat("score"));
+                if (cheakCrawlerVideo(crawlerVideo)) {
+                    crawlerVideoList.add(crawlerVideo);
+                }
+                continue;
+            }
+        }
+        return crawlerVideoList;
+    }
+
+    private boolean cheakCrawlerVideo(CrawlerVideo crawlerVideo) {
+        if (StringUtils.isEmpty(crawlerVideo.getOutVideoId())) {
+            return false;
+        }
+        if (StringUtils.isEmpty(crawlerVideo.getVideoUrl())) {
+            return false;
+        }
+        if (StringUtils.isEmpty(crawlerVideo.getCoverUrl())) {
+            return false;
+        }
+        if (StringUtils.isEmpty(crawlerVideo.getPlatform())) {
+            return false;
+        }
+        if (Objects.isNull(crawlerVideo.getPublishTime())) {
+            crawlerVideo.setPublishTime(new Date());
+        }
+        if (Objects.isNull(crawlerVideo.getDuration())) {
+            crawlerVideo.setDuration(0);
+        }
+        if (Objects.isNull(crawlerVideo.getPlayCount())) {
+            crawlerVideo.setPlayCount(0);
+        }
+        if (Objects.isNull(crawlerVideo.getLikeCount())) {
+            crawlerVideo.setLikeCount(0);
+        }
+        if (Objects.isNull(crawlerVideo.getShareCount())) {
+            crawlerVideo.setShareCount(0);
+        }
+        return true;
+    }
+
+    public List<JSONObject> getVideo(String contentId, LongArticlesText kimiText) {
+
+        List<JSONObject> res = new ArrayList<>();
+        List<JSONObject> kimiSummarys = searchVideo(kimiText.getKimiSummary().substring(0, 15), new ArrayList<>(), "");
+        if (!CollectionUtils.isEmpty(kimiSummarys)) {
+            res.addAll(kimiSummarys);
+        }
+        if (res.size() > 3) {
+            return res;
+        }
+        List<JSONObject> kimiTitles = searchVideo(kimiText.getKimiTitle().substring(0, 15), new ArrayList<>(), "");
+        if (!CollectionUtils.isEmpty(kimiTitles)) {
+            res.addAll(kimiTitles);
+        }
+        if (res.size() > 3) {
+            return res;
+        }
+        String kimiKeys = kimiText.getKimiKeys();
+        JSONArray jsonArray = JSONArray.parseArray(kimiKeys);
+        if (jsonArray == null || jsonArray.isEmpty()) {
+            return res;
+        }
+        for (int i = 0; i < jsonArray.size(); i++) {
+            String key = jsonArray.getString(i);
+            List<JSONObject> keys = searchVideo(key, new ArrayList<>(), "");
+            if (!CollectionUtils.isEmpty(keys)) {
+                res.addAll(keys);
+            }
+            if (res.size() > 3) {
+                return res;
+            }
+        }
+        return res;
+    }
+
+
+    public List<JSONObject> searchVideo(String keyword, List<String> words, String traceId) {
+//        List<JSONObject> jsonObjects = DouyinSearch.douyinSearch(keyword, words, traceId);
+//        if (!CollectionUtils.isEmpty(jsonObjects)) {
+//            for (JSONObject jsonObject : jsonObjects) {
+//                jsonObject.put("platform", "dy_search");
+//            }
+//        }
+//        if (jsonObjects.size() >= 3) {
+//            return jsonObjects;
+//        }
+//        List<JSONObject> jsonObjects1 = HkspSearch.hkspSearch(keyword, words, traceId);
+//        if (CollectionUtils.isEmpty(jsonObjects1)) {
+//            return jsonObjects;
+//        }
+//        for (JSONObject jsonObject : jsonObjects1) {
+//            jsonObject.put("platform", "baidu_search");
+//        }
+//        if (CollectionUtils.isEmpty(jsonObjects)) {
+//            return jsonObjects1;
+//        }
+//        jsonObjects.addAll(jsonObjects1);
+//        return jsonObjects;
+        return null;
+    }
+
+}

+ 1 - 1
long-article-server/src/main/java/com/tzld/piaoquan/longarticle/utils/HttpPoolClientUtil.java

@@ -164,7 +164,7 @@ public class HttpPoolClientUtil {
      * @param retryCount     重试次数
      * @return httpclient instance
      */
-    protected static HttpPoolClientUtil create(int connectTimeout, int socketTimeout, int maxPerRoute, int maxTotal, int retryCount, int connectionWaitTimeout) {
+    public static HttpPoolClientUtil create(int connectTimeout, int socketTimeout, int maxPerRoute, int maxTotal, int retryCount, int connectionWaitTimeout) {
         try {
             RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(connectTimeout).setSocketTimeout(socketTimeout).setConnectionRequestTimeout(connectionWaitTimeout).build();
             CloseableHttpClient client = HttpClientBuilder.create()

+ 85 - 0
long-article-server/src/main/java/com/tzld/piaoquan/longarticle/utils/other/DeepSeekAPI.java

@@ -0,0 +1,85 @@
+package com.tzld.piaoquan.longarticle.utils.other;
+
+import cn.hutool.http.HttpRequest;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.util.CollectionUtils;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class DeepSeekAPI {
+
+    /**
+     * 聊天端点
+     */
+    static String chatEndpoint = "https://api.deepseek.com/chat/completions";
+    /**
+     * api密匙
+     */
+    static String apiKey = "Bearer sk-30d00642c8b643cab3f54e5672f651c9";
+
+    /**
+     * 发送消息
+     *
+     * @param txt 内容
+     * @return {@link String}
+     */
+    public static String chat(String txt) {
+        return chat(txt, null);
+    }
+
+    public static String chat(String txt, Map<String, String> responseFormat) {
+        Map<String, Object> paramMap = new HashMap<>();
+        paramMap.put("model", "deepseek-chat");
+        if (!CollectionUtils.isEmpty(responseFormat)) {
+            paramMap.put("response_format", responseFormat);
+        }
+        List<Map<String, String>> dataList = new ArrayList<>();
+        dataList.add(new HashMap<String, String>() {{
+            put("role", "user");
+            put("content", txt);
+        }});
+        paramMap.put("messages", dataList);
+        JSONObject message = null;
+        try {
+            String body = HttpRequest.post(chatEndpoint)
+                    .header("Authorization", apiKey)
+                    .header("Content-Type", "application/json")
+                    .body(JSONObject.toJSONString(paramMap))
+                    .execute()
+                    .body();
+            JSONObject jsonObject = JSONObject.parseObject(body);
+            JSONArray choices = jsonObject.getJSONArray("choices");
+            JSONObject result = choices.getJSONObject(0);
+            message = result.getJSONObject("message");
+            return message.getString("content");
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return "";
+    }
+
+    public static void main(String[] args) {
+        String title = "63岁大爷退休金13000,再婚娶37岁女护士,新婚当晚,他看到她的肚子,瞬间晕倒在地";
+        String single_title_prompt = "请将以上标题改写成适合小程序点击和传播的小程序标题,小程序标题的写作规范如下,请学习后进行小程序标题的编写。直接输出最终的小程序标题\n" +
+                "        小程序标题写作规范:\n" +
+                "        1.要点前置:将最重要的信息放在标题的最前面,以快速吸引读者的注意力。例如,“5月一辈子同学,三辈子亲,送给我的老同学,听哭无数人!”中的“5月”和“一辈子同学,三辈子亲”都是重要的信息点。\n" +
+                "        2.激发情绪:使用能够触动人心的语言,激发读者的情感共鸣。如“只剩两人同学聚会,看后感动落泪。”使用“感动落泪”激发读者的同情和怀旧情绪。\n" +
+                "        3.使用数字和特殊符号:数字可以提供具体性,而特殊符号如“\uD83D\uDD34”、“\uD83D\uDE04”、“\uD83D\uDD25”等可以吸引视觉注意力,增加点击率。\n" +
+                "        4.悬念和好奇心:创建悬念或提出问题,激发读者的好奇心。例如,“太神奇了!长江水位下降,重庆出现惊奇一幕!”中的“惊奇一幕”就是一个悬念。\n" +
+                "        5.名人效应:如果内容与知名人士相关,提及他们的名字可以增加标题的吸引力。\n" +
+                "        6.社会价值观:触及读者的文化和社会价值观,如家庭、友情、国家荣誉等。\n" +
+                "        7.标点符号的运用:使用感叹号、问号等标点来增强语气和情感表达。\n" +
+                "        8.直接的语言:使用直白、口语化的语言,易于理解,如“狗屁股,笑死我了!”。\n" +
+                "        9.热点人物或事件:提及当前的热点人物或事件,利用热点效应吸引读者。\n" +
+                "        10.字数适中:保持标题在10-20个字之间,既不过长也不过短,确保信息的完整性和吸引力。\n" +
+                "        11.适当的紧迫感:使用“最新”、“首次”、“紧急”等词汇,创造一种紧迫感,促使读者立即行动。\n" +
+                "        12.情感或价值诉求:使用如“感动”、“泪目”、“经典”等词汇,直接与读者的情感或价值观产生共鸣。\n" +
+                "        避免误导:确保标题准确反映内容,避免夸大或误导读者。";
+        System.out.println(chat(title + "\n" + single_title_prompt));
+    }
+}

+ 88 - 0
long-article-server/src/main/java/com/tzld/piaoquan/longarticle/utils/other/DouyinSearch.java

@@ -0,0 +1,88 @@
+package com.tzld.piaoquan.longarticle.utils.other;
+
+import cn.hutool.http.HttpRequest;
+import cn.hutool.http.HttpResponse;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class DouyinSearch {
+
+    public static void main(String[] args) {
+        List<String> sensitiveWords = new ArrayList<>();
+        System.out.println(douyinSearch("你好", sensitiveWords, ""));
+    }
+
+    public static List<JSONObject> douyinSearch(String keyword, List<String> sensitiveWords, String traceId) {
+        String url = "http://8.217.190.241:8888/crawler/dou_yin/top_hub_content";
+        JSONObject payload = new JSONObject();
+        payload.put("keyword", keyword);
+        payload.put("category", "全部");
+        payload.put("period", "近90天");
+        payload.put("content_modal", "视频");
+        payload.put("cursor", "");
+
+        HttpResponse response = HttpRequest.post(url)
+                .header("Content-Type", "application/json")
+                .body(payload.toString())
+                .execute();
+
+        List<JSONObject> resultList = new ArrayList<>();
+
+        try {
+            JSONObject jsonResponse = JSONObject.parseObject(response.body());
+            JSONArray dtList = jsonResponse.getJSONObject("data").getJSONArray("data");
+//            List<JSONObject> dtList = jsonResponse.getByPath("data.data", List.class);
+            for (int i = 0; i < dtList.size(); i++) {
+                JSONObject obj = dtList.getJSONObject(i);
+                try {
+                    String title = obj.getString("video_desc");
+                    String videoId = obj.getString("video_id");
+                    int duration = obj.getInteger("duration");
+
+                    if (sensitiveFlag(sensitiveWords.toString(), title) && duration < 30000) {
+                        JSONObject res = douyinDetail(videoId);
+                        if (res != null) {
+                            resultList.add(res);
+                        }
+                    }
+                } catch (Exception e) {
+                    System.out.println(e.getMessage());
+                    // 处理异常
+                }
+            }
+            return resultList;
+        } catch (Exception e) {
+            return new ArrayList<>();
+        }
+    }
+
+    public static JSONObject douyinDetail(String videoId) {
+        System.out.println(videoId);
+        String url = "http://8.217.190.241:8888/crawler/dou_yin/detail";
+        JSONObject payload = new JSONObject();
+        payload.put("content_id", videoId);
+
+        HttpResponse response = HttpRequest.post(url)
+                .header("Content-Type", "application/json")
+                .body(payload.toString())
+                .execute();
+
+        JSONObject videoInfo = JSONObject.parseObject(response.body()).getJSONObject("data").getJSONObject("data");
+        if ("note".equals(videoInfo.getString("content_type"))) {
+            return null;
+        } else {
+            return videoInfo;
+        }
+    }
+
+    private static boolean sensitiveFlag(String sensitiveWords, String title) {
+        // 实现敏感词检查逻辑
+        return true; // 示例
+    }
+
+}
+
+

+ 85 - 0
long-article-server/src/main/java/com/tzld/piaoquan/longarticle/utils/other/GetContentUtil.java

@@ -0,0 +1,85 @@
+package com.tzld.piaoquan.longarticle.utils.other;
+
+import com.alibaba.fastjson.JSONObject;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.Objects;
+
+public class GetContentUtil {
+    public static void main(String[] args) {
+        String articleTitle = "中国宣布重大消息,韩国被吓,美国不服气,俄罗斯和朝鲜都笑了!";
+        String articleContent = "在当前国际政治舞台上,中朝关系、朝鲜半岛安全局势以及广泛的国际政治关系如同一副错综复杂的画卷,引人注目。\n" +
+                "近期,中国外交部副部长孙卫东带领的代表团访问平壤的消息,为这幅画卷添上了新的一笔,引发了外界对中朝关系未来走向的广泛关注。\n" +
+                "自1949年10月6日中华人民共和国与朝鲜民主主义人民共和国正式建交以来,两国之间的友好合作关系历经风雨,但总体上保持稳定发展。\n" +
+                "两国在多个领域展开了深入合作,包括文化、教育、科技、体育和民生等领域,这些合作不仅加深了两国人民之间的相互理解和友谊,也为地区乃至全球的和平与发展作出了贡献。\n" +
+                "在经济领域,作为朝鲜的重要贸易伙伴和物质援助国,中国对于朝鲜的经济发展起着至关重要的支持作用。中国提供的各种援助,帮助朝鲜在经济上保持稳定,助推其发展。\n" +
+                "而在政治领域,中国一直致力于推动实现半岛无核化,维护朝鲜半岛及地区的和平稳定。在这一过程中,中朝两国保持了密切的沟通与协调。\n" +
+                "\n" +
+                "然而,朝鲜半岛的局势一直是国际政治关系中的一个热点问题。近年来,美国、日本和韩国加强了对朝鲜的军事压力,通过扩大联合军演、部署战略武器等方式,不断提升在半岛的军事存在感,这无疑增加了地区的紧张气氛。对此,俄罗斯表达了对朝鲜的支持,批评美日韩的军事行动,认为这些行动加剧了地区的不稳定因素。\n" +
+                "在这个背景下,中国外交部副部长孙卫东此次访问朝鲜,显得尤为重要。此访旨在深化中朝传统友好,特别是在两国建交75周年之际,进一步加强经济合作,并就朝鲜半岛局势等共同关心的国际和地区问题交换意见。\n" +
+                "这次访问不仅是两国关系发展的一个里程碑,也是在当前复杂国际形势下,中国致力于维护地区和平稳定、推动构建人类命运共同体理念的一个重要体现。\n" +
+                "中朝双方的此次接触,无疑将为推动解决朝鲜半岛问题、维护地区和平稳定发挥积极作用。作为朝鲜半岛问题的直接相关方和重要的地区国家,中国一直倡导通过对话和协商解决问题,反对外部势力的军事干预和压力策略,这一立场在当前形势下显得尤为重要。\n" +
+                "\n" +
+                "未来,随着国际形势的不断变化,中朝之间的合作将更加多元化和深入。两国之间不仅在经济上互为支持,在维护地区和平稳定、推动构建更加公正合理的国际政治经济新秩序方面,也将发挥更加积极的作用。\n" +
+                "作为国际社会的一份子,希望所有国家都能本着相互尊重、平等互利的原则,共同努力,为实现地区乃至全球的和平、稳定与发展作出应有的贡献。\n" +
+                "在国际政治的大棋局中,中朝关系、朝鲜半岛的安全局势和国际政治关系的发展方向,始终是全球关注的焦点。本次中国外交部副部长孙卫东访问朝鲜,不仅是对两国传统友谊的再次肯定,更是在复杂的国际环境中,为维护地区和平稳定、推动构建人类命运共同体贡献中国的智慧和力量。\n" +
+                "让我们共同关注并支持这一重要的外交行动,期待它为地区乃至全球的和平与发展带来积极影响。转发此消息,让更多人了解国际局势的最新动向,共同为世界和平与稳定发声。";
+        String titleTxt = articleTitle + "\n" + promptGenerate("title");
+//        String kimiTitle = KimiAPI.chat(titleTxt);
+        String kimiTitle = "";
+        if (StringUtils.isEmpty(kimiTitle)) {
+            kimiTitle = GptAPI.chat(titleTxt);
+        }
+        if (StringUtils.isEmpty(kimiTitle)) {
+            //TODO 处理异常
+            System.out.println("error");
+        }
+        kimiTitle = kimiTitle.replaceAll("'", "").replaceAll("\"", "").replace("\\", "");
+        System.out.println(kimiTitle);
+        String contentTxt = promptGenerate("text") + "\n" + articleContent;
+//        String kimiContent = KimiAPI.chat(contentTxt);
+        String kimiContent = "";
+        if (StringUtils.isEmpty(kimiContent)) {
+            kimiContent = GptAPI.chat(contentTxt);
+        }
+        if (StringUtils.isEmpty(kimiContent)) {
+            //TODO 处理异常
+            System.out.println("error");
+        }
+        JSONObject jsonObject = JSONObject.parseObject(kimiContent);
+
+
+    }
+
+
+    public static String promptGenerate(String promptType) {
+        if (Objects.equals(promptType, "title")) {
+            return " 请将以上标题改写成适合小程序点击和传播的小程序标题,小程序标题的写作规范如下,请学习后进行小程序标题的编写。直接输出最终的小程序标题\n" +
+                    "                小程序标题写作规范:\n" +
+                    "                1.要点前置:将最重要的信息放在标题的最前面,以快速吸引读者的注意力。例如,“5月一辈子同学,三辈子亲,送给我的老同学,听哭无数人!”中的“5月”和“一辈子同学,三辈子亲”都是重要的信息点。\n" +
+                    "                2.激发情绪:使用能够触动人心的语言,激发读者的情感共鸣。如“只剩两人同学聚会,看后感动落泪。”使用“感动落泪”激发读者的同情和怀旧情绪。\n" +
+                    "                3.使用数字和特殊符号:数字可以提供具体性,而特殊符号如“\uD83D\uDD34”、“\uD83D\uDE04”、“\uD83D\uDD25”等可以吸引视觉注意力,增加点击率。\n" +
+                    "                4.悬念和好奇心:创建悬念或提出问题,激发读者的好奇心。例如,“太神奇了!长江水位下降,重庆出现惊奇一幕!”中的“惊奇一幕”就是一个悬念。\n" +
+                    "                5.名人效应:如果内容与知名人士相关,提及他们的名字可以增加标题的吸引力。\n" +
+                    "                6.社会价值观:触及读者的文化和社会价值观,如家庭、友情、国家荣誉等。\n" +
+                    "                7.标点符号的运用:使用感叹号、问号等标点来增强语气和情感表达。\n" +
+                    "                8.直接的语言:使用直白、口语化的语言,易于理解,如“狗屁股,笑死我了!”。\n" +
+                    "                9.热点人物或事件:提及当前的热点人物或事件,利用热点效应吸引读者。\n" +
+                    "                10.字数适中:保持标题在10-20个字之间,既不过长也不过短,确保信息的完整性和吸引力。\n" +
+                    "                11.适当的紧迫感:使用“最新”、“首次”、“紧急”等词汇,创造一种紧迫感,促使读者立即行动。\n" +
+                    "                12.情感或价值诉求:使用如“感动”、“泪目”、“经典”等词汇,直接与读者的情感或价值观产生共鸣。\n" +
+                    "                避免误导:确保标题准确反映内容,避免夸大或误导读者。";
+
+        } else if (Objects.equals(promptType, "text")) {
+            return "请从我给你的文章中挖掘出以下信息并且返回如下结果。\n" +
+                    "                你返回的结果是一个 json, 格式如下:\n" +
+                    "                {\n" +
+                    "                    \"content_keys\": [] # 同时提供三个与文章内容高度相关的关键词,这些关键词将用于网络上搜索相关视频内容,\n" +
+                    "                    \"content_title\": 一个总结性的标题,该标题应简洁并能够反映文章的主要内容\n" +
+                    "                }\n" +
+                    "                你需要处理的文本是:";
+        } else {
+            return "用中文帮我回答以下问题";
+        }
+    }
+}

+ 90 - 0
long-article-server/src/main/java/com/tzld/piaoquan/longarticle/utils/other/GptAPI.java

@@ -0,0 +1,90 @@
+package com.tzld.piaoquan.longarticle.utils.other;
+
+import cn.hutool.core.convert.ConvertException;
+import cn.hutool.http.HttpException;
+import cn.hutool.http.HttpRequest;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+
+import java.util.*;
+
+public class GptAPI {
+
+    /**
+     * 聊天端点
+     */
+    static String chatEndpoint = "https://api.openai.com/v1/chat/completions";
+    /**
+     * api密匙
+     */
+    static String apiKey = "Bearer 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";
+
+    /**
+     * 发送消息
+     *
+     * @param txt 内容
+     * @return {@link String}
+     */
+    public static String chat(String txt) {
+        Map<String, Object> paramMap = new HashMap<>();
+        paramMap.put("model", "gpt-3.5-turbo");
+        List<Map<String, String>> dataList = new ArrayList<>();
+        dataList.add(new HashMap<String, String>() {{
+            put("role", "system");
+            put("content", "你是一名优秀的中文工作者");
+        }});
+        dataList.add(new HashMap<String, String>() {{
+            put("role", "user");
+            put("content", txt);
+        }});
+        paramMap.put("messages", dataList);
+        JSONObject message = null;
+        try {
+            String body = HttpRequest.post(chatEndpoint)
+                    .header("Authorization", apiKey)
+                    .header("Content-Type", "application/json")
+                    .body(JSONObject.toJSONString(paramMap))
+                    .execute()
+                    .body();
+            JSONObject jsonObject = JSONObject.parseObject(body);
+            JSONArray choices = jsonObject.getJSONArray("choices");
+            JSONObject result = choices.getJSONObject(0);
+            message = result.getJSONObject("message");
+        } catch (HttpException e) {
+            return "出现了异常";
+        }
+        return message.getString("content");
+    }
+
+    public static String promptGenerate(String promptType) {
+        if (Objects.equals(promptType, "title")) {
+            return " 请将以上标题改写成适合小程序点击和传播的小程序标题,小程序标题的写作规范如下,请学习后进行小程序标题的编写。直接输出最终的小程序标题\n" +
+                    "                小程序标题写作规范:\n" +
+                    "                1.要点前置:将最重要的信息放在标题的最前面,以快速吸引读者的注意力。例如,“5月一辈子同学,三辈子亲,送给我的老同学,听哭无数人!”中的“5月”和“一辈子同学,三辈子亲”都是重要的信息点。\n" +
+                    "                2.激发情绪:使用能够触动人心的语言,激发读者的情感共鸣。如“只剩两人同学聚会,看后感动落泪。”使用“感动落泪”激发读者的同情和怀旧情绪。\n" +
+                    "                3.使用数字和特殊符号:数字可以提供具体性,而特殊符号如“\uD83D\uDD34”、“\uD83D\uDE04”、“\uD83D\uDD25”等可以吸引视觉注意力,增加点击率。\n" +
+                    "                4.悬念和好奇心:创建悬念或提出问题,激发读者的好奇心。例如,“太神奇了!长江水位下降,重庆出现惊奇一幕!”中的“惊奇一幕”就是一个悬念。\n" +
+                    "                5.名人效应:如果内容与知名人士相关,提及他们的名字可以增加标题的吸引力。\n" +
+                    "                6.社会价值观:触及读者的文化和社会价值观,如家庭、友情、国家荣誉等。\n" +
+                    "                7.标点符号的运用:使用感叹号、问号等标点来增强语气和情感表达。\n" +
+                    "                8.直接的语言:使用直白、口语化的语言,易于理解,如“狗屁股,笑死我了!”。\n" +
+                    "                9.热点人物或事件:提及当前的热点人物或事件,利用热点效应吸引读者。\n" +
+                    "                10.字数适中:保持标题在10-20个字之间,既不过长也不过短,确保信息的完整性和吸引力。\n" +
+                    "                11.适当的紧迫感:使用“最新”、“首次”、“紧急”等词汇,创造一种紧迫感,促使读者立即行动。\n" +
+                    "                12.情感或价值诉求:使用如“感动”、“泪目”、“经典”等词汇,直接与读者的情感或价值观产生共鸣。\n" +
+                    "                避免误导:确保标题准确反映内容,避免夸大或误导读者。";
+
+        } else if (Objects.equals(promptType, "text")) {
+            return "请从我给你的文章中挖掘出以下信息并且返回如下结果。\n" +
+                    "                你返回的结果是一个 json, 格式如下:\n" +
+                    "                {\n" +
+                    "                    \"content_keys\": [] # 同时提供三个与文章内容高度相关的关键词,这些关键词将用于网络上搜索相关视频内容,\n" +
+                    "                    \"content_title\": 一个总结性的标题,该标题应简洁并能够反映文章的主要内容\n" +
+                    "                }\n" +
+                    "                你需要处理的文本是:";
+        } else {
+            return "用中文帮我回答以下问题";
+        }
+    }
+
+}

+ 151 - 0
long-article-server/src/main/java/com/tzld/piaoquan/longarticle/utils/other/HkspSearch.java

@@ -0,0 +1,151 @@
+package com.tzld.piaoquan.longarticle.utils.other;
+
+import cn.hutool.http.HttpRequest;
+import cn.hutool.http.HttpResponse;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+
+
+import java.io.IOException;
+import java.net.*;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.List;
+import java.util.UUID;
+
+public class HkspSearch {
+
+    public static void main(String[] args) {
+        List<String> sensitiveWords = new ArrayList<>();
+        System.out.println(hkspSearch("你好", sensitiveWords, ""));
+    }
+
+    public static List<JSONObject> hkspSearch(String key, List<String> sensitiveWords, String traceId) {
+        try {
+            long timestampMilliseconds = System.currentTimeMillis();
+            String url = "https://haokan.baidu.com/haokan/ui-search/pc/search/video";
+            String encodedKey = URLEncoder.encode(key, "UTF-8");
+            String strings = String.format("%d_%s_%d_%d_%d", 1, encodedKey, 10, timestampMilliseconds, 1);
+            String sign = md5(strings);
+
+            JSONObject params = new JSONObject();
+            params.put("pn", 1);
+            params.put("rn", 10);
+            params.put("type", "video");
+            params.put("query", key);
+            params.put("sign", sign);
+            params.put("version", 1);
+            params.put("timestamp", timestampMilliseconds);
+
+            String base64String = Base64.getEncoder().encodeToString(UUID.randomUUID().toString().getBytes());
+            // 这行代码是身份验证的关键配置,不然身份验证不起作用
+            System.setProperty("jdk.http.auth.tunneling.disabledSchemes", "");
+            // 身份验证
+            Authenticator.setDefault(
+                    new Authenticator() {
+                        public PasswordAuthentication getPasswordAuthentication() {
+                            return new PasswordAuthentication(
+                                    "t11983523373311", "mtuhdr2z".toCharArray());
+                        }
+                    }
+            );
+            HttpResponse response = HttpRequest.get(url)
+                    .header("authority", "haokan.baidu.com")
+                    .header("accept", "*/*")
+                    .header("accept-language", "zh,en;q=0.9,zh-CN;q=0.8")
+                    .header("cookie", "BIDUPSID=" + base64String)
+                    .header("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0") // 假用户代理
+                    .header("x-requested-with", "xmlhttprequest")
+                    .timeout(120000) // 设置超时时间
+                    .setProxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress("l901.kdltps.com", 15818)))
+                    .form(params)
+                    .execute();
+
+            List<JSONObject> resultList = new ArrayList<>();
+            System.out.println(response.body());
+            JSONObject jsonResponse = JSONObject.parseObject(response.body());
+            JSONArray dataList = jsonResponse.getJSONObject("data").getJSONArray("list");
+//            List<JSONObject> dataList = jsonResponse.getByPath("data.list", List.class);
+
+            for (int i = 0; i < dataList.size(); i++) {
+                JSONObject data = dataList.getJSONObject(i);
+                try {
+                    String videoId = data.getString("vid");
+                    String title = data.getString("title");
+                    int duration = parseDuration(data.getString("duration"));
+
+                    if (sensitiveFlag(sensitiveWords, title) && duration <= 300) {
+                        JSONObject res = getVideoDetail(videoId);
+                        if (res != null) {
+                            resultList.add(res);
+                        }
+                    }
+                } catch (Exception e) {
+                    // 处理异常
+                }
+            }
+            return resultList;
+        } catch (Exception e) {
+            return new ArrayList<>();
+        }
+    }
+
+    private static int parseDuration(String duration) {
+        String[] parts = duration.split(":");
+        return Integer.parseInt(parts[0]) * 60 + Integer.parseInt(parts[1]);
+    }
+
+    private static String md5(String input) {
+        try {
+            MessageDigest md = MessageDigest.getInstance("MD5");
+            byte[] messageDigest = md.digest(input.getBytes());
+            StringBuilder sb = new StringBuilder();
+            for (byte b : messageDigest) {
+                sb.append(String.format("%02x", b));
+            }
+            return sb.toString();
+        } catch (NoSuchAlgorithmException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private static boolean sensitiveFlag(List<String> sensitiveWords, String title) {
+        // 实现敏感词检查逻辑
+        return true; // 示例
+    }
+
+    public static JSONObject getVideoDetail(String videoId) {
+        String url = "https://haokan.baidu.com/v";
+        JSONObject params = new JSONObject();
+        params.put("vid", videoId);
+        params.put("_format", "json");
+
+        String base64String = Base64.getEncoder().encodeToString(UUID.randomUUID().toString().getBytes());
+        // 这行代码是身份验证的关键配置,不然身份验证不起作用
+        System.setProperty("jdk.http.auth.tunneling.disabledSchemes", "");
+        // 身份验证
+        Authenticator.setDefault(
+                new Authenticator() {
+                    public PasswordAuthentication getPasswordAuthentication() {
+                        return new PasswordAuthentication(
+                                "t11983523373311", "mtuhdr2z".toCharArray());
+                    }
+                }
+        );
+        HttpResponse response = HttpRequest.get(url)
+                .header("Accept", "*/*")
+                .header("cookie", "BIDUPSID=" + base64String)
+                .header("Accept-Language", "en,zh;q=0.9,zh-CN;q=0.8")
+                .header("Cache-Control", "no-cache")
+                .header("Connection", "keep-alive")
+                .header("Content-Type", "application/x-www-form-urlencoded")
+                .header("Referer", "https://haokan.baidu.com")
+                .form(params)
+                .setProxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress("l901.kdltps.com", 15818)))
+                .execute();
+        return JSONObject.parseObject(response.body()).getJSONObject("data").getJSONObject("apiData").getJSONObject("curVideoMeta");
+    }
+
+}

+ 92 - 0
long-article-server/src/main/java/com/tzld/piaoquan/longarticle/utils/other/HttpClientUtil.java

@@ -0,0 +1,92 @@
+package com.tzld.piaoquan.longarticle.utils.other;
+
+import com.tzld.piaoquan.longarticle.utils.HttpPoolClientUtil;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * http util
+ *
+ * @author xueyiming
+ */
+public class HttpClientUtil {
+
+    private static final String DEFAULT_SHARED_KEY = "DEFAULT_SHARED_KEY";
+
+    /**
+     * 链接建立的超时时间 ms
+     */
+    private static final int DEFAULT_CONNECTION_TIMEOUT = 3000;
+    /**
+     * 响应超时时间 ms
+     */
+    private static final int DEFAULT_SOCKET_TIMEOUT = 3000;
+
+    /**
+     * 每个路由的最大连接数
+     */
+    private static final int DEFAULT_DEFAULT_MAX_PER_ROUTE = 50;
+
+    /**
+     * 最大连接数
+     */
+    private static final int DEFAULT_DEFAULT_MAX_TOTAL = 200;
+
+    /**
+     * 重试次数,默认0
+     */
+    private static final int DEFAULT_RETRY_COUNT = 5;
+
+    /**
+     * 从connection pool中获得一个connection的超时时间 ms
+     */
+    private static final int DEFAULT_CONNECTION_WAIT_TIMEOUT = 300;
+
+    private static final Map<String, HttpPoolClientUtil> CREATED_HTTP_CLIENTS = new HashMap<>();
+
+    private static final Lock LOCK = new ReentrantLock();
+
+    private HttpClientUtil() {
+    }
+
+    public static HttpPoolClientUtil useDefault() {
+        return createCached(DEFAULT_SHARED_KEY);
+    }
+
+
+    public static HttpPoolClientUtil createCached(String cachedKey) {
+        HttpPoolClientUtil httpPoolClientUtil = CREATED_HTTP_CLIENTS.get(cachedKey);
+        if (httpPoolClientUtil != null) {
+            return httpPoolClientUtil;
+        }
+        LOCK.lock();
+        try {
+            httpPoolClientUtil = CREATED_HTTP_CLIENTS.get(cachedKey);
+            if (httpPoolClientUtil == null) {
+                httpPoolClientUtil = create(DEFAULT_CONNECTION_TIMEOUT, DEFAULT_SOCKET_TIMEOUT, DEFAULT_DEFAULT_MAX_PER_ROUTE, DEFAULT_DEFAULT_MAX_TOTAL, DEFAULT_RETRY_COUNT, DEFAULT_CONNECTION_WAIT_TIMEOUT);
+                CREATED_HTTP_CLIENTS.put(cachedKey, httpPoolClientUtil);
+            }
+        } finally {
+            LOCK.unlock();
+        }
+        return httpPoolClientUtil;
+    }
+
+    /**
+     * 创建httpclient
+     *
+     * @param connectTimeout        连接超时时间 ms
+     * @param socketTimeout         读超时时间(等待数据超时时间)ms
+     * @param maxPerRoute           每个路由的最大连接数
+     * @param maxTotal              最大连接数
+     * @param retryCount            重试次数
+     * @param connectionWaitTimeout 连接等待超市时间 ms
+     * @return httpclient instance
+     */
+    public static HttpPoolClientUtil create(int connectTimeout, int socketTimeout, int maxPerRoute, int maxTotal, int retryCount, int connectionWaitTimeout) {
+        return HttpPoolClientUtil.create(connectTimeout, socketTimeout, maxPerRoute, maxTotal, retryCount, connectionWaitTimeout);
+    }
+}

+ 193 - 0
long-article-server/src/main/java/com/tzld/piaoquan/longarticle/utils/other/HttpPoolClientUtil.java

@@ -0,0 +1,193 @@
+package com.tzld.piaoquan.longarticle.utils.other;
+
+import com.google.common.collect.Lists;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.concurrent.BasicThreadFactory;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpRequestInterceptor;
+import org.apache.http.HttpStatus;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.methods.*;
+import org.apache.http.config.Registry;
+import org.apache.http.config.RegistryBuilder;
+import org.apache.http.conn.HttpClientConnectionManager;
+import org.apache.http.conn.socket.ConnectionSocketFactory;
+import org.apache.http.conn.socket.PlainConnectionSocketFactory;
+import org.apache.http.conn.ssl.NoopHostnameVerifier;
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.entity.mime.MultipartEntityBuilder;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
+import org.apache.http.ssl.SSLContexts;
+import org.apache.http.util.EntityUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.slf4j.MDC;
+
+import javax.net.ssl.SSLContext;
+import java.io.File;
+import java.io.IOException;
+import java.net.SocketTimeoutException;
+import java.nio.charset.StandardCharsets;
+import java.security.KeyManagementException;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.util.List;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * http client
+ *
+ * @author xueyiming
+ */
+public class HttpPoolClientUtil {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(HttpPoolClientUtil.class);
+
+    private static final ScheduledExecutorService SCHEDULED_CLOSED_EXECUTOR = new ScheduledThreadPoolExecutor(1,
+            new BasicThreadFactory.Builder().namingPattern("http conn-closed-thread-%s").priority(Thread.NORM_PRIORITY).daemon(false).build(), (r, e) -> LOGGER.error(" monitor push reject task error={}", e.toString()));
+
+    private static final List<HttpClientConnectionManager> HTTP_CLIENT_CONNECTION_MANAGERS = Lists.newArrayList();
+
+    static {
+        SCHEDULED_CLOSED_EXECUTOR.schedule(() -> HTTP_CLIENT_CONNECTION_MANAGERS.forEach(HttpClientConnectionManager::closeExpiredConnections), 5, TimeUnit.SECONDS);
+    }
+
+    private CloseableHttpClient closeableHttpClient;
+
+    private HttpPoolClientUtil(CloseableHttpClient closeableHttpClient) {
+        this.closeableHttpClient = closeableHttpClient;
+    }
+
+    private static HttpRequestInterceptor getInterceptor() {
+        HttpRequestInterceptor requestInterceptor = (request, context) -> {
+            try {
+                String missSpanId = MDC.get("missSpanId");
+                String missTraceId = MDC.get("request-id");
+                if (missTraceId != null && !"".equals(missTraceId.trim())) {
+                    request.setHeader("request-id", missTraceId);
+                }
+                if (missSpanId != null && !"".equals(missSpanId.trim())) {
+                    request.setHeader("missSpanId", missSpanId);
+                }
+            } catch (Exception e) {
+                LOGGER.error(e.getMessage(), e);
+            }
+        };
+        return requestInterceptor;
+    }
+
+
+    public String get(String url) throws IOException {
+        HttpGet httpGet = new HttpGet(url);
+        return request(httpGet);
+    }
+
+    public String post(String url) throws IOException {
+        HttpPost httpPost = new HttpPost(url);
+        return request(httpPost);
+    }
+
+
+    public String post(String url, String json) throws IOException {
+        HttpPost httpPost = new HttpPost(url);
+        if (StringUtils.isBlank(json)) {
+            return request(httpPost);
+        }
+        StringEntity entity = new StringEntity(json, StandardCharsets.UTF_8);
+        entity.setContentEncoding("UTF-8");
+        entity.setContentType("application/json");
+        httpPost.setEntity(entity);
+        return request(httpPost);
+    }
+
+    public String post(String url, File file) throws IOException {
+        HttpPost uploadFile = new HttpPost(url);
+        MultipartEntityBuilder builder = MultipartEntityBuilder.create();
+        builder.addBinaryBody("media", file);
+        uploadFile.setEntity(builder.build());
+        return request(uploadFile);
+    }
+
+    public String request(HttpRequestBase request) throws IOException {
+
+        if (LOGGER.isDebugEnabled()) {
+            String path = request.getURI().toString();
+            LOGGER.debug("http request url = {} ", path);
+        }
+        HttpEntity entity = null;
+        CloseableHttpResponse response = request((HttpUriRequest) request);
+        if (response == null) {
+            throw new RuntimeException("call api exception no response");
+        }
+        entity = response.getEntity();
+        String content = null;
+        if (entity != null) {
+            content = EntityUtils.toString(entity, "UTF-8");
+        }
+        int httpStatus = response.getStatusLine().getStatusCode();
+        if (httpStatus == HttpStatus.SC_OK) {
+            return content;
+        }
+        String path = request.getURI().toString();
+        LOGGER.error("http call api {} fail response status {} content {}", path, httpStatus, content);
+//            throw new HttpServiceException(httpStatus, content);
+        return null;
+    }
+
+
+    public CloseableHttpResponse request(HttpUriRequest request) {
+        try {
+            CloseableHttpResponse execute = closeableHttpClient.execute(request);
+            return execute;
+        } catch (Exception e) {
+            String path = request.getURI().toString();
+            if (e instanceof SocketTimeoutException) {
+                LOGGER.error(String.format("http timeout request url = %s .", path));
+//                throw new TimeoutException();
+            } else {
+            }
+            throw new RuntimeException(String.format("http exception request url = %s ", path), e);
+        }
+    }
+
+    /**
+     * @param connectTimeout 连接超时时间 ms
+     * @param socketTimeout  读超时时间(等待数据超时时间)ms
+     * @param maxPerRoute    每个路由的最大连接数
+     * @param maxTotal       最大连接数
+     * @param retryCount     重试次数
+     * @return httpclient instance
+     */
+    protected static HttpPoolClientUtil create(int connectTimeout, int socketTimeout, int maxPerRoute, int maxTotal, int retryCount, int connectionWaitTimeout) {
+        try {
+            RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(connectTimeout).setSocketTimeout(socketTimeout).setConnectionRequestTimeout(connectionWaitTimeout).build();
+            CloseableHttpClient client = HttpClientBuilder.create()
+                    .setDefaultRequestConfig(requestConfig)
+                    .setConnectionManager(createConnectionManager(maxPerRoute, maxTotal))
+                    .setRetryHandler(new DefaultHttpRequestRetryHandler(retryCount, false)).addInterceptorFirst(getInterceptor()).build();
+            return new HttpPoolClientUtil(client);
+        } catch (Throwable e) {
+            LOGGER.error("create HttpPoolClient exception", e);
+            throw new RuntimeException("create HttpPoolClient exception");
+        }
+    }
+
+    private static PoolingHttpClientConnectionManager createConnectionManager(int maxPerRoute, int maxTotal) throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
+        SSLContext sslContext = SSLContexts.custom().loadTrustMaterial((chain, authType) -> true).build();
+        Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
+                .register("http", PlainConnectionSocketFactory.getSocketFactory())
+                .register("https", new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE)).build();
+        PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(registry);
+        cm.setDefaultMaxPerRoute(maxPerRoute);
+        cm.setMaxTotal(maxTotal);
+        HTTP_CLIENT_CONNECTION_MANAGERS.add(cm);
+        return cm;
+    }
+
+}

+ 56 - 0
long-article-server/src/main/java/com/tzld/piaoquan/longarticle/utils/other/KimiAPI.java

@@ -0,0 +1,56 @@
+package com.tzld.piaoquan.longarticle.utils.other;
+
+import cn.hutool.http.HttpRequest;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class KimiAPI {
+
+    /**
+     * 聊天端点
+     */
+    static String chatEndpoint = "https://api.moonshot.cn/v1/chat/completions";
+    /**
+     * api密匙
+     */
+    static String apiKey = "Bearer sk-5DqYCa88kche6nwIWjLE1p4oMm8nXrR9kQMKbBolNAWERu7q";
+
+    /**
+     * 发送消息
+     *
+     * @param txt 内容
+     * @return {@link String}
+     */
+    public static String chat(String txt) {
+        Map<String, Object> paramMap = new HashMap<>();
+        paramMap.put("model", "moonshot-v1-8k");
+        List<Map<String, String>> dataList = new ArrayList<>();
+        dataList.add(new HashMap<String, String>() {{
+            put("role", "user");
+            put("content", txt);
+        }});
+        paramMap.put("messages", dataList);
+        JSONObject message = null;
+        try {
+            String body = HttpRequest.post(chatEndpoint)
+                    .header("Authorization", apiKey)
+                    .header("Content-Type", "application/json")
+                    .body(JSONObject.toJSONString(paramMap))
+                    .execute()
+                    .body();
+            JSONObject jsonObject = JSONObject.parseObject(body);
+            JSONArray choices = jsonObject.getJSONArray("choices");
+            JSONObject result = choices.getJSONObject(0);
+            message = result.getJSONObject("message");
+            return message.getString("content");
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return "";
+    }
+}

+ 37 - 0
long-article-server/src/main/java/com/tzld/piaoquan/longarticle/utils/other/OSSUploader.java

@@ -0,0 +1,37 @@
+package com.tzld.piaoquan.longarticle.utils.other;
+
+import com.aliyun.oss.OSS;
+import com.aliyun.oss.OSSClientBuilder;
+import com.aliyun.oss.model.PutObjectRequest;
+
+import java.io.File;
+import java.util.UUID;
+
+public class OSSUploader {
+
+    private static final String ACCESS_KEY_ID = "LTAIP6x1l3DXfSxm"; // 填写您的 Access Key ID
+    private static final String ACCESS_KEY_SECRET = "KbTaM9ars4OX3PMS6Xm7rtxGr1FLon"; // 填写您的 Access Key Secret
+    private static final String ENDPOINT = "oss-cn-hangzhou.aliyuncs.com"; // OSS 访问域名
+    private static final String BUCKET_NAME = "art-pubbucket"; // 存储空间名称
+
+    public static String uploadToOSS(String localVideoPath, String downloadType) {
+        String ossVideoKey = String.format("long_articles/%s/%s", downloadType, UUID.randomUUID().toString());
+
+        // 创建 OSSClient 实例
+        OSS ossClient = new OSSClientBuilder().build(ENDPOINT, ACCESS_KEY_ID, ACCESS_KEY_SECRET);
+
+        try {
+            // 创建上传请求
+            PutObjectRequest putObjectRequest = new PutObjectRequest(BUCKET_NAME, ossVideoKey, new File(localVideoPath));
+            // 上传文件
+            ossClient.putObject(putObjectRequest);
+            return ossVideoKey;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        } finally {
+            // 关闭 OSSClient
+            ossClient.shutdown();
+        }
+    }
+}

+ 108 - 0
long-article-server/src/main/java/com/tzld/piaoquan/longarticle/utils/other/OpenAIUtils.java

@@ -0,0 +1,108 @@
+package com.tzld.piaoquan.longarticle.utils.other;
+
+import com.alibaba.fastjson.JSONObject;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.math.NumberUtils;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class OpenAIUtils {
+
+    public static String getKimiTitle(String title) {
+        String single_title_prompt = "请将以上标题改写成适合小程序点击和传播的小程序标题,小程序标题的写作规范如下,请学习后进行小程序标题的编写。直接输出最终的小程序标题\n" +
+                "        小程序标题写作规范:\n" +
+                "        1.要点前置:将最重要的信息放在标题的最前面,以快速吸引读者的注意力。例如,“5月一辈子同学,三辈子亲,送给我的老同学,听哭无数人!”中的“5月”和“一辈子同学,三辈子亲”都是重要的信息点。\n" +
+                "        2.激发情绪:使用能够触动人心的语言,激发读者的情感共鸣。如“只剩两人同学聚会,看后感动落泪。”使用“感动落泪”激发读者的同情和怀旧情绪。\n" +
+                "        3.使用数字和特殊符号:数字可以提供具体性,而特殊符号如“\uD83D\uDD34”、“\uD83D\uDE04”、“\uD83D\uDD25”等可以吸引视觉注意力,增加点击率。\n" +
+                "        4.悬念和好奇心:创建悬念或提出问题,激发读者的好奇心。例如,“太神奇了!长江水位下降,重庆出现惊奇一幕!”中的“惊奇一幕”就是一个悬念。\n" +
+                "        5.名人效应:如果内容与知名人士相关,提及他们的名字可以增加标题的吸引力。\n" +
+                "        6.社会价值观:触及读者的文化和社会价值观,如家庭、友情、国家荣誉等。\n" +
+                "        7.标点符号的运用:使用感叹号、问号等标点来增强语气和情感表达。\n" +
+                "        8.直接的语言:使用直白、口语化的语言,易于理解,如“狗屁股,笑死我了!”。\n" +
+                "        9.热点人物或事件:提及当前的热点人物或事件,利用热点效应吸引读者。\n" +
+                "        10.字数适中:保持标题在10-20个字之间,既不过长也不过短,确保信息的完整性和吸引力。\n" +
+                "        11.适当的紧迫感:使用“最新”、“首次”、“紧急”等词汇,创造一种紧迫感,促使读者立即行动。\n" +
+                "        12.情感或价值诉求:使用如“感动”、“泪目”、“经典”等词汇,直接与读者的情感或价值观产生共鸣。\n" +
+                "        避免误导:确保标题准确反映内容,避免夸大或误导读者。";
+        return DeepSeekAPI.chat(title + "\n" + single_title_prompt);
+    }
+
+    public static Integer getKimiTitleSafeScore(String kimiTitle) {
+        String prompt = "请你学习一下内容规范,以下标题可能会违反了某条内容规范。请你对标题做一个内容风险评级,1-10分,等级越高内容违规风险越大。 \n" +
+                "        请直接输出内容风险评级的分数,不要输出你的理由、分析等内容。 \n" +
+                "        输出:\n" +
+                "            只需要输出危险分级分数,不要输出任何其他内容。\n" +
+                "        内容规范为: \n" +
+                "        4.2 色俗内容 \n" +
+                "            4.2.1 散布淫秽、色情内容,包括但不限于招嫖、寻找一夜情、性伴侣等。 \n" +
+                "            4.2.2 发布有色情意味的情色文字、情色视频、情色漫画等内容。 \n" +
+                "            4.2.3 以投稿/爆料等形式描述约炮经历、性交体验、偷情、涉隐私部位偷拍等伤风败俗的话题内容。 \n" +
+                "            4.2.4 以低俗的配图引诱用户阅读文章、关注微信公众号。包含性撩拨、性挑逗画面;疑似女性性高潮/性虐场面;偷拍的沐浴/更衣/如厕/亲热等私密画面;女性故意露出敏感部位 (纯裸露的胸、生殖器官)以及敏感部位未打码的真人写真/艺术摄影等。 \n" +
+                "            4.2.5 文内以低俗类的动图或引导图,诱导用户点击进而跳转至另一篇图文页或关注某个公众号。 \n" +
+                "            4.2.6 文章主要描述PUA撩妹、撩汉等相关话题,且引导用户关注公众号/加个人微信号/加群。 \n" +
+                "        4.11 煽动、夸大、误导类内容 平台鼓励创作者提供准确、清晰、能体现文章内容主旨的标题,不允许通过标题噱头诱导用户点击或误导用户。\n" +
+                "            包括但不限于以下情况: \n" +
+                "            4.11.1 标题含有危害人身安全、恐吓侮辱、惊悚、极端内容,或者以命令式语气强迫用户阅读。 \n" +
+                "            4.11.2 标题无依据夸大事件严重程度、紧急程度、受影响面以及事件引发的情绪。 \n" +
+                "            4.11.3 标题以浮夸的描述,反常识强调某种食物/行为对人体健康的影响,煽动人群要/不要去做某行为。 \n" +
+                "            4.11.4 非官方通知或者公告,但标题假借官方名义煽动获取流量,或以信息来源机密、看完即删来诱导用户。 \n" +
+                "            4.11.5 标题故意隐藏关键信息,或无中生有部分信息,给用户造成误导。 \n" +
+                "            4.12 违反国家法律法规禁止的内容 \n" +
+                "                (1)违反宪法确定的基本原则的; \n" +
+                "                (2)危害国家安全,泄露国家秘密,颠覆国家政权,破坏国家统一的; \n" +
+                "                (3)损害国家荣誉和利益的; \n" +
+                "                (4)煽动民族仇恨、民族歧视,破坏民族团结的; \n" +
+                "                (5)破坏国家宗教政策,宣扬邪教和封建迷信的; \n" +
+                "                (6)散布不实信息,扰乱社会秩序,破坏社会稳定的; \n" +
+                "                (7)散布淫秽、色情、赌博、暴力、恐怖或者教唆犯罪的; \n" +
+                "                (8)侮辱或者诽谤他人,侵害他人合法权益的; \n" +
+                "                (9)煽动非法集会、结社、游行、示威、聚众扰乱社会秩序; \n" +
+                "                (10)以非法民间组织名义活动的; \n" +
+                "                (11)不符合《即时通信工具公众信息服务发展管理暂行规定》及遵守法律法规、社会主义制度、国家利益、公民合法利益、公共秩序、社会道德风尚和信息真实性等“七条底线”要求的; \n" +
+                "                (12)含有法律、行政法规禁止的其他内容的。\n" +
+                "        输入的标题是: " + kimiTitle;
+        String res = KimiAPI.chat(prompt);
+        if (StringUtils.isNotEmpty(res) && NumberUtils.isParsable(res)) {
+            return Integer.parseInt(res);
+        }
+        return null;
+    }
+
+    public static String makeKimiTitleSafer(String title) {
+        String prompt = "以下每行为一个文章的标题,请用尽量平实的语言对以上标题进行改写,保持在10~15字左右,请注意:\n" +
+                "            1. 不要虚构或改变标题的含义。\n" +
+                "            2. 不要用笃定的语气描述存疑的可能性,不要将表述可能性的问句改为肯定句。\n" +
+                "            直接输出改写后的标题列表。\n" +
+                "            在改写完成后,再输出一次,在改写的标题前增加和标题情感、语气匹配的特殊符号,如:“\uD83D\uDD34”、“\uD83D\uDE04”、“\uD83D\uDD25”、“\uD83D\uDE28”等等\n" +
+                "            输出:\n" +
+                "                输出结果是Dict, 格式为: \n" +
+                "                {\n" +
+                "                \"title_v1\": 请填写第一次输出的标题,\n" +
+                "                \"title_v2\": 请填写第二次输出的标题\n" +
+                "                }\n" +
+                "            输入的标题是: " + title;
+        String res = KimiAPI.chat(prompt);
+        res = res.split("\\{")[1];
+        res = res.split("}")[0];
+
+        JSONObject jsonObject = JSONObject.parseObject("{" + res + "}");
+        return jsonObject.getString("title_v2");
+    }
+
+    public static JSONObject kimiMining(String text) {
+        String textPrompt = "请从我给你的文章中挖掘出以下信息并且返回如下结果。\n" +
+                "        你返回的结果是一个 json, 格式如下:\n" +
+                "        {\n" +
+                "            \"content_keys\": [] # 同时提供三个与文章内容高度相关的关键词,这些关键词将用于网络上搜索相关视频内容,\n" +
+                "            \"content_title\": 一个总结性的标题,该标题应简洁并能够反映文章的主要内容\n" +
+                "        }\n" +
+                "        你需要处理的文本是:";
+        Map<String, String> responseFormat = new HashMap<>();
+        responseFormat.put("type", "json_object");
+        String content = DeepSeekAPI.chat(textPrompt + text, responseFormat);
+        return JSONObject.parseObject(content);
+    }
+
+
+}

+ 32 - 0
long-article-server/src/main/java/com/tzld/piaoquan/longarticle/utils/other/SearchUtils.java

@@ -0,0 +1,32 @@
+package com.tzld.piaoquan.longarticle.utils.other;
+
+import com.alibaba.fastjson.JSONObject;
+import org.springframework.util.CollectionUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class SearchUtils {
+    public List<JSONObject> searchVideo(String keyword, List<String> words, String traceId) {
+        List<JSONObject> douyinSearch = DouyinSearch.douyinSearch(keyword, words, traceId);
+        if (!CollectionUtils.isEmpty(douyinSearch)) {
+            for (JSONObject jsonObject : douyinSearch) {
+                jsonObject.put("platform", "dy_search");
+            }
+            return douyinSearch;
+        }
+//        List<JSONObject> jsonObjects1 = HkspSearch.hkspSearch(keyword, words, traceId);
+//        if (CollectionUtils.isEmpty(jsonObjects1)) {
+//            return jsonObjects;
+//        }
+//        for (JSONObject jsonObject : jsonObjects1) {
+//            jsonObject.put("platform", "baidu_search");
+//        }
+//        if (CollectionUtils.isEmpty(jsonObjects)) {
+//            return jsonObjects1;
+//        }
+//        jsonObjects.addAll(jsonObjects1);
+//        return jsonObjects;
+        return null;
+    }
+}

+ 55 - 0
long-article-server/src/main/java/com/tzld/piaoquan/longarticle/utils/other/SortUtil.java

@@ -0,0 +1,55 @@
+package com.tzld.piaoquan.longarticle.utils.other;
+
+
+import cn.hutool.json.JSONObject;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public class SortUtil {
+
+    private static double jcdTitleSimilarity(String oriTitle, String searchTitle) {
+        Set<Character> set1 = new HashSet<>();
+        for (char c : oriTitle.toCharArray()) {
+            set1.add(c);
+        }
+
+        Set<Character> set2 = new HashSet<>();
+        for (char c : searchTitle.toCharArray()) {
+            set2.add(c);
+        }
+
+        Set<Character> intersection = new HashSet<>(set1);
+        intersection.retainAll(set2);
+
+        Set<Character> union = new HashSet<>(set1);
+        union.addAll(set2);
+
+        return (double) intersection.size() / union.size();
+    }
+
+    public static List<JSONObject> titleSimilarityRank(String contentTitle, List<JSONObject> recallList) {
+        List<JSONObject> includeTitleList = new ArrayList<>();
+        for (JSONObject videoInfo : recallList) {
+            String platform = (String) videoInfo.get("platform");
+            String title = "";
+
+            if (platform.equals("dy_search") || platform.equals("baidu_search")) {
+                title = (String) videoInfo.get("title");
+            } else if (platform.equals("xg_search")) {
+                title = (String) videoInfo.get("video_title");
+            } else {
+                continue;
+            }
+
+            videoInfo.put("title", title);
+            videoInfo.put("score", jcdTitleSimilarity(contentTitle, title));
+            includeTitleList.add(videoInfo);
+        }
+
+        includeTitleList.sort((a, b) -> Double.compare((double) b.get("score"), (double) a.get("score")));
+        return includeTitleList;
+    }
+}

+ 161 - 0
long-article-server/src/main/java/com/tzld/piaoquan/longarticle/utils/other/VideoDownloader.java

@@ -0,0 +1,161 @@
+package com.tzld.piaoquan.longarticle.utils.other;
+
+import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.message.BasicHeader;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Objects;
+import java.util.UUID;
+
+public class VideoDownloader {
+
+    public static String downloadCover(String filePath, String platform, String coverUrl) {
+        try {
+            HttpResponse response = sendRequest(platform, coverUrl, "cover");
+            if (response.getStatusLine().getStatusCode() != 200) {
+                return null;
+            }
+
+            try (InputStream inputStream = response.getEntity().getContent();
+                 FileOutputStream outputStream = new FileOutputStream(new File(filePath))) {
+                byte[] buffer = new byte[1024];
+                int bytesRead;
+                while ((bytesRead = inputStream.read(buffer)) != -1) {
+                    outputStream.write(buffer, 0, bytesRead);
+                }
+            }
+            return filePath;
+        } catch (Exception e) {
+
+        }
+        return "";
+    }
+
+    public static HttpResponse sendRequest(String platform, String url, String downloadType) throws IOException {
+        CloseableHttpClient httpClient = HttpClients.createDefault();
+        HttpGet request = new HttpGet(url);
+        request.setHeaders(requestHeader(platform, url, downloadType));
+        return httpClient.execute(request);
+    }
+
+    public static org.apache.http.Header[] requestHeader(String platform, String url, String downloadType) {
+        switch (platform) {
+            case "xg_search":
+                // 根据 URL 设置不同的请求头
+                if (url.contains("v9-xg-web-pc.ixigua.com")) {
+                    return new org.apache.http.Header[]{
+                            new BasicHeader("Accept", "*/*"),
+                            new BasicHeader("Accept-Language", "zh-CN,zh;q=0.9"),
+                            new BasicHeader("Host", "v9-xg-web-pc.ixigua.com"),
+                            new BasicHeader("User-Agent", "Mozilla/5.0"),
+                            new BasicHeader("Origin", "https://www.ixigua.com/"),
+                            new BasicHeader("Referer", "https://www.ixigua.com/")
+                    };
+                } else if (url.contains("v3-xg-web-pc.ixigua.com")) {
+                    return new org.apache.http.Header[]{
+                            new BasicHeader("Accept", "*/*"),
+                            new BasicHeader("Accept-Language", "zh-CN,zh;q=0.9"),
+                            new BasicHeader("Host", "v3-xg-web-pc.ixigua.com"),
+                            new BasicHeader("User-Agent", "Mozilla/5.0"),
+                            new BasicHeader("Origin", "https://www.ixigua.com/"),
+                            new BasicHeader("Referer", "https://www.ixigua.com/")
+                    };
+                } else if (Objects.equals(downloadType, "cover")) {
+                    return new org.apache.http.Header[]{
+                            new BasicHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7"),
+                            new BasicHeader("Accept-Language", "en,zh;q=0.9,zh-CN;q=0.8"),
+                            new BasicHeader("Cache-Control", "max-age=0"),
+                            new BasicHeader("Proxy-Connection", "keep-alive"),
+                            new BasicHeader("Upgrade-Insecure-Requests", "1"),
+                            new BasicHeader("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36")
+                    };
+                }
+                // 其他 headers 根据 downloadType 处理
+                break;
+            case "baidu_search":
+                return new org.apache.http.Header[]{
+                        new BasicHeader("Accept", "*/*"),
+                        new BasicHeader("Accept-Language", "zh-CN,zh;q=0.9"),
+                        new BasicHeader("User-Agent", "Mozilla/5.0")
+                };
+            case "wx_search":
+                return new org.apache.http.Header[]{
+                        new BasicHeader("Accept", "*/*"),
+                        new BasicHeader("Accept-Language", "zh-CN,zh;q=0.9"),
+                        new BasicHeader("User-Agent", "Mozilla/5.0"),
+                        new BasicHeader("Origin", "https://mp.weixin.qq.com"),
+                        new BasicHeader("Referer", "https://mp.weixin.qq.com")
+                };
+            case "dy_search":
+                return new org.apache.http.Header[]{
+                        new BasicHeader("accept", "*/*"),
+                        new BasicHeader("accept-language", "en,zh;q=0.9,zh-CN;q=0.8"),
+                        new BasicHeader("priority", "i"),
+                        new BasicHeader("referer", "https://v11-coldf.douyinvod.com/"),
+                        new BasicHeader("user-agent", "Mozilla/5.0")
+                };
+            default:
+                return new org.apache.http.Header[]{};
+        }
+        return new org.apache.http.Header[]{};
+    }
+
+    public static String downloadVideo(String filePath, String platform, String videoUrl, String downloadType) {
+        try {
+            HttpResponse response = sendRequest(platform, videoUrl, downloadType);
+            if (response.getStatusLine().getStatusCode() != 200 && response.getStatusLine().getStatusCode() != 206) {
+                System.out.println("Error: " + response.getStatusLine().getStatusCode());
+                return null;
+            }
+
+            try (InputStream inputStream = response.getEntity().getContent();
+                 FileOutputStream outputStream = new FileOutputStream(new File(filePath))) {
+                byte[] buffer = new byte[1024 * 1024];
+                int bytesRead;
+                while ((bytesRead = inputStream.read(buffer)) != -1) {
+                    outputStream.write(buffer, 0, bytesRead);
+                }
+            }
+            return filePath;
+        } catch (Exception e) {
+            System.out.println(e.getMessage());
+        }
+        return "";
+    }
+
+    public static String generateVideoPath(String platform, String videoId) {
+        String index = String.format("%s-%s-%s", platform, videoId, UUID.randomUUID());
+        String md5Hash = DigestUtils.md5Hex(index);
+        String fileName = String.format("%s.mp4", md5Hash);
+        String filePath = String.join(File.separator, System.getProperty("user.dir"), fileName);
+        return filePath; // 返回文件路径和封面路径
+    }
+
+    public static String generateCoverPath(String platform, String videoId) {
+        String index = String.format("%s-%s-%s", platform, videoId, UUID.randomUUID());
+        String md5Hash = DigestUtils.md5Hex(index);
+        String coverName = String.format("%s.png", md5Hash);
+        String coverPath = String.join(File.separator, System.getProperty("user.dir"), coverName);
+        return coverPath; // 返回文件路径和封面路径
+    }
+
+    public static void main(String[] args) {
+        // 示例用法
+        String coverUrl = "https://example.com/cover.jpg";
+        String filePath = "path/to/cover.png";
+        downloadCover(filePath, "xg_search", coverUrl);
+        String videoUrl = "https://example.com/video.mp4";
+        String videoFilePath = "path/to/video.mp4";
+        downloadVideo(videoFilePath, "xg_search", videoUrl, "video");
+        String paths = generateVideoPath("xg_search", "videoId");
+        System.out.println("Generated paths: " + paths);
+
+    }
+}

+ 9 - 0
pom.xml

@@ -207,5 +207,14 @@
             <version>2.0.0</version>
         </dependency>
 
+        <!-- https://mvnrepository.com/artifact/com.theokanning.openai-gpt3-java/service -->
+        <dependency>
+            <groupId>com.theokanning.openai-gpt3-java</groupId>
+            <artifactId>service</artifactId>
+            <version>0.18.2</version>
+        </dependency>
+
+
+
     </dependencies>
 </project>