diff --git a/inspect-main/inspect-main-statistics/src/main/java/com/inspect/statistics/constant/PatrolScreenConst.java b/inspect-main/inspect-main-statistics/src/main/java/com/inspect/statistics/constant/PatrolScreenConst.java new file mode 100644 index 0000000..57c5cf6 --- /dev/null +++ b/inspect-main/inspect-main-statistics/src/main/java/com/inspect/statistics/constant/PatrolScreenConst.java @@ -0,0 +1,109 @@ +package com.inspect.statistics.constant; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * 巡视大屏统计接口常量:Redis 缓存 key、缓存时长、period 取值与固定名目。 + */ +public class PatrolScreenConst { + + /** 大屏统计缓存 key 统一前缀 */ + public static final String CACHE_PREFIX = "patrol_screen:"; + + /** 巡视设备统计 */ + public static final String CACHE_DEVICE_STATS = CACHE_PREFIX + "device_stats"; + /** 机器人/无人工况 */ + public static final String CACHE_ROBOT_DRONE_STATUS = CACHE_PREFIX + "robot_drone_status"; + /** 摄像机统计 */ + public static final String CACHE_CAMERA_STATS = CACHE_PREFIX + "camera_stats"; + /** 巡视任务统计(需拼接 period) */ + public static final String CACHE_TASK_STATS = CACHE_PREFIX + "task_stats:"; + /** 人工确认缺陷统计(需拼接 period) */ + public static final String CACHE_MANUAL_CONFIRM_DEFECT = CACHE_PREFIX + "manual_confirm_defect:"; + /** 巡视告警统计(需拼接 period) */ + public static final String CACHE_ALARM_STATS = CACHE_PREFIX + "alarm_stats:"; + /** 巡视缺陷统计(需拼接 period) */ + public static final String CACHE_DEFECT_STATS = CACHE_PREFIX + "defect_stats:"; + + /** 低频接口缓存时长(秒):设备统计、机器人/无人工况、摄像机统计 */ + public static final long TTL_LOW_FREQUENCY = 60L; + /** 中频接口缓存时长(秒):巡视任务统计、人工确认缺陷统计 */ + public static final long TTL_MIDDLE_FREQUENCY = 30L; + /** 高频接口缓存时长(秒):巡视告警统计、巡视缺陷统计 */ + public static final long TTL_HIGH_FREQUENCY = 15L; + + public static final String PERIOD_DAY = "day"; + public static final String PERIOD_WEEK = "week"; + public static final String PERIOD_MONTH = "month"; + public static final String PERIOD_YEAR = "year"; + /** period 缺省值 */ + public static final String PERIOD_DEFAULT = PERIOD_DAY; + /** period 合法取值 */ + public static final List PERIODS = Collections.unmodifiableList( + Arrays.asList(PERIOD_DAY, PERIOD_WEEK, PERIOD_MONTH, PERIOD_YEAR)); + + /** basedata_eqpbook.type:摄像头 */ + public static final String DEVICE_TYPE_CAMERA = "10"; + /** basedata_eqpbook.type:无人机 */ + public static final String DEVICE_TYPE_DRONE = "13"; + /** basedata_eqpbook.type:机器人 */ + public static final List DEVICE_TYPE_ROBOTS = Collections.unmodifiableList( + Arrays.asList("1", "2", "3")); + + /** 未删除标志:del_flag / delete_flag 为 0 表示未删除,2 表示已删除 */ + public static final String FLAG_NORMAL = "0"; + + /** sys_dict_data.dict_type:摄像机使用类型(basedata_eqpbook_channel.use_mode 的取值字典) */ + public static final String DICT_USE_MODE = "use_mode"; + /** sys_dict_data.dict_type:通道类型(basedata_eqpbook_channel.channel_type 的取值字典) */ + public static final String DICT_CHANNEL_TYPE = "channel_type"; + + /** 摄像机类型分布与光谱分布的兜底名目:取值未配置或不在对应字典内的通道 */ + public static final String CAMERA_OTHER = "其他"; + + /** basedata_mont_patdev_online.status:在线 */ + public static final String ONLINE_STATUS = "1"; + + /** patrol_task.dev_type:机器人 */ + public static final String PATROL_DEV_TYPE_ROBOT = "1"; + /** patrol_task.dev_type:无人机 */ + public static final String PATROL_DEV_TYPE_DRONE = "0"; + /** 连续运行天数回溯上限(天),用于兜底防止脏数据导致统计失控 */ + public static final int MAX_RUN_DAYS = 3650; + + /** 巡视类型名目(固定顺序,含 0 值) */ + public static final List PATROL_TYPES = Collections.unmodifiableList( + Arrays.asList("全面巡视", "例行巡视", "特殊巡视", "专项巡视", "自定义巡视", "熄灯巡视")); + /** 巡视类型名目对应的 patrol_task.type 取值,顺序与 PATROL_TYPES 一致;空串表示库中无对应取值、恒为 0 */ + public static final List PATROL_TYPE_CODES = Collections.unmodifiableList( + Arrays.asList("", "1", "2", "3", "4", "")); + /** + * 不参与大屏统计的巡视任务类型:5 联动任务、6 静默任务。 + * + *

注意:MyBatis OGNL 会对该集合调用 {@code size()},JDK 17 下无法反射访问 + * {@code Collections$UnmodifiableCollection} 等包私有实现类(InaccessibleObjectException), + * 故这里使用可变 ArrayList,不要改成 unmodifiableList。

+ */ + public static final List PATROL_TYPE_EXCLUDED = new ArrayList<>(Arrays.asList("5", "6")); + + /** 巡视状态名目(固定顺序,含 0 值) */ + public static final List PATROL_STATUSES = Collections.unmodifiableList( + Arrays.asList("正在执行", "已经执行", "暂停", "终止", "未执行", "超期")); + /** 巡视状态名目对应的 patrol_task_status.task_state 取值,顺序与 PATROL_STATUSES 一致 */ + public static final List PATROL_STATUS_CODES = Collections.unmodifiableList( + Arrays.asList("2", "1", "3", "4", "5", "6")); + /** 巡视率指标名目:与数量项分开返回,避免前端按名称硬编码判断 */ + public static final String RATE_POINT_MISS = "巡视点位漏检率"; + public static final String RATE_TASK_CLOSED_LOOP = "任务执行闭环率"; + public static final List PATROL_RATES = Collections.unmodifiableList( + Arrays.asList(RATE_POINT_MISS, RATE_TASK_CLOSED_LOOP)); + /** 告警级别名目(固定顺序,含 0 值) */ + public static final List ALARM_LEVELS = Collections.unmodifiableList( + Arrays.asList("预警", "一般", "严重", "危急")); + /** 缺陷级别名目(固定顺序,含 0 值) */ + public static final List DEFECT_LEVELS = Collections.unmodifiableList( + Arrays.asList("一般缺陷", "严重缺陷", "危急缺陷")); +} diff --git a/inspect-main/inspect-main-statistics/src/main/java/com/inspect/statistics/controller/PatrolStatisticsController.java b/inspect-main/inspect-main-statistics/src/main/java/com/inspect/statistics/controller/PatrolStatisticsController.java index af577f5..050192e 100644 --- a/inspect-main/inspect-main-statistics/src/main/java/com/inspect/statistics/controller/PatrolStatisticsController.java +++ b/inspect-main/inspect-main-statistics/src/main/java/com/inspect/statistics/controller/PatrolStatisticsController.java @@ -15,6 +15,7 @@ import com.inspect.common.log.annotation.Log; import com.inspect.common.log.enums.BizType; import com.inspect.message.MessageUtils; import com.inspect.statistics.domain.StatisticsPatrolResult; +import com.inspect.statistics.domain.vo.ScreenResultVo; import com.inspect.statistics.service.PatrolStatisticsService; import java.io.IOException; @@ -32,6 +33,7 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController @@ -283,4 +285,81 @@ public class PatrolStatisticsController extends BaseController { HashMap colMap = this.statisticsService.colMap(); return AjaxResult.success(colMap); } + + /* + * ==================== 巡视大屏统计接口(/patrol 为网关前缀,本服务内路径为 /statistics/xxx) ==================== + */ + + /** + * 大屏-巡视设备统计:摄像机/机器人/无人机的总数、在线数、离线数。 + */ + @GetMapping({"/device-stats"}) + public AjaxResult deviceStats() { + return this.screenResult(this.statisticsService.deviceStats()); + } + + /** + * 大屏-机器人/无人工况:连续运行天数、巡检天数、巡检出勤率。 + */ + @GetMapping({"/robot-drone-status"}) + public AjaxResult robotDroneStatus() { + return this.screenResult(this.statisticsService.robotDroneStatus()); + } + + /** + * 大屏-摄像机统计:总数、在线/离线占比、类型分布、光谱分布。 + */ + @GetMapping({"/camera-stats"}) + public AjaxResult cameraStats() { + return this.screenResult(this.statisticsService.cameraStats()); + } + + /** + * 大屏-巡视任务统计:巡视类型、巡视率指标、巡视状态。 + * + * @param period 时间维度 day/week/month/year,默认 day + */ + @GetMapping({"/task-stats"}) + public AjaxResult taskStats(@RequestParam(value = "period", required = false) String period) { + return this.screenResult(this.statisticsService.taskStats(period)); + } + + /** + * 大屏-人工确认缺陷统计:严重度分布、审核相关完成率。 + * + * @param period 时间维度 day/week/month/year,默认 day + */ + @GetMapping({"/manual-confirm-defect"}) + public AjaxResult manualConfirmDefect(@RequestParam(value = "period", required = false) String period) { + return this.screenResult(this.statisticsService.manualConfirmDefect(period)); + } + + /** + * 大屏-巡视告警统计:按报警级别统计。 + * + * @param period 时间维度 day/week/month/year,默认 day + */ + @GetMapping({"/alarm-stats"}) + public AjaxResult alarmStats(@RequestParam(value = "period", required = false) String period) { + return this.screenResult(this.statisticsService.alarmStats(period)); + } + + /** + * 大屏-巡视缺陷统计:按缺陷级别统计。 + * + * @param period 时间维度 day/week/month/year,默认 day + */ + @GetMapping({"/defect-stats"}) + public AjaxResult defectStats(@RequestParam(value = "period", required = false) String period) { + return this.screenResult(this.statisticsService.defectStats(period)); + } + + /** + * 大屏统计统一返回:AjaxResult 外层 + 顶层 updateTime(数据实际生成时间)。 + */ + private AjaxResult screenResult(ScreenResultVo screenResult) { + AjaxResult ajaxResult = AjaxResult.success(screenResult.getData()); + ajaxResult.put("updateTime", screenResult.getUpdateTime()); + return ajaxResult; + } } diff --git a/inspect-main/inspect-main-statistics/src/main/java/com/inspect/statistics/domain/DeviceOnlineEvent.java b/inspect-main/inspect-main-statistics/src/main/java/com/inspect/statistics/domain/DeviceOnlineEvent.java new file mode 100644 index 0000000..b6d63af --- /dev/null +++ b/inspect-main/inspect-main-statistics/src/main/java/com/inspect/statistics/domain/DeviceOnlineEvent.java @@ -0,0 +1,23 @@ +package com.inspect.statistics.domain; + +import lombok.Getter; +import lombok.Setter; + +import java.util.Date; + +/** + * 巡视设备在线/离线事件,对应 basedata_mont_patdev_online。 + * + *

该表记录的是状态变化事件(在线事件、离线事件各一条),不是按时间采样的状态快照, + * 因此连续运行天数需要由事件还原在线区间后再统计。

+ */ +@Setter +@Getter +public class DeviceOnlineEvent { + /** 设备台账ID,对应 basedata_mont_patdev_online.eqp_book_id */ + private Long eqpBookId; + /** 状态:1 在线、0 离线 */ + private String status; + /** 事件时间,对应 basedata_mont_patdev_online.action_time */ + private Date actionTime; +} diff --git a/inspect-main/inspect-main-statistics/src/main/java/com/inspect/statistics/domain/PatrolScreenCount.java b/inspect-main/inspect-main-statistics/src/main/java/com/inspect/statistics/domain/PatrolScreenCount.java new file mode 100644 index 0000000..9f88074 --- /dev/null +++ b/inspect-main/inspect-main-statistics/src/main/java/com/inspect/statistics/domain/PatrolScreenCount.java @@ -0,0 +1,18 @@ +package com.inspect.statistics.domain; + +import lombok.Getter; +import lombok.Setter; + +/** + * 巡视大屏分组计数结果:按某个编码字段 group by 得到的「编码 - 数量」。 + * + *

用于巡视类型、巡视状态、告警级别、缺陷级别等固定名目统计,业务侧再把编码翻译成名目。

+ */ +@Setter +@Getter +public class PatrolScreenCount { + /** 分组编码,对应 SQL 的 code 列 */ + private String code; + /** 数量,对应 SQL 的 num 列 */ + private Long num; +} diff --git a/inspect-main/inspect-main-statistics/src/main/java/com/inspect/statistics/domain/PatrolScreenDict.java b/inspect-main/inspect-main-statistics/src/main/java/com/inspect/statistics/domain/PatrolScreenDict.java new file mode 100644 index 0000000..5b461df --- /dev/null +++ b/inspect-main/inspect-main-statistics/src/main/java/com/inspect/statistics/domain/PatrolScreenDict.java @@ -0,0 +1,18 @@ +package com.inspect.statistics.domain; + +import lombok.Getter; +import lombok.Setter; + +/** + * 巡视大屏字典项:对应 sys_dict_data 的字典值(dict_value)与字典名称(dict_label)。 + * + *

大屏的类型分布等名目需要跟随字典配置变化,故运行时从字典表读取,不写死在代码里。

+ */ +@Setter +@Getter +public class PatrolScreenDict { + /** 字典值,对应 SQL 的 code 列(dict_value) */ + private String code; + /** 字典名称,对应 SQL 的 name 列(dict_label) */ + private String name; +} diff --git a/inspect-main/inspect-main-statistics/src/main/java/com/inspect/statistics/domain/PatrolScreenRate.java b/inspect-main/inspect-main-statistics/src/main/java/com/inspect/statistics/domain/PatrolScreenRate.java new file mode 100644 index 0000000..e9e3b32 --- /dev/null +++ b/inspect-main/inspect-main-statistics/src/main/java/com/inspect/statistics/domain/PatrolScreenRate.java @@ -0,0 +1,19 @@ +package com.inspect.statistics.domain; + +import lombok.Getter; +import lombok.Setter; + +/** + * 巡视大屏比率取数结果:总数与有效数。 + * + *

口径与 inspect-main-client 的 {@code ResDomain} 保持一致(totalNum / validNum), + * 便于直接复制既有客户端统计 SQL。

+ */ +@Setter +@Getter +public class PatrolScreenRate { + /** 总数,对应 SQL 的 totalNum 列 */ + private Integer totalNum; + /** 有效数,对应 SQL 的 validNum 列 */ + private Integer validNum; +} diff --git a/inspect-main/inspect-main-statistics/src/main/java/com/inspect/statistics/domain/vo/AlarmStatsVo.java b/inspect-main/inspect-main-statistics/src/main/java/com/inspect/statistics/domain/vo/AlarmStatsVo.java new file mode 100644 index 0000000..23269eb --- /dev/null +++ b/inspect-main/inspect-main-statistics/src/main/java/com/inspect/statistics/domain/vo/AlarmStatsVo.java @@ -0,0 +1,19 @@ +package com.inspect.statistics.domain.vo; + +import lombok.Getter; +import lombok.Setter; + +import java.util.ArrayList; +import java.util.List; + +/** + * 接口6 巡视告警统计:按报警级别(预警、一般、严重、危急)统计。 + */ +@Setter +@Getter +public class AlarmStatsVo { + /** 当前查询时间维度 */ + private String period; + /** 各级别统计,固定返回全部级别(含 0 值) */ + private List items = new ArrayList<>(); +} diff --git a/inspect-main/inspect-main-statistics/src/main/java/com/inspect/statistics/domain/vo/CameraStatsVo.java b/inspect-main/inspect-main-statistics/src/main/java/com/inspect/statistics/domain/vo/CameraStatsVo.java new file mode 100644 index 0000000..f1d805c --- /dev/null +++ b/inspect-main/inspect-main-statistics/src/main/java/com/inspect/statistics/domain/vo/CameraStatsVo.java @@ -0,0 +1,34 @@ +package com.inspect.statistics.domain.vo; + +import lombok.Getter; +import lombok.Setter; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * 接口3 摄像机统计:摄像机详情。 + * + *

total/online/offline 复用 {@link DeviceStatsVo} 中 camera 的同一口径, + * 避免设备总览与摄像机详情两处统计口径漂移。

+ */ +@Setter +@Getter +public class CameraStatsVo extends DeviceOnlineStatVo { + /** 在线率(%),保留 2 位小数 */ + private Double onlineRate = 0D; + /** 离线率(%),保留 2 位小数 */ + private Double offlineRate = 0D; + /** + * 类型分布:key 为字典名称(sys_dict_data.dict_type = use_mode 的 dict_label),value 为摄像机通道数。 + * + *

类型名目跟随字典配置,不固定为三种,故用有序 Map 承载,并按字典顺序返回(含 0 值)。

+ */ + private Map typeDistribution = new LinkedHashMap<>(); + /** + * 光谱分布:key 为通道类型字典名称(sys_dict_data.dict_type = channel_type 的 dict_label),value 为摄像机通道数。 + * + *

名目同样跟随字典配置,按字典顺序返回(含 0 值),末项为兜底名目“其他”。

+ */ + private Map spectrumDistribution = new LinkedHashMap<>(); +} diff --git a/inspect-main/inspect-main-statistics/src/main/java/com/inspect/statistics/domain/vo/DefectStatsVo.java b/inspect-main/inspect-main-statistics/src/main/java/com/inspect/statistics/domain/vo/DefectStatsVo.java new file mode 100644 index 0000000..d8e0e6d --- /dev/null +++ b/inspect-main/inspect-main-statistics/src/main/java/com/inspect/statistics/domain/vo/DefectStatsVo.java @@ -0,0 +1,19 @@ +package com.inspect.statistics.domain.vo; + +import lombok.Getter; +import lombok.Setter; + +import java.util.ArrayList; +import java.util.List; + +/** + * 接口7 巡视缺陷统计:按缺陷级别(一般缺陷、严重缺陷、危急缺陷)统计。 + */ +@Setter +@Getter +public class DefectStatsVo { + /** 当前查询时间维度 */ + private String period; + /** 各级别统计,固定返回全部级别(含 0 值) */ + private List items = new ArrayList<>(); +} diff --git a/inspect-main/inspect-main-statistics/src/main/java/com/inspect/statistics/domain/vo/DeviceOnlineStatVo.java b/inspect-main/inspect-main-statistics/src/main/java/com/inspect/statistics/domain/vo/DeviceOnlineStatVo.java new file mode 100644 index 0000000..6738b70 --- /dev/null +++ b/inspect-main/inspect-main-statistics/src/main/java/com/inspect/statistics/domain/vo/DeviceOnlineStatVo.java @@ -0,0 +1,21 @@ +package com.inspect.statistics.domain.vo; + +import lombok.Getter; +import lombok.Setter; + +/** + * 设备在线统计:总数、在线数、离线数。 + * + *

设备总览({@link DeviceStatsVo})与摄像机统计({@link CameraStatsVo})共用该结构, + * 保证同一口径只统计一次。

+ */ +@Setter +@Getter +public class DeviceOnlineStatVo { + /** 总数 */ + private Long total = 0L; + /** 在线数 */ + private Long online = 0L; + /** 离线数 */ + private Long offline = 0L; +} diff --git a/inspect-main/inspect-main-statistics/src/main/java/com/inspect/statistics/domain/vo/DeviceStatsVo.java b/inspect-main/inspect-main-statistics/src/main/java/com/inspect/statistics/domain/vo/DeviceStatsVo.java new file mode 100644 index 0000000..8a55146 --- /dev/null +++ b/inspect-main/inspect-main-statistics/src/main/java/com/inspect/statistics/domain/vo/DeviceStatsVo.java @@ -0,0 +1,17 @@ +package com.inspect.statistics.domain.vo; + +import lombok.Getter; +import lombok.Setter; + +/** + * 接口1 巡视设备统计:设备总览。 + * + *

只承载各类型设备的在线总览;摄像机明细(类型分布、光谱分布)见 {@link CameraStatsVo}。

+ */ +@Setter +@Getter +public class DeviceStatsVo { + private DeviceOnlineStatVo camera = new DeviceOnlineStatVo(); + private DeviceOnlineStatVo robot = new DeviceOnlineStatVo(); + private DeviceOnlineStatVo drone = new DeviceOnlineStatVo(); +} diff --git a/inspect-main/inspect-main-statistics/src/main/java/com/inspect/statistics/domain/vo/LevelStatVo.java b/inspect-main/inspect-main-statistics/src/main/java/com/inspect/statistics/domain/vo/LevelStatVo.java new file mode 100644 index 0000000..54f95ec --- /dev/null +++ b/inspect-main/inspect-main-statistics/src/main/java/com/inspect/statistics/domain/vo/LevelStatVo.java @@ -0,0 +1,18 @@ +package com.inspect.statistics.domain.vo; + +import lombok.Getter; +import lombok.Setter; + +/** + * 级别统计项:告警级别、缺陷级别共用,结构完全一致故不重复定义。 + */ +@Setter +@Getter +public class LevelStatVo { + /** 级别名称 */ + private String level; + /** 数量 */ + private Long count = 0L; + /** 占比(%),保留 2 位小数 */ + private Double percent = 0D; +} diff --git a/inspect-main/inspect-main-statistics/src/main/java/com/inspect/statistics/domain/vo/ManualConfirmDefectVo.java b/inspect-main/inspect-main-statistics/src/main/java/com/inspect/statistics/domain/vo/ManualConfirmDefectVo.java new file mode 100644 index 0000000..8abff11 --- /dev/null +++ b/inspect-main/inspect-main-statistics/src/main/java/com/inspect/statistics/domain/vo/ManualConfirmDefectVo.java @@ -0,0 +1,46 @@ +package com.inspect.statistics.domain.vo; + +import lombok.Getter; +import lombok.Setter; + +/** + * 接口5 人工确认缺陷统计:严重度分布 + 审核相关完成率。 + */ +@Setter +@Getter +public class ManualConfirmDefectVo { + /** 当前查询时间维度 */ + private String period; + /** 人工确认缺陷严重度分布 */ + private Level level = new Level(); + /** 审核相关完成率 */ + private Rates rates = new Rates(); + + /** + * 严重度分布。 + */ + @Setter + @Getter + public static class Level { + /** 高严重度 */ + private Long high = 0L; + /** 中严重度 */ + private Long medium = 0L; + /** 低严重度 */ + private Long low = 0L; + } + + /** + * 审核相关完成率。 + */ + @Setter + @Getter + public static class Rates { + /** 巡视告警人工审核完成率(%) */ + private Double alarmManualReviewRate = 0D; + /** 巡视告警准确率(%) */ + private Double alarmAccuracyRate = 0D; + /** 巡视结果人工审核完成率(%) */ + private Double resultManualReviewRate = 0D; + } +} diff --git a/inspect-main/inspect-main-statistics/src/main/java/com/inspect/statistics/domain/vo/RobotDroneStatusVo.java b/inspect-main/inspect-main-statistics/src/main/java/com/inspect/statistics/domain/vo/RobotDroneStatusVo.java new file mode 100644 index 0000000..adabe04 --- /dev/null +++ b/inspect-main/inspect-main-statistics/src/main/java/com/inspect/statistics/domain/vo/RobotDroneStatusVo.java @@ -0,0 +1,28 @@ +package com.inspect.statistics.domain.vo; + +import lombok.Getter; +import lombok.Setter; + +/** + * 接口2 机器人/无人工况:连续运行天数、巡检天数、巡检出勤率。 + */ +@Setter +@Getter +public class RobotDroneStatusVo { + private Item robot = new Item(); + private Item drone = new Item(); + + /** + * 单个设备的运行工况。 + */ + @Setter + @Getter + public static class Item { + /** 连续运行天数 */ + private Integer runDays = 0; + /** 巡检天数 */ + private Integer patrolDays = 0; + /** 巡检出勤率(%),保留 2 位小数 */ + private Double attendanceRate = 0D; + } +} diff --git a/inspect-main/inspect-main-statistics/src/main/java/com/inspect/statistics/domain/vo/ScreenResultVo.java b/inspect-main/inspect-main-statistics/src/main/java/com/inspect/statistics/domain/vo/ScreenResultVo.java new file mode 100644 index 0000000..d827e25 --- /dev/null +++ b/inspect-main/inspect-main-statistics/src/main/java/com/inspect/statistics/domain/vo/ScreenResultVo.java @@ -0,0 +1,27 @@ +package com.inspect.statistics.domain.vo; + +import lombok.Getter; +import lombok.Setter; + +/** + * 大屏统计接口统一返回载体:业务数据 + 数据统计时间。 + * + *

updateTime 为该份数据的实际生成时间;命中 Redis 缓存时保持为缓存生成时间, + * 前端可据此判断数据新鲜度。

+ * + * @param 业务数据类型 + */ +@Setter +@Getter +public class ScreenResultVo { + private T data; + private String updateTime; + + public ScreenResultVo() { + } + + public ScreenResultVo(T data, String updateTime) { + this.data = data; + this.updateTime = updateTime; + } +} diff --git a/inspect-main/inspect-main-statistics/src/main/java/com/inspect/statistics/domain/vo/TaskStatsVo.java b/inspect-main/inspect-main-statistics/src/main/java/com/inspect/statistics/domain/vo/TaskStatsVo.java new file mode 100644 index 0000000..22a9892 --- /dev/null +++ b/inspect-main/inspect-main-statistics/src/main/java/com/inspect/statistics/domain/vo/TaskStatsVo.java @@ -0,0 +1,65 @@ +package com.inspect.statistics.domain.vo; + +import lombok.Getter; +import lombok.Setter; + +import java.util.ArrayList; +import java.util.List; + +/** + * 接口4 巡视任务统计:巡视类型统计 + 巡视率指标 + 巡视状态统计。 + * + *

率指标(漏检率、闭环率)与数量类指标拆开返回,前端无需按名称硬编码区分。

+ */ +@Setter +@Getter +public class TaskStatsVo { + /** 当前查询时间维度 */ + private String period; + /** 巡视类型统计(数量项) */ + private List typeStats = new ArrayList<>(); + /** 巡视率指标 */ + private List rateStats = new ArrayList<>(); + /** 巡视状态统计 */ + private List statusStats = new ArrayList<>(); + + /** + * 巡视类型统计项。 + */ + @Setter + @Getter + public static class TypeStat { + /** 巡视类型名称 */ + private String type; + /** 数量 */ + private Long count = 0L; + /** 占比(%),保留 2 位小数 */ + private Double percent = 0D; + } + + /** + * 巡视率指标项。 + */ + @Setter + @Getter + public static class RateStat { + /** 率指标名称 */ + private String name; + /** 比率(%),保留 2 位小数 */ + private Double rate = 0D; + } + + /** + * 巡视状态统计项。 + */ + @Setter + @Getter + public static class StatusStat { + /** 状态名称 */ + private String status; + /** 数量 */ + private Long count = 0L; + /** 占比(%),保留 2 位小数 */ + private Double percent = 0D; + } +} diff --git a/inspect-main/inspect-main-statistics/src/main/java/com/inspect/statistics/mapper/PatrolScreenMapper.java b/inspect-main/inspect-main-statistics/src/main/java/com/inspect/statistics/mapper/PatrolScreenMapper.java new file mode 100644 index 0000000..e32f297 --- /dev/null +++ b/inspect-main/inspect-main-statistics/src/main/java/com/inspect/statistics/mapper/PatrolScreenMapper.java @@ -0,0 +1,201 @@ +package com.inspect.statistics.mapper; + +import com.inspect.statistics.domain.DeviceOnlineEvent; +import com.inspect.statistics.domain.PatrolScreenCount; +import com.inspect.statistics.domain.PatrolScreenDict; +import com.inspect.statistics.domain.PatrolScreenRate; + +import java.util.List; + +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +/** + * 巡视大屏设备类统计的数据访问。 + * + *

设备台账(basedata_eqpbook)可复用 {@code IPatrolResultService#selectBasedataEqpBookList}, + * 但摄像机通道表在其它模块中的查询无法从本模块直接引用,故大屏自持该查询。

+ */ +@Mapper +public interface PatrolScreenMapper { + + /** + * 查询指定设备类型下全部未删除通道的通道编码(摄像机在线状态按通道口径统计)。 + * + * @param eqpBookType basedata_eqpbook.type,摄像机为 10 + * @return 通道编码列表 + */ + List selectCameraChannelCodes(@Param("eqpBookType") String eqpBookType); + + /** + * 查询字典项(字典值 + 字典名称),按字典顺序返回。 + * + * @param dictType sys_dict_data.dict_type + * @return 字典项列表 + */ + List selectDictData(@Param("dictType") String dictType); + + /** + * 摄像机类型分布:type = 10 摄像机名下未删除通道按通道自身 use_mode 分组计数(通道口径)。 + * + * @param eqpBookType basedata_eqpbook.type,摄像机为 10 + * @return 「use_mode - 通道数」列表 + */ + List selectCameraUseModeCounts(@Param("eqpBookType") String eqpBookType); + + /** + * 摄像机类型分布兜底计数:use_mode 未配置(为空)或取值不在 use_mode 字典内的通道, + * 统一计入大屏名目“其他”。 + * + * @param eqpBookType basedata_eqpbook.type,摄像机为 10 + * @param dictType sys_dict_data.dict_type,摄像机使用类型为 use_mode + * @return 未归类通道数 + */ + Long selectCameraOtherTypeCount(@Param("eqpBookType") String eqpBookType, + @Param("dictType") String dictType); + + /** + * 摄像机光谱分布:type = 10 摄像机名下未删除通道按通道自身 channel_type 分组计数(通道口径)。 + * + *

名目取值跟随 sys_dict_data 的 dict_type = channel_type 配置,不写死取值。

+ * + * @param eqpBookType basedata_eqpbook.type,摄像机为 10 + * @return 「channel_type - 通道数」列表 + */ + List selectCameraChannelTypeCounts(@Param("eqpBookType") String eqpBookType); + + /** + * 摄像机光谱分布兜底计数:channel_type 未配置(为空)或取值不在 channel_type 字典内的通道, + * 统一计入大屏名目“其他”。 + * + * @param eqpBookType basedata_eqpbook.type,摄像机为 10 + * @param dictType sys_dict_data.dict_type,通道类型为 channel_type + * @return 未归类通道数 + */ + Long selectCameraOtherChannelTypeCount(@Param("eqpBookType") String eqpBookType, + @Param("dictType") String dictType); + + /** + * 查询指定设备类型下未删除设备的在线/离线事件,按设备、事件时间升序返回。 + * + * @param eqpBookTypes basedata_eqpbook.type 取值集合 + * @param onlineSince 事件时间下限(yyyy-MM-dd),限制回溯窗口 + * @return 在线/离线事件列表 + */ + List selectDeviceOnlineEvents(@Param("eqpBookTypes") List eqpBookTypes, + @Param("onlineSince") String onlineSince); + + /** + * 查询属于指定 dev_type 的巡检执行日期(去重),用于巡检天数统计。 + * + * @param devType patrol_task.dev_type 取值,逗号分隔多值用 find_in_set 匹配 + * @return 日期字符串(yyyy-MM-dd)列表 + */ + List selectPatrolDays(@Param("devType") String devType); + + /** + * 巡视类型数量:按“已执行任务”统计——patrol_task_status 内联 patrol_task, + * 按 task_patrolled_id 去重后按 patrol_task.type 分组,与巡视状态统计同源。 + * + * @param beginTime 统计开始时间(任务开始时间 start_time) + * @param endTime 统计结束时间 + * @param excludeTypes 需排除的 patrol_task.type 取值(联动 5、静默 6) + * @return 「巡视类型编码 - 数量」列表 + */ + List selectTaskTypeCounts(@Param("beginTime") String beginTime, + @Param("endTime") String endTime, + @Param("excludeTypes") List excludeTypes); + + /** + * 巡视状态数量:patrol_task_status 按 task_state 分组, + * 按巡视任务执行ID task_patrolled_id 去重计数(一条执行实例计一次)。 + * + * @param beginTime 统计开始时间 + * @param endTime 统计结束时间 + * @param excludeTypes 需排除的 patrol_task.type 取值(联动 5、静默 6) + * @return 「任务状态编码 - 数量」列表 + */ + List selectTaskStatusCounts(@Param("beginTime") String beginTime, + @Param("endTime") String endTime, + @Param("excludeTypes") List excludeTypes); + + /** + * 任务终止数取数:口径复制自 inspect-main-client 的 ClientStatisticsMapper#queryTaskDone, + * totalNum 为 patrol_task_status 任务总数,validNum 为终止任务数(task_state = 4)。 + * + *

大屏“任务执行闭环率”按名称字面口径取反:闭环率 = (totalNum - validNum) / totalNum。

+ * + * @param beginTime 统计开始时间 + * @param endTime 统计结束时间 + * @return 总数与终止数 + */ + PatrolScreenRate selectTaskDoneRate(@Param("beginTime") String beginTime, + @Param("endTime") String endTime); + + /** + * 巡检点位覆盖数取数:口径复制自 inspect-main-client 的 ClientStatisticsMapper#queryPointNum, + * totalNum 为巡检点位总数,validNum 为已巡检点位数量(patrol_result 出现过的点位)。 + * + *

大屏“巡视点位漏检率”按名称字面口径取反:漏检率 = (totalNum - validNum) / totalNum。

+ * + * @param beginTime 统计开始时间 + * @param endTime 统计结束时间 + * @return 总数与已巡检数 + */ + PatrolScreenRate selectPointNumRate(@Param("beginTime") String beginTime, + @Param("endTime") String endTime); + + /** + * 巡视告警人工审核完成率取数:口径复制自 inspect-main-client 的 ClientStatisticsMapper#queryPersonExamine。 + * + * @param beginTime 统计开始时间 + * @param endTime 统计结束时间 + * @return 总数与有效数 + */ + PatrolScreenRate selectAlarmReviewRate(@Param("beginTime") String beginTime, + @Param("endTime") String endTime); + + /** + * 巡视告警准确率取数:口径复制自 inspect-main-client 的 ClientStatisticsMapper#queryWarning。 + * + * @param beginTime 统计开始时间 + * @param endTime 统计结束时间 + * @return 总数与有效数 + */ + PatrolScreenRate selectAlarmAccuracyRate(@Param("beginTime") String beginTime, + @Param("endTime") String endTime); + + /** + * 巡视结果人工审核完成率取数:口径复制自 inspect-main-client 的 ClientStatisticsMapper#queryResultPersonExamine。 + * + * @param beginTime 统计开始时间 + * @param endTime 统计结束时间 + * @return 总数与有效数 + */ + PatrolScreenRate selectResultReviewRate(@Param("beginTime") String beginTime, + @Param("endTime") String endTime); + + /** + * 告警记录数:result_analysis 中 result_type = 2 的记录。 + * + *

本地 result_analysis 无告警级别字段,该批记录统一按大屏最低级别「预警」计入。

+ * + * @param beginTime 统计开始时间 + * @param endTime 统计结束时间 + * @return 告警记录数 + */ + Long selectAlarmCount(@Param("beginTime") String beginTime, + @Param("endTime") String endTime); + + /** + * 缺陷数量:缺陷记录取 result_analysis 中 result_type = 0 的记录,时间取 create_time。 + * + *

本地结果表没有缺陷等级字段(等级由站端判定,只落在同步镜像表),故只统计总量、不按等级分组。

+ * + * @param beginTime 统计开始时间 + * @param endTime 统计结束时间 + * @return 缺陷数量 + */ + Long selectDefectCount(@Param("beginTime") String beginTime, + @Param("endTime") String endTime); +} diff --git a/inspect-main/inspect-main-statistics/src/main/java/com/inspect/statistics/service/PatrolStatisticsService.java b/inspect-main/inspect-main-statistics/src/main/java/com/inspect/statistics/service/PatrolStatisticsService.java index 638d47c..8570a1e 100644 --- a/inspect-main/inspect-main-statistics/src/main/java/com/inspect/statistics/service/PatrolStatisticsService.java +++ b/inspect-main/inspect-main-statistics/src/main/java/com/inspect/statistics/service/PatrolStatisticsService.java @@ -3,6 +3,14 @@ package com.inspect.statistics.service; import com.alibaba.fastjson.JSONArray; import com.inspect.base.core.web.domain.AjaxResult; import com.inspect.statistics.domain.StatisticsPatrolResult; +import com.inspect.statistics.domain.vo.AlarmStatsVo; +import com.inspect.statistics.domain.vo.CameraStatsVo; +import com.inspect.statistics.domain.vo.DefectStatsVo; +import com.inspect.statistics.domain.vo.DeviceStatsVo; +import com.inspect.statistics.domain.vo.ManualConfirmDefectVo; +import com.inspect.statistics.domain.vo.RobotDroneStatusVo; +import com.inspect.statistics.domain.vo.ScreenResultVo; +import com.inspect.statistics.domain.vo.TaskStatsVo; import java.io.IOException; import java.util.HashMap; @@ -30,4 +38,22 @@ public interface PatrolStatisticsService { void configExport(List resultList, StatisticsPatrolResult result, HttpServletResponse response) throws IOException; HashMap colMap(); + + /* + * 以下为「巡视大屏」统计接口,统计口径见 docs/巡视大屏独立接口文档.md。 + * period 取值 day/week/month/year,仅 deviceStats/robotDroneStatus/cameraStats 无该参数。 + */ + ScreenResultVo deviceStats(); + + ScreenResultVo robotDroneStatus(); + + ScreenResultVo cameraStats(); + + ScreenResultVo taskStats(String period); + + ScreenResultVo manualConfirmDefect(String period); + + ScreenResultVo alarmStats(String period); + + ScreenResultVo defectStats(String period); } diff --git a/inspect-main/inspect-main-statistics/src/main/java/com/inspect/statistics/service/impl/PatrolStatisticsServiceImpl.java b/inspect-main/inspect-main-statistics/src/main/java/com/inspect/statistics/service/impl/PatrolStatisticsServiceImpl.java index 5ea9edd..fbea17a 100644 --- a/inspect-main/inspect-main-statistics/src/main/java/com/inspect/statistics/service/impl/PatrolStatisticsServiceImpl.java +++ b/inspect-main/inspect-main-statistics/src/main/java/com/inspect/statistics/service/impl/PatrolStatisticsServiceImpl.java @@ -4,20 +4,53 @@ import cn.hutool.poi.excel.ExcelWriter; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.alibaba.nacos.common.utils.CollectionUtils; +import com.inspect.base.core.constant.MontCacheConstants; import com.inspect.base.core.exception.ServiceException; +import com.inspect.base.core.utils.DateUtils; import com.inspect.base.core.utils.StringUtils; import com.inspect.base.core.web.domain.AjaxResult; +import com.inspect.base.redis.service.RedisService; +import com.inspect.partrolresult.service.IPatrolResultService; +import com.inspect.statistics.constant.PatrolScreenConst; +import com.inspect.statistics.domain.DeviceOnlineEvent; +import com.inspect.statistics.domain.PatrolScreenCount; +import com.inspect.statistics.domain.PatrolScreenDict; +import com.inspect.statistics.domain.PatrolScreenRate; import com.inspect.statistics.domain.StatisticsPatrolResult; +import com.inspect.statistics.domain.vo.AlarmStatsVo; +import com.inspect.statistics.domain.vo.CameraStatsVo; +import com.inspect.statistics.domain.vo.DefectStatsVo; +import com.inspect.statistics.domain.vo.DeviceOnlineStatVo; +import com.inspect.statistics.domain.vo.DeviceStatsVo; +import com.inspect.statistics.domain.vo.LevelStatVo; +import com.inspect.statistics.domain.vo.ManualConfirmDefectVo; +import com.inspect.statistics.domain.vo.RobotDroneStatusVo; +import com.inspect.statistics.domain.vo.ScreenResultVo; +import com.inspect.statistics.domain.vo.TaskStatsVo; +import com.inspect.statistics.mapper.PatrolScreenMapper; import com.inspect.statistics.mapper.StatisticsPatrolResultMapper; import com.inspect.statistics.service.PatrolStatisticsService; +import com.inspect.system.base.openDomain.BasedataEqpBookMoMain; import java.io.IOException; +import java.math.BigDecimal; +import java.math.RoundingMode; import java.net.URLEncoder; +import java.time.DayOfWeek; +import java.time.LocalDate; +import java.time.ZoneId; +import java.util.ArrayList; import java.util.Comparator; +import java.util.Date; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; import java.util.stream.Collectors; import javax.annotation.Resource; import javax.servlet.http.HttpServletResponse; @@ -38,6 +71,15 @@ public class PatrolStatisticsServiceImpl implements PatrolStatisticsService { @Autowired private MessageUtils messageUtils; + @Resource + RedisService redisService; + + @Resource + IPatrolResultService patrolResultService; + + @Resource + PatrolScreenMapper screenMapper; + public List queryPatrolResult(StatisticsPatrolResult result) { return this.resultMapper.queryPatrolResult(result); } @@ -213,4 +255,726 @@ public class PatrolStatisticsServiceImpl implements PatrolStatisticsService { colMap.put("dataType", messageUtils.get("巡视结果数据来源")); return colMap; } + + /* + * ==================== 巡视大屏统计接口(口径见 docs/巡视大屏独立接口文档.md) ==================== + */ + + @Override + public ScreenResultVo deviceStats() { + return this.loadWithCache(PatrolScreenConst.CACHE_DEVICE_STATS, PatrolScreenConst.TTL_LOW_FREQUENCY, + this::countDeviceStats); + } + + /** + * 巡视设备统计口径:basedata_eqpbook.type 取 1/2/3(机器人)、13(无人机)、10(摄像机); + * 机器人与无人机按设备编码判定在线,摄像机按通道口径统计(见 countCamera),已删除设备不参与统计。 + */ + private DeviceStatsVo countDeviceStats() { + DeviceStatsVo deviceStats = new DeviceStatsVo(); + List eqpBooks = this.patrolResultService + .selectBasedataEqpBookList(new BasedataEqpBookMoMain()); + if (eqpBooks != null) { + for (BasedataEqpBookMoMain eqpBook : eqpBooks) { + if (!PatrolScreenConst.FLAG_NORMAL.equals(eqpBook.getDelFlag())) { + continue; + } + DeviceOnlineStatVo deviceCategory = robotOrDroneCategory(deviceStats, eqpBook.getType()); + if (deviceCategory == null) { + continue; + } + appendDevice(deviceCategory, this.isDeviceOnline(eqpBook.getPatrolDeviceCode())); + } + } + this.countCamera(deviceStats.getCamera()); + return deviceStats; + } + + /** + * 摄像机统计:type=10 的摄像机按 basedata_eqpbook_channel 通道统计, + * 通道在线判定为 Redis 存在 mont_patrol_device_channel_online_status:{channelCode}。 + */ + private void countCamera(DeviceOnlineStatVo camera) { + List channelCodes = this.screenMapper.selectCameraChannelCodes(PatrolScreenConst.DEVICE_TYPE_CAMERA); + if (channelCodes == null || channelCodes.isEmpty()) { + return; + } + for (String channelCode : channelCodes) { + appendDevice(camera, this.isChannelOnline(channelCode)); + } + } + + /** + * 按 basedata_eqpbook.type 定位机器人与无人机分组,其余类型(含摄像机)返回 null(不计入本方法统计)。 + */ + private static DeviceOnlineStatVo robotOrDroneCategory(DeviceStatsVo deviceStats, String type) { + if (PatrolScreenConst.DEVICE_TYPE_DRONE.equals(type)) { + return deviceStats.getDrone(); + } + if (PatrolScreenConst.DEVICE_TYPE_ROBOTS.contains(type)) { + return deviceStats.getRobot(); + } + return null; + } + + /** + * 累加一台设备(或一个通道)的总数以及在线的在线数/离线数。 + */ + private static void appendDevice(DeviceOnlineStatVo deviceCategory, boolean online) { + deviceCategory.setTotal(deviceCategory.getTotal() + 1L); + if (online) { + deviceCategory.setOnline(deviceCategory.getOnline() + 1L); + } else { + deviceCategory.setOffline(deviceCategory.getOffline() + 1L); + } + } + + /** + * 设备在线判定:Redis 存在 mont_patrol_device_online_status:{patrolDeviceCode} 即在线。 + */ + private boolean isDeviceOnline(String patrolDeviceCode) { + return this.hasOnlineKey(MontCacheConstants.MONT_PATROL_DEVICE_ONLINE_STATUS, patrolDeviceCode); + } + + /** + * 通道在线判定:Redis 存在 mont_patrol_device_channel_online_status:{channelCode} 即在线。 + */ + private boolean isChannelOnline(String channelCode) { + return this.hasOnlineKey(MontCacheConstants.MONT_PATROL_DEVICE_CHANNEL_ONLINE_STATUS, channelCode); + } + + /** + * 在线判定通用实现:编码为空按离线;Redis 查询异常按离线处理,避免大屏接口整体失败。 + */ + private boolean hasOnlineKey(String keyPrefix, String code) { + if (StringUtils.isEmpty(code)) { + return false; + } + try { + return Boolean.TRUE.equals(this.redisService.hasKey(keyPrefix + code)); + } catch (Exception e) { + this.logger.warn("[SCREEN] check online status fail, key: {}", keyPrefix + code, e); + return false; + } + } + + @Override + public ScreenResultVo robotDroneStatus() { + return this.loadWithCache(PatrolScreenConst.CACHE_ROBOT_DRONE_STATUS, PatrolScreenConst.TTL_LOW_FREQUENCY, + this::countRobotDroneStatus); + } + + /** + * 机器人/无人工况统计口径: + *
    + *
  1. 设备来源:basedata_eqpbook 中 del_flag = '0' 的机器人(type 1/2/3)与无人机(type 13)。
  2. + *
  3. 巡检天数:patrol_task_status 按 task_code 关联 patrol_task,用 patrol_task.dev_type 归类 + * (1 机器人、0 无人机);dev_type 为逗号分隔多值,用 find_in_set 匹配;排除尚未执行(5)与 + * 超过期限(6)的任务,按自然日去重计数。该口径按设备类型统计,与具体是哪一台设备无关。
  4. + *
  5. 连续运行天数:basedata_mont_patdev_online 记录的是状态变化事件(不是状态快照), + * 先按设备还原在线区间,再折算为“存在在线时刻”的自然日集合,取截至今日的连续天数,同类设备取最大值。
  6. + *
  7. 巡检出勤率 = 巡检天数 / 连续运行天数 * 100,保留 2 位小数;连续运行天数为 0 时返回 0。
  8. + *
+ */ + private RobotDroneStatusVo countRobotDroneStatus() { + RobotDroneStatusVo robotDroneStatus = new RobotDroneStatusVo(); + Map robotDevices = new LinkedHashMap<>(); + Map droneDevices = new LinkedHashMap<>(); + this.collectRobotDroneDevices(robotDevices, droneDevices); + + robotDroneStatus.getRobot().setPatrolDays(this.countPatrolDays(PatrolScreenConst.PATROL_DEV_TYPE_ROBOT)); + robotDroneStatus.getDrone().setPatrolDays(this.countPatrolDays(PatrolScreenConst.PATROL_DEV_TYPE_DRONE)); + + if (!robotDevices.isEmpty() || !droneDevices.isEmpty()) { + Map> eventsByDevice = this.loadDeviceOnlineEvents(); + robotDroneStatus.getRobot().setRunDays(this.maxRunDays(robotDevices, eventsByDevice)); + robotDroneStatus.getDrone().setRunDays(this.maxRunDays(droneDevices, eventsByDevice)); + } + + robotDroneStatus.getRobot().setAttendanceRate(percentage(robotDroneStatus.getRobot().getPatrolDays().longValue(), + robotDroneStatus.getRobot().getRunDays().longValue())); + robotDroneStatus.getDrone().setAttendanceRate(percentage(robotDroneStatus.getDrone().getPatrolDays().longValue(), + robotDroneStatus.getDrone().getRunDays().longValue())); + return robotDroneStatus; + } + + /** + * 收集机器人与无人机台账,key 为设备台账 ID(basedata_mont_patdev_online.eqp_book_id),value 为设备编码。 + */ + private void collectRobotDroneDevices(Map robotDevices, Map droneDevices) { + List eqpBooks = this.patrolResultService + .selectBasedataEqpBookList(new BasedataEqpBookMoMain()); + if (eqpBooks == null) { + return; + } + for (BasedataEqpBookMoMain eqpBook : eqpBooks) { + if (!PatrolScreenConst.FLAG_NORMAL.equals(eqpBook.getDelFlag()) || eqpBook.getEqpBookId() == null) { + continue; + } + if (PatrolScreenConst.DEVICE_TYPE_DRONE.equals(eqpBook.getType())) { + droneDevices.put(eqpBook.getEqpBookId(), eqpBook.getPatrolDeviceCode()); + } else if (PatrolScreenConst.DEVICE_TYPE_ROBOTS.contains(eqpBook.getType())) { + robotDevices.put(eqpBook.getEqpBookId(), eqpBook.getPatrolDeviceCode()); + } + } + } + + /** + * 巡检天数:patrol_task_status 关联 patrol_task 后按 dev_type 归类,统计执行过的不同自然日数量。 + * + *

按确认口径不做任务状态排除,6 种任务状态全部统计。

+ */ + private int countPatrolDays(String devType) { + List patrolDays = this.screenMapper.selectPatrolDays(devType); + return patrolDays == null ? 0 : patrolDays.size(); + } + + /** + * 加载机器人与无人机的在线/离线事件,按设备台账 ID 分组(SQL 已按设备、事件时间升序返回)。 + */ + private Map> loadDeviceOnlineEvents() { + List deviceTypes = new ArrayList<>(PatrolScreenConst.DEVICE_TYPE_ROBOTS); + deviceTypes.add(PatrolScreenConst.DEVICE_TYPE_DRONE); + String onlineSince = LocalDate.now().minusDays(PatrolScreenConst.MAX_RUN_DAYS).toString(); + List events = this.screenMapper.selectDeviceOnlineEvents(deviceTypes, onlineSince); + Map> eventsByDevice = new LinkedHashMap<>(); + if (events == null) { + return eventsByDevice; + } + for (DeviceOnlineEvent event : events) { + if (event.getEqpBookId() == null || event.getActionTime() == null) { + continue; + } + eventsByDevice.computeIfAbsent(event.getEqpBookId(), key -> new ArrayList<>()).add(event); + } + return eventsByDevice; + } + + /** + * 同类设备中连续运行天数的最大值。 + */ + private int maxRunDays(Map devices, Map> eventsByDevice) { + int maxRunDays = 0; + for (Map.Entry device : devices.entrySet()) { + int runDays = this.countRunDays(eventsByDevice.get(device.getKey()), device.getValue()); + if (runDays > maxRunDays) { + maxRunDays = runDays; + } + } + return maxRunDays; + } + + /** + * 单台设备的连续运行天数:把状态变化事件还原成在线区间,折算为存在在线时刻的自然日集合, + * 再取截至今日的连续天数。 + * + *

一天只要发生过在线(含跨天的在线区间),该自然日即视为运行日,与项目内 + * basedata_statis_robotuav_online_day 的“时段内出现过在线即算在线”口径保持一致。

+ */ + private int countRunDays(List events, String patrolDeviceCode) { + if (events == null || events.isEmpty()) { + return 0; + } + Set onlineDays = new HashSet<>(); + LocalDate intervalStart = null; + for (DeviceOnlineEvent event : events) { + LocalDate eventDay = toLocalDate(event.getActionTime()); + if (PatrolScreenConst.ONLINE_STATUS.equals(event.getStatus())) { + if (intervalStart == null) { + intervalStart = eventDay; + } + continue; + } + if (intervalStart != null) { + this.markOnlineDays(onlineDays, intervalStart, eventDay); + intervalStart = null; + } + } + if (intervalStart != null) { + // 末次事件为上线且此后没有下线事件:先用 Redis 现行在线状态确认今天, + // 确认在线则区间延续到今天,否则收敛到该上线事件当天,避免虚增连续天数。 + this.markOnlineDays(onlineDays, intervalStart, + this.isDeviceOnline(patrolDeviceCode) ? LocalDate.now() : intervalStart); + } + return this.countContinuousDays(onlineDays); + } + + /** + * 把在线区间覆盖的自然日全部计入在线日集合。 + */ + private void markOnlineDays(Set onlineDays, LocalDate start, LocalDate end) { + if (start == null || end == null || end.isBefore(start)) { + return; + } + LocalDate day = start; + while (!day.isAfter(end)) { + onlineDays.add(day); + day = day.plusDays(1); + } + } + + /** + * 统计截至今日的连续运行天数:今天不在在线日集合内说明当前未连续运行,返回 0。 + */ + private int countContinuousDays(Set onlineDays) { + if (onlineDays.isEmpty()) { + return 0; + } + int runDays = 0; + LocalDate day = LocalDate.now(); + while (runDays < PatrolScreenConst.MAX_RUN_DAYS && onlineDays.contains(day)) { + runDays++; + day = day.minusDays(1); + } + return runDays; + } + + /** + * 事件时间归属的自然日(服务器时区)。 + */ + private static LocalDate toLocalDate(Date date) { + return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); + } + + @Override + public ScreenResultVo cameraStats() { + return this.loadWithCache(PatrolScreenConst.CACHE_CAMERA_STATS, PatrolScreenConst.TTL_LOW_FREQUENCY, + this::countCameraStats); + } + + /** + * 摄像机统计口径(三项统一按摄像机通道统计,单位都是通道数): + *
    + *
  1. 总数/在线数/离线数与设备总览同源,统一取 deviceStats 的 camera 数据(通道口径),避免两处口径漂移。
  2. + *
  3. 类型分布:type = 10(摄像机)且 del_flag = '0' 的摄像机名下、delete_flag = '0' 的通道, + * 按通道自身 use_mode 分组计数;类型名目取 sys_dict_data 中 dict_type = use_mode 的 dict_label + * (dict_value 对应 use_mode 值),按字典顺序返回;use_mode 未配置或不在字典内的通道统一计入“其他”。
  4. + *
  5. 光谱分布:同一范围内的通道按自身 channel_type 分组计数;名目取 sys_dict_data 中 + * dict_type = channel_type 的 dict_label,按字典顺序返回;channel_type 未配置或不在字典内的通道统一计入“其他”。
  6. + *
+ */ + private CameraStatsVo countCameraStats() { + CameraStatsVo cameraStats = new CameraStatsVo(); + // 摄像机总数/在线数/离线数与设备总览同源,统一取 deviceStats 的 camera 数据,避免两处口径漂移 + DeviceOnlineStatVo camera = this.deviceStats().getData().getCamera(); + cameraStats.setTotal(camera.getTotal()); + cameraStats.setOnline(camera.getOnline()); + cameraStats.setOffline(camera.getOffline()); + cameraStats.setOnlineRate(percentage(camera.getOnline(), camera.getTotal())); + cameraStats.setOfflineRate(percentage(camera.getOffline(), camera.getTotal())); + cameraStats.setTypeDistribution(this.countCameraTypeDistribution()); + cameraStats.setSpectrumDistribution(this.countCameraSpectrumDistribution()); + return cameraStats; + } + + /** + * 摄像机类型分布:名目取自 use_mode 字典(含 0 值),数量按摄像机通道 basedata_eqpbook_channel.use_mode 分组统计; + * use_mode 未配置或不在字典内的通道统一计入兜底名目“其他”。 + */ + private Map countCameraTypeDistribution() { + return this.dictDistribution(PatrolScreenConst.DICT_USE_MODE, + this.toCountMap(this.screenMapper.selectCameraUseModeCounts(PatrolScreenConst.DEVICE_TYPE_CAMERA)), + this.screenMapper.selectCameraOtherTypeCount( + PatrolScreenConst.DEVICE_TYPE_CAMERA, PatrolScreenConst.DICT_USE_MODE)); + } + + /** + * 摄像机光谱分布:名目取自 channel_type 字典(含 0 值),数量按摄像机通道 basedata_eqpbook_channel.channel_type 分组统计; + * channel_type 未配置或不在字典内的通道统一计入兜底名目“其他”。 + */ + private Map countCameraSpectrumDistribution() { + return this.dictDistribution(PatrolScreenConst.DICT_CHANNEL_TYPE, + this.toCountMap(this.screenMapper.selectCameraChannelTypeCounts(PatrolScreenConst.DEVICE_TYPE_CAMERA)), + this.screenMapper.selectCameraOtherChannelTypeCount( + PatrolScreenConst.DEVICE_TYPE_CAMERA, PatrolScreenConst.DICT_CHANNEL_TYPE)); + } + + /** + * 按字典名目组装摄像机通道分布:名目取字典(含 0 值,按字典顺序),数量按字典值回填,末项固定为兜底名目“其他”。 + * + * @param dictType sys_dict_data.dict_type + * @param valueCounts 「字典值 - 数量」映射 + * @param otherCount 取值不在字典内的数量 + */ + private Map dictDistribution(String dictType, Map valueCounts, Long otherCount) { + Map distribution = new LinkedHashMap<>(); + List dictItems = this.screenMapper.selectDictData(dictType); + if (dictItems != null) { + for (PatrolScreenDict dictItem : dictItems) { + if (StringUtils.isEmpty(dictItem.getName())) { + continue; + } + // 字典维护时可能带入首尾空白(如回车),统一去除,避免 JSON key 带控制字符 + distribution.put(dictItem.getName().trim(), this.countOfCode(valueCounts, dictItem.getCode())); + } + } + distribution.put(PatrolScreenConst.CAMERA_OTHER, otherCount == null ? 0L : otherCount); + return distribution; + } + + @Override + public ScreenResultVo taskStats(String period) { + String queryPeriod = this.normalizePeriod(period); + return this.loadWithCache(PatrolScreenConst.CACHE_TASK_STATS + queryPeriod, PatrolScreenConst.TTL_MIDDLE_FREQUENCY, + () -> this.countTaskStats(queryPeriod)); + } + + /** + * 巡视任务统计口径: + *
    + *
  1. 巡视类型:按“已执行任务”统计——patrol_task_status 内联 patrol_task,按 task_patrolled_id 去重后 + * 按 patrol_task.type 分组(1 例行、2 特殊、3 专项、4 自定义),按任务开始时间 start_time 落在时间窗内计数, + * 排除联动(type=5)/静默(type=6)任务。大屏名目中的“全面巡视”“熄灯巡视”库中无对应取值, + * 固定返回 0。
  2. + *
  3. 巡视状态:patrol_task_status 按 task_state 分组,按巡视任务执行ID task_patrolled_id 去重计数, + * 排除联动(type=5)/静默(type=6)任务,按任务开始时间 start_time 落在时间窗内计数。
  4. + *
  5. 巡视率:沿用客户端既有口径,比率 = validNum / totalNum * 100。
  6. + *
+ */ + private TaskStatsVo countTaskStats(String queryPeriod) { + TaskStatsVo taskStats = new TaskStatsVo(); + taskStats.setPeriod(queryPeriod); + String[] range = this.periodRange(queryPeriod); + + Map typeCounts = this.toCountMap( + this.screenMapper.selectTaskTypeCounts(range[0], range[1], PatrolScreenConst.PATROL_TYPE_EXCLUDED)); + List typeStats = this.emptyTypeStats(); + long typeTotal = 0L; + for (int i = 0; i < typeStats.size(); i++) { + TaskStatsVo.TypeStat typeStat = typeStats.get(i); + typeStat.setCount(this.countOfCode(typeCounts, PatrolScreenConst.PATROL_TYPE_CODES.get(i))); + typeTotal += typeStat.getCount(); + } + for (TaskStatsVo.TypeStat typeStat : typeStats) { + typeStat.setPercent(percentage(typeStat.getCount(), typeTotal)); + } + taskStats.setTypeStats(typeStats); + + Map statusCounts = this.toCountMap( + this.screenMapper.selectTaskStatusCounts(range[0], range[1], PatrolScreenConst.PATROL_TYPE_EXCLUDED)); + List statusStats = this.emptyStatusStats(); + long statusTotal = 0L; + for (int i = 0; i < statusStats.size(); i++) { + TaskStatsVo.StatusStat statusStat = statusStats.get(i); + statusStat.setCount(this.countOfCode(statusCounts, PatrolScreenConst.PATROL_STATUS_CODES.get(i))); + statusTotal += statusStat.getCount(); + } + for (TaskStatsVo.StatusStat statusStat : statusStats) { + statusStat.setPercent(percentage(statusStat.getCount(), statusTotal)); + } + taskStats.setStatusStats(statusStats); + + List rateStats = this.emptyRateStats(); + // 大屏按名称字面口径出数:漏检率 = 未巡检点位占比,闭环率 = 非终止任务占比 + this.fillRate(rateStats, PatrolScreenConst.RATE_POINT_MISS, + this.inversePercentage(this.screenMapper.selectPointNumRate(range[0], range[1]))); + this.fillRate(rateStats, PatrolScreenConst.RATE_TASK_CLOSED_LOOP, + this.inversePercentage(this.screenMapper.selectTaskDoneRate(range[0], range[1]))); + taskStats.setRateStats(rateStats); + return taskStats; + } + + @Override + public ScreenResultVo manualConfirmDefect(String period) { + String queryPeriod = this.normalizePeriod(period); + return this.loadWithCache(PatrolScreenConst.CACHE_MANUAL_CONFIRM_DEFECT + queryPeriod, PatrolScreenConst.TTL_MIDDLE_FREQUENCY, + () -> this.countManualConfirmDefect(queryPeriod)); + } + + /** + * 人工确认缺陷统计口径:三项完成率沿用客户端既有口径(比率 = validNum / totalNum * 100)—— + * 告警人工审核完成率取 result_analysis 中告警(result_type = 0)的 res_status = 1 占比; + * 告警准确率取 patrol_result 未关联 patrol_result_aux 的占比; + * 结果人工审核完成率取 patrol_result 中 patrol_status = 1 的占比。 + * + *

高/中/低严重度:本地结果表没有缺陷等级字段(等级由站端判定,只落在同步镜像表), + * 缺陷总量统一计入最低严重度 low,high / medium 固定为 0(口径与“巡视缺陷统计”同源,不按 res_status 过滤)。

+ */ + private ManualConfirmDefectVo countManualConfirmDefect(String queryPeriod) { + ManualConfirmDefectVo manualConfirmDefect = new ManualConfirmDefectVo(); + manualConfirmDefect.setPeriod(queryPeriod); + String[] range = this.periodRange(queryPeriod); + ManualConfirmDefectVo.Rates rates = manualConfirmDefect.getRates(); + rates.setAlarmManualReviewRate(this.validPercentage( + this.screenMapper.selectAlarmReviewRate(range[0], range[1]))); + rates.setAlarmAccuracyRate(this.validPercentage( + this.screenMapper.selectAlarmAccuracyRate(range[0], range[1]))); + rates.setResultManualReviewRate(this.validPercentage( + this.screenMapper.selectResultReviewRate(range[0], range[1]))); + // 高/中/低严重度:本地结果表没有缺陷等级字段(等级由站端判定,只落在同步镜像表), + // 参照“巡视告警统计”的既有口径,缺陷总量统一计入最低严重度 low,high / medium 固定为 0。 + Long defectCount = this.screenMapper.selectDefectCount(range[0], range[1]); + ManualConfirmDefectVo.Level level = manualConfirmDefect.getLevel(); + level.setHigh(0L); + level.setMedium(0L); + level.setLow(defectCount == null ? 0L : defectCount); + return manualConfirmDefect; + } + + @Override + public ScreenResultVo alarmStats(String period) { + String queryPeriod = this.normalizePeriod(period); + return this.loadWithCache(PatrolScreenConst.CACHE_ALARM_STATS + queryPeriod, PatrolScreenConst.TTL_HIGH_FREQUENCY, + () -> this.countAlarmStats(queryPeriod)); + } + + /** + * 巡视告警统计口径:告警记录取本地结果表 result_analysis.result_type = 2,时间取 create_time。 + * + *

本地 result_analysis 没有告警级别字段,该批记录统一计入大屏最低级别「预警」,其余级别为 0; + * 若后续本地提供可用的等级字段,只需改这里与对应 SQL。

+ */ + private AlarmStatsVo countAlarmStats(String queryPeriod) { + AlarmStatsVo alarmStats = new AlarmStatsVo(); + alarmStats.setPeriod(queryPeriod); + String[] range = this.periodRange(queryPeriod); + Map levelCounts = new HashMap<>(); + this.appendCount(levelCounts, PatrolScreenConst.ALARM_LEVELS.get(0), + this.screenMapper.selectAlarmCount(range[0], range[1])); + alarmStats.setItems(this.levelStats(PatrolScreenConst.ALARM_LEVELS, levelCounts)); + return alarmStats; + } + + @Override + public ScreenResultVo defectStats(String period) { + String queryPeriod = this.normalizePeriod(period); + return this.loadWithCache(PatrolScreenConst.CACHE_DEFECT_STATS + queryPeriod, PatrolScreenConst.TTL_HIGH_FREQUENCY, + () -> this.countDefectStats(queryPeriod)); + } + + /** + * 巡视缺陷统计口径:缺陷记录取本地结果表 result_analysis.result_type = 0,时间取 create_time。 + * + *

本地结果表没有缺陷等级字段(等级由站端判定,只落在同步镜像表),故三个级别名目固定返回, + * 缺陷总量统一计入最低级别“一般缺陷”,其余级别为 0。

+ */ + private DefectStatsVo countDefectStats(String queryPeriod) { + DefectStatsVo defectStats = new DefectStatsVo(); + defectStats.setPeriod(queryPeriod); + String[] range = this.periodRange(queryPeriod); + Map levelCounts = new HashMap<>(); + this.appendCount(levelCounts, PatrolScreenConst.DEFECT_LEVELS.get(0), + this.screenMapper.selectDefectCount(range[0], range[1])); + defectStats.setItems(this.levelStats(PatrolScreenConst.DEFECT_LEVELS, levelCounts)); + return defectStats; + } + + /** + * period 折算为统计时间窗,返回 [开始时间, 结束时间](yyyy-MM-dd HH:mm:ss): + * day 当天、week 本周(周一起)、month 本月、year 本年。 + */ + private String[] periodRange(String queryPeriod) { + LocalDate today = LocalDate.now(); + LocalDate begin; + switch (queryPeriod) { + case PatrolScreenConst.PERIOD_WEEK: + begin = today.with(DayOfWeek.MONDAY); + break; + case PatrolScreenConst.PERIOD_MONTH: + begin = today.withDayOfMonth(1); + break; + case PatrolScreenConst.PERIOD_YEAR: + begin = today.withDayOfYear(1); + break; + case PatrolScreenConst.PERIOD_DAY: + default: + begin = today; + break; + } + return new String[]{this.dayStart(begin), this.dayEnd(today)}; + } + + /** 自然日起点:某天 00:00:00。 */ + private String dayStart(LocalDate day) { + return DateUtils.format(DateUtils.YYYY_MM_DD, DateUtils.toDate(day)) + " 00:00:00"; + } + + /** 自然日终点:某天 23:59:59。 */ + private String dayEnd(LocalDate day) { + return DateUtils.format(DateUtils.YYYY_MM_DD, DateUtils.toDate(day)) + " 23:59:59"; + } + + /** 分组计数结果转「编码 - 数量」映射;编码为空的分组不进入名目统计。 */ + private Map toCountMap(List counts) { + Map countMap = new HashMap<>(); + if (counts == null) { + return countMap; + } + for (PatrolScreenCount count : counts) { + if (StringUtils.isEmpty(count.getCode())) { + continue; + } + this.appendCount(countMap, count.getCode(), count.getNum()); + } + return countMap; + } + + /** 累加名目数量。 */ + private void appendCount(Map counts, String name, Long num) { + long value = num == null ? 0L : num; + Long exist = counts.get(name); + counts.put(name, exist == null ? value : exist + value); + } + + /** 按编码取分组数量;编码为空(大屏专有名目,库中无对应取值)时返回 0。 */ + private long countOfCode(Map counts, String code) { + if (StringUtils.isEmpty(code)) { + return 0L; + } + Long count = counts.get(code); + return count == null ? 0L : count; + } + + /** 按名目回填比率,未匹配到的名目保持 0。 */ + private void fillRate(List rateStats, String name, Double rate) { + for (TaskStatsVo.RateStat rateStat : rateStats) { + if (name.equals(rateStat.getName())) { + rateStat.setRate(rate == null ? 0D : rate); + return; + } + } + } + + /** 客户端既有口径:比率 = validNum / totalNum * 100,保留 2 位小数。 */ + private Double validPercentage(PatrolScreenRate rate) { + if (rate == null || rate.getTotalNum() == null || rate.getValidNum() == null) { + return 0D; + } + return percentage(rate.getValidNum().longValue(), rate.getTotalNum().longValue()); + } + + /** + * 按名称字面口径取反:比率 = (总数 - 有效数) / 总数 * 100,保留 2 位小数。 + * + *

用于“漏检率 = 未巡检点位占比”“闭环率 = 非终止任务占比”这类与客户端上报口径方向相反的指标。

+ */ + private Double inversePercentage(PatrolScreenRate rate) { + if (rate == null || rate.getTotalNum() == null || rate.getValidNum() == null) { + return 0D; + } + long total = rate.getTotalNum().longValue(); + return percentage(total - rate.getValidNum().longValue(), total); + } + + /** 按固定级别名目回填数量与占比,保证大屏始终拿到全部级别(含 0 值)。 */ + private List levelStats(List levelNames, Map counts) { + List items = this.emptyLevelStats(levelNames); + long total = 0L; + for (LevelStatVo item : items) { + Long count = counts.get(item.getLevel()); + item.setCount(count == null ? 0L : count); + total += item.getCount(); + } + for (LevelStatVo item : items) { + item.setPercent(percentage(item.getCount(), total)); + } + return items; + } + + /** + * 大屏统计通用取数:先读缓存,未命中再回源计算并写入缓存。 + * + *

缓存读写异常时降级为直接回源,避免大屏接口因缓存故障整体不可用; + * updateTime 与数据一起缓存,命中缓存时仍为数据实际生成时间。

+ * + * @param cacheKey 缓存 key + * @param ttlSeconds 缓存时长(秒) + * @param loader 统计口径计算逻辑 + */ + @SuppressWarnings("unchecked") + private ScreenResultVo loadWithCache(String cacheKey, long ttlSeconds, Supplier loader) { + try { + ScreenResultVo cached = this.redisService.getCacheObject(cacheKey); + if (cached != null) { + return cached; + } + } catch (Exception e) { + this.logger.warn("[SCREEN] read cache fail, key: {}", cacheKey, e); + } + + ScreenResultVo screenResult = new ScreenResultVo<>(loader.get(), DateUtils.getTime()); + try { + this.redisService.setCacheObject(cacheKey, screenResult, ttlSeconds, TimeUnit.SECONDS); + } catch (Exception e) { + this.logger.warn("[SCREEN] write cache fail, key: {}", cacheKey, e); + } + return screenResult; + } + + /** + * period 归一化:空值取 day,非法值按 day 处理并记录告警。 + */ + private String normalizePeriod(String period) { + if (StringUtils.isEmpty(period)) { + return PatrolScreenConst.PERIOD_DEFAULT; + } + String value = period.trim().toLowerCase(); + if (!PatrolScreenConst.PERIODS.contains(value)) { + this.logger.warn("[SCREEN] invalid period: {}, fallback to {}", period, PatrolScreenConst.PERIOD_DEFAULT); + return PatrolScreenConst.PERIOD_DEFAULT; + } + return value; + } + + /** + * 计算占比,保留 2 位小数;分母小于等于 0 时返回 0。 + */ + private static Double percentage(Long numerator, Long denominator) { + if (numerator == null || denominator == null || denominator <= 0L) { + return 0D; + } + return BigDecimal.valueOf(numerator * 100D / denominator).setScale(2, RoundingMode.HALF_UP).doubleValue(); + } + + /** + * 巡视类型统计骨架:固定名目,数量默认 0,由调用方回填真实数量。 + */ + private List emptyTypeStats() { + List typeStats = new ArrayList<>(PatrolScreenConst.PATROL_TYPES.size()); + for (String type : PatrolScreenConst.PATROL_TYPES) { + TaskStatsVo.TypeStat typeStat = new TaskStatsVo.TypeStat(); + typeStat.setType(type); + typeStats.add(typeStat); + } + return typeStats; + } + + /** + * 巡视率指标骨架:固定名目,比率默认 0,由调用方回填真实比率。 + */ + private List emptyRateStats() { + List rateStats = new ArrayList<>(PatrolScreenConst.PATROL_RATES.size()); + for (String rateName : PatrolScreenConst.PATROL_RATES) { + TaskStatsVo.RateStat rateStat = new TaskStatsVo.RateStat(); + rateStat.setName(rateName); + rateStats.add(rateStat); + } + return rateStats; + } + + /** + * 巡视状态统计骨架:固定名目,数量默认 0,由调用方回填真实数量。 + */ + private List emptyStatusStats() { + List statusStats = new ArrayList<>(PatrolScreenConst.PATROL_STATUSES.size()); + for (String status : PatrolScreenConst.PATROL_STATUSES) { + TaskStatsVo.StatusStat statusStat = new TaskStatsVo.StatusStat(); + statusStat.setStatus(status); + statusStats.add(statusStat); + } + return statusStats; + } + + /** + * 级别统计骨架:固定名目,数量默认 0,由调用方回填真实数量;告警与缺陷共用。 + */ + private List emptyLevelStats(List levels) { + List items = new ArrayList<>(levels.size()); + for (String level : levels) { + LevelStatVo item = new LevelStatVo(); + item.setLevel(level); + items.add(item); + } + return items; + } } diff --git a/inspect-main/inspect-main-statistics/src/main/resources/mapper/screen/PatrolScreenMapper.xml b/inspect-main/inspect-main-statistics/src/main/resources/mapper/screen/PatrolScreenMapper.xml new file mode 100644 index 0000000..de6ce49 --- /dev/null +++ b/inspect-main/inspect-main-statistics/src/main/resources/mapper/screen/PatrolScreenMapper.xml @@ -0,0 +1,280 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inspect-main/inspect-main-task/src/main/java/com/inspect/analysis/utils/ResultAnalysisUtils.java b/inspect-main/inspect-main-task/src/main/java/com/inspect/analysis/utils/ResultAnalysisUtils.java index e3dad03..ac20e60 100644 --- a/inspect-main/inspect-main-task/src/main/java/com/inspect/analysis/utils/ResultAnalysisUtils.java +++ b/inspect-main/inspect-main-task/src/main/java/com/inspect/analysis/utils/ResultAnalysisUtils.java @@ -98,6 +98,7 @@ public class ResultAnalysisUtils { // 初筛时, infrared算法要做阈值判断 if ("1".equals(resultAnalysis.getFilter()) && !AlgConstants.INFRARED.equals(algType)) { algType = ""; + resultAnalysis.setResValue(""); } if (StringUtils.isNull(algType)) { diff --git a/inspect-main/pom.xml b/inspect-main/pom.xml index 72a1eb6..2b5b77c 100644 --- a/inspect-main/pom.xml +++ b/inspect-main/pom.xml @@ -13,6 +13,12 @@ inspect-main 3.4.0 pom + + + UTF-8 + UTF-8 + + inspect-main-start inspect-main-task