diff --git a/zh-module-applet/zh-applet-admin/src/main/java/org/jeecg/modules/appmana/controller/DataCenterController.java b/zh-module-applet/zh-applet-admin/src/main/java/org/jeecg/modules/appmana/controller/DataCenterController.java index b29e43d..3c3eed0 100644 --- a/zh-module-applet/zh-applet-admin/src/main/java/org/jeecg/modules/appmana/controller/DataCenterController.java +++ b/zh-module-applet/zh-applet-admin/src/main/java/org/jeecg/modules/appmana/controller/DataCenterController.java @@ -9,6 +9,7 @@ import org.jeecg.common.api.vo.Result; import org.jeecg.common.config.TenantContext; import org.jeecg.common.constant.PollutionConstants; import org.jeecg.common.dto.AlertSummaryDTO; +import org.jeecg.common.dto.SurvSummaryDTO; import org.jeecg.common.entity.SurvDeviceDeploy; import org.jeecg.common.entity.SurvDisplayInfo; import org.jeecg.common.entity.SurvPestlightAlert; @@ -17,10 +18,8 @@ import org.jeecg.common.util.DateTimeRangeUtil; import org.jeecg.common.vo.PestLightAlertVo; import org.jeecg.common.vo.result.DataCenterDataSummary; import org.jeecg.common.vo.statistic.AlertSummaryVo; -import org.jeecg.modules.appmana.service.ISurvAlertRecordService; -import org.jeecg.modules.appmana.service.ISurvDeviceDeployService; -import org.jeecg.modules.appmana.service.ISurvPestlightAlertService; -import org.jeecg.modules.appmana.service.ISurvStationInfoService; +import org.jeecg.common.vo.statistic.SurvSummaryVo; +import org.jeecg.modules.appmana.service.*; import org.jeecg.modules.appmana.service.impl.CommonServiceImpl; import org.jeecg.modules.appmana.service.impl.IotCommonP3ServiceImpl; import org.jeecg.modules.appmana.utils.MonitoringDataRichTextBuilder; @@ -48,12 +47,13 @@ public class DataCenterController { @Autowired private CommonServiceImpl commonService; - @Autowired - private ISurvAlertRecordService survAlertRecordService; + @Autowired private ISurvStationInfoService survStationInfoService; + + /** * 获取所有大屏信息 */ @@ -83,11 +83,12 @@ public class DataCenterController { * 获取设备统计监测统计数据 */ @PostMapping(value = "/deviceAlertStatistic") - public Result deviceAlertStatistic(@RequestBody AlertSummaryDTO alertSummaryDTO) { + public Result deviceAlertStatistic(@RequestBody SurvSummaryDTO survSummaryDTO) { - alertSummaryDTO.setTenantId(TenantContext.getTenant()); - AlertSummaryVo alertSummaryVo = survAlertRecordService.getSummary(alertSummaryDTO); - return Result.OK(alertSummaryVo); + survSummaryDTO.setTenantId(TenantContext.getTenant()); + SurvSummaryVo survSummaryVo = commonService.getSurvSummary(survSummaryDTO); + + return Result.OK(survSummaryVo); } /** diff --git a/zh-module-applet/zh-applet-admin/src/main/java/org/jeecg/modules/appmana/controller/InnerController.java b/zh-module-applet/zh-applet-admin/src/main/java/org/jeecg/modules/appmana/controller/InnerController.java index 9602053..c4f239e 100644 --- a/zh-module-applet/zh-applet-admin/src/main/java/org/jeecg/modules/appmana/controller/InnerController.java +++ b/zh-module-applet/zh-applet-admin/src/main/java/org/jeecg/modules/appmana/controller/InnerController.java @@ -6,6 +6,7 @@ import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; import org.jeecg.common.api.vo.Result; +import org.jeecg.common.dto.SurvSummaryDTO; import org.jeecg.common.entity.PollutionSetVo; import org.jeecg.common.entity.SurvDeviceDeploy; import org.jeecg.common.iot.common.VOIntegrateStatistic; @@ -16,6 +17,7 @@ import org.jeecg.common.vo.CommonDataTrans; import org.jeecg.common.vo.VOBigScreenSurvQ; import org.jeecg.common.vo.VOHisResult; import org.jeecg.common.vo.inner.*; +import org.jeecg.common.vo.statistic.SurvSummaryVo; import org.jeecg.modules.appmana.service.ISurvDeviceDeployService; import org.jeecg.modules.appmana.service.impl.CommonServiceImpl; import org.jeecg.modules.appmana.service.impl.IotCommonP3ServiceImpl; @@ -180,4 +182,16 @@ public class InnerController { return new ArrayList<>(); } } + + @ApiOperation(value = "13. 站点设备多项统计图", notes = "") + @ApiOperationSupport(order = 13) + @PostMapping(value = "/stationDeviceSummary") + public SurvSummaryVo stationDeviceSummary(@RequestBody SurvSummaryDTO survSummaryDTO) { + if(survSummaryDTO!=null){ + return commonService.getSurvSummary(survSummaryDTO); + }else { + return new SurvSummaryVo(); + } + } + } diff --git a/zh-module-applet/zh-applet-admin/src/main/java/org/jeecg/modules/appmana/mapper/FlexibleMonitorMapper.java b/zh-module-applet/zh-applet-admin/src/main/java/org/jeecg/modules/appmana/mapper/FlexibleMonitorMapper.java new file mode 100644 index 0000000..e7842b1 --- /dev/null +++ b/zh-module-applet/zh-applet-admin/src/main/java/org/jeecg/modules/appmana/mapper/FlexibleMonitorMapper.java @@ -0,0 +1,38 @@ +package org.jeecg.modules.appmana.mapper; + + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.jeecg.common.dto.statistic.FlexibleTimeQueryDTO; + +import java.util.Date; +import java.util.List; +import java.util.Map; + +/** + * 灵活监测数据Mapper + */ +@Mapper +public interface FlexibleMonitorMapper extends BaseMapper { + + /** + * 灵活时间维度统计(核心方法) + */ + List> getFlexibleTimeStats(@Param("query") FlexibleTimeQueryDTO query); + + /** + * 检查表是否存在 + */ + Integer checkTableExists(@Param("tableName") String tableName); + + /** + * 获取表的数值列信息 + */ + List> getTableColumns(@Param("tableName") String tableName); + + /** + * 获取表统计信息 + */ + Map getStatistics(@Param("tableName") String tableName); +} \ No newline at end of file diff --git a/zh-module-applet/zh-applet-admin/src/main/java/org/jeecg/modules/appmana/mapper/xml/FlexibleMonitorMapper.xml b/zh-module-applet/zh-applet-admin/src/main/java/org/jeecg/modules/appmana/mapper/xml/FlexibleMonitorMapper.xml new file mode 100644 index 0000000..bab703d --- /dev/null +++ b/zh-module-applet/zh-applet-admin/src/main/java/org/jeecg/modules/appmana/mapper/xml/FlexibleMonitorMapper.xml @@ -0,0 +1,237 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/zh-module-applet/zh-applet-admin/src/main/java/org/jeecg/modules/appmana/service/FlexibleMonitorService.java b/zh-module-applet/zh-applet-admin/src/main/java/org/jeecg/modules/appmana/service/FlexibleMonitorService.java new file mode 100644 index 0000000..0251286 --- /dev/null +++ b/zh-module-applet/zh-applet-admin/src/main/java/org/jeecg/modules/appmana/service/FlexibleMonitorService.java @@ -0,0 +1,202 @@ +package org.jeecg.modules.appmana.service; + + + +import org.jeecg.common.dto.statistic.FlexibleTimeQueryDTO; + +import java.time.LocalDateTime; +import java.util.Date; +import java.util.List; +import java.util.Map; + +/** + * 灵活时间维度监测数据统计服务 + */ +public interface FlexibleMonitorService { + // ==================== 核心方法 ==================== + + /** + * 最底层通用方法(所有其他方法最终都调这个方法) + */ + List> getFlexibleTimeStats(FlexibleTimeQueryDTO query); + + + // ==================== 按小时统计 ==================== + + /** + * 查询最近N小时数据(最常用) + * + * @param tableName 表名 + * @param timeColumn 时间字段名(如:collect_time, create_time, record_time) + * @param deviceColumn 设备字段名(如:device_code, device_id) + * @param deviceId 设备ID + * @param valueColumns 监测列列表 + * @param hours 查询多少小时(默认24) + */ + List> queryHourlyStats( + String tableName, + String timeColumn, + String deviceColumn, + String deviceId, + List valueColumns, + Integer hours + ); + + /** + * 查询指定时间范围的按小时统计数据 + */ + List> queryHourlyStatsByRange( + String tableName, + String timeColumn, + String deviceColumn, + String deviceId, + List valueColumns, + LocalDateTime startTime, + LocalDateTime endTime + ); + + + // ==================== 按天统计 ==================== + + /** + * 查询最近N天数据 + */ + List> queryDailyStats( + String tableName, + String timeColumn, + String deviceColumn, + String deviceId, + List valueColumns, + Integer days + ); + + /** + * 查询指定时间范围的按天统计数据 + */ + List> queryDailyStatsByRange( + String tableName, + String timeColumn, + String deviceColumn, + String deviceId, + List valueColumns, + LocalDateTime startTime, + LocalDateTime endTime + ); + + + // ==================== 按月统计 ==================== + + /** + * 查询最近N个月数据 + */ + List> queryMonthlyStats( + String tableName, + String timeColumn, + String deviceColumn, + String deviceId, + List valueColumns, + Integer months + ); + + /** + * 查询指定时间范围的按月统计数据 + */ + List> queryMonthlyStatsByRange( + String tableName, + String timeColumn, + String deviceColumn, + String deviceId, + List valueColumns, + LocalDateTime startTime, + LocalDateTime endTime + ); + + + // ==================== 按年统计 ==================== + + /** + * 查询最近N年数据 + */ + List> queryYearlyStats( + String tableName, + String timeColumn, + String deviceColumn, + String deviceId, + List valueColumns, + Integer years + ); + + /** + * 查询指定时间范围的按年统计数据 + */ + List> queryYearlyStatsByRange( + String tableName, + String timeColumn, + String deviceColumn, + String deviceId, + List valueColumns, + LocalDateTime startTime, + LocalDateTime endTime + ); + + + // ==================== 按分钟统计 ==================== + + /** + * 查询最近N分钟数据(实时监控用) + */ + List> queryMinuteStats( + String tableName, + String timeColumn, + String deviceColumn, + String deviceId, + List valueColumns, + Integer minutes + ); + + + // ==================== 自定义时间点 ==================== + + /** + * 使用自定义时间点数组查询(只查指定时间点的数据) + */ + List> queryStatsWithCustomPoints( + String tableName, + String timeColumn, + String deviceColumn, + String deviceId, + List valueColumns, + List timePoints + ); + + + // ==================== 多设备对比 ==================== + + /** + * 批量查询多个设备(对比用) + */ + Map>> queryMultiDeviceStats( + String tableName, + String timeColumn, + String deviceColumn, + List deviceIds, + List valueColumns, + FlexibleTimeQueryDTO.TimeDimension dimension, + Integer timeRange, + LocalDateTime startTime, + LocalDateTime endTime + ); + + + // ==================== 辅助方法 ==================== + + /** + * 自动识别表中的监测列(数值类型) + */ + List autoDetectColumns(String tableName); + + /** + * 获取表统计信息 + */ + Map getTableStatistics(String tableName); +} diff --git a/zh-module-applet/zh-applet-admin/src/main/java/org/jeecg/modules/appmana/service/impl/CommonServiceImpl.java b/zh-module-applet/zh-applet-admin/src/main/java/org/jeecg/modules/appmana/service/impl/CommonServiceImpl.java index b43a366..07af13c 100644 --- a/zh-module-applet/zh-applet-admin/src/main/java/org/jeecg/modules/appmana/service/impl/CommonServiceImpl.java +++ b/zh-module-applet/zh-applet-admin/src/main/java/org/jeecg/modules/appmana/service/impl/CommonServiceImpl.java @@ -1,20 +1,24 @@ package org.jeecg.modules.appmana.service.impl; +import cn.hutool.core.lang.Assert; import com.alibaba.fastjson.JSONObject; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import lombok.extern.slf4j.Slf4j; import org.apache.commons.beanutils.BeanUtils; import org.apache.commons.lang3.StringUtils; import org.jeecg.common.config.TenantContext; -import org.jeecg.common.constant.CommonConstants; -import org.jeecg.common.constant.DeviceReadConstants; -import org.jeecg.common.constant.PollutionConstants; +import org.jeecg.common.constant.*; import org.jeecg.common.constant.enums.PollutionEnum; import org.jeecg.common.dto.CommonDTO; +import org.jeecg.common.dto.SurvSummaryDTO; import org.jeecg.common.entity.*; +import org.jeecg.common.util.*; import org.jeecg.common.vo.VOHisResult; +import org.jeecg.common.vo.iot.common.SurvItemInfo; import org.jeecg.common.vo.statistic.AlertReport; +import org.jeecg.common.vo.statistic.AlertSummaryVo; import org.jeecg.common.vo.statistic.ScreenIndexSummaryDetailVo; +import org.jeecg.common.vo.statistic.SurvSummaryVo; import org.jeecg.modules.appmana.service.*; import org.jeecg.modules.appmana.utils.HttpServletRequestUtil; import org.jeecg.modules.appmana.utils.MonitoringDataRichTextBuilder; @@ -29,6 +33,7 @@ import java.time.LocalDateTime; import java.time.LocalTime; import java.time.format.DateTimeFormatter; import java.util.*; +import java.util.stream.Collectors; @Service @Slf4j @@ -47,6 +52,10 @@ public class CommonServiceImpl { @Lazy private SurvConfigServiceImpl configService; + @Autowired + @Lazy + private SurvDeviceDeployServiceImpl deviceDeployService; + @Autowired @Lazy private ScEquZhibiaoServiceImpl zhibiaoService; @@ -55,6 +64,9 @@ public class CommonServiceImpl { @Lazy private ISurvAlertRecordService alertRecordService; + @Autowired + private FlexibleMonitorService flexibleMonitorService; + /** * 获取监测设备的数据 */ @@ -458,4 +470,302 @@ public class CommonServiceImpl { return MonitoringDataRichTextBuilder.buildRichText(stats); } + + public SurvSummaryVo getSurvSummary(SurvSummaryDTO survSummaryDTO) { + + SurvSummaryVo survSummaryVo = new SurvSummaryVo(); + Map itemInfo = new HashMap<>(); + List itemList = new ArrayList<>(); + List timeList = new ArrayList<>(); + Map> dataMap = new HashMap<>(); + + if(StringUtils.isNotBlank(survSummaryDTO.getStationCode())){ + //获取站点下的所有设备 + List deployTypes = new ArrayList<>(); + deployTypes.add(PollutionConstants.SOIL_SURV); + deployTypes.add(PollutionConstants.AIR_SURV); + deployTypes.add(PollutionConstants.WATER_QULITY); + deployTypes.add(PollutionConstants.STINK); + deployTypes.add(PollutionConstants.WATER_LIVE); + deployTypes.add(PollutionConstants.WATER_ORIENT); + + List deviceList = deviceDeployService.getDeviceListByStation(survSummaryDTO.getStationCode(),deployTypes); + if(deviceList!=null && !deviceList.isEmpty()){ + for (SurvDeviceDeploy deploy : deviceList) { + survSummaryDTO.setDeployId(deploy.getId()); + SurvSummaryVo temp = getDeviceSummary(deploy,survSummaryDTO.getSummaryMode(),survSummaryDTO.getStartTime(),survSummaryDTO.getEndTime()); + itemInfo.putAll(temp.getItemInfo()); + itemList.addAll(temp.getItemList()); + dataMap.putAll(temp.getDataMap()); + + timeList = temp.getTimeList(); + } + survSummaryVo.setItemInfo(itemInfo); + survSummaryVo.setItemList(itemList); + survSummaryVo.setDataMap(dataMap); + survSummaryVo.setTimeList(timeList); + } + + }else{ + SurvDeviceDeploy deploy = deviceDeployService.getById(survSummaryDTO.getDeployId()); + Assert.notNull(deploy,"设备不存在"); + survSummaryVo = getDeviceSummary(deploy,survSummaryDTO.getSummaryMode(),survSummaryDTO.getStartTime(),survSummaryDTO.getEndTime()); + } + return survSummaryVo; + } + + + + + + + public SurvSummaryVo getDeviceSummary(SurvDeviceDeploy deploy,String summaryMode,String startTime,String endTime) { + SurvSummaryVo survSummaryVo = new SurvSummaryVo(); + String tableName = ""; + String timeColumn = ""; + String deviceIdColumn = ""; + String timeFormat = ""; + LocalDateTime startDate = null; + LocalDateTime endDate = null; + + + + + if(StringUtils.isBlank(summaryMode)){ + summaryMode = IotConstants.month_days;//默认月查询 + } + + + + + + List zhibiaoList = zhibiaoService.getListByEquid(deploy.getId()); + Map zhibiaoMap = new HashMap<>(); + List zhibiaoKeys = new ArrayList<>(); + List survItemInfos = new ArrayList<>(); + + if (zhibiaoList!=null && !zhibiaoList.isEmpty()){ + zhibiaoList.forEach(zhibiao ->{ + zhibiaoMap.put(zhibiao.getEntityField(),zhibiao); + zhibiaoKeys.add(zhibiao.getEntityField()); + + SurvItemInfo survItemInfo = new SurvItemInfo(); + PollutionEnum pollutionEnum = PollutionEnum.catchPollution(zhibiao.getEntityField()); + survItemInfo.setItemName(zhibiao.getName()); + survItemInfo.setColor(pollutionEnum.getColor()); + survItemInfo.setHighVal(zhibiao.getValHeight()!=null ?zhibiao.getValHeight()+"":null); + survItemInfo.setLowVal(zhibiao.getValLow()!=null?zhibiao.getValLow()+"":null); + survItemInfo.setUnit(zhibiao.getNuit()); + survItemInfo.setEntity(zhibiao.getEntityField()); + survItemInfo.setPid(zhibiao.getEleKey()); + survItemInfos.add(survItemInfo); + + }); + } + + Map itemMap = survItemInfos.stream() + .collect(Collectors.toMap(SurvItemInfo::getEntity, itemss -> itemss)); + survSummaryVo.setItemList(survItemInfos); + survSummaryVo.setItemInfo(itemMap); + + switch (deploy.getDeployType()){ + case PollutionConstants.SOIL_SURV: + case PollutionConstants.WATER_QULITY: + tableName = "surv_hisdata_soil"; + timeColumn = "DATA_DATE_TIME"; + deviceIdColumn = "DEPLOY_ID"; + break; + case PollutionConstants.AIR_SURV: + tableName = "surv_hisdata_air"; + timeColumn = "DATA_DATE_TIME"; + deviceIdColumn = "DEPLOY_ID"; + break; + case PollutionConstants.STINK: + tableName = "surv_hisdata_vocs"; + timeColumn = "DATA_DATE_TIME"; + deviceIdColumn = "DEPLOY_ID"; + break; + case PollutionConstants.WATER_LIVE: + tableName = "surv_hisdata_livestockwater"; + timeColumn = "DATA_DATE_TIME"; + deviceIdColumn = "DEVICE_ID"; + break; + case PollutionConstants.WATER_ORIENT: + tableName = "surv_hisdata_orientwater"; + timeColumn = "DATA_DATE_TIME"; + deviceIdColumn = "DEVICE_ID"; + break; + default: + break; + } + + List dbFields = NamingConverter.camelsToDbFields(zhibiaoKeys); + + List> result = new ArrayList<>(); + + + if(IotConstants.day_hours.equals(summaryMode)){ + boolean isNow = true; + timeFormat = "d日H时"; + if(StringUtils.isNotBlank(startTime)){ + isNow = false; + startDate = DateParseUtils.parseToLocalDateTime(startTime+" 00:00:00","yyyy-MM-dd HH:mm:ss"); + } + if(StringUtils.isNotBlank(endTime)){ + endDate = DateParseUtils.parseToLocalDateTime(endTime+" 23:59:59","yyyy-MM-dd HH:mm:ss").withNano(999999000); + } + if(isNow){ + result = flexibleMonitorService.queryHourlyStats( + tableName, // 表名 + timeColumn, // 时间字段 + deviceIdColumn, // 设备字段 + deploy.getId(), // 设备ID + dbFields, // 监测列 + 24 + ); + }else{ + result = flexibleMonitorService.queryHourlyStatsByRange( + tableName, // 表名 + timeColumn, // 时间字段 + deviceIdColumn, // 设备字段 + deploy.getId(), // 设备ID + dbFields, // 监测列 + startDate, + endDate + ); + } + + } + + else if(IotConstants.custom_days.equals(summaryMode)){ + boolean isNow = true; + timeFormat = "M月d日"; + if(StringUtils.isNotBlank(startTime)){ + isNow = false; + startDate = DateParseUtils.parseToLocalDateTime(startTime+" 00:00:00","yyyy-MM-dd HH:mm:ss"); + } + if(StringUtils.isNotBlank(endTime)){ + endDate = DateParseUtils.parseToLocalDateTime(endTime+" 23:59:59","yyyy-MM-dd HH:mm:ss"); + } + + if(isNow){ + result = flexibleMonitorService.queryDailyStats( + tableName, // 表名 + timeColumn, // 时间字段 + deviceIdColumn, // 设备字段 + deploy.getId(), // 设备ID + dbFields, // 监测列 + 30 + ); + }else{ + result = flexibleMonitorService.queryDailyStatsByRange( + tableName, // 表名 + timeColumn, // 时间字段 + deviceIdColumn, // 设备字段 + deploy.getId(), // 设备ID + dbFields, // 监测列 + startDate, + endDate + ); + } + } + + else if(IotConstants.month_days.equals(summaryMode)){ + boolean isNow = true; + timeFormat = "M月d日"; + if(StringUtils.isNotBlank(startTime)){ + isNow = false; + startDate = DateParseUtils.parseToLocalDateTime(startTime+"-01 00:00:00","yyyy-MM-dd HH:mm:ss"); + } + if(StringUtils.isNotBlank(endTime)){ + endDate = DateParseUtils.parseToLocalDateTime(endTime+"-01 23:59:59","yyyy-MM-dd HH:mm:ss"); + endDate = DateTimeRangeUtil.getMonthEnd(endDate).withNano(999999000); + } + + if(isNow){ + result = flexibleMonitorService.queryDailyStats( + tableName, // 表名 + timeColumn, // 时间字段 + deviceIdColumn, // 设备字段 + deploy.getId(), // 设备ID + dbFields, // 监测列 + 30 + ); + }else{ + result = flexibleMonitorService.queryDailyStatsByRange( + tableName, // 表名 + timeColumn, // 时间字段 + deviceIdColumn, // 设备字段 + deploy.getId(), // 设备ID + dbFields, // 监测列 + startDate, + endDate + ); + } + } + + else if(IotConstants.year_months.equals(summaryMode)){ + boolean isNow = true; + timeFormat = "yy年M月"; + if(StringUtils.isNotBlank(startTime)){ + isNow = false; + startDate = DateParseUtils.parseToLocalDateTime(startTime+"-01-01 00:00:00","yyyy-MM-dd HH:mm:ss"); + } + if(StringUtils.isNotBlank(endTime)){ + endDate = DateParseUtils.parseToLocalDateTime(endTime+"-12-31 23:59:59","yyyy-MM-dd HH:mm:ss"); + endDate = DateTimeRangeUtil.getYearEnd(endDate).withNano(999999000); + } + if(isNow){ + result = flexibleMonitorService.queryMonthlyStats( + tableName, // 表名 + timeColumn, // 时间字段 + deviceIdColumn, // 设备字段 + deploy.getId(), // 设备ID + dbFields, // 监测列 + 3 + ); + }else{ + result = flexibleMonitorService.queryMonthlyStatsByRange( + tableName, // 表名 + timeColumn, // 时间字段 + deviceIdColumn, // 设备字段 + deploy.getId(), // 设备ID + dbFields, // 监测列 + startDate, + endDate + ); + } + + } + + + + + List timeList = new ArrayList<>(); + Map> dataMap = new HashMap<>(); + if(result!=null && !result.isEmpty()){ + + for (Map item : result) { + String timePoint = item.get(StatisticConstants.RESULT_TIME_COLUMN).toString(); + timeList.add(timePoint); + for (String db : dbFields) { + String dbKey = db+StatisticConstants.RESULT_VALUE_MAX; + String value = item.get(dbKey).toString(); + String zhibiaoKey = NamingConverter.dbFieldToCamel(dbKey.replace(StatisticConstants.RESULT_VALUE_MAX,"")); + List zhibiaoData = dataMap.get(zhibiaoKey); + if(zhibiaoData==null){ + zhibiaoData = new ArrayList<>(); + } + zhibiaoData.add(value); + dataMap.put(zhibiaoKey,zhibiaoData); + } + } + } + timeList = DateRangeUtil.convertDateFormatStream(timeList,"yyyy-MM-dd HH:mm:ss",timeFormat); + survSummaryVo.setTimeList(timeList); + survSummaryVo.setDataMap(dataMap); + + return survSummaryVo; + } + } diff --git a/zh-module-applet/zh-applet-admin/src/main/java/org/jeecg/modules/appmana/service/impl/FlexibleMonitorServiceImpl.java b/zh-module-applet/zh-applet-admin/src/main/java/org/jeecg/modules/appmana/service/impl/FlexibleMonitorServiceImpl.java new file mode 100644 index 0000000..eaa7f52 --- /dev/null +++ b/zh-module-applet/zh-applet-admin/src/main/java/org/jeecg/modules/appmana/service/impl/FlexibleMonitorServiceImpl.java @@ -0,0 +1,394 @@ +package org.jeecg.modules.appmana.service.impl; + +import lombok.extern.slf4j.Slf4j; +import org.jeecg.common.dto.statistic.FlexibleTimeQueryDTO; +import org.jeecg.modules.appmana.mapper.FlexibleMonitorMapper; +import org.jeecg.modules.appmana.service.FlexibleMonitorService; +import org.springframework.stereotype.Service; +import javax.annotation.Resource; +import java.time.LocalDateTime; +import java.util.*; + +@Slf4j +@Service +public class FlexibleMonitorServiceImpl implements FlexibleMonitorService { + + @Resource + private FlexibleMonitorMapper flexibleMonitorMapper; + + // ==================== 核心方法 ==================== + + @Override + public List> getFlexibleTimeStats(FlexibleTimeQueryDTO query) { + // 参数自动补全 + autoCompleteParams(query); + logQueryInfo(query); + + long start = System.currentTimeMillis(); + List> result = flexibleMonitorMapper.getFlexibleTimeStats(query); + log.info("查询完成,耗时: {}ms, 记录数: {}", System.currentTimeMillis() - start, result.size()); + + return result; + } + + + // ==================== 按小时统计 ==================== + + @Override + public List> queryHourlyStats( + String tableName, + String timeColumn, + String deviceColumn, + String deviceId, + List valueColumns, + Integer hours) { + + if (hours == null) hours = 24; + + LocalDateTime endTime = LocalDateTime.now(); + LocalDateTime startTime = endTime.minusHours(hours); + + return queryHourlyStatsByRange(tableName, timeColumn, deviceColumn, deviceId, valueColumns, startTime, endTime); + } + + @Override + public List> queryHourlyStatsByRange( + String tableName, + String timeColumn, + String deviceColumn, + String deviceId, + List valueColumns, + LocalDateTime startTime, + LocalDateTime endTime) { + + FlexibleTimeQueryDTO query = buildQuery( + tableName, timeColumn, deviceColumn, deviceId, valueColumns, + FlexibleTimeQueryDTO.TimeDimension.HOUR, startTime, endTime + ); + return getFlexibleTimeStats(query); + } + + + // ==================== 按天统计 ==================== + + @Override + public List> queryDailyStats( + String tableName, + String timeColumn, + String deviceColumn, + String deviceId, + List valueColumns, + Integer days) { + + if (days == null) days = 30; + + LocalDateTime endTime = LocalDateTime.now(); + LocalDateTime startTime = endTime.minusDays(days); + + return queryDailyStatsByRange(tableName, timeColumn, deviceColumn, deviceId, valueColumns, startTime, endTime); + } + + @Override + public List> queryDailyStatsByRange( + String tableName, + String timeColumn, + String deviceColumn, + String deviceId, + List valueColumns, + LocalDateTime startTime, + LocalDateTime endTime) { + + FlexibleTimeQueryDTO query = buildQuery( + tableName, timeColumn, deviceColumn, deviceId, valueColumns, + FlexibleTimeQueryDTO.TimeDimension.DAY, startTime, endTime + ); + return getFlexibleTimeStats(query); + } + + + // ==================== 按月统计 ==================== + + @Override + public List> queryMonthlyStats( + String tableName, + String timeColumn, + String deviceColumn, + String deviceId, + List valueColumns, + Integer months) { + + if (months == null) months = 12; + + LocalDateTime endTime = LocalDateTime.now(); + LocalDateTime startTime = endTime.minusMonths(months); + + return queryMonthlyStatsByRange(tableName, timeColumn, deviceColumn, deviceId, valueColumns, startTime, endTime); + } + + @Override + public List> queryMonthlyStatsByRange( + String tableName, + String timeColumn, + String deviceColumn, + String deviceId, + List valueColumns, + LocalDateTime startTime, + LocalDateTime endTime) { + + FlexibleTimeQueryDTO query = buildQuery( + tableName, timeColumn, deviceColumn, deviceId, valueColumns, + FlexibleTimeQueryDTO.TimeDimension.MONTH, startTime, endTime + ); + return getFlexibleTimeStats(query); + } + + + // ==================== 按年统计 ==================== + + @Override + public List> queryYearlyStats( + String tableName, + String timeColumn, + String deviceColumn, + String deviceId, + List valueColumns, + Integer years) { + + if (years == null) years = 5; + + LocalDateTime endTime = LocalDateTime.now(); + LocalDateTime startTime = endTime.minusYears(years); + + return queryYearlyStatsByRange(tableName, timeColumn, deviceColumn, deviceId, valueColumns, startTime, endTime); + } + + @Override + public List> queryYearlyStatsByRange( + String tableName, + String timeColumn, + String deviceColumn, + String deviceId, + List valueColumns, + LocalDateTime startTime, + LocalDateTime endTime) { + + FlexibleTimeQueryDTO query = buildQuery( + tableName, timeColumn, deviceColumn, deviceId, valueColumns, + FlexibleTimeQueryDTO.TimeDimension.YEAR, startTime, endTime + ); + return getFlexibleTimeStats(query); + } + + + // ==================== 按分钟统计 ==================== + + @Override + public List> queryMinuteStats( + String tableName, + String timeColumn, + String deviceColumn, + String deviceId, + List valueColumns, + Integer minutes) { + + if (minutes == null) minutes = 60; + + LocalDateTime endTime = LocalDateTime.now(); + LocalDateTime startTime = endTime.minusMinutes(minutes); + + FlexibleTimeQueryDTO query = buildQuery( + tableName, timeColumn, deviceColumn, deviceId, valueColumns, + FlexibleTimeQueryDTO.TimeDimension.MINUTE, startTime, endTime + ); + return getFlexibleTimeStats(query); + } + + + // ==================== 自定义时间点 ==================== + + @Override + public List> queryStatsWithCustomPoints( + String tableName, + String timeColumn, + String deviceColumn, + String deviceId, + List valueColumns, + List timePoints) { + + if (timePoints == null || timePoints.isEmpty()) { + throw new IllegalArgumentException("时间点数组不能为空"); + } + + FlexibleTimeQueryDTO query = new FlexibleTimeQueryDTO(); + query.setTableName(tableName); + query.setTimeColumn(timeColumn); + query.setDeviceColumn(deviceColumn); + query.setDeviceId(deviceId); + query.setValueColumns(valueColumns); + query.setDimension(FlexibleTimeQueryDTO.TimeDimension.HOUR); + query.setCustomTimePoints(timePoints); + query.setFillEmpty(true); + query.setEmptyValue(0); + + return getFlexibleTimeStats(query); + } + + + // ==================== 多设备对比 ==================== + + @Override + public Map>> queryMultiDeviceStats( + String tableName, + String timeColumn, + String deviceColumn, + List deviceIds, + List valueColumns, + FlexibleTimeQueryDTO.TimeDimension dimension, + Integer timeRange, + LocalDateTime startTime, + LocalDateTime endTime) { + + if (deviceIds == null || deviceIds.isEmpty()) { + throw new IllegalArgumentException("设备ID列表不能为空"); + } + + // 如果没有指定时间范围,根据维度自动计算 + if (startTime == null || endTime == null) { + endTime = LocalDateTime.now(); + if (timeRange == null) timeRange = getDefaultTimeRange(dimension); + switch (dimension) { + case MINUTE: + startTime = endTime.minusMinutes(timeRange); + break; + case HOUR: + startTime = endTime.minusHours(timeRange); + break; + case DAY: + startTime = endTime.minusDays(timeRange); + break; + case MONTH: + startTime = endTime.minusMonths(timeRange); + break; + case YEAR: + startTime = endTime.minusYears(timeRange); + break; + default: + startTime = endTime.minusHours(24); + } + } + + Map>> resultMap = new LinkedHashMap<>(); + + for (String deviceId : deviceIds) { + FlexibleTimeQueryDTO query = buildQuery( + tableName, timeColumn, deviceColumn, deviceId, valueColumns, + dimension, startTime, endTime + ); + List> data = getFlexibleTimeStats(query); + resultMap.put(deviceId, data); + } + + return resultMap; + } + + + // ==================== 辅助方法 ==================== + + @Override + public List autoDetectColumns(String tableName) { + Integer count = flexibleMonitorMapper.checkTableExists(tableName); + if (count == null || count == 0) { + throw new IllegalArgumentException("表不存在: " + tableName); + } + + List> columns = flexibleMonitorMapper.getTableColumns(tableName); + + List excludeColumns = Arrays.asList( + "id", "create_time", "update_time", "delete_flag", "version", + "collect_time", "record_time", "device_id", "device_code", + "tenant_id", "org_id", "status", "sort", "remark", + "is_deleted", "creator", "modifier", "create_by", "update_by" + ); + + return columns.stream() + .map(col -> col.get("columnName")) + .filter(col -> !excludeColumns.contains(col.toLowerCase())) + .sorted() + .collect(java.util.stream.Collectors.toList()); + } + + @Override + public Map getTableStatistics(String tableName) { + return flexibleMonitorMapper.getStatistics(tableName); + } + + + // ==================== 私有方法 ==================== + + /** + * 构建查询对象 + */ + private FlexibleTimeQueryDTO buildQuery( + String tableName, + String timeColumn, + String deviceColumn, + String deviceId, + List valueColumns, + FlexibleTimeQueryDTO.TimeDimension dimension, + LocalDateTime startTime, + LocalDateTime endTime) { + + FlexibleTimeQueryDTO query = new FlexibleTimeQueryDTO(); + query.setTableName(tableName); + query.setTimeColumn(timeColumn); + query.setDeviceColumn(deviceColumn); + query.setDeviceId(deviceId); + query.setValueColumns(valueColumns); + query.setDimension(dimension); + query.setStartTime(startTime); + query.setEndTime(endTime); + query.setFillEmpty(true); + query.setEmptyValue(0); + query.setAvgScale(2); + + return query; + } + + /** + * 根据维度获取默认时间范围 + */ + private int getDefaultTimeRange(FlexibleTimeQueryDTO.TimeDimension dimension) { + switch (dimension) { + case MINUTE: return 60; + case HOUR: return 24; + case DAY: return 30; + case MONTH: return 12; + case YEAR: return 5; + default: return 24; + } + } + + /** + * 自动补全参数 + */ + private void autoCompleteParams(FlexibleTimeQueryDTO query) { + if (query.getFillEmpty() == null) query.setFillEmpty(true); + if (query.getEmptyValue() == null) query.setEmptyValue(0); + if (query.getTimeColumn() == null) query.setTimeColumn("DATA_DATE_TIME"); + if (query.getAvgScale() == null) query.setAvgScale(2); + if (query.getDeviceColumn() == null && query.getDeviceId() != null) { + query.setDeviceColumn("DEPLOY_ID"); + } + } + + /** + * 记录日志 + */ + private void logQueryInfo(FlexibleTimeQueryDTO query) { + log.info("=== 灵活时间统计查询 ==="); + log.info("表: {}, 时间字段: {}, 设备: {}", + query.getTableName(), query.getTimeColumn(), query.getDeviceId()); + log.info("维度: {}, 监测列: {}, 小数位数: {}", + query.getDimension(), query.getValueColumns(), query.getAvgScale()); + } +} \ No newline at end of file diff --git a/zh-module-applet/zh-applet-api/src/main/java/org/jeecg/system/applet/client/ZhAdminFeignClient.java b/zh-module-applet/zh-applet-api/src/main/java/org/jeecg/system/applet/client/ZhAdminFeignClient.java index 41c7c39..0ecc7b1 100644 --- a/zh-module-applet/zh-applet-api/src/main/java/org/jeecg/system/applet/client/ZhAdminFeignClient.java +++ b/zh-module-applet/zh-applet-api/src/main/java/org/jeecg/system/applet/client/ZhAdminFeignClient.java @@ -1,6 +1,7 @@ package org.jeecg.system.applet.client; import com.alibaba.fastjson.JSONArray; +import org.jeecg.common.dto.SurvSummaryDTO; import org.jeecg.common.entity.PollutionSetVo; import org.jeecg.common.iot.common.VOIntegrateStatistic; import org.jeecg.common.iot.common.VOSurvIntegrateParam; @@ -11,6 +12,7 @@ import org.jeecg.common.vo.VOBigScreenSurvQ; import org.jeecg.common.vo.VOHisResult; import org.jeecg.common.vo.inner.*; import org.jeecg.common.vo.iot.common.Result; +import org.jeecg.common.vo.statistic.SurvSummaryVo; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -93,4 +95,11 @@ public interface ZhAdminFeignClient { */ @PostMapping("/appmana/inner/api/constructStink") List constructStink(@RequestBody ConstructStink constructStink); + + + /** + * 站点设备多项统计图 + */ + @PostMapping("/appmana/inner/api/stationDeviceSummary") + SurvSummaryVo stationDeviceSummary(@RequestBody SurvSummaryDTO survSummaryDTO); } diff --git a/zh-module-applet/zh-applet-api/src/main/java/org/jeecg/system/applet/controller/SurvAlertRecordController.java b/zh-module-applet/zh-applet-api/src/main/java/org/jeecg/system/applet/controller/SurvAlertRecordController.java index 7211e99..d19a529 100644 --- a/zh-module-applet/zh-applet-api/src/main/java/org/jeecg/system/applet/controller/SurvAlertRecordController.java +++ b/zh-module-applet/zh-applet-api/src/main/java/org/jeecg/system/applet/controller/SurvAlertRecordController.java @@ -3,9 +3,12 @@ package org.jeecg.system.applet.controller; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; +import org.jeecg.common.dto.SurvSummaryDTO; import org.jeecg.common.util.R; import org.jeecg.common.vo.statistic.AlertSummaryVo; +import org.jeecg.common.vo.statistic.SurvSummaryVo; import org.jeecg.system.applet.annotation.ApiLogin; +import org.jeecg.system.applet.client.ZhAdminFeignClient; import org.jeecg.system.applet.dto.AlertSummaryDTO; import org.jeecg.system.applet.service.ISurvAlertRecordService; import org.springframework.beans.factory.annotation.Autowired; @@ -28,6 +31,21 @@ import org.springframework.web.bind.annotation.RestController; public class SurvAlertRecordController { @Autowired private ISurvAlertRecordService survAlertRecordService; + @Autowired + private ZhAdminFeignClient zhAdminFeignClient; + + /** + * 站点设备 + * + * @return + */ + @ApiOperation(value="预警曲线", notes="预警曲线") + @PostMapping(value = "/alertSummary") + @ApiLogin + public R alertSummary(@RequestBody SurvSummaryDTO survSummaryDTO) { + SurvSummaryVo survSummaryVo = zhAdminFeignClient.stationDeviceSummary(survSummaryDTO); + return R.ok(survSummaryVo); + } /** * 监控摄像头分页列表查询 @@ -35,9 +53,9 @@ public class SurvAlertRecordController { * @return */ @ApiOperation(value="预警曲线", notes="预警曲线") - @PostMapping(value = "/alertSummary") + @PostMapping(value = "/alertSummary2") @ApiLogin - public R alertSummary(@RequestBody AlertSummaryDTO alertSummaryDTO) { + public R alertSummary2(@RequestBody AlertSummaryDTO alertSummaryDTO) { AlertSummaryVo alertSummaryVo = survAlertRecordService.getSummary(alertSummaryDTO); return R.ok(alertSummaryVo); } diff --git a/zh-module-applet/zh-applet-common/src/main/java/org/jeecg/common/constant/IotConstants.java b/zh-module-applet/zh-applet-common/src/main/java/org/jeecg/common/constant/IotConstants.java index dd070b7..ea34208 100644 --- a/zh-module-applet/zh-applet-common/src/main/java/org/jeecg/common/constant/IotConstants.java +++ b/zh-module-applet/zh-applet-common/src/main/java/org/jeecg/common/constant/IotConstants.java @@ -99,6 +99,11 @@ public interface IotConstants { */ String month_days = "monthDays"; + /** + * 1月日数据 + */ + String custom_days = "customDays"; + /** * 1年月数据 */ diff --git a/zh-module-applet/zh-applet-common/src/main/java/org/jeecg/common/constant/StatisticConstants.java b/zh-module-applet/zh-applet-common/src/main/java/org/jeecg/common/constant/StatisticConstants.java new file mode 100644 index 0000000..6e38291 --- /dev/null +++ b/zh-module-applet/zh-applet-common/src/main/java/org/jeecg/common/constant/StatisticConstants.java @@ -0,0 +1,24 @@ +package org.jeecg.common.constant; + +public interface StatisticConstants { + + /* + * 时间点列名 + */ + String RESULT_TIME_COLUMN = "time_point"; + + /* + * 最大值后缀 + */ + String RESULT_VALUE_MAX = "_max"; + + /* + * 最小值后缀 + */ + String RESULT_VALUE_MIN = "_min"; + + /* + * 平均值后缀 + */ + String RESULT_VALUE_AVG = "_avg"; +} diff --git a/zh-module-applet/zh-applet-common/src/main/java/org/jeecg/common/dto/SurvSummaryDTO.java b/zh-module-applet/zh-applet-common/src/main/java/org/jeecg/common/dto/SurvSummaryDTO.java new file mode 100644 index 0000000..241bb95 --- /dev/null +++ b/zh-module-applet/zh-applet-common/src/main/java/org/jeecg/common/dto/SurvSummaryDTO.java @@ -0,0 +1,28 @@ +package org.jeecg.common.dto; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * 监测统计曲线统一参数 + */ +@Data +public class SurvSummaryDTO { + @ApiModelProperty("站点编号") + private String stationCode; + + @ApiModelProperty("设备ID") + private String deployId; + + @ApiModelProperty("统计模式") + private String summaryMode; + + @ApiModelProperty("开始时间") + private String startTime; + + @ApiModelProperty("结束时间") + private String endTime; + + @ApiModelProperty("租户ID") + private String tenantId; +} diff --git a/zh-module-applet/zh-applet-common/src/main/java/org/jeecg/common/dto/statistic/FlexibleTimeQueryDTO.java b/zh-module-applet/zh-applet-common/src/main/java/org/jeecg/common/dto/statistic/FlexibleTimeQueryDTO.java new file mode 100644 index 0000000..b5bc1cf --- /dev/null +++ b/zh-module-applet/zh-applet-common/src/main/java/org/jeecg/common/dto/statistic/FlexibleTimeQueryDTO.java @@ -0,0 +1,66 @@ +package org.jeecg.common.dto.statistic; + + +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.Date; +import java.util.List; + +/** + * 灵活时间维度查询参数 + */ +@Data +public class FlexibleTimeQueryDTO { + + // ========== 基础配置 ========== + /** 表名 */ + private String tableName; + + /** 时间列名 */ + private String timeColumn = "DATA_DATE_TIME"; + + /** 设备列名 */ + private String deviceColumn; + + /** 设备ID */ + private String deviceId; + + /** 监测列列表 */ + private List valueColumns; + + // ========== 时间维度配置 ========== + /** 时间维度:HOUR, DAY, MONTH, YEAR, MINUTE, WEEK */ + private TimeDimension dimension; + + // ========== 方式一:时间范围模式 ========== + /** 开始时间 */ + private LocalDateTime startTime; + + /** 结束时间 */ + private LocalDateTime endTime; + + // ========== 方式二:自定义时间点数组模式 ========== + /** 自定义时间点数组 */ + private List customTimePoints; + + // ========== 其他配置 ========== + /** 是否填充空值,默认true */ + private Boolean fillEmpty = true; + + /** 空值填充内容,默认0 */ + private Object emptyValue = 0; + + /** 平均值保留小数位数,默认2 */ + private Integer avgScale = 2; + + // ========== 枚举:时间维度 ========== + public enum TimeDimension { + MINUTE, // 分钟 + HOUR, // 小时 + DAY, // 天 + WEEK, // 周 + MONTH, // 月 + YEAR // 年 + } +} \ No newline at end of file diff --git a/zh-module-applet/zh-applet-common/src/main/java/org/jeecg/common/dto/statistic/MultiDeviceQueryDTO.java b/zh-module-applet/zh-applet-common/src/main/java/org/jeecg/common/dto/statistic/MultiDeviceQueryDTO.java new file mode 100644 index 0000000..5b48547 --- /dev/null +++ b/zh-module-applet/zh-applet-common/src/main/java/org/jeecg/common/dto/statistic/MultiDeviceQueryDTO.java @@ -0,0 +1,36 @@ +package org.jeecg.common.dto.statistic; + + +import lombok.Data; +import java.util.Date; +import java.util.List; + +/** + * 多设备查询参数 + */ +@Data +public class MultiDeviceQueryDTO { + /** 表名 */ + private String tableName; + + /** 设备列名 */ + private String deviceColumn = "DEPLOY_ID"; + + /** 时间列名 */ + private String timeColumn = "DATA_DATE_TIME"; + + /** 设备ID列表 */ + private List deviceIds; + + /** 监测列列表 */ + private List valueColumns; + + /** 时间维度 */ + private FlexibleTimeQueryDTO.TimeDimension dimension = FlexibleTimeQueryDTO.TimeDimension.HOUR; + + /** 开始时间 */ + private Date startTime; + + /** 结束时间 */ + private Date endTime; +} \ No newline at end of file diff --git a/zh-module-applet/zh-applet-common/src/main/java/org/jeecg/common/dto/statistic/TimePointDTO.java b/zh-module-applet/zh-applet-common/src/main/java/org/jeecg/common/dto/statistic/TimePointDTO.java new file mode 100644 index 0000000..4867e03 --- /dev/null +++ b/zh-module-applet/zh-applet-common/src/main/java/org/jeecg/common/dto/statistic/TimePointDTO.java @@ -0,0 +1,31 @@ +package org.jeecg.common.dto.statistic; + + +import lombok.Data; +import java.util.Date; + +/** + * 时间点DTO + */ +@Data +public class TimePointDTO { + /** 时间 */ + private Date time; + + /** 格式化后的时间 */ + private String formattedTime; + + /** 时间戳 */ + private Long timestamp; + + /** 显示标签 */ + private String label; + + public TimePointDTO() {} + + public TimePointDTO(Date time, String formattedTime) { + this.time = time; + this.formattedTime = formattedTime; + this.timestamp = time != null ? time.getTime() : null; + } +} \ No newline at end of file diff --git a/zh-module-applet/zh-applet-common/src/main/java/org/jeecg/common/util/DateParseUtils.java b/zh-module-applet/zh-applet-common/src/main/java/org/jeecg/common/util/DateParseUtils.java new file mode 100644 index 0000000..65e11ec --- /dev/null +++ b/zh-module-applet/zh-applet-common/src/main/java/org/jeecg/common/util/DateParseUtils.java @@ -0,0 +1,440 @@ +package org.jeecg.common.util; + +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.Date; + +/** + * 日期时间工具类 + * 专注于日期时间的格式化和解析,所有异常内部处理 + * + * @author Your Name + * @date 2026-07-28 + */ +public class DateParseUtils { + + // 常用日期时间格式常量 + public static final String PATTERN_YMD = "yyyy-MM-dd"; + public static final String PATTERN_YMDHMS = "yyyy-MM-dd HH:mm:ss"; + public static final String PATTERN_YMDHMS_SSS = "yyyy-MM-dd HH:mm:ss.SSS"; + public static final String PATTERN_YMD_SLASH = "yyyy/MM/dd"; + public static final String PATTERN_YMDHMS_SLASH = "yyyy/MM/dd HH:mm:ss"; + public static final String PATTERN_YMD_CHINESE = "yyyy年MM月dd日"; + public static final String PATTERN_YMDHMS_CHINESE = "yyyy年MM月dd日 HH时mm分ss秒"; + public static final String PATTERN_YMD_NO_SEPARATOR = "yyyyMMdd"; + public static final String PATTERN_YMDHMS_NO_SEPARATOR = "yyyyMMddHHmmss"; + public static final String PATTERN_HMS = "HH:mm:ss"; + public static final String PATTERN_HM = "HH:mm"; + + // 默认时区(系统默认时区) + private static final ZoneId DEFAULT_ZONE = ZoneId.systemDefault(); + + // ======================== LocalDateTime 格式化 ======================== + + /** + * 格式化 LocalDateTime + * + * @param dateTime LocalDateTime 对象 + * @param pattern 日期时间格式 + * @return 格式化后的字符串,如果参数为null则返回空字符串 + */ + public static String formatLocalDateTime(LocalDateTime dateTime, String pattern) { + if (dateTime == null || pattern == null || pattern.isEmpty()) { + return ""; + } + try { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern); + return dateTime.format(formatter); + } catch (Exception e) { + return ""; + } + } + + /** + * 格式化 LocalDateTime(使用默认格式 yyyy-MM-dd HH:mm:ss) + * + * @param dateTime LocalDateTime 对象 + * @return 格式化后的字符串,如果参数为null则返回空字符串 + */ + public static String formatLocalDateTime(LocalDateTime dateTime) { + return formatLocalDateTime(dateTime, PATTERN_YMDHMS); + } + + // ======================== LocalDate 格式化 ======================== + + /** + * 格式化 LocalDate + * + * @param date LocalDate 对象 + * @param pattern 日期格式 + * @return 格式化后的字符串,如果参数为null则返回空字符串 + */ + public static String formatLocalDate(LocalDate date, String pattern) { + if (date == null || pattern == null || pattern.isEmpty()) { + return ""; + } + try { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern); + return date.format(formatter); + } catch (Exception e) { + return ""; + } + } + + /** + * 格式化 LocalDate(使用默认格式 yyyy-MM-dd) + * + * @param date LocalDate 对象 + * @return 格式化后的字符串,如果参数为null则返回空字符串 + */ + public static String formatLocalDate(LocalDate date) { + return formatLocalDate(date, PATTERN_YMD); + } + + // ======================== LocalTime 格式化 ======================== + + /** + * 格式化 LocalTime + * + * @param time LocalTime 对象 + * @param pattern 时间格式 + * @return 格式化后的字符串,如果参数为null则返回空字符串 + */ + public static String formatLocalTime(LocalTime time, String pattern) { + if (time == null || pattern == null || pattern.isEmpty()) { + return ""; + } + try { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern); + return time.format(formatter); + } catch (Exception e) { + return ""; + } + } + + /** + * 格式化 LocalTime(使用默认格式 HH:mm:ss) + * + * @param time LocalTime 对象 + * @return 格式化后的字符串,如果参数为null则返回空字符串 + */ + public static String formatLocalTime(LocalTime time) { + return formatLocalTime(time, PATTERN_HMS); + } + + // ======================== Date 格式化 ======================== + + /** + * 格式化 Date + * + * @param date Date 对象 + * @param pattern 日期时间格式 + * @return 格式化后的字符串,如果参数为null则返回空字符串 + */ + public static String formatDate(Date date, String pattern) { + if (date == null || pattern == null || pattern.isEmpty()) { + return ""; + } + try { + LocalDateTime dateTime = dateToLocalDateTime(date); + return formatLocalDateTime(dateTime, pattern); + } catch (Exception e) { + return ""; + } + } + + /** + * 格式化 Date(使用默认格式 yyyy-MM-dd HH:mm:ss) + * + * @param date Date 对象 + * @return 格式化后的字符串,如果参数为null则返回空字符串 + */ + public static String formatDate(Date date) { + return formatDate(date, PATTERN_YMDHMS); + } + + // ======================== 解析为 LocalDateTime ======================== + + /** + * 解析字符串为 LocalDateTime + * + * @param text 日期时间字符串 + * @param pattern 日期时间格式 + * @return LocalDateTime 对象,解析失败返回 null + */ + public static LocalDateTime parseToLocalDateTime(String text, String pattern) { + if (text == null || text.isEmpty() || pattern == null || pattern.isEmpty()) { + return null; + } + try { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern); + return LocalDateTime.parse(text, formatter); + } catch (DateTimeParseException e) { + return null; + } catch (Exception e) { + return null; + } + } + + /** + * 解析字符串为 LocalDateTime(支持多种格式尝试) + * + * @param text 日期时间字符串 + * @param patterns 多种日期时间格式 + * @return LocalDateTime 对象,所有格式都解析失败返回 null + */ + public static LocalDateTime parseToLocalDateTime(String text, String... patterns) { + if (text == null || text.isEmpty() || patterns == null || patterns.length == 0) { + return null; + } + for (String pattern : patterns) { + LocalDateTime result = parseToLocalDateTime(text, pattern); + if (result != null) { + return result; + } + } + return null; + } + + // ======================== 解析为 LocalDate ======================== + + /** + * 解析字符串为 LocalDate + * + * @param text 日期字符串 + * @param pattern 日期格式 + * @return LocalDate 对象,解析失败返回 null + */ + public static LocalDate parseToLocalDate(String text, String pattern) { + if (text == null || text.isEmpty() || pattern == null || pattern.isEmpty()) { + return null; + } + try { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern); + return LocalDate.parse(text, formatter); + } catch (DateTimeParseException e) { + return null; + } catch (Exception e) { + return null; + } + } + + /** + * 解析字符串为 LocalDate(支持多种格式尝试) + * + * @param text 日期字符串 + * @param patterns 多种日期格式 + * @return LocalDate 对象,所有格式都解析失败返回 null + */ + public static LocalDate parseToLocalDate(String text, String... patterns) { + if (text == null || text.isEmpty() || patterns == null || patterns.length == 0) { + return null; + } + for (String pattern : patterns) { + LocalDate result = parseToLocalDate(text, pattern); + if (result != null) { + return result; + } + } + return null; + } + + // ======================== 解析为 LocalTime ======================== + + /** + * 解析字符串为 LocalTime + * + * @param text 时间字符串 + * @param pattern 时间格式 + * @return LocalTime 对象,解析失败返回 null + */ + public static LocalTime parseToLocalTime(String text, String pattern) { + if (text == null || text.isEmpty() || pattern == null || pattern.isEmpty()) { + return null; + } + try { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern); + return LocalTime.parse(text, formatter); + } catch (DateTimeParseException e) { + return null; + } catch (Exception e) { + return null; + } + } + + /** + * 解析字符串为 LocalTime(支持多种格式尝试) + * + * @param text 时间字符串 + * @param patterns 多种时间格式 + * @return LocalTime 对象,所有格式都解析失败返回 null + */ + public static LocalTime parseToLocalTime(String text, String... patterns) { + if (text == null || text.isEmpty() || patterns == null || patterns.length == 0) { + return null; + } + for (String pattern : patterns) { + LocalTime result = parseToLocalTime(text, pattern); + if (result != null) { + return result; + } + } + return null; + } + + // ======================== 解析为 Date ======================== + + /** + * 解析字符串为 Date + * + * @param text 日期时间字符串 + * @param pattern 日期时间格式 + * @return Date 对象,解析失败返回 null + */ + public static Date parseToDate(String text, String pattern) { + if (text == null || text.isEmpty() || pattern == null || pattern.isEmpty()) { + return null; + } + LocalDateTime dateTime = parseToLocalDateTime(text, pattern); + if (dateTime == null) { + return null; + } + return localDateTimeToDate(dateTime); + } + + /** + * 解析字符串为 Date(支持多种格式尝试) + * + * @param text 日期时间字符串 + * @param patterns 多种日期时间格式 + * @return Date 对象,所有格式都解析失败返回 null + */ + public static Date parseToDate(String text, String... patterns) { + if (text == null || text.isEmpty() || patterns == null || patterns.length == 0) { + return null; + } + for (String pattern : patterns) { + Date result = parseToDate(text, pattern); + if (result != null) { + return result; + } + } + return null; + } + + // ======================== Date 和 LocalDateTime 互转 ======================== + + /** + * Date 转 LocalDateTime + * + * @param date Date 对象 + * @return LocalDateTime 对象,如果参数为null则返回null + */ + public static LocalDateTime dateToLocalDateTime(Date date) { + if (date == null) { + return null; + } + try { + return LocalDateTime.ofInstant(date.toInstant(), DEFAULT_ZONE); + } catch (Exception e) { + return null; + } + } + + /** + * LocalDateTime 转 Date + * + * @param dateTime LocalDateTime 对象 + * @return Date 对象,如果参数为null则返回null + */ + public static Date localDateTimeToDate(LocalDateTime dateTime) { + if (dateTime == null) { + return null; + } + try { + return Date.from(dateTime.atZone(DEFAULT_ZONE).toInstant()); + } catch (Exception e) { + return null; + } + } + + /** + * Date 转 LocalDate + * + * @param date Date 对象 + * @return LocalDate 对象,如果参数为null则返回null + */ + public static LocalDate dateToLocalDate(Date date) { + if (date == null) { + return null; + } + try { + return dateToLocalDateTime(date).toLocalDate(); + } catch (Exception e) { + return null; + } + } + + /** + * LocalDate 转 Date(时间部分为00:00:00) + * + * @param localDate LocalDate 对象 + * @return Date 对象,如果参数为null则返回null + */ + public static Date localDateToDate(LocalDate localDate) { + if (localDate == null) { + return null; + } + try { + return localDateTimeToDate(localDate.atStartOfDay()); + } catch (Exception e) { + return null; + } + } + + // ======================== 常用便捷方法 ======================== + + /** + * 获取当前时间的格式化字符串 + * + * @param pattern 日期时间格式 + * @return 格式化后的当前时间字符串 + */ + public static String getCurrentTime(String pattern) { + return formatLocalDateTime(LocalDateTime.now(), pattern); + } + + /** + * 获取当前时间的格式化字符串(默认格式 yyyy-MM-dd HH:mm:ss) + * + * @return 格式化后的当前时间字符串 + */ + public static String getCurrentTime() { + return getCurrentTime(PATTERN_YMDHMS); + } + + /** + * 获取当前日期的格式化字符串 + * + * @param pattern 日期格式 + * @return 格式化后的当前日期字符串 + */ + public static String getCurrentDate(String pattern) { + return formatLocalDate(LocalDate.now(), pattern); + } + + /** + * 获取当前日期的格式化字符串(默认格式 yyyy-MM-dd) + * + * @return 格式化后的当前日期字符串 + */ + public static String getCurrentDate() { + return getCurrentDate(PATTERN_YMD); + } +} \ No newline at end of file diff --git a/zh-module-applet/zh-applet-common/src/main/java/org/jeecg/common/util/NamingConverter.java b/zh-module-applet/zh-applet-common/src/main/java/org/jeecg/common/util/NamingConverter.java new file mode 100644 index 0000000..46b3417 --- /dev/null +++ b/zh-module-applet/zh-applet-common/src/main/java/org/jeecg/common/util/NamingConverter.java @@ -0,0 +1,271 @@ +package org.jeecg.common.util; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * 数据库字段与驼峰命名转换工具类 + * 支持下划线命名(snake_case)和驼峰命名(camelCase)之间的互相转换 + * + * @author Your Name + * @date 2026-07-28 + */ +public class NamingConverter { + + private static final Pattern CAMEL_PATTERN = Pattern.compile("[A-Z]"); + private static final Pattern UNDERSCORE_PATTERN = Pattern.compile("_([a-z])"); + + /** + * 将数据库字段名(下划线命名)转换为驼峰命名 + * 例如:user_name -> userName + * + * @param dbField 数据库字段名(下划线命名) + * @return 驼峰命名字符串 + */ + public static String dbFieldToCamel(String dbField) { + if (dbField == null || dbField.isEmpty()) { + return dbField; + } + + // 如果字段名全部大写,先转为小写 + String field = dbField.toLowerCase(); + + // 处理下划线后跟字母的情况 + Matcher matcher = UNDERSCORE_PATTERN.matcher(field); + StringBuffer result = new StringBuffer(); + + while (matcher.find()) { + String replacement = matcher.group(1).toUpperCase(); + matcher.appendReplacement(result, replacement); + } + matcher.appendTail(result); + + return result.toString(); + } + + /** + * 将数据库字段名(下划线命名)转换为驼峰命名(首字母大写) + * 例如:user_name -> UserName + * + * @param dbField 数据库字段名(下划线命名) + * @return 首字母大写的驼峰命名字符串 + */ + public static String dbFieldToPascal(String dbField) { + String camel = dbFieldToCamel(dbField); + if (camel == null || camel.isEmpty()) { + return camel; + } + return camel.substring(0, 1).toUpperCase() + camel.substring(1); + } + + /** + * 将驼峰命名转换为数据库字段名(下划线命名) + * 例如:userName -> user_name + * + * @param camelName 驼峰命名字符串 + * @return 数据库字段名(下划线命名) + */ + public static String camelToDbField(String camelName) { + if (camelName == null || camelName.isEmpty()) { + return camelName; + } + + // 在大写字母前添加下划线,然后转为小写 + Matcher matcher = CAMEL_PATTERN.matcher(camelName); + StringBuffer result = new StringBuffer(); + + while (matcher.find()) { + String replacement = "_" + matcher.group().toLowerCase(); + matcher.appendReplacement(result, replacement); + } + matcher.appendTail(result); + + // 如果以大写字母开头,会多一个下划线,需要去掉 + String dbField = result.toString(); + if (dbField.startsWith("_")) { + dbField = dbField.substring(1); + } + + return dbField; + } + + /** + * 将驼峰命名转换为数据库字段名(下划线命名,全部大写) + * 例如:userName -> USER_NAME + * + * @param camelName 驼峰命名字符串 + * @return 大写下划线命名字符串 + */ + public static String camelToDbFieldUpper(String camelName) { + return camelToDbField(camelName).toUpperCase(); + } + + // ======================== 批量转换方法(数组) ======================== + + /** + * 批量转换:将下划线命名的数组转换为驼峰命名数组 + * + * @param dbFields 数据库字段名数组 + * @return 驼峰命名字符串数组 + */ + public static String[] dbFieldsToCamels(String[] dbFields) { + if (dbFields == null) { + return null; + } + String[] result = new String[dbFields.length]; + for (int i = 0; i < dbFields.length; i++) { + result[i] = dbFieldToCamel(dbFields[i]); + } + return result; + } + + /** + * 批量转换:将驼峰命名的数组转换为下划线命名数组 + * + * @param camelNames 驼峰命名字符串数组 + * @return 数据库字段名数组 + */ + public static String[] camelsToDbFields(String[] camelNames) { + if (camelNames == null) { + return null; + } + String[] result = new String[camelNames.length]; + for (int i = 0; i < camelNames.length; i++) { + result[i] = camelToDbField(camelNames[i]); + } + return result; + } + + // ======================== 批量转换方法(List) ======================== + + /** + * 批量转换:将下划线命名的List转换为驼峰命名List + * + * @param dbFields 数据库字段名List + * @return 驼峰命名字符串List + */ + public static List dbFieldsToCamels(List dbFields) { + if (dbFields == null) { + return null; + } + List result = new ArrayList<>(dbFields.size()); + for (String dbField : dbFields) { + result.add(dbFieldToCamel(dbField)); + } + return result; + } + + /** + * 批量转换:将驼峰命名的List转换为下划线命名List + * + * @param camelNames 驼峰命名字符串List + * @return 数据库字段名List + */ + public static List camelsToDbFields(List camelNames) { + if (camelNames == null) { + return null; + } + List result = new ArrayList<>(camelNames.size()); + for (String camelName : camelNames) { + result.add(camelToDbField(camelName)); + } + return result; + } + + /** + * 批量转换:将下划线命名的List转换为驼峰命名List(首字母大写) + * + * @param dbFields 数据库字段名List + * @return 帕斯卡命名字符串List + */ + public static List dbFieldsToPascals(List dbFields) { + if (dbFields == null) { + return null; + } + List result = new ArrayList<>(dbFields.size()); + for (String dbField : dbFields) { + result.add(dbFieldToPascal(dbField)); + } + return result; + } + + /** + * 批量转换:将驼峰命名的List转换为大写下划线命名List + * + * @param camelNames 驼峰命名字符串List + * @return 大写下划线命名字符串List + */ + public static List camelsToDbFieldsUpper(List camelNames) { + if (camelNames == null) { + return null; + } + List result = new ArrayList<>(camelNames.size()); + for (String camelName : camelNames) { + result.add(camelToDbFieldUpper(camelName)); + } + return result; + } + + /** + * 在原有的List上直接进行转换(修改原List) + * 将下划线命名的List转换为驼峰命名 + * + * @param dbFields 数据库字段名List(会被修改) + * @return 转换后的List(原List的引用) + */ + public static List convertDbFieldsToCamelsInPlace(List dbFields) { + if (dbFields == null) { + return null; + } + for (int i = 0; i < dbFields.size(); i++) { + dbFields.set(i, dbFieldToCamel(dbFields.get(i))); + } + return dbFields; + } + + /** + * 在原有的List上直接进行转换(修改原List) + * 将驼峰命名的List转换为下划线命名 + * + * @param camelNames 驼峰命名字符串List(会被修改) + * @return 转换后的List(原List的引用) + */ + public static List convertCamelsToDbFieldsInPlace(List camelNames) { + if (camelNames == null) { + return null; + } + for (int i = 0; i < camelNames.size(); i++) { + camelNames.set(i, camelToDbField(camelNames.get(i))); + } + return camelNames; + } + + // ======================== 格式判断方法 ======================== + + /** + * 判断字符串是否为下划线命名格式 + * + * @param str 要检查的字符串 + * @return true表示是下划线命名格式 + */ + public static boolean isUnderScoreFormat(String str) { + if (str == null || str.isEmpty()) { + return false; + } + return str.contains("_"); + } + + /** + * 判断字符串是否为驼峰命名格式 + * + * @param str 要检查的字符串 + * @return true表示是驼峰命名格式 + */ + public static boolean isCamelFormat(String str) { + if (str == null || str.isEmpty()) { + return false; + } + return !str.contains("_") && !str.equals(str.toLowerCase()); + } +} \ No newline at end of file diff --git a/zh-module-applet/zh-applet-common/src/main/java/org/jeecg/common/vo/statistic/SurvSummaryVo.java b/zh-module-applet/zh-applet-common/src/main/java/org/jeecg/common/vo/statistic/SurvSummaryVo.java new file mode 100644 index 0000000..15e42f7 --- /dev/null +++ b/zh-module-applet/zh-applet-common/src/main/java/org/jeecg/common/vo/statistic/SurvSummaryVo.java @@ -0,0 +1,24 @@ +package org.jeecg.common.vo.statistic; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import org.jeecg.common.vo.iot.common.SurvItemInfo; + +import java.util.List; +import java.util.Map; + +/** + * + * 监测统计曲线统一格式 + */ +@Data +public class SurvSummaryVo { + @ApiModelProperty("监测项信息") + private Map itemInfo; + @ApiModelProperty("监测项列表") + private List itemList; + @ApiModelProperty("X轴数据") + private List timeList; + @ApiModelProperty("Y轴数据") + private Map> dataMap; +} diff --git a/zh-module-applet/zh-applet-common/src/main/java/org/jeecg/common/vo/statistic/TimeStatsVO.java b/zh-module-applet/zh-applet-common/src/main/java/org/jeecg/common/vo/statistic/TimeStatsVO.java new file mode 100644 index 0000000..4418ee6 --- /dev/null +++ b/zh-module-applet/zh-applet-common/src/main/java/org/jeecg/common/vo/statistic/TimeStatsVO.java @@ -0,0 +1,24 @@ +package org.jeecg.common.vo.statistic; + + +import lombok.Data; +import java.util.Date; +import java.util.Map; + +/** + * 时间统计结果VO + */ +@Data +public class TimeStatsVO { + /** 时间点 */ + private Date timePoint; + + /** 格式化后的时间 */ + private String timePointFormatted; + + /** 动态监测项数据 */ + private Map metrics; + + /** 记录数 */ + private Long recordCount; +} \ No newline at end of file