diff --git a/hiver-core/src/main/java/cc/hiver/core/common/constant/SettingConstant.java b/hiver-core/src/main/java/cc/hiver/core/common/constant/SettingConstant.java
index 238c0522..c2a68111 100644
--- a/hiver-core/src/main/java/cc/hiver/core/common/constant/SettingConstant.java
+++ b/hiver-core/src/main/java/cc/hiver/core/common/constant/SettingConstant.java
@@ -98,7 +98,13 @@ public interface SettingConstant {
// 重置密码
SMS_RESET_PASS,
// 工作流消息
- SMS_ACTIVITI
+ SMS_ACTIVITI,
+ // 无人接单用户通知
+ SMS_NO_ORDER,
+ // 拒绝接单通知
+ SMS_REJECT_ORDER,
+ // 被指定未处理通知
+ SMS_TIMEOUT
}
/**
diff --git a/hiver-core/src/main/java/cc/hiver/core/service/JPushService.java b/hiver-core/src/main/java/cc/hiver/core/service/JPushService.java
index e2d89d66..9dbbefdc 100644
--- a/hiver-core/src/main/java/cc/hiver/core/service/JPushService.java
+++ b/hiver-core/src/main/java/cc/hiver/core/service/JPushService.java
@@ -2,6 +2,6 @@ package cc.hiver.core.service;
public interface JPushService {
- void sendPushNotification(String registrationId, String message);
+ void sendPushNotification(String registrationId, String message,String orderId);
}
diff --git a/hiver-core/src/main/java/cc/hiver/core/serviceimpl/JPushServiceImpl.java b/hiver-core/src/main/java/cc/hiver/core/serviceimpl/JPushServiceImpl.java
index 28888cce..e93abfd7 100644
--- a/hiver-core/src/main/java/cc/hiver/core/serviceimpl/JPushServiceImpl.java
+++ b/hiver-core/src/main/java/cc/hiver/core/serviceimpl/JPushServiceImpl.java
@@ -22,7 +22,7 @@ public class JPushServiceImpl implements JPushService {
@Override
- public void sendPushNotification(String registrationId, String message) {
+ public void sendPushNotification(String registrationId, String message,String orderId) {
PushPayload payload = PushPayload.newBuilder()
.setPlatform(cn.jpush.api.push.model.Platform.all())
.setAudience(cn.jpush.api.push.model.audience.Audience.registrationId(registrationId))
@@ -31,7 +31,7 @@ public class JPushServiceImpl implements JPushService {
.setMsgContent("消息内容")
.addExtra("custom_key", "custom_value") // 额外参数
.addExtra("type", "订单通知")
- .addExtra("order_id", "123456")
+ .addExtra("order_id",orderId)
.addExtra("timestamp", System.currentTimeMillis())
.build())
.build();
diff --git a/hiver-modules/hiver-base/src/main/java/cc/hiver/base/controller/manage/UserController.java b/hiver-modules/hiver-base/src/main/java/cc/hiver/base/controller/manage/UserController.java
index eefed6d7..3974d7f6 100644
--- a/hiver-modules/hiver-base/src/main/java/cc/hiver/base/controller/manage/UserController.java
+++ b/hiver-modules/hiver-base/src/main/java/cc/hiver/base/controller/manage/UserController.java
@@ -20,9 +20,11 @@ import cc.hiver.core.vo.RoleDTO;
import cc.hiver.core.vo.WechatVo;
import cc.hiver.mall.entity.Shop;
import cc.hiver.mall.entity.ShopArea;
+import cc.hiver.mall.entity.ShopTakeaway;
import cc.hiver.mall.entity.WorkerRelaPrice;
import cc.hiver.mall.service.ShopAreaService;
import cc.hiver.mall.service.ShopService;
+import cc.hiver.mall.service.ShopTakeawayService;
import cc.hiver.mall.service.ShopUserService;
import cc.hiver.mall.service.mybatis.CustomerService;
import cc.hiver.mall.service.mybatis.WorkerRelaPriceService;
@@ -128,6 +130,10 @@ public class UserController {
@Autowired
private MemberSocialService memberSocialService;
+ @Autowired
+ private ShopTakeawayService shopTakeawayService;
+
+
/**
* 微信小程序登录凭证校验
@@ -668,22 +674,35 @@ public class UserController {
List
shop = shopService.getShopByUserid(wechatUser.getId());
if(shop != null){
+ List shopIds = shop.stream().map(Shop::getId).collect(Collectors.toList());
+ List shopTakeaway = shopTakeawayService.selectListByshopId(shopIds);
+ if(shopTakeaway != null){
+ shop.forEach(e -> {
+ for(ShopTakeaway shopTakeawa : shopTakeaway) {
+ if (e.getId().equals(shopTakeawa.getShopId())) {
+ e.setShopTakeaway(shopTakeawa);
+ }
+ }
+ });
+ }
resultMap.put("shop", shop);
}
Worker worker = workerService.findByUserId(wechatUser.getId());
- if(worker.getGetPushOrder() == 1){
+ if(worker!= null && worker.getGetPushOrder() == 1){
final List workerRelaPriceList = workerRelaPriceService.selectByWorkerId(worker.getWorkerId());
List workerRelaPriceListWaimai = new ArrayList<>();
List workerRelaPriceListKuaidi = new ArrayList<>();
- workerRelaPriceList.forEach(workerRelaPrice -> {
- if(workerRelaPrice.getGetPushOrder() == 1){
- if(workerRelaPrice.getOrderType() == 0 ){
- workerRelaPriceListWaimai.add(workerRelaPrice);
- }else{
- workerRelaPriceListKuaidi.add(workerRelaPrice);
+ if(workerRelaPriceList != null && workerRelaPriceList.size() > 0){
+ workerRelaPriceList.forEach(workerRelaPrice -> {
+ if(workerRelaPrice.getGetPushOrder() == 1){
+ if(workerRelaPrice.getOrderType() == 0 ){
+ workerRelaPriceListWaimai.add(workerRelaPrice);
+ }else{
+ workerRelaPriceListKuaidi.add(workerRelaPrice);
+ }
}
- }
- });
+ });
+ }
if(workerRelaPriceListWaimai.size() > 0){
resultMap.put("waimaiData", workerRelaPriceListWaimai);
}
diff --git a/hiver-modules/hiver-mall/pom.xml b/hiver-modules/hiver-mall/pom.xml
index 0e00968e..09424a86 100644
--- a/hiver-modules/hiver-mall/pom.xml
+++ b/hiver-modules/hiver-mall/pom.xml
@@ -34,6 +34,15 @@
1.0-SNAPSHOT
compile
+
+ org.springframework.boot
+ spring-boot-starter-amqp
+
+
+ com.alibaba
+ fastjson
+ 1.2.83
+
\ No newline at end of file
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/AdminSettlementController.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/AdminSettlementController.java
index 66da5cd2..811a26cc 100644
--- a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/AdminSettlementController.java
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/AdminSettlementController.java
@@ -170,6 +170,9 @@ public class AdminSettlementController {
private String settlementDate;
}
+ @Autowired
+ private org.springframework.amqp.rabbit.core.RabbitTemplate rabbitTemplate;
+
@PostMapping("/confirmByShop")
@ApiOperation(value = "每日结算确认(按商家)")
public Result confirmByShop(@RequestBody ConfirmShopReq req) {
@@ -177,28 +180,13 @@ public class AdminSettlementController {
return ResultUtil.error("请选择要确认的商家");
}
try {
- LambdaQueryWrapper qw = new LambdaQueryWrapper<>();
- if (StringUtils.isNotBlank(req.getRegionId())) {
- qw.eq(MallSettlementRecord::getRegionId, req.getRegionId());
- }
- if (StringUtils.isNotBlank(req.getSettlementDate())) {
- qw.apply("DATE(create_time) = {0}", req.getSettlementDate());
- }
- qw.in(MallSettlementRecord::getShopId, req.getShopIds());
- qw.eq(MallSettlementRecord::getStatus, 0);
-
- List list = mallSettlementRecordService.list(qw);
- if (list == null || list.isEmpty()) {
- return ResultUtil.success("没有需要确认的结算记录");
- }
- List recordIds = list.stream().map(MallSettlementRecord::getId).collect(Collectors.toList());
-
- mallSettlementRecordService.confirmSettlements(recordIds);
-
- return ResultUtil.success("结算确认成功");
+ // 将结算参数转成JSON并投递给RabbitMQ
+ String message = com.alibaba.fastjson.JSON.toJSONString(req);
+ rabbitTemplate.convertAndSend(cc.hiver.mall.mq.SettlementMqConfig.SETTLEMENT_EXCHANGE, cc.hiver.mall.mq.SettlementMqConfig.SETTLEMENT_CONFIRM_ROUTING_KEY, message);
+ return ResultUtil.success("结算确认任务已投递,后台排队处理中");
} catch (Exception e) {
- log.error("结算确认失败: {}", e.getMessage(), e);
- return ResultUtil.error("结算确认失败: " + e.getMessage());
+ log.error("推送结算确认MQ消息失败: {}", e.getMessage(), e);
+ return ResultUtil.error("推送任务失败: " + e.getMessage());
}
}
}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/MallDeliveryOrderController.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/MallDeliveryOrderController.java
index cf559d18..db2b5262 100644
--- a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/MallDeliveryOrderController.java
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/MallDeliveryOrderController.java
@@ -212,7 +212,6 @@ public class MallDeliveryOrderController {
@RequestParam String workerId) {
try {
mallDeliveryOrderService.workerPickup(deliveryId, workerId);
- jPushService.sendPushNotification("1507bfd3f6e2c0dbc0a", "您有一笔新的订单");
return ResultUtil.success("取货成功");
} catch (Exception e) {
log.error("取货失败: {}", e.getMessage(), e);
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/PushController.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/PushController.java
index 97ab3512..48fed97d 100644
--- a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/PushController.java
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/PushController.java
@@ -26,7 +26,7 @@ public class PushController {
@GetMapping("/push")
@ApiOperation(value = "推送消息")
public String pushNotification(@RequestParam String registrationId, @RequestParam String message) {
- jPushService.sendPushNotification(registrationId, message);
+ //jPushService.sendPushNotification(registrationId, message);
return "Push request sent!";
}
}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/dao/mapper/MallOrderMapper.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/dao/mapper/MallOrderMapper.java
index 7800c230..d851c843 100644
--- a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/dao/mapper/MallOrderMapper.java
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/dao/mapper/MallOrderMapper.java
@@ -32,4 +32,6 @@ public interface MallOrderMapper extends BaseMapper {
Integer selectPendingBadReviewCount(@Param("shopId") String shopId);
Integer selectRefundCount(@Param("shopId") String shopId);
+
+ List getWeChatId();
}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/mq/OrderAsyncConsumer.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/mq/OrderAsyncConsumer.java
new file mode 100644
index 00000000..b89de096
--- /dev/null
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/mq/OrderAsyncConsumer.java
@@ -0,0 +1,157 @@
+package cc.hiver.mall.mq;
+
+import cc.hiver.core.entity.User;
+import cc.hiver.core.entity.Worker;
+import cc.hiver.core.service.UserService;
+import cc.hiver.core.service.WorkerService;
+import cc.hiver.mall.dao.mapper.MallDeliveryOrderMapper;
+import cc.hiver.mall.dao.mapper.MallOrderMapper;
+import cc.hiver.mall.entity.MallDeliveryOrder;
+import cc.hiver.mall.pojo.dto.CreateOrderDTO;
+import cc.hiver.mall.serviceimpl.mybatis.MallOrderServiceImpl;
+import cc.hiver.mall.utils.WechatSendMessageUtil;
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import lombok.extern.slf4j.Slf4j;
+import me.chanjar.weixin.mp.bean.template.WxMpTemplateData;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.amqp.rabbit.annotation.RabbitListener;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 处理非核心、响应时间不敏感的常规异步队列。
+ */
+@Slf4j
+@Component
+public class OrderAsyncConsumer {
+
+ @Autowired
+ private MallOrderServiceImpl mallOrderService;
+
+ @Autowired
+ private UserService userService;
+
+ @Autowired
+ private MallDeliveryOrderMapper mallDeliveryOrderMapper;
+
+ @Autowired
+ private MallOrderMapper mallOrderMapper;
+
+ @Autowired
+ private WorkerService workerService;
+
+ @Autowired
+ private WechatSendMessageUtil wechatSendMessageUtil;
+
+ // 微信模板配置
+ private static final String TEMPLATE_ID = "K15zpZSHBNivouTfpW5FK1XFz8GbYAK8b9dXXOP_Ka0";
+
+ @RabbitListener(queues = OrderQueueConfig.ASYNC_WORKER_QUEUE)
+ public void handleWechatNotify(String message) {
+ log.info("【异步队列处理】微信模版推送: {}", message);
+ try {
+ JSONObject msg = JSON.parseObject(message);
+ String orderId = msg.getString("orderId");
+
+ // 查询配送单
+ LambdaQueryWrapper qw = new LambdaQueryWrapper<>();
+ qw.eq(MallDeliveryOrder::getOrderId, orderId);
+ MallDeliveryOrder delivery = mallDeliveryOrderMapper.selectOne(qw);
+ if (delivery == null) {
+ return;
+ }
+
+ List targetOpenIds = new ArrayList<>();
+ String orderTypeStr = "抢单大厅单";
+
+ if (StringUtils.isNotBlank(delivery.getWorkerId())) {
+ orderTypeStr = "指定接单";
+ // 获取指定骑手的关联 userId 和 openid
+ String openId = getOfficialAccountOpenidByWorkerId(delivery.getWorkerId());
+ if (StringUtils.isNotBlank(openId)) {
+ targetOpenIds.add(openId);
+ }
+ } else {
+ // 如果没有指定人,发给所有人?业务需求:给所有配送员发微信公众号消息
+ // 这可能需要查出所有 Worker 关联的 user.officialAccountOpenid (实际应用中可能需要加状态过滤)
+ // 为简化并实现逻辑,这里只查有效的骑手 user
+ targetOpenIds = getAllWorkerOpenIds();
+ }
+
+ if (targetOpenIds.isEmpty()) {
+ log.info("【异步队列处理】没有找到可推送微信模板的openid,目标跳过推送。orderId={}", orderId);
+ return;
+ }
+
+ // 组装模板参数 (参照要求配置)
+ List data = new ArrayList<>();
+ data.add(new WxMpTemplateData("thing3", orderTypeStr));
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ String timeFormat = delivery.getCreateTime() != null ? sdf.format(delivery.getCreateTime()) : sdf.format(new java.util.Date());
+ data.add(new WxMpTemplateData("time4", timeFormat));
+
+ String address = StringUtils.defaultString(delivery.getShopName()) + " - " + StringUtils.defaultString(delivery.getReceiverAddress());
+ // 模板参数有长度限制(通常20字),做简略截断以防发送失败
+ if (address.length() > 20) {
+ address = address.substring(0, 17) + "...";
+ }
+ data.add(new WxMpTemplateData("thing10", address));
+
+ // 查询或者组装此单据的物品数量,delivery可能没有存数量,这里可简化显示。
+ // 由于MallDeliveryOrder中可能没存总量,可通过orderId反查Goods(篇幅及耗时原因,也可以在发送消息时传进来)
+ // 查出 MallDeliveryOrder 自带或者去 MallOrderGood
+ data.add(new WxMpTemplateData("character_string15", "1")); // 也可以去 mall_order_goods 查数量
+
+ data.add(new WxMpTemplateData("phrase8", "待接单"));
+
+ wechatSendMessageUtil.sendWechatTempMessage(targetOpenIds, TEMPLATE_ID, data, "pages/index/index");
+ log.info("【异步队列处理】微信模版推送成功,推送总人数:{}", targetOpenIds.size());
+ } catch (Exception e) {
+ log.error("【异步队列处理】微信推送异常(不影响主链路): {}", e.getMessage(), e);
+ }
+ }
+
+
+ private String getOfficialAccountOpenidByWorkerId(String workerId) {
+ Worker worker = workerService.findByWorkerId(workerId);
+ if (worker != null && StringUtils.isNotBlank(worker.getUserId())) {
+ User user = userService.findById(worker.getUserId());
+ if (user != null) {
+ return user.getOfficialAccountOpenid();
+ }
+ }
+ return null;
+ }
+
+ private List getAllWorkerOpenIds() {
+ return mallOrderMapper.getWeChatId();
+ }
+
+ @RabbitListener(queues = OrderQueueConfig.ASYNC_CACHE_QUEUE)
+ public void handleCacheUpdate(String message) {
+ log.info("【异步队列处理】刷新订单附属缓存: {}", message);
+ try {
+ JSONObject body = JSON.parseObject(message);
+ String shopId = body.getString("shopId");
+ String regionId = body.getString("regionId");
+ Integer shopSaleCount = body.getInteger("shopSaleCount");
+ JSONArray itemsArray = body.getJSONArray("items");
+ // 将JSON结构重建后,委托回原有 Service 逻辑执行
+ List items = new ArrayList<>();
+ for (int i = 0; i < itemsArray.size(); i++) {
+ items.add(itemsArray.getJSONObject(i).toJavaObject(CreateOrderDTO.OrderItemDTO.class));
+ }
+ // productMap 重建,为避免过度依赖对象序列化,可以直接在 redis 增量方法里面反查
+ mallOrderService.updateSaleCacheIncrementalViaMq(shopId, regionId, items, shopSaleCount);
+ } catch (Exception e) {
+ log.error("【异步队列处理】缓存更新失败: {}", e.getMessage(), e);
+ }
+ }
+}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/mq/OrderAsyncProducer.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/mq/OrderAsyncProducer.java
new file mode 100644
index 00000000..1f738d2e
--- /dev/null
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/mq/OrderAsyncProducer.java
@@ -0,0 +1,66 @@
+package cc.hiver.mall.mq;
+
+import com.alibaba.fastjson.JSON;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.amqp.AmqpException;
+import org.springframework.amqp.core.Message;
+import org.springframework.amqp.core.MessagePostProcessor;
+import org.springframework.amqp.rabbit.core.RabbitTemplate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 订单相关的MQ异步消息发送者
+ */
+@Slf4j
+@Component
+public class OrderAsyncProducer {
+
+ @Autowired
+ private RabbitTemplate rabbitTemplate;
+
+ /**
+ * 发送微信模板推送请求 (包含参数,接收人标识等)
+ */
+ public void sendWechatNotify(String orderId) {
+ Map msg = new HashMap<>();
+ msg.put("orderId", orderId);
+ rabbitTemplate.convertAndSend(OrderQueueConfig.ORDER_DIRECT_EXCHANGE, OrderQueueConfig.ASYNC_WORKER_ROUTING, JSON.toJSONString(msg));
+ log.info("【订单MQ】已发出微信通知异步处理委托, orderId={}", orderId);
+ }
+
+ /**
+ * 发送异步缓存更新和非核心数据统计任务
+ */
+ public void sendCacheUpdate(Map params) {
+ rabbitTemplate.convertAndSend(OrderQueueConfig.ORDER_DIRECT_EXCHANGE, OrderQueueConfig.ASYNC_CACHE_ROUTING, JSON.toJSONString(params));
+ }
+
+ /**
+ * 发送延迟(死信)超时消息
+ * @param orderId 订单ID
+ * @param delayType 延迟监听类型(如:Worker_Timeout_5m, Worker_Timeout_10m, Shop_Cook_Timeout)
+ * @param delayMillis 需要延迟的毫秒数
+ */
+ public void sendDelayMessage(String orderId, String delayType, long delayMillis) {
+ Map msgBody = new HashMap<>();
+ msgBody.put("orderId", orderId);
+ msgBody.put("delayType", delayType);
+ msgBody.put("timestamp", System.currentTimeMillis());
+
+ rabbitTemplate.convertAndSend(OrderQueueConfig.DELAY_EXCHANGE, OrderQueueConfig.DELAY_ROUTING, JSON.toJSONString(msgBody),
+ new MessagePostProcessor() {
+ @Override
+ public Message postProcessMessage(Message message) throws AmqpException {
+ // 设置每条消息的 TTL (存活时间)
+ message.getMessageProperties().setExpiration(String.valueOf(delayMillis));
+ return message;
+ }
+ });
+
+ log.info("【订单MQ】已发送一条{}延时校验消息,设定在{}ms后触发,orderId={}", delayType, delayMillis, orderId);
+ }
+}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/mq/OrderDelayConsumer.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/mq/OrderDelayConsumer.java
new file mode 100644
index 00000000..25a91c0f
--- /dev/null
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/mq/OrderDelayConsumer.java
@@ -0,0 +1,135 @@
+package cc.hiver.mall.mq;
+
+import cc.hiver.core.common.constant.SettingConstant;
+import cc.hiver.core.common.sms.SmsUtil;
+import cc.hiver.core.serviceimpl.JPushServiceImpl;
+import cc.hiver.mall.dao.mapper.MallDeliveryOrderMapper;
+import cc.hiver.mall.dao.mapper.MallOrderMapper;
+import cc.hiver.mall.dao.mapper.ShopMapper;
+import cc.hiver.mall.entity.MallDeliveryOrder;
+import cc.hiver.mall.entity.MallOrder;
+import cc.hiver.mall.entity.Shop;
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.amqp.rabbit.annotation.RabbitListener;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ * 订单超时控制死信队列消费者
+ */
+@Slf4j
+@Component
+public class OrderDelayConsumer {
+
+ @Autowired
+ private MallDeliveryOrderMapper mallDeliveryOrderMapper;
+
+ @Autowired
+ private MallOrderMapper mallOrderMapper;
+
+ @Autowired
+ private ShopMapper shopMapper;
+
+ @Autowired
+ private SmsUtil smsUtil;
+
+ @Autowired
+ private JPushServiceImpl jPushService;
+
+ @RabbitListener(queues = OrderQueueConfig.DEAD_QUEUE)
+ public void processDelayMessage(String messageBody) {
+ log.info("【订单延时处理】接收到死信超时消息: {}", messageBody);
+ try {
+ JSONObject msg = JSON.parseObject(messageBody);
+ String orderId = msg.getString("orderId");
+ String delayType = msg.getString("delayType");
+
+ switch (delayType) {
+ case "Worker_Timeout_5m":
+ handleWorkerTimeout5m(orderId);
+ break;
+ case "Worker_Timeout_10m":
+ handleWorkerTimeout10m(orderId);
+ break;
+ case "NoWorker_Timeout_10m":
+ handleNoWorkerTimeout10m(orderId);
+ break;
+ case "Shop_Cook_Timeout":
+ handleShopCookTimeout(orderId);
+ break;
+ default:
+ log.warn("【订单延时处理】未知的延迟类型: {}", delayType);
+ }
+ } catch (Exception e) {
+ log.error("【订单延时处理】处理超时消息异常: {}", e.getMessage(), e);
+ }
+ }
+
+ private void handleWorkerTimeout5m(String orderId) {
+ MallDeliveryOrder delivery = getDeliveryOrder(orderId);
+ if (delivery == null || delivery.getStatus() != 0 || StringUtils.isBlank(delivery.getWorkerId())) {
+ // 已被处理、状态不再是0待接单,或者已经不是指定单,安全撤销(忽略)
+ return;
+ }
+ log.info("【订单延时处理】指定配送单 5分钟未接单,向配送员发送短信通知 orderId={}", orderId);
+ // 发送 SMS_TIMEOUT
+ if (StringUtils.isNotBlank(delivery.getWorkerPhone())) {
+ smsUtil.sendCode(delivery.getWorkerPhone(), null,SettingConstant.SMS_TYPE.SMS_TIMEOUT.name());
+ }
+ }
+
+ private void handleWorkerTimeout10m(String orderId) {
+ MallDeliveryOrder delivery = getDeliveryOrder(orderId);
+ if (delivery == null || delivery.getStatus() != 0 || StringUtils.isBlank(delivery.getWorkerId())) {
+ return;
+ }
+ log.info("【订单延时处理】指定配送单 10分钟未接单,移除配送员并通知用户 orderId={}", orderId);
+ // 更新配送单(去掉workerId,退回大厅)
+ LambdaUpdateWrapper uw = new LambdaUpdateWrapper<>();
+ uw.eq(MallDeliveryOrder::getId, delivery.getId())
+ .set(MallDeliveryOrder::getWorkerId, (String)null).set(MallDeliveryOrder::getWorkerName, (String)null)
+ .set(MallDeliveryOrder::getWorkerPhone, (String)null);
+ mallDeliveryOrderMapper.update(null, uw);
+
+ // 给用户通知
+ if (StringUtils.isNotBlank(delivery.getReceiverPhone())) {
+ smsUtil.sendCode(delivery.getReceiverPhone(), null,SettingConstant.SMS_TYPE.SMS_NO_ORDER.name());
+ }
+ }
+
+ private void handleNoWorkerTimeout10m(String orderId) {
+ MallDeliveryOrder delivery = getDeliveryOrder(orderId);
+ if (delivery == null || delivery.getStatus() != 0 || StringUtils.isNotBlank(delivery.getWorkerId())) {
+ return;
+ }
+ log.info("【订单延时处理】大厅抢单 10分钟无人接单,通知用户 orderId={}", orderId);
+ if (StringUtils.isNotBlank(delivery.getReceiverPhone())) {
+ smsUtil.sendCode(delivery.getReceiverPhone(),null, SettingConstant.SMS_TYPE.SMS_NO_ORDER.name());
+ }
+ }
+
+ private void handleShopCookTimeout(String orderId) {
+ MallOrder order = mallOrderMapper.selectById(orderId);
+ if (order == null || order.getShopMakeTime() != null) {
+ // 商家已经点击了出餐 (shopMakeTime != null)
+ return;
+ }
+ log.info("【订单延时处理】商家超时未点击出餐,向商家发送极光推送 orderId={}", orderId);
+ Shop shop = shopMapper.selectById(order.getShopId());
+ if (shop != null && StringUtils.isNotBlank(shop.getClientId())) {
+ // 发送给商家端的 APP Push
+ jPushService.sendPushNotification(shop.getClientId(), "您的订单出餐已超时,请尽快处理!", orderId);
+ }
+ }
+
+ private MallDeliveryOrder getDeliveryOrder(String orderId) {
+ LambdaQueryWrapper qw = new LambdaQueryWrapper<>();
+ qw.eq(MallDeliveryOrder::getOrderId, orderId);
+ return mallDeliveryOrderMapper.selectOne(qw);
+ }
+}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/mq/OrderQueueConfig.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/mq/OrderQueueConfig.java
new file mode 100644
index 00000000..add55809
--- /dev/null
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/mq/OrderQueueConfig.java
@@ -0,0 +1,104 @@
+package cc.hiver.mall.mq;
+
+import org.springframework.amqp.core.Binding;
+import org.springframework.amqp.core.BindingBuilder;
+import org.springframework.amqp.core.DirectExchange;
+import org.springframework.amqp.core.Queue;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 订单流程 MQ 配置,包括普通的异步任务和死信超时延迟任务。
+ */
+@Configuration
+public class OrderQueueConfig {
+
+ public static final String ORDER_DIRECT_EXCHANGE = "order.direct.exchange";
+
+ // 异步业务(如微信通知,缓存更新等)
+ public static final String ASYNC_WORKER_QUEUE = "order.async.worker.queue";
+ public static final String ASYNC_WORKER_ROUTING = "order.async.worker.routing.key";
+
+ public static final String ASYNC_CACHE_QUEUE = "order.async.cache.queue";
+ public static final String ASYNC_CACHE_ROUTING = "order.async.cache.routing.key";
+
+ // 延时消息 DLX 配置
+ // 原理:将消息发至 delay 队列(没有消费者),等过期后,由 rabbitmq 自动抛入 dead 交换机,最终进入 dead 队列供消费。
+ public static final String DELAY_EXCHANGE = "order.delay.exchange";
+ public static final String DELAY_QUEUE = "order.delay.queue";
+ public static final String DELAY_ROUTING = "order.delay.routing.key";
+
+ public static final String DEAD_EXCHANGE = "order.dead.exchange";
+ public static final String DEAD_QUEUE = "order.dead.queue";
+ public static final String DEAD_ROUTING = "order.dead.routing.key";
+
+ // ----------------- 常规业务配置 -----------------
+
+ @Bean
+ public DirectExchange orderDirectExchange() {
+ return new DirectExchange(ORDER_DIRECT_EXCHANGE, true, false);
+ }
+
+ @Bean
+ public Queue asyncWorkerQueue() {
+ return new Queue(ASYNC_WORKER_QUEUE, true);
+ }
+
+ @Bean
+ public Binding bindingAsyncWorkerQueue() {
+ return BindingBuilder.bind(asyncWorkerQueue()).to(orderDirectExchange()).with(ASYNC_WORKER_ROUTING);
+ }
+
+ @Bean
+ public Queue asyncCacheQueue() {
+ return new Queue(ASYNC_CACHE_QUEUE, true);
+ }
+
+ @Bean
+ public Binding bindingAsyncCacheQueue() {
+ return BindingBuilder.bind(asyncCacheQueue()).to(orderDirectExchange()).with(ASYNC_CACHE_ROUTING);
+ }
+
+ // ----------------- 死信(延时)架构配置 -----------------
+
+ @Bean
+ public DirectExchange delayExchange() {
+ return new DirectExchange(DELAY_EXCHANGE, true, false);
+ }
+
+ @Bean
+ public DirectExchange deadExchange() {
+ return new DirectExchange(DEAD_EXCHANGE, true, false);
+ }
+
+ @Bean
+ public Queue delayQueue() {
+ // 设置该队列的死信去向
+ Map args = new HashMap<>(2);
+ // x-dead-letter-exchange: 出现 dead letter 的时候将 letter 发送到这个 exchange
+ args.put("x-dead-letter-exchange", DEAD_EXCHANGE);
+ // x-dead-letter-routing-key: 出现 dead letter 的时候将 letter 路由给该 routing-key
+ args.put("x-dead-letter-routing-key", DEAD_ROUTING);
+ // 这是一个没有消费者的等待队列,消息在这里过期后会进入死信交换机
+ return new Queue(DELAY_QUEUE, true, false, false, args);
+ }
+
+ @Bean
+ public Binding bindingDelayQueue() {
+ return BindingBuilder.bind(delayQueue()).to(delayExchange()).with(DELAY_ROUTING);
+ }
+
+ @Bean
+ public Queue deadQueue() {
+ // 这是真正消费超时的死信队列
+ return new Queue(DEAD_QUEUE, true);
+ }
+
+ @Bean
+ public Binding bindingDeadQueue() {
+ return BindingBuilder.bind(deadQueue()).to(deadExchange()).with(DEAD_ROUTING);
+ }
+}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/mq/SettlementConfirmConsumer.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/mq/SettlementConfirmConsumer.java
new file mode 100644
index 00000000..4a1dba57
--- /dev/null
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/mq/SettlementConfirmConsumer.java
@@ -0,0 +1,63 @@
+package cc.hiver.mall.mq;
+
+import cc.hiver.mall.controller.AdminSettlementController.ConfirmShopReq;
+import cc.hiver.mall.entity.MallSettlementRecord;
+import cc.hiver.mall.service.mybatis.MallSettlementRecordService;
+import com.alibaba.fastjson.JSON;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.amqp.rabbit.annotation.RabbitListener;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 商家结算MQ消费者
+ */
+@Slf4j
+@Component
+public class SettlementConfirmConsumer {
+
+ @Autowired
+ private MallSettlementRecordService mallSettlementRecordService;
+
+ @RabbitListener(queues = SettlementMqConfig.SETTLEMENT_CONFIRM_QUEUE)
+ public void handleSettlementConfirm(String message) {
+ log.info("【MQ结算消费者】接收到按商家确认结算消息: {}", message);
+ try {
+ ConfirmShopReq req = JSON.parseObject(message, ConfirmShopReq.class);
+ if (req == null || req.getShopIds() == null || req.getShopIds().isEmpty()) {
+ log.warn("【MQ结算消费者】消息参数异常(商家ID列表为空), message={}", message);
+ return;
+ }
+
+ LambdaQueryWrapper qw = new LambdaQueryWrapper<>();
+ if (StringUtils.isNotBlank(req.getRegionId())) {
+ qw.eq(MallSettlementRecord::getRegionId, req.getRegionId());
+ }
+ if (StringUtils.isNotBlank(req.getSettlementDate())) {
+ qw.apply("DATE(create_time) = {0}", req.getSettlementDate());
+ }
+ qw.in(MallSettlementRecord::getShopId, req.getShopIds());
+ qw.eq(MallSettlementRecord::getStatus, 0);
+
+ List list = mallSettlementRecordService.list(qw);
+ if (list == null || list.isEmpty()) {
+ log.info("【MQ结算消费者】没有需要确认的结算记录,终止处理");
+ return;
+ }
+
+ List recordIds = list.stream().map(MallSettlementRecord::getId).collect(Collectors.toList());
+ mallSettlementRecordService.confirmSettlements(recordIds);
+ log.info("【MQ结算消费者】结算确认处理成功, 共处理了 {} 条记录", recordIds.size());
+
+ } catch (Exception e) {
+ log.error("【MQ结算消费者】处理结算消息异常: {}", e.getMessage(), e);
+ // 此处由于是简单的配置,如果出现异常会根据 application.yml 中的重试机制进行重试(max-attempts: 3)
+ throw new RuntimeException(e); // 抛出异常以触发重试或放入死信队列
+ }
+ }
+}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/mq/SettlementMqConfig.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/mq/SettlementMqConfig.java
new file mode 100644
index 00000000..71b89ddd
--- /dev/null
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/mq/SettlementMqConfig.java
@@ -0,0 +1,35 @@
+package cc.hiver.mall.mq;
+
+import org.springframework.amqp.core.Binding;
+import org.springframework.amqp.core.BindingBuilder;
+import org.springframework.amqp.core.DirectExchange;
+import org.springframework.amqp.core.Queue;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * 商家结算MQ生产环境配置
+ */
+@Configuration
+public class SettlementMqConfig {
+
+ public static final String SETTLEMENT_EXCHANGE = "mall.settlement.exchange";
+ public static final String SETTLEMENT_CONFIRM_QUEUE = "mall.settlement.confirm.queue";
+ public static final String SETTLEMENT_CONFIRM_ROUTING_KEY = "mall.settlement.confirm.routing.key";
+
+ @Bean
+ public DirectExchange settlementExchange() {
+ return new DirectExchange(SETTLEMENT_EXCHANGE, true, false);
+ }
+
+ @Bean
+ public Queue settlementConfirmQueue() {
+ // durable=true 持久化队列
+ return new Queue(SETTLEMENT_CONFIRM_QUEUE, true);
+ }
+
+ @Bean
+ public Binding bindingSettlementConfirmQueue() {
+ return BindingBuilder.bind(settlementConfirmQueue()).to(settlementExchange()).with(SETTLEMENT_CONFIRM_ROUTING_KEY);
+ }
+}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/pojo/dto/CreateOrderDTO.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/pojo/dto/CreateOrderDTO.java
index 3bdc3237..e0cb333d 100644
--- a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/pojo/dto/CreateOrderDTO.java
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/pojo/dto/CreateOrderDTO.java
@@ -99,6 +99,8 @@ public class CreateOrderDTO {
public static class WorkerParam {
@ApiModelProperty(value = "配送员ID", required = true)
private String workerId;
+ @ApiModelProperty(value = "配送员手机号", required = true)
+ private String workerPhone;
@ApiModelProperty(value = "配送员名称", required = true)
private String workerName;
@ApiModelProperty(value = "配送员规则佣金 orderBkge", required = true)
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/service/mybatis/MallOrderService.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/service/mybatis/MallOrderService.java
index 78dd91fc..39abf30f 100644
--- a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/service/mybatis/MallOrderService.java
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/service/mybatis/MallOrderService.java
@@ -70,6 +70,8 @@ public interface MallOrderService extends IService {
*/
void userRequireMake(String orderId);
+ void updateSaleCacheIncrementalViaMq(String shopId, String regionId, java.util.List items, Integer shopSaleCount);
+
/**
* 商家拒绝退款
*/
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/SendMessageServiceImpl.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/SendMessageServiceImpl.java
index 76f1997b..122deff4 100644
--- a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/SendMessageServiceImpl.java
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/SendMessageServiceImpl.java
@@ -21,7 +21,7 @@ public class SendMessageServiceImpl implements SendMessageService {
Worker worker = workerService.findById(workerId);
String registrationId = worker.getClientId();
String message = "您有一笔新的订单,请注意查看";
- jPushService.sendPushNotification(registrationId, message);
+ //jPushService.sendPushNotification(registrationId, message);
}
@Override
@@ -29,7 +29,7 @@ public class SendMessageServiceImpl implements SendMessageService {
Worker worker = workerService.findById(workerId);
String registrationId = worker.getClientId();
String message = "您有一笔订单被取消!";
- jPushService.sendPushNotification(registrationId, message);
+ //jPushService.sendPushNotification(registrationId, message);
}
@Override
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/MallDeliveryOrderServiceImpl.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/MallDeliveryOrderServiceImpl.java
index 2621fed3..9c7f7a92 100644
--- a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/MallDeliveryOrderServiceImpl.java
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/MallDeliveryOrderServiceImpl.java
@@ -1,16 +1,23 @@
package cc.hiver.mall.serviceimpl.mybatis;
+import cc.hiver.core.common.constant.SettingConstant;
+import cc.hiver.core.common.sms.SmsUtil;
import cc.hiver.core.entity.Worker;
+import cc.hiver.core.serviceimpl.JPushServiceImpl;
import cc.hiver.core.serviceimpl.WorkerServiceImpl;
import cc.hiver.mall.dao.mapper.MallDeliveryOrderMapper;
import cc.hiver.mall.dao.mapper.MallOrderGoodsMapper;
import cc.hiver.mall.dao.mapper.MallOrderGroupMapper;
+import cc.hiver.mall.dao.mapper.ShopTakeawayMapper;
import cc.hiver.mall.entity.*;
+import cc.hiver.mall.mq.OrderAsyncProducer;
import cc.hiver.mall.pojo.query.MallDeliveryOrderPageQuery;
import cc.hiver.mall.pojo.query.MallRefundRecordPageQuery;
+import cc.hiver.mall.service.ShopService;
import cc.hiver.mall.service.mybatis.MallDeliveryOrderService;
import cc.hiver.mall.service.mybatis.MallOrderService;
import cc.hiver.mall.service.mybatis.MallRefundRecordService;
+import cc.hiver.mall.service.mybatis.MallUserCouponService;
import cc.hiver.mall.utils.MerchantOrderSeqUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
@@ -56,6 +63,21 @@ public class MallDeliveryOrderServiceImpl extends ServiceImpl pageDelivery(MallDeliveryOrderPageQuery q) {
@@ -119,7 +141,7 @@ public class MallDeliveryOrderServiceImpl extends ServiceImpl orderIdsSend = new ArrayList<>();
// 更新配送单
Date createTime = new Timestamp(System.currentTimeMillis());
// 1. 获取当前时间 (LocalDateTime)
@@ -136,6 +158,7 @@ public class MallDeliveryOrderServiceImpl extends ServiceImpl orderIds = Arrays.asList(group.getGroupOrderIds().split(","));
+ orderIdsSend = orderIds;
for (String orderId : orderIds) {
MallOrder orderInner = mallOrderService.getById(orderId);
if (orderInner != null) {
@@ -152,6 +176,7 @@ public class MallDeliveryOrderServiceImpl extends ServiceImpl 配送中(或待取货)
if (StringUtils.isNotBlank(delivery.getOrderId())) {
if (order != null && order.getStatus() == ORDER_STATUS_WAIT_DELIVERY) {
+ orderIdsSend.add(delivery.getOrderId());
String latestSeq = merchantOrderSeqUtil.generateOrderSequence(order.getShopId(), 1);
LambdaUpdateWrapper oUw = new LambdaUpdateWrapper<>();
oUw.eq(MallOrder::getId, delivery.getOrderId())
.set(MallOrder::getNumberCode, latestSeq).set(MallOrder::getStatus, 3); // 待取货
//缺少根据groupId更新面对面团所有订单状态
mallOrderService.update(oUw);
+ jPushService.sendPushNotification(shop.getClientId(), "您有一笔新的订单",delivery.getOrderId());
numberCode = latestSeq;
}
}
@@ -173,12 +200,14 @@ public class MallDeliveryOrderServiceImpl extends ServiceImpl 配送中(或待取货)
if (StringUtils.isNotBlank(delivery.getOrderId())) {
if (order != null && order.getStatus() == ORDER_STATUS_WAIT_DELIVERY) {
+ orderIdsSend.add(delivery.getOrderId());
String latestSeq = merchantOrderSeqUtil.generateOrderSequence(order.getShopId(), 1);
LambdaUpdateWrapper oUw = new LambdaUpdateWrapper<>();
oUw.eq(MallOrder::getId, delivery.getOrderId())
.set(MallOrder::getNumberCode, latestSeq).set(MallOrder::getStatus, 3); // 待取货
//缺少根据groupId更新面对面团所有订单状态
mallOrderService.update(oUw);
+ jPushService.sendPushNotification(shop.getClientId(), "您有一笔新的订单",delivery.getOrderId());
numberCode = latestSeq;
}
}
@@ -186,10 +215,32 @@ public class MallDeliveryOrderServiceImpl extends ServiceImpl orderId) {
+ orderId.forEach(id -> {
+ MallOrder order = mallOrderService.getById(id);
+ if (order != null) {
+ long cookTimeMins = 10; // default
+ try {
+ LambdaQueryWrapper qw = new LambdaQueryWrapper<>();
+ qw.eq(cc.hiver.mall.entity.ShopTakeaway::getShopId, order.getShopId());
+ ShopTakeaway shopTakeaway = shopTakeawayMapper.selectOne(qw);
+ if (shopTakeaway != null) {
+ cookTimeMins = shopTakeaway.getCookingTime();
+ }
+ } catch (Exception e) {
+ log.error("获取 ShopTakeaway cookingTime 异常: {}", e.getMessage());
+ }
+ orderAsyncProducer.sendDelayMessage(id, "Shop_Cook_Timeout", cookTimeMins * 60 * 1000L);
+ }
+ });
+ }
+
/**
* 配送员取货(状态:待取货 -> 配送中)
*/
@@ -295,10 +346,15 @@ public class MallDeliveryOrderServiceImpl extends ServiceImpl uw = new LambdaUpdateWrapper<>();
uw.eq(MallDeliveryOrder::getId, deliveryId)
- .set(MallDeliveryOrder::getWorkerId, null);
+ .set(MallDeliveryOrder::getWorkerId, null).set(MallDeliveryOrder::getWorkerName, (String)null)
+ .set(MallDeliveryOrder::getWorkerPhone, (String)null);
this.update(uw);
+ //给用户发送短信通知被拒绝
+ smsUtil.sendCode(delivery.getReceiverPhone(), null, SettingConstant.SMS_TYPE.SMS_REJECT_ORDER.name());
}
// ================================================================
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/MallOrderGroupServiceImpl.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/MallOrderGroupServiceImpl.java
index d2cabeda..c5ef6eae 100644
--- a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/MallOrderGroupServiceImpl.java
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/MallOrderGroupServiceImpl.java
@@ -51,6 +51,9 @@ public class MallOrderGroupServiceImpl extends ServiceImpl dqw = new LambdaQueryWrapper<>();
dqw.eq(MallDeliveryOrder::getOrderId, order.getId()).last("LIMIT 1");
MallDeliveryOrder delivery = mallDeliveryOrderMapper.selectOne(dqw);
-
+ //mq处理
+ triggerDeliveryAsyncEvents(order.getId(),delivery);
if (delivery != null) {
if (StringUtils.isNotBlank(delivery.getWorkerId())) {
boolean isValid = false;
@@ -189,6 +192,27 @@ public class MallOrderGroupServiceImpl extends ServiceImpl().eq("id",dto.getShopId()).set("sale_count",shopSaleCount));
}
- // 增量更新Redis缓存(店铺销量 + 商品销量),避免走refreshShopCache全量查询
+ // 增量更新Redis缓存(店铺销量 + 商品销量),改为交给MQ异步处理
try {
- updateSaleCacheIncremental(dto.getShopId(), dto.getRegionId(), dto.getItems(), productMap, shopSaleCount);
+ java.util.Map asyncParams = new java.util.HashMap<>();
+ asyncParams.put("shopId", dto.getShopId());
+ asyncParams.put("regionId", dto.getRegionId());
+ asyncParams.put("items", dto.getItems());
+ asyncParams.put("shopSaleCount", shopSaleCount);
+ orderAsyncProducer.sendCacheUpdate(asyncParams);
} catch (Exception e) {
- log.warn("增量更新Redis销量缓存失败,不影响下单: {}", e.getMessage());
+ log.warn("推送Redis销量缓存异步任务失败,不影响下单: {}", e.getMessage());
}
BigDecimal packageFee = dto.getPackageFee() != null ? dto.getPackageFee() : BigDecimal.ZERO;
@@ -475,6 +492,9 @@ public class MallOrderServiceImpl extends ServiceImpl uw = new LambdaUpdateWrapper<>();
@@ -523,6 +543,54 @@ public class MallOrderServiceImpl extends ServiceImpl dw = new LambdaQueryWrapper<>();
+ dw.eq(MallDeliveryOrder::getOrderId, orderId);
+ MallDeliveryOrder delivery = mallDeliveryOrderMapper.selectOne(dw);
+ if (delivery != null) {
+ boolean isSpecified = StringUtils.isNotBlank(delivery.getWorkerId());
+ if (isSpecified) {
+ // 指定单:5分钟校验,10分钟校验
+ orderAsyncProducer.sendDelayMessage(orderId, "Worker_Timeout_5m", 5 * 60 * 1000L);
+ orderAsyncProducer.sendDelayMessage(orderId, "Worker_Timeout_10m", 10 * 60 * 1000L);
+ } else {
+ // 抢单大厅:10分钟校验
+ orderAsyncProducer.sendDelayMessage(orderId, "NoWorker_Timeout_10m", 10 * 60 * 1000L);
+ }
+ }
+
+ // 如果需要检测出餐超时,发一条出餐超时延时消息
+ //triggerShopCookTimeoutEvent(orderId);
+ }
+
+ @Autowired
+ private cc.hiver.mall.dao.mapper.ShopTakeawayMapper shopTakeawayMapper;
+
+ private void triggerShopCookTimeoutEvent(String orderId) {
+ MallOrder order = this.getById(orderId);
+ if (order != null) {
+ long cookTimeMins = 15; // default
+ try {
+ LambdaQueryWrapper qw = new LambdaQueryWrapper<>();
+ qw.eq(cc.hiver.mall.entity.ShopTakeaway::getShopId, order.getShopId());
+ List list = shopTakeawayMapper.selectList(qw);
+ if (list != null && !list.isEmpty() && list.get(0).getCookingTime() != null) {
+ cookTimeMins = list.get(0).getCookingTime();
+ }
+ } catch (Exception e) {
+ log.error("获取 ShopTakeaway cookingTime 异常: {}", e.getMessage());
+ }
+ orderAsyncProducer.sendDelayMessage(orderId, "Shop_Cook_Timeout", cookTimeMins * 60 * 1000L);
+ }
+ }
+
@Override
@Transactional(rollbackFor = Exception.class)
public void shopReject(String orderId, String reason) {
@@ -870,11 +938,17 @@ public class MallOrderServiceImpl extends ServiceImpl uw = new LambdaUpdateWrapper<>();
uw.eq(MallOrder::getId, orderId).set(MallOrder::getUserRequireMake,1);
this.update(uw);
+ jPushService.sendPushNotification(shopService.findById(order.getShopId()).getClientId(), "您有一笔新的到店订单",orderId);
+
+ // 触发商家出餐超时监听
+ triggerShopCookTimeoutEvent(orderId);
}
@Override
@@ -1234,6 +1308,7 @@ public class MallOrderServiceImpl extends ServiceImpl goodsList = mallOrderGoodsMapper.selectByOrderId(order.getId());
if (goodsList == null || goodsList.isEmpty()) return;
List returnList = goodsList.stream()
@@ -1585,4 +1667,14 @@ public class MallOrderServiceImpl extends ServiceImpl items, Integer shopSaleCount) {
+ // 反查 productMap
+ List productIds = items.stream().map(CreateOrderDTO.OrderItemDTO::getProductId).collect(Collectors.toList());
+ List productList = productMapper.selectBatchIds(productIds);
+ Map productMap = productList.stream().collect(Collectors.toMap(Product::getId, p -> p));
+
+ updateSaleCacheIncremental(shopId, regionId, items, productMap, shopSaleCount);
+ }
}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/utils/WechatSendMessageUtil.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/utils/WechatSendMessageUtil.java
new file mode 100644
index 00000000..2fa2c63a
--- /dev/null
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/utils/WechatSendMessageUtil.java
@@ -0,0 +1,73 @@
+package cc.hiver.mall.utils;
+
+import me.chanjar.weixin.mp.api.WxMpService;
+import me.chanjar.weixin.mp.bean.template.WxMpTemplateData;
+import me.chanjar.weixin.mp.bean.template.WxMpTemplateMessage;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+
+/**
+ * 微信推送模板消息工具类
+ *
+ * @author Yazhi Li
+ */
+@Component
+public class WechatSendMessageUtil {
+ @Autowired
+ private WxMpService wxMpService;
+
+ @Value("${hiver.social.wechat.appId}")
+ private String appId;
+
+ private static final Logger log = LoggerFactory.getLogger(WechatSendMessageUtil.class);
+ public void sendWechatTempMessage(List officialAccountOpenids,String templateId, List data,String path) throws Exception {
+ for (String openId : officialAccountOpenids) {
+
+ final WxMpTemplateMessage templateMessage = WxMpTemplateMessage.builder()
+ // 要推送的用户openid
+ .toUser(openId)
+ // 消息详情跳转地址:https://blog.csdn.net/qq_34272760/article/details/120152903
+ // 若需要跳转小程序,url则设置为:http://mp.weixin.qq.com,然后设置相关的MiniProgram参数【跳转的小程序必须是公众号关联的小程序!!!】
+ .url("http://mp.weixin.qq.com")
+ // 微信模板ID
+ .templateId(templateId)
+ .build();
+ // data的字段及内容是自定义的,不必按我这儿的来,具体怎么和已有的系统消息结合,实现key和color可配置化需自行考虑
+ //final JsonObject jsonObject = JsonParser.parseString(weChatServerMsgVo.getContent()).getAsJsonObject();
+ /*final List data = new ArrayList<>();
+ for (String key : jsonObject.keySet()) {
+ final Object value = jsonObject.get(key);
+ String afterValue = String.valueOf(value).replace("\"", "");
+ log.info("Key: " + key + ", Value: " + afterValue);
+ data.add(new WxMpTemplateData(key, afterValue));
+
+ }*/
+ templateMessage.setData(data);
+
+ // 跳转小程序相关配置【跳转的小程序必须是公众号关联的小程序!!!】
+ final WxMpTemplateMessage.MiniProgram miniProgram = new WxMpTemplateMessage.MiniProgram();
+ log.info("appId============" + appId);
+ // 小程序的appId
+ miniProgram.setAppid(appId);
+ // 小程序的pagePath 注意,这里是支持传参的!!!
+ miniProgram.setPagePath(path);
+ // 需要跳转首页时,需要设置 usePath = true (默认是 false,只跳转非首页)
+ miniProgram.setUsePath(true);
+ templateMessage.setMiniProgram(miniProgram);
+
+ try {
+ // 发送模板消息
+ wxMpService.getTemplateMsgService().sendTemplateMsg(templateMessage);
+ } catch (Exception e) {
+ log.error("微信公众号模板消息推送失败,接收userId: " + openId, e);
+ }
+
+ }
+ }
+
+}
diff --git a/hiver-modules/hiver-mall/src/main/resources/mapper/MallOrderMapper.xml b/hiver-modules/hiver-mall/src/main/resources/mapper/MallOrderMapper.xml
index 60d16ed8..cb116a26 100644
--- a/hiver-modules/hiver-mall/src/main/resources/mapper/MallOrderMapper.xml
+++ b/hiver-modules/hiver-mall/src/main/resources/mapper/MallOrderMapper.xml
@@ -195,4 +195,8 @@
+
+