|
|
@@ -0,0 +1,380 @@
|
|
|
+package com.zksy.base.service.impl;
|
|
|
+
|
|
|
+import cn.hutool.core.bean.BeanUtil;
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
+import com.zksy.base.domain.EquipmentBase;
|
|
|
+import com.zksy.base.domain.EquipmentType;
|
|
|
+import com.zksy.base.domain.TerminalFault;
|
|
|
+import com.zksy.base.mapper.EquipmentBaseMapper;
|
|
|
+import com.zksy.base.mapper.EquipmentTypeMapper;
|
|
|
+import com.zksy.base.mapper.TerminalFaultMapper;
|
|
|
+import com.zksy.base.service.EquipmentAbnormalService;
|
|
|
+import com.zksy.common.utils.StringUtils;
|
|
|
+import com.zksy.manhole.dto.out.TerminalFaultVO;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+
|
|
|
+import java.text.SimpleDateFormat;
|
|
|
+import java.time.LocalDate;
|
|
|
+import java.time.ZoneId;
|
|
|
+import java.time.format.DateTimeFormatter;
|
|
|
+import java.util.*;
|
|
|
+import java.util.stream.Collectors;
|
|
|
+
|
|
|
+@Slf4j
|
|
|
+@Service
|
|
|
+public class EquipmentAbnormalServiceImpl implements EquipmentAbnormalService {
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private TerminalFaultMapper terminalFaultMapper;
|
|
|
+ @Autowired
|
|
|
+ private EquipmentBaseMapper equipmentBaseMapper;
|
|
|
+ @Autowired
|
|
|
+ private EquipmentTypeMapper equipmentTypeMapper;
|
|
|
+
|
|
|
+ /** 燃气顶级类型: parent_type_id = "3" */
|
|
|
+ private static final String GAS_PARENT_TYPE_ID = "3";
|
|
|
+
|
|
|
+ // 故障等级文本
|
|
|
+ private static final Map<Integer, String> LEVEL_MAP = new HashMap<>();
|
|
|
+ static {
|
|
|
+ LEVEL_MAP.put(1, "严重");
|
|
|
+ LEVEL_MAP.put(2, "一般");
|
|
|
+ LEVEL_MAP.put(3, "轻微");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 故障状态文本
|
|
|
+ private static final Map<Integer, String> STATUS_MAP = new HashMap<>();
|
|
|
+ static {
|
|
|
+ STATUS_MAP.put(1, "待处理");
|
|
|
+ STATUS_MAP.put(2, "处理中");
|
|
|
+ STATUS_MAP.put(3, "已修复");
|
|
|
+ STATUS_MAP.put(4, "已返厂");
|
|
|
+ STATUS_MAP.put(5, "已报废");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Map<String, Object> getList(int pageNum, int pageSize, String searchKey, String level, String type, String status) {
|
|
|
+ // 1. 先查出所有燃气设备(equipment_type_id以GAS-开头)
|
|
|
+ List<EquipmentType> gasTypes = equipmentTypeMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<EquipmentType>()
|
|
|
+ .eq(EquipmentType::getParentTypeId, GAS_PARENT_TYPE_ID)
|
|
|
+ );
|
|
|
+ if (gasTypes.isEmpty()) {
|
|
|
+ Map<String, Object> emptyResult = new LinkedHashMap<>();
|
|
|
+ emptyResult.put("total", 0L);
|
|
|
+ emptyResult.put("rows", new ArrayList<>());
|
|
|
+ return emptyResult;
|
|
|
+ }
|
|
|
+ Set<String> gasTypeIds = gasTypes.stream().map(EquipmentType::getId).collect(Collectors.toSet());
|
|
|
+
|
|
|
+ List<EquipmentBase> gasEquipments = equipmentBaseMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<EquipmentBase>()
|
|
|
+ .in(EquipmentBase::getEquipmentTypeId, gasTypeIds)
|
|
|
+ );
|
|
|
+ if (gasEquipments.isEmpty()) {
|
|
|
+ Map<String, Object> emptyResult = new LinkedHashMap<>();
|
|
|
+ emptyResult.put("total", 0L);
|
|
|
+ emptyResult.put("rows", new ArrayList<>());
|
|
|
+ return emptyResult;
|
|
|
+ }
|
|
|
+ Set<String> gasEquipmentIds = gasEquipments.stream().map(EquipmentBase::getEquipmentId).collect(Collectors.toSet());
|
|
|
+ Map<String, EquipmentBase> equipmentMap = gasEquipments.stream()
|
|
|
+ .collect(Collectors.toMap(EquipmentBase::getEquipmentId, e -> e, (a, b) -> a));
|
|
|
+
|
|
|
+ // 类型ID → 类型名称
|
|
|
+ Map<String, String> typeNameMap = gasTypes.stream()
|
|
|
+ .collect(Collectors.toMap(EquipmentType::getId, EquipmentType::getTypeName, (a, b) -> a));
|
|
|
+
|
|
|
+ // 2. 查询terminal_fault,过滤燃气设备
|
|
|
+ LambdaQueryWrapper<TerminalFault> queryWrapper = new LambdaQueryWrapper<>();
|
|
|
+ queryWrapper.in(TerminalFault::getDeviceId, gasEquipmentIds);
|
|
|
+
|
|
|
+ // 筛选条件
|
|
|
+ if (StringUtils.isNotEmpty(searchKey)) {
|
|
|
+ queryWrapper.and(w -> w.like(TerminalFault::getDeviceCode, searchKey)
|
|
|
+ .or().like(TerminalFault::getFaultDesc, searchKey));
|
|
|
+ }
|
|
|
+ if (StringUtils.isNotEmpty(level)) {
|
|
|
+ Integer lv = parseLevel(level);
|
|
|
+ if (lv != null) queryWrapper.eq(TerminalFault::getFaultLevel, lv);
|
|
|
+ }
|
|
|
+ if (StringUtils.isNotEmpty(status)) {
|
|
|
+ Integer st = parseStatus(status);
|
|
|
+ if (st != null) queryWrapper.eq(TerminalFault::getFaultStatus, st);
|
|
|
+ }
|
|
|
+ queryWrapper.orderByDesc(TerminalFault::getUpdateTime);
|
|
|
+
|
|
|
+ // 3. 分页
|
|
|
+ long total = terminalFaultMapper.selectCount(queryWrapper);
|
|
|
+ int offset = (pageNum - 1) * pageSize;
|
|
|
+ List<TerminalFault> faults = terminalFaultMapper.selectList(
|
|
|
+ queryWrapper.last("limit " + offset + ", " + pageSize)
|
|
|
+ );
|
|
|
+
|
|
|
+ // 4. 组装VO + 设备类型筛选
|
|
|
+ List<TerminalFaultVO> rows = new ArrayList<>();
|
|
|
+ for (int i = 0; i < faults.size(); i++) {
|
|
|
+ TerminalFault f = faults.get(i);
|
|
|
+ EquipmentBase eq = equipmentMap.get(f.getDeviceId());
|
|
|
+ TerminalFaultVO vo = new TerminalFaultVO();
|
|
|
+ vo.setFaultId(f.getFaultId());
|
|
|
+ vo.setFaultNo(f.getFaultNo());
|
|
|
+ vo.setDeviceId(f.getDeviceId());
|
|
|
+ vo.setDeviceCode(f.getDeviceCode());
|
|
|
+ vo.setDeviceName(eq != null ? eq.getEquipmentName() : f.getDeviceCode());
|
|
|
+ vo.setDeviceType(eq != null ? typeNameMap.getOrDefault(eq.getEquipmentTypeId(), "未知") : "未知");
|
|
|
+ vo.setAddress(eq != null ? eq.getEquipmentLocation() : "");
|
|
|
+ vo.setFaultLevel(f.getFaultLevel());
|
|
|
+ vo.setLevelText(LEVEL_MAP.getOrDefault(f.getFaultLevel(), "未知"));
|
|
|
+ vo.setAlarmDesc(f.getFaultDesc());
|
|
|
+ vo.setFaultSource(f.getFaultSource());
|
|
|
+ vo.setFaultStatus(f.getFaultStatus());
|
|
|
+ vo.setProcessStatus(STATUS_MAP.getOrDefault(f.getFaultStatus(), "未知"));
|
|
|
+ vo.setAlarmTime(f.getHappenTime());
|
|
|
+ rows.add(vo);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 设备类型筛选(在内存中做,因为类型名称需要join后才能得到)
|
|
|
+ if (StringUtils.isNotEmpty(type)) {
|
|
|
+ rows = rows.stream().filter(r -> type.equals(r.getDeviceType())).collect(Collectors.toList());
|
|
|
+ }
|
|
|
+
|
|
|
+ Map<String, Object> result = new LinkedHashMap<>();
|
|
|
+ result.put("total", total);
|
|
|
+ result.put("rows", rows);
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Map<String, Object> getStats() {
|
|
|
+ // 燃气设备总故障统计
|
|
|
+ List<EquipmentType> gasTypes = equipmentTypeMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<EquipmentType>().eq(EquipmentType::getParentTypeId, GAS_PARENT_TYPE_ID)
|
|
|
+ );
|
|
|
+ if (gasTypes.isEmpty()) {
|
|
|
+ return buildEmptyStats();
|
|
|
+ }
|
|
|
+ Set<String> gasTypeIds = gasTypes.stream().map(EquipmentType::getId).collect(Collectors.toSet());
|
|
|
+ List<EquipmentBase> gasEquipments = equipmentBaseMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<EquipmentBase>().in(EquipmentBase::getEquipmentTypeId, gasTypeIds)
|
|
|
+ );
|
|
|
+ if (gasEquipments.isEmpty()) {
|
|
|
+ return buildEmptyStats();
|
|
|
+ }
|
|
|
+ Set<String> gasEquipmentIds = gasEquipments.stream().map(EquipmentBase::getEquipmentId).collect(Collectors.toSet());
|
|
|
+ Map<String, String> typeNameMap = gasTypes.stream()
|
|
|
+ .collect(Collectors.toMap(EquipmentType::getId, EquipmentType::getTypeName, (a, b) -> a));
|
|
|
+
|
|
|
+ List<TerminalFault> allFaults;
|
|
|
+ if (!gasEquipmentIds.isEmpty()) {
|
|
|
+ allFaults = terminalFaultMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<TerminalFault>().in(TerminalFault::getDeviceId, gasEquipmentIds)
|
|
|
+ );
|
|
|
+ } else {
|
|
|
+ allFaults = new ArrayList<>();
|
|
|
+ }
|
|
|
+
|
|
|
+ // ---- 日期范围: 前15天 ----
|
|
|
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
|
|
|
+ LocalDate today = LocalDate.now();
|
|
|
+ LocalDate rangeStart = today.minusDays(14);
|
|
|
+ String todayStr = today.toString();
|
|
|
+
|
|
|
+ // 前15天内发生的故障
|
|
|
+ List<TerminalFault> recentFaults = allFaults.stream()
|
|
|
+ .filter(f -> f.getHappenTime() != null)
|
|
|
+ .filter(f -> {
|
|
|
+ LocalDate hd = f.getHappenTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
|
|
|
+ return !hd.isBefore(rangeStart) && !hd.isAfter(today);
|
|
|
+ })
|
|
|
+ .collect(Collectors.toList());
|
|
|
+
|
|
|
+ int totalFaults = allFaults.size();
|
|
|
+ int recentFaultCount = recentFaults.size();
|
|
|
+ long totalDevices = recentFaults.stream().map(TerminalFault::getDeviceId).filter(Objects::nonNull).distinct().count();
|
|
|
+
|
|
|
+ // 今日新增 = happen_time为今天(去重设备数)
|
|
|
+ long todayCount = allFaults.stream()
|
|
|
+ .filter(f -> f.getHappenTime() != null && sdf.format(f.getHappenTime()).equals(todayStr))
|
|
|
+ .map(TerminalFault::getDeviceId).filter(Objects::nonNull).distinct().count();
|
|
|
+
|
|
|
+ int critical = (int) recentFaults.stream().filter(f -> f.getFaultLevel() != null && f.getFaultLevel() == 1).count();
|
|
|
+ int normalLv = (int) recentFaults.stream().filter(f -> f.getFaultLevel() != null && f.getFaultLevel() == 2).count();
|
|
|
+ int minor = (int) recentFaults.stream().filter(f -> f.getFaultLevel() != null && f.getFaultLevel() == 3).count();
|
|
|
+
|
|
|
+ // 已修复: 前15天内故障中 faultStatus==3 的数量
|
|
|
+ int processed = (int) recentFaults.stream().filter(f -> f.getFaultStatus() != null && f.getFaultStatus() == 3).count();
|
|
|
+ // 待处理: 前15天内故障中 faultStatus==1 的数量
|
|
|
+ int pending = (int) recentFaults.stream().filter(f -> f.getFaultStatus() != null && f.getFaultStatus() == 1).count();
|
|
|
+
|
|
|
+ int totalEquipment = gasEquipments.size();
|
|
|
+ // 异常占比 = 前15天异常设备数 / 燃气设备总数 × 100
|
|
|
+ double rate = totalEquipment > 0 ? Math.round(totalDevices * 100.0 / totalEquipment * 10) / 10.0 : 0;
|
|
|
+ // 修复率 = 前15天已修复数 / 前15天故障总数 × 100
|
|
|
+ double processRate = recentFaultCount > 0 ? Math.round(processed * 100.0 / recentFaultCount * 10) / 10.0 : 0;
|
|
|
+
|
|
|
+ // 按设备类型统计(前15天故障记录数)
|
|
|
+ Map<String, Integer> typeCountMap = new LinkedHashMap<>();
|
|
|
+ for (TerminalFault f : recentFaults) {
|
|
|
+ EquipmentBase eq = gasEquipments.stream()
|
|
|
+ .filter(e -> e.getEquipmentId().equals(f.getDeviceId()))
|
|
|
+ .findFirst().orElse(null);
|
|
|
+ String typeName = eq != null ? typeNameMap.getOrDefault(eq.getEquipmentTypeId(), "其他") : "其他";
|
|
|
+ typeCountMap.merge(typeName, 1, Integer::sum);
|
|
|
+ }
|
|
|
+
|
|
|
+ // ---- 近15天趋势 ----
|
|
|
+ // totalData = 累计未修复数 = 前一天total + 当天新增 - 当天修复
|
|
|
+ DateTimeFormatter dateFmt = DateTimeFormatter.ofPattern("MM-dd");
|
|
|
+
|
|
|
+ // 预计算每天的new和fixed数量
|
|
|
+ Map<String, Integer> newByDay = new LinkedHashMap<>();
|
|
|
+ Map<String, Integer> fixedByDay = new LinkedHashMap<>();
|
|
|
+ for (TerminalFault f : allFaults) {
|
|
|
+ if (f.getHappenTime() != null) {
|
|
|
+ newByDay.merge(sdf.format(f.getHappenTime()), 1, Integer::sum);
|
|
|
+ }
|
|
|
+ if (f.getHandleTime() != null) {
|
|
|
+ fixedByDay.merge(sdf.format(f.getHandleTime()), 1, Integer::sum);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ List<String> trendDates = new ArrayList<>();
|
|
|
+ List<Integer> trendTotal = new ArrayList<>();
|
|
|
+ List<Integer> trendNew = new ArrayList<>();
|
|
|
+ List<Integer> trendFixed = new ArrayList<>();
|
|
|
+
|
|
|
+ for (int d = 14; d >= 0; d--) {
|
|
|
+ LocalDate day = today.minusDays(d);
|
|
|
+ String dayStr = day.toString();
|
|
|
+ trendDates.add(day.format(dateFmt));
|
|
|
+ // 当天异常设备数 = happen_time为当天 且 fault_status未修复(1或2) 的去重设备数
|
|
|
+ long totalByDay = allFaults.stream()
|
|
|
+ .filter(f -> f.getHappenTime() != null && sdf.format(f.getHappenTime()).equals(dayStr))
|
|
|
+ .map(TerminalFault::getDeviceId).filter(Objects::nonNull).distinct().count();
|
|
|
+ int n = newByDay.getOrDefault(dayStr, 0);
|
|
|
+ int f = fixedByDay.getOrDefault(dayStr, 0);
|
|
|
+ trendTotal.add((int) totalByDay);
|
|
|
+ trendNew.add(n);
|
|
|
+ trendFixed.add(f);
|
|
|
+ }
|
|
|
+
|
|
|
+ // ---- 异常类型分布 (按fault_source) ----
|
|
|
+ String[] sourceLabels = {"设备报警", "人工上报", "巡检发现"};
|
|
|
+ int[] sourceValues = new int[3];
|
|
|
+ for (TerminalFault f : allFaults) {
|
|
|
+ if (f.getFaultSource() != null && f.getFaultSource() >= 1 && f.getFaultSource() <= 3) {
|
|
|
+ sourceValues[f.getFaultSource() - 1]++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // ---- 等级×状态交叉统计 ----
|
|
|
+ String[] levelLabels = {"严重", "一般", "轻微"};
|
|
|
+ int[] pendingByLevel = new int[3];
|
|
|
+ int[] processingByLevel = new int[3];
|
|
|
+ int[] fixedByLevel = new int[3];
|
|
|
+ for (TerminalFault f : allFaults) {
|
|
|
+ int lv = f.getFaultLevel() != null ? f.getFaultLevel() : 2;
|
|
|
+ if (lv < 1 || lv > 3) lv = 2;
|
|
|
+ int idx = lv - 1;
|
|
|
+ int st = f.getFaultStatus() != null ? f.getFaultStatus() : 1;
|
|
|
+ if (st == 1) pendingByLevel[idx]++;
|
|
|
+ else if (st == 2) processingByLevel[idx]++;
|
|
|
+ else if (st == 3) fixedByLevel[idx]++;
|
|
|
+ }
|
|
|
+
|
|
|
+ Map<String, Object> result = new LinkedHashMap<>();
|
|
|
+ result.put("total", (int) totalDevices); // 异常设备数(去重)
|
|
|
+ result.put("totalFaults", totalFaults); // 故障记录总数
|
|
|
+ result.put("today", todayCount); // 今日新增
|
|
|
+ result.put("rate", rate); // 异常占比(%)
|
|
|
+ result.put("processed", processed); // 已修复
|
|
|
+ result.put("pending", pending); // 待处理
|
|
|
+ result.put("processRate", processRate); // 修复率(%)
|
|
|
+ result.put("critical", critical);
|
|
|
+ result.put("normal", normalLv);
|
|
|
+ result.put("minor", minor);
|
|
|
+ result.put("deviceTypeStats", typeCountMap);
|
|
|
+ // 趋势
|
|
|
+ Map<String, Object> trend = new LinkedHashMap<>();
|
|
|
+ trend.put("dates", trendDates);
|
|
|
+ trend.put("totalData", trendTotal);
|
|
|
+ trend.put("newData", trendNew);
|
|
|
+ trend.put("fixedData", trendFixed);
|
|
|
+ result.put("trend", trend);
|
|
|
+ // 类型分布
|
|
|
+ Map<String, Object> typeDist = new LinkedHashMap<>();
|
|
|
+ typeDist.put("categories", sourceLabels);
|
|
|
+ typeDist.put("values", sourceValues);
|
|
|
+ result.put("typeDistribution", typeDist);
|
|
|
+ // 等级×状态
|
|
|
+ Map<String, Object> levelStatus = new LinkedHashMap<>();
|
|
|
+ levelStatus.put("levels", levelLabels);
|
|
|
+ levelStatus.put("pending", pendingByLevel);
|
|
|
+ levelStatus.put("processing", processingByLevel);
|
|
|
+ levelStatus.put("fixed", fixedByLevel);
|
|
|
+ result.put("levelStatus", levelStatus);
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ private Map<String, Object> buildEmptyStats() {
|
|
|
+ Map<String, Object> result = new LinkedHashMap<>();
|
|
|
+ result.put("total", 0);
|
|
|
+ result.put("today", 0);
|
|
|
+ result.put("rate", 0);
|
|
|
+ result.put("processed", 0);
|
|
|
+ result.put("pending", 0);
|
|
|
+ result.put("processRate", 0);
|
|
|
+ result.put("critical", 0);
|
|
|
+ result.put("normal", 0);
|
|
|
+ result.put("minor", 0);
|
|
|
+ result.put("deviceTypeStats", new LinkedHashMap<>());
|
|
|
+ result.put("trend", buildEmptyTrend());
|
|
|
+ result.put("typeDistribution", buildEmptyTypeDist());
|
|
|
+ result.put("levelStatus", buildEmptyLevelStatus());
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ private Map<String, Object> buildEmptyTrend() {
|
|
|
+ Map<String, Object> t = new LinkedHashMap<>();
|
|
|
+ t.put("dates", new ArrayList<>());
|
|
|
+ t.put("totalData", new ArrayList<>());
|
|
|
+ t.put("newData", new ArrayList<>());
|
|
|
+ t.put("fixedData", new ArrayList<>());
|
|
|
+ return t;
|
|
|
+ }
|
|
|
+
|
|
|
+ private Map<String, Object> buildEmptyTypeDist() {
|
|
|
+ Map<String, Object> d = new LinkedHashMap<>();
|
|
|
+ d.put("categories", new String[]{"设备报警", "人工上报", "巡检发现"});
|
|
|
+ d.put("values", new int[]{0, 0, 0});
|
|
|
+ return d;
|
|
|
+ }
|
|
|
+
|
|
|
+ private Map<String, Object> buildEmptyLevelStatus() {
|
|
|
+ Map<String, Object> ls = new LinkedHashMap<>();
|
|
|
+ ls.put("levels", new String[]{"严重", "一般", "轻微"});
|
|
|
+ ls.put("pending", new int[]{0, 0, 0});
|
|
|
+ ls.put("processing", new int[]{0, 0, 0});
|
|
|
+ ls.put("fixed", new int[]{0, 0, 0});
|
|
|
+ return ls;
|
|
|
+ }
|
|
|
+
|
|
|
+ private Integer parseLevel(String level) {
|
|
|
+ if ("严重".equals(level)) return 1;
|
|
|
+ if ("一般".equals(level)) return 2;
|
|
|
+ if ("轻微".equals(level)) return 3;
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ private Integer parseStatus(String status) {
|
|
|
+ if ("待处理".equals(status)) return 1;
|
|
|
+ if ("处理中".equals(status)) return 2;
|
|
|
+ if ("已修复".equals(status)) return 3;
|
|
|
+ if ("已返厂".equals(status)) return 4;
|
|
|
+ if ("已报废".equals(status)) return 5;
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+}
|