完整教程:Spring Boot 集成华为云 OBS 实现文件上传(含表单上传签名)

在现代 Web 应用中,文件上传是一个非常常见的功能。为了提高性能和安全性,我们通常会将文件存储到云存储服务中,而不是直接存储在应用服务器上。华为云 OBS (Object Storage Service) 就是这样一种稳定、安全、高可用的对象存储服务。

本文将详细介绍如何在 Spring Boot 项目中集成华为云 OBS,实现两种常见的上传方式:

  1. 服务端直接上传:客户端将文件上传到我们的应用服务器,服务器再将文件转发到 OBS。
  2. 基于表单签名上传:服务器生成一个临时的、具有权限的签名,客户端使用这个签名直接向 OBS 上传文件,无需经过应用服务器,大大减轻了服务器压力。
1. 准备工作

在开始编码之前,你需要完成以下准备工作:

  1. 拥有一个华为云账号,并创建一个 OBS 桶 (Bucket)。
  2. 获取 Access Key ID 和 Secret Access Key。这是访问 OBS 服务的凭证,可以在华为云控制台的 “我的凭证” 中创建和查看。
  3. 确保你的 Spring Boot 项目已经创建
2. 添加依赖

首先,在你的 pom.xml 文件中添加华为云 OBS Java SDK 的依赖。


    com.huaweicloud
    esdk-obs-java
    
    3.25.10

注:请根据你的 Spring Boot 版本选择兼容的 OBS SDK 版本。

3. 配置 OBS 客户端

我们使用 Spring Boot 的配置注入功能来管理 OBS 的连接信息,并创建一个单例的 ObsClient Bean。

3.1 添加配置文件

在 application.properties 或 application.yml 中添加你的 OBS 配置:

application.properties 示例:

# OBS 访问密钥
huawei.obs.access-key-id=你的Access-Key-ID
huawei.obs.secret-access-key=你的Secret-Access-Key
# OBS 服务地址 endpoint (例如: https://obs.cn-north-1.myhuaweicloud.com)
huawei.obs.endpoint=你的OBS-Endpoint
# OBS 桶名称
huawei.obs.bucket-name=你的Bucket-Name

3.2 创建配置类 ObsConfig.java

这个类负责读取配置并生成 ObsClient Bean。

import com.huaweicloud.sdk.obs.v3.ObsClient;
import com.huaweicloud.sdk.obs.v3.model.ObsConfiguration;
import lombok.Getter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
 * OBS 配置类
 * 用于创建和管理 ObsClient 实例
 */
@Configuration
@Getter // Lombok 注解,自动生成 getter 方法
public class ObsConfig {
    // 从配置文件读取 OBS 访问密钥 ID
    @Value("${huawei.obs.access-key-id}")
    private String accessKeyId;
    // 从配置文件读取 OBS 秘密访问密钥
    @Value("${huawei.obs.secret-access-key}")
    private String secretAccessKey;
    // 从配置文件读取 OBS 服务端点
    @Value("${huawei.obs.endpoint}")
    private String endpoint;
    // 从配置文件读取 OBS 桶名称
    @Value("${huawei.obs.bucket-name}")
    private String bucketName;
    /**
     * 初始化 OBS 客户端
     * @Bean 注解将 ObsClient 实例交由 Spring 容器管理,成为一个单例 Bean
     * @return ObsClient
     */
    @Bean
    public ObsClient obsClient() {
        // 1. 创建 OBS 配置对象
        ObsConfiguration config = new ObsConfiguration();
        // 设置服务端点
        config.setEndPoint(endpoint);
        // 2. 构造 ObsClient 实例
        // 传入访问密钥 ID, 秘密访问密钥和配置对象
        return new ObsClient(accessKeyId, secretAccessKey, config);
    }
}
4. 创建数据模型

为了方便向前端传递表单上传所需的信息,我们创建一个 ObsForm 类。

ObsForm.java

import lombok.Getter;
import lombok.Setter;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
/**
 * 用于返回给前端的 OBS 表单上传所需信息
 */
@Getter
@Setter
@ApiModel(description = "OBS表单上传信息")
public class ObsForm {
    @ApiModelProperty(value = "策略")
    private String policy;
    @ApiModelProperty(value = "访问密钥ID")
    private String accessKeyId;
    @ApiModelProperty(value = "签名")
    private String signature;
    @ApiModelProperty(value = "OBS服务端点")
    private String endpoint;
    @ApiModelProperty(value = "桶名称")
    private String bucketname;
}

注:这里使用了 Swagger (Springfox) 的注解来生成 API 文档,如果你没有使用 Swagger,可以移除 @ApiModel 和 @ApiModelProperty 注解。

5. 实现上传接口 (Controller)

现在,我们来创建核心的 ObsController,实现两种上传方式的接口。

ObsController.java

import com.huaweicloud.sdk.obs.v3.ObsClient;
import com.huaweicloud.sdk.obs.v3.model.PostSignatureRequest;
import com.huaweicloud.sdk.obs.v3.model.PostSignatureResponse;
import com.huaweicloud.sdk.obs.v3.model.PutObjectRequest;
import com.huaweicloud.sdk.obs.v3.model.PutObjectResult;
import com.huaweicloud.sdk.obs.v3.model.RESTCannedACL;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
 * OBS 文件上传控制器
 */
@RestController
@RequestMapping("/api/obs") // 统一的接口前缀
public class ObsController {
    // 注入由 Spring 容器管理的 ObsClient 实例
    @Autowired
    private ObsClient obsClient;
    // 注入 OBS 配置信息
    @Autowired
    private ObsConfig obsConfig;
    /**
     * 方式一:服务端直接上传文件
     * 客户端将文件上传到应用服务器,服务器再转发到 OBS
     *
     * @param file 前端上传的文件 (MultipartFile)
     * @return 上传成功后的文件 URL
     */
    @PostMapping("/uploadFile")
    public Result uploadFile(@RequestParam("file") MultipartFile file) {
        try {
            // 检查文件是否为空
            if (file.isEmpty()) {
                return Result.fail("上传文件不能为空");
            }
            // 1. 构建 PutObjectRequest 请求对象
            PutObjectRequest request = new PutObjectRequest();
            // 设置要上传到的桶名
            request.setBucketName(obsConfig.getBucketName());
            // 设置文件的访问权限为公共读,这样可以直接通过 URL 访问
            request.setAcl(RESTCannedACL.REST_CANNED_PUBLIC_READ);
            // 2. 处理文件名:为了避免重复,使用 UUID 前缀 + 原文件名
            String originalFileName = file.getOriginalFilename();
            String fileName = UUID.randomUUID().toString().replace("-", "") + "_" + originalFileName;
            // 设置对象键(Object Key),即文件在 OBS 中的名称
            request.setObjectKey(fileName);
            // 3. 设置上传的文件流
            request.setInput(file.getInputStream());
            // 4. 执行上传操作
            PutObjectResult result = obsClient.putObject(request);
            return Result.ok(result.getObjectUrl(), "文件上传成功");
        } catch (Exception e) {
            e.printStackTrace(); // 在生产环境中,应使用日志记录异常
            return Result.fail("文件上传失败: " + e.getMessage());
        }
    }
    /**
     * 方式二:获取表单上传所需的签名信息
     * 服务器生成临时凭证,客户端使用该凭证直接向 OBS 上传文件
     *
     * @return 包含 policy, signature 等信息的 ObsForm 对象
     */
    @GetMapping("/getForm")
    public Result getForm() {
        try {
            // 1. 创建 PostSignatureRequest 请求对象
            PostSignatureRequest request = new PostSignatureRequest();
            // 2. 设置表单上传的策略(Policy)
            // Policy 是一个 JSON 字符串,用于描述允许的上传行为,如过期时间、上传的桶、ACL等
            Map formParams = new HashMap<>();
            // 设置对象的 ACL 为公共读
            formParams.put("x-obs-acl", "public-read");
            // 可以限制上传文件的 Content-Type
            // formParams.put("content-type", "image/jpeg");
            request.setFormParams(formParams);
            // 3. 设置签名的有效期(单位:秒)
            request.setExpires(3600); // 这里设置为 1 小时
            // 4. 生成 Post 签名
            PostSignatureResponse response = obsClient.createPostSignature(request);
            // 5. 构建返回给前端的表单信息对象
            ObsForm obsForm = new ObsForm();
            obsForm.setPolicy(response.getPolicy());         // 编码后的 Policy
            obsForm.setSignature(response.getSignature());   // 签名
            obsForm.setAccessKeyId(obsConfig.getAccessKeyId()); // Access Key ID
            obsForm.setEndpoint(obsConfig.getEndpoint());    // OBS Endpoint
            obsForm.setBucketname(obsConfig.getBucketName());// Bucket Name
            return Result.ok(obsForm, "获取表单签名成功");
        } catch (Exception e) {
            e.printStackTrace();
            return Result.fail("获取表单签名失败: " + e.getMessage());
        }
    }
}
// 注意:这里需要你有一个自定义的 Result 类来统一 API 返回格式
// 下面是一个简单的 Result 类示例:
class Result {
    private int code;
    private String msg;
    private T data;
    // 私有构造函数
    private Result() {}
    // 成功响应
    public static  Result ok(T data, String msg) {
        Result result = new Result<>();
        result.code = 200;
        result.msg = msg;
        result.data = data;
        return result;
    }
    public static  Result ok(T data) {
        return ok(data, "操作成功");
    }
    // 失败响应
    public static  Result fail(String msg) {
        Result result = new Result<>();
        result.code = 500;
        result.msg = msg;
        return result;
    }
    // Getters and Setters
    public int getCode() { return code; }
    public String getMsg() { return msg; }
    public T getData() { return data; }
}
6. 两种上传方式对比与选择
特性服务端直接上传 (/uploadFile)客户端表单签名上传 (/getForm + 前端直传)
数据路径客户端 -> 应用服务器 -> OBS客户端 -> OBS
服务器压力。需要接收、存储(临时)、转发文件流,消耗 CPU 和带宽。。仅负责生成签名,不参与文件传输。
上传速度较慢。受限于应用服务器的上行带宽。较快。客户端直接与 OBS 高速网络交互。
安全性较高。文件内容经过应用服务器,可以进行校验、过滤、 virus scan 等。中等。签名机制保证了上传权限,但服务器无法在上传前检查文件内容。
复杂度。后端逻辑简单,前端就是普通的文件上传。中等。需要前端配合,用 JavaScript 构造 FormData 并上传到 OBS。
适用场景文件较小、需要在服务端进行业务处理(如日志、审核)、对安全性要求极高的场景。文件较大(如图片、视频)、追求用户体验、希望减轻服务器负担的场景。

建议:对于大多数现代 Web 应用,强烈推荐使用客户端表单签名上传的方式,以获得更好的性能和 scalability。

7. 前端如何实现表单直传(示例)

当前端调用 /api/obs/getForm 获取到 policysignature 等信息后,就可以构造一个 FormData 并发送 POST 请求到 OBS 的地址。




    
    OBS 表单直传示例


    

OBS 表单直传示例

<script> async function uploadFile() { const fileInput = document.getElementById('fileInput'); const file = fileInput.files[0]; if (!file) { alert('请选择一个文件'); return; } try { // 1. 从你的后端服务获取上传凭证 console.log('正在获取上传凭证...'); const response = await fetch('/api/obs/getForm'); const result = await response.json(); if (result.code !== 200) { throw new Error('获取上传凭证失败: ' + result.msg); } const { policy, signature, accessKeyId, endpoint, bucketname } = result.data; console.log('获取上传凭证成功'); // 2. 构造 FormData const formData = new FormData(); formData.append('key', 'prefix/' + Date.now() + '_' + file.name); // 自定义文件在 OBS 中的路径和名称 formData.append('policy', policy); formData.append('signature', signature); formData.append('AccessKeyId', accessKeyId); formData.append('x-obs-acl', 'public-read'); // 必须与后端设置的一致 formData.append('content-type', file.type); // 可选,但建议设置 formData.append('file', file); // 文件对象 // 3. 构造 OBS 的上传地址: https://bucketname.endpoint const uploadUrl = `https://${bucketname}.${endpoint.replace('https://', '')}`; console.log('上传地址:', uploadUrl); // 4. 发送 POST 请求到 OBS console.log('开始上传文件...'); const uploadResponse = await fetch(uploadUrl, { method: 'POST', body: formData }); if (uploadResponse.ok) { // 上传成功,OBS 会返回 204 No Content console.log('文件上传成功!'); // 构建文件的访问 URL const fileUrl = `${uploadUrl}/${formData.get('key')}`; console.log('文件URL:', fileUrl); alert('文件上传成功! URL: ' + fileUrl); } else { const errorData = await uploadResponse.text(); throw new Error('文件上传失败: ' + uploadResponse.status + ' ' + errorData); } } catch (error) { console.error('上传过程出错:', error); alert('上传失败: ' + error.message); } } </script>
8. 总结与注意事项
  1. 安全第一

    • 切勿将 Access Key ID 和 Secret Access Key 硬编码在代码中或暴露给前端。
    • 使用 IAM (Identity and Access Management) 角色和策略来最小化 Access Key 的权限。
    • 对于表单上传,设置合理的 Expires 时间,并且在 Policy 中尽可能地限制上传条件(如 bucketkey 前缀,content-type 等)。
  2. 异常处理:代码中加入了基本的异常捕获,但在生产环境中,你需要更完善的异常处理和日志记录。

  3. 桶策略和 CORS

    • 确保你的 OBS 桶策略允许 PutObject 操作。
    • 如果前端页面和 OBS 桶不在同一个域名下,必须为你的 OBS 桶配置 CORS (跨域资源共享) 规则,允许来自你网站域名的 POST 请求。否则前端直传会失败。
  4. 文件命名:始终使用唯一的文件名(如 UUID)来避免覆盖。

通过本文的步骤,你已经掌握了在 Spring Boot 项目中集成华为云 OBS 进行文件上传的两种核心方法。根据你的业务场景选择合适的方案,并记得遵循最佳实践来保障你的应用安全和稳定。

posted @ 2026-01-21 20:20  clnchanpin  阅读(29)  评论(0)    收藏  举报