From f7def4651e48093c47008031a313c88df9811c88 Mon Sep 17 00:00:00 2001 From: Administrator <15274802129@163.com> Date: Tue, 30 Sep 2025 11:51:41 +0800 Subject: [PATCH] feat(ai): 新增知识文件刷新与删除功能 --- src/main/java/cc/mrbird/febs/ai/entity/AiKnowledgeFile.java | 6 + src/main/java/cc/mrbird/febs/ai/service/AiKnowledgeFileService.java | 6 + src/main/resources/templates/febs/views/modules/ai/knowledge/list.html | 43 ++++++++ src/main/java/cc/mrbird/febs/ai/controller/knowledge/AiKnowledgeController.java | 19 +++ src/main/java/cc/mrbird/febs/ai/service/impl/AiKnowledgeFileServiceImpl.java | 124 +++++++++++++++++++++++- src/main/java/cc/mrbird/febs/ai/quartz/KnowledgeJob.java | 24 ++++ src/main/java/cc/mrbird/febs/ai/util/KnowledgeBaseUtil.java | 73 ++++++++++++++ 7 files changed, 284 insertions(+), 11 deletions(-) diff --git a/src/main/java/cc/mrbird/febs/ai/controller/knowledge/AiKnowledgeController.java b/src/main/java/cc/mrbird/febs/ai/controller/knowledge/AiKnowledgeController.java index 6a41772..23d042b 100644 --- a/src/main/java/cc/mrbird/febs/ai/controller/knowledge/AiKnowledgeController.java +++ b/src/main/java/cc/mrbird/febs/ai/controller/knowledge/AiKnowledgeController.java @@ -14,6 +14,7 @@ import org.springframework.web.bind.annotation.*; import javax.validation.Valid; +import javax.validation.constraints.NotNull; import java.util.Map; /** @@ -51,4 +52,22 @@ return aiKnowledgeFileService.update(dto); } + + @GetMapping("refresh/{id}") + @ControllerEndpoint(operation = "刷新", exceptionMessage = "操作失败") + public FebsResponse refresh( + @NotNull(message = "{required}") @PathVariable String id + ) { + + return aiKnowledgeFileService.refresh(id); + } + + @GetMapping("delete/{id}") + @ControllerEndpoint(operation = "删除", exceptionMessage = "操作失败") + public FebsResponse delete( + @NotNull(message = "{required}") @PathVariable String id + ) { + + return aiKnowledgeFileService.delete(id); + } } diff --git a/src/main/java/cc/mrbird/febs/ai/entity/AiKnowledgeFile.java b/src/main/java/cc/mrbird/febs/ai/entity/AiKnowledgeFile.java index 3ba3990..01bc1c0 100644 --- a/src/main/java/cc/mrbird/febs/ai/entity/AiKnowledgeFile.java +++ b/src/main/java/cc/mrbird/febs/ai/entity/AiKnowledgeFile.java @@ -1,6 +1,7 @@ package cc.mrbird.febs.ai.entity; import cc.mrbird.febs.common.entity.AiBaseEntity; +import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; @@ -8,7 +9,7 @@ @TableName("ai_knowledge_file") public class AiKnowledgeFile extends AiBaseEntity { /** - * 状态:0-上传服务器,1-应用数据,2-知识库 + * 状态:0-上传服务器,1-应用数据,2-知识库 3-成功 */ private Integer state; /** @@ -34,4 +35,7 @@ * 公司ID */ private String companyId; + + @TableField(exist = false) + private String companyName; } diff --git a/src/main/java/cc/mrbird/febs/ai/quartz/KnowledgeJob.java b/src/main/java/cc/mrbird/febs/ai/quartz/KnowledgeJob.java new file mode 100644 index 0000000..e4764a7 --- /dev/null +++ b/src/main/java/cc/mrbird/febs/ai/quartz/KnowledgeJob.java @@ -0,0 +1,24 @@ +package cc.mrbird.febs.ai.quartz; + +import cc.mrbird.febs.ai.service.AiKnowledgeFileService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +public class KnowledgeJob { + + @Autowired + private AiKnowledgeFileService aiKnowledgeFileService; + + /** + * 订单失效 + * 五分钟运行一次 + */ + @Scheduled(cron = "0 0/5 * * * ? ") + public void refresh() { + aiKnowledgeFileService.refreshEvent(); + } +} diff --git a/src/main/java/cc/mrbird/febs/ai/service/AiKnowledgeFileService.java b/src/main/java/cc/mrbird/febs/ai/service/AiKnowledgeFileService.java index 5c62103..ccd9073 100644 --- a/src/main/java/cc/mrbird/febs/ai/service/AiKnowledgeFileService.java +++ b/src/main/java/cc/mrbird/febs/ai/service/AiKnowledgeFileService.java @@ -14,4 +14,10 @@ FebsResponse update(AiKnowledgeFile dto); void getAddKnowledge(String id); + + FebsResponse refresh(String id); + + FebsResponse delete(String id); + + void refreshEvent(); } diff --git a/src/main/java/cc/mrbird/febs/ai/service/impl/AiKnowledgeFileServiceImpl.java b/src/main/java/cc/mrbird/febs/ai/service/impl/AiKnowledgeFileServiceImpl.java index 68b811f..7d7e8f7 100644 --- a/src/main/java/cc/mrbird/febs/ai/service/impl/AiKnowledgeFileServiceImpl.java +++ b/src/main/java/cc/mrbird/febs/ai/service/impl/AiKnowledgeFileServiceImpl.java @@ -2,8 +2,6 @@ import cc.mrbird.febs.ai.entity.AiCompany; import cc.mrbird.febs.ai.entity.AiKnowledgeFile; -import cc.mrbird.febs.ai.entity.AiProduct; -import cc.mrbird.febs.ai.entity.AiProductCategory; import cc.mrbird.febs.ai.mapper.AiKnowledgeFileMapper; import cc.mrbird.febs.ai.service.AiCompanyService; import cc.mrbird.febs.ai.service.AiKnowledgeFileService; @@ -26,10 +24,10 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.Date; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.*; import java.util.stream.Collectors; @Slf4j @@ -50,6 +48,25 @@ } query.orderByDesc(AiKnowledgeFile::getCompanyId); Page<AiKnowledgeFile> pages = aiKnowledgeFileMapper.selectPage(page, query); + List<AiKnowledgeFile> records = pages.getRecords(); + if (CollUtil.isNotEmpty(records)){ + //stream流获取全部的公司ID + Set<String> companyIds = records.stream().map(AiKnowledgeFile::getCompanyId).collect(Collectors.toSet()); + List<String> companyIdList = new ArrayList<>(companyIds); + List<AiCompany> listById = aiCompanyService.getListById((companyIdList)); + Map<String, AiCompany> collect = listById.stream().collect(Collectors.toMap(AiCompany::getId, aiCompany -> aiCompany)); + for (AiKnowledgeFile aiKnowledgeFile : records){ + if (StrUtil.isEmpty(aiKnowledgeFile.getCompanyId())){ + aiKnowledgeFile.setCompanyName("公共"); + }else{ + if (StrUtil.isEmpty(aiKnowledgeFile.getCompanyId())){ + aiKnowledgeFile.setCompanyName("公共"); + }else{ + aiKnowledgeFile.setCompanyName(collect.get(aiKnowledgeFile.getCompanyId()).getName()); + } + } + } + } return pages; } @@ -60,6 +77,7 @@ entity.setId(UUID.getSimpleUUIDString()); entity.setCompanyId(dto.getCompanyId()); entity.setName(dto.getName()); + entity.setState(1); entity.setSavePath(dto.getSavePath()); entity.setCreatedTime(new Date()); this.save(entity); @@ -108,4 +126,98 @@ ); } + + @Override + public FebsResponse refresh(String id) { + AiKnowledgeFile aiKnowledgeFile = this.getById(id); + if (ObjectUtil.isNull(aiKnowledgeFile)){ + throw new FebsException("文件不存在"); + } + + Integer state = aiKnowledgeFile.getState(); + + if (state == 1){ + state = KnowledgeBaseUtil.getFileJobStatus(aiKnowledgeFile.getFileId()); + if ( state == 1) { + aiKnowledgeFileMapper.update(null, + Wrappers.lambdaUpdate(AiKnowledgeFile.class) + .set(AiKnowledgeFile::getState, 2) + .eq(AiKnowledgeFile::getId, aiKnowledgeFile.getId()) + ); + } + } + + if (state == 2){ + String knowledgeId = null; + if (StrUtil.isNotEmpty(aiKnowledgeFile.getCompanyId())){ + AiCompany aiCompany = aiCompanyService.getById(aiKnowledgeFile.getCompanyId()); + if (StrUtil.isNotEmpty(aiCompany.getCategoryId())){ + knowledgeId = aiCompany.getKnowledgeId(); + } + }else{ + knowledgeId = KnowledgeBaseUtil.DEFAULT_KNOWLEDGE_ID; + } + state = KnowledgeBaseUtil.getIndexKnowledgeJobStatus(aiKnowledgeFile.getFileId(),knowledgeId); + if ( state == 1) { + aiKnowledgeFileMapper.update(null, + Wrappers.lambdaUpdate(AiKnowledgeFile.class) + .set(AiKnowledgeFile::getState, 3) + .eq(AiKnowledgeFile::getId, aiKnowledgeFile.getId()) + ); + } + } + return new FebsResponse().success().message("操作成功"); + } + + @Override + public FebsResponse delete(String id) { + //如何从服务器上的位置删除对应的文件 + AiKnowledgeFile aiKnowledgeFile = this.getById(id); + if (ObjectUtil.isNull(aiKnowledgeFile)){ + throw new FebsException("文件不存在"); + } + //服务器删除 + try { + Path filePath = Paths.get(aiKnowledgeFile.getSavePath()); + boolean deleted = Files.deleteIfExists(filePath); + if (!deleted) { + throw new FebsException("文件删除成功"); + } + } catch (Exception e) { + throw new FebsException("删除文件时发生错误: " + e.getMessage()); + } + + //知识库删除 + String knowledgeId = null; + if (StrUtil.isNotEmpty(aiKnowledgeFile.getCompanyId())){ + AiCompany aiCompany = aiCompanyService.getById(aiKnowledgeFile.getCompanyId()); + if (StrUtil.isNotEmpty(aiCompany.getCategoryId())){ + knowledgeId = aiCompany.getKnowledgeId(); + } + }else{ + knowledgeId = KnowledgeBaseUtil.DEFAULT_KNOWLEDGE_ID; + } + KnowledgeBaseUtil.knowledgeFileDelete(aiKnowledgeFile.getFileId(), knowledgeId); + + //应用数据删除 + try { + KnowledgeBaseUtil.deleteFile(aiKnowledgeFile.getFileId()); + } catch (Exception e) { + throw new FebsException("应用数据删除时发生错误: " + e.getMessage()); + } + return new FebsResponse().success().message("操作成功"); + } + + @Override + public void refreshEvent() { + LambdaQueryWrapper<AiKnowledgeFile> queryWrapper = Wrappers.lambdaQuery(AiKnowledgeFile.class); + queryWrapper.ne(AiKnowledgeFile::getState, 3); + List<AiKnowledgeFile> list = aiKnowledgeFileMapper.selectList(queryWrapper); + if (CollUtil.isEmpty( list)){ + for (AiKnowledgeFile aiKnowledgeFile : list){ + refresh(aiKnowledgeFile.getId()); + } + } + + } } diff --git a/src/main/java/cc/mrbird/febs/ai/util/KnowledgeBaseUtil.java b/src/main/java/cc/mrbird/febs/ai/util/KnowledgeBaseUtil.java index 9811090..3ea8e89 100644 --- a/src/main/java/cc/mrbird/febs/ai/util/KnowledgeBaseUtil.java +++ b/src/main/java/cc/mrbird/febs/ai/util/KnowledgeBaseUtil.java @@ -9,12 +9,10 @@ import java.io.FileInputStream; import java.net.HttpURLConnection; import java.net.URL; -import java.nio.file.Paths; import java.security.MessageDigest; import java.util.Collections; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.TimeUnit; public class KnowledgeBaseUtil { public static final String ACCESS_KEY_ID = "LTAI5tCyQRwhZ2eimxCFKbdq"; @@ -61,6 +59,20 @@ // 调用添加分类API接口 AddCategoryResponse addCategoryResponse = client.addCategoryWithOptions(WORKSPACE_ID, addCategoryRequest, headers, runtime); return addCategoryResponse.getBody().getData().getCategoryId(); + } + + + /** + * 永久删除应用数据中的指定文件 + * @param fileId 文件 ID + * @throws Exception 当API调用失败或其他异常情况时抛出 + */ + public static void deleteFile(String fileId) throws Exception { + com.aliyun.bailian20231229.Client client = KnowledgeBaseUtil.createClient(); + com.aliyun.teautil.models.RuntimeOptions runtime = new com.aliyun.teautil.models.RuntimeOptions(); + java.util.Map<String, String> headers = new java.util.HashMap<>(); + client.deleteFileWithOptions(fileId, WORKSPACE_ID, headers, runtime); + return; } /** @@ -242,7 +254,7 @@ * @param indexId 知识库ID * @return 阿里云百炼服务的响应对象 */ - public static GetIndexJobStatusResponse getIndexJobStatus(com.aliyun.bailian20231229.Client client, String workspaceId, String jobId, String indexId) throws Exception { + public static GetIndexJobStatusResponse getFileJobStatus(com.aliyun.bailian20231229.Client client, String workspaceId, String jobId, String indexId) throws Exception { Map<String, String> headers = new HashMap<>(); com.aliyun.bailian20231229.models.GetIndexJobStatusRequest getIndexJobStatusRequest = new com.aliyun.bailian20231229.models.GetIndexJobStatusRequest(); getIndexJobStatusRequest.setIndexId(indexId); @@ -381,6 +393,61 @@ } } + public static int getFileJobStatus(String fileId) { + int state = 0; + // 步骤1:初始化客户端(Client) + System.out.println("步骤1:创建Client"); + com.aliyun.bailian20231229.Client client = null; + try { + client = createClient(); + // 步骤6:检查更新后的文件状态 + System.out.println("步骤6:检查阿里云百炼中的文件状态"); + DescribeFileResponse describeResponse = describeFile(client, WORKSPACE_ID, fileId); + String status = describeResponse.getBody().getData().getStatus(); + System.out.println("当前文件状态:" + status); + if ("PARSE_SUCCESS".equals(status)) { + state = 1; + } + } catch (Exception e) { + e.printStackTrace(); + } + return state; + } + + public static int getIndexKnowledgeJobStatus(String jobId, String indexId) { + int state = 0; + // 步骤1:初始化客户端(Client) + System.out.println("步骤1:创建Client"); + com.aliyun.bailian20231229.Client client = null; + try { + client = createClient(); + System.out.println("步骤8:等待追加任务完成"); + GetIndexJobStatusResponse jobStatusResponse = getFileJobStatus(client, WORKSPACE_ID, jobId, indexId); + String status = jobStatusResponse.getBody().getData().getStatus(); + System.out.println("当前索引任务状态:" + status); + if ("COMPLETED".equals(status)) { + state = 1; + } + } catch (Exception e) { + e.printStackTrace(); + } + return state; + } + + + public static void knowledgeFileDelete(String fileId, String indexId) { + com.aliyun.bailian20231229.Client client = null; + try { + System.out.println("步骤1:创建Client"); + client = createClient(); + // 步骤9:删除旧文件 + System.out.println("步骤9:删除旧文件"); + deleteIndexDocument(client, WORKSPACE_ID, indexId, fileId); + } catch (Exception e) { + e.printStackTrace(); + } + } + /** * 向一个文档类知识库追加导入已解析的文件 * diff --git a/src/main/resources/templates/febs/views/modules/ai/knowledge/list.html b/src/main/resources/templates/febs/views/modules/ai/knowledge/list.html index 71aa3da..f514e74 100644 --- a/src/main/resources/templates/febs/views/modules/ai/knowledge/list.html +++ b/src/main/resources/templates/febs/views/modules/ai/knowledge/list.html @@ -50,7 +50,8 @@ </script> <script type="text/html" id="aiKnowledgeFileOption"> - <button class="layui-btn layui-btn-normal layui-btn-sm" type="button" shiro:hasPermission="knowledgeList:info" lay-event="aiKnowledgeFileInfoEvent">编辑</button> + <button class="layui-btn layui-btn-normal layui-btn-sm" type="button" shiro:hasPermission="knowledgeList:add" lay-event="aiKnowledgeFileRefreshEvent">刷新</button> + <button class="layui-btn layui-btn-danger layui-btn-sm" type="button" shiro:hasPermission="knowledgeList:add" lay-event="aiKnowledgeFileDeleteEvent">删除</button> </script> @@ -102,7 +103,41 @@ }); } + if (layEvent === 'aiKnowledgeFileRefreshEvent') { + if (data.state == 3){ + febs.alert.success('文件已成功解析'); + return; + } + febs.modal.confirm('刷新', '确认刷新?', function () { + aiKnowledgeFileRefreshEvent(data.id); + }); + } + + if (layEvent === 'aiKnowledgeFileDeleteEvent') { + if (data.state != 3){ + febs.alert.error('文件解析中,不能中断操作'); + return; + } + febs.modal.confirm('删除', '确认删除?', function () { + aiKnowledgeFileDeleteEvent(data.id); + }); + } + }); + + function aiKnowledgeFileDeleteEvent(id) { + febs.get(ctx + 'admin/aiKnowledgeFile/delete/' + id, null, function (data) { + febs.alert.success(data.message); + $query.click(); + }); + } + + function aiKnowledgeFileRefreshEvent(id) { + febs.get(ctx + 'admin/aiKnowledgeFile/refresh/' + id, null, function (data) { + febs.alert.success(data.message); + $query.click(); + }); + } // 初始化表格操作栏各个按钮功能 table.on('toolbar(aiKnowledgeFileTable)', function (obj) { @@ -134,7 +169,13 @@ {type: 'numbers', title: '', width: 80}, {title: '操作', toolbar: '#aiKnowledgeFileOption', minWidth: 200, align: 'center'}, {field: 'id', title: 'ID', minWidth: 100,align:'center'}, + {field: 'companyName', title: '公司', minWidth: 100,align:'center'}, {field: 'name', title: '名称', minWidth: 100,align:'center'}, + + {field: 'state', title: '状态', minWidth: 100,align:'center', templet: function(d) { + var stateMap = {'0': '上传服务器', '1': '应用数据应用中', '2': '知识库应用中', '3': '成功'}; + return stateMap[d.state] || '未知'; + }} ]] }); } -- Gitblit v1.9.1