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

feat:燃气管网-压力设备告警与可燃设备告警

null 4 дней назад
Родитель
Сommit
1f468db43a

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

@@ -0,0 +1,181 @@
+package com.zksy.web.controller.gasbasic;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.zksy.base.alarm.domain.AlarmData;
+import com.zksy.base.alarm.service.AlarmDataService;
+import com.zksy.base.domain.EquipmentBase;
+import com.zksy.base.domain.EquipmentType;
+import com.zksy.base.mapper.EquipmentBaseMapper;
+import com.zksy.base.mapper.EquipmentTypeMapper;
+import com.zksy.base.constant.GasEquipmentTypeEnum;
+import com.zksy.common.annotation.Anonymous;
+import com.zksy.common.core.domain.AjaxResult;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 可燃气体浓度报警
+ */
+@Slf4j
+@RestController
+@RequestMapping("/api/gas-alarm")
+@Api(tags = "燃气监测-可燃气体报警")
+public class GasAlarmController {
+
+    @Autowired private AlarmDataService alarmDataService;
+    @Autowired private EquipmentTypeMapper equipmentTypeMapper;
+    @Autowired private EquipmentBaseMapper equipmentBaseMapper;
+
+    /** 获取燃气探测器设备编码集合 (type_id = gas_detector) */
+    private Set<String> getDetectorCodes() {
+        List<EquipmentType> types = equipmentTypeMapper.selectList(
+                new LambdaQueryWrapper<EquipmentType>().eq(EquipmentType::getTypeId, GasEquipmentTypeEnum.GAS_DETECTOR));
+        Set<String> detectorTypeIds = types.stream().map(EquipmentType::getId).collect(Collectors.toSet());
+        List<EquipmentBase> eqs = detectorTypeIds.isEmpty()
+                ? Collections.emptyList()
+                : equipmentBaseMapper.selectList(
+                        new LambdaQueryWrapper<EquipmentBase>().in(EquipmentBase::getEquipmentTypeId, detectorTypeIds));
+        return eqs.stream().map(EquipmentBase::getEquipmentCode).filter(Objects::nonNull).collect(Collectors.toSet());
+    }
+
+    /** 加载设备编码→设备信息映射 */
+    private Map<String, EquipmentBase> loadEquipMap() {
+        return equipmentBaseMapper.selectList(null).stream()
+                .collect(Collectors.toMap(EquipmentBase::getEquipmentCode, e -> e, (a, b) -> a));
+    }
+
+    private static final java.time.format.DateTimeFormatter FMT = java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+
+    private Map<String, Object> toVo(AlarmData a, Map<String, EquipmentBase> eqMap) {
+        EquipmentBase eq = eqMap.get(a.getDeviceCode());
+        Map<String, Object> m = new LinkedHashMap<>();
+        m.put("id", a.getId());
+        m.put("deviceCode", a.getDeviceCode());
+        m.put("stationName", eq != null ? eq.getEquipmentName() : a.getDeviceCode());
+        m.put("address", eq != null ? eq.getEquipmentLocation() : "");
+        m.put("concentration", a.getActualValue());
+        m.put("threshold", a.getMaxValue());
+        m.put("alarmLevel", a.getAlarmLevel());
+        m.put("alarmLevelText", a.getAlarmLevel() != null && a.getAlarmLevel() == 1 ? "严重" : "预警");
+        m.put("alarmTime", a.getAlarmTime() != null ? a.getAlarmTime().format(FMT) : "");
+        m.put("alarmStatus", a.getAlarmStatus());
+        m.put("statusText", a.getAlarmStatus() != null && a.getAlarmStatus() == 0 ? "待处理" : "已处理");
+        m.put("resolveTime", a.getHandleTime() != null ? a.getHandleTime().format(FMT) : null);
+        m.put("handler", a.getHandleUser());
+        m.put("remark", a.getHandleRemark());
+        return m;
+    }
+
+    /** 获取所有探测器设备(去重),含最新报警数据 */
+    @GetMapping("/equipment")
+    @ApiOperation("燃气探测器设备列表(含最新报警值)")
+    @Anonymous
+    public AjaxResult equipment() {
+        Set<String> codes = getDetectorCodes();
+        List<EquipmentBase> eqs = equipmentBaseMapper.selectList(null).stream()
+                .filter(e -> codes.contains(e.getEquipmentCode())).collect(Collectors.toList());
+        // 查所有报警,按设备聚合最新一条
+        List<AlarmData> allAlarms = codes.isEmpty() ? Collections.emptyList()
+                : alarmDataService.list(new LambdaQueryWrapper<AlarmData>().in(AlarmData::getDeviceCode, codes)
+                        .orderByDesc(AlarmData::getAlarmTime));
+        Map<String, AlarmData> latestMap = allAlarms.stream()
+                .collect(Collectors.toMap(AlarmData::getDeviceCode, a -> a, (a, b) -> a));
+
+        List<Map<String, Object>> rows = new ArrayList<>();
+        for (EquipmentBase eq : eqs) {
+            AlarmData latest = latestMap.get(eq.getEquipmentCode());
+            Map<String, Object> m = new LinkedHashMap<>();
+            m.put("equipmentId", eq.getEquipmentId());
+            m.put("equipmentCode", eq.getEquipmentCode());
+            m.put("equipmentName", eq.getEquipmentName());
+            m.put("location", eq.getEquipmentLocation());
+            m.put("hasAlarm", latest != null && latest.getAlarmStatus() != null && latest.getAlarmStatus() == 0);
+            m.put("latestConcentration", latest != null ? latest.getActualValue() : null);
+            m.put("latestThreshold", latest != null ? latest.getMaxValue() : null);
+            m.put("latestAlarmTime", latest != null && latest.getAlarmTime() != null ? latest.getAlarmTime().format(FMT) : null);
+            m.put("latestAlarmLevel", latest != null ? latest.getAlarmLevel() : null);
+            rows.add(m);
+        }
+        return AjaxResult.success(rows);
+    }
+
+    @GetMapping("/history")
+    @ApiOperation("历史报警分页(支持按设备筛选)")
+    @Anonymous
+    public AjaxResult history(@RequestParam(defaultValue = "1") int pageNum,
+                              @RequestParam(defaultValue = "8") int pageSize,
+                              @RequestParam(required = false) String startDate,
+                              @RequestParam(required = false) String endDate,
+                              @RequestParam(required = false) String deviceCode) {
+        LambdaQueryWrapper<AlarmData> qw = new LambdaQueryWrapper<AlarmData>();
+        if (deviceCode != null && !deviceCode.isEmpty()) {
+            qw.eq(AlarmData::getDeviceCode, deviceCode);
+        } else {
+            Set<String> codes = getDetectorCodes();
+            if (codes.isEmpty()) return AjaxResult.success(new Page<>(pageNum, pageSize, 0));
+            qw.in(AlarmData::getDeviceCode, codes);
+        }
+        if (startDate != null && !startDate.isEmpty()) qw.ge(AlarmData::getAlarmTime, startDate + " 00:00:00");
+        if (endDate != null && !endDate.isEmpty()) qw.le(AlarmData::getAlarmTime, endDate + " 23:59:59");
+        qw.orderByDesc(AlarmData::getAlarmTime);
+        Page<AlarmData> page = alarmDataService.page(new Page<>(pageNum, pageSize), qw);
+        Map<String, EquipmentBase> eqMap = loadEquipMap();
+        List<Map<String, Object>> rows = page.getRecords().stream().map(a -> toVo(a, eqMap)).collect(Collectors.toList());
+        Map<String, Object> result = new LinkedHashMap<>();
+        result.put("records", rows);
+        result.put("total", page.getTotal());
+        return AjaxResult.success(result);
+    }
+
+    @GetMapping("/stats")
+    @ApiOperation("报警统计")
+    @Anonymous
+    public AjaxResult stats() {
+        Set<String> codes = getDetectorCodes();
+        List<AlarmData> all = codes.isEmpty() ? Collections.emptyList()
+                : alarmDataService.list(new LambdaQueryWrapper<AlarmData>().in(AlarmData::getDeviceCode, codes));
+        long realtime = all.stream().filter(a -> a.getAlarmStatus() != null && a.getAlarmStatus() == 0)
+                .map(AlarmData::getDeviceCode).filter(Objects::nonNull).distinct().count();
+        AlarmData maxAlarm = all.stream().filter(a -> a.getActualValue() != null)
+                .max(Comparator.comparing(a -> a.getActualValue())).orElse(null);
+        double maxConc = maxAlarm != null ? maxAlarm.getActualValue().doubleValue() : 0;
+        String maxDevice = maxAlarm != null ? maxAlarm.getDeviceCode() : "";
+        Map<String, EquipmentBase> eqMap = loadEquipMap();
+        EquipmentBase maxEq = eqMap.get(maxDevice);
+        Map<String, Object> r = new LinkedHashMap<>();
+        r.put("realtimeCount", realtime);
+        r.put("pendingCount", (int) realtime);
+        r.put("maxConcentration", (int) maxConc);
+        r.put("maxConcentrationPoint", maxEq != null ? maxEq.getEquipmentName() : maxDevice);
+        r.put("totalStations", codes.size());
+        return AjaxResult.success(r);
+    }
+
+    @PutMapping("/resolve/{id}")
+    @ApiOperation("标记已处理")
+    @Anonymous
+    public AjaxResult resolve(@PathVariable String id) {
+        AlarmData e = alarmDataService.getById(id);
+        if (e == null) return AjaxResult.error("记录不存在");
+        e.setAlarmStatus(1);
+        e.setHandleTime(java.time.LocalDateTime.now());
+        e.setHandleUser("当前用户");
+        alarmDataService.updateById(e);
+        return AjaxResult.success("已处理");
+    }
+
+    @PostMapping("/dispatch/{id}")
+    @ApiOperation("派发工单(伪)")
+    @Anonymous
+    public AjaxResult dispatch(@PathVariable String id) {
+        // TODO: 后续调用工单系统接口实现真正的派单逻辑
+        return AjaxResult.success("工单已派发(伪)");
+    }
+}

+ 37 - 6
pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/gasbasic/GasEquipmentController.java

@@ -5,12 +5,14 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.zksy.base.domain.EquipmentBase;
 import com.zksy.base.domain.EquipmentPointRel;
 import com.zksy.base.domain.EquipmentStatus;
+import com.zksy.base.domain.GasPipePoint;
 import com.zksy.base.domain.GasPipePointDeviceRel;
 import com.zksy.base.domain.vo.EquipmentFullVO;
 import com.zksy.base.service.GasEquipmentService;
 import com.zksy.base.mapper.EquipmentPointRelMapper;
 import com.zksy.base.mapper.EquipmentStatusMapper;
 import com.zksy.base.mapper.GasPipePointDeviceRelMapper;
+import com.zksy.base.mapper.GasPipePointMapper;
 import com.zksy.common.annotation.Anonymous;
 import com.zksy.common.annotation.Log;
 import com.zksy.common.core.domain.AjaxResult;
@@ -38,6 +40,9 @@ public class GasEquipmentController {
     @Autowired
     private EquipmentStatusMapper equipmentStatusMapper;
 
+    @Autowired
+    private GasPipePointMapper gasPipePointMapper;
+
     @Autowired
     private EquipmentPointRelMapper equipmentPointRelMapper;
 
@@ -122,19 +127,45 @@ public class GasEquipmentController {
     }
 
     @PostMapping("/save")
-    @ApiOperation("新增燃气设备")
+    @ApiOperation("新增燃气设备(含管网关联)")
     @Log(title = "新增燃气设备", businessType = BusinessType.INSERT)
-    public AjaxResult save(@RequestBody EquipmentBase entity) {
+    public AjaxResult save(@RequestBody EquipmentBase entity,
+                           @RequestParam(required = false) String networkId,
+                           @RequestParam(required = false) Long pointId) {
         boolean saved = gasEquipmentService.saveWithCheck(entity);
-        return saved ? AjaxResult.success("新增成功", entity.getEquipmentId()) : AjaxResult.error("新增失败");
+        if (!saved) return AjaxResult.error("新增失败");
+        saveDeviceRel(entity.getEquipmentId(), networkId, pointId);
+        return AjaxResult.success("新增成功", entity.getEquipmentId());
     }
 
     @PutMapping("/updateById")
-    @ApiOperation("修改燃气设备")
+    @ApiOperation("修改燃气设备(含管网关联)")
     @Log(title = "修改燃气设备", businessType = BusinessType.UPDATE)
-    public AjaxResult updateById(@RequestBody EquipmentBase entity) {
+    public AjaxResult updateById(@RequestBody EquipmentBase entity,
+                                  @RequestParam(required = false) String networkId,
+                                  @RequestParam(required = false) Long pointId) {
         boolean updated = gasEquipmentService.updateWithCheck(entity);
-        return updated ? AjaxResult.success("修改成功") : AjaxResult.error("修改失败");
+        if (!updated) return AjaxResult.error("修改失败");
+        saveDeviceRel(entity.getEquipmentId(), networkId, pointId);
+        return AjaxResult.success("修改成功");
+    }
+
+    private void saveDeviceRel(String equipmentId, String networkId, Long pointId) {
+        // 优先用 pointId;否则通过 networkId 找管点
+        Long pid = pointId;
+        if (pid == null && networkId != null && !networkId.isEmpty()) {
+            List<GasPipePoint> points = gasPipePointMapper.selectList(
+                    new LambdaQueryWrapper<GasPipePoint>().eq(GasPipePoint::getNetworkId, networkId));
+            if (!points.isEmpty()) pid = points.get(0).getPointId();
+        }
+        if (pid == null) return;
+        LambdaQueryWrapper<GasPipePointDeviceRel> w = new LambdaQueryWrapper<>();
+        w.eq(GasPipePointDeviceRel::getEquipmentId, equipmentId);
+        gasPipePointDeviceRelMapper.delete(w);
+        GasPipePointDeviceRel rel = new GasPipePointDeviceRel();
+        rel.setEquipmentId(equipmentId);
+        rel.setPointId(pid);
+        gasPipePointDeviceRelMapper.insert(rel);
     }
 
     @DeleteMapping("/deleteById")

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

@@ -23,6 +23,57 @@ public class GasRepairRecordController {
     @Autowired
     private PipeRepairRecordService service;
 
+    @Autowired
+    private com.zksy.base.mapper.PipeNetworkBaseMapper pipeNetworkBaseMapper;
+
+    @GetMapping("/stats")
+    @ApiOperation("维修统计+趋势")
+    @Anonymous
+    public AjaxResult stats() {
+        java.util.List<com.zksy.base.domain.PipeRepairRecord> all = service.list();
+        java.util.Map<String, Object> result = new java.util.LinkedHashMap<>();
+
+        // 维修统计 (按类型)
+        String[] typeNames = {"抢修","计划维修","防腐层修复","管件更换","焊缝修补","阀门维修"};
+        String[] colors = {"#e63946","#457b9d","#f77f00","#2a9d8f","#6c757d","#b08968"};
+        java.util.List<java.util.Map<String, Object>> typeStats = new java.util.ArrayList<>();
+        for (int i = 0; i < typeNames.length; i++) {
+            int idx = i + 1;
+            long cnt = all.stream().filter(r -> r.getRepairType() != null && String.valueOf(idx).equals(r.getRepairType())).count();
+            java.util.Map<String, Object> m = new java.util.LinkedHashMap<>();
+            m.put("name", typeNames[i]); m.put("value", cnt); m.put("color", colors[i]);
+            typeStats.add(m);
+        }
+        result.put("typeStats", typeStats);
+
+        // 维修趋势 (近6月, 按压力等级)
+        java.time.LocalDate today = java.time.LocalDate.now();
+        String[] months = new String[6];
+        int[] highData = new int[6], mediumData = new int[6], lowData = new int[6];
+        for (int i = 0; i < 6; i++) {
+            java.time.LocalDate m = today.minusMonths(5 - i);
+            months[i] = m.getMonthValue() + "月";
+        }
+        java.util.Map<String, String> levelMap = new java.util.LinkedHashMap<>();
+        pipeNetworkBaseMapper.selectList(null).forEach(n -> levelMap.put(n.getNetworkId(), n.getNetworkLevel()));
+        for (com.zksy.base.domain.PipeRepairRecord r : all) {
+            if (r.getRepairDate() == null) continue;
+            int mDiff = (today.getYear() - r.getRepairDate().getYear()) * 12 + today.getMonthValue() - r.getRepairDate().getMonthValue();
+            int idx = 5 - mDiff;
+            if (idx < 0 || idx >= 6) continue;
+            String lv = levelMap.getOrDefault(r.getNetworkId(), "medium");
+            if ("high".equals(lv)) highData[idx]++;
+            else if ("low".equals(lv)) lowData[idx]++;
+            else mediumData[idx]++;
+        }
+        java.util.Map<String, Object> trend = new java.util.LinkedHashMap<>();
+        trend.put("months", months); trend.put("high", highData);
+        trend.put("medium", mediumData); trend.put("low", lowData);
+        result.put("trend", trend);
+
+        return AjaxResult.success(result);
+    }
+
     @GetMapping("/findByPage")
     @ApiOperation("维修记录分页查询")
     @Anonymous

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

@@ -0,0 +1,150 @@
+package com.zksy.web.controller.gasbasic;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.zksy.base.alarm.domain.AlarmData;
+import com.zksy.base.alarm.service.AlarmDataService;
+import com.zksy.base.constant.GasEquipmentTypeEnum;
+import com.zksy.base.domain.EquipmentBase;
+import com.zksy.base.domain.EquipmentType;
+import com.zksy.base.mapper.EquipmentBaseMapper;
+import com.zksy.base.mapper.EquipmentTypeMapper;
+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.*;
+
+import java.time.format.DateTimeFormatter;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 管线压力异常报警
+ */
+@RestController
+@RequestMapping("/api/pressure-alarm")
+@Api(tags = "燃气监测-压力报警")
+public class PressureAlarmController {
+
+    @Autowired private AlarmDataService alarmDataService;
+    @Autowired private EquipmentTypeMapper equipmentTypeMapper;
+    @Autowired private EquipmentBaseMapper equipmentBaseMapper;
+
+    private static final DateTimeFormatter FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+
+    private Set<String> getPressureCodes() {
+        List<EquipmentType> types = equipmentTypeMapper.selectList(
+                new LambdaQueryWrapper<EquipmentType>().eq(EquipmentType::getTypeId, GasEquipmentTypeEnum.PRESSURE));
+        Set<String> typeIds = types.stream().map(EquipmentType::getId).collect(Collectors.toSet());
+        if (typeIds.isEmpty()) return Collections.emptySet();
+        return equipmentBaseMapper.selectList(
+                new LambdaQueryWrapper<EquipmentBase>().in(EquipmentBase::getEquipmentTypeId, typeIds))
+                .stream().map(EquipmentBase::getEquipmentCode).filter(Objects::nonNull).collect(Collectors.toSet());
+    }
+
+    private Map<String, EquipmentBase> loadEquipMap() {
+        return equipmentBaseMapper.selectList(null).stream()
+                .collect(Collectors.toMap(EquipmentBase::getEquipmentCode, e -> e, (a, b) -> a));
+    }
+
+    private Map<String, Object> toVo(AlarmData a, Map<String, EquipmentBase> eqMap) {
+        EquipmentBase eq = eqMap.get(a.getDeviceCode());
+        Map<String, Object> m = new LinkedHashMap<>();
+        m.put("id", a.getId()); m.put("deviceCode", a.getDeviceCode());
+        m.put("pointName", eq != null ? eq.getEquipmentName() : a.getDeviceCode());
+        m.put("address", eq != null ? eq.getEquipmentLocation() : "");
+        m.put("pressure", a.getActualValue()); m.put("threshold", a.getMaxValue());
+        m.put("alarmLevelText", a.getAlarmLevel() != null && a.getAlarmLevel() == 1 ? "严重" : "预警");
+        m.put("alarmTime", a.getAlarmTime() != null ? a.getAlarmTime().format(FMT) : "");
+        m.put("alarmStatus", a.getAlarmStatus());
+        m.put("statusText", a.getAlarmStatus() != null && a.getAlarmStatus() == 0 ? "待处理" : "已处理");
+        m.put("resolveTime", a.getHandleTime() != null ? a.getHandleTime().format(FMT) : null);
+        m.put("handler", a.getHandleUser()); m.put("remark", a.getHandleRemark());
+        return m;
+    }
+
+    @GetMapping("/equipment")
+    @ApiOperation("压力设备列表(含最新报警值)") @Anonymous
+    public AjaxResult equipment() {
+        Set<String> codes = getPressureCodes();
+        List<EquipmentBase> eqs = equipmentBaseMapper.selectList(null).stream()
+                .filter(e -> codes.contains(e.getEquipmentCode())).collect(Collectors.toList());
+        List<AlarmData> all = codes.isEmpty() ? Collections.emptyList()
+                : alarmDataService.list(new LambdaQueryWrapper<AlarmData>().in(AlarmData::getDeviceCode, codes)
+                        .orderByDesc(AlarmData::getAlarmTime));
+        Map<String, AlarmData> latestMap = all.stream()
+                .collect(Collectors.toMap(AlarmData::getDeviceCode, a -> a, (a, b) -> a));
+        List<Map<String, Object>> rows = new ArrayList<>();
+        for (EquipmentBase eq : eqs) {
+            AlarmData latest = latestMap.get(eq.getEquipmentCode());
+            Map<String, Object> m = new LinkedHashMap<>();
+            m.put("equipmentId", eq.getEquipmentId()); m.put("equipmentCode", eq.getEquipmentCode());
+            m.put("equipmentName", eq.getEquipmentName()); m.put("location", eq.getEquipmentLocation());
+            m.put("hasAlarm", latest != null && latest.getAlarmStatus() != null && latest.getAlarmStatus() == 0);
+            m.put("latestPressure", latest != null ? latest.getActualValue() : null);
+            m.put("latestThreshold", latest != null ? latest.getMaxValue() : null);
+            m.put("latestAlarmTime", latest != null && latest.getAlarmTime() != null ? latest.getAlarmTime().format(FMT) : null);
+            m.put("latestAlarmLevel", latest != null ? latest.getAlarmLevel() : null);
+            rows.add(m);
+        }
+        return AjaxResult.success(rows);
+    }
+
+    @GetMapping("/history")
+    @ApiOperation("历史报警分页") @Anonymous
+    public AjaxResult history(@RequestParam(defaultValue = "1") int pageNum,
+                              @RequestParam(defaultValue = "8") int pageSize,
+                              @RequestParam(required = false) String startDate,
+                              @RequestParam(required = false) String endDate,
+                              @RequestParam(required = false) String deviceCode) {
+        LambdaQueryWrapper<AlarmData> qw = new LambdaQueryWrapper<AlarmData>();
+        if (deviceCode != null && !deviceCode.isEmpty()) {
+            qw.eq(AlarmData::getDeviceCode, deviceCode);
+        } else {
+            Set<String> codes = getPressureCodes();
+            if (codes.isEmpty()) return AjaxResult.success(new Page<>(pageNum, pageSize, 0));
+            qw.in(AlarmData::getDeviceCode, codes);
+        }
+        if (startDate != null && !startDate.isEmpty()) qw.ge(AlarmData::getAlarmTime, startDate + " 00:00:00");
+        if (endDate != null && !endDate.isEmpty()) qw.le(AlarmData::getAlarmTime, endDate + " 23:59:59");
+        qw.orderByDesc(AlarmData::getAlarmTime);
+        Page<AlarmData> page = alarmDataService.page(new Page<>(pageNum, pageSize), qw);
+        Map<String, EquipmentBase> eqMap = loadEquipMap();
+        List<Map<String, Object>> rows = page.getRecords().stream().map(a -> toVo(a, eqMap)).collect(Collectors.toList());
+        Map<String, Object> result = new LinkedHashMap<>();
+        result.put("records", rows); result.put("total", page.getTotal());
+        return AjaxResult.success(result);
+    }
+
+    @GetMapping("/stats")
+    @ApiOperation("统计") @Anonymous
+    public AjaxResult stats() {
+        Set<String> codes = getPressureCodes();
+        List<AlarmData> all = codes.isEmpty() ? Collections.emptyList()
+                : alarmDataService.list(new LambdaQueryWrapper<AlarmData>().in(AlarmData::getDeviceCode, codes));
+        long realtime = all.stream().filter(a -> a.getAlarmStatus() != null && a.getAlarmStatus() == 0)
+                .map(AlarmData::getDeviceCode).filter(Objects::nonNull).distinct().count();
+        double maxP = all.stream().filter(a -> a.getActualValue() != null)
+                .mapToDouble(a -> a.getActualValue().doubleValue()).max().orElse(0);
+        Map<String, Object> r = new LinkedHashMap<>();
+        r.put("realtimeCount", (int) realtime); r.put("pendingCount", (int) realtime);
+        r.put("maxPressure", (int) maxP); r.put("totalPoints", codes.size());
+        return AjaxResult.success(r);
+    }
+
+    @PutMapping("/resolve/{id}") @ApiOperation("标记已处理") @Anonymous
+    public AjaxResult resolve(@PathVariable String id) {
+        AlarmData e = alarmDataService.getById(id);
+        if (e == null) return AjaxResult.error("记录不存在");
+        e.setAlarmStatus(1); e.setHandleTime(java.time.LocalDateTime.now()); e.setHandleUser("当前用户");
+        alarmDataService.updateById(e);
+        return AjaxResult.success("已处理");
+    }
+
+    @PostMapping("/dispatch/{id}") @ApiOperation("派发工单(伪)") @Anonymous
+    public AjaxResult dispatch(@PathVariable String id) {
+        return AjaxResult.success("工单已派发(伪)");
+    }
+}

+ 22 - 0
pipe-network-service/zksy-system/src/main/java/com/zksy/base/constant/GasEquipmentTypeEnum.java

@@ -0,0 +1,22 @@
+package com.zksy.base.constant;
+
+/** 燃气设备类型常量 */
+public final class GasEquipmentTypeEnum {
+
+    private GasEquipmentTypeEnum() {}
+
+    /** 燃气顶级分类 parent_type_id */
+    public static final String PARENT_TYPE_ID = "3";
+
+    /** 可燃气体探测器 type_id */
+    public static final String GAS_DETECTOR = "gas_detector";
+
+    /** 压力设备 type_id */
+    public static final String PRESSURE = "gas_pressure";
+
+    /** 环境设备 type_id */
+    public static final String ENVIRONMENT = "gas_env";
+
+    /** 监测点类型 */
+    public static final String POINT_TYPE_GAS = "燃气监测";
+}

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

@@ -2,6 +2,7 @@ package com.zksy.base.service.impl;
 
 import cn.hutool.core.bean.BeanUtil;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.zksy.base.constant.GasEquipmentTypeEnum;
 import com.zksy.base.domain.EquipmentBase;
 import com.zksy.base.domain.EquipmentType;
 import com.zksy.base.domain.TerminalFault;
@@ -33,8 +34,8 @@ public class EquipmentAbnormalServiceImpl implements EquipmentAbnormalService {
     @Autowired
     private EquipmentTypeMapper equipmentTypeMapper;
 
-    /** 燃气顶级类型: parent_type_id = "3" */
-    private static final String GAS_PARENT_TYPE_ID = "3";
+    /** 燃气顶级类型 parent_type_id */
+    private static final String GAS_PARENT_TYPE_ID = GasEquipmentTypeEnum.PARENT_TYPE_ID;
 
     // 故障等级文本
     private static final Map<Integer, String> LEVEL_MAP = new HashMap<>();
@@ -195,7 +196,7 @@ public class EquipmentAbnormalServiceImpl implements EquipmentAbnormalService {
         int recentFaultCount = recentFaults.size();
         long totalDevices = recentFaults.stream().map(TerminalFault::getDeviceId).filter(Objects::nonNull).distinct().count();
 
-        // 今日新增 = happen_time为今天(去重设备数)
+        // 今日新增 = 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();

+ 69 - 1
pipe-network-service/zksy-system/src/main/java/com/zksy/base/service/impl/GasDashboardServiceImpl.java

@@ -1,6 +1,8 @@
 package com.zksy.base.service.impl;
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.zksy.base.constant.GasEquipmentTypeEnum;
 import com.zksy.base.domain.*;
 import com.zksy.base.mapper.*;
 import com.zksy.base.service.GasDashboardService;
@@ -34,6 +36,12 @@ public class GasDashboardServiceImpl implements GasDashboardService {
     @Autowired
     private EquipmentTypeMapper equipmentTypeMapper;
 
+    @Autowired
+    private PipeNetworkBaseMapper pipeNetworkBaseMapper;
+
+    @Autowired
+    private com.zksy.base.pressure.mapper.FirefightingPressureMapper firefightingPressureMapper;
+
     @Override
     public Map<String, Object> summary() {
         Map<String, Object> result = new LinkedHashMap<>();
@@ -50,7 +58,7 @@ public class GasDashboardServiceImpl implements GasDashboardService {
 
         // 2. 燃气设备 + 异常设备统计
         List<String> gasTypeIds = equipmentTypeMapper.selectList(null).stream()
-                .filter(t -> "3".equals(t.getParentTypeId()))
+                .filter(t -> GasEquipmentTypeEnum.PARENT_TYPE_ID.equals(t.getParentTypeId()))
                 .map(EquipmentType::getId)
                 .collect(Collectors.toList());
         List<EquipmentBase> gasEquipments;
@@ -83,6 +91,66 @@ public class GasDashboardServiceImpl implements GasDashboardService {
         baseStats.put("alarmCount", alarmCount);
         result.put("baseStats", baseStats);
 
+        // ---- 管网统计 ----
+        List<PipeNetworkBase> allNetworks = pipeNetworkBaseMapper.
+                selectList(new LambdaQueryWrapper<PipeNetworkBase>().eq(PipeNetworkBase::getNetworkType, "GAS"));
+        // 管网总长度(公里)
+        double totalLength = allNetworks.stream()
+                .filter(n -> n.getLength() != null)
+                .mapToDouble(n -> n.getLength().doubleValue())
+                .sum();
+        double totalLengthKm = Math.round(totalLength / 1000.0 * 100) / 100.0;
+        // 燃气管网数量
+        long gasNetworkCount = allNetworks.stream()
+                .filter(n -> gasTypeIds.contains(n.getNetworkType()) || "GAS".equals(n.getNetworkType()))
+                .count();
+
+        Map<String, Object> pipeStats = new LinkedHashMap<>();
+        pipeStats.put("totalLength", totalLengthKm);        // 管网总长度(km)
+        pipeStats.put("totalNetworks", allNetworks.size()); // 管网总数
+        pipeStats.put("gasNetworks", gasNetworkCount);      // 燃气管网数
+        result.put("pipeStats", pipeStats);
+
+        // ---- 设备统计 ----
+        // 门阀数 = 燃气设备中类型名称含"阀"的数量
+        Map<String, String> typeIdToName = equipmentTypeMapper.selectList(null).stream()
+                .collect(Collectors.toMap(EquipmentType::getId, EquipmentType::getTypeName, (a, b) -> a));
+        long valveCount = gasEquipments.stream()
+                .filter(e -> {
+                    String typeName = typeIdToName.getOrDefault(e.getEquipmentTypeId(), "");
+                    return typeName.contains("阀");
+                })
+                .count();
+        // 今日告警
+        String todayStr = LocalDate.now().toString();
+        long todayAlarm = equipmentStatusMapper.selectList(
+                new LambdaQueryWrapper<EquipmentStatus>()
+                        .in(EquipmentStatus::getEquipmentId, gasEquipmentIds)
+                        .eq(EquipmentStatus::getAlarmStatus, 1)).stream()
+                .filter(s -> s.getStatusUpdateTime() != null && s.getStatusUpdateTime().toString().startsWith(todayStr))
+                .count();
+
+        // 压力均值(从firefighting_pressure取最新一条)
+        double avgPressure = 0;
+        try {
+            List<com.zksy.base.pressure.domain.FirefightingPressure> fpList =
+                    firefightingPressureMapper.selectList(null);
+            if (!fpList.isEmpty()) {
+                avgPressure = fpList.stream()
+                        .filter(p -> p.getPressureValue() != null)
+                        .mapToDouble(com.zksy.base.pressure.domain.FirefightingPressure::getPressureValue)
+                        .average().orElse(0);
+            }
+        } catch (Exception ignored) {}
+        double avgPressureMpa = Math.round(avgPressure / 1000.0 * 100) / 100.0; // kPa → MPa
+
+        Map<String, Object> equipmentStats = new LinkedHashMap<>();
+        equipmentStats.put("valveCount", valveCount);
+        equipmentStats.put("totalEquipment", gasEquipments.size());
+        equipmentStats.put("todayAlarm", todayAlarm);
+        equipmentStats.put("avgPressure", avgPressureMpa);
+        result.put("equipmentStats", equipmentStats);
+
         List<EquipmentBase> equipmentList = gasEquipments.stream()
                 .limit(10)
                 .collect(Collectors.toList());

+ 9 - 1
pipe-network-service/zksy-system/src/main/java/com/zksy/base/service/impl/GasEquipmentServiceImpl.java

@@ -4,6 +4,7 @@ import cn.hutool.core.bean.BeanUtil;
 import cn.hutool.core.collection.CollUtil;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.zksy.base.constant.GasEquipmentTypeEnum;
 import com.zksy.base.domain.*;
 import com.zksy.base.domain.vo.EquipmentFullVO;
 import com.zksy.base.mapper.*;
@@ -132,9 +133,16 @@ public class GasEquipmentServiceImpl implements GasEquipmentService {
     public Page<EquipmentFullVO> findFullByPage(long pageNum, long pageSize,
                                                  String equipmentCode, String equipmentName,
                                                  String equipmentModel, String manufacturer) {
-        // 1. 分页查询设备基础信息
+        // 只查燃气类型设备: parent_type_id = "3"
+        List<String> gasTypeIds = equipmentTypeMapper.selectList(
+                new LambdaQueryWrapper<EquipmentType>().eq(EquipmentType::getParentTypeId, GasEquipmentTypeEnum.PARENT_TYPE_ID)
+        ).stream().map(EquipmentType::getId).collect(Collectors.toList());
+
         Page<EquipmentBase> page = new Page<>(pageNum, pageSize);
         LambdaQueryWrapper<EquipmentBase> qw = new LambdaQueryWrapper<>();
+        if (!gasTypeIds.isEmpty()) {
+            qw.in(EquipmentBase::getEquipmentTypeId, gasTypeIds);
+        }
         if (equipmentCode != null && !equipmentCode.isEmpty()) {
             qw.like(EquipmentBase::getEquipmentCode, equipmentCode);
         }

+ 25 - 7
pipe-network-service/zksy-system/src/main/java/com/zksy/base/service/impl/GasMonitorServiceImpl.java

@@ -3,6 +3,7 @@ package com.zksy.base.service.impl;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.zksy.base.domain.EquipmentBase;
 import com.zksy.base.domain.EquipmentPointRel;
+import com.zksy.base.domain.EquipmentType;
 import com.zksy.base.domain.MonitorPoint;
 import com.zksy.base.environment.domain.ERealTimeData;
 import com.zksy.base.environment.mapper.ERealTimeDataMapper;
@@ -10,6 +11,7 @@ import com.zksy.base.gas.domain.GasMonitorData;
 import com.zksy.base.gas.mapper.GasMonitorDataMapper;
 import com.zksy.base.mapper.EquipmentBaseMapper;
 import com.zksy.base.mapper.EquipmentPointRelMapper;
+import com.zksy.base.mapper.EquipmentTypeMapper;
 import com.zksy.base.mapper.MonitorPointMapper;
 import com.zksy.base.service.GasMonitorService;
 import lombok.extern.slf4j.Slf4j;
@@ -36,21 +38,24 @@ public class GasMonitorServiceImpl implements GasMonitorService {
     private GasMonitorDataMapper gasMonitorDataMapper;
     @Autowired
     private ERealTimeDataMapper eRealTimeDataMapper;
+    @Autowired
+    private EquipmentTypeMapper equipmentTypeMapper;
 
     private static final DateTimeFormatter DT_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
 
-    // 设备类型常量
-    private static final String TYPE_GAS = "GAS-COMBUSTIBLE";
-    private static final String TYPE_ENV = "GAS-ENV";
+    /** 燃气设备父类ID */
+    private static final String GAS_PARENT_TYPE_ID = "3";
 
     @Override
     public List<Map<String, Object>> getPoints() {
         List<MonitorPoint> points = monitorPointMapper.selectList(
                 new LambdaQueryWrapper<MonitorPoint>()
-                        .eq(MonitorPoint::getPointType, "燃气监测")
+                        .eq(MonitorPoint::getPointType, "GAS")
                         .eq(MonitorPoint::getDelFlag, "0")
         );
 
+        Set<String> gasParentTypeIds = getGasParentTypeIds();
+
         List<Map<String, Object>> result = new ArrayList<>();
         for (MonitorPoint p : points) {
             Map<String, Object> item = new LinkedHashMap<>();
@@ -74,7 +79,7 @@ public class GasMonitorServiceImpl implements GasMonitorService {
                 item.put("deviceCount", equipments.size());
 
                 for (EquipmentBase eq : equipments) {
-                    if (TYPE_GAS.equals(eq.getEquipmentTypeId())) {
+                    if (gasParentTypeIds.contains(eq.getEquipmentTypeId())) {
                         // 气体设备: mac_address = equipment_code
                         GasMonitorData latestGas = getLatestGasData(eq.getEquipmentCode());
                         if (latestGas != null) {
@@ -84,7 +89,7 @@ public class GasMonitorServiceImpl implements GasMonitorService {
                             item.put("alertStatus", methane > 20.0);
                         }
                     }
-                    if (TYPE_ENV.equals(eq.getEquipmentTypeId())) {
+                    if (eq.getEquipmentTypeId() != null && eq.getEquipmentTypeId().contains("ENV")) {
                         // 环境设备: device_id = Integer(equipment_code)
                         ERealTimeData latestEnv = getLatestEnvData(parseIntSafe(eq.getEquipmentCode()));
                         if (latestEnv != null) {
@@ -180,8 +185,9 @@ public class GasMonitorServiceImpl implements GasMonitorService {
         if (rels.isEmpty()) return null;
         List<String> eqIds = rels.stream().map(EquipmentPointRel::getEquipmentId).collect(Collectors.toList());
         List<EquipmentBase> eqs = equipmentBaseMapper.selectBatchIds(eqIds);
+        Set<String> gasParentTypeIds = getGasParentTypeIds();
         for (EquipmentBase eq : eqs) {
-            if (TYPE_GAS.equals(eq.getEquipmentTypeId())) {
+            if (gasParentTypeIds.contains(eq.getEquipmentTypeId())) {
                 return eq.getEquipmentCode();
             }
         }
@@ -211,4 +217,16 @@ public class GasMonitorServiceImpl implements GasMonitorService {
     private int parseIntSafe(String s) {
         try { return Integer.parseInt(s); } catch (Exception e) { return 0; }
     }
+
+    /**
+     * 查询父类ID为3(燃气)的所有设备类型ID集合
+     */
+    private Set<String> getGasParentTypeIds() {
+        List<EquipmentType> gasTypes = equipmentTypeMapper.selectList(
+                new LambdaQueryWrapper<EquipmentType>()
+                        .eq(EquipmentType::getParentTypeId, GAS_PARENT_TYPE_ID));
+        return gasTypes.stream()
+                .map(EquipmentType::getId)
+                .collect(Collectors.toSet());
+    }
 }

+ 1 - 1
pipe-network-service/zksy-system/src/main/java/com/zksy/base/service/impl/PressureMonitorServiceImpl.java

@@ -49,7 +49,7 @@ public class PressureMonitorServiceImpl implements PressureMonitorService {
     public List<Map<String, Object>> getPoints() {
         List<MonitorPoint> points = monitorPointMapper.selectList(
                 new LambdaQueryWrapper<MonitorPoint>()
-                        .eq(MonitorPoint::getPointType, "燃气监测")
+                        .eq(MonitorPoint::getPointType, "GAS")
                         .eq(MonitorPoint::getDelFlag, "0")
         );