栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

谷粒商城项目(学习笔记十六)

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

谷粒商城项目(学习笔记十六)

第十三章:秒杀服务 一、秒杀后台

1)、添加网关

        - id: coupon_route
          uri: lb://gulimall-coupon
          predicates:
            - Path=/api/coupon
    @RequestMapping("/list")
    public R list(@RequestParam Map params){
        PageUtils page = seckillSkuRelationService.queryPage(params);

        return R.ok().put("page", page);
    }

4)、测试后台秒杀服务的增删改查功能

二、秒杀上架

1)、配置定时推送功能

@EnableAsync
@EnableScheduling
@Configuration
public class ScheduledConfig {
}

2)、定时推送秒杀

@Slf4j
@Service
public class SeckillSkuScheduled {

    @Autowired
    SeckillService seckillService;

    @Autowired
    RedissonClient redissonClient;

    private  final String  upload_lock = "seckill:upload:lock";

    //TODO 幂等性处理
//    @Scheduled(cron = "*/3 * * * * ?")
    @Scheduled(cron = "0 * * * * ?") //每分钟执行一次吧,上线后调整为每天晚上3点执行
//    @Scheduled(cron = "0 0 3 * * ?") 线上模式
    public void uploadSeckillSkuLatest3Days(){
        //1、重复上架无需处理
        log.info("上架秒杀的商品信息...");
        // 分布式锁。锁的业务执行完成,状态已经更新完成。释放锁以后。其他人获取到就会拿到最新的状态。
        RLock lock = redissonClient.getLock(upload_lock);
        lock.lock(10, TimeUnit.SECONDS);
        try{
            seckillService.uploadSeckillSkuLatest3Days();
        }finally {
            lock.unlock();
        }

    }

}

3)、上架秒杀功能

    @Override
    public void uploadSeckillSkuLatest3Days() {
        //1、扫描最近三天需要参与秒杀的活动
        R session = couponFeignService.getLates3DaySession();
        if (session.getCode() == 0) {
            //上架商品
            List sessionData = session.getData(new TypeReference>() {
            });
            //缓存到redis
            //1、缓存活动信息
            saveSessionInfos(sessionData);
            //2、缓存活动的关联商品信息
            saveSessionSkuInfos(sessionData);
        }

    }

4)、获取最近三天的秒杀

    @GetMapping("/lates3DaySession")
    public R getLates3DaySession(){
        List sessions = seckillSessionService.getLates3DaySession();
        return R.ok().setData(sessions);
    }

5)、缓存活动信息

    private void saveSessionInfos(List sesssions) {
        if (sesssions != null){
            sesssions.stream().forEach(sesssion -> {

                Long startTime = sesssion.getStartTime().getTime();
                Long endTime = sesssion.getEndTime().getTime();
                String key = SESSIONS_CACHE_PREFIX + startTime + "_" + endTime;
                Boolean hasKey = redisTemplate.hasKey(key);
                if (!hasKey) {
                    List collect = sesssion.getRelationSkus().stream().map(item -> item.getPromotionSessionId() + "_" + item.getSkuId().toString()).collect(Collectors.toList());
                    //缓存活动信息
                    redisTemplate.opsForList().leftPushAll(key, collect);
                    //TODO 设置过期时间[已完成]
                    redisTemplate.expireAt(key, new Date(endTime));
                }


            });
        }

    }

6)、缓存活动的关联商品信息

 private void saveSessionSkuInfos(List sesssions) {
        if (sesssions != null){
            sesssions.stream().forEach(sesssion -> {
                //准备hash操作
                BoundHashOperations ops = redisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX);
                sesssion.getRelationSkus().stream().forEach(seckillSkuVo -> {
                    //4、随机码?  seckill?skuId=1&key=dadlajldj;
                    String token = UUID.randomUUID().toString().replace("-", "");

                    if (!ops.hasKey(seckillSkuVo.getPromotionSessionId().toString() + "_" + seckillSkuVo.getSkuId().toString())) {
                        //缓存商品
                        SecKillSkuRedisTo redisTo = new SecKillSkuRedisTo();
                        //1、sku的基本数据
                        R skuInfo = productFeignService.getSkuInfo(seckillSkuVo.getSkuId());
                        if (skuInfo.getCode() == 0) {
                            SkuInfoVo info = skuInfo.getData("skuInfo", new TypeReference() {
                            });
                            redisTo.setSkuInfo(info);
                        }

                        //2、sku的秒杀信息
                        BeanUtils.copyProperties(seckillSkuVo, redisTo);

                        //3、设置上当前商品的秒杀时间信息
                        redisTo.setStartTime(sesssion.getStartTime().getTime());
                        redisTo.setEndTime(sesssion.getEndTime().getTime());

                        redisTo.setRandomCode(token);
                        String jsonString = JSON.toJSonString(redisTo);
                        //TODO 每个商品的过期时间不一样。所以,我们在获取当前商品秒杀信息的时候,做主动删除,代码在 getSkuSeckillInfo 方法里面
                        ops.put(seckillSkuVo.getPromotionSessionId().toString() + "_" + seckillSkuVo.getSkuId().toString(), jsonString);
                        //如果当前这个场次的商品的库存信息已经上架就不需要上架
                        //5、使用库存作为分布式的信号量  限流;
                        RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + token);
                        //商品可以秒杀的数量作为信号量
                        semaphore.trySetPermits(seckillSkuVo.getSeckillCount());
                        //TODO 设置过期时间。
                        semaphore.expireAt(sesssion.getEndTime());
                    }
                });
            });
    }
    }
三、秒杀上架

1)、获取秒杀信息的json

    
    @ResponseBody
    @GetMapping("/currentSeckillSkus")
    public R getCurrentSeckillSkus(){
        log.info("currentSeckillSkus正在执行。。。");

        List vos = seckillService.getCurrentSeckillSkus();
        return R.ok().setData(vos);
    }

2)、在首页添加秒杀信息

    function to_href(skuId){
     location.href = "http://item.gulimall.com/"+skuId+".html";
    }
    $.get("http://seckill.gulimall.com/currentSeckillSkus",function(resp){
        if(resp.data.length > 0){
            resp.data.forEach(function(item){

                $("
  • ") .append($("")) .append($("

    "+item.skuInfo.skuTitle+"

    ")) .append($(""+item.seckillPrice+"")) .append($(""+item.skuInfo.price+"")) .appendTo("#seckillSkuContent"); }) } });

    3)、秒杀页面的渲染

        @ResponseBody
        @GetMapping("/sku/seckill/{skuId}")
        public R getSkuSeckillInfo(@PathVariable("skuId") Long skuId){
    
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            SecKillSkuRedisTo to =  seckillService.getSkuSeckillInfo(skuId);
            return R.ok().setData(to);
        }
    

    4)、查询当前商品是否参与秒杀

            //3、查询当前sku是否参与秒杀优惠
            CompletableFuture secKillFuture = CompletableFuture.runAsync(() -> {
                R seckillInfo = seckillFeignService.getSkuSeckillInfo(skuId);
                if (seckillInfo.getCode() == 0) {
                    SeckillInfoVo seckillInfoVo = seckillInfo.getData(new TypeReference() {
                    });
                    skuItemVo.setSeckillInfo(seckillInfoVo);
                }
            }, executor);
    

    如果有秒杀,就在价格旁进行回显

    四、秒杀流程

    1)、登录验证

    1.引入依赖

    2.配置应用session和redis

    3.添加拦截器和拦截配置

    2)、秒杀业务

        @GetMapping("/kill")
        public String secKill(@RequestParam("killId") String killId,
                              @RequestParam("key") String key,
                              @RequestParam("num") Integer num,
                              Model model){
    
           String orderSn =  seckillService.kill(killId,key,num);
    
            model.addAttribute("orderSn",orderSn);
            //1、判断是否登录
            return "success";
        }
    

    3)、业务实现

      // TODO 上架秒杀商品的时候,每一个数据都有过期时间。
        // TODO 秒杀后续的流程,简化了收货地址等信息。
        @Override
        public String kill(String killId, String key, Integer num) {
    
            long s1 = System.currentTimeMillis();
            MemberRespVo respVo = LoginUserInterceptor.loginUser.get();
    
            //1、获取当前秒杀商品的详细信息
            BoundHashOperations hashOps = redisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX);
    
            String json = hashOps.get(killId);
            if (StringUtils.isEmpty(json)) {
                return null;
            } else {
                SecKillSkuRedisTo redis = JSON.parseObject(json, SecKillSkuRedisTo.class);
                //校验合法性
                Long startTime = redis.getStartTime();
                Long endTime = redis.getEndTime();
                long time = new Date().getTime();
    
                long ttl = endTime - time;
    
                //1、校验时间的合法性
                if (time >= startTime && time <= endTime) {
                    //2、校验随机码和商品id
                    String randomCode = redis.getRandomCode();
                    String skuId = redis.getPromotionSessionId() + "_" + redis.getSkuId();
                    if (randomCode.equals(key) && killId.equals(skuId)) {
                        //3、验证购物数量是否合理
                        if (num <= redis.getSeckillLimit()) {
                            //4、验证这个人是否已经购买过。幂等性; 如果只要秒杀成功,就去占位。  userId_SessionId_skuId
                            //SETNX
                            String redisKey = respVo.getId() + "_" + skuId;
                            //自动过期
                            Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(redisKey, num.toString(), ttl, TimeUnit.MILLISECONDS);
                            if (aBoolean) {
                                //占位成功说明从来没有买过
                                RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + randomCode);
                                //120  20ms
                                boolean b = semaphore.tryAcquire(num);
                                if (b) {
                                    //秒杀成功;
                                    //快速下单。发送MQ消息  10ms
                                    String timeId = IdWorker.getTimeId();
                                    SeckillOrderTo orderTo = new SeckillOrderTo();
                                    orderTo.setOrderSn(timeId);
                                    orderTo.setMemberId(respVo.getId());
                                    orderTo.setNum(num);
                                    orderTo.setPromotionSessionId(redis.getPromotionSessionId());
                                    orderTo.setSkuId(redis.getSkuId());
                                    orderTo.setSeckillPrice(redis.getSeckillPrice());
                                    rabbitTemplate.convertAndSend("order-event-exchange", "order.seckill.order", orderTo);
                                    long s2 = System.currentTimeMillis();
                                    log.info("耗时...{}", (s2 - s1));
                                    return timeId;
                                }
                                return null;
    
                            } else {
                                //说明已经买过了
                                return null;
                            }
    
                        }
                    } else {
                        return null;
                    }
    
                } else {
                    return null;
                }
            }
    
    
            return null;
        }

    4)、监听秒杀订单

    @Slf4j
    @RabbitListener(queues = "order.seckill.order.queue")
    @Component
    public class OrderSeckillListener {
    
        @Autowired
        OrderService orderService;
        @RabbitHandler
        public void listener(SeckillOrderTo seckillOrder, Channel channel, Message message) throws IOException {
    
            try{
                log.info("准备创建秒杀单的详细信息。。。");
                orderService.createSeckillOrder(seckillOrder);
                //手动调用支付宝收单;
                channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
            }catch (Exception e){
                channel.basicReject(message.getMessageProperties().getDeliveryTag(),true);
            }
    
        }
    }

    转载请注明:文章转载自 www.mshxw.com
    本文地址:https://www.mshxw.com/it/686969.html
    我们一直用心在做
    关于我们 文章归档 网站地图 联系我们

    版权所有 (c)2021-2022 MSHXW.COM

    ICP备案号:晋ICP备2021003244-6号