1、前言
阿里云对象存储OSS(Object Storage Service)是一款海量、安全、低成本、高可靠的云存储服务,这也是我们开发过程中较为常用的一个服务。Web端常见的上传方法是用户在浏览器或App端上传文件到应用服务器,应用服务器再把文件上传到OSS。
而在这里则是基于 Post Policy(用户表单上传的策略) 的使用规则在服务端完成签名,然后通过表单直传数据到OSS。由于服务端签名直传无需将AccessKey暴露在前端页面,相比JavaScript客户端签名直传具有更高的安全性。
2、阿里云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、配置文件
oss:
endpoint: 你的地域节点
accessKeyId: 你的AK
accessKeySecret: 你的AS
bucket: 你的Bucket名
为了数据的统一管理,将服务所需数据统一填写在nacos文件中,同样也可以直接存放在类中,下面是各个属性数据的获取处:
- endpoint:地域节点,位于对象存储/Bucket
列表/所创建的Bucket/概览下的访问域名的外网访问的Endpoint(地域节点); - accessKeyId:创建子账户时生成的AK,如果前面保存了可直接cv,没有的话也可前往子账户界面进行查看;
- accessKeySecret:创建子账户时生成的AS,只能前面保存然后进行cv,没有保存的话需重新创建子账户。
- bucket:所创建的Bucket名;
3.3、代码编写
编写实体类
@Data
public class OssConfigProperties {
private String endpoint;
private String accessKeyId;
private String accessKeySecret;
private String bucket;
}
初始化容器
@Slf4j
@Configuration
public class OssConfig {
@Bean("ossConfigPro")
@ConfigurationProperties(prefix = "oss")
public OssConfigProperties ossConfigProperties() {
return new OssConfigProperties();
}
@Bean("OssUtils")
public OssUtils ossOperationUtils(@Autowired OssConfigProperties ossConfigPro) {
return new OssUtils(ossConfigPro);
}
}
@Slf4j
public class OssUtils {
private final OSS ossClient;
private final String bucketName;
private final String accessId;
private String endpoint;
//初始化oss客户端
public OssUtils(OssConfigProperties ossConfigPro) {
String endpoint = ossConfigPro.getEndpoint();
String accessId = ossConfigPro.getAccessId();
String accessKey = ossConfigPro.getAccessKey();
String bucketName = ossConfigPro.getBucket();
if (StringUtils.isBlank(endpoint) || StringUtils.isBlank(accessId)
|| StringUtils.isBlank(accessKey) || StringUtils.isBlank(bucketName)) {
throw new ServiceException("please check oss config!");
}
this.ossClient = new OSSClientBuilder().build(endpoint, accessId, accessKey);
this.bucketName = bucketName;
this.accessId = accessId;
this.endpoint = endpoint;
}
}
/**
* 获取签名信息
*
* @return {@link PolicyVo}
*/
public PolicyVo getPolicy(String dir) {
// host的格式为 bucketname.endpoint,即https://你的bucket名.地域节点/文件名.文件后缀
endpoint = endpoint.replace("https://", "");
// 用户上传文件时指定的前缀,即存放在以时间命名的文件夹内
dir = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
String host = "https://" + bucketName + "." + endpoint;
PolicyVo policyVo = new PolicyVo();
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);
// AK
policyVo.setAccessId(accessId);
// 用户表单上传的策略(Policy)
policyVo.setPolicy(encodedPolicy);
// 签名
policyVo.setSignature(postSignature);
// 上传文件时指定的前缀
policyVo.setDir(dir);
// oss保存文件的host
policyVo.setHost(host);
// 过期时间
policyVo.setExpire(String.valueOf(expireEndTime / 1000));
} catch (Exception e) {
log.info(e.getMessage());
}
return policyVo;
}
签名信息实体
@Data
@ApiModel("签名信息实体")
public class PolicyVo {
@ApiModelProperty("AK")
private String accessId;
@ApiModelProperty("用户表单上传的策略(Policy)")
private String policy;
@ApiModelProperty("签名")
private String signature;
@ApiModelProperty("上传文件时指定的前缀")
private String dir;
@ApiModelProperty("oss保存文件的host")
private String host;
@ApiModelProperty("过期时间")
private String expire;
}
控制层编写
@Resource
private OssUtils ossUtils;
@GetMapping(path = "/getPolicy")
@ApiOperation(value = "获取签名信息(获取文件签名)")
public BaseResponse<PolicyVo> getPolicy() {
return BaseResponse.success(ossUtils.getPolicy());
}
参考产品 https://help.aliyun.com/document_detail/91868.html?spm=a2c4g.267439.0.0.68125d3f9Nc7YP
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>
- ps:前端直传
// 设置服务端返回200状态码,默认返回204。
‘success_action_status’ : ‘200’,
4.2、开启跨域访问
这时候功能按道理来说其实都是可以正常运行的了,只是在前端测试的时候会出现CORS跨域问题。这时因为客户端进行表单直传到OSS时,会从浏览器向OSS发送带有Origin的请求消息。OSS对带有Origin头的请求消息会进行跨域规则(CORS)的验证。因此需要为Bucket设置跨域规则以支持Post方法。
- 登录OSS管理控制台。
- 单击Bucket列表,然后单击目标Bucket名称。
- 在左侧导航栏,选择****权限管理** > 跨域设置,然后在跨域设置区域,单击设置**。
- 单击创建规则,配置如下图所示,确认即可。
敬请期待我的下一篇文章,谢谢。
参考文章:
https://blog.csdn.net/Aqting/article/details/126486282?spm=1001.2014.3001.5501