阿里云 OSS

阿里云对象存储 OSS(Object Storage Service)适合公有云场景,提供 CDN 加速、免运维、按量计费。


依赖

implementation 'com.aliyun.oss:aliyun-sdk-oss:3.17.4'
<dependency>
    <groupId>com.aliyun.oss</groupId>
    <artifactId>aliyun-sdk-oss</artifactId>
    <version>3.17.4</version>
</dependency>

配置

app:
  oss:
    endpoint: https://oss-cn-hangzhou.aliyuncs.com
    access-key-id: your-access-key-id
    access-key-secret: your-access-key-secret
    bucket: your-bucket-name
    url-prefix: https://your-bucket.oss-cn-hangzhou.aliyuncs.com

密钥建议通过环境变量注入,不要硬编码。参见 配置管理


配置类

@Configuration
@ConfigurationProperties(prefix = "app.oss")
@Data
public class OssProperties {
    private String endpoint;
    private String accessKeyId;
    private String accessKeySecret;
    private String bucket;
    private String urlPrefix;
}
 
@Configuration
@RequiredArgsConstructor
public class OssConfig {
 
    private final OssProperties props;
 
    @Bean
    public OSS ossClient() {
        return new OSSClientBuilder()
            .build(props.getEndpoint(),
                   props.getAccessKeyId(),
                   props.getAccessKeySecret());
    }
}

基础操作

@Service
@RequiredArgsConstructor
public class OssStorageService implements FileStorageService {
 
    private final OSS ossClient;
    private final OssProperties props;
 
    @Override
    public String store(MultipartFile file) throws IOException {
        String objectName = buildObjectName(file.getOriginalFilename());
 
        ObjectMetadata metadata = new ObjectMetadata();
        metadata.setContentType(file.getContentType());
        metadata.setContentLength(file.getSize());
 
        ossClient.putObject(
            props.getBucket(), objectName,
            file.getInputStream(), metadata
        );
 
        return props.getUrlPrefix() + "/" + objectName;
    }
 
    public void delete(String objectName) {
        ossClient.deleteObject(props.getBucket(), objectName);
    }
 
    public boolean exists(String objectName) {
        return ossClient.doesObjectExist(props.getBucket(), objectName);
    }
 
    public byte[] download(String objectName) throws IOException {
        OSSObject obj = ossClient.getObject(props.getBucket(), objectName);
        try (InputStream is = obj.getObjectContent()) {
            return is.readAllBytes();
        }
    }
 
    private String buildObjectName(String originalFilename) {
        String ext = "";
        if (originalFilename != null && originalFilename.contains(".")) {
            ext = originalFilename.substring(originalFilename.lastIndexOf('.'));
        }
        String date = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd"));
        return date + "/" + UUID.randomUUID() + ext;
    }
}

预签名 URL(临时访问)

私有 Bucket 下生成有时效的访问链接,无需暴露密钥:

// 下载用预签名 URL
public String getPresignedUrl(String objectName, int expiryMinutes) {
    Date expiration = new Date(System.currentTimeMillis()
                               + (long) expiryMinutes * 60 * 1000);
    URL url = ossClient.generatePresignedUrl(
        props.getBucket(), objectName, expiration);
    return url.toString();
}
 
// 上传用预签名 URL(前端直传)
public String getUploadPresignedUrl(String objectName, int expiryMinutes) {
    Date expiration = new Date(System.currentTimeMillis()
                               + (long) expiryMinutes * 60 * 1000);
    GeneratePresignedUrlRequest request =
        new GeneratePresignedUrlRequest(props.getBucket(), objectName, HttpMethod.PUT);
    request.setExpiration(expiration);
    request.setContentType("application/octet-stream");
    return ossClient.generatePresignedUrl(request).toString();
}

前端直传(服务端签名)

绕过服务端中转,前端直接 POST 到 OSS,降低服务器带宽压力。

服务端生成签名

@GetMapping("/oss/policy")
public Map<String, String> getUploadPolicy(@RequestParam String filename) {
 
    long expireSeconds = 300L;
    String dir = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd")) + "/";
 
    PolicyConditions conditions = new PolicyConditions();
    conditions.addConditionItem(
        PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 100 * 1024 * 1024L);
    conditions.addConditionItem(
        MatchMode.StartWith, PolicyConditions.COND_KEY, dir);
 
    Date expiration = new Date(System.currentTimeMillis() + expireSeconds * 1000);
    String policy = ossClient.generatePostPolicy(expiration, conditions);
    String encodedPolicy = Base64.getEncoder()
        .encodeToString(policy.getBytes(StandardCharsets.UTF_8));
    String signature = ossClient.calculatePostSignature(policy);
 
    String ext = filename.substring(filename.lastIndexOf('.'));
    String objectName = dir + UUID.randomUUID() + ext;
    String host = "https://" + props.getBucket() + "."
                  + props.getEndpoint().replace("https://", "");
 
    return Map.of(
        "host",           host,
        "key",            objectName,
        "policy",         encodedPolicy,
        "OSSAccessKeyId", props.getAccessKeyId(),
        "signature",      signature,
        "expire",         String.valueOf(expiration.getTime() / 1000)
    );
}

前端直传

async function uploadToOss(file) {
    const { data } = await axios.get('/oss/policy', {
        params: { filename: file.name }
    })
 
    const form = new FormData()
    form.append('key',            data.key)
    form.append('policy',         data.policy)
    form.append('OSSAccessKeyId', data.OSSAccessKeyId)
    form.append('signature',      data.signature)
    form.append('file',           file)
 
    await axios.post(data.host, form)
    return data.host + '/' + data.key
}

图片处理(OSS 图片服务)

在 URL 后追加参数实时处理图片,无需额外服务:

# 缩放到宽 300px(高等比)
https://bucket.oss-cn-hangzhou.aliyuncs.com/img.jpg?x-oss-process=image/resize,w_300

# 裁剪 200×200 居中
?x-oss-process=image/crop,w_200,h_200,g_center

# 转换格式为 WebP
?x-oss-process=image/format,webp

# 组合:缩放 + WebP + 质量 80
?x-oss-process=image/resize,w_800/format,webp/quality,q_80

Bucket 权限说明

权限说明适用场景
私有读写均需鉴权用户隐私文件
公共读任何人可读,写需鉴权静态资源、图片
公共读写任何人可读写不推荐生产使用

跨域配置(前端直传必须)

OSS 控制台 → Bucket → 跨域设置 → 新增规则:

来源:         https://your-domain.com
允许 Methods: GET, POST, PUT
允许 Headers: *
暴露 Headers: ETag, x-oss-request-id

相关链接

  • 文件上传下载 — 通用上传方案及 MinIO / OSS / 本地存储对比
  • 配置管理 — OSS 密钥等敏感配置外部化
  • MinIO — 私有化部署对象存储替代方案