微信支付配合秒杀+消息队列监听+事务回滚
重复排队
下面这个是解决重复抢单得流程:
先查询抢单得时间状态:
之后在查询某个具体的商品,查看库存和信息量以及具体的ID
在之后开始抢单,调用SecKillOrder的Add请求发起抢单操作
抢单成功之后模拟重复抢单出现100异常,可以看到目前暂时解决了重复抢单的问题
Redis中的Count自增的数量也加到了14,但是商品的库存只减少了1个
商品存入Redis中制作一个队列
商品得超卖问题得解决,给每个商品都创造一个Redis得队列,之后在从队列中取数据,这里要声明一个可变长度得数组得函数
//可变长度得数组,将每个商品得ID作为数组元素传入进去,库存数量作为数组得长度
public Long[] getCount(Integer num,Long id){
Long[] ids=new Long[num];
for (int i = 0; i < ids.length; i++) {
ids[i]=id;
}
return ids;
}
在从Redis中取数据得时候进行个判断,如果又数据就去出来,没有数据就是该用户不能排队,则删除之前得用户状态和之前得信息
/**
* 清空所有得用户信息
*/
public void clearUser(String username){
//排队信息清理
redisTemplate.boundHashOps("UserQueueStatus").delete(username);
//排队标识
redisTemplate.boundHashOps("UserQueueCount").delete(username);
}
运行结果查看
现在要抢得商品得数量得库存数为2个,在Redis中存取订单得时候,不管多少用户去抢单应该都是2笔订单
用户aa
三个人抢同一商品,结果只可能是两笔订单,并且库存要削减为0
Redis中开始记录了多个用户得抢单信息
三个用户都进行了抢单,因为商品得库存数量只又1个所以最后得Redis中存得订单得数量只有2笔因为商品得库存为2,所以只能生成两笔订单
PS:这里面我,一开始每注意,后面发现又一些问题, 比如这里削减库存之后要同步Redis,但是我Put得时候把key值写成了username,导致他每次同步也能执行同步,但是商品得库存一直不递减,每次都是2,后来发现这个同步得这个地方,没有办法去从Redis中读写对应得记录,而是新生成了一笔记录,导致生成订单失败。另一个问题就是他这边上传得时候我发起多个用户抢单,竟然等减到负1,后来发现这个判断库存得这个地方一定要写成小于等于0,才行,不然会减到-1;
解决商品部精准得问题
下面对接得是秒杀支付
动态从微信服务器获取队列名字
微信API支持这个功能
从微信服务器中区分出来不同得队列来,实现秒杀订单和普通订单得区分,从而实现动态获取订单数据
接下来就是要封装信息到微信得接口参数里面,attach,直接在生成二维码得时候,传入得map,参数里面获取,然后在获取得参数里面在封装到微信得接口参数里面,在发送监听队列得,时候从微信响应得接口参数attch里面获取对应得队列名称
实现监听得秒杀操作,消息队列进行得相关得操作先在秒杀微服务中创建一个新得MQ监听器,然后再在支付得消息队列中,在创建新得秒杀队列,即可
秒杀微服务
支付微服务这个秒杀队列和之前得一样,只要加上Seckill区分一下就可以了,没什么区别
秒杀订单和微信支付得接口对接,先生成微信支付二维码,这个是生成二维码得HTML代码:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=9; IE=8; IE=7; IE=EDGE">
<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7" />
<title>微信支付页</title>
</head>
<body>
<div id="qrcode"></div>
<script src="qrcode.min.js" ></script>
<script type="text/javascript">
let qrcode = new QRCode(document.getElementById("qrcode"), {
width : 200,
height : 200
})
qrcode.makeCode( "XXXXX");
</script>
</body>
</html>
开始发送微信支付得创建二维码得请求,多加额外得exchage和queue得两个参数
http://mmcy36sg.xiaomy.net/weixin/pay/create/native?outtradeno=123242s134TEDST123CHANGGOU13254&totalfee=1&exchange=exchange.seckillorder&queue=queue.seckillorder
接着就是创建二维码扫描订单,等待微信服务器得回调,支付成功
微信回调中参数以及包含进去
秒杀微服务也监听到了微信得回调参数
这个RabbitMq捕捉腾讯响应过来得得消息速度很慢,如果想让MQ能及时得抓到腾讯服务器响应过来得支付成功得信息,建议在发送得时候打个断点,这样就可以给MQ捕捉到腾讯服务器响应数据得时间,不然响应时间太快,MQ来不及更新,数据及时得响应并且接口对调成
秒杀微服务来监听MQ得数据返回到控制台得信息
下面就是修改订单状态和删除订单回滚信息
先将用户名封装到要传得参数里面去
更新订单操作:
/**
* 更新订单
* @param username
* @param transactionid
* @param endtim
* @throws ParseException
*/
@Override
public void updateOrder(String username, String transactionid, String endtim) throws ParseException {
//1.先查询出来是否存在该笔订单
SeckillOrder seckillOrder = (SeckillOrder) redisTemplate.boundHashOps("SeckillOrder").get(username);
if (seckillOrder!=null){
//2.修改订单的状态
SimpleDateFormat simpleFormatter=new SimpleDateFormat("yyyyMMddHHmmss");
Date date = simpleFormatter.parse(endtim);
seckillOrder.setPayTime(date);
seckillOrder.setTransactionId(transactionid);
seckillOrder.setStatus("1");
seckillOrderMapper.insertSelective(seckillOrder);
//3.清空Redis中的订单缓存
redisTemplate.boundHashOps("SeckillOrder").delete(username);
//4.清空排队信息
clearUser(username);
}
}
删除订单和回滚库存
/**
* 删除订单
* @param username
*/
@Override
public void deleteOrder(String username) {
//1.删除Redis中的订单信息
redisTemplate.boundHashOps("SeckillOrder").delete(username);
//2.查询出来用户排队状态
SeckillStatus userQueueStatus = (SeckillStatus) redisTemplate.boundHashOps("UserQueueStatus").get(username);
//3。清空排队信息
clearUser(username);
//4.判断商品是否为空,库存是否要回滚
String namespace="SeckillGoods_" + userQueueStatus.getTime();
SeckillGoods seckillGoods = (SeckillGoods) redisTemplate.boundHashOps(namespace).get(userQueueStatus.getGoodsId());
if (seckillGoods==null){
seckillGoods=seckillGoodsMapper.selectByPrimaryKey(userQueueStatus.getGoodsId());
seckillGoods.setStockCount(1);
seckillGoodsMapper.updateByPrimaryKeySelective(seckillGoods);
}else
{
seckillGoods.setStockCount(seckillGoods.getStockCount()+1);
}
//4.同步缓存
redisTemplate.boundHashOps(namespace).put(username,seckillGoods);
redisTemplate.boundListOps("SeckillGoodsCountList_" + userQueueStatus.getGoodsId()).leftPush(seckillGoods.getId());
}
Seckill微服务中的对订单信息的监听及对业务结果做出的判断就比较简单了
@RabbitHandler
public void getMessage(String message) throws ParseException {
Map<String,String> result = JSON.parseObject(message, Map.class);
String out_trade_no = result.get("out_trade_no"); //订单编码
String time_end = result.get("time_end"); //支付结束时间
String attach = result.get("attach"); //用户附加数据包
Map<String,String> attachMap = JSON.parseObject(attach, Map.class);
String username = attachMap.get("username");
String result_code = result.get("result_code"); //业务结果
String transaction_id = result.get("transaction_id"); //微信交易订单号
if (result_code .equals("SUCCESS")){
seckillOrderService.updateOrder(username,transaction_id,time_end); //修改订单状态
}else
{
seckillOrderService.deleteOrder(username); //回滚订单
}
}
开始秒杀抢购流程测试:
1.Redis中存入的用户数据情况:订单信息,用户状态,抢单数量都已经存入Redis中
发起请求:http://localhost:18093/seckillOrder/add?time=2020021020&id=1131814839631613952&username=szitheima
2.开始生成微信支付二维码:复制抢单成功的订单号
发送请求:
http://mmcy36sg.xiaomy.net/weixin/pay/create/native?outtradeno=1226835870380855296&totalfee=1&exchange=exchange.seckillorder&routingkey=queue.seckillorder&username=szitheima
创建订单成功:开始扫码支付查询数据库订单状态已更新
‘
秒杀订单延时订单回滚
配置延时队列的配置文件信息,一个延时队列,一个监听队列,一个交换机
/**
* 创建超时队列
*/
@Bean
public Queue orderSeckillDelayQueue(){
return QueueBuilder.durable("orderSeckillDelayQueue")
.withArgument("x-dead-letter-exchange","orderSeckillListenerExchange")
.withArgument("x-dead-letter-routing-key", "orderSeckillListenerQueue")
.build();
}
/**
* 创建监听队列
*/
@Bean
public Queue orderSeckillListenerQueue(){
return new Queue("orderSeckillListenerQueue",true);
}
/**
* 创建交换机
*/
@Bean
public Exchange orderSeckillListenerExchange(){
return new DirectExchange("orderSeckillListenerExchange");
}
/**
* 绑定信息
*/
@Bean
public Binding exchangetoqueue(Queue orderSeckillListenerQueue ,Exchange orderSeckillListenerExchange ){
return BindingBuilder.bind(orderSeckillListenerQueue).to(orderSeckillListenerExchange).with("orderSeckillListenerQueue").noargs();
}
给RabbitMq发送监听消息这里模拟的是延迟10秒中
rabbitTemplate.convertAndSend("orderSeckillDelayQueue", (Object) JSON.toJSONString(seckillStatus), new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyyMMdd HH:mm:ss");
String format = simpleDateFormat.format(new Date());
System.out.println("下单时间:"+format);
message.getMessageProperties().setExpiration("10000");
return message;
}
});
监听到消息之后要做出判断判断,该用户是否支付该笔订单,如果没有支付该笔订单就要回滚这笔订单关闭交易
@RabbitHandler
public void getMessage(String message) throws ParseException {
SeckillStatus seckillStatus = JSON.parseObject(message, SeckillStatus.class);
SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyyMMdd HH:mm:ss");
String format = simpleDateFormat.format(new Date());
System.out.println("延时时间订单:"+format);
String username = seckillStatus.getUsername();
Object userQueueStatus = redisTemplate.boundHashOps("UserQueueStatus").get(username);
if (userQueueStatus!=null){ //如果用户信息还在Redis中则该用户就没有支付订单
redisTemplate.boundHashOps("UserQueueStatus").delete(username);
}
}
完成结果测试: