feat: 修改知识库文件列表表为目录树结构,增加知识库文件表

This commit is contained in:
wenjinbo 2025-09-09 14:04:25 +08:00
parent a82c8c71be
commit 93bd565cb6
10 changed files with 1296 additions and 6 deletions

View File

@ -199,7 +199,7 @@ const scrollToBottom = async () => {
await nextTick()
setTimeout(() => {
requestAnimationFrame(() => {
if (messagesContainer.value && !isUserScrolling) {
if (messagesContainer.value && (!isUserScrolling || isLoading.value)) {
bottomAnchor.value?.scrollIntoView({
behavior: 'smooth',
block: 'start'
@ -220,6 +220,14 @@ const throttledScrollToBottom = throttle(() => {
scrollToBottom()
}, 300, { leading: true, trailing: true })
//
const streamingScrollToBottom = throttle(() => {
if (messagesContainer.value && isLoading.value) {
//
messagesContainer.value.scrollTop = messagesContainer.value.scrollHeight
}
}, 100, { leading: true, trailing: true })
//
const handleCreateNewChat = async () => {
//
@ -293,6 +301,10 @@ const handleSendMessage = async (messageText: string) => {
// ID
updateSessionIds(data.conversationId, data.messageId)
emit('streamComplete', data)
},
() => {
//
streamingScrollToBottom()
}
)
@ -345,6 +357,10 @@ const handleRegenerateMessage = async (index: number) => {
(data) => {
updateSessionIds(data.conversationId, data.messageId)
emit('streamComplete', data)
},
() => {
//
streamingScrollToBottom()
}
)

View File

@ -944,7 +944,7 @@ const hasAnySources = (sources: TraceData | null | undefined) => {
overflow: auto;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
background: white;
font-size: 0.9rem;
font-size: 0.83rem; /* 比正文(0.95rem)小2px约为0.83rem */
line-height: 1.4;
border: 1px solid #e5e7eb;
}
@ -979,6 +979,7 @@ const hasAnySources = (sources: TraceData | null | undefined) => {
max-width: 200px;
word-break: break-word;
white-space: normal;
font-size: 0.83rem; /* 确保表格单元格字体也比正文小2px */
}
.answer-content :deep(th) {
@ -986,7 +987,7 @@ const hasAnySources = (sources: TraceData | null | undefined) => {
background: #f8fafc;
font-weight: 600;
color: #374151;
font-size: 0.85rem;
font-size: 0.83rem; /* 与表格整体字体大小保持一致比正文小2px */
text-transform: uppercase;
letter-spacing: 0.025em;
position: sticky;
@ -1042,7 +1043,7 @@ const hasAnySources = (sources: TraceData | null | undefined) => {
@media (max-width: 768px) {
.answer-content :deep(table) {
min-width: 600px;
font-size: 0.8rem;
font-size: 0.75rem; /* 移动端保持比正文小的比例 */
margin: 1rem 0;
border: 1px solid #e5e7eb;
}
@ -1052,10 +1053,11 @@ const hasAnySources = (sources: TraceData | null | undefined) => {
padding: 0.5rem 0.75rem;
max-width: 150px;
border: 1px solid #e5e7eb;
font-size: 0.75rem; /* 移动端表格单元格字体 */
}
.answer-content :deep(th) {
font-size: 0.75rem;
font-size: 0.75rem; /* 移动端表头字体 */
border-bottom: 2px solid #e5e7eb;
}
}

View File

@ -142,7 +142,8 @@ export function useMessageHandlers() {
chatType: string,
conversationId?: string,
onMessageUpdate?: (message: Message) => void,
onStreamComplete?: (data: { conversationId: string; messageId: string; content: string }) => void
onStreamComplete?: (data: { conversationId: string; messageId: string; content: string }) => void,
onStreamUpdate?: () => void
) => {
try {
isLoading.value = true
@ -217,6 +218,9 @@ export function useMessageHandlers() {
if (data.taskId) {
currentTaskId.value = data.taskId
}
// 触发流式更新回调,用于滚动
onStreamUpdate?.()
}
} catch (e) {
console.error('解析错误:', e)
@ -243,6 +247,9 @@ export function useMessageHandlers() {
if (data.messageId) {
newMessageId = data.messageId
}
// 触发流式更新回调,用于滚动
onStreamUpdate?.()
}
} catch (e) {
console.error('最终解析错误:', e)

View File

@ -3,7 +3,9 @@ import cn.hutool.core.io.resource.InputStreamResource;
import com.bjtds.brichat.entity.dataset.DatasetsDocRenameReq;
import com.bjtds.brichat.entity.dataset.DocUploadReq;
import com.bjtds.brichat.entity.dataset.DocumentUploadReq;
import com.bjtds.brichat.entity.dataset.TDatasetFiles;
import com.bjtds.brichat.entity.dto.PdfTaskDto;
import com.bjtds.brichat.service.DatasetFilesService;
import com.bjtds.brichat.service.DatasetsDocService;
import com.bjtds.brichat.service.dify.DifyDatasetApiService;
import com.bjtds.brichat.util.Constants;
@ -64,6 +66,10 @@ public class DatasetDocController {
private String difyDatasetApiKey;
@Resource
private DatasetFilesService datasetFilesService;
@Value("${dify.url}")
private String difyUrl;
@Autowired
@ -94,6 +100,19 @@ public class DatasetDocController {
public ResultUtils upload(@RequestPart("file") MultipartFile file,
@RequestPart("request") DocUploadReq req) throws Exception{
ResponseEntity<Map> documentByFile = difyDatasetApiService.createDocByFile(req, file,"");
Map<String, String> document = (Map<String, String>) documentByFile.getBody().get("document");
String documentId = document.get("id");
TDatasetFiles datasetFiles = new TDatasetFiles();
datasetFiles.setParentId(null);
datasetFiles.setType("file");
datasetFiles.setName(file.getOriginalFilename());
datasetFiles.setSize(file.getSize());
datasetFiles.setDifyDatasetId(req.getDatasetId());
datasetFiles.setOwnerId(1L);
datasetFiles.setDifyDocId(documentId);
datasetFilesService.createFile(datasetFiles);
return ResultUtils.success(documentByFile);
}

View File

@ -0,0 +1,209 @@
package com.bjtds.brichat.controller;
import com.bjtds.brichat.entity.dataset.TDatasetFiles;
import com.bjtds.brichat.service.DatasetFilesService;
import com.bjtds.brichat.util.ResultUtils;
import com.bjtds.common.utils.Pagination;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
* 知识库文件/文件夹控制器
*
* @author system
*/
@RestController
@RequestMapping("/api/dataset-files")
public class DatasetFilesController {
@Autowired
private DatasetFilesService datasetFilesService;
/**
* 根据知识库id和parent_id 分页查询 文件/文件夹
*
* @param difyDatasetId 知识库ID
* @param parentId 父目录ID
* @param orderBy 排序字段
* @param orderDirection 排序方向
* @param pageNo 页码
* @param pageSize 页大小
* @return 分页结果
*/
@GetMapping("/list")
public Object getFilesByParent(@RequestParam String difyDatasetId,
@RequestParam(required = false) Long parentId,
@RequestParam(required = false, defaultValue = "name") String orderBy,
@RequestParam(required = false, defaultValue = "ASC") String orderDirection,
@RequestParam(required = false, defaultValue = "1") Integer pageNo,
@RequestParam(required = false, defaultValue = "20") Integer pageSize) {
try {
Pagination<TDatasetFiles> result = datasetFilesService.getFilesByDatasetIdAndParentId(
difyDatasetId, parentId, orderBy, orderDirection, pageNo, pageSize);
return ResultUtils.success(result);
} catch (Exception e) {
return ResultUtils.error(e.getMessage());
}
}
/**
* 根据知识库id和name查询 文件/文件夹
*
* @param difyDatasetId 知识库ID
* @param name 文件/文件夹名称
* @param orderBy 排序字段
* @param orderDirection 排序方向
* @param pageNo 页码
* @param pageSize 页大小
* @return 分页结果
*/
@GetMapping("/search")
public Object searchFilesByName(@RequestParam String difyDatasetId,
@RequestParam String name,
@RequestParam(required = false, defaultValue = "name") String orderBy,
@RequestParam(required = false, defaultValue = "ASC") String orderDirection,
@RequestParam(required = false, defaultValue = "1") Integer pageNo,
@RequestParam(required = false, defaultValue = "20") Integer pageSize) {
try {
Pagination<TDatasetFiles> result = datasetFilesService.getFilesByDatasetIdAndName(
difyDatasetId, name, orderBy, orderDirection, pageNo, pageSize);
return ResultUtils.success(result);
} catch (Exception e) {
return ResultUtils.error(e.getMessage());
}
}
/**
* 根据ID查询文件/文件夹详情
*
* @param id 文件/文件夹ID
* @return 文件/文件夹信息
*/
@GetMapping("/{id}")
public Object getFileById(@PathVariable Integer id) {
try {
TDatasetFiles file = datasetFilesService.getFileById(id);
if (file == null) {
return ResultUtils.error("文件/文件夹不存在");
}
return ResultUtils.success(file);
} catch (Exception e) {
return ResultUtils.error(e.getMessage());
}
}
/**
* 创建文件/文件夹
*
* @param datasetFiles 文件/文件夹信息
* @return 创建结果
*/
@PostMapping("/create")
public Object createFile(@RequestBody TDatasetFiles datasetFiles) {
try {
TDatasetFiles file = datasetFilesService.createFile(datasetFiles);
return ResultUtils.success(file);
} catch (Exception e) {
return ResultUtils.error(e.getMessage());
}
}
/**
* 删除文件/文件夹
*
* @param id 文件/文件夹ID
* @return 删除结果
*/
@DeleteMapping("/{id}")
public Object deleteFile(@PathVariable Integer id) {
try {
boolean success = datasetFilesService.deleteFile(id);
if (success) {
return ResultUtils.success("删除成功");
} else {
return ResultUtils.error("删除失败");
}
} catch (Exception e) {
return ResultUtils.error(e.getMessage());
}
}
/**
* 重命名文件/文件夹
*
* @param id 文件/文件夹ID
* @param request 重命名请求
* @return 重命名结果
*/
@PutMapping("/{id}/rename")
public Object renameFile(@PathVariable Integer id, @RequestBody RenameFileRequest request) {
try {
boolean success = datasetFilesService.renameFile(id, request.getNewName());
if (success) {
return ResultUtils.success("重命名成功");
} else {
return ResultUtils.error("重命名失败");
}
} catch (Exception e) {
return ResultUtils.error(e.getMessage());
}
}
/**
* 检查文件/文件夹名称是否存在
*
* @param difyDatasetId 知识库ID
* @param parentId 父目录ID
* @param name 文件/文件夹名称
* @param excludeId 排除的ID
* @return 检查结果
*/
@GetMapping("/check-name")
public Object checkNameExists(@RequestParam String difyDatasetId,
@RequestParam(required = false) Long parentId,
@RequestParam String name,
@RequestParam(required = false) Integer excludeId) {
try {
boolean exists = datasetFilesService.checkNameExists(parentId, name, difyDatasetId, excludeId);
return ResultUtils.success(exists);
} catch (Exception e) {
return ResultUtils.error(e.getMessage());
}
}
/**
* 根据路径查询文件/文件夹
*
* @param path 路径
* @param difyDatasetId 知识库ID
* @return 文件/文件夹信息
*/
@GetMapping("/by-path")
public Object getFileByPath(@RequestParam String path, @RequestParam String difyDatasetId) {
try {
TDatasetFiles file = datasetFilesService.getFileByPath(path, difyDatasetId);
if (file == null) {
return ResultUtils.error("文件/文件夹不存在");
}
return ResultUtils.success(file);
} catch (Exception e) {
return ResultUtils.error(e.getMessage());
}
}
/**
* 重命名文件/文件夹请求类
*/
public static class RenameFileRequest {
private String newName;
public String getNewName() {
return newName;
}
public void setNewName(String newName) {
this.newName = newName;
}
}
}

View File

@ -0,0 +1,241 @@
package com.bjtds.brichat.entity.dataset;
import java.time.LocalDateTime;
/**
* 知识库文件/文件夹实体类
* 对应数据库表t_dataset_files
*/
public class TDatasetFiles {
/**
* 文件/文件夹唯一标识符
*/
private Integer id;
/**
* 文件或文件夹名称
*/
private String name;
/**
* 类型: file-文件, folder-文件夹
*/
private String type;
/**
* 父目录ID如果是根目录则为NULL
*/
private Long parentId;
/**
* 完整路径从根目录开始的完整路径
*/
private String path;
/**
* 文件大小(字节)文件夹通常为0或子项总和
*/
private Long size;
/**
* 所有者用户ID
*/
private Long ownerId;
/**
* 创建时间
*/
private LocalDateTime createdAt;
/**
* 最后更新时间
*/
private LocalDateTime updatedAt;
/**
* 逻辑删除标志false-正常true-已删除
*/
private Boolean isDeleted;
/**
* 文件MIME类型如图像/jpeg文本/plain等
*/
private String mimeType;
/**
* 文件扩展名.txt.jpg等
*/
private String extension;
/**
* 关联的Dify文档ID用于与Dify平台集成
*/
private String difyDocId;
/**
* dify的解析状态
*/
private String indexingStatus;
/**
* dify知识库id
*/
private String difyDatasetId;
// 构造函数
public TDatasetFiles() {}
public TDatasetFiles(String name, String type, Long parentId, String path, Long ownerId, String difyDatasetId) {
this.name = name;
this.type = type;
this.parentId = parentId;
this.path = path;
this.ownerId = ownerId;
this.difyDatasetId = difyDatasetId;
this.size = 0L;
this.isDeleted = false;
}
// Getter and Setter methods
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public Long getParentId() {
return parentId;
}
public void setParentId(Long parentId) {
this.parentId = parentId;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public Long getSize() {
return size;
}
public void setSize(Long size) {
this.size = size;
}
public Long getOwnerId() {
return ownerId;
}
public void setOwnerId(Long ownerId) {
this.ownerId = ownerId;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
}
public Boolean getIsDeleted() {
return isDeleted;
}
public void setIsDeleted(Boolean isDeleted) {
this.isDeleted = isDeleted;
}
public String getMimeType() {
return mimeType;
}
public void setMimeType(String mimeType) {
this.mimeType = mimeType;
}
public String getExtension() {
return extension;
}
public void setExtension(String extension) {
this.extension = extension;
}
public String getDifyDocId() {
return difyDocId;
}
public void setDifyDocId(String difyDocId) {
this.difyDocId = difyDocId;
}
public String getIndexingStatus() {
return indexingStatus;
}
public void setIndexingStatus(String indexingStatus) {
this.indexingStatus = indexingStatus;
}
public String getDifyDatasetId() {
return difyDatasetId;
}
public void setDifyDatasetId(String difyDatasetId) {
this.difyDatasetId = difyDatasetId;
}
@Override
public String toString() {
return "TDatasetFiles{" +
"id=" + id +
", name='" + name + '\'' +
", type='" + type + '\'' +
", parentId=" + parentId +
", path='" + path + '\'' +
", size=" + size +
", ownerId=" + ownerId +
", createdAt=" + createdAt +
", updatedAt=" + updatedAt +
", isDeleted=" + isDeleted +
", mimeType='" + mimeType + '\'' +
", extension='" + extension + '\'' +
", difyDocId='" + difyDocId + '\'' +
", indexingStatus='" + indexingStatus + '\'' +
", difyDatasetId='" + difyDatasetId + '\'' +
'}';
}
}

View File

@ -0,0 +1,148 @@
package com.bjtds.brichat.mapper.opengauss;
import com.bjtds.brichat.entity.dataset.TDatasetFiles;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 知识库文件/文件夹 Mapper 接口
*
* @author system
*/
@Mapper
public interface TDatasetFilesMapper {
/**
* 根据知识库id和parent_id 分页查询 文件/文件夹
*
* @param difyDatasetId 知识库ID
* @param parentId 父目录ID
* @param orderBy 排序字段 (name, size, created_at, updated_at)
* @param orderDirection 排序方向 (ASC, DESC)
* @param offset 偏移量
* @param limit 限制数量
* @return 文件/文件夹列表
*/
List<TDatasetFiles> selectByDatasetIdAndParentId(@Param("difyDatasetId") String difyDatasetId,
@Param("parentId") Long parentId,
@Param("orderBy") String orderBy,
@Param("orderDirection") String orderDirection,
@Param("offset") Integer offset,
@Param("limit") Integer limit);
/**
* 根据知识库id和parent_id 查询总数
*
* @param difyDatasetId 知识库ID
* @param parentId 父目录ID
* @return 总数
*/
int countByDatasetIdAndParentId(@Param("difyDatasetId") String difyDatasetId,
@Param("parentId") Long parentId);
/**
* 根据知识库id和name查询 文件/文件夹
*
* @param difyDatasetId 知识库ID
* @param name 文件/文件夹名称支持模糊查询
* @param orderBy 排序字段
* @param orderDirection 排序方向
* @param offset 偏移量
* @param limit 限制数量
* @return 文件/文件夹列表
*/
List<TDatasetFiles> selectByDatasetIdAndName(@Param("difyDatasetId") String difyDatasetId,
@Param("name") String name,
@Param("orderBy") String orderBy,
@Param("orderDirection") String orderDirection,
@Param("offset") Integer offset,
@Param("limit") Integer limit);
/**
* 根据知识库id和name查询总数
*
* @param difyDatasetId 知识库ID
* @param name 文件/文件夹名称
* @return 总数
*/
int countByDatasetIdAndName(@Param("difyDatasetId") String difyDatasetId,
@Param("name") String name);
/**
* 根据ID查询文件/文件夹
*
* @param id 文件/文件夹ID
* @return 文件/文件夹信息
*/
TDatasetFiles selectById(@Param("id") Integer id);
/**
* 插入文件/文件夹
*
* @param datasetFiles 文件/文件夹信息
* @return 影响行数
*/
int insert(TDatasetFiles datasetFiles);
/**
* 更新文件/文件夹信息
*
* @param datasetFiles 文件/文件夹信息
* @return 影响行数
*/
int updateById(TDatasetFiles datasetFiles);
/**
* 逻辑删除文件/文件夹设置 is_deleted = true
*
* @param id 文件/文件夹ID
* @return 影响行数
*/
int deleteById(@Param("id") Integer id);
/**
* 根据父目录ID逻辑删除所有子文件/文件夹
*
* @param parentId 父目录ID
* @return 影响行数
*/
int deleteByParentId(@Param("parentId") Long parentId);
/**
* 重命名文件/文件夹
*
* @param id 文件/文件夹ID
* @param newName 新名称
* @param newPath 新路径
* @return 影响行数
*/
int renameById(@Param("id") Integer id,
@Param("newName") String newName,
@Param("newPath") String newPath);
/**
* 检查同一父目录下是否存在同名文件/文件夹
*
* @param parentId 父目录ID
* @param name 文件/文件夹名称
* @param difyDatasetId 知识库ID
* @param excludeId 排除的ID用于重命名时排除自己
* @return 存在的记录数
*/
int checkNameExists(@Param("parentId") Long parentId,
@Param("name") String name,
@Param("difyDatasetId") String difyDatasetId,
@Param("excludeId") Integer excludeId);
/**
* 根据路径查询文件/文件夹
*
* @param path 路径
* @param difyDatasetId 知识库ID
* @return 文件/文件夹信息
*/
TDatasetFiles selectByPath(@Param("path") String path,
@Param("difyDatasetId") String difyDatasetId);
}

View File

@ -0,0 +1,129 @@
package com.bjtds.brichat.service;
import com.bjtds.brichat.entity.dataset.TDatasetFiles;
import com.bjtds.common.utils.Pagination;
import java.util.List;
/**
* 知识库文件/文件夹业务层接口
*
* @author system
*/
public interface DatasetFilesService {
/**
* 根据知识库id和parent_id 分页查询 文件/文件夹
*
* @param difyDatasetId 知识库ID
* @param parentId 父目录ID
* @param orderBy 排序字段 (name, size, created_at, updated_at)
* @param orderDirection 排序方向 (ASC, DESC)
* @param pageNo 页码
* @param pageSize 页大小
* @return 分页结果
*/
Pagination<TDatasetFiles> getFilesByDatasetIdAndParentId(String difyDatasetId,
Long parentId,
String orderBy,
String orderDirection,
Integer pageNo,
Integer pageSize);
/**
* 根据知识库id和name查询 文件/文件夹
*
* @param difyDatasetId 知识库ID
* @param name 文件/文件夹名称支持模糊查询
* @param orderBy 排序字段
* @param orderDirection 排序方向
* @param pageNo 页码
* @param pageSize 页大小
* @return 分页结果
*/
Pagination<TDatasetFiles> getFilesByDatasetIdAndName(String difyDatasetId,
String name,
String orderBy,
String orderDirection,
Integer pageNo,
Integer pageSize);
/**
* 根据ID查询文件/文件夹详情
*
* @param id 文件/文件夹ID
* @return 文件/文件夹信息
*/
TDatasetFiles getFileById(Integer id);
/**
* 创建文件/文件夹
*
* @param datasetFiles 文件/文件夹信息对象
* @return 创建的文件/文件夹信息
*/
TDatasetFiles createFile(TDatasetFiles datasetFiles);
/**
* 删除文件/文件夹逻辑删除
*
* @param id 文件/文件夹ID
* @return 是否删除成功
*/
boolean deleteFile(Integer id);
/**
* 重命名文件/文件夹
*
* @param id 文件/文件夹ID
* @param newName 新名称
* @return 是否重命名成功
*/
boolean renameFile(Integer id, String newName);
/**
* 检查同一父目录下是否存在同名文件/文件夹
*
* @param parentId 父目录ID
* @param name 文件/文件夹名称
* @param difyDatasetId 知识库ID
* @param excludeId 排除的ID用于重命名时排除自己
* @return 是否存在同名文件/文件夹
*/
boolean checkNameExists(Long parentId, String name, String difyDatasetId, Integer excludeId);
/**
* 根据路径查询文件/文件夹
*
* @param path 路径
* @param difyDatasetId 知识库ID
* @return 文件/文件夹信息
*/
TDatasetFiles getFileByPath(String path, String difyDatasetId);
/**
* 更新文件/文件夹信息
*
* @param datasetFiles 文件/文件夹信息
* @return 是否更新成功
*/
boolean updateFile(TDatasetFiles datasetFiles);
/**
* 构建文件完整路径
*
* @param parentPath 父目录路径
* @param fileName 文件名
* @return 完整路径
*/
String buildFilePath(String parentPath, String fileName);
/**
* 获取父目录路径
*
* @param parentId 父目录ID
* @param difyDatasetId 知识库ID
* @return 父目录路径如果是根目录返回空字符串
*/
String getParentPath(Long parentId, String difyDatasetId);
}

View File

@ -0,0 +1,331 @@
package com.bjtds.brichat.service.impl;
import com.bjtds.brichat.entity.dataset.TDatasetFiles;
import com.bjtds.brichat.mapper.opengauss.TDatasetFilesMapper;
import com.bjtds.brichat.service.DatasetFilesService;
import com.bjtds.common.utils.Pagination;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.util.Arrays;
import java.util.List;
/**
* 知识库文件/文件夹业务层实现类
*
* @author system
*/
@Service
public class DatasetFilesServiceImpl implements DatasetFilesService {
@Autowired
private TDatasetFilesMapper datasetFilesMapper;
// 允许的排序字段
private static final List<String> ALLOWED_ORDER_FIELDS = Arrays.asList("name", "size", "created_at", "updated_at", "type");
@Override
public Pagination<TDatasetFiles> getFilesByDatasetIdAndParentId(String difyDatasetId,
Long parentId,
String orderBy,
String orderDirection,
Integer pageNo,
Integer pageSize) {
// 参数校验
if (!StringUtils.hasText(difyDatasetId)) {
throw new IllegalArgumentException("知识库ID不能为空");
}
if (pageNo == null || pageNo < 1) {
pageNo = 1;
}
if (pageSize == null || pageSize < 1) {
pageSize = 20;
}
// 排序参数校验
orderBy = validateOrderBy(orderBy);
orderDirection = validateOrderDirection(orderDirection);
// 计算偏移量
Integer offset = (pageNo - 1) * pageSize;
// 查询数据
List<TDatasetFiles> files = datasetFilesMapper.selectByDatasetIdAndParentId(
difyDatasetId, parentId, orderBy, orderDirection, offset, pageSize);
// 查询总数
int total = datasetFilesMapper.countByDatasetIdAndParentId(difyDatasetId, parentId);
// 构建分页结果
Pagination<TDatasetFiles> pagination = new Pagination<>();
pagination.setContent(files);
pagination.setTotal((long) total);
pagination.setPageNo(pageNo);
pagination.setPageSize(pageSize);
return pagination;
}
@Override
public Pagination<TDatasetFiles> getFilesByDatasetIdAndName(String difyDatasetId,
String name,
String orderBy,
String orderDirection,
Integer pageNo,
Integer pageSize) {
// 参数校验
if (!StringUtils.hasText(difyDatasetId)) {
throw new IllegalArgumentException("知识库ID不能为空");
}
if (!StringUtils.hasText(name)) {
throw new IllegalArgumentException("搜索名称不能为空");
}
if (pageNo == null || pageNo < 1) {
pageNo = 1;
}
if (pageSize == null || pageSize < 1) {
pageSize = 20;
}
// 排序参数校验
orderBy = validateOrderBy(orderBy);
orderDirection = validateOrderDirection(orderDirection);
// 计算偏移量
Integer offset = (pageNo - 1) * pageSize;
// 查询数据
List<TDatasetFiles> files = datasetFilesMapper.selectByDatasetIdAndName(
difyDatasetId, name, orderBy, orderDirection, offset, pageSize);
// 查询总数
int total = datasetFilesMapper.countByDatasetIdAndName(difyDatasetId, name);
// 构建分页结果
Pagination<TDatasetFiles> pagination = new Pagination<>();
pagination.setContent(files);
pagination.setTotal((long) total);
pagination.setPageNo(pageNo);
pagination.setPageSize(pageSize);
return pagination;
}
@Override
public TDatasetFiles getFileById(Integer id) {
if (id == null) {
throw new IllegalArgumentException("文件ID不能为空");
}
return datasetFilesMapper.selectById(id);
}
@Override
@Transactional
public TDatasetFiles createFile(TDatasetFiles datasetFiles) {
// 参数校验
if (datasetFiles == null) {
throw new IllegalArgumentException("文件/文件夹信息不能为空");
}
if (!StringUtils.hasText(datasetFiles.getName())) {
throw new IllegalArgumentException("文件/文件夹名称不能为空");
}
if (!StringUtils.hasText(datasetFiles.getType())) {
throw new IllegalArgumentException("类型不能为空");
}
if (!"file".equals(datasetFiles.getType()) && !"folder".equals(datasetFiles.getType())) {
throw new IllegalArgumentException("类型必须是 file 或 folder");
}
if (!StringUtils.hasText(datasetFiles.getDifyDatasetId())) {
throw new IllegalArgumentException("知识库ID不能为空");
}
if (datasetFiles.getOwnerId() == null) {
throw new IllegalArgumentException("所有者ID不能为空");
}
// 检查同名文件/文件夹是否存在
if (checkNameExists(datasetFiles.getParentId(), datasetFiles.getName(), datasetFiles.getDifyDatasetId(), null)) {
throw new IllegalArgumentException("同一目录下已存在同名文件/文件夹:" + datasetFiles.getName());
}
// 构建路径
String parentPath = getParentPath(datasetFiles.getParentId(), datasetFiles.getDifyDatasetId());
String fullPath = buildFilePath(parentPath, datasetFiles.getName());
datasetFiles.setPath(fullPath);
// 设置默认值
if (datasetFiles.getSize() == null) {
datasetFiles.setSize(0L);
}
if (datasetFiles.getIsDeleted() == null) {
datasetFiles.setIsDeleted(false);
}
// 根据文件类型设置相应字段
if ("file".equals(datasetFiles.getType())) {
// 文件类型需要确保有 difyDocId, indexingStatus, difyDatasetId
if (!StringUtils.hasText(datasetFiles.getDifyDocId())) {
// 如果没有提供 difyDocId可以在这里生成或抛出异常
// datasetFiles.setDifyDocId(generateDifyDocId()); // 根据业务需求实现
}
if (!StringUtils.hasText(datasetFiles.getIndexingStatus())) {
// 可以设置默认的索引状态
datasetFiles.setIndexingStatus("pending"); // 或其他默认状态
}
} else if ("folder".equals(datasetFiles.getType())) {
// 文件夹类型只需要 difyDatasetId清除文件相关字段
datasetFiles.setDifyDocId(null);
datasetFiles.setIndexingStatus(null);
datasetFiles.setMimeType(null);
datasetFiles.setExtension(null);
}
// 插入数据库
int result = datasetFilesMapper.insert(datasetFiles);
if (result > 0) {
return datasetFiles;
} else {
throw new RuntimeException("创建文件/文件夹失败");
}
}
@Override
@Transactional
public boolean deleteFile(Integer id) {
if (id == null) {
throw new IllegalArgumentException("文件ID不能为空");
}
// 查询文件信息
TDatasetFiles file = datasetFilesMapper.selectById(id);
if (file == null) {
throw new IllegalArgumentException("文件/文件夹不存在");
}
// 如果是文件夹需要删除所有子文件/文件夹
if ("folder".equals(file.getType())) {
datasetFilesMapper.deleteByParentId(id.longValue());
}
// 删除文件/文件夹本身
return datasetFilesMapper.deleteById(id) > 0;
}
@Override
@Transactional
public boolean renameFile(Integer id, String newName) {
if (id == null) {
throw new IllegalArgumentException("文件ID不能为空");
}
if (!StringUtils.hasText(newName)) {
throw new IllegalArgumentException("新名称不能为空");
}
// 查询原文件信息
TDatasetFiles file = datasetFilesMapper.selectById(id);
if (file == null) {
throw new IllegalArgumentException("文件/文件夹不存在");
}
// 检查同名文件/文件夹是否存在排除自己
if (checkNameExists(file.getParentId(), newName, file.getDifyDatasetId(), id)) {
throw new IllegalArgumentException("同一目录下已存在同名文件/文件夹:" + newName);
}
// 构建新路径
String parentPath = getParentPath(file.getParentId(), file.getDifyDatasetId());
String newPath = buildFilePath(parentPath, newName);
// 更新名称和路径
return datasetFilesMapper.renameById(id, newName, newPath) > 0;
}
@Override
public boolean checkNameExists(Long parentId, String name, String difyDatasetId, Integer excludeId) {
if (!StringUtils.hasText(name) || !StringUtils.hasText(difyDatasetId)) {
return false;
}
int count = datasetFilesMapper.checkNameExists(parentId, name, difyDatasetId, excludeId);
return count > 0;
}
@Override
public TDatasetFiles getFileByPath(String path, String difyDatasetId) {
if (!StringUtils.hasText(path) || !StringUtils.hasText(difyDatasetId)) {
return null;
}
return datasetFilesMapper.selectByPath(path, difyDatasetId);
}
@Override
@Transactional
public boolean updateFile(TDatasetFiles datasetFiles) {
if (datasetFiles == null || datasetFiles.getId() == null) {
throw new IllegalArgumentException("文件信息或ID不能为空");
}
return datasetFilesMapper.updateById(datasetFiles) > 0;
}
@Override
public String buildFilePath(String parentPath, String fileName) {
if (!StringUtils.hasText(fileName)) {
throw new IllegalArgumentException("文件名不能为空");
}
if (!StringUtils.hasText(parentPath) || "/".equals(parentPath)) {
return "/" + fileName;
}
// 确保父路径以 / 开头不以 / 结尾
if (!parentPath.startsWith("/")) {
parentPath = "/" + parentPath;
}
if (parentPath.endsWith("/") && !"/".equals(parentPath)) {
parentPath = parentPath.substring(0, parentPath.length() - 1);
}
return parentPath + "/" + fileName;
}
@Override
public String getParentPath(Long parentId, String difyDatasetId) {
if (parentId == null) {
return "/";
}
TDatasetFiles parentFile = datasetFilesMapper.selectById(parentId.intValue());
if (parentFile == null) {
return "/";
}
return parentFile.getPath();
}
/**
* 校验排序字段
*/
private String validateOrderBy(String orderBy) {
if (!StringUtils.hasText(orderBy)) {
return null;
}
if (!ALLOWED_ORDER_FIELDS.contains(orderBy.toLowerCase())) {
return null;
}
return orderBy.toLowerCase();
}
/**
* 校验排序方向
*/
private String validateOrderDirection(String orderDirection) {
if (!StringUtils.hasText(orderDirection)) {
return "ASC";
}
String direction = orderDirection.toUpperCase();
if (!"ASC".equals(direction) && !"DESC".equals(direction)) {
return "ASC";
}
return direction;
}
}

View File

@ -0,0 +1,188 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.bjtds.brichat.mapper.opengauss.TDatasetFilesMapper">
<!-- 结果映射 -->
<resultMap id="TDatasetFilesResultMap" type="com.bjtds.brichat.entity.dataset.TDatasetFiles">
<id column="id" property="id" jdbcType="INTEGER"/>
<result column="name" property="name" jdbcType="VARCHAR"/>
<result column="type" property="type" jdbcType="VARCHAR"/>
<result column="parent_id" property="parentId" jdbcType="BIGINT"/>
<result column="path" property="path" jdbcType="VARCHAR"/>
<result column="size" property="size" jdbcType="BIGINT"/>
<result column="owner_id" property="ownerId" jdbcType="BIGINT"/>
<result column="created_at" property="createdAt" jdbcType="TIMESTAMP"/>
<result column="updated_at" property="updatedAt" jdbcType="TIMESTAMP"/>
<result column="is_deleted" property="isDeleted" jdbcType="BOOLEAN"/>
<result column="mime_type" property="mimeType" jdbcType="VARCHAR"/>
<result column="extension" property="extension" jdbcType="VARCHAR"/>
<result column="dify_doc_id" property="difyDocId" jdbcType="VARCHAR"/>
<result column="indexing_status" property="indexingStatus" jdbcType="VARCHAR"/>
<result column="dify_dataset_id" property="difyDatasetId" jdbcType="VARCHAR"/>
</resultMap>
<!-- 基础 SQL 片段 -->
<sql id="Base_Column_List">
id, name, type, parent_id, path, size, owner_id, created_at, updated_at,
is_deleted, mime_type, extension, dify_doc_id, indexing_status, dify_dataset_id
</sql>
<!-- 根据知识库id和parent_id 分页查询 文件/文件夹 -->
<select id="selectByDatasetIdAndParentId" resultMap="TDatasetFilesResultMap">
SELECT <include refid="Base_Column_List"/>
FROM t_dataset_files
WHERE dify_dataset_id = #{difyDatasetId}
AND is_deleted = false
<if test="parentId != null">
AND parent_id = #{parentId}
</if>
<if test="parentId == null">
AND parent_id IS NULL
</if>
<if test="orderBy != null and orderBy != ''">
ORDER BY ${orderBy} ${orderDirection}
</if>
<if test="orderBy == null or orderBy == ''">
ORDER BY type DESC, name ASC
</if>
<if test="limit != null and limit > 0">
LIMIT #{limit}
</if>
<if test="offset != null and offset > 0">
OFFSET #{offset}
</if>
</select>
<!-- 根据知识库id和parent_id 查询总数 -->
<select id="countByDatasetIdAndParentId" resultType="int">
SELECT COUNT(*)
FROM t_dataset_files
WHERE dify_dataset_id = #{difyDatasetId}
AND is_deleted = false
<if test="parentId != null">
AND parent_id = #{parentId}
</if>
<if test="parentId == null">
AND parent_id IS NULL
</if>
</select>
<!-- 根据知识库id和name查询 文件/文件夹 -->
<select id="selectByDatasetIdAndName" resultMap="TDatasetFilesResultMap">
SELECT <include refid="Base_Column_List"/>
FROM t_dataset_files
WHERE dify_dataset_id = #{difyDatasetId}
AND is_deleted = false
AND name ILIKE CONCAT('%', #{name}, '%')
<if test="orderBy != null and orderBy != ''">
ORDER BY ${orderBy} ${orderDirection}
</if>
<if test="orderBy == null or orderBy == ''">
ORDER BY type DESC, name ASC
</if>
<if test="limit != null and limit > 0">
LIMIT #{limit}
</if>
<if test="offset != null and offset > 0">
OFFSET #{offset}
</if>
</select>
<!-- 根据知识库id和name查询总数 -->
<select id="countByDatasetIdAndName" resultType="int">
SELECT COUNT(*)
FROM t_dataset_files
WHERE dify_dataset_id = #{difyDatasetId}
AND is_deleted = false
AND name ILIKE CONCAT('%', #{name}, '%')
</select>
<!-- 根据ID查询文件/文件夹 -->
<select id="selectById" resultMap="TDatasetFilesResultMap">
SELECT <include refid="Base_Column_List"/>
FROM t_dataset_files
WHERE id = #{id} AND is_deleted = false
</select>
<!-- 插入文件/文件夹 -->
<insert id="insert" parameterType="com.bjtds.brichat.entity.dataset.TDatasetFiles" useGeneratedKeys="true" keyProperty="id">
INSERT INTO t_dataset_files (
name, type, parent_id, path, size, owner_id,
is_deleted, mime_type, extension, dify_doc_id,
indexing_status, dify_dataset_id
) VALUES (
#{name}, #{type}, #{parentId}, #{path}, #{size}, #{ownerId},
#{isDeleted}, #{mimeType}, #{extension}, #{difyDocId},
#{indexingStatus}, #{difyDatasetId}
)
</insert>
<!-- 更新文件/文件夹信息 -->
<update id="updateById" parameterType="com.bjtds.brichat.entity.dataset.TDatasetFiles">
UPDATE t_dataset_files
<set>
<if test="name != null">name = #{name},</if>
<if test="type != null">type = #{type},</if>
<if test="parentId != null">parent_id = #{parentId},</if>
<if test="path != null">path = #{path},</if>
<if test="size != null">size = #{size},</if>
<if test="ownerId != null">owner_id = #{ownerId},</if>
<if test="isDeleted != null">is_deleted = #{isDeleted},</if>
<if test="mimeType != null">mime_type = #{mimeType},</if>
<if test="extension != null">extension = #{extension},</if>
<if test="difyDocId != null">dify_doc_id = #{difyDocId},</if>
<if test="indexingStatus != null">indexing_status = #{indexingStatus},</if>
<if test="difyDatasetId != null">dify_dataset_id = #{difyDatasetId},</if>
</set>
WHERE id = #{id}
</update>
<!-- 逻辑删除文件/文件夹 -->
<update id="deleteById">
UPDATE t_dataset_files
SET is_deleted = true
WHERE id = #{id}
</update>
<!-- 根据父目录ID逻辑删除所有子文件/文件夹 -->
<update id="deleteByParentId">
UPDATE t_dataset_files
SET is_deleted = true
WHERE parent_id = #{parentId}
</update>
<!-- 重命名文件/文件夹 -->
<update id="renameById">
UPDATE t_dataset_files
SET name = #{newName}, path = #{newPath}
WHERE id = #{id}
</update>
<!-- 检查同一父目录下是否存在同名文件/文件夹 -->
<select id="checkNameExists" resultType="int">
SELECT COUNT(*)
FROM t_dataset_files
WHERE dify_dataset_id = #{difyDatasetId}
AND is_deleted = false
AND name = #{name}
<if test="parentId != null">
AND parent_id = #{parentId}
</if>
<if test="parentId == null">
AND parent_id IS NULL
</if>
<if test="excludeId != null">
AND id != #{excludeId}
</if>
</select>
<!-- 根据路径查询文件/文件夹 -->
<select id="selectByPath" resultMap="TDatasetFilesResultMap">
SELECT <include refid="Base_Column_List"/>
FROM t_dataset_files
WHERE path = #{path}
AND dify_dataset_id = #{difyDatasetId}
AND is_deleted = false
</select>
</mapper>