SpringBoot 整合 MinIO 实现文件存储——私有化 OSS 方案

📅 2026/6/30 5:28:12
SpringBoot 整合 MinIO 实现文件存储——私有化 OSS 方案
项目里总少不了文件上传下载的功能——用户头像、合同附件、产品图片。用阿里云 OSS 方便但要钱自己存服务器又麻烦。MinIO 是一个开源的对象存储服务兼容 S3 协议可以私有化部署性能和功能完全不输商业 OSS。一、MinIO 简介MinIO vs 其他方案 阿里云 OSS → 按量付费省心但长期用成本高 FastDFS → 部署复杂社区不活跃 MinIO → 开源免费部署简单性能强悍号称读写 183GB/s 自己存磁盘 → 简单但不支持分布式备份困难MinIO 的优势兼容 AWS S3 接口SDK 直接可用部署简单一个 Docker 命令启动支持分布式部署多台机器做集群有 Web 管理界面开源且社区活跃二、安装 MinIO1. Docker 一键部署推荐dockerrun-d\--nameminio\-p9000:9000\-p9001:9001\-eMINIO_ROOT_USERadmin\-eMINIO_ROOT_PASSWORDadmin123456\-vD:\minio\data:/data\quay.io/minio/minio server /data --console-address:9001启动后访问API 端口http://localhost:9000管理后台http://localhost:9001账号 admin / 密码 admin1234562. 在管理台创建 Bucket登录管理后台 → 点击「Create Bucket」→ 输入名称如my-bucket→ 确认。三、SpringBoot 集成 MinIO1. 引入依赖dependencygroupIdio.minio/groupIdartifactIdminio/artifactIdversion8.5.7/version/dependency2. 配置minio:endpoint:http://localhost:9000access-key:adminsecret-key:admin123456bucket:my-bucket3. 配置类ConfigurationpublicclassMinIOConfig{Value(${minio.endpoint})privateStringendpoint;Value(${minio.access-key})privateStringaccessKey;Value(${minio.secret-key})privateStringsecretKey;BeanpublicMinioClientminioClient(){returnMinioClient.builder().endpoint(endpoint).credentials(accessKey,secretKey).build();}}四、文件上传下载1. 文件上传服务ServicepublicclassFileService{AutowiredprivateMinioClientminioClient;Value(${minio.bucket})privateStringbucket;/** * 上传文件 * param file 上传的文件 * param objectName 存储的文件名如 avatar/2026/06/abc123.jpg */publicStringupload(MultipartFilefile,StringobjectName)throwsException{// 检查 bucket 是否存在booleanfoundminioClient.bucketExists(BucketExistsArgs.builder().bucket(bucket).build());if(!found){minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucket).build());}// 上传minioClient.putObject(PutObjectArgs.builder().bucket(bucket).object(objectName).stream(file.getInputStream(),file.getSize(),-1).contentType(file.getContentType()).build());// 返回可访问的 URLreturnendpoint/bucket/objectName;}/** * 上传文件自动生成文件名 */publicStringupload(MultipartFilefile)throwsException{// 原始文件名StringoriginalFilenamefile.getOriginalFilename();// 扩展名StringextoriginalFilename.substring(originalFilename.lastIndexOf(.));// 新文件名日期 UUIDStringobjectNameDateUtil.today()/IdUtil.simpleUUID()ext;returnupload(file,objectName);}/** * 上传文件指定目录前缀 */publicStringupload(MultipartFilefile,Stringprefix,LonguserId)throwsException{StringextoriginalFilename.substring(originalFilename.lastIndexOf(.));StringobjectNameprefix/userId/IdUtil.simpleUUID()ext;returnupload(file,objectName);}}2. ControllerRestControllerRequestMapping(/file)publicclassFileController{AutowiredprivateFileServicefileService;PostMapping(/upload)publicResultVOStringupload(RequestParam(file)MultipartFilefile){if(file.isEmpty()){returnResultVO.error(400,请选择文件);}try{// 校验文件大小10MBif(file.getSize()10*1024*1024){returnResultVO.error(400,文件不能超过10MB);}// 校验文件类型只允许图片和 PDFStringcontentTypefile.getContentType();if(contentTypenull||!contentType.startsWith(image/)!contentType.equals(application/pdf)){returnResultVO.error(400,不支持的文件格式);}StringurlfileService.upload(file);returnResultVO.success(url);}catch(Exceptione){returnResultVO.error(500,上传失败: e.getMessage());}}PostMapping(/upload/avatar)publicResultVOStringuploadAvatar(RequestParam(file)MultipartFilefile,RequestParamLonguserId){try{StringurlfileService.upload(file,avatar,userId);returnResultVO.success(url);}catch(Exceptione){returnResultVO.error(500,上传失败);}}}五、文件删除publicvoiddelete(StringobjectName)throwsException{minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucket).object(objectName).build());}publicvoiddeleteByUrl(StringfileUrl){// 从 URL 中提取 objectName// http://localhost:9000/my-bucket/avatar/1/xxx.jpgStringprefixendpoint/bucket/;StringobjectNamefileUrl.substring(prefix.length());delete(objectName);}六、获取文件列表publicListStringlistFiles(Stringprefix){ListStringfilesnewArrayList();IterableResultItemresultsminioClient.listObjects(ListObjectsArgs.builder().bucket(bucket).prefix(prefix)// 按前缀过滤.recursive(true)// 递归查询.build());for(ResultItemresult:results){files.add(result.get().objectName());}returnfiles;}七、生成临时访问链接有些文件不想公开访问可以生成带有效期的临时链接publicStringgetPresignedUrl(StringobjectName,intexpiryMinutes)throwsException{returnminioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder().bucket(bucket).object(objectName).method(Method.GET).expiry(expiryMinutes,TimeUnit.MINUTES).build());}八、前端上传formiduploadFormenctypemultipart/form-datainputtypefilenamefileidfileInputbuttontypebuttononclickuploadFile()上传/button/formscriptasyncfunctionuploadFile(){constfileInputdocument.getElementById(fileInput);constformDatanewFormData();formData.append(file,fileInput.files[0]);constrespawaitfetch(/file/upload,{method:POST,body:formData,});constresultawaitresp.json();if(result.code200){console.log(文件地址:,result.data);// 回显图片document.getElementById(preview).srcresult.data;}}/script九、Nginx 代理 MinIO生产环境中MinIO 一般不直接暴露端口而是通过 Nginx 代理server { listen 80; server_name file.example.com; location / { proxy_pass http://127.0.0.1:9000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }配置后访问http://file.example.com/my-bucket/xxx.jpg即可查看文件。十、MinIO vs 阿里云 OSS 怎么选场景推荐方案个人/小项目没有公网服务器阿里云 OSS省心公司项目服务器在本地机房MinIO省成本高并发、大流量场景阿里云 OSSCDN 加速数据隐私要求高政务、金融MinIO 私有化部署学习/练手项目MinIODocker 几分钟搞定一句话不差钱上阿里云 OSS想省钱且能自己维护服务器的用 MinIO功能体验几乎一样。 觉得有用的话点赞 关注【张老师技术栈】吧每周更新 Java/Python/爬虫 实战干货不让你白来。