
安装minio说明 用于存储上传的文档和图片。如果不需要文档上传功能可以跳过此步骤。下载地址 https://min.io/download启动# Windows minio.exe server D:\minio-data# Linux/Mac./minio server/datamavendependencygroupIdio.minio/groupIdartifactIdminio/artifactIdversion8.5.7/version/dependencyjava工具类配置文件# MinIO服务地址 minio.endpointhttp://localhost:9000 # MinIO访问密钥 minio.access-keyminioadmin # MinIO密钥 minio.secret-keyminioadmin # 知识库文件存储桶名称 minio.bucket.knowledgeknowledge-files # 图片存储桶名称 minio.bucket.imagesknowledge-images配置类在这里插入代码片/***MinIO 对象存储配置**authorlaomao*/Slf4jDataConfigurationConfigurationProperties(prefixminio)public class MinioConfig{/***MinIO 服务端点*/private String endpoint;/***访问密钥*/private String accessKey;/***秘密密钥*/private String secretKey;/***存储桶配置*/private BucketConfig bucketnew BucketConfig();Datapublic static class BucketConfig{/***知识库文件存储桶*/private String knowledgeknowledge-files;/***图片存储桶*/private String imagesknowledge-images;}Beanpublic MinioClient minioClient(){log.info(✅ 初始化 MinIO 客户端: {},endpoint);returnMinioClient.builder().endpoint(endpoint).credentials(accessKey,secretKey).build();}}封装工具类/** * MinIO 对象存储服务接口 * * author laomao */publicinterfaceMinioService{/** * 上传知识库文件 * * param file 文件 * param fileName 文件名可选为空则使用原始文件名 * return 文件访问路径 */StringuploadKnowledgeFile(MultipartFilefile,StringfileName);/** * 上传知识库文件从输入流 * * param inputStream 输入流 * param fileName 文件名 * param contentType 内容类型 * return 文件访问路径 */StringuploadKnowledgeFile(InputStreaminputStream,StringfileName,StringcontentType);/** * 上传图片 * * param image 图片 * param knowledgeBaseId 知识库ID * param pageNumber 页码 * param imageIndex 图片索引 * return 图片访问路径 */StringuploadImage(BufferedImageimage,LongknowledgeBaseId,intpageNumber,intimageIndex);/** * 获取文件访问URL * * param objectName 对象名称 * param bucket 存储桶名称 * return 访问URL */StringgetFileUrl(StringobjectName,Stringbucket);/** * 获取文件输入流 * * param objectName 对象名称 * param bucket 存储桶名称 * return 输入流 */InputStreamgetFileStream(StringobjectName,Stringbucket);/** * 删除文件 * * param objectName 对象名称 * param bucket 存储桶名称 * return 是否成功 */booleandeleteFile(StringobjectName,Stringbucket);/** * 删除知识库相关的所有图片 * * param knowledgeBaseId 知识库ID * return 删除的文件数量 */intdeleteImagesByKnowledgeBaseId(LongknowledgeBaseId);/** * 获取知识库文件存储桶名称 */StringgetKnowledgeBucket();/** * 获取图片存储桶名称 */StringgetImagesBucket();/** * MinIO 对象存储服务实现类 * * author laomao */Slf4jServicepublicclassMinioServiceImplimplementsMinioService{AutowiredprivateMinioClientminioClient;AutowiredprivateMinioConfigminioConfig;/** * 初始化存储桶 */PostConstructpublicvoidinit(){try{// 确保知识库文件存储桶存在ensureBucketExists(minioConfig.getBucket().getKnowledge());// 确保图片存储桶存在ensureBucketExists(minioConfig.getBucket().getImages());log.info(✅ MinIO 存储桶初始化完成);}catch(Exceptione){log.error(❌ MinIO 存储桶初始化失败: {},e.getMessage(),e);}}/** * 确保存储桶存在 */privatevoidensureBucketExists(StringbucketName)throwsException{booleanexistsminioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());if(!exists){minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());log.info(✅ 创建存储桶: {},bucketName);}}OverridepublicStringuploadKnowledgeFile(MultipartFilefile,StringfileName){try{StringobjectNamegenerateObjectName(fileName!null?fileName:file.getOriginalFilename());StringcontentTypefile.getContentType();if(contentTypenull){contentTypeapplication/octet-stream;}minioClient.putObject(PutObjectArgs.builder().bucket(minioConfig.getBucket().getKnowledge()).object(objectName).stream(file.getInputStream(),file.getSize(),-1).contentType(contentType).build());log.info(✅ 文件上传成功: {}/{},minioConfig.getBucket().getKnowledge(),objectName);returnobjectName;}catch(Exceptione){log.error(❌ 文件上传失败: {},e.getMessage(),e);thrownewBusinessException(ErrorCode.DOCUMENT_UPLOAD_FAILED,文件上传失败: e.getMessage(),e);}}OverridepublicStringuploadKnowledgeFile(InputStreaminputStream,StringfileName,StringcontentType){try{StringobjectNamegenerateObjectName(fileName);if(contentTypenull){contentTypeapplication/octet-stream;}// 读取输入流到字节数组以获取大小byte[]bytesinputStream.readAllBytes();minioClient.putObject(PutObjectArgs.builder().bucket(minioConfig.getBucket().getKnowledge()).object(objectName).stream(newByteArrayInputStream(bytes),bytes.length,-1).contentType(contentType).build());log.info(✅ 文件上传成功: {}/{},minioConfig.getBucket().getKnowledge(),objectName);returnobjectName;}catch(Exceptione){log.error(❌ 文件上传失败: {},e.getMessage(),e);thrownewBusinessException(ErrorCode.DOCUMENT_UPLOAD_FAILED,文件上传失败: e.getMessage(),e);}}OverridepublicStringuploadImage(BufferedImageimage,LongknowledgeBaseId,intpageNumber,intimageIndex){try{// 生成图片对象名称StringobjectNameString.format(%d/page_%d_img_%d.jpg,knowledgeBaseId,pageNumber,imageIndex);// 将 BufferedImage 转换为字节数组ByteArrayOutputStreambaosnewByteArrayOutputStream();booleansuccessImageIO.write(image,jpg,baos);if(!success){// 尝试 PNG 格式baos.reset();ImageIO.write(image,png,baos);objectNameobjectName.replace(.jpg,.png);}byte[]imageBytesbaos.toByteArray();if(imageBytes.length0){log.warn(图片转换失败字节数组为空);returnnull;}StringcontentTypeobjectName.endsWith(.png)?image/png:image/jpeg;minioClient.putObject(PutObjectArgs.builder().bucket(minioConfig.getBucket().getImages()).object(objectName).stream(newByteArrayInputStream(imageBytes),imageBytes.length,-1).contentType(contentType).build());log.info(✅ 图片上传成功: {}/{},minioConfig.getBucket().getImages(),objectName);returnobjectName;}catch(Exceptione){log.error(❌ 图片上传失败: {},e.getMessage(),e);returnnull;}}OverridepublicStringgetFileUrl(StringobjectName,Stringbucket){try{returnminioClient.getPresignedObjectUrl(io.minio.GetPresignedObjectUrlArgs.builder().method(io.minio.http.Method.GET).bucket(bucket).object(objectName).expiry(7*24*60*60)// 7天有效期.build());}catch(Exceptione){log.error(❌ 获取文件URL失败: {},e.getMessage(),e);// 返回直接访问路径returnminioConfig.getEndpoint()/bucket/objectName;}}OverridepublicInputStreamgetFileStream(StringobjectName,Stringbucket){try{returnminioClient.getObject(GetObjectArgs.builder().bucket(bucket).object(objectName).build());}catch(Exceptione){log.error(❌ 获取文件流失败: {},e.getMessage(),e);thrownewBusinessException(ErrorCode.DOCUMENT_NOT_FOUND,获取文件流失败: e.getMessage(),e);}}OverridepublicbooleandeleteFile(StringobjectName,Stringbucket){try{minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucket).object(objectName).build());log.info(✅ 文件删除成功: {}/{},bucket,objectName);returntrue;}catch(Exceptione){log.error(❌ 文件删除失败: {},e.getMessage(),e);returnfalse;}}OverridepublicintdeleteImagesByKnowledgeBaseId(LongknowledgeBaseId){intcount0;try{StringprefixknowledgeBaseId/;IterableResultItemresultsminioClient.listObjects(ListObjectsArgs.builder().bucket(minioConfig.getBucket().getImages()).prefix(prefix).recursive(true).build());for(ResultItemresult:results){Itemitemresult.get();deleteFile(item.objectName(),minioConfig.getBucket().getImages());count;}log.info(✅ 删除知识库 {} 的 {} 张图片,knowledgeBaseId,count);}catch(Exceptione){log.error(❌ 批量删除图片失败: {},e.getMessage(),e);}returncount;}OverridepublicStringgetKnowledgeBucket(){returnminioConfig.getBucket().getKnowledge();}OverridepublicStringgetImagesBucket(){returnminioConfig.getBucket().getImages();}/** * 生成唯一的对象名称 */privateStringgenerateObjectName(StringoriginalFilename){Stringextension;if(originalFilename!nulloriginalFilename.contains(.)){extensionoriginalFilename.substring(originalFilename.lastIndexOf(.));}returnSystem.currentTimeMillis()_UUID.randomUUID().toString().substring(0,8)extension;}}使用案列// 创建知识库文件实体对象用于存储文件上传相关信息KnowledgeBaseknowledgenewKnowledgeBase();// 设置原始文件名称用户上传时的文件名knowledge.setFileName(file.getOriginalFilename());// 设置文件大小单位字节knowledge.setFileSize(file.getSize());// 设置文件后缀类型如pdf、docx、txt等knowledge.setFileType(getFileExtension(file.getOriginalFilename()));// 设置文件上传时间当前系统时间knowledge.setUploadTime(LocalDateTime.now());// 设置文件处理状态1处理中文件刚上传待解析/转换knowledge.setStatus(1);StringsavedFilePathminioService.uploadKnowledgeFile(file,file.getOriginalFilename());// 设置文件在服务器上的实际存储路径knowledge.setFilePath(savedFilePath);