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 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.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.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 StoreProductColorPriceMapper prodColorPriceMapper; final IPictureService pictureService; final NoticeMapper noticeMapper; final UserNoticeMapper userNoticeMapper; final UserSubscriptionsMapper userSubMapper; final StoreMemberMapper storeMemberMapper; final IExpressService expressService; final ZtoExpressManagerImpl ztoExpressManager; final SysDictDataMapper dictDataMapper; public void test() throws IOException { System.err.println("aaa"); } /** * 每天执行定时任务 * 每年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() + "年冬季"; } if (StringUtils.isEmpty(seasonLabel)) { return; } log.info("生成季节标签:{}", seasonLabel); List dictDataList = this.dictDataMapper.selectList(new LambdaQueryWrapper() .eq(SysDictData::getDictType, Constants.RELEASE_YEAR_SEASON).eq(SysDictData::getDelFlag, Constants.UNDELETED) .eq(SysDictData::getStatus, "0")); // 当前最大排序 final Long maxSort = dictDataList.stream().max(Comparator.comparingLong(SysDictData::getDictSort)) .map(x -> x.getDictSort()).orElse(100L); // 往sys_dict_data表插入一条数据 SysDictData dictData = new SysDictData(); dictData.setDictLabel(seasonLabel); dictData.setDictValue(seasonLabel); dictData.setDictType(Constants.RELEASE_YEAR_SEASON); dictData.setDictSort(maxSort + 1); dictData.setStatus("0"); dictData.setCreateBy("admin"); this.dictDataMapper.insert(dictData); } /** * 每天凌晨12: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); }); } /** * 凌晨12: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 i = 0; i < advert.getPlayRound(); i++) { // 如果i = 0 则表明从未创建过推广位,直接新建所有 final Integer launchStatus = i == 0 ? AdLaunchStatus.LAUNCHING.getValue() : AdLaunchStatus.UN_LAUNCH.getValue(); final LocalDate now = i == 0 ? LocalDate.now() : LocalDate.now().plusDays((long) advert.getPlayInterval() * i); // 间隔时间 final LocalDate endDate = now.plusDays(advert.getPlayInterval() - 1); // 按照播放数量依次生成下一轮播放的推广位 for (int j = 0; j < advert.getPlayNum(); j++) { // 依次按照26个字母顺序 如果i == 0 则A i == 1 则B i==2则C final String position = String.valueOf((char) ('A' + j)); // 当前播放轮次id final int roundId = i + 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()) .setSymbol(Objects.equals(advert.getShowType(), AdShowType.POSITION_ENUM.getValue()) // 如果是位置枚举的推广位,则需要精确到某一个position的推广位,反之,若是时间范围,则直接精确到播放轮次即可 ? advert.getBasicSymbol() + roundId + position : advert.getBasicSymbol() + roundId)); } } } else { // 判断当天是否为播放轮次最小结束时间的下一天 最小结束时间为:yyyy-MM-dd格式 final Date compareDate = java.sql.Date.valueOf(LocalDate.now().minusDays(1)); final Date minEndTime = roundList.stream().min(Comparator.comparing(AdvertRound::getEndTime)).get().getEndTime(); if (Objects.equals(minEndTime, compareDate)) { // 将播放轮次为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.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); // 如果播放轮次有更新,则需重新判断 int diff = advert.getPlayRound() - roundList.stream().mapToInt(AdvertRound::getRoundId).max().getAsInt(); // 当前最大轮次 int maxRoundId = roundList.stream().mapToInt(AdvertRound::getRoundId).max().getAsInt(); // diff < 0 代表轮次有减少,则不新增播放轮, diff == 0 则代表播放轮次不增不减,不做调整 if (diff > 0) { // 最大轮次的结束时间 final 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 j = 0; j < diff; j++) { // 推广轮次 + 1 maxRoundId += 1; final LocalDate startDate = j == 0 ? maxEndTimeNextDay : maxEndTimeNextDay.plusDays((long) advert.getPlayInterval() * j); // 间隔时间 final LocalDate endDate = startDate.plusDays(advert.getPlayInterval() - 1); // 每一轮的播放数量 for (int i = 0; i < advert.getPlayNum(); i++) { // 依次按照26个字母顺序 如果i == 0 则A i == 1 则B i==2则C final String position = String.valueOf((char) ('A' + j)); // 生成最新的下一轮推广位 updateList.add(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()) // java.sql.Date 直接转化成yyyy-MM-dd格式 .setStartTime(java.sql.Date.valueOf(startDate)).setEndTime(java.sql.Date.valueOf(endDate)).setDeadline(advert.getDeadline()) .setSymbol(Objects.equals(advert.getShowType(), AdShowType.POSITION_ENUM.getValue()) // 如果是位置枚举的推广位,则需要精确到某一个position的推广位,反之,若是时间范围,则直接精确到播放轮次即可 ? advert.getBasicSymbol() + maxRoundId + position : advert.getBasicSymbol() + maxRoundId)); } } } } } }); if (CollectionUtils.isNotEmpty(updateList)) { this.advertRoundMapper.insertOrUpdate(updateList); } } /** * 凌晨00:04更新symbol对应的锁资源到redis中 */ public void saveSymbolToRedis() { advertRoundService.initAdvertLockMap(); } /** * 凌晨00:08更新各推广轮次结束时间 */ public void saveAdvertDeadlineToRedis() { // 直接调service方法,若当时redis出了问题,也方便第一时间 通过业务流程弥补 两边都有一个补偿机制 this.advertRoundService.saveAdvertDeadlineToRedis(); } /** * 凌晨00: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())); } /** * 凌晨00: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())); } /** * 凌晨00: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())); } /** * 凌晨00: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().minusDays(1)); // 及当前日期前一天的前一周,并转为 Date 格式 final Date pastDate = java.sql.Date.valueOf(LocalDate.now().minusDays(8)); // 获取各项子分类最近一周的销售数量 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); } /** * 凌晨00: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<>(); // 单据日期为当然 final Date now = java.sql.Date.valueOf(LocalDate.now()); // 根据LocalDate 获取当前日期前一天 final Date yesterday = java.sql.Date.valueOf(LocalDate.now().minusDays(1)); // 使用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(now, yesterday, oneMonthAgo, tagList); // 2. 打 销量榜 标签 this.tagStoreSaleRank(now, yesterday, oneMonthAgo, tagList); // 3. 打 爆款频出 标签,根据销量前50的商品中 档口 先后顺序排列 this.tagHotRank(now, yesterday, oneMonthAgo, tagList); // 4. 打 新款频出 标签,根据最近一周档口商品上新速度,先后排序 this.tagNewProd(now, yesterday, oneWeekAgo, tagList); // 5. 打 关注榜 标签,根据关注量,进行排序 this.tagAttentionRank(now, yesterday, tagList); // 6. 打 库存榜 标签,根据库存量,进行排序 this.tagStockTag(yesterday, oneMonthAgo, tagList); // 7. 打 七日上新 标签 this.tag7DaysNewTag(now, yesterday, oneWeekAgo, tagList); if (CollectionUtils.isEmpty(tagList)) { return; } this.dailyStoreTagMapper.insert(tagList); } /** * 凌晨00:40 更新商品标签 */ @Transactional public void dailyProdTag() throws IOException { // 先删除所有的商品标签,保证数据唯一性 List existList = this.dailyProdTagMapper.selectList(new LambdaQueryWrapper() .eq(DailyProdTag::getDelFlag, Constants.UNDELETED)); if (CollectionUtils.isNotEmpty(existList)) { this.dailyProdTagMapper.deleteByIds(existList.stream().map(DailyProdTag::getId).collect(Collectors.toList())); } final Date now = java.sql.Date.valueOf(LocalDate.now()); // 根据LocalDate 获取当前日期前一天 final Date yesterday = java.sql.Date.valueOf(LocalDate.now().minusDays(1)); // 使用LocalDate 获取当前日期4天前,并转为 Date 格式 final Date fourDaysAgo = java.sql.Date.valueOf(LocalDate.now().minusDays(4)); // 使用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(now, yesterday, oneMonthAgo, tagList); // 2. 打 销量榜 标签 this.tagProdSaleTop10(now, yesterday, oneMonthAgo, tagList); // 3. 当月(近一月)爆款 this.tagMonthHot(now, yesterday, oneMonthAgo, tagList); // 4. 档口热卖 this.tagStoreHotTop20(now, yesterday, oneMonthAgo, tagList); // 5. 图搜榜 this.tagImgSearchTop10(now, yesterday, oneMonthAgo, tagList); // 6. 收藏榜 this.tagCollectionTop10(now, yesterday, oneMonthAgo, tagList); // 7. 下载榜 铺货榜 this.tagDownloadTop10(now, yesterday, oneMonthAgo, tagList); // 8. 三日上新 this.tagThreeDayNew(now, yesterday, fourDaysAgo, tagList); // 9. 七日上新 this.tagSevenDayNew(now, yesterday, fourDaysAgo, oneWeekAgo, tagList); // 10. 风格 this.tagStyle(now, yesterday, tagList); if (CollectionUtils.isEmpty(tagList)) { return; } this.dailyProdTagMapper.insert(tagList); // 更新商品的标签到ES this.updateESProdTags(tagList); } /** * 凌晨12: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)); } /** * 凌晨1:00 更新档口商品的各项权重数据 */ @Transactional public void dailyProdWeight() { List storeProdList = this.storeProdMapper.selectList(new LambdaQueryWrapper() .eq(StoreProduct::getDelFlag, Constants.UNDELETED)); if (CollectionUtils.isEmpty(storeProdList)) { return; } // 获取 商品销售、商品浏览量、商品收藏量、商品下载量 List statisticsList = this.storeProdStatMapper.selectList(new LambdaQueryWrapper() .eq(StoreProductStatistics::getDelFlag, Constants.UNDELETED)); // 商品浏览量、下载量 Map prodStatMap = statisticsList.stream().collect(Collectors.toMap(StoreProductStatistics::getStoreProdId, Function.identity())); // 商品收藏量 List userFavList = this.userFavMapper.selectList(new LambdaQueryWrapper() .eq(UserFavorites::getDelFlag, Constants.UNDELETED)); Map userFavMap = userFavList.stream().collect(Collectors.groupingBy(UserFavorites::getStoreProdId, Collectors.summingLong(UserFavorites::getId))); // 商品销售量 List saleDetailList = this.saleDetailMapper.selectList(new LambdaQueryWrapper() .eq(StoreSaleDetail::getDelFlag, Constants.UNDELETED).eq(StoreSaleDetail::getSaleType, SaleType.GENERAL_SALE.getValue())); Map saleMap = saleDetailList.stream().collect(Collectors.groupingBy(StoreSaleDetail::getStoreProdId, Collectors.summingLong(StoreSaleDetail::getQuantity))); storeProdList.forEach(x -> { final long viewCount = ObjectUtils.isEmpty(prodStatMap.get(x.getId())) ? 0L : prodStatMap.get(x.getId()).getViewCount(); final long downloadCount = ObjectUtils.isEmpty(prodStatMap.get(x.getId())) ? 0L : prodStatMap.get(x.getId()).getDownloadCount(); final long favCount = ObjectUtils.isEmpty(userFavMap.get(x.getId())) ? 0L : userFavMap.get(x.getId()); final long saleCount = ObjectUtils.isEmpty(saleMap.get(x.getId())) ? 0L : saleMap.get(x.getId()); // 计算推荐权重,权重计算公式为:(浏览次数 * 0.3 + 下载次数 + 收藏次数 + 销售量 * 0.3) final long recommendWeight = (long) (viewCount * 0.3 + downloadCount + favCount + saleCount * 0.3); // 根据销售数量计算销售权重,权重占销售数量的30% final long saleWeight = (long) (saleCount * 0.3); // 计算人气权重,权重计算公式为:(浏览次数 * 0.1 和 100 取最小值) + 下载次数 + 收藏次数 final long popularityWeight = (long) (Math.min(viewCount * 0.1, 100) + downloadCount + favCount); x.setRecommendWeight(recommendWeight).setSaleWeight(saleWeight).setPopularityWeight(popularityWeight); }); this.storeProdMapper.updateById(storeProdList); } /** * 凌晨1:10 更新档口权重数据 */ @Transactional public void dailyStoreWeight() { List storeProdList = this.storeProdMapper.selectList(new LambdaQueryWrapper().eq(StoreProduct::getDelFlag, Constants.UNDELETED)); 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(); 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 和 storeRecommnedWeight List recommendList = BeanUtil.copyToList(storeRecommendList, PCStoreRecommendDTO.class); // 放到redis中 redisCache.setCacheObject(CacheConstants.PC_STORE_RECOMMEND_LIST, recommendList); } /** * 凌晨02:00 更新用户搜索历史入库 */ @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:05 更新用户浏览记录入库 */ @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(x -> x.getUserId())) .forEach((userId, list) -> { // 按照浏览时间升序排 list.sort(Comparator.comparing(UserBrowsingHistory::getBrowsingTime)); redisCache.setCacheObject(CacheConstants.USER_BROWSING_HISTORY + userId, BeanUtil.copyToList(list, UserBrowsingHisDTO.class)); }); } /** * 凌晨02:10 更新系统热搜到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("-------------统计图搜热款结束-------------"); } /** * 凌晨2:35 更新试用期过期档口 */ public void autoCloseTrialStore() { } /** * 凌晨2:40 更新年费过期档口 */ public void autoCloseTimeoutStore() { // TODO 更新未交年费档口 // TODO 更新未交年费档口 } /** * 凌晨2: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::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())); } /** * 将档口会员存到redis中 */ public void saveStoreMemberToRedis() { List memberList = this.storeMemberMapper.selectList(new LambdaQueryWrapper() .eq(StoreMember::getDelFlag, Constants.UNDELETED)); if (CollectionUtils.isEmpty(memberList)) { return; } memberList.forEach(x -> redisCache.setCacheObject(CacheConstants.STORE_MEMBER + x.getStoreId(), x)); } /** * 凌晨2:50 更新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); } /** * 凌晨3:00更新档口权重到redis */ public void updateStoreWeightToES() throws IOException { // 找到昨天开通会员的所有档口 List memberList = this.storeMemberMapper.selectList(new LambdaQueryWrapper() .eq(StoreMember::getDelFlag, Constants.UNDELETED) .eq(StoreMember::getLevel, StoreMemberLevel.STRENGTH_CONSTRUCT.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).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.STORE_WEIGHT_DEFAULT_ZERO)); }})) .id(String.valueOf(storeProd.getId())) .index(Constants.ES_IDX_PRODUCT_INFO)) .build()); }); try { // 调用bulk方法执行批量更新操作 BulkResponse bulkResponse = esClientWrapper.getEsClient().bulk(e -> e.index(Constants.ES_IDX_PRODUCT_INFO).operations(list)); log.info("bulkResponse.result() = {}", bulkResponse.items()); } catch (IOException | RuntimeException e) { // 记录日志并抛出或处理异常 log.error("向ES更新档口权重失败,商品ID: {}, 错误信息: {}", storeProdList.stream().map(StoreProduct::getId).collect(Collectors.toList()), e.getMessage()); throw e; // 或者做其他补偿处理,比如异步重试 } } /** * 每晚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::getProdStatus, EProductStatus.UN_PUBLISHED.getValue())); if (CollectionUtils.isEmpty(unpublicList)) { return; } System.err.println(unpublicList); final List storeProdIdList = unpublicList.stream().map(x -> x.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(x -> x.getStoreProdId(), Collectors.mapping(x -> x.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(x -> x.getStoreProdId(), x -> x)); // 主图视频map Map mainVideoMap = mainPicList.stream().filter(x -> Objects.equals(x.getFileType(), FileType.MAIN_PIC_VIDEO.getValue())) .collect(Collectors.toMap(x -> x.getStoreProdId(), x -> true)); // 所有的分类 List prodCateList = this.prodCateMapper.selectList(new LambdaQueryWrapper() .eq(SysProductCategory::getDelFlag, Constants.UNDELETED)); Map prodCateMap = prodCateList.stream().collect(Collectors.toMap(x -> x.getId(), x -> x)); // 父级分类 Map parProdCateMap = prodCateList.stream().collect(Collectors.toMap(x -> x.getParentId(), x -> x.getParentId(), (s1, s2) -> s2)); // 子分类 Map childProdCateMap = prodCateList.stream().collect(Collectors.toMap(x -> x.getId(), x -> x.getId())); // 获取当前商品最低价格 Map prodMinPriceMap = this.prodColorPriceMapper.selectStoreProdMinPriceList(storeProdIdList).stream().collect(Collectors .toMap(x -> x.getStoreProdId(), x -> x.getPrice())); // 档口商品的属性map Map cateAttrMap = this.cateAttrMapper.selectList(new LambdaQueryWrapper() .eq(StoreProductCategoryAttribute::getDelFlag, Constants.UNDELETED).in(StoreProductCategoryAttribute::getStoreProdId, storeProdIdList)) .stream().collect(Collectors.toMap(x -> x.getStoreProdId(), x -> x)); // 档口商品对应的档口 Map storeMap = this.storeMapper.selectList(new LambdaQueryWrapper().eq(Store::getDelFlag, Constants.UNDELETED) .in(Store::getId, unpublicList.stream().map(x -> x.getStoreId()).collect(Collectors.toList()))) .stream().collect(Collectors.toMap(x -> x.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(Constants.ES_IDX_PRODUCT_INFO) // 插入的数据 .document(esProductDTO)) .build(); bulkOperations.add(bulkOperation); this.createNotice(product, store.getStoreName()); } // 执行批量插入 try { BulkResponse response = esClientWrapper.getEsClient().bulk(b -> b.index(Constants.ES_IDX_PRODUCT_INFO).operations(bulkOperations)); log.info("批量插入到 ES 成功,共处理 {} 条记录", response.items().size()); } catch (Exception e) { log.error("批量插入到 ES 失败", e); } for (StoreProduct product : unpublicList) { List mainPicUrlList = mainPicMap.get(product.getId()); if (CollUtil.isEmpty(mainPicUrlList)) { return; } this.sync2ImgSearchServer(product.getId(), mainPicUrlList, true); } } /** * 自动关闭超时订单 */ 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, 14); 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 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 = 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 now 今天 * @param yesterday 昨天 * @param oneMonthAgo 一月前 * @param tagList 标签列表 */ private void tagMonthSaleThousand(Date now, 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(now).build()).collect(Collectors.toList())); } /** * 给商品打销量榜标签 * * @param now 今天 * @param yesterday 昨天 * @param oneMonthAgo 一月前 * @param tagList 标签列表 */ private void tagProdSaleTop10(Date now, 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(now).build()); } } /** * 给商品打图搜标签 * * @param now 现在 * @param yesterday 昨天 * @param oneMonthAgo 一月前 * @param tagList 标签列表 */ private void tagImgSearchTop10(Date now, 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(now).build()); } } /** * 给商品打收藏榜前10标签 * * @param now 现在 * @param yesterday 昨天 * @param oneMonthAgo 一月前 * @param tagList 标签列表 */ private void tagCollectionTop10(Date now, 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(now).build()); } } /** * 给商品打下载榜前10标签 * * @param now 现在 * @param yesterday 昨天 * @param oneMonthAgo 一月前 * @param tagList 标签列表 */ private void tagDownloadTop10(Date now, 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(now).build()); } } /** * 给商品打风格标签 * * @param now 今天 * @param yesterday 昨天 * @param tagList 标签列表 */ private void tagStyle(Date now, 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(now).build()) .collect(Collectors.toList())); } /** * 给商品打标 七日上新 * * @param now 现在 * @param yesterday 昨天 * @param fourDaysAgo 4天前 * @param oneWeekAgo 一周前 * @param tagList 标签列表 */ private void tagSevenDayNew(Date now, Date yesterday, Date fourDaysAgo, Date oneWeekAgo, List tagList) { List storeProdList = this.storeProdMapper.selectList(new LambdaQueryWrapper() .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(now).build()) .collect(Collectors.toList())); } /** * 三日上新 * * @param now 现在 * @param yesterday 昨天 * @param fourDaysAgo 3天前 * @param tagList 标签列表 */ private void tagThreeDayNew(Date now, Date yesterday, Date fourDaysAgo, List tagList) { List storeProdList = this.storeProdMapper.selectList(new LambdaQueryWrapper() .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(now).build()).collect(Collectors.toList())); } /** * 筛选档口热卖商品 * * @param now * @param yesterday 昨天 * @param oneMonthAgo 一月前 * @param tagList 标签集合 */ private void tagStoreHotTop20(Date now, Date yesterday, Date oneMonthAgo, List tagList) { List saleProdList = this.dailySaleProdMapper.selectList(new LambdaQueryWrapper() .eq(DailySaleProduct::getDelFlag, Constants.UNDELETED).eq(DailySaleProduct::getVoucherDate, now)); if (CollectionUtils.isEmpty(saleProdList)) { return; } // 筛选每个档口,销量排名前20的商品 Map> storeHotSaleMap = saleProdList.stream().collect(Collectors .groupingBy(DailySaleProduct::getStoreId, Collectors .collectingAndThen(Collectors.toList(), list -> list.stream().limit(20).collect(Collectors.toList())))); storeHotSaleMap.forEach((storeId, saleList) -> { tagList.addAll(saleList.stream().map(x -> DailyProdTag.builder().storeId(x.getStoreId()).storeProdId(x.getStoreProdId()) .type(ProdTagType.STORE_HOT.getValue()).tag(ProdTagType.STORE_HOT.getLabel()).voucherDate(now).build()) .collect(Collectors.toList())); }); } /** * 给商品打标 本月爆款 * * @param now 今天 * @param yesterday 昨天 * @param oneMonthAgo 一月前 * @param tagList 标签列表 */ private void tagMonthHot(Date now, 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(now).build()) .collect(Collectors.toList())); } /** * 档口基础标签 * * @param now 今天 * @param yesterday 昨日 * @param oneWeekAgo 一周前 * @param tagList 标签列表 */ private void tag7DaysNewTag(Date now, Date yesterday, Date oneWeekAgo, List tagList) { List newProdList = this.storeProdMapper.selectList(new LambdaQueryWrapper() .eq(StoreProduct::getDelFlag, Constants.UNDELETED) .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 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 now 今天 * @param yesterday 昨天 * @param tagList 档口标签列表 */ private void tagAttentionRank(Date now, 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(now).build()); } } /** * 给档口打销量过千标签 * * @param now 今天 * @param yesterday 昨天 * @param oneMonthAgo 一月前 * @param tagList 档口标签列表 */ private void tagSaleThousand(Date now, 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(now) .type(StoreTagType.MONTH_SALES_THOUSAND.getValue()).tag(StoreTagType.MONTH_SALES_THOUSAND.getLabel()).build()) .collect(Collectors.toList())); } /** * 统计最近一月档口销售数据。 1. 销量榜 规则:销量排名第1,打标:销量第一 销量第2、第3,打标:销量前三 ; * 销量前5,打标:销量前五;销量第6-10,打标:销量前十 * * @param now 当天 * @param yesterday 昨天 * @param oneMonthAgo 一月前 * @param tagList 档口标签列表 */ private void tagStoreSaleRank(Date now, 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(now).build()); } } } /** * 打销量爆款标签 * * @param now 当天 * @param yesterday 昨天 * @param oneMonthAgo 一月前 * @param tagList 档口标签列表 */ private void tagHotRank(Date now, 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(now).build()) .collect(Collectors.toList())); } /** * 给档口打新品频出标签 * * @param now 今天 * @param yesterday 昨天 * @param oneWeekAgo 一周前 * @param tagList 标签列表 */ private void tagNewProd(Date now, 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(now).build()) .collect(Collectors.toList())); } /** * 更新商品的标签到ES * * @param tagList 标签集合 * @throws IOException */ private void updateESProdTags(List tagList) throws IOException { // 构建一个批量数据集合 List list = new ArrayList<>(); tagList.stream().collect(Collectors.groupingBy(DailyProdTag::getStoreProdId)) .forEach((storeProdId, tags) -> { // 构建部分文档更新请求 list.add(new BulkOperation.Builder().update(u -> u .action(a -> a.doc(new HashMap() {{ put("tags", tags.stream().sorted(Comparator.comparing(x -> x.getType())).map(DailyProdTag::getTag).collect(Collectors.toList())); }})) .id(String.valueOf(storeProdId)) .index(Constants.ES_IDX_PRODUCT_INFO)) .build()); }); // 调用bulk方法执行批量更新操作 BulkResponse bulkResponse = esClientWrapper.getEsClient().bulk(e -> e.index(Constants.ES_IDX_PRODUCT_INFO).operations(list)); System.out.println("bulkResponse.items() = " + bulkResponse.items()); } /** * 根据支付渠道匹配支付类 * * @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 storeProdFiles */ private void sync2ImgSearchServer(Long storeProductId, List picKeys, boolean async) { if (async) { ThreadUtil.execAsync(() -> { ProductPicSyncResultDTO r = pictureService.sync2ImgSearchServer(new ProductPicSyncDTO(storeProductId, picKeys)); log.info("商品图片同步至搜图服务器: id: {}, result: {}", storeProductId, JSONUtil.toJsonStr(r)); } ); } else { 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("-------------同步行政区划结束-------------"); } }