package cn.hobbystocks.auc.web; import cn.hobbystocks.auc.app.AppClient; import cn.hobbystocks.auc.cache.CacheMap; import cn.hobbystocks.auc.common.constant.Constants; import cn.hobbystocks.auc.common.core.controller.BaseController; import cn.hobbystocks.auc.common.core.domain.AjaxResult; import cn.hobbystocks.auc.common.core.redis.RedisCache; import cn.hobbystocks.auc.common.core.text.Convert; import cn.hobbystocks.auc.common.user.UserInfo; import cn.hobbystocks.auc.common.user.UserUtils; import cn.hobbystocks.auc.common.utils.CloneUtils; import cn.hobbystocks.auc.common.utils.PaginationUtil; import cn.hobbystocks.auc.common.utils.SensitiveDataUtils; import cn.hobbystocks.auc.domain.*; import cn.hobbystocks.auc.event.EventPublisher; import cn.hobbystocks.auc.forest.CommonForestClient; 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.BidRecordMapper; import cn.hobbystocks.auc.mapper.DepositOrderMapper; import cn.hobbystocks.auc.service.IBidService; import cn.hobbystocks.auc.service.ILotService; import cn.hobbystocks.auc.vo.BidVO; import com.alibaba.fastjson.JSON; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.github.pagehelper.Page; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.*; import java.util.*; import java.util.stream.Collectors; /** * 出价 */ @RestController @RequestMapping({"/bid/bidding"}) @Slf4j @Api(tags = "出价相关接口") public class BidingController extends BaseController { // region Params @Value("${hobbystocks.host.pointUrl}") private String pointUrl; @Autowired private CommonForestClient client; @Autowired private IBidService bidService; @Autowired private ILotService lotService; @Autowired private AppClient appClient; @Autowired private EventPublisher eventPublisher; @Autowired private CacheMap cacheMap; @Autowired private BidRecordMapper bidRecordMapper; @Autowired private RedisCache redisCache; @Autowired private RuleHandlerHolder ruleHandlerHolder; @Autowired private DepositOrderMapper depositOrderMapper; // endregion // region Controllers @ApiOperation("同步数据") @PostMapping("/sync") public AjaxResult sync() { eventPublisher.sync(); return AjaxResult.success(); } @ApiOperation("出价拍卖增加价格") @PostMapping({"/addPrice"}) public AjaxResult addPriceV2(@RequestBody BidVO bid) { log.info("add price {}", bid); // 创建bidRecord UserInfo userInfo = UserUtils.getUserInfo(); Lot dbLot = lotService.selectLotById(bid.getLotId()); if (!hasPaidDeposit(userInfo.getId(), dbLot)) { log.warn("addPrice fail, user has no paid deposit, userId:{} auctionId:{} lotId:{}", userInfo.getId(), bid.getAuctionId(), bid.getLotId()); return AjaxResult.error("请先缴纳保证金"); } // 创建bidRecord bid.setCreateBy(userInfo.getSub()); bid.setAccount(userInfo.getAccount()); String accountId = String.valueOf(userInfo.getId()); bid.setAccountId(accountId); bid.setAvatar(userInfo.getAvatar()); bid.setUserCode(bidService.getOrCreateUserCode(dbLot.getAuctionId(), accountId)); bid.setBidNo(UUID.randomUUID().toString()); //用户出价记录日志,无论是否出价成功都记录 Long recordId = createBidRecord(bid); /*if (Objects.equals(2, userInfo.getAccountStatus())) { return AjaxResult.error("暂时无法参与当前竞价"); } if (!Objects.equals(1, userInfo.getFaceVerify()) && !bid.getTest()) { return AjaxResult.error("请先进行实名认证"); } if(!userInfo.isHasAddress()) { return AjaxResult.error("请先添加地址"); }*/ // 判断拍卖状态 String error = checkLotStatus(bid); if (StringUtils.hasLength(error)) { log.warn("出价失败:{}", error); return AjaxResult.error("出价失败"); } Lot lot = cacheMap.viewLot(bid.getAuctionId(), bid.getLotId()).getLot(); /*if (Objects.equals(lot.getMerchantId().intValue(), userInfo.getMerchantId())) { log.warn("出价失败,商家无法参与自己的竞价拍卖"); return AjaxResult.error("出价失败,商家无法参与自己的竞价拍卖"); }*/ try { bidService.addPrice(bid,() ->{ }); }catch (RuntimeException e) { log.error("addPrice error {}", bid, e); return AjaxResult.error("当前价格已变动"); } return AjaxResult.success(); } /* * 直播间竞拍提速版,使用redis维护出价信息,拍卖结束后统一录入数据库,每秒更新redis数据同步到数据库的表里面 * 出价限制由拍卖的接口提供,这里只检查缓存中是否存在,不存在则不能参加: * */ @PostMapping({"/v3/addPrice"}) public AjaxResult addPriceV4(@RequestBody BidVO bid) { UserInfo userInfo = UserUtils.getSimpleUserInfo(); if(userInfo!=null){ String nickname = String.valueOf(userInfo.getSub()); bid.setCreateBy(nickname); bid.setAccount(nickname); String accountId = String.valueOf(userInfo.getId()); bid.setAccountId(accountId); bid.setAvatar(userInfo.getAvatar()); bid.setAuctionId(2L); bid.setBidNo(UUID.randomUUID().toString()); createBidRecordCache(bid); } else{return AjaxResult.error("出价失败");} //从本场拍卖的缓存中拿限制信息,未能通过的用户不能出价参拍 Map forbbidenUsermap = redisCache.getCacheMap(String.format(Constants.REDIS_MAP_AUC_LOT_BID_USER_TEMPLATE, bid.getLotId())); if(forbbidenUsermap.containsKey(userInfo.getId().toString())){ return AjaxResult.error(forbbidenUsermap.get(userInfo.getId().toString())); } // 判断拍卖状态 String error = checkLotStatus(bid); if (StringUtils.hasLength(error)) { log.warn("出价失败:{}", error); return AjaxResult.error("出价失败"); } try { bidService.addPriceV3(bid,() ->{ }); }catch (RuntimeException e) { log.error("addPriceV3 error {}", bid, e); return AjaxResult.error("当前价格已变动"); } return AjaxResult.success(); } @ApiOperation("查询并返回指定拍卖中已结束的拍卖物品列表,并以分页的形式展示") @PostMapping("/view/{auctionId}/finished") public AjaxResult viewFinished(@RequestBody BidVO bidVO, @PathVariable Long auctionId) { log.info("= " + bidVO + "="); bidVO.setOrderBy("real_end_time desc"); startPage(bidVO); List lots = lotService.selectLotList(Lot.builder() .auctionId(auctionId) .delFlag(Constants.DEL_FLAG_NO_DELETE) .goodsType(bidVO.getGoodsType()) .status(Constants.LOT_STATUS_SOLD) .privateDomain(0) .build()); Page page = (Page) lots; PaginationUtil.PageResult lotlist = new PaginationUtil.PageResult<>(((Page) page).getResult(), Integer.parseInt(page.getTotal() + ""), page.getPageNum(), bidVO.getPageSize()); lotlist.getItems().forEach(lot->{ lot.setDealAccount(SensitiveDataUtils.maskString(lot.getDealAccount(), 4)); lot.setUpdateBy(SensitiveDataUtils.maskString(lot.getUpdateBy(), 4)); }); return AjaxResult.success(lotlist); } @ApiOperation("获取指定拍卖ID的所有拍卖品信息") @PostMapping("/view/{auctionId}/starting") public AjaxResult viewBidding(@RequestBody BidVO bidVO, @PathVariable Long auctionId) { log.info("= " + bidVO + "="); Map map = bidService.view(auctionId); Set statusSet = Sets.newHashSet(Constants.LOT_STATUS_STARTING, Constants.LOT_STATUS_BIDDING, Constants.LOT_STATUS_WAITING); List list = map.values().stream() .filter(live -> statusSet.contains(live.getLot().getStatus()) && live.getShow()) .filter(live -> StringUtils.isEmpty(bidVO.getGoodsType()) || Objects.equals(live.getLot().getGoodsType(), bidVO.getGoodsType())) .map(SensitiveDataUtils::handleViewDataSafe) .sorted(this::sort) .collect(Collectors.toList()); return AjaxResult.success(PaginationUtil.page(bidVO, list)); } @ApiOperation("从 Redis 缓存中获取id对应的 Live 对象,并将其返回") @GetMapping("/viewEx/{auctionId}/{id}") public AjaxResult viewEx(@PathVariable Long auctionId,@PathVariable Long id) { Live live = redisCache.getCacheMapValue(String.format(Constants.REDIS_AUC_TEMPLATE, auctionId), id.toString()); return AjaxResult.success(JSON.toJSON(live)); } @ApiOperation("获取并返回Live 对象,该对象包含了特定拍卖品的详细信息,包括出价记录、交易账户信息等") @GetMapping("/view/{auctionId}/{id}") public AjaxResult view(@PathVariable Long auctionId, @PathVariable Long id) { Live live = bidService.viewLot(auctionId, id); Lot lot = lotService.selectLotById(id); UserInfo userInfo = null; try { userInfo = UserUtils.getUserInfo(Convert.toInt(lot.getDealAccountId())); } catch (Exception e) { log.error("getUserInfo error", e); } try { log.info("request param auction {} id {}",auctionId,id); // region 获取用户禁止拍卖的检查信息并写入缓存 UserInfo curruserInfo = UserUtils.getUserInfo(getUserId()); boolean isForbiddenUser = false; String forbiddenReason = ""; if (Objects.equals(2, curruserInfo.getAccountStatus())) { isForbiddenUser = true; forbiddenReason = "暂时无法参与当前竞价"; } if (!Objects.equals(1, curruserInfo.getFaceVerify())) { isForbiddenUser = true; forbiddenReason = "请先进行实名认证"; } if(!curruserInfo.isHasAddress()) { isForbiddenUser = true; forbiddenReason = "请先添加地址"; } if (Objects.equals(lot.getMerchantId().intValue(), curruserInfo.getMerchantId())) { isForbiddenUser = true; forbiddenReason = "出价失败"; } Long merchantId = lot.getMerchantId(); Long userId = Long.valueOf(getUserId()); if(isForbiddenUser){ redisCache.setCacheMapValue(String.format(Constants.REDIS_MAP_AUC_LOT_BID_USER_TEMPLATE, lot.getId()), curruserInfo.getId().toString(), forbiddenReason); } else{ redisCache.delCacheMapValue(String.format(Constants.REDIS_MAP_AUC_LOT_BID_USER_TEMPLATE, lot.getId()), curruserInfo.getId().toString()); } // endregion } catch (Exception e) { log.error("get curruserInfo error", e); } final Lot clone = CloneUtils.clone(lot); List bids = bidService.selectBidList(Bid.builder().lotId(lot.getId()).build()); List bidsDealList = bids.stream() .filter(bid -> bid.getStatus() == 1 && bid.getAccountId().equals(lot.getDealAccountId())) .collect(Collectors.toList()); // 检查 bidsList 是否存在,并且如果存在,找到 anonymous 字段等于 1 的 Bid 对象 boolean isDealAccountAnonymous = false; if (!bidsDealList.isEmpty()) { for (Bid bid : bidsDealList) { if (bid.getAnonymous() == 1) { isDealAccountAnonymous = true; break; // 如果只需要找到第一个符合条件的对象,可以在这里停止循环 } } } if (Objects.isNull(live)) { LiveContext liveContext = LiveContext.builder().dbLot(lot).bidList(bids).build(); ruleHandlerHolder.sync(liveContext); live = liveContext.getLive(); if (Objects.equals(auctionId, 2L)) { Long groupId = lot.getGroupId(); LotGroup lotGroup = lotService.selectLotGroupById(groupId); if (lotGroup.getFinishNum() >= lotGroup.getNum()) { live.setFinish(1); }else { live.setFinish(0); } } if(Objects.nonNull(userInfo)){ live.getLot().setDealAccountavatar(userInfo.getAvatar()); if (clone != null) { clone.setDealAccountavatar(userInfo.getAvatar()); } } if(Objects.equals(getUserId().toString(), lot.getDealAccountId())){ // live.getLot().setOrderId(lot.getOrderId()); } else{ if (clone != null) { if(isDealAccountAnonymous){ clone.setDealAccountavatar(null); clone.setDealAccount(null); } } } live.setLot(clone); return AjaxResult.success(SensitiveDataUtils.handleViewDataSafe(live)); } live.setLot(clone); if (Objects.equals(auctionId, 2L)) { Long groupId = lot.getGroupId(); LotGroup lotGroup = lotService.selectLotGroupById(groupId); if (lotGroup.getFinishNum() >= lotGroup.getNum()) { live.setFinish(1); }else { live.setFinish(0); } } if(Objects.nonNull(userInfo)){ live.getLot().setDealAccountavatar(userInfo.getAvatar()); } if(Objects.equals(getUserId().toString(), lot.getDealAccountId())){ // live.getLot().setOrderId(lot.getOrderId()); } else{ // live.getLot().setOrderId(null); if(isDealAccountAnonymous){ live.getLot().setDealAccountavatar(null); live.getLot().setDealAccount(null); } } log.info("/bid/bidding/view/ result {}", live); log.info("/bid/bidding/view/ result {}", SensitiveDataUtils.handleViewDataSafe(live)); return AjaxResult.success(SensitiveDataUtils.handleViewDataSafe(live)); } /* * 配合v3的竞价使用,查询缓存中的竞价结果 * */ @GetMapping("/viewv3/{auctionId}/{id}") public AjaxResult viewV4(@PathVariable Long auctionId, @PathVariable Long id) { Live live = bidService.viewLot(auctionId, id); Lot lot = lotService.selectLotById(id); UserInfo userInfo = UserUtils.getUserInfo(Convert.toInt(lot.getDealAccountId())); final Lot clone = CloneUtils.clone(lot); List bids = bidService.selectBidList(Bid.builder().lotId(lot.getId()).build()); List bidsDealList = bids.stream() .filter(bid -> bid.getStatus() == 1 && bid.getAccountId().equals(lot.getDealAccountId())) .collect(Collectors.toList()); // 检查 bidsList 是否存在,并且如果存在,找到 anonymous 字段等于 1 的 Bid 对象 boolean isDealAccountAnonymous = false; if (!bidsDealList.isEmpty()) { for (Bid bid : bidsDealList) { if (bid.getAnonymous() == 1) { isDealAccountAnonymous = true; break; // 如果只需要找到第一个符合条件的对象,可以在这里停止循环 } } } if (Objects.isNull(live)) { LiveContext liveContext = LiveContext.builder().dbLot(lot).bidList(bids).build(); ruleHandlerHolder.sync(liveContext); live = liveContext.getLive(); if (Objects.equals(auctionId, 2L)) { Long groupId = lot.getGroupId(); LotGroup lotGroup = lotService.selectLotGroupById(groupId); if (lotGroup.getFinishNum() >= lotGroup.getNum()) { live.setFinish(1); }else { live.setFinish(0); } } if(Objects.nonNull(userInfo)){ live.getLot().setDealAccountavatar(userInfo.getAvatar()); if (clone != null) { clone.setDealAccountavatar(userInfo.getAvatar()); } } if(Objects.equals(getUserId().toString(), lot.getDealAccountId())){ // live.getLot().setOrderId(lot.getOrderId()); } else{ if (clone != null) { // clone.setOrderId(null); if(isDealAccountAnonymous){ clone.setDealAccountavatar(null); clone.setDealAccount(null); } } } live.setLot(clone); log.info("/bid/bidding/view/ result {}", live); log.info("/bid/bidding/view/ result {}", SensitiveDataUtils.handleViewDataSafe(live)); return AjaxResult.success(SensitiveDataUtils.handleViewDataSafe(live)); } if (Objects.equals(auctionId, 2L)) { Long groupId = lot.getGroupId(); LotGroup lotGroup = lotService.selectLotGroupById(groupId); if (lotGroup.getFinishNum() >= lotGroup.getNum()) { live.setFinish(1); }else { live.setFinish(0); } } if(Objects.nonNull(userInfo)){ live.getLot().setDealAccountavatar(userInfo.getAvatar()); } if(Objects.equals(getUserId().toString(), lot.getDealAccountId())){ // live.getLot().setOrderId(lot.getOrderId()); } else{ // live.getLot().setOrderId(null); if(isDealAccountAnonymous){ live.getLot().setDealAccountavatar(null); live.getLot().setDealAccount(null); } } log.info("/bid/bidding/view/ result {}", live); log.info("/bid/bidding/view/ result {}", SensitiveDataUtils.handleViewDataSafe(live)); return AjaxResult.success(SensitiveDataUtils.handleViewDataSafe(live)); } @GetMapping("/viewcache/{auctionId}/{id}") public AjaxResult viewV3cache(@PathVariable Long auctionId, @PathVariable Long id) { List bidList = redisCache.getCacheList(String.format(Constants.REDIS_MAP_AUC_LOT_BID_LIST_PREFIX,id)); return AjaxResult.success(bidList); } @ApiOperation("查询拍卖品出价记录等信息") @PostMapping("/list") public AjaxResult listBid(@RequestBody BidVO bidVO) { Lot dbLot = lotService.selectLotById(bidVO.getLotId()); if (Constants.LOT_STATUS_SOLD.equals(dbLot.getStatus()) || Constants.LOT_STATUS_CANCELLED.equals(dbLot.getStatus()) || Constants.LOT_STATUS_PASS.equals(dbLot.getStatus())) { return findBidPageFromDb(bidVO); } if (bidVO.getPageSize() > 20 || (bidVO.getPageSize() * bidVO.getPageNum()) > 20) { return findBidPageFromDb(bidVO); }else { Integer userId = UserUtils.getSimpleUserInfo().getId(); List list = bidService.selectCacheBidList(bidVO).stream().map(bid -> { boolean sensitive=false; if (Objects.equals(1, bid.getAnonymous())) { bid.setAvatar("/MC143302022060920611/avatar/1666182630730.jpg"); bid.setAccount("匿名用户"); sensitive=true; } if (org.apache.commons.lang3.StringUtils.isBlank(bid.getAvatar())){ bid.setAvatar("/MC143302022060920611/avatar/1666182630730.jpg"); } if (org.apache.commons.lang3.StringUtils.isBlank(bid.getAccount())){ bid.setAccount("匿名用户"); sensitive=true; } bid.setCurrentAccount(Objects.equals(Integer.parseInt(bid.getAccountId()), userId)); if (!sensitive) { return SensitiveDataUtils.handleViewDataSafe(bid); } return bid; }).collect(Collectors.toList()); if (!CollectionUtils.isEmpty(list)) { log.info("CollectionUtils list {}", list); // 使用 Comparator 接口进行排序 list.sort((bid1, bid2) -> bid2.getAmount().compareTo(bid1.getAmount())); log.info("CollectionUtils list size after sort {}", list.size()); Bid bidLast = list.get(0); Long total = bidLast.getRound(); log.info("CollectionUtils bidLast {}", bidLast); List> partition = Lists.partition(list, bidVO.getPageSize()); List resultList = Lists.newArrayList(); log.info("CollectionUtils partition {}", partition.size()); if (partition.size() >= bidVO.getPageNum()) { log.info("CollectionUtils partitionget {}", partition.get(bidVO.getPageNum() - 1)); log.info("CollectionUtils partitionget {}", resultList.size()); resultList = partition.get(bidVO.getPageNum() - 1); } return AjaxResult.success(new PaginationUtil.PageResult<>(resultList, total.intValue(), bidVO.getPageNum(), bidVO.getPageSize())); }else { log.info("CollectionUtils list is empty"); return AjaxResult.success(PaginationUtil.page(bidVO, list)); } } } private String checkLotStatus(BidVO bid) { log.info("checkLotStatus cacheMap {}",cacheMap); log.info("checkLotStatus bid {}",bid); Live live = cacheMap.viewLot(bid.getAuctionId(), bid.getLotId()); log.info("checkLotStatus live {}",live); if (Objects.isNull(live)) { List bids = bidService.selectBidList(Bid.builder().lotId(bid.getLotId()).build()); log.info("sync bids {}",bids); Lot lot = lotService.selectLotById(bid.getLotId()); log.info("sync lot {}",lot); LiveContext liveContext = LiveContext.builder().dbLot(lot).bidList(bids).build(); ruleHandlerHolder.sync(liveContext); log.info("sync liveContext {}",liveContext); live = liveContext.getLive(); live.setLot(lot); live.setFinish(0); cacheMap.putLive(live); log.info("cacheMap putLive {}",live); return null; } if (Constants.LOT_STATUS_WAITING.equals(live.getLot().getStatus())) { return "拍卖还未开始"; } if (!Lists.newArrayList(Constants.LOT_STATUS_BIDDING, Constants.LOT_STATUS_STARTING).contains(live.getLot().getStatus())) { return "拍卖已结束"; } return null; } private Long createBidRecord(Bid bid){ BidRecord record = BidRecord.builder() .lotId(bid.getLotId()) .bidNo(bid.getBidNo()) .userId(Long.parseLong(bid.getAccountId())) .amount(bid.getAmount()) .createTime(new Date()).build(); bidRecordMapper.insertBidRecord(record); return record.getId(); } private boolean hasPaidDeposit(Integer userId, Lot lot) { if (Objects.isNull(userId) || Objects.isNull(lot)) { return false; } LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper() .eq(DepositOrder::getUserId, userId) .eq(DepositOrder::getStatus, 1); if (Objects.nonNull(lot.getDeposit())) { queryWrapper.eq(DepositOrder::getDepositType, "拍品") .eq(DepositOrder::getLotId, lot.getId()); } else { queryWrapper.eq(DepositOrder::getDepositType, "拍卖会") .eq(DepositOrder::getAuctionId, lot.getAuctionId()); } return depositOrderMapper.selectCount(queryWrapper) > 0; } private void createBidRecordCache(Bid bid){ List bidList = new ArrayList<>(); BidRecord record = BidRecord.builder() .lotId(bid.getLotId()) .bidNo(bid.getBidNo()) .userId(Long.parseLong(bid.getAccountId())) .amount(bid.getAmount()) .createTime(new Date()).build(); bidList.add(record); redisCache.addToCacheList(String.format(Constants.REDIS_MAP_AUC_LOT_BIDRECORD_LIST_PREFIX, bid.getLotId()), bidList); } private int sort(Live live1, Live live2) { int sort1 = Objects.nonNull(live1.getLot().getSort()) ? live1.getLot().getSort() : 0; int sort2 = Objects.nonNull(live2.getLot().getSort()) ? live2.getLot().getSort() : 0; return (sort2 - sort1) == 0 ? (int)(live1.getCurrentEndTime() - live2.getCurrentEndTime()) : (sort2 - sort1); } private AjaxResult findBidPageFromDb(BidVO bidVO) { bidVO.setDelFlag(Constants.DEL_FLAG_NO_DELETE); startPage(bidVO); List data = bidService.selectBidList(bidVO); Integer userId = UserUtils.getSimpleUserInfo().getId(); data.forEach(bid -> { boolean sensitive=false; if (Objects.equals(1, bid.getAnonymous())) { bid.setAvatar("/MC143302022060920611/avatar/1666182630730.jpg"); bid.setAccount("匿名用户"); sensitive=true; } if (org.apache.commons.lang3.StringUtils.isBlank(bid.getAvatar())){ bid.setAvatar("/MC143302022060920611/avatar/1666182630730.jpg"); } if (org.apache.commons.lang3.StringUtils.isBlank(bid.getAccount())){ bid.setAccount("匿名用户"); sensitive=true; } bid.setCurrentAccount(Objects.equals(Integer.parseInt(bid.getAccountId()), userId)); if (!sensitive) { SensitiveDataUtils.handleViewDataSafe(bid); } }); Page page = (Page) data; return AjaxResult.success(new PaginationUtil.PageResult<>(((Page) data).getResult(), Integer.parseInt(page.getTotal() + ""), page.getPageNum(), bidVO.getPageSize())); } }