ruoyi-modules/ruoyi-order/pom.xml | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
ruoyi-modules/ruoyi-order/src/main/java/com/ruoyi/order/util/douyin/ClientTokenUtil.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
ruoyi-modules/ruoyi-order/src/main/java/com/ruoyi/order/util/douyin/DYWebHookController.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
ruoyi-modules/ruoyi-order/src/main/java/com/ruoyi/order/util/douyin/DouyinConfig.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
ruoyi-modules/ruoyi-order/src/main/java/com/ruoyi/order/util/douyin/OrderUtil.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
ruoyi-modules/ruoyi-order/src/main/java/com/ruoyi/order/util/douyin/model/Order.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
ruoyi-modules/ruoyi-order/src/main/java/com/ruoyi/order/util/douyin/model/OrderWebHook.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
ruoyi-modules/ruoyi-order/src/main/java/com/ruoyi/order/util/douyin/model/WebHook.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
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> ruoyi-modules/ruoyi-order/src/main/java/com/ruoyi/order/util/douyin/ClientTokenUtil.java
New file @@ -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()); } } } ruoyi-modules/ruoyi-order/src/main/java/com/ruoyi/order/util/douyin/DYWebHookController.java
New file @@ -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); } } ruoyi-modules/ruoyi-order/src/main/java/com/ruoyi/order/util/douyin/DouyinConfig.java
New file @@ -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"; } ruoyi-modules/ruoyi-order/src/main/java/com/ruoyi/order/util/douyin/OrderUtil.java
New file @@ -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; } } ruoyi-modules/ruoyi-order/src/main/java/com/ruoyi/order/util/douyin/model/Order.java
New file @@ -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; } ruoyi-modules/ruoyi-order/src/main/java/com/ruoyi/order/util/douyin/model/OrderWebHook.java
New file @@ -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; } ruoyi-modules/ruoyi-order/src/main/java/com/ruoyi/order/util/douyin/model/WebHook.java
New file @@ -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; }