zhangyi před 6 měsíci
rodič
revize
2b972d7487

+ 4 - 13
pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-parent</artifactId>
-        <version>4.0.3</version>
+        <version>3.3.7</version>
         <relativePath/> <!-- lookup parent from repository -->
     </parent>
     <groupId>com.seecoder.ai</groupId>
@@ -42,9 +42,10 @@
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-web</artifactId>
         </dependency>
+
         <dependency>
             <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-webmvc</artifactId>
+            <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
         </dependency>
         <dependency>
             <groupId>com.mysql</groupId>
@@ -59,17 +60,7 @@
         </dependency>
         <dependency>
             <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-data-jdbc-test</artifactId>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-data-jpa-test</artifactId>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-webmvc-test</artifactId>
+            <artifactId>spring-boot-starter-test</artifactId>
             <scope>test</scope>
         </dependency>
         <dependency>

+ 2 - 0
src/main/java/com/seecoder/ai/alarm/AlarmApplication.java

@@ -2,8 +2,10 @@ package com.seecoder.ai.alarm;
 
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.data.web.config.EnableSpringDataWebSupport;
 
 @SpringBootApplication
+@EnableSpringDataWebSupport(pageSerializationMode = EnableSpringDataWebSupport.PageSerializationMode.VIA_DTO)
 public class AlarmApplication {
 
     public static void main(String[] args) {

+ 115 - 0
src/main/java/com/seecoder/ai/alarm/controller/AlarmRecordController.java

@@ -0,0 +1,115 @@
+package com.seecoder.ai.alarm.controller;
+
+import com.seecoder.ai.alarm.common.ApiResult;
+import com.seecoder.ai.alarm.dto.AlarmRecordQueryDTO;
+import com.seecoder.ai.alarm.dto.AlarmRecordUpdateDTO;
+import com.seecoder.ai.alarm.service.AlarmRecordService;
+import com.seecoder.ai.alarm.vo.AlarmRecordVO;
+import org.springframework.data.domain.Page;
+import org.springframework.web.bind.annotation.*;
+
+import jakarta.validation.Valid;
+
+@RestController
+@RequestMapping("/alarm-records")
+public class AlarmRecordController {
+
+    private final AlarmRecordService alarmRecordService;
+
+    public AlarmRecordController(AlarmRecordService alarmRecordService) {
+        this.alarmRecordService = alarmRecordService;
+    }
+
+    /**
+     * 根据ID查询报警记录 - GET
+     */
+    @GetMapping("/{id}")
+    public ApiResult<AlarmRecordVO> getAlarmRecordById(@RequestParam Long id) {
+        try {
+            AlarmRecordVO result = alarmRecordService.getAlarmRecordById(id);
+            return ApiResult.success(result);
+        } catch (Exception e) {
+            return ApiResult.error("查询报警记录失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 分页查询报警记录 - POST
+     */
+    @PostMapping("/list")
+    public ApiResult<Page<AlarmRecordVO>> pageQueryAlarmRecords(@Valid @RequestBody AlarmRecordQueryDTO queryDTO) {
+        try {
+            Page<AlarmRecordVO> result = alarmRecordService.pageQueryAlarmRecords(queryDTO);
+            return ApiResult.success(result);
+        } catch (Exception e) {
+            return ApiResult.error("查询报警记录列表失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 更新报警记录 - POST
+     */
+    @PostMapping("/update")
+    public ApiResult<AlarmRecordVO> updateAlarmRecord(@Valid @RequestBody AlarmRecordUpdateDTO updateDTO) {
+        try {
+            AlarmRecordVO result = alarmRecordService.updateAlarmRecord(updateDTO);
+            return ApiResult.success(result);
+        } catch (Exception e) {
+            return ApiResult.error("更新报警记录失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 更新报警状态 - GET
+     */
+    @GetMapping("/update-status")
+    public ApiResult<Void> updateStatus(@RequestParam Long id, @RequestParam Integer status) {
+        try {
+            alarmRecordService.updateStatus(id, status);
+            return ApiResult.success();
+        } catch (Exception e) {
+            return ApiResult.error("更新状态失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 添加处理记录 - GET
+     */
+    @GetMapping("/add-handle-record")
+    public ApiResult<Void> addHandleRecord(@RequestParam Long id, @RequestParam String handleRecord) {
+        try {
+            alarmRecordService.addHandleRecord(id, handleRecord);
+            return ApiResult.success();
+        } catch (Exception e) {
+            return ApiResult.error("添加处理记录失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 人工判断AI分析有效性 - GET
+     */
+    @GetMapping("/verify-ai")
+    public ApiResult<Void> verifyAiAnalysis(@RequestParam Long id,
+                                            @RequestParam Integer humanVerifyAi,
+                                            @RequestParam(required = false) String remark) {
+        try {
+            alarmRecordService.verifyAiAnalysis(id, humanVerifyAi, remark);
+            return ApiResult.success();
+        } catch (Exception e) {
+            return ApiResult.error("人工验证失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 设置最终处理方式 - GET
+     */
+    @GetMapping("/set-final-method")
+    public ApiResult<Void> setFinalHandleMethod(@RequestParam Long id, @RequestParam String finalHandleMethod) {
+        try {
+            alarmRecordService.setFinalHandleMethod(id, finalHandleMethod);
+            return ApiResult.success();
+        } catch (Exception e) {
+            return ApiResult.error("设置最终处理方式失败: " + e.getMessage());
+        }
+    }
+}

+ 1 - 1
src/main/java/com/seecoder/ai/alarm/controller/AlarmRuleController.java

@@ -10,7 +10,7 @@ import org.springframework.web.bind.annotation.*;
 
 
 @RestController
-@RequestMapping("/api/alarm/rules")
+@RequestMapping("/alarm/rules")
 public class AlarmRuleController {
 
     private final AlarmRuleService alarmRuleService;

+ 181 - 0
src/main/java/com/seecoder/ai/alarm/controller/LogController.java

@@ -0,0 +1,181 @@
+package com.seecoder.ai.alarm.controller;
+
+import com.seecoder.ai.alarm.common.ApiResult;
+import com.seecoder.ai.alarm.dto.LogQueryDTO;
+import com.seecoder.ai.alarm.service.LogService;
+import com.seecoder.ai.alarm.vo.LogVO;
+import org.springframework.data.domain.Page;
+import org.springframework.web.bind.annotation.*;
+
+import jakarta.validation.Valid;
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.List;
+
+@RestController
+@RequestMapping("/logs")
+public class LogController {
+
+    private final LogService logService;
+
+    public LogController(LogService logService) {
+        this.logService = logService;
+    }
+
+    /**
+     * 获取20条模拟日志数据
+     */
+
+    // 保留原有的其他方法...
+    @PostMapping("/query")
+    public ApiResult<Page<LogVO>> queryLogs(@Valid @RequestBody LogQueryDTO queryDTO) {
+        try {
+            Page<LogVO> result = logService.queryLogs(queryDTO);
+            return ApiResult.success(result);
+        } catch (Exception e) {
+            return ApiResult.error("查询日志失败: " + e.getMessage());
+        }
+    }
+
+    @GetMapping("/trace/{traceId}")
+    public ApiResult<Page<LogVO>> queryByTraceId(@RequestParam String traceId) {
+        try {
+            Page<LogVO> result = logService.queryLogsByTraceId(traceId);
+            return ApiResult.success(result);
+        } catch (Exception e) {
+            return ApiResult.error("查询trace日志失败: " + e.getMessage());
+        }
+    }
+
+    @PostMapping("/parse")
+    public ApiResult<LogVO> parseLog(@RequestBody String rawLog) {
+        try {
+            LogVO result = logService.parseRawLog(rawLog);
+            if (result != null) {
+                return ApiResult.success(result);
+            } else {
+                return ApiResult.error("日志解析失败");
+            }
+        } catch (Exception e) {
+            return ApiResult.error("解析日志异常: " + e.getMessage());
+        }
+    }
+
+    @GetMapping("/mock-process")
+    public ApiResult<List<LogVO>> getProcessMockLogs() {
+        List<LogVO> mockLogs = new ArrayList<>();
+        String traceId = "" + System.currentTimeMillis(); // 统一的traceId
+
+        // 模拟完整的考试系统调用流程
+
+        // 1. 门户登录流程
+        mockLogs.add(createProcessLogVO("2025-12-01 14:30:00.000", "portal-login", "http-nio-8080-exec-1", "INFO",
+                "com.seecoder.exam.portal.controller.LoginController", "用户登录请求",
+                traceId, "0", List.of("userId=10001", "username=student001", "ip=192.168.1.100")));
+
+        mockLogs.add(createProcessLogVO("2025-12-01 14:30:00.100", "portal-login", "auth-service-thread-1", "INFO",
+                "com.seecoder.exam.portal.service.AuthService", "验证用户凭证",
+                traceId, "0.0", List.of("username=student001", "password=encrypted")));
+
+        mockLogs.add(createProcessLogVO("2025-12-01 14:30:00.200", "portal-login", "db-pool-1", "INFO",
+                "com.seecoder.exam.portal.repository.UserRepository", "查询用户信息",
+                traceId, "0.0.0", List.of("sql=SELECT * FROM users WHERE username=?", "params=[student001]")));
+
+        mockLogs.add(createProcessLogVO("2025-12-01 14:30:00.250", "portal-login", "redis-client-1", "INFO",
+                "com.seecoder.exam.portal.cache.SessionCache", "生成会话信息",
+                traceId, "0.0.1", List.of("sessionId=SID123456", "expire=7200")));
+
+        mockLogs.add(createProcessLogVO("2025-12-01 14:30:00.300", "portal-login", "http-nio-8080-exec-1", "INFO",
+                "com.seecoder.exam.portal.controller.LoginController", "登录成功返回token",
+                traceId, "0", List.of("token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9", "expires_in=7200")));
+
+        // 2. 考试开始流程
+        mockLogs.add(createProcessLogVO("2025-12-01 14:35:00.000", "coder-exam", "http-nio-8080-exec-2", "INFO",
+                "com.seecoder.exam.coder.controller.ExamController", "开始考试请求",
+                traceId, "0.1", List.of("examId=EXAM20251201", "userId=10001")));
+
+        mockLogs.add(createProcessLogVO("2025-12-01 14:35:00.050", "coder-exam", "exam-service-thread-1", "INFO",
+                "com.seecoder.exam.coder.service.ExamService", "验证考试权限",
+                traceId, "0.1.0", List.of("userId=10001", "examId=EXAM20251201", "status=authorized")));
+
+        mockLogs.add(createProcessLogVO("2025-12-01 14:35:00.100", "coder-exam", "question-service-client", "INFO",
+                "com.seecoder.exam.coder.client.QuestionClient", "获取考试题目列表",
+                traceId, "0.1.0.0", List.of("examId=EXAM20251201", "count=10")));
+
+        mockLogs.add(createProcessLogVO("2025-12-01 14:35:00.200", "coder-exam", "db-pool-2", "INFO",
+                "com.seecoder.exam.coder.repository.QuestionRepository", "查询题目详情",
+                traceId, "0.1.0.0.0", List.of("questionIds=[Q001,Q002,Q003]", "fields=title,content,options")));
+
+        // 3. 代码编写和提交流程
+        mockLogs.add(createProcessLogVO("2025-12-01 14:45:00.000", "coder-exam", "http-nio-8080-exec-1", "INFO",
+                "com.seecoder.exam.coder.controller.CodeController", "保存代码草稿",
+                traceId, "0.2", List.of("examId=EXAM20251201", "questionId=Q001", "language=java")));
+
+        mockLogs.add(createProcessLogVO("2025-12-01 14:45:00.050", "coder-exam", "redis-client-2", "INFO",
+                "com.seecoder.exam.coder.cache.CodeCache", "缓存代码内容",
+                traceId, "0.2.0", List.of("key=code:10001:EXAM20251201:Q001", "expire=3600")));
+
+        mockLogs.add(createProcessLogVO("2025-12-01 14:50:00.000", "coder-exam", "http-nio-8080-exec-2", "INFO",
+                "com.seecoder.exam.coder.controller.CodeController", "提交最终代码",
+                traceId, "0.3", List.of("examId=EXAM20251201", "questionId=Q001", "submit=true")));
+
+        mockLogs.add(createProcessLogVO("2025-12-01 14:50:00.050", "coder-exam", "compile-service-client", "INFO",
+                "com.seecoder.exam.coder.client.CompileClient", "代码编译请求",
+                traceId, "0.3.0", List.of("language=java", "source_length=1234", "timeout=30s")));
+
+        mockLogs.add(createProcessLogVO("2025-12-01 14:50:01.000", "coder-exam", "docker-container-1", "INFO",
+                "com.seecoder.exam.compile.Compiler", "执行编译过程",
+                traceId, "0.3.0.0", List.of("container_id=compile-java-123", "command=javac Main.java")));
+
+        // 4. 判题服务调用流程
+        mockLogs.add(createProcessLogVO("2025-12-01 14:50:02.000", "jenkins-judge", "judge-worker-1", "INFO",
+                "com.seecoder.exam.judge.JudgeService", "开始代码评判",
+                traceId, "0.3.0.1", List.of("submission_id=SUB20251201001", "question_id=Q001")));
+
+        mockLogs.add(createProcessLogVO("2025-12-01 14:50:02.100", "jenkins-judge", "test-runner-1", "INFO",
+                "com.seecoder.exam.judge.TestCaseRunner", "执行测试用例",
+                traceId, "0.3.0.1.0", List.of("test_cases=5", "timeout=10s")));
+
+        mockLogs.add(createProcessLogVO("2025-12-01 14:50:03.500", "jenkins-judge", "result-service-1", "INFO",
+                "com.seecoder.exam.judge.ResultService", "汇总评判结果",
+                traceId, "0.3.0.1.1", List.of("passed=4", "failed=1", "score=80")));
+
+        mockLogs.add(createProcessLogVO("2025-12-01 14:50:03.600", "jenkins-judge", "db-pool-3", "INFO",
+                "com.seecoder.exam.judge.repository.ResultRepository", "保存评判结果",
+                traceId, "0.3.0.1.2", List.of("sql=INSERT INTO results VALUES (?, ?, ?)", "params=[SUB20251201001, 80, PASSED]")));
+
+        // 5. GitLab代码管理流程
+        mockLogs.add(createProcessLogVO("2025-12-01 14:55:00.000", "gitlab-manager", "gitlab-client-1", "INFO",
+                "com.seecoder.exam.gitlab.GitLabService", "创建代码仓库",
+                traceId, "0.4", List.of("project_name=exam_EXAM20251201_10001", "visibility=private")));
+
+        mockLogs.add(createProcessLogVO("2025-12-01 14:55:00.500", "gitlab-manager", "http-client-1", "INFO",
+                "com.seecoder.exam.gitlab.GitLabClient", "调用GitLab API",
+                traceId, "0.4.0", List.of("api=/api/v4/projects", "method=POST", "response_code=201")));
+
+        mockLogs.add(createProcessLogVO("2025-12-01 14:55:01.000", "gitlab-manager", "webhook-service-1", "INFO",
+                "com.seecoder.exam.gitlab.WebHookService", "配置仓库Webhook",
+                traceId, "0.4.1", List.of("url=https://exam-system/callback", "events=[push,merge_request]")));
+
+        return ApiResult.success(mockLogs);
+    }
+
+    /**
+     * 创建流程调用日志的辅助方法
+     */
+    private LogVO createProcessLogVO(String timestampStr, String appName, String threadName, String level,
+                                     String className, String operation, String traceId, String spanId,
+                                     List<String> otherInfo) {
+        LogVO logVO = new LogVO();
+        logVO.setTimestamp(LocalDateTime.parse(timestampStr.replace(" ", "T").substring(0, 23)));
+        logVO.setAppName(appName);
+        logVO.setThreadName(threadName);
+        logVO.setLevel(level);
+        logVO.setClassName(className);
+        logVO.setTraceId(traceId);
+        logVO.setSpanId(spanId);
+        logVO.setOtherInfo(otherInfo);
+
+        return logVO;
+    }
+}

+ 27 - 0
src/main/java/com/seecoder/ai/alarm/dto/AlarmRecordQueryDTO.java

@@ -0,0 +1,27 @@
+package com.seecoder.ai.alarm.dto;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.time.LocalDateTime;
+
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class AlarmRecordQueryDTO extends PageQueryDTO {
+
+    private Long ruleId;
+
+    private Integer status;
+
+    private Integer aiValidAnalysis;
+
+    private Integer humanVerifyAi;
+
+    private String currentHandler;
+
+    private LocalDateTime startTime;
+
+    private LocalDateTime endTime;
+
+    private String keyword;
+}

+ 19 - 0
src/main/java/com/seecoder/ai/alarm/dto/AlarmRecordUpdateDTO.java

@@ -0,0 +1,19 @@
+package com.seecoder.ai.alarm.dto;
+
+import lombok.Data;
+
+@Data
+public class AlarmRecordUpdateDTO {
+
+    private Long id;
+
+    private Integer status;
+
+    private String handleRecords;
+
+    private Integer humanVerifyAi;
+
+    private String remark;
+
+    private String finalHandleMethod;
+}

+ 53 - 0
src/main/java/com/seecoder/ai/alarm/dto/LogQueryDTO.java

@@ -0,0 +1,53 @@
+package com.seecoder.ai.alarm.dto;
+
+import lombok.Data;
+import java.time.LocalDateTime;
+
+@Data
+public class LogQueryDTO {
+
+    /**
+     * 关键词匹配脚本
+     */
+    private String keyword;
+
+    /**
+     * 应用名称
+     */
+    private String appName;
+
+    /**
+     * 日志级别
+     */
+    private String level;
+
+    /**
+     * 类名
+     */
+    private String className;
+
+    /**
+     * 开始时间
+     */
+    private LocalDateTime startTime;
+
+    /**
+     * 结束时间
+     */
+    private LocalDateTime endTime;
+
+    /**
+     * 分页页码
+     */
+    private Integer page = 0;
+
+    /**
+     * 分页大小
+     */
+    private Integer size = 10;
+
+    /**
+     * traceId
+     */
+    private String traceId;
+}

+ 135 - 0
src/main/java/com/seecoder/ai/alarm/po/AlarmRecordPO.java

@@ -0,0 +1,135 @@
+package com.seecoder.ai.alarm.po;
+
+import jakarta.persistence.*;
+import lombok.Data;
+import java.time.LocalDateTime;
+
+@Data
+@Entity
+@Table(name = "alarm_record")
+public class AlarmRecordPO {
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private Long id;
+
+    /**
+     * 关联的报警规则 ID
+     */
+    @Column(name = "rule_id", nullable = false)
+    private Long ruleId;
+
+    /**
+     * 报警时间
+     */
+    @Column(name = "alarm_time", nullable = false)
+    private LocalDateTime alarmTime;
+
+    /**
+     * 相关日志Trace(JSON格式存储相关日志摘要)
+     */
+    @Column(name = "related_log_traces", length = 500)
+    private String relatedLogTraces;
+
+    /**
+     * 相关指标信息
+     */
+    @Column(name = "related_metrics", length = 500)
+    private String relatedMetrics;
+
+    /**
+     * 通知人(JSON格式存储多个通知人)
+     */
+    @Column(name = "notify_receivers", length = 500)
+    private String notifyReceivers;
+
+    /**
+     * 当前处理人
+     */
+    @Column(name = "current_handler", length = 100)
+    private String currentHandler;
+
+    /**
+     * 报警状态
+     * 0-待处理, 1-处理中, 2-已解决, 3-误报
+     */
+    @Column(name = "status", nullable = false)
+    private Integer status;
+
+    /**
+     * 处理记录(JSON格式存储处理历史)
+     */
+    @Column(name = "handle_records", columnDefinition = "TEXT")
+    private String handleRecords;
+
+    /**
+     * AI分析是否为有效报警
+     * 0-无效, 1-有效, 2-不确定
+     */
+    @Column(name = "ai_valid_analysis")
+    private Integer aiValidAnalysis;
+
+    /**
+     * AI有效性分析的文本输出
+     */
+    @Column(name = "ai_valid_analysis_output", columnDefinition = "TEXT")
+    private String aiValidAnalysisOutput;
+
+    /**
+     * 人工判断AI分析是否有效
+     * 0-否, 1-是, 2-部分正确
+     */
+    @Column(name = "human_verify_ai")
+    private Integer humanVerifyAi;
+
+    /**
+     * 人工分析备注
+     */
+    @Column(name = "human_verify_remark", length = 500)
+    private String humanVerifyRemark;
+
+    /**
+     * AI报警处理建议
+     */
+    @Column(name = "ai_handle_suggestion", columnDefinition = "TEXT")
+    private String aiHandleSuggestion;
+
+    /**
+     * 最终处理方式
+     */
+    @Column(name = "final_handle_method", length = 200)
+    private String finalHandleMethod;
+
+    /**
+     * 处理完成时间
+     */
+    @Column(name = "handle_complete_time")
+    private LocalDateTime handleCompleteTime;
+
+    /**
+     * 创建时间
+     */
+    @Column(name = "create_time")
+    private LocalDateTime createTime;
+
+    /**
+     * 更新时间
+     */
+    @Column(name = "update_time")
+    private LocalDateTime updateTime;
+
+    @PrePersist
+    public void prePersist() {
+        this.createTime = LocalDateTime.now();
+        this.updateTime = LocalDateTime.now();
+        // 默认状态为待处理
+        if (this.status == null) {
+            this.status = 0;
+        }
+    }
+
+    @PreUpdate
+    public void preUpdate() {
+        this.updateTime = LocalDateTime.now();
+    }
+}

+ 44 - 0
src/main/java/com/seecoder/ai/alarm/repository/AlarmRecordRepository.java

@@ -0,0 +1,44 @@
+package com.seecoder.ai.alarm.repository;
+
+import com.seecoder.ai.alarm.po.AlarmRecordPO;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+import org.springframework.stereotype.Repository;
+
+import java.time.LocalDateTime;
+
+@Repository
+public interface AlarmRecordRepository extends JpaRepository<AlarmRecordPO, Long>, JpaSpecificationExecutor<AlarmRecordPO> {
+
+    /**
+     * 根据ID查询报警记录,同时关联查询报警规则名称
+     */
+    @Query("SELECT ar, al.ruleName FROM AlarmRecordPO ar LEFT JOIN AlarmRulePO al ON ar.ruleId = al.id WHERE ar.id = :id")
+    Object[] findWithRuleNameById(@Param("id") Long id);
+
+    /**
+     * 分页查询报警记录,同时关联查询报警规则名称
+     */
+    @Query("SELECT ar, al.ruleName FROM AlarmRecordPO ar LEFT JOIN AlarmRulePO al ON ar.ruleId = al.id " +
+            "WHERE (:ruleId IS NULL OR ar.ruleId = :ruleId) " +
+            "AND (:status IS NULL OR ar.status = :status) " +
+            "AND (:aiValidAnalysis IS NULL OR ar.aiValidAnalysis = :aiValidAnalysis) " +
+            "AND (:humanVerifyAi IS NULL OR ar.humanVerifyAi = :humanVerifyAi) " +
+            "AND (:currentHandler IS NULL OR ar.currentHandler LIKE %:currentHandler%) " +
+            "AND (:startTime IS NULL OR ar.alarmTime >= :startTime) " +
+            "AND (:endTime IS NULL OR ar.alarmTime <= :endTime) " +
+            "ORDER BY ar.createTime DESC")
+    Page<Object[]> findWithRuleNameByConditions(
+            @Param("ruleId") Long ruleId,
+            @Param("status") Integer status,
+            @Param("aiValidAnalysis") Integer aiValidAnalysis,
+            @Param("humanVerifyAi") Integer humanVerifyAi,
+            @Param("currentHandler") String currentHandler,
+            @Param("startTime") LocalDateTime startTime,
+            @Param("endTime") LocalDateTime endTime,
+            Pageable pageable);
+}

+ 51 - 0
src/main/java/com/seecoder/ai/alarm/repository/LogRepository.java

@@ -0,0 +1,51 @@
+package com.seecoder.ai.alarm.repository;
+
+import com.seecoder.ai.alarm.vo.LogVO;
+import org.springframework.data.elasticsearch.annotations.Query;
+import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
+import org.springframework.stereotype.Repository;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Repository
+public interface LogRepository extends ElasticsearchRepository<LogVO, String> {
+
+    /**
+     * 根据关键词和时间范围查询日志
+     */
+    @Query("{\"bool\": {\"must\": [{\"wildcard\": {\"rawLog\": \"*?0*\"}}, {\"range\": {\"timestamp\": {\"gte\": \"?1\", \"lte\": \"?2\"}}}]}}")
+    List<LogVO> findByKeywordAndTimeRange(String keyword, LocalDateTime startTime, LocalDateTime endTime);
+
+    /**
+     * 根据应用名称和关键词查询
+     */
+    @Query("{\"bool\": {\"must\": [{\"match\": {\"appName\": \"?0\"}}, {\"wildcard\": {\"rawLog\": \"*?1*\"}}]}}")
+    List<LogVO> findByAppNameAndKeyword(String appName, String keyword);
+
+    /**
+     * 根据traceId查询
+     */
+    List<LogVO> findByTraceId(String traceId);
+
+    /**
+     * 综合查询方法
+     */
+    @Query("""
+        {
+          "bool": {
+            "must": [
+              #{#keyword != null ? '{"wildcard": {"rawLog": "*' + #keyword + '*"}}' : '{"match_all": {}}'},
+              #{#appName != null ? '{"match": {"appName": "' + #appName + '"}}' : '{"match_all": {}}'},
+              #{#level != null ? '{"match": {"level": "' + #level + '"}}' : '{"match_all": {}}'},
+              #{#className != null ? '{"match": {"className": "' + #className + '"}}' : '{"match_all": {}}'},
+              #{#startTime != null && #endTime != null ? 
+                '{"range": {"timestamp": {"gte": "' + #startTime + '", "lte": "' + #endTime + '"}}}' : 
+                '{"match_all": {}}'}
+            ]
+          }
+        }
+        """)
+    List<LogVO> findByConditions(String keyword, String appName, String level,
+                                 String className, LocalDateTime startTime, LocalDateTime endTime);
+}

+ 44 - 0
src/main/java/com/seecoder/ai/alarm/service/AlarmRecordService.java

@@ -0,0 +1,44 @@
+package com.seecoder.ai.alarm.service;
+
+import com.seecoder.ai.alarm.dto.AlarmRecordQueryDTO;
+import com.seecoder.ai.alarm.dto.AlarmRecordUpdateDTO;
+import com.seecoder.ai.alarm.vo.AlarmRecordVO;
+import org.springframework.data.domain.Page;
+
+public interface AlarmRecordService {
+
+    /**
+     * 根据ID查询报警记录
+     */
+    AlarmRecordVO getAlarmRecordById(Long id);
+
+    /**
+     * 分页查询报警记录
+     */
+    Page<AlarmRecordVO> pageQueryAlarmRecords(AlarmRecordQueryDTO queryDTO);
+
+    /**
+     * 更新报警记录状态和处理信息
+     */
+    AlarmRecordVO updateAlarmRecord(AlarmRecordUpdateDTO updateDTO);
+
+    /**
+     * 仅更新报警状态
+     */
+    void updateStatus(Long id, Integer status);
+
+    /**
+     * 添加处理记录
+     */
+    void addHandleRecord(Long id, String handleRecord);
+
+    /**
+     * 人工判断AI分析有效性
+     */
+    void verifyAiAnalysis(Long id, Integer humanVerifyAi, String remark);
+
+    /**
+     * 设置最终处理方式
+     */
+    void setFinalHandleMethod(Long id, String finalHandleMethod);
+}

+ 23 - 0
src/main/java/com/seecoder/ai/alarm/service/LogService.java

@@ -0,0 +1,23 @@
+package com.seecoder.ai.alarm.service;
+
+import com.seecoder.ai.alarm.dto.LogQueryDTO;
+import com.seecoder.ai.alarm.vo.LogVO;
+import org.springframework.data.domain.Page;
+
+public interface LogService {
+
+    /**
+     * 根据条件查询日志
+     */
+    Page<LogVO> queryLogs(LogQueryDTO queryDTO);
+
+    /**
+     * 根据 traceId查询相关日志
+     */
+    Page<LogVO> queryLogsByTraceId(String traceId);
+
+    /**
+     * 解析原始日志字符串为 LogVO对象
+     */
+    LogVO parseRawLog(String rawLog);
+}

+ 244 - 0
src/main/java/com/seecoder/ai/alarm/service/impl/AlarmRecordServiceImpl.java

@@ -0,0 +1,244 @@
+package com.seecoder.ai.alarm.service.impl;
+
+import com.seecoder.ai.alarm.dto.AlarmRecordQueryDTO;
+import com.seecoder.ai.alarm.dto.AlarmRecordUpdateDTO;
+import com.seecoder.ai.alarm.po.AlarmRecordPO;
+import com.seecoder.ai.alarm.repository.AlarmRecordRepository;
+import com.seecoder.ai.alarm.service.AlarmRecordService;
+import com.seecoder.ai.alarm.service.AlarmRuleService;
+import com.seecoder.ai.alarm.vo.AlarmRecordVO;
+import com.seecoder.ai.alarm.vo.AlarmRuleVO;
+import org.springframework.beans.BeanUtils;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageImpl;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
+import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+@Service
+public class AlarmRecordServiceImpl implements AlarmRecordService {
+
+    private final AlarmRecordRepository alarmRecordRepository;
+    private final AlarmRuleService alarmRuleService;
+
+    public AlarmRecordServiceImpl(AlarmRecordRepository alarmRecordRepository,
+                                  AlarmRuleService alarmRuleService) {
+        this.alarmRecordRepository = alarmRecordRepository;
+        this.alarmRuleService = alarmRuleService;
+        System.out.println("AlarmRecordServiceImpl initialized");
+    }
+
+    @Override
+    public AlarmRecordVO getAlarmRecordById(Long id) {
+        Object[] result = alarmRecordRepository.findWithRuleNameById(id);
+        if (result != null && result.length > 0) {
+            AlarmRecordPO po = (AlarmRecordPO) result[0];
+            String ruleName = (String) result[1];
+            return convertToVOWithRuleName(po, ruleName);
+        }
+        return null;
+    }
+
+    @Override
+    public Page<AlarmRecordVO> pageQueryAlarmRecords(AlarmRecordQueryDTO queryDTO) {
+        Pageable pageable = PageRequest.of(queryDTO.getPage(), queryDTO.getSize(),
+                Sort.by(Sort.Direction.DESC, "createTime"));
+
+        Page<Object[]> pageResult = alarmRecordRepository.findWithRuleNameByConditions(
+                queryDTO.getRuleId(),
+                queryDTO.getStatus(),
+                queryDTO.getAiValidAnalysis(),
+                queryDTO.getHumanVerifyAi(),
+                queryDTO.getCurrentHandler(),
+                queryDTO.getStartTime(),
+                queryDTO.getEndTime(),
+                pageable
+        );
+
+        List<AlarmRecordVO> voList = pageResult.getContent().stream()
+                .map(row -> {
+                    AlarmRecordPO po = (AlarmRecordPO) row[0];
+                    String ruleName = (String) row[1];
+                    return convertToVOWithRuleName(po, ruleName);
+                })
+                .collect(Collectors.toList());
+
+        return new PageImpl<>(voList, pageable, pageResult.getTotalElements());
+    }
+
+    @Override
+    public AlarmRecordVO updateAlarmRecord(AlarmRecordUpdateDTO updateDTO) {
+        Optional<AlarmRecordPO> optional = alarmRecordRepository.findById(updateDTO.getId());
+        if (!optional.isPresent()) {
+            throw new RuntimeException("报警记录不存在");
+        }
+
+        AlarmRecordPO po = optional.get();
+
+        if (updateDTO.getStatus() != null) {
+            po.setStatus(updateDTO.getStatus());
+        }
+
+        if (StringUtils.hasText(updateDTO.getHandleRecords())) {
+            po.setHandleRecords(updateDTO.getHandleRecords());
+        }
+
+        if (updateDTO.getHumanVerifyAi() != null) {
+            po.setHumanVerifyAi(updateDTO.getHumanVerifyAi());
+        }
+
+        if (StringUtils.hasText(updateDTO.getRemark())) {
+            po.setHumanVerifyRemark(updateDTO.getRemark());
+        }
+
+        if (StringUtils.hasText(updateDTO.getFinalHandleMethod())) {
+            po.setFinalHandleMethod(updateDTO.getFinalHandleMethod());
+        }
+
+        AlarmRecordPO saved = alarmRecordRepository.save(po);
+
+        // 保存后重新查询以获取规则名称
+        Object[] result = alarmRecordRepository.findWithRuleNameById(saved.getId());
+        if (result != null && result.length > 0) {
+            AlarmRecordPO updatedPo = (AlarmRecordPO) result[0];
+            String ruleName = (String) result[1];
+            return convertToVOWithRuleName(updatedPo, ruleName);
+        }
+
+        return convertToVO(saved);
+    }
+
+    @Override
+    public void updateStatus(Long id, Integer status) {
+        Optional<AlarmRecordPO> optional = alarmRecordRepository.findById(id);
+        if (optional.isPresent()) {
+            AlarmRecordPO po = optional.get();
+            po.setStatus(status);
+            if (status == 2) { // 已解决状态
+                po.setHandleCompleteTime(LocalDateTime.now());
+            }
+            alarmRecordRepository.save(po);
+        }
+    }
+
+    @Override
+    public void addHandleRecord(Long id, String handleRecord) {
+        Optional<AlarmRecordPO> optional = alarmRecordRepository.findById(id);
+        if (optional.isPresent()) {
+            AlarmRecordPO po = optional.get();
+            String currentRecords = po.getHandleRecords();
+            if (StringUtils.hasText(currentRecords)) {
+                po.setHandleRecords(currentRecords + "\n" + LocalDateTime.now() + ": " + handleRecord);
+            } else {
+                po.setHandleRecords(LocalDateTime.now() + ": " + handleRecord);
+            }
+            alarmRecordRepository.save(po);
+        }
+    }
+
+    @Override
+    public void verifyAiAnalysis(Long id, Integer humanVerifyAi, String remark) {
+        Optional<AlarmRecordPO> optional = alarmRecordRepository.findById(id);
+        if (optional.isPresent()) {
+            AlarmRecordPO po = optional.get();
+            po.setHumanVerifyAi(humanVerifyAi);
+            if (StringUtils.hasText(remark)) {
+                po.setHumanVerifyRemark(remark);
+            }
+            alarmRecordRepository.save(po);
+        }
+    }
+
+    @Override
+    public void setFinalHandleMethod(Long id, String finalHandleMethod) {
+        Optional<AlarmRecordPO> optional = alarmRecordRepository.findById(id);
+        if (optional.isPresent()) {
+            AlarmRecordPO po = optional.get();
+            po.setFinalHandleMethod(finalHandleMethod);
+            alarmRecordRepository.save(po);
+        }
+    }
+
+    private AlarmRecordVO convertToVO(AlarmRecordPO po) {
+        AlarmRecordVO vo = new AlarmRecordVO();
+        BeanUtils.copyProperties(po, vo);
+
+        // 设置枚举描述
+        if (po.getStatus() != null) {
+            vo.setStatusDesc(getStatusDesc(po.getStatus()));
+        }
+        if (po.getAiValidAnalysis() != null) {
+            vo.setAiValidAnalysisDesc(getAiValidAnalysisDesc(po.getAiValidAnalysis()));
+        }
+        if (po.getHumanVerifyAi() != null) {
+            vo.setHumanVerifyAiDesc(getHumanVerifyAiDesc(po.getHumanVerifyAi()));
+        }
+
+        // 获取报警规则名称
+        if (po.getRuleId() != null) {
+            AlarmRuleVO ruleVO = alarmRuleService.getAlarmRuleById(po.getRuleId());
+            if (ruleVO != null) {
+                vo.setRuleName(ruleVO.getRuleName());
+            }
+        }
+
+        return vo;
+    }
+
+    // 新增:带规则名称的转换方法
+    private AlarmRecordVO convertToVOWithRuleName(AlarmRecordPO po, String ruleName) {
+        AlarmRecordVO vo = new AlarmRecordVO();
+        BeanUtils.copyProperties(po, vo);
+
+        // 设置枚举描述
+        if (po.getStatus() != null) {
+            vo.setStatusDesc(getStatusDesc(po.getStatus()));
+        }
+        if (po.getAiValidAnalysis() != null) {
+            vo.setAiValidAnalysisDesc(getAiValidAnalysisDesc(po.getAiValidAnalysis()));
+        }
+        if (po.getHumanVerifyAi() != null) {
+            vo.setHumanVerifyAiDesc(getHumanVerifyAiDesc(po.getHumanVerifyAi()));
+        }
+
+        // 直接设置规则名称
+        vo.setRuleName(ruleName);
+
+        return vo;
+    }
+
+    private String getStatusDesc(Integer statusCode) {
+        return switch (statusCode) {
+            case 0 -> "待处理";
+            case 1 -> "处理中";
+            case 2 -> "已解决";
+            case 3 -> "误报";
+            default -> "未知状态";
+        };
+    }
+
+    private String getAiValidAnalysisDesc(Integer code) {
+        return switch (code) {
+            case 0 -> "无效";
+            case 1 -> "有效";
+            case 2 -> "不确定";
+            default -> "未知";
+        };
+    }
+
+    private String getHumanVerifyAiDesc(Integer code) {
+        return switch (code) {
+            case 0 -> "否";
+            case 1 -> "是";
+            case 2 -> "部分正确";
+            default -> "未知";
+        };
+    }
+}

+ 1 - 1
src/main/java/com/seecoder/ai/alarm/service/impl/AlarmRuleServiceImpl.java

@@ -67,7 +67,7 @@ public class AlarmRuleServiceImpl implements AlarmRuleService {
 
     @Override
     public Page<AlarmRuleVO> pageQueryAlarmRules(PageQueryDTO queryDTO) {
-        Pageable pageable = PageRequest.of(queryDTO.getPage(), queryDTO.getSize(),
+        Pageable pageable = PageRequest.of(queryDTO.getPage()-1, queryDTO.getSize(),
                 Sort.by(Sort.Direction.DESC, "createTime"));
 
         Specification<AlarmRulePO> specification = (root, query, criteriaBuilder) -> {

+ 135 - 0
src/main/java/com/seecoder/ai/alarm/service/impl/LogServiceImpl.java

@@ -0,0 +1,135 @@
+package com.seecoder.ai.alarm.service.impl;
+
+import com.seecoder.ai.alarm.dto.LogQueryDTO;
+import com.seecoder.ai.alarm.repository.LogRepository;
+import com.seecoder.ai.alarm.service.LogService;
+import com.seecoder.ai.alarm.vo.LogVO;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageImpl;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+@Service
+public class LogServiceImpl implements LogService {
+
+    private static final Logger log = LoggerFactory.getLogger(LogServiceImpl.class);
+
+    private final LogRepository logRepository;
+
+    // 日志格式正则表达式
+    private static final Pattern LOG_PATTERN = Pattern.compile(
+            "(\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}\\.\\d{3})\\s+" +  // 时间
+                    "\\[([^\\]]+)\\]\\s+" +  // 应用名
+                    "\\[([^\\]]+)\\]\\s+" +  // 线程名
+                    "(INFO|WARN|ERROR|DEBUG)\\s+" +  // 日志级别
+                    "([^\\s]+)\\s+-\\s+" +  // 类名
+                    "([^|]+)\\|" +  // traceId
+                    "([^|]*)\\|" +  // spanId
+                    "(.*)$"  // 其他信息
+    );
+
+    public LogServiceImpl(LogRepository logRepository) {
+        this.logRepository = logRepository;
+    }
+
+    @Override
+    public Page<LogVO> queryLogs(LogQueryDTO queryDTO) {
+        try {
+            int page = Math.max(queryDTO.getPage(), 0);
+            int size = queryDTO.getSize() > 0 ? queryDTO.getSize() : 10;
+            Pageable pageable = PageRequest.of(page, size);
+
+            List<LogVO> logs;
+
+            // 如果提供了traceId,优先按traceId查询
+            if (StringUtils.hasText(queryDTO.getTraceId())) {
+                logs = logRepository.findByTraceId(queryDTO.getTraceId());
+            } else {
+                // 使用综合查询
+                logs = logRepository.findByConditions(
+                        queryDTO.getKeyword(),
+                        queryDTO.getAppName(),
+                        queryDTO.getLevel(),
+                        queryDTO.getClassName(),
+                        queryDTO.getStartTime(),
+                        queryDTO.getEndTime()
+                );
+            }
+
+            // 手动分页
+            int start = (int) pageable.getOffset();
+            int end = Math.min(start + pageable.getPageSize(), logs.size());
+
+            List<LogVO> pagedLogs = start <= logs.size() ?
+                    logs.subList(start, end) : new ArrayList<>();
+
+            return new PageImpl<>(pagedLogs, pageable, logs.size());
+
+        } catch (Exception e) {
+            log.error("查询日志失败", e);
+            return Page.empty();
+        }
+    }
+
+    @Override
+    public Page<LogVO> queryLogsByTraceId(String traceId) {
+        LogQueryDTO queryDTO = new LogQueryDTO();
+        queryDTO.setTraceId(traceId);
+        return queryLogs(queryDTO);
+    }
+
+    @Override
+    public LogVO parseRawLog(String rawLog) {
+        if (!StringUtils.hasText(rawLog)) {
+            return null;
+        }
+
+        Matcher matcher = LOG_PATTERN.matcher(rawLog.trim());
+        if (!matcher.matches()) {
+            log.warn("日志格式不匹配: {}", rawLog);
+            return null;
+        }
+
+        LogVO logVO = new LogVO();
+        try {
+            // 解析时间
+            String timeStr = matcher.group(1);
+            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
+            logVO.setTimestamp(LocalDateTime.parse(timeStr, formatter));
+
+            // 设置其他字段
+            logVO.setAppName(matcher.group(2));
+            logVO.setThreadName(matcher.group(3));
+            logVO.setLevel(matcher.group(4));
+            logVO.setClassName(matcher.group(5));
+            logVO.setTraceId(matcher.group(6));
+            logVO.setSpanId(matcher.group(7));
+
+            // 解析其他信息
+            String otherInfoStr = matcher.group(8);
+            if (StringUtils.hasText(otherInfoStr)) {
+                String[] parts = otherInfoStr.split("\\|");
+                logVO.setOtherInfo(Arrays.asList(parts));
+            }
+
+
+        } catch (Exception e) {
+            log.error("解析日志失败: {}", rawLog, e);
+            return null;
+        }
+
+        return logVO;
+    }
+}

+ 55 - 0
src/main/java/com/seecoder/ai/alarm/vo/AlarmRecordVO.java

@@ -0,0 +1,55 @@
+package com.seecoder.ai.alarm.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+import java.time.LocalDateTime;
+
+@Data
+public class AlarmRecordVO {
+
+    private Long id;
+
+    private Long ruleId;
+
+    private String ruleName; // 新增:报警规则名称
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime alarmTime;
+
+    private String relatedLogTraces;
+
+    private String relatedMetrics;
+
+    private String notifyReceivers;
+
+    private String currentHandler;
+
+    private Integer status;
+    private String statusDesc;
+
+    private String handleRecords;
+
+    private Integer aiValidAnalysis;
+    private String aiValidAnalysisDesc;
+
+    private String aiValidAnalysisOutput;
+
+
+    private Integer humanVerifyAi;
+    private String humanVerifyAiDesc;
+
+    private String remark;
+
+    private String aiHandleSuggestion;
+
+    private String finalHandleMethod;
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime handleCompleteTime;
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime createTime;
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime updateTime;
+}

+ 59 - 0
src/main/java/com/seecoder/ai/alarm/vo/LogVO.java

@@ -0,0 +1,59 @@
+package com.seecoder.ai.alarm.vo;
+
+import lombok.Data;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.elasticsearch.annotations.Document;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Data
+@Document(indexName = "logs") // 指定 Elasticsearch 索引名称
+public class LogVO {
+
+    /**
+     * 文档ID
+     */
+    @Id
+    private String id;
+
+    /**
+     * 时间戳
+     */
+    private LocalDateTime timestamp;
+
+    /**
+     * 应用名称
+     */
+    private String appName;
+
+    /**
+     * 线程名称
+     */
+    private String threadName;
+
+    /**
+     * 日志级别
+     */
+    private String level;
+
+    /**
+     * 类名
+     */
+    private String className;
+
+    /**
+     * Trace ID
+     */
+    private String traceId;
+
+    /**
+     * Span ID
+     */
+    private String spanId;
+
+    /**
+     * 其他日志信息列表
+     */
+    private List<String> otherInfo;
+}

+ 4 - 0
src/main/resources/application-dev.yml

@@ -13,6 +13,10 @@ spring:
       hibernate:
         dialect: org.hibernate.dialect.MySQL8Dialect
         format_sql: true
+  elasticsearch:
+    uris: http://localhost:9200
+    username: elastic
+    password: zy18794936163
 
 logging:
   level: