package cn.lili.modules.promotion.serviceimpl; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.date.DatePattern; import cn.hutool.core.date.DateTime; import cn.hutool.core.date.DateUtil; import cn.lili.cache.Cache; import cn.lili.cache.CachePrefix; import cn.lili.common.enums.PromotionTypeEnum; import cn.lili.common.enums.ResultCode; import cn.lili.common.exception.ServiceException; import cn.lili.common.vo.PageVO; import cn.lili.modules.goods.entity.dos.GoodsSku; import cn.lili.modules.goods.service.GoodsSkuService; import cn.lili.modules.promotion.entity.dos.BasePromotions; import cn.lili.modules.promotion.entity.dos.PromotionGoods; import cn.lili.modules.promotion.entity.dos.Seckill; import cn.lili.modules.promotion.entity.dos.SeckillApply; import cn.lili.modules.promotion.entity.dto.search.PromotionGoodsSearchParams; import cn.lili.modules.promotion.entity.dto.search.SeckillSearchParams; import cn.lili.modules.promotion.entity.enums.PromotionsApplyStatusEnum; import cn.lili.modules.promotion.entity.vos.SeckillApplyVO; import cn.lili.modules.promotion.entity.vos.SeckillGoodsVO; import cn.lili.modules.promotion.entity.vos.SeckillTimelineVO; import cn.lili.modules.promotion.mapper.SeckillApplyMapper; import cn.lili.modules.promotion.service.PromotionGoodsService; import cn.lili.modules.promotion.service.SeckillApplyService; import cn.lili.modules.promotion.service.SeckillService; import cn.lili.modules.promotion.tools.PromotionTools; import cn.lili.mybatis.util.PageUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.text.SimpleDateFormat; import java.util.*; import java.util.stream.Collectors; /** * 秒杀申请业务层实现 * * @author Chopper * @since 2020/8/21 */ @Service @Slf4j public class SeckillApplyServiceImpl extends ServiceImpl implements SeckillApplyService { /** * 缓存 */ @Autowired private Cache cache; /** * 规格商品 */ @Autowired private GoodsSkuService goodsSkuService; /** * 促销商品 */ @Autowired private PromotionGoodsService promotionGoodsService; /** * 秒杀 */ @Autowired private SeckillService seckillService; @Override public List getSeckillTimeline() { try { //秒杀活动缓存key return getSeckillTimelineInfo(); } catch (Exception e) { log.error("获取秒杀时间轴失败", e); return new ArrayList<>(); } } @Override public List getSeckillGoods(Integer timeline) { try { List seckillGoodsVoS = new ArrayList<>(); //获取 List seckillTimelineToCache = getSeckillTimelineInfo(); Optional first = seckillTimelineToCache.stream().filter(i -> i.getTimeLine().equals(timeline)).findFirst(); if (first.isPresent()) { seckillGoodsVoS = first.get().getSeckillGoodsList(); } return seckillGoodsVoS; } catch (Exception e) { log.error("获取秒杀商品失败", e); return new ArrayList<>(); } } @Override public IPage getSeckillApplyPage(SeckillSearchParams queryParam, PageVO pageVo) { IPage seckillApplyPage = this.page(PageUtil.initPage(pageVo), queryParam.queryWrapper()); if (seckillApplyPage != null && !seckillApplyPage.getRecords().isEmpty()) { //获取skuId List skuIds = seckillApplyPage.getRecords().stream() .map(SeckillApply::getSkuId).collect(Collectors.toList()); //循环获取 店铺/全平台 参与的促销商品库存进行填充 if (!skuIds.isEmpty()) { List skuStock = promotionGoodsService.getPromotionGoodsStock(PromotionTypeEnum.SECKILL, queryParam.getSeckillId(), skuIds); for (int i = 0; i < skuIds.size(); i++) { seckillApplyPage.getRecords().get(i).setQuantity(skuStock.get(i)); } } } return seckillApplyPage; } /** * 分页查询限时请购申请列表 * * @param queryParam 秒杀活动申请查询参数 * @return 限时请购申请列表 */ @Override public List getSeckillApplyList(SeckillSearchParams queryParam) { return this.list(queryParam.queryWrapper()); } /** * 查询限时请购申请列表总数 * * @param queryParam 查询条件 * @return 限时请购申请列表总数 */ @Override public long getSeckillApplyCount(SeckillSearchParams queryParam) { return this.count(queryParam.queryWrapper()); } /** * 查询限时请购申请 * * @param queryParam 秒杀活动申请查询参数 * @return 限时请购申请 */ @Override public SeckillApply getSeckillApply(SeckillSearchParams queryParam) { return this.getOne(queryParam.queryWrapper(), false); } @Override @Transactional(rollbackFor = Exception.class) public void addSeckillApply(String seckillId, String storeId, List seckillApplyList) { Seckill seckill = this.seckillService.getById(seckillId); if (seckill == null) { throw new ServiceException(ResultCode.SECKILL_NOT_EXIST_ERROR); } if (seckillApplyList == null || seckillApplyList.isEmpty()) { return; } //检查秒杀活动申请是否合法 checkSeckillApplyList(seckill.getHours(), seckillApplyList); //获取已参与活动的秒杀活动活动申请列表 List skuIds = seckillApplyList.stream().map(SeckillApply::getSkuId).collect(Collectors.toList()); List originList = new ArrayList<>(); List promotionGoodsList = new ArrayList<>(); for (SeckillApplyVO seckillApply : seckillApplyList) { //获取参与活动的商品信息 GoodsSku goodsSku = goodsSkuService.getCanPromotionGoodsSkuByIdFromCache(seckillApply.getSkuId()); if (!goodsSku.getStoreId().equals(storeId)) { continue; } //获取秒杀活动时间段 DateTime startTime = DateUtil.offsetHour(DateUtil.beginOfDay(seckill.getStartTime()), seckillApply.getTimeLine()); //检测是否可以发布促销商品 checkSeckillGoodsSku(seckill, seckillApply, goodsSku, startTime); //设置秒杀申请默认内容 seckillApply.setOriginalPrice(goodsSku.getPrice()); seckillApply.setPromotionApplyStatus(PromotionsApplyStatusEnum.PASS.name()); seckillApply.setSalesNum(0); originList.add(seckillApply); //获取促销商品 PromotionGoods promotionGoods = this.setSeckillGoods(goodsSku, seckillApply, seckill); promotionGoodsList.add(promotionGoods); } boolean result = true; this.remove(new LambdaQueryWrapper().eq(SeckillApply::getSeckillId, seckillId).in(SeckillApply::getSkuId, skuIds)); this.saveBatch(originList); //保存促销活动商品信息 if (!promotionGoodsList.isEmpty()) { PromotionGoodsSearchParams searchParams = new PromotionGoodsSearchParams(); searchParams.setPromotionId(seckillId); searchParams.setStoreId(storeId); searchParams.setPromotionType(PromotionTypeEnum.SECKILL.name()); searchParams.setSkuIds(promotionGoodsList.stream().map(PromotionGoods::getSkuId).collect(Collectors.toList())); promotionGoodsService.deletePromotionGoods(searchParams); //初始化促销商品 PromotionTools.promotionGoodsInit(promotionGoodsList, seckill, PromotionTypeEnum.SECKILL); result = promotionGoodsService.saveBatch(promotionGoodsList); } //设置秒杀活动的商品数量、店铺数量 seckillService.updateSeckillGoodsNum(seckillId); cache.vagueDel(CachePrefix.STORE_ID_SECKILL); if (result) { this.seckillService.updateEsGoodsSeckill(seckill, originList); } } /** * 批量删除秒杀活动申请 * * @param seckillId 秒杀活动活动id * @param id id */ @Override @Transactional(rollbackFor = Exception.class) public void removeSeckillApply(String seckillId, String id) { Seckill seckill = this.seckillService.getById(seckillId); if (seckill == null) { throw new ServiceException(ResultCode.SECKILL_NOT_EXIST_ERROR); } SeckillApply seckillApply = this.getById(id); if (seckillApply == null) { throw new ServiceException(ResultCode.SECKILL_APPLY_NOT_EXIST_ERROR); } //清除秒杀活动中的商品 this.remove(new LambdaQueryWrapper() .eq(SeckillApply::getSeckillId, seckillId) .in(SeckillApply::getId, id)); this.seckillService.deleteEsGoodsSeckill(seckill, Collections.singletonList(seckillApply.getSkuId())); //删除促销商品 this.promotionGoodsService.deletePromotionGoods(seckillId, Collections.singletonList(seckillApply.getSkuId())); } /** * 更新秒杀商品出售数量 * * @param seckillId 秒杀活动id * @param skuId 商品skuId * @param saleNum 库存 */ @Override public void updateSeckillApplySaleNum(String seckillId, String skuId, Integer saleNum) { LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>(); updateWrapper.eq(SeckillApply::getSeckillId, seckillId).eq(SeckillApply::getSkuId, skuId); updateWrapper.set(SeckillApply::getSalesNum, saleNum); this.update(updateWrapper); } /** * 更新秒杀活动时间 * * @param seckill 秒杀活动 * @return 是否更新成功 */ @Override @Transactional(rollbackFor = Exception.class) public boolean updateSeckillApplyTime(Seckill seckill) { boolean result = false; List promotionGoodsList = new ArrayList<>(); LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(SeckillApply::getSeckillId, seckill.getId()); List list = this.list(queryWrapper).stream().filter(i -> i.getTimeLine() != null && seckill.getHours().contains(i.getTimeLine().toString())).collect(Collectors.toList()); for (SeckillApply seckillApply : list) { //获取参与活动的商品信息 GoodsSku goodsSku = goodsSkuService.getCanPromotionGoodsSkuByIdFromCache(seckillApply.getSkuId()); //获取促销商品 PromotionGoods promotionGoods = this.setSeckillGoods(goodsSku, seckillApply, seckill); promotionGoodsList.add(promotionGoods); } //保存促销活动商品信息 if (!promotionGoodsList.isEmpty()) { PromotionGoodsSearchParams searchParams = new PromotionGoodsSearchParams(); searchParams.setPromotionType(PromotionTypeEnum.SECKILL.name()); searchParams.setPromotionId(seckill.getId()); promotionGoodsService.deletePromotionGoods(searchParams); //初始化促销商品 List promotionGoods = PromotionTools.promotionGoodsInit(promotionGoodsList, seckill, PromotionTypeEnum.SECKILL); result = promotionGoodsService.saveBatch(promotionGoods); this.seckillService.updateEsGoodsSeckill(seckill, list); LambdaQueryWrapper deleteWrapper = new LambdaQueryWrapper<>(); deleteWrapper.eq(SeckillApply::getSeckillId, seckill.getId()); deleteWrapper.notIn(SeckillApply::getSkuId, promotionGoodsList.stream().map(PromotionGoods::getSkuId).collect(Collectors.toList())); this.remove(deleteWrapper); } seckillService.updateSeckillGoodsNum(seckill.getId()); return result; } /** * 检查秒杀活动申请列表参数信息 * * @param hours 秒杀活动时间段 * @param seckillApplyList 秒杀活动申请列表 */ private void checkSeckillApplyList(String hours, List seckillApplyList) { List existSku = new ArrayList<>(); for (SeckillApplyVO seckillApply : seckillApplyList) { if (seckillApply.getPrice() > seckillApply.getOriginalPrice()) { throw new ServiceException(ResultCode.SECKILL_PRICE_ERROR); } //检查秒杀活动申请的时刻,是否存在在秒杀活动的时间段内 String[] rangeHours = hours.split(","); boolean containsSame = Arrays.stream(rangeHours).anyMatch(i -> i.equals(seckillApply.getTimeLine().toString())); if (!containsSame) { throw new ServiceException(ResultCode.SECKILL_TIME_ERROR); } //检查商品是否参加多个时间段的活动 if (existSku.contains(seckillApply.getSkuId())) { throw new ServiceException(seckillApply.getGoodsName() + "该商品不能同时参加多个时间段的活动"); } else { existSku.add(seckillApply.getSkuId()); } } } /** * 获取秒杀活动信息 * * @return 秒杀活动信息 */ private List getSeckillTimelineInfo() { List timelineList = new ArrayList<>(); LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); //查询当天时间段内的秒杀活动活动 Date now = new Date(); queryWrapper.between(BasePromotions::getStartTime, DateUtil.beginOfDay(now), DateUtil.endOfDay(now)); queryWrapper.ge(BasePromotions::getEndTime, DateUtil.endOfDay(now)); List seckillList = this.seckillService.list(queryWrapper); for (Seckill seckill : seckillList) { //读取系统时间的时刻 Calendar c = Calendar.getInstance(); int hour = c.get(Calendar.HOUR_OF_DAY); String[] split = seckill.getHours().split(","); int[] hoursSored = Arrays.stream(split).mapToInt(Integer::parseInt).toArray(); Arrays.sort(hoursSored); for (int i = 0; i < hoursSored.length; i++) { SeckillTimelineVO tempTimeline = new SeckillTimelineVO(); boolean hoursSoredHour = (hoursSored[i] >= hour || ((i + 1) < hoursSored.length && hoursSored[i + 1] > hour)); boolean lastHour = i == hoursSored.length - 1 && hoursSored[i] < hour; if (hoursSoredHour || lastHour) { SimpleDateFormat format = new SimpleDateFormat(DatePattern.NORM_DATE_PATTERN); String date = format.format(new Date()); //当前时间的秒数 long currentTime = DateUtil.currentSeconds(); //秒杀活动的时刻 long timeLine = cn.lili.common.utils.DateUtil.getDateline(date + " " + hoursSored[i], "yyyy-MM-dd HH"); Long distanceTime = timeLine - currentTime < 0 ? 0 : timeLine - currentTime; tempTimeline.setDistanceStartTime(distanceTime); tempTimeline.setStartTime(timeLine); tempTimeline.setTimeLine(hoursSored[i]); tempTimeline.setSeckillGoodsList(wrapperSeckillGoods(hoursSored[i], seckill.getId())); timelineList.add(tempTimeline); } } } return timelineList; } /** * 组装当时间秒杀活动的商品数据 * w * * @param startTimeline 秒杀活动开始时刻 * @return 当时间秒杀活动的商品数据 */ private List wrapperSeckillGoods(Integer startTimeline, String seckillId) { List seckillGoodsVoS = new ArrayList<>(); List seckillApplyList = this.list(new LambdaQueryWrapper().eq(SeckillApply::getSeckillId, seckillId)); if (!seckillApplyList.isEmpty()) { List collect = seckillApplyList.stream().filter(i -> i.getTimeLine().equals(startTimeline) && i.getPromotionApplyStatus().equals(PromotionsApplyStatusEnum.PASS.name())).collect(Collectors.toList()); for (SeckillApply seckillApply : collect) { GoodsSku goodsSku = goodsSkuService.getCanPromotionGoodsSkuByIdFromCache(seckillApply.getSkuId()); if (goodsSku != null) { SeckillGoodsVO goodsVO = new SeckillGoodsVO(); BeanUtil.copyProperties(seckillApply, goodsVO); goodsVO.setGoodsImage(goodsSku.getThumbnail()); goodsVO.setGoodsId(goodsSku.getGoodsId()); goodsVO.setGoodsName(goodsSku.getGoodsName()); seckillGoodsVoS.add(goodsVO); } } } return seckillGoodsVoS; } /** * 检测秒杀申请的商品 * * @param seckill 秒杀活动 * @param seckillApply 秒杀活动申请 * @param goodsSku 商品SKU * @param startTime 秒杀时段开启时间 */ private void checkSeckillGoodsSku(Seckill seckill, SeckillApplyVO seckillApply, GoodsSku goodsSku, DateTime startTime) { //活动库存不能大于商品库存 if (goodsSku.getQuantity() < seckillApply.getQuantity()) { throw new ServiceException(seckillApply.getGoodsName() + ",此商品库存不足"); } //查询是否在同一时间段参与了拼团活动 if (promotionGoodsService.findInnerOverlapPromotionGoods(PromotionTypeEnum.PINTUAN.name(), goodsSku.getId(), startTime, seckill.getEndTime(), seckill.getId()) > 0) { throw new ServiceException("商品[" + goodsSku.getGoodsName() + "]已经在重叠的时间段参加了拼团活动,不能参加秒杀活动"); } //查询是否在同一时间段参与了秒杀活动活动 if (promotionGoodsService.findInnerOverlapPromotionGoods(PromotionTypeEnum.SECKILL.name(), goodsSku.getId(), startTime, seckill.getEndTime(), seckill.getId()) > 0) { throw new ServiceException("商品[" + goodsSku.getGoodsName() + "]已经在重叠的时间段参加了秒杀活动,不能参加秒杀活动活动"); } } /** * 获取秒杀活动促销商品 * * @param goodsSku 商品SKU * @param seckillApply 秒杀活动申请 * @param seckill 秒杀活动 * @return 秒杀活动促销商品 */ private PromotionGoods setSeckillGoods(GoodsSku goodsSku, SeckillApply seckillApply, Seckill seckill) { //设置促销商品默认内容 PromotionGoods promotionGoods = new PromotionGoods(goodsSku); promotionGoods.setPrice(seckillApply.getPrice()); promotionGoods.setQuantity(seckillApply.getQuantity()); //设置单独每个促销商品的结束时间 DateTime startTime = DateUtil.offsetHour(DateUtil.beginOfDay(seckill.getStartTime()), seckillApply.getTimeLine()); promotionGoods.setStartTime(startTime); if (seckill.getEndTime() == null) { promotionGoods.setEndTime(DateUtil.endOfDay(startTime)); } else { promotionGoods.setEndTime(seckill.getEndTime()); } return promotionGoods; } }