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語句




免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM