OrderScheduler.java 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341
  1. package com.sqx.scheduler.order;
  2. import cn.hutool.core.collection.CollUtil;
  3. import cn.hutool.core.util.ObjectUtil;
  4. import cn.hutool.core.util.StrUtil;
  5. import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
  6. import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
  7. import com.ekyong.www.pay.pay.qrcode.api.RhtQrcodePayApi;
  8. import com.ekyong.www.pay.pay.qrcode.bean.QrcodeQueryRequestBean;
  9. import com.ekyong.www.pay.pay.qrcode.bean.QrcodeQueryResponseBean;
  10. import com.sqx.modules.common.entity.CommonInfo;
  11. import com.sqx.modules.common.service.CommonInfoService;
  12. import com.sqx.modules.goods.entity.GoodsShop;
  13. import com.sqx.modules.goods.service.GoodsShopService;
  14. import com.sqx.modules.order.entity.TbOrder;
  15. import com.sqx.modules.order.service.AppOrderService;
  16. import com.sqx.modules.pay.config.WechatPayConfig;
  17. import com.sqx.modules.pay.entity.PayDetails;
  18. import com.sqx.modules.pay.enums.PayStateEnums;
  19. import com.sqx.modules.pay.service.PayDetailsService;
  20. import com.sqx.modules.shop.entity.ShopType;
  21. import com.sqx.modules.shop.service.ShopTypeService;
  22. import com.sqx.scheduler.config.SchedulerLock;
  23. import lombok.RequiredArgsConstructor;
  24. import lombok.extern.slf4j.Slf4j;
  25. import org.redisson.api.RLock;
  26. import org.redisson.api.RedissonClient;
  27. import org.springframework.scheduling.annotation.Async;
  28. import org.springframework.scheduling.annotation.Scheduled;
  29. import org.springframework.stereotype.Component;
  30. import java.time.LocalDateTime;
  31. import java.time.format.DateTimeFormatter;
  32. import java.util.ArrayList;
  33. import java.util.Collections;
  34. import java.util.Date;
  35. import java.util.HashMap;
  36. import java.util.List;
  37. import java.util.Map;
  38. import java.util.concurrent.TimeUnit;
  39. import java.util.stream.Collectors;
  40. /**
  41. * 订单定时任务
  42. *
  43. * @author : codingliang
  44. * @date : 2024-08-19 20:42
  45. */
  46. @Slf4j
  47. @Component
  48. @RequiredArgsConstructor
  49. public class OrderScheduler {
  50. private final RedissonClient redissonClient;
  51. private final AppOrderService orderService;
  52. private final CommonInfoService commonInfoService;
  53. private final PayDetailsService payDetailsService;
  54. private final ShopTypeService shopTypeService;
  55. private final GoodsShopService goodsShopService;
  56. private final static String RHT_PAY_BASE_URL = "https://api.ekbuyclub.com";
  57. /**
  58. * 预约订单自动接单
  59. * 5分钟运行一次
  60. */
  61. @Async
  62. @Scheduled(cron = "0 */5 * * * ?")
  63. public void reservationAutoReceivingOrder() {
  64. RLock lock = redissonClient.getLock(SchedulerLock.ORDER_OF_RESERVATION_AUTO_RECEIVING_LOCK);
  65. boolean locked = false;
  66. try {
  67. locked = lock.tryLock(0, 120, TimeUnit.SECONDS);
  68. if (!locked) {
  69. // 获取不到锁,说明有其他实例正在执行此任务
  70. log.info("未获取到预约订单自动接单锁,跳过本次执行");
  71. return;
  72. }
  73. log.info("预约订单自动接单任务开始运行");
  74. List<Long> orderIds = orderService.getCurWaitReceivingOrderIds();
  75. if (CollUtil.isEmpty(orderIds)) {
  76. log.info("没有符合条件的预约订单,预约订单自动接单任务运行....");
  77. return;
  78. }
  79. List<TbOrder> waitOrders = orderIds.stream().map(orderId -> {
  80. TbOrder waitOrder = new TbOrder();
  81. waitOrder.setOrderId(orderId);
  82. waitOrder.setStatus(6);
  83. return waitOrder;
  84. }).collect(Collectors.toList());
  85. for (TbOrder waitOrder : waitOrders) {
  86. log.info("预约订单id:{},正在自动接单", waitOrder.getOrderId());
  87. try {
  88. orderService.updateOrder(waitOrder);
  89. log.info("预约订单id:{},自动接单成功", waitOrder.getOrderId());
  90. } catch (Exception e) {
  91. e.printStackTrace();
  92. log.error("预约单id:{},自动接单失败,失败原因:{}", waitOrder.getOrderId(), e.getMessage());
  93. }
  94. }
  95. log.info("预约订单自动接单任务运行成功");
  96. } catch (Exception e) {
  97. log.error("预约订单自动接单任务运行失败失败原因:{}", e.getMessage());
  98. } finally {
  99. if (locked && lock.isHeldByCurrentThread()) {
  100. lock.unlock();
  101. }
  102. }
  103. }
  104. /**
  105. * 制作中订单自动制作完成
  106. * 每分钟30秒时运行一次
  107. */
  108. @Async
  109. @Scheduled(cron = "30 */2 * * * ?")
  110. public void prodIngOrderAutoCompleted(){
  111. try {
  112. log.info("自动制作完成制作中订单任务开始运行");
  113. // 获取配置:是否开启制作中订单自动完成
  114. String value = commonInfoService.findOne(418).getValue();
  115. if("是".equals(value)){
  116. String minute = commonInfoService.findOne(419).getValue();
  117. LocalDateTime minusMinutes = LocalDateTime.now().minusMinutes(Long.valueOf(minute));
  118. // 获取制作中状态的订单列表
  119. List<TbOrder> orders = orderService.getProdIngOrders(minusMinutes);
  120. if (CollUtil.isEmpty(orders)) {
  121. log.info("当前时间段没有需要自动制作完成的订单");
  122. } else {
  123. // 订单随机排序
  124. Collections.shuffle(orders);
  125. for (TbOrder order: orders){
  126. try {
  127. log.info("订单:{},开始自动制作完成", order.getOrderId());
  128. order.setStatus(3);
  129. orderService.updateOrder(order);
  130. log.info("订单:{},自动制作完成", order.getOrderId());
  131. } catch (Exception e){
  132. e.printStackTrace();
  133. log.error("订单:{},自动完成异常,异常原因:【{}】", order.getOrderId(), e);
  134. }
  135. }
  136. }
  137. }
  138. log.info("自动制作完成制作中订单任务运行成功");
  139. } catch (Exception e) {
  140. log.error("制作中订单自动制作完成任务运行失败失败原因:【{}】", e);
  141. }
  142. }
  143. /**
  144. * 自动完成订单
  145. * 每30s运行一次
  146. */
  147. @Async
  148. @Scheduled(cron = "45 */1 * * * ?")
  149. public void autoFinishOrder() {
  150. RLock lock = redissonClient.getLock(SchedulerLock.ORDER_OF_WAIT_DELIVERY_AUTO_FINISH_LOCK);
  151. boolean locked = false;
  152. try {
  153. locked = lock.tryLock(0, 120, TimeUnit.SECONDS);
  154. if (!locked) {
  155. log.info("未获取到自动完成订单锁,跳过本次执行");
  156. return;
  157. }
  158. log.info("自动完成订单任务开始运行");
  159. CommonInfo one = commonInfoService.findOne(268);
  160. DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
  161. // 查询三大运营商的店铺
  162. List<Long> shopList = new ArrayList<>();
  163. QueryWrapper<ShopType> wrapperShopType = new QueryWrapper<>();
  164. wrapperShopType.eq("shop_type_name", "三大运营商");
  165. ShopType shopType = shopTypeService.getOne(wrapperShopType);
  166. if (ObjectUtils.isNotEmpty(shopType)) {
  167. QueryWrapper<GoodsShop> wrapperGoodShop = new QueryWrapper<>();
  168. wrapperGoodShop.eq("shop_type_id", shopType.getId());
  169. List<GoodsShop> goodsShops = goodsShopService.list(wrapperGoodShop);
  170. for (GoodsShop goodsShop : goodsShops) {
  171. Long shopId = goodsShop.getShopId();
  172. shopList.add(shopId);
  173. }
  174. }
  175. log.info("所属运营商店铺:"+shopList);
  176. // 查询所有 待取餐/派送中 状态的订单
  177. QueryWrapper<TbOrder> queryWrapper = new QueryWrapper<>();
  178. queryWrapper.eq("status", 3);
  179. queryWrapper.notIn(ObjectUtils.isNotEmpty(shopList)&&shopList.size()>0,"shop_id",shopList);
  180. List<TbOrder> orders = orderService.list(queryWrapper);
  181. for (TbOrder order : orders) {
  182. try {
  183. LocalDateTime updateTime = LocalDateTime.parse(order.getUpdateTime(), df);
  184. // 计算当前订单什么时候超时完成
  185. LocalDateTime overDateTime = updateTime.plusHours(Integer.parseInt(one.getValue()));
  186. if (LocalDateTime.now().isAfter(overDateTime)) {
  187. log.info("订单id:{},开始自动完成", order.getOrderId());
  188. orderService.accomplishOrder(order.getOrderId(), 2);
  189. log.info("订单id:{},完成自动完成", order.getOrderId());
  190. }
  191. } catch (Exception e) {
  192. log.error("订单id:{},开始自动完成失败,失败原因【{}】", order.getOrderId(), e);
  193. }
  194. }
  195. log.info("自动完成订单任务运行成功");
  196. } catch (Exception e) {
  197. log.info("自动完成订单任务运行异常,异常", e);
  198. } finally {
  199. if (locked && lock.isHeldByCurrentThread()) {
  200. lock.unlock();
  201. }
  202. }
  203. }
  204. /**
  205. * 自动更改超时订单
  206. * 每3分钟运行一次
  207. */
  208. @Async
  209. @Scheduled(cron = "0 */3 * * * ?")
  210. public void changeTimeOutOrder() {
  211. RLock lock = redissonClient.getLock(SchedulerLock.TIME_OUT_LOCK);
  212. boolean locked = false;
  213. try {
  214. locked = lock.tryLock(0, 300, TimeUnit.SECONDS);
  215. if (!locked) {
  216. // 获取不到锁,说明有其他实例正在执行此任务
  217. log.info("未获取到超时订单处理锁,跳过本次执行");
  218. return;
  219. }
  220. log.info("超时订单自动取消任务开始运行");
  221. // 加5分钟在前端强制取消前执行 ??
  222. long time = new Date().getTime() + 10*60*1000;
  223. List<TbOrder> orderList = orderService.changeTimeOutOrder(time);
  224. if (orderList.size() == 0) {
  225. log.info("没有符合条件的超时订单,超时订单自动取消任务运行....");
  226. return;
  227. }
  228. WechatPayConfig wechatMchConfig = WechatPayConfig.builder()
  229. .appId(commonInfoService.findOne(45).getValue())
  230. .mchId(commonInfoService.findOne(434).getValue())
  231. .mchKey(commonInfoService.findOne(435).getValue())
  232. .h5Url(commonInfoService.findOne(19).getValue())
  233. .build();
  234. RhtQrcodePayApi qrcodePay = new RhtQrcodePayApi(wechatMchConfig.getMchId(), wechatMchConfig.getMchKey(), RHT_PAY_BASE_URL);
  235. ArrayList<TbOrder> orders = new ArrayList<>();
  236. for (TbOrder tbOrder : orderList) {
  237. String orderNumber = tbOrder.getOrderNumber();
  238. try {
  239. // 状态为待支付需要调用支付系统订单查询接口判断支付系统对应的订单状态
  240. Map<String, String> data = new HashMap<>();
  241. // 商户订单号
  242. data.put("out_trade_no", orderNumber);
  243. QrcodeQueryRequestBean qqrb = new QrcodeQueryRequestBean();
  244. qqrb.setTraceno(orderNumber);// 商户流水号
  245. QrcodeQueryResponseBean response2 = qrcodePay.query(qqrb);
  246. String respCode = response2.getRespCode();
  247. log.info("超时任务处理订单【{}】查询支付结果,支付通知信息:{}", orderNumber, response2);
  248. // 0未支付,1支付成功,2支付失败,4退款中,5退款成功,6退款失败
  249. if (StrUtil.equals("1", respCode)){
  250. String payTime = response2.getTransDate()+ " " + response2.getTransTime();
  251. PayDetails payDetails = payDetailsService.getByOrderNo(orderNumber);
  252. if (!ObjectUtil.isNull(payDetails)) {
  253. payDetails.setState(PayStateEnums.PAY_SUCCESS.getStateCode());
  254. payDetails.setPayTime(payTime);
  255. payDetails.setTradeNo(response2.getChannelOrderno());
  256. payDetails.setRemark("Scheduled RHT PAY SUCCESS");
  257. payDetailsService.updateDetail(payDetails);
  258. }else {
  259. log.error("超时任务处理订单【{}】支付成功,但未获取到支付订单!支付通知信息:{}", orderNumber, response2);
  260. }
  261. tbOrder.setPayTime(payTime);
  262. tbOrder.setPayType(1);
  263. orderService.updateOrderAfterPaySuccess(tbOrder,payTime);
  264. } else {
  265. // 判断当前订单是否到超时时间,到了则取消
  266. Long timeOut = tbOrder.getTimeOut();
  267. if (new Date().getTime()>=timeOut) {
  268. tbOrder.setStatus(5);
  269. orders.add(tbOrder);
  270. }
  271. }
  272. } catch (Exception e) {
  273. log.error("超时任务处理订单【{}】查询支付结果失败,失败原因:{}", orderNumber, e.getMessage());
  274. e.printStackTrace();
  275. }
  276. }
  277. // list为空不要执行
  278. if(!orders.isEmpty()){
  279. orderService.updateBatchById(orders);
  280. }
  281. log.info("超时订单自动取消运行成功");
  282. } catch (Exception e) {
  283. log.error("超时订单自动取消任务运行失败失败原因:{}", e.getMessage());
  284. e.printStackTrace();
  285. } finally {
  286. if (locked && lock.isHeldByCurrentThread()) {
  287. lock.unlock();
  288. }
  289. }
  290. }
  291. /**
  292. * 更新店铺销量
  293. * 每天1点执行
  294. */
  295. @Async
  296. @Scheduled(cron = "0 0 1 * * ?")
  297. public void updateShopSales() {
  298. RLock lock = redissonClient.getLock(SchedulerLock.UPDATE_SHOP_SALES_LOCK);
  299. boolean locked = false;
  300. try {
  301. locked = lock.tryLock(0, 60, TimeUnit.SECONDS);
  302. if (!locked) {
  303. log.info("更新店铺销量任务:获取锁失败,本次跳过");
  304. return;
  305. }
  306. log.info("更新店铺销量任务开始运行");
  307. Map<Long, Integer> shopSalesMap = orderService.countFinishGroupByShopId();
  308. goodsShopService.updateShopSales(shopSalesMap);
  309. log.info("更新店铺销量任务运行成功");
  310. } catch (Exception e) {
  311. log.error("更新店铺销量任务运行失败,失败原因:{}", e.getMessage());
  312. } finally {
  313. if (locked && lock.isHeldByCurrentThread()) {
  314. lock.unlock();
  315. }
  316. }
  317. }
  318. }