文件存储几乎是每个后端项目绕不开的环节,用户头像、商品图片、合同附件、日志导出等,都需要一个可靠的落盘位置。传统做法是把文件存到应用服务器的某个目录下,再通过 Nginx 等工具对外提供访问。这种方式在单机部署时还能应付,一旦服务需要横向扩容,多台机器之间的文件同步就成了麻烦。更不用说磁盘写满、单点故障、备份恢复这类运维问题。阿里云对象存储 OSS 用简单的 API 把这些问题抽象掉了,文件上传之后由 OSS 负责持久化和高可用,应用服务器只需要保存一个文件地址。接下来就从一个 Spring Boot 项目出发,看看整合过程要做到哪些事。

一、引入依赖并完成基础配置
要在 Spring Boot 中使用 OSS,首先需要引入官方 SDK。打开项目的 pom.xml,在 <dependencies> 节点中加入以下内容。版本号可以参考 Maven 仓库里的最新稳定版,这里以 3.17.4 为例,如果项目已经使用了 Spring Cloud Alibaba,也可以直接引入其封装的 starter,但原生 SDK 的用法更直观,适合理解原理。
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.17.4</version>
</dependency>
依赖加入之后,需要准备 OSS 的访问凭证。登录阿里云控制台,创建 Bucket,记录 Bucket 名称、所在地域的 Endpoint、AccessKey ID 和 AccessKey Secret。这里要提醒一点,AccessKey 属于敏感信息,不要硬编码在代码里。推荐的做法是放到 application.yml 或环境变量中,再由配置类读取。下面是一个配置示例,其中 endpoint 的值根据 Bucket 所在地域选择,比如杭州地域为 oss-cn-hangzhou.aliyuncs.com。
aliyun:
oss:
endpoint: oss-cn-hangzhou.aliyuncs.com
access-key-id: your-access-key-id
access-key-secret: your-access-key-secret
bucket-name: your-bucket-name
接着写一个配置类,把这些属性绑定到一个实体上,并初始化 OSSClient。这里使用 @ConfigurationProperties 可以让配置更清晰,也可以直接用 @Value 逐个注入。需要注意的是 OSSClient 是线程安全的,可以在应用启动时创建单例,项目关闭时调用 shutdown() 释放连接资源。
@Data
@Component
@ConfigurationProperties(prefix = "aliyun.oss")
public class OssProperties {
private String endpoint;
private String accessKeyId;
private String accessKeySecret;
private String bucketName;
}
@Configuration
public class OssConfig {
@Bean
public OSS ossClient(OssProperties properties) {
return new OSSClientBuilder().build(
properties.getEndpoint(),
properties.getAccessKeyId(),
properties.getAccessKeySecret()
);
}
}
基础配置完成后,先不急着写业务代码,可以用一个测试方法确认客户端能够正常连接 OSS。比如调用 ossClient.doesBucketExist(bucketName),返回 true 就说明凭证和网络都通了。这一步骤虽小,但能避免后面因为 Endpoint 写错或 AccessKey 权限不足而反复排查。
二、封装核心的文件操作工具类
实际项目中,不建议把 OSS 的调用代码散落在 Controller 或 Service 里,最好封装一个独立的工具类或 Service,统一管理上传、下载、删除和地址生成。这样后续如果更换云厂商或改用 MinIO,只需要修改这一个类,业务层不受影响。下面是一个基础的 OssService 示例,覆盖了最常见的文件操作。
@Service
@RequiredArgsConstructor
public class OssService {
private final OSS ossClient;
private final OssProperties ossProperties;
public String uploadFile(String objectName, InputStream inputStream) {
String bucketName = ossProperties.getBucketName();
ossClient.putObject(bucketName, objectName, inputStream);
return "https://" + bucketName + "." + ossProperties.getEndpoint() + "/" + objectName;
}
public InputStream downloadFile(String objectName) {
OSSObject ossObject = ossClient.getObject(ossProperties.getBucketName(), objectName);
return ossObject.getObjectContent();
}
public void deleteFile(String objectName) {
ossClient.deleteObject(ossProperties.getBucketName(), objectName);
}
public String generatePresignedUrl(String objectName, long expireInSeconds) {
Date expiration = new Date(System.currentTimeMillis() + expireInSeconds * 1000);
URL url = ossClient.generatePresignedUrl(ossProperties.getBucketName(), objectName, expiration);
return url.toString();
}
}
上传方法接收一个 InputStream,意味着可以对接 MultipartFile 的流,也可以直接读取本地文件。返回的 URL 使用了 OSS 的默认访问域名,如果配置了 CDN 或自定义域名,可以替换成自己的地址前缀。对象名称 objectName 建议不要直接使用原始文件名,而是按日期或业务类型生成路径,例如 avatar/2025/01/uuid.jpg,这样能避免重名覆盖,也方便后续清理。
下载方法返回的是 InputStream,调用方需要自行关闭流。对于小文件,可以一次性读入字节数组返回,对于大文件,则更适合用流式响应。删除操作比较简单,但要注意 deleteObject 只能删除单个对象,如果需要批量删除,可以使用 deleteObjects 并传入 List<String> 键列表。生成签名 URL 的功能在需要临时授权访问私有文件时非常有用,比如用户购买课程后两小时内可以下载视频文件。
在 Controller 中使用这个 Service 时,可以按业务做一层薄封装。比如用户上传头像,先校验文件类型和大小,再调用 uploadFile,最后把返回的 URL 存到数据库。这里给出一个基础的接口示意,代码中去掉了异常处理和响应包装,只保留核心流程。
@RestController
@RequestMapping("/file")
@RequiredArgsConstructor
public class FileController {
private final OssService ossService;
@PostMapping("/upload")
public String upload(@RequestParam("file") MultipartFile file) throws IOException {
String objectName = "upload/" + System.currentTimeMillis() + "_" + file.getOriginalFilename();
return ossService.uploadFile(objectName, file.getInputStream());
}
}
这个例子虽然简单,但已经能跑通上传流程。生产环境中需要增加文件大小限制、扩展名白名单、用户身份校验等逻辑,这些属于通用安全措施,不展开讨论。从这段代码也能看出,OSS 的集成并不会改变 Spring Boot 原本的请求处理方式,只是把落盘目标从本地换成了云端。
三、实现前端直传以避免服务器压力
如果所有文件都经过应用服务器再转发到 OSS,那么在文件体积较大或并发较高时,服务器带宽和内存很容易成为瓶颈。更好的方案是前端直接从浏览器上传到 OSS,应用服务器只负责签发一个短暂有效的上传凭证。这样文件数据不经过服务器,既降低了负载,也能利用 OSS 的全球加速能力。具体做法是后端提供一个接口,返回 OSS 的签名策略,前端使用该策略通过表单或者 JavaScript SDK 直接上传。
在服务端生成签名策略,需要用到 OSS 的 Policy 和 Signature 机制。以下代码生成一个允许上传到指定目录的策略,有效期为 30 分钟,并对策略做 Base64 编码和签名。前端拿到这些字段后,可以构造一个 FormData 提交到 OSS 的 endpoint。
@PostMapping("/sign")
public Map<String, String> createUploadSignature() {
String dir = "upload/";
long expireTime = System.currentTimeMillis() + 30 * 60 * 1000;
Date expiration = new Date(expireTime);
PolicyConditions policyConds = new PolicyConditions();
policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000);
policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir);
String postPolicy = ossClient.generatePostPolicy(expiration, policyConds);
String encodedPolicy = BinaryUtil.toBase64String(postPolicy.getBytes(StandardCharsets.UTF_8));
String postSignature = ossClient.calculatePostSignature(postPolicy);
Map<String, String> result = new HashMap<>();
result.put("accessId", ossProperties.getAccessKeyId());
result.put("policy", encodedPolicy);
result.put("signature", postSignature);
result.put("dir", dir);
result.put("host", "https://" + ossProperties.getBucketName() + "." + ossProperties.getEndpoint());
result.put("expire", String.valueOf(expireTime / 1000));
return result;
}
前端拿到这些字段后,用 FormData 拼装参数,其中 key 字段表示对象名,通常由前端生成一个随机文件名,也可以由后端预生成返回。上传 URL 就是上面返回的 host。下面是一个基于原生 JavaScript 的示例,实际项目可以封装成组件。
async function uploadToOss(file, signData) {
const formData = new FormData();
formData.append('key', signData.dir + file.name);
formData.append('policy', signData.policy);
formData.append('OSSAccessKeyId', signData.accessId);
formData.append('signature', signData.signature);
formData.append('success_action_status', '200');
formData.append('file', file);
const response = await fetch(signData.host, {
method: 'POST',
body: formData
});
if (response.ok) {
return signData.host + '/' + signData.dir + file.name;
}
throw new Error('Upload failed');
}
前端直传方案虽然减轻了服务器压力,但也带来了新的安全考虑。签名策略中的 dir 和大小限制需要严格设置,避免用户上传到非法路径或者上传超大文件。同时,如果文件上传成功后需要记录到业务数据库,通常有两种做法:一是前端拿到文件地址后调用业务接口保存,二是配置 OSS 的回调通知,让 OSS 在文件上传完成后主动请求后端接口。第二种方式更可靠,但配置稍复杂,需要在上传策略中增加 callback 字段,并在控制台或策略中配置回调地址和鉴权。
四、生产环境中的常见问题与建议
接入 OSS 之后,有几个细节值得提前注意。第一个是对象命名规范。许多开发者习惯直接用原始文件名,这在用户上传同名文件时会导致覆盖,也可能因为特殊字符引发访问问题。建议使用 UUID 或雪花 ID 生成唯一名,并保留原始扩展名。目录层级可以根据业务模块和时间划分,例如 order/attachment/202502/,方便管理生命周期。第二是访问控制。如果 Bucket 设置为公共读,那么只要知道 URL 就能访问文件,适合公开图片;如果文件包含敏感信息,应设置 Bucket 为私有读写,访问时通过签名 URL 授权。
第三个常见问题是 CORS 跨域。前端直传时,浏览器会先发送一个 OPTIONS 预检请求到 OSS,如果 Bucket 没有配置跨域规则,上传会直接失败。解决办法是在阿里云控制台给 Bucket 添加 CORS 规则,允许的来源设置为自己的前端域名,允许的方法至少包括 GET、PUT、POST,允许的头可以设置为 * 或具体值。规则保存后通常几秒内生效,不需要重启服务。第四个问题是费用控制。OSS 的存储成本相对较低,但外网下行流量和请求次数会产生费用,频繁访问的大文件建议配合 CDN 使用,既能加速也能降低 OSS 的流量成本。
如果项目中有大量小文件,比如头像、缩略图,可以考虑在 OSS 上开启图片处理功能,通过 URL 参数直接生成不同尺寸的图片,避免额外搭建图片服务器。比如在原图地址后面追加 ?x-oss-process=image/resize,w_200 就能获得宽度 200 像素的缩略图。这种能力对提升页面加载速度很有帮助,尤其是移动端场景。需要注意的是,该参数必须使用 OSS 默认域名访问,自定义域名需要额外绑定处理功能。
综合来看,Spring Boot 整合阿里云 OSS 的难度并不高,核心工作在于配置初始化、封装统一操作入口,以及根据业务选择服务器转发还是前端直传。只要把异常处理、权限校验和命名规范这些细节做好,文件存储这一环就可以稳定运行。文中的代码可以作为脚手架使用,不同项目只需要微调对象名生成规则和业务校验逻辑即可。
Spring Boot阿里云OSS文件上传修改时间:2026-09-20 04:29:21