• 【业务功能篇105】 微服务-springcloud-springboot-电商订单模块--秒杀服务-定时任务【上篇】


    秒杀服务

    一、商品上架

    秒杀活动的结构图

    image.png

    通过定时任务触发:

    • 定时任务由spring提供,需要通过注解开启,这里通过定义一个配置类,注入spring,对其配置类进行相应的注解,当然也可以注解放在我们的服务启动类上
    • cron表达式定时示例

    0 * * * * ? 每1分钟触发一次
    0 0 * * * ? 每天每1小时触发一次
    0 0 10 * * ? 每天10点触发一次
    0 * 14 * * ? 在每天下午2点到下午2:59期间的每1分钟触发
    0 30 9 1 * ? 每月1号上午9点半
    0 15 10 15 * ? 每月15日上午10:15触发
    */5 * * * * ? 每隔5秒执行一次
    0 */1 * * * ? 每隔1分钟执行一次
    0 0 5-15 * * ? 每天5-15点整点触发
    0 0/3 * * * ? 每三分钟触发一次
    0 0 0 1 * ? 每月1号凌晨执行一次

    对照上面的字段含义写自定义的cron时间表达式基本就ok了,写完后可以到 http://cron.qqe2.com/ 验证下。

    注意:"0 0/50 * * * ?"这个表达式很多人会认为是每隔50分钟执行,实际不是,会每个小时的50分、60分钟跑一次,例如1:50,2:00,2:50,3:00……

    //开启异步,还需要在具体的任务方法加@Async表示需要异步 ,定时任务默认是同步的,主要是为了定时可以按时跑起来,比如说我们任务是每10秒执行一次,但是任务逻辑跑完不止10秒,这样就需要等任务执行完再接着跑,这样频率就没有达到预期效果,所以我们开启异步,多线程去每隔10秒运行任务,即使任务10秒内没跑完也会开启其他线程来跑,而这里的线程池的数量,是系统默认给的,这里可以通过配置参数在yaml文件自定义
    //spring.task.execution.pool.core-size=5  .max-size=20 进行设置
    @EnableAsync
    
    //开启定时功能  还需要在具体扥任务方法加@Scheduled表示定时频率
    @EnableScheduling
    @Configuration
    public class ScheduleConfig {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    /**
     * 定时上架秒杀商品信息
     */
    @Slf4j
    @Component
    public class SeckillSkuSchedule {
    
        @Autowired
        SeckillService seckillService;
    
        @Autowired
        RedissonClient redissonClient;
    
        /**
         *
         */
        @Async
        //每5s执行一次
        @Scheduled(cron = "*/5 * * * * *")
        public void uploadSeckillSku3Days(){
            log.info("定时上架秒杀商品执行了...." + new Date());
            // 分布式锁 避免多节点多集群场景下,会重复执行,先提供一个分布式锁,使各个节点通过这种获取锁方式来执行,当然拿到锁还是会去重复执行的,所以接着再业务方法里头还要通过逻辑判断去过滤处理,比如已经存储在redis的key 那么就不再执行,不存在的说明还没执行就进行插入 if(!key)
            RLock lock = redissonClient.getLock("seckill:upload:lock");
            lock.lock(10, TimeUnit.SECONDS);
            try {
                // 调用上架商品的方法
                seckillService.uploadSeckillSku3Days();
            }catch (Exception e){
                lock.unlock();
            }
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    进入到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));
                    }
                });
            });
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84

    通过OpenFegin 远程调用Coupon服务中接口来获取未来三天的秒杀活动的商品
    在秒杀服务中,创建fegin调用接口,去调用Coupon的接口服务

    package com.msb.mall.feign;
    @FeignClient("mall-coupon")
    public interface CouponFeignService {
    
      @GetMapping("/coupon/seckillsession/getLates3DaysSession")
      public R getLates3DaysSession();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    Coupon服务也就是会员服务中创建接口,获取会员可以调用的 秒杀活动商品信息
    controller层

    
    @RestController
    @RequestMapping("coupon/seckillsession")
    public class SeckillSessionController {
          @Autowired
        private SeckillSessionService seckillSessionService;
    
        @GetMapping("/getLates3DaysSession")
        public R getLates3DaysSession(){
            List<SeckillSessionEntity> lates3DaysSession = seckillSessionService.getLates3DaysSession();
            String json = JSON.toJSONString(lates3DaysSession);
            return R.ok().put("data",json);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    service层

    @Service("seckillSessionService")
    public class SeckillSessionServiceImpl extends ServiceImpl<SeckillSessionDao, SeckillSessionEntity> implements SeckillSessionService {
    
        @Autowired
        SeckillSkuRelationService relationService;
    
     @Override
        public List<SeckillSessionEntity> getLates3DaysSession() {
            // 计算未来3天的时间
            List<SeckillSessionEntity> list =  this.list(new QueryWrapper<SeckillSessionEntity>().
                    between("start_time",startTime(),endTime()));
            List<SeckillSessionEntity> newList = list.stream().map(session -> {
                // 根据对应的sessionId活动编号查询出对应的活动商品信息
                List<SeckillSkuRelationEntity> relationEntities = relationService.list(new QueryWrapper<SeckillSkuRelationEntity>()
                        .eq("promotion_session_id", session.getId()));
                session.setRelationEntities(relationEntities);
                return session;
            }).collect(Collectors.toList());
            return newList;
        }
    
        private String startTime(){
            LocalDate now = LocalDate.now();
            LocalDate startDay = now.plusDays(0);
            LocalTime min = LocalTime.MIN;
            LocalDateTime start = LocalDateTime.of(startDay, min);
            return start.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        }
    
        private String endTime(){
            LocalDate now = LocalDate.now();
            LocalDate endDay = now.plusDays(2);
            LocalTime max = LocalTime.MAX;
            LocalDateTime end = LocalDateTime.of(endDay, max);
            return end.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

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

    image.png

    image.png

    二、秒杀商品查询

      通过当前时间获取对应的秒杀活动及对应的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;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    然后定义相关的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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    然后对应的访问效果:

    image.png

    三、页面渲染

    1.网关配置

    首先在host中配置域名

    image.png

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

    image.png

    然后重启服务访问:

    image.png

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

    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){
             $("
  • "
    ).append("") .append("

    "+item.skuInfoVo.skuSubtitle+"

    "
    ) .append(""+item.seckillPrice+"") .append(""+item.skuInfoVo.price+"") .appendTo("#seckillSessionContent"); }) /*
  • 花王 (Merries) 妙而舒 纸尿裤 大号 L54片 尿不湿(9-14千克) (日本官方直采) 花王 (Merries) 妙而舒 纸尿裤 大号 L54片 尿不湿(9-14千

    ¥83.9¥99.9
  • */
    } })
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    展示的效果

    image.png

    3.商品详情

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

    image.png

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

       /**
         * 根据SKUID查询秒杀活动对应的信息
         * @param skuId
         * @return
         */
        @Override
        public SeckillSkuRedisDto getSeckillSessionBySkuId(Long skuId) {
            // 1.找到所有需要参与秒杀的商品的sku信息
            BoundHashOperations ops = redisTemplate.boundHashOps(SeckillConstant.SKU_CHACE_PREFIX);
            Set 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;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

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

    image.png

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

    <div class="box-summary clear">
                        <ul>
                            <li>京东价li>
                            <li>
                                <span>span>
                                <span th:text="${#numbers.formatDecimal(item.info.price,3,2)}">4499.00span>
                            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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    首页调整到商品详情页

    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){
             $("
  • "
    ) .append("") .append("

    "+item.skuInfoVo.skuSubtitle+"

    "
    ) .append(""+item.seckillPrice+"") .append(""+item.skuInfoVo.price+"") .appendTo("#seckillSessionContent"); }) /*
  • 花王 (Merries) 妙而舒 纸尿裤 大号 L54片 尿不湿(9-14千克) (日本官方直采) 花王 (Merries) 妙而舒 纸尿裤 大号 L54片 尿不湿(9-14千

    ¥83.9¥99.9
  • */
    } })
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
  • 相关阅读:
    电气走线——部件、线缆、线号、端子排
    rsync下行同步+inotify实时同步部署
    成都瀚网科技有限公司:抖音小店选品策略引领电商潮流
    练习26-30:多表关联查询,子查询
    模拟 Junit 框架
    Python通过selenium调用IE11浏览器报错解决方法
    0.网络是怎么连接的 --- 前言
    水质查询接口
    我为什么不喜欢关电脑?
    机器人控制——C++ HSM状态机基础知识
  • 原文地址:https://blog.csdn.net/studyday1/article/details/132814256