el-upload + 前端直传minio(通过签名地址)

el-upload + 前端直传minio(通过签名地址)

概述

需求

前端请求后端获取预签名url(其中包含了上传到AWS S3所需要的认证信息),然后通过该签名url将文件直传到minio服务器。

问题

minio签名url只支持put请求,使用其他请求会提示错误的请求方法:

post请求报错

前端使用el-upload,默认调用其内部action,通过查看源码,可以看到el-upload组件引用了upload组件,进入upload发现还引入了ajax组件,在ajax组件的upload方法内部,可以看到请求方式是post请求(vue不熟悉,具体原理不清楚,只是站在自己理解的角度):

ajax-post请求

故通过minio签名url直传方式不能成功。

解决

最后为了使用el-upload,只好折中,引入axios,即el-upload + axios + minio上传方式。

后端生成签名地址

minio具体相关信息在yml文件配置,或者交给nacos管理,不做讨论。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
package com.jivehaha.gulimall.thirdparty.controller;

import com.jievhaha.common.utils.Constant;
import com.jievhaha.common.utils.R;
import io.minio.GetPresignedObjectUrlArgs;
import io.minio.MinioClient;
import io.minio.http.Method;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;

/**
* @Author: jievhaha
* @Date: 2022/5/20 8:56
*/
@RestController
public class MinioController {
@Value("${minio.endpoint}")
private String endpoint;
@Value("${minio.accessKey}")
private String accessKey;
@Value("${minio.secretKey}")
private String secretKey;
@Value("${minio.bucket}")
private String bucket;

@GetMapping("/minio/policy")
private R policy(@RequestParam("pic")String pic) {
String name = UUID.randomUUID() + "-" + pic; // 修改文件名防止上传相同文件被覆盖
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
String format = dateTimeFormatter.format(LocalDateTime.now());
String dir = format; // 用户上传文件时指定的前缀,即上传目录。
pic = dir + "/" + name;// 文件上传目录+名称
String url = "";
String path = "/" + bucket + "/" + pic;// 该值暂没用到,前端不熟直接使用没用域名的图片地址不知道怎么处理

Map<String, String> respMap=null;
try {

MinioClient minioClient = MinioClient.builder().endpoint(endpoint)
.credentials(accessKey,secretKey).build();
Map<String, String> reqParams = new HashMap<>();
reqParams.put("response-content-type", "application/json");
url = minioClient.getPresignedObjectUrl(
GetPresignedObjectUrlArgs.builder()
.method(Method.PUT)//这里必须是PUT,如果是GET的话就是文件访问地址了。如果是POST上传会报错.
.bucket(bucket)
.object(pic)
.expiry(60 * 60 * 24)
.extraQueryParams(reqParams)
.build());
System.out.println(url); // 前端直传需要的url地址
respMap= new LinkedHashMap<>();
respMap.put("name", name);
respMap.put("host", url);
respMap.put("path", path);
respMap.put("url", Constant.MINIO_URL + path);// Constant.MINIO_URL自己的minio服务器地址
} catch (Exception e) {
System.out.println("Error occurred: " + e);
}
return R.ok().put("data", respMap);
}
}

el-upload + axios

引入axios

安装axios:

1
npm install axios --save-dev

在main.js引用axios

1
2
import axios from 'axios';
Vue.prototype.$axios = axios //全局注册,使用方法为:this.$axios

前端获取签名url

1
2
3
4
5
6
7
8
9
10
11
12
import http from '@/utils/httpRequest.js'
export function policy(pic) {
return new Promise((resolve,reject)=>{
http({
url: http.adornUrl("/thirdparty/minio/policy"),
method: "get",
params: http.adornParams({pic})
}).then(({ data }) => {
resolve(data);
})
});
}

上传组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
<template>
<div>
<el-upload
action = ''
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>
import {policy} from './policy_minio'
export default {
name: 'singleUpload',
props: {
value: String
},
computed: {
imageUrl() {
return this.value;
},
imageName() {
if (this.value != null && this.value !== '') {
return this.value.substring(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 {
dialogVisible: false
};
},
methods: {
emitInput(val) {
this.$emit('input', val)
},
handleRemove(file, fileList) {
this.emitInput('');
},
handlePreview(file) {
this.dialogVisible = true;
},
beforeUpload(file)
// 上传之前先调用policy_minio组件的policy方法获取签名url
return new Promise((resolve, reject) => {
policy(file.name).then(response => {
let url = response.data.url;
//将文件名改为后台返回的(原文件名前拼了段uuid),不然同名文件会覆盖
let newFileName = response.data.name;
let imageType = "image/" + newFileName.substring(newFileName.lastIndexOf(".") + 1);
let newFile = new File([file], response.data.name,{type:imageType});
this.$axios.request({
url: response.data.host,
method: 'put',
data: newFile
}).then((res)=> {
this.showFileList = true;
this.fileList.pop();
this.fileList.push({name: file.name, url: url});
this.emitInput(this.fileList[0].url);
}).catch(()=> {
console.log("响应数据:上传失败");
})

}).catch(err => {
console.log(JSON.stringify(err));
reject(false)
})
})
},
handleUploadSuccess(res, file) {
}
}
}
</script>
<style>

</style>

着重点在beforeUpload方法,上传之前先调用policy_minio组件的policy方法获取签名url,然后再通过axios组件上传(假如使用FormData对象传递,最后会发现上传成功后,文件大小改变,且打开失败,故直接上传文件对象)。

效果

通过组件上传,上传成功预览。

上传并预览

上传成功后服务器文件。

服务器上传成功

注意:需要正常展示下载文件需要修改对应bucket权限,否则直接通过文件在服务器的路径访问,会直接进入文件夹,新增read and write权限。

bucket权限


el-upload + 前端直传minio(通过签名地址)
http://www.muzili.ren/2022/06/11/el-upload + 前端直传minio/
作者
jievhaha
发布于
2022年6月11日
许可协议