diff --git a/src/main/java/com/gxwebsoft/ai/config/KnowledgeBaseConfig.java b/src/main/java/com/gxwebsoft/ai/config/KnowledgeBaseConfig.java index d34d1ae..5191617 100644 --- a/src/main/java/com/gxwebsoft/ai/config/KnowledgeBaseConfig.java +++ b/src/main/java/com/gxwebsoft/ai/config/KnowledgeBaseConfig.java @@ -12,5 +12,5 @@ public class KnowledgeBaseConfig { private String accessKeyId; private String accessKeySecret; private String workspaceId; - + private String topCategoryId; } \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/controller/AiCloudDocController.java b/src/main/java/com/gxwebsoft/ai/controller/AiCloudDocController.java new file mode 100644 index 0000000..f243e09 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/controller/AiCloudDocController.java @@ -0,0 +1,141 @@ +package com.gxwebsoft.ai.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.ai.service.AiCloudDocService; + +import cn.hutool.core.util.StrUtil; + +import com.gxwebsoft.ai.entity.AiCloudDoc; +import com.gxwebsoft.ai.param.AiCloudDocParam; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.system.entity.User; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; + +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * AI云文档目录表控制器 + * + * @author yc + */ +@Tag(name = "AI云文档目录表管理") +@RestController +@RequestMapping("/api/ai/doc") +public class AiCloudDocController extends BaseController { + @Resource + private AiCloudDocService aiCloudDocService; + + //@PreAuthorize("hasAuthority('ai:aiCloudDoc:list')") + @Operation(summary = "分页查询AI云文档目录表") + @GetMapping("/page") + public ApiResult> page(AiCloudDocParam param) { + // 使用关联查询 + return success(aiCloudDocService.pageRel(param)); + } + + //@PreAuthorize("hasAuthority('ai:aiCloudDoc:list')") + @Operation(summary = "查询全部AI云文档目录表") + @GetMapping() + public ApiResult> list(AiCloudDocParam param) { + // 使用关联查询 + return success(aiCloudDocService.listRel(param)); + } + + //@PreAuthorize("hasAuthority('ai:aiCloudDoc:list')") + @Operation(summary = "根据id查询AI云文档目录表") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + // 使用关联查询 + return success(aiCloudDocService.getByIdRel(id)); + } + + //@PreAuthorize("hasAuthority('ai:aiCloudDoc:list')") + @Operation(summary = "根据ids查询AI云文档目录表") + @GetMapping("/byIds/{ids}") + public ApiResult> getByIds(@PathVariable("ids") String ids) { + List idList = StrUtil.split(ids, ','); + List ret = aiCloudDocService.list(new LambdaQueryWrapper().in(AiCloudDoc::getId, idList)); + return success(ret); + } + + //@PreAuthorize("hasAuthority('ai:aiCloudDoc:save')") + @Operation(summary = "添加AI云文档目录表") + @PostMapping() + public ApiResult save(@RequestBody AiCloudDoc aiCloudDoc) { + // 记录当前登录用户id + User loginUser = getLoginUser(); + if (loginUser != null) { + aiCloudDoc.setUserId(loginUser.getUserId()); + } + if(aiCloudDoc.getParentId()>0) { + AiCloudDoc aiCloudDocParent = aiCloudDocService.getByIdRel(aiCloudDoc.getParentId()); + aiCloudDoc.setCategoryId(aiCloudDocParent.getCategoryId()); + aiCloudDoc.setCompanyId(aiCloudDocParent.getCompanyId()); + } + if (aiCloudDocService.save(aiCloudDoc)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + //@PreAuthorize("hasAuthority('ai:aiCloudDoc:update')") + @Operation(summary = "修改AI云文档目录表") + @PutMapping() + public ApiResult update(@RequestBody AiCloudDoc aiCloudDoc) { + if (aiCloudDocService.updateById(aiCloudDoc)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + //@PreAuthorize("hasAuthority('ai:aiCloudDoc:remove')") + @Operation(summary = "删除AI云文档目录表") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (aiCloudDocService.removeById(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + //@PreAuthorize("hasAuthority('ai:aiCloudDoc:save')") + @Operation(summary = "批量添加AI云文档目录表") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (aiCloudDocService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + //@PreAuthorize("hasAuthority('ai:aiCloudDoc:update')") + @Operation(summary = "批量修改AI云文档目录表") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(aiCloudDocService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + //@PreAuthorize("hasAuthority('ai:aiCloudDoc:remove')") + @Operation(summary = "批量删除AI云文档目录表") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (aiCloudDocService.removeByIds(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + +} diff --git a/src/main/java/com/gxwebsoft/ai/controller/AiCloudFileController.java b/src/main/java/com/gxwebsoft/ai/controller/AiCloudFileController.java new file mode 100644 index 0000000..3df59c0 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/controller/AiCloudFileController.java @@ -0,0 +1,209 @@ +package com.gxwebsoft.ai.controller; + +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.ai.service.AiCloudFileService; +import com.gxwebsoft.ai.entity.AiCloudFile; +import com.gxwebsoft.ai.param.AiCloudFileParam; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.system.entity.User; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; + +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import javax.annotation.Resource; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +/** + * AI云文件表控制器 + * + * @author yc + */ +@Tag(name = "AI云文件表管理") +@RestController +@RequestMapping("/api/ai/file") +public class AiCloudFileController extends BaseController { + + @Resource + private AiCloudFileService aiCloudFileService; + + //@PreAuthorize("hasAuthority('ai:aiCloudFile:list')") + @Operation(summary = "分页查询AI云文件表") + @GetMapping("/page") + public ApiResult> page(AiCloudFileParam param) { + return success(aiCloudFileService.pageRel(param)); + } + + //@PreAuthorize("hasAuthority('ai:aiCloudFile:list')") + @Operation(summary = "查询全部AI云文件表") + @GetMapping() + public ApiResult> list(AiCloudFileParam param) { + return success(aiCloudFileService.listRel(param)); + } + + //@PreAuthorize("hasAuthority('ai:aiCloudFile:list')") + @Operation(summary = "根据id查询AI云文件表") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { + return success(aiCloudFileService.getByIdRel(id)); + } + + //@PreAuthorize("hasAuthority('ai:aiCloudFile:list')") + @Operation(summary = "根据ids查询AI云文件表") + @GetMapping("/byIds/{ids}") + public ApiResult> getByIds(@PathVariable("ids") String ids) { + List idList = cn.hutool.core.util.StrUtil.split(ids, ','); + List ret = aiCloudFileService.list(new LambdaQueryWrapper().in(AiCloudFile::getId, idList)); + return success(ret); + } + + //@PreAuthorize("hasAuthority('ai:aiCloudFile:save')") + @Operation(summary = "添加AI云文件表") + @PostMapping() + public ApiResult save(@RequestBody AiCloudFile aiCloudFile) { + User loginUser = getLoginUser(); + if (loginUser != null) { + aiCloudFile.setUserId(loginUser.getUserId()); + } + if (aiCloudFileService.save(aiCloudFile)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + //@PreAuthorize("hasAuthority('ai:aiCloudFile:update')") + @Operation(summary = "修改AI云文件表") + @PutMapping() + public ApiResult update(@RequestBody AiCloudFile aiCloudFile) { + if (aiCloudFileService.updateById(aiCloudFile)) { + return success("修改成功"); + } + return fail("修改失败"); + } + + //@PreAuthorize("hasAuthority('ai:aiCloudFile:remove')") + @Operation(summary = "删除AI云文件表") + @DeleteMapping("/{id}") + public ApiResult remove(@PathVariable("id") Integer id) { + if (aiCloudFileService.removeFileWithCloudAndIndex(id)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + //@PreAuthorize("hasAuthority('ai:aiCloudFile:save')") + @Operation(summary = "批量添加AI云文件表") + @PostMapping("/batch") + public ApiResult saveBatch(@RequestBody List list) { + if (aiCloudFileService.saveBatch(list)) { + return success("添加成功"); + } + return fail("添加失败"); + } + + //@PreAuthorize("hasAuthority('ai:aiCloudFile:update')") + @Operation(summary = "批量修改AI云文件表") + @PutMapping("/batch") + public ApiResult removeBatch(@RequestBody BatchParam batchParam) { + if (batchParam.update(aiCloudFileService, "id")) { + return success("修改成功"); + } + return fail("修改失败"); + } + + //@PreAuthorize("hasAuthority('ai:aiCloudFile:remove')") + @Operation(summary = "批量删除AI云文件表") + @DeleteMapping("/batch") + public ApiResult removeBatch(@RequestBody List ids) { + if (aiCloudFileService.removeFilesWithCloudAndIndex(ids)) { + return success("删除成功"); + } + return fail("删除失败"); + } + + @Operation(summary = "上传文档到云中心") + @PostMapping("/uploadFiles") + public ApiResult uploadFiles(@RequestParam Integer docId, @RequestParam String categoryId, @RequestParam("files") MultipartFile[] files) { + try { + if (files == null || files.length == 0) { + return fail("请选择要上传的文件"); + } + + User loginUser = getLoginUser(); + + List> futures = new ArrayList<>(); + List successFiles = new ArrayList<>(); + List failedFiles = new ArrayList<>(); + + // 收集所有异步任务 + for (MultipartFile file : files) { + if (file.isEmpty()) { + continue; + } + if (file.getSize() > 100 * 1024 * 1024) { + failedFiles.add(file.getOriginalFilename() + "(文件过大)"); + continue; + } + + // 调用Service层的异步方法并收集Future + CompletableFuture future = aiCloudFileService.processFileAsync(categoryId, docId, file, loginUser); + futures.add(future); + } + + // 等待所有异步任务完成 + CompletableFuture allFutures = CompletableFuture.allOf( + futures.toArray(new CompletableFuture[0]) + ); + + // 获取所有任务结果 + CompletableFuture> allResults = allFutures.thenApply(v -> + futures.stream() + .map(CompletableFuture::join) + .collect(Collectors.toList()) + ); + + // 等待所有任务完成并处理结果 + List results = allResults.get(10, TimeUnit.MINUTES); // 设置10分钟超时 + + List fileIds = new ArrayList<>(); + // 统计成功和失败的文件 + for (int i = 0; i < futures.size(); i++) { + CompletableFuture future = futures.get(i); + try { + String fileId = future.get(); + fileIds.add(fileId); + successFiles.add(files[i].getOriginalFilename()); + } catch (Exception e) { + failedFiles.add(files[i].getOriginalFilename() + "(" + e.getCause().getMessage() + ")"); + } + } + + // 构建返回消息 + StringBuilder message = new StringBuilder("文件上传完成"); + if (!successFiles.isEmpty()) { + message.append(",成功上传: ").append(successFiles.size()).append("个"); + } + if (!failedFiles.isEmpty()) { + message.append(",失败: ").append(String.join("、", failedFiles)); + } + + // 返回前提交上传的文档到单位默认知识库 + aiCloudFileService.submitDocuments(docId, fileIds); + + return success(message.toString()); + + } catch (Exception e) { + return fail("上传失败: " + e.getMessage()); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/entity/AiCloudDoc.java b/src/main/java/com/gxwebsoft/ai/entity/AiCloudDoc.java new file mode 100644 index 0000000..280dca1 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/entity/AiCloudDoc.java @@ -0,0 +1,61 @@ +package com.gxwebsoft.ai.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * AI云文档目录表 + * + * @author yc + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "AiCloudDoc对象", description = "AI云文档目录表") +public class AiCloudDoc implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "云目录ID") + private String categoryId; + + @Schema(description = "单位ID") + private Integer companyId; + + @Schema(description = "上级目录ID") + private Integer parentId; + + @Schema(description = "目录名称") + private String name; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "状态, 0正常, 1冻结") + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "创建用户ID") + private Integer userId; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + private LocalDateTime updateTime; + +} diff --git a/src/main/java/com/gxwebsoft/ai/entity/AiCloudFile.java b/src/main/java/com/gxwebsoft/ai/entity/AiCloudFile.java new file mode 100644 index 0000000..2fe1db2 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/entity/AiCloudFile.java @@ -0,0 +1,76 @@ +package com.gxwebsoft.ai.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; +import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * AI云文件表 + * + * @author yc + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Schema(name = "AiCloudFile对象", description = "AI云文件表") +public class AiCloudFile implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @Schema(description = "文档目录ID") + private Integer docId; + + @Schema(description = "文件名") + private String fileName; + + @Schema(description = "文件大小(字节)") + private Long fileSize; + + @Schema(description = "文件类型") + private String fileType; + + @Schema(description = "工作空间ID") + private String workspaceId; + + @Schema(description = "云文件ID") + private String fileId; + + @Schema(description = "文件地址URL") + private String fileUrl; + + @Schema(description = "文件扩展名") + private String fileExt; + + @Schema(description = "上传时间") + private LocalDateTime uploadTime; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "状态, 0正常, 1冻结") + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @TableLogic + private Integer deleted; + + @Schema(description = "上传用户ID") + private Integer userId; + + @Schema(description = "租户id") + private Integer tenantId; + + @Schema(description = "创建时间") + private LocalDateTime createTime; + + @Schema(description = "修改时间") + private LocalDateTime updateTime; + +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/mapper/AiCloudDocMapper.java b/src/main/java/com/gxwebsoft/ai/mapper/AiCloudDocMapper.java new file mode 100644 index 0000000..4a73b3b --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/mapper/AiCloudDocMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.ai.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.ai.entity.AiCloudDoc; +import com.gxwebsoft.ai.param.AiCloudDocParam; + +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * AI云文档目录表Mapper + * + * @author yc + */ +public interface AiCloudDocMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") AiCloudDocParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") AiCloudDocParam param); + +} diff --git a/src/main/java/com/gxwebsoft/ai/mapper/AiCloudFileMapper.java b/src/main/java/com/gxwebsoft/ai/mapper/AiCloudFileMapper.java new file mode 100644 index 0000000..3eb8586 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/mapper/AiCloudFileMapper.java @@ -0,0 +1,37 @@ +package com.gxwebsoft.ai.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.gxwebsoft.ai.entity.AiCloudFile; +import com.gxwebsoft.ai.param.AiCloudFileParam; + +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * AI云文件表Mapper + * + * @author yc + */ +public interface AiCloudFileMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页对象 + * @param param 查询参数 + * @return List + */ + List selectPageRel(@Param("page") IPage page, + @Param("param") AiCloudFileParam param); + + /** + * 查询全部 + * + * @param param 查询参数 + * @return List + */ + List selectListRel(@Param("param") AiCloudFileParam param); + +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/mapper/xml/AiCloudDocMapper.xml b/src/main/java/com/gxwebsoft/ai/mapper/xml/AiCloudDocMapper.xml new file mode 100644 index 0000000..be5a5a5 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/mapper/xml/AiCloudDocMapper.xml @@ -0,0 +1,63 @@ + + + + + + + SELECT a.* + FROM ai_cloud_doc a + + + AND a.id = #{param.id} + + + AND a.category_id = #{param.categoryId} + + + AND a.company_id = #{param.companyId} + + + AND a.parent_id = #{param.parentId} + + + AND a.name LIKE CONCAT('%', #{param.name}, '%') + + + AND a.sort_number = #{param.sortNumber} + + + AND a.status = #{param.status} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.user_id = #{param.userId} + + + AND a.create_time >= #{param.createTimeStart} + + + AND a.create_time <= #{param.createTimeEnd} + + + AND (a.name LIKE CONCAT('%', #{param.keywords}, '%') + OR a.comments LIKE CONCAT('%', #{param.keywords}, '%')) + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/mapper/xml/AiCloudFileMapper.xml b/src/main/java/com/gxwebsoft/ai/mapper/xml/AiCloudFileMapper.xml new file mode 100644 index 0000000..b19363a --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/mapper/xml/AiCloudFileMapper.xml @@ -0,0 +1,73 @@ + + + + + + + SELECT a.* + FROM ai_cloud_file a + + + AND a.id = #{param.id} + + + AND a.doc_id = #{param.docId} + + + AND a.file_name LIKE CONCAT('%', #{param.fileName}, '%') + + + AND a.file_size = #{param.fileSize} + + + AND a.file_type = #{param.fileType} + + + AND a.workspace_id = #{param.workspaceId} + + + AND a.file_id = #{param.fileId} + + + AND a.file_ext = #{param.fileExt} + + + AND a.upload_time >= #{param.uploadTimeStart} + + + AND a.upload_time <= #{param.uploadTimeEnd} + + + AND a.sort_number = #{param.sortNumber} + + + AND a.status = #{param.status} + + + AND a.deleted = #{param.deleted} + + + AND a.deleted = 0 + + + AND a.user_id = #{param.userId} + + + AND (a.file_name LIKE CONCAT('%', #{param.keywords}, '%') + OR a.file_type LIKE CONCAT('%', #{param.keywords}, '%') + OR a.file_ext LIKE CONCAT('%', #{param.keywords}, '%')) + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/param/AiCloudDocParam.java b/src/main/java/com/gxwebsoft/ai/param/AiCloudDocParam.java new file mode 100644 index 0000000..f09aade --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/param/AiCloudDocParam.java @@ -0,0 +1,57 @@ +package com.gxwebsoft.ai.param; + +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * AI云文档目录表查询参数 + * + * @author yc + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "AiCloudDocParam对象", description = "AI云文档目录表查询参数") +public class AiCloudDocParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "云目录ID") + @QueryField(type = QueryType.EQ) + private String categoryId; + + @Schema(description = "单位ID") + @QueryField(type = QueryType.EQ) + private Integer companyId; + + @Schema(description = "上级目录ID") + @QueryField(type = QueryType.EQ) + private Integer parentId; + + @Schema(description = "目录名称") + private String name; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "状态, 0正常, 1冻结") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + + @Schema(description = "创建用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + +} diff --git a/src/main/java/com/gxwebsoft/ai/param/AiCloudFileParam.java b/src/main/java/com/gxwebsoft/ai/param/AiCloudFileParam.java new file mode 100644 index 0000000..85c6427 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/param/AiCloudFileParam.java @@ -0,0 +1,74 @@ +package com.gxwebsoft.ai.param; + +import com.gxwebsoft.common.core.annotation.QueryField; +import com.gxwebsoft.common.core.annotation.QueryType; +import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * AI云文件表查询参数 + * + * @author yc + */ +@Data +@EqualsAndHashCode(callSuper = false) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(name = "AiCloudFileParam对象", description = "AI云文件表查询参数") +public class AiCloudFileParam extends BaseParam { + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + @QueryField(type = QueryType.EQ) + private Integer id; + + @Schema(description = "文档目录ID") + @QueryField(type = QueryType.EQ) + private Integer docId; + + @Schema(description = "文件名") + private String fileName; + + @Schema(description = "文件大小(字节)") + private Long fileSize; + + @Schema(description = "文件类型") + private String fileType; + + @Schema(description = "工作空间ID") + private String workspaceId; + + @Schema(description = "云文件ID") + private String fileId; + + @Schema(description = "文件扩展名") + private String fileExt; + + @Schema(description = "上传时间开始") + private LocalDateTime uploadTimeStart; + + @Schema(description = "上传时间结束") + private LocalDateTime uploadTimeEnd; + + @Schema(description = "排序(数字越小越靠前)") + private Integer sortNumber; + + @Schema(description = "状态, 0正常, 1冻结") + @QueryField(type = QueryType.EQ) + private Integer status; + + @Schema(description = "是否删除, 0否, 1是") + @QueryField(type = QueryType.EQ) + private Integer deleted; + + @Schema(description = "上传用户ID") + @QueryField(type = QueryType.EQ) + private Integer userId; + + @Schema(description = "关键词搜索") + private String keywords; +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/service/AiCloudDocService.java b/src/main/java/com/gxwebsoft/ai/service/AiCloudDocService.java new file mode 100644 index 0000000..d21997e --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/service/AiCloudDocService.java @@ -0,0 +1,49 @@ +package com.gxwebsoft.ai.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.ai.entity.AiCloudDoc; +import com.gxwebsoft.ai.param.AiCloudDocParam; +import com.gxwebsoft.common.core.web.PageResult; + +import java.util.List; + +/** + * AI云文档目录表Service + * + * @author yc + */ +public interface AiCloudDocService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(AiCloudDocParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(AiCloudDocParam param); + + /** + * 根据id查询 + * + * @param id ID + * @return AiCloudDoc + */ + AiCloudDoc getByIdRel(Integer id); + + /** + * 根据目录ID获取本身及所有子孙目录 + * + * @param docId 目录ID + * @return List 目录列表 + */ + List getSelfAndChildren(Integer docId); + +} diff --git a/src/main/java/com/gxwebsoft/ai/service/AiCloudFileService.java b/src/main/java/com/gxwebsoft/ai/service/AiCloudFileService.java new file mode 100644 index 0000000..743a5b2 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/service/AiCloudFileService.java @@ -0,0 +1,113 @@ +package com.gxwebsoft.ai.service; + +import com.gxwebsoft.common.system.entity.User; +import org.springframework.web.multipart.MultipartFile; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gxwebsoft.ai.entity.AiCloudFile; +import com.gxwebsoft.ai.param.AiCloudFileParam; +import com.gxwebsoft.common.core.web.PageResult; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +/** + * AI云文件表Service + * + * @author yc + */ +public interface AiCloudFileService extends IService { + + /** + * 分页关联查询 + * + * @param param 查询参数 + * @return PageResult + */ + PageResult pageRel(AiCloudFileParam param); + + /** + * 关联查询全部 + * + * @param param 查询参数 + * @return List + */ + List listRel(AiCloudFileParam param); + + /** + * 根据id查询 + * + * @param id ID + * @return AiCloudFile + */ + AiCloudFile getByIdRel(Integer id); + + /** + * 异步处理文件上传 + * + * @param client 阿里云客户端 + * @param workspaceId 工作空间ID + * @param categoryId 分类ID + * @param docId 文档ID + * @param file 文件 + * @param loginUser 登录用户 + * @return CompletableFuture 返回包含文件ID的Future + */ + CompletableFuture processFileAsync(String categoryId, Integer docId, MultipartFile file, User loginUser); + + /** + * 删除文件(包括云存储和数据库记录) + * + * @param id 文件ID + * @return 是否删除成功 + */ + boolean removeFileWithCloud(Integer id); + + /** + * 批量删除文件(包括云存储和数据库记录) + * + * @param ids 文件ID列表 + * @return 是否删除成功 + */ + boolean removeFilesWithCloud(List ids); + + /** + * 获取文件扩展名 + * + * @param filename 文件名 + * @return 文件扩展名 + */ + String getFileExtension(String filename); + + /** + * 获取公司知识库ID + * + * @param docId 文档ID + * @return 知识库ID + */ + String getCompanyKbId(Integer docId); + + /** + * 提交文档到知识库 + * + * @param docId 文档ID + * @param fileIds 文件ID列表 + */ + void submitDocuments(Integer docId, List fileIds); + + /** + * 删除文件(包括云存储、数据库记录和知识库索引) + * + * @param id 文件ID + * @return 是否删除成功 + */ + boolean removeFileWithCloudAndIndex(Integer id); + + /** + * 批量删除文件(包括云存储、数据库记录和知识库索引) + * + * @param ids 文件ID列表 + * @return 是否删除成功 + */ + boolean removeFilesWithCloudAndIndex(List ids); +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/service/KnowledgeBaseService.java b/src/main/java/com/gxwebsoft/ai/service/KnowledgeBaseService.java index 6b325be..42a02da 100644 --- a/src/main/java/com/gxwebsoft/ai/service/KnowledgeBaseService.java +++ b/src/main/java/com/gxwebsoft/ai/service/KnowledgeBaseService.java @@ -48,4 +48,9 @@ public interface KnowledgeBaseService { * 上传知识库文档 */ boolean uploadDocuments(String kbId, MultipartFile[] files); + + /** + * 知识库追加导入已解析的文档 + */ + void submitDocuments(String kbId, String fileId); } \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/service/impl/AiCloudDocServiceImpl.java b/src/main/java/com/gxwebsoft/ai/service/impl/AiCloudDocServiceImpl.java new file mode 100644 index 0000000..a3924f2 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/service/impl/AiCloudDocServiceImpl.java @@ -0,0 +1,90 @@ +package com.gxwebsoft.ai.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.ai.mapper.AiCloudDocMapper; +import com.gxwebsoft.ai.service.AiCloudDocService; +import com.gxwebsoft.ai.entity.AiCloudDoc; +import com.gxwebsoft.ai.param.AiCloudDocParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * AI云文档目录表Service实现 + * + * @author yc + */ +@Service +public class AiCloudDocServiceImpl extends ServiceImpl implements AiCloudDocService { + + @Override + public PageResult pageRel(AiCloudDocParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, create_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(AiCloudDocParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, create_time desc"); + return page.sortRecords(list); + } + + @Override + public AiCloudDoc getByIdRel(Integer id) { + AiCloudDocParam param = new AiCloudDocParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + + @Override + public List getSelfAndChildren(Integer docId) { + // 查询所有未删除的目录 + AiCloudDocParam param = new AiCloudDocParam(); + param.setDeleted(0); + List allDocs = this.listRel(param); + + // 查找指定目录 + AiCloudDoc targetDoc = allDocs.stream() + .filter(doc -> doc.getId().equals(docId)) + .findFirst() + .orElse(null); + + if (targetDoc == null) { + return new ArrayList<>(); + } + + // 递归获取所有子孙目录 + List result = new ArrayList<>(); + result.add(targetDoc); + getChildrenRecursive(allDocs, targetDoc.getId(), result); + + return result; + } + + /** + * 递归获取所有子目录 + * + * @param allDocs 所有目录列表 + * @param parentId 父目录ID + * @param result 结果列表 + */ + private void getChildrenRecursive(List allDocs, Integer parentId, List result) { + List children = allDocs.stream() + .filter(doc -> parentId.equals(doc.getParentId())) + .collect(Collectors.toList()); + + for (AiCloudDoc child : children) { + result.add(child); + getChildrenRecursive(allDocs, child.getId(), result); + } + } +} diff --git a/src/main/java/com/gxwebsoft/ai/service/impl/AiCloudFileServiceImpl.java b/src/main/java/com/gxwebsoft/ai/service/impl/AiCloudFileServiceImpl.java new file mode 100644 index 0000000..b1fc80f --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/service/impl/AiCloudFileServiceImpl.java @@ -0,0 +1,225 @@ +package com.gxwebsoft.ai.service.impl; + +import com.aliyun.bailian20231229.Client; +import com.aliyun.bailian20231229.models.AddFileResponse; +import com.gxwebsoft.ai.config.KnowledgeBaseConfig; +import com.gxwebsoft.ai.factory.KnowledgeBaseClientFactory; +import com.gxwebsoft.ai.util.AiCloudDataCenterUtil; +import com.gxwebsoft.ai.util.AiCloudKnowledgeBaseUtil; +import com.gxwebsoft.common.system.entity.User; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.ai.mapper.AiCloudFileMapper; +import com.gxwebsoft.ai.service.AiCloudFileService; +import com.gxwebsoft.ai.entity.AiCloudFile; +import com.gxwebsoft.ai.entity.AiCloudDoc; +import com.gxwebsoft.ai.service.AiCloudDocService; +import com.gxwebsoft.ai.service.KnowledgeBaseService; +import com.gxwebsoft.ai.param.AiCloudFileParam; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.oa.entity.OaCompany; +import com.gxwebsoft.oa.service.OaCompanyService; + +import javax.annotation.Resource; +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +/** + * AI云文件表Service实现 + * + * @author yc + */ +@Slf4j +@Service +public class AiCloudFileServiceImpl extends ServiceImpl implements AiCloudFileService { + + @Resource + private KnowledgeBaseConfig config; + + @Resource + private KnowledgeBaseClientFactory clientFactory; + + @Resource + private AiCloudDocService aiCloudDocService; + + @Resource + private OaCompanyService oaCompanyService; + + @Resource + private KnowledgeBaseService knowledgeBaseService; + + @Override + public PageResult pageRel(AiCloudFileParam param) { + PageParam page = new PageParam<>(param); + page.setDefaultOrder("sort_number asc, upload_time desc"); + List list = baseMapper.selectPageRel(page, param); + return new PageResult<>(list, page.getTotal()); + } + + @Override + public List listRel(AiCloudFileParam param) { + List list = baseMapper.selectListRel(param); + // 排序 + PageParam page = new PageParam<>(); + page.setDefaultOrder("sort_number asc, upload_time desc"); + return page.sortRecords(list); + } + + @Override + public AiCloudFile getByIdRel(Integer id) { + AiCloudFileParam param = new AiCloudFileParam(); + param.setId(id); + return param.getOne(baseMapper.selectListRel(param)); + } + + @Async + @Override + public CompletableFuture processFileAsync(String categoryId, Integer docId, MultipartFile file, User loginUser) { + return CompletableFuture.supplyAsync(() -> { + try { + String workspaceId = config.getWorkspaceId(); + Client client = clientFactory.createClient(); + AddFileResponse addFileResponse = AiCloudDataCenterUtil.uploadFile(client, workspaceId, categoryId, file); + String fileId = addFileResponse.getBody().getData().getFileId(); + + // 保存文件信息到数据库 + AiCloudFile aiCloudFile = new AiCloudFile(); + aiCloudFile.setDocId(docId); + aiCloudFile.setFileName(file.getOriginalFilename()); + aiCloudFile.setFileSize(file.getSize()); + aiCloudFile.setFileType(getFileExtension(file.getOriginalFilename())); + aiCloudFile.setFileExt(getFileExtension(file.getOriginalFilename())); + aiCloudFile.setFileId(fileId); + aiCloudFile.setUploadTime(LocalDateTime.now()); + aiCloudFile.setWorkspaceId(workspaceId); + + if (loginUser != null) { + aiCloudFile.setUserId(loginUser.getUserId()); + aiCloudFile.setTenantId(loginUser.getTenantId()); + } + + this.save(aiCloudFile); + log.info("文件上传成功: {}", file.getOriginalFilename()); + return fileId; + + } catch (Exception e) { + log.error("异步处理文件上传失败: {}", e.getMessage(), e); + throw new RuntimeException("文件上传失败: " + file.getOriginalFilename(), e); + } + }); + } + + @Override + public boolean removeFileWithCloud(Integer id) { + AiCloudFile aiCloudFile = this.getById(id); + if (aiCloudFile == null) { + return false; + } + + try { + String workspaceId = config.getWorkspaceId(); + Client client = clientFactory.createClient(); + AiCloudDataCenterUtil.deleteFile(client, workspaceId, aiCloudFile.getFileId()); + } catch (Exception e) { + log.error("删除云文件失败: {}", e.getMessage(), e); + return false; + } + + return this.removeById(id); + } + + @Override + public boolean removeFilesWithCloud(List ids) { + boolean allSuccess = true; + for (Integer id : ids) { + if (!removeFileWithCloud(id)) { + allSuccess = false; + } + } + return allSuccess; + } + + @Override + public String getFileExtension(String filename) { + if (filename == null || filename.lastIndexOf(".") == -1) { + return ""; + } + return filename.substring(filename.lastIndexOf(".") + 1); + } + + @Override + public String getCompanyKbId(Integer docId) { + if (docId == null) { + return ""; + } + AiCloudDoc aiCloudDoc = aiCloudDocService.getById(docId); + if (aiCloudDoc == null || aiCloudDoc.getCompanyId() == null) { + return ""; + } + OaCompany oaCompany = oaCompanyService.getById(aiCloudDoc.getCompanyId()); + return oaCompany != null && oaCompany.getKbId() != null ? oaCompany.getKbId() : ""; + } + + @Override + public void submitDocuments(Integer docId, List fileIds) { + if (docId == null || fileIds == null || fileIds.isEmpty()) { + return; + } + String kbId = getCompanyKbId(docId); + if (kbId == null || kbId.isEmpty()) { + return; + } + // 异步添加文件 + for(String fileId : fileIds) { + if (fileId != null && !fileId.isEmpty()) { + knowledgeBaseService.submitDocuments(kbId, fileId); + } + } + } + + @Override + public boolean removeFileWithCloudAndIndex(Integer id) { + AiCloudFile aiCloudFile = this.getById(id); + if (aiCloudFile == null) { + return false; + } + + // 删除云文件 + if (!removeFileWithCloud(id)) { + return false; + } + + // 同时删除默认知识库文件 + String kbId = getCompanyKbId(aiCloudFile.getDocId()); + if (!kbId.isEmpty()) { + try { + String workspaceId = config.getWorkspaceId(); + Client client = clientFactory.createClient(); + AiCloudKnowledgeBaseUtil.deleteIndexDocument(client, workspaceId, kbId, Arrays.asList(aiCloudFile.getFileId())); + } catch (Exception e) { + log.error("删除知识库索引失败: {}", e.getMessage(), e); + // 这里可以根据业务需求决定是否抛出异常 + } + } + + return true; + } + + @Override + public boolean removeFilesWithCloudAndIndex(List ids) { + boolean allSuccess = true; + for (Integer id : ids) { + if (!removeFileWithCloudAndIndex(id)) { + allSuccess = false; + } + } + return allSuccess; + } +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/service/impl/KnowledgeBaseServiceImpl.java b/src/main/java/com/gxwebsoft/ai/service/impl/KnowledgeBaseServiceImpl.java index 8889da6..904bd1d 100644 --- a/src/main/java/com/gxwebsoft/ai/service/impl/KnowledgeBaseServiceImpl.java +++ b/src/main/java/com/gxwebsoft/ai/service/impl/KnowledgeBaseServiceImpl.java @@ -12,10 +12,12 @@ import com.gxwebsoft.ai.constants.KnowledgeBaseConstants; import com.gxwebsoft.ai.dto.KnowledgeBaseRequest; import com.gxwebsoft.ai.factory.KnowledgeBaseClientFactory; import com.gxwebsoft.ai.service.KnowledgeBaseService; +import com.gxwebsoft.ai.util.AiCloudKnowledgeBaseUtil; import com.gxwebsoft.ai.util.KnowledgeBaseUploader; import com.gxwebsoft.ai.util.KnowledgeBaseUtil; import cn.hutool.core.util.StrUtil; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; @@ -156,5 +158,18 @@ public class KnowledgeBaseServiceImpl implements KnowledgeBaseService { throw new RuntimeException("上传文档到知识库失败: " + e.getMessage(), e); } } + + @Async + @Override + public void submitDocuments(String kbId, String fileId) { + String workspaceId = config.getWorkspaceId(); + try { + Client client = clientFactory.createClient(); + AiCloudKnowledgeBaseUtil.submitIndexAddDocumentsJob(client, workspaceId, kbId, fileId); + } catch (Exception e) { + throw new RuntimeException("添加文档到知识库失败: " + e.getMessage(), e); + } + } + } \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/util/AiCloudDataCenterUtil.java b/src/main/java/com/gxwebsoft/ai/util/AiCloudDataCenterUtil.java new file mode 100644 index 0000000..8917330 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/util/AiCloudDataCenterUtil.java @@ -0,0 +1,319 @@ +package com.gxwebsoft.ai.util; + +import com.aliyun.bailian20231229.Client; +import com.aliyun.bailian20231229.models.*; +import com.aliyun.teautil.models.RuntimeOptions; +import com.fasterxml.jackson.databind.ObjectMapper; + +import cn.hutool.core.util.StrUtil; + +import java.util.Map; + +import org.springframework.web.multipart.MultipartFile; + +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.security.MessageDigest; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +/** + * 云数据中心工具类 + * @author yc + * + */ +public class AiCloudDataCenterUtil { + + /** + * 类目列表 + * + * @param client 客户端对象 + * @param workspaceId 业务空间ID + * @param categoryId 父类目ID + * @return 阿里云百炼服务的响应 + */ + public static ListCategoryResponse listCategory(Client client, String workspaceId, String parentCategoryId) throws Exception { + ListCategoryRequest listCategoryRequest = new ListCategoryRequest(); + listCategoryRequest.setCategoryType("UNSTRUCTURED"); + listCategoryRequest.setParentCategoryId(parentCategoryId); + RuntimeOptions runtime = new RuntimeOptions(); + Map headers = new HashMap<>(); + return client.listCategoryWithOptions(workspaceId, listCategoryRequest, headers, runtime); + } + + /** + * 删除类目 + * + * @param client 客户端对象 + * @param workspaceId 业务空间ID + * @param categoryId 类目ID + * @return 阿里云百炼服务的响应 + */ + public static DeleteCategoryResponse deleteCategory(Client client, String workspaceId, String categoryId) throws Exception { + RuntimeOptions runtime = new RuntimeOptions(); + Map headers = new HashMap<>(); + return client.deleteCategoryWithOptions(categoryId, workspaceId, headers, runtime); + } + + /** + * 添加类目 + * + * @param client 客户端对象 + * @param workspaceId 业务空间ID + * @param addCategoryRequest 添加类目请求对象 + * @return 阿里云百炼服务的响应 + */ + public static AddCategoryResponse addCategory(Client client, String workspaceId, String parentCategoryId, String categoryName) throws Exception { + AddCategoryRequest addCategoryRequest = new AddCategoryRequest(); + addCategoryRequest.setCategoryType("UNSTRUCTURED"); + addCategoryRequest.setParentCategoryId(parentCategoryId); + addCategoryRequest.setCategoryName(categoryName); + RuntimeOptions runtime = new RuntimeOptions(); + Map headers = new HashMap<>(); + return client.addCategoryWithOptions(workspaceId, addCategoryRequest, headers, runtime); + } + + /** + * 文件列表 + * + * @param client 客户端对象 + * @param workspaceId 业务空间ID + * @return 阿里云百炼服务的响应 + */ + public static ListFileResponse listFile(Client client, String workspaceId, String categoryId, String fileName) throws Exception { + ListFileRequest listFileRequest = new ListFileRequest(); + listFileRequest.setCategoryId(categoryId); + if(StrUtil.isNotBlank(fileName)) { + listFileRequest.setFileName(fileName); + } + RuntimeOptions runtime = new RuntimeOptions(); + Map headers = new HashMap<>(); + return client.listFileWithOptions(workspaceId, listFileRequest, headers, runtime); + } + + /** + * 添加文件 + * + * @param client 客户端对象 + * @param workspaceId 业务空间ID + * @param addFileRequest 添加文件请求对象 + * @return 阿里云百炼服务的响应 + */ + public static AddFileResponse addFile(Client client, String workspaceId, String categoryId, String leaseId) throws Exception { + AddFileRequest addFileRequest = new AddFileRequest(); + addFileRequest.setCategoryId(categoryId); + addFileRequest.setLeaseId(leaseId); + addFileRequest.setParser("DASHSCOPE_DOCMIND"); + RuntimeOptions runtime = new RuntimeOptions(); + Map headers = new HashMap<>(); + return client.addFileWithOptions(workspaceId, addFileRequest, headers, runtime); + } + +// /** +// * 从授权OSS添加文件(弃用) +// * +// * @param client 客户端对象 +// * @param workspaceId 业务空间ID +// * @param addFilesFromAuthorizedOssRequest 从授权OSS添加文件请求对象 +// * @return 阿里云百炼服务的响应 +// */ +// public static AddFilesFromAuthorizedOssResponse addFilesFromAuthorizedOss(Client client, String workspaceId, AddFilesFromAuthorizedOssRequest addFilesFromAuthorizedOssRequest) throws Exception { +// RuntimeOptions runtime = new RuntimeOptions(); +// Map headers = new HashMap<>(); +// return client.addFilesFromAuthorizedOssWithOptions(workspaceId, addFilesFromAuthorizedOssRequest, headers, runtime); +// } + + /** + * 申请文件上传租约 + * + * @param client 客户端对象 + * @param workspaceId 业务空间ID + * @param categoryId 类目ID + * @param applyFileUploadLeaseRequest 申请文件上传租约请求对象 + * @return 阿里云百炼服务的响应 + */ + public static ApplyFileUploadLeaseResponse applyFileUploadLease(Client client, String workspaceId, String categoryId, String fileName, String md5, String size) throws Exception { + ApplyFileUploadLeaseRequest applyFileUploadLeaseRequest = new ApplyFileUploadLeaseRequest(); + applyFileUploadLeaseRequest.setFileName(fileName); + applyFileUploadLeaseRequest.setMd5(md5); + applyFileUploadLeaseRequest.setSizeInBytes(size); + RuntimeOptions runtime = new RuntimeOptions(); + Map headers = new HashMap<>(); + return client.applyFileUploadLeaseWithOptions(categoryId, workspaceId, applyFileUploadLeaseRequest, headers, runtime); + } + + /** + * 删除文件 + * + * @param client 客户端对象 + * @param workspaceId 业务空间ID + * @param fileId 文件ID + * @return 阿里云百炼服务的响应 + */ + public static DeleteFileResponse deleteFile(Client client, String workspaceId, String fileId) throws Exception { + RuntimeOptions runtime = new RuntimeOptions(); + Map headers = new HashMap<>(); + return client.deleteFileWithOptions(fileId, workspaceId, headers, runtime); + } + + /** + * 描述文件 + * + * @param client 客户端对象 + * @param workspaceId 业务空间ID + * @param fileId 文件ID + * @return 阿里云百炼服务的响应 + */ + public static DescribeFileResponse describeFile(Client client, String workspaceId, String fileId) throws Exception { + RuntimeOptions runtime = new RuntimeOptions(); + Map headers = new HashMap<>(); + return client.describeFileWithOptions(workspaceId, fileId, headers, runtime); + } + + /** + * 更新文件标签 + * + * @param client 客户端对象 + * @param workspaceId 业务空间ID + * @param fileId 文件ID + * @param updateFileTagRequest 更新文件标签请求对象 + * @return 阿里云百炼服务的响应 + */ + public static UpdateFileTagResponse updateFileTag(Client client, String workspaceId, String fileId, List tags) throws Exception { + UpdateFileTagRequest updateFileTagRequest = new UpdateFileTagRequest(); + updateFileTagRequest.setTags(tags); + RuntimeOptions runtime = new RuntimeOptions(); + Map headers = new HashMap<>(); + return client.updateFileTagWithOptions(workspaceId, fileId, updateFileTagRequest, headers, runtime); + } + +// /** +// * 获取解析设置 +// * +// * @param client 客户端对象 +// * @param workspaceId 业务空间ID +// * @param getParseSettingsRequest 获取解析设置请求对象 +// * @return 阿里云百炼服务的响应 +// */ +// public static GetParseSettingsResponse getParseSettings(Client client, String workspaceId, GetParseSettingsRequest getParseSettingsRequest) throws Exception { +// RuntimeOptions runtime = new RuntimeOptions(); +// Map headers = new HashMap<>(); +// return client.getParseSettingsWithOptions(workspaceId, getParseSettingsRequest, headers, runtime); +// } +// +// /** +// * 获取可用解析器类型 +// * +// * @param client 客户端对象 +// * @param workspaceId 业务空间ID +// * @param getAvailableParserTypesRequest 获取可用解析器类型请求对象 +// * @return 阿里云百炼服务的响应 +// */ +// public static GetAvailableParserTypesResponse getAvailableParserTypes(Client client, String workspaceId, GetAvailableParserTypesRequest getAvailableParserTypesRequest) throws Exception { +// RuntimeOptions runtime = new RuntimeOptions(); +// Map headers = new HashMap<>(); +// return client.getAvailableParserTypesWithOptions(workspaceId, getAvailableParserTypesRequest, headers, runtime); +// } +// +// /** +// * 更改解析设置 +// * +// * @param client 客户端对象 +// * @param workspaceId 业务空间ID +// * @param changeParseSettingRequest 更改解析设置请求对象 +// * @return 阿里云百炼服务的响应 +// */ +// public static ChangeParseSettingResponse changeParseSetting(Client client, String workspaceId, ChangeParseSettingRequest changeParseSettingRequest) throws Exception { +// RuntimeOptions runtime = new RuntimeOptions(); +// Map headers = new HashMap<>(); +// return client.changeParseSettingWithOptions(workspaceId, changeParseSettingRequest, headers, runtime); +// } + + + + /** + * 上传文件 + * + * @param client 客户端对象 + * @param workspaceId 业务空间ID + * @param addFileRequest 添加文件请求对象 + * @return 阿里云百炼服务的响应 + */ + public static AddFileResponse uploadFile(Client client, String workspaceId, String categoryId, MultipartFile file) throws Exception { + // 准备文档信息 + String fileName = file.getOriginalFilename(); + String fileMd5 = calculateMD5(file.getInputStream()); + String fileSize = String.valueOf(file.getSize()); + + ApplyFileUploadLeaseResponse applyFileUploadLeaseResponse = applyFileUploadLease(client, workspaceId, categoryId, fileName, fileMd5, fileSize); + + String leaseId = applyFileUploadLeaseResponse.getBody().getData().getFileUploadLeaseId(); + String uploadUrl = applyFileUploadLeaseResponse.getBody().getData().getParam().getUrl(); + + // 上传文件 + ObjectMapper mapper = new ObjectMapper(); + Map headers = mapper.readValue(mapper.writeValueAsString(applyFileUploadLeaseResponse.getBody().getData().getParam().getHeaders()), Map.class); + uploadFile(uploadUrl, headers, file); + + AddFileResponse addFileResponse = addFile(client, workspaceId, categoryId, leaseId); + + String fileId = addFileResponse.getBody().getData().getFileId(); + waitForFileParsing(client, workspaceId, fileId); + return addFileResponse; + } + + private static void uploadFile(String preSignedUrl, Map headers, MultipartFile file) throws Exception { + HttpURLConnection conn = (HttpURLConnection) new URL(preSignedUrl).openConnection(); + conn.setRequestMethod("PUT"); + conn.setDoOutput(true); + conn.setRequestProperty("X-bailian-extra", headers.get("X-bailian-extra")); + conn.setRequestProperty("Content-Type", headers.get("Content-Type")); + + try (InputStream inputStream = file.getInputStream()) { + byte[] buffer = new byte[4096]; + int bytesRead; + while ((bytesRead = inputStream.read(buffer)) != -1) { + conn.getOutputStream().write(buffer, 0, bytesRead); + } + } + + if (conn.getResponseCode() != 200) { + throw new RuntimeException("上传失败: " + conn.getResponseCode()); + } + } + + private static String calculateMD5(InputStream inputStream) throws Exception { + MessageDigest md = MessageDigest.getInstance("MD5"); + byte[] buffer = new byte[4096]; + int bytesRead; + while ((bytesRead = inputStream.read(buffer)) != -1) { + md.update(buffer, 0, bytesRead); + } + StringBuilder sb = new StringBuilder(); + for (byte b : md.digest()) { + sb.append(String.format("%02x", b & 0xff)); + } + return sb.toString(); + } + + private static void waitForFileParsing(Client client, String workspaceId, String fileId) throws Exception { + long timeout = 30 * 60 * 1000; // 30分钟超时 + long startTime = System.currentTimeMillis(); + while (true) { + // 检查超时 + if (System.currentTimeMillis() - startTime > timeout) { + throw new RuntimeException("文档解析超时"); + } + DescribeFileResponse response = describeFile(client, workspaceId, fileId); + String status = response.getBody().getData().getStatus(); + if ("PARSE_SUCCESS".equals(status)) { + break; + } else if ("PARSE_FAILED".equals(status)) { + throw new RuntimeException("文档解析失败"); + } + Thread.sleep(3000); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/gxwebsoft/ai/util/AiCloudKnowledgeBaseUtil.java b/src/main/java/com/gxwebsoft/ai/util/AiCloudKnowledgeBaseUtil.java new file mode 100644 index 0000000..8db1891 --- /dev/null +++ b/src/main/java/com/gxwebsoft/ai/util/AiCloudKnowledgeBaseUtil.java @@ -0,0 +1,159 @@ +package com.gxwebsoft.ai.util; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.aliyun.bailian20231229.models.CreateIndexResponse; +import com.aliyun.bailian20231229.models.DeleteFileResponse; +import com.aliyun.bailian20231229.models.DeleteIndexDocumentResponse; +import com.aliyun.bailian20231229.models.DeleteIndexResponse; +import com.aliyun.bailian20231229.models.ListIndexDocumentsResponse; +import com.aliyun.bailian20231229.models.ListIndicesResponse; +import com.aliyun.bailian20231229.models.RetrieveRequest; +import com.aliyun.bailian20231229.models.RetrieveResponse; +import com.aliyun.bailian20231229.models.SubmitIndexAddDocumentsJobRequest; +import com.aliyun.bailian20231229.models.SubmitIndexAddDocumentsJobResponse; +import com.aliyun.teautil.models.RuntimeOptions; + +/** + * 知识库工具类 + * @author GIIT-YC + * + */ +public class AiCloudKnowledgeBaseUtil { + + /** + * 在指定的知识库中检索信息。 + * + * @param client 客户端对象(bailian20231229Client) + * @param workspaceId 业务空间ID + * @param indexId 知识库ID + * @param query 检索查询语句 + * @return 阿里云百炼服务的响应 + */ + public static RetrieveResponse retrieveIndex(com.aliyun.bailian20231229.Client client, String workspaceId, String indexId, String query) throws Exception { + RetrieveRequest retrieveRequest = new RetrieveRequest(); + retrieveRequest.setIndexId(indexId); + retrieveRequest.setQuery(query); + retrieveRequest.setDenseSimilarityTopK(null); + RuntimeOptions runtime = new RuntimeOptions(); + return client.retrieveWithOptions(workspaceId, retrieveRequest, null, runtime); + } + + /** + * 在阿里云百炼服务中创建知识库(初始化)。 + * + * @param client 客户端对象 + * @param workspaceId 业务空间ID + * @param name 知识库名称 + * @param desc 知识库描述 + * @return 阿里云百炼服务的响应对象 + */ + public static CreateIndexResponse createIndex(com.aliyun.bailian20231229.Client client, String workspaceId, String name, String desc) throws Exception { + Map headers = new HashMap<>(); + com.aliyun.bailian20231229.models.CreateIndexRequest createIndexRequest = new com.aliyun.bailian20231229.models.CreateIndexRequest(); + createIndexRequest.setStructureType("unstructured"); + createIndexRequest.setName(name); + createIndexRequest.setDescription(desc); + createIndexRequest.setSinkType("DEFAULT"); + createIndexRequest.setEmbeddingModelName("text-embedding-v4"); + com.aliyun.teautil.models.RuntimeOptions runtime = new com.aliyun.teautil.models.RuntimeOptions(); + return client.createIndexWithOptions(workspaceId, createIndexRequest, headers, runtime); + } + + /** + * 获取指定业务空间下一个或多个知识库的详细信息 + * + * @param client 客户端(Client) + * @param workspaceId 业务空间ID + * @return 阿里云百炼服务的响应 + */ + public static ListIndicesResponse listIndices(com.aliyun.bailian20231229.Client client, String workspaceId) throws Exception { + Map headers = new HashMap<>(); + com.aliyun.bailian20231229.models.ListIndicesRequest listIndicesRequest = new com.aliyun.bailian20231229.models.ListIndicesRequest(); + com.aliyun.teautil.models.RuntimeOptions runtime = new com.aliyun.teautil.models.RuntimeOptions(); + return client.listIndicesWithOptions(workspaceId, listIndicesRequest, headers, runtime); + } + + /** + * 永久性删除指定的知识库 + * + * @param client 客户端(Client) + * @param workspaceId 业务空间ID + * @param indexId 知识库ID + * @return 阿里云百炼服务的响应 + */ + public static DeleteIndexResponse deleteIndex(com.aliyun.bailian20231229.Client client, String workspaceId, String indexId) throws Exception { + Map headers = new HashMap<>(); + com.aliyun.bailian20231229.models.DeleteIndexRequest deleteIndexRequest = new com.aliyun.bailian20231229.models.DeleteIndexRequest(); + deleteIndexRequest.setIndexId(indexId); + com.aliyun.teautil.models.RuntimeOptions runtime = new com.aliyun.teautil.models.RuntimeOptions(); + return client.deleteIndexWithOptions(workspaceId, deleteIndexRequest, headers, runtime); + } + + /** + * 查询知识库下的文档列表 + * + * @param client 客户端(Client) + * @param workspaceId 业务空间ID + * @param indexId 知识库ID + * @return 阿里云百炼服务的响应 + */ + public static ListIndexDocumentsResponse listIndexDocuments(com.aliyun.bailian20231229.Client client, String workspaceId, String indexId, Integer pageSize, Integer pageNumber) throws Exception { + com.aliyun.bailian20231229.models.ListIndexDocumentsRequest listIndexDocumentsRequest = new com.aliyun.bailian20231229.models.ListIndexDocumentsRequest(); + listIndexDocumentsRequest.setIndexId(indexId); + listIndexDocumentsRequest.setPageSize(pageSize); + listIndexDocumentsRequest.setPageNumber(pageNumber); + return client.listIndexDocuments(workspaceId, listIndexDocumentsRequest); + } + + /** + * 删除知识库下的文档 + * + * @param client 客户端(Client) + * @param workspaceId 业务空间ID + * @param indexId 知识库ID + * @param ids 删除文件ID列表 + * @return 阿里云百炼服务的响应 + */ + public static DeleteIndexDocumentResponse deleteIndexDocument(com.aliyun.bailian20231229.Client client, String workspaceId, String indexId, List ids) throws Exception { + com.aliyun.bailian20231229.models.DeleteIndexDocumentRequest deleteIndexDocumentRequest = new com.aliyun.bailian20231229.models.DeleteIndexDocumentRequest(); + deleteIndexDocumentRequest.setIndexId(indexId); + deleteIndexDocumentRequest.setDocumentIds(ids); + return client.deleteIndexDocument(workspaceId, deleteIndexDocumentRequest); + } + + /** + * 删除阿里云应用数据文档 + * + * @param client 客户端(Client) + * @param workspaceId 业务空间ID + * @param fileId 删除文件ID + * @return 阿里云百炼服务的响应 + */ + public static DeleteFileResponse deleteAppDocument(com.aliyun.bailian20231229.Client client, String workspaceId, String fileId) throws Exception { + return client.deleteFile(fileId, workspaceId); + } + + /** + * 向一个非结构化知识库追加导入已解析的文档 + * + * @param client 客户端(Client) + * @param workspaceId 业务空间ID + * @param indexId 知识库ID + * @param fileId 文档ID + * @param sourceType 数据类型 + * @return 阿里云百炼服务的响应 + */ + public static SubmitIndexAddDocumentsJobResponse submitIndexAddDocumentsJob(com.aliyun.bailian20231229.Client client, String workspaceId, String indexId, String fileId) throws Exception { + Map headers = new HashMap<>(); + SubmitIndexAddDocumentsJobRequest submitIndexAddDocumentsJobRequest = new SubmitIndexAddDocumentsJobRequest(); + submitIndexAddDocumentsJobRequest.setIndexId(indexId); + submitIndexAddDocumentsJobRequest.setDocumentIds(Collections.singletonList(fileId)); + submitIndexAddDocumentsJobRequest.setSourceType("DATA_CENTER_FILE"); + RuntimeOptions runtime = new RuntimeOptions(); + return client.submitIndexAddDocumentsJobWithOptions(workspaceId, submitIndexAddDocumentsJobRequest, headers, runtime); + } +} diff --git a/src/main/java/com/gxwebsoft/oa/controller/OaCompanyController.java b/src/main/java/com/gxwebsoft/oa/controller/OaCompanyController.java index 638300b..84d1dfd 100644 --- a/src/main/java/com/gxwebsoft/oa/controller/OaCompanyController.java +++ b/src/main/java/com/gxwebsoft/oa/controller/OaCompanyController.java @@ -79,9 +79,11 @@ public class OaCompanyController extends BaseController { if (oaCompanyService.save(oaCompany)) { try { String kbId = knowledgeBaseService.createKnowledgeBase(oaCompany.getCompanyName(), oaCompany.getCompanyCode()); - //绑定知识库 + // 绑定知识库 oaCompany.setKbId(kbId); oaCompanyService.updateById(oaCompany); + // 初始化11个目录 - 调用Service层方法 + oaCompanyService.initCompanyDocDirectories(oaCompany, getLoginUserId()); } catch (Exception e) { return fail("添加失败:"+e.getMessage()); } diff --git a/src/main/java/com/gxwebsoft/oa/service/OaCompanyService.java b/src/main/java/com/gxwebsoft/oa/service/OaCompanyService.java index fb91784..5b0fa01 100644 --- a/src/main/java/com/gxwebsoft/oa/service/OaCompanyService.java +++ b/src/main/java/com/gxwebsoft/oa/service/OaCompanyService.java @@ -39,4 +39,11 @@ public interface OaCompanyService extends IService { */ OaCompany getByIdRel(Integer companyId); + /** + * 初始化企业目录 + * + * @param oaCompany 企业信息 + * @param userId 创建用户ID + */ + void initCompanyDocDirectories(OaCompany oaCompany, Integer userId); } diff --git a/src/main/java/com/gxwebsoft/oa/service/impl/OaCompanyServiceImpl.java b/src/main/java/com/gxwebsoft/oa/service/impl/OaCompanyServiceImpl.java index 1c53913..a2b63bf 100644 --- a/src/main/java/com/gxwebsoft/oa/service/impl/OaCompanyServiceImpl.java +++ b/src/main/java/com/gxwebsoft/oa/service/impl/OaCompanyServiceImpl.java @@ -1,14 +1,25 @@ package com.gxwebsoft.oa.service.impl; +import com.aliyun.bailian20231229.Client; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.gxwebsoft.oa.mapper.OaCompanyMapper; import com.gxwebsoft.oa.service.OaCompanyService; import com.gxwebsoft.oa.entity.OaCompany; import com.gxwebsoft.oa.param.OaCompanyParam; +import com.gxwebsoft.ai.config.KnowledgeBaseConfig; +import com.gxwebsoft.ai.entity.AiCloudDoc; +import com.gxwebsoft.ai.factory.KnowledgeBaseClientFactory; +import com.gxwebsoft.ai.service.AiCloudDocService; +import com.gxwebsoft.ai.util.AiCloudDataCenterUtil; import com.gxwebsoft.common.core.web.PageParam; import com.gxwebsoft.common.core.web.PageResult; + +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; /** @@ -20,6 +31,15 @@ import java.util.List; @Service public class OaCompanyServiceImpl extends ServiceImpl implements OaCompanyService { + @Autowired + private AiCloudDocService aiCloudDocService; + + @Autowired + private KnowledgeBaseConfig config; + + @Autowired + private KnowledgeBaseClientFactory clientFactory; + @Override public PageResult pageRel(OaCompanyParam param) { PageParam page = new PageParam<>(param); @@ -44,4 +64,43 @@ public class OaCompanyServiceImpl extends ServiceImpl directoryNames = Arrays.asList( + "贯彻决策部署", "单位发展战略执行", "三重一大执行", "目标完成", "财务管理", + "国资管理", "重大投资情况", "治理结构", "人员编制", "廉政情况", "历史审计问题" + ); + + List directories = new ArrayList<>(); + + for (int i = 0; i < directoryNames.size(); i++) { + AiCloudDoc doc = new AiCloudDoc(); + doc.setCategoryId(categoryId); + doc.setCompanyId(oaCompany.getCompanyId()); + doc.setParentId(0); // 顶级目录 + doc.setName(directoryNames.get(i)); + doc.setSortNumber(i + 1); + doc.setStatus(0); + doc.setDeleted(0); + doc.setUserId(userId); + doc.setTenantId(oaCompany.getTenantId()); + doc.setCreateTime(LocalDateTime.now()); + doc.setUpdateTime(LocalDateTime.now()); + + directories.add(doc); + } + + // 批量保存目录 + aiCloudDocService.saveBatch(directories); + } } diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index 057cb94..c0ab3fc 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -69,6 +69,7 @@ aliyun: access-key-id: LTAI5tD5YRKuxWz6Eg7qrM4P access-key-secret: bO8TBDXflOwbtSKimPpG8XrJnyzgTk workspace-id: llm-4pf5auwewoz34zqu + top-category-id: cate_1db52d0dad764658bb7057293f7e8033_10377381 ai: template: diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml index c27504d..961dbf5 100644 --- a/src/main/resources/application-prod.yml +++ b/src/main/resources/application-prod.yml @@ -75,6 +75,7 @@ aliyun: access-key-id: LTAI5tD5YRKuxWz6Eg7qrM4P access-key-secret: bO8TBDXflOwbtSKimPpG8XrJnyzgTk workspace-id: llm-4pf5auwewoz34zqu + top-category-id: cate_1db52d0dad764658bb7057293f7e8033_10377381 ai: template: