diff --git a/CLAUDE.md b/CLAUDE.md
index 87558f0..5e36709 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -71,6 +71,18 @@ AI 智能客服系统,基于 Spring AI Alibaba + 通义千问 + PGVector,支
**向量化加固**(`DocumentProcessingService`):逐批 try-catch 隔离,失败批只记录缺失区间后继续,已入库块保留;`chunk_count` 记实际入库块数,`error_message` 聚合"已入库 x/y 块 + 缺失区间 + 原因";文档级失败自动整体重试至多 2 次(仅瞬时/限流/超时类错误,4xx 不空转),重试前先清残留向量再重建。**无逐块 AI 关键词提取环节**(`MyKeywordEnricher` 已移除,其产出 `excerpt_keywords` 全库无检索消费点)。
+**`content` 列存全文 + 重新处理的数据源规则**(`DocumentService`,修复了「重跑丢内容」缺陷):
+
+| 项 | 规则 |
+|---|---|
+| 存储 | `knowledge_document.content` 存**原文全文**(不再截断),并写 `extra_config.contentComplete = true` 作为完整性标记 |
+| 序列化 | 实体 `content` 标 `@JsonIgnore`,**任何接口都不返回全文**(否则列表接口每行都带整篇正文)。仅文档详情接口(`DocumentController.toDetailMap`)返回 2000 字预览(键名仍是 `content`)+ `contentTruncated` 布尔标志 |
+| 重新处理的数据源 | ① 有 `contentComplete` 标记 → 用库内全文(语义最忠实,如 JSON 的 fields/pointer 模式)② 历史遗留文档(无标记,content 是 2000 字截断预览)→ 按 `fileType` 从原始文件重解析,成功后**回填全文并打标记**(此后不再依赖文件)③ 两者都不可用 → **明确报错拒绝** |
+
+**踩坑记录**:`reprocessDocument` 原先直接 `simpleStringDocumentReader.read(doc.getContent())`,而 `content` 是 2000 字截断预览,且 `DocumentProcessingService` 以 `cleanBeforeFirstAttempt=true` 运行(**先删光旧向量**)—— 对超过 2000 字符的文档重跑会永久丢失其余内容。修复前**不要**对存量文档执行 `POST /document/batch/reprocess`。判断遗留文档:`extra_config->>'contentComplete' IS NULL`;其中 `length(content) = 2000` 的才是真正被截断的。
+
+**JSON 解析模式的已知降级**:JSON 的 basic/fields/pointer 三种模式上传时未持久化,历史 JSON 文档从文件重解析只能按 basic 还原(不丢数据,仅抽取口径可能变化,日志有 WARN)。新文档走库内全文分支,语义不变。
+
## 关键配置
diff --git a/README.md b/README.md
index c475ef0..8680556 100644
--- a/README.md
+++ b/README.md
@@ -94,7 +94,7 @@ CREATE TABLE knowledge_document (
source_name VARCHAR(500), -- 原始文件名
file_type VARCHAR(20) NOT NULL, -- 文件类型
file_size BIGINT DEFAULT 0, -- 文件大小(字节)
- content TEXT, -- 原文内容(截断预览)
+ content TEXT, -- 原文全文(重处理用;接口仅返回 2000 字预览)
category_id BIGINT DEFAULT 0, -- 所属分类ID
tags JSONB DEFAULT '{}', -- 标签(JSON对象)
chunk_count INTEGER DEFAULT 0, -- 分块数量
diff --git a/frontend/src/views/DocDetail.vue b/frontend/src/views/DocDetail.vue
index d3be730..6ba682d 100644
--- a/frontend/src/views/DocDetail.vue
+++ b/frontend/src/views/DocDetail.vue
@@ -26,7 +26,7 @@
diff --git a/src/main/java/com/wok/supportbot/config/DatabaseInitConfig.java b/src/main/java/com/wok/supportbot/config/DatabaseInitConfig.java
index 4e69124..9e6afe3 100644
--- a/src/main/java/com/wok/supportbot/config/DatabaseInitConfig.java
+++ b/src/main/java/com/wok/supportbot/config/DatabaseInitConfig.java
@@ -1500,7 +1500,7 @@ public class DatabaseInitConfig {
executeComment("COLUMN knowledge_document.source_name", "原始文件名");
executeComment("COLUMN knowledge_document.file_type", "文件类型: pdf / md / json / txt / word / excel 等");
executeComment("COLUMN knowledge_document.file_size", "文件大小(字节)");
- executeComment("COLUMN knowledge_document.content", "原文内容(截断预览)");
+ executeComment("COLUMN knowledge_document.content", "原文全文(用于重新处理时重建分块;接口仅返回 2000 字预览)");
executeComment("COLUMN knowledge_document.category_id", "所属分类 ID(0 表示未分类)");
executeComment("COLUMN knowledge_document.folder_id", "所属目录 ID(0 表示未指定目录,直接挂分类根)");
executeComment("COLUMN knowledge_document.tags", "标签(JSON 格式)");
diff --git a/src/main/java/com/wok/supportbot/controller/DocumentController.java b/src/main/java/com/wok/supportbot/controller/DocumentController.java
index 8e61063..938797a 100644
--- a/src/main/java/com/wok/supportbot/controller/DocumentController.java
+++ b/src/main/java/com/wok/supportbot/controller/DocumentController.java
@@ -1,5 +1,7 @@
package com.wok.supportbot.controller;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
import com.wok.supportbot.entity.CategoryNode;
import com.wok.supportbot.entity.KnowledgeCategory;
import com.wok.supportbot.entity.KnowledgeDocument;
@@ -30,6 +32,11 @@ public class DocumentController {
private DocumentService documentService;
@Autowired
private CategoryFilter categoryFilter;
+ @Autowired
+ private ObjectMapper objectMapper;
+
+ /** 文档详情接口返回的原文预览上限(字符)。仅影响传输展示,不影响存库与重新处理 */
+ private static final int CONTENT_PREVIEW_LIMIT = 2000;
// ==================== 上传校验常量 ====================
@@ -384,7 +391,7 @@ public class DocumentController {
}
return ResponseEntity.ok(Map.of(
"success", true,
- "data", doc
+ "data", toDetailMap(doc)
));
} catch (Exception e) {
return ResponseEntity.status(500).body(Map.of(
@@ -394,6 +401,28 @@ public class DocumentController {
}
}
+ /**
+ * 组装文档详情响应。
+ *
+ * 实体上的 {@code content} 带 {@code @JsonIgnore}(存的是全文,可达数十万字符,不能直接返回给前端),
+ * 此处显式补回键名仍为 {@code content} 的**截断预览**,并附 {@code contentTruncated} 标志供前端提示。
+ * 预览仅用于展示,与存库内容、与重新处理的数据源无关。
+ */
+ private Map toDetailMap(KnowledgeDocument doc) {
+ Map data = objectMapper.convertValue(doc, new TypeReference