feat(phase1): 完成 JPA 实体类和 Repository 层实现
- 创建 3 个 JPA 实体类:DiagnosisRecord、CaseLibrary、ApiDocument - 创建 3 个 Repository 接口,实现基础 CRUD 和自定义查询方法 - 实现 DiagnosisRecordRepository 单元测试(6 个测试全部通过) - 修复 Hibernate schema 验证问题(枚举类型使用 VARCHAR) - 更新 application.yml,添加完整的数据库和 Redis 配置 - 更新 OpenSpec tasks.md,标记已完成任务(12/33) 测试结果: - DiagnosisRecordRepositoryTest: 6/6 通过 - 编译成功,无错误 Progress: 12/33 tasks completed
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
package org.example.domain.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import com.superbiz.agent.domain.enums.FaultCategory;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* API 文档元数据实体
|
||||
* 对应表: api_document
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "api_document", indexes = {
|
||||
@Index(name = "idx_doc_id", columnList = "doc_id"),
|
||||
@Index(name = "idx_fault_source", columnList = "fault_source"),
|
||||
@Index(name = "idx_status", columnList = "status"),
|
||||
@Index(name = "idx_created_at", columnList = "created_at")
|
||||
}, uniqueConstraints = {
|
||||
@UniqueConstraint(name = "uk_file_hash", columnNames = "file_hash")
|
||||
})
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class ApiDocument {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(name = "doc_id", unique = true, nullable = false, length = 64)
|
||||
private String docId;
|
||||
|
||||
// 文档分类
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "fault_category", length = 32, columnDefinition = "VARCHAR(32)")
|
||||
private FaultCategory faultCategory = FaultCategory.EXTERNAL_API;
|
||||
|
||||
@Column(name = "fault_source", length = 128)
|
||||
private String faultSource;
|
||||
|
||||
@Column(name = "api_name", length = 128)
|
||||
private String apiName;
|
||||
|
||||
@Column(name = "version", length = 32)
|
||||
private String version = "v1.0";
|
||||
|
||||
// 文件信息
|
||||
@Column(name = "file_name", nullable = false, length = 256)
|
||||
private String fileName;
|
||||
|
||||
@Column(name = "file_path", length = 512)
|
||||
private String filePath;
|
||||
|
||||
@Column(name = "file_hash", length = 64)
|
||||
private String fileHash;
|
||||
|
||||
@Column(name = "file_size")
|
||||
private Long fileSize;
|
||||
|
||||
// 索引状态
|
||||
@Column(name = "status", length = 16)
|
||||
private String status = "PENDING";
|
||||
|
||||
@Column(name = "chunk_count")
|
||||
private Integer chunkCount = 0;
|
||||
|
||||
@Column(name = "error_message", columnDefinition = "TEXT")
|
||||
private String errorMessage;
|
||||
|
||||
// 时间字段
|
||||
@Column(name = "indexed_at")
|
||||
private LocalDateTime indexedAt;
|
||||
|
||||
@Column(name = "created_at", nullable = false, updatable = false)
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@Column(name = "updated_at")
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
@PrePersist
|
||||
protected void onCreate() {
|
||||
createdAt = LocalDateTime.now();
|
||||
updatedAt = LocalDateTime.now();
|
||||
}
|
||||
|
||||
@PreUpdate
|
||||
protected void onUpdate() {
|
||||
updatedAt = LocalDateTime.now();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package org.example.domain.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import com.superbiz.agent.domain.enums.FaultCategory;
|
||||
import com.superbiz.agent.domain.enums.SourceType;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 案例库实体
|
||||
* 对应表: case_library
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "case_library", indexes = {
|
||||
@Index(name = "idx_fault_category", columnList = "fault_category"),
|
||||
@Index(name = "idx_error_code", columnList = "error_code"),
|
||||
@Index(name = "idx_fault_source", columnList = "fault_source"),
|
||||
@Index(name = "idx_diagnosis_id", columnList = "diagnosis_id"),
|
||||
@Index(name = "idx_reference_count", columnList = "reference_count"),
|
||||
@Index(name = "idx_created_at", columnList = "created_at")
|
||||
})
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class CaseLibrary {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(name = "case_id", unique = true, nullable = false, length = 64)
|
||||
private String caseId;
|
||||
|
||||
// 来源关联
|
||||
@Column(name = "diagnosis_id", length = 64)
|
||||
private String diagnosisId;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "source_type", length = 16, columnDefinition = "VARCHAR(16)")
|
||||
private SourceType sourceType = SourceType.AUTO;
|
||||
|
||||
// 案例分类
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "fault_category", length = 32, columnDefinition = "VARCHAR(32)")
|
||||
private FaultCategory faultCategory;
|
||||
|
||||
@Column(name = "fault_source", length = 128)
|
||||
private String faultSource;
|
||||
|
||||
@Column(name = "fault_target", length = 256)
|
||||
private String faultTarget;
|
||||
|
||||
@Column(name = "error_code", length = 64)
|
||||
private String errorCode;
|
||||
|
||||
// 案例内容
|
||||
@Column(name = "title", nullable = false, length = 256)
|
||||
private String title;
|
||||
|
||||
@Column(name = "root_cause", nullable = false, columnDefinition = "TEXT")
|
||||
private String rootCause;
|
||||
|
||||
@Column(name = "solution", nullable = false, columnDefinition = "TEXT")
|
||||
private String solution;
|
||||
|
||||
// 简单统计
|
||||
@Column(name = "reference_count")
|
||||
private Integer referenceCount = 0;
|
||||
|
||||
// 元数据
|
||||
@Column(name = "created_by", length = 64)
|
||||
private String createdBy;
|
||||
|
||||
@Column(name = "created_at", nullable = false, updatable = false)
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@Column(name = "updated_at")
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
@PrePersist
|
||||
protected void onCreate() {
|
||||
createdAt = LocalDateTime.now();
|
||||
updatedAt = LocalDateTime.now();
|
||||
}
|
||||
|
||||
@PreUpdate
|
||||
protected void onUpdate() {
|
||||
updatedAt = LocalDateTime.now();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
package org.example.domain.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import com.superbiz.agent.domain.enums.DiagnosisStatus;
|
||||
import com.superbiz.agent.domain.enums.FaultCategory;
|
||||
import org.hibernate.annotations.JdbcTypeCode;
|
||||
import org.hibernate.type.SqlTypes;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 诊断记录实体
|
||||
* 对应表: diagnosis_record
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "diagnosis_record", indexes = {
|
||||
@Index(name = "idx_business_id", columnList = "business_id"),
|
||||
@Index(name = "idx_trace_id", columnList = "trace_id"),
|
||||
@Index(name = "idx_session_id", columnList = "session_id"),
|
||||
@Index(name = "idx_fault_category", columnList = "fault_category"),
|
||||
@Index(name = "idx_error_code", columnList = "error_code"),
|
||||
@Index(name = "idx_created_at", columnList = "created_at"),
|
||||
@Index(name = "idx_status", columnList = "status")
|
||||
})
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class DiagnosisRecord {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(name = "diagnosis_id", unique = true, nullable = false, length = 64)
|
||||
private String diagnosisId;
|
||||
|
||||
// 关联信息
|
||||
@Column(name = "session_id", length = 64)
|
||||
private String sessionId;
|
||||
|
||||
@Column(name = "business_id", length = 128)
|
||||
private String businessId;
|
||||
|
||||
@Column(name = "trace_id", length = 64)
|
||||
private String traceId;
|
||||
|
||||
// 故障分类
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "fault_category", length = 32, columnDefinition = "VARCHAR(32)")
|
||||
private FaultCategory faultCategory;
|
||||
|
||||
@Column(name = "fault_source", length = 128)
|
||||
private String faultSource;
|
||||
|
||||
@Column(name = "fault_target", length = 256)
|
||||
private String faultTarget;
|
||||
|
||||
// 错误信息
|
||||
@Column(name = "error_code", length = 64)
|
||||
private String errorCode;
|
||||
|
||||
@Column(name = "error_message", columnDefinition = "TEXT")
|
||||
private String errorMessage;
|
||||
|
||||
@Column(name = "stack_trace", columnDefinition = "TEXT")
|
||||
private String stackTrace;
|
||||
|
||||
// 诊断结果
|
||||
@Column(name = "problem_type", length = 32)
|
||||
private String problemType;
|
||||
|
||||
@Column(name = "root_cause", columnDefinition = "TEXT")
|
||||
private String rootCause;
|
||||
|
||||
@Column(name = "solution", columnDefinition = "TEXT")
|
||||
private String solution;
|
||||
|
||||
@Column(name = "report_markdown", columnDefinition = "TEXT")
|
||||
private String reportMarkdown;
|
||||
|
||||
// 评估指标
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "status", length = 16, columnDefinition = "VARCHAR(16)")
|
||||
private DiagnosisStatus status = DiagnosisStatus.PENDING;
|
||||
|
||||
@Column(name = "confidence")
|
||||
private Integer confidence;
|
||||
|
||||
@Column(name = "duration")
|
||||
private Integer duration;
|
||||
|
||||
// 用户反馈
|
||||
@Column(name = "feedback", length = 16)
|
||||
private String feedback;
|
||||
|
||||
// 调试字段 - JSON 类型
|
||||
@JdbcTypeCode(SqlTypes.JSON)
|
||||
@Column(name = "tool_calls", columnDefinition = "JSON")
|
||||
private List<Map<String, Object>> toolCalls;
|
||||
|
||||
// 元数据
|
||||
@Column(name = "created_by", length = 64)
|
||||
private String createdBy;
|
||||
|
||||
@Column(name = "created_at", nullable = false, updatable = false)
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@Column(name = "updated_at")
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
@PrePersist
|
||||
protected void onCreate() {
|
||||
createdAt = LocalDateTime.now();
|
||||
updatedAt = LocalDateTime.now();
|
||||
}
|
||||
|
||||
@PreUpdate
|
||||
protected void onUpdate() {
|
||||
updatedAt = LocalDateTime.now();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package org.example.repository;
|
||||
|
||||
import com.superbiz.agent.domain.enums.FaultCategory;
|
||||
import org.example.domain.entity.ApiDocument;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* API 文档 Repository
|
||||
*/
|
||||
@Repository
|
||||
public interface ApiDocumentRepository extends JpaRepository<ApiDocument, Long> {
|
||||
|
||||
/**
|
||||
* 根据文档ID查询
|
||||
*/
|
||||
Optional<ApiDocument> findByDocId(String docId);
|
||||
|
||||
/**
|
||||
* 根据文件hash查询(去重)
|
||||
*/
|
||||
Optional<ApiDocument> findByFileHash(String fileHash);
|
||||
|
||||
/**
|
||||
* 根据故障源查询
|
||||
*/
|
||||
List<ApiDocument> findByFaultSource(String faultSource);
|
||||
|
||||
/**
|
||||
* 根据故障类别和故障源查询
|
||||
*/
|
||||
List<ApiDocument> findByFaultCategoryAndFaultSource(FaultCategory category, String faultSource);
|
||||
|
||||
/**
|
||||
* 根据状态查询
|
||||
*/
|
||||
List<ApiDocument> findByStatus(String status);
|
||||
|
||||
/**
|
||||
* 根据状态查询(分页)
|
||||
*/
|
||||
Page<ApiDocument> findByStatus(String status, Pageable pageable);
|
||||
|
||||
/**
|
||||
* 查询已索引的文档
|
||||
*/
|
||||
List<ApiDocument> findByStatusAndChunkCountGreaterThan(String status, Integer chunkCount);
|
||||
|
||||
/**
|
||||
* 模糊搜索文件名
|
||||
*/
|
||||
Page<ApiDocument> findByFileNameContaining(String keyword, Pageable pageable);
|
||||
|
||||
/**
|
||||
* 根据故障源模糊搜索(分页)
|
||||
*/
|
||||
Page<ApiDocument> findByFaultSourceContaining(String keyword, Pageable pageable);
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package org.example.repository;
|
||||
|
||||
import com.superbiz.agent.domain.enums.FaultCategory;
|
||||
import com.superbiz.agent.domain.enums.SourceType;
|
||||
import org.example.domain.entity.CaseLibrary;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 案例库 Repository
|
||||
*/
|
||||
@Repository
|
||||
public interface CaseLibraryRepository extends JpaRepository<CaseLibrary, Long> {
|
||||
|
||||
/**
|
||||
* 根据案例ID查询
|
||||
*/
|
||||
Optional<CaseLibrary> findByCaseId(String caseId);
|
||||
|
||||
/**
|
||||
* 根据诊断ID查询
|
||||
*/
|
||||
Optional<CaseLibrary> findByDiagnosisId(String diagnosisId);
|
||||
|
||||
/**
|
||||
* 根据故障类别查询
|
||||
*/
|
||||
List<CaseLibrary> findByFaultCategory(FaultCategory category);
|
||||
|
||||
/**
|
||||
* 根据错误码查询
|
||||
*/
|
||||
List<CaseLibrary> findByErrorCode(String errorCode);
|
||||
|
||||
/**
|
||||
* 根据故障类别和错误码查询
|
||||
*/
|
||||
List<CaseLibrary> findByFaultCategoryAndErrorCode(FaultCategory category, String errorCode);
|
||||
|
||||
/**
|
||||
* 根据故障类别、故障源和错误码查询(精确匹配)
|
||||
*/
|
||||
List<CaseLibrary> findByFaultCategoryAndFaultSourceAndErrorCode(
|
||||
FaultCategory category, String faultSource, String errorCode);
|
||||
|
||||
/**
|
||||
* 根据来源类型查询(分页)
|
||||
*/
|
||||
Page<CaseLibrary> findBySourceType(SourceType sourceType, Pageable pageable);
|
||||
|
||||
/**
|
||||
* 查询热门案例(按引用次数排序)
|
||||
*/
|
||||
List<CaseLibrary> findTop10ByOrderByReferenceCountDesc();
|
||||
|
||||
/**
|
||||
* 根据故障类别查询热门案例
|
||||
*/
|
||||
List<CaseLibrary> findTop5ByFaultCategoryOrderByReferenceCountDesc(FaultCategory category);
|
||||
|
||||
/**
|
||||
* 模糊搜索标题
|
||||
*/
|
||||
Page<CaseLibrary> findByTitleContaining(String keyword, Pageable pageable);
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package org.example.repository;
|
||||
|
||||
import com.superbiz.agent.domain.enums.DiagnosisStatus;
|
||||
import com.superbiz.agent.domain.enums.FaultCategory;
|
||||
import org.example.domain.entity.DiagnosisRecord;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 诊断记录 Repository
|
||||
*/
|
||||
@Repository
|
||||
public interface DiagnosisRecordRepository extends JpaRepository<DiagnosisRecord, Long> {
|
||||
|
||||
/**
|
||||
* 根据诊断ID查询
|
||||
*/
|
||||
Optional<DiagnosisRecord> findByDiagnosisId(String diagnosisId);
|
||||
|
||||
/**
|
||||
* 根据业务ID查询
|
||||
*/
|
||||
Optional<DiagnosisRecord> findByBusinessId(String businessId);
|
||||
|
||||
/**
|
||||
* 根据链路追踪ID查询
|
||||
*/
|
||||
Optional<DiagnosisRecord> findByTraceId(String traceId);
|
||||
|
||||
/**
|
||||
* 根据会话ID查询所有记录
|
||||
*/
|
||||
List<DiagnosisRecord> findBySessionId(String sessionId);
|
||||
|
||||
/**
|
||||
* 根据故障类别和错误码查询
|
||||
*/
|
||||
List<DiagnosisRecord> findByFaultCategoryAndErrorCode(FaultCategory category, String errorCode);
|
||||
|
||||
/**
|
||||
* 根据故障类别、故障源和错误码查询
|
||||
*/
|
||||
List<DiagnosisRecord> findByFaultCategoryAndFaultSourceAndErrorCode(
|
||||
FaultCategory category, String faultSource, String errorCode);
|
||||
|
||||
/**
|
||||
* 根据状态查询
|
||||
*/
|
||||
List<DiagnosisRecord> findByStatus(DiagnosisStatus status);
|
||||
|
||||
/**
|
||||
* 根据时间范围查询(分页)
|
||||
*/
|
||||
Page<DiagnosisRecord> findByCreatedAtBetween(
|
||||
LocalDateTime start, LocalDateTime end, Pageable pageable);
|
||||
|
||||
/**
|
||||
* 根据故障类别和时间范围查询(分页)
|
||||
*/
|
||||
Page<DiagnosisRecord> findByFaultCategoryAndCreatedAtBetween(
|
||||
FaultCategory category, LocalDateTime start, LocalDateTime end, Pageable pageable);
|
||||
|
||||
/**
|
||||
* 查询有用反馈的高置信度记录(用于生成案例)
|
||||
*/
|
||||
List<DiagnosisRecord> findByFeedbackAndConfidenceGreaterThanEqual(String feedback, Integer confidence);
|
||||
}
|
||||
Reference in New Issue
Block a user