Skip to content

Alibaba Cloud OSS 对象存储 ​

Alibaba Cloud OSS(Object Storage Service)是阿里云提供的海量、安全、低成本、高可靠的对象存储服务。Spring Cloud Alibaba 内置了 OSS 的集成支持,可以非常方便地在 Spring Boot 2.7.x 项目中使用。

核心概念 ​

概念说明
Bucket存储空间,用于存储 Object 的容器,全局唯一
Object对象,OSS 存储的基本单元,由 Key、Data、MetaData 组成
EndpointOSS 对外服务的访问域名
AccessKey访问密钥,包含 AccessKeyId 和 AccessKeySecret
Region地域,OSS 数据中心所在的物理位置

版本兼容 ​

Spring Cloud AlibabaSpring BootOSS SDK
2021.0.5.02.7.x内置于 spring-cloud-alibaba-starter

创建 Bucket 并获取密钥 ​

本节以阿里云控制台操作为例。

  1. 登录 阿里云 OSS 控制台
  2. 创建 Bucket,选择地域、存储类型(标准存储)、读写权限(私有)
  3. 在 RAM 访问控制 创建 AccessKey,获取 AccessKeyId 和 AccessKeySecret

注意

AccessKeySecret 仅在创建时显示一次,请妥善保存。生产环境建议使用 RAM 子账号 + 最小权限策略。

快速开始 ​

1. 添加 Maven 依赖 ​

xml
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alicloud-oss</artifactId>
</dependency>

无需指定版本号,Spring Cloud Alibaba BOM 已统一管理。

2. 配置文件(application.yml) ​

yaml
spring:
  cloud:
    alicloud:
      access-key: your-access-key-id
      secret-key: your-access-key-secret
      oss:
        endpoint: oss-cn-hangzhou.aliyuncs.com
        bucket: your-bucket-name

生产环境安全建议

不要将 AccessKey 明文写在配置文件中,推荐使用环境变量或 Jasypt 加密:

yaml
spring:
  cloud:
    alicloud:
      access-key: ${ALIBABA_CLOUD_ACCESS_KEY}
      secret-key: ${ALIBABA_CLOUD_SECRET_KEY}

3. 注入并使用 OSS Client ​

java
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.OSSObject;
import com.aliyun.oss.model.PutObjectResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.ByteArrayInputStream;
import java.io.InputStream;

@Service
public class OssService {

    @Autowired
    private OSS ossClient; // Spring Cloud Alibaba 自动配置的 Bean

    /**
     * 上传文件
     */
    public String upload(String objectName, byte[] content) {
        ossClient.putObject("your-bucket-name", objectName,
                new ByteArrayInputStream(content));
        return "https://your-bucket-name.oss-cn-hangzhou.aliyuncs.com/" + objectName;
    }

    /**
     * 下载文件
     */
    public InputStream download(String objectName) {
        OSSObject ossObject = ossClient.getObject("your-bucket-name", objectName);
        return ossObject.getObjectContent();
    }

    /**
     * 删除文件
     */
    public void delete(String objectName) {
        ossClient.deleteObject("your-bucket-name", objectName);
    }

    /**
     * 判断文件是否存在
     */
    public boolean exists(String objectName) {
        return ossClient.doesObjectExist("your-bucket-name", objectName);
    }
}

文件上传进阶 ​

简单上传 ​

java
// 上传字符串
ossClient.putObject(bucketName, "hello.txt",
        new ByteArrayInputStream("Hello OSS".getBytes()));

// 上传本地文件
ossClient.putObject(bucketName, "images/photo.jpg",
        new File("/local/path/photo.jpg"));

带 Metadata 上传 ​

java
ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentType("image/jpeg");
metadata.setContentDisposition("inline");
// 自定义元信息
metadata.addUserMetadata("author", "chief-fei");

PutObjectRequest request = new PutObjectRequest(
        bucketName, "images/photo.jpg",
        new File("/local/path/photo.jpg"));
request.setMetadata(metadata);

ossClient.putObject(request);

分片上传(大文件) ​

java
import com.aliyun.oss.model.*;

// 初始化分片上传
InitiateMultipartUploadRequest initRequest =
        new InitiateMultipartUploadRequest(bucketName, "large-file.zip");
InitiateMultipartUploadResult initResult =
        ossClient.initiateMultipartUpload(initRequest);
String uploadId = initResult.getUploadId();

// 分片上传(每片 5MB)
final long partSize = 5 * 1024 * 1024L;
File file = new File("/local/path/large-file.zip");
long fileLength = file.length();
int partCount = (int) (fileLength / partSize) + (fileLength % partSize != 0 ? 1 : 0);

List<PartETag> partETags = new ArrayList<>();
for (int i = 0; i < partCount; i++) {
    long startPos = i * partSize;
    long curPartSize = Math.min(partSize, fileLength - startPos);

    InputStream instream = new FileInputStream(file);
    instream.skip(startPos);

    UploadPartRequest uploadPartRequest = new UploadPartRequest();
    uploadPartRequest.setBucketName(bucketName);
    uploadPartRequest.setKey("large-file.zip");
    uploadPartRequest.setUploadId(uploadId);
    uploadPartRequest.setInputStream(instream);
    uploadPartRequest.setPartSize(curPartSize);
    uploadPartRequest.setPartNumber(i + 1);

    UploadPartResult uploadPartResult = ossClient.uploadPart(uploadPartRequest);
    partETags.add(uploadPartResult.getPartETag());
}

// 完成分片上传
CompleteMultipartUploadRequest completeRequest =
        new CompleteMultipartUploadRequest(bucketName, "large-file.zip",
                uploadId, partETags);
ossClient.completeMultipartUpload(completeRequest);

断点续传(Resumable Upload) ​

java
import com.aliyun.oss.model.UploadFileRequest;

// 支持断点续传,提供进度回调
UploadFileRequest uploadFileRequest = new UploadFileRequest(
        bucketName, "big-video.mp4");
uploadFileRequest.setUploadFile("/local/path/big-video.mp4");
uploadFileRequest.setTaskNum(5);   // 并发线程数
uploadFileRequest.setPartSize(10 * 1024 * 1024); // 10MB 分片
uploadFileRequest.setEnableCheckpoint(true);     // 开启断点续传

// 进度监听
uploadFileRequest.withProgressListener((bytesWritten, totalBytes, progress) ->
    System.out.printf("进度: %d/%d (%.2f%%)\n",
            bytesWritten, totalBytes, progress * 100)
);

ossClient.uploadFile(uploadFileRequest);

文件下载 ​

流式下载 ​

java
OSSObject ossObject = ossClient.getObject(bucketName, "hello.txt");
InputStream content = ossObject.getObjectContent();
// 读取内容...
content.close();

下载到本地文件 ​

java
ossClient.getObject(
        new GetObjectRequest(bucketName, "photo.jpg"),
        new File("/local/path/photo.jpg")
);

范围下载(断点续传下载) ​

java
GetObjectRequest getObjectRequest =
        new GetObjectRequest(bucketName, "large-file.zip");
// 只下载 0-999 字节
getObjectRequest.setRange(0, 999);
OSSObject ossObject = ossClient.getObject(getObjectRequest);

预签名 URL ​

预签名 URL 可以让临时用户(无需 AccessKey)在有效时间内下载或上传文件。

java
import java.net.URL;
import java.util.Date;

// 生成下载签名 URL(1 小时有效)
Date expiration = new Date(System.currentTimeMillis() + 3600 * 1000);
URL signedUrl = ossClient.generatePresignedUrl(
        bucketName, "private-file.pdf", expiration);
System.out.println("下载链接: " + signedUrl);

// 生成上传签名 URL
GeneratePresignedUrlRequest uploadRequest =
        new GeneratePresignedUrlRequest(bucketName, "upload.jpg", HttpMethod.PUT);
uploadRequest.setExpiration(expiration);
uploadRequest.setContentType("image/jpeg");
URL uploadUrl = ossClient.generatePresignedUrl(uploadRequest);

文件管理 ​

列举文件 ​

java
// 列举所有文件
ObjectListing objectListing = ossClient.listObjects(bucketName);
for (OSSObjectSummary summary : objectListing.getObjectSummaries()) {
    System.out.printf("%s - %d bytes - %s\n",
            summary.getKey(), summary.getSize(), summary.getLastModified());
}

// 按前缀列举(模拟文件夹)
ListObjectsRequest listRequest = new ListObjectsRequest(bucketName);
listRequest.setPrefix("images/2024/");
listRequest.setMaxKeys(100);
ObjectListing listing = ossClient.listObjects(listRequest);

拷贝文件 ​

java
// 同 Bucket 内复制
ossClient.copyObject(bucketName, "source.jpg",
        bucketName, "backup/source.jpg");

设置文件访问权限 ​

java
// 设置为公共读
ossClient.setObjectAcl(bucketName, "public-file.jpg", CannedAccessControlList.PublicRead);

// 设置为私有
ossClient.setObjectAcl(bucketName, "private-file.pdf", CannedAccessControlList.Private);

Spring Boot 2.7.x 完整集成示例 ​

配置类 ​

java
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class OssConfig {

    @Value("${spring.cloud.alicloud.access-key}")
    private String accessKeyId;

    @Value("${spring.cloud.alicloud.secret-key}")
    private String accessKeySecret;

    @Value("${spring.cloud.alicloud.oss.endpoint}")
    private String endpoint;

    @Bean(destroyMethod = "shutdown")
    public OSS ossClient() {
        return new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
    }
}

工具类封装 ​

java
import com.aliyun.oss.OSS;
import com.aliyun.oss.model.PutObjectResult;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Date;
import java.util.UUID;

@Slf4j
@Component
@RequiredArgsConstructor
public class OssUtils {

    private final OSS ossClient;

    @Value("${spring.cloud.alicloud.oss.bucket}")
    private String bucketName;

    @Value("${spring.cloud.alicloud.oss.endpoint}")
    private String endpoint;

    /**
     * 上传文件并返回访问 URL
     */
    public String upload(MultipartFile file, String dir) throws IOException {
        String originalFilename = file.getOriginalFilename();
        String extension = originalFilename != null
                ? originalFilename.substring(originalFilename.lastIndexOf("."))
                : "";
        String objectName = dir + "/" + UUID.randomUUID() + extension;

        ossClient.putObject(bucketName, objectName, file.getInputStream());
        return "https://" + bucketName + "." + endpoint + "/" + objectName;
    }

    /**
     * 生成带签名的临时访问 URL
     */
    public String generatePresignedUrl(String objectName, long expireSeconds) {
        Date expiration = new Date(System.currentTimeMillis() + expireSeconds * 1000);
        URL url = ossClient.generatePresignedUrl(bucketName, objectName, expiration);
        return url.toString();
    }

    /**
     * 删除文件
     */
    public void delete(String objectName) {
        ossClient.deleteObject(bucketName, objectName);
        log.info("已删除 OSS 文件: {}", objectName);
    }
}

Controller 示例 ​

java
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;

@RestController
@RequestMapping("/api/oss")
@RequiredArgsConstructor
public class OssController {

    private final OssUtils ossUtils;

    /**
     * 上传文件
     */
    @PostMapping("/upload")
    public ResponseEntity<String> upload(@RequestParam("file") MultipartFile file) throws IOException {
        String url = ossUtils.upload(file, "uploads");
        return ResponseEntity.ok(url);
    }

    /**
     * 获取临时访问链接
     */
    @GetMapping("/presigned-url")
    public ResponseEntity<String> presignedUrl(@RequestParam String objectName) {
        String url = ossUtils.generatePresignedUrl(objectName, 3600); // 1 小时
        return ResponseEntity.ok(url);
    }

    /**
     * 删除文件
     */
    @DeleteMapping("/delete")
    public ResponseEntity<String> delete(@RequestParam String objectName) {
        ossUtils.delete(objectName);
        return ResponseEntity.ok("删除成功");
    }
}

Bucket 策略与安全 ​

通过 Bucket Policy 控制访问 ​

json
{
  "Version": "1",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": ["*"],
      "Action": ["oss:GetObject"],
      "Resource": ["acs:oss:*:your-bucket:public/*"],
      "Condition": {
        "IpAddress": {
          "acs:SourceIp": ["192.168.0.0/16"]
        }
      }
    }
  ]
}

防盗链设置(Referer 白名单) ​

在 OSS 控制台 → Bucket → 数据安全 → 防盗链 中配置允许的 Referer 白名单。

常见问题 ​

1. 上传权限不足 ​

com.aliyun.oss.OSSException: AccessDenied

解决方案:检查 AccessKey 是否有对应 Bucket 的读写权限,建议使用 RAM 子账号并授权 AliyunOSSFullAccess 或自定义权限策略。

2. Endpoint 配置错误 ​

确保 endpoint 格式为 oss-cn-hangzhou.aliyuncs.com(不包含 https:// 和 Bucket 名称)。

3. 大文件上传 OOM ​

使用分片上传或断点续传替代简单上传,避免将整个文件读入内存。

4. 跨域(CORS)问题 ​

在 OSS 控制台 → Bucket → 数据安全 → 跨域设置 中配置:

参数值
来源* 或具体域名
Allow MethodsGET, PUT, POST, DELETE
Allow Headers*
Expose HeadersETag

费用优化建议 ​

  • 不常访问的文件使用低频访问存储或归档存储
  • 设置生命周期规则自动删除过期文件或转换存储类型
  • 启用 CDN 加速,减少 OSS 直接请求费用
  • 上传前对图片进行压缩,减小存储和流量成本

相关文档 ​

本文档基于 Spring Cloud Alibaba 2021.0.5.0 + Spring Boot 2.7.x + aliyun-oss-spring-boot-starter 编写。

最近更新