package cn.hobbystocks.auc.service.impl; import cn.hobbystocks.auc.cache.CacheMap; import cn.hobbystocks.auc.common.constant.Constants; import cn.hobbystocks.auc.common.core.redis.Locker; import cn.hobbystocks.auc.common.core.redis.RedisCache; import cn.hobbystocks.auc.common.core.text.Convert; import cn.hobbystocks.auc.common.user.UserUtils; import cn.hobbystocks.auc.common.utils.CloneUtils; import cn.hobbystocks.auc.common.utils.DateUtils; import cn.hobbystocks.auc.common.utils.SensitiveDataUtils; import cn.hobbystocks.auc.common.utils.StringUtils; import cn.hobbystocks.auc.domain.*; import cn.hobbystocks.auc.event.ChangeEvent; import cn.hobbystocks.auc.event.EventPublisher; import cn.hobbystocks.auc.event.JPushEvent; import cn.hobbystocks.auc.event.StartBiddingEvent; import cn.hobbystocks.auc.handle.RuleHandlerHolder; import cn.hobbystocks.auc.handle.context.Live; import cn.hobbystocks.auc.handle.context.LiveContext; import cn.hobbystocks.auc.mapper.*; import cn.hobbystocks.auc.service.ILotService; import cn.hobbystocks.auc.task.DynamicTaskService; import cn.hobbystocks.auc.vo.LiveVO; import cn.hobbystocks.auc.vo.LotVO; import cn.hobbystocks.auc.vo.SelfVO; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; import java.util.*; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; @Service @Slf4j public class LotServiceImpl extends ServiceImpl implements ILotService { @Autowired private LotMapper lotMapper; @Autowired private BidMapper bidMapper; @Autowired private Locker locker; @Autowired private RuleHandlerHolder ruleHandlerHolder; @Autowired private RedisCache redisCache; @Autowired private AuctionMapper auctionMapper; @Autowired public EventPublisher eventPublisher; @Autowired(required = false) private CacheMap cacheMap; @Autowired private DynamicTaskService dynamicTaskService; @Autowired private LotServiceImpl _thiz; @Autowired private LotGroupMapper lotGroupMapper; @Autowired private LotFansMapper lotFansMapper; @Override public Lot selectLotById(Long id) { return lotMapper.selectLotById(id); } @Override public LotGroup selectLotGroupById(Long id) { return lotGroupMapper.selectLotGroupById(id); } @Override public List selectLotList(Lot lot) { IPage lotIPage=new Page<>(lot.getPageNum(),lot.getPageSize()); return lotMapper.selectLotList(lotIPage,lot); } @Override public List selectLotByGroupIds(Long[] groupIds) { return null; } @Override public List selectLotGroupList(LotGroup lotGroup) { return lotGroupMapper.selectLotGroupList(lotGroup); } @Override public void cancelLotGroup(Long id) { LotGroup dbLotGroup = lotGroupMapper.selectLotGroupById(id); dbLotGroup.setStatus(Constants.GROUP_STATUS_WAITING); dbLotGroup.setPubStatus(Constants.PUB_STATUS_NO_PUBLISHED); lotGroupMapper.updateLotGroup(dbLotGroup); } @Override public List selectLotByGroupId(Long id) { return lotMapper.selectLotByGroupId(id); } @Override public List canCreateLive(Long merchantId) { return lotGroupMapper.canCreateLive(merchantId); } @Override public List findPubbedLotGroupByIds(Long[] lotGroupIds,Long merchantId) { return lotGroupMapper.findPubbedLotGroupByLotIds(lotGroupIds,merchantId); } @Override @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class) public Long party(LotGroup lotGroup) { AtomicLong result = new AtomicLong(0); locker.tryLock(String.format(Constants.REDIS_GROUP_LOCK_LOT_TEMPLATE, lotGroup.getId()), () -> { LotGroup dbLotGroup = lotGroupMapper.selectLotGroupById(lotGroup.getId()); Lot currLot = lotMapper.selectLotById(dbLotGroup.getNextLotId()); Lot nextLot = new Lot(); BeanUtils.copyProperties(dbLotGroup, nextLot, "id"); nextLot.setStatus(Constants.LOT_STATUS_WAITING); nextLot.setStartTime(null); nextLot.setCreateTime(new Date()); nextLot.setUpdateTime(null); nextLot.setPubTime(new Date()); nextLot.setPubStatus(Constants.PUB_STATUS_PUBLISHED); nextLot.setGroupId(lotGroup.getId()); nextLot.setAuctionId(2L); String name = currLot.getName(); int i = name.lastIndexOf("#"); if (i > 0 && i != name.length() -1) { String numStr = name.substring(i + 1); String nextNum = ""; try { long l = Long.parseLong(numStr); nextNum = "#" + (l + 1); }catch (Exception ignored) {} nextLot.setName(nextLot.getName() + nextNum); } lotMapper.updateLot( Lot.builder() .id(currLot.getId()) .startTime(new Date()) .build()); currLot.setStartTime(new Date()); long l = Objects.isNull(dbLotGroup.getFinishNum()) ? 0 : dbLotGroup.getFinishNum(); if (l >= dbLotGroup.getNum()) { throw new RuntimeException("商品库存已经用完"); }else if (l findStartingGroupByLotIds(Long[] lotIds) { return lotGroupMapper.findStartingGroupByIds(lotIds); } @Override @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class) public void updateExpire(LotFans fans) { lotFansMapper.updateLotFans( LotFans.builder() .id(fans.getId()) .type("pay_expire") .expire(DateUtils.addDays(fans.getCreateTime(), 1)) .build()); lotMapper.updateLot(Lot.builder().id(fans.getLotId()).status(Constants.LOT_STATUS_PASS).build()); } @Override @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class) public void updateSoldAndPaid(LotFans fans, Long groupId) { lotFansMapper.deleteLotFansById(fans.getId()); lotMapper.updatePay(fans.getLotId(), 1); lotGroupMapper.addSold(groupId); } @Override @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class) public void handleEndLotGroup(LotGroup lotGroup, String status) { LotGroup dbLotGroup = lotGroupMapper.selectLotGroupById(lotGroup.getId()); lotGroupMapper.updateLotGroupSold(dbLotGroup.getId()); } @Override @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class) public int insertLot(Lot lot) { lot.setCreateTime(DateUtils.getNowDate()); int result = lotMapper.insertLot(lot); dynamicTask(lot); return result; } @Override @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class) public int insertLotGroup(LotGroup lotGroup) { final int i = lotGroupMapper.insertLotGroup(lotGroup); Lot lot = new Lot(); BeanUtils.copyProperties(lotGroup, lot, "id"); lot.setStatus(Constants.LOT_STATUS_WAITING); lot.setStartTime(null); lot.setCreateTime(new Date()); lot.setUpdateTime(null); lot.setPubTime(new Date()); lot.setPubStatus(Constants.PUB_STATUS_PUBLISHED); lot.setGroupId(lotGroup.getId()); lot.setNum(1L); lot.setName(lot.getName() + "#1"); lotMapper.insertLot(lot); lotGroupMapper.updateLotGroup(LotGroup.builder().id(lotGroup.getId()).nextLotId(lot.getId()).build()); return i; } @Override public int updateLot(Lot lot) { lot.setUpdateTime(DateUtils.getNowDate()); return lotMapper.updateLot(lot); } @Override @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class) public int updateLotGroup(LotGroup lotGroup) { LotGroup dbLotGroup = lotGroupMapper.selectLotGroupById(lotGroup.getId()); if (Objects.nonNull(lotGroup.getNum()) && dbLotGroup.getNum() > lotGroup.getNum()) { int count = lotMapper.selectLotByGroupId(lotGroup.getId()).size() - 1; if (count > lotGroup.getNum()) { throw new RuntimeException("数量不合法"); } } lotGroup.setUpdateTime(DateUtils.getNowDate()); int result = lotGroupMapper.updateLotGroup(lotGroup); dbLotGroup = lotGroupMapper.selectLotGroupById(lotGroup.getId()); Lot lot = lotMapper.selectLotById(dbLotGroup.getNextLotId()); if (Constants.LOT_STATUS_WAITING.equals(lot.getStatus())) { String name = lot.getName(); String nextNum = ""; int i = name.lastIndexOf("#"); if (i > 0 && i != name.length() -1) { String numStr = name.substring(i + 1); try { long l = Long.parseLong(numStr); nextNum = "#" + l; }catch (Exception ignored) {} } BeanUtils.copyProperties(dbLotGroup, lot, "id","createTime", "name"); lot.setStatus(Constants.LOT_STATUS_WAITING); lot.setStartTime(null); lot.setPubTime(new Date()); lot.setPubStatus(Constants.PUB_STATUS_PUBLISHED); lot.setGroupId(lotGroup.getId()); lot.setName(dbLotGroup.getName() + nextNum); updateLot(lot); } return result; } @Override @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class) public int updateLotGroup0(LotGroup lotGroup) { return lotGroupMapper.updateLotGroup(lotGroup); } @Override public int updateLotView(Lot lot) { lot.setUpdateTime(DateUtils.getNowDate()); int result = lotMapper.updateLotView(lot); dynamicTask(lot); return result; } @Override @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class) public int updateLotEx(Lot lot) { lot.setUpdateTime(DateUtils.getNowDate()); int result = lotMapper.updateLotEx(lot); dynamicTask(lot); return result; } @Override public List selectBidding() { return lotMapper.selectBidding(); } @Override public List selectCancel() { return lotMapper.selectCancel(); } @Override public int deleteLotByIds(String ids) { return lotMapper.deleteLotByIds(Convert.toLongArray(ids)); } @Override public int deleteLotById(Long id) { return lotMapper.deleteLotById(id); } @Override @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class) public void pubLot(Lot lot) { locker.tryLock(String.format(Constants.REDIS_LOCK_LOT_TEMPLATE, lot.getId()), () ->{ lot.setPubTime(new Date()); lot.setPubStatus(Constants.PUB_STATUS_PUBLISHED); lot.setUpdateTime(new Date()); lotMapper.updateLot(lot); //构建竞价拍卖上下文 LiveContext liveContext = ruleHandlerHolder.pubLive(lot); if (liveContext.isLotUpdate()) { lotMapper.updateLot(lot); } redisCache.setCacheMapValue(String.format(Constants.REDIS_MAP_AUC_LOT_TEMPLATE, lot.getAuctionId()), lot.getId().toString(), liveContext.getLive()); }, true); } @Override @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class) public void pubLots(Auction auction) { List lots = lotMapper.selectLotByAucId(auction.getId()); for (Lot lot : lots) { lot.setUpdateBy(auction.getUpdateBy()); pubLot(lot); } } @Override @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class) public void insertLotAndPub(Lot lot) { insertLot(lot); pubLot(lot); } @Override @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class) public void updateLotAndPub(Lot lot) { updateLot(lot); pubLot(lot); } @Override public void removeLot(Lot lot) { Lot condition = new Lot(); condition.setId(lot.getId()); condition.setDelFlag(Constants.DEL_FLAG_DELETED); lotMapper.updateLot(condition); } @Override public void cancelLot(Lot lot) { lot.setUpdateTime(new Date()); lot.setStatus(Constants.LOT_STATUS_CANCELLED); lot.setPubStatus(Constants.PUB_STATUS_NO_PUBLISHED); lotMapper.updateLot(lot); ruleHandlerHolder.cancelLot(lot); } @Override @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class) public void live(Live live) { live = redisCache.getCacheMapValue(String.format(Constants.REDIS_MAP_AUC_LOT_TEMPLATE, live.getLot().getAuctionId()), live.getLot().getId().toString()); // 1秒的轮询 会存在失效数据 判断是否是失效数据 if (Objects.isNull(live)) { //是失效数据 return; } cacheMap.putLive(live); List bidList = redisCache.getCacheList(String.format(Constants.REDIS_MAP_AUC_LOT_BID_TEMPLATE, live.getLot().getId())); // 创建当前 context LiveContext liveContext = LiveContext.builder() .live(live) .dbLot(Lot.builder() .id(live.getLot().getId()) .auctionId(live.getLot().getAuctionId()) .build()) .bidList(bidList) .dbBid(getLastBid(bidList)) .build(); // 分析并设置当前状态 ruleHandlerHolder.live(liveContext); // 更新当前状态 到数据库和缓存 updateToRedisAndDb(liveContext); // 发送极光推送 jPush(liveContext); } private void jPush(LiveContext liveContext) { Live live = liveContext.getLive(); if (live.getCurrentEndTime() < (System.currentTimeMillis() + 1000 * 60 * 5)) { Long expire = redisCache.redisTemplate.getExpire(String.format(Constants.REDIS_LOCK_PUSH_LOT_TEMPLATE, live.getLot().getId())); if (Objects.isNull(expire) || expire < 0L) { eventPublisher.publishJPush(JPushEvent.builder().live(live).build()); redisCache.setCacheObject(String.format(Constants.REDIS_LOCK_PUSH_LOT_TEMPLATE, live.getLot().getId()), "jPush", 10, TimeUnit.MINUTES); } } } private Bid getLastBid(List bidList) { if (!CollectionUtils.isEmpty(bidList)) { return bidList.get(0); } return null; } private void updateToRedisAndDb(LiveContext liveContext) { Live live = liveContext.getLive(); List dataList = liveContext.getBidList(); ChangeEvent changeEvent = new ChangeEvent(live); if (liveContext.isLotUpdate()) { liveContext.getDbLot().setUpdateTime(new Date()); lotMapper.updateLot(liveContext.getDbLot()); } if (liveContext.isBidUpdate()) { bidMapper.updateBid(liveContext.getDbBid()); redisCache.setList(String.format(Constants.REDIS_MAP_AUC_LOT_BID_TEMPLATE, live.getLot().getId()), dataList); changeEvent.setBid(liveContext.getDbBid()); } if (liveContext.isLiveUpdate()) { if (Constants.LOT_STATUS_SOLD.equals(live.getLot().getStatus()) || Constants.LOT_STATUS_PASS.equals(live.getLot().getStatus())) { redisCache.delCacheMapValue(String.format(Constants.REDIS_MAP_AUC_LOT_TEMPLATE, live.getLot().getAuctionId()), live.getLot().getId().toString()); redisCache.deleteObject(String.format(Constants.REDIS_MAP_AUC_LOT_BID_TEMPLATE, live.getLot().getId())); if (Constants.LOT_STATUS_SOLD.equals(live.getLot().getStatus())) { if(live.getBidId()==null){ long bidid = bidMapper.getCurrentBid(live.getLot().getId()); live.setBidId(bidid); } eventPublisher.publishSoldEvent(live, dataList); } eventPublisher.publishEndEvent(live); }else { redisCache.setCacheMapValue(String.format(Constants.REDIS_MAP_AUC_LOT_TEMPLATE, live.getLot().getAuctionId()), live.getLot().getId().toString(), live); } eventPublisher.publishChangeEvent(changeEvent); } } @Override public List live(Long auctionId) { LambdaQueryWrapper queryWrapper=new LambdaQueryWrapper<>(); queryWrapper.eq(Lot::getAuctionId,auctionId).eq(Lot::getDelFlag,Constants.DEL_FLAG_NO_DELETE); return lotMapper.selectList(queryWrapper); } @Override public List selfLive(SelfVO selfVO) { Map cacheLive = CloneUtils.clone(cacheMap.viewAuction(selfVO.getAuctionId())); return cacheLive.values().stream() .filter(live -> live.getAccountList().contains(UserUtils.getSimpleUserInfo().getId().toString()) && Constants.LOT_STATUS_BIDDING.equals(live.getLot().getStatus())) .map(live -> { List bidList = bidMapper.selectBidByLotId(live.getLot().getId()); LiveVO liveVO = new LiveVO(live); if (!CollectionUtils.isEmpty(bidList)) { liveVO.setLastPriceTime(bidList.get(0).getCreateTime().getTime()); } liveVO.setCurrUserBids( Objects.requireNonNull(bidList).stream() .filter(bid -> bid.getAccountId().equals(UserUtils.getSimpleUserInfo().getId().toString())) .collect(Collectors.toList()) ); return SensitiveDataUtils.handleViewDataSafe(liveVO); }).sorted(this::sort) .collect(Collectors.toList()); } private int sort(LiveVO live1, LiveVO live2) { return (int)(live2.getLastPriceTime() - live1.getLastPriceTime()); } @Override public List selfFinish(SelfVO selfVO) { return finishOrWin(selfVO, null); } @Override public List selfWin(SelfVO selfVO) { return finishOrWin(selfVO, 1); } private List finishOrWin(SelfVO selfVO, Integer win) { String account = UserUtils.getSimpleUserInfo().getId().toString(); List resultList = new ArrayList<>(); List lotList = lotMapper.selectLotByAucId(selfVO.getAuctionId()); lotList.forEach(lot -> { if (Constants.LOT_STATUS_PASS.equals(lot.getStatus()) || Constants.LOT_STATUS_SOLD.equals(lot.getStatus())) { Bid condition = new Bid(); condition.setLotId(lot.getId()); condition.setAccountId(account); condition.setStatus(win); List bids = bidMapper.selectBidList(condition); if (!CollectionUtils.isEmpty(bids)) { LotVO lotVO = new LotVO(); BeanUtils.copyProperties(lot, lotVO); lotVO.setBids(bids); resultList.add(SensitiveDataUtils.handleViewDataSafe(lotVO)); } } }); return resultList; } @Override @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class) public void handleDelay(Long id) { Lot dbLot = lotMapper.selectLotById(id); Lot update = new Lot(); BeanUtils.copyProperties(dbLot, update); update.setCreateBy("SYSTEM"); update.setCreateTime(new Date()); update.setPubStatus(Constants.PUB_STATUS_NO_PUBLISHED); update.setStatus(Constants.LOT_STATUS_WAITING); update.setDelFlag(Constants.DEL_FLAG_NO_DELETE); update.setStartTime(DateUtils.addDays(dbLot.getStartTime(), 1)); update.setEndTime(DateUtils.addDays(dbLot.getEndTime(), 1)); String name = update.getName(); if (!StringUtils.isEmpty(name)) { Pattern pattern = Pattern.compile("第(\\d+)期"); Matcher matcher = pattern.matcher(name); if (matcher.find()) { String group = matcher.group(0); String noStr = matcher.group(1); int no = (Integer.parseInt(noStr)) + 1; name = name.replace(group, "第" + no + "期"); update.setName(name); } } lotMapper.insertLotClone(update); dbLot.setDelayPublish(null); lotMapper.updateLotView(dbLot); if (!Objects.equals(dbLot.getPubStatus(), Constants.PUB_STATUS_PUBLISHED)) { pubLot(dbLot); } dynamicTask(update); } @Override public void dynamicTasks() { lotMapper.dynamicLot().forEach(this::dynamicTask); } @Override public void dynamicTask(Lot lot) { //延时发布时间 String delayPublish = lot.getDelayPublish(); if (!StringUtils.isEmpty(delayPublish)) { dynamicTaskService.updateTask(lot.getId().toString(), DateUtils.getNextExecutionTime(delayPublish), () -> { _thiz.handleDelay(lot.getId()); }); }else { dynamicTaskService.removeTask(lot.getId().toString()); } } }