redis事件监听及在订单系统中的使用
来源:互联网 发布:java生成文件夹目录 编辑:程序博客网 时间:2024/06/05 11:29
通常在网上买好物品,或者说手机扫码后,点击付款,这时就会向后台发送请求,生成订单信息,以及够买商品的信息存入到数据库对应的表比如:订单表和商品销售表,或者还有物流信息表等。简单起见,就拿扫码购物来说,这里就不需要物流信息表了,只需要订单表,商品销售表,而且一次只能买一个商品,对应生成一个订单。
注:这里用到的是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=8081spring.datasource.url=jdbc:mysql://localhost:3306/logisticspring.datasource.username=rootspring.datasource.password=123456spring.datasource.driver-class-name=com.mysql.jdbc.Driverspring.jpa.properties.hibernate.hbm2ddl.auto=updatespring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialectspring.jpa.show-sql= truespring.jpa.properties.hibernate.format_sql=truespring.redis.host=192.168.25.128spring.redis.pool.max-active=1000spring.redis.pool.max-idle=100spring.redis.pool.max-wait=-1spring.redis.pool.min-idle=0spring.redis.port=6379spring.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个实体类)
@Repositorypublic interface GoodsRepository extends CrudRepository<Goods, Long>{}
@Repositorypublic interface UserRepository extends CrudRepository<User, Long>{}
@Repositorypublic 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);}
@Repositorypublic 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);}
@Servicepublic 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 */@Configurationpublic 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);}
@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; }}
其中存入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方法}
8、Controller
@RestControllerpublic class OrderController { @Autowired private OrderService orderService; @PostMapping("/order/pay") public ResponseTemplate payOrder(@RequestBody PayOrderRequestVo requestVo){ String tradeNo = orderService.payOrder(requestVo); return new ResponseTemplate(tradeNo); }}
@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方法}
为了看的直观,目录结构还是截了下来
测试:启动项目(执行SpringDataJpaApplication的main方法)
数据库logistic会多出来4张表,手动分别添加一条记录到用户表和商品表中,用来测试。
假如id为1的用户(Tom)购买了id为1的商品(张飞牛肉),然后就会下单,但他一直没支付。那么两分钟后该订单就失效(支付状态为2,失败)。
使用postman模拟数据发送请求。
查看数据库表,订单表和商品销售表都产生了记录,且状态为0(待支付)。
过两分钟再去查看,发现订单失效(支付状态为2)
控制台输出:
sql语句终于失效了order:5a8378f66b4e473cba5f4014a813810esql语句
- redis事件监听及在订单系统中的使用
- Redis在京东到家的订单中的使用 【转载】
- Redis在京东到家的订单中的使用
- Redis在京东到家的订单中的使用
- Redis在京东到家的订单中的使用
- Cookie及Redis在商城购物车系统中的使用
- Redis特点及在系统中的应用
- 支付系统中订单redis防重的使用
- Redis 事件监听
- CollapsingToolbarLayout的使用及折叠事件监听
- 使用监听Logcat来监听系统home事件
- Redis入门及在商城案例中的使用
- Spring中的事件监听机制在项目中的应用
- 在VMware的Linux系统上安装Redis redis及memcache使用
- 缓存技术Redis在C#中的使用及Redis的封装
- 缓存技术Redis在C#中的使用及Redis的封装
- Redis 在 Java 中的使用
- redis在shell中的使用
- Java中关于Map经常使用的方法
- 嵌入式系统的知识体系、学习误区及学习建议
- JAVA设计模式(二十三)----策略模式
- 剑指Offer—55—链表中环的入口节点
- Oracle监听的静态注册和动态注册
- redis事件监听及在订单系统中的使用
- [Data Structure & Algorithm] 八大排序算法
- JMeter之断言--判断响应数据是否符合预期
- js 抽象工厂模式
- Linux的vim编辑器的使用
- 35. Search Insert Position —— Java
- Mysql运行脚本时出现问题:Failed to open file 'file_name' , error: 2
- Oracle分析函数之first()和last()函数
- CFile与CStdioFile的文件读写使用方法详解