实现SpringBoot上传图片到七牛云

实现SpringBoot上传图片到七牛云

这是最近在制作项目的时候遇到的一个问题,这个项目是一个商城,我在对图片进行处理的时候,一般是把图片转化为Byte,再存入数据库的。步骤和方法都没错,但是考虑到我在阿里云租借的ECS云服务器硬盘大小只有40G,对之后的图片,可能出现容量不足的情况。

而且对一个只有2G内存的服务器,大量的对图片进行IO处理,恐怕会使得服务器效率低下,所以,便想到了,在存储图片的时候,直接上传到七牛云,并生成一个随机ID来代表图片的名称,之后读取的之后,只需要在数据库读到到这个ID值,并在页面实现拼串,就能够将图片显示出来了。

所以我们来实验一下:

基础准备

这里我直接选取了官网提供的依赖:

pom.xml

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
<!--七牛配置依赖-->
<!-- 七牛云 -->
<dependency>
<groupId>com.qiniu</groupId>
<artifactId>qiniu-java-sdk</artifactId>
<version>[7.2.0, 7.2.99]</version>
</dependency>

<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.14.2</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.5</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.qiniu</groupId>
<artifactId>happy-dns-java</artifactId>
<version>0.1.6</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>

然后需要进行设置,获取到七牛云的外链和密钥:

application.yml

1
2
3
4
5
qiniu:
accessKey: xxxxxxxxxxxxxxx
secretKey: xxxxxxxxxxxxxx
bucket: imgof
prefix: www-xxxxxxxxx.com

虽然我们在写入的时候 它会提示了 cannot configuration。。。,但是不用理他,那是因为这个依赖并不归属于Spring管理。

但是我们仍然需要将其加入容器中:

QiniuProperties

1
2
3
4
5
6
7
8
9
10
11
12
@Data
@ConfigurationProperties(prefix = "qiniu")
public class QiniuProperties {

private String accessKey;

private String secretKey;

private String bucket;

private String prefix;
}

这个加入了以后,上方可能会提示你不能找到classpath,但是也不用管它。

因为我们使用的是yml,而不是properties,所以这个并不是报错。

但是配置还没有完,我们仅仅是定义了七牛云的连接属性,还需要对其连接的各个方式进行配置:

QiniuFileConfig

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
@Configuration
@EnableConfigurationProperties(QiniuProperties.class)
public class QiniuFileConfig {

@Autowired
private QiniuProperties qiniuProperties;

/**
* 华南机房,配置自己空间所在的区域
*/
@Bean
public com.qiniu.storage.Configuration qiniuConfig() {
return new com.qiniu.storage.Configuration(Zone.zone2());
}

/**
* 构建一个七牛上传工具实例
*/
@Bean
public UploadManager uploadManager() {
return new UploadManager(qiniuConfig());
}

/**
* 认证信息实例
* @return
*/
@Bean
public Auth auth() {
return Auth.create(qiniuProperties.getAccessKey(), qiniuProperties.getSecretKey());
}

/**
* 构建七牛空间管理实例
*/
@Bean
public BucketManager bucketManager() {
return new BucketManager(auth(), qiniuConfig());
}

@Bean
public Gson gson() {
return new Gson();
}
}

但是这段代码并没有特别需要深究的地方,注意定义好自己的机房位置就行了,比如:华东是Z0,华北是Z1。

以上的基础配置可以通过复制粘贴的方式引入,并没有过多的定制化操作。

服务实现

之后,就是我们把这些服务实现的时候了,首先需要一个接口:

QiniuService

1
2
3
4
5
6
7
8
public interface QiniuService {

String uploadFile(File file) throws QiniuException;

String uploadFile(InputStream inputStream) throws QiniuException;

Response delete(String key) throws QiniuException;
}

定义了上传和删除。

之后便是服务的实现,也是最重要的内容:

QiniuServiceImpl

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
@Slf4j
@Service
@EnableConfigurationProperties(QiniuProperties.class)
public class QiniuServiceImpl implements QiniuService, InitializingBean {

@Autowired
private QiniuProperties qiniuProperties;

@Autowired
private UploadManager uploadManager;

@Autowired
private BucketManager bucketManager;

@Autowired
private Auth auth;

// 定义七牛云上传的相关策略
private StringMap putPolicy;


/**
* 以文件的形式上传
*
* @param file
* @return
* @throws QiniuException
*/
@Override
public String uploadFile(File file) throws QiniuException {
Response response = this.uploadManager.put(file, null, getUploadToken());
int retry = 0;
while (response.needRetry() && retry < 3) {
response = this.uploadManager.put(file, null, getUploadToken());
retry++;
}
//解析结果
DefaultPutRet putRet = JSON.parseObject(response.bodyString(), DefaultPutRet.class);
String return_path = qiniuProperties.getPrefix() + "/" + putRet.key;
log.info("文件名称={}", return_path);
return return_path;
}

/**
* 以流的形式上传
*
* @param inputStream
* @return
* @throws QiniuException
*/
@Override
public String uploadFile(InputStream inputStream) throws QiniuException {
Response response = this.uploadManager.put(inputStream, null, getUploadToken(), null, null);
int retry = 0;
while (response.needRetry() && retry < 3) {
response = this.uploadManager.put(inputStream, null, getUploadToken(), null, null);
retry++;
}
//解析结果
DefaultPutRet putRet = JSON.parseObject(response.bodyString(), DefaultPutRet.class);
String return_path = qiniuProperties.getPrefix() + "/" + putRet.key;
log.info("文件名称={}", return_path);
System.out.println(return_path);
return return_path;
}

/**
* 删除七牛云上的相关文件
* incompatible
* @param key
* @return
* @throws QiniuException
*/
@Override
public Response delete(String key) throws QiniuException {
Response response = bucketManager.delete(qiniuProperties.getBucket(), key);
int retry = 0;
while (response.needRetry() && retry++ < 3) {
response = bucketManager.delete(qiniuProperties.getBucket(), key);
}
return response;
}


@Override
public void afterPropertiesSet() throws Exception {
this.putPolicy = new StringMap();
putPolicy.put("returnBody", "{\"key\":\"$(key)\",\"hash\":\"$(etag)\",\"bucket\":\"$(bucket)\",\"width\":$(imageInfo.width), \"height\":${imageInfo.height}}");
// 自定义文件名字
// putPolicy.put("saveKey", "qqqqq");
}

/**
* 获取上传凭证
*
* @return
*/
private String getUploadToken() {
return this.auth.uploadToken(qiniuProperties.getBucket(), null, 3600, putPolicy);
}
}

这里的代码比较长,我们就挑选出最为重要的一部分来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Override
public String uploadFile(InputStream inputStream) throws QiniuException {
Response response = this.uploadManager.put(inputStream, null, getUploadToken(), null, null);
int retry = 0;
while (response.needRetry() && retry < 3) {
response = this.uploadManager.put(inputStream, null, getUploadToken(), null, null);
retry++;
}
//解析结果
DefaultPutRet putRet = JSON.parseObject(response.bodyString(), DefaultPutRet.class);
String return_path = qiniuProperties.getPrefix() + "/" + putRet.key;
log.info("文件名称={}", return_path);
System.out.println(return_path);
return return_path;
}

比如这个方法,以流的形式上传。我们会获取到一个文件的流,然后在调用七牛云的API,即uploadManager.put,将其上传到七牛云服务器。

至于那个retry和while,仅仅是为了防止上传失败的进行的重试而已,如果上传失败超过三次,则取消上传,删除也是如此。

之后的

1
DefaultPutRet putRet = JSON.parseObject(response.bodyString(), DefaultPutRet.class);

便是返回对图片上传的解析结果,解析结果里面包含了图片的名字,图片的上传的路径等等。

可能你会问,我将文件以流的形式上传了,但是在这个过程中,我并没有定义文件的名字啊?

对了,这里有一个误区就是上传的文件名字,就一定等于自己的文件名字,这个其实是错误的,如果你有两个口味相同,版本不同的奥利奥,该怎么办呢?那就拼串一个版本号,但是版本相同,口味不同的呢?难道你要不停的拼串吗?

那么说起来,上传的文件名字到底是什么呢?

这里还有一行代码:

1
2
3
4
5
6
7
    @Override
public void afterPropertiesSet() throws Exception {
this.putPolicy = new StringMap();
putPolicy.put("returnBody", "{\"key\":\"$(key)\",\"hash\":\"$(etag)\",\"bucket\":\"$(bucket)\",\"width\":$(imageInfo.width), \"height\":${imageInfo.height}}");
// 自定义文件名字
// putPolicy.put("saveKey", "???");
}

我们发现这个afterPropertiesSet是一个重写的方法,而这个方法来自于接口InitializingBean。

这个方法将在所有的属性被初始化后调用,但是会在init前调用。

也就是说,在这个类中,通过这个方法来构造Bean容器,而我们的上传的文件,会自动执行初始化,对上传的图片进行定制,可以在定义图片的时候,使用:

1
putPolicy.put("saveKey", "???");

去定义图片的名字,而这个???可以是一个生成全局唯一ID的生成器。这样保证了图片的连接唯一性,保证不同图片的共存而不会覆盖。

而之后返回的图片地址,在存入数据就行了。

那么返回的图片地址怎么处理呢?那就归控制器管理了。

QiniuController

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
@RestController
@RequestMapping("/qiniu")
public class QiniuController {

@Autowired
private QiniuService qiniuService;



/**
* 以流的形式上传图片
*
* @param file
* @return 返回访问路径
* @throws IOException
*/
@PostMapping("upload")
public String uploadFile(@RequestParam(value = "file") MultipartFile file) throws IOException {
return qiniuService.uploadFile(file.getInputStream());
}

/**
* 删除文件
*
* @param key
* @return
* @throws IOException
*/
@GetMapping("delete/{key}")
public Response deleteFile(@PathVariable String key) throws IOException {
return qiniuService.delete(key);
}
}

这样就完成了,可以自制一个页面进行测试。