SpringBoot整合阿里云OSS对象存储

1,813 阅读3分钟

「这是我参与11月更文挑战的第13天,活动详情查看:2021最后一次更文挑战」。

前言

附件上传相信大家都了解的,整合阿里云OSS对象存储时踩了点小坑。这里记录正确整合步骤,方便以后快速整合。官方文档,感兴趣大家可以自行阅读,这里不赘述了。

附件上传

常见的上传逻辑应该是,Web端上传文件到应用服务器,应用服务器再把文件上传到OSS。具体流程如下图所示。

image.png

但是这种方案存在以下缺点:

  • 速度慢,经过两道传输,时间起码增加一倍
  • 浪费性能,万一用户群体变大,服务器将成为我们的瓶颈

最佳方案 客户端签名直传

由于OSS上行流量是免费的,如果数据直传到OSS,速度会大幅缩减,并且节约了服务器资源,缓解了服务端压力。 image.png

  1. 用户向应用服务器请求上传Policy
  2. 应用服务器返回上传Policy和签名给用户。
  3. 用户直接向OSS发送文件上传请求。

整合步骤

1. 开通阿里云OSS对象存储服务

这个就不介绍了,登录阿里云平台,跟着引导操作就行

2. 获取以下四个参数信息

  • endpoint
  • accessKeyId
  • accessKeySecret
  • bucketName endpoint 点击Bucket列表 --> 新建Bucket --> 填好bucket名称 --> 选择地域 --> 根据地域不同会给出不同的endpoint image.png 到这里我们已经有了bucketNameendpoint

如果忘记了可以在以下路径查找 image.png

accessKeyId和accessKeySecret

点击右上角账户头像 --> 点击AccessKey管理

image.png

通过创建子账号的方式创建一个只有OSS相关权限的用户

image.png

创建用户

image.png

创建accesskey,生成完一定要记录下来,页面关掉就只能重新生成了 image.png

给用户添加权限

image.png

添加读写权限 image.png

到这里所有准备工作就完成了,接下来代码整合

SpringBoot代码整合

1. 引入依赖

<!--   阿里云oss存储     -->
<dependency>
    <groupId>com.aliyun.oss</groupId>
    <artifactId>aliyun-sdk-oss</artifactId>
    <version>3.13.2</version>
</dependency>

2. 配置文件定义属性值

application.yml中添加配置属性

# 阿里云oss
aliyun:
  oss:
    endpoint: oss-cn-shanghai.aliyuncs.com
    accessKeyId: LTAI5tH********Cr88dn
    accessKeySecret: ayKgKy4BY********0gLaUI9
    bucketName: yourname

3. 添加配置对象

自动配置OSSClient对象

@Component
public class OssClient {

    @Value("${aliyun.oss.endpoint}")
    private String endpoint;

    @Value("${aliyun.oss.bucketName}")
    private String bucketName;

    @Value("${aliyun.oss.accessKeyId}")
    private String accessKeyId;

    @Value("${aliyun.oss.accessKeySecret}")
    private String accessKeySecret;

    @Bean
    public OSS getOSSClient() {
        return new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
    }
}

4. 编写获取policy接口

@RestController
@RequestMapping("/oss")
public class OSSController {

    @Autowired
    OSS ossClient;
    
    @Value("${aliyun.oss.endpoint}")
    private String endpoint;

    @Value("${aliyun.oss.bucketName}")
    private String bucketName;

    @Value("${aliyun.oss.accessKeyId}")
    private String accessKeyId;

    @ApiOperation("获取签名policy")
    @GetMapping("/policy")
    public Map<String, String> policy() {
        // host的格式为 bucketname.endpoint
        String host = "https://" + bucketName + "." + endpoint;
        // callbackUrl为上传回调服务器的URL,请将下面的IP和Port配置为您自己的真实信息。
        // String callbackUrl = "http://88.88.88.88:8888";
        String today = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
        // 按日期分文件夹存储
        String dir = today + "/"; // 用户上传文件时指定的前缀。
        Map<String, String> respMap = null;
        try {
            long expireTime = 30;
            long expireEndTime = System.currentTimeMillis() + expireTime * 1000;
            Date expiration = new Date(expireEndTime);
            // PostObject请求最大可支持的文件大小为5 GB,即CONTENT_LENGTH_RANGE为5*1024*1024*1024。
            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);
            byte[] binaryData = postPolicy.getBytes(StandardCharsets.UTF_8);
            String encodedPolicy = BinaryUtil.toBase64String(binaryData);
            String postSignature = ossClient.calculatePostSignature(postPolicy);

            respMap = new LinkedHashMap<String, String>();
            respMap.put("accessid", accessKeyId);
            respMap.put("policy", encodedPolicy);
            respMap.put("signature", postSignature);
            respMap.put("dir", dir);
            respMap.put("host", host);
            respMap.put("expire", String.valueOf(expireEndTime / 1000));
            // respMap.put("expire", formatISO8601Date(expiration));
        } catch (Exception e) {
            // Assert.fail(e.getMessage());
            System.out.println(e.getMessage());
        } finally {
            ossClient.shutdown();
        }
        return respMap;
    }
}

到这里服务端的工作就完成了

5. 测试

调用获取policy接口返回

{
    "accessid": "LTAI5tHZ1ro3zUUuZmCr88dn",
    "policy": "eyJleHBpcmF0aW9uIj1iMjAyMS0xMS0xNVQwOTozODowOS4zOThaIiwiY29uZGl0aW9ucyI6W1siY29udGVudC1sZW5ndGgtcmFuZ2UiLDAsMTA0ODU3NjAwMF0sWyJzdGFydHMtd2l0aCIsIiRrZXkiLCIyMDIxLTExLTE1LyJdXX0=",
    "signature": "3K/6hXZPCYCNBwUFBaec7CmAa70=",
    "dir": "2021-11-15/",
    "host": "https://thinkfon-member.oss-cn-shanghai.aliyuncs.com",
    "expire": "1636969089"
}
字段描述
accessid用户请求的AccessKey ID。
host用户发送上传请求的域名。
policy用户表单上传的策略(Policy),Policy为经过Base64编码过的字符串。详情请参见Post Policy
signature对Policy签名后的字符串。详情请参见Post Signature
expire由服务器端指定的Policy过期时间,格式为Unix时间戳(自UTC时间1970年01月01号开始的秒数)。
dir限制上传的文件前缀。