校园课程助手【8】-RabbitMQ实现异步选课

本节是此项目核心问题,保证在高并发情况下选课业务能够高效、正确的完成。

1.在进行选课前将课程库存提前加载到Redis中:

//在抢课Controller中实现InitializingBean接口

    //初始化时执行将库存预加载到Redis
    @Override
    public void afterPropertiesSet() throws Exception {
        List<CourseVo> list = courseService.findCourseVo();
        if (CollectionUtils.isEmpty(list)) {
            return;
        }
        list.forEach(CourseVo-> {
            redisTemplate.opsForValue().set("seckillCourese:"+CourseVo.getId(),CourseVo.getStockCount());
            if(CourseVo.getStockCount() > 0)
                emptyStockMap.put(CourseVo.getId(),false);
            else
                emptyStockMap.put(CourseVo.getId(),true);
        });
    }

2.抢课Controller

//使用前后端分离,对象缓存减少前端页面的数据访问,同时使用Redis判断是否重复抢购
@RequestMapping(value = "/{path}/doSeckill",method = RequestMethod.POST)
@ResponseBody
public RespBean doSecKill(@PathVariable String path,User user,Long CourseId){
        if(user == null)
        return RespBean.error(RespBeanEnum.SESSION_ERROR);

        boolean check = orderService.checkPath(user,CourseId,path);
        if(!check){
        return RespBean.error(RespBeanEnum.REQUEST_ILLEGAL);
        }
        //判断是否重复抢购
        TSeckillOrder seckillOrder =
        (TSeckillOrder) redisTemplate.opsForValue().get("order:"+user.getId()+":"+CourseId);
        if(seckillOrder != null){
        return RespBean.error(RespBeanEnum.REPEATE_ERROR);
        }
        if(emptyStockMap.get(CourseId)){
        return  RespBean.error(RespBeanEnum.EMPTY_STOCK);
        }
        //通过Redis预减库存
        ValueOperations valueOperations = redisTemplate.opsForValue();
        //原子性预减库存操作
        Long stock = valueOperations.decrement("seckillCourse:"+CourseId);

        //也可以使用Redis结合lua脚本
//        Long stock = (Long) redisTemplate.execute(script, Collections.singletonList("seckillCourse:"+CourseId),
//                Collections.EMPTY_LIST);
        if(stock < 0){
        emptyStockMap.put(CourseId,true);
        valueOperations.increment("seckillCourse:"+CourseId);
        return RespBean.error(RespBeanEnum.EMPTY_STOCK);
        }
        SeckillMessage seckillMessage = new SeckillMessage(user, CourseId);
        mqSender.sendSeckillMessage(JsonUtil.object2JsonStr(seckillMessage));
//        通过RabbitMQ消息队列下单,0状态表示排队中
        return  RespBean.success(0);
        }

3.配置RabbitMQ


@Configuration
public class RabbitMQTopicConfig {

    private static final String QUEUE = "seckillQueue";
    private static final String EXCHANGE = "seckillExchange";

    @Bean
    public Queue queue() {
        return new Queue(QUEUE);
    }

    @Bean
    public TopicExchange topicExchange() {
        return new TopicExchange(EXCHANGE);
    }

    @Bean
    public Binding binding() {
        return BindingBuilder.bind(queue()).to(topicExchange()).with("seckill.#");
    }

4.消息发送


@Service
@Slf4j
public class MQSender {
    @Autowired
    private RabbitTemplate rabbitTemplate;

    //发送秒杀信息
    public void sendSeckillMessage(String msg){
        log.info("发送"+msg);
        rabbitTemplate.convertAndSend("seckillExchange","seckill.msg",msg);
    }
}

5.消息接收


@Service
@Slf4j
public class MQReceiver {

    @Autowired
    private ITCourseService courseService;
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private ITOrderService orderService;

    //接收消息,实际上进行下单操作
    @RabbitListener(queues = "seckillQueue")
    public void receive(String msg){
        log.info("接收"+msg);
        SeckillMessage seckillMessage = JsonUtil.jsonStr2Object(msg, SeckillMessage.class);
        Long courseId = seckillMessage.getCourseId();
        User user = seckillMessage.getUser();
        CourseVo courseVobyCourseId= courseService.findCourseVobyCourseId(courseId );
        if(courseVobyCourseId.getStockCount() < 1){
            return;
        }
        //判断是否重复抢购
        TSeckillOrder seckillOrder =
                (TSeckillOrder) redisTemplate.opsForValue().get("order:"+user.getId()+":"+courseId );
        if(seckillOrder != null){
            return ;
        }
        //下单
        orderService.secKill(user,courseVobyCourseId);
    }
}

6.选课成功sevice下单逻辑


@Transactional
@Override
public TOrder secKill(User user, CourseVo CourseVo) {
        ValueOperations valueOperations = redisTemplate.opsForValue();
        //从后端重新查询库存,拿到秒殺商品信息
        TSeckillCourse seckillCourse = itSeckillCourseService.getOne(new QueryWrapper<TSeckillCourse>().eq("Course_id", CourseVo.getId()));
        //库存减一
        seckillCourse.setStockCount(seckillCourse.getStockCount() - 1);
        //id没问题同时库存>0才更新
        boolean seckillCourseResult = itSeckillCourseService.update(new UpdateWrapper<TSeckillCourse>()
        .setSql("stock_count = " + "stock_count-1")
        .eq("Course_id", CourseVo.getId())
        .gt("stock_count", 0)
        );
        if (seckillCourse.getStockCount() < 1) {
        valueOperations.set("isStockEmapty:"+ CourseVo.getId(),"0");
        return null;
        }

        //生成订单
        TOrder order = new TOrder();
        order.setUserId(user.getId());
        order.setCourseId(CourseVo.getId());
        order.setDeliveryAddrId(0L);
        order.setCourseName(CourseVo.getCourseName());
        order.setCourseCount(1);
        order.setCoursePrice(seckillCourse.getSeckillPrice());
        order.setOrderChannel(1);
        order.setStatus(0);
        order.setCreateDate(new Date());
        tOrderMapper.insert(order);
        TSeckillOrder tSeckillOrder = new TSeckillOrder();
        tSeckillOrder.setUserId(user.getId());
        tSeckillOrder.setOrderId(order.getId());
        tSeckillOrder.setCourseId(CourseVo.getId());
        itSeckillOrderService.save(tSeckillOrder);
        
        //这里使用Redis缓存用户id和订单id作为联合key,在高并发时订单控制器可以先进行查询防止一个人多次
        redisTemplate.opsForValue().set("order:" + user.getId() + ":" + CourseVo.getId(),
        tSeckillOrder, 1, TimeUnit.MINUTES);
        return order;
        }

7.前端通过轮询得知抢课是否成功

function doSeckill(path) {
        $.ajax({
            url: '/seckill/'+path+'/doSeckill',
            type: "POST",
            data: {
                courseId: $('#courseId').val()
                // path:path改用注解接收
            },
            success: function (data) {
                if (data.code == 200) 
                    //使用RabbitMQ轮询时
                    getResult($("#courseId").val());
                } else {
                    layer.msg(data.message);
                }
            }, error: function () {
                layer.msg("客户端请求出错");
            }
        });
    }
    function getResult(courseId){
        g_showLoading();
        $.ajax({
            url:"/seckill/result",
            type:"GET",
            data: {
                courseId: courseId
            },
            success: function (data) {
                if (data.code == 200) {
                    var result = data.object;
                    if (result < 0) {
                        layer.msg("对不起,秒杀失败");
                    } else if (result == 0) {
                        setTimeout(function () {
                            getResult(courseId)
                        },50);
                    } else {
                        layer.confirm("恭喜您,秒杀成功!查看订单?", {btn: ["确定", "取消"]},
                            function () {
                                window.location.href = "/orderDetail.htm?orderId=" + result;
                            },
                            function () {
                                layer.close();
                            }
                        )
                    }
                }
            },
            error: function () {
                layer.msg("客户端请求错误");
            }
        });
    }
    //获取抢课结果,成功返回订单id,失败-1,正在排队0
    @RequestMapping(value = "/result",method = RequestMethod.GET)
    @ResponseBody
    public RespBean getResult(User user, Long goodsId){
        if(user == null){
            return  RespBean.error(RespBeanEnum.SESSION_ERROR);
        }
        Long orderId = seckillOrderService.getResult(user,goodsId);
        return  RespBean.success(orderId);
    }
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

miss writer

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值