订单过期监听和处理
业务需求
有些时候 用户发起订单 但是没有付款 这个时候一般来说 会设置一个订单过期时间 如果订单过期 则需要重新下单
问题来了 如果每过一段很小的时间就去盘一次数据库 那压力也太大了
demo 搭建 用到的 mysql mybatis plus redis rabbit mq
目录结构
基本配置
实体类
pojo.java
package com.example.demo.pojo;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* <p>
*
* </p>
*
* @author chenyaoyan
* @since 2022-07-16
*/
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("t_order")
public class Order implements Serializable {
private static final long serialVersionUID = 1L;
@TableId
private Long id;
private String order_id;
@TableField(fill = FieldFill.INSERT)
private Integer status;
@TableField(fill = FieldFill.INSERT)
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonSerialize(using = LocalDateTimeSerializer.class)
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
private LocalDateTime create_time;
@TableField(fill = FieldFill.INSERT)
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonSerialize(using = LocalDateTimeSerializer.class)
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
private LocalDateTime expired;
}
mapper接口
OrderMapper.java
package com.example.demo.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.demo.pojo.Order;
/**
* <p>
* Mapper 接口
* </p>
*
* @author chenyaoyan
* @since 2022-07-16
*/
public interface OrderMapper extends BaseMapper<Order> {
}
SnowFlake.java
这个是订单生成的一个工具类
package com.example.demo.utils;
/**
* @author ChenYaoYan
* @version 1.0
* @Description TODO
* @date 2022/7/16 14:26
*/
public class SnowFlake {
/**
* 开始时间截 (2018-07-03)
*/
private final long twepoch = 1530607760000L;
/**
* 机器id所占的位数
*/
private final long workerIdBits = 5L;
/**
* 数据标识id所占的位数
*/
private final long datacenterIdBits = 5L;
/**
* 支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数)
*/
private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
/**
* 支持的最大数据标识id,结果是31
*/
private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
/**
* 序列在id中占的位数
*/
private final long sequenceBits = 12L;
/**
* 机器ID向左移12位
*/
private final long workerIdShift = sequenceBits;
/**
* 数据标识id向左移17位(12+5)
*/
private final long datacenterIdShift = sequenceBits + workerIdBits;
/**
* 时间截向左移22位(5+5+12)
*/
private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
/**
* 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095)
*/
private final long sequenceMask = -1L ^ (-1L << sequenceBits);
/**
* 工作机器ID(0~31)
*/
private long workerId;
/**
* 数据中心ID(0~31)
*/
private long datacenterId;
/**
* 毫秒内序列(0~4095)
*/
private long sequence = 0L;
/**
* 上次生成ID的时间截
*/
private long lastTimestamp = -1L;
//==============================Constructors=====================================
/**
* 构造函数
*
* @param workerId 工作ID (0~31)
* @param datacenterId 数据中心ID (0~31)
*/
public SnowFlake(long workerId, long datacenterId) {
if (workerId > maxWorkerId || workerId < 0) {
throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
}
if (datacenterId > maxDatacenterId || datacenterId < 0) {
throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
}
this.workerId = workerId;
this.datacenterId = datacenterId;
}
// ==============================Methods==========================================
/**
* 获得下一个ID (该方法是线程安全的)
*
* @return SnowflakeId
*/
public synchronized long nextId() {
long timestamp = timeGen();
//如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常
if (timestamp < lastTimestamp) {
throw new RuntimeException(
String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
}
//如果是同一时间生成的,则进行毫秒内序列
if (lastTimestamp == timestamp) {
sequence = (sequence + 1) & sequenceMask;
//毫秒内序列溢出
if (sequence == 0) {
//阻塞到下一个毫秒,获得新的时间戳
timestamp = tilNextMillis(lastTimestamp);
}
}
//时间戳改变,毫秒内序列重置
else {
sequence = 0L;
}
//上次生成ID的时间截
lastTimestamp = timestamp;
//移位并通过或运算拼到一起组成64位的ID
return (((timestamp - twepoch) << timestampLeftShift)
| (datacenterId << datacenterIdShift)
| (workerId << workerIdShift)
| sequence);
}
/**
* 阻塞到下一个毫秒,直到获得新的时间戳
*
* @param lastTimestamp 上次生成ID的时间截
* @return 当前时间戳
*/
protected long tilNextMillis(long lastTimestamp) {
long timestamp = timeGen();
while (timestamp <= lastTimestamp) {
timestamp = timeGen();
}
return timestamp;
}
/**
* 返回以毫秒为单位的当前时间
*
* @return 当前时间(毫秒)
*/
protected long timeGen() {
return System.currentTimeMillis();
}
}
TypeUtils.java
将时间戳转成long 比较大小
package com.example.demo.utils;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
/**
* @author ChenYaoYan
* @version 1.0
* @Description TODO
* @date 2022/7/16 16:09
*/
public class TypeUtils {
public static long getTimesatme(LocalDateTime localDateTime) {
return localDateTime.toInstant(ZoneOffset.UTC).toEpochMilli();
}
}
MybatisAutoFillHandler.java
mybatisPlus配置 用来处理自动插入的时候默认值的问题
package com.example.demo.handlers;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.example.demo.common.OrderStatusEnum;
import com.example.demo.pojo.Order;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Date;
/**
* @author 陈垚焱
*/
@Component
public class MybatisAutoFillHandler implements MetaObjectHandler {
@Value("${order.outtime}")
private long timeout;
@Override
public void insertFill(MetaObject metaObject) {
this.strictInsertFill(metaObject, "create_time", LocalDateTime.class, LocalDateTime.now());
//过期时间当前时间+1
this.strictInsertFill(metaObject, "expired", LocalDateTime.class, LocalDateTime.now().plusSeconds(timeout));
this.strictInsertFill(metaObject, "status", Integer.class, OrderStatusEnum.CreateOrder.getStatus());
}
@Override
public void updateFill(MetaObject metaObject) {
}
}
OrderStatusEnum
订单状态枚举类
一般喜欢用枚举 如果麻烦的话 可以用final 定义一个常量也行
package com.example.demo.common;
/**
* @author ChenYaoYan
* @version 1.0
* @Description TODO
* @date 2022/7/16 12:02
*/
public enum OrderStatusEnum {
// 数据操作错误定义
CreateOrder( 0,"创建订单"),
OrderPay( 1,"订单已支付"),
OrderExpired( 2,"订单过期");
/** 错误码 */
private Integer status;
/** 错误描述 */
private String statusMsg;
OrderStatusEnum(Integer status,String statusMsg) {
this.status = status;
this.statusMsg = statusMsg;
}
public Integer getStatus() {
return status;
}
public String getStatusMsg() {
return statusMsg;
}
}
application.yaml
spring:
#数据库配置
datasource:
password: 123456
username: root
url: jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
# Redis配置
redis:
timeout: 10000ms
host: ip地址
port: 端口
password: #数据库密码
database: 0 # 选择哪个库,默认0库
lettuce:
pool:
max-active: 1024 # 最大连接数,默认 8
max-wait: 10000ms # 最大连接阻塞等待时间,单位毫秒,默认 -1
max-idle: 200 # 最大空闲连接,默认 8
min-idle: 5
#rabbitmq配置
rabbitmq:
host: 你的ip地址
port: 5672
password: 密码
username: 用户名
virtualHost: /
# # 消息确认回调
# publisher-confirm-type: correlated
# # 消息失败回调
# publisher-returns: true
#mybatis-plus配置
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
map-underscore-to-camel-case: false
mapper-locations: classpath:mapper/*.xml
typeAliasesPackage: com.example.demo.pojo
global-config:
db-config:
id-type: ASSIGN_ID
# logic-delete-field: isDelete # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
# logic-delete-value: 1 # 逻辑已删除值(默认为 1)
# logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
mybatis:
table:
auto: update
#create 系统启动后,会将所有的表删除掉,然后根据model中配置的结构重新建表,该操作会破坏原有数据。
#update 系统会自动判断哪些表是新建的,哪些字段要修改类型等,哪些字段要删除,哪些字段要新增,该操作不会破坏原有数据。
#none 系统不做任何处理。
#add 新增表/新增字段/新增索引/新增唯一约束的功能,不做做修改和删除 (只在版本1.0.9.RELEASE及以上支持)。
model:
pack: com.server.pojo.entity #扫描用于创建表的对象的包名,多个包用“,”隔开
database:
type: mysql #数据库类型 目前只支持mysql
#basePath: http://localhost:8080/
server:
port: 8080
order:
#订单过期时间 单位s
outtime: 120
orderKey: orderId
处理方法
轮询
开一个定时任务,每过一段时间就去盘一次数据库 这太耗资源和性能了 而且误差的时间也比较大
这种不建议
如果使用定时任务的话 记得要加一个@EnableScheduling注解
package com.example.demo.task;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.example.demo.common.OrderStatusEnum;
import com.example.demo.pojo.Order;
import com.example.demo.service.IOrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
/**
* @author ChenYaoYan
* @version 1.0
* @Description TODO
* @date 2022/7/16 15:38
*/
@Component
@EnableScheduling
public class OrderExpiredTask {
@Autowired
private IOrderService orderService;
@Scheduled(cron = "0/20 * * * * ?")
public void lunxunTask(){
//找到超时的订单
orderService.update(new UpdateWrapper<Order>().set("status", OrderStatusEnum.OrderExpired.getStatus()).eq("status",OrderStatusEnum.CreateOrder.getStatus()).le("expired", LocalDateTime.now()));
}
}
判断处理
要前端发请求,后端给前端一个过期时间,然后前端根据过期时间展示,然后时间到向后端发请求
再者就是后端写接口的时候先判断 然后进行不同的操作
controller
//下单
@GetMapping("/placeOrderNoRedis")
public void placeOrderNoRedis(){
orderService.placeOrderNoRedis();
}
@GetMapping("/payOrder2")
public void payOrder2(String orderId){
System.out.println(orderId);
Order order = orderService.getOne(new QueryWrapper<Order>().eq("order_id", orderId));
if (order==null){
System.out.println("订单不存在");
}else if(TypeUtils.getTimesatme(order.getExpired())<=TypeUtils.getTimesatme(LocalDateTime.now())){
order.setStatus(OrderStatusEnum.OrderExpired.getStatus());
orderService.save(order);
System.out.println("订单已过期");
}else if (order.getStatus()==OrderStatusEnum.OrderPay.getStatus()){
System.out.println("订单已支付,无需再付");
}else {
order.setStatus(OrderStatusEnum.OrderPay.getStatus());
orderService.save(order);
redisTemplate.delete(orderKey + ":" + order.getOrder_id());
System.out.println("支付成功");
}
}
service
/**
* 用户下单
*/
void placeOrderNoRedis();
serviceImpl
@Override
public void placeOrderNoRedis() {
SnowFlake idWorker = new SnowFlake(0, 0);
Order order = new Order();
order.setOrder_id(Long.toString(idWorker.nextId()));
this.save(order);
System.out.println("订单已生成,订单号为:"+order.getOrder_id()+"请点击改连接完成支付: "+"http://localhost:8080/payOrder2?orderId="+order.getOrder_id());
}
redis
利用key的失效时间 然后去监听失效的key 这个办法只是一种解决思路 不建议 因为不稳定 万一服务器宕机或者重启什么的 中间会有大量的key丢失
redis配置
RedisConfig
解决序列化问题 和开启监听
package com.example.demo.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* @author ChenYaoYan
* @version 1.0
* @date 2022/7/16 15:35
*/
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
redisTemplate.setConnectionFactory(redisConnectionFactory);
return redisTemplate;
}
@Bean
RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
return container;
}
}
RedisKeyExpirationListener
处理redis Key失效
package com.example.demo.listener;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.example.demo.common.OrderStatusEnum;
import com.example.demo.pojo.Order;
import com.example.demo.service.IOrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.listener.KeyExpirationEventMessageListener;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.stereotype.Component;
/**
* @author ChenYaoYan
* @version 1.0
* @Description TODO
* @date 2022/7/16 16:48
*/
@Component
public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {
@Autowired
private IOrderService orderService;
@Value("${order.orderKey}")
private String orderKey;
public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) {
super(listenerContainer);
}
/**
* 针对redis数据失效事件,进行数据处理
* @param message
* @param pattern
*/
@Override
public void onMessage(Message message, byte[] pattern) {
// 用户做自己的业务处理即可,注意message.toString()可以获取失效的key
String expiredKey = message.toString();
if (expiredKey.contains(":")){
String[] key = expiredKey.split(":");
if (key[0].equals(orderKey)){
orderService.update(new UpdateWrapper<Order>().eq("status",OrderStatusEnum.CreateOrder.getStatus()).set("status", OrderStatusEnum.OrderExpired.getStatus()).eq("order_id",key[1]));
}
}
}
}
controller
@GetMapping("/placeOrder")
public void placeOrder(){
orderService.placeOrder();
}
@GetMapping("/payOrder")
public void payOrder(String orderId){
System.out.println(orderId);
Order order = orderService.getOne(new QueryWrapper<Order>().eq("order_id", orderId));
System.out.println(order);
if (order==null||order.getStatus()== OrderStatusEnum.OrderExpired.getStatus()){
System.out.println("订单不存在或者已过期");
}else if (order.getStatus()==OrderStatusEnum.OrderPay.getStatus()){
System.out.println("订单已支付,无需再付");
}else {
order.setStatus(OrderStatusEnum.OrderPay.getStatus());
orderService.save(order);
redisTemplate.delete(orderKey + ":" + order.getOrder_id());
System.out.println("支付成功");
}
service
/**
* 用户下单
*/
void placeOrder();
serviceImpl
@Override
public void placeOrder() {
SnowFlake idWorker = new SnowFlake(0, 0);
Order order = new Order();
order.setOrder_id(Long.toString(idWorker.nextId()));
this.save(order);
redisTemplate.opsForValue().set(orderKey+":"+order.getOrder_id(),order.getOrder_id(),timeout, TimeUnit.SECONDS);
System.out.println("订单已生成,订单号为:"+order.getOrder_id()+"请点击改连接完成支付: "+"http://localhost:8080/payOrder?orderId="+order.getOrder_id());
}
rabbit mq
这里使用的是死信队列 原理跟redis失效key一样
RabbitMQConfig
package com.example.demo.config;
import com.example.demo.common.MailConstants;
import org.springframework.amqp.core.*;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
import static com.example.demo.common.MailConstants.QUEUE_CHECK_ORDER;
/**
* @author ChenYaoYan
* @version 1.0
* @Description rabbitMQ配置类
* @date 2022/7/16 20:56
*/
@Configuration
public class RabbitMQConfig {
@Value("${order.outtime}")
private long timeout;
//定义交换机
@Bean
public Exchange exchangeDelay(){
return ExchangeBuilder.directExchange(MailConstants.EXCHNAGE_DELAY).durable(true).build();
}
//检测订单
@Bean(QUEUE_CHECK_ORDER)
public Queue queueCheckOrder(){
Map<String,Object> map = new HashMap<>();
//过期的消息给哪个交换机的名字
map.put("x-dead-letter-exchange", MailConstants.EXCHNAGE_DELAY);
//设置死信交换机把过期的消息给哪个路由键接收
map.put("x-dead-letter-routing-key", MailConstants.ROUTINGKEY_QUEUE_DELAY);
//队列消息过期时间10s
map.put("x-message-ttl", timeout*1000);
return new Queue(QUEUE_CHECK_ORDER,true,false,false,map);
}
//死信队列
@Bean(MailConstants.QUEUE_DELAY)
public Queue queueDelay(){
return new Queue(MailConstants.QUEUE_DELAY,true);
}
// 支付成功队列
@Bean(MailConstants.QUEUE_PAY_SUCCESS)
public Queue queuePaySuccess(){
return new Queue(MailConstants.QUEUE_PAY_SUCCESS,true);
}
// 订单队列
@Bean(MailConstants.QUEUE_ORDER)
public Queue queueOrder(){
return new Queue(MailConstants.QUEUE_ORDER,true);
}
// 绑定队列与交换器
@Bean
public Binding queueOrderBinding(){
return BindingBuilder.bind(queueOrder()).to(exchangeDelay()).with(MailConstants.ROUTINGKEY_QUEUE_ORDER).noargs();
}
@Bean
public Binding queueCheckOrderBinding(){
return BindingBuilder.bind(queueCheckOrder()).to(exchangeDelay()).with(MailConstants.ROUTINGKEY_QUEUE_CHECK_ORDER).noargs();
}
@Bean
public Binding queueDelayBinding(){
return BindingBuilder.bind(queueDelay()).to(exchangeDelay()).with(MailConstants.ROUTINGKEY_QUEUE_DELAY).noargs();
}
@Bean
public Binding queuePayBinding(){
return BindingBuilder.bind(queuePaySuccess()).to(exchangeDelay()).with(MailConstants.ROUTINGKEY_QUEUE_PAY_SUCCESS).noargs();
}
@Bean
public MessageConverter messageConverter(){
return new Jackson2JsonMessageConverter();
}
}
配置信息
package com.example.demo.common;
/**
* @author ChenYaoYan
* @version 1.0
* @Description 消息常量
* @date 2022/7/16 18:35
*/
public class MailConstants {
//交换机
public static final String EXCHNAGE_DELAY = "EXCHNAGE_DELAY";
// 订单队列
public static final String QUEUE_ORDER = "QUEUE_ORDER";
//死信队列 用来接收延迟队列的消息
public static final String QUEUE_DELAY = "QUEUE_DELAY";
// 检测订单队列 (延迟队列)时间过期后,该数据会被推送至死信队列
public static final String QUEUE_CHECK_ORDER = "QUEUE_CHECK_ORDER";
// 订单支付成功路由键
public static final String QUEUE_PAY_SUCCESS = "QUEUE_PAY_SUCCESS";
//订单路由键
public static final String ROUTINGKEY_QUEUE_ORDER = "ROUTINGKEY_QUEUE_ORDER";
// 成功支付路由健
public static final String ROUTINGKEY_QUEUE_PAY_SUCCESS = "ROUTINGKEY_QUEUE_PAY_SUCCESS";
// 订单检测路由键
public static final String ROUTINGKEY_QUEUE_CHECK_ORDER = "ROUTINGKEY_QUEUE_CHECK_ORDER";
// 死信路由键
public static final String ROUTINGKEY_QUEUE_DELAY = "ROUTINGKEY_QUEUE_DELAY";
}
消息监听
package com.example.demo.client;
import com.example.demo.common.MailConstants;
import com.example.demo.common.OrderStatusEnum;
import com.example.demo.mapper.OrderMapper;
import com.example.demo.pojo.Order;
import com.example.demo.service.IOrderService;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.stereotype.Component;
/**
* @author ChenYaoYan
* @version 1.0
* @Description TODO
* @date 2022/7/17 11:58
*/
@Component
public class RabbitReceive {
@Autowired
private RabbitTemplate rabbitTemplate;
@Autowired
private OrderMapper orderMapper;
@Autowired
private IOrderService orderService;
// @RabbitListener(queues = MailConstants.QUEUE_ORDER)
@RabbitListener(bindings =@QueueBinding(
value = @Queue(value = MailConstants.QUEUE_ORDER, durable = "true"),
exchange = @Exchange(name = MailConstants.EXCHNAGE_DELAY, durable = "true", ignoreDeclarationExceptions = "true"),
key = MailConstants.ROUTINGKEY_QUEUE_ORDER
)
)
@RabbitHandler
public void handlerOrder(@Payload Order order, Message message){
// 保存订单
orderMapper.insert(order);
System.out.println("新建了一个订单, orderId:"+order.getOrder_id());
System.out.println("支付链接:http://localhost:8080/paySuccess?orderId="+order.getOrder_id());
// 发送该订单至核验队列
rabbitTemplate.convertAndSend(
MailConstants.EXCHNAGE_DELAY,
MailConstants.ROUTINGKEY_QUEUE_CHECK_ORDER,
order);
}
// 核验队列(延迟)后 会将消息发送至死信队列。死信队列判断该订单是否过期
@RabbitHandler
@RabbitListener(bindings =@QueueBinding(
value = @Queue(value = MailConstants.QUEUE_DELAY, durable = "true"),
exchange = @Exchange(name = MailConstants.EXCHNAGE_DELAY, durable = "true", ignoreDeclarationExceptions = "true"),
key = MailConstants.ROUTINGKEY_QUEUE_DELAY
)
)
public void handlerDelayOrder(@Payload Order order, Message message){
// 查找数据库该订单是否已支付
Order order1= orderService.selectOrderByOrderId(order.getOrder_id());
System.out.println(order.getId());
if(order1.getStatus() == OrderStatusEnum.OrderPay.getStatus()){
System.out.println(String.format("订单id:%s支付成功~",order1.getId()));
}else{
order1.setStatus(OrderStatusEnum.OrderExpired.getStatus());
orderMapper.updateById(order);
System.out.println(String.format("订单id:%s长时间未支付,已过期",order1.getOrder_id()));
}
}
// 支付成功
@RabbitListener(bindings =@QueueBinding(
value = @Queue(value = MailConstants.QUEUE_PAY_SUCCESS, durable = "true"),
exchange = @Exchange(name = MailConstants.EXCHNAGE_DELAY, durable = "true", ignoreDeclarationExceptions = "true"),
key = MailConstants.ROUTINGKEY_QUEUE_PAY_SUCCESS
)
)
@RabbitHandler
public void handlerPayOrder(@Payload String orderId, Message message) {
if (orderId == null || orderId.equals("")) {
return;
}
Order order = orderService.selectOrderByOrderId(orderId);
order.setStatus(OrderStatusEnum.OrderPay.getStatus());
orderMapper.updateById(order);
}
}
controller
// 使用消息队列实现的订单过期
@GetMapping("/createOrder")
public String createOrder(){
SnowFlake idWorker = new SnowFlake(0, 0);
Order order = new Order();
order.setOrder_id(Long.toString(idWorker.nextId()));
orderService.addOrder(order);
return "已生成订单,请在"+timeout+"s内完成支付";
}
@GetMapping("/paySuccess")
public String paySuccess(String orderId){
orderService.orderPay(orderId);
return "您已支付!祝您生活愉快~";
}
service
/**
* 添加订单
* @param order
*/
void addOrder(Order order);
/**
* 支付订单
* @param orderId
*/
void orderPay(String orderId);
serviceImpl
@Override
public void addOrder(Order order) {
rabbitTemplate.convertAndSend(
MailConstants.EXCHNAGE_DELAY,
MailConstants.ROUTINGKEY_QUEUE_ORDER,
order);
}
@Override
public void orderPay(String orderId) {
rabbitTemplate.convertAndSend(
MailConstants.EXCHNAGE_DELAY,
MailConstants.ROUTINGKEY_QUEUE_PAY_SUCCESS,
orderId);
}
总结
一般来说 用消息队列或者后端去判断 前端调用都可以
轮询耗资源 时间不准 redis去弄的话 容易丢失
如果项目才起步 不用中间件最好 能不能就不用
demo访问地址
标签:过期,监听,order,QUEUE,订单,org,import,com,public From: https://www.cnblogs.com/chenyaoyan/p/orderHandler.html