sunhuijun hace 2 semanas
padre
commit
3da04bf11b
Se han modificado 39 ficheros con 3405 adiciones y 2 borrados
  1. 45 0
      pom.xml
  2. 37 0
      src/main/java/cn/chinaunicom/omniFlowNetCompute/configure/WebConfig.java
  3. 150 0
      src/main/java/cn/chinaunicom/omniFlowNetCompute/controller/AlarmController.java
  4. 165 0
      src/main/java/cn/chinaunicom/omniFlowNetCompute/controller/InspectionController.java
  5. 100 0
      src/main/java/cn/chinaunicom/omniFlowNetCompute/controller/LoginController.java
  6. 19 0
      src/main/java/cn/chinaunicom/omniFlowNetCompute/domain/Alarm.java
  7. 16 0
      src/main/java/cn/chinaunicom/omniFlowNetCompute/domain/InspectionItem.java
  8. 21 0
      src/main/java/cn/chinaunicom/omniFlowNetCompute/domain/InspectionRecord.java
  9. 19 0
      src/main/java/cn/chinaunicom/omniFlowNetCompute/domain/InspectionTask.java
  10. 16 0
      src/main/java/cn/chinaunicom/omniFlowNetCompute/dto/AlarmQueryDTO.java
  11. 10 0
      src/main/java/cn/chinaunicom/omniFlowNetCompute/dto/DashboardDataDTO.java
  12. 11 0
      src/main/java/cn/chinaunicom/omniFlowNetCompute/dto/GetCaptchaImagReq.java
  13. 15 0
      src/main/java/cn/chinaunicom/omniFlowNetCompute/dto/InspectionTaskDTO.java
  14. 10 0
      src/main/java/cn/chinaunicom/omniFlowNetCompute/dto/LoginDTO.java
  15. 44 0
      src/main/java/cn/chinaunicom/omniFlowNetCompute/mapper/AlarmMapper.java
  16. 37 0
      src/main/java/cn/chinaunicom/omniFlowNetCompute/mapper/InspectionMapper.java
  17. 29 0
      src/main/java/cn/chinaunicom/omniFlowNetCompute/mapper/InspectionTaskMapper.java
  18. 16 0
      src/main/java/cn/chinaunicom/omniFlowNetCompute/pojo/AlarmStatisticVO.java
  19. 28 0
      src/main/java/cn/chinaunicom/omniFlowNetCompute/pojo/DashboardVO.java
  20. 22 0
      src/main/java/cn/chinaunicom/omniFlowNetCompute/pojo/InspectionRecordVO.java
  21. 10 0
      src/main/java/cn/chinaunicom/omniFlowNetCompute/pojo/OpportunityFreemarketVO.java
  22. 14 0
      src/main/java/cn/chinaunicom/omniFlowNetCompute/pojo/UserVO.java
  23. 41 0
      src/main/java/cn/chinaunicom/omniFlowNetCompute/service/AlarmService.java
  24. 43 0
      src/main/java/cn/chinaunicom/omniFlowNetCompute/service/DashboardService.java
  25. 70 0
      src/main/java/cn/chinaunicom/omniFlowNetCompute/service/InspectionService.java
  26. 228 0
      src/main/java/cn/chinaunicom/omniFlowNetCompute/service/impl/AlarmServiceImpl.java
  27. 203 0
      src/main/java/cn/chinaunicom/omniFlowNetCompute/service/impl/InspectionServiceImpl.java
  28. 161 0
      src/main/java/cn/chinaunicom/omniFlowNetCompute/until/DateUtil.java
  29. 310 0
      src/main/java/cn/chinaunicom/omniFlowNetCompute/until/ExcelExportUtil.java
  30. 185 0
      src/main/java/cn/chinaunicom/omniFlowNetCompute/until/FileUtil.java
  31. 337 0
      src/main/java/cn/chinaunicom/omniFlowNetCompute/until/ImageUtil.java
  32. 62 0
      src/main/java/cn/chinaunicom/omniFlowNetCompute/until/PhotoUtil.java
  33. 205 0
      src/main/java/cn/chinaunicom/omniFlowNetCompute/until/SimpleExcelExportUtil.java
  34. 111 0
      src/main/java/cn/chinaunicom/omniFlowNetCompute/until/WaitDealTaskReq.java
  35. 22 2
      src/main/resources/application.yml
  36. 167 0
      src/main/resources/mapper/omni/AlarmMapper.xml
  37. 1 0
      src/main/resources/mapper/omni/FreemarketMapper.xml
  38. 225 0
      src/main/resources/mapper/omni/InspectionMapper.xml
  39. 200 0
      src/main/resources/mapper/omni/InspectionTaskMapper.xml

+ 45 - 0
pom.xml

@@ -19,6 +19,7 @@
         <maven.compiler.target>8</maven.compiler.target>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
         <java.version>17</java.version>
+        <log4j2.version>2.17.1</log4j2.version>
     </properties>
 
     <dependencies>
@@ -97,6 +98,50 @@
             <artifactId>fastjson2</artifactId>
             <version>2.0.53</version>
         </dependency>
+
+        <!-- Image Processing -->
+        <dependency>
+            <groupId>com.drewnoakes</groupId>
+            <artifactId>metadata-extractor</artifactId>
+            <version>2.18.0</version>
+        </dependency>
+        <!--引入图形码工具-->
+        <dependency>
+            <groupId>com.github.whvcse</groupId>
+            <artifactId>easy-captcha</artifactId>
+            <version>1.6.2</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.poi</groupId>
+            <artifactId>poi</artifactId>
+            <version>4.1.2</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.poi</groupId>
+            <artifactId>poi-ooxml</artifactId>
+            <version>4.1.2</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.poi</groupId>
+            <artifactId>poi-ooxml-schemas</artifactId>
+            <version>4.1.2</version>
+        </dependency>
+        <!-- log4j2核心依赖 -->
+        <dependency>
+            <groupId>org.apache.logging.log4j</groupId>
+            <artifactId>log4j-api</artifactId>
+            <version>${log4j2.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.logging.log4j</groupId>
+            <artifactId>log4j-core</artifactId>
+            <version>${log4j2.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.logging.log4j</groupId>
+            <artifactId>log4j-slf4j-impl</artifactId>
+            <version>${log4j2.version}</version>
+        </dependency>
     </dependencies>
 
     <build>

+ 37 - 0
src/main/java/cn/chinaunicom/omniFlowNetCompute/configure/WebConfig.java

@@ -0,0 +1,37 @@
+package cn.chinaunicom.omniFlowNetCompute.configure;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.CorsRegistry;
+import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+@Configuration
+public class WebConfig implements WebMvcConfigurer {
+
+    /**
+     * 跨域配置
+     */
+    @Override
+    public void addCorsMappings(CorsRegistry registry) {
+        registry.addMapping("/**")
+                .allowedOrigins("*")
+                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
+                .allowedHeaders("*")
+                .allowCredentials(false)
+                .maxAge(3600);
+    }
+
+    /**
+     * 静态资源映射
+     */
+    @Override
+    public void addResourceHandlers(ResourceHandlerRegistry registry) {
+        // 映射上传文件的访问路径
+        registry.addResourceHandler("/stage-api/uploads/**")
+                .addResourceLocations("file:./uploads/");
+
+        // 映射前端静态资源
+        registry.addResourceHandler("/static/**")
+                .addResourceLocations("classpath:/static/");
+    }
+}

+ 150 - 0
src/main/java/cn/chinaunicom/omniFlowNetCompute/controller/AlarmController.java

@@ -0,0 +1,150 @@
+package cn.chinaunicom.omniFlowNetCompute.controller;
+
+import cn.chinaunicom.omniFlowNetCompute.core.web.controller.BaseController;
+import cn.chinaunicom.omniFlowNetCompute.core.web.domain.AjaxResult;
+import cn.chinaunicom.omniFlowNetCompute.service.impl.AlarmServiceImpl;
+import cn.chinaunicom.omniFlowNetCompute.until.DateUtil;
+import cn.chinaunicom.omniFlowNetCompute.until.ExcelExportUtil;
+import cn.chinaunicom.omniFlowNetCompute.until.SimpleExcelExportUtil;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.RequiredArgsConstructor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.*;
+
+@RestController
+@RequestMapping("/stage-api/alarm")
+@RequiredArgsConstructor
+public class AlarmController extends BaseController {
+
+    private final AlarmServiceImpl alarmService;
+    @Autowired
+    private ExcelExportUtil excelExportUtil;
+    // @Autowired
+    // private SimpleExcelExportUtil excelExportUtil;
+    private final ObjectMapper objectMapper = new ObjectMapper();
+
+    /**
+     * 场景2:获取告警报表统计数据
+     * @param month 月份,格式:YYYY-MM
+     */
+    @GetMapping("/report/stats")
+    public AjaxResult getAlarmReportStats(
+            @RequestParam(required = false) String month) {
+        //return alarmService.getAlarmReportStats(month);
+        return AjaxResult.success(alarmService.getAlarmReportStats(month));
+    }
+
+    /**
+     * 场景2:获取月份列表
+     */
+    @GetMapping("/months")
+    public Map<String, Object> getMonthList() {
+        Map<String, Object> result = new HashMap<>();
+        try {
+            List<String> months = alarmService.getAllMonths();
+            result.put("success", true);
+            result.put("data", months);
+            result.put("message", "获取月份列表成功");
+        } catch (Exception e) {
+            result.put("success", false);
+            result.put("message", "获取月份列表失败:" + e.getMessage());
+        }
+        return result;
+    }
+
+    /**
+     * 场景2:导出Excel报表
+     */
+    @GetMapping("/export")
+    public void exportAlarmReport(
+            @RequestParam(required = false) String month,
+            HttpServletResponse response) {
+
+        try {
+            // 如果没有指定月份,使用当前月份
+            if (month == null || month.isEmpty()) {
+                month = DateUtil.getCurrentMonth();
+            }
+
+            // 获取导出数据
+            Map<String, Object> exportData = alarmService.getExportData(month);
+
+            // 提取数据
+            @SuppressWarnings("unchecked")
+            Map<String, Object> overallStats = (Map<String, Object>) exportData.get("overallStats");
+
+            @SuppressWarnings("unchecked")
+            List<Map<String, Object>> systemStats = (List<Map<String, Object>>) exportData.get("systemStats");
+
+            // @SuppressWarnings("unchecked")
+            // List<Map<String, Object>> systemIndicatorStats = (List<Map<String, Object>>) exportData.get("systemIndicatorStats");
+
+            // 导出Excel
+            excelExportUtil.exportAlarmReportToExcel(response, month,
+                    overallStats, systemStats);
+            // 导出Excel
+            // excelExportUtil.exportSimpleExcel(response, month,
+            //         overallStats, systemStats, systemIndicatorStats);
+
+        } catch (Exception e) {
+            // 处理异常,返回错误信息
+            try {
+                response.setContentType("application/json");
+                response.setCharacterEncoding("UTF-8");
+                Map<String, Object> errorResult = new HashMap<>();
+                errorResult.put("success", false);
+                errorResult.put("message", "导出失败:" + e.getMessage());
+                response.getWriter().write(new ObjectMapper().writeValueAsString(errorResult));
+            } catch (IOException ioException) {
+                ioException.printStackTrace();
+            }
+        }
+    }
+
+    /**
+     * 场景3:获取告警数据驾驶舱数据
+     */
+    @GetMapping("/dashboard")
+    public Map<String, Object> getAlarmDashboard() {
+        return alarmService.getAlarmDashboardData();
+    }
+
+    /**
+     * 场景3:获取最新数据(用于自动刷新)
+     */
+    @GetMapping("/dashboard/latest")
+    public Map<String, Object> getLatestDashboard() {
+        return alarmService.getLatestDashboardData();
+    }
+
+    /**
+     * 获取实时告警列表
+     */
+    @GetMapping("/realtime")
+    public AjaxResult getRealTimeAlarms(
+            @RequestParam(defaultValue = "20") Integer limit) {
+        return AjaxResult.success(alarmService.getRealTimeAlarms(limit));
+    }
+
+    /**
+     * 测试接口:获取所有告警
+     */
+    @GetMapping("/test/all")
+    public Map<String, Object> getAllAlarms() {
+        Map<String, Object> result = new HashMap<>();
+        try {
+            // 这里只是示例,实际应该从数据库查询
+            result.put("success", true);
+            result.put("message", "测试接口调用成功");
+            result.put("timestamp", new Date());
+        } catch (Exception e) {
+            result.put("success", false);
+            result.put("message", "测试接口调用失败:" + e.getMessage());
+        }
+        return result;
+    }
+}

+ 165 - 0
src/main/java/cn/chinaunicom/omniFlowNetCompute/controller/InspectionController.java

@@ -0,0 +1,165 @@
+package cn.chinaunicom.omniFlowNetCompute.controller;
+
+import cn.chinaunicom.omniFlowNetCompute.core.web.controller.BaseController;
+import cn.chinaunicom.omniFlowNetCompute.core.web.domain.AjaxResult;
+import cn.chinaunicom.omniFlowNetCompute.dto.InspectionTaskDTO;
+import cn.chinaunicom.omniFlowNetCompute.domain.InspectionRecord;
+import cn.chinaunicom.omniFlowNetCompute.domain.InspectionTask;
+import cn.chinaunicom.omniFlowNetCompute.service.InspectionService;
+import cn.chinaunicom.omniFlowNetCompute.until.ImageUtil;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.time.LocalDateTime;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@RestController
+@RequestMapping("/stage-api/inspection")
+@RequiredArgsConstructor
+public class InspectionController extends BaseController {
+
+    private final InspectionService inspectionService;
+
+    @GetMapping("/tasks")
+    public Map<String, Object> getTaskList() {
+        Map<String, Object> result = new HashMap<>();
+
+        try {
+            List<InspectionTask> tasks = inspectionService.getAllTasks();
+            result.put("success", true);
+            result.put("data", tasks);
+            result.put("total", tasks.size());
+
+        } catch (Exception e) {
+            result.put("success", false);
+            result.put("message", "获取任务列表失败: " + e.getMessage());
+        }
+
+        return result;
+    }
+
+    @PostMapping("/task")
+    public Map<String, Object> createTask(@RequestBody InspectionTaskDTO taskDTO) {
+        Map<String, Object> result = new HashMap<>();
+
+        try {
+            InspectionTask task = inspectionService.createTask(taskDTO);
+            result.put("success", true);
+            result.put("taskId", task.getId());
+            result.put("message", "任务创建成功");
+
+        } catch (Exception e) {
+            result.put("success", false);
+            result.put("message", "创建任务失败: " + e.getMessage());
+        }
+
+        return result;
+    }
+
+    @PostMapping("/start")
+    public Map<String, Object> startInspection(
+            @RequestParam Long taskId,
+            @RequestParam String inspector) {
+
+        Map<String, Object> result = new HashMap<>();
+
+        try {
+            LocalDateTime startTime = LocalDateTime.now();
+            String recordId = inspectionService.startInspection(taskId, inspector, startTime);
+
+            result.put("success", true);
+            result.put("recordId", recordId);
+            result.put("startTime", startTime);
+            result.put("message", "巡检开始成功");
+
+        } catch (Exception e) {
+            result.put("success", false);
+            result.put("message", "开始巡检失败: " + e.getMessage());
+        }
+
+        return result;
+    }
+
+    @PostMapping("/upload-photo")
+    public AjaxResult uploadWorkPhoto(
+            @RequestParam("file") MultipartFile file,
+            @RequestParam String recordId,
+            @RequestParam String startTimeStr) {
+        try {
+            // 将字符串转换为 Date 对象
+            Date startTime = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(startTimeStr);
+
+            // 校验图片时间
+            Map<String, Object> timeCheck = ImageUtil.validatePhotoTime(file, startTime);
+            if (!(boolean) timeCheck.get("valid")) {
+                return AjaxResult.error(timeCheck.get("message").toString());
+            }
+
+            // 保存图片
+            return AjaxResult.success("上传工作照成功", inspectionService.saveWorkPhoto(file, recordId));
+
+        } catch (Exception e) {
+            throw new RuntimeException("上传工作照失败: " + e.getMessage());
+        }
+    }
+
+    @PostMapping("/saveBenchmarkPhoto")
+    public AjaxResult saveBenchmarkPhoto(@RequestParam("file") MultipartFile file) {
+        return AjaxResult.success("图片上传成功", inspectionService.saveBenchmarkPhoto(file));
+    }
+
+    @PostMapping("/submit")
+    public Map<String, Object> submitInspection(@RequestBody Map<String, Object> inspectionData) {
+        Map<String, Object> result = new HashMap<>();
+
+        try {
+            // 这里解析并保存巡检结果
+            // inspectionService.submitInspection(inspectionData);
+
+            result.put("success", true);
+            result.put("message", "巡检记录提交成功");
+
+        } catch (Exception e) {
+            result.put("success", false);
+            result.put("message", "提交巡检记录失败: " + e.getMessage());
+        }
+
+        return result;
+    }
+
+    @GetMapping("/records")
+    public Map<String, Object> getRecords() {
+        Map<String, Object> result = new HashMap<>();
+
+        try {
+            List<InspectionRecord> records = inspectionService.getAllRecords();
+            result.put("success", true);
+            result.put("data", records);
+            result.put("total", records.size());
+
+        } catch (Exception e) {
+            result.put("success", false);
+            result.put("message", "获取巡检记录失败: " + e.getMessage());
+        }
+
+        return result;
+    }
+
+    @PutMapping("/task/{id}")
+    public AjaxResult updateTask(
+            @PathVariable Long id,
+            @RequestBody InspectionTaskDTO taskDTO) {
+        taskDTO.setId(id);
+        return toAjax(inspectionService.updateTask(taskDTO));
+    }
+
+    @DeleteMapping("/task/{id}")
+    public AjaxResult deleteTask(@PathVariable Long id) {
+        return toAjax(inspectionService.deleteTask(id));
+    }
+}

+ 100 - 0
src/main/java/cn/chinaunicom/omniFlowNetCompute/controller/LoginController.java

@@ -0,0 +1,100 @@
+package cn.chinaunicom.omniFlowNetCompute.controller;
+
+import cn.chinaunicom.omniFlowNetCompute.core.domain.R;
+import cn.chinaunicom.omniFlowNetCompute.dto.LoginDTO;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import com.wf.captcha.SpecCaptcha;
+import com.wf.captcha.base.Captcha;
+
+@RestController
+@RequestMapping("/stage-api/auth")
+@Slf4j
+public class LoginController {
+    @Resource
+    private HttpServletResponse httpServletResponse;
+	@Resource
+	private StringRedisTemplate stringRedisTemplate;
+
+    @PostMapping("/login")
+    public R<?> login(@RequestBody LoginDTO loginDTO) {
+        Map<String, Object> result = new HashMap<>();
+
+		//验证图形验证码是否正确
+		String captchaImagSession = stringRedisTemplate.opsForValue().get("app:imageCode:" + loginDTO.getCaptcha().toLowerCase());
+		if (captchaImagSession == null) {
+			return R.fail(1001, "图形验证码不正确");
+		}
+		//删除图形验证码
+		stringRedisTemplate.delete("app:imageCode:" + loginDTO.getCaptcha().toLowerCase());
+        // 简化登录验证,直接返回成功
+        if ("admin".equals(loginDTO.getUsername()) && "Welcome1#".equals(loginDTO.getPassword())) {
+            // 模拟菜单数据
+            Map<String, Object> menus = new HashMap<>();
+            menus.put("告警情况统计分析", "/alarm/statistics");
+            menus.put("机房巡检管理", "/inspection/manage");
+            menus.put("告警数据驾驶舱", "/dashboard/alarm");
+
+            result.put("menus", menus);
+            Map<String, Object> user = new HashMap<>();
+            user.put("username", loginDTO.getUsername());
+			user.put("name", "管理员");
+            user.put("role", "管理员");
+            result.put("user", user);
+        } else {
+            return R.fail(1002, "账号或密码错误");
+        }
+        return R.ok(result);
+    }
+
+    @GetMapping("/logout")
+    public Map<String, Object> logout() {
+        Map<String, Object> result = new HashMap<>();
+        result.put("success", true);
+        result.put("message", "退出成功");
+        return result;
+    }
+    /**
+	 * 获取图形验证码
+	 */
+	@PostMapping("/getCaptchaImag")
+	public void getCaptchaImag() {
+		// 设置请求头为输出图片类型
+		httpServletResponse.setContentType("image/jpeg");
+		httpServletResponse.setHeader("Pragma", "No-cache");
+		httpServletResponse.setHeader("Cache-Control", "no-cache");
+		httpServletResponse.setDateHeader("Expires", 0);
+		Captcha captcha = new SpecCaptcha(130, 48, 5);
+		captcha.setCharType(Captcha.TYPE_DEFAULT);
+		ServletOutputStream servletOutputStream = null;
+		try {
+			String text = captcha.text();
+			stringRedisTemplate.opsForValue().set("app:imageCode:" + text.toLowerCase(), text, 2L, TimeUnit.MINUTES);
+			servletOutputStream = httpServletResponse.getOutputStream();
+			captcha.out(servletOutputStream);
+		} catch (IOException ioException) {
+			log.error("获取图片验证码失败,异常原因【{}】", ioException.getMessage(), ioException);
+		} finally {
+			try {
+				if (servletOutputStream != null) {
+					servletOutputStream.flush();
+					servletOutputStream.close();
+					httpServletResponse.flushBuffer();
+				}
+			} catch (IOException ioException) {
+				log.error("获取图片验证码失败,异常原因【{}】", ioException.getMessage(),
+						ioException);
+			}
+		}
+	}
+}

+ 19 - 0
src/main/java/cn/chinaunicom/omniFlowNetCompute/domain/Alarm.java

@@ -0,0 +1,19 @@
+package cn.chinaunicom.omniFlowNetCompute.domain;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.util.Date;
+
+@Data
+public class Alarm {
+    private Long id;
+    private String alarmType; // 内容类/阈值类/存在性
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date occurTime;   // 告警时间
+    private Integer processStatus; // 0-未处理,1-已处理
+    private String month;     // YYYY-MM
+    private Date createTime;
+    private String indicator; // 监控指标
+    private String systemName; // 系统名称 - 根据需求添加
+}

+ 16 - 0
src/main/java/cn/chinaunicom/omniFlowNetCompute/domain/InspectionItem.java

@@ -0,0 +1,16 @@
+package cn.chinaunicom.omniFlowNetCompute.domain;
+
+import lombok.Data;
+import java.time.LocalDateTime;
+
+@Data
+public class InspectionItem {
+    private Long id;
+    private Long taskId;
+    private String itemName;          // 巡检项名称
+    private String category;          // 分类
+    private String checkMethod;       // 检查方法
+    private String standardValue;     // 标准值
+    private Integer sortOrder;
+    private LocalDateTime createTime;
+}

+ 21 - 0
src/main/java/cn/chinaunicom/omniFlowNetCompute/domain/InspectionRecord.java

@@ -0,0 +1,21 @@
+package cn.chinaunicom.omniFlowNetCompute.domain;
+
+import lombok.Data;
+import java.time.LocalDateTime;
+
+@Data
+public class InspectionRecord {
+    private Long id;
+    private String recordId;
+    private Long taskId;
+    private String taskName;
+    private String inspector;
+    private String inspectorId;
+    private LocalDateTime startTime;
+    private LocalDateTime endTime;
+    private String result;           // 正常/异常
+    private String resultDescription;
+    private String workPhotoPath;
+    private String photoCheckResult;
+    private LocalDateTime createTime;
+}

+ 19 - 0
src/main/java/cn/chinaunicom/omniFlowNetCompute/domain/InspectionTask.java

@@ -0,0 +1,19 @@
+package cn.chinaunicom.omniFlowNetCompute.domain;
+
+import lombok.Data;
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Data
+public class InspectionTask {
+    private Long id;
+    private String taskName;          // 任务名称
+    private String taskDescription;   // 任务说明
+    private String baselineImage;     // 基准图路径
+    private LocalDateTime createTime;
+    private LocalDateTime updateTime;
+    private Integer status;           // 状态:0-禁用,1-启用
+
+    // 巡检项列表
+    private List<InspectionItem> items;
+}

+ 16 - 0
src/main/java/cn/chinaunicom/omniFlowNetCompute/dto/AlarmQueryDTO.java

@@ -0,0 +1,16 @@
+package cn.chinaunicom.omniFlowNetCompute.dto;
+
+import lombok.Data;
+import java.time.LocalDateTime;
+
+@Data
+public class AlarmQueryDTO {
+    private String month;           // 月份:YYYY-MM
+    private String systemName;      // 系统名称
+    private String alarmType;       // 告警类型
+    private Integer processStatus;  // 处理状态
+    private LocalDateTime startTime; // 开始时间
+    private LocalDateTime endTime;   // 结束时间
+    private Integer pageNum = 1;    // 页码
+    private Integer pageSize = 10;  // 每页大小
+}

+ 10 - 0
src/main/java/cn/chinaunicom/omniFlowNetCompute/dto/DashboardDataDTO.java

@@ -0,0 +1,10 @@
+package cn.chinaunicom.omniFlowNetCompute.dto;
+import lombok.Data;
+
+@Data
+public class DashboardDataDTO {
+    private Integer days = 7;        // 统计天数,默认7天
+    private String systemName;       // 系统名称
+    private String alarmType;        // 告警类型
+    private Boolean autoRefresh = false; // 是否自动刷新
+}

+ 11 - 0
src/main/java/cn/chinaunicom/omniFlowNetCompute/dto/GetCaptchaImagReq.java

@@ -0,0 +1,11 @@
+package cn.chinaunicom.omniFlowNetCompute.dto;
+
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+
+@Data
+public class GetCaptchaImagReq {
+    @NotBlank(message = "验证图形码唯一标识不能为空")
+    private String verifyCode;
+}

+ 15 - 0
src/main/java/cn/chinaunicom/omniFlowNetCompute/dto/InspectionTaskDTO.java

@@ -0,0 +1,15 @@
+package cn.chinaunicom.omniFlowNetCompute.dto;
+
+import cn.chinaunicom.omniFlowNetCompute.domain.InspectionItem;
+import lombok.Data;
+import java.util.List;
+
+@Data
+public class InspectionTaskDTO {
+    private Long id;
+    private String taskName;        // 任务名称
+    private String taskDescription; // 任务说明
+    private String baselineImage;   // 基准图路径
+    private Integer status = 1;     // 状态:0-禁用,1-启用
+    private List<InspectionItem> items; // 巡检项列表
+}

+ 10 - 0
src/main/java/cn/chinaunicom/omniFlowNetCompute/dto/LoginDTO.java

@@ -0,0 +1,10 @@
+package cn.chinaunicom.omniFlowNetCompute.dto;
+
+import lombok.Data;
+
+@Data
+public class LoginDTO {
+    private String username;
+    private String password;
+    private String captcha;  // 图形验证码
+}

+ 44 - 0
src/main/java/cn/chinaunicom/omniFlowNetCompute/mapper/AlarmMapper.java

@@ -0,0 +1,44 @@
+package cn.chinaunicom.omniFlowNetCompute.mapper;
+
+import cn.chinaunicom.omniFlowNetCompute.domain.Alarm;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+import java.util.Map;
+
+@Mapper
+public interface AlarmMapper {
+
+    // 场景2:按月份统计告警数据
+    List<Map<String, Object>> getAlarmStatsByMonth(@Param("month") String month);
+
+    // 场景2:按月份统计告警数据
+    List<Map<String, Object>> getAlarmStatsByMonthAndSystem(@Param("month") String month);
+
+    // 场景2:按月份和指标统计告警数据
+    List<Map<String, Object>> getAlarmStatsByMonthAndIndicator(@Param("month") String month);
+
+    // 场景3:获取未处理的告警列表(实时告警)
+    List<Alarm> getUnprocessedAlarms(@Param("limit") Integer limit);
+
+    // 场景3:获取七日告警分布(按指标)
+    List<Map<String, Object>> getAlarmDistributionLast7Days();
+
+    // 场景3:获取七日告警趋势(按类型)
+    List<Map<String, Object>> getAlarmTrendLast7Days();
+
+    // 场景3:获取各系统七日告警统计
+    List<Map<String, Object>> getSystemAlarmStatsLast7Days();
+
+    // 场景3:获取各指标七日告警统计
+    List<Map<String, Object>> getIndicatorAlarmStatsLast7Days();
+
+    // 获取所有月份列表
+    List<String> getAllMonths();
+
+    // 根据ID查询告警
+    Alarm getAlarmById(@Param("id") Long id);
+    // 场景3:获取七日告警趋势(数据获取情况)
+    List<Map<String, Object>> getAlarmSituationLast7Days();
+}

+ 37 - 0
src/main/java/cn/chinaunicom/omniFlowNetCompute/mapper/InspectionMapper.java

@@ -0,0 +1,37 @@
+package cn.chinaunicom.omniFlowNetCompute.mapper;
+
+import cn.chinaunicom.omniFlowNetCompute.domain.InspectionItem;
+import cn.chinaunicom.omniFlowNetCompute.domain.InspectionRecord;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Map;
+
+@Mapper
+public interface InspectionMapper {
+
+    // 巡检记录相关
+    int insertRecord(InspectionRecord record);
+    int updateRecord(InspectionRecord record);
+    InspectionRecord selectRecordById(String recordId);
+    List<InspectionRecord> selectAllRecords();
+    List<InspectionRecord> selectRecordsByTaskId(Long taskId);
+    List<InspectionRecord> selectRecordsByInspector(String inspector);
+    int deleteRecordById(String recordId);
+
+    // 巡检项详情
+    int insertRecordDetail(@Param("recordId") String recordId,
+                           @Param("item") InspectionItem item,
+                           @Param("checkResult") String checkResult,
+                           @Param("description") String description);
+
+    List<Map<String, Object>> selectRecordDetails(String recordId);
+
+    // 统计相关
+    Map<String, Object> getInspectionStatistics(@Param("startTime") LocalDateTime startTime,
+                                                @Param("endTime") LocalDateTime endTime);
+
+    List<Map<String, Object>> getInspectionTrend(@Param("days") int days);
+}

+ 29 - 0
src/main/java/cn/chinaunicom/omniFlowNetCompute/mapper/InspectionTaskMapper.java

@@ -0,0 +1,29 @@
+package cn.chinaunicom.omniFlowNetCompute.mapper;
+
+import cn.chinaunicom.omniFlowNetCompute.domain.InspectionTask;
+import cn.chinaunicom.omniFlowNetCompute.domain.InspectionItem;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+@Mapper
+public interface InspectionTaskMapper {
+
+    // 巡检任务相关
+    int insertTask(InspectionTask task);
+    int updateTask(InspectionTask task);
+    int deleteTask(Long id);
+    InspectionTask selectTaskById(Long id);
+    List<InspectionTask> selectAllTasks();
+    List<InspectionTask> selectTasksByStatus(Integer status);
+
+    // 巡检项相关
+    int insertItem(InspectionItem item);
+    int batchInsertItems(List<InspectionItem> items);
+    int updateItem(InspectionItem item);
+    int deleteItem(Long id);
+    int deleteItemsByTaskId(Long taskId);
+    List<InspectionItem> selectItemsByTaskId(Long taskId);
+    InspectionItem selectItemById(Long id);
+}

+ 16 - 0
src/main/java/cn/chinaunicom/omniFlowNetCompute/pojo/AlarmStatisticVO.java

@@ -0,0 +1,16 @@
+package cn.chinaunicom.omniFlowNetCompute.pojo;
+
+import lombok.Data;
+import java.util.List;
+import java.util.Map;
+
+@Data
+public class AlarmStatisticVO {
+    private Long totalCount;          // 告警总数
+    private Long processedCount;      // 已处理告警总数
+    private Long unprocessedCount;    // 未处理告警总数
+    private Long contentCount;        // 内容类告警总数
+    private Long thresholdCount;      // 阈值类告警总数
+    private Long existenceCount;      // 存在性告警总数
+    private List<Map<String, Object>> systemStats; // 各系统统计数据
+}

+ 28 - 0
src/main/java/cn/chinaunicom/omniFlowNetCompute/pojo/DashboardVO.java

@@ -0,0 +1,28 @@
+package cn.chinaunicom.omniFlowNetCompute.pojo;
+
+import lombok.Data;
+import java.util.List;
+import java.util.Map;
+
+@Data
+public class DashboardVO {
+    // 指标分类数据获取情况
+    private Map<String, Object> indicatorStatus;
+
+    // 实时告警列表
+    private List<Map<String, Object>> realTimeAlarms;
+
+    // 七日告警数据分布
+    private Map<String, Object> sevenDaysDistribution;
+
+    // 七日告警趋势分析
+    private List<Map<String, Object>> sevenDaysTrend;
+
+    // 各系统七日告警统计
+    private List<Map<String, Object>> systemSevenDaysStats;
+
+    // 页面刷新相关信息
+    private Long refreshTime;
+    private Long nextRefresh;
+    private Boolean autoRefresh = false;
+}

+ 22 - 0
src/main/java/cn/chinaunicom/omniFlowNetCompute/pojo/InspectionRecordVO.java

@@ -0,0 +1,22 @@
+package cn.chinaunicom.omniFlowNetCompute.pojo;
+
+import lombok.Data;
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Map;
+
+@Data
+public class InspectionRecordVO {
+    private String recordId;
+    private String taskName;
+    private String inspector;
+    private String inspectorId;
+    private LocalDateTime startTime;
+    private LocalDateTime endTime;
+    private String result;           // 正常/异常
+    private String resultDescription;
+    private String workPhotoPath;
+    private String photoCheckResult;
+    private LocalDateTime createTime;
+    private List<Map<String, Object>> details; // 巡检详情
+}

+ 10 - 0
src/main/java/cn/chinaunicom/omniFlowNetCompute/pojo/OpportunityFreemarketVO.java

@@ -1,5 +1,6 @@
 package cn.chinaunicom.omniFlowNetCompute.pojo;
 
+import com.fasterxml.jackson.annotation.JsonFormat;
 import lombok.Data;
 
 import java.math.BigDecimal;
@@ -14,6 +15,7 @@ public class OpportunityFreemarketVO {
     private Integer oppNaturalCustomerId;   // 商机-自然客户id
     private String oppNaturalCustomerName;  // 商机-自然客户名称
     private BigDecimal estimatedContractAmount; // 预计合同总金额(万元)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     private Date estimatedSignDate;         // 预计签约时间
     private String opportunityOwner;        // 商机归属人
     private String opportunityUnit;         // 商机单位
@@ -22,8 +24,10 @@ public class OpportunityFreemarketVO {
     private String customerRequirement;     // 客户需求简介
     private String supportDepartment;       // 支撑部门
     private String customerFundSource;      // 客户资金来源
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     private Date fillTime;                  // 填报时间
     private String opportunityStage;        // 商机阶段
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     private Date establishTime;             // 建立时间
     
     // ========== Freemarket 表字段 ==========
@@ -41,11 +45,14 @@ public class OpportunityFreemarketVO {
     private String tenderAgent;             // 招标代理
     private String tenderAgentPhone;        // 招标代理联系电话
     private String matchKeywords;           // 信息匹配词
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     private Date bidDocumentDeadline;       // 购买标书截止时间
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     private Date bidOpeningDate;            // 开标日期
     private String announcementUrl;         // 公告地址(链接)
     private String jianyuUrl;               // 剑鱼地址(链接)
     private BigDecimal budget;              // 预算(万元)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     private Date tenderInfoPublishTime;     // 招标信息发布时间
     private String branchCompany;           // 分公司
     private String industry;                // 行业
@@ -57,7 +64,10 @@ public class OpportunityFreemarketVO {
     private Integer isAware;                // 是否知晓(1=是,0=否)
     private String rbgLabel;                // 红黄绿牌
     private String discardApprovalNumber;   // 弃标审批单号
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     private Date publishTime;               // 发布时间
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     private Date fmCreateTime;              // 公告创建时间
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     private Date fmUpdateTime;              // 公告更新时间
 }

+ 14 - 0
src/main/java/cn/chinaunicom/omniFlowNetCompute/pojo/UserVO.java

@@ -0,0 +1,14 @@
+package cn.chinaunicom.omniFlowNetCompute.pojo;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class UserVO {
+    private String username;
+    private String realName;
+    private String role;
+}

+ 41 - 0
src/main/java/cn/chinaunicom/omniFlowNetCompute/service/AlarmService.java

@@ -0,0 +1,41 @@
+package cn.chinaunicom.omniFlowNetCompute.service;
+
+import cn.chinaunicom.omniFlowNetCompute.dto.AlarmQueryDTO;
+import cn.chinaunicom.omniFlowNetCompute.domain.Alarm;
+import cn.chinaunicom.omniFlowNetCompute.pojo.AlarmStatisticVO;
+
+import java.util.List;
+import java.util.Map;
+
+public interface AlarmService {
+
+    /**
+     * 获取告警统计数据
+     */
+    AlarmStatisticVO getAlarmStatistics(AlarmQueryDTO queryDTO);
+
+    /**
+     * 根据月份查询告警
+     */
+    List<Alarm> getAlarmsByMonth(String month);
+
+    /**
+     * 添加测试告警
+     */
+    int addTestAlarm(Alarm alarm);
+
+    /**
+     * 批量添加告警
+     */
+    int batchAddAlarms(List<Alarm> alarms);
+
+    /**
+     * 获取未处理告警
+     */
+    List<Alarm> getUnprocessedAlarms(int limit);
+
+    /**
+     * 获取七日告警数据
+     */
+    List<Map<String, Object>> getSevenDaysAlarms(int days);
+}

+ 43 - 0
src/main/java/cn/chinaunicom/omniFlowNetCompute/service/DashboardService.java

@@ -0,0 +1,43 @@
+package cn.chinaunicom.omniFlowNetCompute.service;
+
+import cn.chinaunicom.omniFlowNetCompute.pojo.DashboardVO;
+
+import java.util.Map;
+
+public interface DashboardService {
+
+    /**
+     * 获取告警驾驶舱数据
+     */
+    DashboardVO getAlarmDashboardData();
+
+    /**
+     * 获取指标分类数据获取情况
+     */
+    Map<String, Object> getIndicatorStatus();
+
+    /**
+     * 获取实时告警列表
+     */
+    Map<String, Object> getRealTimeAlarms();
+
+    /**
+     * 获取七日告警数据分布
+     */
+    Map<String, Object> getSevenDaysDistribution();
+
+    /**
+     * 获取七日告警趋势分析
+     */
+    Map<String, Object> getSevenDaysTrend();
+
+    /**
+     * 获取各系统七日告警统计
+     */
+    Map<String, Object> getSystemSevenDaysStats();
+
+    /**
+     * 添加测试告警数据
+     */
+    void addTestAlarm(Map<String, Object> testData);
+}

+ 70 - 0
src/main/java/cn/chinaunicom/omniFlowNetCompute/service/InspectionService.java

@@ -0,0 +1,70 @@
+package cn.chinaunicom.omniFlowNetCompute.service;
+
+import cn.chinaunicom.omniFlowNetCompute.dto.InspectionTaskDTO;
+import cn.chinaunicom.omniFlowNetCompute.domain.InspectionRecord;
+import cn.chinaunicom.omniFlowNetCompute.domain.InspectionTask;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Map;
+
+public interface InspectionService {
+
+    /**
+     * 获取所有巡检任务
+     */
+    List<InspectionTask> getAllTasks();
+
+    /**
+     * 根据ID获取任务
+     */
+    InspectionTask getTaskById(Long id);
+
+    /**
+     * 创建巡检任务
+     */
+    InspectionTask createTask(InspectionTaskDTO taskDTO);
+
+    /**
+     * 更新巡检任务
+     */
+    int updateTask(InspectionTaskDTO taskDTO);
+
+    /**
+     * 删除巡检任务
+     */
+    int deleteTask(Long id);
+
+    /**
+     * 开始巡检
+     */
+    String startInspection(Long taskId, String inspector, LocalDateTime startTime);
+
+    /**
+     * 保存工作照
+     */
+    String saveWorkPhoto(MultipartFile file, String recordId);
+
+    String saveBenchmarkPhoto(MultipartFile file);
+
+    /**
+     * 提交巡检结果
+     */
+    InspectionRecord submitInspection(InspectionRecord record);
+
+    /**
+     * 获取所有巡检记录
+     */
+    List<InspectionRecord> getAllRecords();
+
+    /**
+     * 根据记录ID获取巡检记录
+     */
+    InspectionRecord getRecordById(String recordId);
+
+    /**
+     * 获取任务名称
+     */
+    String getTaskName(Long taskId);
+}

+ 228 - 0
src/main/java/cn/chinaunicom/omniFlowNetCompute/service/impl/AlarmServiceImpl.java

@@ -0,0 +1,228 @@
+package cn.chinaunicom.omniFlowNetCompute.service.impl;
+
+import cn.chinaunicom.omniFlowNetCompute.domain.Alarm;
+import cn.chinaunicom.omniFlowNetCompute.mapper.AlarmMapper;
+import cn.chinaunicom.omniFlowNetCompute.until.DateUtil;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+
+import java.text.SimpleDateFormat;
+import java.util.*;
+
+@Service
+@RequiredArgsConstructor
+public class AlarmServiceImpl {
+
+    private final AlarmMapper alarmMapper;
+
+    // 场景2:获取告警报表统计数据
+    public Map<String, Object> getAlarmReportStats(String month) {
+        Map<String, Object> result = new HashMap<>();
+
+        // 如果没有指定月份,使用当前月份
+        if (month == null || month.isEmpty()) {
+            month = DateUtil.getCurrentMonth();
+        }
+
+        // 获取整体统计
+        List<Map<String, Object>> overallStats = alarmMapper.getAlarmStatsByMonth(month);
+        if (!overallStats.isEmpty()) {
+            result.put("overall", overallStats.get(0));
+        } else {
+            // 返回空统计
+            Map<String, Object> emptyStats = new HashMap<>();
+            emptyStats.put("total", 0);
+            emptyStats.put("processed", 0);
+            emptyStats.put("unprocessed", 0);
+            emptyStats.put("contentCount", 0);
+            emptyStats.put("thresholdCount", 0);
+            emptyStats.put("existenceCount", 0);
+            result.put("overall", emptyStats);
+        }
+
+        // 获取各指标统计
+        //List<Map<String, Object>> indicatorStats = alarmMapper.getAlarmStatsByMonthAndIndicator(month);
+        List<Map<String, Object>> indicatorStats = alarmMapper.getAlarmStatsByMonthAndSystem(month);
+        result.put("indicatorStats", indicatorStats);
+
+        // 获取月份列表
+        List<String> months = alarmMapper.getAllMonths();
+        if (months == null || months.isEmpty()) {
+            // 如果没有数据,返回最近3个月
+            months = Arrays.asList(DateUtil.getRecentMonths(3));
+        }
+        result.put("months", months);
+
+        result.put("currentMonth", month);
+
+        return result;
+    }
+
+    // // 场景2:导出Excel数据
+    // public Map<String, Object> getExportData(String month) {
+    //     Map<String, Object> exportData = new HashMap<>();
+    //
+    //     // 如果没有指定月份,使用当前月份
+    //     if (month == null || month.isEmpty()) {
+    //         month = DateUtil.getCurrentMonth();
+    //     }
+    //
+    //     // 整体统计数据
+    //     List<Map<String, Object>> overallStats = alarmMapper.getAlarmStatsByMonth(month);
+    //     if (!overallStats.isEmpty()) {
+    //         exportData.put("overallStats", overallStats.get(0));
+    //     }
+    //
+    //     // 各指标统计数据
+    //     List<Map<String, Object>> indicatorStats = alarmMapper.getAlarmStatsByMonthAndIndicator(month);
+    //     exportData.put("indicatorStats", indicatorStats);
+    //
+    //     exportData.put("exportMonth", month);
+    //     exportData.put("exportTime", new Date());
+    //
+    //     return exportData;
+    // }
+
+    // 场景3:获取告警数据驾驶舱数据
+    public Map<String, Object> getAlarmDashboardData() {
+        Map<String, Object> dashboardData = new HashMap<>();
+
+        // 1. 指标分类数据获取情况
+        List<Map<String, Object>> indicatorStatus = alarmMapper.getAlarmSituationLast7Days();
+        dashboardData.put("indicatorStatus", indicatorStatus);
+
+        // 2. 实时告警列表
+        List<Alarm> realTimeAlarms = alarmMapper.getUnprocessedAlarms(20);
+        dashboardData.put("realTimeAlarms", realTimeAlarms);
+
+        // 3. 七日告警数据分布
+        List<Map<String, Object>> alarmDistribution = alarmMapper.getAlarmDistributionLast7Days();
+        dashboardData.put("alarmDistribution", alarmDistribution);
+
+        // 4. 七日告警趋势分析
+        List<Map<String, Object>> alarmTrend = alarmMapper.getAlarmTrendLast7Days();
+        dashboardData.put("alarmTrend", alarmTrend);
+
+        // 5. 各系统七日告警统计
+        List<Map<String, Object>> systemAlarms = alarmMapper.getSystemAlarmStatsLast7Days();
+        dashboardData.put("systemAlarms", systemAlarms);
+
+        // 6. 统计信息
+        Map<String, Object> summary = new HashMap<>();
+        summary.put("totalAlarms", realTimeAlarms.size());
+        summary.put("lastUpdate", new Date());
+        dashboardData.put("summary", summary);
+
+        return dashboardData;
+    }
+
+    // 获取指标分类数据获取情况
+    // private List<Map<String, Object>> getIndicatorStatusStats() {
+    //     // 这里模拟数据,因为缺少配置表
+    //     List<Map<String, Object>> result = new ArrayList<>();
+    //
+    //     // 内容类指标
+    //     Map<String, Object> contentStats = new HashMap<>();
+    //     contentStats.put("category", "内容类");
+    //     contentStats.put("normal", 5);  // 正常获取数据的指标数
+    //     contentStats.put("noData", 2);  // 无数据的指标数
+    //     contentStats.put("notConfigured", 3);  // 未配置的指标数
+    //     result.add(contentStats);
+    //
+    //     // 阈值类指标
+    //     Map<String, Object> thresholdStats = new HashMap<>();
+    //     thresholdStats.put("category", "阈值类");
+    //     thresholdStats.put("normal", 3);
+    //     thresholdStats.put("noData", 1);
+    //     thresholdStats.put("notConfigured", 6);
+    //     result.add(thresholdStats);
+    //
+    //     // 存在性指标
+    //     Map<String, Object> existenceStats = new HashMap<>();
+    //     existenceStats.put("category", "存在性");
+    //     existenceStats.put("normal", 4);
+    //     existenceStats.put("noData", 2);
+    //     existenceStats.put("notConfigured", 4);
+    //     result.add(existenceStats);
+    //
+    //     return result;
+    // }
+
+    // 获取最近7天的日期列表
+    private List<String> getLast7Days() {
+        List<String> days = new ArrayList<>();
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+        Calendar calendar = Calendar.getInstance();
+
+        for (int i = 6; i >= 0; i--) {
+            calendar.add(Calendar.DAY_OF_YEAR, -i);
+            days.add(sdf.format(calendar.getTime()));
+            calendar.add(Calendar.DAY_OF_YEAR, i); // 重置
+        }
+
+        return days;
+    }
+
+    // 获取最新数据(用于自动刷新)
+    public Map<String, Object> getLatestDashboardData() {
+        return getAlarmDashboardData();
+    }
+
+    // 获取实时告警
+    public List<Alarm> getRealTimeAlarms(Integer limit) {
+        if (limit == null || limit <= 0) {
+            limit = 20;
+        }
+        return alarmMapper.getUnprocessedAlarms(limit);
+    }
+
+    // 获取所有月份列表
+    public List<String> getAllMonths() {
+        List<String> months = alarmMapper.getAllMonths();
+        if (months == null || months.isEmpty()) {
+            // 如果没有数据,返回最近3个月
+            months = Arrays.asList(DateUtil.getRecentMonths(3));
+        }
+        return months;
+    }
+    // AlarmService.java - 新增方法
+    /**
+     * 获取导出Excel所需的数据
+     */
+    public Map<String, Object> getExportData(String month) {
+        Map<String, Object> exportData = new HashMap<>();
+
+        // 如果没有指定月份,使用当前月份
+        if (month == null || month.isEmpty()) {
+            month = DateUtil.getCurrentMonth();
+        }
+
+        // 整体统计数据
+        List<Map<String, Object>> overallStatsList = alarmMapper.getAlarmStatsByMonth(month);
+        Map<String, Object> overallStats = new HashMap<>();
+        if (!overallStatsList.isEmpty()) {
+            overallStats = overallStatsList.get(0);
+        } else {
+            overallStats.put("total", 0);
+            overallStats.put("processed", 0);
+            overallStats.put("unprocessed", 0);
+            overallStats.put("contentCount", 0);
+            overallStats.put("thresholdCount", 0);
+            overallStats.put("existenceCount", 0);
+        }
+        exportData.put("overallStats", overallStats);
+
+        // 各系统统计数据
+        List<Map<String, Object>> systemStats = alarmMapper.getAlarmStatsByMonthAndSystem(month);
+        exportData.put("systemStats", systemStats);
+
+        // // 各系统各指标的告警总数
+        // List<Map<String, Object>> systemIndicatorStats = alarmMapper.getSystemIndicatorStatsByMonth(month);
+        // exportData.put("systemIndicatorStats", systemIndicatorStats);
+
+        exportData.put("exportMonth", month);
+        exportData.put("exportTime", new Date());
+
+        return exportData;
+    }
+}

+ 203 - 0
src/main/java/cn/chinaunicom/omniFlowNetCompute/service/impl/InspectionServiceImpl.java

@@ -0,0 +1,203 @@
+package cn.chinaunicom.omniFlowNetCompute.service.impl;
+
+import cn.chinaunicom.omniFlowNetCompute.dto.InspectionTaskDTO;
+import cn.chinaunicom.omniFlowNetCompute.domain.InspectionRecord;
+import cn.chinaunicom.omniFlowNetCompute.domain.InspectionTask;
+import cn.chinaunicom.omniFlowNetCompute.domain.InspectionItem;
+import cn.chinaunicom.omniFlowNetCompute.mapper.InspectionMapper;
+import cn.chinaunicom.omniFlowNetCompute.mapper.InspectionTaskMapper;
+import cn.chinaunicom.omniFlowNetCompute.service.InspectionService;
+import cn.chinaunicom.omniFlowNetCompute.until.FileUtil;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.UUID;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class InspectionServiceImpl implements InspectionService {
+
+    private final InspectionTaskMapper inspectionTaskMapper;
+    private final InspectionMapper inspectionMapper;
+    private final FileUtil fileUtil;
+
+    @Override
+    public List<InspectionTask> getAllTasks() {
+        // 获取所有任务
+        List<InspectionTask> tasks = inspectionTaskMapper.selectAllTasks();
+
+        // 为每个任务加载巡检项
+        for (InspectionTask task : tasks) {
+            List<InspectionItem> items = inspectionTaskMapper.selectItemsByTaskId(task.getId());
+            task.setItems(items);
+        }
+
+        return tasks;
+    }
+
+    @Override
+    public InspectionTask getTaskById(Long id) {
+        InspectionTask task = inspectionTaskMapper.selectTaskById(id);
+        if (task != null) {
+            List<InspectionItem> items = inspectionTaskMapper.selectItemsByTaskId(id);
+            task.setItems(items);
+        }
+        return task;
+    }
+
+    @Override
+    @Transactional
+    public InspectionTask createTask(InspectionTaskDTO taskDTO) {
+        InspectionTask task = new InspectionTask();
+        task.setTaskName(taskDTO.getTaskName());
+        task.setTaskDescription(taskDTO.getTaskDescription());
+        task.setBaselineImage(taskDTO.getBaselineImage());
+        task.setStatus(taskDTO.getStatus());
+        task.setCreateTime(LocalDateTime.now());
+
+        int result = inspectionTaskMapper.insertTask(task);
+        if (result <= 0) {
+            throw new RuntimeException("创建巡检任务失败");
+        }
+
+        // 保存巡检项
+        if (taskDTO.getItems() != null && !taskDTO.getItems().isEmpty()) {
+            for (InspectionItem item : taskDTO.getItems()) {
+                item.setTaskId(task.getId());
+                item.setCreateTime(LocalDateTime.now());
+                inspectionTaskMapper.insertItem(item);
+            }
+        }
+
+        return task;
+    }
+
+    @Override
+    @Transactional
+    public int updateTask(InspectionTaskDTO taskDTO) {
+        InspectionTask existingTask = getTaskById(taskDTO.getId());
+        if (existingTask == null) {
+            throw new RuntimeException("巡检任务不存在");
+        }
+
+        existingTask.setTaskName(taskDTO.getTaskName());
+        existingTask.setTaskDescription(taskDTO.getTaskDescription());
+        existingTask.setBaselineImage(taskDTO.getBaselineImage());
+        existingTask.setStatus(taskDTO.getStatus());
+        existingTask.setUpdateTime(LocalDateTime.now());
+
+        int result = inspectionTaskMapper.updateTask(existingTask);
+        if (result <= 0) {
+            throw new RuntimeException("更新巡检任务失败");
+        }
+
+        // 删除原有的巡检项
+        inspectionTaskMapper.deleteItemsByTaskId(taskDTO.getId());
+
+        // 保存新的巡检项
+        if (taskDTO.getItems() != null && !taskDTO.getItems().isEmpty()) {
+            for (InspectionItem item : taskDTO.getItems()) {
+                item.setTaskId(taskDTO.getId());
+                item.setCreateTime(LocalDateTime.now());
+                inspectionTaskMapper.insertItem(item);
+            }
+        }
+
+        return 1;
+    }
+
+    @Override
+    @Transactional
+    public int deleteTask(Long id) {
+        // 先删除巡检项
+        inspectionTaskMapper.deleteItemsByTaskId(id);
+
+        // 再删除任务
+        return inspectionTaskMapper.deleteTask(id);
+    }
+
+    @Override
+    public String startInspection(Long taskId, String inspector, LocalDateTime startTime) {
+        InspectionTask task = getTaskById(taskId);
+        if (task == null) {
+            throw new RuntimeException("巡检任务不存在");
+        }
+
+        // 生成记录ID
+        String recordId = "INSP_" + UUID.randomUUID().toString().replace("-", "").substring(0, 16);
+
+        // 创建巡检记录
+        InspectionRecord record = new InspectionRecord();
+        record.setRecordId(recordId);
+        record.setTaskId(taskId);
+        record.setTaskName(task.getTaskName());
+        record.setInspector(inspector);
+        record.setStartTime(startTime);
+        record.setCreateTime(LocalDateTime.now());
+
+        int result = inspectionMapper.insertRecord(record);
+        if (result <= 0) {
+            throw new RuntimeException("创建巡检记录失败");
+        }
+
+        return recordId;
+    }
+
+    @Override
+    public String saveWorkPhoto(MultipartFile file, String recordId) {
+        try {
+            // 保存文件
+            return fileUtil.saveFile(file, "work-photos");
+        } catch (Exception e) {
+            log.error("保存工作照失败", e);
+            throw new RuntimeException("保存工作照失败: " + e.getMessage());
+        }
+    }
+
+    @Override
+    public String saveBenchmarkPhoto(MultipartFile file) {
+        try {
+            // 保存文件
+            return fileUtil.saveBasicFile(file, "benchmark-photos");
+        } catch (Exception e) {
+            log.error("巡检对照基准图", e);
+            throw new RuntimeException("巡检对照基准图: " + e.getMessage());
+        }
+    }
+
+    @Override
+    @Transactional
+    public InspectionRecord submitInspection(InspectionRecord record) {
+        record.setEndTime(LocalDateTime.now());
+        record.setCreateTime(LocalDateTime.now());
+
+        int result = inspectionMapper.updateRecord(record);
+        if (result <= 0) {
+            throw new RuntimeException("提交巡检记录失败");
+        }
+
+        return record;
+    }
+
+    @Override
+    public List<InspectionRecord> getAllRecords() {
+        return inspectionMapper.selectAllRecords();
+    }
+
+    @Override
+    public InspectionRecord getRecordById(String recordId) {
+        return inspectionMapper.selectRecordById(recordId);
+    }
+
+    @Override
+    public String getTaskName(Long taskId) {
+        InspectionTask task = getTaskById(taskId);
+        return task != null ? task.getTaskName() : "未知任务";
+    }
+}

+ 161 - 0
src/main/java/cn/chinaunicom/omniFlowNetCompute/until/DateUtil.java

@@ -0,0 +1,161 @@
+package cn.chinaunicom.omniFlowNetCompute.until;
+
+import java.text.SimpleDateFormat;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.TemporalAdjusters;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+
+public class DateUtil {
+
+    private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+    private static final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+    private static final DateTimeFormatter MONTH_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM");
+
+    /**
+     * 获取当前日期字符串
+     */
+    public static String getCurrentDate() {
+        return LocalDate.now().format(DATE_FORMATTER);
+    }
+
+    /**
+     * 获取当前日期时间字符串
+     */
+    public static String getCurrentDateTime() {
+        return LocalDateTime.now().format(DATETIME_FORMATTER);
+    }
+
+    /**
+     * 获取当前月份
+     */
+    public static String getCurrentMonth() {
+        return LocalDate.now().format(MONTH_FORMATTER);
+    }
+
+    /**
+     * 获取指定月份的上个月
+     */
+    public static String getPreviousMonth(String month) {
+        LocalDate date = LocalDate.parse(month + "-01", DateTimeFormatter.ofPattern("yyyy-MM-dd"));
+        return date.minusMonths(1).format(MONTH_FORMATTER);
+    }
+
+    /**
+     * 获取指定月份的下个月
+     */
+    public static String getNextMonth(String month) {
+        LocalDate date = LocalDate.parse(month + "-01", DateTimeFormatter.ofPattern("yyyy-MM-dd"));
+        return date.plusMonths(1).format(MONTH_FORMATTER);
+    }
+
+    /**
+     * 获取指定月份的第一天
+     */
+    public static LocalDate getFirstDayOfMonth(String month) {
+        return LocalDate.parse(month + "-01", DateTimeFormatter.ofPattern("yyyy-MM-dd"));
+    }
+
+    /**
+     * 获取指定月份的最后一天
+     */
+    public static LocalDate getLastDayOfMonth(String month) {
+        LocalDate firstDay = getFirstDayOfMonth(month);
+        return firstDay.with(TemporalAdjusters.lastDayOfMonth());
+    }
+
+    /**
+     * 获取最近N天的日期列表
+     */
+    public static List<String> getRecentDays(int days) {
+        List<String> dateList = new ArrayList<>();
+        LocalDate today = LocalDate.now();
+
+        for (int i = days - 1; i >= 0; i--) {
+            LocalDate date = today.minusDays(i);
+            dateList.add(date.format(DATE_FORMATTER));
+        }
+
+        return dateList;
+    }
+
+    /**
+     * 格式化日期
+     */
+    public static String formatDate(LocalDate date) {
+        if (date == null) return "";
+        return date.format(DATE_FORMATTER);
+    }
+
+    /**
+     * 格式化日期时间
+     */
+    public static String formatDateTime(LocalDateTime dateTime) {
+        if (dateTime == null) return "";
+        return dateTime.format(DATETIME_FORMATTER);
+    }
+
+    /**
+     * 解析日期字符串
+     */
+    public static LocalDate parseDate(String dateStr) {
+        try {
+            return LocalDate.parse(dateStr, DATE_FORMATTER);
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    /**
+     * 解析日期时间字符串
+     */
+    public static LocalDateTime parseDateTime(String dateTimeStr) {
+        try {
+            return LocalDateTime.parse(dateTimeStr, DATETIME_FORMATTER);
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    /**
+     * 计算两个日期之间的天数差
+     */
+    public static long daysBetween(LocalDate startDate, LocalDate endDate) {
+        return Math.abs(endDate.toEpochDay() - startDate.toEpochDay());
+    }
+
+
+    private static final String MONTH_FORMAT = "yyyy-MM";
+    private static final String DATE_FORMAT = "yyyy-MM-dd";
+
+    /**
+     * 格式化月份
+     */
+    public static String formatMonth(Date date) {
+        if (date == null) {
+            return null;
+        }
+        SimpleDateFormat sdf = new SimpleDateFormat(MONTH_FORMAT);
+        return sdf.format(date);
+    }
+
+    /**
+     * 获取最近N个月的列表
+     */
+    public static String[] getRecentMonths(int months) {
+        String[] recentMonths = new String[months];
+        Calendar calendar = Calendar.getInstance();
+
+        for (int i = 0; i < months; i++) {
+            calendar.add(Calendar.MONTH, -i);
+            recentMonths[i] = formatMonth(calendar.getTime());
+            calendar.add(Calendar.MONTH, i); // 重置
+        }
+
+        return recentMonths;
+    }
+}

+ 310 - 0
src/main/java/cn/chinaunicom/omniFlowNetCompute/until/ExcelExportUtil.java

@@ -0,0 +1,310 @@
+// ExcelExportUtil.java
+package cn.chinaunicom.omniFlowNetCompute.until;
+import org.apache.poi.ss.usermodel.*;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.Map;
+
+@Component
+public class ExcelExportUtil {
+
+    /**
+     * 导出告警报表统计Excel
+     */
+    public void exportAlarmReportToExcel(HttpServletResponse response,
+                                         String month,
+                                         Map<String, Object> overallStats,
+                                         List<Map<String, Object>> systemStats) throws IOException {
+
+        // 创建工作簿 - 兼容POI 4.1.2
+        Workbook workbook = new XSSFWorkbook();
+
+        // 创建样式
+        CellStyle headerStyle = createHeaderStyle(workbook);
+        CellStyle dataStyle = createDataStyle(workbook);
+
+        // 1. 创建整体统计Sheet
+        createOverallStatsSheet(workbook, headerStyle, dataStyle, month, overallStats);
+
+        // 2. 创建系统统计Sheet
+        createSystemStatsSheet(workbook, headerStyle, dataStyle, month, systemStats);
+
+        // // 3. 创建系统指标统计Sheet
+        // createSystemIndicatorStatsSheet(workbook, headerStyle, dataStyle, month, systemIndicatorStats);
+
+        // 设置响应头
+        String fileName = "告警报表统计_" + month + ".xlsx";
+        fileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.name())
+                .replaceAll("\\+", "%20");
+
+        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+        response.setCharacterEncoding(StandardCharsets.UTF_8.name());
+        response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + fileName);
+
+        // 写入响应流
+        try (OutputStream outputStream = response.getOutputStream()) {
+            workbook.write(outputStream);
+        } finally {
+            workbook.close();
+        }
+    }
+
+    /**
+     * 创建整体统计Sheet
+     */
+    private void createOverallStatsSheet(Workbook workbook, CellStyle headerStyle,
+                                         CellStyle dataStyle, String month,
+                                         Map<String, Object> overallStats) {
+        Sheet sheet = workbook.createSheet("整体统计");
+
+        // 创建标题行
+        Row titleRow = sheet.createRow(0);
+        Cell titleCell = titleRow.createCell(0);
+        titleCell.setCellValue(month + "告警整体统计");
+        titleCell.setCellStyle(headerStyle);
+
+        // 合并单元格 - POI 4.1.2的写法
+        sheet.addMergedRegion(new org.apache.poi.ss.util.CellRangeAddress(0, 0, 0, 6));
+
+        // 创建表头
+        Row headerRow = sheet.createRow(1);
+        String[] headers = {"统计项", "总数", "已处理", "未处理", "内容类", "阈值类", "存在性"};
+
+        for (int i = 0; i < headers.length; i++) {
+            Cell cell = headerRow.createCell(i);
+            cell.setCellValue(headers[i]);
+            cell.setCellStyle(headerStyle);
+        }
+
+        // 创建数据行
+        Row dataRow = sheet.createRow(2);
+        Cell cell0 = dataRow.createCell(0);
+        cell0.setCellValue("告警统计");
+        cell0.setCellStyle(dataStyle);
+
+        String[] fields = {"total", "processed", "unprocessed", "contentCount", "thresholdCount", "existenceCount"};
+        for (int i = 0; i < fields.length; i++) {
+            Cell cell = dataRow.createCell(i + 1);
+            Object value = overallStats.get(fields[i]);
+            if (value instanceof Number) {
+                cell.setCellValue(((Number) value).doubleValue());
+            } else if (value != null) {
+                cell.setCellValue(value.toString());
+            } else {
+                cell.setCellValue(0);
+            }
+            cell.setCellStyle(dataStyle);
+        }
+
+        // 自动调整列宽
+        for (int i = 0; i < headers.length; i++) {
+            //sheet.autoSizeColumn(i);
+            sheet.setColumnWidth(i, 20 * 256);
+        }
+    }
+
+    /**
+     * 创建系统统计Sheet
+     */
+    private void createSystemStatsSheet(Workbook workbook, CellStyle headerStyle,
+                                        CellStyle dataStyle, String month,
+                                        List<Map<String, Object>> systemStats) {
+        Sheet sheet = workbook.createSheet("系统统计");
+
+        // 创建标题行
+        Row titleRow = sheet.createRow(0);
+        Cell titleCell = titleRow.createCell(0);
+        titleCell.setCellValue(month + "各系统告警统计");
+        titleCell.setCellStyle(headerStyle);
+
+        // 合并单元格
+        sheet.addMergedRegion(new org.apache.poi.ss.util.CellRangeAddress(0, 0, 0, 15));
+
+        // 创建表头
+        Row headerRow = sheet.createRow(1);
+        String[] headers = {"系统名称", "告警总数", "已处理数", "未处理数",
+                "内容类总数", "阈值类总数", "存在性总数",
+                "数据未送达告警总数", "内部连接性告警总数", "访问接口频率告警总数",
+                "数据库连接数告警总数", "页面防篡改告警总数", "网络边界连通性告警总数",
+                "定时任务告警总数", "OSS使用情况告警总数", "异常访问告警总数"};
+
+        for (int i = 0; i < headers.length; i++) {
+            Cell cell = headerRow.createCell(i);
+            cell.setCellValue(headers[i]);
+            cell.setCellStyle(headerStyle);
+        }
+
+        // 创建数据行
+        int rowNum = 2;
+        if (systemStats != null) {
+            for (Map<String, Object> stat : systemStats) {
+                Row dataRow = sheet.createRow(rowNum++);
+
+                // 系统名称
+                Cell cell0 = dataRow.createCell(0);
+                cell0.setCellValue(stat.getOrDefault("system_name", "").toString());
+                cell0.setCellStyle(dataStyle);
+
+                // 各统计字段
+                String[] fields = {"total", "processed", "unprocessed",
+                        "contentCount", "thresholdCount", "existenceCount",
+                        "indicator_dataNoDeliveredCount","indicator_internalConnetCount","indicator_interfaceFrequencyCount",
+                        "indicator_databaseConnectCount","indicator_pageTamperProofCount","indicator_networkBoundaryConnectCount",
+                        "indicator_scheduledTaskCount","indicator_ossUseCount","indicator_abnormalAccessCount"};
+
+                for (int i = 0; i < fields.length; i++) {
+                    Cell cell = dataRow.createCell(i + 1);
+                    Object value = stat.get(fields[i]);
+                    if (value instanceof Number) {
+                        cell.setCellValue(((Number) value).doubleValue());
+                    } else if (value != null) {
+                        cell.setCellValue(value.toString());
+                    } else {
+                        cell.setCellValue(0);
+                    }
+                    cell.setCellStyle(dataStyle);
+                }
+            }
+        }
+
+        // 自动调整列宽
+        for (int i = 0; i < headers.length; i++) {
+            sheet.setColumnWidth(i, 20 * 256);
+        }
+    }
+
+    /**
+     * 创建系统指标统计Sheet
+     */
+    private void createSystemIndicatorStatsSheet(Workbook workbook, CellStyle headerStyle,
+                                                 CellStyle dataStyle, String month,
+                                                 List<Map<String, Object>> systemIndicatorStats) {
+        Sheet sheet = workbook.createSheet("系统指标统计");
+
+        // 创建标题行
+        Row titleRow = sheet.createRow(0);
+        Cell titleCell = titleRow.createCell(0);
+        titleCell.setCellValue(month + "各系统指标告警统计");
+        titleCell.setCellStyle(headerStyle);
+
+        // 合并单元格
+        sheet.addMergedRegion(new org.apache.poi.ss.util.CellRangeAddress(0, 0, 0, 6));
+
+        // 创建表头
+        Row headerRow = sheet.createRow(1);
+        String[] headers = {"系统名称", "监控指标", "指标告警总数",
+                "内容类数量", "阈值类数量", "存在性数量"};
+
+        for (int i = 0; i < headers.length; i++) {
+            Cell cell = headerRow.createCell(i);
+            cell.setCellValue(headers[i]);
+            cell.setCellStyle(headerStyle);
+        }
+
+        // 创建数据行
+        int rowNum = 2;
+        if (systemIndicatorStats != null) {
+            for (Map<String, Object> stat : systemIndicatorStats) {
+                Row dataRow = sheet.createRow(rowNum++);
+
+                // 系统名称
+                Cell cell0 = dataRow.createCell(0);
+                cell0.setCellValue(stat.getOrDefault("systemName", "").toString());
+                cell0.setCellStyle(dataStyle);
+
+                // 监控指标
+                Cell cell1 = dataRow.createCell(1);
+                cell1.setCellValue(stat.getOrDefault("indicator", "").toString());
+                cell1.setCellStyle(dataStyle);
+
+                // 指标告警总数
+                Cell cell2 = dataRow.createCell(2);
+                Object total = stat.get("indicatorCount");
+                if (total instanceof Number) {
+                    cell2.setCellValue(((Number) total).doubleValue());
+                } else if (total != null) {
+                    cell2.setCellValue(total.toString());
+                } else {
+                    cell2.setCellValue(0);
+                }
+                cell2.setCellStyle(dataStyle);
+
+                // 各类型数量
+                String[] typeFields = {"indicatorContentCount", "indicatorThresholdCount", "indicatorExistenceCount"};
+                for (int i = 0; i < typeFields.length; i++) {
+                    Cell cell = dataRow.createCell(i + 3);
+                    Object value = stat.get(typeFields[i]);
+                    if (value instanceof Number) {
+                        cell.setCellValue(((Number) value).doubleValue());
+                    } else if (value != null) {
+                        cell.setCellValue(value.toString());
+                    } else {
+                        cell.setCellValue(0);
+                    }
+                    cell.setCellStyle(dataStyle);
+                }
+            }
+        }
+
+        // 自动调整列宽
+        for (int i = 0; i < headers.length; i++) {
+            sheet.autoSizeColumn(i);
+        }
+    }
+
+    /**
+     * 创建表头样式 - POI 4.1.2兼容写法
+     */
+    private CellStyle createHeaderStyle(Workbook workbook) {
+        CellStyle style = workbook.createCellStyle();
+
+        // 设置背景色
+        style.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
+        style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
+
+        // 设置边框
+        style.setBorderBottom(BorderStyle.THIN);
+        style.setBorderTop(BorderStyle.THIN);
+        style.setBorderRight(BorderStyle.THIN);
+        style.setBorderLeft(BorderStyle.THIN);
+
+        // 设置字体
+        Font font = workbook.createFont();
+        font.setBold(true);
+        font.setFontHeightInPoints((short) 12);
+        style.setFont(font);
+
+        // 居中
+        style.setAlignment(HorizontalAlignment.CENTER);
+        style.setVerticalAlignment(VerticalAlignment.CENTER);
+
+        return style;
+    }
+
+    /**
+     * 创建数据样式 - POI 4.1.2兼容写法
+     */
+    private CellStyle createDataStyle(Workbook workbook) {
+        CellStyle style = workbook.createCellStyle();
+
+        // 设置边框
+        style.setBorderBottom(BorderStyle.THIN);
+        style.setBorderTop(BorderStyle.THIN);
+        style.setBorderRight(BorderStyle.THIN);
+        style.setBorderLeft(BorderStyle.THIN);
+
+        // 设置字体
+        Font font = workbook.createFont();
+        font.setFontHeightInPoints((short) 11);
+        style.setFont(font);
+
+        return style;
+    }
+}

+ 185 - 0
src/main/java/cn/chinaunicom/omniFlowNetCompute/until/FileUtil.java

@@ -0,0 +1,185 @@
+package cn.chinaunicom.omniFlowNetCompute.until;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+
+@Slf4j
+@Component
+public class FileUtil {
+
+    @Value("${file.upload.path:./uploads/}")
+    private String uploadPath;
+
+    @Value("${file.upload.basicPath}")
+    private String basicPath;
+
+    /**
+     * 保存上传的文件
+     */
+    public String saveFile(MultipartFile file, String subDir) throws IOException {
+        if (file == null || file.isEmpty()) {
+            throw new IllegalArgumentException("文件为空");
+        }
+
+        String fullDir = basicPath+ "/" + subDir + "/";
+
+        // 创建目录
+        Path dirPath = Paths.get(fullDir);
+        if (!Files.exists(dirPath)) {
+            Files.createDirectories(dirPath);
+            log.info("创建目录: {}", fullDir);
+        }
+
+        // 生成文件名
+        String originalFilename = file.getOriginalFilename();
+        String filename = generateFileName(originalFilename);
+        String filePath = fullDir + filename;
+
+        // 保存文件
+        Path fileSavePath = Paths.get(filePath);
+        file.transferTo(fileSavePath.toFile());
+
+        log.info("文件保存成功: {}", filePath);
+        // 返回相对路径
+        return "/uploads/" + subDir + "/" + filename;
+    }
+    /**
+     * 保存上传的文件
+     */
+    public String saveBasicFile(MultipartFile file, String subDir) throws IOException {
+        if (file == null || file.isEmpty()) {
+            throw new IllegalArgumentException("basic文件为空");
+        }
+
+        String fullDir = basicPath+ "/" + subDir + "/";
+
+        // 创建目录
+        Path dirPath = Paths.get(fullDir);
+        if (!Files.exists(dirPath)) {
+            Files.createDirectories(dirPath);
+            log.info("basic创建目录: {}", fullDir);
+        }
+
+        // 生成文件名
+        String originalFilename = file.getOriginalFilename();
+        String filename = generateFileName(originalFilename);
+        String filePath = fullDir + filename;
+
+        // 保存文件
+        Path fileSavePath = Paths.get(filePath);
+        file.transferTo(fileSavePath.toFile());
+
+        log.info("basic文件保存成功: {}", filePath);
+
+        // 返回相对路径
+        return "/uploads/" + subDir + "/" + filename;
+    }
+
+    /**
+     * 生成唯一的文件名
+     */
+    private String generateFileName(String originalFilename) {
+        if (originalFilename == null) {
+            return System.currentTimeMillis() + ".dat";
+        }
+
+        String extension = "";
+        int lastDotIndex = originalFilename.lastIndexOf(".");
+        if (lastDotIndex > 0) {
+            extension = originalFilename.substring(lastDotIndex);
+        }
+
+        String timestamp = String.valueOf(System.currentTimeMillis());
+        String random = String.valueOf((int)(Math.random() * 1000));
+
+        return timestamp + "_" + random + extension;
+    }
+
+    /**
+     * 删除文件
+     */
+    public boolean deleteFile(String filePath) {
+        if (filePath == null || filePath.isEmpty()) {
+            return false;
+        }
+
+        try {
+            Path path = Paths.get(uploadPath, filePath);
+            return Files.deleteIfExists(path);
+        } catch (IOException e) {
+            log.error("删除文件失败: {}", filePath, e);
+            return false;
+        }
+    }
+
+    /**
+     * 检查文件是否存在
+     */
+    public boolean fileExists(String filePath) {
+        if (filePath == null || filePath.isEmpty()) {
+            return false;
+        }
+
+        Path path = Paths.get(uploadPath, filePath);
+        return Files.exists(path);
+    }
+
+    /**
+     * 获取文件大小
+     */
+    public long getFileSize(String filePath) {
+        if (filePath == null || filePath.isEmpty()) {
+            return 0;
+        }
+
+        try {
+            Path path = Paths.get(uploadPath, filePath);
+            return Files.size(path);
+        } catch (IOException e) {
+            log.error("获取文件大小失败: {}", filePath, e);
+            return 0;
+        }
+    }
+
+    /**
+     * 清理过期的文件
+     */
+    public void cleanupExpiredFiles(int days) {
+        File uploadDir = new File(uploadPath);
+        if (!uploadDir.exists() || !uploadDir.isDirectory()) {
+            return;
+        }
+
+        long cutoffTime = System.currentTimeMillis() - (days * 24L * 60 * 60 * 1000);
+
+        cleanupDirectory(uploadDir, cutoffTime);
+    }
+
+    private void cleanupDirectory(File dir, long cutoffTime) {
+        File[] files = dir.listFiles();
+        if (files == null) return;
+
+        for (File file : files) {
+            if (file.isDirectory()) {
+                cleanupDirectory(file, cutoffTime);
+                // 删除空目录
+                if (file.listFiles() != null && file.listFiles().length == 0) {
+                    file.delete();
+                }
+            } else if (file.lastModified() < cutoffTime) {
+                file.delete();
+                log.info("删除过期文件: {}", file.getAbsolutePath());
+            }
+        }
+    }
+}

+ 337 - 0
src/main/java/cn/chinaunicom/omniFlowNetCompute/until/ImageUtil.java

@@ -0,0 +1,337 @@
+package cn.chinaunicom.omniFlowNetCompute.until;
+
+import com.drew.imaging.ImageMetadataReader;
+import com.drew.imaging.ImageProcessingException;
+import com.drew.metadata.Metadata;
+import com.drew.metadata.exif.ExifSubIFDDirectory;
+import com.drew.metadata.file.FileSystemDirectory;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.imageio.ImageIO;
+import javax.imageio.ImageReader;
+import javax.imageio.stream.ImageInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.*;
+
+@Slf4j
+@Component
+public class ImageUtil {
+    // EXIF时间格式:通常为 "yyyy:MM:dd HH:mm:ss"
+    private static final DateTimeFormatter EXIF_DATE_FORMATTER =
+            DateTimeFormatter.ofPattern("yyyy:MM:dd HH:mm:ss");
+
+    /**
+     * 验证图片时间(增强版,支持多种时间获取方式)
+     */
+    public static Map<String, Object> validatePhotoTime(MultipartFile file, Date startTime) {
+        Map<String, Object> result = new HashMap<>();
+
+        if (file == null || file.isEmpty()) {
+            result.put("valid", false);
+            result.put("message", "文件为空");
+            return result;
+        }
+
+        String originalFilename = file.getOriginalFilename();
+        String contentType = file.getContentType();
+
+        log.info("验证图片时间 - 文件名: {}, 文件类型: {}, 文件大小: {}字节",
+                originalFilename, contentType, file.getSize());
+
+        try {
+            // 尝试多种方式获取图片时间
+            Date photoTime = null;
+            String timeSource = "";
+
+            // 方式1:尝试从EXIF信息获取
+            photoTime = getPhotoTimeFromExif(file);
+            if (photoTime != null) {
+                timeSource = "EXIF信息";
+            }
+
+            // 方式2:如果EXIF失败,尝试使用文件最后修改时间
+            if (photoTime == null) {
+                photoTime = getPhotoTimeFromFileMetadata(file);
+                if (photoTime != null) {
+                    timeSource = "文件元数据";
+                }
+            }
+
+            // 方式3:如果前两种方式都失败,使用当前时间作为最后手段
+            if (photoTime == null) {
+                photoTime = new Date();
+                timeSource = "当前系统时间";
+                log.warn("无法获取图片拍摄时间,使用当前系统时间作为替代");
+            }
+
+            log.info("获取到图片时间: {} (来源: {})", photoTime, timeSource);
+
+            Date currentTime = new Date();
+
+            // 校验拍摄时间是否在巡检开始时间之后
+            if (photoTime.before(startTime)) {
+                result.put("valid", false);
+                result.put("message", "图片" + timeSource + "早于巡检开始时间");
+                result.put("photoTime", photoTime);
+                result.put("startTime", startTime);
+                result.put("timeSource", timeSource);
+                return result;
+            }
+
+            // 校验拍摄时间是否在当前时间之后(不应该发生)
+            if (photoTime.after(currentTime)) {
+                result.put("valid", false);
+                result.put("message", "图片" + timeSource + "晚于当前时间,时间设置可能不正确");
+                result.put("photoTime", photoTime);
+                result.put("currentTime", currentTime);
+                result.put("timeSource", timeSource);
+                return result;
+            }
+
+            result.put("valid", true);
+            result.put("message", "时间校验通过(时间来源: " + timeSource + ")");
+            result.put("photoTime", photoTime);
+            result.put("timeSource", timeSource);
+
+        } catch (Exception e) {
+            log.error("图片时间校验失败", e);
+            result.put("valid", false);
+            result.put("message", "图片时间校验失败: " + e.getMessage());
+        }
+
+        return result;
+    }
+
+    /**
+     * 从EXIF信息获取图片拍摄时间
+     */
+    private static Date getPhotoTimeFromExif(MultipartFile file) {
+        try (InputStream inputStream = new ByteArrayInputStream(file.getBytes())) {
+            Metadata metadata = ImageMetadataReader.readMetadata(inputStream);
+
+            // 尝试从多个位置获取时间信息
+            Date photoDate = null;
+
+            // 1. 尝试从ExifSubIFDDirectory获取
+            ExifSubIFDDirectory exifDirectory = metadata.getFirstDirectoryOfType(ExifSubIFDDirectory.class);
+            if (exifDirectory != null) {
+                photoDate = exifDirectory.getDate(ExifSubIFDDirectory.TAG_DATETIME_ORIGINAL);
+                if (photoDate == null) {
+                    photoDate = exifDirectory.getDate(ExifSubIFDDirectory.TAG_DATETIME);
+                }
+                if (photoDate == null) {
+                    photoDate = exifDirectory.getDate(ExifSubIFDDirectory.TAG_DATETIME_DIGITIZED);
+                }
+
+                if (photoDate != null) {
+                    log.debug("从EXIF获取到拍摄时间: {}", photoDate);
+                    // 将UTC时间转换为北京时间(+8小时)
+                    Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
+                    calendar.setTime(photoDate);
+
+                    // 设置目标时区为北京时间
+                    Calendar targetCalendar = Calendar.getInstance(TimeZone.getTimeZone("Asia/Shanghai"));
+                    targetCalendar.set(Calendar.YEAR, calendar.get(Calendar.YEAR));
+                    targetCalendar.set(Calendar.MONTH, calendar.get(Calendar.MONTH));
+                    targetCalendar.set(Calendar.DAY_OF_MONTH, calendar.get(Calendar.DAY_OF_MONTH));
+                    targetCalendar.set(Calendar.HOUR_OF_DAY, calendar.get(Calendar.HOUR_OF_DAY));
+                    targetCalendar.set(Calendar.MINUTE, calendar.get(Calendar.MINUTE));
+                    targetCalendar.set(Calendar.SECOND, calendar.get(Calendar.SECOND));
+                    targetCalendar.set(Calendar.MILLISECOND, calendar.get(Calendar.MILLISECOND));
+                    return targetCalendar.getTime();
+                }
+            }
+
+            // 2. 尝试从FileSystemDirectory获取
+            FileSystemDirectory fileSystemDirectory = metadata.getFirstDirectoryOfType(FileSystemDirectory.class);
+            if (fileSystemDirectory != null) {
+                photoDate = fileSystemDirectory.getDate(FileSystemDirectory.TAG_FILE_MODIFIED_DATE);
+                if (photoDate != null) {
+                    log.debug("从文件系统元数据获取到修改时间: {}", photoDate);
+                    // 将UTC时间转换为北京时间(+8小时)
+                    Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
+                    calendar.setTime(photoDate);
+
+                    // 设置目标时区为北京时间
+                    Calendar targetCalendar = Calendar.getInstance(TimeZone.getTimeZone("Asia/Shanghai"));
+                    targetCalendar.set(Calendar.YEAR, calendar.get(Calendar.YEAR));
+                    targetCalendar.set(Calendar.MONTH, calendar.get(Calendar.MONTH));
+                    targetCalendar.set(Calendar.DAY_OF_MONTH, calendar.get(Calendar.DAY_OF_MONTH));
+                    targetCalendar.set(Calendar.HOUR_OF_DAY, calendar.get(Calendar.HOUR_OF_DAY));
+                    targetCalendar.set(Calendar.MINUTE, calendar.get(Calendar.MINUTE));
+                    targetCalendar.set(Calendar.SECOND, calendar.get(Calendar.SECOND));
+                    targetCalendar.set(Calendar.MILLISECOND, calendar.get(Calendar.MILLISECOND));
+                    return targetCalendar.getTime();
+                }
+            }
+
+            log.warn("图片不包含EXIF信息或无法读取时间信息");
+            return null;
+
+        } catch (ImageProcessingException e) {
+            log.warn("图片EXIF处理异常: {}", e.getMessage());
+            return null;
+        } catch (IOException e) {
+            log.error("读取图片失败", e);
+            return null;
+        } catch (Exception e) {
+            log.error("获取EXIF信息失败", e);
+            return null;
+        }
+    }
+
+    /**
+     * 从文件元数据获取时间(备用方案)
+     */
+    private static Date getPhotoTimeFromFileMetadata(MultipartFile file) {
+        try {
+            log.debug("无法从MultipartFile获取文件系统时间,使用上传时间");
+
+            // 返回当前时间作为备用
+            return new Date();
+
+        } catch (Exception e) {
+            log.error("获取文件元数据失败", e);
+            return null;
+        }
+    }
+
+    /**
+     * 检查图片是否包含EXIF信息
+     */
+    public static boolean hasExifInfo(MultipartFile file) {
+        try (InputStream inputStream = new ByteArrayInputStream(file.getBytes())) {
+            Metadata metadata = ImageMetadataReader.readMetadata(inputStream);
+
+            // 检查是否有EXIF目录
+            ExifSubIFDDirectory exifDirectory = metadata.getFirstDirectoryOfType(ExifSubIFDDirectory.class);
+            if (exifDirectory != null) {
+                // 尝试获取时间信息
+                Date photoDate = exifDirectory.getDate(ExifSubIFDDirectory.TAG_DATETIME_ORIGINAL);
+                if (photoDate == null) {
+                    photoDate = exifDirectory.getDate(ExifSubIFDDirectory.TAG_DATETIME);
+                }
+
+                boolean hasExif = photoDate != null;
+                log.debug("图片{}EXIF信息: {}", hasExif ? "包含" : "不包含", file.getOriginalFilename());
+                return hasExif;
+            }
+
+            return false;
+
+        } catch (Exception e) {
+            log.warn("检查EXIF信息失败: {}", e.getMessage());
+            return false;
+        }
+    }
+
+    /**
+     * 获取图片格式信息
+     */
+    public static Map<String, Object> getImageInfo(MultipartFile file) {
+        Map<String, Object> info = new HashMap<>();
+
+        try (InputStream inputStream = new ByteArrayInputStream(file.getBytes())) {
+            // 获取图片格式
+            ImageInputStream imageInputStream = ImageIO.createImageInputStream(inputStream);
+            Iterator<ImageReader> readers = ImageIO.getImageReaders(imageInputStream);
+
+            if (readers.hasNext()) {
+                ImageReader reader = readers.next();
+                String formatName = reader.getFormatName();
+                info.put("format", formatName);
+
+                // 获取图片尺寸
+                reader.setInput(imageInputStream);
+                int width = reader.getWidth(0);
+                int height = reader.getHeight(0);
+                info.put("width", width);
+                info.put("height", height);
+
+                reader.dispose();
+            }
+
+            // 检查EXIF
+            boolean hasExif = hasExifInfo(file);
+            info.put("hasExif", hasExif);
+
+            // 文件信息
+            info.put("fileName", file.getOriginalFilename());
+            info.put("fileSize", file.getSize());
+            info.put("contentType", file.getContentType());
+
+            log.info("图片信息: {}", info);
+
+        } catch (Exception e) {
+            log.error("获取图片信息失败", e);
+        }
+
+        return info;
+    }
+
+    /**
+     * 简化的时间校验(用于测试)
+     */
+    public static Map<String, Object> validatePhotoTimeSimple(MultipartFile file, LocalDateTime startTime) {
+        Map<String, Object> result = new HashMap<>();
+
+        try {
+            // 直接使用当前时间(跳过EXIF检查)
+            LocalDateTime photoTime = LocalDateTime.now();
+            LocalDateTime currentTime = LocalDateTime.now();
+
+            log.info("简化时间校验 - 使用当前时间: {}, 开始时间: {}", photoTime, startTime);
+
+            // 允许5分钟的误差(考虑到系统时间可能不同步)
+            LocalDateTime adjustedStartTime = startTime.minusMinutes(5);
+
+            if (photoTime.isBefore(adjustedStartTime)) {
+                result.put("valid", false);
+                result.put("message", "图片时间早于巡检开始时间(考虑5分钟误差)");
+                return result;
+            }
+
+            result.put("valid", true);
+            result.put("message", "时间校验通过(简化模式)");
+            result.put("photoTime", photoTime);
+            result.put("timeSource", "当前系统时间");
+
+        } catch (Exception e) {
+            log.error("简化时间校验失败", e);
+            result.put("valid", false);
+            result.put("message", "时间校验失败: " + e.getMessage());
+        }
+
+        return result;
+    }
+
+    /**
+     * 调试方法:显示图片的所有EXIF信息
+     */
+    public static void debugExifInfo(MultipartFile file) {
+        try (InputStream inputStream = new ByteArrayInputStream(file.getBytes())) {
+            Metadata metadata = ImageMetadataReader.readMetadata(inputStream);
+
+            log.info("=== 图片EXIF信息调试 ===");
+            log.info("文件名: {}", file.getOriginalFilename());
+
+            metadata.getDirectories().forEach(directory -> {
+                log.info("目录: {}", directory.getName());
+                directory.getTags().forEach(tag -> {
+                    log.info("  {}: {}", tag.getTagName(), tag.getDescription());
+                });
+            });
+
+        } catch (Exception e) {
+            log.error("调试EXIF信息失败", e);
+        }
+    }
+}

+ 62 - 0
src/main/java/cn/chinaunicom/omniFlowNetCompute/until/PhotoUtil.java

@@ -0,0 +1,62 @@
+
+package cn.chinaunicom.omniFlowNetCompute.until;
+
+import com.drew.imaging.ImageMetadataReader;
+import com.drew.metadata.Metadata;
+import com.drew.metadata.exif.ExifIFD0Directory;
+import com.drew.metadata.exif.ExifSubIFDDirectory;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.TimeZone;
+
+public class PhotoUtil {
+
+    private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+
+    public static Date getPhotoDate(String filePath) {
+        File imageFile = new File(filePath);
+        try {
+            Metadata metadata = ImageMetadataReader.readMetadata(imageFile);
+            ExifSubIFDDirectory directory = metadata.getFirstDirectoryOfType(ExifSubIFDDirectory.class);
+
+            if (directory != null && directory.containsTag(ExifSubIFDDirectory.TAG_DATETIME_ORIGINAL)) {
+                Date originalDate = directory.getDate(ExifSubIFDDirectory.TAG_DATETIME_ORIGINAL);
+
+                // 将UTC时间转换为北京时间(+8小时)
+                Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
+                calendar.setTime(originalDate);
+
+                // 设置目标时区为北京时间
+                Calendar targetCalendar = Calendar.getInstance(TimeZone.getTimeZone("Asia/Shanghai"));
+                targetCalendar.set(Calendar.YEAR, calendar.get(Calendar.YEAR));
+                targetCalendar.set(Calendar.MONTH, calendar.get(Calendar.MONTH));
+                targetCalendar.set(Calendar.DAY_OF_MONTH, calendar.get(Calendar.DAY_OF_MONTH));
+                targetCalendar.set(Calendar.HOUR_OF_DAY, calendar.get(Calendar.HOUR_OF_DAY));
+                targetCalendar.set(Calendar.MINUTE, calendar.get(Calendar.MINUTE));
+                targetCalendar.set(Calendar.SECOND, calendar.get(Calendar.SECOND));
+                targetCalendar.set(Calendar.MILLISECOND, calendar.get(Calendar.MILLISECOND));
+
+                return targetCalendar.getTime();
+            } else {
+                System.out.println("没有找到拍摄时间信息。");
+                return null;
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    public static void main(String[] args) {
+        String filePath = "F:\\IMG20260130085427.jpg";
+        Date photoDate = getPhotoDate(filePath);
+
+        if (photoDate != null) {
+            System.out.println("照片拍摄时间: " + DATE_FORMAT.format(photoDate));
+        }
+    }
+}

+ 205 - 0
src/main/java/cn/chinaunicom/omniFlowNetCompute/until/SimpleExcelExportUtil.java

@@ -0,0 +1,205 @@
+package cn.chinaunicom.omniFlowNetCompute.until;
+
+import org.apache.poi.ss.usermodel.*;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.List;
+import java.util.Map;
+
+@Component
+public class SimpleExcelExportUtil {
+
+    /**
+     * 简单的Excel导出方法
+     */
+    public void exportSimpleExcel(HttpServletResponse response,
+                                  String month,
+                                  Map<String, Object> overallStats,
+                                  List<Map<String, Object>> systemStats,
+                                  List<Map<String, Object>> systemIndicatorStats) throws IOException {
+
+        // 创建工作簿
+        Workbook workbook = new XSSFWorkbook();
+
+        try {
+            // 1. 创建整体统计Sheet
+            Sheet overallSheet = workbook.createSheet("整体统计");
+            createOverallSheet(overallSheet, month, overallStats);
+
+            // 2. 创建系统统计Sheet
+            if (systemStats != null && !systemStats.isEmpty()) {
+                Sheet systemSheet = workbook.createSheet("系统统计");
+                createSystemSheet(systemSheet, month, systemStats);
+            }
+
+            // 3. 创建系统指标统计Sheet
+            if (systemIndicatorStats != null && !systemIndicatorStats.isEmpty()) {
+                Sheet indicatorSheet = workbook.createSheet("系统指标统计");
+                createIndicatorSheet(indicatorSheet, month, systemIndicatorStats);
+            }
+
+            // 设置响应头
+            String fileName = "告警报表统计_" + month + ".xlsx";
+            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+            response.setHeader("Content-Disposition", "attachment; filename=" + fileName);
+
+            // 写入响应流
+            OutputStream outputStream = response.getOutputStream();
+            workbook.write(outputStream);
+            outputStream.flush();
+
+        } finally {
+            workbook.close();
+        }
+    }
+
+    private void createOverallSheet(Sheet sheet, String month, Map<String, Object> overallStats) {
+        // 创建标题行
+        Row titleRow = sheet.createRow(0);
+        Cell titleCell = titleRow.createCell(0);
+        titleCell.setCellValue(month + "告警整体统计");
+
+        // 创建表头
+        Row headerRow = sheet.createRow(2);
+        String[] headers = {"统计项", "总数", "已处理", "未处理", "内容类", "阈值类", "存在性"};
+        for (int i = 0; i < headers.length; i++) {
+            Cell cell = headerRow.createCell(i);
+            cell.setCellValue(headers[i]);
+        }
+
+        // 创建数据行
+        Row dataRow = sheet.createRow(3);
+        dataRow.createCell(0).setCellValue("告警统计");
+
+        // 填充数据
+        String[] fields = {"total", "processed", "unprocessed", "contentCount", "thresholdCount", "existenceCount"};
+        for (int i = 0; i < fields.length; i++) {
+            Object value = overallStats.get(fields[i]);
+            Cell cell = dataRow.createCell(i + 1);
+            if (value != null) {
+                if (value instanceof Number) {
+                    cell.setCellValue(((Number) value).doubleValue());
+                } else {
+                    cell.setCellValue(value.toString());
+                }
+            } else {
+                cell.setCellValue(0);
+            }
+        }
+
+        // 设置列宽
+        for (int i = 0; i < headers.length; i++) {
+            sheet.autoSizeColumn(i);
+        }
+    }
+
+    private void createSystemSheet(Sheet sheet, String month, List<Map<String, Object>> systemStats) {
+        // 创建标题行
+        Row titleRow = sheet.createRow(0);
+        titleRow.createCell(0).setCellValue(month + "各系统告警统计");
+
+        // 创建表头
+        Row headerRow = sheet.createRow(2);
+        String[] headers = {"系统名称", "告警总数", "已处理数", "未处理数",
+                "内容类总数", "阈值类总数", "存在性总数"};
+        for (int i = 0; i < headers.length; i++) {
+            headerRow.createCell(i).setCellValue(headers[i]);
+        }
+
+        // 填充数据
+        int rowIndex = 3;
+        for (Map<String, Object> stat : systemStats) {
+            Row dataRow = sheet.createRow(rowIndex++);
+
+            // 系统名称
+            dataRow.createCell(0).setCellValue(stat.getOrDefault("systemName", "").toString());
+
+            // 统计字段
+            String[] fields = {"total", "processed", "unprocessed",
+                    "contentCount", "thresholdCount", "existenceCount"};
+
+            for (int i = 0; i < fields.length; i++) {
+                Object value = stat.get(fields[i]);
+                Cell cell = dataRow.createCell(i + 1);
+                if (value != null) {
+                    if (value instanceof Number) {
+                        cell.setCellValue(((Number) value).doubleValue());
+                    } else {
+                        cell.setCellValue(value.toString());
+                    }
+                } else {
+                    cell.setCellValue(0);
+                }
+            }
+        }
+
+        // 设置列宽
+        for (int i = 0; i < headers.length; i++) {
+            sheet.autoSizeColumn(i);
+        }
+    }
+
+    private void createIndicatorSheet(Sheet sheet, String month, List<Map<String, Object>> systemIndicatorStats) {
+        // 创建标题行
+        Row titleRow = sheet.createRow(0);
+        titleRow.createCell(0).setCellValue(month + "各系统指标告警统计");
+
+        // 创建表头
+        Row headerRow = sheet.createRow(2);
+        String[] headers = {"系统名称", "监控指标", "指标告警总数",
+                "内容类数量", "阈值类数量", "存在性数量"};
+        for (int i = 0; i < headers.length; i++) {
+            headerRow.createCell(i).setCellValue(headers[i]);
+        }
+
+        // 填充数据
+        int rowIndex = 3;
+        for (Map<String, Object> stat : systemIndicatorStats) {
+            Row dataRow = sheet.createRow(rowIndex++);
+
+            // 系统名称
+            dataRow.createCell(0).setCellValue(stat.getOrDefault("systemName", "").toString());
+
+            // 监控指标
+            dataRow.createCell(1).setCellValue(stat.getOrDefault("indicator", "").toString());
+
+            // 指标告警总数
+            Object total = stat.get("indicatorCount");
+            Cell totalCell = dataRow.createCell(2);
+            if (total != null) {
+                if (total instanceof Number) {
+                    totalCell.setCellValue(((Number) total).doubleValue());
+                } else {
+                    totalCell.setCellValue(total.toString());
+                }
+            } else {
+                totalCell.setCellValue(0);
+            }
+
+            // 各类型数量
+            String[] typeFields = {"indicatorContentCount", "indicatorThresholdCount", "indicatorExistenceCount"};
+            for (int i = 0; i < typeFields.length; i++) {
+                Object value = stat.get(typeFields[i]);
+                Cell cell = dataRow.createCell(i + 3);
+                if (value != null) {
+                    if (value instanceof Number) {
+                        cell.setCellValue(((Number) value).doubleValue());
+                    } else {
+                        cell.setCellValue(value.toString());
+                    }
+                } else {
+                    cell.setCellValue(0);
+                }
+            }
+        }
+
+        // 设置列宽
+        for (int i = 0; i < headers.length; i++) {
+            sheet.autoSizeColumn(i);
+        }
+    }
+}

+ 111 - 0
src/main/java/cn/chinaunicom/omniFlowNetCompute/until/WaitDealTaskReq.java

@@ -0,0 +1,111 @@
+package cn.chinaunicom.omniFlowNetCompute.until;
+
+import lombok.Data;
+
+import javax.imageio.ImageIO;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import java.awt.image.BufferedImage;
+import java.io.File;
+
+/**
+ * 待办任务表(WaitDealTask)实体类
+ *
+ * @author xingmengtian
+ * @since 2025-04-01 13:51:38
+ */
+@Data
+public class WaitDealTaskReq {
+
+    @NotNull(message = "当前页不能为空")
+    private Integer pageNum;
+    @NotNull(message = "每页条数不能为空")
+    private Integer pageSize;
+
+    /**
+     * 待办id
+     */
+    private String sourceId;
+    /**
+     * 待办标题
+     */
+    private String subject;
+    /**
+     * 接收人
+     */
+    private String receiver;
+    /**
+     * 状态0已完成1未完成
+     */
+    private String status;
+    /**
+     * 发送人
+     */
+    @NotBlank(message = "发送人不能为空")
+    private String creator;
+
+//    public static void main(String[] args) {
+//        File file = new File("C:\\Users\\ASUS\\Desktop\\1720074273058.jpg"); // 替换为你的图片文件路径
+//        try {
+//            // 获取基本文件属性(通过 File 转 Path 再获取属性,因为 File 没有直接读取 BasicFileAttributes 的方法)
+//            BasicFileAttributes attrs = java.nio.file.Files.readAttributes(file.toPath(), BasicFileAttributes.class);
+//
+//            // 获取创建时间并转换为本地时区
+//            FileTime creationTime = attrs.creationTime();
+//            ZonedDateTime creationZoned = creationTime.toInstant().atZone(ZoneId.systemDefault());
+//            System.out.println("Creation Time: " + creationZoned);
+//
+//            // 获取最后修改时间并转换为本地时区
+//            long lastModified = file.lastModified(); // File 的方法,返回毫秒时间戳
+//            ZonedDateTime modifiedZoned = java.time.Instant.ofEpochMilli(lastModified)
+//                    .atZone(ZoneId.systemDefault());
+//            System.out.println("Last Modified Time: " + modifiedZoned);
+//
+//            // 注意:File 类没有直接获取最后访问时间的方法,需要通过 Files.readAttributes
+//            FileTime lastAccessTime = attrs.lastAccessTime();
+//            ZonedDateTime accessZoned = lastAccessTime.toInstant().atZone(ZoneId.systemDefault());
+//            System.out.println("Last Access Time: " + accessZoned);
+//
+//        } catch (IOException e) {
+//            e.printStackTrace();
+//        }
+//    }
+
+    public static double compareHistograms(BufferedImage img1, BufferedImage img2) {
+        int[] hist1 = calculateHistogram(img1);
+        int[] hist2 = calculateHistogram(img2);
+
+        // 计算直方图交集
+        double intersection = 0;
+        for (int i = 0; i < 256; i++) {
+            intersection += Math.min(hist1[i], hist2[i]);
+        }
+
+        // 归一化
+        double total1 = img1.getWidth() * img1.getHeight();
+        double total2 = img2.getWidth() * img2.getHeight();
+        return intersection / Math.min(total1, total2);
+    }
+
+    private static int[] calculateHistogram(BufferedImage img) {
+        int[] hist = new int[256];
+        for (int y = 0; y < img.getHeight(); y++) {
+            for (int x = 0; x < img.getWidth(); x++) {
+                int rgb = img.getRGB(x, y);
+                int gray = (int) (0.299 * ((rgb >> 16) & 0xFF) +
+                        0.587 * ((rgb >> 8) & 0xFF) +
+                        0.114 * (rgb & 0xFF));
+                hist[gray]++;
+            }
+        }
+        return hist;
+    }
+
+   public static void main(String[] args) throws Exception {
+       BufferedImage img1 = ImageIO.read(new File("C:\\Users\\ASUS\\Desktop\\b66b9fbdb27d0040453ba58dcfd0ce81.jpg"));
+       BufferedImage img2 = ImageIO.read(new File("C:\\Users\\ASUS\\Desktop\\ad2a8a7a6238de806b9c47ab133f3369.jpg"));
+       double similarity = compareHistograms(img1, img2);
+       System.out.println("直方图相似度: " + similarity);
+   }
+}
+

+ 22 - 2
src/main/resources/application.yml

@@ -7,13 +7,18 @@ spring:
       maxRedirects: 5
       nodes: 10.130.17.218:14378,10.130.17.229:14378,10.130.17.232:14378,10.130.17.238:14378,10.130.17.240:14378,10.130.17.243:14378
       timeout: 300000
-    password: gQ3#aR7#lA
+    password: Sunsd10#
   datasource:
     driver-class-name: com.oceanbase.jdbc.Driver
     url: jdbc:oceanbase://10.125.41.228:2883,10.125.41.242:2883,10.125.41.249:2883/data_yc?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=Asia/Shanghai&connectTimeout=30000&socketTimeout=60000&allowPublicKeyRetrieval=true
     username: jeecg@szh_yxbznl#armdb4_2_xxtst
     password: fuNS7yk1G#$
 
+  servlet:
+    multipart:
+      max-file-size: 10MB
+      max-request-size: 10MB
+
 # mybatis配置
 mybatis:
   configuration:
@@ -34,4 +39,19 @@ pagehelper:
   # 分页参数绑定别名,固定写法
   params: count=countSql
   # 【OceanBase必加配置】解决分页count总数不准确/分页失效问题
-  auto-dialect: true
+  auto-dialect: true
+# 文件上传配置
+file:
+  upload:
+    path: ./uploads/
+    #basicPath: D:/project/liantong/omniFlowNetCompute/uploads/
+    basicPath: /opt/app/uploads/
+    allowed-extensions: jpg,jpeg,png,gif
+    max-size: 5242880  # 5MB
+# 日志配置
+logging:
+  level:
+    cn.chinaunicom.omniFlowNetCompute: DEBUG
+    cn.chinaunicom.omniFlowNetCompute.mapper: DEBUG
+  file:
+    name: logs/monitoring.log

+ 167 - 0
src/main/resources/mapper/omni/AlarmMapper.xml

@@ -0,0 +1,167 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<mapper namespace="cn.chinaunicom.omniFlowNetCompute.mapper.AlarmMapper">
+    <resultMap type="cn.chinaunicom.omniFlowNetCompute.domain.Alarm" id="AlarmResult">
+        <id property="id" column="id" />
+        <result property="alarmType" column="alarm_type" />
+        <result property="occurTime" column="occur_time" />
+        <result property="processStatus" column="process_status" />
+        <result property="month" column="month" />
+        <result property="createTime" column="create_time" />
+        <result property="indicator" column="indicator" />
+        <result property="systemName" column="system_name" />
+    </resultMap>
+    <!-- 场景2:按月份统计告警数据 -->
+    <select id="getAlarmStatsByMonth" resultType="java.util.Map">
+        SELECT
+            COUNT(*) as total,
+            SUM(CASE WHEN process_status = 1 THEN 1 ELSE 0 END) as processed,
+            SUM(CASE WHEN process_status = 0 THEN 1 ELSE 0 END) as unprocessed,
+            SUM(CASE WHEN alarm_type = '内容类' THEN 1 ELSE 0 END) as contentCount,
+            SUM(CASE WHEN alarm_type = '阈值类' THEN 1 ELSE 0 END) as thresholdCount,
+            SUM(CASE WHEN alarm_type = '存在性' THEN 1 ELSE 0 END) as existenceCount
+        FROM alarm
+        WHERE month = #{month}
+    </select>
+    <!-- 场景2:按月份和系统统计告警数据 -->
+    <select id="getAlarmStatsByMonthAndSystem" resultType="java.util.Map">
+        select
+            system_name,
+            COUNT(*) as total,
+            SUM(case when process_status = 1 then 1 else 0 end) as processed,
+            SUM(case when process_status = 0 then 1 else 0 end) as unprocessed,
+            SUM(case when alarm_type = '内容类' then 1 else 0 end) as contentCount,
+            SUM(case when alarm_type = '阈值类' then 1 else 0 end) as thresholdCount,
+            SUM(case when alarm_type = '存在性' then 1 else 0 end) as existenceCount,
+
+            SUM(case when indicator = '数据未送达' then 1 else 0 end) as indicator_dataNoDeliveredCount,
+            SUM(case when indicator = '内部连接性' then 1 else 0 end) as indicator_internalConnetCount,
+            SUM(case when indicator = '访问接口频率' then 1 else 0 end) as indicator_interfaceFrequencyCount,
+            SUM(case when indicator = '数据库连接数' then 1 else 0 end) as indicator_databaseConnectCount,
+            SUM(case when indicator = '页面防篡改' then 1 else 0 end) as indicator_pageTamperProofCount,
+            SUM(case when indicator = '网络边界连通性' then 1 else 0 end) as indicator_networkBoundaryConnectCount,
+            SUM(case when indicator = '定时任务' then 1 else 0 end) as indicator_scheduledTaskCount,
+            SUM(case when indicator = 'OSS使用情况' then 1 else 0 end) as indicator_ossUseCount,
+            SUM(case when indicator = '异常访问' then 1 else 0 end) as indicator_abnormalAccessCount
+        from
+            data_yc.alarm
+        where
+            month = #{month}
+        group by
+            system_name
+        order by
+            total desc
+    </select>
+
+    <!-- 场景2:按月份和指标统计告警数据 -->
+    <select id="getAlarmStatsByMonthAndIndicator" resultType="java.util.Map">
+        SELECT
+            indicator,
+            COUNT(*) as total,
+            SUM(CASE WHEN process_status = 1 THEN 1 ELSE 0 END) as processed,
+            SUM(CASE WHEN process_status = 0 THEN 1 ELSE 0 END) as unprocessed,
+            SUM(CASE WHEN alarm_type = '内容类' THEN 1 ELSE 0 END) as contentCount,
+            SUM(CASE WHEN alarm_type = '阈值类' THEN 1 ELSE 0 END) as thresholdCount,
+            SUM(CASE WHEN alarm_type = '存在性' THEN 1 ELSE 0 END) as existenceCount
+        FROM alarm
+        WHERE month = #{month}
+        GROUP BY indicator
+        ORDER BY total DESC
+    </select>
+
+    <!-- 场景3:获取未处理的告警列表 -->
+    <select id="getUnprocessedAlarms" resultMap="AlarmResult">
+        SELECT
+        id, alarm_type, occur_time, process_status,
+        month, create_time, indicator
+        FROM alarm
+        WHERE process_status = 0
+        ORDER BY occur_time DESC
+        <if test="limit != null and limit > 0">
+            LIMIT #{limit}
+        </if>
+    </select>
+
+    <!-- 场景3:获取七日告警分布(按指标) -->
+    <select id="getAlarmDistributionLast7Days" resultType="java.util.Map">
+        SELECT
+            indicator,
+            COUNT(*) as count
+        FROM alarm
+        WHERE occur_time >= DATE_SUB(NOW(), INTERVAL 7 DAY)
+        GROUP BY indicator
+        ORDER BY count DESC
+    </select>
+
+    <!-- 场景3:获取七日告警趋势(按类型) -->
+    <select id="getAlarmTrendLast7Days" resultType="java.util.Map">
+        SELECT
+            DATE(occur_time) as date,
+            alarm_type as type,
+            COUNT(*) as count
+        FROM alarm
+        WHERE occur_time >= DATE_SUB(NOW(), INTERVAL 7 DAY)
+        GROUP BY DATE(occur_time), alarm_type
+        ORDER BY date, alarm_type
+    </select>
+
+    <!-- 场景3:获取各指标七日告警统计 -->
+    <select id="getIndicatorAlarmStatsLast7Days" resultType="java.util.Map">
+        SELECT
+            indicator,
+            alarm_type as type,
+            COUNT(*) as count
+        FROM alarm
+        WHERE occur_time >= DATE_SUB(NOW(), INTERVAL 7 DAY)
+        GROUP BY indicator, alarm_type
+        ORDER BY indicator, type
+    </select>
+
+    <!-- 场景3:获取各系统七日告警统计 -->
+    <select id="getSystemAlarmStatsLast7Days" resultType="java.util.Map">
+        SELECT
+            system_name as systemName,
+            alarm_type as type,
+            COUNT(*) as count
+        FROM alarm
+        WHERE occur_time >= DATE_SUB(NOW(), INTERVAL 7 DAY)
+        GROUP BY system_name, alarm_type
+        ORDER BY systemName, type
+    </select>
+
+    <!-- 获取所有月份列表 -->
+    <select id="getAllMonths" resultType="java.lang.String">
+        SELECT DISTINCT month
+        FROM alarm
+        WHERE month IS NOT NULL AND month != ''
+        ORDER BY month DESC
+    </select>
+
+    <!-- 插入告警数据 -->
+    <insert id="insertAlarm" parameterType="cn.chinaunicom.omniFlowNetCompute.domain.Alarm" useGeneratedKeys="true" keyProperty="id">
+        INSERT INTO alarm (
+            alarm_type, occur_time, process_status,
+            month, indicator, create_time
+        ) VALUES (
+                     #{alarmType}, #{occurTime}, #{processStatus},
+                     #{month}, #{indicator}, NOW()
+                 )
+    </insert>
+
+    <!-- 根据ID查询告警 -->
+    <select id="getAlarmById" resultType="cn.chinaunicom.omniFlowNetCompute.domain.Alarm">
+        SELECT * FROM alarm WHERE id = #{id}
+    </select>
+
+    <!-- 场景3:获取七日告警趋势(数据获取情况) -->
+    <select id="getAlarmSituationLast7Days" resultType="java.util.Map">
+        SELECT
+            alarm_type as type,
+            situation as situation,
+            COUNT(*) as count
+        FROM alarm
+        GROUP BY type, situation
+        ORDER BY type, situation
+    </select>
+</mapper>

+ 1 - 0
src/main/resources/mapper/omni/FreemarketMapper.xml

@@ -204,6 +204,7 @@
             f.publish_time,
             f.create_time as fm_create_time,
             f.update_time as fm_update_time,
+            f.data_source as dataSource,
             
             o.id as opp_id,
             o.opportunity_code,

+ 225 - 0
src/main/resources/mapper/omni/InspectionMapper.xml

@@ -0,0 +1,225 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="cn.chinaunicom.omniFlowNetCompute.mapper.InspectionMapper">
+
+    <!-- 巡检记录结果映射 -->
+    <resultMap id="RecordResultMap" type="cn.chinaunicom.omniFlowNetCompute.domain.InspectionRecord">
+        <id column="id" property="id" />
+        <result column="record_id" property="recordId" />
+        <result column="task_id" property="taskId" />
+        <result column="task_name" property="taskName" />
+        <result column="inspector" property="inspector" />
+        <result column="inspector_id" property="inspectorId" />
+        <result column="start_time" property="startTime" />
+        <result column="end_time" property="endTime" />
+        <result column="result" property="result" />
+        <result column="result_description" property="resultDescription" />
+        <result column="work_photo_path" property="workPhotoPath" />
+        <result column="photo_check_result" property="photoCheckResult" />
+        <result column="create_time" property="createTime" />
+    </resultMap>
+
+    <!-- ========== 巡检记录SQL ========== -->
+
+    <!-- 插入巡检记录 -->
+    <insert id="insertRecord" parameterType="cn.chinaunicom.omniFlowNetCompute.domain.InspectionRecord"
+            useGeneratedKeys="true" keyProperty="id">
+        INSERT INTO inspection_record (
+            record_id, task_id, task_name, inspector,
+            inspector_id, start_time, end_time, result,
+            result_description, work_photo_path, photo_check_result, create_time
+        ) VALUES (
+                     #{recordId}, #{taskId}, #{taskName}, #{inspector},
+                     #{inspectorId}, #{startTime}, #{endTime}, #{result},
+                     #{resultDescription}, #{workPhotoPath}, #{photoCheckResult}, NOW()
+                 )
+    </insert>
+
+    <!-- 更新巡检记录 -->
+    <update id="updateRecord" parameterType="cn.chinaunicom.omniFlowNetCompute.domain.InspectionRecord">
+        UPDATE inspection_record
+        <set>
+            <if test="endTime != null">end_time = #{endTime},</if>
+            <if test="result != null">result = #{result},</if>
+            <if test="resultDescription != null">result_description = #{resultDescription},</if>
+            <if test="workPhotoPath != null">work_photo_path = #{workPhotoPath},</if>
+            <if test="photoCheckResult != null">photo_check_result = #{photoCheckResult},</if>
+        </set>
+        WHERE record_id = #{recordId}
+    </update>
+
+    <!-- 根据记录ID查询巡检记录 -->
+    <select id="selectRecordById" resultMap="RecordResultMap">
+        SELECT * FROM inspection_record
+        WHERE record_id = #{recordId}
+    </select>
+
+    <!-- 查询所有巡检记录 -->
+    <select id="selectAllRecords" resultMap="RecordResultMap">
+        SELECT * FROM inspection_record
+        ORDER BY create_time DESC
+    </select>
+
+    <!-- 根据任务ID查询巡检记录 -->
+    <select id="selectRecordsByTaskId" resultMap="RecordResultMap">
+        SELECT * FROM inspection_record
+        WHERE task_id = #{taskId}
+        ORDER BY create_time DESC
+    </select>
+
+    <!-- 根据巡检人员查询巡检记录 -->
+    <select id="selectRecordsByInspector" resultMap="RecordResultMap">
+        SELECT * FROM inspection_record
+        WHERE inspector = #{inspector}
+        ORDER BY create_time DESC
+    </select>
+
+    <!-- 根据时间范围查询巡检记录 -->
+    <select id="selectRecordsByTimeRange" resultMap="RecordResultMap">
+        SELECT * FROM inspection_record
+        WHERE create_time BETWEEN #{startTime} AND #{endTime}
+        ORDER BY create_time DESC
+    </select>
+
+    <!-- 根据结果查询巡检记录 -->
+    <select id="selectRecordsByResult" resultMap="RecordResultMap">
+        SELECT * FROM inspection_record
+        WHERE result = #{result}
+        ORDER BY create_time DESC
+    </select>
+
+    <!-- 分页查询巡检记录 -->
+    <select id="selectRecordsByPage" resultMap="RecordResultMap">
+        SELECT * FROM inspection_record
+        ORDER BY create_time DESC
+            LIMIT #{offset}, #{pageSize}
+    </select>
+
+    <!-- 统计巡检记录总数 -->
+    <select id="countRecords" resultType="int">
+        SELECT COUNT(*) FROM inspection_record
+    </select>
+
+    <!-- 统计条件查询的记录数 -->
+    <select id="countRecordsByCondition" resultType="int">
+        SELECT COUNT(*) FROM inspection_record
+        <where>
+            <if test="taskId != null">
+                AND task_id = #{taskId}
+            </if>
+            <if test="inspector != null and inspector != ''">
+                AND inspector = #{inspector}
+            </if>
+            <if test="result != null and result != ''">
+                AND result = #{result}
+            </if>
+            <if test="startTime != null">
+                AND create_time >= #{startTime}
+            </if>
+            <if test="endTime != null">
+                AND create_time &lt;= #{endTime}
+            </if>
+        </where>
+    </select>
+
+    <!-- 删除巡检记录 -->
+    <delete id="deleteRecordById">
+        DELETE FROM inspection_record WHERE record_id = #{recordId}
+    </delete>
+
+    <!-- ========== 巡检记录详情SQL ========== -->
+
+    <!-- 插入巡检记录详情 -->
+    <insert id="insertRecordDetail">
+        INSERT INTO inspection_detail (
+            record_id, item_id, item_name,
+            check_result, description, create_time
+        ) VALUES (
+                     #{recordId}, #{itemId}, #{itemName},
+                     #{checkResult}, #{description}, NOW()
+                 )
+    </insert>
+
+    <!-- 查询巡检记录详情 -->
+    <select id="selectRecordDetails" resultType="map">
+        SELECT
+            d.item_name,
+            d.check_result,
+            d.description,
+            i.category,
+            i.check_method,
+            i.standard_value
+        FROM inspection_detail d
+                 LEFT JOIN inspection_item i ON d.item_id = i.id
+        WHERE d.record_id = #{recordId}
+        ORDER BY i.sort_order
+    </select>
+
+    <!-- 删除巡检记录详情 -->
+    <delete id="deleteRecordDetails">
+        DELETE FROM inspection_detail WHERE record_id = #{recordId}
+    </delete>
+
+    <!-- ========== 统计查询SQL ========== -->
+
+    <!-- 获取巡检统计 -->
+    <select id="getInspectionStatistics" resultType="map">
+        SELECT
+            COUNT(*) as total_count,
+            SUM(CASE WHEN result = '正常' THEN 1 ELSE 0 END) as normal_count,
+            SUM(CASE WHEN result = '异常' THEN 1 ELSE 0 END) as abnormal_count
+        FROM inspection_record
+        WHERE create_time BETWEEN #{startTime} AND #{endTime}
+    </select>
+
+    <!-- 获取巡检趋势 -->
+    <select id="getInspectionTrend" resultType="map">
+        SELECT
+            DATE(create_time) as date,
+            COUNT(*) as count,
+            result
+        FROM inspection_record
+        WHERE create_time >= DATE_SUB(NOW(), INTERVAL #{days} DAY)
+        GROUP BY DATE(create_time), result
+        ORDER BY date DESC
+    </select>
+
+    <!-- 获取每日巡检统计 -->
+    <select id="getDailyInspectionStats" resultType="map">
+        SELECT
+            DATE(create_time) as date,
+            COUNT(*) as total_count,
+            SUM(CASE WHEN result = '正常' THEN 1 ELSE 0 END) as normal_count,
+            SUM(CASE WHEN result = '异常' THEN 1 ELSE 0 END) as abnormal_count
+        FROM inspection_record
+        WHERE create_time >= DATE_SUB(NOW(), INTERVAL #{days} DAY)
+        GROUP BY DATE(create_time)
+        ORDER BY date DESC
+    </select>
+
+    <!-- 获取巡检人员统计 -->
+    <select id="getInspectorStats" resultType="map">
+        SELECT
+            inspector,
+            COUNT(*) as total_count,
+            SUM(CASE WHEN result = '正常' THEN 1 ELSE 0 END) as normal_count,
+            SUM(CASE WHEN result = '异常' THEN 1 ELSE 0 END) as abnormal_count
+        FROM inspection_record
+        GROUP BY inspector
+        ORDER BY total_count DESC
+    </select>
+
+    <!-- 获取任务完成情况统计 -->
+    <select id="getTaskCompletionStats" resultType="map">
+        SELECT
+            task_name,
+            COUNT(*) as total_count,
+            SUM(CASE WHEN result = '正常' THEN 1 ELSE 0 END) as normal_count,
+            SUM(CASE WHEN result = '异常' THEN 1 ELSE 0 END) as abnormal_count
+        FROM inspection_record
+        GROUP BY task_name
+        ORDER BY total_count DESC
+    </select>
+
+</mapper>

+ 200 - 0
src/main/resources/mapper/omni/InspectionTaskMapper.xml

@@ -0,0 +1,200 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="cn.chinaunicom.omniFlowNetCompute.mapper.InspectionTaskMapper">
+
+    <!-- InspectionItem结果映射 -->
+    <resultMap id="InspectionItemResultMap" type="cn.chinaunicom.omniFlowNetCompute.domain.InspectionItem">
+        <id column="id" property="id" />
+        <result column="task_id" property="taskId" />
+        <result column="item_name" property="itemName" />
+        <result column="category" property="category" />
+        <result column="check_method" property="checkMethod" />
+        <result column="standard_value" property="standardValue" />
+        <result column="sort_order" property="sortOrder" />
+        <result column="create_time" property="createTime" />
+    </resultMap>
+
+    <!-- InspectionTask结果映射(基础) -->
+    <resultMap id="InspectionTaskResultMap" type="cn.chinaunicom.omniFlowNetCompute.domain.InspectionTask">
+        <id column="id" property="id" />
+        <result column="task_name" property="taskName" />
+        <result column="task_description" property="taskDescription" />
+        <result column="baseline_image" property="baselineImage" />
+        <result column="status" property="status" />
+        <result column="create_time" property="createTime" />
+        <result column="update_time" property="updateTime" />
+    </resultMap>
+
+    <!-- InspectionTask结果映射(包含巡检项) -->
+    <resultMap id="InspectionTaskWithItemsResultMap" type="cn.chinaunicom.omniFlowNetCompute.domain.InspectionTask"
+               extends="InspectionTaskResultMap">
+        <collection property="items" resultMap="InspectionItemResultMap" columnPrefix="item_" />
+    </resultMap>
+
+    <!-- ========== 巡检任务SQL ========== -->
+
+    <!-- 插入巡检任务 -->
+    <insert id="insertTask" parameterType="cn.chinaunicom.omniFlowNetCompute.domain.InspectionTask"
+            useGeneratedKeys="true" keyProperty="id">
+        INSERT INTO inspection_task (
+            task_name, task_description, baseline_image,
+            status, create_time, update_time
+        ) VALUES (
+                     #{taskName}, #{taskDescription}, #{baselineImage},
+                     #{status}, NOW(), NOW()
+                 )
+    </insert>
+
+    <!-- 更新巡检任务 -->
+    <update id="updateTask" parameterType="cn.chinaunicom.omniFlowNetCompute.domain.InspectionTask">
+        UPDATE inspection_task
+        <set>
+            <if test="taskName != null">task_name = #{taskName},</if>
+            <if test="taskDescription != null">task_description = #{taskDescription},</if>
+            <if test="baselineImage != null">baseline_image = #{baselineImage},</if>
+            <if test="status != null">status = #{status},</if>
+            update_time = NOW()
+        </set>
+        WHERE id = #{id}
+    </update>
+
+    <!-- 删除巡检任务 -->
+    <delete id="deleteTask">
+        DELETE FROM inspection_task WHERE id = #{id}
+    </delete>
+
+    <!-- 根据ID查询巡检任务(不包含巡检项) -->
+    <select id="selectTaskById" resultMap="InspectionTaskResultMap">
+        SELECT * FROM inspection_task WHERE id = #{id}
+    </select>
+
+    <!-- 查询所有巡检任务(不包含巡检项) -->
+    <select id="selectAllTasks" resultMap="InspectionTaskResultMap">
+        SELECT * FROM inspection_task
+        ORDER BY create_time DESC
+    </select>
+
+    <!-- 根据状态查询巡检任务 -->
+    <select id="selectTasksByStatus" resultMap="InspectionTaskResultMap">
+        SELECT * FROM inspection_task
+        WHERE status = #{status}
+        ORDER BY create_time DESC
+    </select>
+
+    <!-- 根据任务名称模糊查询 -->
+    <select id="selectTasksByName" resultMap="InspectionTaskResultMap">
+        SELECT * FROM inspection_task
+        WHERE task_name LIKE CONCAT('%', #{taskName}, '%')
+        ORDER BY create_time DESC
+    </select>
+
+    <!-- ========== 巡检项SQL ========== -->
+
+    <!-- 插入巡检项 -->
+    <insert id="insertItem" parameterType="cn.chinaunicom.omniFlowNetCompute.domain.InspectionItem"
+            useGeneratedKeys="true" keyProperty="id">
+        INSERT INTO inspection_item (
+            task_id, item_name, category, check_method,
+            standard_value, sort_order, create_time
+        ) VALUES (
+                     #{taskId}, #{itemName}, #{category}, #{checkMethod},
+                     #{standardValue}, #{sortOrder}, NOW()
+                 )
+    </insert>
+
+    <!-- 批量插入巡检项 -->
+    <insert id="batchInsertItems" parameterType="list">
+        INSERT INTO inspection_item (
+        task_id, item_name, category, check_method,
+        standard_value, sort_order, create_time
+        ) VALUES
+        <foreach collection="list" item="item" separator=",">
+            (#{item.taskId}, #{item.itemName}, #{item.category}, #{item.checkMethod},
+            #{item.standardValue}, #{item.sortOrder}, NOW())
+        </foreach>
+    </insert>
+
+    <!-- 更新巡检项 -->
+    <update id="updateItem" parameterType="cn.chinaunicom.omniFlowNetCompute.domain.InspectionItem">
+        UPDATE inspection_item
+        <set>
+            <if test="itemName != null">item_name = #{itemName},</if>
+            <if test="category != null">category = #{category},</if>
+            <if test="checkMethod != null">check_method = #{checkMethod},</if>
+            <if test="standardValue != null">standard_value = #{standardValue},</if>
+            <if test="sortOrder != null">sort_order = #{sortOrder},</if>
+        </set>
+        WHERE id = #{id}
+    </update>
+
+    <!-- 删除巡检项 -->
+    <delete id="deleteItem">
+        DELETE FROM inspection_item WHERE id = #{id}
+    </delete>
+
+    <!-- 根据任务ID删除所有巡检项 -->
+    <delete id="deleteItemsByTaskId">
+        DELETE FROM inspection_item WHERE task_id = #{taskId}
+    </delete>
+
+    <!-- 根据任务ID查询巡检项 -->
+    <select id="selectItemsByTaskId" resultMap="InspectionItemResultMap">
+        SELECT * FROM inspection_item
+        WHERE task_id = #{taskId}
+        ORDER BY sort_order, id
+    </select>
+
+    <!-- 根据ID查询巡检项 -->
+    <select id="selectItemById" resultMap="InspectionItemResultMap">
+        SELECT * FROM inspection_item WHERE id = #{id}
+    </select>
+
+    <!-- ========== 统计查询SQL ========== -->
+
+    <!-- 统计任务数量 -->
+    <select id="countTasks" resultType="int">
+        SELECT COUNT(*) FROM inspection_task
+    </select>
+
+    <!-- 统计指定状态的巡检任务数量 -->
+    <select id="countTasksByStatus" resultType="int">
+        SELECT COUNT(*) FROM inspection_task WHERE status = #{status}
+    </select>
+
+    <!-- 获取任务及其巡检项(关联查询) -->
+    <select id="selectTaskWithItems" resultMap="InspectionTaskWithItemsResultMap">
+        SELECT
+            t.*,
+            i.id as item_id,
+            i.task_id as item_task_id,
+            i.item_name as item_item_name,
+            i.category as item_category,
+            i.check_method as item_check_method,
+            i.standard_value as item_standard_value,
+            i.sort_order as item_sort_order,
+            i.create_time as item_create_time
+        FROM inspection_task t
+                 LEFT JOIN inspection_item i ON t.id = i.task_id
+        WHERE t.id = #{id}
+        ORDER BY i.sort_order, i.id
+    </select>
+
+    <!-- 获取所有任务及其巡检项(关联查询) -->
+    <select id="selectAllTasksWithItems" resultMap="InspectionTaskWithItemsResultMap">
+        SELECT
+            t.*,
+            i.id as item_id,
+            i.task_id as item_task_id,
+            i.item_name as item_item_name,
+            i.category as item_category,
+            i.check_method as item_check_method,
+            i.standard_value as item_standard_value,
+            i.sort_order as item_sort_order,
+            i.create_time as item_create_time
+        FROM inspection_task t
+                 LEFT JOIN inspection_item i ON t.id = i.task_id
+        ORDER BY t.create_time DESC, i.sort_order, i.id
+    </select>
+
+</mapper>