Spring Boot 集成阿里OSS对象存储

一、准备工作

  • 基础知识

    阿里OSS对象存储:云对象存储,一种分布式的文件存储实现方案
    费用计算:阿里OSS计费分三部分,存储费用、访问流量费用、请求费用

  • 相关依赖

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

    最新依赖获取地址:

二、OSS访问权限设置

  • 1. 访问控制

    OSS文件的访问控制一般都是基于用户的密钥和密匙来完成的;而授权的方式主要分为以下三种:

    • 给用户直接赋予OSS读写权限(以下简称用户模式)
    • 给用户分配用户组,给用户组赋予OSS权限(以下简称用户组模式)
    • 给角色赋予OSS权限,修改RAM角色信任策略(以下简称ARN模式),给用户分配AliyunSTSAssumeRoleAccess权限,允许扮演RAM角色。
  • 2. 创建用户密匙和密钥

在用户中选择访问OSS的用户,创建AccessKey(建议复制的同时将密匙文件下载下来),此时所生成的AccessKeyId和AccessKeySecret就是所需要使用的访问凭证。

创建AccessKey

  • 3. 权限赋予

创建用户(略)
  • 3.1 用户模式
    选择用户,创建用户(如果使用已有用户,跳过此步),然后给用户设置OSS读写权限

    OSS用户添加OSS读写权限

  • 3.2 用户组模式
    切换到用户组,同理,创建用户组(如果使用已有用户组,跳过此步),给用户在赋予OSS读写权限

    OSS用户组权限设置

    将指定的用户加入到当前组,该用户将会拥有组所拥有的所有权限

    用户加入组

  • 3.3 ARN模式
    ARN模式比较复杂,主要基于角色来控制权限,用户和权限不再直接绑定。
    a. 给用户赋予允许使用角色的权限:AliyunSTSAssumeRoleAccess

    用户STS权限

    b. 在角色页按步骤创建角色

    角色创建

    c. 为角色赋予OSS读写权限

    为角色授权

    d. 修改角色的策略

    修改角色信任策略

Tips:当前信任策略指仅对用户akina-oss生效,其他人无法使用当前角色

三、OssClient的配置、创建

阿里OSS对文件的操作主要基于OssClient对象,而根据使用的授权模式不同,其OssClient创建对象的方式也不同。

1. 创建OssClient的方式

  • 1.1 用户拥有OSS权限
    • 用户模式和用户组模式下用户都属于直接拥有权限,通过Key就可进行OSS的相关操作。
    // 直接放入端点、密钥和密匙
    OSS ossClient = new OSSClientBuilder()
                    .build("Endpoint", "AccessKeyId", "AccessKeySecret");
    
  • 1.2 用户通过角色间接拥有OSS权限
    // 先创建 STSAssumeRoleSessionCredentialsProvider 对象,放入地域、访问凭证、角色ARN码
    // region:形如 cn-chengdu 的地域属性(官方地域属性)
    // accessKeyId:略,accessKeySecret:略
    // roleArn;角色ARN代码,在创建角色修改信任策略时有提到
    STSAssumeRoleSessionCredentialsProvider credentialsProvider =
            CredentialsProviderFactory
                    .newSTSAssumeRoleSessionCredentialsProvider(
                            region, accessKeyId, accessKeySecret, roleArn);
    OSS ossClient = new OSSClientBuilder().build("Endpoint", credentialsProvider);
    

2. 基于配置文件的实现

  • 2.1. 配置和读取信息
    • 编写配置文件:配置密钥、密匙和可能需要动态配置的信息(仅供参考)
      ## ali-oss-config.properties 配置文件,Spring读取配置时 aaa-bbb-ccc 会默认转换为驼峰格式
     ## OSS 配置项
     # (必选)endpoint 访问端点,一般根据oss桶的地域进行选择,此处以成都为例
     oss.endpoint=oss-cn-chengdu.aliyuncs.com
     # (必选)授权密钥ID
     oss.access-key-id=LTAI5t************
     # (必选)授权密匙
     oss.access-key-secret=Mnwicrbz28UcG2L*****************
     # (必选)桶名称,创建桶时所取得名字
     oss.bucket-name=oss-nep-akina
     # (自选)文件上传后存放的根目录文件夹
     # 如果多个项目用了同一个桶,可以用项目名作为根目录文件夹,便于区分文件所属项目或者模块
     oss.folder-root=blog
    
    • 编写配置类:读取配置信息
     // 交给ioc容器管理
    @Component
    // 设置读取的配置项前缀,上述配置都是以oss开头
    @ConfigurationProperties(value = "oss")
    // 设置读取的配置文件,文件应存放于resources文件夹下
    @PropertySource("classpath:ali-oss-config.properties")
    public class AliOssProperties {
        /** 访问的域名 */
        public String endpoint;
        /** 阿里云账号访问ID */
        public String accessKeyId;
        /** 阿里云账号访问密匙 */
        public String accessKeySecret;
        /** Bucket名称 */
        public String bucketName;
        /** 主文件夹 */
        public String folderRoot;
        /** get和set方法 */
    }
    

3. 扩展:基于环境变量配置信息

  • 在系统环境变量中配置指定项,在代码中取获取
    // 从环境变量中获取键为OSS_ACCESS_KEY_ID的值
    String accessKeyId = System.getenv("OSS_ACCESS_KEY_ID");
    

四、简单工具类模板

上传和删除功能,
实际使用中应对ossClient调用方法做try-catch-final处理,catch处理异常,final释放资源
/**
 * AliOss工具类
 * @author Ak
 * @version 1.0
 */
public class AliOssManagerUtil {

    private static AliOssProperties properties;

    public AliOssManagerUtil(AliOssProperties properties) {
        AliOssManagerUtil.properties = properties;
    }

    /**
     * 上传文件到oss
     * @param folder 文价夹
     * @param file 文件
     * @return 路径
     */
    public static String uploadFile(String folder, MultipartFile file) throws Exception {
        OSS ossClient = new OSSClientBuilder()
                .build(properties.endpoint, properties.accessKeyId, properties.accessKeySecret);
        // 处理文件名
        String fileName = file.getOriginalFilename();
        String newName = properties.folderRoot + "/" + folder + "/" + UUID.randomUUID();
        if (null != fileName && !"".equals(fileName)) {
            newName += fileName.substring(fileName.lastIndexOf('.'));
        }

        ossClient.putObject(properties.bucketName, newName, file.getInputStream());
        ossClient.shutdown();
        return newName;
    }

    /**
     * 根据名字删除OSS服务器上的文件
     * @param folder 文件所属文价夹 如"head"
     * @param fileName 文件名 如:"cake.jpg"
     */
    public static void deleteFile(String folder, String fileName){
        OSS ossClient = new OSSClientBuilder()
                .build(properties.endpoint, properties.accessKeyId, properties.accessKeySecret);
        ossClient.deleteObject(properties.bucketName, properties.folderRoot + "/" + folder + "/" + fileName);
    }
}

五、代码中几种常用操作

  • 创建桶

    Bucket bucket = ossClient.createBucket(bucketName);
    
  • 文件上传

    签名模式:后端提供签名,由前端直传到OSS(此处只列举后端)

    // 获取签名,也可以创建一个对象来存方数据
    public Map<String, String> getOssSign() {
        // 上传的地址,配置了CDN加速的也可以拼接成加速域名
        String host = "https://" + properties.bucketName + "." + properties.endpoint;
        // 用户上传文件时文件所存放的文件夹
        String dir = properties.folderRoot;
        // 创建 OSSClient 实例
        OSS ossClient = null;
        try {
            // 策略过期时间(单位:秒)
            long expireTime = 100;
            // 计算出到期时间戳
            long expireEndTime = System.currentTimeMillis() + expireTime * 1000;
            Date expiration = new Date(expireEndTime);
    
            // PostObject 请求最大可支持的文件大小为5 GB,即CONTENT_LENGTH_RANGE为 5 * 1024 * 1024 * 1024
            PolicyConditions policyConditions = new PolicyConditions();
            policyConditions.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000);
            policyConditions.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir);
    
            // 创建 OSSClient 实例
            ossClient = new OSSClientBuilder()
                    .build(properties.endpoint, properties.accessKeyId, properties.accessKeySecret);
    
            // 获取发布策略
            String postPolicy = ossClient.generatePostPolicy(expiration, policyConditions);
            byte[] binaryData = postPolicy.getBytes(StandardCharsets.UTF_8);
            // Base64加密
            String encodedPolicy = BinaryUtil.toBase64String(binaryData);
            // 获取最终签名
            String postSignature = ossClient.calculatePostSignature(postPolicy);
    
            // 返回签名以及 OSS 相关参数
            Map<String, String> respMap = new LinkedHashMap<>();
            respMap.put("accessid", properties.accessKeyId);
            respMap.put("policy", encodedPolicy);
            respMap.put("signature", postSignature);
            respMap.put("dir", dir);
            respMap.put("host", host);
            respMap.put("expire", String.valueOf(expireEndTime / 1000));
            return respMap;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
        return null;
    }
    

    后端模式

    // 文本文件
    PutObjectResult result = ossClient.putObject("oss-nep-akina", "testArn.text", new ByteArrayInputStream(content.getBytes()));
    // 任意文件,此处的file为MultipartFile类型,前端传的文件一般用该对象接收
    PutObjectResult result = ossClient.putObject("oss-nep-akina", "test.text", file.getInputStream());
    
  • 文件删除

    // 删除文件
    ossClient.deleteObject(properties.bucketName, fileFullPath);
    
  • 文件列举

    // 获取指定文件夹下的所有文件(不指定prefix会列举全部文件)
    ObjectListing objectListing = ossClient.listObjects(properties.bucketName, "blog/test/");
    // objectListing.getObjectSummaries获取所有文件的描述信息
    for (OSSObjectSummary objectSummary : objectListing.getObjectSummaries()) {
        System.out.println(" - " + objectSummary.getKey() + "  " + "(size = " + objectSummary.getSize() + ")");
    }
    

参考文章:对象存储 OSS 阿里官方文档