Эх сурвалжийг харах

feat(warning): 新增行业预警处置分析功能

- 在EarlyWarningMapper中添加四个新的查询方法:selectIndustryAnalysisSummary、selectIndustryStatusDistribution、selectIndustryEfficiencyComparison和selectIndustryAnalysisList
- 实现复杂的CTE查询逻辑,支持设备实例编码和设备类别业务编码两种历史格式的兼容处理
- 创建EarlyWarningIndustryAnalysisController控制器,提供汇总统计、状态分布、效率对比和明细列表四个接口
- 实现EarlyWarningIndustryAnalysisServiceImpl服务层,包含完整的业务逻辑处理
- 添加行业代码标准化处理,支持water_supply、drainage、gas、manhole四个行业类型
- 实现预警状态归一化处理,将CLOSED与RESOLVED统一为已解除状态
- 提供分页查询和排序功能,支持按处置时长和发布时间排序
- 集成Swagger文档注解,提供完整的API文档支持
林仔 1 сар өмнө
parent
commit
53a02e2b6f

+ 98 - 0
pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/warning/EarlyWarningIndustryAnalysisController.java

@@ -0,0 +1,98 @@
+package com.zksy.web.controller.warning;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.zksy.base.warning.service.IEarlyWarningIndustryAnalysisService;
+import com.zksy.common.core.domain.AjaxResult;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.format.annotation.DateTimeFormat;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.time.LocalDateTime;
+import java.util.Map;
+
+@RestController
+@RequestMapping("/warning/industryAnalysis")
+@Api(tags = "预警信息-行业处置分析模块")
+public class EarlyWarningIndustryAnalysisController {
+
+    private static final Logger log = LoggerFactory.getLogger(EarlyWarningIndustryAnalysisController.class);
+
+    @Autowired
+    private IEarlyWarningIndustryAnalysisService industryAnalysisService;
+
+    @GetMapping("/summary")
+    @PreAuthorize("@ss.hasPermi('warning:statistics:industry')")
+    @ApiOperation("各行业预警处置总体情况")
+    public AjaxResult summary(
+            @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime startTime,
+            @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime endTime,
+            @RequestParam(required = false) String industryCode) {
+        try {
+            return AjaxResult.success(industryAnalysisService.getSummary(startTime, endTime, industryCode));
+        } catch (Exception e) {
+            log.error("行业处置汇总查询异常", e);
+            return AjaxResult.error("统计失败:" + e.getMessage());
+        }
+    }
+
+    @GetMapping("/status")
+    @PreAuthorize("@ss.hasPermi('warning:statistics:industry')")
+    @ApiOperation("各行业预警处置状态分布")
+    public AjaxResult status(
+            @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime startTime,
+            @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime endTime,
+            @RequestParam(required = false) String industryCode) {
+        try {
+            return AjaxResult.success(industryAnalysisService.getStatusDistribution(startTime, endTime, industryCode));
+        } catch (Exception e) {
+            log.error("行业处置状态查询异常", e);
+            return AjaxResult.error("统计失败:" + e.getMessage());
+        }
+    }
+
+    @GetMapping("/efficiency")
+    @PreAuthorize("@ss.hasPermi('warning:statistics:industry')")
+    @ApiOperation("各行业预警处置时效对比")
+    public AjaxResult efficiency(
+            @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime startTime,
+            @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime endTime,
+            @RequestParam(required = false) String industryCode) {
+        try {
+            return AjaxResult.success(industryAnalysisService.getEfficiencyComparison(startTime, endTime, industryCode));
+        } catch (Exception e) {
+            log.error("行业处置时效查询异常", e);
+            return AjaxResult.error("统计失败:" + e.getMessage());
+        }
+    }
+
+    @GetMapping("/page")
+    @PreAuthorize("@ss.hasPermi('warning:statistics:industry')")
+    @ApiOperation("行业预警处置分析明细")
+    public AjaxResult page(
+            @RequestParam(defaultValue = "1") Integer pageNum,
+            @RequestParam(defaultValue = "10") Integer pageSize,
+            @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime startTime,
+            @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime endTime,
+            @RequestParam(required = false) String industryCode,
+            @RequestParam(required = false) String status,
+            @RequestParam(defaultValue = "desc") String sortType) {
+        try {
+            Page<Map<String, Object>> page = new Page<>(pageNum, pageSize);
+            IPage<Map<String, Object>> result = industryAnalysisService.queryPage(
+                    page, startTime, endTime, industryCode, status, sortType);
+            return AjaxResult.success(result);
+        } catch (Exception e) {
+            log.error("行业处置分析明细查询异常", e);
+            return AjaxResult.error("查询失败:" + e.getMessage());
+        }
+    }
+}

+ 23 - 0
pipe-network-service/zksy-system/src/main/java/com/zksy/base/warning/mapper/EarlyWarningMapper.java

@@ -196,6 +196,29 @@ public interface EarlyWarningMapper extends BaseMapper<EarlyWarning> {
             @Param("status") String status,
             @Param("sortType") String sortType);
 
+    List<Map<String, Object>> selectIndustryAnalysisSummary(
+            @Param("startTime") LocalDateTime startTime,
+            @Param("endTime") LocalDateTime endTime,
+            @Param("industryCode") String industryCode);
+
+    List<Map<String, Object>> selectIndustryStatusDistribution(
+            @Param("startTime") LocalDateTime startTime,
+            @Param("endTime") LocalDateTime endTime,
+            @Param("industryCode") String industryCode);
+
+    List<Map<String, Object>> selectIndustryEfficiencyComparison(
+            @Param("startTime") LocalDateTime startTime,
+            @Param("endTime") LocalDateTime endTime,
+            @Param("industryCode") String industryCode);
+
+    IPage<Map<String, Object>> selectIndustryAnalysisList(
+            Page<Map<String, Object>> page,
+            @Param("startTime") LocalDateTime startTime,
+            @Param("endTime") LocalDateTime endTime,
+            @Param("industryCode") String industryCode,
+            @Param("status") String status,
+            @Param("sortType") String sortType);
+
     /**
      * Warning overview: current active warnings, warnings resolved today and
      * warnings published today. Drafts and unsupported facility types are excluded.

+ 24 - 0
pipe-network-service/zksy-system/src/main/java/com/zksy/base/warning/service/IEarlyWarningIndustryAnalysisService.java

@@ -0,0 +1,24 @@
+package com.zksy.base.warning.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Map;
+
+public interface IEarlyWarningIndustryAnalysisService {
+
+    List<Map<String, Object>> getSummary(LocalDateTime startTime, LocalDateTime endTime, String industryCode);
+
+    List<Map<String, Object>> getStatusDistribution(LocalDateTime startTime, LocalDateTime endTime, String industryCode);
+
+    List<Map<String, Object>> getEfficiencyComparison(LocalDateTime startTime, LocalDateTime endTime, String industryCode);
+
+    IPage<Map<String, Object>> queryPage(Page<Map<String, Object>> page,
+                                         LocalDateTime startTime,
+                                         LocalDateTime endTime,
+                                         String industryCode,
+                                         String status,
+                                         String sortType);
+}

+ 64 - 0
pipe-network-service/zksy-system/src/main/java/com/zksy/base/warning/service/impl/EarlyWarningIndustryAnalysisServiceImpl.java

@@ -0,0 +1,64 @@
+package com.zksy.base.warning.service.impl;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.zksy.base.warning.mapper.EarlyWarningMapper;
+import com.zksy.base.warning.service.IEarlyWarningIndustryAnalysisService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+@Service
+public class EarlyWarningIndustryAnalysisServiceImpl implements IEarlyWarningIndustryAnalysisService {
+
+    private static final Set<String> SUPPORTED_INDUSTRIES = Set.of("water_supply", "drainage", "gas", "manhole");
+
+    @Autowired
+    private EarlyWarningMapper earlyWarningMapper;
+
+    @Override
+    public List<Map<String, Object>> getSummary(LocalDateTime startTime, LocalDateTime endTime, String industryCode) {
+        return earlyWarningMapper.selectIndustryAnalysisSummary(startTime, endTime, normalizeIndustry(industryCode));
+    }
+
+    @Override
+    public List<Map<String, Object>> getStatusDistribution(LocalDateTime startTime, LocalDateTime endTime, String industryCode) {
+        return earlyWarningMapper.selectIndustryStatusDistribution(startTime, endTime, normalizeIndustry(industryCode));
+    }
+
+    @Override
+    public List<Map<String, Object>> getEfficiencyComparison(LocalDateTime startTime, LocalDateTime endTime, String industryCode) {
+        return earlyWarningMapper.selectIndustryEfficiencyComparison(startTime, endTime, normalizeIndustry(industryCode));
+    }
+
+    @Override
+    public IPage<Map<String, Object>> queryPage(Page<Map<String, Object>> page,
+                                                LocalDateTime startTime,
+                                                LocalDateTime endTime,
+                                                String industryCode,
+                                                String status,
+                                                String sortType) {
+        String safeSortType = "asc".equalsIgnoreCase(sortType) ? "asc" : "desc";
+        return earlyWarningMapper.selectIndustryAnalysisList(page, startTime, endTime,
+                normalizeIndustry(industryCode), normalizeStatus(status), safeSortType);
+    }
+
+    private String normalizeIndustry(String industryCode) {
+        if (industryCode == null || industryCode.trim().isEmpty()) {
+            return null;
+        }
+        String normalized = industryCode.trim().toLowerCase();
+        return SUPPORTED_INDUSTRIES.contains(normalized) ? normalized : null;
+    }
+
+    private String normalizeStatus(String status) {
+        if (status == null || status.trim().isEmpty()) {
+            return null;
+        }
+        return status.trim().toUpperCase();
+    }
+}

+ 165 - 0
pipe-network-service/zksy-system/src/main/resources/mapper/warning/EarlyWarningMapper.xml

@@ -993,4 +993,169 @@
         FROM early_warning
     </select>
 
+    <!-- 行业处置分析:兼容 device_code 存设备实例编码或设备类别业务编码两种历史格式。 -->
+    <sql id="IndustryWarningCte">
+        WITH RECURSIVE type_tree AS (
+            SELECT et.id AS leaf_id, et.id AS current_id, et.type_id AS current_type_id,
+                   et.type_name AS current_name, et.parent_type_id, CAST(0 AS bigint) AS depth
+            FROM app_user.equipment_type et
+            UNION ALL
+            SELECT tree.leaf_id, parent.id, parent.type_id, parent.type_name,
+                   parent.parent_type_id, tree.depth + 1
+            FROM type_tree tree
+            INNER JOIN app_user.equipment_type parent ON parent.id = tree.parent_type_id
+        ),
+        root_ranked AS (
+            SELECT leaf_id, current_type_id AS industry_code, current_name AS industry_name,
+                   ROW_NUMBER() OVER (PARTITION BY leaf_id ORDER BY depth DESC) AS row_num
+            FROM type_tree
+        ),
+        type_roots AS (
+            SELECT leaf_id, industry_code, industry_name
+            FROM root_ranked
+            WHERE row_num = 1
+              AND industry_code IN ('water_supply', 'drainage', 'gas', 'manhole')
+        ),
+        equipment_map AS (
+            SELECT equipment_code, MIN(equipment_type_id) AS equipment_type_id
+            FROM app_user.equipment_base
+            WHERE del_flag = '0'
+            GROUP BY equipment_code
+        ),
+        type_code_map AS (
+            SELECT type_id, MIN(id) AS id
+            FROM app_user.equipment_type
+            GROUP BY type_id
+        ),
+        warning_industry AS (
+            SELECT w.*, roots.industry_code, roots.industry_name
+            FROM app_user.early_warning w
+            LEFT JOIN equipment_map equipment ON equipment.equipment_code = w.device_code
+            LEFT JOIN type_code_map direct_type ON direct_type.type_id = w.device_code
+            INNER JOIN type_roots roots
+                    ON roots.leaf_id = COALESCE(equipment.equipment_type_id, direct_type.id)
+            WHERE w.status != 'DRAFT'
+            <if test="startTime != null">
+                AND w.publish_time &gt;= #{startTime}
+            </if>
+            <if test="endTime != null">
+                AND w.publish_time &lt; #{endTime}
+            </if>
+            <if test="industryCode != null and industryCode != ''">
+                AND roots.industry_code = #{industryCode}
+            </if>
+        ),
+        completed_actions AS (
+            SELECT disposal.warning_id, MAX(disposal.create_time) AS dispose_time
+            FROM app_user.warning_disposal disposal
+            WHERE disposal.disposal_type IN ('HANDLE', 'RESOLVE', 'CLEAR', 'CLOSE', 'MISREPORT')
+            GROUP BY disposal.warning_id
+        )
+    </sql>
+
+    <select id="selectIndustryAnalysisSummary" resultType="java.util.Map">
+        <include refid="IndustryWarningCte"/>
+        SELECT warning.industry_code,
+               warning.industry_name,
+               COUNT(*) AS total_count,
+               SUM(CASE WHEN warning.status IN ('PENDING', 'RELEASED') THEN 1 ELSE 0 END) AS pending_count,
+               SUM(CASE WHEN warning.status = 'PROCESSING' THEN 1 ELSE 0 END) AS processing_count,
+               SUM(CASE WHEN warning.status IN ('HANDLED', 'CLOSED', 'RESOLVED') THEN 1 ELSE 0 END) AS completed_count,
+               ROUND(100.0 * SUM(CASE WHEN warning.status IN ('HANDLED', 'CLOSED', 'RESOLVED') THEN 1 ELSE 0 END)
+                     / NULLIF(COUNT(*), 0), 2) AS completion_rate,
+               ROUND(AVG(CASE
+                   WHEN warning.status IN ('HANDLED', 'CLOSED', 'RESOLVED')
+                    AND action.dispose_time &gt;= warning.publish_time
+                   THEN EXTRACT(EPOCH FROM (CAST(action.dispose_time AS TIMESTAMPTZ)
+                                           - CAST(warning.publish_time AS TIMESTAMPTZ))) / 3600
+               END), 2) AS avg_dispose_hours,
+               SUM(CASE
+                   WHEN warning.status IN ('HANDLED', 'CLOSED', 'RESOLVED')
+                    AND action.dispose_time &gt;= warning.publish_time
+                    AND EXTRACT(EPOCH FROM (CAST(action.dispose_time AS TIMESTAMPTZ)
+                                           - CAST(warning.publish_time AS TIMESTAMPTZ))) / 3600 &gt; 24
+                   THEN 1 ELSE 0 END) AS over_24h_count
+        FROM warning_industry warning
+        LEFT JOIN completed_actions action ON action.warning_id = warning.warning_id
+        GROUP BY warning.industry_code, warning.industry_name
+        ORDER BY CASE warning.industry_code
+            WHEN 'gas' THEN 1 WHEN 'water_supply' THEN 2
+            WHEN 'drainage' THEN 3 WHEN 'manhole' THEN 4 ELSE 5 END
+    </select>
+
+    <select id="selectIndustryStatusDistribution" resultType="java.util.Map">
+        <include refid="IndustryWarningCte"/>
+        <!-- CLOSED 与 RESOLVED 是历史上并存的两个“已解除”编码,分析时统一为 CLOSED。 -->
+        SELECT industry_code,
+               industry_name,
+               CASE WHEN status IN ('CLOSED', 'RESOLVED') THEN 'CLOSED' ELSE status END AS status,
+               COUNT(*) AS warning_count
+        FROM warning_industry
+        GROUP BY industry_code,
+                 industry_name,
+                 CASE WHEN status IN ('CLOSED', 'RESOLVED') THEN 'CLOSED' ELSE status END
+        ORDER BY industry_name,
+                 CASE WHEN status IN ('CLOSED', 'RESOLVED') THEN 'CLOSED' ELSE status END
+    </select>
+
+    <select id="selectIndustryEfficiencyComparison" resultType="java.util.Map">
+        <include refid="IndustryWarningCte"/>
+        SELECT warning.industry_code,
+               warning.industry_name,
+               COUNT(*) AS completed_count,
+               ROUND(AVG(EXTRACT(EPOCH FROM (CAST(action.dispose_time AS TIMESTAMPTZ)
+                                            - CAST(warning.publish_time AS TIMESTAMPTZ))) / 3600), 2) AS avg_dispose_hours,
+               ROUND(MAX(EXTRACT(EPOCH FROM (CAST(action.dispose_time AS TIMESTAMPTZ)
+                                            - CAST(warning.publish_time AS TIMESTAMPTZ))) / 3600), 2) AS max_dispose_hours,
+               ROUND(MIN(EXTRACT(EPOCH FROM (CAST(action.dispose_time AS TIMESTAMPTZ)
+                                            - CAST(warning.publish_time AS TIMESTAMPTZ))) / 3600), 2) AS min_dispose_hours,
+               ROUND(100.0 * SUM(CASE WHEN EXTRACT(EPOCH FROM (CAST(action.dispose_time AS TIMESTAMPTZ)
+                                            - CAST(warning.publish_time AS TIMESTAMPTZ))) / 3600 &lt;= 24
+                                      THEN 1 ELSE 0 END) / NULLIF(COUNT(*), 0), 2) AS within_24h_rate
+        FROM warning_industry warning
+        INNER JOIN completed_actions action ON action.warning_id = warning.warning_id
+        WHERE warning.status IN ('HANDLED', 'CLOSED', 'RESOLVED')
+          AND action.dispose_time &gt;= warning.publish_time
+        GROUP BY warning.industry_code, warning.industry_name
+        ORDER BY avg_dispose_hours DESC
+    </select>
+
+    <select id="selectIndustryAnalysisList" resultType="java.util.Map">
+        <include refid="IndustryWarningCte"/>
+        SELECT warning.warning_id,
+               warning.warning_no,
+               warning.warning_name,
+               warning.device_code,
+               warning.device_name,
+               warning.industry_code,
+               warning.industry_name,
+               warning.warning_level,
+               warning.publish_time,
+               warning.status,
+               action.dispose_time,
+               CASE WHEN action.dispose_time &gt;= warning.publish_time
+                    THEN ROUND(EXTRACT(EPOCH FROM (CAST(action.dispose_time AS TIMESTAMPTZ)
+                                                 - CAST(warning.publish_time AS TIMESTAMPTZ))) / 3600, 2)
+                    ELSE NULL END AS dispose_hours
+        FROM warning_industry warning
+        LEFT JOIN completed_actions action ON action.warning_id = warning.warning_id
+        <where>
+            <if test="status != null and status != ''">
+                <choose>
+                    <!-- 页面只提供一个“已解除”筛选项,但兼容两种历史状态值。 -->
+                    <when test="status == 'CLOSED' or status == 'RESOLVED'">
+                        warning.status IN ('CLOSED', 'RESOLVED')
+                    </when>
+                    <otherwise>
+                        warning.status = #{status}
+                    </otherwise>
+                </choose>
+            </if>
+        </where>
+        ORDER BY dispose_hours
+        <if test="sortType != null and sortType == 'desc'">DESC</if>
+        <if test="sortType == null or sortType == 'asc'">ASC</if>
+        NULLS LAST, warning.publish_time DESC
+    </select>
+
 </mapper>