DeliveryOrderNotificationService.java 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  1. package com.ruoyi.app.order;
  2. import com.alibaba.fastjson2.JSON;
  3. import com.alibaba.fastjson2.JSONObject;
  4. import com.ruoyi.app.order.dto.OrderPushBodyDto;
  5. import com.ruoyi.app.push.BusinessPushProperties;
  6. import com.ruoyi.app.push.NativeRiderBatchPushService;
  7. import com.ruoyi.app.push.UserNotificationScene;
  8. import org.slf4j.Logger;
  9. import org.slf4j.LoggerFactory;
  10. import com.ruoyi.app.utils.PayPush;
  11. import com.ruoyi.app.utils.event.PushEventService;
  12. import com.ruoyi.framework.manager.AsyncManager;
  13. import com.ruoyi.system.domain.InfoUser;
  14. import com.ruoyi.system.domain.PosOrder;
  15. import com.ruoyi.system.domain.RiderPosition;
  16. import com.ruoyi.system.mapper.RiderPositionMapper;
  17. import com.ruoyi.system.service.IInfoUserService;
  18. import org.springframework.beans.factory.annotation.Autowired;
  19. import org.springframework.stereotype.Service;
  20. import org.springframework.transaction.support.TransactionSynchronization;
  21. import org.springframework.transaction.support.TransactionSynchronizationManager;
  22. import java.util.LinkedHashSet;
  23. import java.util.List;
  24. import java.util.Set;
  25. import java.util.TimerTask;
  26. /** 外送订单开放接单及取消后的推送。 */
  27. @Service
  28. public class DeliveryOrderNotificationService {
  29. private static final Logger log = LoggerFactory.getLogger(DeliveryOrderNotificationService.class);
  30. private static final int RIDER_NOTIFICATION_LIMIT = 20;
  31. private final RiderPositionMapper riderPositionMapper;
  32. private final PushEventService pushEventService;
  33. private final IInfoUserService infoUserService;
  34. private final MerchantNotificationRouter merchantNotificationRouter;
  35. private final BusinessPushProperties pushProperties;
  36. private final NativeRiderBatchPushService nativeRiderBatch;
  37. public DeliveryOrderNotificationService(RiderPositionMapper riderPositionMapper,
  38. PushEventService pushEventService) {
  39. this(riderPositionMapper, pushEventService, null, null);
  40. }
  41. public DeliveryOrderNotificationService(RiderPositionMapper riderPositionMapper,
  42. PushEventService pushEventService,
  43. IInfoUserService infoUserService) {
  44. this(riderPositionMapper, pushEventService, infoUserService, null);
  45. }
  46. public DeliveryOrderNotificationService(RiderPositionMapper riderPositionMapper,
  47. PushEventService pushEventService,
  48. IInfoUserService infoUserService,
  49. MerchantNotificationRouter merchantNotificationRouter) {
  50. this(riderPositionMapper, pushEventService, infoUserService, merchantNotificationRouter, null, null);
  51. }
  52. @Autowired
  53. public DeliveryOrderNotificationService(RiderPositionMapper riderPositionMapper,
  54. PushEventService pushEventService,
  55. IInfoUserService infoUserService,
  56. MerchantNotificationRouter merchantNotificationRouter,
  57. BusinessPushProperties pushProperties,
  58. NativeRiderBatchPushService nativeRiderBatch) {
  59. this.riderPositionMapper = riderPositionMapper;
  60. this.pushEventService = pushEventService;
  61. this.infoUserService = infoUserService;
  62. this.merchantNotificationRouter = merchantNotificationRouter;
  63. this.pushProperties = pushProperties;
  64. this.nativeRiderBatch = nativeRiderBatch;
  65. }
  66. /**
  67. * 支付完成后的新外送订单通知:自配送订单通知商家,其余订单通知附近可接单骑手。
  68. * 所有下单、支付回调与线下转账确认入口复用这里,避免遗漏自配送分流。
  69. */
  70. public void notifyOrderAvailable(PosOrder order) {
  71. if (isAvailableForSelfDeliveryMerchant(order)) {
  72. notifySelfDeliveryOrderToMerchant(order);
  73. return;
  74. }
  75. if (!isAvailableForRider(order)) {
  76. return;
  77. }
  78. runAfterCommit(() -> {
  79. List<RiderPosition> riders = riderPositionMapper.getAcceptRiderList(
  80. order.getLongitude(), order.getLatitude(), RIDER_NOTIFICATION_LIMIT);
  81. if (riders != null) {
  82. if (pushProperties != null && pushProperties.getMode() == BusinessPushProperties.Mode.NATIVE
  83. && nativeRiderBatch != null) {
  84. String ddId = String.valueOf(order.getDdId());
  85. String body = OrderPushBodyDto.getJson(ddId, String.valueOf(order.getState()), 0);
  86. nativeRiderBatch.sendNewOrder(riders, "no.message.push.message",
  87. "no.message.push.new.food.order", body, ddId,
  88. rider -> sendAvailableOrderToRider(rider, order));
  89. } else {
  90. riders.forEach(rider -> sendAvailableOrderToRider(rider, order));
  91. }
  92. }
  93. });
  94. }
  95. /** 036 只为一个配送组排队一次骑手新任务提醒,商家仍逐子单收到自己的新订单。 */
  96. public void notifyMultiStoreGroupAvailable(Long groupId, Long eventId, PosOrder anchor,
  97. List<PosOrder> activeOrders, boolean waiting) {
  98. if (groupId == null || eventId == null || anchor == null || activeOrders == null
  99. || activeOrders.isEmpty()) throw new IllegalArgumentException("Invalid multi-store notification");
  100. for (PosOrder order : activeOrders) {
  101. if (merchantNotificationRouter != null) {
  102. merchantNotificationRouter.sendStoreNotification(order.getMdId(), order.getShId(),
  103. "no.message.push.message", "no.message.push.new.order",
  104. multiStoreBody(order, groupId, eventId), order.getDdId());
  105. }
  106. }
  107. if (!waiting) return;
  108. List<RiderPosition> riders = riderPositionMapper.getAcceptRiderList(
  109. anchor.getLongitude(), anchor.getLatitude(), RIDER_NOTIFICATION_LIMIT);
  110. if (riders == null) return;
  111. String body = multiStoreBody(anchor, groupId, eventId);
  112. String orderNo = anchor.getDdId();
  113. for (RiderPosition rider : riders) {
  114. if (rider == null || rider.getRiderId() == null) continue;
  115. AsyncManager.me().execute(new TimerTask() {
  116. @Override
  117. public void run() {
  118. PayPush.qsPushHandleLocal(new PayPush(), pushEventService, rider.getRiderId(), rider.getCid(),
  119. "no.message.push.message", "no.message.push.new.food.order", body, "", orderNo);
  120. }
  121. });
  122. }
  123. }
  124. private String multiStoreBody(PosOrder order, Long groupId, Long eventId) {
  125. JSONObject body = JSON.parseObject(OrderPushBodyDto.getJson(order.getDdId(),
  126. String.valueOf(order.getState()), 0));
  127. // 通知投递可能重试,前端按 eventId 去重并始终刷新 groupId 详情,不用旧单笔接单入口。
  128. body.put("groupId", groupId);
  129. body.put("eventId", eventId);
  130. return body.toJSONString();
  131. }
  132. /** 外送订单取消后,通知除取消人外的相关方。 */
  133. public void notifyOrderCancelled(PosOrder order, Long operatorId) {
  134. notifyOrderCancelled(order, operatorId, false);
  135. }
  136. public void notifyNoRiderCancelled(PosOrder order) {
  137. notifyOrderCancelled(order, null, true);
  138. }
  139. private void notifyOrderCancelled(PosOrder order, Long operatorId, boolean noRiderTimeout) {
  140. if (order == null || !Long.valueOf(0L).equals(order.getType())
  141. || infoUserService == null) {
  142. return;
  143. }
  144. runAfterCommit(() -> {
  145. Set<Long> recipientIds = new LinkedHashSet<>();
  146. recipientIds.add(order.getUserId());
  147. recipientIds.add(order.getQsId());
  148. if (merchantNotificationRouter == null) {
  149. recipientIds.add(order.getShId());
  150. }
  151. recipientIds.remove(null);
  152. recipientIds.remove(operatorId);
  153. if (recipientIds.isEmpty()) {
  154. return;
  155. }
  156. List<InfoUser> recipients = infoUserService.listByIds(recipientIds);
  157. if (recipients != null) {
  158. recipients.forEach(recipient -> sendCancelledOrder(recipient, order, noRiderTimeout));
  159. }
  160. if (merchantNotificationRouter != null && shouldNotifyMerchant(operatorId)) {
  161. String ddId = String.valueOf(order.getDdId());
  162. String body = OrderPushBodyDto.getJson(ddId, "4", 0);
  163. merchantNotificationRouter.sendStoreNotification(order.getMdId(), order.getShId(),
  164. "no.message.push.message", "no.message.push.order.cancelled", body, ddId);
  165. }
  166. });
  167. }
  168. private boolean shouldNotifyMerchant(Long operatorId) {
  169. if (operatorId == null || infoUserService == null) {
  170. return true;
  171. }
  172. InfoUser operator = infoUserService.getById(operatorId);
  173. return operator == null || "0".equals(operator.getUserType()) || "2".equals(operator.getUserType());
  174. }
  175. void sendAvailableOrderToRider(RiderPosition rider, PosOrder order) {
  176. if (rider == null || rider.getRiderId() == null) {
  177. return;
  178. }
  179. String ddId = String.valueOf(order.getDdId());
  180. String body = OrderPushBodyDto.getJson(ddId, String.valueOf(order.getState()), 0);
  181. AsyncManager.me().execute(new TimerTask() {
  182. @Override
  183. public void run() {
  184. PayPush.qsPushHandleLocal(new PayPush(), pushEventService, rider.getRiderId(), rider.getCid(),
  185. "no.message.push.message", "no.message.push.new.food.order", body, "", ddId);
  186. }
  187. });
  188. }
  189. void sendCancelledOrder(InfoUser recipient, PosOrder order) {
  190. sendCancelledOrder(recipient, order, false);
  191. }
  192. private void sendCancelledOrder(InfoUser recipient, PosOrder order, boolean noRiderTimeout) {
  193. if (recipient == null || recipient.getUserId() == null) {
  194. return;
  195. }
  196. String ddId = String.valueOf(order.getDdId());
  197. String body = OrderPushBodyDto.getJson(ddId, "4", 0);
  198. AsyncManager.me().execute(new TimerTask() {
  199. @Override
  200. public void run() {
  201. PayPush push = new PayPush();
  202. if (recipient.getUserId().equals(order.getQsId())) {
  203. PayPush.qsPushHandleLocal(push, pushEventService, recipient.getUserId(), recipient.getCid(),
  204. "no.message.push.message", "no.message.push.order.cancelled", body, "", ddId);
  205. } else if (recipient.getUserId().equals(order.getShId())) {
  206. PayPush.shPushHandleLocal(push, pushEventService, recipient.getUserId(), recipient.getCid(),
  207. "no.message.push.message", "no.message.push.order.cancelled", body, "", ddId);
  208. } else {
  209. // 只有原链路实际通知的用户带场景,取消操作者和非用户通知范围不变。
  210. String userBody = OrderPushBodyDto.getUserJson(ddId, "4", 0, 1,
  211. UserNotificationScene.ORDER_CANCELLED);
  212. String messageKey = noRiderTimeout && "3".equals(order.getPayType())
  213. ? "no.message.push.order.no.rider.line.cancelled"
  214. : "no.message.push.order.cancelled";
  215. PayPush.userPushHandleLocal(push, pushEventService, recipient.getUserId(), recipient.getCid(),
  216. "no.message.push.message", messageKey, userBody, "", ddId);
  217. }
  218. }
  219. });
  220. }
  221. /** 自配送新单只允许通知实际门店及其归属商家,绝不进入骑手通知链路。 */
  222. private void notifySelfDeliveryOrderToMerchant(PosOrder order) {
  223. if (merchantNotificationRouter == null) {
  224. return;
  225. }
  226. runAfterCommit(() -> {
  227. String ddId = String.valueOf(order.getDdId());
  228. String body = OrderPushBodyDto.getJson(ddId, String.valueOf(order.getState()), 0);
  229. merchantNotificationRouter.sendStoreNotification(order.getMdId(), order.getShId(),
  230. "no.message.push.message", "no.message.push.new.order", body, ddId);
  231. });
  232. }
  233. private boolean isAvailableForSelfDeliveryMerchant(PosOrder order) {
  234. return isPaidUnassignedDeliveryOrder(order) && Integer.valueOf(1).equals(order.getSelfDelivery());
  235. }
  236. private boolean isAvailableForRider(PosOrder order) {
  237. return isPaidUnassignedDeliveryOrder(order)
  238. && Boolean.TRUE.equals(order.getIsDisplay())
  239. && !Integer.valueOf(1).equals(order.getSelfDelivery());
  240. }
  241. private boolean isPaidUnassignedDeliveryOrder(PosOrder order) {
  242. return order != null
  243. && Long.valueOf(0L).equals(order.getType())
  244. && Long.valueOf(0L).equals(order.getState())
  245. && Long.valueOf(0L).equals(order.getDeliveryStatus())
  246. && Long.valueOf(1L).equals(order.getPayStatus())
  247. && Long.valueOf(0L).equals(order.getAfterSaleStatus())
  248. && order.getQsId() == null;
  249. }
  250. private static void runAfterCommit(Runnable action) {
  251. // 已提交的取消/开放接单不能被通知准备或调度失败反向影响。
  252. Runnable safeAction = () -> {
  253. try {
  254. action.run();
  255. } catch (Exception exception) {
  256. log.error("外送订单通知准备失败", exception);
  257. }
  258. };
  259. if (!TransactionSynchronizationManager.isSynchronizationActive()) {
  260. safeAction.run();
  261. return;
  262. }
  263. TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
  264. @Override
  265. public void afterCommit() {
  266. safeAction.run();
  267. }
  268. });
  269. }
  270. }