Răsfoiți Sursa

feat: 智能入口清洗完成后默认自动治理

- 新增 DoCleanDTO.autoGovern 标记:清洗完成后是否强制自动治理(忽略治理配置)
- 后端 DmService / AiSmartCleanService 终态判定改为 autoGovern || hasGovern,控制器与智能清洗链路透传
- 前端智能入口(clean_flow_source=smart)发起清洗时带 autoGovern,并跳过治理配置读取、刷新恢复后仍强制处理
- 自主入口不传标记,继续遵守治理配置,行为不变
cc 2 zile în urmă
părinte
comite
d5141033f0

+ 5 - 0
ai-electron/frontend/src/case/api/govern/types/dto.ts

@@ -46,6 +46,11 @@ export interface DoCleanDTO {
   fileInfos: FileInfo[];
   reClean?: 0 | 1;
   fileId?: ApiId | number;
+  /**
+   * 清洗完成后是否强制自动治理(忽略「同时治理」配置)。
+   * 智能入口传 true;自主入口不传,按治理配置执行。
+   */
+  autoGovern?: boolean;
 }
 
 /** 批次阶段:判定 → 交既有清洗 → AI 入库 → 终态 */

+ 19 - 1
ai-electron/frontend/src/case/views/data/cleanProgress.vue

@@ -545,7 +545,18 @@
   const getRequestErrorMessage = (error: any, fallback = '操作失败') =>
     error?.response?.data?.message || error?.message || fallback;
 
+  /**
+   * 是否「智能入口」发起:智能界面进入的上传/清洗流程默认自动治理,忽略治理配置;
+   * 自主入口(含直接打开本页)继续遵守治理配置。
+   */
+  const isSmartFlowEntry = () => getCleanFlowSource() === 'smart';
+
   const loadGovernSwitch = async () => {
+    // 智能入口:默认自动治理,直接忽略治理配置(也不发起配置查询)
+    if (isSmartFlowEntry()) {
+      shouldRunGovernAfterClean.value = true;
+      return;
+    }
     try {
       const list = await getGovernConfList();
       const hasGovernConf = (Array.isArray(list) ? list : []).find(
@@ -876,14 +887,16 @@
     persistImportProgress();
     try {
       const reCleanContext = loadReCleanContext();
+      const autoGovern = isSmartFlowEntry();
       await doClean(
         reCleanContext
           ? {
               fileInfos,
               reClean: 1,
               fileId: reCleanContext.fileId,
+              autoGovern,
             }
-          : { fileInfos },
+          : { fileInfos, autoGovern },
       );
       // 清洗任务已发起:文件列表已读入 currentCleanFiles、进度快照由 persistImportProgress 维护,
       // 因此立即清空「已上传文件缓存」与清洗流程中间缓存,避免后续进入流程时复用上一批文件。
@@ -1172,6 +1185,11 @@
 
   const initializeProgress = async () => {
     if (restoreImportProgress()) {
+      // 刷新恢复:智能入口仍强制自动治理,避免旧快照把治理开关带回配置值
+      if (isSmartFlowEntry()) {
+        shouldRunGovernAfterClean.value = true;
+        persistImportProgress();
+      }
       eventSource = getImportProgressEventSource();
       if (!eventSource) {
         connectSse();

+ 1 - 1
ai-electron/frontend/src/case/views/data/upload.vue

@@ -750,7 +750,7 @@
     try {
       const fileInfos = allFileInfos.value as unknown as FileInfo[];
       persistFileInfos(allFileInfos.value);
-      const res = await smartClean({ fileInfos, reClean: 0 });
+      const res = await smartClean({ fileInfos, reClean: 0, autoGovern: resolveCleanFlowSource() === 'smart' });
       if (!res?.batchId) {
         throw new Error('后端未返回批次号');
       }

+ 7 - 0
ai-server/src/main/java/com/zsjz/ai/common/model/dm/dto/DoCleanDTO.java

@@ -10,4 +10,11 @@ public class DoCleanDTO {
     private List<FileInfo> fileInfos;
     private Integer reClean;
     private Long fileId;
+    /**
+     * 清洗完成后是否强制自动治理(忽略「同时治理」配置)。
+     *
+     * <p>从智能界面进入的上传/清洗流程默认 true:清洗完成即自动治理;
+     * 自主界面发起时不传该标记,继续遵守治理配置(hasGovern)。
+     */
+    private Boolean autoGovern;
 }

+ 3 - 1
ai-server/src/main/java/com/zsjz/ai/module/aiclean/api/AiSmartCleanController.java

@@ -44,7 +44,9 @@ public class AiSmartCleanController {
     public Result<AiSmartCleanService.SmartCleanVO> smartClean(@RequestBody DoCleanDTO dto) {
         Long caseId = CaseContextHolder.currentCaseId();
         Long userId = CaseContextHolder.currentUserId();
-        return Result.succeed(smartCleanService.submit(caseId, userId, dto == null ? null : dto.getFileInfos()));
+        return Result.succeed(smartCleanService.submit(caseId, userId,
+                dto == null ? null : dto.getFileInfos(),
+                dto != null && Boolean.TRUE.equals(dto.getAutoGovern())));
     }
 
     /**

+ 18 - 4
ai-server/src/main/java/com/zsjz/ai/module/aiclean/service/AiSmartCleanService.java

@@ -107,6 +107,16 @@ public class AiSmartCleanService {
      * @param fileInfos 前端上传页那棵树(根节点带 children=sheet)
      */
     public SmartCleanVO submit(Long caseId, Long userId, List<FileInfo> fileInfos) {
+        return submit(caseId, userId, fileInfos, false);
+    }
+
+    /**
+     * 受理批次并立即返回,后台跑完整流程。
+     *
+     * @param autoGovern 智能入口为 true:清洗完成后自动治理,忽略「同时治理」配置;
+     *                   自主入口为 false:按治理配置执行
+     */
+    public SmartCleanVO submit(Long caseId, Long userId, List<FileInfo> fileInfos, boolean autoGovern) {
         if (caseId == null) {
             throw new ServerException("未打开案件,无法智能清洗");
         }
@@ -137,7 +147,7 @@ public class AiSmartCleanService {
         // 必须一开始就放同步容器:判定阶段多个虚拟线程会并发 addReview。
         // 若这里放裸 ArrayList,下方 computeIfAbsent 的同步工厂永远不会生效
         reviewItems.put(batchId, java.util.Collections.synchronizedList(new ArrayList<>()));
-        startAsync(() -> CaseContextHolder.runWith(caseId, userId, () -> run(batchId, caseId, usable)));
+        startAsync(() -> CaseContextHolder.runWith(caseId, userId, () -> run(batchId, caseId, usable, autoGovern)));
         return new SmartCleanVO(batchId, AiCleanStageEnum.JUDGING.name(), usable.size(), sheetTotal,
                 matched, Math.max(0, sheetTotal - matched));
     }
@@ -196,7 +206,7 @@ public class AiSmartCleanService {
      * 智能清洗的 doClean 各趟都是静默的(见 {@code dispatchExistingByLevel}),
      * 因此这一步是全链路唯一的终态出口 —— 纯 AI/无既有清洗的批次也靠它让前端完成跳转。
      */
-    private void pushTerminal(Long caseId, Long batchId) {
+    private void pushTerminal(Long caseId, Long batchId, boolean autoGovern) {
         try {
             Long userId = CaseContextHolder.getUserId();
             if (userId == null) {
@@ -249,7 +259,7 @@ public class AiSmartCleanService {
                 sseService.sendSee(userId, new SseDTO(103, 100, "AI 清洗流程完成",
                         new SeeDataDTO(0, 0, vo)));
             }
-            if (governConfService.hasGovern()) {
+            if (autoGovern || governConfService.hasGovern()) {
                 sseService.sendSee(userId, new SseDTO(102, 100, "[数据治理]开始数据治理!",
                         new SeeDataDTO(0, 0)));
             }
@@ -264,6 +274,10 @@ public class AiSmartCleanService {
      * 后台主流程:判定(并发)→ 一次 doClean → AI 自建表入库 → 终态
      */
     void run(Long batchId, Long caseId, List<FileInfo> roots) {
+        run(batchId, caseId, roots, false);
+    }
+
+    void run(Long batchId, Long caseId, List<FileInfo> roots, boolean autoGovern) {
         long start = System.currentTimeMillis();
         Stats st = new Stats();
         try {
@@ -355,7 +369,7 @@ public class AiSmartCleanService {
             stage(batchId, finalStage);
             // 终态收口(全 SSE):统计 → 状态快照 → 治理宣告;纯 AI/无既有清洗的批次
             // 没有 doClean 的完成回调,前端的「完成并跳转统计页」完全依赖这一推
-            pushTerminal(caseId, batchId);
+            pushTerminal(caseId, batchId, autoGovern);
             // 清洗全部完成:预览阶段动态建的 raw_{sheetId} 临时表已无用,统一清理。
             // 融合链路里 doClean 完成回调已清过一次,这里再兜一次纯 AI 批次(没有 doClean 的情况)。
             try {

+ 6 - 4
ai-server/src/main/java/com/zsjz/ai/module/dm/service/DmService.java

@@ -598,7 +598,7 @@ public class DmService {
     }
 
     private TaskCompleteCallback createDefaultCallback(SlowDownProgress slowDownProgress, Long batchId,
-                                                       boolean silent, CountDownLatch latch) {
+                                                       boolean silent, boolean autoGovern, CountDownLatch latch) {
         return (totalTaskCount, completedTaskCount, message) -> {
             log.info("任务进度:已完成{} / {}({}%)",
                     completedTaskCount, totalTaskCount, Math.round((double) completedTaskCount / totalTaskCount * 100));
@@ -637,8 +637,9 @@ public class DmService {
                         // 静默趟:收口消息(终态/关会话/同时治理)由智能清洗批次统一决定时机,
                         // 但事实性清理(全文检索刷盘/临时文件)与统计照常执行
                         log.info("静默趟清洗完成(batchId={}),收口消息留给智能清洗批次", batchId);
-                    } else if (governConfService.hasGovern()) {
-                        log.info("已开启同时治理数据!");
+                    } else if (autoGovern || governConfService.hasGovern()) {
+                        // 智能入口(autoGovern=true)忽略治理配置,清洗完成后直接进入治理
+                        log.info(autoGovern ? "智能入口:忽略治理配置,清洗完成后自动治理!" : "已开启同时治理数据!");
                         sseService.sendSee(new SseDTO(101, slowDownProgress.calculateNextProgress(), "[数据清洗]所有文件清洗完成!", new SeeDataDTO(completedCount, failNum)));
                         sseService.sendSee(new SseDTO(102, slowDownProgress.calculateNextProgress(), "[数据治理]开始数据治理!", new SeeDataDTO(0, 0)));
                     } else {
@@ -711,7 +712,8 @@ public class DmService {
         int maxConcurrency = Math.min(10, totalTaskCount);
         AtomicInteger completedTaskCount = new AtomicInteger(0);
 
-        TaskCompleteCallback callback = createDefaultCallback(slowDownProgress, cleanJobs.getFirst().fileInfo.getBatchId(), silent, latch);
+        TaskCompleteCallback callback = createDefaultCallback(slowDownProgress, cleanJobs.getFirst().fileInfo.getBatchId(),
+                silent, Boolean.TRUE.equals(dto.getAutoGovern()), latch);
 
         if (!silent) {
             sseService.sendSee(new SseDTO(101, slowDownProgress.calculateNextProgress(), "[数据清洗]文件读取完成!", new SeeDataDTO(0, 0)));