diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/vo/product/ErpProductPageReqVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/vo/product/ErpProductPageReqVO.java index 9b0b53b0b..03366d081 100644 --- a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/vo/product/ErpProductPageReqVO.java +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/vo/product/ErpProductPageReqVO.java @@ -27,7 +27,10 @@ public class ErpProductPageReqVO extends PageParam { @Schema(description = "产品编号", example = "11161") private String code; + @Schema(description = "产品编码", example = "P-001") + private String barCode; + @Schema(description = "产品规格", example = "红色") private String standard; -} \ No newline at end of file +} diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/product/ErpProductMapper.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/product/ErpProductMapper.java index 0a39d3189..a6508130b 100644 --- a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/product/ErpProductMapper.java +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/product/ErpProductMapper.java @@ -27,10 +27,17 @@ import java.util.List; @Mapper public interface ErpProductMapper extends BaseMapperX { + // 适配barCode和code两种情况 + default String resolveCode(ErpProductPageReqVO reqVO){ + String barCode = StringUtils.hasText(reqVO.getBarCode()) ? reqVO.getBarCode() : reqVO.getCode(); + return barCode; + } + default PageResult selectPage(ErpProductPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() .likeIfPresent(ErpProductDO::getName, reqVO.getName()) - .likeIfPresent(ErpProductDO::getBarCode, reqVO.getCode()) + .likeIfPresent(ErpProductDO::getBarCode, resolveCode(reqVO)) .eqIfPresent(ErpProductDO::getCategoryId, reqVO.getCategoryId()) .betweenIfPresent(ErpProductDO::getCreateTime, reqVO.getCreateTime()) .orderByDesc(ErpProductDO::getId)); @@ -47,9 +54,10 @@ public interface ErpProductMapper extends BaseMapperX { } default PageResult selectProductCodeExist(ErpProductPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() .eqIfPresent(ErpProductDO::getName, reqVO.getName()) - .eqIfPresent(ErpProductDO::getBarCode, reqVO.getCode()) + .eqIfPresent(ErpProductDO::getBarCode, resolveCode(reqVO)) .orderByAsc(ErpProductDO::getId)); } @@ -62,9 +70,7 @@ public interface ErpProductMapper extends BaseMapperX { Long count = selectCount(new LambdaQueryWrapper() .eq(ErpProductDO::getName, reqVO.getName()) .eq(ErpProductDO::getStandard, reqVO.getStandard())); - return count > 0; - } default Long selectCountByCategoryId(Long categoryId) { 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 ef0681e90..c2d1dc613 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 @@ -239,6 +239,14 @@ public class DeviceController { return success(deviceContactModelDO); } + @GetMapping("/singleDeviceFrom") + @Operation(summary = "单设备查看") +// @PreAuthorize("@ss.hasPermission('iot:device:query')") + public CommonResult>> singleDeviceFrom(@RequestParam("deviceId") Long deviceId) throws JsonProcessingException { + List> deviceContactModelDO=deviceService.singleDeviceFrom(deviceId); + return success(deviceContactModelDO); + } + @GetMapping("/historyRecord") @Operation(summary = "历史记录查询") @@ -254,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 = "获取首页设备运行状态") @@ -287,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 4d399004d..5644fb52a 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,10 @@ public interface DeviceService { PageResult getAvailableDevicePage(DevicePageReqVO pageReqVO); List deviceList(@Valid 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 eb0150190..c6c7fd610 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; @@ -45,6 +47,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; @@ -54,6 +57,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; @@ -65,9 +69,13 @@ import org.springframework.validation.annotation.Validated; import javax.annotation.Resource; import java.sql.Timestamp; import java.text.SimpleDateFormat; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.time.*; import java.time.format.DateTimeFormatter; import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; import java.util.stream.Collectors; @@ -128,12 +136,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 @@ -972,6 +985,197 @@ public class DeviceServiceImpl implements DeviceService { return resultMap; } + @Override + public List> singleDeviceFrom(Long deviceId) { + Map>> resultMap = new LinkedHashMap<>(); + List> resultList = new ArrayList<>(); + List records = deviceContactModelMapper.selectList(Wrappers.lambdaQuery() + .eq(DeviceContactModelDO::getDeviceId,deviceId) + .orderByDesc(DeviceContactModelDO::getId)); + + if (records == null || records.isEmpty()) { + return resultList; + } + + try { + // 获取最新一行数据 + Map latestRow = tdengineService.newSelectLatestRow(deviceId); + if (latestRow == null || latestRow.isEmpty()) { + return resultList; + } + + // 属性类型映射 + Map idToNameMap = deviceAttributeTypeMapper.selectList() + .stream() + .collect(Collectors.toMap( + DeviceAttributeTypeDO::getId, + DeviceAttributeTypeDO::getName + )); + Map attributeTypeCache = new HashMap<>(); + + // 遍历 records 填充数据 + for (DeviceContactModelDO record : records) { + + String attributeCode = record.getAttributeCode(); + if (attributeCode == null) continue; + + Object rawValue = latestRow.get(attributeCode); + + Object finalValue = DataTypeParseUtil.parse(rawValue, record.getDataType()); + + record.setAddressValue(finalValue); + + // 属性类型名称 + String attributeTypeName = "其他"; + Object typeObj = record.getAttributeType(); + if (typeObj != null) { + String typeStr = typeObj.toString(); + try { + Long typeId = Long.parseLong(typeStr); + attributeTypeName = attributeTypeCache.computeIfAbsent(typeId, + k -> idToNameMap.getOrDefault(typeId, typeStr)); + } catch (NumberFormatException e) { + attributeTypeName = typeStr; + } + } + + Map simplifiedData = new HashMap<>(); + simplifiedData.put("value", record.getAddressValue()); + simplifiedData.put("label", record.getAttributeName()); + simplifiedData.put("prop", "parpmeter_" + record.getId()); + resultMap.computeIfAbsent(attributeTypeName, k -> new ArrayList<>()).add(simplifiedData); + } + if (!resultMap.isEmpty()) { + AtomicInteger a= new AtomicInteger(0); + resultMap.forEach((key, value) -> { + HashMap objectObjectHashMap = new HashMap<>(); + objectObjectHashMap.put("id", a.incrementAndGet()); + objectObjectHashMap.put("title", key); + objectObjectHashMap.put("items", value); + resultList.add(objectObjectHashMap); + }); + + } + + } catch (Exception e) { + log.warn("处理设备 {} 最新数据时异常: {}", deviceId, e.getMessage()); + } + + 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().filter(d -> d.getTypeName() != null).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( @@ -1749,6 +1953,7 @@ public class DeviceServiceImpl implements DeviceService { return deviceMapper.selectList(pageReqVO); } + /** * 按时间区间查询设备整体开机率、稼动率日趋势 * 一次性查询时间范围内按天、按设备的统计数据,避免按天循环重复查库 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 3a5f66bfe..1461c7f34 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 @@ -2560,6 +2560,44 @@ 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(); + } + } // ========================= app报表相关接口 =================== 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