侧边栏壁纸
博主头像
包包博主等级

talk is cheap,show me the code

  • 累计撰写 25 篇文章
  • 累计创建 59 个标签
  • 累计收到 55 条评论

阿里云对象存储Java-SDK实战

包包
2021-06-24 / 0 评论 / 4 点赞 / 1,516 阅读 / 6,847 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2022-04-21,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

目前项目中要保存上传的文件,很多时候都会用到对象存储。本文介绍阿里云对象存储Java-SDK在实际开发中的基本用法

基本使用

1.开通OSS服务

首先要开通阿里云OSS服务,登录阿里云,找到对象存储OSS

点击立即开通

2.创建子AccessKey

接着我们要用java代码完成将文件上传到阿里云OSS的功能。调用阿里云的服务接口需要AccessKey,默认的AccessKey是全局的,可以登录阿里云,并且权限很大,不推荐在程序中直接使用。我们可以新建一个子AccessKey,专门用于编程中使用。进入AccessKey管理,新建一个用于编程访问的子AccessKey,并给其授予管理对象存储的权限

完成后,记得保存子AccessKey的Id与Secret,后续编程访问的时候会用到

3.创建Bucket

一个Bucket相当于一个文件仓库,可以用来存放我们上传的文件。我们可以在管理控制台创建Bucket

4.编程访问

首先引入OSS的SDK依赖

<dependency>
    <groupId>com.aliyun.oss</groupId>
    <artifactId>aliyun-sdk-oss</artifactId>
    <version>3.8.0</version>
</dependency>

然后编写测试代码,上传一个本地文件,过程如下:

  1. 利用endpointaccessKeyIdaccessKeySecret创建OSS对象
  2. 利用OSS对象将文件流写入到之前创建的Bucket
  3. 关闭OSS对象
@Test
public void testUploadFile() throws FileNotFoundException {
    // Endpoint以杭州为例,其它Region请按实际情况填写。
    String endpoint = "oss-cn-hangzhou.aliyuncs.com";
    // 云账号AccessKey有所有API访问权限,建议遵循阿里云安全最佳实践,创建并使用RAM子账号进行API访问或日常运维,请登录 https://ram.console.aliyun.com 创建。
    String accessKeyId = "用之前创建的子AccessKey";
    String accessKeySecret = "用之前创建的子AccessKey";

    // 创建OSSClient实例。
    OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
    // 上传文件流。
    InputStream inputStream = new FileInputStream("C:\\Users\\baobao\\Desktop\\guojia.jpg");
    // 将文件写入Bucket,参数1:Bucket名称  参数2:上传后的文件名  参数3:文件输入流
    ossClient.putObject("gulimall-baobao", "guojia.jpg", inputStream);
    // 关闭OSSClient。
    ossClient.shutdown();
}

代码运行成功后即可看到上传的文件

使用SpringCloud Alibaba快捷操作

之前我们是自己创建OSS对象来进行操作,实际上还可以整合SpringCloud Alibaba来更加方便地使用OSS。打开官方文档,找到OSS相关

在SpringBoot项目中引入OSS的starter

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

可以看到starter已经帮我们引入了OSS的基础SDK

然后在yml配置文件中配置endpointaccessKeyIdaccessKeySecret

server:
  port: 30000
spring:
  cloud:
    alicloud:
      oss:
        endpoint: oss-cn-hangzhou.aliyuncs.com
      access-key: ...
      secret-key: ...

在测试代码中无需创建ossClient,只需要将其注入进来即可使用

@RunWith(SpringRunner.class)
@SpringBootTest
public class GulimallThirdpartyApplicationTests {
    // 注入ossClient
    @Autowired
    private OSS ossClient;

    @Test
    public void testUploadFile() throws FileNotFoundException {
        // 上传文件流。
        InputStream inputStream = new FileInputStream("C:\\Users\\baobao\\Desktop\\guojia.jpg");
        ossClient.putObject("gulimall-baobao", "guojia222.jpg", inputStream);
        // 关闭OSSClient
        ossClient.shutdown();
    }
}

前端直传

如果我们用之前的方式上传文件到OSS,那么后端需要先接收前端表单上传的文件,然后再通过SDK将文件上传到OSS。但是实际开发中不推荐这么做,因为这种方式使得文件流先从客户端到后端应用服务器,然后再由服务器转给OSS,相当于多了1次中转,十分浪费服务器带宽,当很多用户都在上传的时候对后端应用服务器的带宽以及处理压力很大

我们可以采取的优化方式是:在浏览器提交上传Policy请求给服务器,服务器返回上传Policy和签名,将上传地址等重要信息告诉浏览器,这样就可以由浏览器在客户端直接完成上传到oss的操作,分担了服务器压力

新建一个OssController,编写生成Policy和前面的方法,参考阿里云官方SDK文档

@RestController
public class OssController {
    // 注入ossClient
    @Autowired
    private OSS ossClient;
    // 从配置文件中获取accessKey、endpoint等必要信息
    @Value("${spring.cloud.alicloud.oss.endpoint}")
    private String endpoint;
    @Value("${spring.cloud.alicloud.access-key}")
    private String accessKey;
    @Value("${spring.cloud.alicloud.secret-key}")
    private String secretKey;
    @Value("${spring.cloud.alicloud.bucket-name}")
    private String bucketName;

    @RequestMapping("oss/policy")
    public Map<String, String> policy() {
        // 用户上传文件时指定的前缀,指定当前日期
        String date = DateTimeFormatter.ofPattern("yyyy-MM-dd").format(LocalDate.now());
        String dir = date + "/";
        // host的格式为 bucketname.endpoint
        String host = "https://" + bucketName + "." + endpoint;
        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("utf-8");
            String encodedPolicy = BinaryUtil.toBase64String(binaryData);
            String postSignature = ossClient.calculatePostSignature(postPolicy);

            Map<String, String> respMap = new LinkedHashMap<>();
            respMap.put("accessid", accessKey);
            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));
            return respMap;
        } catch (Exception e) {
            // Assert.fail(e.getMessage());
            System.out.println(e.getMessage());
            return null;
        } finally {
            ossClient.shutdown();
        }
    }
}

然后注意由于我们自定义了bucketName也从yml配置文件中取,所以要在yml中定义

server:
  port: 30000
spring:
  cloud:
    alicloud:
      oss:
        endpoint: oss-cn-hangzhou.aliyuncs.com
      access-key: ...
      secret-key: ...
      bucket-name: gulimall-baobao  # 配置bucket-name

测试访问接口,可以获取相关参数。前端拿到这些参数后,可以利用accessIdpolicysignature将文件直传到host+dir的目录中

实际开发中建议合理规划好文件保存的dir目录,可以用日期时间划分,也可以根据实际情况用不同的业务模块划分

临时URL访问文件

之前我们对Bucket的读写权限设置为公共读,这样只要获取了文件的url,就可以在浏览器随意访问了,安全性不好

一般建议是将Bucket的读写权限设置为私有,此时再通过文件url访问将会提示没有权限

需要在url上附带一些参数,用这个附带参数的临时url才能访问到文件,而临时url过期后就无法再访问了。我们进入oss控制台,点击文件的详情就会显示临时url

分析一下这个url的结构

https://gulimall-baobao.oss-cn-hangzhou.aliyuncs.com/guojia.jpg?Expires=1616766718&OSSAccessKeyId=TMP.3KjvuWJovuqLjNppoo3D3jvDbZFbrfovBR39zhHkdmzrm6M9mdwJAkbc1Ffdhn77nyVWTi2PATz97t5zSKr2TDhoKiE1SY&Signature=jAVkArqRd8K2jFswLy9%2BzH62Emk%3D

可以看出其携带了3个参数:

  • Expires:临时url的过期时间,默认5分钟
  • OSSAccessKeyId:临时的accessKey
  • Signature:签名

我们每点击一次详情,都会生成一个新的临时url,过期时间为点击后的5分钟,临时的accessKey都相同,但是签名不同。可以推测生成签名的参数中有过期时间

在我们自己的应用中,公共读的方式下,后端数据库只要保存文件上传后的url即可,前端需要展示图片的时候只要向后端请求获取图片url并交给图片组件即可。然而改成私有以后,前端如何向后端请求临时url并显示图片文件呢?此时后端接收到前端访问图片的请求,并获取数据库中图片的url后,不能直接返回给前端,需要先解析出图片文件在Bucket中的路径,然后根据bucketName、临时url超时时间、要访问文件在Bucket中的路径这3个参数生成一个临时的url返回给前端

@Component
public class AliyunOssService {
    // Endpoint以杭州为例,其它Region请按实际情况填写。
    String endpoint = "oss-cn-hangzhou.aliyuncs.com";
    // 阿里云主账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM账号进行API访问或日常运维,请登录RAM控制台创建RAM账号。
    String accessKeyId = "...";
    String accessKeySecret = "...";
    String bucketName = "gulimall-baobao";

    // objectName即文件在Bucket中的路径
    public String getTempUrl(String objectName){
        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
        // 设置URL过期时间为1小时。
        Date expiration = new Date(System.currentTimeMillis() + 3600 * 1000);
        // 生成以GET方法访问的签名URL,访客可以直接通过浏览器访问相关内容。其中objectName即文件在Bucket中的路径
        URL url = ossClient.generatePresignedUrl(bucketName, objectName, expiration);
        // 关闭OSSClient。
        ossClient.shutdown();
        return url.toString();
    }
}
@SpringBootTest
class AliyunOssDemoApplicationTests {
    @Autowired
    private AliyunOssService aliyunOssService;

    @Test
    void contextLoads() {
        String tempUrl = aliyunOssService.getTempUrl("guojia.jpg");
        System.out.println(tempUrl);
    }
}

测试生成的临时url如下,生成的规则是http://bucketName.endPoint/要访问的文件在Bucket中的路径,后面携带超时时间、临时的accessKey、签名等参数

如果要访问的文件位于Bucket中的某个文件夹下,获取临时url时传入的文件路径参数只需要带上文件夹即可,注意最外层文件夹前面不能带/

4

评论区