业务 七牛云图片上传

1. 七牛云OSS配置

1.1. 创建存储空间

  • 存储空间名称:自定义
  • 存储区域:与服务器在同一区域即可,保证网络通畅
  • 访问空间:公开空间谁都可以访问,私有空间需要身份认证才能访问。如果作为图片服务器,谁都能看,就选择公开空间

2. 七牛云图片上传实现原理

2.1. 资源路径说明

七牛云通过存储空间名+文件key来唯一定位一个资源。

常用的key有几种形式:

  • UUID
  • 原始文件名(非中文)
  • 自定义的具有唯一性的字符串

key由用户上传时决定

上传之后,资源的URL路径为 http://外链域名/key

2.2. 上传原理

七牛云图片上传分为两步

  1. 用账号的AccessKey和SecretKey连接七牛服务器进行身份校验,成功则得到token
  2. 用token就可以上传资源

2.3. 上传实现方式

实现上传有几种思路:

  • 纯后端实现:前端将图片上传到后端,再由后端将图片上传到七牛云
  • 纯前端实现:前端直接将图片上传到七牛云
  • 前后端结合:后端提供获取token的接口,前端获取token之后,再根据token直接将图片上传到七牛云

纯后端实现,安全,但是服务器压力大。纯前端实现,前端要知道AccessKey和SecretKey,又不安全。

前后端结合的方式是最佳的。前端不需要知道AccessKey和SecretKey是什么,只需要访问后端接口,由后端去获取token,再将token返回给前端,最后由前端上传图片。这样既安全又高效

2.4. HTTP上传(表单上传)接口

前端通过HTTP接口上传,具体文档可以查询 七云开发者OSS文档->上传资源->表单上传 https://developer.qiniu.com/kodo/manual/1272/form-upload

HTML表单示例

1
2
3
4
5
6
7
8
9
10
<form method="post" action="http://upload.qiniup.com/"
enctype="multipart/form-data">
<input name="key" type="hidden" value="<resource_key>">
<input name="x:<custom_name>" type="hidden" value="<custom_value>">
<input name="token" type="hidden" value="<upload_token>">
<input name="crc32" type="hidden" />
<input name="accept" type="hidden" />
<input name="file" type="file" />
<input type="submit" value="上传文件" />
</form>

上传时的参数如下

名称 类型 必填 说明
action string 上传地址,可参考存储区域
resource_key string 资源名,必须是 UTF-8 编码。注意: 如果上传凭证中 scope 指定为 :, 则该字段也必须指定。
custom_name string 自定义变量的名字,不限个数。
custom_value string 自定义变量的值。
upload_token string 必须是一个符合相应规格的上传凭证,否则会返回 401 表示权限认证失败。
crc32 string 上传内容的 crc32 校验码。如填入,则七牛服务器会使用此值进行内容检验。
accept string 当 HTTP 请求指定 accept 头部时,七牛会返回 content-type 头部的值。该值用于兼容低版本 IE 浏览器行为。低版本 IE 浏览器在表单上传时,返回 application/json 表示下载,返回 text/plain 才会显示返回内容。
file file 文件本身。二进制数据

action是上传地址,不算参数。主要考虑resource_keyupload_tokenfile这3个参数

3. SpringMVC后端实现

3.1. 依赖

1
2
3
4
5
6
7
8
9
10
11
12
<!-- SpringMVC -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- qiniu -->
<dependency>
<groupId>com.qiniu</groupId>
<artifactId>qiniu-java-sdk</artifactId>
<version>[7.2.0, 7.2.99]</version>
</dependency>

3.2. qiniu.properties 七云配置文件

1
2
3
4
5
6
# 七牛账号的accessKey 
qiniu.accessKey=XXXXXXXXXXX-XXXX-XXXXXXXXXXXXXXXXXXXXXXX
# 七牛账号的secretKey
qiniu.secretKey=BBB-BBBB-BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
# OSS存储空间名
qiniu.bucket=image

3.3. QiniuTokenVO 响应返回的VO

封装key和token结果,返回给客户端

1
2
3
4
5
6
7
@Data
@NoArgsConstructor
@AllArgsConstructor
public class QiniuTokenVO {
private String key;
private String token;
}

3.4. QiniuUtils 获取Token的工具类

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
public class QiniuUtils {
private static String ACCESS_KEY;
private static String SECRET_KEY;
private static String BUCKET;
static {
try {
// 从配置文件中读取
InputStream in = QiniuUtils.class.getClassLoader().getResourceAsStream("properties/qiniu.properties");
Properties properties = new Properties();
properties.load(new InputStreamReader(in, "UTF-8"));
ACCESS_KEY = (String) properties.get("qiniu.accessKey");
SECRET_KEY = (String) properties.get("qiniu.secretKey");
BUCKET = (String) properties.get("qiniu.bucket");
} catch (Exception e) {
e.printStackTrace();
}
}

/** 获取上传所需的token */
public static QiniuTokenVO getUpToken() {
Auth auth = Auth.create(ACCESS_KEY, SECRET_KEY);
String key = UUID.randomUUID().toString();
// uploadToken(bucket) 只指定将资源上传到哪个bucket(存储空间)
/// String upToken = auth.uploadToken(BUCKET);
// uploadToken(bucket, key) 指定bucket和key。这样在上传资源时,限定资源名必须是key,否则验证不通过
String upToken = auth.uploadToken(bucket, key);
return new QiniuTokenVO(key, upToken);
}


public static void main(String[] args) {
// 测试
QiniuTokenVO upToken = getUpToken();
System.out.println(upToken);
}
}

3.5. 处理器

1
2
3
4
5
6
7
@RestController
public class QiniuController {
@GetMapping("/qiniu/upload/token")
public QiniuTokenVO uptoken() {
return QiniuUtils.getUpToken();
}
}

4. Vue前端实现

4.1. @/views/qiniu/upload.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
<template>
<el-upload :data="dataObj" action="https://upload.qbox.me"
:before-upload="beforeUpload"
:on-success="handleUploadSuccess"
:on-progress="handleUploadProgess"
:show-file-list="false"
accept=".jpg,.jpeg,.png,.gif,.bmp,,.JPG,.JPEG,.PNG,.GIF,.BMP"
drag>
<!-- 如果上传成功,则显示图片 -->
<img v-if="imageUrl" :src="imageUrl" width="100%" height="100%">
<!-- 否则显示上传图片的提示 -->
<div v-else v-loading="loading">
<i class="el-icon-upload" />
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
</div>
</el-upload>
</template>

<script>

export default {
props: {
// 父子组件v-model双向绑定,名称必须是value
value: {
type: String,
default: ''
}
},
computed: {
// 相当于将value重命名为imageUrl
imageUrl() {
return this.value
}
},
data() {
return {
// 上传到七牛云,需要token和key参数
dataObj: { token: '', key: '' },
// 图片加载
loading: false,
}
},
methods: {
beforeUpload() {
this.$emit('input', '')

const _self = this
return new Promise((resolve, reject) => {
this.axios.get('/qiniu/upload/token').then(response => {
_self._data.dataObj.token = response.data.token
_self._data.dataObj.key = response.data.key
resolve(true)
}).catch(err => {
console.log(err)
reject(false)
})
})
},
handleUploadProgess(event, file) {
this.loading = true
},
handleUploadSuccess(response, file) {
this.loading = false
// 上传成功,将父子组件双向绑定的值修改,格式为 this.$emit('input', 新值)response.key是文件的key,拼接上图片的URL前缀,就得到完整的URL
this.$emit('input', "http://pq6yxn7wg.bkt.clouddn.com/" + response.key)
console.log('response: ', response)
console.log('file: ', file)
},
}
}
</script>

4.2. 图片上传测试页

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<template>
<div class="home" >
<!-- v-model父子组件双向绑定通信 -->
<Upload v-model="logoUrl" />
</div>
</template>

<script>
// 导入图片上传组件
import Upload from '@/views/qiniu/upload.vue'

export default {
name: 'home',
components: {
Upload
},
data() {
return {
logoUrl: '',
}
},
}
</script>

5. HTML前端表单提交实现

表单方式实现如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script src="https://cdn.bootcss.com/jquery/3.4.0/jquery.min.js"></script>
<script>
$(() => {
$.get('http://localhost:8080/qiniu/upload/token', data => {
$('#key').val(data.key)
$('#token').val(data.token)
console.log(data.key, data.token)
})
})
</script>
<form method="post" action="http://upload.qiniup.com/" enctype="multipart/form-data">
<input id="key" name="key" type="hidden">
<input id="token" name="token" type="hidden">
<input name="file" type="file" />
<input type="submit" value="提交" />
</form>
panchaoxin wechat
关注我的公众号
支持一下