Browse Source

fix(document): 修复文件夹上传连接池耗尽与目录树独立滚动

- 移除 DocumentProcessingService 异步方法上的跨方法事务,AI 关键词提取/向量化期间不再占用数据库连接,避免文件夹批量上传时连接池被占满超时
- 新增有界 documentExecutor 线程池,替代默认无界 SimpleAsyncTaskExecutor,防线程爆炸与大模型限流
- 文档列表页目录树与列表改为左右独立滚动
Spring-AI-1.1.2
wanghanlin 2 weeks ago
parent
commit
367ec79401
  1. 12
      frontend/src/views/DocList.vue
  2. 18
      src/main/java/com/wok/supportbot/config/AsyncExecutorConfig.java
  3. 11
      src/main/java/com/wok/supportbot/service/DocumentProcessingService.java

12
frontend/src/views/DocList.vue

@ -1,5 +1,5 @@
<template> <template>
<t-card title="文档列表" :bordered="false">
<t-card class="doc-list-card" title="文档列表" :bordered="false">
<div class="doc-layout"> <div class="doc-layout">
<!-- 左侧目录树面板 --> <!-- 左侧目录树面板 -->
<aside class="doc-tree-panel"> <aside class="doc-tree-panel">
@ -522,15 +522,19 @@ loadData()
.selected-count { font-size: 12px; font-weight: 600; color: var(--td-text-color-primary); } .selected-count { font-size: 12px; font-weight: 600; color: var(--td-text-color-primary); }
/* 左右分栏布局 */ /* 左右分栏布局 */
.doc-layout { display: flex; gap: 16px; align-items: flex-start; }
/* 卡片撑满内容区,实现左右独立滚动 */
.doc-list-card { height: 100%; display: flex; flex-direction: column; }
.doc-list-card :deep(.t-card__body) { flex: 1; min-height: 0; overflow: hidden; }
.doc-layout { display: flex; gap: 16px; align-items: stretch; height: 100%; }
.doc-tree-panel { .doc-tree-panel {
width: 240px; width: 240px;
flex-shrink: 0; flex-shrink: 0;
min-height: 420px;
overflow-y: auto;
padding-right: 12px; padding-right: 12px;
border-right: 1px solid var(--color-border); border-right: 1px solid var(--color-border);
} }
.doc-content { flex: 1; min-width: 0; }
.doc-content { flex: 1; min-width: 0; overflow-y: auto; }
.tree-panel-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; } .tree-panel-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; }
.tree-panel-title { font-size: 13px; font-weight: 600; } .tree-panel-title { font-size: 13px; font-weight: 600; }

18
src/main/java/com/wok/supportbot/config/AsyncExecutorConfig.java

@ -33,4 +33,22 @@ public class AsyncExecutorConfig {
executor.initialize(); executor.initialize();
return executor; return executor;
} }
/**
* 文档异步处理线程池分块 关键词提取 向量化
* 有界线程池 + CallerRunsPolicy避免文件夹上传大量文件时
* 默认 {@code SimpleAsyncTaskExecutor} 每任务新建线程导致的线程爆炸
* 同时限制并发向量化调用避免打爆大模型限流
*/
@Bean("documentExecutor")
public ThreadPoolTaskExecutor documentExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(4);
executor.setQueueCapacity(500);
executor.setThreadNamePrefix("doc-process-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
} }

11
src/main/java/com/wok/supportbot/service/DocumentProcessingService.java

@ -11,8 +11,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -43,7 +41,8 @@ public class DocumentProcessingService {
/** /**
* 异步处理文档分块 关键词提取 向量化 更新状态 * 异步处理文档分块 关键词提取 向量化 更新状态
* 使用 REQUIRES_NEW 在独立事务中执行避免与主事务冲突
* 不加跨方法事务AI 关键词提取与向量化属于慢速网络调用期间不占用数据库连接
* 避免文件夹批量上传时多个异步任务把连接池占满导致连接超时
* *
* @param docId 文档ID * @param docId 文档ID
* @param documents 已解析的原始文档列表 * @param documents 已解析的原始文档列表
@ -54,8 +53,7 @@ public class DocumentProcessingService {
* @param chunkSize 分块大小可选覆盖全局配置 * @param chunkSize 分块大小可选覆盖全局配置
* @param overlap 重叠大小可选覆盖全局配置 * @param overlap 重叠大小可选覆盖全局配置
*/ */
@Async
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
@Async("documentExecutor")
public void processDocumentAsync(Long docId, List<Document> documents, String sourceName, public void processDocumentAsync(Long docId, List<Document> documents, String sourceName,
String title, Long categoryId, List<String> tags, String title, Long categoryId, List<String> tags,
Integer chunkSize, Integer overlap) { Integer chunkSize, Integer overlap) {
@ -117,8 +115,7 @@ public class DocumentProcessingService {
* @param docId 文档ID * @param docId 文档ID
* @param documents 解析后的文档列表 * @param documents 解析后的文档列表
*/ */
@Async
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
@Async("documentExecutor")
public void reprocessDocumentAsync(Long docId, List<Document> documents) { public void reprocessDocumentAsync(Long docId, List<Document> documents) {
// 等待主事务提交确保文档记录可见 // 等待主事务提交确保文档记录可见
KnowledgeDocument doc = waitForDocument(docId); KnowledgeDocument doc = waitForDocument(docId);

Loading…
Cancel
Save