实现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()); }
@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;
@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; }
@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; }
@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}}");
}
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}}");
}
|
我们发现这个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;
@PostMapping("upload") public String uploadFile(@RequestParam(value = "file") MultipartFile file) throws IOException { return qiniuService.uploadFile(file.getInputStream()); }
@GetMapping("delete/{key}") public Response deleteFile(@PathVariable String key) throws IOException { return qiniuService.delete(key); } }
|
这样就完成了,可以自制一个页面进行测试。