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