package com.alatus.mall.seckill.service.impl; import com.alatus.common.to.mq.SeckillOrderTo; import com.alatus.common.utils.R; import com.alatus.common.vo.MemberRespVo; import com.alatus.mall.seckill.constant.SecKillConstants; import com.alatus.mall.seckill.feign.CouponFeignService; import com.alatus.mall.seckill.feign.ProductFeignService; import com.alatus.mall.seckill.interceptor.LoginUserInterceptor; import com.alatus.mall.seckill.service.SecKillService; import com.alatus.mall.seckill.to.SecKillSkuRedisTo; import com.alatus.mall.seckill.vo.SeckillPageVo; import com.alatus.mall.seckill.vo.SeckillSessionEntityVo; import com.alatus.mall.seckill.vo.SkuInfoVo; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.TypeReference; import org.apache.commons.lang.StringUtils; import com.baomidou.mybatisplus.core.toolkit.IdWorker; import org.redisson.api.RSemaphore; import org.redisson.api.RedissonClient; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.BoundHashOperations; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service; import java.util.*; import java.util.concurrent.TimeUnit; import java.util.regex.Pattern; import java.util.stream.Collectors; @Service public class SecKillServiceImpl implements SecKillService { @Autowired private CouponFeignService couponFeignService; @Autowired private ProductFeignService productFeignService; @Autowired private StringRedisTemplate redisTemplate; @Autowired private RedissonClient redissonClient; @Autowired private RabbitTemplate rabbitTemplate; @Override public void uploadSecKillSkuLatest3Days() { // 扫描需要参与秒杀的活动 R daySession = couponFeignService.Latest3DaySession(); if(daySession.getCode() == 0){ List<SeckillSessionEntityVo> seckillSessionVos = daySession.getData(new TypeReference<List<SeckillSessionEntityVo>>() {}); // 缓存活动信息 saveSessionInfos(seckillSessionVos); // 缓存活动关联的商品信息 saveSkuInfos(seckillSessionVos); } } // 返回当前事件可以参与的秒杀商品信息 @Override public List<SecKillSkuRedisTo> getCurrentSeckillSkus() { long time = new Date().getTime(); Set<String> keys = redisTemplate.keys(SecKillConstants.SESSION_CACHE_PREFIX + "*"); List<Object> currentSeckillSkus = new ArrayList<>(); if (keys != null) { for (String key : keys) { String replace = key.replace(SecKillConstants.SESSION_CACHE_PREFIX, ""); String[] tempString = replace.split(":"); String timeString = tempString[1]; String[] timeSplit = timeString.split("-"); long start = Long.parseLong(timeSplit[0]); long end = Long.parseLong(timeSplit[1]); if(time >= start && time <= end){ List<String> range = redisTemplate.opsForList().range(key, -100, 100); BoundHashOperations<String, String, Object> operations = redisTemplate.boundHashOps(SecKillConstants.SKU_CACHE_PREFIX); List<Object> strings = null; if (range != null) { strings = operations.multiGet(range); } if(strings != null && !strings.isEmpty()){ currentSeckillSkus.addAll(strings); } } } } if(!currentSeckillSkus.isEmpty()){ // 遍历获取到的JSON集合并转换为对象,并完整添加进去方便展示出来 return currentSeckillSkus.stream().map(item -> { SecKillSkuRedisTo secKillSkuRedisTo = JSON.parseObject(item.toString(), SecKillSkuRedisTo.class); // 屏蔽掉随机码 secKillSkuRedisTo.setRandomCode(null); return secKillSkuRedisTo; }).collect(Collectors.toList()); } else{ return null; } } @Override public SecKillSkuRedisTo getSkuSeckillInfo(Long skuId) { // 找到所有需要参与秒杀的SKU的Key信息 BoundHashOperations<String, String, String> operations = redisTemplate.boundHashOps(SecKillConstants.SKU_CACHE_PREFIX); Set<String> keys = operations.keys(); if (keys != null && !keys.isEmpty()) { String regx = "\\d_" +skuId; for (String key : keys) { if(Pattern.matches(regx,key)){ String json = operations.get(key); SecKillSkuRedisTo secKillSkuRedisTo = JSON.parseObject(json, SecKillSkuRedisTo.class); if(secKillSkuRedisTo.getEndTime() > new Date().getTime()){ if(secKillSkuRedisTo.getStartTime() > new Date().getTime()){ secKillSkuRedisTo.setRandomCode(null); } return secKillSkuRedisTo; } } } } return null; } @Override public SeckillPageVo kill(String killId, String key, Integer num) { MemberRespVo memberRespVo = LoginUserInterceptor.loginUser.get(); // 获取当前秒杀商品的详细信息 BoundHashOperations<String, String, String> operations = redisTemplate.boundHashOps(SecKillConstants.SKU_CACHE_PREFIX); String skuInfo = operations.get(killId); // 没找到这么一个秒杀商品 if(!StringUtils.isEmpty(skuInfo)){ SecKillSkuRedisTo secKillSkuRedisTo = JSON.parseObject(skuInfo, SecKillSkuRedisTo.class); Long startTime = secKillSkuRedisTo.getStartTime(); Long endTime = secKillSkuRedisTo.getEndTime(); long time = new Date().getTime(); // 在活动时间范围内 if(time >= startTime && time <= endTime){ // 拼接商品ID String skuId = secKillSkuRedisTo.getPromotionSessionId()+"_"+secKillSkuRedisTo.getSkuId(); // 商品随机码 String randomCode = secKillSkuRedisTo.getRandomCode(); // 校验随机码和商品ID if(key.equals(randomCode) && killId.equals(skuId)){ // 验证购物数量是否合理 if(secKillSkuRedisTo.getSeckillLimit().intValue() >= num){ String redisKey = memberRespVo.getId() + "_" + skuId; // 验证这个人是否已经购买过,幂等性处理,如果秒杀成功,就去redis占位 // 而且这个占位必须让它自动过期,不然会一直占用redis的资源 long ttl = endTime - time; Boolean check = redisTemplate.opsForValue().setIfAbsent(redisKey, num.toString(), ttl, TimeUnit.MILLISECONDS); // 如果这里可以占位成功说明,当前用户没有在该场次购买过该商品 if(check){ RSemaphore semaphore = redissonClient.getSemaphore(SecKillConstants.SKU_STOCK_SEMAPHORE + randomCode); boolean acquire = semaphore.tryAcquire(num); // 秒杀成功以后,我们就可以做一个快速下单,发送一个MQ消息 if(acquire){ String orderSn = IdWorker.getTimeId(); SeckillOrderTo secKillOrderTo = new SeckillOrderTo(); BeanUtils.copyProperties(secKillSkuRedisTo,secKillOrderTo); secKillOrderTo.setOrderSn(orderSn); secKillOrderTo.setMemberId(memberRespVo.getId()); secKillOrderTo.setNum(num); // TODO 发送MQ消息 rabbitTemplate.convertAndSend("order-event-exchange","order.seckill.order",secKillOrderTo); SeckillPageVo seckillPageVo = new SeckillPageVo(); seckillPageVo.setOrderSn(orderSn); BeanUtils.copyProperties(secKillSkuRedisTo,seckillPageVo); BeanUtils.copyProperties(secKillSkuRedisTo.getSkuInfo(),seckillPageVo); return seckillPageVo; } } } } } } return null; } private void saveSessionInfos(List<SeckillSessionEntityVo> seckillSessionVos) { seckillSessionVos.stream().forEach(session -> { //获取秒杀活动开始和结束时间 long start = session.getStartTime().getTime(); long end = session.getEndTime().getTime(); String key = SecKillConstants.SESSION_CACHE_PREFIX+session.getName()+":"+start+"-"+end; // 保存活动的信息 if (!redisTemplate.hasKey(key)) { List<String> ids = session.getRelationSkus().stream().map(item->{ return item.getPromotionSessionId()+"_"+item.getSkuId().toString(); }).collect(Collectors.toList()); redisTemplate.opsForList().leftPushAll(key,ids); } }); } private void saveSkuInfos(List<SeckillSessionEntityVo> seckillSessionVos){ seckillSessionVos.forEach(session -> { // 准备哈希操作 BoundHashOperations ops = redisTemplate.boundHashOps(SecKillConstants.SKU_CACHE_PREFIX); session.getRelationSkus().forEach(relationSku -> { // 生成随机码 String token = UUID.randomUUID().toString().replace("-", ""); // 拼接Key保存商品 String key = relationSku.getPromotionSessionId().toString()+"_"+relationSku.getSkuId().toString(); if(!ops.hasKey(key)){ // 缓存商品 SecKillSkuRedisTo secKillSkuRedisTo = new SecKillSkuRedisTo(); // sku的基本信息 R info = productFeignService.getSkuInfo(relationSku.getSkuId()); if(info.getCode() == 0){ SkuInfoVo skuInfoVo = info.get("skuInfo",new TypeReference<SkuInfoVo>(){}); secKillSkuRedisTo.setSkuInfo(skuInfoVo); } // sku的秒杀信息 BeanUtils.copyProperties(relationSku,secKillSkuRedisTo); // 设置上秒杀数量啊!! secKillSkuRedisTo.setSeckillCount(relationSku.getSeckillCount()); // 设置秒杀的时间 secKillSkuRedisTo.setStartTime(session.getStartTime().getTime()); secKillSkuRedisTo.setEndTime(session.getEndTime().getTime()); secKillSkuRedisTo.setRandomCode(token); // 保存商品 String skuJson = JSON.toJSONString(secKillSkuRedisTo); ops.put(key,skuJson); // 如果当前这个场次的商品的库存信息已经上架,就不需要再次上架了 // 设置库存为分布式秒杀的信号量,限流 RSemaphore semaphore = redissonClient.getSemaphore(SecKillConstants.SKU_STOCK_SEMAPHORE + token); // 商品可以秒杀的数量作为信号量 semaphore.trySetPermits(relationSku.getSeckillCount().intValue()); } }); }); } }
package com.alatus.mall.seckill.service.impl; import com.alatus.common.to.mq.SeckillOrderTo; import com.alatus.common.utils.R; import com.alatus.common.vo.MemberRespVo; import com.alatus.mall.seckill.constant.SecKillConstants; import com.alatus.mall.seckill.feign.CouponFeignService; import com.alatus.mall.seckill.feign.ProductFeignService; import com.alatus.mall.seckill.interceptor.LoginUserInterceptor; import com.alatus.mall.seckill.service.SecKillService; import com.alatus.mall.seckill.to.SecKillSkuRedisTo; import com.alatus.mall.seckill.vo.SeckillPageVo; import com.alatus.mall.seckill.vo.SeckillSessionEntityVo; import com.alatus.mall.seckill.vo.SkuInfoVo; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.TypeReference; import org.apache.commons.lang.StringUtils; import com.baomidou.mybatisplus.core.toolkit.IdWorker; import org.redisson.api.RSemaphore; import org.redisson.api.RedissonClient; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.BoundHashOperations; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service; import java.util.*; import java.util.concurrent.TimeUnit; import java.util.regex.Pattern; import java.util.stream.Collectors; @Service public class SecKillServiceImpl implements SecKillService { @Autowired private CouponFeignService couponFeignService; @Autowired private ProductFeignService productFeignService; @Autowired private StringRedisTemplate redisTemplate; @Autowired private RedissonClient redissonClient; @Autowired private RabbitTemplate rabbitTemplate; @Override public void uploadSecKillSkuLatest3Days() { // 扫描需要参与秒杀的活动 R daySession = couponFeignService.Latest3DaySession(); if(daySession.getCode() == 0){ List<SeckillSessionEntityVo> seckillSessionVos = daySession.getData(new TypeReference<List<SeckillSessionEntityVo>>() {}); // 缓存活动信息 saveSessionInfos(seckillSessionVos); // 缓存活动关联的商品信息 saveSkuInfos(seckillSessionVos); } } // 返回当前事件可以参与的秒杀商品信息 @Override public List<SecKillSkuRedisTo> getCurrentSeckillSkus() { long time = new Date().getTime(); Set<String> keys = redisTemplate.keys(SecKillConstants.SESSION_CACHE_PREFIX + "*"); List<Object> currentSeckillSkus = new ArrayList<>(); if (keys != null) { for (String key : keys) { String replace = key.replace(SecKillConstants.SESSION_CACHE_PREFIX, ""); String[] tempString = replace.split(":"); String timeString = tempString[1]; String[] timeSplit = timeString.split("-"); long start = Long.parseLong(timeSplit[0]); long end = Long.parseLong(timeSplit[1]); if(time >= start && time <= end){ List<String> range = redisTemplate.opsForList().range(key, -100, 100); BoundHashOperations<String, String, Object> operations = redisTemplate.boundHashOps(SecKillConstants.SKU_CACHE_PREFIX); List<Object> strings = null; if (range != null) { strings = operations.multiGet(range); } if(strings != null && !strings.isEmpty()){ currentSeckillSkus.addAll(strings); } } } } if(!currentSeckillSkus.isEmpty()){ // 遍历获取到的JSON集合并转换为对象,并完整添加进去方便展示出来 return currentSeckillSkus.stream().map(item -> { SecKillSkuRedisTo secKillSkuRedisTo = JSON.parseObject(item.toString(), SecKillSkuRedisTo.class); // 屏蔽掉随机码 secKillSkuRedisTo.setRandomCode(null); return secKillSkuRedisTo; }).collect(Collectors.toList()); } else{ return null; } } @Override public SecKillSkuRedisTo getSkuSeckillInfo(Long skuId) { // 找到所有需要参与秒杀的SKU的Key信息 BoundHashOperations<String, String, String> operations = redisTemplate.boundHashOps(SecKillConstants.SKU_CACHE_PREFIX); Set<String> keys = operations.keys(); if (keys != null && !keys.isEmpty()) { String regx = "\\d_" +skuId; for (String key : keys) { if(Pattern.matches(regx,key)){ String json = operations.get(key); SecKillSkuRedisTo secKillSkuRedisTo = JSON.parseObject(json, SecKillSkuRedisTo.class); if(secKillSkuRedisTo.getEndTime() > new Date().getTime()){ if(secKillSkuRedisTo.getStartTime() > new Date().getTime()){ secKillSkuRedisTo.setRandomCode(null); } return secKillSkuRedisTo; } } } } return null; } @Override public SeckillPageVo kill(String killId, String key, Integer num) { MemberRespVo memberRespVo = LoginUserInterceptor.loginUser.get(); // 获取当前秒杀商品的详细信息 BoundHashOperations<String, String, String> operations = redisTemplate.boundHashOps(SecKillConstants.SKU_CACHE_PREFIX); String skuInfo = operations.get(killId); // 没找到这么一个秒杀商品 if(!StringUtils.isEmpty(skuInfo)){ SecKillSkuRedisTo secKillSkuRedisTo = JSON.parseObject(skuInfo, SecKillSkuRedisTo.class); Long startTime = secKillSkuRedisTo.getStartTime(); Long endTime = secKillSkuRedisTo.getEndTime(); long time = new Date().getTime(); // 在活动时间范围内 if(time >= startTime && time <= endTime){ // 拼接商品ID String skuId = secKillSkuRedisTo.getPromotionSessionId()+"_"+secKillSkuRedisTo.getSkuId(); // 商品随机码 String randomCode = secKillSkuRedisTo.getRandomCode(); // 校验随机码和商品ID if(key.equals(randomCode) && killId.equals(skuId)){ // 验证购物数量是否合理 if(secKillSkuRedisTo.getSeckillLimit().intValue() >= num){ String redisKey = memberRespVo.getId() + "_" + skuId; // 验证这个人是否已经购买过,幂等性处理,如果秒杀成功,就去redis占位 // 而且这个占位必须让它自动过期,不然会一直占用redis的资源 long ttl = endTime - time; Boolean check = redisTemplate.opsForValue().setIfAbsent(redisKey, num.toString(), ttl, TimeUnit.MILLISECONDS); // 如果这里可以占位成功说明,当前用户没有在该场次购买过该商品 if(check){ RSemaphore semaphore = redissonClient.getSemaphore(SecKillConstants.SKU_STOCK_SEMAPHORE + randomCode); boolean acquire = semaphore.tryAcquire(num); // 秒杀成功以后,我们就可以做一个快速下单,发送一个MQ消息 if(acquire){ String orderSn = IdWorker.getTimeId(); SeckillOrderTo secKillOrderTo = new SeckillOrderTo(); BeanUtils.copyProperties(secKillSkuRedisTo,secKillOrderTo); secKillOrderTo.setOrderSn(orderSn); secKillOrderTo.setMemberId(memberRespVo.getId()); secKillOrderTo.setNum(num); // TODO 发送MQ消息 rabbitTemplate.convertAndSend("order-event-exchange","order.seckill.order",secKillOrderTo); SeckillPageVo seckillPageVo = new SeckillPageVo(); seckillPageVo.setOrderSn(orderSn); BeanUtils.copyProperties(secKillSkuRedisTo,seckillPageVo); BeanUtils.copyProperties(secKillSkuRedisTo.getSkuInfo(),seckillPageVo); return seckillPageVo; } } } } } } return null; } private void saveSessionInfos(List<SeckillSessionEntityVo> seckillSessionVos) { seckillSessionVos.stream().forEach(session -> { //获取秒杀活动开始和结束时间 long start = session.getStartTime().getTime(); long end = session.getEndTime().getTime(); String key = SecKillConstants.SESSION_CACHE_PREFIX+session.getName()+":"+start+"-"+end; // 保存活动的信息 if (!redisTemplate.hasKey(key)) { List<String> ids = session.getRelationSkus().stream().map(item->{ return item.getPromotionSessionId()+"_"+item.getSkuId().toString(); }).collect(Collectors.toList()); redisTemplate.opsForList().leftPushAll(key,ids); } }); } private void saveSkuInfos(List<SeckillSessionEntityVo> seckillSessionVos){ seckillSessionVos.forEach(session -> { // 准备哈希操作 BoundHashOperations ops = redisTemplate.boundHashOps(SecKillConstants.SKU_CACHE_PREFIX); session.getRelationSkus().forEach(relationSku -> { // 生成随机码 String token = UUID.randomUUID().toString().replace("-", ""); // 拼接Key保存商品 String key = relationSku.getPromotionSessionId().toString()+"_"+relationSku.getSkuId().toString(); if(!ops.hasKey(key)){ // 缓存商品 SecKillSkuRedisTo secKillSkuRedisTo = new SecKillSkuRedisTo(); // sku的基本信息 R info = productFeignService.getSkuInfo(relationSku.getSkuId()); if(info.getCode() == 0){ SkuInfoVo skuInfoVo = info.get("skuInfo",new TypeReference<SkuInfoVo>(){}); secKillSkuRedisTo.setSkuInfo(skuInfoVo); } // sku的秒杀信息 BeanUtils.copyProperties(relationSku,secKillSkuRedisTo); // 设置上秒杀数量啊!! secKillSkuRedisTo.setSeckillCount(relationSku.getSeckillCount()); // 设置秒杀的时间 secKillSkuRedisTo.setStartTime(session.getStartTime().getTime()); secKillSkuRedisTo.setEndTime(session.getEndTime().getTime()); secKillSkuRedisTo.setRandomCode(token); // 保存商品 String skuJson = JSON.toJSONString(secKillSkuRedisTo); ops.put(key,skuJson); // 如果当前这个场次的商品的库存信息已经上架,就不需要再次上架了 // 设置库存为分布式秒杀的信号量,限流 RSemaphore semaphore = redissonClient.getSemaphore(SecKillConstants.SKU_STOCK_SEMAPHORE + token); // 商品可以秒杀的数量作为信号量 semaphore.trySetPermits(relationSku.getSeckillCount().intValue()); } }); }); } }
package com.alatus.mall.seckill.web; import com.alatus.mall.seckill.service.SecKillService; import com.alatus.mall.seckill.vo.SeckillPageVo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; @Controller public class SeckillWebController { @Autowired private SecKillService secKillService; @GetMapping("/kill") public String seckill(@RequestParam("killId")String killId, @RequestParam("key")String key, @RequestParam("num")Integer num, Model model){ SeckillPageVo seckillPageVo = secKillService.kill(killId,key,num); if(seckillPageVo != null){ model.addAttribute("seckillPageVo",seckillPageVo); return "detail"; } else{ return "fail"; } } }
package com.alatus.mall.seckill.web; import com.alatus.mall.seckill.service.SecKillService; import com.alatus.mall.seckill.vo.SeckillPageVo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; @Controller public class SeckillWebController { @Autowired private SecKillService secKillService; @GetMapping("/kill") public String seckill(@RequestParam("killId")String killId, @RequestParam("key")String key, @RequestParam("num")Integer num, Model model){ SeckillPageVo seckillPageVo = secKillService.kill(killId,key,num); if(seckillPageVo != null){ model.addAttribute("seckillPageVo",seckillPageVo); return "detail"; } else{ return "fail"; } } }
package com.alatus.mall.seckill.service; import com.alatus.mall.seckill.to.SecKillSkuRedisTo; import com.alatus.mall.seckill.vo.SeckillPageVo; import java.util.List; public interface SecKillService { void uploadSecKillSkuLatest3Days(); List<SecKillSkuRedisTo> getCurrentSeckillSkus(); SecKillSkuRedisTo getSkuSeckillInfo(Long skuId); SeckillPageVo kill(String killId, String key, Integer num); }
package com.alatus.mall.seckill.service; import com.alatus.mall.seckill.to.SecKillSkuRedisTo; import com.alatus.mall.seckill.vo.SeckillPageVo; import java.util.List; public interface SecKillService { void uploadSecKillSkuLatest3Days(); List<SecKillSkuRedisTo> getCurrentSeckillSkus(); SecKillSkuRedisTo getSkuSeckillInfo(Long skuId); SeckillPageVo kill(String killId, String key, Integer num); }
package com.alatus.mall.seckill.vo; import lombok.Data; import java.math.BigDecimal; @Data public class SeckillPageVo { /** * 订单号 */ private String orderSn; /** * 商品id */ private Long skuId; /** * 秒杀价格 */ private BigDecimal seckillPrice; /** * 秒杀总量 */ private BigDecimal seckillCount; /** * 秒杀开始时间 */ private Long startTime; /** * 秒杀结束时间 */ private Long endTime; /** * spuId */ private Long spuId; /** * sku名称 */ private String skuName; /** * sku介绍描述 */ private String skuDesc; /** * 所属分类id */ private Long catalogId; /** * 品牌id */ private Long brandId; /** * 默认图片 */ private String skuDefaultImg; /** * 标题 */ private String skuTitle; /** * 副标题 */ private String skuSubtitle; /** * 价格 */ private BigDecimal price; /** * 销量 */ private Long saleCount; }
package com.alatus.mall.seckill.vo; import lombok.Data; import java.math.BigDecimal; @Data public class SeckillPageVo { /** * 订单号 */ private String orderSn; /** * 商品id */ private Long skuId; /** * 秒杀价格 */ private BigDecimal seckillPrice; /** * 秒杀总量 */ private BigDecimal seckillCount; /** * 秒杀开始时间 */ private Long startTime; /** * 秒杀结束时间 */ private Long endTime; /** * spuId */ private Long spuId; /** * sku名称 */ private String skuName; /** * sku介绍描述 */ private String skuDesc; /** * 所属分类id */ private Long catalogId; /** * 品牌id */ private Long brandId; /** * 默认图片 */ private String skuDefaultImg; /** * 标题 */ private String skuTitle; /** * 副标题 */ private String skuSubtitle; /** * 价格 */ private BigDecimal price; /** * 销量 */ private Long saleCount; }