本文从本人博客搬运,原文格式更加美观,可以移步原文阅读:阿里云对象存储Java-SDK实战
目前项目中要保存上传的文件,很多时候都会用到对象存储。本文介绍阿里云对象存储Java-SDK在实际开发中的基本用法
首先要开通阿里云OSS服务,登录阿里云,找到对象存储OSS
点击立即开通
接着我们要用java代码完成将文件上传到阿里云OSS的功能。调用阿里云的服务接口需要AccessKey
,默认的AccessKey
是全局的,可以登录阿里云,并且权限很大,不推荐在程序中直接使用。我们可以新建一个子AccessKey
,专门用于编程中使用。进入AccessKey
管理,新建一个用于编程访问的子AccessKey
,并给其授予管理对象存储的权限
完成后,记得保存子AccessKey的Id与Secret,后续编程访问的时候会用到
一个Bucket
相当于一个文件仓库,可以用来存放我们上传的文件。我们可以在管理控制台创建Bucket
首先引入OSS的SDK依赖
<dependency>
<groupId>com.aliyun.ossgroupId>
<artifactId>aliyun-sdk-ossartifactId>
<version>3.8.0version>
dependency>
然后编写测试代码,上传一个本地文件,过程如下:
endpoint
、accessKeyId
、accessKeySecret
创建OSS
对象OSS
对象将文件流写入到之前创建的Bucket
中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();
}
代码运行成功后即可看到上传的文件
之前我们是自己创建OSS对象来进行操作,实际上还可以整合SpringCloud Alibaba来更加方便地使用OSS。打开官方文档,找到OSS相关
在SpringBoot项目中引入OSS的starter
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alicloud-ossartifactId>
dependency>
可以看到starter已经帮我们引入了OSS的基础SDK
然后在yml配置文件中配置endpoint
、accessKeyId
、accessKeySecret
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
测试访问接口,可以获取相关参数。前端拿到这些参数后,可以利用accessId
、policy
、signature
将文件直传到host
+dir
的目录中
实际开发中建议合理规划好文件保存的
dir
目录,可以用日期时间划分,也可以根据实际情况用不同的业务模块划分
之前我们对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
:临时的accessKeySignature
:签名我们每点击一次详情,都会生成一个新的临时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时传入的文件路径参数只需要带上文件夹即可,注意最外层文件夹前面不能带/