文章目录
1、前言
阿里云对象存储**OSS(Object Storage Service)**是一款海量、安全、低成本、高可靠的云存储服务,这也是我们开发过程中较为常用的一个服务。Web端常见的上传方法是用户在浏览器或App端上传文件到应用服务器,应用服务器再把文件上传到OSS。
而在这里则是基于 Post Policy(用户表单上传的策略) 的使用规则在服务端完成签名,然后通过表单直传数据到OSS。由于服务端签名直传无需将AccessKey暴露在前端页面,相比JavaScript客户端签名直传具有更高的安全性。
参考文档:
另外一种接收文件集合上传返回图片链接的上传方式:【OSS】SpringBoot搭配线程池整合阿里云OSS实现图片异步上传
2、阿里云OSS
2.1、开通服务
进入到阿里云控制台,打开左上角的菜单选中对象存储OSS(找不到的可以在产品与服务中搜索)
2.2、创建Bucket
在开通OSS对象存储服务之后,点击新建Bucket,填写自己的Bucket Name和地狱,优先选择靠近自己所在地的,完成设置之后确认即可记得这里保存后弹出来的界面的概览的访问域名的外网访问的Endpoint(地域节点)。
下面是我个人的一个选项参数供大家参考(个人用途为项目存储图片):
- 存储类型:低频访问存储,因为为个人练手项目,因此不需要很高的访问量;
- HDFS服务:不搞,穷得叮当响;
- 同城冗余存储:不搞,穷得叮当响;
- 版本控制:不搞,穷得叮当响;
- 读写权限:公共读,方便访问,但是不要开公共读写;
- 服务端加密方式:不搞,穷得叮当响;
- 实时日志查询:不搞,穷得叮当响。
2.3、创建子账户
创建子账户获取AccessKey ID
和AccessKey Secret
。这一操作在鼠标停顿在右上角头像 -> AccessKey管理(前提是进入控制台之后头像才会有这一菜单)。
- 在里面创建用户,选中Open API之后确认,会提示你进行认证,这里记得记得,AK和AS一定要复制下来保存好,截个图也行,因为你关掉这一信息之后再也找不到AS了。
- 在用户界面找到创建的子账户,点击添加权限进行分配权限,让它拥有操作OSS的权限。
3、服务端
3.1、依赖导入
在服务端,主要就是对OSS进行一个配置连接,利用阿里云提供的API生成需要的签名。针对这一需求,我们需要导入阿里云OSS云存储依赖,注意的是这里已忽略Spring web、Lombok等依赖。
<dependencies>
<!-- 略 -->
<!--阿里云OSS云存储依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alicloud-oss</artifactId>
<version>2.2.0.RELEASE</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.6.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
3.2、配置文件
为了数据的统一管理,将服务所需数据统一填写在application.yml文件中,同样也可以直接存放在类中,下面是各个属性数据的获取处:
- endpoint:地域节点,位于
对象存储/Bucket 列表/所创建的Bucket/概览
下的访问域名的外网访问的Endpoint(地域节点); - bucket:所创建的Bucket名;
- access-key:创建子账户时生成的AK,如果前面保存了可直接cv,没有的话也可前往子账户界面进行查看;
- secret-key:创建子账户时生成的AS,只能前面保存然后进行cv,没有保存的话需重新创建子账户。
# 指定端口号
server:
port: 8808
spring:
# 服务名称
application:
name: 你的服务名称
# OSS配置
cloud:
alicloud:
oss:
endpoint: 你的地域节点
bucket: 你的Bucket名
access-key: 你的AK
secret-key: 你的AS
3.3、控制器编写
在controller包下新建OssController类,在其中编写生成签名等信息的相关操作。
@RestController
@RequestMapping("/oss")
public class OssController {
@Autowired
OSS ossClient;
@Value("${spring.cloud.alicloud.oss.endpoint}")
String endpoint;
@Value("${spring.cloud.alicloud.oss.bucket}")
String bucket;
@Value("${spring.cloud.alicloud.access-key}")
String accessId;
@Value("${spring.cloud.alicloud.secret-key}")
String accessKey;
@GetMapping("/policy")
public Result policy() {
// host的格式为 bucketname.endpoint,即https://你的bucket名.地域节点/文件名.文件后缀
String host = "https://" + bucket + "." + endpoint;
// 用户上传文件时指定的前缀,即存放在以时间命名的文件夹内
String dir = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
Map<String, String> respMap = null;
try {
// 过期时间
long expireTime = 30;
long expireEndTime = System.currentTimeMillis() + expireTime * 1000;
Date expiration = new Date(expireEndTime);
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);
respMap = new LinkedHashMap<String, String>();
// AK
respMap.put("accessid", accessId);
// 用户表单上传的策略(Policy)
respMap.put("policy", encodedPolicy);
// 签名
respMap.put("signature", postSignature);
// 上传文件时指定的前缀
respMap.put("dir", dir);
// oss保存文件的host
respMap.put("host", host);
// 过期时间
respMap.put("expire", String.valueOf(expireEndTime / 1000));
} catch (Exception e) {
// Assert.fail(e.getMessage());
System.out.println(e.getMessage());
} finally {
// 关闭oss客户端流
ossClient.shutdown();
}
return Result.ok().put("data", respMap);
}
}
3.4、接口测试
由于请求的方式为Get,因此可以直接使用浏览器,或使用Postman、Apifox等接口测试工具进行测试,这里使用的是Apifox,请求http://localhost:8808/oss/policy
。
4、前端联调
4.1、组件编写
这里使用到的前端技术为Vue+Element-UI,这里只提供了单独的文件上传组件,大家自行导入使用即可,但是记得下载Element-UI以及UUID的依赖包。注意的是上传组件中的action需要你创建的Bucket中的Bucket域名,这个和地域节点获取的地方一致。
<template>
<div>
<el-upload
action="你创建的Bucket中的Bucket域名,xu"
:data="dataObj"
list-type="picture"
:multiple="false" :show-file-list="showFileList"
:file-list="fileList"
:before-upload="beforeUpload"
:on-remove="handleRemove"
:on-success="handleUploadSuccess"
:on-preview="handlePreview">
<el-button size="small" type="primary">点击上传</el-button>
<div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过10MB</div>
</el-upload>
<el-dialog :visible.sync="dialogVisible">
<img width="100%" :src="fileList[0].url" alt="">
</el-dialog>
</div>
</template>
<script>
export default {
name: 'singleUpload',
props: {
value: String
},
computed: {
imageUrl() {
return this.value;
},
imageName() {
if (this.value != null && this.value !== '') {
return this.value.substr(this.value.lastIndexOf("/") + 1);
} else {
return null;
}
},
fileList() {
return [{
name: this.imageName,
url: this.imageUrl
}]
},
showFileList: {
get: function () {
return this.value !== null && this.value !== ''&& this.value!==undefined;
},
set: function (newValue) {
}
}
},
data() {
return {
// 封装服务端返回的数据
dataObj: {
policy: '',
signature: '',
key: '',
ossaccessKeyId: '',
dir: '',
host: '',
},
dialogVisible: false
};
},
methods: {
emitInput(val) {
this.$emit('input', val)
},
handleRemove(file, fileList) {
this.emitInput('');
},
handlePreview(file) {
this.dialogVisible = true;
},
beforeUpload(file) {
console.log(file.size);
let _self = this;
return new Promise((resolve, reject) => {
// 这里使用了封装的请求方式,可以直接换成axios即可
http({
url: http.adornUrl("/oss/policy"),
method: "get",
params: http.adornParams({})
}).then(response => {
console.log("响应的数据",response);
_self.dataObj.policy = response.data.policy;
_self.dataObj.signature = response.data.signature;
_self.dataObj.ossaccessKeyId = response.data.accessid;
// 存放进oss的命名格式为uuid+原本文件名
_self.dataObj.key = response.data.dir + '/' + getUUID()+'_${filename}';
_self.dataObj.dir = response.data.dir;
_self.dataObj.host = response.data.host;
resolve(true)
}).catch(err => {
reject(false)
})
})
},
handleUploadSuccess(res, file) {
console.log("上传成功...")
this.showFileList = true;
this.fileList.pop();
this.fileList.push({name: file.name, url: this.dataObj.host + '/' + this.dataObj.key.replace("${filename}",file.name) });
this.emitInput(this.fileList[0].url);
}
}
}
</script>
<style>
</style>
4.2、开启跨域访问
这时候功能按道理来说其实都是可以正常运行的了,只是在前端测试的时候会出现CORS跨域问题。这时因为客户端进行表单直传到OSS时,会从浏览器向OSS发送带有Origin
的请求消息。OSS对带有Origin
头的请求消息会进行跨域规则(CORS)的验证。因此需要为Bucket设置跨域规则以支持Post方法。
- 登录OSS管理控制台。
- 单击Bucket列表,然后单击目标Bucket名称。
- 在左侧导航栏,选择****权限管理** > *跨域设置*,然后在跨域设置区域,单击设置**。
- 单击创建规则,配置如下图所示,确认即可。
4.3、整体测试
将前端项目和后端项目都跑起来之后,我们来到前端界面进行上传测试,下面是测试结果: