Browse Source

对接拼团数据1

master
wangfukang 3 days ago
parent
commit
998d9f6438
  1. 5
      hiver-admin/pom.xml
  2. 37
      hiver-admin/src/main/java/cc/hiver/config/RabbitMqConfig.java
  3. 24
      hiver-admin/src/main/java/cc/hiver/rabbitmq/RabbitMqConsumer.java
  4. 28
      hiver-admin/src/main/java/cc/hiver/rabbitmq/RabbitMqProducer.java
  5. 21
      hiver-admin/src/main/resources/application.yml
  6. 16
      hiver-admin/test-output/test-report.html
  7. 8
      hiver-core/src/main/java/cc/hiver/core/common/constant/SettingConstant.java
  8. 2
      hiver-core/src/main/java/cc/hiver/core/service/JPushService.java
  9. 4
      hiver-core/src/main/java/cc/hiver/core/serviceimpl/JPushServiceImpl.java
  10. 21
      hiver-modules/hiver-base/src/main/java/cc/hiver/base/controller/manage/UserController.java
  11. 9
      hiver-modules/hiver-mall/pom.xml
  12. 30
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/AdminSettlementController.java
  13. 1
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/MallDeliveryOrderController.java
  14. 2
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/PushController.java
  15. 2
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/dao/mapper/MallOrderMapper.java
  16. 157
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/mq/OrderAsyncConsumer.java
  17. 66
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/mq/OrderAsyncProducer.java
  18. 135
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/mq/OrderDelayConsumer.java
  19. 104
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/mq/OrderQueueConfig.java
  20. 63
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/mq/SettlementConfirmConsumer.java
  21. 35
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/mq/SettlementMqConfig.java
  22. 2
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/pojo/dto/CreateOrderDTO.java
  23. 2
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/service/mybatis/MallOrderService.java
  24. 4
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/SendMessageServiceImpl.java
  25. 62
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/MallDeliveryOrderServiceImpl.java
  26. 28
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/MallOrderGroupServiceImpl.java
  27. 98
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/MallOrderServiceImpl.java
  28. 73
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/utils/WechatSendMessageUtil.java
  29. 4
      hiver-modules/hiver-mall/src/main/resources/mapper/MallOrderMapper.xml

5
hiver-admin/pom.xml

@ -71,6 +71,11 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- RabbitMQ -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
</dependencies>
<build>

37
hiver-admin/src/main/java/cc/hiver/config/RabbitMqConfig.java

@ -0,0 +1,37 @@
package cc.hiver.config;
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;
/**
* RabbitMQ 配置类
*/
@Configuration
public class RabbitMqConfig {
public static final String EXCHANGE_NAME = "hiver.direct.exchange";
public static final String QUEUE_NAME = "hiver.demo.queue";
public static final String ROUTING_KEY = "hiver.demo.routing.key";
// 1. 定义Direct交换机
@Bean
public DirectExchange directExchange() {
return new DirectExchange(EXCHANGE_NAME, true, false);
}
// 2. 定义队列 (durable=true 持久化)
@Bean
public Queue demoQueue() {
return new Queue(QUEUE_NAME, true);
}
// 3. 将队列绑定到交换机
@Bean
public Binding bindingDemoQueue() {
return BindingBuilder.bind(demoQueue()).to(directExchange()).with(ROUTING_KEY);
}
}

24
hiver-admin/src/main/java/cc/hiver/rabbitmq/RabbitMqConsumer.java

@ -0,0 +1,24 @@
package cc.hiver.rabbitmq;
import cc.hiver.config.RabbitMqConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* RabbitMQ 消费者示例
*/
@Slf4j
@Component
public class RabbitMqConsumer {
/**
* 监听指定的队列
* @param message 消息内容
*/
@RabbitListener(queues = RabbitMqConfig.QUEUE_NAME)
public void receiveMessage(String message) {
log.info("【RabbitMQ消费消息】收到来自队列 {} 的消息: {}", RabbitMqConfig.QUEUE_NAME, message);
// 此处编写业务处理逻辑
}
}

28
hiver-admin/src/main/java/cc/hiver/rabbitmq/RabbitMqProducer.java

@ -0,0 +1,28 @@
package cc.hiver.rabbitmq;
import cc.hiver.config.RabbitMqConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* RabbitMQ 生产者示例
*/
@Slf4j
@Component
public class RabbitMqProducer {
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* 发送消息
* @param message 消息内容
*/
public void sendMessage(String message) {
log.info("【RabbitMQ生产消息】试图发送消息: {}", message);
rabbitTemplate.convertAndSend(RabbitMqConfig.EXCHANGE_NAME, RabbitMqConfig.ROUTING_KEY, message);
log.info("【RabbitMQ生产消息】发送成功");
}
}

21
hiver-admin/src/main/resources/application.yml

@ -85,6 +85,27 @@ spring:
rest:
# 要连接的ES客户端Rest Uri 多个逗号分隔
uris: http://localhost:9200
# RabbitMQ配置
rabbitmq:
host: 8.140.253.224
port: 5672
username: root
password: ziyi123QQ
virtual-host: /
# 连接超时,单位毫秒
connection-timeout: 15S
listener:
simple:
# 手动确认消息
acknowledge-mode: manual
# 开启重试
retry:
enabled: true
max-attempts: 3
template:
retry:
enabled: true
max-attempts: 3
# 定时任务
quartz:
# 任务信息存储至数据库

16
hiver-admin/test-output/test-report.html

@ -35,7 +35,7 @@
<a href="#"><span class="badge badge-primary">Hiver</span></a>
</li>
<li class="m-r-10">
<a href="#"><span class="badge badge-primary">三月 22, 2026 16:23:04</span></a>
<a href="#"><span class="badge badge-primary">四月 18, 2026 14:58:33</span></a>
</li>
</ul>
</div>
@ -84,7 +84,7 @@
<div class="test-detail">
<span class="meta text-white badge badge-sm"></span>
<p class="name">passTest</p>
<p class="text-sm"><span>16:23:04 下午</span> / <span>0.019 secs</span></p>
<p class="text-sm"><span>14:58:34 下午</span> / <span>0.017 secs</span></p>
</div>
<div class="test-contents d-none">
<div class="detail-head">
@ -92,9 +92,9 @@
<div class="info">
<div class='float-right'><span class='badge badge-default'>#test-id=1</span></div>
<h5 class="test-status text-pass">passTest</h5>
<span class='badge badge-success'>03.22.2026 16:23:04</span>
<span class='badge badge-danger'>03.22.2026 16:23:04</span>
<span class='badge badge-default'>0.019 secs</span>
<span class='badge badge-success'>04.18.2026 14:58:34</span>
<span class='badge badge-danger'>04.18.2026 14:58:34</span>
<span class='badge badge-default'>0.017 secs</span>
</div>
<div class="m-t-10 m-l-5"></div>
</div>
@ -104,7 +104,7 @@
<tbody>
<tr class="event-row">
<td><span class="badge log pass-bg">Pass</span></td>
<td>16:23:04</td>
<td>14:58:34</td>
<td>
Test passed
</td>
@ -128,13 +128,13 @@
<div class="col-md-3">
<div class="card"><div class="card-body">
<p class="m-b-0">Started</p>
<h3>三月 22, 2026 16:23:04</h3>
<h3>四月 18, 2026 14:58:33</h3>
</div></div>
</div>
<div class="col-md-3">
<div class="card"><div class="card-body">
<p class="m-b-0">Ended</p>
<h3>三月 22, 2026 16:23:04</h3>
<h3>四月 18, 2026 14:58:34</h3>
</div></div>
</div>
<div class="col-md-3">

8
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
}
/**

2
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);
}

4
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();

21
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,13 +674,25 @@ public class UserController {
List<Shop> shop = shopService.getShopByUserid(wechatUser.getId());
if(shop != null){
List<String> shopIds = shop.stream().map(Shop::getId).collect(Collectors.toList());
List<ShopTakeaway> 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<WorkerRelaPrice> workerRelaPriceList = workerRelaPriceService.selectByWorkerId(worker.getWorkerId());
List<WorkerRelaPrice> workerRelaPriceListWaimai = new ArrayList<>();
List<WorkerRelaPrice> workerRelaPriceListKuaidi = new ArrayList<>();
if(workerRelaPriceList != null && workerRelaPriceList.size() > 0){
workerRelaPriceList.forEach(workerRelaPrice -> {
if(workerRelaPrice.getGetPushOrder() == 1){
if(workerRelaPrice.getOrderType() == 0 ){
@ -684,6 +702,7 @@ public class UserController {
}
}
});
}
if(workerRelaPriceListWaimai.size() > 0){
resultMap.put("waimaiData", workerRelaPriceListWaimai);
}

9
hiver-modules/hiver-mall/pom.xml

@ -34,6 +34,15 @@
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version>
</dependency>
</dependencies>
</project>

30
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<MallSettlementRecord> 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<MallSettlementRecord> list = mallSettlementRecordService.list(qw);
if (list == null || list.isEmpty()) {
return ResultUtil.success("没有需要确认的结算记录");
}
List<String> 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());
}
}
}

1
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);

2
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!";
}
}

2
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/dao/mapper/MallOrderMapper.java

@ -32,4 +32,6 @@ public interface MallOrderMapper extends BaseMapper<MallOrder> {
Integer selectPendingBadReviewCount(@Param("shopId") String shopId);
Integer selectRefundCount(@Param("shopId") String shopId);
List<String> getWeChatId();
}

157
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<MallDeliveryOrder> qw = new LambdaQueryWrapper<>();
qw.eq(MallDeliveryOrder::getOrderId, orderId);
MallDeliveryOrder delivery = mallDeliveryOrderMapper.selectOne(qw);
if (delivery == null) {
return;
}
List<String> 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<WxMpTemplateData> 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<String> 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<CreateOrderDTO.OrderItemDTO> 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);
}
}
}

66
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<String, Object> 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<String, Object> 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<String, Object> 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);
}
}

135
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<MallDeliveryOrder> 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<MallDeliveryOrder> qw = new LambdaQueryWrapper<>();
qw.eq(MallDeliveryOrder::getOrderId, orderId);
return mallDeliveryOrderMapper.selectOne(qw);
}
}

104
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<String, Object> 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);
}
}

63
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<MallSettlementRecord> 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<MallSettlementRecord> list = mallSettlementRecordService.list(qw);
if (list == null || list.isEmpty()) {
log.info("【MQ结算消费者】没有需要确认的结算记录,终止处理");
return;
}
List<String> 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); // 抛出异常以触发重试或放入死信队列
}
}
}

35
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);
}
}

2
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)

2
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/service/mybatis/MallOrderService.java

@ -70,6 +70,8 @@ public interface MallOrderService extends IService<MallOrder> {
*/
void userRequireMake(String orderId);
void updateSaleCacheIncrementalViaMq(String shopId, String regionId, java.util.List<cc.hiver.mall.pojo.dto.CreateOrderDTO.OrderItemDTO> items, Integer shopSaleCount);
/**
* 商家拒绝退款
*/

4
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

62
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<MallDeliveryOrderM
@Lazy
private MallOrderService mallOrderService;
@Autowired
JPushServiceImpl jPushService;
@Autowired
private OrderAsyncProducer orderAsyncProducer;
@Autowired
private SmsUtil smsUtil;
@Autowired
private ShopTakeawayMapper shopTakeawayMapper;
@Autowired
private ShopService shopService;
@Autowired
private MallOrderGroupMapper mallOrderGroupMapper;
@ -72,7 +94,7 @@ public class MallDeliveryOrderServiceImpl extends ServiceImpl<MallDeliveryOrderM
private WorkerServiceImpl workerServiceImpl;
@Autowired
private cc.hiver.mall.service.mybatis.MallUserCouponService mallUserCouponService;
private MallUserCouponService mallUserCouponService;
@Override
public IPage<MallDeliveryOrder> pageDelivery(MallDeliveryOrderPageQuery q) {
@ -119,7 +141,7 @@ public class MallDeliveryOrderServiceImpl extends ServiceImpl<MallDeliveryOrderM
if (StringUtils.isNotBlank(delivery.getWorkerId()) && !delivery.getWorkerId().equals(workerId)) {
return 2;//throw new RuntimeException("该单已指定其他配送员,无法抢单");
}
List<String> orderIdsSend = new ArrayList<>();
// 更新配送单
Date createTime = new Timestamp(System.currentTimeMillis());
// 1. 获取当前时间 (LocalDateTime)
@ -136,6 +158,7 @@ public class MallDeliveryOrderServiceImpl extends ServiceImpl<MallDeliveryOrderM
.set(MallDeliveryOrder::getMustFinishTime, mustFinishTime);
MallOrder order = mallOrderService.getById(delivery.getOrderId());
Shop shop = shopService.findById(order.getShopId());
String numberCode = "";
//需要查询该group的所有关联订单,如果是面对面拼团需要更新订单序号
if(delivery.getGroupId() != null){
@ -144,6 +167,7 @@ public class MallDeliveryOrderServiceImpl extends ServiceImpl<MallDeliveryOrderM
MallOrderGroup group = mallOrderGroupMapper.selectOne(gq);
if(group.getIsFace() == 1){
List<String> 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<MallDeliveryOrderM
oUw.eq(MallOrder::getId, orderId)
.set(MallOrder::getNumberCode, latestSeq).set(MallOrder::getStatus, 3);
mallOrderService.update(oUw);
jPushService.sendPushNotification(shop.getClientId(), "您有一笔新的订单",orderId);
numberCode+=latestSeq+"+";
}
}
@ -159,12 +184,14 @@ public class MallDeliveryOrderServiceImpl extends ServiceImpl<MallDeliveryOrderM
// 同步更新关联订单状态:待配送员接单 -> 配送中(或待取货)
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<MallOrder> 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<MallDeliveryOrderM
// 同步更新关联订单状态:待配送员接单 -> 配送中(或待取货)
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<MallOrder> 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<MallDeliveryOrderM
//更新订单编号
uw.set(MallDeliveryOrder::getNumberCode, numberCode);
this.update(uw);
triggerShopCookTimeoutEvent(orderIdsSend);
log.info("配送员 {} 接单成功,deliveryId={}", workerId, deliveryId);
return 3;
}
//设置商家出餐超时提醒
private void triggerShopCookTimeoutEvent(List<String> orderId) {
orderId.forEach(id -> {
MallOrder order = mallOrderService.getById(id);
if (order != null) {
long cookTimeMins = 10; // default
try {
LambdaQueryWrapper<cc.hiver.mall.entity.ShopTakeaway> 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<MallDeliveryOrderM
@Override
public void rejectDelivery(String deliveryId) {
MallDeliveryOrder delivery = this.getById(deliveryId);
if (delivery == null) throw new RuntimeException("配送单不存在");
LambdaUpdateWrapper<MallDeliveryOrder> 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());
}
// ================================================================

28
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/MallOrderGroupServiceImpl.java

@ -51,6 +51,9 @@ public class MallOrderGroupServiceImpl extends ServiceImpl<MallOrderGroupMapper,
@Lazy
private MallOrderService mallOrderService;
@Autowired
private cc.hiver.mall.mq.OrderAsyncProducer orderAsyncProducer;
@Autowired
private MallUserCouponService mallUserCouponService;
@ -138,12 +141,12 @@ public class MallOrderGroupServiceImpl extends ServiceImpl<MallOrderGroupMapper,
// 面对面团参团人共享团长配送单,因此参团人本身没有配送单,直接跳过
continue;
}
// 取出预创建的运单进行校验
LambdaQueryWrapper<MallDeliveryOrder> 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<MallOrderGroupMapper,
log.info("拼团 {} 成团成功,激活 {} 条子订单", groupId, waitingOrders.size());
}
/**
* 触发配送订单异步事件微信推给骑手加入超时检查死信队列等
*/
private void triggerDeliveryAsyncEvents(String orderId,MallDeliveryOrder delivery) {
// 1. 发送微信通知(交由MQ)
orderAsyncProducer.sendWechatNotify(orderId);
// 配送单判断是否指定
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);
}
}
}
/**
* 拼团过期所有子订单取消并生成退款记录
*/

98
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/MallOrderServiceImpl.java

@ -2,14 +2,17 @@ package cc.hiver.mall.serviceimpl.mybatis;
import cc.hiver.core.common.redis.RedisTemplateHelper;
import cc.hiver.core.common.utils.SnowFlakeUtil;
import cc.hiver.core.serviceimpl.JPushServiceImpl;
import cc.hiver.mall.dao.mapper.*;
import cc.hiver.mall.entity.*;
import cc.hiver.mall.mq.OrderAsyncProducer;
import cc.hiver.mall.pojo.dto.CreateOrderDTO;
import cc.hiver.mall.pojo.dto.ShopCacheDTO;
import cc.hiver.mall.pojo.query.CommentQuery;
import cc.hiver.mall.pojo.query.MallOrderPageQuery;
import cc.hiver.mall.pojo.vo.MallOrderVO;
import cc.hiver.mall.service.CommentService;
import cc.hiver.mall.service.ShopService;
import cc.hiver.mall.service.mybatis.MallOrderGroupService;
import cc.hiver.mall.service.mybatis.MallOrderService;
import cc.hiver.mall.utils.MerchantOrderSeqUtil;
@ -80,6 +83,9 @@ public class MallOrderServiceImpl extends ServiceImpl<MallOrderMapper, MallOrder
@Autowired
private MallOrderGoodsMapper mallOrderGoodsMapper;
@Autowired
JPushServiceImpl jPushService;
@Autowired
private MallOrderGroupMapper mallOrderGroupMapper;
@ -116,12 +122,18 @@ public class MallOrderServiceImpl extends ServiceImpl<MallOrderMapper, MallOrder
@Autowired
private cc.hiver.mall.dao.mapper.ShopMapper shopMapper;
@Autowired
private ShopService shopService;
@Autowired
private cc.hiver.mall.service.mybatis.MallUserCouponService mallUserCouponService;
@Autowired
private RedisTemplateHelper redisTemplateHelper;
@Autowired
private OrderAsyncProducer orderAsyncProducer;
// ================================================================
// 核心下单逻辑
// ================================================================
@ -183,11 +195,16 @@ public class MallOrderServiceImpl extends ServiceImpl<MallOrderMapper, MallOrder
shopMapper.update(null,new UpdateWrapper<Shop>().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<String, Object> 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<MallOrderMapper, MallOrder
duw.eq(MallDeliveryOrder::getOrderId, orderId)
.set(MallDeliveryOrder::getStatus, 0).set(MallDeliveryOrder::getCreateTime, createTime).set(MallDeliveryOrder::getMustFinishTime, mustFinishTime);
mallDeliveryOrderMapper.update(null, duw);
// 触发异步推送和延迟监听流程
triggerDeliveryAsyncEvents(orderId);
} else {
String latestSeq = merchantOrderSeqUtil.generateOrderSequence(order.getShopId(), 2);
LambdaUpdateWrapper<MallOrder> uw = new LambdaUpdateWrapper<>();
@ -523,6 +543,54 @@ public class MallOrderServiceImpl extends ServiceImpl<MallOrderMapper, MallOrder
}
}
/**
* 触发配送订单异步事件微信推给骑手加入超时检查死信队列等
*/
private void triggerDeliveryAsyncEvents(String orderId) {
// 1. 发送微信通知(交由MQ)
orderAsyncProducer.sendWechatNotify(orderId);
// 获取配送单以判断是否指定
LambdaQueryWrapper<MallDeliveryOrder> 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<cc.hiver.mall.entity.ShopTakeaway> qw = new LambdaQueryWrapper<>();
qw.eq(cc.hiver.mall.entity.ShopTakeaway::getShopId, order.getShopId());
List<cc.hiver.mall.entity.ShopTakeaway> 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<MallOrderMapper, MallOrder
this.update(uw);
}
//自取订单用户点击立即出餐
@Override
public void userRequireMake(String orderId) {
MallOrder order = this.getById(orderId);
LambdaUpdateWrapper<MallOrder> 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<MallOrderMapper, MallOrder
// 指定配送员
if (dto.getWorkerParam() != null && StringUtils.isNotBlank(dto.getWorkerParam().getWorkerId())) {
delivery.setWorkerId(dto.getWorkerParam().getWorkerId());
delivery.setWorkerPhone(dto.getWorkerParam().getWorkerPhone());
delivery.setWorkerName(dto.getWorkerParam().getWorkerName());
if (order.getOrderType() == ORDER_TYPE_NORMAL) {
BigDecimal bonus = order.getGoodsAmount()
@ -1376,6 +1451,8 @@ public class MallOrderServiceImpl extends ServiceImpl<MallOrderMapper, MallOrder
.subtract(record.getRefundAmount().multiply(ratio)).setScale(2, RoundingMode.HALF_UP));
}
mallRefundRecordMapper.insert(record);
//发送商家通知
jPushService.sendPushNotification(shopService.findById(order.getShopId()).getClientId(), "您有一笔订单申请退款",order.getId());
}else if(refundTypeStatus == 2){
record.setLinkId(workerId);
if(ratio != null){
@ -1383,6 +1460,8 @@ public class MallOrderServiceImpl extends ServiceImpl<MallOrderMapper, MallOrder
.subtract(record.getRefundAmount().multiply(ratio)).setScale(2, RoundingMode.HALF_UP));
}
mallRefundRecordMapper.insert(record);
//发送商家通知
jPushService.sendPushNotification(shopService.findById(order.getShopId()).getClientId(), "您有一笔订单申请退款,配送员原因",order.getId());
}else if(refundTypeStatus == 3){
//商家、配送员都加一条
record.setLinkId(order.getShopId());
@ -1392,6 +1471,8 @@ public class MallOrderServiceImpl extends ServiceImpl<MallOrderMapper, MallOrder
.subtract(record.getRefundAmount().multiply(ratio)).setScale(2, RoundingMode.HALF_UP));
}
mallRefundRecordMapper.insert(record);
//发送商家通知
jPushService.sendPushNotification(shopService.findById(order.getShopId()).getClientId(), "您有一笔订单申请退款",order.getId());
MallRefundRecord mallRefundRecord1 = record;
mallRefundRecord1.setId(SnowFlakeUtil.nextId().toString());
mallRefundRecord1.setLinkId(workerId);
@ -1403,6 +1484,7 @@ public class MallOrderServiceImpl extends ServiceImpl<MallOrderMapper, MallOrder
mallRefundRecordMapper.insert(mallRefundRecord1);
}
}
List<MallOrderGoods> goodsList = mallOrderGoodsMapper.selectByOrderId(order.getId());
if (goodsList == null || goodsList.isEmpty()) return;
List<MallReturnOrderGoods> returnList = goodsList.stream()
@ -1585,4 +1667,14 @@ public class MallOrderServiceImpl extends ServiceImpl<MallOrderMapper, MallOrder
}
}
}
@Override
public void updateSaleCacheIncrementalViaMq(String shopId, String regionId, List<CreateOrderDTO.OrderItemDTO> items, Integer shopSaleCount) {
// 反查 productMap
List<String> productIds = items.stream().map(CreateOrderDTO.OrderItemDTO::getProductId).collect(Collectors.toList());
List<Product> productList = productMapper.selectBatchIds(productIds);
Map<String, Product> productMap = productList.stream().collect(Collectors.toMap(Product::getId, p -> p));
updateSaleCacheIncremental(shopId, regionId, items, productMap, shopSaleCount);
}
}

73
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<String> officialAccountOpenids,String templateId, List<WxMpTemplateData> 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<WxMpTemplateData> 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);
}
}
}
}

4
hiver-modules/hiver-mall/src/main/resources/mapper/MallOrderMapper.xml

@ -195,4 +195,8 @@
</where>
</select>
<select id="getWeChatId" resultType="java.lang.String">
select u.official_account_openid from t_worker w LEFT JOIN t_user u on w.user_id = u.id where u.official_account_openid is not null
</select>
</mapper>

Loading…
Cancel
Save