第二十篇商城系统-秒杀功能设计与实现(代码片段)

波波烤鸭 波波烤鸭     2022-12-09     375

关键词:

秒杀服务

一、商品上架

秒杀活动的结构图

通过定时任务触发:

/**
 * 定时上架秒杀商品信息
 */
@Slf4j
@Component
public class SeckillSkuSchedule 

    @Autowired
    SeckillService seckillService;

    @Autowired
    RedissonClient redissonClient;

    /**
     *
     */
    @Async
    @Scheduled(cron = "*/5 * * * * *")
    public void uploadSeckillSku3Days()
        log.info("定时上架秒杀商品执行了...." + new Date());
        // 分布式锁
        RLock lock = redissonClient.getLock("seckill:upload:lock");
        lock.lock(10, TimeUnit.SECONDS);
        try 
            // 调用上架商品的方法
            seckillService.uploadSeckillSku3Days();
        catch (Exception e)
            lock.unlock();
        
    


进入到Service中处理

@Override
    public void uploadSeckillSku3Days() 
        // 1. 通过OpenFegin 远程调用Coupon服务中接口来获取未来三天的秒杀活动的商品
        R r = couponFeignService.getLates3DaysSession();
        if(r.getCode() == 0)
            // 表示查询操作成功
            String json = (String) r.get("data");
            List<SeckillSessionEntity> seckillSessionEntities = JSON.parseArray(json,SeckillSessionEntity.class);
            // 2. 上架商品  Redis数据保存
            // 缓存商品
            //  2.1 缓存每日秒杀的SKU基本信息
            saveSessionInfos(seckillSessionEntities);
            // 2.2  缓存每日秒杀的商品信息
            saveSessionSkuInfos(seckillSessionEntities);

        
    

/**
     * 保存每日活动的信息到Redis中
     * @param seckillSessionEntities
     */
    private void saveSessionInfos(List<SeckillSessionEntity> seckillSessionEntities) 
        for (SeckillSessionEntity seckillSessionEntity : seckillSessionEntities) 
            // 循环缓存每一个活动  key: start_endTime
            long start = seckillSessionEntity.getStartTime().getTime();
            long end = seckillSessionEntity.getEndTime().getTime();
            // 生成Key
            String key = SeckillConstant.SESSION_CHACE_PREFIX+start+"_"+end;
            Boolean flag = redisTemplate.hasKey(key);
            if(!flag)// 表示这个秒杀活动在Redis中不存在,也就是还没有上架,那么需要保存
                // 需要存储到Redis中的这个秒杀活动涉及到的相关的商品信息的SKUID
                List<String> collect = seckillSessionEntity.getRelationEntities().stream().map(item -> 
                    // 秒杀活动存储的 VALUE是 sessionId_SkuId
                    return item.getPromotionSessionId()+"_"+item.getSkuId().toString();
                ).collect(Collectors.toList());
                redisTemplate.opsForList().leftPushAll(key,collect);
            
        
    

    /**
     * 存储活动对应的 SKU信息
     * @param seckillSessionEntities
     */
    private void saveSessionSkuInfos(List<SeckillSessionEntity> seckillSessionEntities) 
        seckillSessionEntities.stream().forEach(session -> 
            // 循环取出每个Session,然后取出对应SkuID 封装相关的信息
            BoundHashOperations<String, Object, Object> hashOps = redisTemplate.boundHashOps(SeckillConstant.SKU_CHACE_PREFIX);
            session.getRelationEntities().stream().forEach(item->
                String skuKey = item.getPromotionSessionId()+"_"+item.getSkuId();
                Boolean flag = redisTemplate.hasKey(skuKey);
                if(!flag)
                    SeckillSkuRedisDto dto = new SeckillSkuRedisDto();
                    // 1.获取SKU的基本信息
                    R info = productFeignService.info(item.getSkuId());
                    if(info.getCode() == 0)
                        // 表示查询成功
                        String json = (String) info.get("skuInfoJSON");
                        dto.setSkuInfoVo(JSON.parseObject(json,SkuInfoVo.class));
                    
                    // 2.获取SKU的秒杀信息
                    /*dto.setSkuId(item.getSkuId());
                    dto.setSeckillPrice(item.getSeckillPrice());
                    dto.setSeckillCount(item.getSeckillCount());
                    dto.setSeckillLimit(item.getSeckillLimit());
                    dto.setSeckillSort(item.getSeckillSort());*/
                    BeanUtils.copyProperties(item,dto);
                    // 3.设置当前商品的秒杀时间
                    dto.setStartTime(session.getStartTime().getTime());
                    dto.setEndTime(session.getEndTime().getTime());

                    // 4. 随机码
                    String token = UUID.randomUUID().toString().replace("-","");
                    dto.setRandCode(token);
                    // 分布式信号量的处理  限流的目的
                    RSemaphore semaphore = redissonClient.getSemaphore(SeckillConstant.SKU_STOCK_SEMAPHORE + token);
                    // 把秒杀活动的商品数量作为分布式信号量的信号量
                    semaphore.trySetPermits(item.getSeckillCount().intValue());
                    hashOps.put(skuKey,JSON.toJSONString(dto));
                
            );
        );
    

启动服务,数据会被保存到Redis中

二、秒杀商品查询

  通过当前时间获取对应的秒杀活动及对应的SKU信息。

   /**
     * 查询出当前时间内的秒杀活动及对应的商品SKU信息
     * @return
     */
    @Override
    public List<SeckillSkuRedisDto> getCurrentSeckillSkus() 
        // 1.确定当前时间是属于哪个秒杀活动的
        long time = new Date().getTime();
        // 从Redis中查询所有的秒杀活动
        Set<String> keys = redisTemplate.keys(SeckillConstant.SESSION_CHACE_PREFIX + "*");
        for (String key : keys) 
            //seckill:sessions1656468000000_1656469800000
            String replace = key.replace(SeckillConstant.SESSION_CHACE_PREFIX, "");
            // 1656468000000_1656469800000
            String[] s = replace.split("_");
            Long start = Long.parseLong(s[0]); // 活动开始的时间
            Long end = Long.parseLong(s[1]); // 活动结束的时间
            if(time > start && time < end)
                // 说明的秒杀活动就是当前时间需要参与的活动
                // 取出来的是SKU的ID  2_9
                List<String> range = redisTemplate.opsForList().range(key, -100, 100);
                BoundHashOperations<String, String, String> ops = redisTemplate.boundHashOps(SeckillConstant.SKU_CHACE_PREFIX);
                List<String> list = ops.multiGet(range);
                if(list != null && list.size() > 0)
                    List<SeckillSkuRedisDto> collect = list.stream().map(item -> 
                        SeckillSkuRedisDto seckillSkuRedisDto = JSON.parseObject(item, SeckillSkuRedisDto.class);
                        return seckillSkuRedisDto;
                    ).collect(Collectors.toList());
                    return collect;
                
            
        
        return null;
    

然后定义相关的Controller接口就可以访问了

@RestController
@RequestMapping("/seckill")
public class SeckillController 

    @Autowired
    SeckillService seckillService;

    @GetMapping("/currentSeckillSessionSkus")
    public R getCurrentSeckillSessionSkus()
        List<SeckillSkuRedisDto> currentSeckillSkus = seckillService.getCurrentSeckillSkus();

        return R.ok().put("data", JSON.toJSONString(currentSeckillSkus));
    


然后对应的访问效果:

三、页面渲染

1.网关配置

首先在host中配置域名

然后在网关中配置路由信息

然后重启服务访问:

能访问到数据就表示域名配置成功

2.首页配置

  通过Ajax来访问获取秒杀的相关信息

  $.get("http://seckill.msb.com/seckill/currentSeckillSessionSkus",function(resp)
     if(resp.data.length > 0)
        // 说明有秒杀的数据
        console.log($.parseJSON(resp.data))
       $.parseJSON(resp.data).forEach(function(item)
         $("<li></li>").append("<img width='130px' height='130px' src='"+item.skuInfoVo.skuDefaultImg+"'/>")
                 .append("<p>"+item.skuInfoVo.skuSubtitle+"</p>")
                 .append("<span>"+item.seckillPrice+"</span>")
                 .append("<s>"+item.skuInfoVo.price+"</s>")
                 .appendTo("#seckillSessionContent");
       )
       /*<li>
       <img src="/static/index/img/section_second_list_img1.jpg" alt="">
               <p>花王 (Merries) 妙而舒 纸尿裤 大号 L54片 尿不湿(9-14千克) (日本官方直采) 花王 (Merries) 妙而舒 纸尿裤 大号 L54片 尿不湿(9-14千</p>
       <span>¥83.9</span><s>¥99.9</s>
     </li>*/

     
  )

展示的效果

3.商品详情

  在购买商品的时候,进入到商品详情页,如果该商品也参与了秒杀活动,那么对应的需要展示相关的信息

首先我们需要在秒杀服务中提供一个根据SKUID查询相关的秒杀活动的接口

   /**
     * 根据SKUID查询秒杀活动对应的信息
     * @param skuId
     * @return
     */
    @Override
    public SeckillSkuRedisDto getSeckillSessionBySkuId(Long skuId) 
        // 1.找到所有需要参与秒杀的商品的sku信息
        BoundHashOperations<String, String, String> ops = redisTemplate.boundHashOps(SeckillConstant.SKU_CHACE_PREFIX);
        Set<String> keys = ops.keys();
        if(keys != null && keys.size() > 0)
            String regx = "\\\\d_"+ skuId;
            for (String key : keys) 
                boolean matches = Pattern.matches(regx, key);
                if(matches)
                    // 说明找到了对应的SKU的信息
                    String json = ops.get(key);
                    SeckillSkuRedisDto dto = JSON.parseObject(json, SeckillSkuRedisDto.class);
                    return dto;
                
            
        
        return null;
    

然后在查询商品详情的时候异步查询出对应的秒杀活动信息

然后在模板页面中展示相关的信息

<div class="box-summary clear">
                    <ul>
                        <li>京东价</li>
                        <li>
                            <span></span>
                            <span th:text="$#numbers.formatDecimal(item.info.price,3,2)">4499.00</span>
                        </li>
                        <li style="color: red">
                            <span th:if="$#dates.createNow().getTime() < item.seckillVO.startTime">
                                商品将在:[[$#dates.format(new java.util.Date(item.seckillVO.startTime),'yyyy-MM-dd HH:mm:ss')]] 开始秒杀
                            </span>
                            <span th:if="$#dates.createNow().getTime() > item.seckillVO.startTime
                             && #dates.createNow().getTime() < item.seckillVO.endTime ">
                                秒杀价: [[$#numbers.formatDecimal(item.seckillVO.seckillPrice,1,2)]]
                            </span>
                        </li>
                        <li>
                            <a href="/static/item/">
                                预约说明
                            </a>
                        </li>
                    </ul>
                </div>

首页调整到商品详情页

function goItem(skuId)
    location.href="http://item.msb.com/"+skuId+".html"
  

  $.get("http://seckill.msb.com/seckill/currentSeckillSessionSkus",function(resp)
     if(resp.data.length > 0)
        // 说明有秒杀的数据
        console.log($.parseJSON(resp.data))
       $.parseJSON(resp.data).forEach(function(item)
         $("<li οnclick='goItem("+item.skuId+")'></li>")
                 .append("<img width='130px' height='130px' src='"+item.skuInfoVo.skuDefaultImg+"'/>")
                 .append("<p>"+item.skuInfoVo.skuSubtitle+"</p>")
                 .append("<span>"+item.seckillPrice+"</span>")
                 .append("<s>"+item.skuInfoVo.price+"</s>")
                 .appendTo("#seckillSessionContent");
       )
       /*<li>
       <img src="/static/index/img/section_second_list_img1.jpg" alt="">
               <p>花王 (Merries) 妙而舒 纸尿裤 大号 L54片 尿不湿(9-14千克) (日本官方直采) 花王 (Merries) 妙而舒 纸尿裤 大号 L54片 尿不湿(9-14千</p>
       <span>¥83.9</span><s>¥99.9</s>
     </li>*/

     
  )

四、秒杀活动

1.秒杀活动关注点

  秒杀活动的最大特点就是高并发而且是短时间内的高并发,那么对我们的服务要求就非常高,针对这种情况所产生的共性问题,对应的解决方案:

2. 秒杀服务前端

  当我们点击 秒杀抢购按钮后,对应我们需要把当前的商品信息提交到后端服务。活动编号+“_”+SkuId,Code随机码,抢购商品的数量。

<div class="box-btns-two" th:if="$#dates.createNow().getTime() < item.seckillVO.startTime
                            || #dates.createNow().getTime() >  item.seckillVO.endTime ">
                        <a href="#" id="addCart" th:attr="skuId=$item.info.skuId">
                            加入购物车
                        </a>
                    </div>
                    <div class="box-btns-two" th:if="$#dates.createNow().getTime() > item.seckillVO.startTime
                             && #dates.createNow().getTime() < item.seckillVO.endTime ">
                        <a href="#" id="seckillId" th:attr="skuId=$item.info.skuId,sessionId=$item.seckillVO.promotionSessionId,code=$item.seckillVO.randCode">
                            抢购商品
                        </a>
                    </div>

对应的js操作

$("#seckillId").click(function()
        var isLogin = [[$session.loginUser !=null]]
        if(isLogin)
            // 1. 获取活动编号和SkuId 2_10
            var killId = $(this).attr("sessionId") + "_" + $(this).attr("skuId");
            // 2. 获取对应的随机码
            var code = $(this).attr("code");
            // 3. 获取秒杀的商品数量
            var num = $("#numInput").val();
            location.href="http://seckill.msb.com/seckill/kill?killId="+killId + "&code="+code+"&num="+num;
        else
            alert("请先登录才能参加秒杀活动!!!");
        

        return false;
    );

访问测试:

3.后端逻辑处理

  前端提交的秒杀请求,在后端具体的处理

3.1 登录校验

  秒杀活动必须是在登录状态下进行的,如果没有认证就不让秒杀。这时我们需要整合进来SpringSession。

        <dependency>
            <groupId>org.springframework.session</groupId>
            <artifactId>spring-session-data-redis</artifactId>
        </dependency>

然后添加对应的配置信息

查看详情

第二十篇-如何写配置文件(代码片段)

配置文件不同的格式所用不同函数,可能可以优化WriteData.javapackagecom.example.aimee.logtest;importandroid.os.Build;importandroid.support.annotation.RequiresApi;importandroid.util.Log;importjava.io.BufferedReader;importjava. 查看详情

第二十二篇商城系统-skywalking链路追踪商城系统完结篇(代码片段)

Skywalkingskywalking是一个apm系统,包含监控,追踪,并拥有故障诊断能力的分布式系统一、Skywalking介绍1.什么是SkyWalking  Skywalking是由国内开源爱好者吴晟开源并提交到Apache孵化器的产品,它同时吸收了Zipkin/Pinpoint... 查看详情

第二十二篇商城系统-skywalking链路追踪商城系统完结篇(代码片段)

Skywalkingskywalking是一个apm系统,包含监控,追踪,并拥有故障诊断能力的分布式系统一、Skywalking介绍1.什么是SkyWalking  Skywalking是由国内开源爱好者吴晟开源并提交到Apache孵化器的产品,它同时吸收了Zipkin/Pinpoint... 查看详情

第二十篇flowable中的任务回退(代码片段)

Flowable中的任务回退1.串行的回退  我们先从最简单的串行流程来分析,案例如下完整的xml文件内容:<?xmlversion="1.0"encoding="UTF-8"?><definitionsxmlns="http://www.omg.org/spec/BPMN/2010052 查看详情

第二十篇flowable中的任务回退(代码片段)

Flowable中的任务回退1.串行的回退  我们先从最简单的串行流程来分析,案例如下完整的xml文件内容:<?xmlversion="1.0"encoding="UTF-8"?><definitionsxmlns="http://www.omg.org/spec/BPMN/2010052 查看详情

c++从青铜到王者第二十篇:stl之setmapmultisetmultimap的初识(代码片段)

系列文章目录文章目录系列文章目录前言一、关联式容器二、键值对三、树形结构的关联式容器四、set的介绍和使用1.set的介绍2.set的使用1.set的模板参数列表2.set的构造3.set的容量4.set的修改操作5.set的迭代器五、map的介绍和使用1... 查看详情

网上商城购物系统设计与实现(java+web+ssm+mysql)(代码片段)

目录1绪论11.1研究背景11.2目的和意义11.3开发工具及技术12需求分析32.1功能需求分析32.1.1网站前台功能32.1.2网站后台功能32.2性能分析32.3系统用户用例图43系统设计53.1系统的总体设计53.2数据库的分析与设计53.2.1数据库概念设计63.2.... 查看详情

第十篇商城系统-性能测试(代码片段)

系统性能压力测试一、压力测试  压力测试是给软件不断加压,强制其在极限的情况下运行,观察它可以运行到何种程度,从而发现性能缺陷,是通过搭建与实际环境相似的测试环境,通过测试程序在同一... 查看详情

秒杀系统的设计与实现(限时抢购抢救接口单用户限制实现)(代码片段)

...器直接搞炸,要开始关心一些细节问题。现在设计的系统还有一些问题:我们应该在一定的时间内执行秒杀处理,不能再任意时间都接受秒杀请求。如何加入时间验证?对于稍微懂点电脑的,又会动歪脑筋的人来说开始通过... 查看详情

秒杀系统——秒杀功能设计理念(代码片段)

文章目录电商系统下单功能概述订单量:从0到1000(架构1)订单量:从1000到100万锁机制悲观锁乐观锁分布式锁消息队列消息队列:架构2从电商系统到秒杀系统流量限制热门资源隔离最终:架构3总结1.秒杀... 查看详情

秒杀系统——秒杀功能设计理念(代码片段)

文章目录电商系统下单功能概述订单量:从0到1000(架构1)订单量:从1000到100万锁机制悲观锁乐观锁分布式锁消息队列消息队列:架构2从电商系统到秒杀系统流量限制热门资源隔离最终:架构3总结1.秒杀... 查看详情

小刘同学的第二十篇博文

我有罪。。。昨天遗留的数据库的问题还没有解决,而且今天也没写js代码……这两天主要在看DOM的视频,其实DOM的用法大概懂一点,但是一直没有系统完整的去学。。。还是对着文档来写吧,就这样听老师讲了一遍其实印象并... 查看详情

第二十一篇商城系统-服务熔断降级sentinel(代码片段)

Sentinel一、熔断、降级、限流1.熔断  服务熔断的作用类似于我们家用的保险丝,当某服务出现不可用或响应超时的情况时,为了防止整个系统出现雪崩,暂时停止对该服务的调用。停止是说,当前服务一旦对下... 查看详情

第二十一篇商城系统-服务熔断降级sentinel(代码片段)

Sentinel一、熔断、降级、限流1.熔断  服务熔断的作用类似于我们家用的保险丝,当某服务出现不可用或响应超时的情况时,为了防止整个系统出现雪崩,暂时停止对该服务的调用。停止是说,当前服务一旦对下... 查看详情

秒杀系统的设计与实现接口限流方案(代码片段)

...:是对某一时间窗口内的请求数进行限制,保持系统的可用性和稳定性,防止因流量暴增而导致的系 查看详情

linux篇第二十篇——http协议(认识协议+http协议+https)(代码片段)

⭐️本篇博客开始给大家介绍应用层的一个协议——HTTP协议,我会带大家先认识协议,再谈HTTP协议的格式、报头、状态码和方法等相关细节,我还会简单介绍一下在他基础之上扩增一层软件层的协议——HTTPS,它... 查看详情

学子商城网站的设计与实现(代码片段)

作者主页:编程指南针作者简介:Java领域优质创作者、CSDN博客专家、掘金特邀作者、多年架构师设计经验、腾讯课堂常驻讲师主要内容:Java项目、毕业设计、简历模板、学习资料、面试题库、技术互助文末获取源码... 查看详情