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语句
redis事件监听及在订单系统中的使用的更多相关文章
-
JS 中的事件绑定、事件监听、事件委托
事件绑定 要想让 JavaScript 对用户的操作作出响应,首先要对 DOM 元素绑定事件处理函数.所谓事件处理函数,就是处理用户操作的函数,不同的操作对应不同的名称. 在JavaScript中,有 ...
-
Javascript事件模型系列(三)jQuery中的事件监听方式及异同点
作为全球最知名的js框架之一,jQuery的火热程度堪称无与伦比,简单易学的API再加丰富的插件,几乎是每个前端程序员的必修课.从读<锋利的jQuery>开始,到现在使用jQuery有一年 ...
-
jQuery中的事件监听方式及异同点
jQuery中的事件监听方式及异同点 作为全球最知名的js框架之一,jQuery的火热程度堪称无与伦比,简单易学的API再加丰富的插件,几乎是每个前端程序员的必修课.从读<锋利的jQuery&g ...
-
简单剖析Node中的事件监听机制(一)
使用js的class类简单的实现一个事件监听机制,不同于浏览器中的时间绑定与监听,类似于node中的时间监听,并且会在接下来的文章中去根据自己的理解去写一下Event模块中的原理. Node.js使用 ...
-
JS 事件绑定、事件监听、事件委托详细介绍
原:http://www.jb51.net/article/93752.htm 在JavaScript的学习中,我们经常会遇到JavaScript的事件机制,例如,事件绑定.事件监听.事件委托(事件代 ...
-
JS 中的事件绑定、事件监听、事件委托是什么?
在JavaScript的学习中,我们经常会遇到JavaScript的事件机制,例如,事件绑定.事件监听.事件委托(事件代理)等.这些名词是什么意思呢,有什么作用呢? 事件绑定 要想让 JavaScri ...
-
SpringBoot框架(6)--事件监听
一.场景:类与类之间的消息通信,例如创建一个对象前后做拦截,日志等等相应的事件处理. 二.事件监听步骤 (1)自定义事件继承ApplicationEvent抽象类 (2)自定义事件监听器,一般实现Ap ...
-
Spring 事件监听机制及原理分析
简介 在JAVA体系中,有支持实现事件监听机制,在Spring 中也专门提供了一套事件机制的接口,方便我们实现.比如我们可以实现当用户注册后,给他发送一封邮件告诉他注册成功的一些信息,比如用户订阅的主 ...
-
springboot使用Redis,监听Redis键过期的事件设置与使用代码
我使用的是Windows下的Redis服务,所以一下Redis设置都是在Windows平台进行. 1.修改Redis配置文件 1.1:Windows下的Redis存在两个配置文件 修改带有servic ...
随机推荐
-
pm2无法自动重启
在服务器上有个上传文件的服务,之前是pm2启动,每当有文件上传会自动重启 现在为了应对服务器宕机,我把启动脚本放在了另一文件夹内,所以就无法自动重启, 原文在 http://pm2.keymetric ...
-
磁盘文件系统Fat、Fat32、NTFS、exFAT的优缺点
我们在Windows系统里格式化磁盘的时候,文件系统的选项里可以看到有“FAT”.“FAT32”.“NTFS”等选项,在对U盘或其他移动存储设备 格式化的时候还会出现“exFAT”选项,那么这四种磁盘 ...
-
js控制html文字提示语的出现和隐藏
有时我们需要在点击html输入框的时候,旁边会出现提示语.在输入字符的时候,输入框下边会出现输入了多少字符的提示. 请看下面实例. <!DOCTYPE html> <html> ...
-
ListView优化相关
链接1 http://www.jb51.net/article/35273.htm 链接2 http://www.cnblogs.com/xilinch/archive/2012/11/08/2760 ...
-
SQL Server 2008 R2 主从数据库同步设置
一.准备工作: 主数据库服务器: OS:Windows Server 2008 R2 DB: SQL Server 2008 R2 Hostname : CXMasterDB IP: 192.1 ...
-
POJ1258-Agri-Net-ACM
Description Farmer John has been elected mayor of his town! One of his campaign promises was to brin ...
-
c# winform 路径选择和文件读写
//读文件 private void readBtn_Click(object sender, EventArgs e) { try { if (pathTxt.Text == "" ...
-
shell脚本执行错误 $&#39;\r&#39;:command not found
shell脚本执行错误 $'\r':command not found Linux下有命令dos2unix 可以用一下命令测试 vi -b filename 我们只要输入dos2unix *.sh就可 ...
-
[py][mx]django邮箱注册的验证码部分-django-simple-captcha库使用
邮箱注册-验证码 验证码使用第三方库django-simple-captcha 这个安装图形插件步骤官网有哦 - 1.Install django-simple-captcha via pip: pi ...
-
退出循环break,在while、for、do...while、循环中使用break语句退出当前循环,直接执行后面的代码。
在while.for.do...while循环中使用break语句退出当前循环,直接执行后面的代码. 格式如下: for(初始条件;判断条件;循环后条件值更新) { if(特殊情况) {break;} ...