# IwcServiceImpl.java

    package cloud.wconnect.rateshop.service.rateshop.impl;

    import cloud.wconnect.commonlib.component.BeanMapper;
    import cloud.wconnect.commonlib.cons.BaseConstant;
    import cloud.wconnect.commonlib.pojo.dto.PropertyDTO;
    import cloud.wconnect.commonlib.pojo.dto.internationalization.InternationalizationNumberDTO;
    import cloud.wconnect.commonlib.redis.PropertyI18nRedisService;
    import cloud.wconnect.commonlib.service.CommonService;
    import cloud.wconnect.commonlib.util.DateTimeUtils;
    import cloud.wconnect.commonlib.util.InternationalizationUtils;
    import cloud.wconnect.commonlib.util.JsonUtils;
    import cloud.wconnect.commonlib.util.TransactionUtils;
    import cloud.wconnect.rateshop.cons.PrecisionCons;
    import cloud.wconnect.rateshop.cons.rateshop.MarketDemandTrendEnum;
    import cloud.wconnect.rateshop.entity.rateshop.Iwc;
    import cloud.wconnect.rateshop.entity.rateshop.IwcWeightConfig;
    import cloud.wconnect.rateshop.entity.rateshop.Setting;
    import cloud.wconnect.rateshop.entity.rateshop.Trend;
    import cloud.wconnect.rateshop.mapper.rateshop.IwcMapper;
    import cloud.wconnect.rateshop.pojo.dto.rateshop.PriceCacheKeyDTO;
    import cloud.wconnect.rateshop.pojo.dto.rateshop.iwc.IwcDTO;
    import cloud.wconnect.rateshop.pojo.dto.rateshop.iwc.OTAWeightConfigDTO;
    import cloud.wconnect.rateshop.pojo.dto.rateshop.iwc.RateRiseTimesWeightConfigDTO;
    import cloud.wconnect.rateshop.pojo.dto.rateshop.iwc.SoldOutWeightConfigDTO;
    import cloud.wconnect.rateshop.pojo.dto.rateshop.sender.AlertEventMessage;
    import cloud.wconnect.rateshop.pojo.dto.rateshop.sender.IwcChangeContent;
    import cloud.wconnect.rateshop.pojo.dto.rateshop.sender.IwcChangeMessage;
    import cloud.wconnect.rateshop.pojo.query.rateshop.IwcQuery;
    import cloud.wconnect.rateshop.service.AmazonSqsService;
    import cloud.wconnect.rateshop.service.rateshop.IIwcService;
    import cloud.wconnect.rateshop.service.rateshop.IIwcWeightConfigService;
    import cloud.wconnect.rateshop.service.rateshop.ITrendService;
    import com.baomidou.dynamic.datasource.annotation.DS;
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.google.common.collect.Lists;
    import jakarta.annotation.Resource;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.collections.CollectionUtils;
    import org.apache.commons.lang3.StringUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.data.redis.core.RedisCallback;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.PlatformTransactionManager;
    import org.springframework.transaction.TransactionDefinition;
    import org.springframework.transaction.TransactionStatus;

    import java.math.BigDecimal;
    import java.math.RoundingMode;
    import java.time.LocalDate;
    import java.time.Month;
    import java.time.temporal.ChronoUnit;
    import java.util.*;
    import java.util.concurrent.Executor;
    import java.util.stream.Collectors;
    import java.util.stream.IntStream;

    /**
     * <p>
     * IWC值表 服务实现类
     * </p>
     *
     * @author lichen
     * @since 2025-04-02
     */
    @Slf4j
    @Service
    @DS("rate_shop")
    public class IwcServiceImpl extends ServiceImpl<IwcMapper, Iwc> implements IIwcService {

        @Autowired
        private RedisTemplate<String, String> redisTemplate;
        @Autowired
        private BeanMapper beanMapper;
        @Autowired
        private IIwcWeightConfigService iIwcWeightConfigService;
        @Autowired
        private ITrendService trendService;
        @Autowired
        private AmazonSqsService amazonSqsService;
        @Autowired
        private ObjectMapper objectMapper;
        @Autowired
        private PlatformTransactionManager transactionManager;
        @Autowired
        private TransactionDefinition transactionDefinition;
        @Autowired
        private PropertyI18nRedisService propertyI18nRedisService;
        @Autowired
        private Executor executor;
        @Value("${sqs.queue-url.alert_event_queue}")
        private String alertEventUrl;
        @Resource
        private CommonService commonService;

        /**
         * IWC缓存前缀
         */
        public static final String IWC_CACHE_KEY_PREFIX = "iwc";
        private static final BigDecimal HUNDRED = new BigDecimal("100");

        /**
         * 计算IWC的值
         *
         * @param propertyId           资产ID
         * @param trendLocationId      资产配置的需求趋势地址ID
         * @param waitCalculateIwcList 待计算的iwc集合
         * @param setting              资产的rate shop配置
         * @param year                 年份
         * @return void
         * @author lichen
         * @date 2025/4/3
         */
        @Override
        public void calculateIwc(Integer propertyId, Integer trendLocationId, List<Iwc> waitCalculateIwcList, Setting setting, String year) {
            if (CollectionUtils.isEmpty(waitCalculateIwcList)) return;

            //国际化参数
            PropertyDTO propertyDTO = propertyI18nRedisService.get(propertyId);

            //查询资产的权重配置
            List<IwcWeightConfig> iwcWeightConfigs = iIwcWeightConfigService.listByPropertyId(propertyId);

            List<Long> checkInDateList = waitCalculateIwcList.stream().map(Iwc::getCheckInDate).toList();
            //查询资产的需求趋势数据
            Map<Long, BigDecimal> checkInDate2Rate = trendService.listByLocationIdAndDataRange(trendLocationId,
                            Collections.min(checkInDateList), Collections.max(checkInDateList))
                    .stream().collect(Collectors.toMap(Trend::getCheckInDate, Trend::getRate));

            //当前时间
            long nowTimestamp = DateTimeUtils.nowTimestamp();
            //待更新iwc集合
            ArrayList<Iwc> updateIwcList = Lists.newArrayListWithCapacity(waitCalculateIwcList.size());
            for (Iwc iwc : waitCalculateIwcList) {
                //需求趋势数据
                BigDecimal rateByTrend = checkInDate2Rate.get(iwc.getCheckInDate());
                if (Objects.isNull(rateByTrend)) continue;

                //生成房价缓存key的DTO
                PriceCacheKeyDTO priceCacheKeyDTO = beanMapper.map(iwc, PriceCacheKeyDTO.class);

                //最低价格
                String lowestRateStr = redisTemplate.opsForValue().get(priceCacheKeyDTO.generateLowestRateCacheKey());
                //判断是否sold out
                boolean isSoldOut = StringUtils.isNotBlank(lowestRateStr) && new BigDecimal(lowestRateStr).compareTo(BigDecimal.ZERO) == 0;

                //获取调账次数
                String ristTimesStr = redisTemplate.opsForValue().get(priceCacheKeyDTO.generateLowestRateRiseTimesCacheKey());
                int ristTimes = StringUtils.isNotBlank(ristTimesStr) ? Integer.parseInt(ristTimesStr) : 0;

                //计算iwc的值
                BigDecimal iwcValue = doCalculateIwc(propertyDTO, rateByTrend, isSoldOut, ristTimes, iwcWeightConfigs);

                iwc.setTenantId(propertyDTO.getTenantId());
                iwc.setIwcValue(iwcValue);
                iwc.setLevel(calculateIwcLevel(iwcValue));
                iwc.setUpdateTime(nowTimestamp);
                updateIwcList.add(iwc);
            }

            if (CollectionUtils.isEmpty(updateIwcList)) return;

            //分批批量持久化数据库
            List<List<Iwc>> lists = splitListDynamic(updateIwcList, 100);
            //表名
            String tableName = Iwc.TABLE_NAME_PREFIX + year;
            for (List<Iwc> itemList : lists) {
                TransactionStatus transaction = transactionManager.getTransaction(transactionDefinition);
                try {
                    //更新iwc
                    baseMapper.saveOrUpdateByUnique(tableName, itemList);
                    //提交事务
                    transactionManager.commit(transaction);

                    //比较iwc的级别是否发生变化，如果发生变化，则组装成对象发送alert message
                    TransactionUtils.afterCommitAsyncExecute(executor, () -> sendIwcChangeAlert(propertyId, itemList));
                } catch (Exception e) {
                    transactionManager.rollback(transaction);
                    throw e;
                }
            }
        }

        /**
         * 查询IWC值
         *
         * @param propertyDTO 资产DTO
         * @param hotelId     资产的自身酒店ID
         * @param locationId  资产配置的市场需求趋势地址ID
         * @param setting     资产的rate shop配置
         * @param iwcQuery    查询条件
         * @return java.util.List<cloud.wconnect.rateshop.pojo.dto.rateshop.iwc.IwcDTO>
         * @author lichen
         * @date 2025/4/7
         */
        @Override
        public List<IwcDTO> list(PropertyDTO propertyDTO, Integer hotelId, Integer locationId, Setting setting, IwcQuery iwcQuery) {
            Map<Integer, List<String>> year2RangeDate = getYear2RangeDate(iwcQuery.getFromDate(), iwcQuery.getToDate());
            //表名集合
            List<String> tableNames = year2RangeDate.keySet().stream().map(year -> Iwc.TABLE_NAME_PREFIX + year.toString()).toList();
            //查询存在的表名
            List<String> existTableNames = commonService.listTableName(tableNames);

            ArrayList<Iwc> iwcs = new ArrayList<>();

            for (Map.Entry<Integer, List<String>> entry : year2RangeDate.entrySet()) {
                Integer year = entry.getKey();
                String tableName = Iwc.TABLE_NAME_PREFIX + year.toString();
                if (!existTableNames.contains(tableName)) continue;

                List<String> dateRanges = entry.getValue();

                //构建查询对象
                Iwc iwc = new Iwc().setPropertyId(propertyDTO.getId())
                        .setTrendLocationId(locationId)
                        .setHotelId(hotelId)
                        .setPos(setting.getPos())
                        .setCurrencyId(setting.getCurrencyId())
                        .setDatasourceId(iwcQuery.getDatasourceId())
                        .setPlatformId(iwcQuery.getPlatformId())
                        .setRateType(iwcQuery.getRateType())
                        .setContainTaxFlag(setting.getContainTaxFlag());
                List<Iwc> iwcList = baseMapper.select(tableName, iwc, Integer.parseInt(dateRanges.get(0)), Integer.parseInt(dateRanges.get(1)));
                if (CollectionUtils.isNotEmpty(iwcList)) {
                    iwcs.addAll(iwcList);
                }
            }

            //国际化参数
            InternationalizationNumberDTO number = propertyI18nRedisService.get(propertyDTO.getId()).getInternationalization().getNumber();
            String calculationRule = number.getCalculationRule();

            ArrayList<IwcDTO> resultList = Lists.newArrayListWithCapacity(iwcs.size());
            Map<Long, List<Iwc>> checkInDate2IwcList = iwcs.stream().collect(Collectors.groupingBy(Iwc::getCheckInDate));
            for (Map.Entry<Long, List<Iwc>> entry : checkInDate2IwcList.entrySet()) {
                Long checkInDate = entry.getKey();
                List<Iwc> iwcListByCheckInDate = entry.getValue();

                if (CollectionUtils.isEmpty(iwcListByCheckInDate)) continue;

                //求和
                BigDecimal sum = iwcListByCheckInDate.stream().map(Iwc::getIwcValue).reduce(BigDecimal::add).orElse(BigDecimal.ZERO);
                //求平均
                BigDecimal avg = sum.divide(new BigDecimal(iwcListByCheckInDate.size()), PrecisionCons.RATE_SHOP_PRECISION,
                        InternationalizationUtils.getRoundingMode(calculationRule));
                //求最大的更新时间
                Long maxUpdateTime = iwcListByCheckInDate.stream().map(Iwc::getUpdateTime).max(Long::compareTo).orElse(0L);

                //转化日期格式
                LocalDate localDate = DateTimeUtils.strToLocalDate(checkInDate.toString(), BaseConstant.DATE_FORMAT_YYYYMMDD);
                String date = DateTimeUtils.localDateToStr(localDate, BaseConstant.DATE_FORMAT_YYYY_MM_DD);

                IwcDTO iwcDTO = new IwcDTO();
                iwcDTO.setDate(date);
                iwcDTO.setIwc(avg);
                iwcDTO.setUpdateTime(maxUpdateTime);
                resultList.add(iwcDTO);
            }
            return resultList;
        }

        /**
         * 计算IWC的级别
         *
         * @param iwcValue iwc的值
         * @return java.lang.Integer
         * @author lichen
         * @date 2025/4/5
         */
        public Integer calculateIwcLevel(BigDecimal iwcValue) {
            //计算趋势级别
            int level;
            if (iwcValue.compareTo(BigDecimal.ZERO) > 0 && iwcValue.compareTo(new BigDecimal("30")) <= 0) {
                level = 1;
            } else if (iwcValue.compareTo(new BigDecimal("30")) > 0 && iwcValue.compareTo(new BigDecimal("70")) <= 0) {
                level = 2;
            } else {//大于70
                level = 3;
            }
            return level;
        }

        /**
         * 计算iwc的级别
         *
         * @param propertyDTO      资产DTO
         * @param rateByTrend      需求趋势数据（OTA）
         * @param isSoldOut        是否售罄
         * @param ristTimes        调涨次数
         * @param iwcWeightConfigs iwc的权重配置
         * @return BigDecimal
         * @author lichen
         * @date 2025/4/4
         */
        public BigDecimal doCalculateIwc(PropertyDTO propertyDTO, BigDecimal rateByTrend, boolean isSoldOut, int ristTimes, List<IwcWeightConfig> iwcWeightConfigs) {
            Map<Integer, String> type2WeightConfig = iwcWeightConfigs.stream().collect(Collectors.toMap(IwcWeightConfig::getType, IwcWeightConfig::getWeightConfig));

            //OTA的趋势乘以100参与计算
            //四舍五入小数位的原因：OTA权重区间是整数，带小数位会导致无法命中区间。
            //例如：OTA权重区间是[0, 50]，[51,80]...，rateByTrend为0.50231，则乘以100后为50.231，无法命中任何一个区间
            rateByTrend = rateByTrend.multiply(new BigDecimal("100")).setScale(0, RoundingMode.HALF_UP);

            Integer otaWeight = 0;
            List<OTAWeightConfigDTO> otaWeightConfigDTOS = JsonUtils.convertStringToList(objectMapper, type2WeightConfig.get(IwcWeightConfig.OTA_WEIGHT_CONFIG), OTAWeightConfigDTO.class);
            for (OTAWeightConfigDTO otaWeightConfigDTO : otaWeightConfigDTOS) {
                //区间范围
                String[] rangeArray = otaWeightConfigDTO.getRange().split(",");
                BigDecimal startValue = new BigDecimal(rangeArray[0]);
                BigDecimal endValue = new BigDecimal(rangeArray[1]);
                //计算OTA权重
                if (rateByTrend.compareTo(startValue) >= 0 && rateByTrend.compareTo(endValue) <= 0) {
                    otaWeight = otaWeightConfigDTO.getValue();
                    break;
                }
            }

            Integer soldOutWeight = 0;
            Integer soldOutScore = 0;
            List<SoldOutWeightConfigDTO> soldOutWeightConfigDTOS = JsonUtils.convertStringToList(objectMapper, type2WeightConfig.get(IwcWeightConfig.SOLD_OUT_WEIGHT_CONFIG), SoldOutWeightConfigDTO.class);
            for (SoldOutWeightConfigDTO soldOutWeightConfigDTO : soldOutWeightConfigDTOS) {
                //计算售罄权重
                if (soldOutWeightConfigDTO.getIsSoldOut().equals(isSoldOut)) {
                    soldOutWeight = soldOutWeightConfigDTO.getValue();
                    soldOutScore = soldOutWeightConfigDTO.getScore();
                    break;
                }
            }

            Integer rateRiseTimesWeight = 0;
            Integer rateRiseTimesScore = 0;
            List<RateRiseTimesWeightConfigDTO> rateRiseTimesWeightConfigDTOS = JsonUtils.convertStringToList(objectMapper, type2WeightConfig.get(IwcWeightConfig.RATE_RISE_TIMES_WEIGHT_CONFIG), RateRiseTimesWeightConfigDTO.class);
            for (RateRiseTimesWeightConfigDTO rateRiseTimesWeightConfigDTO : rateRiseTimesWeightConfigDTOS) {
                String range = rateRiseTimesWeightConfigDTO.getRange();
                if (range.contains(",")) {
                    //区间范围
                    String[] rangeArray = range.split(",");
                    int startValue = Integer.parseInt(rangeArray[0]);
                    int endValue = Integer.parseInt(rangeArray[1]);
                    //计算权重
                    if (ristTimes >= startValue && ristTimes <= endValue) {
                        rateRiseTimesWeight = rateRiseTimesWeightConfigDTO.getValue();
                        rateRiseTimesScore = rateRiseTimesWeightConfigDTO.getScore();
                        break;
                    }
                } else {
                    //计算权重
                    if (Integer.parseInt(range) == ristTimes) {
                        rateRiseTimesWeight = rateRiseTimesWeightConfigDTO.getValue();
                        rateRiseTimesScore = rateRiseTimesWeightConfigDTO.getScore();
                        break;
                    }
                }
            }

            //计算总权重
            int totalWeight = otaWeight + soldOutWeight + rateRiseTimesWeight;
            if (totalWeight == 0) {
                return BigDecimal.ZERO;
            }

            InternationalizationNumberDTO number = propertyDTO.getInternationalization().getNumber();
            RoundingMode roundingMode = InternationalizationUtils.getRoundingMode(number.getCalculationRule());

            //计算各权重占总权重比例
            BigDecimal otaWeightRatio = new BigDecimal(otaWeight).divide(new BigDecimal(totalWeight), BaseConstant.DATA_PRECISION, roundingMode);
            BigDecimal soldOutWeightRatio = new BigDecimal(soldOutWeight).divide(new BigDecimal(totalWeight), BaseConstant.DATA_PRECISION, roundingMode);
            BigDecimal rateRiseTimesWeightRatio = new BigDecimal(rateRiseTimesWeight).divide(new BigDecimal(totalWeight), BaseConstant.DATA_PRECISION, roundingMode);

            //计算iwc：ota分值 * 权重 + soldOut分值 * 权重 + ristTime分值 * 权重
            BigDecimal iwcValue = otaWeightRatio.multiply(rateByTrend)
                    .add(soldOutWeightRatio.multiply(new BigDecimal(soldOutScore)))
                    .add(rateRiseTimesWeightRatio.multiply(new BigDecimal(rateRiseTimesScore)));

            //计算iwcValue大于100
            if (iwcValue.compareTo(HUNDRED) >= 0) {
                log.info("OTA info, otaWeight:{},otaWeightRatio:{},otaScore:{}", otaWeight, otaWeightRatio, rateByTrend);
                log.info("Sold info, isSoldOut:{},soldOutWeight:{},soldOutWeightRatio:{},soldOutScore:{}", isSoldOut, soldOutWeight, soldOutWeightRatio, soldOutScore);
                log.info("rateRiseTimes info, rateRiseTimes:{},rateRiseTimesWeight:{},rateRiseTimesWeightRatio:{},rateRiseTimesScore:{}", ristTimes, rateRiseTimesWeight, rateRiseTimesWeightRatio, rateRiseTimesScore);
            }

            //设置精度
            return iwcValue.setScale(BaseConstant.DATA_PRECISION, roundingMode);
        }

        /**
         * 发送iwc变化alert message
         *
         * @param propertyId 资产ID
         * @param iwcList    iwc集合
         * @return void
         * @author lichen
         * @date 2025/4/4
         */
        private void sendIwcChangeAlert(Integer propertyId, List<Iwc> iwcList) {
            //当前日期
            LocalDate now = LocalDate.now();

            HashMap<String, Iwc> msgKey2Iwc = new HashMap<>();
            HashMap<String, Long> cacheKey2ExpireTime = new HashMap<>();
            HashMap<String, String> cacheKey2Value = new HashMap<>();
            for (Iwc iwc : iwcList) {
                Long checkInDate = iwc.getCheckInDate();
                //计算checkInDate与当前日期的差值
                long diffDays = ChronoUnit.DAYS.between(now, DateTimeUtils.strToLocalDate(checkInDate.toString(), BaseConstant.DATE_FORMAT_YYYYMMDD));
                //判断checkInDate是否在未来14天之内
                if (diffDays > 14) continue;

                String cacheKey = generateIwcCacheKey(iwc);

                cacheKey2ExpireTime.put(cacheKey, diffDays + 1);
                cacheKey2Value.put(cacheKey, iwc.getLevel().toString());
                msgKey2Iwc.put(cacheKey, iwc);
            }

            ArrayList<String> cacheKeyList = new ArrayList<>(msgKey2Iwc.keySet());
            if (CollectionUtils.isEmpty(cacheKeyList)) {
                return;
            }

            //从缓存中获取数据
            List<String> cacheValues = redisTemplate.opsForValue().multiGet(cacheKeyList);

            List<IwcChangeContent> changeContentList = new ArrayList<>();
            //判断是否发生趋势级别变化
            for (int i = 0; i < cacheKeyList.size(); i++) {
                String cacheKey = cacheKeyList.get(i);
                //消息中的IWC
                Iwc iwcByMsg = msgKey2Iwc.get(cacheKey);
                //缓存中的IWC
                String iwcByCache = cacheValues.get(i);
                //缓存中的当前级别不为空 且 和消息中最新级别不一致时
                if (StringUtils.isNotEmpty(iwcByCache) && !iwcByCache.equals(iwcByMsg.getLevel().toString())) {
                    //市场趋势级别变化类型
                    int changeType = iwcByMsg.getLevel() > Integer.parseInt(iwcByCache) ?
                            MarketDemandTrendEnum.RISEN.getTrend() : MarketDemandTrendEnum.DROPPED.getTrend();

                    IwcChangeContent iwcChangeContent = beanMapper.map(iwcByMsg, IwcChangeContent.class);
                    iwcChangeContent.setChangeType(changeType);
                    iwcChangeContent.setChangeAfterLevel(iwcByMsg.getLevel());
                    changeContentList.add(iwcChangeContent);
                }
            }

            if (CollectionUtils.isNotEmpty(changeContentList)) {
                IwcChangeMessage iwcChangeMessage = new IwcChangeMessage();
                iwcChangeMessage.setPropertyId(propertyId);
                iwcChangeMessage.setIwcChangeContents(changeContentList);

                AlertEventMessage alertEventMessage = new AlertEventMessage();
                alertEventMessage.setType(AlertEventMessage.TYPE_IWC_LEVEL_CHANGE);
                alertEventMessage.setContent(JsonUtils.convertObjToString(objectMapper, iwcChangeMessage));

                amazonSqsService.sendMessage(alertEventUrl, JsonUtils.convertObjToString(objectMapper, alertEventMessage));
            }

            redisTemplate.opsForValue().multiSet(cacheKey2Value);
            //用一次连接，设置过期时间
            redisTemplate.executePipelined((RedisCallback<Void>) connection -> {
                for (String key : msgKey2Iwc.keySet()) {
                    //过期时间（天）
                    Long expireTime = cacheKey2ExpireTime.get(key);
                    //过期时间checkInDate距离今天的天数+1, 1小时 * 24（1天） * n（天）
                    connection.expire(key.getBytes(), 3600L * 24L * expireTime);
                }
                return null;
            });
        }

        /**
         * 生成iwc缓存key
         *
         * @param iwc iwc数据
         * @return java.lang.String
         * @author lichen
         * @date 2025/4/4
         */
        private String generateIwcCacheKey(Iwc iwc) {
            ArrayList<String> list = Lists.newArrayList(IWC_CACHE_KEY_PREFIX, iwc.getPropertyId().toString(),
                    iwc.getTrendLocationId().toString(), iwc.getHotelId().toString(), iwc.getPos(), iwc.getCurrencyId().toString(), iwc.getCheckInDate().toString(),
                    iwc.getDatasourceId().toString(), iwc.getPlatformId().toString(), iwc.getRateType().toString(), iwc.getContainTaxFlag().toString());
            return StringUtils.join(list, ":");
        }

        /**
         * 分割集合
         *
         * @param originalList 原集合
         * @param maxSize      每个子集合最大长度
         * @return java.util.List<java.util.List < cloud.wconnect.rateshop.entity.rateshop.Iwc>>
         * @author lichen
         * @date 2025/4/5
         */
        public List<List<Iwc>> splitListDynamic(List<Iwc> originalList, int maxSize) {
            int totalSize = originalList.size();
            int numberOfSublists = (int) Math.ceil((double) totalSize / maxSize);

            return IntStream.range(0, numberOfSublists)
                    .mapToObj(i -> {
                        int start = i * maxSize;
                        int end = Math.min(start + maxSize, totalSize);
                        return originalList.subList(start, end);
                    })
                    .collect(Collectors.toList());
        }

        /**
         * 根据日期区间获取年份和日期范围
         *
         * @param startDateStr 开始日期（yyyy-MM-dd）
         * @param endDateStr   结束日期（yyyy-MM-dd）
         * @return java.util.Map<java.lang.Integer, java.util.List < java.lang.String>>
         * @author lichen
         * @date 2025/4/7
         */
        private Map<Integer, List<String>> getYear2RangeDate(String startDateStr, String endDateStr) {
            LocalDate startDate = DateTimeUtils.strToLocalDate(startDateStr, BaseConstant.DATE_FORMAT_YYYY_MM_DD);
            LocalDate endDate = DateTimeUtils.strToLocalDate(endDateStr, BaseConstant.DATE_FORMAT_YYYY_MM_DD);

            HashMap<Integer, List<String>> resultMap = new HashMap<>();

            if (startDate.getYear() == endDate.getYear()) {
                resultMap.put(startDate.getYear(), Lists.newArrayList(startDateStr.replace("-", ""),
                        endDateStr.replace("-", "")));
            } else {
                LocalDate firstEnd = LocalDate.of(startDate.getYear(), Month.DECEMBER, 31);
                resultMap.put(startDate.getYear(), Lists.newArrayList(startDateStr.replace("-", ""),
                        DateTimeUtils.localDateToStr(firstEnd, BaseConstant.DATE_FORMAT_YYYYMMDD)));

                LocalDate secondStart = LocalDate.of(endDate.getYear(), Month.JANUARY, 1);
                resultMap.put(secondStart.getYear(), Lists.newArrayList(DateTimeUtils.localDateToStr(secondStart, BaseConstant.DATE_FORMAT_YYYYMMDD),
                        endDateStr.replace("-", "")));
            }
            return resultMap;
        }
    }