Browse Source

feat(douyin): 添加抖音工具类及相关功能实现

- 新增DouYinUtils工具类,实现getABogus方法用于生成抖音反爬虫参数
- 添加getHeader方法生成抖音请求头信息,包含完整的Cookie和其他必要字段
- 实现getRegexString方法用于正则匹配提取字符串内容
- 添加decodeUnicode方法处理Unicode编码转换
- 集成OkHttpClientUtils作为HTTP客户端工具类
- 在WechatConfigController中新增配置获取接口
- 实现WechatConfigService服务层接口定义
- 创建WechatConfigMapper数据访问层接口
- 配置MyBatis映射文件WechatConfigMapper.xml
- 实现WechatConfigServiceImpl服务层具体逻辑
- 在StringUtils中新增字符串处理工具方法
- 添加抖音测试工具类验证功能实现
JX.Li 3 weeks ago
parent
commit
c4333887dd

+ 7 - 0
nexo-common/nexo-common-core/pom.xml

@@ -16,6 +16,13 @@
     </description>
     </description>
 
 
     <dependencies>
     <dependencies>
+
+        <!--    okhttp    -->
+        <dependency>
+            <groupId>com.squareup.okhttp3</groupId>
+            <artifactId>okhttp</artifactId>
+            <version>${okhttp.version}</version>
+        </dependency>
         <!-- Source: https://mvnrepository.com/artifact/com.alibaba.fastjson2/fastjson2 -->
         <!-- Source: https://mvnrepository.com/artifact/com.alibaba.fastjson2/fastjson2 -->
         <dependency>
         <dependency>
             <groupId>com.alibaba.fastjson2</groupId>
             <groupId>com.alibaba.fastjson2</groupId>

+ 292 - 0
nexo-common/nexo-common-core/src/main/java/com/nexo/common/core/utils/OkHttpClientUtils.java

@@ -0,0 +1,292 @@
+package com.nexo.common.core.utils;
+
+import com.alibaba.fastjson2.JSONObject;
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.*;
+import org.springframework.stereotype.Component;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author luoqifeng
+ */
+@Slf4j
+@Component
+public class OkHttpClientUtils {
+    private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
+    private static final MediaType XML = MediaType.parse("application/xml; charset=utf-8");
+    private static OkHttpClient client;
+
+
+    static {
+        client = new OkHttpClient().newBuilder()
+                .connectTimeout(1, TimeUnit.MINUTES)
+                .readTimeout(1, TimeUnit.MINUTES)
+                .writeTimeout(1, TimeUnit.MINUTES)
+                .build();
+        log.info("初始化 okhttp 配置");
+    }
+
+    public static String doGetFollowRedirects(String url, Map<String, String> headers) {
+        try {
+            OkHttpClient client = new OkHttpClient().newBuilder().followRedirects(false).build();
+            Request.Builder builder = new Request.Builder();
+
+            if (headers != null && !headers.isEmpty()) {
+                for (String header : headers.keySet()) {
+                    builder.addHeader(header, headers.get(header));
+                }
+            }
+            Request build = builder.url(url).get().build();
+            Response response = client.newCall(build).execute();
+            return response.body().string();
+        } catch (IOException e) {
+            return null;
+        }
+    }
+
+    /**
+     * get 请求
+     *
+     * @param url 请求url地址
+     * @return string
+     */
+    public static String doGet(String url) {
+        return doGet(url, null, null);
+    }
+
+    public static byte[] doGetByte(String url) {
+        return doGetByte(url, null, null);
+    }
+
+    public static String doPost(String url) {
+        return doPost(url, null, null);
+    }
+
+    /**
+     * get 请求
+     *
+     * @param url    请求url地址
+     * @param params 请求参数 map
+     * @return string
+     */
+    public static String doGetToParams(String url, Map<String, String> params) {
+        return doGet(url, params, null);
+    }
+
+    /**
+     * get 请求
+     *
+     * @param url     请求url地址
+     * @param headers 请求头字段 {k1, v1 k2, v2, ...}
+     * @return string
+     */
+    public static String doGetToHeaders(String url, Map<String, String> headers) {
+        return doGet(url, null, headers);
+    }
+
+    /**
+     * get 请求
+     *
+     * @param url     请求url地址
+     * @param params  请求参数 map
+     * @param headers 请求头字段 {k1, v1 k2, v2, ...}
+     * @return string
+     */
+    public static String doGet(String url, Map<String, String> params, Map<String, String> headers) {
+        StringBuilder sb = new StringBuilder(url);
+        if (params != null && params.keySet().size() > 0) {
+            boolean firstFlag = true;
+            for (String key : params.keySet()) {
+                if (firstFlag) {
+                    sb.append("?").append(key).append("=").append(params.get(key));
+                    firstFlag = false;
+                } else {
+                    sb.append("&").append(key).append("=").append(params.get(key));
+                }
+            }
+        }
+        Request.Builder builder = new Request.Builder();
+
+        if (headers != null && !headers.isEmpty()) {
+            for (String header : headers.keySet()) {
+                builder.addHeader(header, headers.get(header));
+            }
+        }
+        Request request = builder.url(sb.toString()).build();
+        return executeBody(request);
+    }
+
+    public static byte[] doGetByte(String url, Map<String, String> params, Map<String, String> headers) {
+        StringBuilder sb = new StringBuilder(url);
+        if (params != null && params.keySet().size() > 0) {
+            boolean firstFlag = true;
+            for (String key : params.keySet()) {
+                if (firstFlag) {
+                    sb.append("?").append(key).append("=").append(params.get(key));
+                    firstFlag = false;
+                } else {
+                    sb.append("&").append(key).append("=").append(params.get(key));
+                }
+            }
+        }
+        Request.Builder builder = new Request.Builder();
+        if (headers != null && !headers.isEmpty()) {
+            for (String header : headers.keySet()) {
+                builder.addHeader(header, headers.get(header));
+            }
+        }
+        Request request = builder.url(sb.toString()).build();
+        return executeByte(request);
+    }
+
+    /**
+     * post 请求
+     *
+     * @param url    请求url地址
+     * @param params 请求参数 map
+     * @return string
+     */
+    public static String doPostForm(String url, Map<String, String> params) {
+        FormBody.Builder builder = new FormBody.Builder();
+        if (params != null && params.keySet().size() > 0) {
+            for (String key : params.keySet()) {
+                builder.add(key, params.get(key));
+            }
+        }
+        Request request = new Request.Builder().url(url).post(builder.build()).build();
+        return execute(request);
+    }
+
+    /**
+     * post 请求
+     *
+     * @param url     请求url地址
+     * @param params  请求参数 map
+     * @param headers 请求头字段 {k1:v1, k2: v2, ...}
+     * @return string
+     */
+    public static String doPost(String url, Map<String, String> params, Map<String, String> headers) {
+        FormBody.Builder builder = new FormBody.Builder();
+        if (params != null && params.keySet().size() > 0) {
+            for (String key : params.keySet()) {
+                builder.add(key, params.get(key));
+            }
+        }
+        Request.Builder requestBuilder = new Request.Builder();
+        if (headers != null && !headers.isEmpty()) {
+            for (String header : headers.keySet()) {
+                requestBuilder.addHeader(header, headers.get(header));
+            }
+        }
+        Request request = requestBuilder.url(url).post(builder.build()).build();
+        return execute(request);
+    }
+
+    /**
+     * post 请求, 请求数据为 json 的字符串
+     *
+     * @param url  请求url地址
+     * @param json 请求数据, json 字符串
+     * @return string
+     */
+    public static String doPostJson(String url, JSONObject json) {
+        return executePost(url, json.toJSONString(), JSON);
+    }
+
+    /**
+     * post 请求, 请求数据为 json 的字符串
+     *
+     * @param url     请求url地址
+     * @param json    请求数据, json 字符串
+     * @param headers 请求头字段 {k1, v1 k2, v2, ...}
+     * @return string
+     */
+    public static String doPostJson(String url, JSONObject json, Map<String, String> headers) {
+        RequestBody requestBody = RequestBody.create(json.toJSONString(), JSON);
+        Request.Builder builder = new Request.Builder();
+        if (headers != null && !headers.isEmpty()) {
+            for (String header : headers.keySet()) {
+                builder.addHeader(header, headers.get(header));
+            }
+        }
+        Request request = builder.url(url).post(requestBody).build();
+        return execute(request);
+    }
+
+    /**
+     * post 请求, 请求数据为 xml 的字符串
+     *
+     * @param url 请求url地址
+     * @param xml 请求数据, xml 字符串
+     * @return string
+     */
+    public static String doPostXml(String url, String xml) {
+        return executePost(url, xml, XML);
+    }
+
+    private static String executePost(String url, String data, MediaType contentType) {
+        RequestBody requestBody = RequestBody.create(data, contentType);
+        Request request = new Request.Builder().url(url).post(requestBody).build();
+        return execute(request);
+    }
+
+    private static String execute(Request request) {
+        Response response = null;
+        try {
+            response = client.newCall(request).execute();
+            if (response.isSuccessful()) {
+                String string = response.body().string();
+                //                log.info("返回数据:{}", string);
+                return string;
+            }
+        } catch (Exception e) {
+        } finally {
+            if (response != null) {
+                response.close();
+            }
+        }
+        return "";
+    }
+
+    private static String executeBody(Request request) {
+        Response response = null;
+        try {
+            response = client.newCall(request).execute();
+            if (response.body() != null) {
+                String string = response.body().string();
+                //                log.info("返回数据:{}", string);
+                return string;
+            }
+        } catch (Exception e) {
+            return "";
+        } finally {
+            if (response != null) {
+                response.close();
+            }
+        }
+        return "";
+    }
+
+    private static byte[] executeByte(Request request) {
+        Response response = null;
+        try {
+            response = client.newCall(request).execute();
+            if (response.isSuccessful()) {
+                byte[] bytes = response.body().bytes();
+                //                log.info("返回数据:{}", bytes);
+                return bytes;
+            }
+        } catch (Exception e) {
+        } finally {
+            if (response != null) {
+                response.close();
+            }
+        }
+        return null;
+    }
+
+}
+

+ 51 - 0
nexo-common/nexo-common-core/src/main/java/com/nexo/common/core/utils/StringUtils.java

@@ -322,4 +322,55 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils {
             .collect(Collectors.toList());
             .collect(Collectors.toList());
     }
     }
 
 
+    /**
+     * 字符串取左边内容
+     *
+     * @param str       被切分的字符串
+     * @param separator 分隔符
+     * @return 字符串左边内容
+     */
+    public static String left(String str, String separator) {
+        if (isEmpty(str)) {
+            return str;
+        }
+        return StrUtil.subBefore(str, separator, true);
+    }
+
+    /**
+     * 字符串取右边内容
+     *
+     * @param str       被切分的字符串
+     * @param separator 分隔符
+     * @return 字符串右边内容
+     */
+    public static String right(String str, String separator) {
+        if (isEmpty(str)) {
+            return str;
+        }
+        return StrUtil.subAfter(str, separator, true);
+    }
+
+    /**
+     * 提取两个字符串之间的文本
+     *
+     * @param source 源字符串
+     * @param start  起始字符串
+     * @param end    结束字符串
+     * @return 中间文本
+     */
+    public static String extractMiddleText(String source, String start, String end) {
+        int startIndex = source.indexOf(start);
+        if (startIndex == -1) {
+            return "";
+        }
+        startIndex += start.length();
+
+        int endIndex = source.indexOf(end, startIndex);
+        if (endIndex == -1) {
+            return "";
+        }
+
+        return source.substring(startIndex, endIndex);
+    }
+
 }
 }

File diff suppressed because it is too large
+ 7511 - 0
nexo-example/nexo-model/config/sin.js


File diff suppressed because it is too large
+ 74 - 0
nexo-example/nexo-model/src/main/java/com/nexo/model/douyin/utils/DouYinUtils.java


+ 31 - 1
nexo-example/nexo-model/src/main/java/com/nexo/model/wechat/controller/IWechatConfigController.java

@@ -1,20 +1,50 @@
 package com.nexo.model.wechat.controller;
 package com.nexo.model.wechat.controller;
 
 
+import com.nexo.model.wechat.service.IWechatConfigService;
 import lombok.RequiredArgsConstructor;
 import lombok.RequiredArgsConstructor;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 import org.springframework.web.bind.annotation.RestController;
 
 
+import javax.annotation.Resource;
+
 @Validated
 @Validated
 @RequiredArgsConstructor
 @RequiredArgsConstructor
 @RestController
 @RestController
 @RequestMapping("/config")
 @RequestMapping("/config")
 public class IWechatConfigController {
 public class IWechatConfigController {
 
 
+    @Resource
+    IWechatConfigService wechatConfigService;
+
+    // 获取程序公告
     @GetMapping("/notice")
     @GetMapping("/notice")
     public String notice() {
     public String notice() {
-        return "hello world";
+        return wechatConfigService.notice();
+    }
+
+    // 获取程序版本
+    @GetMapping("/version")
+    public String version() {
+        return wechatConfigService.geyConfig("tool_version");
+    }
+
+    // 获取个微下载地址
+    @GetMapping("/downloadWechat")
+    public String downloadWechat() {
+        return wechatConfigService.geyConfig("wechat_download_addrs");
+    }
+    // 获取个微下载地址
+    @GetMapping("/downloadWXWork")
+    public String downloadWXWork() {
+        return wechatConfigService.geyConfig("wxwork_download_addrs");
+    }
+
+    // 获取个微下载地址
+    @GetMapping("/updata")
+    public String updata() {
+        return wechatConfigService.geyConfig("new_version_dow_url");
     }
     }
 
 
 }
 }

+ 11 - 0
nexo-example/nexo-model/src/main/java/com/nexo/model/wechat/mapper/WechatConfigMapper.java

@@ -0,0 +1,11 @@
+package com.nexo.model.wechat.mapper;
+
+public interface WechatConfigMapper {
+
+    // 获取程序公告
+    String notice();
+
+    // 获取程序版本
+    String geyConfig(String key);
+
+}

+ 10 - 0
nexo-example/nexo-model/src/main/java/com/nexo/model/wechat/service/IWechatConfigService.java

@@ -0,0 +1,10 @@
+package com.nexo.model.wechat.service;
+
+public interface IWechatConfigService {
+
+    // 获取程序公告
+    String notice();
+    // 获取程序版本
+    String geyConfig(String key);
+
+}

+ 28 - 0
nexo-example/nexo-model/src/main/java/com/nexo/model/wechat/service/Impl/WechatConfigServiceImpl.java

@@ -0,0 +1,28 @@
+package com.nexo.model.wechat.service.Impl;
+
+import com.nexo.model.wechat.mapper.WechatConfigMapper;
+import com.nexo.model.wechat.service.IWechatConfigService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+
+@Service
+@Slf4j
+public class WechatConfigServiceImpl implements IWechatConfigService {
+
+    @Resource
+    private WechatConfigMapper wechatConfigMapper;
+
+    // 获取程序公告
+    @Override
+    public String notice() {
+        return wechatConfigMapper.notice();
+    }
+
+    // 获取程序版本
+    @Override
+    public String geyConfig(String key) {
+        return wechatConfigMapper.geyConfig(key);
+    }
+}

+ 16 - 0
nexo-example/nexo-model/src/main/resources/mapper/WechatConfigMapper.xml

@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.nexo.model.wechat.mapper.WechatConfigMapper">
+
+    <select id="notice" resultType="string">
+        SELECT notice_content FROM `sys_notice` WHERE notice_type = 3 ORDER BY create_time DESC LIMIT 1
+    </select>
+
+    <select id="geyConfig" resultType="string">
+        SELECT config_value FROM `sys_config` WHERE config_key = #{key}
+    </select>
+
+
+</mapper>

+ 230 - 0
nexo-example/nexo-model/src/test/java/com/nexo/model/douyin/抖音测试工具类.java

@@ -0,0 +1,230 @@
+package com.nexo.model.douyin;
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.nexo.common.core.utils.OkHttpClientUtils;
+import com.nexo.common.core.utils.StringUtils;
+import com.nexo.model.douyin.utils.DouYinUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+import javax.script.Invocable;
+import javax.script.ScriptEngine;
+import javax.script.ScriptEngineManager;
+import javax.script.ScriptException;
+import java.io.FileReader;
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static org.apache.ibatis.ognl.DynamicSubscript.mid;
+
+@Slf4j
+@SpringBootTest
+public class 抖音测试工具类 {
+
+    @Test
+    public void 单个作品解析() {
+        String 抖音视频链接 = "1.23 cAG:/ :0pm 12/29 A@G.IV p身材教程来啦~ # 剪辑教程  https://v.douyin.com/wssqLCJ9O4c/ 复制此链接,打开Dou音搜索,直接观看视频!";
+        String 抖音链接 = "5.66 :6pm 01/27 h@o.Qk mDh:/ 手机全屏壁纸。# 性感 # 完美身材  https://v.douyin.com/qDWqlDX8ouQ/ 复制此链接,打开Dou音搜索,直接观看视频!";
+        String string = DouYinUtils.getRegexString(抖音链接, "[a-zA-z]+://[^\\s]*", 0);
+        HashMap<String, String> hashMap = new HashMap<>();
+        hashMap.put("User-Agent", "Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1 Edg/148.0.0.0");
+        String done = OkHttpClientUtils.doGetFollowRedirects(string, hashMap);
+        String string1 = StringUtils.extractMiddleText(done, "\"", "\"");
+        String string2 = OkHttpClientUtils.doGetFollowRedirects(string1, hashMap);
+        String right = StringUtils.right(string2, "window._ROUTER_DATA = ");
+        String left = StringUtils.left(right, "</script>");
+        JSONObject jsonObject = JSONObject.parseObject(DouYinUtils.decodeUnicode(left));
+        JSONObject data = jsonObject.getJSONObject("loaderData").getJSONObject("video_(id)/page");
+        data = data.getJSONObject("videoInfoRes").getJSONArray("item_list").getJSONObject(0);
+        log.info("{}", data);
+        log.info("===================== 发布者 ========================");
+        String sec_uid = data.getJSONObject("author").getString("sec_uid");
+        log.info("sec_uid:{}", sec_uid);
+        String short_id = data.getJSONObject("author").getString("short_id");
+        log.info("short_id:{}", short_id);
+        String nickname = data.getJSONObject("author").getString("nickname");
+        log.info("nickname:{}", nickname);
+        String signature = data.getJSONObject("author").getString("signature");
+        log.info("signature:{}", signature);
+
+        log.info("===================== 信息 ========================");
+        if (data.getInteger("aweme_type") == 4) {
+            String play_addr = data.getJSONObject("video").getJSONObject("play_addr").getJSONArray("url_list").getString(0);
+            log.info("play_addr:{}", play_addr.replaceAll("playwm", "play"));
+            String cover = data.getJSONObject("video").getJSONObject("cover").getJSONArray("url_list").getString(0);
+            log.info("cover:{}", cover);
+        } else if (data.getInteger("aweme_type") == 2) {
+            for (int i = 0; i < data.getJSONArray("images").size(); i++) {
+                String imgs = data.getJSONArray("images").getJSONObject(i).getJSONArray("url_list").getString(0);
+                log.info("img[{}]:{}", i + 1, imgs);
+            }
+        }
+
+        String desc = data.getString("desc");
+        log.info("desc:{}", desc);
+        Integer comment_count = data.getJSONObject("statistics").getInteger("comment_count");
+        Integer share_count = data.getJSONObject("statistics").getInteger("share_count");
+        Integer digg_count = data.getJSONObject("statistics").getInteger("digg_count");
+        Integer collect_count = data.getJSONObject("statistics").getInteger("collect_count");
+        log.info("comment_count:{}", comment_count);
+        log.info("share_count:{}", share_count);
+        log.info("digg_count:{}", digg_count);
+        log.info("collect_count:{}", collect_count);
+    }
+
+    @Test
+    public void 获取用户作品列表() {
+        String 分享连接 = "0- 长按复制此条消息,打开抖音搜索,查看TA的更多作品。 https://v.douyin.com/iz6uY8RfrJ8/ 7@5.com :2pm";
+        String string = DouYinUtils.getRegexString(分享连接, "[a-zA-z]+://[^\\s]*", 0);
+        String done = OkHttpClientUtils.doGetFollowRedirects(string, null);
+        String mid = null;
+        try {
+            mid = URLDecoder.decode(StringUtils.extractMiddleText(done, "\"", "\""), "UTF-8");
+        } catch (UnsupportedEncodingException e) {
+            throw new RuntimeException(e);
+        }
+        String maxcursor = "";
+        String sec_user_id = DouYinUtils.getRegexString(mid, "user/(.*?)\\?", 1);
+        log.info("sec_user_id:{}", sec_user_id);
+        Integer count = 0;
+        while (true) {
+            String params = "device_platform=webapp&aid=6383&channel=channel_pc_web&sec_user_id=" + sec_user_id + "&max_cursor=" + maxcursor + "&locate_item_id=7388761148507639094&locate_query=false&show_live_replay_strategy=1&need_time_list=" + "0" + "&time_list_query=0&whale_cut_token=&cut_version=1&count=18&publish_video_strategy_type=2&update_version_code=170400&pc_client_type=1&version_code=290100&version_name=29.1.0&cookie_enabled=true&screen_width=1832&screen_height=314&browser_language=zh-CN&browser_platform=Win32&browser_name=Edge&browser_version=126.0.0.0&browser_online=true&engine_name=Blink&engine_version=126.0.0.0&os_name=Android&os_version=6.0&cpu_core_num=12&device_memory=8&platform=Android&downlink=10&effective_type=4g&round_trip_time=150&webid=7347601222205687359&verifyFp=verify_lwllt9d5_131z6m2c_JOGv_4TDi_Aoje_kOMojbHCPw0e&fp=verify_lwllt9d5_131z6m2c_JOGv_4TDi_Aoje_kOMojbHCPw0e&msToken=MIOLSj1Hic1rlNKxvhty424gjUhagKo0ti6PK1s9uFfs0keS0miQ0metd3ZljkX0KjkQU_3nmJZm8tuYhMjIC1jntmfmuTRAfcZEbz1UzWZVqMDrHgg%3D";
+            String aBogus = DouYinUtils.getABogus(params);
+            String url = "https://www.douyin.com/aweme/v1/web/aweme/post/?" + params + "&a_bogus=" + aBogus + "&verifyFp=verify_lwllt9d5_131z6m2c_JOGv_4TDi_Aoje_kOMojbHCPw0e&fp=verify_lwllt9d5_131z6m2c_JOGv_4TDi_Aoje_kOMojbHCPw0e";
+            HashMap<String, String> header = DouYinUtils.getHeader();
+            String reqStr = OkHttpClientUtils.doGet(url, null, header);
+            // 解析json
+            JSONObject reqJson = JSONObject.parseObject(reqStr);
+            maxcursor = reqJson.getString("max_cursor");
+            JSONArray awemeList = reqJson.getJSONArray("aweme_list");
+            count += awemeList.size();
+            log.info("maxcursor:{}", maxcursor);
+            log.info("aweme_list:{}", awemeList.size());
+            if (reqJson.getInteger("has_more") != 1) {
+                break;
+            }
+        }
+        log.info("count:{}", count);
+
+    }
+
+    @Test
+    public void 获取用户信息() {
+        String 分享连接 = "0- 长按复制此条消息,打开抖音搜索,查看TA的更多作品。 https://v.douyin.com/iz6uY8RfrJ8/ 7@5.com :2pm";
+        String string = DouYinUtils.getRegexString(分享连接, "[a-zA-z]+://[^\\s]*", 0);
+        String done = OkHttpClientUtils.doGetFollowRedirects(string, null);
+        String mid = null;
+        try {
+            mid = URLDecoder.decode(StringUtils.extractMiddleText(done, "\"", "\""), "UTF-8");
+        } catch (UnsupportedEncodingException e) {
+            throw new RuntimeException(e);
+        }
+        log.info("mid:{}", mid);
+        String sec_user_id = DouYinUtils.getRegexString(mid, "user/(.*?)\\?", 0);
+        sec_user_id = StringUtils.extractMiddleText(sec_user_id, "user/", "?");
+        log.info("sec_user_id:{}", sec_user_id);
+        String params = "?device_platform=webapp&aid=6383&channel=channel_pc_web&publish_video_strategy_type=2&source=channel_pc_web&sec_user_id=" + sec_user_id + "&personal_center_strategy=1&profile_other_record_enable=1&land_to=1&update_version_code=170400&pc_client_type=1&pc_libra_divert=Windows&support_h265=0&support_dash=1&cpu_core_num=24&version_code=170400&version_name=17.4.0&cookie_enabled=true&screen_width=1920&screen_height=1080&browser_language=zh-CN&browser_platform=Win32&browser_name=Edge&browser_version=148.0.0.0&browser_online=true&engine_name=Blink&engine_version=148.0.0.0&os_name=Windows&os_version=10&device_memory=32&platform=PC&downlink=10&effective_type=4g&round_trip_time=50&webid=7639313678160397875";
+        String aBogus = DouYinUtils.getABogus(params);
+        String url = "https://www.douyin.com/aweme/v1/web/user/profile/other/" + params + "&a_bogus=" + aBogus + "&verifyFp=verify_mp4xmi43_R2ZADT9W_IKha_4C4e_9b3a_ctEUtT2EsPOe&fp=verify_mp4xmi43_R2ZADT9W_IKha_4C4e_9b3a_ctEUtT2EsPOe";
+        log.info("url:{}", url);
+        HashMap<String, String> header = DouYinUtils.getHeader();
+        String reqStr = OkHttpClientUtils.doGet(url, null, header);
+        // 解析json
+        JSONObject reqJson = JSONObject.parseObject(reqStr);
+        log.info("reqJson:{}", reqJson);
+
+    }
+
+    @Test
+    public void 获取抖音热榜() {
+        String url = "https://so-landing.douyin.com/aweme/v1/hot/search/list/?aid=581610&detail_list=1&board_type=0&board_sub_type=&need_board_tab=true&need_covid_tab=false&version_code=32.3.0 ";
+        String string = OkHttpClientUtils.doGet(url);
+        JSONObject jsonObject = JSONObject.parseObject(string);
+        JSONArray jsonArray = jsonObject.getJSONObject("data").getJSONArray("word_list");
+        for (int i = 0; i < jsonArray.size(); i++) {
+            JSONObject wordJson = jsonArray.getJSONObject(i);
+            if (wordJson.containsKey("group_id")) {
+                String url2 = null;
+                try {
+                    url2 = "https://so.douyin.com/s?is_from_mobile_home=1&search_entrance=aweme&enter_method=hot_list_page&innerWidth=430&innerHeight=932&reloadNavStart=1778841683198&is_no_width_reload=0&keyword=" + URLEncoder.encode(wordJson.getString("word"), "UTF-8") + "&gid=" + wordJson.getString("group_id");
+                } catch (UnsupportedEncodingException e) {
+                    throw new RuntimeException(e);
+                }
+                HashMap<String, String> hashMap = new HashMap<>();
+                hashMap.put("User-Agent", "Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1 Edg/148.0.0.0");
+
+                String string2 = OkHttpClientUtils.doGet(url2, null, hashMap);
+                String substring = string2.substring(string2.indexOf("let data = {\"business_data\":") + "let data = {\"business_data\":".length(), string2.indexOf(",\"render_info\""));
+                JSONArray parsed = JSONObject.parseArray(substring);
+                JSONObject object = new JSONObject();
+                object.put("group_id", wordJson.getString("group_id"));
+                object.put("word", wordJson.getString("word"));
+                JSONArray array = new JSONArray();
+                for (int i1 = 0; i1 < parsed.size(); i1++) {
+                    String string1 = parsed.getJSONObject(i1).getJSONObject("data").getString("provider_doc_id_str");
+                    if (parsed.getJSONObject(i1).getInteger("type") == 1) {
+                        array.add("https://www.douyin.com/video/" + string1);
+                    }
+                }
+                object.put("list", array);
+                log.info("{}", object);
+            }
+
+        }
+
+    }
+
+    @Test
+    public void 获取直播流() {
+        String url = "https://live.douyin.com/660321581729";
+        HashMap<String, String> hashMap = new HashMap<>();
+        hashMap.put("User-Agent", "Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1 Edg/148.0.0.0");
+        String done = OkHttpClientUtils.doGet(url, null, hashMap);
+        String roomId = DouYinUtils.getRegexString(done, "roomId=\"(.*?)\"", 1);
+        if (StringUtils.isEmpty(roomId)) {
+            throw new RuntimeException("未找到房间号");
+        }
+        String url2 = "https://webcast.amemv.com/webcast/room/reflow/info/?type_id=0&live_id=1&room_id=" + roomId + "&sec_user_id=&app_id=1128";
+        String string = OkHttpClientUtils.doGet(url2, null, hashMap);
+        JSONObject data = JSONObject.parseObject(string);
+        JSONObject stream = data.getJSONObject("data").getJSONObject("room").getJSONObject("stream_url");
+        JSONObject resolutionName = stream.getJSONObject("resolution_name");
+        log.info("========================== flv ==========================");
+        JSONObject flv_pull_url = stream.getJSONObject("flv_pull_url");
+        flv_pull_url.forEach((key, value) -> {
+            String resolution = resolutionName.getString(key);
+            log.info("{}:{}", resolution, value);
+        });
+
+        log.info("========================== hls ==========================");
+        JSONObject hls_pull_url_map = stream.getJSONObject("hls_pull_url_map");
+        hls_pull_url_map.forEach((key, value) -> {
+            String resolution = resolutionName.getString(key);
+            log.info("{}:{}", resolution, value);
+        });
+
+        log.info("========================== 主播信息 ==========================");
+
+        JSONObject owner = data.getJSONObject("data").getJSONObject("room").getJSONObject("owner");
+        String avatar = owner.getJSONObject("avatar_large").getJSONArray("url_list").getString(0);
+        log.info("头像:{}", avatar);
+        log.info("昵称:{}", owner.getString("nickname"));
+        log.info("签名:{}", owner.getString("signature"));
+        Integer following_count = owner.getJSONObject("follow_info").getInteger("following_count");
+        log.info("关注数:{}", following_count);
+        Integer follower_count = owner.getJSONObject("follow_info").getInteger("follower_count");
+        log.info("粉丝数:{}", follower_count);
+        log.info("SECUID:{}", owner.getString("sec_uid"));
+        log.info("抖音号:{}", owner.getString("display_id"));
+
+
+    }
+
+}

+ 1 - 0
pom.xml

@@ -52,6 +52,7 @@
 
 
         <!-- SMS 配置 -->
         <!-- SMS 配置 -->
         <sms4j.version>2.2.0</sms4j.version>
         <sms4j.version>2.2.0</sms4j.version>
+        <okhttp.version>4.10.0</okhttp.version>
     </properties>
     </properties>
 
 
     <profiles>
     <profiles>

Some files were not shown because too many files changed in this diff