From 9a03e46771f68bae27b172c561b6b6f49b5f0505 Mon Sep 17 00:00:00 2001
From: Pu Zhibing <393733352@qq.com>
Date: 星期三, 11 六月 2025 20:22:59 +0800
Subject: [PATCH] 对接部分抖音接口

---
 ruoyi-modules/ruoyi-order/src/main/java/com/ruoyi/order/util/douyin/ClientTokenUtil.java     |   70 ++++++++
 ruoyi-modules/ruoyi-order/src/main/java/com/ruoyi/order/util/douyin/DouyinConfig.java        |   23 ++
 ruoyi-modules/ruoyi-order/src/main/java/com/ruoyi/order/util/douyin/OrderUtil.java           |  106 +++++++++++++
 ruoyi-modules/ruoyi-order/src/main/java/com/ruoyi/order/util/douyin/model/WebHook.java       |   29 +++
 ruoyi-modules/ruoyi-order/src/main/java/com/ruoyi/order/util/douyin/model/OrderWebHook.java  |   21 ++
 ruoyi-modules/ruoyi-order/src/main/java/com/ruoyi/order/util/douyin/model/Order.java         |   35 ++++
 ruoyi-modules/ruoyi-order/pom.xml                                                            |   28 ++-
 ruoyi-modules/ruoyi-order/src/main/java/com/ruoyi/order/util/douyin/DYWebHookController.java |  123 +++++++++++++++
 8 files changed, 427 insertions(+), 8 deletions(-)

diff --git a/ruoyi-modules/ruoyi-order/pom.xml b/ruoyi-modules/ruoyi-order/pom.xml
index 9db90d8..a6e1c7c 100644
--- a/ruoyi-modules/ruoyi-order/pom.xml
+++ b/ruoyi-modules/ruoyi-order/pom.xml
@@ -149,18 +149,24 @@
             <version>4.13.1</version>
             <scope>test</scope>
         </dependency>
-
+    
         <dependency>
             <groupId>cn.hutool</groupId>
             <artifactId>hutool-all</artifactId>
             <version>5.8.22</version>
         </dependency>
-	    <dependency>
-		    <groupId>com.ruoyi</groupId>
-		    <artifactId>ruoyi-api-goods</artifactId>
-		    <version>3.6.2</version>
-		    <scope>compile</scope>
-	    </dependency>
+        <dependency>
+            <groupId>com.ruoyi</groupId>
+            <artifactId>ruoyi-api-goods</artifactId>
+            <version>3.6.2</version>
+            <scope>compile</scope>
+        </dependency>
+        <!--抖音sdk-->
+        <dependency>
+            <groupId>com.douyin.openapi</groupId>
+            <artifactId>sdk</artifactId>
+            <version>1.0.0</version>
+        </dependency>
     </dependencies>
 
     <build>
@@ -179,5 +185,11 @@
             </plugin>
         </plugins>
     </build>
-   
+    <repositories>
+        <!--抖音sdk-->
+        <repository>
+            <id>douyin-openapi-repo</id>
+            <url>https://artifacts-cn-beijing.volces.com/repository/douyin-openapi/</url>
+        </repository>
+    </repositories>
 </project>
\ No newline at end of file
diff --git a/ruoyi-modules/ruoyi-order/src/main/java/com/ruoyi/order/util/douyin/ClientTokenUtil.java b/ruoyi-modules/ruoyi-order/src/main/java/com/ruoyi/order/util/douyin/ClientTokenUtil.java
new file mode 100644
index 0000000..98618ef
--- /dev/null
+++ b/ruoyi-modules/ruoyi-order/src/main/java/com/ruoyi/order/util/douyin/ClientTokenUtil.java
@@ -0,0 +1,70 @@
+package com.ruoyi.order.util.douyin;
+
+import com.aliyun.tea.TeaException;
+import com.douyin.openapi.client.Client;
+import com.douyin.openapi.client.models.OauthClientTokenRequest;
+import com.douyin.openapi.client.models.OauthClientTokenResponse;
+import com.douyin.openapi.client.models.OauthClientTokenResponseData;
+import com.douyin.openapi.credential.models.Config;
+import lombok.extern.slf4j.Slf4j;
+
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
+
+/**
+ * 抖音获取client_token工具类
+ * @author zhibing.pu
+ * @Date 2025/6/11 18:46
+ */
+@Slf4j
+public class ClientTokenUtil {
+	
+	public static String token = "";
+	
+	public static Long expiration_time = 0L;
+	
+	
+	/**
+	 * 获取client_token
+	 */
+	public static void getClientToken() {
+		try {
+			Config config = new Config().setClientKey(DouyinConfig.CLIENT_KEY).setClientSecret(DouyinConfig.CLIENT_SECRET); // 改成自己的app_id跟secret
+			Client client = new Client(config);
+            /* 构建请求参数,该代码示例中只给出部分参数,请用户根据需要自行构建参数值
+                token:
+                   1.若用户自行维护token,将用户维护的token赋值给该参数即可
+                   2.SDK包中有获取token的函数,请根据接口path在《OpenAPI SDK 总览》文档中查找获取token函数的名字
+                     在使用过程中,请注意token互刷问题
+                header:
+                   sdk中默认填充content-type请求头,若不需要填充除content-type之外的请求头,删除该参数即可
+            */
+			OauthClientTokenRequest sdkRequest = new OauthClientTokenRequest();
+			sdkRequest.setClientKey(DouyinConfig.CLIENT_KEY);
+			sdkRequest.setClientSecret(DouyinConfig.CLIENT_SECRET);
+			sdkRequest.setGrantType("client_credential");
+			OauthClientTokenResponse sdkResponse = client.OauthClientToken(sdkRequest);
+			String message = sdkResponse.getMessage();
+			if("success".equals(message)){
+				OauthClientTokenResponseData data = sdkResponse.getData();
+				if(data.getErrorCode() != 0){
+					log.error("【抖音】获取client_token失败:{}", data.getDescription());
+					throw new RuntimeException("【抖音】获取client_token失败:" + data.getDescription());
+				}
+				token = data.getAccessToken();
+				long second = LocalDateTime.now().toEpochSecond(ZoneOffset.of("+8"));
+				expiration_time = second + data.getExpiresIn() - 30;
+			}else{
+				OauthClientTokenResponseData data = sdkResponse.getData();
+				if(data.getErrorCode() != 0){
+					log.error("【抖音】获取client_token失败:{}", data.getDescription());
+					throw new RuntimeException("【抖音】获取client_token失败:" + data.getDescription());
+				}
+			}
+		} catch (TeaException e) {
+			System.out.println(e.getMessage());
+		} catch (Exception e) {
+			System.out.println(e.getMessage());
+		}
+	}
+}
diff --git a/ruoyi-modules/ruoyi-order/src/main/java/com/ruoyi/order/util/douyin/DYWebHookController.java b/ruoyi-modules/ruoyi-order/src/main/java/com/ruoyi/order/util/douyin/DYWebHookController.java
new file mode 100644
index 0000000..9a93645
--- /dev/null
+++ b/ruoyi-modules/ruoyi-order/src/main/java/com/ruoyi/order/util/douyin/DYWebHookController.java
@@ -0,0 +1,123 @@
+package com.ruoyi.order.util.douyin;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.ruoyi.common.redis.service.RedisService;
+import com.ruoyi.order.util.douyin.model.OrderWebHook;
+import com.ruoyi.order.util.douyin.model.WebHook;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.BufferedReader;
+import java.io.PrintWriter;
+import java.util.concurrent.TimeUnit;
+
+
+/**
+ * @author zhibing.pu
+ * @Date 2025/6/11 19:36
+ */
+@Slf4j
+@RestController
+@RequestMapping("/douyin")
+public class DYWebHookController {
+	
+	@Resource
+	private RedisService redisService;
+	
+	
+	/**
+	 * 抖音webhook
+	 * @param request
+	 * @param response
+	 */
+	@ResponseBody
+	@PostMapping("/webhook")
+	public void orderWebHook(HttpServletRequest request, HttpServletResponse response) {
+		// 获取消息中body
+		String str, wholeStr = "";
+		try{
+			BufferedReader br = request.getReader();
+			while((str = br.readLine()) != null){
+				wholeStr += str;
+			}
+			log.info("【抖音】webhook获取请求内容:{}",  wholeStr);
+		} catch (Exception e){
+			log.error("【抖音】webhook获取请求内容失败");
+			return;
+		}
+		// 获取请求头中的加签信息
+		String msgId = request.getHeader("Msg-Id");
+		String signature = request.getHeader("X-Douyin-Signature");
+		String data = DouyinConfig.CLIENT_SECRET + wholeStr;
+		String sign = DigestUtils.sha1Hex(data);
+		if(!sign.equals(signature)){
+			log.error("【抖音】webhook验签失败");
+			return;
+		}
+		if(redisService.hasKey(msgId)){
+			return;
+		}
+		redisService.setCacheObject(msgId, "", 60L, TimeUnit.SECONDS);
+		WebHook webHook = JSON.parseObject(wholeStr,  WebHook.class);
+		String event = webHook.getEvent();
+		switch (event){
+				//  验证
+			case "verify_webhook":
+				verifyWebhook(webHook, response);
+				break;
+				//订单消息通知
+			case "life_trade_order_notify":
+				lifeTradeOrderNotify(webHook, response);
+				break;
+				//券消息通知
+			case "life_trade_certificate_notify":
+				break;
+			default:
+				break;
+		}
+		
+	}
+	
+	
+	/**
+	 * 校验webhook
+	 * @param webHook
+	 * @param response
+	 */
+	public void verifyWebhook(WebHook webHook, HttpServletResponse response){
+		JSONObject jsonObject = JSON.parseObject(webHook.getContent());
+		//响应
+		PrintWriter out = null;
+		try {
+			out = response.getWriter();
+			out.print(jsonObject);
+			out.flush();
+			out.close();
+		}catch (Exception e){
+			e.printStackTrace();
+		}finally {
+			if(null != out){
+				out.close();
+			}
+		}
+	}
+	
+	
+	/**
+	 * 生活服务订单webhook
+	 * @param webHook
+	 * @param response
+	 */
+	public void lifeTradeOrderNotify(WebHook webHook, HttpServletResponse response){
+		OrderWebHook orderWebHook = JSON.parseObject(webHook.getContent(), OrderWebHook.class);
+		
+	}
+}
diff --git a/ruoyi-modules/ruoyi-order/src/main/java/com/ruoyi/order/util/douyin/DouyinConfig.java b/ruoyi-modules/ruoyi-order/src/main/java/com/ruoyi/order/util/douyin/DouyinConfig.java
new file mode 100644
index 0000000..f6da792
--- /dev/null
+++ b/ruoyi-modules/ruoyi-order/src/main/java/com/ruoyi/order/util/douyin/DouyinConfig.java
@@ -0,0 +1,23 @@
+package com.ruoyi.order.util.douyin;
+
+/**
+ * 抖音配置文件
+ * @author zhibing.pu
+ * @Date 2025/6/11 18:41
+ */
+public interface DouyinConfig {
+	
+	String APP_ID = "your app id";
+	/**
+	 * 应用唯一标识
+	 */
+	String CLIENT_KEY = "your app secret";
+	/**
+	 * 应用的密钥
+	 */
+	String CLIENT_SECRET = "your redirect uri";
+	/**
+	 * 来客商户根账户ID
+	 */
+	String ACCOUNT_ID = "your redirect uri";
+}
diff --git a/ruoyi-modules/ruoyi-order/src/main/java/com/ruoyi/order/util/douyin/OrderUtil.java b/ruoyi-modules/ruoyi-order/src/main/java/com/ruoyi/order/util/douyin/OrderUtil.java
new file mode 100644
index 0000000..8a5d454
--- /dev/null
+++ b/ruoyi-modules/ruoyi-order/src/main/java/com/ruoyi/order/util/douyin/OrderUtil.java
@@ -0,0 +1,106 @@
+package com.ruoyi.order.util.douyin;
+
+import com.douyin.openapi.client.Client;
+import com.douyin.openapi.client.models.*;
+import com.douyin.openapi.credential.models.Config;
+
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
+
+/**
+ * 抖音订单工具类
+ * @author zhibing.pu
+ * @Date 2025/6/11 18:58
+ */
+public class OrderUtil {
+	
+	
+	/**
+	 * 查询订单列表
+	 * @param page
+	 * @param pageSize
+	 * @param startTime
+	 * @param endTime
+	 * @return
+	 */
+	public static OrderQueryResponse queryOrderList(Integer page, Integer pageSize, LocalDateTime startTime, LocalDateTime endTime) {
+		//判断token是否过期
+		long now = LocalDateTime.now().toEpochSecond(ZoneOffset.of("+8"));
+		if(ClientTokenUtil.expiration_time < now){
+			ClientTokenUtil.getClientToken();
+		}
+		try {
+			Config config = new Config().setClientKey(DouyinConfig.CLIENT_KEY).setClientSecret(DouyinConfig.CLIENT_SECRET); // 改成自己的app_id跟secret
+			Client client = new Client(config);
+			OrderQueryRequest queryRequest = new OrderQueryRequest();
+			queryRequest.setAccessToken(ClientTokenUtil.token);
+			queryRequest.setAccountId(DouyinConfig.ACCOUNT_ID);
+			queryRequest.setPage(page);
+			queryRequest.setSize(pageSize);
+			if(null != startTime){
+				queryRequest.setStartTime(startTime.toEpochSecond(ZoneOffset.of("+8")));
+			}
+			if(null != endTime){
+				queryRequest.setEndTime(endTime.toEpochSecond(ZoneOffset.of("+8")));
+			}
+			OrderQueryResponse queryResponse = client.OrderQuery(queryRequest);
+			return queryResponse;
+		}catch (Exception e) {
+			e.printStackTrace();
+		}
+		return null;
+	}
+	
+	
+	/**
+	 * 根据订单id查询订单详情
+	 * @param orderId
+	 * @return
+	 */
+	public static OrderGetResponse getOrder(String orderId) {
+		//判断token是否过期
+		long now = LocalDateTime.now().toEpochSecond(ZoneOffset.of("+8"));
+		if(ClientTokenUtil.expiration_time < now){
+			ClientTokenUtil.getClientToken();
+		}
+		try {
+			Config config = new Config().setClientKey(DouyinConfig.CLIENT_KEY).setClientSecret(DouyinConfig.CLIENT_SECRET); // 改成自己的app_id跟secret
+			Client client = new Client(config);
+			OrderGetRequest getRequest = new OrderGetRequest();
+			getRequest.setAccessToken(ClientTokenUtil.token);
+			getRequest.setOrderId(orderId);
+			OrderGetResponse orderGetResponse = client.OrderGet(getRequest);
+			return orderGetResponse;
+		}catch (Exception e) {
+			e.printStackTrace();
+		}
+		return null;
+	}
+	
+	
+	/**
+	 * 根据订单id获取详细的订单信息
+	 * @param orderId
+	 * @return
+	 */
+	public static OrderDetailGetResponse getOrderDetail(String orderId) {
+		//判断token是否过期
+		long now = LocalDateTime.now().toEpochSecond(ZoneOffset.of("+8"));
+		if(ClientTokenUtil.expiration_time < now){
+			ClientTokenUtil.getClientToken();
+		}
+		try {
+			Config config = new Config().setClientKey(DouyinConfig.CLIENT_KEY).setClientSecret(DouyinConfig.CLIENT_SECRET); // 改成自己的app_id跟secret
+			Client client = new Client(config);
+			OrderDetailGetRequest request = new OrderDetailGetRequest();
+			request.setAccessToken(ClientTokenUtil.token);
+			request.setOrderId(orderId);
+			OrderDetailGetResponse orderDetailGetResponse = client.OrderDetailGet(request);
+			return orderDetailGetResponse;
+		}catch (Exception e) {
+			e.printStackTrace();
+		}
+		return null;
+	}
+	
+}
diff --git a/ruoyi-modules/ruoyi-order/src/main/java/com/ruoyi/order/util/douyin/model/Order.java b/ruoyi-modules/ruoyi-order/src/main/java/com/ruoyi/order/util/douyin/model/Order.java
new file mode 100644
index 0000000..c450f23
--- /dev/null
+++ b/ruoyi-modules/ruoyi-order/src/main/java/com/ruoyi/order/util/douyin/model/Order.java
@@ -0,0 +1,35 @@
+package com.ruoyi.order.util.douyin.model;
+
+import lombok.Data;
+
+/**
+ * @author zhibing.pu
+ * @Date 2025/6/11 19:34
+ */
+@Data
+public class Order {
+	/**
+	 * 商家账号id
+	 */
+	private String account_id;
+	/**
+	 * 创单时间,秒级时间戳
+	 */
+	private Long create_time;
+	/**
+	 * 订单id
+	 */
+	private String order_id;
+	/**
+	 * 售卖价格(分)
+	 */
+	private Long original_amount;
+	/**
+	 * 用户支付价格(分)
+	 */
+	private Long pay_amount;
+	/**
+	 * 支付时间,秒级时间戳
+	 */
+	private Long pay_time;
+}
diff --git a/ruoyi-modules/ruoyi-order/src/main/java/com/ruoyi/order/util/douyin/model/OrderWebHook.java b/ruoyi-modules/ruoyi-order/src/main/java/com/ruoyi/order/util/douyin/model/OrderWebHook.java
new file mode 100644
index 0000000..36cd569
--- /dev/null
+++ b/ruoyi-modules/ruoyi-order/src/main/java/com/ruoyi/order/util/douyin/model/OrderWebHook.java
@@ -0,0 +1,21 @@
+package com.ruoyi.order.util.douyin.model;
+
+import lombok.Data; /**
+ * @author zhibing.pu
+ * @Date 2025/6/11 19:32
+ */
+@Data
+public class OrderWebHook {
+	/**
+	 * 事件类型
+	 */
+	private String action;
+	/**
+	 * 消息发送时间
+	 */
+	private Long msg_time;
+	/**
+	 * 订单信息
+	 */
+	private Order order;
+}
diff --git a/ruoyi-modules/ruoyi-order/src/main/java/com/ruoyi/order/util/douyin/model/WebHook.java b/ruoyi-modules/ruoyi-order/src/main/java/com/ruoyi/order/util/douyin/model/WebHook.java
new file mode 100644
index 0000000..fc77f23
--- /dev/null
+++ b/ruoyi-modules/ruoyi-order/src/main/java/com/ruoyi/order/util/douyin/model/WebHook.java
@@ -0,0 +1,29 @@
+package com.ruoyi.order.util.douyin.model;
+
+import lombok.Data; /**
+ * @author zhibing.pu
+ * @Date 2025/6/11 19:42
+ */
+@Data
+public class WebHook {
+	/**
+	 * 消息类型,用于区分各类消息
+	 */
+	private String event;
+	/**
+	 * 对应服务商平台或开发者平台中的APPID,应用ID
+	 */
+	private String client_key;
+	/**
+	 * 标识用户身份的openId,同一用户在不同的APPID中openId不相同
+	 */
+	private String from_user_id;
+	/**
+	 * 消息内容,根据需要解析消息内容,不同类型的消息内容不同
+	 */
+	private String content;
+	/**
+	 * 抖音内部日志id,可提供给抖音方便排查问题
+	 */
+	private String log_id;
+}

--
Gitblit v1.7.1