From 77320d3da3f95dbb6f244ba1b09c483b30e85358 Mon Sep 17 00:00:00 2001 From: liutao <790864623@qq.com> Date: Tue, 28 Apr 2026 17:28:18 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8E=86=E5=8F=B2=E8=AE=B0=E5=BD=95=E6=9F=A5?= =?UTF-8?q?=E8=AF=A2=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/device/DeviceController.java | 20 ++ .../admin/device/vo/DeviceSaveReqVO.java | 2 + .../iot/dal/dataobject/device/DeviceDO.java | 5 + .../DeviceContactModelMapper.java | 3 + .../iot/service/device/DeviceService.java | 4 + .../iot/service/device/DeviceServiceImpl.java | 121 +++++++ .../iot/service/device/TDengineService.java | 38 +++ .../iot/util/MapListStatsCalculator.java | 312 ++++++++++++++++++ 8 files changed, 505 insertions(+) create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/util/MapListStatsCalculator.java diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/DeviceController.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/DeviceController.java index 12182bddc..bfcbbcbd5 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/DeviceController.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/DeviceController.java @@ -262,6 +262,18 @@ public class DeviceController { return success(deviceContactModelDO); } + @GetMapping("/historyAnalyse") + @Operation(summary = "历史记录分析查询") +// @PreAuthorize("@ss.hasPermission('iot:device:query')") + public CommonResult> historyAnalyse(@RequestParam("deviceId") Long deviceId, + @RequestParam(name = "collectionStartTime", required = false) String collectionStartTime, + @RequestParam(name = "collectionEndTime", required = false) String collectionEndTime, + @RequestParam(name = "attributeCodes", required = false)List attributeCodes + ) throws JsonProcessingException { + Map deviceContactModelDO=deviceService.historyAnalyse(deviceId,collectionStartTime,collectionEndTime,attributeCodes); + return success(deviceContactModelDO); + } + @GetMapping("/getDeviceOperationalStatus") @Operation(summary = "获取首页设备运行状态") @@ -295,6 +307,14 @@ public class DeviceController { return success(deviceService.getDeviceAttributePage(pageParam, deviceModelAttributePageReqVO)); } + @GetMapping("/device-attribute/groupList") + @Operation(summary = "获得设备属性") + @Parameter(name = "deviceId", description = "设备id") + @PreAuthorize("@ss.hasPermission('iot:device:query')") + public CommonResult>> getDeviceAttributeGroupList(DeviceContactModelPageReqVO deviceModelAttributePageReqVO) { + return success(deviceService.getDeviceAttributeGroupList(deviceModelAttributePageReqVO)); + } + @GetMapping("/device-attribute/list") @Operation(summary = "获得设备属性列表") diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/DeviceSaveReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/DeviceSaveReqVO.java index 77c34e256..c7b322a86 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/DeviceSaveReqVO.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/DeviceSaveReqVO.java @@ -78,5 +78,7 @@ public class DeviceSaveReqVO { @Schema(description = "mqtt订阅主题") private String topic; + @Schema(description = "记录分析的条件json", example = "{}") + private String contactInfo; } \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/DeviceDO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/DeviceDO.java index 51a283c07..67e155309 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/DeviceDO.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/DeviceDO.java @@ -117,4 +117,9 @@ public class DeviceDO extends BaseDO { */ private String topic; + /** + * 记录分析的条件json + */ + private String contactInfo; + } \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/devicecontactmodel/DeviceContactModelMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/devicecontactmodel/DeviceContactModelMapper.java index b41d55d83..bb61d4f14 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/devicecontactmodel/DeviceContactModelMapper.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/devicecontactmodel/DeviceContactModelMapper.java @@ -69,4 +69,7 @@ public interface DeviceContactModelMapper extends BaseMapperX selectAttributeNameAndUnitById(@Param("id") Long id); + default List selectListById(DeviceContactModelPageReqVO reqVO){ + return selectList(new LambdaQueryWrapperX().eqIfPresent(DeviceContactModelDO::getDeviceId, reqVO.getDeviceId())); + }; } \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/DeviceService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/DeviceService.java index f7152b7c2..d0a526bf8 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/DeviceService.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/DeviceService.java @@ -159,4 +159,8 @@ public interface DeviceService { PageResult getAvailableDevicePage(DevicePageReqVO pageReqVO); List> singleDeviceFrom(Long deviceId); + + List> getDeviceAttributeGroupList(DeviceContactModelPageReqVO deviceModelAttributePageReqVO); + + Map historyAnalyse(Long deviceId, String collectionStartTime, String collectionEndTime, List attributeCodes); } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/DeviceServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/DeviceServiceImpl.java index c92b01c84..5fc7e4f4d 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/DeviceServiceImpl.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/DeviceServiceImpl.java @@ -8,6 +8,8 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.opc.OpcUtils; +import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductUnitDO; +import cn.iocoder.yudao.module.erp.dal.mysql.product.ErpProductUnitMapper; import cn.iocoder.yudao.module.iot.controller.admin.device.enums.DeviceRateTrendPeriodEnum; import cn.iocoder.yudao.module.iot.controller.admin.device.enums.DeviceStatusEnum; import cn.iocoder.yudao.module.iot.controller.admin.device.scheduled.utils.CronExpressionUtils; @@ -44,6 +46,7 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.device.DeviceAttributeDO; import cn.iocoder.yudao.module.iot.dal.mysql.device.DeviceAttributeMapper; import cn.iocoder.yudao.module.iot.framework.mqtt.consumer.IMqttservice; +import cn.iocoder.yudao.module.iot.util.MapListStatsCalculator; import com.alibaba.fastjson.JSON; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; @@ -53,6 +56,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang.math.NumberUtils; import org.apache.commons.lang3.StringUtils; import org.eclipse.paho.client.mqttv3.MqttException; import org.springframework.beans.factory.annotation.Qualifier; @@ -130,12 +134,17 @@ public class DeviceServiceImpl implements DeviceService { @Resource private IMqttservice mqttService; + @Resource + private ErpProductUnitMapper productUnitMapper; + // @Resource // private GatewayService gatewayService; @Resource private GatewayMapper gatewayMapper; + private static final String[] colors = new String[]{"#4888FF", "#00B42A", "#722ED1", "#FF7D00", "#F53F3F", "#86909C", "#FF7D00", + "#13A8A8","#7C3AED","#EF4444","#10B981","#FF9800","#4F46E5","#E11D48","#22C55E","#F97316","#9333EA","#F87171","#34D399","#FBBF24","#A855F7","#F87171","#FBBF24","#A855F7","#F87171","#FBBF24","#A855F7","#F871"}; @Override @@ -1053,6 +1062,118 @@ public class DeviceServiceImpl implements DeviceService { return resultList; } + @Override + public List> getDeviceAttributeGroupList(DeviceContactModelPageReqVO deviceModelAttributePageReqVO) { + //Map>> resultMap = new LinkedHashMap<>(); + List> resultList = new ArrayList<>(); + // 参数校验 + if (deviceModelAttributePageReqVO.getDeviceId() == null) { + throw exception(DEVICE_ID_MODEL_NOT_EXISTS); + + } + //查询采集设备 + DeviceDO deviceDO = deviceMapper.selectById(deviceModelAttributePageReqVO.getDeviceId()); + // 判断设备模型ID是否有效 + List deviceModelAttributeDOPageResult = deviceContactModelMapper.selectListById(deviceModelAttributePageReqVO); + if(CollUtil.isNotEmpty(deviceModelAttributeDOPageResult)){ + List typeNames = deviceModelAttributeDOPageResult.stream().map(DeviceContactModelDO::getTypeName).distinct().collect(Collectors.toList()); + List codes = deviceModelAttributeDOPageResult.stream().map(DeviceContactModelDO::getAttributeCode).distinct().collect(Collectors.toList()); + Map> resultMap = deviceModelAttributeDOPageResult.stream().collect(Collectors.groupingBy(DeviceContactModelDO::getTypeName)); + resultMap.forEach((key, value) -> { + HashMap objectObjectHashMap = new HashMap<>(); + objectObjectHashMap.put("group", key); + objectObjectHashMap.put("typeNames", typeNames); + objectObjectHashMap.put("codes", codes); + List> objects = new ArrayList<>(); + value.stream().forEach(item -> { + Map objectObjectHashMap1 = new HashMap<>(); + objectObjectHashMap1.put("name", item.getAttributeName()); + objectObjectHashMap1.put("code", item.getAttributeCode()); + objects.add(objectObjectHashMap1); + }); + objectObjectHashMap.put("points", objects); + objectObjectHashMap.put("deviceDO", deviceDO); + resultList.add(objectObjectHashMap); + }); + } + return resultList; + } + + @Override + public Map historyAnalyse(Long deviceId, String collectionStartTime, String collectionEndTime, List attributeCodes) { + + if (deviceId == null) { + return null; + } + List deviceContactModelDOS = deviceContactModelMapper.selectList(Wrappers.lambdaQuery(DeviceContactModelDO.class).eq(DeviceContactModelDO::getDeviceId, deviceId)); + if (attributeCodes == null) { + attributeCodes=deviceContactModelDOS.stream().map(DeviceContactModelDO::getAttributeCode).collect(Collectors.toList()); + } + //查询所有单位 + List erpProductUnitDOS = productUnitMapper.selectList(); + Map> unitMap = erpProductUnitDOS.stream().collect(Collectors.groupingBy(ErpProductUnitDO::getId)); + try { + // 1. 查询TDengine分页数据 + List> deviceDataList = tdengineService.queryHistoryList(deviceId, collectionStartTime, collectionEndTime,attributeCodes); + + if (deviceDataList.isEmpty()) { + return null; + } + Map resultMap = new HashMap(); + List time = deviceDataList.stream().map(map -> map.get("time")).collect(Collectors.toList()); + resultMap.put("time", time); + + //查询点位 + + Map> collect = deviceContactModelDOS.stream().collect(Collectors.groupingBy(DeviceContactModelDO::getAttributeCode)); + List> dataMap=new ArrayList<>(); + AtomicInteger a= new AtomicInteger(0); + attributeCodes.stream().forEach(item-> { + Map map = new HashMap(); + List deviceContactModelDOS1 = collect.get(item); + if (CollUtil.isNotEmpty(deviceContactModelDOS1)) + map.put("name", deviceContactModelDOS1.get(0).getAttributeName()); + map.put("type", "line"); + map.put("color", colors[a.incrementAndGet()]); + map.put("data", deviceDataList.stream().map(map1 -> map1.get(item)).collect(Collectors.toList())); + dataMap.add(map); + a.getAndIncrement(); + }); + resultMap.put("series", dataMap); + resultMap.put("data", deviceDataList); + + //查询点位的最大值,及最小值及平局值及波动值 + + // 简洁用法 + Map maxValues = MapListStatsCalculator.getMaxValues(deviceDataList, attributeCodes); + Map minValues = MapListStatsCalculator.getMinValues(deviceDataList, attributeCodes); + Map avgValues = MapListStatsCalculator.getAvgValues(deviceDataList, attributeCodes); + Map rangeValues = MapListStatsCalculator.getRangeValues(deviceDataList, attributeCodes); + List> analyseData=new ArrayList<>(); + + List finalAttributeCodes = attributeCodes; + deviceContactModelDOS.stream().filter(device -> finalAttributeCodes.contains(device.getAttributeCode())).forEach(item ->{ + Map map = new HashMap(); + map.put("code", item.getAttributeCode()); + map.put("name", item.getAttributeName()); + List erpProductUnitDOS1 = unitMap.get( NumberUtils.toLong(item.getDataUnit(), 0)); + if (CollUtil.isNotEmpty(erpProductUnitDOS1)) map.put("unit", erpProductUnitDOS1.get(0).getName()); + map.put("max", maxValues.get(item.getAttributeCode())); + map.put("min", minValues.get(item.getAttributeCode())); + map.put("avg", avgValues.get(item.getAttributeCode())); + map.put("range", rangeValues.get(item.getAttributeCode())); + analyseData.add(map); + }); + resultMap.put("analyseData", analyseData); + return resultMap; + } catch (Exception e) { + + log.error("处理设备历史数据异常", e); + + return null; + } + } + @Override public PageResult> historyRecord( diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/TDengineService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/TDengineService.java index caa958bc9..5690d77c9 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/TDengineService.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/TDengineService.java @@ -2534,5 +2534,43 @@ public class TDengineService { return false; } } + @DS("tdengine") + public List> queryHistoryList(Long deviceId, String startTime, String endTime,List attributeCodes) { + if (deviceId == null || startTime == null || endTime == null || attributeCodes.isEmpty()) { + return Collections.emptyList(); + } + String tableName = "besure_server.d_" + deviceId; + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("SELECT "); + attributeCodes.stream().forEach(item -> { + sqlBuilder.append("first(").append(item).append(") as ").append(item).append(","); + }); + + // 构建 SQL + sqlBuilder.append(" TO_CHAR(_wstart, 'hh24:mi') as time from ").append(tableName) + .append(" WHERE 1=1 "); + + if (StringUtils.isNotBlank(startTime)) { + sqlBuilder.append(" AND ts >= '").append(startTime).append("' "); + } + if (StringUtils.isNotBlank(endTime)) { + sqlBuilder.append(" AND ts <= '").append(endTime).append("' "); + } + + sqlBuilder.append(" INTERVAL(5m) ORDER BY _wstart "); + + String sql = sqlBuilder.toString(); + + try { + List> list = jdbcTemplate.queryForList(sql); + return list; + + } catch (EmptyResultDataAccessException e) { + return Collections.emptyList(); + } catch (Exception e) { + log.error("查询设备 {} 功率数据异常", deviceId, e); + return Collections.emptyList(); + } + } } \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/util/MapListStatsCalculator.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/util/MapListStatsCalculator.java new file mode 100644 index 000000000..67ed75f99 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/util/MapListStatsCalculator.java @@ -0,0 +1,312 @@ +package cn.iocoder.yudao.module.iot.util; + +import java.util.*; +import java.util.stream.Collectors; +import java.util.DoubleSummaryStatistics; +import java.text.DecimalFormat; +import java.math.BigDecimal; +import java.math.RoundingMode; + +public class MapListStatsCalculator { + + // 默认保留小数位数 + private static final int DEFAULT_SCALE = 2; + + /** + * 获取指定key列表的最大值(四舍五入) + */ + public static Map getMaxValues(List> dataList, List keys) { + return getMaxValues(dataList, keys, DEFAULT_SCALE); + } + + public static Map getMaxValues(List> dataList, List keys, int scale) { + return keys.stream() + .collect(Collectors.toMap( + key -> key, + key -> { + OptionalDouble max = dataList.stream() + .filter(map -> map.containsKey(key) && map.get(key) != null) + .mapToDouble(map -> convertToDouble(map.get(key))) + .max(); + return roundValue(max.orElse(Double.NaN), scale); + } + )); + } + + /** + * 获取指定key列表的最小值(四舍五入) + */ + public static Map getMinValues(List> dataList, List keys) { + return getMinValues(dataList, keys, DEFAULT_SCALE); + } + + public static Map getMinValues(List> dataList, List keys, int scale) { + return keys.stream() + .collect(Collectors.toMap( + key -> key, + key -> { + OptionalDouble min = dataList.stream() + .filter(map -> map.containsKey(key) && map.get(key) != null) + .mapToDouble(map -> convertToDouble(map.get(key))) + .min(); + return roundValue(min.orElse(Double.NaN), scale); + } + )); + } + + /** + * 获取指定key列表的平均值(四舍五入) + */ + public static Map getAvgValues(List> dataList, List keys) { + return getAvgValues(dataList, keys, DEFAULT_SCALE); + } + + public static Map getAvgValues(List> dataList, List keys, int scale) { + return keys.stream() + .collect(Collectors.toMap( + key -> key, + key -> { + OptionalDouble avg = dataList.stream() + .filter(map -> map.containsKey(key) && map.get(key) != null) + .mapToDouble(map -> convertToDouble(map.get(key))) + .average(); + return roundValue(avg.orElse(Double.NaN), scale); + } + )); + } + + /** + * 获取指定key列表的最大值与最小值相差(四舍五入) + */ + public static Map getRangeValues(List> dataList, List keys) { + return getRangeValues(dataList, keys, DEFAULT_SCALE); + } + + public static Map getRangeValues(List> dataList, List keys, int scale) { + return keys.stream() + .collect(Collectors.toMap( + key -> key, + key -> { + DoubleSummaryStatistics stats = dataList.stream() + .filter(map -> map.containsKey(key) && map.get(key) != null) + .mapToDouble(map -> convertToDouble(map.get(key))) + .summaryStatistics(); + double range = stats.getCount() > 0 ? stats.getMax() - stats.getMin() : 0.0; + return roundValue(range, scale); + } + )); + } + + /** + * 一次性获取所有统计信息(四舍五入) + */ + public static Map> getAllStats(List> dataList, List keys) { + return getAllStats(dataList, keys, DEFAULT_SCALE); + } + + public static Map> getAllStats(List> dataList, List keys, int scale) { + return keys.stream() + .collect(Collectors.toMap( + key -> key, + key -> { + DoubleSummaryStatistics stats = dataList.stream() + .filter(map -> map.containsKey(key) && map.get(key) != null) + .mapToDouble(map -> convertToDouble(map.get(key))) + .summaryStatistics(); + + Map result = new HashMap<>(); + result.put("max", roundValue(stats.getCount() > 0 ? stats.getMax() : 0.0, scale)); + result.put("min", roundValue(stats.getCount() > 0 ? stats.getMin() : 0.0, scale)); + result.put("avg", roundValue(stats.getCount() > 0 ? stats.getAverage() : 0.0, scale)); + result.put("range", roundValue(stats.getCount() > 0 ? stats.getMax() - stats.getMin() : 0.0, scale)); + result.put("count", (double) stats.getCount()); // 数量保持整数 + result.put("sum", roundValue(stats.getSum(), scale)); + return result; + } + )); + } + + /** + * 对象转换为Double + */ + private static double convertToDouble(Object value) { + if (value == null) { + return 0.0; + } + if (value instanceof Number) { + return ((Number) value).doubleValue(); + } + if (value instanceof String) { + try { + return Double.parseDouble(value.toString().trim()); + } catch (NumberFormatException e) { + return 0.0; + } + } + if (value instanceof Boolean) { + return (Boolean) value ? 1.0 : 0.0; + } + return 0.0; + } + + /** + * 四舍五入方法 - 使用BigDecimal(最准确) + */ + public static double roundValue(double value, int scale) { + if (Double.isNaN(value) || Double.isInfinite(value)) { + return value; + } + return BigDecimal.valueOf(value) + .setScale(scale, RoundingMode.HALF_UP) + .doubleValue(); + } + + /** + * 四舍五入方法 - 使用DecimalFormat(格式化输出) + */ + public static String roundValueToString(double value, int scale) { + if (Double.isNaN(value)) { + return "NaN"; + } + if (Double.isInfinite(value)) { + return value > 0 ? "Infinity" : "-Infinity"; + } + + // 构建格式字符串 + StringBuilder pattern = new StringBuilder("0"); + if (scale > 0) { + pattern.append("."); + for (int i = 0; i < scale; i++) { + pattern.append("0"); + } + } + + DecimalFormat df = new DecimalFormat(pattern.toString()); + df.setRoundingMode(RoundingMode.HALF_UP); + return df.format(value); + } + + /** + * 格式化数字为字符串,自动去除末尾的0 + */ + public static String formatValue(double value, int maxScale) { + if (Double.isNaN(value) || Double.isInfinite(value)) { + return String.valueOf(value); + } + + // 先四舍五入 + double rounded = roundValue(value, maxScale); + + // 转换为字符串,去除末尾的0 + String str = String.valueOf(rounded); + if (str.contains(".")) { + str = str.replaceAll("0*$", "").replaceAll("\\.$", ""); + } + return str; + } + + /** + * 获取统计信息(字符串格式,已四舍五入) + */ + public static Map> getAllStatsAsString( + List> dataList, + List keys, + int scale) { + + return keys.stream() + .collect(Collectors.toMap( + key -> key, + key -> { + DoubleSummaryStatistics stats = dataList.stream() + .filter(map -> map.containsKey(key) && map.get(key) != null) + .mapToDouble(map -> convertToDouble(map.get(key))) + .summaryStatistics(); + + Map result = new HashMap<>(); + result.put("max", roundValueToString(stats.getCount() > 0 ? stats.getMax() : 0.0, scale)); + result.put("min", roundValueToString(stats.getCount() > 0 ? stats.getMin() : 0.0, scale)); + result.put("avg", roundValueToString(stats.getCount() > 0 ? stats.getAverage() : 0.0, scale)); + result.put("range", roundValueToString(stats.getCount() > 0 ? stats.getMax() - stats.getMin() : 0.0, scale)); + result.put("count", String.valueOf(stats.getCount())); + result.put("sum", roundValueToString(stats.getSum(), scale)); + return result; + } + )); + } + + /** + * 使用示例 + */ + public static void main(String[] args) { + // 创建测试数据 + List> dataList = new ArrayList<>(); + + Map map1 = new HashMap<>(); + map1.put("temperature", 25.56789); + map1.put("humidity", 60.12345); + map1.put("pressure", 100.23456); + dataList.add(map1); + + Map map2 = new HashMap<>(); + map2.put("temperature", 28.33333); + map2.put("humidity", 55.55555); + map2.put("pressure", 102.77777); + dataList.add(map2); + + Map map3 = new HashMap<>(); + map3.put("temperature", 22.11111); + map3.put("humidity", 65.99999); + map3.put("pressure", 98.65432); + dataList.add(map3); + + // 要统计的key + List keys = Arrays.asList("temperature", "humidity", "pressure"); + + System.out.println("=== 默认保留2位小数 ==="); + Map> allStats = getAllStats(dataList, keys); + printStats(allStats); + + System.out.println("\n=== 保留3位小数 ==="); + Map> stats3 = getAllStats(dataList, keys, 3); + printStats(stats3); + + System.out.println("\n=== 字符串格式(保留2位小数)==="); + Map> stringStats = getAllStatsAsString(dataList, keys, 2); + printStringStats(stringStats); + + System.out.println("\n=== 单独使用四舍五入方法 ==="); + double testValue = 123.456789; + System.out.println("原始值: " + testValue); + System.out.println("保留0位: " + roundValue(testValue, 0)); + System.out.println("保留2位: " + roundValue(testValue, 2)); + System.out.println("保留4位: " + roundValue(testValue, 4)); + System.out.println("格式化输出: " + roundValueToString(testValue, 2)); + System.out.println("智能格式化: " + formatValue(testValue, 2)); + } + + private static void printStats(Map> stats) { + for (Map.Entry> entry : stats.entrySet()) { + System.out.println("\n列: " + entry.getKey()); + Map colStats = entry.getValue(); + System.out.printf(" 最大值: %.4f%n", colStats.get("max")); + System.out.printf(" 最小值: %.4f%n", colStats.get("min")); + System.out.printf(" 平均值: %.4f%n", colStats.get("avg")); + System.out.printf(" 差值: %.4f%n", colStats.get("range")); + System.out.printf(" 数量: %.0f%n", colStats.get("count")); + System.out.printf(" 总和: %.4f%n", colStats.get("sum")); + } + } + + private static void printStringStats(Map> stats) { + for (Map.Entry> entry : stats.entrySet()) { + System.out.println("\n列: " + entry.getKey()); + Map colStats = entry.getValue(); + System.out.println(" 最大值: " + colStats.get("max")); + System.out.println(" 最小值: " + colStats.get("min")); + System.out.println(" 平均值: " + colStats.get("avg")); + System.out.println(" 差值: " + colStats.get("range")); + System.out.println(" 数量: " + colStats.get("count")); + System.out.println(" 总和: " + colStats.get("sum")); + } + } +} \ No newline at end of file