feat: 完善知识库批量导入与公共 API

- 新增批量异步导入、状态查询、失败重试与中断恢复链路

- 拆分知识库读取、导入、维护权限并完善 Public API 契约

- 补充数据库迁移、管理端交互、接口说明与相关测试
This commit is contained in:
2026-08-03 11:13:48 +08:00
parent 6df3dd9981
commit 51dbfd41b6
99 changed files with 16274 additions and 480 deletions

View File

@@ -0,0 +1,217 @@
package tech.easyflow.publicapi.dto;
import cn.hutool.core.bean.BeanUtil;
import com.mybatisflex.core.paginate.Page;
import tech.easyflow.ai.entity.Document;
import tech.easyflow.ai.entity.DocumentCollection;
import java.math.BigInteger;
import java.util.Collections;
import java.util.Date;
import java.util.List;
/**
* 公开知识库详情响应。
*
* <p>知识库原有字段保持在响应顶层,文档摘要通过 {@code documents} 分页返回。</p>
*/
public class PublicKnowledgeDetailResponse extends DocumentCollection {
/**
* 已上传文档的分页摘要。
*/
private final Page<DocumentSummary> documents;
/**
* 创建公开知识库详情响应。
*
* @param knowledge 知识库基本信息
* @param documentPage 文档实体分页FAQ 知识库可传 {@code null}
*/
public PublicKnowledgeDetailResponse(
DocumentCollection knowledge,
Page<Document> documentPage
) {
BeanUtil.copyProperties(knowledge, this);
this.documents = toDocumentSummaryPage(documentPage);
}
/**
* 获取文档分页摘要。
*
* @return 文档分页摘要
*/
public Page<DocumentSummary> getDocuments() {
return documents;
}
/**
* 将文档实体分页映射为公开摘要分页。
*
* @param source 文档实体分页
* @return 不包含内部路径、正文和配置的公开摘要分页
*/
private static Page<DocumentSummary> toDocumentSummaryPage(Page<Document> source) {
if (source == null) {
return new Page<>(Collections.emptyList(), 1, 50, 0L);
}
List<DocumentSummary> records = source.getRecords() == null
? Collections.emptyList()
: source.getRecords().stream().map(DocumentSummary::new).toList();
return new Page<>(
records,
source.getPageNumber(),
source.getPageSize(),
source.getTotalRow()
);
}
/**
* 公开文档摘要。
*/
public static class DocumentSummary {
/**
* 文档 ID。
*/
private final BigInteger id;
/**
* 文档标题。
*/
private final String title;
/**
* 文档类型。
*/
private final String documentType;
/**
* 文档内容类型。
*/
private final String contentType;
/**
* 文档处理状态。
*/
private final String processStatus;
/**
* 文档分块数。
*/
private final long chunkCount;
/**
* 处理进度百分比。
*/
private final Integer progressPercent;
/**
* 创建时间。
*/
private final Date created;
/**
* 最后修改时间。
*/
private final Date modified;
/**
* 从文档实体创建公开摘要。
*
* @param document 文档实体
*/
public DocumentSummary(Document document) {
this.id = document.getId();
this.title = document.getTitle();
this.documentType = document.getDocumentType();
this.contentType = document.getContentType();
this.processStatus = document.getProcessStatus();
this.chunkCount = document.getDisplayChunkCount();
this.progressPercent = document.getProgressPercent();
this.created = document.getCreated();
this.modified = document.getModified();
}
/**
* 获取文档 ID。
*
* @return 文档 ID
*/
public BigInteger getId() {
return id;
}
/**
* 获取文档标题。
*
* @return 文档标题
*/
public String getTitle() {
return title;
}
/**
* 获取文档类型。
*
* @return 文档类型
*/
public String getDocumentType() {
return documentType;
}
/**
* 获取文档内容类型。
*
* @return 文档内容类型
*/
public String getContentType() {
return contentType;
}
/**
* 获取文档处理状态。
*
* @return 文档处理状态
*/
public String getProcessStatus() {
return processStatus;
}
/**
* 获取文档分块数。
*
* @return 文档分块数
*/
public long getChunkCount() {
return chunkCount;
}
/**
* 获取处理进度百分比。
*
* @return 处理进度百分比
*/
public Integer getProgressPercent() {
return progressPercent;
}
/**
* 获取创建时间。
*
* @return 创建时间
*/
public Date getCreated() {
return created;
}
/**
* 获取最后修改时间。
*
* @return 最后修改时间
*/
public Date getModified() {
return modified;
}
}
}

View File

@@ -0,0 +1,176 @@
package tech.easyflow.publicapi.dto;
import com.fasterxml.jackson.annotation.JsonInclude;
import tech.easyflow.ai.dto.KnowledgeSearchResultItem;
import java.math.BigInteger;
/**
* 公开知识库检索结果。
*
* <p>根据命中来源补充文档或 FAQ 标识,便于调用方继续查询对应详情。</p>
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
public class PublicKnowledgeSearchResultItem extends KnowledgeSearchResultItem {
/**
* 命中来源类型。
*/
private String resultType;
/**
* 来源文档 ID。
*/
private BigInteger documentId;
/**
* 来源文档名称。
*/
private String documentName;
/**
* 来源 FAQ ID。
*/
private BigInteger faqId;
/**
* 来源 FAQ 问题。
*/
private String question;
/**
* 来源 FAQ 纯文本答案。
*/
private String answerText;
/**
* 来源 FAQ 分类 ID。
*/
private BigInteger categoryId;
/**
* 获取命中来源类型。
*
* @return {@code DOCUMENT} 或 {@code FAQ}
*/
public String getResultType() {
return resultType;
}
/**
* 设置命中来源类型。
*
* @param resultType 命中来源类型
*/
public void setResultType(String resultType) {
this.resultType = resultType;
}
/**
* 获取来源文档 ID。
*
* @return 来源文档 ID
*/
public BigInteger getDocumentId() {
return documentId;
}
/**
* 设置来源文档 ID。
*
* @param documentId 来源文档 ID
*/
public void setDocumentId(BigInteger documentId) {
this.documentId = documentId;
}
/**
* 获取来源文档名称。
*
* @return 来源文档名称
*/
public String getDocumentName() {
return documentName;
}
/**
* 设置来源文档名称。
*
* @param documentName 来源文档名称
*/
public void setDocumentName(String documentName) {
this.documentName = documentName;
}
/**
* 获取来源 FAQ ID。
*
* @return 来源 FAQ ID
*/
public BigInteger getFaqId() {
return faqId;
}
/**
* 设置来源 FAQ ID。
*
* @param faqId 来源 FAQ ID
*/
public void setFaqId(BigInteger faqId) {
this.faqId = faqId;
}
/**
* 获取来源 FAQ 问题。
*
* @return 来源 FAQ 问题
*/
public String getQuestion() {
return question;
}
/**
* 设置来源 FAQ 问题。
*
* @param question 来源 FAQ 问题
*/
public void setQuestion(String question) {
this.question = question;
}
/**
* 获取来源 FAQ 纯文本答案。
*
* @return 来源 FAQ 纯文本答案
*/
public String getAnswerText() {
return answerText;
}
/**
* 设置来源 FAQ 纯文本答案。
*
* @param answerText 来源 FAQ 纯文本答案
*/
public void setAnswerText(String answerText) {
this.answerText = answerText;
}
/**
* 获取来源 FAQ 分类 ID。
*
* @return 来源 FAQ 分类 ID
*/
public BigInteger getCategoryId() {
return categoryId;
}
/**
* 设置来源 FAQ 分类 ID。
*
* @param categoryId 来源 FAQ 分类 ID
*/
public void setCategoryId(BigInteger categoryId) {
this.categoryId = categoryId;
}
}