Просмотр исходного кода

feat:燃气管网-监测异常设备 管线压力监测实现接口

null 1 неделя назад
Родитель
Сommit
c7184d5729

+ 39 - 0
pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/gasbasic/EquipmentAbnormalController.java

@@ -0,0 +1,39 @@
+package com.zksy.web.controller.gasbasic;
+
+import com.zksy.base.service.EquipmentAbnormalService;
+import com.zksy.common.annotation.Anonymous;
+import com.zksy.common.core.domain.AjaxResult;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 设备异常 — 终端故障关联设备信息
+ */
+@Api(tags = "燃气监测-设备异常")
+@RestController
+@RequestMapping("/api/equipment-abnormal")
+@Anonymous
+public class EquipmentAbnormalController {
+
+    @Autowired
+    private EquipmentAbnormalService equipmentAbnormalService;
+
+    @GetMapping("/list")
+    @ApiOperation("分页查询燃气设备故障列表(关联设备名称/类型/地址)")
+    public AjaxResult getList(@RequestParam(defaultValue = "1") int pageNum,
+                              @RequestParam(defaultValue = "10") int pageSize,
+                              @RequestParam(required = false) String searchKey,
+                              @RequestParam(required = false) String level,
+                              @RequestParam(required = false) String type,
+                              @RequestParam(required = false) String status) {
+        return AjaxResult.success(equipmentAbnormalService.getList(pageNum, pageSize, searchKey, level, type, status));
+    }
+
+    @GetMapping("/stats")
+    @ApiOperation("燃气设备故障统计数据")
+    public AjaxResult getStats() {
+        return AjaxResult.success(equipmentAbnormalService.getStats());
+    }
+}

+ 15 - 0
pipe-network-service/zksy-system/src/main/java/com/zksy/base/service/EquipmentAbnormalService.java

@@ -0,0 +1,15 @@
+package com.zksy.base.service;
+
+import java.util.Map;
+
+/**
+ * 设备异常页面服务 — 终端故障关联设备信息
+ */
+public interface EquipmentAbnormalService {
+
+    /** 分页查询燃气类型设备故障列表 */
+    Map<String, Object> getList(int pageNum, int pageSize, String searchKey, String level, String type, String status);
+
+    /** 故障统计数据 */
+    Map<String, Object> getStats();
+}

+ 380 - 0
pipe-network-service/zksy-system/src/main/java/com/zksy/base/service/impl/EquipmentAbnormalServiceImpl.java

@@ -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;
+    }
+}

+ 64 - 0
pipe-network-service/zksy-system/src/main/java/com/zksy/manhole/dto/out/TerminalFaultVO.java

@@ -0,0 +1,64 @@
+package com.zksy.manhole.dto.out;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 终端故障VO — 含关联设备信息
+ */
+@Data
+@ApiModel("终端故障-关联设备信息")
+public class TerminalFaultVO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty("故障ID")
+    private Long faultId;
+
+    @ApiModelProperty("故障编号")
+    private String faultNo;
+
+    @ApiModelProperty("设备ID")
+    private String deviceId;
+
+    @ApiModelProperty("设备编码")
+    private String deviceCode;
+
+    @ApiModelProperty("设备名称(来自equipment_base)")
+    private String deviceName;
+
+    @ApiModelProperty("设备类型名称")
+    private String deviceType;
+
+    @ApiModelProperty("安装地址")
+    private String address;
+
+    @ApiModelProperty("故障等级: 1-严重 2-一般")
+    private Integer faultLevel;
+
+    @ApiModelProperty("故障等级文本")
+    private String levelText;
+
+    @ApiModelProperty("故障描述")
+    private String alarmDesc;
+
+    @ApiModelProperty("故障来源")
+    private Integer faultSource;
+
+    @ApiModelProperty("故障状态")
+    private Integer faultStatus;
+
+    @ApiModelProperty("处理状态文本")
+    private String processStatus;
+
+    @ApiModelProperty("故障时间")
+    @JsonFormat(locale = "zh", pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date alarmTime;
+}