vue+oss实现服务端签名后直传的图片上传功能
vue+oss实现服务端签名后直传的图片上传功能
vue+oss实现服务端签名后直传的图片上传功能
- vue+oss实现服务端签名后直传的图片上传功能
- 前言
- 一、java服务端签名接口设计
- 二、Vue + element UI的upload图片上传
- 三、前端直传oss可能出现跨域问题
- 四、服务端签名流程
- 总结
前言
基于Post Policy的使用规则在服务端通过java后端代码完成签名,然后通过表单直传数据到OSS。由于服务端签名直传无需将AccessKey暴露在前端页面,相比JavaScript客户端签名直传具有更高的安全性。
一、java服务端签名接口设计
- 引入OSS依赖
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alicloud-oss</artifactId><version>2.2.0.RELEASE</version></dependency>
- Controller层设计
@GetMapping("/policy")public R policy() {LinkedHashMap<String, String> map = ossService.policy();return R.ok().put("data",map);}
- 实现类设计
@Autowired
OSS ossClient;
@Value("${spring.cloud.alicloud.oss.endpoint}")
private String endpoint;
@Value("${spring.cloud.alicloud.oss.bucket}")
private String bucket;@Value("${spring.cloud.alicloud.access-key}")
private String accessId;public LinkedHashMap<String, String> policy() {// 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。// 填写Host地址,格式为https://bucketname.endpoint。String host = "https://" + bucket + "." + endpoint;// 设置上传回调URL,即回调服务器地址,用于处理应用服务器与OSS之间的通信。OSS会在文件上传完成后,把文件上传信息通过此回调URL发送给应用服务器。
// String callbackUrl = "https://192.168.0.0:8888";// 设置上传到OSS文件的前缀,可置空此项。置空后,文件将上传至Bucket的根目录下。String dir = "banner/uat/";Map<String, String> respMap = null;// 创建ossClient实例。
// OSS ossClient = new OSSClientBuilder().build(endpoint, accessId, accessKey);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>();respMap.put("accessId", accessId);respMap.put("policy", encodedPolicy);respMap.put("signature", postSignature);respMap.put("dir", dir);respMap.put("host", host);respMap.put("expire", String.valueOf(expireEndTime / 1000));// 设置服务端返回200状态码,默认返回204。respMap.put("success_action_status","200");} catch (Exception e) {// Assert.fail(e.getMessage());System.out.println(e.getMessage());}return (LinkedHashMap<String, String>) respMap;}
二、Vue + element UI的upload图片上传
- 页面
- 图片上传前处理方法
- 上传成功后调用方法
- 前端完整代码
父组件:
<el-form-item label="上传图片" :prop="image" :rules="rules.image"><SingleUpload v-model="image"></SingleUpload></el-form-item>
import SingleUpload from "@/views/subupload";
子组件:
<template><div><el-uploadclass="avatar-uploader":data="dataObj"action="https://ron-test.oss-cn-shanghai.aliyuncs.com":show-file-list="false":on-success="handleAvatarSuccess":before-upload="beforeAvatarUpload"><img v-if="imageUrl" :src="imageUrl" class="avatar" /><i v-else class="el-icon-plus avatar-uploader-icon"></i></el-upload></div>
</template><script>
import { policy } from "@/api/upload";
export default {name: "singleUpload",props: {value: String},computed: {imageUrl() {return this.value;}},data() {return {// imageUrl: "",dataObj: {accessId: "",policy: "",signature: "",dir: "",host: "",expire: ""}};},methods: {emitInput(val) {this.$emit("input", val);},handleAvatarSuccess(file) {console.log("上传成功", file);// this.imageUrl = URL.createObjectURL(file.raw);this.emitInput(// 将图片路径传给父组件this.dataObj.host + "/" + this.dataObj.key.replace("${filename}", file.name));},beforeAvatarUpload(file) {console.log(file);let _self = this;return policy().then(response => {console.log("响应的数据", response.data);_self.dataObj.policy = response.data.policy;_self.dataObj.signature = response.data.signature;_self.dataObj.ossaccessKeyId = response.data.accessId;_self.dataObj.key = response.data.dir + "random" + file.name;_self.dataObj.dir = response.data.dir;_self.dataObj.host = response.data.host;// 设置服务端返回200状态码,默认返回204。_self.dataObj.success_action_status = response.data.success_action_status;console.log("响应的数据222。。。", _self.dataObj);}).catch(err => {console.log("error", err);});}},created() {},mounted() {}
};
</script><style>
.avatar-uploader .el-upload {border: 1px dashed #d9d9d9;border-radius: 6px;cursor: pointer;position: relative;overflow: hidden;
}
.avatar-uploader .el-upload:hover {border-color: #409eff;
}
.avatar-uploader-icon {font-size: 28px;color: #8c939d;width: 178px;height: 178px;line-height: 178px;text-align: center;
}
.avatar {width: 178px;height: 178px;display: block;
}
.bg-purple {background: #d3dce6;
}
.bg-purple-light {background: #e5e9f2;
}
.grid-content {border-radius: 4px;min-height: 36px;
}
</style>
三、前端直传oss可能出现跨域问题
需要进入oss修改相关跨域策略
编辑跨域规则
确定保存
四、服务端签名流程
- 用户向应用服务器请求上传Policy和回调
- 应用服务器返回上传Policy和签名给用户
字段 | 描述 |
---|---|
accessId | 用户请求的AccessKey ID |
policy | 用户表单上传的策略(Policy),Policy为经过Base64编码过的字符串 |
signature | 对Policy签名后的字符串 |
dir | 限制上传的文件前缀 |
host | 用户发送上传请求的域名 说明:host不支持自定义域名 |
expire | 由服务器端指定的Policy过期时间,格式为Unix时间戳(自UTC时间1970年01月01号开始的秒数) |
- 用户使用Post方法向OSS发送文件上传请求
总结
与数据上传至服务器,再由服务器上传到oss相比:(Vue+element Upload利用http-request实现第三方地址图片上传)
- 上传快:通过应用服务端上传,用户数据需先上传到应用服务器,之后再上传到OSS,网络传输时间比直传到OSS多一倍。如果用户数据不通过应用服务器中转,而是直传到OSS,速度将大大提升。而且OSS采用BGP带宽,能保证各地各运营商之间的传输速度。
- 扩展性好:如果后续用户数量逐渐增加,则应用服务器会成为瓶颈。
- 费用低:由于OSS上行流量是免费的,如果数据直传到OSS,将节省多台应用服务器的费用。