• redis事件监听及在订单系统中的使用


    https://blog.csdn.net/qq_37334135/article/details/77717248

    通常在网上买好物品,或者说手机扫码后,点击付款,这时就会向后台发送请求,生成订单信息,以及够买商品的信息存入到数据库对应的表比如:订单表和商品销售表,或者还有物流信息表等。简单起见,就拿扫码购物来说,这里就不需要物流信息表了,只需要订单表,商品销售表,而且一次只能买一个商品,对应生成一个订单。
    注:这里用到的是spring data +redis,也用到了spring data +jpa所以前提这两个都了解。
    订单表字段有:订单id、创建时间、修改时间、付款方式、金额、支付状态、订单编号;
    商品销售表字段有:id、创建时间、修改时间,支付状态,商品id(外键),订单id(外键),用户id(外键);
    有三个外键,那么肯定还有另外的商品表,用户表
    商品表字段:id、创建时间、修改时间、商品名称、价格;
    用户表:id、创建时间、修改时间、邮箱、用户名、密码、电话。
    所以一个涉及4张表

    流程大致是:用户点击付款,发送请求,后台接收到请求后,生成订单信息和商品销售信息,保存到数据库表中。同时把订单信息存入到redis中,key可以设为订单编号,同时设置过期时间。到了过期时间后,redis监听器监听到了过期的key,取出该key查询数据库订单表,如果发现支付状态不是成功(用户为付款,需要使订单失效),那么修改支付状态为失败(也就是用户下单后一直不付款,到了一定时间后,那么就应该让这个订单作废。如果用户付款了,在支付宝回调的接口里面会将支付状态修改为成功)。

    创建spring boot项目,如果用的eclipse的话最好安装STS插件。

    1、MySql、jpa、redis配置

    server.port=8081
    
    spring.datasource.url=jdbc:mysql://localhost:3306/logistic
    spring.datasource.username=root
    spring.datasource.password=123456
    spring.datasource.driver-class-name=com.mysql.jdbc.Driver
    
    spring.jpa.properties.hibernate.hbm2ddl.auto=update
    spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
    spring.jpa.show-sql= true
    spring.jpa.properties.hibernate.format_sql=true
    
    spring.redis.host=192.168.25.128
    spring.redis.pool.max-active=1000
    spring.redis.pool.max-idle=100
    spring.redis.pool.max-wait=-1
    spring.redis.pool.min-idle=0
    spring.redis.port=6379
    spring.redis.timeout=0
    

    2、创建好项目后编写4个实体类

    @Entity
    @Table(name="t_goods")
    public class Goods {
    
        @Id
        @GeneratedValue
        private long id;
        private String name;
        private Integer price;
    
        @Column(name="create_time")
        @Temporal(TemporalType.TIMESTAMP)
        private Date createTime;
        @Column(name="modified_time")
        @Temporal(TemporalType.TIMESTAMP)
        private Date modifiedTime;
        get、set方法
    }
    @Entity
    @Table(name = "t_user")
    public class User {
    
        @Id
        @GeneratedValue(strategy=GenerationType.IDENTITY)
        private Long id;
    
        @Column(name="user_name")
        private String userName;
    
        private String password;
    
        private String telephone;
    
        private String email;
    
        @Column(name="create_time")
        @Temporal(TemporalType.DATE)
        private Date createTime;
        @Column(name="modified_time")
        @Temporal(TemporalType.TIMESTAMP)
        private Date modifiedTime;
        get、set方法   
    }
    @Entity
    @Table(name="t_order")
    public class Order {
        @Id
        @GeneratedValue(strategy=GenerationType.IDENTITY)
        private Long id;
        private Date createTime= new Date();
        private Date modifiedTime = new Date();
        //金额
        private Integer payment;
        //0:待支付 1:支付成功 2:支付失败
        private Integer status;
        //支付方式 0:支付宝 1:微信
        private Integer channel;
        //订单编号
        private String tradeNo;
        get、set方法
    }
    @Entity
    @Table(name="t_goods_sells")
    public class GoodsSell {
    
        @Id
        @GeneratedValue
        private long id;
        @ManyToOne
        private Goods goods;
        @OneToOne
        private Order order;
        @ManyToOne
        private User user;
        private int count;
        //0:待付款 1:已付款 2:未付款
        private int status;
    
        @Column(name="create_time")
        @Temporal(TemporalType.TIMESTAMP)
        private Date createTime;
        @Column(name="modified_time")
        @Temporal(TemporalType.TIMESTAMP)
        private Date modifiedTime;
        get、set方法
    }

    3、4个Repository接口(对应4个实体类)

    @Repository
    public interface GoodsRepository extends CrudRepository<Goods, Long>{
    }
    @Repository
    public interface UserRepository extends CrudRepository<User, Long>{
    }
    @Repository
    public interface PayOrderRepository extends JpaRepository<Order, Long>{
    
        //根据订单编号查询订单信息
        @Query(value="SELECT o FROM Order o WHERE o.tradeNo=?1")
        Order findOrderByTradeNo(String tradeNo);
    
        //根据订单id修改订单状态
        @Modifying
        @Query("UPDATE Order o SET o.status=?1 WHERE o.id=?2")
        int setStatusByOrderId(int status, long orderId);
    }
    @Repository
    public interface GoodsSellsRepository extends CrudRepository<GoodsSell, Long>{
        //根据商品id修改商品销售状态
        @Modifying
        @Query("UPDATE GoodsSell o SET o.status=?1 WHERE o.id=?2")
        int setStatusByGoodsId(int status, long goodsId);
    
        //根据订单id查询商品销售信息
        @Query(value="SELECT * FROM t_goods_sells t WHERE t.order_id=?1",nativeQuery=true)
        GoodsSell findOrderByOrderId(long orderId);
    }

    4、订单Redis接口和实现类

    public interface OrderRedisService {
    
        public void saveOrder(String outTradeNo,OrderRedisDo redisDo);
        public String getOrder(String outTradeNo);
        public void deleteOrder(String outTradeNo);
    
    }
    @Service
    public class OrderRedisServiceImpl implements OrderRedisService{
    
        @Autowired
        private StringRedisTemplate redisTemplate;
    
        /*
         * 保存订单
         */
        public void saveOrder(String outTradeNo, OrderRedisDo redisDo) {
            String key = "order:"+outTradeNo;
            //key过期时间为120秒
            redisTemplate.opsForValue().set(key, JsonUtils.objectToJson(redisDo), 120, TimeUnit.SECONDS);
        }
    
        /*
         * 获取订单
         */
        public String getOrder(String outTradeNo) {
            String key = "order:"+outTradeNo;
            String message = redisTemplate.opsForValue().get(key);
            return message;
        }
    
        /*
         * 删除订单
         */
        public void deleteOrder(String outTradeNo) {
            String key = "order:"+outTradeNo;
            redisTemplate.delete(key);
        }
    }

    工具类JSONUtils如下:

    public class JsonUtils {
    
        // 定义jackson对象
        private static final ObjectMapper MAPPER = new ObjectMapper();
    
        /**
         * 将对象转换成json字符串。
         */
        public static String objectToJson(Object data) {
            try {
                String string = MAPPER.writeValueAsString(data);
                return string;
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
            return null;
        }
    
        /**
         * 将json结果集转化为对象
         */
        public static <T> T jsonToPojo(String jsonData, Class<T> beanType) {
            try {
                T t = MAPPER.readValue(jsonData, beanType);
                return t;
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    
        /**
         * 将json数据转换成pojo对象list
         */
        public static <T>List<T> jsonToList(String jsonData, Class<T> beanType) {
            JavaType javaType = MAPPER.getTypeFactory().constructParametricType(List.class, beanType);
            try {
                List<T> list = MAPPER.readValue(jsonData, javaType);
                return list;
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            return null;
        }
    
    }

    StringRedisTemplate redisTemplate:为spring data为我们提供的redis模板,能操作常用的5中redis数据类型,分别对应如下

    redisTemplate.opsForValue();
    redisTemplate.opsForHash();
    redisTemplate.opsForList();
    redisTemplate.opsForSet();
    redisTemplate.opsForZSet();

    基本都提供了增删改查的方法,会使用jedis操作redis那么这个redisTemplate使用也不会是问题。

     5、redis过期监听器

    @Service(value=OrderRedisListener.SERVICE_NAME)
    public class OrderRedisListener implements MessageListener{
    
        public static final String SERVICE_NAME="com.scu.listener.OrderRedisListener";
    
        @Autowired
        private PayOrderRepository payOrderRepository;
        @Autowired
        private GoodsSellsRepository goodsSellsRepository;
    
        private static Log log = LogFactory.getLog(OrderRedisListener.class);
        @Override
        @Transactional 
        public void onMessage(Message message, byte[] pattern) {
            //获取过期的key
            String expireKey = new String(message.getBody());
            System.out.println("终于失效了");
            log.debug("key is:"+ expireKey);
            System.out.println(expireKey);
            //截取订单号
            String tradeNo = expireKey.substring(expireKey.indexOf(":")+1);
            Order order = payOrderRepository.findOrderByTradeNo(tradeNo);
            if(order!=null && order.getStatus()!=1){
                //修改订单支付状态为失败
                payOrderRepository.setStatusByOrderId(2, order.getId());
                GoodsSell goodsSell = goodsSellsRepository.findOrderByOrderId(order.getId());
    //修改商品购买状态为失败           goodsSellsRepository.setStatusByGoodsId(2, goodsSell.getId());
            }
        }
    }

    6、配置监听容器

    /**
     * redis监听容器
     * @author 12706
     */
    @Configuration
    public class RedisConfig {
    
        @Autowired
        @Qualifier(OrderRedisListener.SERVICE_NAME)
        private MessageListener messageListener;
    
        @Autowired
        private RedisTemplate redisTemplate;
    
        @Bean
        RedisMessageListenerContainer container(MessageListenerAdapter listenerAdapter) {
    
            RedisMessageListenerContainer container = new RedisMessageListenerContainer();
            container.setConnectionFactory(redisTemplate.getConnectionFactory());
            container.addMessageListener(listenerAdapter, new PatternTopic("__keyevent@0__:expired"));
            return container;
        }
        @Bean
        MessageListenerAdapter listenerAdapter() {
            return new MessageListenerAdapter(messageListener);
        }
    }

    获取RedisMessageListenerContainer也可以写成

    @Bean
        RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory,
                                                MessageListenerAdapter listenerAdapter) {
    
            RedisMessageListenerContainer container = new RedisMessageListenerContainer();
            container.setConnectionFactory(connectionFactory);
            container.addMessageListener(listenerAdapter, new PatternTopic("__keyevent@0__:expired"));
    
            return container;
        }
    
    

    因为连接工厂在spring容器中是已经存在的,如果是自己配置spring data与redis整合是需要自己配置连接工厂,但是spring boot已经帮我们配置好了,所以可以直接注入。

    注意:监听器能监听到redis中过期的key是有个要求的,必须在redis配置文件redis.conf里面设置能够监听到key过期事件,配置如下:


    参数说明如下:

    可以测试一下,打开两个窗口,分别启动redis客户端连接,
    命令: ./redis-cli

    窗口1监听:

    窗口2设置key及过期时间10秒

    10秒后再看窗口1,监听到了过期的key

    说明配置生效了。
    如果用过ActiveMQ的话,会发现其实配置是很类似的,配置消费者的话会配置连接工厂,配置目的地,配置监听器,配置监听容器,两者都是用来监听消息的,主要是在onMessage方法里面进行逻辑处理。

    7、Service层处理

    public interface OrderService {
    public String payOrder(PayOrderRequestVo requestVo);
    }
    1
    2
    3
    @Service(value=OrderServiceImpl.SERVICE_NAME)
    public class OrderServiceImpl implements OrderService{

    public static final String SERVICE_NAME="com.scu.service.impl.OrderServiceImpl";

    @Autowired
    private PayOrderRepository payOrderRepository;

    @Autowired
    private GoodsRepository goodsRepository;

    @Autowired
    private UserRepository UserRepository;

    @Autowired
    private GoodsSellsRepository goodsSellsRepository;

    @Autowired
    private OrderRedisService orderRedisService;

    /*
    * 保存订单
    */
    @Transactional
    public String payOrder(PayOrderRequestVo requestVo){
    //模拟生成订单号 (由支付宝或者微信生成)
    String tradeNo = UUID.randomUUID().toString().replace("-", "");
    //保存订单信息
    Order order = new Order();
    order.setCreateTime(new Date());
    order.setModifiedTime(new Date());
    order.setChannel(requestVo.getChannel());
    order.setPayment(requestVo.getCount());
    order.setTradeNo(tradeNo);
    order.setStatus(0);
    payOrderRepository.save(order);
    //保存商品支付信息
    GoodsSell goodsSell = new GoodsSell();
    goodsSell.setCreateTime(new Date());
    goodsSell.setModifiedTime(new Date());
    goodsSell.setCount(requestVo.getCount());
    goodsSell.setStatus(0);
    goodsSell.setOrder(order);
    //查询用户信息,关联商品销售
    User user = UserRepository.findOne(requestVo.getUserId());
    goodsSell.setUser(user);
    //查询商品信息,关联商品销售
    Goods goods = goodsRepository.findOne(requestVo.getGoodsId());
    goodsSell.setGoods(goods);
    goodsSellsRepository.save(goodsSell);
    //信息存入redis的对象
    OrderRedisDo orderRedisDo = new OrderRedisDo();
    //复制部分属性
    BeanUtils.copyProperties(order,orderRedisDo);
    orderRedisDo.setGoodsId(requestVo.getGoodsId());
    //保存信息
    orderRedisService.saveOrder(tradeNo, orderRedisDo);
    return tradeNo;
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    其中存入redis中的实OrderRedisDo类如下:

    public class OrderRedisDo {

    private Long id;//订单id
    private Integer payment;//付款金额
    private Integer channel;//支付方式
    private Integer status;//支付状态
    private Long goodsId;//商品id
    private String tradeNo;//订单号
    get、set方法
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    8、Controller

    @RestController
    public class OrderController {

    @Autowired
    private OrderService orderService;

    @PostMapping("/order/pay")
    public ResponseTemplate payOrder(@RequestBody PayOrderRequestVo requestVo){
    String tradeNo = orderService.payOrder(requestVo);
    return new ResponseTemplate(tradeNo);
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @RestController注解相当于@Controller+@ResponseBody+..,返回的是个json。
    ResponseTemplate是个返回信息模板

    /**
    * 返回信息模板
    * @author 12706
    */
    public class ResponseTemplate {

    private int code;
    private String message;
    private List<String> errors;
    private Object data;
    public ResponseTemplate(Object data) {
    super();
    this.data = data;
    }
    public ResponseTemplate() {
    super();
    }
    get、set方法
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    为了看的直观,目录结构还是截了下来


    测试:启动项目(执行SpringDataJpaApplication的main方法)
    数据库logistic会多出来4张表,手动分别添加一条记录到用户表和商品表中,用来测试。

    假如id为1的用户(Tom)购买了id为1的商品(张飞牛肉),然后就会下单,但他一直没支付。那么两分钟后该订单就失效(支付状态为2,失败)。
    使用postman模拟数据发送请求。


    查看数据库表,订单表和商品销售表都产生了记录,且状态为0(待支付)。

    过两分钟再去查看,发现订单失效(支付状态为2)

    控制台输出:

    sql语句

    终于失效了
    order:5a8378f66b4e473cba5f4014a813810e

    sql语句



  • 相关阅读:
    vue_组件化开发
    C++ / C# 访问网络共享文件夹
    PetaLinux 设置操作系统内存
    linux 不用./ 直接执行程序
    Visual Studio Code 开发环境搭建 —— C# 扩展插件
    Visual Studio Code 调试项目时传参
    PetaLinux 安装
    Ubuntu 报 "xxx is not in the sudoers file.This incident will be reported" 错误解决方法
    常用 Linux 命令
    搭建 Git 服务器(Ubuntu 系统)
  • 原文地址:https://www.cnblogs.com/yuluoxingkong/p/9882452.html
Copyright © 2020-2023  润新知