20260423:数据分析
This commit is contained in:
parent
30a8100d64
commit
1586645838
@ -44,7 +44,7 @@ public class DeviceJobServiceImpl implements IDeviceJobService {
|
|||||||
/**
|
/**
|
||||||
* 项目启动时,初始化定时器 主要是防止手动修改数据库导致未同步到定时任务处理(注:不能手动修改数据库ID和任务组名,否则会导致脏数据)
|
* 项目启动时,初始化定时器 主要是防止手动修改数据库导致未同步到定时任务处理(注:不能手动修改数据库ID和任务组名,否则会导致脏数据)
|
||||||
*/
|
*/
|
||||||
@PostConstruct
|
//@PostConstruct
|
||||||
public void init() throws SchedulerException, TaskException {
|
public void init() throws SchedulerException, TaskException {
|
||||||
scheduler.clear();
|
scheduler.clear();
|
||||||
// 设备定时任务
|
// 设备定时任务
|
||||||
|
|||||||
@ -9,7 +9,10 @@ import com.fastbee.common.utils.DateUtils;
|
|||||||
import com.fastbee.common.utils.StringUtils;
|
import com.fastbee.common.utils.StringUtils;
|
||||||
import com.fastbee.iot.domain.ThingsModel;
|
import com.fastbee.iot.domain.ThingsModel;
|
||||||
import com.fastbee.iot.model.AlertCountVO;
|
import com.fastbee.iot.model.AlertCountVO;
|
||||||
|
import com.fastbee.iot.model.DataQualityVO;
|
||||||
import com.fastbee.iot.model.DeviceHistoryParam;
|
import com.fastbee.iot.model.DeviceHistoryParam;
|
||||||
|
import com.fastbee.iot.model.DeviceStatsVO;
|
||||||
|
import com.fastbee.iot.model.HistogramVO;
|
||||||
import com.fastbee.iot.model.SubDeviceCompareParam;
|
import com.fastbee.iot.model.SubDeviceCompareParam;
|
||||||
import com.fastbee.iot.model.ThingsModelLogCountVO;
|
import com.fastbee.iot.model.ThingsModelLogCountVO;
|
||||||
import com.fastbee.iot.model.dto.ThingsModelDTO;
|
import com.fastbee.iot.model.dto.ThingsModelDTO;
|
||||||
@ -68,6 +71,48 @@ public class DataCenterController {
|
|||||||
return AjaxResult.success(jsonObject);
|
return AjaxResult.success(jsonObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOperation("查询设备物模型统计指标(最大/最小/均值/累计/数据量/最新值),支持网关设备")
|
||||||
|
@PreAuthorize("@ss.hasPermi('dataCenter:analysis:list')")
|
||||||
|
@PostMapping("/deviceStats")
|
||||||
|
public AjaxResult deviceStats(@RequestBody DeviceHistoryParam deviceHistoryParam) {
|
||||||
|
if (deviceHistoryParam.getDeviceId() == null) {
|
||||||
|
return AjaxResult.error("请选择设备");
|
||||||
|
}
|
||||||
|
if (StringUtils.isNotEmpty(deviceHistoryParam.getSerialNumber())) {
|
||||||
|
deviceHistoryParam.setSerialNumber(deviceHistoryParam.getSerialNumber().toUpperCase());
|
||||||
|
}
|
||||||
|
List<DeviceStatsVO> list = dataCenterService.deviceStats(deviceHistoryParam);
|
||||||
|
return AjaxResult.success(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation("数据质量分析:上报间隔估算、缺数时间段、连续重复值检测、质量评分、7×24热力图")
|
||||||
|
@PreAuthorize("@ss.hasPermi('dataCenter:analysis:list')")
|
||||||
|
@PostMapping("/deviceDataQuality")
|
||||||
|
public AjaxResult deviceDataQuality(@RequestBody DeviceHistoryParam deviceHistoryParam) {
|
||||||
|
if (deviceHistoryParam.getDeviceId() == null) {
|
||||||
|
return AjaxResult.error("请选择设备");
|
||||||
|
}
|
||||||
|
if (StringUtils.isNotEmpty(deviceHistoryParam.getSerialNumber())) {
|
||||||
|
deviceHistoryParam.setSerialNumber(deviceHistoryParam.getSerialNumber().toUpperCase());
|
||||||
|
}
|
||||||
|
List<DataQualityVO> list = dataCenterService.deviceDataQuality(deviceHistoryParam);
|
||||||
|
return AjaxResult.success(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation("值分布直方图:统计每个物模型标识符在查询时段内的值区间分布频次")
|
||||||
|
@PreAuthorize("@ss.hasPermi('dataCenter:analysis:list')")
|
||||||
|
@PostMapping("/deviceHistogram")
|
||||||
|
public AjaxResult deviceHistogram(@RequestBody DeviceHistoryParam deviceHistoryParam) {
|
||||||
|
if (deviceHistoryParam.getDeviceId() == null) {
|
||||||
|
return AjaxResult.error("请选择设备");
|
||||||
|
}
|
||||||
|
if (StringUtils.isNotEmpty(deviceHistoryParam.getSerialNumber())) {
|
||||||
|
deviceHistoryParam.setSerialNumber(deviceHistoryParam.getSerialNumber().toUpperCase());
|
||||||
|
}
|
||||||
|
List<HistogramVO> list = dataCenterService.deviceHistogram(deviceHistoryParam);
|
||||||
|
return AjaxResult.success(list);
|
||||||
|
}
|
||||||
|
|
||||||
@ApiOperation("网关产品-可导入的子产品物模型列表(按identifier去重)")
|
@ApiOperation("网关产品-可导入的子产品物模型列表(按identifier去重)")
|
||||||
@PreAuthorize("@ss.hasPermi('iot:model:list')")
|
@PreAuthorize("@ss.hasPermi('iot:model:list')")
|
||||||
@GetMapping("/subProductThingsModel")
|
@GetMapping("/subProductThingsModel")
|
||||||
|
|||||||
@ -22,22 +22,66 @@ public class DeviceDeferredCommand {
|
|||||||
// 执行失败
|
// 执行失败
|
||||||
public static final int STATUS_FAILED = 3;
|
public static final int STATUS_FAILED = 3;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主键
|
||||||
|
*/
|
||||||
private Long id;
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设备序列号
|
||||||
|
*/
|
||||||
private String serialNumber;
|
private String serialNumber;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主题名称
|
||||||
|
*/
|
||||||
private String topicName;
|
private String topicName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 消息体hex
|
||||||
|
*/
|
||||||
private String messageHex;
|
private String messageHex;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据类型
|
||||||
|
*/
|
||||||
private String dataType;
|
private String dataType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 任务类型
|
||||||
|
*/
|
||||||
private Integer taskType;
|
private Integer taskType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 状态(0:待执行,1:执行中,2:执行成功,3:执行失败)
|
||||||
|
*/
|
||||||
private Integer status;
|
private Integer status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 消息体hash值
|
||||||
|
*/
|
||||||
private String payloadHash;
|
private String payloadHash;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 错误信息
|
||||||
|
*/
|
||||||
private String errMsg;
|
private String errMsg;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
private Date createTime;
|
private Date createTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 过期时间
|
||||||
|
*/
|
||||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
private Date expireTime;
|
private Date expireTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调度时间
|
||||||
|
*/
|
||||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
private Date dispatchTime;
|
private Date dispatchTime;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,62 @@
|
|||||||
|
package com.fastbee.iot.model;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设备物模型数据质量 VO
|
||||||
|
* 包含:上报间隔估算、缺数时间段列表、重复值统计
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class DataQualityVO {
|
||||||
|
|
||||||
|
/** 标识符 */
|
||||||
|
private String identifier;
|
||||||
|
|
||||||
|
/** 物模型名称 */
|
||||||
|
private String modelName;
|
||||||
|
|
||||||
|
/** 所属设备序列号 */
|
||||||
|
private String serialNumber;
|
||||||
|
|
||||||
|
/** 时间范围内实际数据量 */
|
||||||
|
private Long totalCount;
|
||||||
|
|
||||||
|
/** 估算平均上报间隔(秒) */
|
||||||
|
private Long avgIntervalSeconds;
|
||||||
|
|
||||||
|
/** 缺数时间段列表(间隔超过 5× 平均间隔视为缺数) */
|
||||||
|
private List<MissingSegment> missingSegments;
|
||||||
|
|
||||||
|
/** 缺数段数量 */
|
||||||
|
private Integer missingCount;
|
||||||
|
|
||||||
|
/** 缺数总时长(分钟) */
|
||||||
|
private Long totalMissingMinutes;
|
||||||
|
|
||||||
|
/** 连续重复值数量 */
|
||||||
|
private Long repeatCount;
|
||||||
|
|
||||||
|
/** 连续重复率(0~100) */
|
||||||
|
private Double repeatRate;
|
||||||
|
|
||||||
|
/** 数据质量评分(0~100,综合缺数率和重复率计算) */
|
||||||
|
private Integer qualityScore;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 7×24 上报热力图数据,每项为 [星期几(0=周日~6=周六), 小时(0~23), 数据量]
|
||||||
|
* 用于 ECharts heatmap 的 data 字段
|
||||||
|
*/
|
||||||
|
private List<List<Integer>> heatmapData;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class MissingSegment {
|
||||||
|
/** 缺数段开始时间(上一条记录时间) */
|
||||||
|
private String gapStart;
|
||||||
|
/** 缺数段结束时间(下一条记录时间) */
|
||||||
|
private String gapEnd;
|
||||||
|
/** 缺数时长(分钟) */
|
||||||
|
private Long durationMinutes;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,41 @@
|
|||||||
|
package com.fastbee.iot.model;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设备物模型统计指标 VO
|
||||||
|
* 包含查询时间范围内某一标识符的最大/最小/均值/累计值/数据量/最新值
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class DeviceStatsVO {
|
||||||
|
|
||||||
|
/** 标识符 */
|
||||||
|
private String identifier;
|
||||||
|
|
||||||
|
/** 物模型名称 */
|
||||||
|
private String modelName;
|
||||||
|
|
||||||
|
/** 所属设备序列号(网关子设备与网关本身 SN 不同) */
|
||||||
|
private String serialNumber;
|
||||||
|
|
||||||
|
/** 最大值(仅数值型属性有效) */
|
||||||
|
private String maxValue;
|
||||||
|
|
||||||
|
/** 最小值(仅数值型属性有效) */
|
||||||
|
private String minValue;
|
||||||
|
|
||||||
|
/** 平均值(仅数值型属性有效) */
|
||||||
|
private String avgValue;
|
||||||
|
|
||||||
|
/** 累计值(仅数值型属性有效) */
|
||||||
|
private String sumValue;
|
||||||
|
|
||||||
|
/** 数据量(时间范围内上报次数) */
|
||||||
|
private Long dataCount;
|
||||||
|
|
||||||
|
/** 最新值(时间范围内最后一条记录的值) */
|
||||||
|
private String lastValue;
|
||||||
|
|
||||||
|
/** 最新值对应的时间 */
|
||||||
|
private String lastTime;
|
||||||
|
}
|
||||||
@ -0,0 +1,42 @@
|
|||||||
|
package com.fastbee.iot.model;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设备物模型值分布直方图 VO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class HistogramVO {
|
||||||
|
|
||||||
|
/** 标识符 */
|
||||||
|
private String identifier;
|
||||||
|
|
||||||
|
/** 物模型名称 */
|
||||||
|
private String modelName;
|
||||||
|
|
||||||
|
/** 所属设备序列号 */
|
||||||
|
private String serialNumber;
|
||||||
|
|
||||||
|
/** 分桶列表 */
|
||||||
|
private List<Bucket> buckets;
|
||||||
|
|
||||||
|
/** 非数值条数(布尔/枚举/字符串类型) */
|
||||||
|
private Long nonNumericCount;
|
||||||
|
|
||||||
|
/** 非数值枚举分布(key=值, value=次数),用于布尔/枚举类型 */
|
||||||
|
private java.util.Map<String, Long> enumDistribution;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class Bucket {
|
||||||
|
/** 区间标签,如 "18.00 ~ 20.00" */
|
||||||
|
private String label;
|
||||||
|
/** 区间下界(含) */
|
||||||
|
private Double rangeMin;
|
||||||
|
/** 区间上界(不含,最后一个桶含) */
|
||||||
|
private Double rangeMax;
|
||||||
|
/** 落在该区间的数据量 */
|
||||||
|
private Long count;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -3,7 +3,10 @@ package com.fastbee.iot.service;
|
|||||||
import com.alibaba.fastjson2.JSONObject;
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
import com.fastbee.iot.domain.ThingsModel;
|
import com.fastbee.iot.domain.ThingsModel;
|
||||||
import com.fastbee.iot.model.AlertCountVO;
|
import com.fastbee.iot.model.AlertCountVO;
|
||||||
|
import com.fastbee.iot.model.DataQualityVO;
|
||||||
import com.fastbee.iot.model.DeviceHistoryParam;
|
import com.fastbee.iot.model.DeviceHistoryParam;
|
||||||
|
import com.fastbee.iot.model.DeviceStatsVO;
|
||||||
|
import com.fastbee.iot.model.HistogramVO;
|
||||||
import com.fastbee.iot.model.SubDeviceCompareParam;
|
import com.fastbee.iot.model.SubDeviceCompareParam;
|
||||||
import com.fastbee.iot.model.dto.ThingsModelDTO;
|
import com.fastbee.iot.model.dto.ThingsModelDTO;
|
||||||
import com.fastbee.iot.model.ThingsModelLogCountVO;
|
import com.fastbee.iot.model.ThingsModelLogCountVO;
|
||||||
@ -100,4 +103,31 @@ public interface DataCenterService {
|
|||||||
* @return 仅计算物模型的 DTO 列表
|
* @return 仅计算物模型的 DTO 列表
|
||||||
*/
|
*/
|
||||||
List<ThingsModelDTO> listGatewayCalculatedModels(Long gwDeviceId);
|
List<ThingsModelDTO> listGatewayCalculatedModels(Long gwDeviceId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询设备物模型在时间范围内的统计指标(最大/最小/均值/累计/数据量/最新值)
|
||||||
|
* 支持网关设备(自动按子设备 SN 分组查询)
|
||||||
|
*
|
||||||
|
* @param param deviceId、serialNumber、identifierList(可选)、beginTime、endTime
|
||||||
|
* @return 每个标识符对应的统计指标列表
|
||||||
|
*/
|
||||||
|
List<DeviceStatsVO> deviceStats(DeviceHistoryParam param);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据质量分析:上报间隔估算、缺数时间段、连续重复值检测、质量评分
|
||||||
|
* 支持网关设备(自动按子设备 SN 分组查询)
|
||||||
|
*
|
||||||
|
* @param param deviceId、serialNumber、identifierList(可选)、beginTime、endTime
|
||||||
|
* @return 每个标识符对应的数据质量报告
|
||||||
|
*/
|
||||||
|
List<DataQualityVO> deviceDataQuality(DeviceHistoryParam param);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 值分布直方图:按标识符统计值在各区间的频次分布
|
||||||
|
* 支持网关设备(自动按子设备 SN 分组查询)
|
||||||
|
*
|
||||||
|
* @param param deviceId、serialNumber、identifierList(可选)、beginTime、endTime
|
||||||
|
* @return 每个标识符对应的直方图数据
|
||||||
|
*/
|
||||||
|
List<HistogramVO> deviceHistogram(DeviceHistoryParam param);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,6 +14,10 @@ import com.fastbee.iot.model.gateWay.SubDeviceListVO;
|
|||||||
import com.fastbee.iot.model.param.DataCenterParam;
|
import com.fastbee.iot.model.param.DataCenterParam;
|
||||||
import com.fastbee.iot.model.scenemodel.SceneHistoryParam;
|
import com.fastbee.iot.model.scenemodel.SceneHistoryParam;
|
||||||
import com.fastbee.iot.service.*;
|
import com.fastbee.iot.service.*;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
import java.util.Calendar;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.collections4.CollectionUtils;
|
import org.apache.commons.collections4.CollectionUtils;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
@ -59,60 +63,82 @@ public class DataCenterServiceImpl implements DataCenterService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<JSONObject> deviceHistory(DeviceHistoryParam deviceHistoryParam) {
|
public List<JSONObject> deviceHistory(DeviceHistoryParam deviceHistoryParam) {
|
||||||
List<JSONObject> resultList = new ArrayList<>();
|
// snToIdentifiers: 每个 serialNumber 对应该 SN 下需要查询的标识符列表
|
||||||
List<DeviceHistoryParam.IdentifierVO> identifierVOList = new ArrayList<>();
|
// 对于普通设备只有一个 SN;对于网关设备,自身与各子设备 SN 不同,需分别查询
|
||||||
|
Map<String, List<DeviceHistoryParam.IdentifierVO>> snToIdentifiers = new LinkedHashMap<>();
|
||||||
|
List<String> allIdentifiers = new ArrayList<>();
|
||||||
|
|
||||||
if (CollectionUtils.isEmpty(deviceHistoryParam.getIdentifierList())) {
|
if (CollectionUtils.isEmpty(deviceHistoryParam.getIdentifierList())) {
|
||||||
|
// 未指定标识符时,从 listThingsModel 获取(已携带每个物模型真实的 serialNumber)
|
||||||
List<ThingsModelDTO> list = deviceService.listThingsModel(deviceHistoryParam.getDeviceId());
|
List<ThingsModelDTO> list = deviceService.listThingsModel(deviceHistoryParam.getDeviceId());
|
||||||
for (ThingsModelDTO thingsModelDTO : list) {
|
for (ThingsModelDTO dto : list) {
|
||||||
DeviceHistoryParam.IdentifierVO identifierVO = new DeviceHistoryParam.IdentifierVO();
|
DeviceHistoryParam.IdentifierVO vo = new DeviceHistoryParam.IdentifierVO();
|
||||||
identifierVO.setIdentifier(thingsModelDTO.getIdentifier());
|
vo.setIdentifier(dto.getIdentifier());
|
||||||
identifierVO.setType(thingsModelDTO.getType());
|
vo.setType(dto.getType());
|
||||||
identifierVOList.add(identifierVO);
|
String sn = StringUtils.isNotEmpty(dto.getSerialNumber())
|
||||||
|
? dto.getSerialNumber()
|
||||||
|
: deviceHistoryParam.getSerialNumber();
|
||||||
|
snToIdentifiers.computeIfAbsent(sn, k -> new ArrayList<>()).add(vo);
|
||||||
|
if (!allIdentifiers.contains(dto.getIdentifier())) {
|
||||||
|
allIdentifiers.add(dto.getIdentifier());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
identifierVOList = deviceHistoryParam.getIdentifierList();
|
// 显式传入标识符列表时统一使用请求中的 serialNumber
|
||||||
|
snToIdentifiers.put(deviceHistoryParam.getSerialNumber(), deviceHistoryParam.getIdentifierList());
|
||||||
|
deviceHistoryParam.getIdentifierList()
|
||||||
|
.forEach(vo -> allIdentifiers.add(vo.getIdentifier()));
|
||||||
}
|
}
|
||||||
if (CollectionUtils.isEmpty(identifierVOList)) {
|
|
||||||
return resultList;
|
if (snToIdentifiers.isEmpty()) {
|
||||||
|
return new ArrayList<>();
|
||||||
}
|
}
|
||||||
List<String> identifierList = identifierVOList.stream().map(DeviceHistoryParam.IdentifierVO::getIdentifier)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
List<HistoryModel> historyModelList = new ArrayList<>();
|
List<HistoryModel> historyModelList = new ArrayList<>();
|
||||||
|
for (Map.Entry<String, List<DeviceHistoryParam.IdentifierVO>> entry : snToIdentifiers.entrySet()) {
|
||||||
|
String sn = entry.getKey();
|
||||||
|
List<DeviceHistoryParam.IdentifierVO> identifierVOList = entry.getValue();
|
||||||
|
|
||||||
List<String> propertyIdentifierList = identifierVOList.stream().filter(t -> 1 == t.getType())
|
List<String> propertyIdentifierList = identifierVOList.stream().filter(t -> 1 == t.getType())
|
||||||
.map(DeviceHistoryParam.IdentifierVO::getIdentifier).collect(Collectors.toList());
|
.map(DeviceHistoryParam.IdentifierVO::getIdentifier).collect(Collectors.toList());
|
||||||
if (CollectionUtils.isNotEmpty(propertyIdentifierList)) {
|
if (CollectionUtils.isNotEmpty(propertyIdentifierList)) {
|
||||||
DeviceLog deviceLog = new DeviceLog();
|
DeviceLog deviceLog = new DeviceLog();
|
||||||
deviceLog.setIdentityList(propertyIdentifierList);
|
deviceLog.setIdentityList(propertyIdentifierList);
|
||||||
deviceLog.setSerialNumber(deviceHistoryParam.getSerialNumber());
|
deviceLog.setSerialNumber(sn);
|
||||||
deviceLog.setBeginTime(deviceHistoryParam.getBeginTime());
|
deviceLog.setBeginTime(deviceHistoryParam.getBeginTime());
|
||||||
deviceLog.setEndTime(deviceHistoryParam.getEndTime());
|
deviceLog.setEndTime(deviceHistoryParam.getEndTime());
|
||||||
historyModelList.addAll(deviceLogService.listHistory(deviceLog));
|
historyModelList.addAll(deviceLogService.listHistory(deviceLog));
|
||||||
}
|
}
|
||||||
|
|
||||||
List<String> functionIdentifierList = identifierVOList.stream().filter(t -> 2 == t.getType())
|
List<String> functionIdentifierList = identifierVOList.stream().filter(t -> 2 == t.getType())
|
||||||
.map(DeviceHistoryParam.IdentifierVO::getIdentifier).collect(Collectors.toList());
|
.map(DeviceHistoryParam.IdentifierVO::getIdentifier).collect(Collectors.toList());
|
||||||
if (CollectionUtils.isNotEmpty(functionIdentifierList)) {
|
if (CollectionUtils.isNotEmpty(functionIdentifierList)) {
|
||||||
FunctionLog functionLog = new FunctionLog();
|
FunctionLog functionLog = new FunctionLog();
|
||||||
functionLog.setIdentifyList(functionIdentifierList);
|
functionLog.setIdentifyList(functionIdentifierList);
|
||||||
functionLog.setSerialNumber(deviceHistoryParam.getSerialNumber());
|
functionLog.setSerialNumber(sn);
|
||||||
functionLog
|
functionLog.setBeginTime(
|
||||||
.setBeginTime(DateUtils.dateTime(DateUtils.YY_MM_DD_HH_MM_SS, deviceHistoryParam.getBeginTime()));
|
DateUtils.dateTime(DateUtils.YY_MM_DD_HH_MM_SS, deviceHistoryParam.getBeginTime()));
|
||||||
functionLog.setEndTime(DateUtils.dateTime(DateUtils.YY_MM_DD_HH_MM_SS, deviceHistoryParam.getEndTime()));
|
functionLog.setEndTime(
|
||||||
|
DateUtils.dateTime(DateUtils.YY_MM_DD_HH_MM_SS, deviceHistoryParam.getEndTime()));
|
||||||
historyModelList.addAll(functionLogService.listHistory(functionLog));
|
historyModelList.addAll(functionLogService.listHistory(functionLog));
|
||||||
}
|
}
|
||||||
|
|
||||||
List<String> eventIdentifierList = identifierVOList.stream().filter(t -> 3 == t.getType())
|
List<String> eventIdentifierList = identifierVOList.stream().filter(t -> 3 == t.getType())
|
||||||
.map(DeviceHistoryParam.IdentifierVO::getIdentifier).collect(Collectors.toList());
|
.map(DeviceHistoryParam.IdentifierVO::getIdentifier).collect(Collectors.toList());
|
||||||
if (CollectionUtils.isNotEmpty(eventIdentifierList)) {
|
if (CollectionUtils.isNotEmpty(eventIdentifierList)) {
|
||||||
EventLog eventLog = new EventLog();
|
EventLog eventLog = new EventLog();
|
||||||
eventLog.setIdentityList(eventIdentifierList);
|
eventLog.setIdentityList(eventIdentifierList);
|
||||||
eventLog.setSerialNumber(deviceHistoryParam.getSerialNumber());
|
eventLog.setSerialNumber(sn);
|
||||||
Map<String, Object> params = new HashMap<>(2);
|
Map<String, Object> params = new HashMap<>(2);
|
||||||
params.put("beginTime", deviceHistoryParam.getBeginTime());
|
params.put("beginTime", deviceHistoryParam.getBeginTime());
|
||||||
params.put("endTime", deviceHistoryParam.getEndTime());
|
params.put("endTime", deviceHistoryParam.getEndTime());
|
||||||
eventLog.setParams(params);
|
eventLog.setParams(params);
|
||||||
historyModelList.addAll(eventLogService.listHistory(eventLog));
|
historyModelList.addAll(eventLogService.listHistory(eventLog));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
historyModelList.sort(Comparator.comparing(HistoryModel::getTime));
|
historyModelList.sort(Comparator.comparing(HistoryModel::getTime));
|
||||||
return this.handleData(identifierList, historyModelList);
|
return this.handleData(allIdentifiers, historyModelList);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<JSONObject> handleData(List<String> identifierList, List<HistoryModel> historyModelList) {
|
private List<JSONObject> handleData(List<String> identifierList, List<HistoryModel> historyModelList) {
|
||||||
@ -510,10 +536,353 @@ public class DataCenterServiceImpl implements DataCenterService {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<DataQualityVO> deviceDataQuality(DeviceHistoryParam param) {
|
||||||
|
Map<String, List<DeviceHistoryParam.IdentifierVO>> snToIdentifiers = new LinkedHashMap<>();
|
||||||
|
Map<String, String> identifierToModelName = new LinkedHashMap<>();
|
||||||
|
Map<String, String> identifierToSn = new LinkedHashMap<>();
|
||||||
|
buildSnGrouping(param, snToIdentifiers, identifierToModelName, identifierToSn);
|
||||||
|
if (snToIdentifiers.isEmpty()) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按 identifier 聚合原始历史(仅属性类型)
|
||||||
|
Map<String, List<HistoryModel>> rawByIdentifier = fetchRawByIdentifier(param, snToIdentifiers, true, false);
|
||||||
|
|
||||||
|
List<DataQualityVO> result = new ArrayList<>();
|
||||||
|
for (Map.Entry<String, String> entry : identifierToSn.entrySet()) {
|
||||||
|
String identifier = entry.getKey();
|
||||||
|
List<HistoryModel> hList = rawByIdentifier.getOrDefault(identifier, new ArrayList<>());
|
||||||
|
// 按时间升序排列
|
||||||
|
hList.sort(Comparator.comparing(HistoryModel::getTime, Comparator.nullsLast(Comparator.naturalOrder())));
|
||||||
|
|
||||||
|
DataQualityVO vo = new DataQualityVO();
|
||||||
|
vo.setIdentifier(identifier);
|
||||||
|
vo.setModelName(identifierToModelName.getOrDefault(identifier, identifier));
|
||||||
|
vo.setSerialNumber(entry.getValue());
|
||||||
|
vo.setTotalCount((long) hList.size());
|
||||||
|
|
||||||
|
if (hList.size() >= 2) {
|
||||||
|
// 计算所有相邻记录的时间差(秒)
|
||||||
|
List<Long> gaps = new ArrayList<>();
|
||||||
|
for (int i = 1; i < hList.size(); i++) {
|
||||||
|
Date t1 = hList.get(i - 1).getTime();
|
||||||
|
Date t2 = hList.get(i).getTime();
|
||||||
|
if (t1 != null && t2 != null) {
|
||||||
|
gaps.add((t2.getTime() - t1.getTime()) / 1000L);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!gaps.isEmpty()) {
|
||||||
|
// 平均上报间隔(秒)
|
||||||
|
long avgGap = (long) gaps.stream().mapToLong(Long::longValue).average().orElse(0);
|
||||||
|
vo.setAvgIntervalSeconds(avgGap);
|
||||||
|
|
||||||
|
// 缺数检测阈值:最大取平均间隔的5倍或5分钟,最小不超过1小时
|
||||||
|
long threshold = Math.max(avgGap * 5, 300L);
|
||||||
|
threshold = Math.min(threshold, 3600L);
|
||||||
|
|
||||||
|
List<DataQualityVO.MissingSegment> segments = new ArrayList<>();
|
||||||
|
long totalMissingSeconds = 0;
|
||||||
|
for (int i = 0; i < gaps.size(); i++) {
|
||||||
|
if (gaps.get(i) > threshold) {
|
||||||
|
DataQualityVO.MissingSegment seg = new DataQualityVO.MissingSegment();
|
||||||
|
seg.setGapStart(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, hList.get(i).getTime()));
|
||||||
|
seg.setGapEnd(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, hList.get(i + 1).getTime()));
|
||||||
|
seg.setDurationMinutes(gaps.get(i) / 60);
|
||||||
|
segments.add(seg);
|
||||||
|
totalMissingSeconds += gaps.get(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vo.setMissingSegments(segments);
|
||||||
|
vo.setMissingCount(segments.size());
|
||||||
|
vo.setTotalMissingMinutes(totalMissingSeconds / 60);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
vo.setMissingSegments(new ArrayList<>());
|
||||||
|
vo.setMissingCount(0);
|
||||||
|
vo.setTotalMissingMinutes(0L);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 连续重复值检测
|
||||||
|
long repeatCount = 0;
|
||||||
|
for (int i = 1; i < hList.size(); i++) {
|
||||||
|
String prev = hList.get(i - 1).getValue();
|
||||||
|
String curr = hList.get(i).getValue();
|
||||||
|
if (prev != null && prev.equals(curr)) {
|
||||||
|
repeatCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vo.setRepeatCount(repeatCount);
|
||||||
|
if (hList.size() > 1) {
|
||||||
|
vo.setRepeatRate(Math.round(repeatCount * 10000.0 / (hList.size() - 1)) / 100.0);
|
||||||
|
} else {
|
||||||
|
vo.setRepeatRate(0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 质量评分(0~100):缺数越多扣分,重复率越高扣分
|
||||||
|
int score = 100;
|
||||||
|
if (vo.getMissingCount() != null && vo.getMissingCount() > 0) {
|
||||||
|
// 每段缺数扣5分,最多扣50分
|
||||||
|
score -= Math.min(vo.getMissingCount() * 5, 50);
|
||||||
|
}
|
||||||
|
if (vo.getRepeatRate() != null && vo.getRepeatRate() > 5) {
|
||||||
|
score -= (int) Math.min((vo.getRepeatRate() - 5) / 2, 30);
|
||||||
|
}
|
||||||
|
vo.setQualityScore(Math.max(score, 0));
|
||||||
|
|
||||||
|
// 7×24 热力图:[dayOfWeek(0-6), hour(0-23), count]
|
||||||
|
int[][] heatmap = new int[7][24];
|
||||||
|
Calendar cal = Calendar.getInstance();
|
||||||
|
for (HistoryModel h : hList) {
|
||||||
|
if (h.getTime() != null) {
|
||||||
|
cal.setTime(h.getTime());
|
||||||
|
int dow = cal.get(Calendar.DAY_OF_WEEK) - 1; // 0=周日
|
||||||
|
int hour = cal.get(Calendar.HOUR_OF_DAY);
|
||||||
|
heatmap[dow][hour]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
List<List<Integer>> heatmapData = new ArrayList<>();
|
||||||
|
for (int d = 0; d < 7; d++) {
|
||||||
|
for (int h = 0; h < 24; h++) {
|
||||||
|
if (heatmap[d][h] > 0) {
|
||||||
|
List<Integer> point = new ArrayList<>();
|
||||||
|
point.add(h); // ECharts x = 小时
|
||||||
|
point.add(d); // ECharts y = 星期
|
||||||
|
point.add(heatmap[d][h]);
|
||||||
|
heatmapData.add(point);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vo.setHeatmapData(heatmapData);
|
||||||
|
result.add(vo);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<HistogramVO> deviceHistogram(DeviceHistoryParam param) {
|
||||||
|
Map<String, List<DeviceHistoryParam.IdentifierVO>> snToIdentifiers = new LinkedHashMap<>();
|
||||||
|
Map<String, String> identifierToModelName = new LinkedHashMap<>();
|
||||||
|
Map<String, String> identifierToSn = new LinkedHashMap<>();
|
||||||
|
buildSnGrouping(param, snToIdentifiers, identifierToModelName, identifierToSn);
|
||||||
|
if (snToIdentifiers.isEmpty()) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, List<HistoryModel>> rawByIdentifier = fetchRawByIdentifier(param, snToIdentifiers, true, false);
|
||||||
|
|
||||||
|
List<HistogramVO> result = new ArrayList<>();
|
||||||
|
for (Map.Entry<String, String> entry : identifierToSn.entrySet()) {
|
||||||
|
String identifier = entry.getKey();
|
||||||
|
List<HistoryModel> hList = rawByIdentifier.getOrDefault(identifier, new ArrayList<>());
|
||||||
|
|
||||||
|
HistogramVO vo = new HistogramVO();
|
||||||
|
vo.setIdentifier(identifier);
|
||||||
|
vo.setModelName(identifierToModelName.getOrDefault(identifier, identifier));
|
||||||
|
vo.setSerialNumber(entry.getValue());
|
||||||
|
|
||||||
|
// 尝试数值解析
|
||||||
|
List<Double> numericValues = new ArrayList<>();
|
||||||
|
Map<String, Long> enumDist = new LinkedHashMap<>();
|
||||||
|
long nonNumericCount = 0;
|
||||||
|
for (HistoryModel h : hList) {
|
||||||
|
if (h.getValue() == null || h.getValue().trim().isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
numericValues.add(Double.parseDouble(h.getValue().trim()));
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
nonNumericCount++;
|
||||||
|
enumDist.merge(h.getValue().trim(), 1L, (a, b) -> a + b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vo.setNonNumericCount(nonNumericCount);
|
||||||
|
|
||||||
|
if (!numericValues.isEmpty()) {
|
||||||
|
double min = numericValues.stream().mapToDouble(Double::doubleValue).min().orElse(0);
|
||||||
|
double max = numericValues.stream().mapToDouble(Double::doubleValue).max().orElse(0);
|
||||||
|
int bucketCount = 10;
|
||||||
|
double range = max - min;
|
||||||
|
// 若所有值相同,设一个区间宽度
|
||||||
|
double bucketWidth = (range == 0) ? 1.0 : range / bucketCount;
|
||||||
|
long[] counts = new long[bucketCount];
|
||||||
|
for (double v : numericValues) {
|
||||||
|
int idx = (range == 0) ? 0 : (int) Math.min((v - min) / bucketWidth, bucketCount - 1);
|
||||||
|
counts[idx]++;
|
||||||
|
}
|
||||||
|
List<HistogramVO.Bucket> buckets = new ArrayList<>();
|
||||||
|
for (int i = 0; i < bucketCount; i++) {
|
||||||
|
HistogramVO.Bucket bucket = new HistogramVO.Bucket();
|
||||||
|
double lo = min + i * bucketWidth;
|
||||||
|
double hi = min + (i + 1) * bucketWidth;
|
||||||
|
bucket.setRangeMin(Math.round(lo * 100.0) / 100.0);
|
||||||
|
bucket.setRangeMax(Math.round(hi * 100.0) / 100.0);
|
||||||
|
bucket.setLabel(String.format("%.2f~%.2f", lo, hi));
|
||||||
|
bucket.setCount(counts[i]);
|
||||||
|
buckets.add(bucket);
|
||||||
|
}
|
||||||
|
vo.setBuckets(buckets);
|
||||||
|
} else if (!enumDist.isEmpty()) {
|
||||||
|
// 全部是枚举/布尔值,返回枚举分布
|
||||||
|
vo.setEnumDistribution(enumDist);
|
||||||
|
vo.setBuckets(new ArrayList<>());
|
||||||
|
} else {
|
||||||
|
vo.setBuckets(new ArrayList<>());
|
||||||
|
}
|
||||||
|
result.add(vo);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 公共辅助:按 SN 分组,构建 snToIdentifiers / identifierToModelName / identifierToSn
|
||||||
|
*/
|
||||||
|
private void buildSnGrouping(DeviceHistoryParam param,
|
||||||
|
Map<String, List<DeviceHistoryParam.IdentifierVO>> snToIdentifiers,
|
||||||
|
Map<String, String> identifierToModelName,
|
||||||
|
Map<String, String> identifierToSn) {
|
||||||
|
if (CollectionUtils.isEmpty(param.getIdentifierList())) {
|
||||||
|
List<ThingsModelDTO> list = deviceService.listThingsModel(param.getDeviceId());
|
||||||
|
for (ThingsModelDTO dto : list) {
|
||||||
|
String sn = StringUtils.isNotEmpty(dto.getSerialNumber())
|
||||||
|
? dto.getSerialNumber() : param.getSerialNumber();
|
||||||
|
DeviceHistoryParam.IdentifierVO vo = new DeviceHistoryParam.IdentifierVO();
|
||||||
|
vo.setIdentifier(dto.getIdentifier());
|
||||||
|
vo.setType(dto.getType());
|
||||||
|
snToIdentifiers.computeIfAbsent(sn, k -> new ArrayList<>()).add(vo);
|
||||||
|
identifierToModelName.putIfAbsent(dto.getIdentifier(), dto.getModelName());
|
||||||
|
identifierToSn.putIfAbsent(dto.getIdentifier(), sn);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
snToIdentifiers.put(param.getSerialNumber(), param.getIdentifierList());
|
||||||
|
param.getIdentifierList().forEach(vo -> {
|
||||||
|
identifierToSn.put(vo.getIdentifier(), param.getSerialNumber());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 公共辅助:按 snToIdentifiers 分组查询原始历史,返回 identifier -> List<HistoryModel>
|
||||||
|
*
|
||||||
|
* @param includeProperty 是否查属性日志(type=1)
|
||||||
|
* @param includeFunction 是否查功能日志(type=2)
|
||||||
|
*/
|
||||||
|
private Map<String, List<HistoryModel>> fetchRawByIdentifier(DeviceHistoryParam param,
|
||||||
|
Map<String, List<DeviceHistoryParam.IdentifierVO>> snToIdentifiers,
|
||||||
|
boolean includeProperty, boolean includeFunction) {
|
||||||
|
Map<String, List<HistoryModel>> rawByIdentifier = new LinkedHashMap<>();
|
||||||
|
for (Map.Entry<String, List<DeviceHistoryParam.IdentifierVO>> entry : snToIdentifiers.entrySet()) {
|
||||||
|
String sn = entry.getKey();
|
||||||
|
List<DeviceHistoryParam.IdentifierVO> vos = entry.getValue();
|
||||||
|
if (includeProperty) {
|
||||||
|
List<String> propIds = vos.stream().filter(v -> Integer.valueOf(1).equals(v.getType()))
|
||||||
|
.map(DeviceHistoryParam.IdentifierVO::getIdentifier).collect(Collectors.toList());
|
||||||
|
if (CollectionUtils.isNotEmpty(propIds)) {
|
||||||
|
DeviceLog dl = new DeviceLog();
|
||||||
|
dl.setSerialNumber(sn);
|
||||||
|
dl.setIdentityList(propIds);
|
||||||
|
dl.setBeginTime(param.getBeginTime());
|
||||||
|
dl.setEndTime(param.getEndTime());
|
||||||
|
for (HistoryModel h : deviceLogService.listHistory(dl)) {
|
||||||
|
rawByIdentifier.computeIfAbsent(h.getIdentity(), k -> new ArrayList<>()).add(h);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (includeFunction) {
|
||||||
|
List<String> funcIds = vos.stream().filter(v -> Integer.valueOf(2).equals(v.getType()))
|
||||||
|
.map(DeviceHistoryParam.IdentifierVO::getIdentifier).collect(Collectors.toList());
|
||||||
|
if (CollectionUtils.isNotEmpty(funcIds)) {
|
||||||
|
FunctionLog fl = new FunctionLog();
|
||||||
|
fl.setSerialNumber(sn);
|
||||||
|
fl.setIdentifyList(funcIds);
|
||||||
|
fl.setBeginTime(DateUtils.dateTime(DateUtils.YY_MM_DD_HH_MM_SS, param.getBeginTime()));
|
||||||
|
fl.setEndTime(DateUtils.dateTime(DateUtils.YY_MM_DD_HH_MM_SS, param.getEndTime()));
|
||||||
|
for (HistoryModel h : functionLogService.listHistory(fl)) {
|
||||||
|
rawByIdentifier.computeIfAbsent(h.getIdentity(), k -> new ArrayList<>()).add(h);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rawByIdentifier;
|
||||||
|
}
|
||||||
|
|
||||||
private Map<String, String> parseCrossDeviceRefs(String formula) {
|
private Map<String, String> parseCrossDeviceRefs(String formula) {
|
||||||
return CalculateUtils.parseCrossDeviceRefs(formula);
|
return CalculateUtils.parseCrossDeviceRefs(formula);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<DeviceStatsVO> deviceStats(DeviceHistoryParam param) {
|
||||||
|
Map<String, List<DeviceHistoryParam.IdentifierVO>> snToIdentifiers = new LinkedHashMap<>();
|
||||||
|
Map<String, String> identifierToModelName = new LinkedHashMap<>();
|
||||||
|
Map<String, String> identifierToSn = new LinkedHashMap<>();
|
||||||
|
buildSnGrouping(param, snToIdentifiers, identifierToModelName, identifierToSn);
|
||||||
|
|
||||||
|
if (snToIdentifiers.isEmpty()) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, List<HistoryModel>> rawByIdentifier = fetchRawByIdentifier(param, snToIdentifiers, true, true);
|
||||||
|
|
||||||
|
// 计算每个 identifier 的统计指标
|
||||||
|
List<DeviceStatsVO> result = new ArrayList<>();
|
||||||
|
for (Map.Entry<String, String> entry : identifierToSn.entrySet()) {
|
||||||
|
String identifier = entry.getKey();
|
||||||
|
String sn = entry.getValue();
|
||||||
|
List<HistoryModel> hList = rawByIdentifier.getOrDefault(identifier, new ArrayList<>());
|
||||||
|
|
||||||
|
DeviceStatsVO vo = new DeviceStatsVO();
|
||||||
|
vo.setIdentifier(identifier);
|
||||||
|
vo.setModelName(identifierToModelName.getOrDefault(identifier, identifier));
|
||||||
|
vo.setSerialNumber(sn);
|
||||||
|
vo.setDataCount((long) hList.size());
|
||||||
|
|
||||||
|
if (!hList.isEmpty()) {
|
||||||
|
// listHistory 返回 desc 排序,第一条即最新
|
||||||
|
HistoryModel latest = hList.get(0);
|
||||||
|
vo.setLastValue(latest.getValue());
|
||||||
|
if (latest.getTime() != null) {
|
||||||
|
vo.setLastTime(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, latest.getTime()));
|
||||||
|
}
|
||||||
|
// 数值统计
|
||||||
|
BigDecimal max = null, min = null, sum = BigDecimal.ZERO;
|
||||||
|
int numCount = 0;
|
||||||
|
for (HistoryModel h : hList) {
|
||||||
|
if (h.getValue() == null || h.getValue().isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
BigDecimal v = new BigDecimal(h.getValue().trim());
|
||||||
|
if (max == null || v.compareTo(max) > 0) {
|
||||||
|
max = v;
|
||||||
|
}
|
||||||
|
if (min == null || v.compareTo(min) < 0) {
|
||||||
|
min = v;
|
||||||
|
}
|
||||||
|
sum = sum.add(v);
|
||||||
|
numCount++;
|
||||||
|
} catch (NumberFormatException ignored) {
|
||||||
|
// 非数值型(布尔、枚举、字符串等)跳过数值计算
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (numCount > 0) {
|
||||||
|
vo.setMaxValue(stripTrailingZeros(max));
|
||||||
|
vo.setMinValue(stripTrailingZeros(min));
|
||||||
|
vo.setAvgValue(stripTrailingZeros(sum.divide(BigDecimal.valueOf(numCount), 4, RoundingMode.HALF_UP)));
|
||||||
|
vo.setSumValue(stripTrailingZeros(sum));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.add(vo);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String stripTrailingZeros(BigDecimal value) {
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return value.stripTrailingZeros().toPlainString();
|
||||||
|
}
|
||||||
|
|
||||||
private List<Date> buildMinuteSlots(String beginTime, String endTime) {
|
private List<Date> buildMinuteSlots(String beginTime, String endTime) {
|
||||||
Date start = DateUtils.parseDate(beginTime);
|
Date start = DateUtils.parseDate(beginTime);
|
||||||
Date end = DateUtils.parseDate(endTime);
|
Date end = DateUtils.parseDate(endTime);
|
||||||
|
|||||||
@ -45,6 +45,33 @@ export function getDataCenterCountThingsModelInvoke(query) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 查询设备物模型统计指标(最大/最小/均值/累计/数据量/最新值)
|
||||||
|
export function getDataCenterDeviceStats(data) {
|
||||||
|
return request({
|
||||||
|
url: '/data/center/deviceStats',
|
||||||
|
method: 'post',
|
||||||
|
data: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 数据质量分析(上报间隔、缺数时间段、重复值检测、质量评分、7×24热力图)
|
||||||
|
export function getDataCenterDeviceDataQuality(data) {
|
||||||
|
return request({
|
||||||
|
url: '/data/center/deviceDataQuality',
|
||||||
|
method: 'post',
|
||||||
|
data: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 值分布直方图
|
||||||
|
export function getDataCenterDeviceHistogram(data) {
|
||||||
|
return request({
|
||||||
|
url: '/data/center/deviceHistogram',
|
||||||
|
method: 'post',
|
||||||
|
data: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// 导出设备历史数据Excel
|
// 导出设备历史数据Excel
|
||||||
export function exportDeviceHistory(data) {
|
export function exportDeviceHistory(data) {
|
||||||
return request({
|
return request({
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user