package com.ruoyi.quartz.task; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.date.DateField; import cn.hutool.core.date.DateUtil; import cn.hutool.core.map.MapUtil; import cn.hutool.core.thread.ThreadUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.json.JSONUtil; import co.elastic.clients.elasticsearch.core.BulkResponse; import co.elastic.clients.elasticsearch.core.bulk.BulkOperation; import co.elastic.clients.elasticsearch.core.bulk.BulkResponseItem; import com.alibaba.fastjson2.JSON; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.ruoyi.common.constant.CacheConstants; import com.ruoyi.common.constant.Constants; import com.ruoyi.common.constant.HttpStatus; import com.ruoyi.common.core.domain.SimpleEntity; import com.ruoyi.common.core.domain.entity.SysDictData; import com.ruoyi.common.core.redis.RedisCache; import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.utils.DateUtils; import com.ruoyi.common.utils.SecurityUtils; import com.ruoyi.framework.es.EsClientWrapper; import com.ruoyi.framework.notice.fs.FsNotice; import com.ruoyi.system.mapper.SysDictDataMapper; import com.ruoyi.xkt.domain.*; import com.ruoyi.xkt.dto.account.WithdrawPrepareResult; import com.ruoyi.xkt.dto.advertRound.pc.store.PCStoreRecommendDTO; import com.ruoyi.xkt.dto.advertRound.pc.store.PCStoreRecommendTempDTO; import com.ruoyi.xkt.dto.dailySale.DailySaleCusDTO; import com.ruoyi.xkt.dto.dailySale.DailySaleDTO; import com.ruoyi.xkt.dto.dailySale.DailySaleProdDTO; import com.ruoyi.xkt.dto.dailySale.WeekCateSaleDTO; import com.ruoyi.xkt.dto.dailyStoreProd.DailyStoreProdSaleDTO; import com.ruoyi.xkt.dto.dailyStoreTag.DailyStoreTagDTO; import com.ruoyi.xkt.dto.es.ESProductDTO; import com.ruoyi.xkt.dto.order.StoreOrderCancelDTO; import com.ruoyi.xkt.dto.order.StoreOrderRefund; import com.ruoyi.xkt.dto.picture.ProductPicSyncDTO; import com.ruoyi.xkt.dto.picture.ProductPicSyncResultDTO; import com.ruoyi.xkt.dto.storeProdColorPrice.StoreProdMinPriceDTO; import com.ruoyi.xkt.dto.storeProductFile.StoreProdFileLatestFourProdDTO; import com.ruoyi.xkt.dto.storeProductFile.StoreProdFileResDTO; import com.ruoyi.xkt.dto.useSearchHistory.UserSearchHistoryDTO; import com.ruoyi.xkt.dto.userBrowsingHistory.UserBrowsingHisDTO; import com.ruoyi.xkt.enums.*; import com.ruoyi.xkt.manager.PaymentManager; import com.ruoyi.xkt.manager.impl.ZtoExpressManagerImpl; import com.ruoyi.xkt.mapper.*; import com.ruoyi.xkt.service.*; import com.ruoyi.xkt.thirdpart.zto.ZtoRegion; import io.jsonwebtoken.lang.Assert; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; import java.io.IOException; import java.math.BigDecimal; import java.text.ParseException; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.util.*; import java.util.concurrent.TimeUnit; import java.util.function.Function; import java.util.stream.Collectors; /** * 鞋库通定时任务 * * @author ruoyi */ @Slf4j @Component("xktTask") @RequiredArgsConstructor public class XktTask { final DailySaleMapper dailySaleMapper; final StoreSaleMapper saleMapper; final StoreSaleDetailMapper saleDetailMapper; final StoreProductStorageMapper storageMapper; final DailySaleCustomerMapper dailySaleCusMapper; final DailySaleProductMapper dailySaleProdMapper; final SysProductCategoryMapper prodCateMapper; final WeekCateSaleMapper weekCateSaleMapper; final DailyStoreTagMapper dailyStoreTagMapper; final UserSubscriptionsMapper userSubsMapper; final UserFavoritesMapper userFavMapper; final StoreProductStockMapper stockMapper; final StoreMapper storeMapper; final StoreProductMapper storeProdMapper; final DailyProdTagMapper dailyProdTagMapper; final StoreProductCategoryAttributeMapper cateAttrMapper; final EsClientWrapper esClientWrapper; final AdvertMapper advertMapper; final AdvertRoundMapper advertRoundMapper; final RedisCache redisCache; final IAdvertRoundService advertRoundService; final IStoreOrderService storeOrderService; final IAssetService assetService; final IAlipayCallbackService alipayCallbackService; final FsNotice fsNotice; final List paymentManagers; final IStoreProductService storeProductService; final IPictureSearchService pictureSearchService; final PictureSearchMapper pictureSearchMapper; final StoreProductStatisticsMapper storeProdStatMapper; final StoreProductFileMapper storeProdFileMapper; final UserSearchHistoryMapper userSearchHisMapper; final UserBrowsingHistoryMapper userBrowHisMapper; final IPictureService pictureService; final NoticeMapper noticeMapper; final UserNoticeMapper userNoticeMapper; final UserSubscriptionsMapper userSubMapper; final StoreMemberMapper storeMemberMapper; final IExpressService expressService; final ZtoExpressManagerImpl ztoExpressManager; final SysDictDataMapper dictDataMapper; final StoreProductColorSizeMapper prodColorSizeMapper; final VoucherSequenceMapper voucherSequenceMapper; final IWebsitePCService websitePCService; final IWebsitePCIndexService websitePCIndexService; final IWebsitePCNewProdService websitePCNewProdService; final IWebsitePCStoreService websitePCStoreService; final IWebsiteAPPService websiteAPPService; @Value("${es.indexName}") private String ES_INDEX_NAME; /** * 每天执行定时任务 * 每年3月1日、6月1日、9月1日、12月1日执行 生成夏秋冬春标签 */ @Transactional public void seasonTag() { LocalDate today = LocalDate.now(); int month = today.getMonthValue(); int day = today.getDayOfMonth(); String seasonLabel = ""; if (month == 3 && day == 1) { seasonLabel = today.getYear() + "年夏季"; } else if (month == 6 && day == 1) { seasonLabel = today.getYear() + "年秋季"; } else if (month == 9 && day == 1) { seasonLabel = today.getYear() + "年冬季"; } else if (month == 12 && day == 1) { seasonLabel = today.getYear() + 1 + "年春季"; } if (StringUtils.isEmpty(seasonLabel)) { return; } log.info("生成季节标签:{}", seasonLabel); List dictDataList = this.dictDataMapper.selectList(new LambdaQueryWrapper() .eq(SysDictData::getDictType, Constants.RELEASE_YEAR_SEASON_DICT).eq(SysDictData::getDelFlag, Constants.UNDELETED) .eq(SysDictData::getStatus, "0")); // 当前最大排序 final Long maxSort = dictDataList.stream().max(Comparator.comparingLong(SysDictData::getDictSort)) .map(SysDictData::getDictSort).orElse(100L); // 往sys_dict_data表插入一条数据 SysDictData dictData = new SysDictData(); dictData.setDictLabel(seasonLabel); dictData.setDictValue(seasonLabel); dictData.setDictType(Constants.RELEASE_YEAR_SEASON_DICT); dictData.setDictSort(maxSort + 1); dictData.setStatus("0"); dictData.setCreateBy("admin"); this.dictDataMapper.insert(dictData); } /** * 每天凌晨01:00:01秒 更新store到redis中 */ public void saveStoreToRedis() { List storeList = this.storeMapper.selectList(new LambdaQueryWrapper() .eq(Store::getDelFlag, Constants.UNDELETED)); if (CollectionUtils.isEmpty(storeList)) { return; } storeList.forEach(store -> { redisCache.setCacheObject(CacheConstants.STORE_KEY + store.getId(), store); }); } /** * 凌晨01:01 更新推广轮次 */ @Transactional public void dailyAdvertRound() { List advertList = this.advertMapper.selectList(new LambdaQueryWrapper() .eq(Advert::getDelFlag, Constants.UNDELETED).eq(Advert::getOnlineStatus, AdOnlineStatus.ONLINE.getValue())); if (CollectionUtils.isEmpty(advertList)) { return; } // 正在投放 或 待投放列表 List advertRoundList = this.advertRoundMapper.selectList(new LambdaQueryWrapper() .eq(AdvertRound::getDelFlag, Constants.UNDELETED) .in(AdvertRound::getLaunchStatus, Arrays.asList(AdLaunchStatus.UN_LAUNCH.getValue(), AdLaunchStatus.LAUNCHING.getValue()))); // 投放轮次按照advertId进行分组 Map> advertRoundMap = advertRoundList.stream().collect(Collectors.groupingBy(AdvertRound::getAdvertId)); // 待更新或待新增的推广轮次列表 List updateList = new ArrayList<>(); advertList.forEach(advert -> { List roundList = advertRoundMap.get(advert.getId()); // 如果没有 投放中 或 待投放的推广轮次,则 一次性创建所有的轮次 if (CollectionUtils.isEmpty(roundList)) { // 播放的轮次 for (int playRound = 0; playRound < advert.getPlayRound(); playRound++) { // 如果i = 0 则表明从未创建过推广位,直接新建所有 final Integer launchStatus = playRound == 0 ? AdLaunchStatus.LAUNCHING.getValue() : AdLaunchStatus.UN_LAUNCH.getValue(); final LocalDate now = playRound == 0 ? LocalDate.now() : LocalDate.now().plusDays((long) advert.getPlayInterval() * playRound); // 间隔时间 final LocalDate endDate = now.plusDays(advert.getPlayInterval() - 1); // 按照播放数量依次生成下一轮播放的推广位 for (int playNum = 0; playNum < advert.getPlayNum(); playNum++) { // 依次按照26个字母顺序 如果i == 0 则A i == 1 则B i==2则C final String position = String.valueOf((char) ('A' + playNum)); // 当前播放轮次id final int roundId = playRound + 1; updateList.add(new AdvertRound().setAdvertId(advert.getId()).setTypeId(advert.getTypeId()).setRoundId(roundId).setLaunchStatus(launchStatus) .setStartTime(java.sql.Date.valueOf(now)).setEndTime(java.sql.Date.valueOf(endDate)).setPosition(position).setStartPrice(advert.getStartPrice()) .setSysIntercept(AdSysInterceptType.UN_INTERCEPT.getValue()).setShowType(advert.getShowType()) .setDisplayType(advert.getDisplayType()).setDeadline(advert.getDeadline()) .setBiddingStatus(AdBiddingStatus.UN_BIDDING.getValue()).setBiddingTempStatus(AdBiddingStatus.UN_BIDDING.getValue()) .setSymbol(Objects.equals(advert.getShowType(), AdShowType.POSITION_ENUM.getValue()) // 如果是位置枚举的推广位,则需要精确到某一个position的推广位,反之,若是时间范围,则直接精确到播放轮次即可 ? advert.getBasicSymbol() + roundId + position : advert.getBasicSymbol() + roundId)); } } } else { // 判断当天是否为播放轮次最小结束时间的下一天 最小结束时间为:yyyy-MM-dd格式 final String compareDateStr = DateTimeFormatter.ofPattern(DateUtils.YYYY_MM_DD).format(LocalDate.now().minusDays(1)); final Date minEndTime = roundList.stream().min(Comparator.comparing(AdvertRound::getEndTime)).map(AdvertRound::getEndTime).orElse(null); final String minEndTimeStr = ObjectUtils.isNotEmpty(minEndTime) ? DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD, minEndTime) : null; if (Objects.equals(compareDateStr, minEndTimeStr)) { // 当前所有播放轮次,用于判断是否修改了配置。必须在这里直接获取才行 final Integer currentRoundNum = roundList.stream().map(AdvertRound::getRoundId).max(Comparator.comparingInt(x -> x)).orElse(0); // 将播放轮次为1的推广轮置为:已过期 roundList.stream().filter(x -> Objects.equals(x.getRoundId(), AdRoundType.PLAY_ROUND.getValue())).forEach(x -> x.setLaunchStatus(AdLaunchStatus.EXPIRED.getValue())); // 将播放轮次 大于 1 的推广轮 依次减1 roundList.stream().filter(x -> x.getRoundId() > AdRoundType.PLAY_ROUND.getValue()).forEach(x -> x.setRoundId(x.getRoundId() - 1)); // 将播放轮次为1 且 投放状态为:待投放的 置为投放中 roundList.stream().filter(x -> Objects.equals(x.getRoundId(), AdRoundType.PLAY_ROUND.getValue()) && Objects.equals(x.getLaunchStatus(), AdLaunchStatus.UN_LAUNCH.getValue())).forEach(x -> x.setLaunchStatus(AdLaunchStatus.LAUNCHING.getValue())); // 非 “已过期”的播放轮次 重新生成每一轮的symbol roundList.stream() .filter(x -> !Objects.equals(x.getLaunchStatus(), AdLaunchStatus.EXPIRED.getValue())) .forEach(x -> x.setSymbol(Objects.equals(advert.getShowType(), AdShowType.POSITION_ENUM.getValue()) // 如果是位置枚举的推广位,则需要精确到某一个position的推广位,反之,若是时间范围,则直接精确到播放轮次即可 ? advert.getBasicSymbol() + x.getRoundId() + x.getPosition() : advert.getBasicSymbol() + x.getRoundId())); updateList.addAll(roundList); // 需要新增最后一个待播放轮次的推广(固定增加最后一个播放轮次) this.createAdvertRound(roundList, 1, advert, updateList); // 如果播放轮次有更新,则需重新判断 final int diffRound = advert.getPlayRound() - currentRoundNum; // diff < 0 代表轮次有减少,则不新增播放轮, diff == 0 则代表播放轮次不增不减,不做调整 if (diffRound > 0) { this.createAdvertRound(roundList, diffRound, advert, updateList); } } } }); if (CollectionUtils.isNotEmpty(updateList)) { this.advertRoundMapper.insertOrUpdate(updateList); } } /** * 凌晨01:04更新symbol对应的锁资源到redis中 */ public void saveSymbolToRedis() { advertRoundService.initAdvertLockMap(); } /** * 凌晨01:08更新各推广轮次结束时间 */ public void saveAdvertDeadlineToRedis() { // 直接调service方法,若当时redis出了问题,也方便第一时间 通过业务流程弥补 两边都有一个补偿机制 this.advertRoundService.saveAdvertDeadlineToRedis(); } /** * 凌晨01:10 同步档口销售数据 */ @Transactional public void dailySale() { // 使用LocalDate获取当前日期前一天,并转为 Date 格式 final Date yesterday = java.sql.Date.valueOf(LocalDate.now().minusDays(1)); // 先检查是否有该天的销售数据,若有则先删除,保证数据唯一性 List existList = this.dailySaleMapper.selectList(new LambdaQueryWrapper() .eq(DailySale::getVoucherDate, yesterday).eq(DailySale::getDelFlag, Constants.UNDELETED)); if (CollectionUtils.isNotEmpty(existList)) { this.dailySaleMapper.deleteByIds(existList.stream().map(DailySale::getId).collect(Collectors.toList())); } // 查询当前的销售数据 List saleList = this.dailySaleMapper.selectDailySale(yesterday); if (CollectionUtils.isEmpty(saleList)) { return; } this.dailySaleMapper.insert(saleList.stream().map(x -> BeanUtil.toBean(x, DailySale.class) .setVoucherDate(yesterday)).collect(Collectors.toList())); } /** * 凌晨01:15 同步档口客户销售数据 */ @Transactional public void dailySaleCustomer() { // 使用LocalDate获取当前日期前一天,并转为 Date 格式 final Date yesterday = java.sql.Date.valueOf(LocalDate.now().minusDays(1)); // 先检查是否有该天的销售数据,若有则先删除,保证数据唯一性 List existList = this.dailySaleCusMapper.selectList(new LambdaQueryWrapper() .eq(DailySaleCustomer::getVoucherDate, yesterday).eq(DailySaleCustomer::getDelFlag, Constants.UNDELETED)); if (CollectionUtils.isNotEmpty(existList)) { this.dailySaleCusMapper.deleteByIds(existList.stream().map(DailySaleCustomer::getId).collect(Collectors.toList())); } // 查询当前的客户销售数据 List cusSaleList = this.dailySaleCusMapper.selectDailySale(yesterday); if (CollectionUtils.isEmpty(cusSaleList)) { return; } this.dailySaleCusMapper.insert(cusSaleList.stream().map(x -> BeanUtil.toBean(x, DailySaleCustomer.class) .setVoucherDate(yesterday)).collect(Collectors.toList())); } /** * 凌晨01:20 同步档口商品销售数据 */ @Transactional public void dailySaleProduct() { // 使用LocalDate获取当前日期前一天,并转为 Date 格式 final Date yesterday = java.sql.Date.valueOf(LocalDate.now().minusDays(1)); // 先检查是否有该天的销售数据,若有则先删除,保证数据唯一性 List existList = this.dailySaleProdMapper.selectList(new LambdaQueryWrapper() .eq(DailySaleProduct::getVoucherDate, yesterday).eq(DailySaleProduct::getDelFlag, Constants.UNDELETED)); if (CollectionUtils.isNotEmpty(existList)) { this.dailySaleProdMapper.deleteByIds(existList.stream().map(DailySaleProduct::getId).collect(Collectors.toList())); } // 查询档口商品的销售数据 List saleList = this.dailySaleProdMapper.selectDailySale(yesterday); if (CollectionUtils.isEmpty(saleList)) { return; } this.dailySaleProdMapper.insert(saleList.stream().map(x -> BeanUtil.toBean(x, DailySaleProduct.class) .setVoucherDate(yesterday)).collect(Collectors.toList())); } /** * 凌晨01:25 同步商品最新分类排序 */ @Transactional public void dailyCategorySort() { // 系统所有的商品分类 List cateList = this.prodCateMapper.selectList(new LambdaQueryWrapper() .eq(SysProductCategory::getDelFlag, Constants.UNDELETED).eq(SysProductCategory::getStatus, Constants.UNDELETED)); if (CollectionUtils.isEmpty(cateList)) { throw new ServiceException("商品分类不存在!", HttpStatus.ERROR); } // 根据LocalDate 获取当前日期前一天 final Date yesterday = java.sql.Date.valueOf(LocalDate.now()); // 及当前日期前一天的前一周,并转为 Date 格式 final Date pastDate = java.sql.Date.valueOf(LocalDate.now().minusWeeks(1)); // 获取各项子分类最近一周的销售数量 List weekCateSaleList = this.weekCateSaleMapper.selectWeekCateSale(yesterday, pastDate); if (CollectionUtils.isEmpty(weekCateSaleList)) { return; } // 将各个小项销售数量转化为map Map itemCateCountMap = weekCateSaleList.stream().collect(Collectors.toMap(WeekCateSaleDTO::getProdCateId, WeekCateSaleDTO::getCount)); // 按照大类对应的各小类以此进行数量统计及排序 List sortList = new ArrayList<>(); cateList.stream() // 过滤掉父级为0的分类,以及父级为1的分类(父级为1的为子分类) .filter(x -> !Objects.equals(x.getParentId(), 0L) && !Objects.equals(x.getParentId(), 1L)) .collect(Collectors.groupingBy(SysProductCategory::getParentId)) .forEach((parentId, itemList) -> sortList.add(new WeekCateSaleDTO() {{ setCount(itemList.stream().mapToInt(x -> itemCateCountMap.getOrDefault(x.getId(), 0)).sum()); setProdCateId(parentId); }})); // 按照大类的数量倒序排列 sortList.sort(Comparator.comparing(WeekCateSaleDTO::getCount).reversed()); Map topCateSortMap = new LinkedHashMap<>(); // 按照sortList的顺序,结合 topCateMap 依次更新SysProductCategory 的 sortNum的排序 for (int i = 0; i < sortList.size(); i++) { topCateSortMap.put(sortList.get(i).getProdCateId(), i + 1); } // 顶级分类的数量 Integer topCateSize = Math.toIntExact(cateList.stream().filter(x -> Objects.equals(x.getParentId(), 1L)).count()); // 次级分类列表 List updateList = cateList.stream().filter(x -> Objects.equals(x.getParentId(), 1L)) // 如果存在具体销售数量,则按照具体销售数量排序,否则将排序置为最大值 .peek(x -> x.setOrderNum(topCateSortMap.getOrDefault(x.getId(), topCateSize))) .collect(Collectors.toList()); this.prodCateMapper.updateById(updateList); } /** * 凌晨01:30 更新档口的标签 */ @Transactional public void dailyStoreTag() { // 先删除所有的档口标签,保证数据唯一性 List existList = this.dailyStoreTagMapper.selectList(new LambdaQueryWrapper() .eq(DailyStoreTag::getDelFlag, Constants.UNDELETED)); if (CollectionUtils.isNotEmpty(existList)) { this.dailyStoreTagMapper.deleteByIds(existList.stream().map(DailyStoreTag::getId).collect(Collectors.toList())); } List tagList = new ArrayList<>(); // 根据LocalDate 获取当前日期前一天 final Date yesterday = java.sql.Date.valueOf(LocalDate.now()); // 使用LocalDate 获取当前日期前一天的前一周,并转为 Date 格式 final Date oneWeekAgo = java.sql.Date.valueOf(LocalDate.now().minusWeeks(1)); // 使用LocalDate 获取当前日期前一天的前一个月 final Date oneMonthAgo = java.sql.Date.valueOf(LocalDate.now().minusMonths(1)); // 1. 打 月销千件 标签 this.tagSaleThousand(yesterday, oneMonthAgo, tagList); // 2. 打 销量榜 标签 this.tagStoreSaleRank(yesterday, oneMonthAgo, tagList); // 3. 打 爆款频出 标签,根据销量前50的商品中 档口 先后顺序排列 this.tagHotRank(yesterday, oneMonthAgo, tagList); // 4. 打 新款频出 标签,根据最近一周档口商品上新速度,先后排序 this.tagNewProd(yesterday, oneWeekAgo, tagList); // 5. 打 关注榜 标签,根据关注量,进行排序 this.tagAttentionRank(yesterday, tagList); // 6. 打 库存榜 标签,根据库存量,进行排序 2025.10.11 注释 // this.tagStockTag(yesterday, oneMonthAgo, tagList); // 7. 打 七日上新 标签 this.tag7DaysNewTag(yesterday, oneWeekAgo, tagList); if (CollectionUtils.isEmpty(tagList)) { return; } this.dailyStoreTagMapper.insert(tagList); } /** * 凌晨01:40 更新商品标签 */ @Transactional public void dailyProdTag() throws IOException { // 先删除所有的商品标签,保证数据唯一性 List existList = this.dailyProdTagMapper.selectList(new LambdaQueryWrapper() .eq(DailyProdTag::getDelFlag, Constants.UNDELETED)); // 已存在于ES中的标签 Map> existProdTagMap = CollectionUtils.isEmpty(existList) ? new HashMap<>() : existList.stream().collect(Collectors.toMap(DailyProdTag::getStoreProdId, x -> new ArrayList<>(), (s1, s2) -> s2)); if (CollectionUtils.isNotEmpty(existList)) { this.dailyProdTagMapper.deleteByIds(existList.stream().map(DailyProdTag::getId).collect(Collectors.toList())); } // 根据LocalDate 获取当前日期前一天 yyyy-MM-dd 00:00:00 final Date yesterday = java.sql.Date.valueOf(LocalDate.now()); // 使用LocalDate 获取当前日期3天前,并转为 Date 格式 final Date fourDaysAgo = java.sql.Date.valueOf(LocalDate.now().minusDays(3)); // 使用LocalDate 获取当前日期前一天的前一周,并转为 Date 格式 final Date oneWeekAgo = java.sql.Date.valueOf(LocalDate.now().minusWeeks(1)); // 使用LocalDate 获取当前日期前一天的前一个月 final Date oneMonthAgo = java.sql.Date.valueOf(LocalDate.now().minusMonths(1)); List tagList = new ArrayList<>(); // 1. 打 月销千件 标签 this.tagMonthSaleThousand(yesterday, oneMonthAgo, tagList); // 2. 打 销量榜 标签 this.tagProdSaleTop10(yesterday, oneMonthAgo, tagList); // 3. 当月(近一月)爆款 this.tagMonthHot(yesterday, oneMonthAgo, tagList); // 4. 档口热卖 this.tagStoreHotTop5(yesterday, oneWeekAgo, tagList); // 5. 图搜榜 this.tagImgSearchTop10(yesterday, oneMonthAgo, tagList); // 6. 收藏榜 this.tagCollectionTop10(yesterday, oneMonthAgo, tagList); // 7. 下载榜 铺货榜 this.tagDownloadTop10(yesterday, oneMonthAgo, tagList); // 8. 三日上新 this.tagThreeDayNew(yesterday, fourDaysAgo, tagList); // 9. 七日上新 this.tagSevenDayNew(yesterday, fourDaysAgo, oneWeekAgo, tagList); // 10. 风格 this.tagStyle(yesterday, tagList); if (CollectionUtils.isEmpty(tagList)) { return; } this.dailyProdTagMapper.insert(tagList); // 最新的待更新的商品标签列表 Map> tagMap = tagList.stream().collect(Collectors .groupingBy(DailyProdTag::getStoreProdId, Collectors.mapping(DailyProdTag::getTag, Collectors.toList()))); existProdTagMap.forEach((storeProdId, tags) -> { if (!tagMap.containsKey(storeProdId)) { tagMap.put(storeProdId, tags); } }); // 更新商品的标签到ES this.updateESProdTags(tagMap); } /** * 凌晨01:45 将advert的数据暂存到redis中 */ public void saveAdvertToRedis() { List advertList = this.advertMapper.selectList(new LambdaQueryWrapper() .eq(Advert::getDelFlag, Constants.UNDELETED)); if (CollectionUtils.isEmpty(advertList)) { return; } advertList.forEach(advert -> redisCache.setCacheObject(CacheConstants.ADVERT_KEY + advert.getId(), advert, 1, TimeUnit.DAYS)); } /** * 凌晨02:00 更新档口商品的各项权重数据 */ @Transactional public void dailyProdWeight() { // 筛选非私款的商品 List storeProdList = this.storeProdMapper.selectList(new LambdaQueryWrapper() .eq(StoreProduct::getDelFlag, Constants.UNDELETED).eq(StoreProduct::getPrivateItem, EProductItemType.NON_PRIVATE_ITEM.getValue())); if (CollectionUtils.isEmpty(storeProdList)) { return; } Set updateProdIdSet = new HashSet<>(); final Date now = java.sql.Date.valueOf(LocalDate.now()); final Date fifteenDaysAgo = java.sql.Date.valueOf(LocalDate.now().minusDays(15)); final Date twoMonthAgo = java.sql.Date.valueOf(LocalDate.now().minusMonths(2)); // 获取 商品销售、商品浏览量、商品收藏量、商品下载量 最近2月 List statisticsList = this.storeProdStatMapper.selectList(new LambdaQueryWrapper() .eq(StoreProductStatistics::getDelFlag, Constants.UNDELETED).between(StoreProductStatistics::getVoucherDate, twoMonthAgo, now)); CollectionUtils.addAll(updateProdIdSet, statisticsList.stream().map(StoreProductStatistics::getStoreProdId).collect(Collectors.toSet())); // 商品浏览量 Map viewCountMap = statisticsList.stream().collect(Collectors.groupingBy(StoreProductStatistics::getStoreProdId, Collectors .summingLong(x -> ObjectUtils.defaultIfNull(x.getViewCount(), 0L)))); // 商品下载量 Map downCountMap = statisticsList.stream().collect(Collectors.groupingBy(StoreProductStatistics::getStoreProdId, Collectors .summingLong(x -> ObjectUtils.defaultIfNull(x.getDownloadCount(), 0L)))); // 商品收藏量 List userFavList = this.userFavMapper.selectList(new LambdaQueryWrapper() .eq(UserFavorites::getDelFlag, Constants.UNDELETED)); CollectionUtils.addAll(updateProdIdSet, userFavList.stream().map(UserFavorites::getStoreProdId).collect(Collectors.toSet())); Map userFavMap = userFavList.stream().collect(Collectors.groupingBy(UserFavorites::getStoreProdId, Collectors.summingLong(UserFavorites::getId))); // 商品销售量 最近15天 List saleDetailList = this.saleDetailMapper.selectList(new LambdaQueryWrapper() .eq(StoreSaleDetail::getDelFlag, Constants.UNDELETED).eq(StoreSaleDetail::getSaleType, SaleType.GENERAL_SALE.getValue()) .between(StoreSaleDetail::getVoucherDate, fifteenDaysAgo, now)); Map saleMap = saleDetailList.stream().collect(Collectors.groupingBy(StoreSaleDetail::getStoreProdId, Collectors.summingLong(StoreSaleDetail::getQuantity))); CollectionUtils.addAll(updateProdIdSet, saleMap.keySet()); List updateList = storeProdList.stream() .filter(x -> CollectionUtils.isEmpty(updateProdIdSet) || updateProdIdSet.contains(x.getId())) .map(x -> { final long viewCount = viewCountMap.getOrDefault(x.getId(), 0L); final long downloadCount = downCountMap.getOrDefault(x.getId(), 0L); final long favCount = userFavMap.getOrDefault(x.getId(), 0L); final long saleCount = saleMap.getOrDefault(x.getId(), 0L); // 计算推荐权重,权重计算公式为:(浏览次数 * 0.2 + 下载次数 * 0.3 + 收藏次数 * 0.2 + 销售量 * 0.3) final long recommendWeight = (long) (viewCount * 0.2 + downloadCount * 0.3 + favCount * 0.2 + saleCount * 0.3); // 根据销售数量计算销售权重,权重计算公式为:(销售量 * 0.7 + 下载次数 * 0.1 + 收藏次数 * 0.1 + 浏览次数 * 0.1) final long saleWeight = (long) (saleCount * 0.7 + downloadCount * 0.1 + favCount * 0.1 + viewCount * 0.1); // 计算人气权重,权重计算公式为:(浏览次数 * 0.3 + 下载次数 * 0.2 + 收藏次数 * 0.2 + 销售量 * 0.3) final long popularityWeight = (long) (viewCount * 0.3 + downloadCount * 0.2 + favCount * 0.2 + saleCount * 0.3); return x.setRecommendWeight(recommendWeight).setSaleWeight(saleWeight).setPopularityWeight(popularityWeight); }).collect(Collectors.toList()); if (CollectionUtils.isEmpty(updateList)) { return; } this.storeProdMapper.updateById(updateList); } /** * 凌晨02:10 更新PC档口馆推荐列表权重数据 */ @Transactional public void dailyStoreWeight() { // 筛选非私款的商品 List storeProdList = this.storeProdMapper.selectList(new LambdaQueryWrapper() .eq(StoreProduct::getDelFlag, Constants.UNDELETED).eq(StoreProduct::getPrivateItem, EProductItemType.NON_PRIVATE_ITEM.getValue())); if (CollectionUtils.isEmpty(storeProdList)) { return; } // 按照storeId 的维度,对档口权重 按照 recommendWeight 进行汇总,按照降序排列 Map storeWeightMap = storeProdList.stream().collect(Collectors.groupingBy(StoreProduct::getStoreId, Collectors.summingLong(x -> ObjectUtils.defaultIfNull(x.getRecommendWeight(), 0L)))); // 筛选每个档口最新的4个商品及主图 List storeList = this.storeMapper.selectList(new LambdaQueryWrapper().eq(Store::getDelFlag, Constants.UNDELETED)); Map storeMap = storeList.stream().collect(Collectors.toMap(Store::getId, Function.identity())); List storeTagList = this.dailyStoreTagMapper.selectList(new LambdaQueryWrapper() .eq(DailyStoreTag::getDelFlag, Constants.UNDELETED)); // 档口标签map Map> storeTagMap = storeTagList.stream().collect(Collectors .groupingBy(DailyStoreTag::getStoreId, Collectors.collectingAndThen(Collectors.toList(), list -> list.stream() .sorted(Comparator.comparing(DailyStoreTag::getType)).map(DailyStoreTag::getTag).collect(Collectors.toList())))); List latest4ProdList = this.storeProdFileMapper.selectLatestFourProdList(Collections.emptyList()); Map> latestProdMap = latest4ProdList.stream().collect(Collectors .groupingBy(StoreProdFileLatestFourProdDTO::getStoreId)); List storeRecommendList = new ArrayList<>(); storeWeightMap.forEach((storeId, recommendWeight) -> { final Store store = storeMap.get(storeId); storeRecommendList.add(new PCStoreRecommendTempDTO().setStoreId(storeId).setTags(storeTagMap.get(storeId)) .setAdvert(Boolean.FALSE).setRecommendWeight(recommendWeight) .setStoreWeight(ObjectUtils.isNotEmpty(store) ? store.getStoreWeight() : null) .setStoreName(ObjectUtils.isNotEmpty(store) ? store.getStoreName() : "") .setStoreAddress(ObjectUtils.isNotEmpty(store) ? store.getStoreAddress() : "") .setContactPhone(ObjectUtils.isNotEmpty(store) ? store.getContactPhone() : "") .setQqAccount(ObjectUtils.isNotEmpty(store) ? store.getQqAccount() : "") .setWechatAccount(ObjectUtils.isNotEmpty(store) ? store.getWechatAccount() : "") .setProdList(BeanUtil.copyToList(latestProdMap.get(storeId), PCStoreRecommendTempDTO.PCSRNewProdDTO.class))); }); if (CollectionUtils.isEmpty(storeRecommendList)) { return; } // 先按照 档口权重 倒序排,再按照 推荐权重 倒序排 storeRecommendList.sort(Comparator.comparing(PCStoreRecommendTempDTO::getStoreWeight, Comparator.nullsLast(Comparator.reverseOrder())) .thenComparing(PCStoreRecommendTempDTO::getRecommendWeight, Comparator.nullsLast(Comparator.reverseOrder()))); // 返回给前端的数据 不包含 storeWeight 和 storeRecommendWeight List recommendList = BeanUtil.copyToList(storeRecommendList, PCStoreRecommendDTO.class); // 放到redis中 redisCache.setCacheObject(CacheConstants.PC_STORE_RECOMMEND_LIST, recommendList); } /** * 凌晨02:15 更新用户搜索历史入库 */ @Transactional public void dailyUpdateUserSearchHistory() { Collection keyList = this.redisCache.scanKeys(CacheConstants.USER_SEARCH_HISTORY + "*"); if (CollectionUtils.isEmpty(keyList)) { return; } List redisSearchList = new ArrayList<>(); keyList.forEach(key -> { List tempList = this.redisCache.getCacheObject(key); CollectionUtils.addAll(redisSearchList, tempList); }); // 用户最新搜索列表 List insertSearchList = redisSearchList.stream().filter(x -> ObjectUtils.isEmpty(x.getId())).collect(Collectors.toList()); if (CollectionUtils.isNotEmpty(insertSearchList)) { this.userSearchHisMapper.insert(BeanUtil.copyToList(insertSearchList, UserSearchHistory.class)); } final Date sixMonthAgo = java.sql.Date.valueOf(LocalDate.now().minusMonths(6)); final Date now = java.sql.Date.valueOf(LocalDate.now()); // 将最新的数据更新到redis中 List latestSearchList = this.userSearchHisMapper.selectList(new LambdaQueryWrapper() .eq(UserSearchHistory::getDelFlag, Constants.UNDELETED).between(UserSearchHistory::getSearchTime, sixMonthAgo, now)); if (CollectionUtils.isEmpty(latestSearchList)) { return; } latestSearchList.stream().collect(Collectors.groupingBy(UserSearchHistory::getUserId)) .forEach((userId, list) -> { // 获取用户最新搜索的20条数据 list = list.stream().sorted(Comparator.comparing(UserSearchHistory::getSearchTime).reversed()).limit(20).collect(Collectors.toList()); // 反转列表 Collections.reverse(list); redisCache.setCacheObject(CacheConstants.USER_SEARCH_HISTORY + userId, BeanUtil.copyToList(list, UserSearchHistoryDTO.class)); }); } /** * 凌晨02:20 更新用户浏览记录入库 */ @Transactional public void dailyUpdateUserBrowsingHistory() { Collection keyList = this.redisCache.scanKeys(CacheConstants.USER_BROWSING_HISTORY + "*"); if (CollectionUtils.isEmpty(keyList)) { return; } List redisBrowsingList = new ArrayList<>(); keyList.forEach(key -> { List tempList = this.redisCache.getCacheObject(key); CollectionUtils.addAll(redisBrowsingList, tempList); }); // 用户最新浏览列表 List insertBrowsingList = redisBrowsingList.stream().filter(x -> ObjectUtils.isEmpty(x.getId())).collect(Collectors.toList()); if (CollectionUtils.isNotEmpty(insertBrowsingList)) { this.userBrowHisMapper.insert(BeanUtil.copyToList(insertBrowsingList, UserBrowsingHistory.class)); } final Date sixMonthAgo = java.sql.Date.valueOf(LocalDate.now().minusMonths(6)); final Date now = java.sql.Date.valueOf(LocalDate.now()); // 将最新的数据更新到redis中 List latestBrowsingList = this.userBrowHisMapper.selectList(new LambdaQueryWrapper() .eq(UserBrowsingHistory::getDelFlag, Constants.UNDELETED).between(UserBrowsingHistory::getBrowsingTime, sixMonthAgo, now)); if (CollectionUtils.isEmpty(latestBrowsingList)) { return; } latestBrowsingList.stream().collect(Collectors.groupingBy(UserBrowsingHistory::getUserId)) .forEach((userId, list) -> { // 按照浏览时间升序排 list.sort(Comparator.comparing(UserBrowsingHistory::getBrowsingTime)); redisCache.setCacheObject(CacheConstants.USER_BROWSING_HISTORY + userId, BeanUtil.copyToList(list, UserBrowsingHisDTO.class)); }); } /** * 凌晨02:25 更新系统热搜到redis中 */ @Transactional public void dailyUpdateSearchHotToRedis() { Collection keyList = this.redisCache.scanKeys(CacheConstants.USER_SEARCH_HISTORY + "*"); if (CollectionUtils.isEmpty(keyList)) { return; } List redisSearchList = new ArrayList<>(); keyList.forEach(key -> { List tempList = this.redisCache.getCacheObject(key); CollectionUtils.addAll(redisSearchList, tempList); }); Map searchCountMap = redisSearchList.stream().collect(Collectors.groupingBy(UserSearchHistoryDTO::getSearchContent, Collectors.summingLong(UserSearchHistoryDTO::getUserId))); // searchCountMap 按照 value 大小倒序排,取前20条热搜 List> top20List = searchCountMap.entrySet().stream().sorted(Map.Entry.comparingByValue().reversed()) .limit(20).collect(Collectors.toList()); List top20Keys = top20List.stream().map(Map.Entry::getKey).collect(Collectors.toList()); redisCache.setCacheObject(CacheConstants.SEARCH_HOT_KEY, top20Keys); } /** * 凌晨02:30 更新统计图搜热款 */ public void imgSearchTopProductStatistics() { log.info("-------------统计图搜热款开始-------------"); pictureSearchService.cacheImgSearchTopProduct(); log.info("-------------统计图搜热款结束-------------"); } /** * 凌晨02:35 更新试用期过期档口 */ public void autoCloseTrialStore() { } /** * 凌晨02:40 更新年费过期档口 */ public void autoCloseTimeoutStore() { // TODO 更新未交年费档口 // TODO 更新未交年费档口 } /** * 凌晨02:45 更新档口会员过期 */ public void autoCloseExpireStoreMember() { final Date yesterday = java.sql.Date.valueOf(LocalDate.now().minusDays(1)); // 截止昨天,会员过期的档口 必须是正式使用的会员 List expireList = this.storeMemberMapper.selectList(new LambdaQueryWrapper() .eq(StoreMember::getDelFlag, Constants.UNDELETED).eq(StoreMember::getMemberStatus, StoreMemberStatus.AUDIT_PASS.getValue()) .eq(StoreMember::getEndTime, yesterday)); if (CollectionUtils.isEmpty(expireList)) { return; } expireList.forEach(x -> x.setDelFlag(Constants.DELETED)); this.storeMemberMapper.updateById(expireList); // 删除redis中过期会员 expireList.forEach(x -> redisCache.deleteObject(CacheConstants.STORE_MEMBER + x.getId())); } /** * 凌晨02:50 将档口会员存到redis中 */ public void saveStoreMemberToRedis() { List memberList = this.storeMemberMapper.selectList(new LambdaQueryWrapper() .eq(StoreMember::getMemberStatus, StoreMemberStatus.AUDIT_PASS.getValue()).eq(StoreMember::getDelFlag, Constants.UNDELETED)); if (CollectionUtils.isEmpty(memberList)) { return; } memberList.forEach(x -> redisCache.setCacheObject(CacheConstants.STORE_MEMBER + x.getStoreId(), x)); } /** * 凌晨02:55 更新APP商品销量榜、分类商品销量榜 */ public void dailyProdTopSale() { final Date oneMonthAgo = java.sql.Date.valueOf(LocalDate.now().minusMonths(1)); final Date now = java.sql.Date.valueOf(LocalDate.now()); // 销量前100的ID列表,直接放到redis中 List top50ProdList = this.dailySaleProdMapper.prodSaleTop50List(oneMonthAgo, now); if (CollectionUtils.isNotEmpty(top50ProdList)) { redisCache.setCacheObject(CacheConstants.TOP_50_SALE_PROD, top50ProdList); } // 按照商品分类来进行销量排序 List cateSaleTop50ProdList = this.dailySaleProdMapper.prodCateSaleTop50List(oneMonthAgo, now); if (CollectionUtils.isEmpty(cateSaleTop50ProdList)) { return; } // 将各个分类销量数据存到redis中 redisCache.setCacheObject(CacheConstants.CATE_TOP_50_SALE_PROD, cateSaleTop50ProdList); } /** * 凌晨03:00更新档口权重到redis */ public void updateStoreWeightToES() { // 找到昨天开通会员的所有档口 List memberList = this.storeMemberMapper.selectList(new LambdaQueryWrapper() .eq(StoreMember::getDelFlag, Constants.UNDELETED).eq(StoreMember::getMemberStatus, StoreMemberStatus.AUDIT_PASS.getValue()) .eq(StoreMember::getVoucherDate, java.sql.Date.valueOf(LocalDate.now().minusDays(1)))); if (CollectionUtils.isEmpty(memberList)) { return; } final List storeIdList = memberList.stream().map(StoreMember::getStoreId).collect(Collectors.toList()); List storeList = this.storeMapper.selectList(new LambdaQueryWrapper() .eq(Store::getDelFlag, Constants.UNDELETED).in(Store::getId, storeIdList)); // 非私款商品才会计入权重 List storeProdList = this.storeProdMapper.selectList(new LambdaQueryWrapper() .eq(StoreProduct::getDelFlag, Constants.UNDELETED).eq(StoreProduct::getPrivateItem, EProductItemType.NON_PRIVATE_ITEM.getValue()) .in(StoreProduct::getStoreId, storeIdList)); if (CollectionUtils.isEmpty(storeProdList)) { return; } // 档口权重map Map storeWeightMap = storeList.stream().collect(Collectors .toMap(Store::getId, x -> ObjectUtils.defaultIfNull(x.getStoreWeight(), 0))); // 构建一个批量数据集合 List list = new ArrayList<>(); storeProdList.forEach(storeProd -> { // 构建部分文档更新请求 list.add(new BulkOperation.Builder().update(u -> u .action(a -> a.doc(new HashMap() {{ put("storeWeight", ObjectUtils.defaultIfNull(storeWeightMap.get(storeProd.getStoreId()), Constants.WEIGHT_DEFAULT_ZERO)); }})) .id(String.valueOf(storeProd.getId())) .index(ES_INDEX_NAME)) .build()); }); try { // 调用bulk方法执行批量更新操作 BulkResponse bulkResponse = esClientWrapper.getEsClient().bulk(e -> e.index(ES_INDEX_NAME).operations(list)); log.info("定时任务,批量更新档口权重到 ES 成功的 id列表: {}", bulkResponse.items().stream().map(BulkResponseItem::id).collect(Collectors.toList())); // 有哪些没执行成功的,需要发飞书通知 List successIdList = bulkResponse.items().stream().map(BulkResponseItem::id).collect(Collectors.toList()); List unExeIdList = storeProdList.stream().map(String::valueOf).filter(x -> !successIdList.contains(x)).collect(Collectors.toList()); if (CollectionUtils.isNotEmpty(unExeIdList)) { fsNotice.sendMsg2DefaultChat("定时任务,批量更新档口权重到 ES 失败", "以下storeProdId未执行成功: " + unExeIdList); } } catch (Exception e) { log.error("定时任务,批量更新档口权重到 ES 失败", e); fsNotice.sendMsg2DefaultChat("全部失败,定时任务批量更新档口权重到 ES 失败", storeProdList.stream().map(StoreProduct::getId).map(String::valueOf).collect(Collectors.joining(","))); } } /** * 凌晨03:05更新档口访问量 */ @Transactional public void updateStoreVisitCount() { // 档口访问量 Map storeVisitMap = redisCache.getCacheMap(CacheConstants.STORE_VISIT_COUNT); if (MapUtil.isEmpty(storeVisitMap)) { return; } List storeList = this.storeMapper.selectList(new LambdaQueryWrapper() .eq(Store::getDelFlag, Constants.UNDELETED)); if (CollectionUtils.isEmpty(storeList)) { return; } storeList.forEach(store -> { Long viewCount = storeVisitMap.getOrDefault(store.getId().toString(), 0L); Long existViewCount = ObjectUtils.defaultIfNull(store.getViewCount(), 0L); store.setViewCount(existViewCount + viewCount); // 清除当日缓存 redisCache.deleteCacheMapValue(CacheConstants.STORE_VISIT_COUNT, store.getId().toString()); }); // 更新档口访问量 this.storeMapper.updateById(storeList); } /** * 凌晨04:00更新最新的广告展示数据 */ @Transactional public void clearAndUpdateAdvertShowData() { // 1. PC 首页顶部通栏 redisCache.deleteObject(CacheConstants.PC_ADVERT + CacheConstants.PC_ADVERT_INDEX_TOP); this.websitePCIndexService.getPcIndexTop(); // 2. PC 首页 顶部左侧 轮播图 redisCache.deleteObject(CacheConstants.PC_ADVERT + CacheConstants.PC_ADVERT_INDEX_TOP_LEFT); this.websitePCIndexService.getPcIndexTopLeftBanner(); // 3. PC 首页 顶部右侧 纵向轮播图 redisCache.deleteObject(CacheConstants.PC_ADVERT + CacheConstants.PC_ADVERT_INDEX_TOP_RIGHT); this.websitePCIndexService.getPcIndexTopRightBanner(); // 4. PC 首页 销售榜 redisCache.deleteObject(CacheConstants.PC_ADVERT + CacheConstants.PC_ADVERT_INDEX_MID_SALE); this.websitePCIndexService.getPcIndexMidSaleList(); // 5. PC 首页 风格榜 redisCache.deleteObject(CacheConstants.PC_ADVERT + CacheConstants.PC_ADVERT_INDEX_MID_STYLE); this.websitePCIndexService.getPcIndexMidStyleList(); // 6. PC 首页 人气榜 redisCache.deleteObject(CacheConstants.PC_ADVERT + CacheConstants.PC_ADVERT_INDEX_BOTTOM_POPULAR); this.websitePCIndexService.getPcIndexBottomPopularList(); // 7. PC 首页 固定侧边栏 redisCache.deleteObject(CacheConstants.PC_ADVERT + CacheConstants.PC_ADVERT_INDEX_FIXED_EAR); this.websitePCIndexService.getPcIndexFixedEar(); // 8. 获取搜索框下档口名称 redisCache.deleteObject(CacheConstants.PC_ADVERT + CacheConstants.PC_ADVERT_INDEX_SEARCH_UNDERLINE_STORE_NAME); this.websitePCIndexService.getPcIndexSearchUnderlineStoreName(); // 9. 搜索框中推荐商品 redisCache.deleteObject(CacheConstants.PC_ADVERT + CacheConstants.PC_ADVERT_INDEX_SEARCH_RECOMMEND_PROD); this.websitePCIndexService.getPcIndexSearchRecommendProd(); // 10. PC 新品馆 顶部左侧 轮播图 redisCache.deleteObject(CacheConstants.PC_ADVERT + CacheConstants.PC_ADVERT_NEW_TOP_LEFT); this.websitePCNewProdService.getPcNewTopLeftBanner(); // 11. PC 新品馆 顶部右侧 轮播图 redisCache.deleteObject(CacheConstants.PC_ADVERT + CacheConstants.PC_ADVERT_NEW_TOP_RIGHT); this.websitePCNewProdService.getPcNewTopRight(); // 12. PC 新品馆 品牌馆 redisCache.deleteObject(CacheConstants.PC_ADVERT + CacheConstants.PC_ADVERT_NEW_MID_BRAND); this.websitePCNewProdService.getPcNewMidBrandList(); // 13. PC 新品馆 热卖榜左侧 redisCache.deleteObject(CacheConstants.PC_ADVERT + CacheConstants.PC_ADVERT_NEW_MID_HOT_LEFT); this.websitePCNewProdService.getPcNewMidHotLeftList(); // 14. PC 新品馆 热卖榜右侧商品 redisCache.deleteObject(CacheConstants.PC_ADVERT + CacheConstants.PC_ADVERT_NEW_MID_HOT_RIGHT); this.websitePCNewProdService.getPcNewMidHotRightList(); // 15. PC 新品馆 底部横幅 redisCache.deleteObject(CacheConstants.PC_ADVERT + CacheConstants.PC_ADVERT_NEW_BOTTOM_BANNER); this.websitePCNewProdService.getPcNewBottomBannerList(); // 16. PC 档口馆 顶部横幅及商品 redisCache.deleteObject(CacheConstants.PC_ADVERT + CacheConstants.PC_ADVERT_STORE_TOP_BANNER); this.websitePCStoreService.getPcStoreTopBannerList(); // 17. PC 档口馆 中间横幅 redisCache.deleteObject(CacheConstants.PC_ADVERT + CacheConstants.PC_ADVERT_STORE_MID_BANNER); this.websitePCStoreService.getPcStoreMidBannerList(); // 18. 以图搜款 广告 redisCache.deleteObject(CacheConstants.PC_ADVERT + CacheConstants.PIC_SEARCH); this.websitePCService.getPicSearchList(); // 19. PC 用户中心 redisCache.deleteObject(CacheConstants.PC_ADVERT + CacheConstants.PC_USER_CENTER); this.websitePCService.getPcUserCenterList(); // 20. PC 下载页 redisCache.deleteObject(CacheConstants.PC_ADVERT + CacheConstants.PC_DOWNLOAD); this.websitePCService.getPcDownloadList(); // 21. PC 搜索结果广告列表 redisCache.deleteObject(CacheConstants.PC_ADVERT + CacheConstants.PC_SEARCH_RESULT_ADVERT); this.websitePCService.getPcSearchAdvertList(); // 22. APP 首页顶部轮播图 redisCache.deleteObject(CacheConstants.APP_ADVERT + CacheConstants.APP_INDEX_TOP_BANNER); this.websiteAPPService.getAppIndexTopBanner(); // 23. APP 首页中部品牌好货 redisCache.deleteObject(CacheConstants.APP_ADVERT + CacheConstants.APP_INDEX_MID_BRAND); this.websiteAPPService.getAppIndexMidBrand(); // 24. APP 首页热卖精选右侧固定位置 redisCache.deleteObject(CacheConstants.APP_ADVERT + CacheConstants.APP_INDEX_HOT_SALE_RIGHT_FIX); this.websiteAPPService.getAppIndexHotSaleRightFix(); // 25. APP 分类页 redisCache.deleteObject(CacheConstants.APP_ADVERT + CacheConstants.APP_CATE); this.websiteAPPService.getAppCateList(); // 26. APP 我的猜你喜欢列表 redisCache.deleteObject(CacheConstants.APP_ADVERT + CacheConstants.APP_OWN_GUESS_LIKE); this.websiteAPPService.getAppOwnGuessLikeList(); } /** * 每月第一天凌晨5:00更新voucher_sequence */ @Transactional public void resetVoucherSequence() { List voucherSequenceList = this.voucherSequenceMapper.selectList(new LambdaQueryWrapper() .eq(VoucherSequence::getDelFlag, Constants.UNDELETED)); // 全部更新为1 voucherSequenceList.forEach(x -> x.setNextSequence(1)); this.voucherSequenceMapper.updateById(voucherSequenceList); } /** * 每晚22:00:10 更新广告位竞价状态 将biddingTempStatus赋值给biddingStatus */ @Transactional public void updateAdvertRoundBiddingStatus() throws ParseException { this.advertRoundService.updateBiddingStatus(); } /** * 每个小时执行一次,发布商品 */ @Transactional public void hourPublicStoreProduct() { // 获取当前时间 格式化为 yyyy-MM-dd HH:00:00 String hourTime = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:00:00").format(LocalDateTime.now()); // 当前整点待发布的商品 必须为非私款商品 List unPublicList = this.storeProdMapper.selectList(new LambdaQueryWrapper() .eq(StoreProduct::getDelFlag, Constants.UNDELETED).eq(StoreProduct::getListingWaySchedule, hourTime) .eq(StoreProduct::getPrivateItem, EProductItemType.NON_PRIVATE_ITEM.getValue()) .eq(StoreProduct::getProdStatus, EProductStatus.UN_PUBLISHED.getValue())); if (CollectionUtils.isEmpty(unPublicList)) { return; } final List storeProdIdList = unPublicList.stream().map(StoreProduct::getId).map(String::valueOf).collect(Collectors.toList()); // 获取所有的商品的第一张主图 List mainPicList = this.storeProdFileMapper.selectMainPic(storeProdIdList); // 所有的商品主图map Map> mainPicMap = mainPicList.stream().filter(x -> Objects.equals(x.getFileType(), FileType.MAIN_PIC.getValue())) .collect(Collectors.groupingBy(StoreProdFileResDTO::getStoreProdId, Collectors.mapping(StoreProdFileResDTO::getFileUrl, Collectors.toList()))); // 第一张商品主图map Map firstMainPicMap = mainPicList.stream().filter(x -> Objects.equals(x.getFileType(), FileType.MAIN_PIC.getValue())) .filter(x -> Objects.equals(x.getOrderNum(), Constants.ORDER_NUM_1)).collect(Collectors .toMap(StoreProdFileResDTO::getStoreProdId, x -> x)); // 主图视频map Map mainVideoMap = mainPicList.stream().filter(x -> Objects.equals(x.getFileType(), FileType.MAIN_PIC_VIDEO.getValue())) .collect(Collectors.toMap(StoreProdFileResDTO::getStoreProdId, x -> true)); // 所有的分类 List prodCateList = this.prodCateMapper.selectList(new LambdaQueryWrapper() .eq(SysProductCategory::getDelFlag, Constants.UNDELETED)); Map prodCateMap = prodCateList.stream().collect(Collectors.toMap(SysProductCategory::getId, x -> x)); // 获取当前商品最低价格 Map prodMinPriceMap = this.prodColorSizeMapper.selectStoreProdMinPriceList(storeProdIdList).stream().collect(Collectors .toMap(StoreProdMinPriceDTO::getStoreProdId, StoreProdMinPriceDTO::getPrice)); // 档口商品的属性map Map cateAttrMap = this.cateAttrMapper.selectList(new LambdaQueryWrapper() .eq(StoreProductCategoryAttribute::getDelFlag, Constants.UNDELETED).in(StoreProductCategoryAttribute::getStoreProdId, storeProdIdList)) .stream().collect(Collectors.toMap(StoreProductCategoryAttribute::getStoreProdId, x -> x)); // 档口商品对应的档口 Map storeMap = this.storeMapper.selectList(new LambdaQueryWrapper().eq(Store::getDelFlag, Constants.UNDELETED) .in(Store::getId, unPublicList.stream().map(StoreProduct::getStoreId).collect(Collectors.toList()))) .stream().collect(Collectors.toMap(Store::getId, x -> x)); // 构建批量操作请求 List bulkOperations = new ArrayList<>(); for (StoreProduct product : unPublicList) { final SysProductCategory cate = prodCateMap.get(product.getProdCateId()); final SysProductCategory parCate = ObjectUtils.isEmpty(cate) ? null : prodCateMap.get(cate.getParentId()); final Store store = storeMap.get(product.getStoreId()); final StoreProdFileResDTO mainPic = firstMainPicMap.get(product.getId()); final BigDecimal prodMinPrice = prodMinPriceMap.get(product.getId()); final StoreProductCategoryAttribute cateAttr = cateAttrMap.get(product.getId()); final Boolean hasVideo = mainVideoMap.getOrDefault(product.getId(), Boolean.FALSE); ESProductDTO esProductDTO = new ESProductDTO().setStoreProdId(product.getId().toString()).setProdArtNum(product.getProdArtNum()) .setHasVideo(hasVideo).setProdCateId(product.getProdCateId().toString()).setCreateTime(DateUtils.getTime()) .setProdCateName(ObjectUtils.isNotEmpty(cate) ? cate.getName() : "") .setSaleWeight("0").setRecommendWeight("0").setPopularityWeight("0") .setMainPicUrl(ObjectUtils.isNotEmpty(mainPic) ? mainPic.getFileUrl() : "") .setMainPicName(ObjectUtils.isNotEmpty(mainPic) ? mainPic.getFileName() : "") .setMainPicSize(ObjectUtils.isNotEmpty(mainPic) ? mainPic.getFileSize() : BigDecimal.ZERO) .setParCateId(ObjectUtils.isNotEmpty(parCate) ? parCate.getId().toString() : "") .setParCateName(ObjectUtils.isNotEmpty(parCate) ? parCate.getName() : "") .setProdPrice(ObjectUtils.isNotEmpty(prodMinPrice) ? prodMinPrice.toString() : "") .setSeason(ObjectUtils.isNotEmpty(cateAttr) ? cateAttr.getSuitableSeason() : "") .setProdStatus(product.getProdStatus().toString()) .setStoreId(product.getStoreId().toString()) .setStoreName(ObjectUtils.isNotEmpty(store) ? store.getStoreName() : "") .setStyle(ObjectUtils.isNotEmpty(cateAttr) ? cateAttr.getStyle() : "") .setTags(ObjectUtils.isNotEmpty(cateAttr) ? Collections.singletonList(cateAttr.getStyle()) : new ArrayList<>()) .setProdTitle(product.getProdTitle()); // 创建更新操作 BulkOperation bulkOperation = new BulkOperation.Builder() .index(i -> i // 使用商品ID作为文档ID .id(String.valueOf(product.getId())) // 索引名称 .index(ES_INDEX_NAME) // 插入的数据 .document(esProductDTO)) .build(); bulkOperations.add(bulkOperation); this.createNotice(product, store.getStoreName()); } // 执行批量插入 try { BulkResponse bulkResponse = esClientWrapper.getEsClient().bulk(b -> b.index(ES_INDEX_NAME).operations(bulkOperations)); log.info("定时发布商品,批量新增商品到 ES 成功的 id列表: {}", bulkResponse.items().stream().map(BulkResponseItem::id).collect(Collectors.toList())); // 有哪些没执行成功的,需要发飞书通知 List successIdList = bulkResponse.items().stream().map(BulkResponseItem::id).collect(Collectors.toList()); List unExeIdList = unPublicList.stream().map(String::valueOf).filter(x -> !successIdList.contains(x)).collect(Collectors.toList()); if (CollectionUtils.isNotEmpty(unExeIdList)) { fsNotice.sendMsg2DefaultChat("定时发布商品,批量新增商品到 ES 失败", "以下storeProdId未执行成功: " + unExeIdList); } } catch (Exception e) { log.error("定时发布商品,批量新增商品到 ES 失败", e); fsNotice.sendMsg2DefaultChat("定时发布商品,批量新增商品到 ES 失败", unPublicList.stream().map(StoreProduct::getId).map(String::valueOf).collect(Collectors.joining(","))); } for (StoreProduct product : unPublicList) { List mainPicUrlList = mainPicMap.get(product.getId()); if (CollUtil.isEmpty(mainPicUrlList)) { return; } this.sync2ImgSearchServer(product.getId(), mainPicUrlList); } } /** * 自动关闭超时订单 */ public void autoCloseTimeoutStoreOrder() { log.info("-------------自动关闭超时订单开始-------------"); Integer batchCount = 20; Date beforeDate = DateUtil.offset(new Date(), DateField.MINUTE, -60); List storeOrderIds = storeOrderService.listNeedAutoCloseOrder(beforeDate, batchCount); for (Long storeOrderId : storeOrderIds) { log.info("开始处理: {}", storeOrderId); try { StoreOrderCancelDTO cancelDTO = new StoreOrderCancelDTO(); cancelDTO.setStoreOrderId(storeOrderId); cancelDTO.setRemark("超时订单自动关闭"); storeOrderService.cancelOrder(cancelDTO); } catch (Exception e) { log.error("自动关闭超时订单异常", e); fsNotice.sendMsg2DefaultChat("自动关闭超时订单异常", "订单ID:" + storeOrderId); } } log.info("-------------自动关闭超时订单结束-------------"); } public void autoCompleteStoreOrder() { log.info("-------------自动完成已发货订单开始-------------"); Integer batchCount = 20; Date beforeDate = DateUtil.offset(new Date(), DateField.DAY_OF_YEAR, -15); List storeOrderIds = storeOrderService.listNeedAutoCompleteOrder(beforeDate, batchCount); for (Long storeOrderId : storeOrderIds) { log.info("开始处理: {}", storeOrderId); try { storeOrderService.completeOrder(storeOrderId, -1L); } catch (Exception e) { log.error("自动完成已发货订单异常", e); fsNotice.sendMsg2DefaultChat("自动完成已发货订单异常", "订单ID:" + storeOrderId); } } log.info("-------------自动完成已发货订单结束-------------"); } public void autoCompletePendingShipmentStoreOrder() { log.info("-------------自动完成待发货订单开始-------------"); List storeOrderIds = storeOrderService.listNeedAutoCompletePendingShipmentOrderIds(); for (Long storeOrderId : storeOrderIds) { log.info("开始处理: {}", storeOrderId); try { storeOrderService.completeOrder(storeOrderId, -1L); } catch (Exception e) { log.error("自动完成待发货订单异常", e); fsNotice.sendMsg2DefaultChat("自动完成待发货订单异常", "订单ID:" + storeOrderId); } } log.info("-------------自动完成待发货订单结束-------------"); } public void autoRefundStoreOrder() { log.info("-------------自动订单退款开始-------------"); Integer batchCount = 20; List storeOrderIds = storeOrderService.listNeedAutoRefundOrder(batchCount); for (Long storeOrderId : storeOrderIds) { log.info("开始处理: {}", storeOrderId); try { StoreOrderRefund storeOrderRefund = storeOrderService.prepareRefundByOriginOrder(storeOrderId); callRefund(storeOrderRefund); } catch (Exception e) { log.error("自动订单退款异常", e); fsNotice.sendMsg2DefaultChat("自动订单退款异常", "订单ID:" + storeOrderId); } } log.info("-------------自动订单退款结束-------------"); } /** * 继续处理退款(异常中断补偿,非正常流程) */ public void continueProcessRefund() { log.info("-------------继续处理退款开始-------------"); Integer batchCount = 20; List storeOrderRefunds = storeOrderService.listNeedContinueRefundOrder(batchCount); for (StoreOrderRefund storeOrderRefund : storeOrderRefunds) { log.info("开始处理: {}", storeOrderRefund); callRefund(storeOrderRefund); } log.info("-------------继续处理退款结束-------------"); } private void callRefund(StoreOrderRefund storeOrderRefund) { try { //支付宝接口要求:同一笔交易的退款至少间隔3s后发起 String markKey = CacheConstants.STORE_ORDER_REFUND_PROCESSING_MARK + storeOrderRefund.getRefundOrder().getId(); boolean less3s = redisCache.hasKey(markKey); if (less3s) { log.warn("订单[{}]退款间隔小于3s跳过执行", storeOrderRefund.getRefundOrder().getId()); return; } PaymentManager paymentManager = getPaymentManager(EPayChannel.of(storeOrderRefund.getRefundOrder().getPayChannel())); //查询退款结果 ENetResult queryResult = paymentManager.queryStoreOrderRefundResult( storeOrderRefund.getRefundOrder().getOrderNo(), storeOrderRefund.getOriginOrder().getOrderNo()); if (ENetResult.SUCCESS == queryResult) { //退款成功 //支付状态->已支付,收款单到账 storeOrderService.refundSuccess(storeOrderRefund.getRefundOrder().getId(), storeOrderRefund.getRefundOrderDetails().stream().map(SimpleEntity::getId).collect(Collectors.toList()), null); } else { //可能是退款失败,也可能是退款处理中,重复调用支付宝接口时只要参数正确也不会重复退款 boolean success = paymentManager.refundStoreOrder(storeOrderRefund); //标记 redisCache.setCacheObject(markKey, 1, 3, TimeUnit.SECONDS); if (success) { //支付状态->已支付,收款单到账 storeOrderService.refundSuccess(storeOrderRefund.getRefundOrder().getId(), storeOrderRefund.getRefundOrderDetails().stream().map(SimpleEntity::getId).collect(Collectors.toList()), null); } else { fsNotice.sendMsg2DefaultChat("退款失败", "参数: " + JSON.toJSONString(storeOrderRefund)); } } } catch (Exception e) { log.error("继续处理退款异常", e); fsNotice.sendMsg2DefaultChat("退款异常", "参数: " + JSON.toJSONString(storeOrderRefund)); } } /** * 继续处理档口提现(异常中断补偿,非正常流程) */ public void continueProcessWithdraw() { log.info("-------------继续处理档口提现开始-------------"); Integer batchCount = 20; Map> map = assetService.getNeedContinueWithdrawGroupMap(batchCount); for (Map.Entry> entry : map.entrySet()) { PaymentManager paymentManager = getPaymentManager(entry.getKey()); for (WithdrawPrepareResult prepareResult : entry.getValue()) { log.info("开始处理: {}", prepareResult); try { //查询转账结果 ENetResult queryResult = paymentManager.queryTransferResult(prepareResult.getBillNo()); if (ENetResult.SUCCESS == queryResult) { //转账成功,直接到账 assetService.withdrawSuccess(prepareResult.getFinanceBillId()); } else if (ENetResult.FAILURE == queryResult) { //转账失败,尝试重新支付 boolean success = paymentManager.transfer(prepareResult.getBillNo(), prepareResult.getAccountOwnerNumber(), prepareResult.getAccountOwnerName(), prepareResult.getAmount()); //付款单到账 if (success) { assetService.withdrawSuccess(prepareResult.getFinanceBillId()); } else { fsNotice.sendMsg2DefaultChat("档口提现失败", "参数: " + JSON.toJSONString(prepareResult)); } } else { log.warn("档口提现支付宝处理中: {}", prepareResult); } } catch (Exception e) { log.error("继续继续处理档口提现异常", e); fsNotice.sendMsg2DefaultChat("档口提现异常", "参数: " + JSON.toJSONString(prepareResult)); } } } log.info("-------------继续处理档口提现结束-------------"); } /** * 继续处理支付宝支付回调信息(异常中断补偿,非正常流程) */ public void continueProcessAliCallback() { log.info("-------------继续处理支付宝支付回调信息开始-------------"); Integer batchCount = 20; List callbacks = alipayCallbackService.listNeedContinueProcessCallback(batchCount); for (AlipayCallback callback : callbacks) { log.info("开始处理: {}", callback); try { alipayCallbackService.continueProcess(Collections.singletonList(callback)); } catch (Exception e) { log.error("继续处理支付宝支付回调异常", e); fsNotice.sendMsg2DefaultChat("支付回调处理异常", "回调信息: " + JSON.toJSONString(callback)); } } log.info("-------------继续处理支付宝支付回调信息结束-------------"); } /** * 商品当日浏览量、下载量、图搜次数统计 */ public void dailyProductStatistics() { log.info("-------------商品信息每日统计开始-------------"); Map iscMap = redisCache.getCacheMap(CacheConstants.PRODUCT_STATISTICS_IMG_SEARCH_COUNT); Map vcMap = redisCache.getCacheMap(CacheConstants.PRODUCT_STATISTICS_VIEW_COUNT); Map dcMap = redisCache.getCacheMap(CacheConstants.PRODUCT_STATISTICS_DOWNLOAD_COUNT); Set storeProdIds = new HashSet<>(); storeProdIds.addAll(MapUtil.emptyIfNull(iscMap).keySet()); storeProdIds.addAll(MapUtil.emptyIfNull(vcMap).keySet()); storeProdIds.addAll(MapUtil.emptyIfNull(dcMap).keySet()); Date now = new Date(); for (String storeProdId : storeProdIds) { try { // 保存到数据库 storeProductService.insertOrUpdateProductStatistics(Long.parseLong(storeProdId), vcMap.get(storeProdId), dcMap.get(storeProdId), iscMap.get(storeProdId), now); // 清除当日缓存 redisCache.deleteCacheMapValue(CacheConstants.PRODUCT_STATISTICS_IMG_SEARCH_COUNT, storeProdId); redisCache.deleteCacheMapValue(CacheConstants.PRODUCT_STATISTICS_VIEW_COUNT, storeProdId); redisCache.deleteCacheMapValue(CacheConstants.PRODUCT_STATISTICS_DOWNLOAD_COUNT, storeProdId); } catch (Exception e) { log.error("商品信息每日统计异常:" + storeProdId, e); } } // 清除当日缓存 // redisCache.deleteObject(CacheConstants.PRODUCT_STATISTICS_IMG_SEARCH_COUNT); // redisCache.deleteObject(CacheConstants.PRODUCT_STATISTICS_VIEW_COUNT); // redisCache.deleteObject(CacheConstants.PRODUCT_STATISTICS_DOWNLOAD_COUNT); log.info("-------------商品信息每日统计结束-------------"); } /** * 给商品打销量过千标签 * * @param yesterday 昨天 * @param oneMonthAgo 一月前 * @param tagList 标签列表 */ private void tagMonthSaleThousand(Date yesterday, Date oneMonthAgo, List tagList) { List prodSale1000List = this.dailySaleProdMapper.prodSale1000List(oneMonthAgo, yesterday); if (CollectionUtils.isEmpty(prodSale1000List)) { return; } tagList.addAll(prodSale1000List.stream().map(x -> DailyProdTag.builder().storeId(x.getStoreId()).storeProdId(x.getStoreProdId()) .tag(ProdTagType.MONTH_SALES_THOUSAND.getLabel()).type(ProdTagType.MONTH_SALES_THOUSAND.getValue()) .voucherDate(yesterday).build()).collect(Collectors.toList())); } /** * 给商品打销量榜标签 * * @param yesterday 昨天 * @param oneMonthAgo 一月前 * @param tagList 标签列表 */ private void tagProdSaleTop10(Date yesterday, Date oneMonthAgo, List tagList) { List prodSaleTop10List = this.dailySaleProdMapper.prodSaleTop10List(oneMonthAgo, yesterday); if (CollectionUtils.isEmpty(prodSaleTop10List)) { return; } int saleRankType = ProdTagType.SALES_RANK.getValue(); // 定义标签映射规则 Map rankTags = new HashMap<>(); rankTags.put(0, "销量第一"); rankTags.put(1, "销量第二"); rankTags.put(2, "销量前三"); rankTags.put(3, "销量前五"); rankTags.put(4, "销量前五"); // 遍历 top10List 并生成标签 确保不会超出 top10List 的大小 for (int i = 0; i < Math.min(prodSaleTop10List.size(), 10); i++) { DailyStoreTagDTO tagDTO = prodSaleTop10List.get(i); // 构建 DailyStoreTag 对象并添加到 tagList tagList.add(DailyProdTag.builder().storeId(tagDTO.getStoreId()).storeProdId(tagDTO.getStoreProdId()).type(saleRankType) .tag(rankTags.getOrDefault(i, "销量前十")).voucherDate(yesterday).build()); } } /** * 给商品打图搜标签 * * @param yesterday 昨天 * @param oneMonthAgo 一月前 * @param tagList 标签列表 */ private void tagImgSearchTop10(Date yesterday, Date oneMonthAgo, List tagList) { List imgSearchTop10List = this.storeProdStatMapper.searchTop10Prod(oneMonthAgo, yesterday); if (CollectionUtils.isEmpty(imgSearchTop10List)) { return; } int imgSearchType = ProdTagType.IMG_SEARCH_RANK.getValue(); // 定义标签映射规则 Map rankTags = new HashMap<>(); rankTags.put(0, "图搜第一"); rankTags.put(1, "图搜第二"); rankTags.put(2, "图搜前三"); rankTags.put(3, "图搜前五"); rankTags.put(4, "图搜前五"); // 遍历 imgSearchTop10List 并生成标签 确保不会超出 top10List 的大小 for (int i = 0; i < Math.min(imgSearchTop10List.size(), 10); i++) { DailyStoreTagDTO tagDTO = imgSearchTop10List.get(i); // 构建 DailyProdTag 对象并添加到 tagList tagList.add(DailyProdTag.builder().storeId(tagDTO.getStoreId()).storeProdId(tagDTO.getStoreProdId()).type(imgSearchType) .tag(rankTags.getOrDefault(i, "图搜前十")).voucherDate(yesterday).build()); } } /** * 给商品打收藏榜前10标签 * * @param yesterday 昨天 * @param oneMonthAgo 一月前 * @param tagList 标签列表 */ private void tagCollectionTop10(Date yesterday, Date oneMonthAgo, List tagList) { List collectTop10List = this.userFavMapper.searchTop10Prod(oneMonthAgo, yesterday); if (CollectionUtils.isEmpty(collectTop10List)) { return; } int imgSearchType = ProdTagType.COLLECTION_RANK.getValue(); // 定义标签映射规则 Map rankTags = new HashMap<>(); rankTags.put(0, "收藏第一"); rankTags.put(1, "收藏第二"); rankTags.put(2, "收藏前三"); rankTags.put(3, "收藏前五"); rankTags.put(4, "收藏前五"); // 遍历 collectTop10List 并生成标签 确保不会超出 top10List 的大小 for (int i = 0; i < Math.min(collectTop10List.size(), 10); i++) { DailyStoreTagDTO tagDTO = collectTop10List.get(i); // 构建 DailyProdTag 对象并添加到 tagList tagList.add(DailyProdTag.builder().storeId(tagDTO.getStoreId()).storeProdId(tagDTO.getStoreProdId()).type(imgSearchType) .tag(rankTags.getOrDefault(i, "收藏前十")).voucherDate(yesterday).build()); } } /** * 给商品打下载榜前10标签 * * @param yesterday 昨天 * @param oneMonthAgo 一月前 * @param tagList 标签列表 */ private void tagDownloadTop10(Date yesterday, Date oneMonthAgo, List tagList) { List downloadTop10List = this.storeProdStatMapper.downloadTop10Prod(oneMonthAgo, yesterday); if (CollectionUtils.isEmpty(downloadTop10List)) { return; } int downloadType = ProdTagType.DOWNLOAD_RANK.getValue(); // 定义标签映射规则 Map rankTags = new HashMap<>(); rankTags.put(0, "铺货第一"); rankTags.put(1, "铺货第二"); rankTags.put(2, "铺货前三"); rankTags.put(3, "铺货前五"); rankTags.put(4, "铺货前五"); // 遍历 downloadTop10List 并生成标签 确保不会超出 top10List 的大小 for (int i = 0; i < Math.min(downloadTop10List.size(), 10); i++) { DailyStoreTagDTO tagDTO = downloadTop10List.get(i); // 构建 DailyProdTag 对象并添加到 tagList tagList.add(DailyProdTag.builder().storeId(tagDTO.getStoreId()).storeProdId(tagDTO.getStoreProdId()).type(downloadType) .tag(rankTags.getOrDefault(i, "铺货前十")).voucherDate(yesterday).build()); } } /** * 给商品打风格标签 * * @param yesterday 昨天 * @param tagList 标签列表 */ private void tagStyle(Date yesterday, List tagList) { List cateAttrList = this.cateAttrMapper.selectList(new LambdaQueryWrapper() .eq(StoreProductCategoryAttribute::getDelFlag, Constants.UNDELETED)); if (CollectionUtils.isEmpty(cateAttrList)) { return; } tagList.addAll(cateAttrList.stream().filter(x -> StringUtils.isNotBlank(x.getStyle())) .map(x -> DailyProdTag.builder().storeId(x.getStoreId()).storeProdId(x.getStoreProdId()) .tag(x.getStyle()).type(ProdTagType.STYLE.getValue()).voucherDate(yesterday).build()) .collect(Collectors.toList())); } /** * 给商品打标 七日上新 * * @param yesterday 昨天 * @param fourDaysAgo 4天前 * @param oneWeekAgo 一周前 * @param tagList 标签列表 */ private void tagSevenDayNew(Date yesterday, Date fourDaysAgo, Date oneWeekAgo, List tagList) { List storeProdList = this.storeProdMapper.selectList(new LambdaQueryWrapper() .eq(StoreProduct::getPrivateItem, EProductItemType.NON_PRIVATE_ITEM.getValue()) .eq(StoreProduct::getDelFlag, Constants.UNDELETED).between(StoreProduct::getCreateTime, oneWeekAgo, fourDaysAgo)); if (CollectionUtils.isEmpty(storeProdList)) { return; } tagList.addAll(storeProdList.stream().map(x -> DailyProdTag.builder().storeId(x.getStoreId()).storeProdId(x.getId()) .type(ProdTagType.SEVEN_DAY_NEW.getValue()).tag(ProdTagType.SEVEN_DAY_NEW.getLabel()).voucherDate(yesterday).build()) .collect(Collectors.toList())); } /** * 三日上新 * * @param yesterday 昨天 * @param fourDaysAgo 3天前 * @param tagList 标签列表 */ private void tagThreeDayNew(Date yesterday, Date fourDaysAgo, List tagList) { List storeProdList = this.storeProdMapper.selectList(new LambdaQueryWrapper() .eq(StoreProduct::getPrivateItem, EProductItemType.NON_PRIVATE_ITEM.getValue()) .eq(StoreProduct::getDelFlag, Constants.UNDELETED).between(StoreProduct::getCreateTime, fourDaysAgo, yesterday)); if (CollectionUtils.isEmpty(storeProdList)) { return; } tagList.addAll(storeProdList.stream().map(x -> DailyProdTag.builder().storeId(x.getStoreId()).storeProdId(x.getId()) .type(ProdTagType.THREE_DAY_NEW.getValue()).tag(ProdTagType.THREE_DAY_NEW.getLabel()) .voucherDate(yesterday).build()).collect(Collectors.toList())); } /** * 筛选档口热卖商品 * * @param yesterday 昨天 * @param oneWeekAgo 一周前 * @param tagList 标签集合 */ private void tagStoreHotTop5(Date yesterday, Date oneWeekAgo, List tagList) { // 筛选近一周档口销量 List saleProdList = this.dailySaleProdMapper.selectList(new LambdaQueryWrapper() .eq(DailySaleProduct::getDelFlag, Constants.UNDELETED).between(DailySaleProduct::getVoucherDate, oneWeekAgo, yesterday)); if (CollectionUtils.isEmpty(saleProdList)) { return; } // 筛选每个档口,销量排名前5的商品 Map> storeHotSaleMap = saleProdList.stream().collect(Collectors .groupingBy(DailySaleProduct::getStoreId, Collectors.groupingBy(DailySaleProduct::getStoreProdId, Collectors .summingInt(DailySaleProduct::getSaleNum)))); storeHotSaleMap.forEach((storeId, prodSaleMap) -> { // 筛选prodSaleMap中销量前5的商品 List> top5ProdList = prodSaleMap.entrySet().stream().sorted(Map.Entry.comparingByValue().reversed()) .limit(5).collect(Collectors.toList()); top5ProdList.forEach(entry -> tagList.add(DailyProdTag.builder().storeId(storeId).storeProdId(entry.getKey()).type(ProdTagType.STORE_HOT.getValue()) .tag(ProdTagType.STORE_HOT.getLabel()).voucherDate(yesterday).build())); }); } /** * 给商品打标 本月爆款 * * @param yesterday 昨天 * @param oneMonthAgo 一月前 * @param tagList 标签列表 */ private void tagMonthHot(Date yesterday, Date oneMonthAgo, List tagList) { List top50List = this.dailySaleProdMapper.selectTop50ProdList(yesterday, oneMonthAgo); if (CollectionUtils.isEmpty(top50List)) { return; } tagList.addAll(top50List.stream().map(x -> DailyProdTag.builder().storeId(x.getStoreId()).storeProdId(x.getStoreProdId()) .tag(ProdTagType.MONTH_HOT.getLabel()).type(ProdTagType.MONTH_HOT.getValue()).voucherDate(yesterday).build()) .collect(Collectors.toList())); } /** * 档口基础标签 * * @param yesterday 昨日 * @param oneWeekAgo 一周前 * @param tagList 标签列表 */ private void tag7DaysNewTag(Date yesterday, Date oneWeekAgo, List tagList) { List newProdList = this.storeProdMapper.selectList(new LambdaQueryWrapper() .eq(StoreProduct::getDelFlag, Constants.UNDELETED).eq(StoreProduct::getPrivateItem, EProductItemType.NON_PRIVATE_ITEM.getValue()) .in(StoreProduct::getProdStatus, Collections.singletonList(EProductStatus.ON_SALE.getValue())) .between(StoreProduct::getCreateTime, oneWeekAgo, yesterday)); if (CollectionUtils.isEmpty(newProdList)) { return; } newProdList.stream().map(StoreProduct::getStoreId).distinct().forEach(x -> { tagList.add(DailyStoreTag.builder().storeId(x).type(StoreTagType.SEVEN_DAY_NEW_RANK.getValue()) .tag("七日上新").voucherDate(yesterday).build()); }); } /** * 给档口打库存标签 * * @param yesterday 今天 * @param yesterday 昨天 * @param oneMonthAgo 一月前 * @param tagList 标签列表 */ private void tagStockTag(Date yesterday, Date oneMonthAgo, List tagList) { List top10List = this.stockMapper.selectTop10List(yesterday, oneMonthAgo); if (CollectionUtils.isEmpty(top10List)) { return; } tagList.addAll(top10List.stream().map(x -> DailyStoreTag.builder().storeId(x.getStoreId()).type(StoreTagType.STOCK_RANK.getValue()) .tag("库存充足").voucherDate(yesterday).build()).collect(Collectors.toList())); } /** * 给档口打关注标签 * * @param yesterday 昨天 * @param tagList 档口标签列表 */ private void tagAttentionRank(Date yesterday, List tagList) { List top10List = this.userSubsMapper.selectTop10List(); if (CollectionUtils.isEmpty(top10List)) { return; } int attentionRankType = StoreTagType.ATTENTION_RANK.getValue(); // 定义标签映射规则 Map rankTags = new HashMap<>(); rankTags.put(0, "关注第一"); rankTags.put(1, "关注第二"); rankTags.put(2, "关注前三"); rankTags.put(3, "关注前五"); rankTags.put(4, "关注前五"); // 遍历 top10List 并生成标签 for (int i = 0; i < Math.min(top10List.size(), 10); i++) { // 确保不会超出 top10List 的大小 // 构建 DailyStoreTag 对象并添加到 tagList tagList.add(DailyStoreTag.builder().storeId(top10List.get(i).getStoreId()).type(attentionRankType) .tag(rankTags.getOrDefault(i, "关注前十")).voucherDate(yesterday).build()); } } /** * 给档口打销量过千标签 * * @param yesterday 昨天 * @param oneMonthAgo 一月前 * @param tagList 档口标签列表 */ private void tagSaleThousand(Date yesterday, Date oneMonthAgo, List tagList) { List thousandSaleList = this.dailySaleMapper.selectSaleThousand(yesterday, oneMonthAgo); if (CollectionUtils.isEmpty(thousandSaleList)) { return; } tagList.addAll(thousandSaleList.stream().map(x -> DailyStoreTag.builder().storeId(x.getStoreId()).voucherDate(yesterday) .type(StoreTagType.MONTH_SALES_THOUSAND.getValue()).tag(StoreTagType.MONTH_SALES_THOUSAND.getLabel()).build()) .collect(Collectors.toList())); } /** * 统计最近一月档口销售数据。 1. 销量榜 规则:销量排名第1,打标:销量第一 销量第2、第3,打标:销量前三 ; * 销量前5,打标:销量前五;销量第6-10,打标:销量前十 * * @param yesterday 昨天 * @param oneMonthAgo 一月前 * @param tagList 档口标签列表 */ private void tagStoreSaleRank(Date yesterday, Date oneMonthAgo, List tagList) { // 获取最近一月销量前10的档口 List saleTop10List = this.dailySaleMapper.selectTop10List(yesterday, oneMonthAgo); if (CollectionUtils.isEmpty(saleTop10List)) { return; } int salesRankType = StoreTagType.SALES_RANK.getValue(); // 定义标签映射规则 Map rankTags = new HashMap<>(); rankTags.put(0, "销量第一"); rankTags.put(1, "销量第二"); rankTags.put(2, "销量前三"); rankTags.put(3, "销量前五"); rankTags.put(4, "销量前五"); // 遍历 saleTop10List 并生成标签 for (int i = 0; i < Math.min(10, saleTop10List.size()); i++) { // 确保不会超出 saleTop10List 的大小 DailyStoreTagDTO storeTagDTO = saleTop10List.get(i); if (ObjectUtils.isNotEmpty(storeTagDTO)) { String tag = rankTags.getOrDefault(i, "销量前十"); tagList.add(DailyStoreTag.builder().storeId(storeTagDTO.getStoreId()).type(salesRankType) .tag(tag).voucherDate(yesterday).build()); } } } /** * 打销量爆款标签 * * @param yesterday 昨天 * @param oneMonthAgo 一月前 * @param tagList 档口标签列表 */ private void tagHotRank(Date yesterday, Date oneMonthAgo, List tagList) { List top50List = this.dailySaleProdMapper.selectTop50ProdList(yesterday, oneMonthAgo); if (CollectionUtils.isEmpty(top50List)) { return; } tagList.addAll(top50List.stream().map(DailyStoreTagDTO::getStoreId).distinct().map(storeId -> DailyStoreTag.builder() .storeId(storeId).type(StoreTagType.HOT_RANK.getValue()).tag(StoreTagType.HOT_RANK.getLabel()) .voucherDate(yesterday).build()) .collect(Collectors.toList())); } /** * 给档口打新品频出标签 * * @param yesterday 昨天 * @param oneWeekAgo 一周前 * @param tagList 标签列表 */ private void tagNewProd(Date yesterday, Date oneWeekAgo, List tagList) { List top20List = this.storeProdMapper.selectTop20List(yesterday, oneWeekAgo); if (CollectionUtils.isEmpty(top20List)) { return; } tagList.addAll(top20List.stream().map(DailyStoreTagDTO::getStoreId).distinct().map(storeId -> DailyStoreTag.builder() .storeId(storeId).type(StoreTagType.NEW_PRODUCT.getValue()).tag(StoreTagType.NEW_PRODUCT.getLabel()) .voucherDate(yesterday).build()) .collect(Collectors.toList())); } /** * 更新商品的标签到ES * * @param prodTagMap 标签map */ private void updateESProdTags(Map> prodTagMap) { // 构建一个批量数据集合 List list = new ArrayList<>(); prodTagMap.forEach((storeProdId, updateTags) -> { // 构建部分文档更新请求 list.add(new BulkOperation.Builder().update(u -> u .action(a -> a.doc(new HashMap() {{ put("tags", updateTags); }})) .id(String.valueOf(storeProdId)) .index(ES_INDEX_NAME)) .build()); }); try { // 调用bulk方法执行批量更新操作 BulkResponse bulkResponse = esClientWrapper.getEsClient().bulk(e -> e.index(ES_INDEX_NAME).operations(list)); log.info("批量更新商品标签到 ES 成功的 id列表: {}", bulkResponse.items().stream().map(BulkResponseItem::id).collect(Collectors.toList())); // 有哪些没执行成功的,需要发飞书通知 List successIdList = bulkResponse.items().stream().map(BulkResponseItem::id).collect(Collectors.toList()); List unExeIdList = prodTagMap.keySet().stream().map(String::valueOf).filter(x -> !successIdList.contains(x)).collect(Collectors.toList()); if (CollectionUtils.isNotEmpty(unExeIdList)) { fsNotice.sendMsg2DefaultChat("定时任务批量更新商品标签到 ES 失败", "以下storeProdId未执行成功: " + unExeIdList); } } catch (Exception e) { log.error("定时任务批量更新标签到 ES 失败", e); fsNotice.sendMsg2DefaultChat("全部失败,定时任务批量更新商品标签到 ES 失败", prodTagMap.keySet().toString()); } } /** * 根据支付渠道匹配支付类 * * @param payChannel * @return */ private PaymentManager getPaymentManager(EPayChannel payChannel) { Assert.notNull(payChannel); for (PaymentManager paymentManager : paymentManagers) { if (paymentManager.channel() == payChannel) { return paymentManager; } } throw new ServiceException("未知支付渠道"); } /** * 搜图服务同步商品 * * @param storeProductId * @param picKeys */ private void sync2ImgSearchServer(Long storeProductId, List picKeys) { ThreadUtil.execAsync(() -> { ProductPicSyncResultDTO r = pictureService.sync2ImgSearchServer(new ProductPicSyncDTO(storeProductId, picKeys)); log.info("商品图片同步至搜图服务器: id: {}, result: {}", storeProductId, JSONUtil.toJsonStr(r)); } ); } /** * 新增商品时,新增系统通知 * * @param storeProd 档口商品 * @param storeName 档口名称 */ private void createNotice(StoreProduct storeProd, String storeName) { // 新增档口 商品动态 通知公告 Long userId = SecurityUtils.getUserId(); // 新增一条档口消息通知 Notice notice = new Notice().setNoticeTitle(storeName + "商品上新啦!").setNoticeType(NoticeType.NOTICE.getValue()) .setNoticeContent(storeName + "上新了货号为: " + storeProd.getProdArtNum() + " 的商品!请及时关注!") .setOwnerType(NoticeOwnerType.SYSTEM.getValue()).setStoreId(storeProd.getStoreId()) .setUserId(userId).setPerpetuity(NoticePerpetuityType.PERMANENT.getValue()); this.noticeMapper.insert(notice); final Date voucherDate = java.sql.Date.valueOf(LocalDate.now()); // 新增消息通知列表 List userNoticeList = new ArrayList<>(); // 新增档口商品动态 userNoticeList.add(new UserNotice().setNoticeId(notice.getId()) .setUserId(userId).setReadStatus(NoticeReadType.UN_READ.getValue()).setVoucherDate(voucherDate) .setTargetNoticeType(UserNoticeType.PRODUCT_DYNAMIC.getValue())); List userSubList = this.userSubMapper.selectList(new LambdaQueryWrapper() .eq(UserSubscriptions::getStoreId, storeProd.getStoreId())); if (CollectionUtils.isNotEmpty(userSubList)) { userSubList.forEach(x -> userNoticeList.add(new UserNotice().setNoticeId(notice.getId()) .setUserId(x.getUserId()).setReadStatus(NoticeReadType.UN_READ.getValue()).setVoucherDate(voucherDate) .setTargetNoticeType(UserNoticeType.FOCUS_STORE.getValue()))); } if (CollectionUtils.isEmpty(userNoticeList)) { return; } this.userNoticeMapper.insert(userNoticeList); } /** * 从中通同步行政区划 */ public void syncRegionFromZto() { log.info("-------------同步行政区划开始-------------"); try { Map ztoRegionMap = ztoExpressManager.getAllRegion() .stream() .filter(o -> Integer.valueOf(1).equals(o.getEnabled())) .collect(Collectors.toMap(ZtoRegion::getId, o -> o, (o, n) -> n)); List expressRegions = new ArrayList<>(ztoRegionMap.size()); for (ZtoRegion ztoRegion : ztoRegionMap.values()) { if (ztoRegion.getLayer() != null && ztoRegion.getLayer() > 4) { continue; } ExpressRegion expressRegion = new ExpressRegion(); expressRegion.setRegionCode(ztoRegion.getCode()); expressRegion.setRegionName(ztoRegion.getFullName()); if (ztoRegion.getParentId() != null) { ZtoRegion ztoParentRegion = ztoRegionMap.get(ztoRegion.getParentId()); if (ztoParentRegion != null) { expressRegion.setParentRegionCode(ztoParentRegion.getCode()); expressRegion.setParentRegionName(ztoParentRegion.getFullName()); } } expressRegion.setRegionLevel(Optional.ofNullable(ztoRegion.getLayer()).map(n -> n - 1).orElse(0)); expressRegion.setVersion(0L); expressRegion.setDelFlag(Constants.UNDELETED); expressRegions.add(expressRegion); } expressService.clearAndInsertAllRegion(expressRegions); } catch (Exception e) { log.error("同步行政区划异常", e); fsNotice.sendMsg2DefaultChat("同步行政区划异常", StrUtil.emptyIfNull(e.getMessage())); } log.info("-------------同步行政区划结束-------------"); } /** * 创建推广轮次 * * @param roundList 当前所有的推广轮次 * @param diffRound 需要创建多少轮 * @param advert 当前推广数据 * @param updateList 待更新列表 * @return */ private void createAdvertRound(List roundList, int diffRound, Advert advert, List updateList) { // 当前最大轮次 int maxRoundId = roundList.stream().mapToInt(AdvertRound::getRoundId).max().getAsInt(); // 最大轮次的结束时间 LocalDate maxEndTime = roundList.stream().max(Comparator.comparing(AdvertRound::getEndTime)) .map(round -> round.getEndTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDate()) .orElseThrow(() -> new ServiceException("获取推广轮次最大结束时间失败", HttpStatus.ERROR)); LocalDate maxEndTimeNextDay = maxEndTime.plusDays(1); // 根据轮次差来判断当前需要补多少播放轮 for (int diff = 0; diff < diffRound; diff++) { // 推广轮次 + 1 maxRoundId += 1; final LocalDate startDate = diff == 0 ? maxEndTimeNextDay : maxEndTimeNextDay.plusDays((long) advert.getPlayInterval() * diff); // 间隔时间 final LocalDate endDate = startDate.plusDays(advert.getPlayInterval() - 1); // 每一轮的播放数量 for (int playNum = 0; playNum < advert.getPlayNum(); playNum++) { // 依次按照26个字母顺序 如果i == 0 则A i == 1 则B i==2则C final String position = String.valueOf((char) ('A' + playNum)); AdvertRound advertRound = new AdvertRound().setAdvertId(advert.getId()).setTypeId(advert.getTypeId()).setRoundId(maxRoundId) .setLaunchStatus(AdLaunchStatus.UN_LAUNCH.getValue()).setPosition(position).setStartPrice(advert.getStartPrice()) .setSysIntercept(AdSysInterceptType.UN_INTERCEPT.getValue()).setShowType(advert.getShowType()).setDisplayType(advert.getDisplayType()) .setStartTime(Date.from(startDate.atStartOfDay(ZoneId.systemDefault()).toInstant())) .setEndTime(Date.from(endDate.atStartOfDay(ZoneId.systemDefault()).toInstant())).setDeadline(advert.getDeadline()) .setBiddingStatus(AdBiddingStatus.UN_BIDDING.getValue()).setBiddingTempStatus(AdBiddingStatus.UN_BIDDING.getValue()) .setSymbol(Objects.equals(advert.getShowType(), AdShowType.POSITION_ENUM.getValue()) // 如果是位置枚举的推广位,则需要精确到某一个position的推广位,反之,若是时间范围,则直接精确到播放轮次即可 ? advert.getBasicSymbol() + maxRoundId + position : advert.getBasicSymbol() + maxRoundId); // 添加到推广轮次列表,如果playNum有调整,后续会用到roundList roundList.add(advertRound); // 生成最新的下一轮推广位 updateList.add(advertRound); } } } }