当前位置: 首页 > news >正文

Spring Boot 集成阿里云 OSS 实现文件上传下载的完整指南(从概念到代码)

Spring Boot 集成阿里云 OSS 实现文件上传下载的完整指南(从概念到代码)


一、什么是 OSS

OSS(Object Storage Service,对象存储服务)是云厂商提供的海量文件存储服务。你可以把它理解为一个无限容量的网盘 API,通过代码上传/下载/删除文件。

类比理解:

概念类比说明
OSS 服务网盘(百度网盘)存文件的地方
Bucket(存储桶)网盘里的根文件夹一个项目通常对应一个 Bucket
Object(对象)网盘里的文件每个上传的文件就是一个 Object
Object Key文件路径images/2024/avatar.jpg
Endpoint(节点)服务器地址oss-cn-qingdao.aliyuncs.com
AccessKey账号密码用于身份认证

为什么不直接存本地服务器:

对比项本地存储OSS
容量受限于磁盘大小无限
可靠性服务器挂了文件丢失99.9999999% 可靠性
访问速度单机带宽瓶颈CDN 加速,全球访问
多实例部署文件只在一台机器上所有实例都能访问
成本需要买大磁盘按量付费,用多少算多少

二、核心概念详解

1. Bucket(存储桶)
一个 Bucket = 一个独立的文件命名空间 例如: my-edu-platform(教育平台的文件桶) ├── avatars/user001.jpg ├── courses/math/chapter1.pdf └── exports/report_2024.xlsx

Bucket 创建后有一个访问域名:https://my-edu-platform.oss-cn-qingdao.aliyuncs.com

2. Object Key(文件路径)

OSS 没有真正的"文件夹"概念,/只是 Key 的一部分:

avatars/user001.jpg → 这是一个完整的 Object Key courses/math/chapter1.pdf → 这也是一个完整的 Object Key
3. 访问权限
权限说明适用场景
private必须签名才能访问用户隐私文件、付费内容
public-read任何人可读头像、公开图片
public-read-write任何人可读写几乎不用(不安全)
4. 签名 URL(临时访问链接)

对于 private 文件,可以生成一个带过期时间的临时访问链接:

https://my-bucket.oss-cn-qingdao.aliyuncs.com/secret/file.pdf ?OSSAccessKeyId=xxx &Expires=1704067200 &Signature=abc123

过期后链接自动失效,适合付费下载、临时分享等场景。


三、Maven 依赖

<dependency><groupId>com.aliyun.oss</groupId><artifactId>aliyun-sdk-oss</artifactId><version>3.15.1</version></dependency>

四、完整示例:个人博客图片管理系统

场景

一个个人博客系统,用户可以:

  • 上传文章配图
  • 获取图片访问链接
  • 删除不需要的图片
  • 批量上传多张图片

1. 配置文件 application.yml
aliyun:oss:endpoint:oss-cn-qingdao.aliyuncs.comaccess-key-id:your-access-key-idaccess-key-secret:your-access-key-secretbucket-name:my-blog-images# 文件访问域名前缀(如果绑定了自定义域名)url-prefix:https://my-blog-images.oss-cn-qingdao.aliyuncs.com

2. OSS 配置类
packagecom.example.config;importcom.aliyun.oss.OSS;importcom.aliyun.oss.OSSClientBuilder;importorg.springframework.beans.factory.annotation.Value;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;@ConfigurationpublicclassOssConfig{@Value("${aliyun.oss.endpoint}")privateStringendpoint;@Value("${aliyun.oss.access-key-id}")privateStringaccessKeyId;@Value("${aliyun.oss.access-key-secret}")privateStringaccessKeySecret;/** * 创建 OSS 客户端 Bean * 整个应用共享一个实例(线程安全) */@BeanpublicOSSossClient(){returnnewOSSClientBuilder().build(endpoint,accessKeyId,accessKeySecret);}}

3. OSS 工具服务类
packagecom.example.service;importcom.aliyun.oss.OSS;importcom.aliyun.oss.model.ObjectMetadata;importcom.aliyun.oss.model.PutObjectRequest;importlombok.extern.slf4j.Slf4j;importorg.springframework.beans.factory.annotation.Value;importorg.springframework.stereotype.Service;importorg.springframework.web.multipart.MultipartFile;importjava.io.InputStream;importjava.net.URL;importjava.util.Date;importjava.util.UUID;@Slf4j@ServicepublicclassOssService{privatefinalOSSossClient;@Value("${aliyun.oss.bucket-name}")privateStringbucketName;@Value("${aliyun.oss.url-prefix}")privateStringurlPrefix;publicOssService(OSSossClient){this.ossClient=ossClient;}/** * 上传文件 * * @param file 前端上传的文件 * @param directory 存储目录(如 "articles/2024/06") * @return 文件的访问URL */publicStringuploadFile(MultipartFilefile,Stringdirectory){// 1. 生成唯一文件名(防止重名覆盖)StringoriginalFilename=file.getOriginalFilename();Stringextension=originalFilename.substring(originalFilename.lastIndexOf("."));StringobjectKey=directory+"/"+UUID.randomUUID().toString().replace("-","")+extension;// 例如:articles/2024/06/a1b2c3d4e5f6.jpgtry{// 2. 设置文件元信息ObjectMetadatametadata=newObjectMetadata();metadata.setContentType(file.getContentType());metadata.setContentLength(file.getSize());// 3. 上传到 OSSInputStreaminputStream=file.getInputStream();PutObjectRequestputRequest=newPutObjectRequest(bucketName,objectKey,inputStream,metadata);ossClient.putObject(putRequest);// 4. 返回访问URLStringfileUrl=urlPrefix+"/"+objectKey;log.info("文件上传成功, objectKey={}, url={}",objectKey,fileUrl);returnfileUrl;}catch(Exceptione){log.error("文件上传失败, fileName={}, error={}",originalFilename,e.getMessage());thrownewRuntimeException("文件上传失败:"+e.getMessage());}}/** * 生成临时访问链接(适用于私有文件) * * @param objectKey 文件路径 * @param expireMinutes 过期时间(分钟) * @return 带签名的临时URL */publicStringgeneratePresignedUrl(StringobjectKey,intexpireMinutes){// 设置过期时间Dateexpiration=newDate(System.currentTimeMillis()+expireMinutes*60*1000L);// 生成签名URLURLurl=ossClient.generatePresignedUrl(bucketName,objectKey,expiration);returnurl.toString();}/** * 删除文件 * * @param objectKey 文件路径 */publicvoiddeleteFile(StringobjectKey){try{ossClient.deleteObject(bucketName,objectKey);log.info("文件删除成功, objectKey={}",objectKey);}catch(Exceptione){log.error("文件删除失败, objectKey={}, error={}",objectKey,e.getMessage());thrownewRuntimeException("文件删除失败:"+e.getMessage());}}/** * 判断文件是否存在 */publicbooleandoesFileExist(StringobjectKey){returnossClient.doesObjectExist(bucketName,objectKey);}/** * 从完整URL中提取objectKey * 例如:https://my-blog-images.oss-cn-qingdao.aliyuncs.com/articles/2024/06/abc.jpg * 提取为:articles/2024/06/abc.jpg */publicStringextractObjectKey(StringfileUrl){returnfileUrl.replace(urlPrefix+"/","");}}

4. Controller
packagecom.example.controller;importcom.example.service.OssService;importio.swagger.annotations.Api;importio.swagger.annotations.ApiOperation;importorg.springframework.web.bind.annotation.*;importorg.springframework.web.multipart.MultipartFile;importjava.time.LocalDate;importjava.time.format.DateTimeFormatter;@Api(tags="图片管理")@RestController@RequestMapping("/image")publicclassImageController{privatefinalOssServiceossService;publicImageController(OssServiceossService){this.ossService=ossService;}/** * 上传图片 * 前端用 form-data 格式提交,字段名为 file */@ApiOperation("上传图片")@PostMapping("/upload")publicR<String>upload(@RequestParam("file")MultipartFilefile){// 按日期分目录存储:articles/2024/06/xxx.jpgStringdirectory="articles/"+LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM"));Stringurl=ossService.uploadFile(file,directory);returnnewR<>(url);}/** * 获取私有文件的临时访问链接 */@ApiOperation("获取临时访问链接")@GetMapping("/presignedUrl")publicR<String>getPresignedUrl(@RequestParam("objectKey")StringobjectKey){Stringurl=ossService.generatePresignedUrl(objectKey,30);// 30分钟有效returnnewR<>(url);}/** * 删除图片 */@ApiOperation("删除图片")@DeleteMapping("/delete")publicR<String>delete(@RequestParam("fileUrl")StringfileUrl){StringobjectKey=ossService.extractObjectKey(fileUrl);ossService.deleteFile(objectKey);returnnewR<>("删除成功");}}

五、执行过程演示

上传文件
前端:POST /image/upload (form-data, file=photo.jpg) 后端处理: 1. 生成 objectKey = "articles/2024/06/a1b2c3d4e5f6.jpg" 2. 调用 ossClient.putObject(bucketName, objectKey, inputStream, metadata) 3. OSS 存储文件,返回成功 4. 拼接访问URL = "https://my-blog-images.oss-cn-qingdao.aliyuncs.com/articles/2024/06/a1b2c3d4e5f6.jpg" 返回给前端: { "code": "1", "data": "https://my-blog-images.oss-cn-qingdao.aliyuncs.com/articles/2024/06/a1b2c3d4e5f6.jpg" } 前端拿到URL后插入到文章的 <img src="..."> 中
生成临时链接
请求:GET /image/presignedUrl?objectKey=private/report.pdf 后端处理: 1. 设置过期时间 = 当前时间 + 30分钟 2. 调用 ossClient.generatePresignedUrl(bucket, key, expiration) 3. OSS SDK 本地计算签名(不需要网络请求) 返回: "https://my-blog-images.oss-cn-qingdao.aliyuncs.com/private/report.pdf ?OSSAccessKeyId=LTAI5t*** &Expires=1704069000 &Signature=abc123def456" 30分钟后这个链接自动失效,无法再访问

六、OSS 存储结构示例

Bucket: my-blog-images │ ├── articles/ ← 文章配图 │ ├── 2024/ │ │ ├── 05/ │ │ │ ├── a1b2c3.jpg │ │ │ └── d4e5f6.png │ │ └── 06/ │ │ └── g7h8i9.jpg │ └── 2025/ │ └── ... │ ├── avatars/ ← 用户头像 │ ├── user001.jpg │ └── user002.png │ └── exports/ ← 导出文件 ├── report_20240601.xlsx └── task_xxx.zip

七、常见使用模式

模式 1:公开文件(头像、文章图片)
// 上传后直接返回公开URL,任何人可访问Stringurl="https://bucket.oss-cn-qingdao.aliyuncs.com/avatars/user001.jpg";
模式 2:私有文件 + 临时链接(付费内容、敏感文件)
// 上传时设为私有,访问时生成临时签名URLStringpresignedUrl=ossService.generatePresignedUrl("private/vip-course.mp4",60);// 返回给前端,60分钟内有效
模式 3:服务端直传(大文件)
// 后端生成上传凭证,前端直接传到OSS,不经过后端服务器// 适合大文件(视频、大型PDF),避免占用后端带宽
模式 4:回调通知
前端上传到OSS → OSS上传完成后回调后端接口 → 后端记录文件信息到数据库

八、注意事项

事项说明
AccessKey 安全绝不能暴露在前端代码或 Git 仓库中
文件名唯一用 UUID 生成,避免覆盖
目录规划按业务/日期分目录,便于管理和清理
文件大小限制普通上传最大 5GB,超过用分片上传
跨域配置前端直传需要在 OSS 控制台配置 CORS
费用按存储量 + 请求次数 + 流量计费,注意监控

九、总结

OSS 的本质就是:通过 API 操作的无限容量文件系统

核心操作只有四个:

ossClient.putObject(bucket,key,inputStream);// 上传ossClient.getObject(bucket,key);// 下载ossClient.deleteObject(bucket,key);// 删除ossClient.generatePresignedUrl(bucket,key,expire);// 生成临时链接

在项目中的典型用法:

  1. 用户上传文件 → 存到 OSS → 数据库只存 URL/objectKey
  2. 用户访问文件 → 公开文件直接用 URL / 私有文件生成临时链接
  3. 异步任务生成文件 → 存到 OSS → 返回下载链接给前端
http://www.jsqmd.com/news/868543/

相关文章:

  • 联想集团第一季营收216亿美元:净利5.9亿美元 股价上涨19% 市值近2000亿港元
  • 分布式锁与事务配合:为什么锁要在事务提交后释放
  • OAuthlib错误排查实战:从invalid_grant到server_error的根因定位
  • 面试:如果让你设计一个客服 Agent,你会如何划分四大组件的职责?
  • Keil µVision TAB显示异常问题分析与解决方案
  • Agentic o3调度器与Gemma/Nemotron-H推理范式演进
  • 量子退火与模拟退火在组合优化中的应用对比
  • 加拿大AI公共咨询:以人为本的政府技术治理实践
  • NXP MX芯片EMOV指令周期分析与优化
  • 解锁Linux无线网卡配置:RTL8821CU驱动实战深度指南
  • Frida-ps -U 连接失败的五层排查法
  • 量子纠错码与逻辑门优化实现技术解析
  • GE图引擎架构剖析:怎么做到“代码零修改,性能最大化“
  • 用 PS 抠公章最详细步骤|零基础一键抠取透明公章
  • 量子态相似性度量:迹距离与保真度的工程应用
  • 8051串口通信:Keil µVision输入失效问题解析
  • UDS_自动化脚本生成_10服务_V01
  • 去哪儿旅行Bella参数逆向解析:HMAC-SHA256前端签名与Python复现
  • AI国家安全治理:从动态阈值到人机协同的操作化路径
  • 量子扩散模型:量子物理与生成式AI的融合创新
  • 图神经网络在高能物理暗物质探测中的实战应用
  • 海克斯大乱斗:普攻英雄“锻体”收益的严谨数学分析
  • 【紧急预警】Lovable v4.8.2存在未公开API权限漏洞!立即升级+3行代码热修复方案(仅限前500名开发者获取)
  • 暗物质AI建模:物理约束嵌入与可解释神经网络实践
  • Frida绕过Android签名校验实战指南
  • 从账单明细分析不同模型在代码生成任务上的性价比
  • AI Agent Harness状态管理:长对话上下文维护
  • Frida-ps-U连接失败的五层故障排查指南
  • 好莱坞已悄悄启用AI拍片:2024年7部奥斯卡入围作品背后的生成式视频技术全拆解
  • Android签名校验绕过实战:Frida动态Hook四层防御体系