boolean result = seckillGoodsService.update(new UpdateWrapper<SeckillGoods>().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<GoodsVo> list = goodsService.findGoodsVo();
if(CollectionUtils.isEmpty(list)){
return;
}
list.forEach(goodsVo ->{
redisTemplate.opsForValue().set("seckillGoods:"+goodsVo.getId(),goodsVo.getStockCount());
EmptyStockMap.put(goodsVo.getId(),false);
}
);
}
原来是先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);
}
放在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;
见SeckillController afterPropertiesSet()和 SeckillController.doSeckill() 关于EmptyStockMap.put操作
校验场景
//内存标记,减少读取redis的次数 当redis中库存为0,在内存中吧map对应goodsid的库存为空状态设置为true
if(EmptyStockMap.get(goodsId)){
return RespBean.error(RespBeanEnum.EMPTY_STOCK);
}
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){
}
通过准备每个商品,每个用户一个秒杀地址,方式代刷
seckillPath:userId:goodsId redis中的值,已经有效期
SeckillController.path()
<img src=""id="captchashow" alt="验证码">
<script>
$("#captchashow").click(function (){
getCaptcha();
})
function getCaptcha(){
console.log("getCaptcha...");
$("#captchashow").attr("src","/seckill/captcha?goodsId="+goodsId+"&time="+(new Date()).getTime());
}
script>
SeckillController.captcha()
@AccessLimit(second=5,maxCount=5,needLogin=true)
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);
}
@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<Long> redisScript;
private Map<Long,Boolean> 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;
/*
String captchaRedisStr=(String) valueOperations.get("captcha:"+user.getId()+":"+goodsId);
Long captchaRedis=Long.valueOf(captchaRedisStr);
log.info("captchaRedis:"+captchaRedis);
*/
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;
/*
String captchaRedisStr=(String) valueOperations.get("captcha:"+user.getId()+":"+goodsId);
Long captchaRedis=Long.valueOf(captchaRedisStr);
log.info("captchaRedis:"+captchaRedis);
*/
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);
/*
GoodsVo goodsVo = goodsService.findGoodsVoById(goodsId);
if(goodsVo.getStockCount()<1){
model.addAttribute("errorMsg", RespBeanEnum.EMPTY_STOCK.getMessage());
return RespBean.error(RespBeanEnum.EMPTY_STOCK);
}
SeckillOrder seckillOrder = (SeckillOrder)redisTemplate.opsForValue().get("order:" + user.getId() + ":" + goodsId);
//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 RespBean.error(RespBeanEnum.REPEATE_ERROR);
}
Order order= orderService.seckill(goodsVo,user);
return RespBean.success(order);
*/
}
@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<SeckillOrder>().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<GoodsVo> list = goodsService.findGoodsVo();
if(CollectionUtils.isEmpty(list)){
return;
}
list.forEach(goodsVo ->{
redisTemplate.opsForValue().set("seckillGoods:"+goodsVo.getId(),goodsVo.getStockCount());
EmptyStockMap.put(goodsVo.getId(),false);
}
);
}
}
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;
/**
*
* 服务类
*
*
* @author cch
* @since 2021-11-17
*/
public interface IOrderService extends IService<Order> {
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;
/**
*
* 服务实现类
*
*
* @author cch
* @since 2021-11-17
*/
@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements 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<SeckillGoods>().eq("goods_id",goodsVo.getId()));
seckillGoods.setStockCount(seckillGoods.getStockCount()-1);
//seckillGoodsService.updateById(seckillGoods);
//单独更新库存 设置条件 id= stock_count>0
/*
boolean result = seckillGoodsService.update(new UpdateWrapper().set("stock_count", seckillGoods.getStockCount())
.eq("id", seckillGoods.getId()).gt("stock_count", 0));
if(!result){
return null;
}
*/
boolean result = seckillGoodsService.update(new UpdateWrapper<SeckillGoods>().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;
}
/*
* return orderId 成功 id /秒杀失败-1 /排队中 0
* */
@Override
public Long getResult(User user, Long goodsId) {
//改为从redis中获取秒杀成功信息
SeckillOrder seckillOrder =(SeckillOrder)redisTemplate.opsForValue().get("order:"+user.getId()+":"+goodsId);
/*
SeckillOrder seckillOrder = seckillOrderMapper.selectOne(new QueryWrapper().eq("goods_id", goodsId)
.eq("user_id", user.getId()));
*/
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<String,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
RedisTemplate<String,Object> 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<Long> script(){
DefaultRedisScript<Long> redisScript =new DefaultRedisScript<>();
//lock.lua脚本位置和application.yml 同级目录
redisScript.setLocation(new ClassPathResource("stock.lua"));
redisScript.setResultType(Long.class);
return redisScript;
}
}
异步下单,发送消息体
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;
}
异步下单,发送消息
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);
}
/*
public void send(Object msg){
log.info("发送消息:"+msg);
rabbitTemplate.convertAndSend("q1",msg);
}
public void sendFanout(Object msg){
log.info("fanout发送消息:"+msg);
rabbitTemplate.convertAndSend("fanoutExchange","",msg);
}
public void sendDirect01(Object msg){
log.info("directExchange发送消息:"+msg);
rabbitTemplate.convertAndSend("direct_Exchange","queue.red",msg);
}
public void sendDirect02(Object msg){
log.info("directExchange发送消息:"+msg);
rabbitTemplate.convertAndSend("direct_Exchange","queue.green",msg);
}
public void sendTopic01(Object msg){
log.info("topic_Exchange发送消息:"+msg);
rabbitTemplate.convertAndSend("topic_Exchange","aaa.queue.bbb",msg);
}
public void sendTopic02(Object msg){
log.info("topic_Exchange发送消息:"+msg);
rabbitTemplate.convertAndSend("topic_Exchange","queue.red.message",msg);
}
public void sendHead01(String msg){
log.info("topic_Exchange发送消息:"+msg);
MessageProperties properties=new MessageProperties();
properties.setHeader("color","red");
properties.setHeader("speed","low");
Message message=new Message(msg.getBytes(),properties);
rabbitTemplate.convertAndSend("head_Exchange","",message);
}
public void sendHead02(String msg){
log.info("topic_Exchange发送消息:"+msg);
MessageProperties properties=new MessageProperties();
properties.setHeader("color","red");
properties.setHeader("speed","fast");
Message message=new Message(msg.getBytes(),properties);
rabbitTemplate.convertAndSend("head_Exchange","",message);
}
*/
}
异步下单,接受消息,进入下单流程
@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<SeckillOrder>().eq("user_id", user.getId()).eq("goods_id", goodsId));
if(null!=seckillOrderDB){
return;
}
//重复抢购
orderService.seckill(goodsVo,user);
}
/*
@RabbitListener(queues = "q1")
public void receive(Object msg){
log.info("接受消息:"+msg);
}
@RabbitListener(queues = "queue_fanout01")
public void receive1(Object msg){
log.info("queue_fanout01接受消息:"+msg);
}
@RabbitListener(queues = "queue_fanout02")
public void receive2(Object msg){
log.info("queue_fanout02接受消息:"+msg);
}
@RabbitListener(queues = "direct_queue01")
public void receive3(Object msg){
log.info("direct_queue01接受消息:"+msg);
}
@RabbitListener(queues = "direct_queue02")
public void receive4(Object msg){
log.info("direct_queue02接受消息:"+msg);
}
@RabbitListener(queues = "topic_queue01")
public void receive5(Message msg){
log.info("topic_queue01接受消息:"+msg);
}
@RabbitListener(queues = "topic_queue02")
public void receive6(Message msg){
log.info("topic_queue02接受消息:"+msg);
}
@RabbitListener(queues = "head_queue01")
public void receive7(Message msg){
log.info("head_queue01接受消息:"+new String(msg.getBody()));
}
@RabbitListener(queues = "head_queue02")
public void receive8(Message msg){
log.info("head_queue02接受消息:"+new String(msg.getBody()));
}
*/
}
初始化 队列,交换机 路由键,已经绑定
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);
}
}