|
|
@@ -68,7 +68,9 @@ public class AiSmartCleanService {
|
|
|
|
|
|
private final DmService dmService;
|
|
|
|
|
|
- /** 进度与终态全部走 SSE 推送(cmd 103),前端不再轮询状态接口 */
|
|
|
+ /**
|
|
|
+ * 进度与终态全部走 SSE 推送(cmd 103),前端不再轮询状态接口
|
|
|
+ */
|
|
|
private final SseService sseService;
|
|
|
|
|
|
private final FileInfoMapper fileInfoMapper;
|
|
|
@@ -88,11 +90,15 @@ public class AiSmartCleanService {
|
|
|
@Value("${zsjz.ai-clean.batch-token-budget:0}")
|
|
|
private long batchTokenBudget;
|
|
|
|
|
|
- /** 每任务一个虚拟线程,真正的并发上限由闸门控制(与 FileRecognitionService 同一套) */
|
|
|
+ /**
|
|
|
+ * 每任务一个虚拟线程,真正的并发上限由闸门控制(与 FileRecognitionService 同一套)
|
|
|
+ */
|
|
|
private final ExecutorService executor = Executors.newThreadPerTaskExecutor(
|
|
|
Thread.ofVirtual().name("ai-clean-", 0).factory());
|
|
|
|
|
|
- /** 待人工明细:判定阶段并发写入 */
|
|
|
+ /**
|
|
|
+ * 待人工明细:判定阶段并发写入
|
|
|
+ */
|
|
|
private final Map<Long, List<NeedReviewItem>> reviewItems = new ConcurrentHashMap<>();
|
|
|
|
|
|
/**
|
|
|
@@ -119,7 +125,7 @@ public class AiSmartCleanService {
|
|
|
int sheetTotal = usable.stream()
|
|
|
.mapToInt(r -> r.getChildren() == null ? 0 : r.getChildren().size()).sum();
|
|
|
int matched = (int) usable.stream().flatMap(r -> r.getChildren() == null
|
|
|
- ? java.util.stream.Stream.<FileInfo>empty() : r.getChildren().stream())
|
|
|
+ ? java.util.stream.Stream.<FileInfo>empty() : r.getChildren().stream())
|
|
|
.filter(s -> s != null && s.getTemplateId() != null).count();
|
|
|
BatchRef ref = upsertBatch(caseId, batchId, usable.size(), sheetTotal, matched);
|
|
|
if (!ref.fresh()) {
|
|
|
@@ -143,7 +149,9 @@ public class AiSmartCleanService {
|
|
|
Thread.startVirtualThread(body);
|
|
|
}
|
|
|
|
|
|
- /** 前端轮询(兼容旧入口;实时进度已改为 SSE 推送,见 {@link #pushProgress}) */
|
|
|
+ /**
|
|
|
+ * 前端轮询(兼容旧入口;实时进度已改为 SSE 推送,见 {@link #pushProgress})
|
|
|
+ */
|
|
|
public SmartCleanStatusVO status(Long caseId, Long batchId) {
|
|
|
AiCleanBatch batch = batchMapper.selectOne(Wrappers.<AiCleanBatch>lambdaQuery()
|
|
|
.eq(AiCleanBatch::getCaseId, caseId)
|
|
|
@@ -227,7 +235,9 @@ public class AiSmartCleanService {
|
|
|
|
|
|
// ==================== 后台主流程 ====================
|
|
|
|
|
|
- /** 后台主流程:判定(并发)→ 一次 doClean → AI 自建表入库 → 终态 */
|
|
|
+ /**
|
|
|
+ * 后台主流程:判定(并发)→ 一次 doClean → AI 自建表入库 → 终态
|
|
|
+ */
|
|
|
void run(Long batchId, Long caseId, List<FileInfo> roots) {
|
|
|
long start = System.currentTimeMillis();
|
|
|
Stats st = new Stats();
|
|
|
@@ -371,7 +381,9 @@ public class AiSmartCleanService {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /** 单表失败:只影响它自己那条 job 与计数,不中断整批 */
|
|
|
+ /**
|
|
|
+ * 单表失败:只影响它自己那条 job 与计数,不中断整批
|
|
|
+ */
|
|
|
private void fail(Long batchId, PendingAi p, String reason, Stats st) {
|
|
|
aiCleanService.finish(p.job(), AiCleanStatusEnum.FAIL, reason, System.currentTimeMillis());
|
|
|
st.failed.incrementAndGet();
|
|
|
@@ -434,7 +446,9 @@ public class AiSmartCleanService {
|
|
|
return applied;
|
|
|
}
|
|
|
|
|
|
- /** 把本级的 sheet 挂到根节点<b>浅拷贝</b>上发一趟既有清洗,并等它全部落库 */
|
|
|
+ /**
|
|
|
+ * 把本级的 sheet 挂到根节点<b>浅拷贝</b>上发一趟既有清洗,并等它全部落库
|
|
|
+ */
|
|
|
private void runCleanPass(List<FileInfo> roots, Map<FileInfo, List<FileInfo>> sheetsByRoot, boolean silent) {
|
|
|
// 根节点用浅拷贝:不改动前端提交的原始树;两趟各持各的根,互不覆盖
|
|
|
// (sheet 对象本身仍是同一批引用,瞬态字段只在分组时写一次)
|
|
|
@@ -492,9 +506,9 @@ public class AiSmartCleanService {
|
|
|
|
|
|
/**
|
|
|
* 建/重置批次。
|
|
|
- *
|
|
|
- * @param fresh false 表示上一轮还在跑(非终态),调用方应复用而不是再起一遍 ——
|
|
|
- * 否则同一批次会有两个线程各自发一次 doClean。
|
|
|
+ * <p>
|
|
|
+ * 表示上一轮还在跑(非终态),调用方应复用而不是再起一遍 ——
|
|
|
+ * 否则同一批次会有两个线程各自发一次 doClean。
|
|
|
*/
|
|
|
private BatchRef upsertBatch(Long caseId, Long batchId, int fileCount, int sheetTotal, int matched) {
|
|
|
AiCleanBatch exist = batchMapper.selectOne(batchKey(caseId, batchId));
|
|
|
@@ -545,7 +559,9 @@ public class AiSmartCleanService {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /** 批次行 + 是否该由本次调用启动后台流程 */
|
|
|
+ /**
|
|
|
+ * 批次行 + 是否该由本次调用启动后台流程
|
|
|
+ */
|
|
|
private record BatchRef(AiCleanBatch batch, boolean fresh) {
|
|
|
}
|
|
|
|
|
|
@@ -630,7 +646,9 @@ public class AiSmartCleanService {
|
|
|
private final AtomicInteger aiLoad = new AtomicInteger();
|
|
|
private final AtomicInteger needReview = new AtomicInteger();
|
|
|
private final AtomicInteger failed = new AtomicInteger();
|
|
|
- /** 本轮已消耗的模型 token(判定回写),供批次预算护栏比对 */
|
|
|
+ /**
|
|
|
+ * 本轮已消耗的模型 token(判定回写),供批次预算护栏比对
|
|
|
+ */
|
|
|
private final AtomicLong tokens = new AtomicLong();
|
|
|
}
|
|
|
|
|
|
@@ -643,7 +661,9 @@ public class AiSmartCleanService {
|
|
|
executor.shutdownNow();
|
|
|
}
|
|
|
|
|
|
- /** 一个待判定的 AI 表块 */
|
|
|
+ /**
|
|
|
+ * 一个待判定的 AI 表块
|
|
|
+ */
|
|
|
private record PendingAi(FileInfo root, FileInfo sheet, AiCleanJob job) {
|
|
|
}
|
|
|
|
|
|
@@ -655,12 +675,16 @@ public class AiSmartCleanService {
|
|
|
String reason) {
|
|
|
}
|
|
|
|
|
|
- /** 受理响应 */
|
|
|
+ /**
|
|
|
+ * 受理响应
|
|
|
+ */
|
|
|
public record SmartCleanVO(Long batchId, String stage, int fileCount, int sheetTotal,
|
|
|
int matchedCount, int aiSheetCount) {
|
|
|
}
|
|
|
|
|
|
- /** 轮询响应 */
|
|
|
+ /**
|
|
|
+ * 轮询响应
|
|
|
+ */
|
|
|
public record SmartCleanStatusVO(String stage, int fileCount, int sheetTotal, int matchedCount,
|
|
|
int aiJudgedCount, int existingHitCount, int aiLoadCount,
|
|
|
int needReviewCount, int failedCount, List<NeedReviewItem> needReview) {
|