package com.ruoyi.cms.util; import com.ruoyi.cms.constant.CommonConstant; import com.ruoyi.cms.constant.enums.QueryTimeTypeEnum; import com.ruoyi.cms.domain.vo.HeatmapVo; import com.ruoyi.cms.domain.vo.QueryResultVo; import com.ruoyi.cms.domain.vo.TreadVo; import org.apache.commons.collections4.CollectionUtils; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.Comparator; import java.util.List; import java.util.Objects; import java.util.Optional; /** * 数学工具 * * @author 马宝龙 * @date 2026/1/21 */ public class MathUtil { private MathUtil() { } /** * 除以 10000 * * @param n 原数 * @return 结果 */ public static BigDecimal divideTenThousand(Long n) { if (Objects.isNull(n)) { return null; } BigDecimal bigDecimal = new BigDecimal(n.toString()); return divideTenThousand(bigDecimal); } /** * 除以 10000 * * @param bigDecimal 原数 * @return 结果 */ public static BigDecimal divideTenThousand(BigDecimal bigDecimal) { if (Objects.isNull(bigDecimal)) { return null; } String plainString = bigDecimal.divide(new BigDecimal("10000"), 2, RoundingMode.HALF_UP) .stripTrailingZeros().toPlainString(); return new BigDecimal(plainString); } /** * 计算比率 * * @param divisor 除数(分母) * @param dividend 被除数(分子) * @return 比率 */ public static BigDecimal divide(BigDecimal divisor, BigDecimal dividend) { if (Objects.isNull(divisor) || Objects.isNull(dividend)) { return null; } String plainString = dividend.divide(divisor, 2, RoundingMode.HALF_UP) .stripTrailingZeros().toPlainString(); return new BigDecimal(plainString); } /** * 计算比率 * * @param divisor 除数(分母) * @param dividend 被除数(分子) * @return 比率 */ public static BigDecimal calculatePercentage(Long divisor, Long dividend) { BigDecimal divisorBigDecimal = Optional.ofNullable(divisor) .map(item -> new BigDecimal(item.toString())).orElse(null); BigDecimal dividendBigDecimal = Optional.ofNullable(dividend) .map(item -> new BigDecimal(item.toString())).orElse(null); return calculatePercentage(divisorBigDecimal, dividendBigDecimal); } /** * 计算比率 * * @param divisor 除数(分母) * @param dividend 被除数(分子) * @return 比率 */ public static BigDecimal calculatePercentage(BigDecimal divisor, BigDecimal dividend) { if (null == dividend || BigDecimal.ZERO.compareTo(dividend) == 0) { return BigDecimal.ZERO; } else { if (null == divisor || BigDecimal.ZERO.compareTo(divisor) == 0) { return new BigDecimal("100"); } else { String plainString = dividend.multiply(new BigDecimal("100")) .divide(divisor, 2, RoundingMode.HALF_UP) .stripTrailingZeros().toPlainString(); return new BigDecimal(plainString); } } } /** * 计算占比 * * @param list 数据 */ public static void calculatePercentage(List list) { if (CollectionUtils.isEmpty(list)) { return; } long totalCount = 0; for (QueryResultVo dto : list) { totalCount = totalCount + Optional.ofNullable(dto.getCount()).orElse(0L); } for (QueryResultVo dto : list) { dto.setCountPercentage(calculatePercentage(totalCount, dto.getCount())); } } /** * 计算环比 * * @param lastCount 上期数据 * @param currentCount 本期数据 * @return 环比 */ public static BigDecimal calculatePercentageGrowthRate(Long lastCount, Long currentCount) { lastCount = Optional.ofNullable(lastCount).orElse(0L); currentCount = Optional.ofNullable(currentCount).orElse(0L); return calculatePercentage(lastCount, (currentCount - lastCount)); } /** * 计算环比 * * @param lastBigDecimal 上期数据 * @param currentBigDecimal 本期数据 * @return 环比 */ public static BigDecimal calculatePercentageGrowthRate(BigDecimal lastBigDecimal, BigDecimal currentBigDecimal) { lastBigDecimal = Optional.ofNullable(lastBigDecimal).orElse(BigDecimal.ZERO); currentBigDecimal = Optional.ofNullable(currentBigDecimal).orElse(BigDecimal.ZERO); return calculatePercentage(lastBigDecimal, (currentBigDecimal.subtract(lastBigDecimal))); } /** * 计算同比和环比 * * @param queryTimeType 查询时间类型 * @param list 趋势数据列表 */ public static void calculatePercentageGrowthRate(QueryTimeTypeEnum queryTimeType, List list) { if (CollectionUtils.isEmpty(list)) { return; } // 按月份正序排序 list.sort(Comparator.comparingInt(TreadVo::getSort)); for (int i = 0; i < list.size(); i++) { TreadVo item = list.get(i); long currentCount = Optional.ofNullable(item.getCurrentCount()) .orElse(0L); long lastYearCount = Optional.ofNullable(item.getLastYearCount()) .orElse(0L); // 年度不计算同比 if (queryTimeType != QueryTimeTypeEnum.YEAR) { // 计算同比 item.setYearOnYearGrowthDiff(currentCount - lastYearCount); if (0 == currentCount) { item.setYearOnYearGrowthRate(BigDecimal.ZERO); } else if (0 == lastYearCount) { item.setYearOnYearGrowthRate(new BigDecimal("100.00")); } else { item.setYearOnYearGrowthRate(calculatePercentage(lastYearCount, (currentCount - lastYearCount))); } } // 计算环比 long lastMonthCount; if (0 == i) { // 第一个取上期的最后一个数据 lastMonthCount = Optional.ofNullable(list.get(list.size() - 1).getLastYearCount()) .orElse(0L); } else { lastMonthCount = Optional.ofNullable(list.get(i - 1).getLastYearCount()) .orElse(0L); } item.setLastMonthCount(lastMonthCount); item.setMonthOnMonthGrowthDiff(currentCount - lastMonthCount); if (0 == currentCount) { item.setMonthOnMonthGrowthRate(BigDecimal.ZERO); } else if (0 == lastMonthCount) { item.setMonthOnMonthGrowthRate(new BigDecimal("100.00")); } else { item.setMonthOnMonthGrowthRate(calculatePercentage(lastMonthCount, (currentCount - lastMonthCount))); } } } /** * 计算占比 - 热力图 * * @param list 热力图数据 */ public static void calculatePercentageHeatmap(List list) { if (CollectionUtils.isEmpty(list)) { return; } // 计算占比 long totalCount = 0; for (HeatmapVo vo : list) { totalCount = totalCount + Optional.ofNullable(vo.getCount()).orElse(0L); } for (HeatmapVo vo : list) { vo.setPercentage(calculatePercentage(totalCount, vo.getCount())); vo.setAreaName(Optional.ofNullable(CommonConstant.AREA_MAP.get(vo.getAreaCode())).orElse("未知")); } } }