minIO图床配合el-upload和springboot实现后端签名前端直传
概述
背景
谷粒商城中有许多需要保存图片的场景,正规做法是在数据库中保存图片地址,文件则保存在对象存储服务(OSS)中,官方视频教学的是使用阿里云的OSS,这样还可以搭配spring boot cloud alibaba使用,但是本人希望能自己部署的环境尽量自己部署,查询后找到了minIO这款工具,可以实现OSS的大部分功能,使用docker部署也非常方便,这样的话就需要重构很多前后端代码
分析问题
本来个人想要做成前端把文件传到后端,后端再上传到minIO图床,这套流程个人很熟悉。但是教程中使用的是签名直传的方案,想来少了一段文件传输的流程也确实速度更快也更稳定,这在minIO里也可以实现,只是需要一些技巧
签名直传方案的原理是,前端向后端发送请求,携带文件名参数,后端返回给前端一个直传地址,前端直接请求该地址即可上传文件,这样保证了既不会需要在前端填写图床信息(通过返回参数获得),还可以直接由前端上传文件
在el-upload中填入action参数是官方做法,这样该组件会自己调用封装好的post方法请求这个参数地址来直传文件
minIO这个软件给的api里,直传地址只能通过put请求访问
所以如果想使用el-upload组件的话,只能放弃自带的请求方法,自己编写上传请求
解决
环境部署
简单记录下该软件的部署过程
使用docker安装minIO
1
2docker pull minio/minio
docker run -p 9000:9000 minio/minio server /data注意配置用户名密码的时候密码需要大于8位数,不然报错
在springboot中配置minIO相关库
1
2
3
4
5
6
7
8
9
10<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.5.10</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.11.0</version>
</dependency>注意该库需要高版本的okhttp库,如果版本不对应则需要单独引入
后端代码实现
在thirdparty模块中添加miniocontroller并添加以下代码
1 | package com.example.gulimall.thirdparty.demos.web; |
前端代码实现
需要修改谷粒商城源代码的policy.js和upload文件
思路是自己在beforeUpload方法中添加请求直传地址的方法,使用自定义的代码直接将文件上传,不使用el-upload自带的post请求
需要先安装axios
1 | npm install axios --save-dev |
然后在src/main.js下全局引用一下
1 | import axios from 'axios'; |
policy,js
1
2
3
4
5
6
7
8
9
10
11
12
13
14import 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)
})
})
}singleUpload.Vue
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
103
104
105
106
107
108
109
110
111
112<template>
<div>
<el-upload
action=""
: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>
import {policy} from './policy'
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: ''
// callback:'',
},
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(err)
})
})
},
handleUploadSuccess (res, file) {
}
}
}
</script>
<style>
</style>