Springboot 如何自动上传秒杀商品数据到Redis中上架商品

一、概述

如下图秒杀活动:

在这里插入图片描述
在这个秒杀活动中,需要自动上架一定时间段的商品,我们如何实现自动上传呢?

我们可以通过定时任务来实现的。在秒杀活动开始前,需要将商品信息存储到数据库中,并设置好库存和价格等信息。然后,可以通过定时任务的方式,每天定时从数据库中读取商品信息,并将其上传到秒杀页面上。这样,就可以实现自动上传商品的功能了。

二、Springboot定时任务配置

由于秒杀活动涉及的商品比较多,采用异步上传的方式

@EnableAsync
// 开启定时任务
@EnableScheduling
// 配置类
@Configuration
public class ScheduledConfig {
    
    

}

三、秒杀表设计

1. 秒杀场次表,主要展示哪个时间段进行秒杀

在这里插入图片描述

CREATE TABLE `kmall_coupon`.`sms_seckill_session`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '场次名称',
  `start_time` datetime(0) NULL DEFAULT NULL COMMENT '每日开始时间',
  `end_time` datetime(0) NULL DEFAULT NULL COMMENT '每日结束时间',
  `status` tinyint(1) NULL DEFAULT NULL COMMENT '启用状态',
  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '秒杀活动场次' ROW_FORMAT = Dynamic;

2 秒杀商品表,主要存储哪个时间段内进行秒杀的商品

与场次关联的字段是 promotion_session_id
在这里插入图片描述

四、具体业务实现

1. 流程图

在这里插入图片描述

2. 开启定时任务,上传商品到redis

由于是异步开启定时任务,在上架商品时采用了redisson分布式锁机制

@Slf4j
@Service
public class SeckillScheduled {
    
    


    @Autowired
    SeckillService seckillService;
    @Autowired
    RedissonClient redissonClient;

 
    /**
     * 秒杀商品定时上架,保证幂等性问题
     *  每天晚上3点,上架最近三天需要秒杀的商品
     *  当天00:00:00 - 23:59:59
     *  明天00:00:00 - 23:59:59
     *  后天00:00:00 - 23:59:59
     */
    @Scheduled(cron = "*/10 * * * * ? ")   
    public void uploadSeckillSkuLatest3Days() {
    
    
        // 重复上架无需处理
        log.info("上架秒杀的商品...");

        // 分布式锁(幂等性)
        RLock lock = redissonClient.getLock(SeckillConstant.UPLOAD_LOCK);
        try {
    
    
            lock.lock(10, TimeUnit.SECONDS);
            // 上架最近三天需要秒杀的商品
            seckillService.uploadSeckillSkuLatest3Days();
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            lock.unlock();
        }
    }
}

3. 上架最近三天需要秒杀的商品

  1. 查询最近三天需要参加秒杀的场次+商品
  2. 上架场次信息
  3. 上架商品信息
@Override
public void uploadSeckillSkuLatest3Days() {
    
    
    // 1.查询最近三天需要参加秒杀的场次+商品
    R lates3DaySession = couponFeignService.getLates3DaySession();
    if (lates3DaySession.getCode() == 0) {
    
    
        // 获取场次
        List<SeckillSessionWithSkusTO> sessions = lates3DaySession.getData("data", new TypeReference<List<SeckillSessionWithSkusTO>>() {
    
    
        });
        // 2.上架场次信息
        saveSessionInfos(sessions);
        // 3.上架商品信息
        saveSessionSkuInfo(sessions);
    }
}

2.1 查询最近三天需要参加秒杀的场次+商品

  1. 计算最近三天起止时间
  2. 查询起止时间内的秒杀场次
  3. 组合秒杀关联的商品信息
 @Override
    public List<SeckillSessionEntity> getLates3DaySession() {
    
    
        // 计算最近三天起止时间
        String startTime = DateUtils.currentStartTime();// 当天00:00:00
        String endTime = DateUtils.getTimeByOfferset(2);// 后天23:59:59

        // 查询起止时间内的秒杀场次
        List<SeckillSessionEntity> sessions = baseMapper.selectList(new QueryWrapper<SeckillSessionEntity>()
                .between("start_time", startTime, endTime));

        // 组合秒杀关联的商品信息
        if (!CollectionUtils.isEmpty(sessions)) {
    
    
            // 组合场次ID
            List<Long> sessionIds = sessions.stream().map(SeckillSessionEntity::getId).collect(Collectors.toList());
            // 查询秒杀场次关联商品信息
            Map<Long, List<SeckillSkuRelationEntity>> skuMap = seckillSkuRelationService
                    .list(new QueryWrapper<SeckillSkuRelationEntity>().in("promotion_session_id", sessionIds))
                    .stream().collect(Collectors.groupingBy(SeckillSkuRelationEntity::getPromotionSessionId));

            sessions.forEach(session -> session.setRelationSkus(skuMap.get(session.getId())));
        }
        return sessions;
    }

2.2 上架场次信息

  1. 遍历场次
  2. 判断场次是否已上架(幂等性)
  3. 封装场次信息
  4. 上架 redisTemplate.opsForList().leftPushAll(key, skuIds);
private void saveSessionInfos(List<SeckillSessionWithSkusTO> sessions) {
    
    
        if (!CollectionUtils.isEmpty(sessions)) {
    
    
            sessions.stream().forEach(session -> {
    
    
                // 1.遍历场次
                long startTime = session.getStartTime().getTime();// 场次开始时间戳
                long endTime = session.getEndTime().getTime();// 场次结束时间戳
                String key = SeckillConstant.SESSION_CACHE_PREFIX + startTime + "_" + endTime;// 场次的key

                // 2.判断场次是否已上架(幂等性)
                Boolean hasKey = redisTemplate.hasKey(key);
                if (!hasKey) {
    
    
                    // 未上架
                    // 3.封装场次信息
                    List<String> skuIds = session.getRelationSkus().stream()
                            .map(item -> item.getPromotionSessionId() + "_" + item.getSkuId().toString())
                            .collect(Collectors.toList());// skuId集合
                    // 4.上架
                    redisTemplate.opsForList().leftPushAll(key, skuIds);
                }
            });
        }
    }

2.3 上架商品信息

  1. 查询所有商品信息
  2. 将查询结果封装成Map集合
  3. 绑定秒杀商品hash
  4. 遍历场次,遍历商品,判断商品是否已上架(幂等性)
  5. 封装商品信息
  6. 上架商品(序列化成json格式存入Redis中)
  7. 上架商品的分布式信号量,key:商品随机码 值:库存(限流)
private void saveSessionSkuInfo(List<SeckillSessionWithSkusTO> sessions) {
    
    
        if (!CollectionUtils.isEmpty(sessions)) {
    
    
            // 查询所有商品信息
            List<Long> skuIds = new ArrayList<>();
            sessions.stream().forEach(session -> {
    
    
                List<Long> ids = session.getRelationSkus().stream().map(SeckillSkuVO::getSkuId).collect(Collectors.toList());
                skuIds.addAll(ids);
            });
            R info = productFeignService.getSkuInfos(skuIds);
            if (info.getCode() == 0) {
    
    
                // 将查询结果封装成Map集合
                Map<Long, SkuInfoTO> skuMap = info.getData(new TypeReference<List<SkuInfoTO>>() {
    
    
                }).stream().collect(Collectors.toMap(SkuInfoTO::getSkuId, val -> val));
                // 绑定秒杀商品hash
                BoundHashOperations<String, Object, Object> operations = redisTemplate.boundHashOps(SeckillConstant.SECKILL_CHARE_KEY);
                // 1.遍历场次
                sessions.stream().forEach(session -> {
    
    
                    // 2.遍历商品
                    session.getRelationSkus().stream().forEach(seckillSku -> {
    
    
                        // 判断商品是否已上架(幂等性)
                        String skuKey = seckillSku.getPromotionSessionId().toString() + "_" + seckillSku.getSkuId().toString();// 商品的key(需要添加场次ID前缀,同一款商品可能场次不同)
                        if (!operations.hasKey(skuKey)) {
    
    
                            // 未上架
                            // 3.封装商品信息
                            SeckillSkuRedisTO redisTo = new SeckillSkuRedisTO();// 存储到redis的对象
                            SkuInfoTO sku = skuMap.get(seckillSku.getSkuId());
                            BeanUtils.copyProperties(seckillSku, redisTo);// 商品秒杀信息
                            redisTo.setSkuInfo(sku);// 商品详细信息
                            redisTo.setStartTime(session.getStartTime().getTime());// 秒杀开始时间
                            redisTo.setEndTime(session.getEndTime().getTime());// 秒杀结束时间
                            // 商品随机码:用户参与秒杀时,请求需要带上随机码(防止恶意攻击)
                            String token = UUID.randomUUID().toString().replace("-", "");// 商品随机码(随机码只会在秒杀开始时暴露)
                            redisTo.setRandomCode(token);// 设置商品随机码

                            // 4.上架商品(序列化成json格式存入Redis中)
                            String jsonString = JSONObject.toJSONString(redisTo);
                            operations.put(skuKey, jsonString);

                            // 5.上架商品的分布式信号量,key:商品随机码 值:库存(限流)
                            RSemaphore semaphore = redissonClient.getSemaphore(SeckillConstant.SKU_STOCK_SEMAPHORE + token);
                            // 信号量(扣减成功才进行后续操作,否则快速返回)
                            semaphore.trySetPermits(seckillSku.getSeckillCount());
                        }
                    });
                });
            }
        }
    }

猜你喜欢

转载自blog.csdn.net/lovoo/article/details/131456464
今日推荐