
- 在商品详情页面等待秒杀倒计时–http://localhost:8080/goodsDetail.htm?goodsId=2
- 倒计时为0,开始秒杀,点【秒杀】按钮开始秒杀 --http://localhost:8080/seckill/doSeckill?goodsId=2
- 服务端收到秒杀请求,首先判断是不是在秒杀期间,再判断秒杀商品是否有库存
- 上面条件都满足,则进入下单流程,下单流程分3步,1.减库存 2.新增订单信息 3.新增秒杀订单信息
- 商品超卖:10个商品库存,会出现超卖200多的订单
- 秒杀商品库存数会变成负数:因为进入下单流程后,扣减库存是直接减1,大量请求过来,就容易被减为负数
- 解决办法:1.通过下面的接口优化,减少过滤服务端的请求压力 2.优化sql,避免出现商品超卖,库存为负数的情况
- 如果用户秒杀成功,把秒杀订单信息放在redis里面(key-order:userId:goodsId),这样可以防止重复下单
- 通过在redis中设置 isStockEmpty:goodsId的值,有值,表示该商品已经全部秒杀结束,直接返回
- 通过内存标记:Map
EmptyStockMap,在SeckillController初始化时,把秒杀商品 放在内存中,如果商品库存为空,设置EmptyStockMap ,后面直接查这个HashMap的值,如果为true,直接返回,不需要再次访问redis - 在SeckillController初始化时,把每个秒杀商品的库存放在redis的seckillGoods:goodsId中,以方便后面在redis预减库存
boolean result = seckillGoodsService.update(new UpdateWrapper唯一索引 t_seckill_order 3.功能要点(接口优化) 1.redis预减库存 SeckillController afterPropertiesSet()().setSql("stock_count=stock_count-1") .eq("id", seckillGoods.getId()).gt("stock_count", 0)); if(!result){ return null; }
SeckillController初始化时,把每个秒杀商品的库存放在redis的seckillGoods:goodsId中
@Override
public void afterPropertiesSet() throws Exception {
//初始化执行方法 商品库存数量加载到redis
List list = goodsService.findGoodsVo();
if(CollectionUtils.isEmpty(list)){
return;
}
list.forEach(goodsVo ->{
redisTemplate.opsForValue().set("seckillGoods:"+goodsVo.getId(),goodsVo.getStockCount());
EmptyStockMap.put(goodsVo.getId(),false);
}
);
}
SeckillController.doSeckill() seckillGoods:goodsId
原来是先decrement,如果库存<=0,再increment,防止redis中库存为0 ,为了保证redsi事务一致,用lua脚本
Long stock = valueOperations.decrement("seckillGoods:" + goodsId);
log.info("stock:"+stock);
if(stock<=0){
EmptyStockMap.put(goodsId,true);
valueOperations.increment("seckillGoods:" + goodsId);
return RespBean.error(RespBeanEnum.EMPTY_STOCK);
}
lua脚本可以防止redis中库存变成-1
//lua脚本可以防止redis中库存变成-1
Long stock=(Long)redisTemplate.execute(redisscript, Collections.singletonList("seckillGoods:" + goodsId),Collections.emptyList());
log.info("stock:"+stock);
if(stock<=0){
EmptyStockMap.put(goodsId,true);
//valueOperations.increment("seckillGoods:" + goodsId);
return RespBean.error(RespBeanEnum.EMPTY_STOCK);
}
stock.lua
放在resources目录下面,与static 同级
if (redis.call('exists',KEYS[1]) == 1) then
local stock = tonumber(redis.call('get',KEYS[1]));
if (stock > 0) then
redis.call('incrby',KEYS[1],-1);
return stock;
end;
return 0;
end;
2. 内存标记减少redis访问
初始化以及更新
见SeckillController afterPropertiesSet()和 SeckillController.doSeckill() 关于EmptyStockMap.put *** 作
校验场景
//内存标记,减少读取redis的次数 当redis中库存为0,在内存中吧map对应goodsid的库存为空状态设置为true
if(EmptyStockMap.get(goodsId)){
return RespBean.error(RespBeanEnum.EMPTY_STOCK);
}
3. RabbitMQ异步下单
SeckillController.doSeckill 中校验库存,判断是否重复抢购,以及预减库存之后,发送消息,再通过MQ接受消息,异步下单
准备下单消息,发送 //下单
SeckillMessage seckillMessage = new SeckillMessage(user, goodsId);
mqSender.sendSeckillMessage(JSON.toJSONString(seckillMessage));
//正在排队中 0
return RespBean.success(0);
接受消息,校验判断后,下单
MQReceiver
@RabbitListener(queues = "seckillQueue")
public void receive(String message){
}
4.功能要点(安全优化)
1. 隐藏秒杀地址
通过准备每个商品,每个用户一个秒杀地址,方式代刷
seckillPath:userId:goodsId redis中的值,已经有效期
SeckillController.path()
再通过 /seckill/"+path+"/doSeckill 这个真正的秒杀地址,秒杀 2. 验证码 通过验证码图片,校验每次提交 *** 作是不是人手动 *** 作,屏蔽机器人自动提交SeckillController.captcha()
3. 接口防刷@AccessLimit(second=5,maxCount=5,needLogin=true)
通过设置接口AccessLimit注解,设置每5秒钟,最大请求次数为5次,需要有用户登录信息 通过redis计数器实现String key=request.getRequestURI()+ user.getId();
通过设置5秒杀的有效期,累加当前5秒钟内的总的访问次数
@RequestMapping(value = "/path",method = RequestMethod.GET)
@ResponseBody
@AccessLimit(second=5,maxCount=5,needLogin=true)
//通用接口限流
public RespBean path(Model model, User user,Long goodsId,Long captcha,HttpServletRequest request) {
}
AccessLimitInterceptor.preHandle()
Integer count = (Integer)valueOperations.get(key);
if(null==count){
//第一次 设置count值 设置超时时间 5秒
valueOperations.set(key ,1,5, TimeUnit.SECONDS);
}else if(count>5){
log.info("AccessLimitInterceptor:"+"短时间之内请求次数太多");
render(response,RespBeanEnum.REQUEST_LIMITED);
return false;
}else {
valueOperations.increment(key);
}
5.代码实现
SeckillController
@RequestMapping(value = “/path”,method = RequestMethod.GET)
path()
@RequestMapping(value = “/{path}/doSeckill”,method = RequestMethod.POST)
doSeckill()
@RequestMapping("/getResult")
getResult()
@RequestMapping("/captcha")
captcha()
package com.example.miaosha.controller;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.example.miaosha.config.AccessLimit;
import com.example.miaosha.exception.GlobalException;
import com.example.miaosha.pojo.Order;
import com.example.miaosha.pojo.SeckillMessage;
import com.example.miaosha.pojo.SeckillOrder;
import com.example.miaosha.pojo.User;
import com.example.miaosha.rabbitmq.MQSender;
import com.example.miaosha.service.IGoodsService;
import com.example.miaosha.service.IOrderService;
import com.example.miaosha.service.ISeckillOrderService;
import com.example.miaosha.vo.GoodsVo;
import com.example.miaosha.vo.RespBean;
import com.example.miaosha.vo.RespBeanEnum;
import com.wf.captcha.ArithmeticCaptcha;
import com.wf.captcha.SpecCaptcha;
import com.wf.captcha.base.Captcha;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.script.Redisscript;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@Controller
@Slf4j
@RequestMapping("/seckill")
public class SeckillController implements InitializingBean {
@Autowired
private IGoodsService goodsService;
@Autowired
private ISeckillOrderService seckillOrderService;
@Autowired
private IOrderService orderService;
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private MQSender mqSender;
@Autowired
private Redisscript redisscript;
private Map EmptyStockMap=new HashMap<>();
@RequestMapping(value = "/path",method = RequestMethod.GET)
@ResponseBody
@AccessLimit(second=5,maxCount=5,needLogin=true)
//通用接口限流
public RespBean path(Model model, User user,Long goodsId,Long captcha,HttpServletRequest request) {
log.info("captchaPage:"+captcha);
if(null==user){
return RespBean.error(RespBeanEnum.SESSION_ERROR);
}
ValueOperations valueOperations = redisTemplate.opsForValue();
//现在访问次数 5秒内,访问5次 为了便于次数,验证码默认为0
captcha=0L;
Long captchaRedis=0L;
if(captchaRedis!=captcha){
return RespBean.error(RespBeanEnum.CAPTCHA_ERROR);
}
String str=orderService.createPath(user,goodsId);
return RespBean.success(str);
}
@RequestMapping(value = "/path0",method = RequestMethod.GET)
@ResponseBody
//通用接口限流
public RespBean path0(Model model, User user,Long goodsId,Long captcha,HttpServletRequest request) {
log.info("captchaPage:"+captcha);
if(null==user){
return RespBean.error(RespBeanEnum.SESSION_ERROR);
}
ValueOperations valueOperations = redisTemplate.opsForValue();
String url=request.getRequestURI();
log.info("url:"+url);
//现在访问次数 5秒内,访问5次 为了便于次数,验证码默认为0
captcha=0L;
Long captchaRedis=0L;
if(captchaRedis!=captcha){
return RespBean.error(RespBeanEnum.CAPTCHA_ERROR);
}
//
Integer count = (Integer)valueOperations.get(url + ":" + user.getId());
if(null==count){
//第一次 设置count值 设置超时时间 5秒
valueOperations.set(url + ":" + user.getId(),1,5,TimeUnit.SECONDS);
}else if(count>5){
return RespBean.error(RespBeanEnum.REQUEST_LIMITED);
}else {
valueOperations.increment(url + ":" + user.getId());
}
String str=orderService.createPath(user,goodsId);
return RespBean.success(str);
}
//秒杀静态化,在商品详情页,直接ajax请求秒杀,成功后,跳转秒杀成功静态页面
@RequestMapping(value = "/{path}/doSeckill",method = RequestMethod.POST)
@ResponseBody
public RespBean doSeckill(Model model, User user,Long goodsId,@PathVariable String path) {
//判断秒杀库存 判断是否重复秒杀 秒杀(减库存 添加订单信息,添加秒杀订单信息)
if(null==user){
return RespBean.error(RespBeanEnum.SESSION_ERROR);
}
ValueOperations valueOperations = redisTemplate.opsForValue();
Boolean check=orderService.checkPath(user,goodsId,path);
if(!check){
return RespBean.error(RespBeanEnum.REQUEST_ILLEGAL);
}
//判断库存是否>0
//预减库存
//内存标记,减少读取redis的次数 当redis中库存为0,在内存中吧map对应goodsid的库存为空状态设置为true
if(EmptyStockMap.get(goodsId)){
return RespBean.error(RespBeanEnum.EMPTY_STOCK);
}
//Long stock = valueOperations.decrement("seckillGoods:" + goodsId);
//redis lua脚本返回的库存 stokc是减之前的库存,如果库存是8,那返回的是8,实际上redis里面已经变成7,如果redis里面stock 是0,那直接返回0
//lua脚本可以防止redis中库存变成-1
Long stock=(Long)redisTemplate.execute(redisscript, Collections.singletonList("seckillGoods:" + goodsId),Collections.emptyList());
log.info("stock:"+stock);
if(stock<=0){
EmptyStockMap.put(goodsId,true);
//valueOperations.increment("seckillGoods:" + goodsId);
return RespBean.error(RespBeanEnum.EMPTY_STOCK);
}
// 判断是否重复抢购
SeckillOrder seckillOrder = (SeckillOrder)valueOperations.get("order:" + user.getId() + ":" + goodsId);
if(null!=seckillOrder){
return RespBean.error(RespBeanEnum.REPEATE_ERROR);
}
//下单
SeckillMessage seckillMessage = new SeckillMessage(user, goodsId);
mqSender.sendSeckillMessage(JSON.toJSONString(seckillMessage));
//正在排队中 0
return RespBean.success(0);
}
@RequestMapping("/getResult")
@ResponseBody
public RespBean getResult(Model model, User user, @RequestParam Long goodsId){
if(null==user){
return RespBean.error(RespBeanEnum.SESSION_ERROR);
}
//从秒杀订单表中搜索当前用户对应的goodsId有没有订单,有订单表示秒杀成功
Long orderId=orderService.getResult(user,goodsId);
return RespBean.success(orderId);
}
@RequestMapping("/captcha")
public void captcha(HttpServletRequest request, HttpServletResponse response,User user,Long goodsId) throws Exception {
if(user==null||goodsId<0){
throw new GlobalException(RespBeanEnum.REQUEST_ILLEGAL);
}
// 设置请求头为输出图片类型
response.setContentType("image/gif");
response.setHeader("Pragma", "No-cache");
response.setHeader("Cache-Control", "no-cache");
response.setDateHeader("Expires", 0);
// 算术类型
ArithmeticCaptcha captcha = new ArithmeticCaptcha(130, 48,3);
//captcha.setLen(3); // 几位数运算,默认是两位
//captcha.getArithmeticString(); // 获取运算的公式:3+2=?
//captcha.text(); // 获取运算的结果:5
redisTemplate.opsForValue().set("captcha:"+user.getId()+":"+goodsId,captcha.text(),300, TimeUnit.SECONDS);
log.info("captcha公式:"+captcha.getArithmeticString());
log.info("captcha结果:"+captcha.text());
captcha.out(response.getOutputStream()); // 输出验证码
}
//原始秒杀
@RequestMapping("/doSeckill0")
public String doSeckill0(Model model, User user,Long goodsId){
//判断秒杀库存 判断是否重复秒杀 秒杀(减库存 添加订单信息,添加秒杀订单信息)
GoodsVo goodsVo = goodsService.findGoodsVoById(goodsId);
if(goodsVo.getStockCount()<1){
model.addAttribute("errorMsg", RespBeanEnum.EMPTY_STOCK.getMessage());
return "seckillFail";
}
SeckillOrder seckillOrder = seckillOrderService.getOne(new QueryWrapper().eq("goods_id", goodsId).eq("user_id", user.getId()));
if(null!=seckillOrder){
model.addAttribute("errorMsg", RespBeanEnum.REPEATE_ERROR.getMessage());
return "seckillFail";
}
Order order= orderService.seckill(goodsVo,user);
model.addAttribute("order",order);
return "orderDetail";
}
@Override
public void afterPropertiesSet() throws Exception {
//初始化执行方法 商品库存数量加载到redis
List list = goodsService.findGoodsVo();
if(CollectionUtils.isEmpty(list)){
return;
}
list.forEach(goodsVo ->{
redisTemplate.opsForValue().set("seckillGoods:"+goodsVo.getId(),goodsVo.getStockCount());
EmptyStockMap.put(goodsVo.getId(),false);
}
);
}
}
IOrderService
seckill
getResult
createPath
checkPath
package com.example.miaosha.service; import com.example.miaosha.pojo.Order; import com.baomidou.mybatisplus.extension.service.IService; import com.example.miaosha.pojo.SeckillOrder; import com.example.miaosha.pojo.User; import com.example.miaosha.vo.GoodsVo; public interface IOrderService extends IServiceOrderServiceImpl{ Order seckill(GoodsVo goodsVo, User user); Long getResult(User user, Long goodsId); String createPath(User user, Long goodsId); Boolean checkPath(User user, Long goodsId,String path); }
seckill
getResult
createPath
checkPath
package com.example.miaosha.service.impl; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; import com.example.miaosha.mapper.SeckillOrderMapper; import com.example.miaosha.pojo.Order; import com.example.miaosha.mapper.OrderMapper; import com.example.miaosha.pojo.SeckillGoods; import com.example.miaosha.pojo.SeckillOrder; import com.example.miaosha.pojo.User; import com.example.miaosha.service.IOrderService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.example.miaosha.service.ISeckillGoodsService; import com.example.miaosha.service.ISeckillOrderService; import com.example.miaosha.utils.MD5Util; import com.example.miaosha.utils.UUIDUtil; import com.example.miaosha.vo.GoodsVo; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ValueOperations; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.Date; import java.util.concurrent.TimeUnit; @Service public class OrderServiceImpl extends ServiceImplRedisConfigimplements IOrderService { @Autowired private ISeckillGoodsService seckillGoodsService; @Autowired private OrderMapper orderMapper; @Autowired private SeckillOrderMapper seckillOrderMapper; @Autowired private RedisTemplate redisTemplate; @Override @Transactional public Order seckill(GoodsVo goodsVo, User user) { SeckillGoods seckillGoods = seckillGoodsService.getOne(new QueryWrapper ().eq("goods_id",goodsVo.getId())); seckillGoods.setStockCount(seckillGoods.getStockCount()-1); //seckillGoodsService.updateById(seckillGoods); //单独更新库存 设置条件 id= stock_count>0 boolean result = seckillGoodsService.update(new UpdateWrapper ().setSql("stock_count=stock_count-1") .eq("id", seckillGoods.getId()).gt("stock_count", 0)); if(!result){ return null; } Order order = new Order(); order.setUserId(user.getId()); order.setGoodsId(goodsVo.getId()); order.setDeliveryAddrId(1L); order.setGoodsName(goodsVo.getGoodsName()); order.setGoodsCount(1); order.setGoodsPrice(goodsVo.getSeckillPrice()); order.setOrderChannel(1); order.setStatus(0); order.setCreateDate(new Date()); orderMapper.insert(order); SeckillOrder seckillOrder = new SeckillOrder(); seckillOrder.setUserId(user.getId()); seckillOrder.setOrderId(order.getId()); seckillOrder.setGoodsId(goodsVo.getId()); seckillOrderMapper.insert(seckillOrder); //秒杀成功,表秒杀订单存入redis uid+gid redisTemplate.opsForValue().set("order:"+user.getId()+":"+goodsVo.getId(),seckillOrder); return order; } @Override public Long getResult(User user, Long goodsId) { //改为从redis中获取秒杀成功信息 SeckillOrder seckillOrder =(SeckillOrder)redisTemplate.opsForValue().get("order:"+user.getId()+":"+goodsId); if(null !=seckillOrder){ return seckillOrder.getOrderId(); } //如果没有redis中没有查到订单消息,看是否秒杀结束,如果已经秒杀结束,由于本次秒杀结束,所以判断没有秒杀到 //特殊情况:如果秒杀100,只有10人参与,那肯定都会秒杀到,所以不存在活动没有结束,一直查询秒杀结果的情况 if(redisTemplate.hasKey("isStockEmpty:"+goodsId)){ //秒杀结束 return -1L; } return 0L; } @Override public String createPath(User user, Long goodsId) { //获取秒杀地址 String str = MD5Util.md5(UUIDUtil.uuid() + "123456"); ValueOperations valueOperations = redisTemplate.opsForValue(); valueOperations.set("seckillPath:"+user.getId()+":"+goodsId,str,60, TimeUnit.SECONDS); return str; } @Override public Boolean checkPath(User user, Long goodsId,String path) { ValueOperations valueOperations = redisTemplate.opsForValue(); String redisPath = (String)valueOperations.get("seckillPath:" + user.getId() + ":" + goodsId); if(user==null||goodsId<0||StringUtils.isEmpty(redisPath)){ return false; } if(redisPath.equals(path)){ return true; } return false; } }
redisTemplate
redisscript
package com.example.miaosha.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisscript;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory){
RedisTemplate redisTemplate= new RedisTemplate<>();
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.setConnectionFactory(redisConnectionFactory);
return redisTemplate;
}
@Bean
public DefaultRedisscript script(){
DefaultRedisscript redisscript =new DefaultRedisscript<>();
//lock.lua脚本位置和application.yml 同级目录
redisscript.setLocation(new ClassPathResource("stock.lua"));
redisscript.setResultType(Long.class);
return redisscript;
}
}
SeckillMessage
异步下单,发送消息体
package com.example.miaosha.pojo;
import com.example.miaosha.vo.GoodsVo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SeckillMessage {
private User user;
private Long goodsId;
}
MQSender
异步下单,发送消息
package com.example.miaosha.rabbitmq;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
@Slf4j
public class MQSender {
@Autowired
private RabbitTemplate rabbitTemplate;
public void sendSeckillMessage(String message){
log.info("sendSeckill发送消息:"+message);
rabbitTemplate.convertAndSend("seckillExchange","seckill.message",message);
}
}
MQReceiver
异步下单,接受消息,进入下单流程
@RabbitListener(queues = “seckillQueue”)
public void receive(String message){}
package com.example.miaosha.rabbitmq;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.example.miaosha.pojo.SeckillMessage;
import com.example.miaosha.pojo.SeckillOrder;
import com.example.miaosha.pojo.User;
import com.example.miaosha.service.IGoodsService;
import com.example.miaosha.service.IOrderService;
import com.example.miaosha.service.ISeckillOrderService;
import com.example.miaosha.utils.JsonUtil;
import com.example.miaosha.vo.GoodsVo;
import com.example.miaosha.vo.RespBean;
import com.example.miaosha.vo.RespBeanEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestMapping;
@Service
@Slf4j
public class MQReceiver {
@Autowired
private IGoodsService goodsService;
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private ISeckillOrderService seckillOrderService;
@Autowired
private IOrderService orderService;
@RabbitListener(queues = "seckillQueue")
public void receive(String message){
log.info("seckillQueue接受消息:"+message);
SeckillMessage seckillMessage = JSON.parseObject(message, SeckillMessage.class);
Long goodsId = seckillMessage.getGoodsId();
User user = seckillMessage.getUser();
//下单 *** 作
GoodsVo goodsVo = goodsService.findGoodsVoById(goodsId);
if(goodsVo.getStockCount()<1){
//如果有key,表示,没有库存,结束
redisTemplate.opsForValue().set("isStockEmpty:"+goodsId,"0");
return;
}
//1. 判断是否重复抢购 从redis中判断
ValueOperations valueOperations = redisTemplate.opsForValue();
SeckillOrder seckillOrder = (SeckillOrder)valueOperations.get("order:" + user.getId() + ":" + goodsId);
if(null!=seckillOrder){
return;
}
//重复抢购
//2.从t_seckill_order中获取
SeckillOrder seckillOrderDB = seckillOrderService.getOne(new QueryWrapper().eq("user_id", user.getId()).eq("goods_id", goodsId));
if(null!=seckillOrderDB){
return;
}
//重复抢购
orderService.seckill(goodsVo,user);
}
}
RabbitMQConfig
初始化 队列,交换机 路由键,已经绑定
QUEUE
EXCHANGE
ROUTINGKEY
package com.example.miaosha.config;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class RabbitMQConfig {
private static final String QUEUE="seckillQueue";
private static final String EXCHANGE="seckillExchange";
private static final String ROUTINGKEY="seckill.#";
@Bean
public Queue seckillQueue(){
return new Queue(QUEUE);
}
@Bean
public TopicExchange seckillExchange(){
return new TopicExchange(EXCHANGE);
}
@Bean
public Binding binding(){
return BindingBuilder.bind(seckillQueue()).to(seckillExchange()).with(ROUTINGKEY);
}
}
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)