1. Day14 订单处理
2. 超时未支付订单处理
超过限定时间并未支付的订单,我们需要进行超时订单的处理:先调用微信支付api,查询该订单的支付状态。如果未支付调用关闭订单的api,并修改订单状态为已关闭,并回滚库存数。如果该订单已经支付,则做补偿操作(修改订单状态和记录)。
2.1. 延迟消息队列(死信队列)
延迟消息队列,就是消息的生产者发送的消息并不会立刻被消费,而是在设定的时间之后才可以消费。
我们可以在订单创建时发送一个延迟消息,消息为订单号,系统会在限定时间之后取出这个消息,然后查询订单的支付状态,根据结果做出相应的处理。
rabbitmq中并没有提供延迟队列功能 但是我们可以通过使用 TTL+死信队列 组合实现延迟队列的效果
2.1.1. 消息的TTL(Time To Live)
消息的TTL就是消息的存活时间。RabbitMQ可以对队列和消息分别设置TTL。对队列设置就是队列没有消费者连着的保留时间,也可以对每一个单独的消息做单独的设置。超过了这个时间,我们认为这个消息就死了,称之为死信。
2.1.2. 死信交换器 Dead Letter Exchanges
一个消息在满足如下条件下,会进死信交换机,记住这里是交换机而不是队列,一个交换机可以对应很多队列。
(1) 一个消息被Consumer拒收了,并且reject方法的参数里requeue是false(不把消息重新放入原目标队列.requeue=false)。也就是说不会被再次放在队列里,被其他消费者使用。
(2)上面的消息的TTL到了,消息过期了。
(3)队列的长度限制满了。排在前面的消息会被丢弃或者扔到死信交换机上。
2.1.3. 创建死信队列
进入MQ web管理页面http://192.168.130.128:15672/
创建死信交换器
exchange.ordertimeout(fanout)
创建队列
queue.ordertimeout
建立死信交换器
exchange.ordertimeout与队列queue.ordertimeout之间的绑定
创建队列
queue.ordercreate,Arguments添加
`x-message-ttl`=`10000``x-dead-letter-exchange`: `exchange.ordertimeout`
- 测试:向queue.ordercreate队列添加消息,等待10秒后消息从queue.ordercreate队列消失
2.2. 代码实现

2.2.1. 微信支付-查询订单
在changgou_service_pay的pay下controller包的WxPayController类添加查询订单方法
//基于微信支付查询订单@GetMapping("/query/{orderId}")public Result queryOrder(@PathVariable("orderId") String orderId) {Map map = wxPayService.queryOrder(orderId);return new Result(true, StatusCode.OK, "查询订单成功", map);}
将查询订单方法暴露给feign
在changgou_service_pay_api里的feign包的PayFeign添加查询方法
//基于微信支付查询订单@GetMapping("/wxpay/query/{orderId}")public Result queryOrder(@PathVariable("orderId") String orderId);
2.2.2. 微信支付-关闭订单
在changgou_service_pay的pay下service包的WxpayService接口 添加关闭订单方法定义
//基于微信支付关闭订单Map closeOrder(String orderId);
实现方法 调用微信支付sdk中的closeOrder方法
//基于微信支付关闭订单@Overridepublic Map closeOrder(String orderId) {try {Map<String,String> map = new HashMap();map.put("out_trade_no",orderId);Map<String, String> resultMap = wxPay.closeOrder(map);return resultMap;}catch (Exception e){e.printStackTrace();return null;}}
collection层
//基于微信支付关闭订单@PutMapping("/close/{orderID}")public Result closeOrder(@PathVariable("orderId") String orderID){Map map = wxPayService.closeOrder(orderID);return new Result(true,StatusCode.OK,"关闭订单成功",map);}
同样以feign形式声明
//基于微信支付关闭订单@PutMapping("/wxpay/close/{orderID}")public Result closeOrder(@PathVariable("orderId") String orderID);
2.2.3. 回滚库存
在changgou_service_goods包dao层的SkuMapper实体接口 添加回滚库存的自定义sql
//回滚库存(增加库存并扣减销量)@Update("update tb_suk set num=num+#{num},sale_num=sale_num-#{num} where id=#{skuId}")void resumeStockNum(@Param("skuId") String skuId,@Param("num") Integer num);
在SkuService中添加方法声明
//回滚库存void resumeStockNum(String skuId, Integer num);
impl实现方法
//回滚库存@Override@Transactionalpublic void resumeStockNum(String skuId, Integer num) {skuMapper.resumeStockNum(skuId,num);}
collection层
//回滚库存@RequestMapping("/resumeStockNum")public Result resumeStockNum(@RequestParam("skuId") String skuId,@RequestParam("num") Integer num){skuService.resumeStockNum(skuId,num);return new Result(true,StatusCode.OK,"回滚库存成功");}
在changgou_service_goods_api包下声明feign
//回滚库存@RequestMapping("/sku/resumeStockNum")public Result resumeStockNum(@RequestParam("skuId") String skuId,@RequestParam("num") Integer num);
2.2.4. 消息队列发送和接受
在changgou_service_order项目下的service.impl包下的OrderServiceImpl
注入rabbitmq模块
@Autowiredprivate RabbitTemplate rabbitTemplate;
在add方法最后一行添加代码
//发送延时消息rabbitTemplate.convertAndSend("","queue.ordercreate",orderId);

在此项目的listener包添加OrderTimeOutListener
@Componentpublic class OrderTimeOutListener {@Autowiredprivate OrderService orderService;//监听死信队列@RabbitListener(queues = "queue.ordertimeout")public void receiveCloseOrderMessage(String orderId){System.out.println("接受到关闭订单的消息"+orderId);try {//调用业务层完成关闭订单操作orderService.closeOrder(orderId);} catch (Exception e){e.printStackTrace();}}}
在OrderService 声明closeOrder方法
//关闭订单void closeOrder(String oderId);
在changgou_server_order添加 pay的feign依赖导入 在pom.xml添加依赖
<dependency><groupId>com.changgou</groupId><artifactId>changgou_service_pay_api</artifactId><version>1.0-SNAPSHOT</version></dependency>
在启动类中扫描feign包注解添加pay包
@EnableFeignClients(basePackages = {"com.changgou.goods.feign" , "com.changgou.pay.feign"})
impl实现接口声明的方法
@Autowiredprivate PayFeign payFeign;@Override@Transactionalpublic void closeOrder(String orderId) {System.out.println("关闭订单的业务开启" + orderId);Order order = orderMapper.selectByPrimaryKey(orderId);if (order == null) {throw new RuntimeException("订单不存在");}//1.根据订单id查询mysql的订单信息 判断订单是否存在 判断订单的支付状态//判断是否为已支付if (!"0".equals(order.getPayStatus())) {System.out.println("当前订单已支付,无需关闭");return;}System.out.println("关闭订单校验通过" + orderId);//2.基于微信查询订单信息Map wxQueryMap = (Map) payFeign.queryOrder(orderId).getData();System.out.println("查询微信支付订单成功" + wxQueryMap);//如果订单支付状态为已支付,进行数据补偿if ("SUCCESS".equals(wxQueryMap.get("trade_state"))) {String transactionId = String.valueOf(wxQueryMap.get("transaction_id"));this.updatePayStatus(orderId, transactionId); //订单支付成功 进行减少库存 添加销量操作System.out.println("完成数据补偿");}//如果订单支付状态为未支付 则回滚mysql中的订单信息 新增订单日志 恢复商品的库存 基于微信支付关闭订单if ("NOTPAY".equals(wxQueryMap.get("trade_state"))) {System.out.println("执行关闭操作");order.setUpdateTime(new Date());order.setOrderStatus("4"); //订单已关闭orderMapper.updateByPrimaryKeySelective(order); //修改mysql订单信息//订单日志OrderLog orderLog = new OrderLog();orderLog.setId(idWorker.nextId() + "");orderLog.setOperater("system");orderLog.setOperateTime(new Date());orderLog.setOrderStatus("4"); //订单已关闭orderLog.setOrderId(orderId);orderLogMapper.insert(orderLog);//写入日志//恢复商品库存OrderItem _orderItem = new OrderItem();_orderItem.setOrderId(orderId);List<OrderItem> orderItemList = orderItemMapper.select(_orderItem); //根据orderId查询orderItemfor (OrderItem orderItem : orderItemList) {skuFeign.resumeStockNum(orderItem.getSkuId(), orderItem.getNum());}//基于微信关闭订单payFeign.closeOrder(orderId);}}
3. 订单批量发货
在changgou_service_order项目的Service包下的OrderService类添加方法声明
//批量发货void batchSend(List<Order> orders);
OrderServiceImpl实现该方法
@Override@Transactionalpublic void batchSend(List<Order> orders) {//判断每个订单和物流公司的值是否存在for (Order order : orders) {if (order.getId() == null){throw new RuntimeException("订单号不存在")}if (order.getShippingCode() == null || order.getShippingName() == null) {throw new RuntimeException("请输入快递单号或物流公司名称");}}//进行订单状态的校验for (Order order : orders) {Order order1 = orderMapper.selectByPrimaryKey(order.getId());//发货状态 是否存在if (!"0".equals(order1.getConsignStatus()) || !"1".equals(order1.getConsignStatus())){throw new RuntimeException("订单状态不合法");}}//修改订单状态为已发货for (Order order : orders) {order.setOrderStatus("2"); // 已发货状态order.setConsignStatus("1"); //已发货order.setConsignTime(new Date());order.setUpdateTime(new Date());orderMapper.updateByPrimaryKeySelective(order); //写入数据//记录订单日志OrderLog orderLog =new OrderLog();orderLog.setId(String.valueOf(idWorker.nextId()));orderLog.setOperater("admin");orderLog.setOperateTime(new Date());orderLog.setOrderId(order.getId());orderLog.setOrderStatus("2");orderLog.setConsignStatus("1");orderLogMapper.insert(orderLog); //写入日志}}
OrderController新增方法
@PostMapping("/batchSend")public Result batchSend(@RequestBody List<Order> orders){orderService.batchSend(orders);return new Result(true,StatusCode.OK,"发货成功");}
3.1. 对接第三方物流查询
第三方的物流系统。比较常用的有菜鸟物流、快递鸟等。我们这里推荐使用快递鸟 http://www.kdniao.com
4. 确认收货和自动收货
当物流公司将货物送到了用户收货地址之后,需要用户点击确认收货,当用户点击了确认收货之后,会修改订单状态为已完成
4.1. 手动确认收货
在changgou_service_order项目的Service包下的OrderService类添加方法声明
//手动确认收货void confirmTask(String orderId,String operator);
impl实现
//手动确认收货@Overridepublic void confirmTask(String orderId, String operator) {Order order = orderMapper.selectByPrimaryKey(orderId);if (order == null) {throw new RuntimeException("订单不存在");}if (!"1".equals(order.getConsignStatus())){throw new RuntimeException("订单未发货");}order.setConsignStatus("2"); //已送达order.setOrderStatus("3"); //已完成order.setUpdateTime(new Date());order.setEndTime(new Date());orderMapper.updateByPrimaryKeySelective(order);//订单日志OrderLog orderLog =new OrderLog();orderLog.setId(String.valueOf(idWorker.nextId()));orderLog.setOperater(operator);orderLog.setOperateTime(new Date());orderLog.setOrderId(order.getId());orderLog.setOrderStatus("3"); //订单已完成orderLog.setConsignStatus("2"); //订单已送达orderLogMapper.insert(orderLog); //写入日志}
4.2. 自动收货
如果用户在15天(可以在订单配置表中配置)没有确认收货,系统将自动收货。如何实现?我们这里采用定时任务springTask来实现.
4.2.1. Cron表达式
Cron表达式是一个字符串,字符串分为七个部分,每一个域代表一个含义。
Cron表达式7个域格式为: 秒 分 小时 日 月 星期几 年
Cron表达式6个域格式为: 秒 分 小时 日 月 周
| 序号 | 说明 | 是否必填 | 允许填写的值 | 允许的通配符 |
|---|---|---|---|---|
| 1 | 秒 | 是 | 0-59 | , - * / |
| 2 | 分 | 是 | 0-59 | , - * / |
| 3 | 小时 | 是 | 0-23 | , - * / |
| 4 | 日 | 是 | 1-31 | , - * ? / L W |
| 5 | 月 | 是 | 1-12或JAN-DEC | , - * / |
| 6 | 星期几 | 是 | 1-7或SUN-SAT | , - * ? / L W |
| 7 | 年 | 否 | empty 或1970-2099 | , - * / |
通配符
通配符说明:* 表示所有值. 例如:在分的字段上设置 "*",表示每一分钟都会触发。? 表示不指定值。使用的场景为不需要关心当前设置这个字段的值。例如:要在每月的10号触发一个操作,但不关心是周几,所以需要周位置的那个字段设置为"?" 具体设置为 0 0 0 10 * ?- 表示区间。例如 在小时上设置 "10-12",表示 10,11,12点都会触发。, 表示指定多个值,例如在周字段上设置 "MON,WED,FRI" 表示周一,周三和周五触发/ 用于递增触发。如在秒上面设置"5/15" 表示从5秒开始,每增15秒触发(5,20,35,50)。 在月字段上设置'1/3'所示每月1号开始,每隔三天触发一次。L 表示最后的意思。在日字段设置上,表示当月的最后一天(依据当前月份,如果是二月还会依据是否是润年[leap]), 在周字段上表示星期六,相当于"7"或"SAT"。如果在"L"前加上数字,则表示该数据的最后一个。例如在周字段上设置"6L"这样的格式,则表示“本月最后一个星期五"W 表示离指定日期的最近那个工作日(周一至周五). 例如在日字段上设置"15W",表示离每月15号最近的那个工作日触发。如果15号正好是周六,则找最近的周五(14号)触发, 如果15号是周未,则找最近的下周一(16号)触发.如果15号正好在工作日(周一至周五),则就在该天触发。如果指定格式为 "1W",它则表示每月1号往后最近的工作日触发。如果1号正是周六,则将在3号下周一触发。(注,"W"前只能设置具体的数字,不允许区间"-").# 序号(表示每月的第几个周几),例如在周字段上设置"6#3"表示在每月的第三个周六.注意如果指定"#5",正好第五周没有周六,则不会触发该配置(用在母亲节和父亲节再合适不过了) ;
常用表达式
0 0 10,14,16 * * ? 每天上午10点,下午2点,4点0 0/30 9-17 * * ? 朝九晚五工作时间内每半小时0 0 12 ? * WED 表示每个星期三中午12点"0 0 12 * * ?" 每天中午12点触发"0 15 10 ? * *" 每天上午10:15触发"0 15 10 * * ?" 每天上午10:15触发"0 15 10 * * ? *" 每天上午10:15触发"0 15 10 * * ? 2005" 2005年的每天上午10:15触发"0 * 14 * * ?" 在每天下午2点到下午2:59期间的每1分钟触发"0 0/5 14 * * ?" 在每天下午2点到下午2:55期间的每5分钟触发"0 0/5 14,18 * * ?" 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发"0 0-5 14 * * ?" 在每天下午2点到下午2:05期间的每1分钟触发"0 10,44 14 ? 3 WED" 每年三月的星期三的下午2:10和2:44触发"0 15 10 ? * MON-FRI" 周一至周五的上午10:15触发"0 15 10 15 * ?" 每月15日上午10:15触发"0 15 10 L * ?" 每月最后一日的上午10:15触发"0 15 10 ? * 6L" 每月的最后一个星期五上午10:15触发"0 15 10 ? * 6L 2002-2005" 2002年至2005年的每月的最后一个星期五上午10:15触发"0 15 10 ? * 6#3" 每月的第三个星期五上午10:15触发
2.2. 代码实现
4.2.2.1. 发送消息
创建工程changgou_task,引入依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><dependency><groupId>org.springframework.amqp</groupId><artifactId>spring-rabbit</artifactId></dependency>
创建配置文件
server:port: 9202spring:application:name: taskrabbitmq:host: 192.168.130.128
创建com.changgou包 再创建启动类 TaskApplication
@SpringBootApplication@EnableScheduling //开启定时任务public class TaskApplication {public static void main(String[] args) {SpringApplication.run(TaskApplication.class,args);}}
创建com.changgou.config包 创建RabbitMQConfig类
注意Queue为org.springframework.amqp.core.Queue下的Queue
@Configurationpublic class RabbitMQConfig {public static final String ORDER_TACK="order_tack";@Beanpublic Queue queue(){return new Queue(ORDER_TACK);}}
创建com.changgou.task包 创建OrderTask类
@Configurationpublic class RabbitMQConfig {public static final String ORDER_TACK="order_tack";@Beanpublic Queue queue(){return new Queue(ORDER_TACK);}}
4.2.2.2. 接受消息
在changgou_service_order项目 order.config包 的RabbitMQConfig 添加自动收货消息队列
//自动收货消息队列public static final String ORDER_TACK="order_tack";
@Beanpublic Queue ORDER_TASK() {return new Queue(ORDER_TACK);}
在OrderService添加方法声明
//自动确认收货void autoTack();
impl实现
@Autowiredprivate OrderConfigMapper orderConfigMapper;@Override@Transactionalpublic void autoTack() {//1.从订单配置表获取到订单自动确认的时间点OrderConfig orderConfig = orderConfigMapper.selectByPrimaryKey(1); //目前只有1条写死为获取 id为1的//2.得到当前时间节点向前数 15(默认为) 天 作为过期的时间节点LocalDate now = LocalDate.now();LocalDate date = now.plusDays(-orderConfig.getTakeTimeout());//3.按条件查询 获取订单列表Example example = new Example(Order.class);Example.Criteria criteria = example.createCriteria();criteria.andLessThan("consignTime",date); //发货时间小于过期时间criteria.andEqualTo("orderStatus","2"); //收货状态为未确认List<Order> orderList = orderMapper.selectByExample(example);for (Order order : orderList) {this.confirmTask(order.getId(),"system"); //调用手动确认方法}}
在listener包 创建OrderTackListener 类
@Componentpublic class OrderTackListener {@Autowiredprivate OrderService orderService;@RabbitListener(queues = RabbitMQConfig.ORDER_TACK)public void receiveOrderTaskMessage(String message){System.out.println("收到自动确认收货消息");orderService.autoTack();}}
