传统上传方式
用户访问服务器,上传图片的操作由服务器完成。
缺点
- 大量请求访问服务器会造成很大的压力;
- 在微服务中,必须要有一个统一固定的地方来存放文件资源,否则在多模块的微服务中,不能保证文件的正确性、一致性。
服务端签名后直传
- 前端请求服务器;
- 服务器响应一个上传文件的签名给前端;
- 前端拿着签名直接将需要上传的文件上传到oss中。
实现步骤
实现代码前确保在阿里云已经开通Oss存储功能,创建对应的bucket管理文件。
后端代码
- 导入依赖
<!-- oss --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alicloud-oss</artifactId> <version>2.1.0.RELEASE</version> </dependency>
- 编写controller层代码
@Controller @RequestMapping("/thirdparty/oss") public class OssController { @Autowired private OSS ossClient; @Value("${spring.cloud.alicloud.access-key}") private String accessId; @Value("${spring.cloud.alicloud.oss.endpoint}") private String endpoint; @Value("${spring.cloud.alicloud.oss.bucket}") private String bucket; /** * 响应上传Policy和签名信息 * @return Policy和签名信息 */ @RequestMapping("/policy") @ResponseBody public R policy() { // 填写Bucket名称,例如examplebucket。 // 填写Host地址,格式为https://bucketname.endpoint。 String host = "https://" + bucket + "." + endpoint; // 设置上传回调URL,即回调服务器地址,用于处理应用服务器与OSS之间的通信。OSS会在文件上传完成后,把文件上传信息通过此回调URL发送给应用服务器。 // String callbackUrl = "https://192.168.0.0:8888"; // 设置上传到OSS文件的前缀,可置空此项。置空后,文件将上传至Bucket的根目录下。 // 文件上传路径: 使用当前日期作为上传文件的目录 String format = new SimpleDateFormat("yyyy-MM-dd").format(new Date()); String dir = format + "/"; 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>(); 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)); // respMap.put("expire", formatISO8601Date(expiration)); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } return R.ok().put("data", respMap); } }
该控制器的功能就是响应签名给前端,直接复制使用即可。
- application.yml文件
右上角用户头像可以查看secret-key与access-key,如果没有请先点击创建。
- 发一个请求测试一下,是否能够拿到签名信息
前端代码
SingleUpload.vue
<template>
<div>
<el-upload
:action="dataObj.host"
:data="dataObj"
list-type="picture-card"
:multiple="false"
:before-upload="beforeUpload"
:on-remove="handleRemove"
:on-success="handleUploadSuccess"
:on-preview="handlePictureCardPreview"
:limit="1"
:on-exceed="onExceed"
>
<i class="el-icon-plus"></i>
</el-upload>
<el-dialog :visible.sync="dialogVisible">
<img width="100%" :src="fileList[0].url" alt />
</el-dialog>
</div>
</template>
<script>
import { v4 as uuidv4 } from "uuid";
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
}
];
}
},
data() {
return {
dataObj: {
policy: "",
signature: "",
key: "",
ossaccessKeyId: "",
dir: "",
host: ""
// callback:'',
},
dialogVisible: false
};
},
methods: {
onExceed() {
this.$message({
type: 'error',
message: '最多上传一张图片'
});
},
emitInput(val) {
this.$emit("input", val);
},
handleRemove(file, fileList) {
console.log("handleRemove...");
this.emitInput("");
},
handlePictureCardPreview(file) {
console.log("show image...");
this.dialogVisible = true;
},
beforeUpload(file) {
console.log("start beforeUpload...");
console.log("uuid", uuidv4());
let _self = this;
return new Promise((resolve, reject) => {
this.$http
.post("/thirdparty/oss/policy")
.then(result => {
let response = result.data;
console.log("响应的数据:", response);
_self.dataObj.policy = response.data.policy;
_self.dataObj.signature = response.data.signature;
_self.dataObj.ossaccessKeyId = response.data.accessid;
_self.dataObj.key = response.data.dir + uuidv4() + "_${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)
});
console.log("image url:", this.fileList[0].url);
this.emitInput(this.fileList[0].url);
}
}
};
</script>
这里使用了element-ui的上传组件。
页面
在页面导入组件。
- 当我们点击上传组件时,会执行beforeUpload方法,给服务器发送请求;
- 服务器接收到请求会响应签名信息给前端;
- 前端拿到签名信息之后,会调用handleUploadSuccess方法,该方法中又调用了emitInput方法,携带签名信息直接向Oss请求上传文件。
- 测试,随便上传一个图片
这里在后端实现了上传的文件会存储到当前日期的文件夹中,如果有其他需要可以直接在后端代码中修改。
可能出现的问题:
5. 前端请求oss上传文件时,请求响应You have no right to access this object because of bucket acl.
- 检查后端controller配置是否有误,确保响应的请求host路径和签名信息正确。
- 检查阿里云AccessKey是否配置了权限。
6. 跨域访问
找到权限管理任务栏,拉到最底下,点击跨域设置
创建规则允许跨域即可