关于订单过期的监听和处理

news/2024/5/19 1:33:59/文章来源:https://www.cnblogs.com/chenyaoyan/p/orderHandler.html

订单过期监听和处理

业务需求

有些时候 用户发起订单 但是没有付款 这个时候一般来说 会设置一个订单过期时间 如果订单过期 则需要重新下单

问题来了 如果每过一段很小的时间就去盘一次数据库 那压力也太大了

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;@TableIdprivate 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位的IDreturn (((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;@Overridepublic void insertFill(MetaObject metaObject) {this.strictInsertFill(metaObject, "create_time", LocalDateTime.class, LocalDateTime.now());//过期时间当前时间+1this.strictInsertFill(metaObject, "expired", LocalDateTime.class, LocalDateTime.now().plusSeconds(timeout));this.strictInsertFill(metaObject, "status", Integer.class, OrderStatusEnum.CreateOrder.getStatus());}@Overridepublic 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: 123456username: rooturl: jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8# Redis配置      redis:timeout: 10000mshost: ip地址port: 端口password:  #数据库密码database: 0 # 选择哪个库,默认0库lettuce:pool:max-active: 1024 # 最大连接数,默认 8max-wait: 10000ms # 最大连接阻塞等待时间,单位毫秒,默认 -1max-idle: 200 # 最大空闲连接,默认 8min-idle: 5#rabbitmq配置rabbitmq:host: 你的ip地址port: 5672password: 密码username: 用户名virtualHost: /
#      #    消息确认回调
#    publisher-confirm-type: correlated
#      #    消息失败回调
#    publisher-returns: true#mybatis-plus配置
mybatis-plus:configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImplmap-underscore-to-camel-case: falsemapper-locations: classpath:mapper/*.xmltypeAliasesPackage: com.example.demo.pojoglobal-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: 8080order:#订单过期时间 单位souttime: 120orderKey: 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 {@Autowiredprivate 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 {@Beanpublic 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;}@BeanRedisMessageListenerContainer 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 {@Autowiredprivate IOrderService orderService;@Value("${order.orderKey}")private String orderKey;public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) {super(listenerContainer);}/*** 针对redis数据失效事件,进行数据处理* @param message* @param pattern*/@Overridepublic void onMessage(Message message, byte[] pattern) {// 用户做自己的业务处理即可,注意message.toString()可以获取失效的keyString 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

 @Overridepublic 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;//定义交换机@Beanpublic 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);//队列消息过期时间10smap.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);}// 绑定队列与交换器@Beanpublic Binding queueOrderBinding(){return BindingBuilder.bind(queueOrder()).to(exchangeDelay()).with(MailConstants.ROUTINGKEY_QUEUE_ORDER).noargs();}@Beanpublic Binding queueCheckOrderBinding(){return BindingBuilder.bind(queueCheckOrder()).to(exchangeDelay()).with(MailConstants.ROUTINGKEY_QUEUE_CHECK_ORDER).noargs();}@Beanpublic Binding queueDelayBinding(){return BindingBuilder.bind(queueDelay()).to(exchangeDelay()).with(MailConstants.ROUTINGKEY_QUEUE_DELAY).noargs();}@Beanpublic Binding queuePayBinding(){return BindingBuilder.bind(queuePaySuccess()).to(exchangeDelay()).with(MailConstants.ROUTINGKEY_QUEUE_PAY_SUCCESS).noargs();}@Beanpublic 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 {@Autowiredprivate RabbitTemplate rabbitTemplate;@Autowiredprivate OrderMapper orderMapper;@Autowiredprivate 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))@RabbitHandlerpublic 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))@RabbitHandlerpublic 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

 @Overridepublic void addOrder(Order order) {rabbitTemplate.convertAndSend(MailConstants.EXCHNAGE_DELAY,MailConstants.ROUTINGKEY_QUEUE_ORDER,order);}@Overridepublic void orderPay(String orderId) {rabbitTemplate.convertAndSend(MailConstants.EXCHNAGE_DELAY,MailConstants.ROUTINGKEY_QUEUE_PAY_SUCCESS,orderId);}

总结

一般来说 用消息队列或者后端去判断 前端调用都可以

轮询耗资源 时间不准 redis去弄的话 容易丢失

如果项目才起步 不用中间件最好 能不能就不用

demo访问地址

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_5730.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【毕业设计】单片机远程wifi红外无接触体温测量系统 - 物联网 stm32

文章目录0 前言1 简介2 主要器件3 实现效果4 设计原理4.1 **MLX90614红外测温传感器**4.2 TOF10120激光测距传感器4.3 DS18B20传感器**DS18B20单总线协议**5 部分核心代码5 最后0 前言 &#x1f525; 这两年开始毕业设计和毕业答辩的要求和难度不断提升&#xff0c;传统的毕设…

精妙绝伦

精妙绝伦啊,精妙绝伦啊,大妙! 今天讨论到一个二级联动省和市在一个表中的情况, 这么一组数据,需要达成一个sql语句便能把省和市同时显示出来,愚绞尽脑汁思虑良久,未得有用之策,经同事提点,顿醍醐灌顶! 先来解释一下这串代码:Select * from TBSpace inner join TBPla…

three.js绘制地图(平面、曲面)

加载中国地图json数据 let loader = new THREE.FileLoader(); loader.load(model/chinaJson.json, function (data) {let jsonData = JSON.parse(data);initMap(jsonData); // 解析并绘制地图 });绘制曲面地图function initMap( chinaJson ) {//创建一个空对象存放对象map = ne…

Vue指令

Vue指令分为内置指令和自定义指令 内置指令 v-bind 单向绑定解析表达式&#xff0c; 简写&#xff1a; &#xff1a;xxx <div id"root">单项数据绑定&#xff1a;<input type"text" v-bind:value"name"><br></div> v…

2023秋招——快手数据研发一、二面面经

&#x1f33c;今天来总结一下快手数据研发的一、二面&#xff0c;在面试中进步&#xff0c;在总结中成长&#xff01;对往期内容感兴趣的小伙伴可以参考下面&#x1f447;&#xff1a; 链接: 2022暑期实习字节跳动数据研发面试经历-基础数仓.链接: 2022百度大数据开发工程师实…

three.js实现鼠标拾取例子

基本思路 <script> var renderer,scene,camera; var light; var raycaster,//相机->鼠标的射线mouse,//鼠标所在位置actionObject;//选中的物体 init(); animation();function init(){//渲染器//场景//相机//方向光//创建2000个立方体//创建射线//创建鼠标二维向量(圆…

epoll实现异步请求数据---以UDP为例

文章目录同步UDP请求数据的问题异步请求的模型具体的代码同步UDP请求数据的问题 不管是请求DNS资源还是其他资源。如果以串行的方式请求数据&#xff0c;也就是send以后recv阻塞等待获取数据&#xff0c;这样做的效率非常低效&#xff0c;网络延迟、服务器处理请求、再加上rec…

【C# 学习笔记 ②】C#基本语法(数组、判断和循环、字符串、枚举、结构体)

由于在自己的工作和学习过程中&#xff0c;只查看某个大佬的教程或文章无法满足自己的学习需求和解决遇到的问题&#xff0c;所以自己在追赶大佬们步伐的基础上&#xff0c;又自己总结、整理、汇总了一些资料&#xff0c;方便自己理解和后续回顾&#xff0c;同时也希望给大家带…

【我不熟悉的css】07. css命名,bem规范,跟着组件库element-ui学习组件命名

在去年&#xff0c;我总结了一篇文章&#xff0c;跟着element-ui学习css命名 【系统学习css】跟着element-ui学习css的命名_我有一棵树的博客-CSDN博客每日鸡汤&#xff0c;每一个你想要学习的念头都是未来的你向自己求救写css 最烦人的就是给class起名字了&#xff0c;这里不…

这一次,我们把AI自治数据库带到了世界人工智能大会上

9月3日,2022世界人工智能大会(WAIC)在上海圆满落幕。WAIC2022 秉持“智联世界”理念,通过线上线下联动的会展形式,汇聚顶级科学家、企业家、政府官员、专家学者、国际组织、投资人、创新团队一同探讨科技前沿话题,推动全球“共创、共建、共融、共治、共享、共赢”。WAIC …

【Python刷题篇】——Python入门 09 字典(上)

&#x1f935;‍♂️ 个人主页: 北极的三哈 个人主页 &#x1f468;‍&#x1f4bb; 作者简介&#xff1a;Python领域新星创作者。 &#x1f4d2; 系列专栏&#xff1a;《牛客题库-Python篇》 &#x1f310;推荐《牛客网》——找工作神器|笔试题库|面试经验|实习经验内推&am…

【数据结构与算法】之深入解析“乘法表中第K小的数”的求解思路与算法示例

一、题目要求 几乎每一个人都用乘法表,但是你能在乘法表中快速找到第 k 小的数字吗?乘法表是大小为 m x n 的一个整数矩阵,其中 mat[i][j] == i * j(下标从 1 开始)。给你三个整数 m、n 和 k,请你在大小为 m x n 的乘法表中,找出并返回第 k 小的数字。示例 1:输入:m =…

MATLAB | 全网唯一,三元相映射图绘制(三元相分级统计地图)

示例 对上期的补充 本期文章名叫三元相映射图绘制&#xff0c;但不单单讲这一点&#xff0c;同时会对上一篇多元映射地图做出补充https://slandarer.blog.csdn.net/article/details/126702967&#xff1a; 上篇中可以绘制这样的三变量映射地图&#xff1a; 但这只是对多边形…

Vue学习之--------路由(Router)的基本使用(1)(2022/9/5)

理解&#xff1a; 一个路由&#xff08;route&#xff09;就是一组映射关系&#xff08;key - value&#xff09;&#xff0c;多个路由需要路由器&#xff08;router&#xff09;进行管理。前端路由&#xff1a;key是路径&#xff0c;value是组件。控制页面的跳转 文章目录1、基…

01-linux基础命令

1、常用命令 常用Linux命令的基本使用序号命令对应英文作用01 ls list 查看当前目录下的内容02 pwd print working derectory 查看当前所在文件夹03 cd [目录名] change directory 切换文件夹04 touch [文件名] touch 如果文件不存在, 新建文件05 mkdir [目录名] make director…

【JavaSE】多线程篇(三)用户线程、守护线程与线程的7大状态

&#x1f481; 个人主页&#xff1a;黄小黄的博客主页 ❤️ 支持我&#xff1a;&#x1f44d; 点赞 &#x1f337; 收藏 &#x1f918;关注 &#x1f38f; 格言&#xff1a;All miracles start from sometime somewhere, make it right now. 本文来自专栏&#xff1a;JavaSE从入…

关于使用@Scheduled()注解造成的多任务冲突问题解决

问题描述&#xff1a; 2022-08-18 01:59:00.001 INFO [irms-transfer-service,,,] 15 --- [ scheduling-1] c.s.i.t.service.impl.ProjectManageSvc : -------定时扫描导出任务开启------- 2022-08-18 01:59:00.004 INFO [irms-transfer-service,,,] 15 --- [ schedu…

如何查看navicat已连接用户的密码

1:从注册表中查看加密后的密码。1.1:windows键+r,输入 regedit1.2:在注册表中找到 \HKEY_CURRENT_USER\SOFTWARE\PremiumSoft\navicat\servers 并找到 UserName 即 账号1.3:从上面步骤中,打开注册表、并找到如下图片中的密码栏:Pwd(加密的密码)2:解密密码,打开在线工…

React 应用程序中的简单路由

React 应用程序中的简单路由一个简单的、可扩展的路由解决方案可以满足您的应用程序的所有需求,而不会引入像 React-Router 这样的工具所提供的复杂性。 进入: 浏览器的 窗口位置 目的。 它提供了为您的应用程序创建一个简单的“路由”系统所需的一切。例如,这里是什么 窗口…

【Selenium Other】一键杀死进程 进程清理大师

前言 有位读者留言&#xff0c;遇到了一件两难全的事儿。在关闭Python使用os模块执行cmd命令调用的chromedriver驱动打开的chrome浏览器时&#xff0c;有以下问题~ 使用driver.quit()退出浏览器&#xff0c;chromedriver.exe进程退出&#xff0c;关闭没有窗口使用driver.close(…