|
|
@@ -0,0 +1,286 @@
|
|
|
+package com.etotem.cfc.controller.admin;
|
|
|
+
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|
|
+import com.etotem.cfc.common.Result;
|
|
|
+import com.etotem.cfc.entity.*;
|
|
|
+import com.etotem.cfc.mapper.*;
|
|
|
+import com.etotem.cfc.service.ReportImportService;
|
|
|
+import com.fasterxml.jackson.core.type.TypeReference;
|
|
|
+import com.fasterxml.jackson.databind.ObjectMapper;
|
|
|
+import org.slf4j.Logger;
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
+import org.springframework.web.bind.annotation.PostMapping;
|
|
|
+import org.springframework.web.bind.annotation.RequestBody;
|
|
|
+import org.springframework.web.bind.annotation.RequestMapping;
|
|
|
+import org.springframework.web.bind.annotation.RestController;
|
|
|
+
|
|
|
+import javax.annotation.Resource;
|
|
|
+import java.util.*;
|
|
|
+
|
|
|
+@RestController
|
|
|
+@RequestMapping("/api/admin/report-parser")
|
|
|
+public class ReportParserAdminController {
|
|
|
+
|
|
|
+ private static final Logger log = LoggerFactory.getLogger(ReportParserAdminController.class);
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private ReportTypeRegistryMapper typeRegistryMapper;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private ReportFingerprintMapper fingerprintMapper;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private ReportUnknownClusterMapper unknownClusterMapper;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private ReportUnknownUploadMapper unknownUploadMapper;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private ReportParserImportMapper importMapper;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private ReportImportService reportImportService;
|
|
|
+
|
|
|
+ private final ObjectMapper objectMapper = new ObjectMapper();
|
|
|
+
|
|
|
+ // ==================== 报告类型管理 ====================
|
|
|
+
|
|
|
+ @PostMapping("/types")
|
|
|
+ public Result<Page<ReportTypeRegistry>> listTypes(@RequestBody Map<String, Object> params) {
|
|
|
+ int page = params.get("page") != null ? ((Number) params.get("page")).intValue() : 1;
|
|
|
+ int size = params.get("size") != null ? ((Number) params.get("size")).intValue() : 999;
|
|
|
+ Page<ReportTypeRegistry> pageParam = new Page<>(page, size);
|
|
|
+ LambdaQueryWrapper<ReportTypeRegistry> wrapper = new LambdaQueryWrapper<ReportTypeRegistry>()
|
|
|
+ .orderByDesc(ReportTypeRegistry::getId);
|
|
|
+ return Result.success(typeRegistryMapper.selectPage(pageParam, wrapper));
|
|
|
+ }
|
|
|
+
|
|
|
+ @PostMapping("/types/save")
|
|
|
+ public Result<Void> saveType(@RequestBody ReportTypeRegistry type) {
|
|
|
+ if (type.getTypeId() == null || type.getTypeId().isEmpty()) {
|
|
|
+ return Result.error("typeId 不能为空");
|
|
|
+ }
|
|
|
+ if (type.getDisplayName() == null || type.getDisplayName().isEmpty()) {
|
|
|
+ return Result.error("displayName 不能为空");
|
|
|
+ }
|
|
|
+ if (type.getId() != null) {
|
|
|
+ type.setUpdatedAt(new Date());
|
|
|
+ typeRegistryMapper.updateById(type);
|
|
|
+ } else {
|
|
|
+ Long exists = typeRegistryMapper.selectCount(
|
|
|
+ new LambdaQueryWrapper<ReportTypeRegistry>()
|
|
|
+ .eq(ReportTypeRegistry::getTypeId, type.getTypeId()));
|
|
|
+ if (exists != null && exists > 0) {
|
|
|
+ return Result.error("typeId 已存在");
|
|
|
+ }
|
|
|
+ type.setIsActive(type.getIsActive() != null ? type.getIsActive() : true);
|
|
|
+ type.setSource(type.getSource() != null ? type.getSource() : "prebuilt");
|
|
|
+ type.setReviewStatus(type.getReviewStatus() != null ? type.getReviewStatus() : "approved");
|
|
|
+ type.setVersion(type.getVersion() != null ? type.getVersion() : "1.0");
|
|
|
+ type.setMinConfidence(type.getMinConfidence() != null ? type.getMinConfidence() : 10);
|
|
|
+ type.setCreatedAt(new Date());
|
|
|
+ type.setUpdatedAt(new Date());
|
|
|
+ typeRegistryMapper.insert(type);
|
|
|
+ }
|
|
|
+ return Result.success();
|
|
|
+ }
|
|
|
+
|
|
|
+ @PostMapping("/types/toggle")
|
|
|
+ public Result<Void> toggleType(@RequestBody Map<String, Object> params) {
|
|
|
+ String typeId = (String) params.get("typeId");
|
|
|
+ Boolean isActive = params.get("isActive") != null ? (Boolean) params.get("isActive") : false;
|
|
|
+ if (typeId == null) return Result.error("typeId 不能为空");
|
|
|
+ ReportTypeRegistry type = typeRegistryMapper.selectOne(
|
|
|
+ new LambdaQueryWrapper<ReportTypeRegistry>().eq(ReportTypeRegistry::getTypeId, typeId));
|
|
|
+ if (type == null) return Result.error("类型不存在");
|
|
|
+ type.setIsActive(isActive);
|
|
|
+ type.setUpdatedAt(new Date());
|
|
|
+ typeRegistryMapper.updateById(type);
|
|
|
+ return Result.success();
|
|
|
+ }
|
|
|
+
|
|
|
+ @PostMapping("/types/delete")
|
|
|
+ public Result<Void> deleteType(@RequestBody Map<String, Object> params) {
|
|
|
+ String typeId = (String) params.get("typeId");
|
|
|
+ if (typeId == null) return Result.error("typeId 不能为空");
|
|
|
+ typeRegistryMapper.delete(new LambdaQueryWrapper<ReportTypeRegistry>()
|
|
|
+ .eq(ReportTypeRegistry::getTypeId, typeId));
|
|
|
+ fingerprintMapper.delete(new LambdaQueryWrapper<ReportFingerprint>()
|
|
|
+ .eq(ReportFingerprint::getTypeId, typeId));
|
|
|
+ return Result.success();
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== 指纹规则管理 ====================
|
|
|
+
|
|
|
+ @PostMapping("/fingerprints/list")
|
|
|
+ public Result<List<ReportFingerprint>> listFingerprints(@RequestBody Map<String, Object> params) {
|
|
|
+ String typeId = (String) params.get("typeId");
|
|
|
+ if (typeId == null) return Result.error("typeId 不能为空");
|
|
|
+ List<ReportFingerprint> list = fingerprintMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<ReportFingerprint>()
|
|
|
+ .eq(ReportFingerprint::getTypeId, typeId)
|
|
|
+ .orderByAsc(ReportFingerprint::getSortOrder));
|
|
|
+ return Result.success(list);
|
|
|
+ }
|
|
|
+
|
|
|
+ @PostMapping("/fingerprints/save")
|
|
|
+ public Result<Void> saveFingerprint(@RequestBody ReportFingerprint fp) {
|
|
|
+ if (fp.getTypeId() == null || fp.getPattern() == null) {
|
|
|
+ return Result.error("typeId 和 pattern 不能为空");
|
|
|
+ }
|
|
|
+ fp.setWeight(fp.getWeight() != null ? fp.getWeight() : 10);
|
|
|
+ fp.setIsExclusion(fp.getIsExclusion() != null ? fp.getIsExclusion() : false);
|
|
|
+ fp.setSortOrder(fp.getSortOrder() != null ? fp.getSortOrder() : 0);
|
|
|
+ fp.setCreatedAt(new Date());
|
|
|
+ fingerprintMapper.insert(fp);
|
|
|
+ return Result.success();
|
|
|
+ }
|
|
|
+
|
|
|
+ @PostMapping("/fingerprints/delete")
|
|
|
+ public Result<Void> deleteFingerprint(@RequestBody Map<String, Object> params) {
|
|
|
+ Long id = params.get("id") != null ? ((Number) params.get("id")).longValue() : null;
|
|
|
+ if (id == null) return Result.error("id 不能为空");
|
|
|
+ fingerprintMapper.deleteById(id);
|
|
|
+ return Result.success();
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== 导入管理 ====================
|
|
|
+
|
|
|
+ @PostMapping("/imports")
|
|
|
+ public Result<Page<ReportParserImport>> listImports(@RequestBody Map<String, Object> params) {
|
|
|
+ int page = params.get("page") != null ? ((Number) params.get("page")).intValue() : 1;
|
|
|
+ int size = params.get("size") != null ? ((Number) params.get("size")).intValue() : 20;
|
|
|
+ Page<ReportParserImport> pageParam = new Page<>(page, size);
|
|
|
+ LambdaQueryWrapper<ReportParserImport> wrapper = new LambdaQueryWrapper<ReportParserImport>()
|
|
|
+ .orderByDesc(ReportParserImport::getCreatedAt);
|
|
|
+ return Result.success(importMapper.selectPage(pageParam, wrapper));
|
|
|
+ }
|
|
|
+
|
|
|
+ @PostMapping("/imports/import")
|
|
|
+ public Result<ReportParserImport> uploadImport(@RequestBody Map<String, Object> params) {
|
|
|
+ String jsonContent = (String) params.get("jsonContent");
|
|
|
+ String fileName = (String) params.get("fileName");
|
|
|
+ Long userId = params.get("userId") != null ? ((Number) params.get("userId")).longValue() : null;
|
|
|
+ if (jsonContent == null) return Result.error("jsonContent 不能为空");
|
|
|
+ return reportImportService.importPackage(fileName, jsonContent, userId);
|
|
|
+ }
|
|
|
+
|
|
|
+ @PostMapping("/imports/approve")
|
|
|
+ public Result<Map<String, Object>> approveImport(@RequestBody Map<String, Object> params) {
|
|
|
+ Long importId = params.get("id") != null ? ((Number) params.get("id")).longValue() : null;
|
|
|
+ Long reviewerId = params.get("reviewerId") != null ? ((Number) params.get("reviewerId")).longValue() : null;
|
|
|
+ if (importId == null) return Result.error("id 不能为空");
|
|
|
+ return reportImportService.approveImport(importId, reviewerId);
|
|
|
+ }
|
|
|
+
|
|
|
+ @PostMapping("/imports/reject")
|
|
|
+ public Result<Void> rejectImport(@RequestBody Map<String, Object> params) {
|
|
|
+ Long importId = params.get("id") != null ? ((Number) params.get("id")).longValue() : null;
|
|
|
+ Long reviewerId = params.get("reviewerId") != null ? ((Number) params.get("reviewerId")).longValue() : null;
|
|
|
+ String note = (String) params.get("note");
|
|
|
+ if (importId == null) return Result.error("id 不能为空");
|
|
|
+ return reportImportService.rejectImport(importId, reviewerId, note);
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== 未知报告聚类 ====================
|
|
|
+
|
|
|
+ @PostMapping("/unknown-clusters")
|
|
|
+ public Result<Page<ReportUnknownCluster>> listClusters(@RequestBody Map<String, Object> params) {
|
|
|
+ int page = params.get("page") != null ? ((Number) params.get("page")).intValue() : 1;
|
|
|
+ int size = params.get("size") != null ? ((Number) params.get("size")).intValue() : 20;
|
|
|
+ String status = (String) params.get("status");
|
|
|
+ Page<ReportUnknownCluster> pageParam = new Page<>(page, size);
|
|
|
+ LambdaQueryWrapper<ReportUnknownCluster> wrapper = new LambdaQueryWrapper<ReportUnknownCluster>()
|
|
|
+ .orderByDesc(ReportUnknownCluster::getReportCount);
|
|
|
+ if (status != null && !status.isEmpty()) {
|
|
|
+ wrapper.eq(ReportUnknownCluster::getStatus, status);
|
|
|
+ }
|
|
|
+ return Result.success(unknownClusterMapper.selectPage(pageParam, wrapper));
|
|
|
+ }
|
|
|
+
|
|
|
+ @PostMapping("/unknown-uploads")
|
|
|
+ public Result<List<ReportUnknownUpload>> listUploads(@RequestBody Map<String, Object> params) {
|
|
|
+ Long clusterId = params.get("clusterId") != null ? ((Number) params.get("clusterId")).longValue() : null;
|
|
|
+ if (clusterId == null) return Result.error("clusterId 不能为空");
|
|
|
+ List<ReportUnknownUpload> list = unknownUploadMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<ReportUnknownUpload>()
|
|
|
+ .eq(ReportUnknownUpload::getClusterId, clusterId)
|
|
|
+ .orderByDesc(ReportUnknownUpload::getUploadedAt));
|
|
|
+ return Result.success(list);
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== 自学习生成 ====================
|
|
|
+
|
|
|
+ @PostMapping("/generate-type")
|
|
|
+ public Result<Map<String, Object>> generateType(@RequestBody Map<String, Object> params) {
|
|
|
+ Long clusterId = params.get("clusterId") != null ? ((Number) params.get("clusterId")).longValue() : null;
|
|
|
+ if (clusterId == null) return Result.error("clusterId 不能为空");
|
|
|
+
|
|
|
+ ReportUnknownCluster cluster = unknownClusterMapper.selectById(clusterId);
|
|
|
+ if (cluster == null) return Result.error("聚类不存在");
|
|
|
+ if (!"ready".equals(cluster.getStatus())) {
|
|
|
+ return Result.error("该聚类不可生成,当前状态: " + cluster.getStatus());
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ String clusterKey = cluster.getClusterKey();
|
|
|
+ String typeId = "auto_" + clusterKey.toLowerCase().replaceAll("[^a-z0-9]", "_").replaceAll("_+", "_");
|
|
|
+
|
|
|
+ // 创建报告类型
|
|
|
+ ReportTypeRegistry type = new ReportTypeRegistry();
|
|
|
+ type.setTypeId(typeId);
|
|
|
+ type.setFamily("auto_generated");
|
|
|
+ type.setDisplayName("自动生成 - " + typeId);
|
|
|
+ type.setDescription("由自学习系统自动生成,基于 " + cluster.getReportCount() + " 份同类报告");
|
|
|
+ type.setParserPath(typeId + ".py");
|
|
|
+ type.setMinConfidence(10);
|
|
|
+ type.setIsActive(false);
|
|
|
+ type.setSource(ReportTypeRegistry.SOURCE_AUTO_GENERATED);
|
|
|
+ type.setReviewStatus(ReportTypeRegistry.REVIEW_PENDING);
|
|
|
+ type.setVersion("1.0");
|
|
|
+ type.setCreatedAt(new Date());
|
|
|
+ type.setUpdatedAt(new Date());
|
|
|
+ typeRegistryMapper.insert(type);
|
|
|
+
|
|
|
+ // 从公共签名生成指纹规则
|
|
|
+ String signatures = cluster.getCommonSignatures();
|
|
|
+ if (signatures != null && !signatures.isEmpty()) {
|
|
|
+ // 提取可能的文本特征作为指纹
|
|
|
+ String[] candidates = signatures.split("[\n\r]+");
|
|
|
+ Set<String> seen = new HashSet<>();
|
|
|
+ int sort = 0;
|
|
|
+ for (String line : candidates) {
|
|
|
+ String trimmed = line.trim();
|
|
|
+ if (trimmed.length() >= 4 && trimmed.length() <= 100 && !seen.contains(trimmed)) {
|
|
|
+ seen.add(trimmed);
|
|
|
+ ReportFingerprint fp = new ReportFingerprint();
|
|
|
+ fp.setTypeId(typeId);
|
|
|
+ fp.setPattern(trimmed);
|
|
|
+ fp.setWeight(10);
|
|
|
+ fp.setIsExclusion(false);
|
|
|
+ fp.setSortOrder(sort++);
|
|
|
+ fp.setCreatedAt(new Date());
|
|
|
+ fingerprintMapper.insert(fp);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新聚类
|
|
|
+ cluster.setStatus("generated");
|
|
|
+ cluster.setGeneratedTypeId(typeId);
|
|
|
+ cluster.setUpdatedAt(new Date());
|
|
|
+ unknownClusterMapper.updateById(cluster);
|
|
|
+
|
|
|
+ Map<String, Object> result = new LinkedHashMap<>();
|
|
|
+ result.put("typeId", typeId);
|
|
|
+ result.put("displayName", type.getDisplayName());
|
|
|
+ result.put("fingerprintCount", type.getMinConfidence());
|
|
|
+ result.put("reviewStatus", "pending");
|
|
|
+ return Result.success(result);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("自学习生成类型失败 clusterId={}", clusterId, e);
|
|
|
+ return Result.error("生成失败: " + e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|