本文主要对SpringBoot2.x集成七牛云对象存储Kodo进行简单总结,其中SpringBoot使用的2.4.5版本。

一、七牛云对象存储Kodo简介

七牛云对象存储Kodo是七牛云提供的高可靠、强安全、低成本、可扩展的存储服务。您可通过控制台、API、SDK等方式简单快速地接入七牛存储服务,实现海量数据的存储和管理。通过Kodo可以进行文件的上传、下载和管理。

七牛云对象存储Kodo官方文档:https://developer.qiniu.com/kodo。

二、准备工作

1.注册七牛云并认证

首先需要在七牛云官网注册七牛云账号,然后对账号进行认证,之后才可以在对象存储中新建存储空间。

2.新建空间

在对象存储的空间管理中新建空间:

这里新建了两个用于测试的空间:

在空间概览中可以获取一个CDN测试域名,这里使用测试域名来测试:

测试域名只能用于测试,生成后30天后会被回收。如果是生产环境,需要配置域名,可以在域名管理中绑定备案过的域名:

3.七牛云密钥

在个人中心的密钥管理下获取七牛云密钥:

三、集成七牛云对象存储kodo

通过Maven新建一个名为springboot-qiniu-kodo的项目。

1.引入依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency><groupId>com.qiniu</groupId><artifactId>qiniu-java-sdk</artifactId><version>7.7.0</version>
</dependency>
<dependency><groupId>com.google.code.gson</groupId><artifactId>gson</artifactId><version>2.8.5</version>
</dependency>
<dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.47</version>
</dependency>
<!-- lombok插件 -->
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.8</version>
</dependency>

2.编写配置类

package com.rtxtitanv.config;import com.qiniu.cdn.CdnManager;
import com.qiniu.storage.BucketManager;
import com.qiniu.storage.Region;
import com.qiniu.storage.UploadManager;
import com.qiniu.storage.persistent.FileRecorder;
import com.qiniu.util.Auth;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.MultipartConfigFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.unit.DataSize;import javax.servlet.MultipartConfigElement;
import java.io.IOException;
import java.nio.file.Paths;/*** @author rtxtitanv* @version 1.0.0* @name com.rtxtitanv.config.QiniuKodoConfig* @description 七牛云对象存储配置类* @date 2021/6/20 12:39*/
@ConfigurationProperties(prefix = "qiniu.kodo")
@Configuration
@Data
public class QiniuKodoConfig {private String accessKey;private String secretKey;private String bucket;private String region;private String domain;/*** 带有指定Region对象的配置实例** @return com.qiniu.storage.Configuration*/@Beanpublic com.qiniu.storage.Configuration config() {if ("huadong".equals(region)) {return new com.qiniu.storage.Configuration(Region.huadong());}if ("huabei".equals(region)) {return new com.qiniu.storage.Configuration(Region.huabei());}if ("huanan".equals(region)) {return new com.qiniu.storage.Configuration(Region.huanan());}if ("beimei".equals(region)) {return new com.qiniu.storage.Configuration(Region.beimei());}if ("xinjiapo".equals(region)) {return new com.qiniu.storage.Configuration(Region.xinjiapo());}return new com.qiniu.storage.Configuration();}/*** 七牛云上传管理器实例** @return com.qiniu.storage.UploadManager*/@Beanpublic UploadManager uploadManager() {return new UploadManager(config());}/*** 断点续传的七牛云上传管理器实例** @return com.qiniu.storage.UploadManager* @throws IOException IOException*/@Beanpublic UploadManager resumableUploadManager() throws IOException {com.qiniu.storage.Configuration config = config();// 指定分片上传版本config.resumableUploadAPIVersion = com.qiniu.storage.Configuration.ResumableUploadAPIVersion.V2;// 设置分片上传并发,1:采用同步上传;大于1:采用并发上传config.resumableUploadMaxConcurrentTaskCount = 2;String localTempDir = Paths.get(System.getenv("java.io.tmpdir"), bucket).toString();// 设置断点续传文件进度保存目录FileRecorder fileRecorder = new FileRecorder(localTempDir);return new UploadManager(config, fileRecorder);}/*** 认证信息实例** @return com.qiniu.util.Auth*/@Beanpublic Auth auth() {return Auth.create(accessKey, secretKey);}/*** 空间资源管理器实例** @return com.qiniu.storage.BucketManager*/@Beanpublic BucketManager bucketManager() {return new BucketManager(auth(), config());}/*** CDN管理器实例** @return com.qiniu.cdn.CdnManager*/@Beanpublic CdnManager cdnManager() {return new CdnManager(auth());}/*** 文件上传配置** @return javax.servlet.MultipartConfigElement*/@Beanpublic MultipartConfigElement multipartConfigElement() {MultipartConfigFactory multipartConfigFactory = new MultipartConfigFactory();// 设置multipart/form-data请求允许的最大数据大小,这里设置为1GbmultipartConfigFactory.setMaxRequestSize(DataSize.ofGigabytes(1));// 设置上传文件允许的最大大小,这里设置为1GbmultipartConfigFactory.setMaxFileSize(DataSize.ofGigabytes(1));return multipartConfigFactory.createMultipartConfig();}
}

3.编写配置文件

# 自定义七牛云存储配置
qiniu:kodo:# 配置accessKeyaccessKey: tE5fkJWqWZ0wZFZLSNfkgjJCQ-5_IR-cm2LuNIiu# 配置secretKeysecretKey: NCvKu-Xs7TvggV8_n7svfiPDRuB4yI622i7R_Y6w# 配置空间名称bucket: public-test-space# 配置机房region: huanan# 配置域名domain: qv1q6zvik.hn-bkt.clouddn.com

4.编写工具类

package com.rtxtitanv.util;import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;/*** @author rtxtitanv* @version 1.0.0* @name com.rtxtitanv.util.SpringUtil* @description 获取Bean的工具类* @date 2021/6/21 11:24*/
@Component
public class SpringUtil implements ApplicationContextAware {private static ApplicationContext applicationContext;/*** 设置ApplicationContext** @param applicationContext 上下文对象* @throws BeansException BeansException*/@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {SpringUtil.applicationContext = applicationContext;}/*** 获取ApplicationContext实例** @return ApplicationContext实例*/public static ApplicationContext getApplicationContext() {return applicationContext;}/*** 通过名称获取Bean** @param name 名称* @return Bean*/public static Object getBean(String name) {return getApplicationContext().getBean(name);}/*** 通过class获取Bean** @param requiredType class对象* @param <T>          class所代表的类型* @return Bean*/public static <T> T getBean(Class<T> requiredType) {return getApplicationContext().getBean(requiredType);}/*** 通过name和class获取Bean** @param name         name* @param requiredType class* @param <T>          class所代表的类型* @return Bean*/public static <T> T getBean(String name, Class<T> requiredType) {return getApplicationContext().getBean(name, requiredType);}
}
package com.rtxtitanv.util;import com.alibaba.fastjson.JSON;
import com.google.gson.Gson;
import com.qiniu.cdn.CdnManager;
import com.qiniu.cdn.CdnResult;
import com.qiniu.common.QiniuException;
import com.qiniu.http.Response;
import com.qiniu.storage.BucketManager;
import com.qiniu.storage.UploadManager;
import com.qiniu.storage.model.DefaultPutRet;
import com.qiniu.util.Auth;
import com.qiniu.util.StringMap;
import com.rtxtitanv.config.QiniuKodoConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;/*** @author rtxtitanv* @version 1.0.0* @name com.rtxtitanv.util.QiniuKodoUtils* @description 七牛云对象存储工具类* @date 2021/6/20 12:35*/
public class QiniuKodoUtils {private static final Logger LOGGER = LoggerFactory.getLogger(QiniuKodoUtils.class);private static final String BUCKET;private static final String DOMAIN;private static final UploadManager UPLOAD_MANAGER;private static final UploadManager RESUMABLE_UPLOAD_MANAGER;private static final BucketManager BUCKET_MANAGER;private static final Auth AUTH;private static final CdnManager CDN_MANAGER;static {QiniuKodoConfig qiniuKodoConfig = SpringUtil.getBean(QiniuKodoConfig.class);BUCKET = qiniuKodoConfig.getBucket();DOMAIN = qiniuKodoConfig.getDomain();UPLOAD_MANAGER = SpringUtil.getBean("uploadManager", UploadManager.class);RESUMABLE_UPLOAD_MANAGER = SpringUtil.getBean("resumableUploadManager", UploadManager.class);BUCKET_MANAGER = SpringUtil.getBean(BucketManager.class);AUTH = SpringUtil.getBean(Auth.class);CDN_MANAGER = SpringUtil.getBean(CdnManager.class);}/*** 简单上传的凭证** @return 上传凭证*/public static String getUpToken() {return AUTH.uploadToken(BUCKET);}/*** 覆盖上传的凭证** @param key 要想进行覆盖的文件名称,必须与上传文件名一致* @return 上传凭证*/public static String getUpToken(String key) {return AUTH.uploadToken(BUCKET, key);}/*** 自定义上传回复的凭证** @return 上传凭证*/public static String getCustomUpToken() {StringMap putPolicy = new StringMap();// 通过设置returnBody参数来实现返回的JSON格式的内容putPolicy.put("returnBody","{\"key\":\"$(key)\",\"hash\":\"$(etag)\",\"bucket\":\"$(bucket)\",\"fsize\":$(fsize)}");long expireSeconds = 3600;return AUTH.uploadToken(BUCKET, null, expireSeconds, putPolicy);}/*** 带回调业务服务器的凭证** @return 上传凭证*/public static String getUpTokenWithCallback() {StringMap putPolicy = new StringMap();// 回调地址,该地址需要允许公网访问putPolicy.put("callbackUrl", "http://83527e8a63ff.ngrok.io/qiniu/upload/callback");putPolicy.put("callbackBody","{\"key\":\"$(key)\",\"hash\":\"$(etag)\",\"bucket\":\"$(bucket)\",\"fsize\":$(fsize)}");putPolicy.put("callbackBodyType", "application/json");long expireSeconds = 3600;return AUTH.uploadToken(BUCKET, null, expireSeconds, putPolicy);}/*** 本地文件上传** @param localFilePath 本地路径* @param fileName      文件名* @param override      是否覆盖上传凭证* @return 文件链接*/public static String uploadFile(String localFilePath, String fileName, boolean override) {String upToken;if (override) {// 覆盖上传凭证upToken = getUpToken(fileName);} else {upToken = getUpToken();}try {Response response = UPLOAD_MANAGER.put(localFilePath, fileName, upToken);String fileUrl = fileUrl(response, fileName);String[] urls = {fileUrl};refreshFiles(urls);return fileUrl;} catch (QiniuException ex) {qiniuException(ex);throw new RuntimeException(ex.getMessage());}}/*** 字节数组上传** @param bytes    字节数组* @param fileName 文件名* @return 文件链接*/public static String uploadFile(byte[] bytes, String fileName) {try {Response response = UPLOAD_MANAGER.put(bytes, fileName, getUpToken());return fileUrl(response, fileName);} catch (QiniuException ex) {qiniuException(ex);throw new RuntimeException(ex.getMessage());}}/*** 数据流上传** @param inputStream 输入流* @param fileName    文件名* @return 文件链接*/public static String uploadFile(InputStream inputStream, String fileName) {try {Response response = UPLOAD_MANAGER.put(inputStream, fileName, getUpToken(), null, null);return fileUrl(response, fileName);} catch (QiniuException ex) {qiniuException(ex);throw new RuntimeException(ex.getMessage());}}/*** 断点续传** @param inputStream 输入流* @param fileName    文件名* @return 文件链接*/public static String resumableUploadFile(InputStream inputStream, String fileName) {try {Response response = RESUMABLE_UPLOAD_MANAGER.put(inputStream, fileName, getUpToken(), null, null);return fileUrl(response, fileName);} catch (QiniuException ex) {qiniuException(ex);throw new RuntimeException(ex.getMessage());}}/*** 手动拼接方式获取公开空间文件链接** @param fileName 文件名* @return 文件链接* @throws UnsupportedEncodingException*/public static String getFileUrl(String fileName) throws UnsupportedEncodingException {// 对文件名进行urlencode以兼容不同的字符String encoderFileName = URLEncoder.encode(fileName, "UTF-8").replace("+", "20%");// 拼接文件链接String finalUrl = String.format("%s/%s", "http://" + DOMAIN, encoderFileName);LOGGER.info(finalUrl);return finalUrl;}/*** 手动拼接方式获取私有空间文件链接** @param fileName 文件名* @return 文件链接* @throws UnsupportedEncodingException*/public static String getPrivateFileUrl(String fileName) throws UnsupportedEncodingException {// 构建对应的公开空间访问链接String encoderFileName = URLEncoder.encode(fileName, "UTF-8").replace("+", "20%");String publicUrl = String.format("%s/%s", "http://" + DOMAIN, encoderFileName);// 1小时,可以自定义链接过期时间long expireInSeconds = 3600;// 对该链接进行私有授权签名String finalUrl = AUTH.privateDownloadUrl(publicUrl, expireInSeconds);LOGGER.info(finalUrl);return finalUrl;}/*** 删除空间中的文件** @param fileName 文件名* @return*/public static String delete(String fileName) {try {Response response = BUCKET_MANAGER.delete(BUCKET, fileName);return response.statusCode == 200 ? "删除成功" : "删除失败";} catch (QiniuException ex) {LOGGER.error(ex.code() + "");LOGGER.error(ex.response.toString());throw new RuntimeException(ex.getMessage());}}/*** 文件刷新** @param urls 待刷新的文件链接数组*/public static void refreshFiles(String[] urls) {try {CdnResult.RefreshResult refreshResult = CDN_MANAGER.refreshUrls(urls);LOGGER.info(refreshResult.code + "");} catch (QiniuException e) {LOGGER.error(e.response.toString());}}/*** 返回文件链接** @param response com.qiniu.http.Response* @param fileName 文件名* @return 文件链接* @throws QiniuException QiniuException*/private static String fileUrl(Response response, String fileName) throws QiniuException {// 解析上传成功的结果DefaultPutRet putRet = new Gson().fromJson(response.bodyString(), DefaultPutRet.class);LOGGER.info(putRet.key);LOGGER.info(putRet.hash);LOGGER.info("上传文件成功 {}", JSON.toJSONString(putRet));return "http://" + DOMAIN + "/" + fileName;}/*** QiniuException异常处理** @param ex QiniuException*/private static void qiniuException(QiniuException ex) {Response response = ex.response;LOGGER.error(response.toString());LOGGER.error("上传文件失败 {}", ex);try {LOGGER.error(response.bodyString());} catch (QiniuException e) {e.printStackTrace();}}
}

5.Controller层

package com.rtxtitanv.controller;import com.rtxtitanv.service.QiniuKodoService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;/*** @author rtxtitanv* @version 1.0.0* @name com.rtxtitanv.controller.QiniuKodoController* @description QiniuKodoController* @date 2021/6/20 12:36*/
@RestController
public class QiniuKodoController {@Resourceprivate QiniuKodoService qiniuKodoService;@PostMapping("/upload/path")public String uploadPath(@RequestParam(value = "localFilePath") String localFilePath,@RequestParam(value = "fileName") String filename, @RequestParam(value = "override") boolean override) {return qiniuKodoService.uploadPath(localFilePath, filename, override);}@PostMapping("/upload/bytes")public String uploadBytes(@RequestPart(value = "file") MultipartFile file) {return qiniuKodoService.uploadBytes(file);}@PostMapping("/upload/stream")public String uploadStream(@RequestPart(value = "file") MultipartFile file) {return qiniuKodoService.uploadStream(file);}@PostMapping("/upload/resumable")public String resumableUpload(@RequestPart(value = "file") MultipartFile file) {return qiniuKodoService.resumableUpload(file);}@GetMapping("/download/{fileName}")public void download(@PathVariable(value = "fileName") String fileName, HttpServletResponse response) {try {String fileUrl = qiniuKodoService.download(fileName);response.sendRedirect(fileUrl);} catch (IOException e) {e.printStackTrace();}}@GetMapping("/download/private/{fileName}")public void privateDownload(@PathVariable(value = "fileName") String fileName, HttpServletResponse response) {try {String fileUrl = qiniuKodoService.privateDownload(fileName);response.sendRedirect(fileUrl);} catch (IOException e) {e.printStackTrace();}}@DeleteMapping("/delete/{fileName}")public String delete(@PathVariable(value = "fileName") String fileName) {return qiniuKodoService.delete(fileName);}@GetMapping("/token")public String getUpToken() {return qiniuKodoService.getUpToken();}@GetMapping("/token/custom")public String getCustomUpToken() {return qiniuKodoService.getCustomUpToken();}@GetMapping("/token/callback")public String getUpTokenWithCallback() {return qiniuKodoService.getUpTokenWithCallback();}@PostMapping("/qiniu/upload/callback")public void qiNiuCallback(HttpServletRequest request) {try {String line = "";BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(request.getInputStream()));StringBuilder stringBuilder = new StringBuilder();while ((line = bufferedReader.readLine()) != null) {stringBuilder.append(line);}Logger logger = LoggerFactory.getLogger(QiniuKodoController.class);logger.info("callbackBody:" + stringBuilder);} catch (IOException e) {e.printStackTrace();}}
}

6.Service层

package com.rtxtitanv.service;import org.springframework.web.multipart.MultipartFile;import java.io.UnsupportedEncodingException;/*** @author rtxtitanv* @version 1.0.0* @name com.rtxtitanv.service.QiniuKodoService* @description QiniuKodoService* @date 2021/6/20 12:37*/
public interface QiniuKodoService {/*** 本地文件上传** @param localFilePath 本地文件路径* @param fileName      文件名* @param override      是否覆盖上传凭证* @return 文件链接*/String uploadPath(String localFilePath, String fileName, boolean override);/*** 字节数组上传** @param file MultipartFile对象* @return 文件链接*/String uploadBytes(MultipartFile file);/*** 数据流上传** @param file MultipartFile对象* @return 文件链接*/String uploadStream(MultipartFile file);/*** 断点续传** @param file MultipartFile对象* @return 文件链接*/String resumableUpload(MultipartFile file);/*** 公开空间文件下载** @param fileName 文件名* @return 文件链接* @throws UnsupportedEncodingException*/String download(String fileName) throws UnsupportedEncodingException;/*** 私有空间文件下载** @param fileName 文件名* @return 文件链接* @throws UnsupportedEncodingException*/String privateDownload(String fileName) throws UnsupportedEncodingException;/*** 删除空间中的文件** @param fileName 文件名* @return*/String delete(String fileName);/*** 获取简单上传的配置** @return 上传凭证*/String getUpToken();/*** 获取自定义上传回复的凭证** @return 上传凭证*/String getCustomUpToken();/*** 获取带回调业务服务器的凭证** @return 上传凭证*/String getUpTokenWithCallback();
}
package com.rtxtitanv.service.impl;import com.qiniu.util.StringUtils;
import com.rtxtitanv.service.QiniuKodoService;
import com.rtxtitanv.util.QiniuKodoUtils;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.UUID;/*** @author rtxtitanv* @version 1.0.0* @name com.rtxtitanv.service.impl.QiniukodoServiceImpl* @description QiniukodoService实现类* @date 2021/6/20 12:37*/
@Service
public class QiniukodoServiceImpl implements QiniuKodoService {@Overridepublic String uploadPath(String localFilePath, String fileName, boolean override) {if (StringUtils.isNullOrEmpty(localFilePath)) {throw new RuntimeException("文件路径为空");}return QiniuKodoUtils.uploadFile(localFilePath, fileName, override);}@Overridepublic String uploadBytes(MultipartFile file) {String originalFilename = file.getOriginalFilename();String fileName = UUID.randomUUID() + "-" + originalFilename;if (file.isEmpty()) {throw new RuntimeException("文件为空");}try {byte[] bytes = file.getBytes();return QiniuKodoUtils.uploadFile(bytes, fileName);} catch (IOException e) {e.printStackTrace();throw new RuntimeException(e.getMessage());}}@Overridepublic String uploadStream(MultipartFile file) {String originalFilename = file.getOriginalFilename();String fileName = UUID.randomUUID() + "-" + originalFilename;if (file.isEmpty()) {throw new RuntimeException("文件为空");}try {ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(file.getBytes());return QiniuKodoUtils.uploadFile(byteArrayInputStream, fileName);} catch (IOException e) {e.printStackTrace();throw new RuntimeException(e.getMessage());}}@Overridepublic String resumableUpload(MultipartFile file) {String originalFilename = file.getOriginalFilename();String fileName = UUID.randomUUID() + "-" + originalFilename;if (file.isEmpty()) {throw new RuntimeException("文件为空");}try {ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(file.getBytes());return QiniuKodoUtils.resumableUploadFile(byteArrayInputStream, fileName);} catch (IOException e) {e.printStackTrace();throw new RuntimeException(e.getMessage());}}@Overridepublic String download(String fileName) throws UnsupportedEncodingException {if (StringUtils.isNullOrEmpty(fileName)) {throw new RuntimeException("文件名为空");}return QiniuKodoUtils.getFileUrl(fileName);}@Overridepublic String privateDownload(String fileName) throws UnsupportedEncodingException {if (StringUtils.isNullOrEmpty(fileName)) {throw new RuntimeException("文件名为空");}return QiniuKodoUtils.getPrivateFileUrl(fileName);}@Overridepublic String delete(String fileName) {if (StringUtils.isNullOrEmpty(fileName)) {throw new RuntimeException("文件名为空");}return QiniuKodoUtils.delete(fileName);}@Overridepublic String getUpToken() {return QiniuKodoUtils.getUpToken();}@Overridepublic String getCustomUpToken() {return QiniuKodoUtils.getCustomUpToken();}@Overridepublic String getUpTokenWithCallback() {return QiniuKodoUtils.getUpTokenWithCallback();}
}

四、七牛云对象存储测试

运行项目,使用Postman进行七牛云对象存储相关测试。

1.服务端直传测试

文件上传分为客户端上传(主要是指网页端和移动端等面向终端用户的场景)和服务端上传两种场景。服务端直传是指客户利用七牛服务端SDK从服务端直接上传文件到七牛云,交互的双方一般都在机房里面,所以服务端可以自己生成上传凭证,然后利用SDK中的上传逻辑进行上传,最后从七牛云获取上传的结果,这个过程中由于双方都是业务服务器,所以很少利用到上传回调的功能,而是直接自定义returnBody来获取自定义的回复内容。

(1)本地文件上传

直接指定文件的完整路径即可完成本地文件上传。发送如下POST请求上传文件,请求地址为http://localhost:8080/upload/path,不覆盖上传的凭证:

然后查看空间管理中的文件管理,文件成功上传:

上传的时候如果没有指定fileName,则以文件内容的hash值作为文件名。

然后访问返回的文件链接查看文件:

不覆盖上传的凭证,再次发送如下POST请求上传另一个文件,但文件名与之前的一致会报错:

查看控制台打印的错误日志:

如果想要对文件进行覆盖,需要覆盖上传的凭证,并且想要覆盖的文件名必须与上传的文件名一致。发送如下POST请求上传另一个文件,覆盖上传的凭证:

在存储空间中查看文件,这里可以看到文件大小已经变化了:

然后访问返回的文件链接查看文件,发现文件已经被覆盖:

这里注意在上传成功之后需要刷新文件。因为访问的时候会访问到CDN缓存,如果不刷新文件,查看到的还是原来的文件。也可以在CDN中的刷新预取中刷新文件:

(2)字节数组上传

可以支持将内存中的字节数组上传到空间中。发送如下POST请求上传文件,请求地址为http://localhost:8080/upload/bytes

查看存储空间中的文件,发现成功上传:

然后访问返回的文件链接查看文件:

(3)数据流上传

通过InputStream进行上传,适用于所有的InputStream子类。发送如下POST请求上传文件,请求地址为http://localhost:8080/upload/stream

查看存储空间中的文件,发现成功上传:

然后访问返回的文件链接查看文件:

(4)断点续传

断点续传是在分片上传的基础上实现。SDK内置表单上传和分片上传两种上传方式。表单上传适合小文件上传,分片上传适合大文件上传。七牛文件上传管理器UploadManager上传文件时,会自动根据定义的Configuration.putThreshold来判断是采用表单上传还是分片上传的方法,超过了定义的Configuration.putThreshold就会采用 分片上传的方法,可以在构造该类对象的时候,通过Configuration类来自定义这个值。分片上传分为V1和V2版,默认为V1版。

发送如下POST请求上传文件,请求地址为http://localhost:8080/upload/resumable,上传过程中断开网络后再连接:

查看存储空间中的文件,发现成功上传:

2.文件下载测试

文件下载分为公开空间的文件下载和私有空间的文件下载。文件的链接地址可以通过手动拼接方式和sdk自动生成方式获取,这里使用手动拼接方式获取文件的链接地址。对于公开空间,其访问的链接主要是将空间绑定的域名拼接上空间里面的文件名即可访问,对于私有空间,首先需要按照公开空间的文件访问方式构建对应的公开空间访问链接,然后再对这个链接进行私有授权签名。

(1)公开空间下载

访问http://localhost:8080/download/27c53ab8-5287-4151-8b80-47635b55df24-87091240.png,查看到了文件,可见最终成功重定向到了文件链接地址:

查看控制台打印的文件链接:

(2)私有空间下载

将配置文件中的空间名和域名修改为私有空间的空间名和域名后重启项目,然后向私有空间上传一个文件:

按照公开空间的方式无法下载私有空间的文件:

访问http://localhost:8080/download/private/b0eda5b4-a070-48f3-9cc7-9e969fa6e20d-72780175.png,查看到了文件,可见最终成功重定向到了文件链接地址:

查看控制台打印的文件链接:

3.删除文件测试

发送DELETE请求http://localhost:8080/delete/b0eda5b4-a070-48f3-9cc7-9e969fa6e20d-72780175.png删除指定文件:

查看存储空间中的文件,发现成功删除:

4.客户端上传测试

客户端(移动端或者Web端)上传文件的时候,需要从客户自己的业务服务器获取上传凭证,而这些上传凭证是通过服务端的SDK来生成的,然后通过客户自己的业务API分发给客户端使用。根据上传的业务需求不同,七牛云Java SDK支持丰富的上传凭证生成方式。

(1)简单上传凭证

最简单的上传凭证只需要AccessKeySecretKeyBucket就可以。访问http://localhost:8080/token获取简单上传的凭证:

复制上传凭证然后然后上传如下文件到华南区域的加速上传地址http://upload-z2.qiniup.com:

表单没有指定key,可以使用上传策略的saveKey字段所指定魔法变量生成Key,如果没有模板,则使用文件内容Hash值作为Key。

查看存储空间中的文件,发现成功上传:

将返回的key拼接到http://localhost:8080/download/private/访问即可重定向到文件链接地址查看文件:

客户端上传的地址在七牛云对象存储的存储区域中查看,不同的存储区域的域名不同。七牛云对象存储的存储区域:https://developer.qiniu.com/kodo/1671/region-endpoint-fq。

(2)自定义上传回复凭证

默认情况下,文件上传到七牛之后,在没有设置returnBody或者callback相关的参数情况下,七牛返回给上传端的回复格式为hashkey。之前的简单上传凭证就是默认的回复格式。通过设置returnBody参数可以实现自定义返回的JSON格式的内容。

访问http://localhost:8080/token/custom获取自定义上传回复的凭证:

复制上传凭证然后然后上传如下文件到华南区域的加速上传地址http://upload-z2.qiniup.com,返回的是自定义的JSON格式:

查看存储空间中的文件,发现成功上传:

将返回的key拼接到http://localhost:8080/download/private/访问即可重定向到文件链接地址查看文件:

(3)带回调业务服务器的凭证

回调通知是指客户端在上传时指定服务端在处理完上传请求后,应该通知某个特定服务器,在该服务器确认接收了该回调后才将所有结果返回给客户端。

开发者可以要求七牛云存储在某文件上传完成后向特定的URL发起一个回调请求。七牛云存储会将该回调的响应内容作为文件上传响应的一部分一并返回给客户端。回调的流程如下:

要启用回调功能,业务服务器在签发上传凭证时需要设置上传策略(PutPolicy)中的callbackUrlcallbackBody字段,callbackUrl需要允许公网访问。在上传策略里面设置了上传回调相关参数的时候,七牛在文件上传到服务器之后,会主动地向callbackUrl发送POST请求的回调,回调的内容为callbackBody模版所定义的内容。业务服务器在收到来自七牛的回调请求的时候,可以根据请求头部的Authorization字段来进行验证,查看该请求是否是来自七牛的未经篡改的请求。

自定义上传回复的凭证适用于上传端(无论是客户端还是服务端)和七牛服务器之间进行直接交互的情况下。在客户端上传的场景之下,有时候客户端需要在文件上传到七牛之后,从业务服务器获取相关的信息,这个时候就要用到七牛的上传回调及相关回调参数的设置。

访问http://localhost:8080/token/callback获取带回调业务服务器的凭证:

复制上传凭证然后然后上传如下文件到华南区域的加速上传地址http://upload-z2.qiniup.com:

查看存储空间中的文件,发现成功上传:

查看控制台打印的日志,可以说明在文件上传成功之后七牛云存储主动向callbackUrl发送了POST请求的回调,回调的内容为callbackBody模版所定义的内容:

将key拼接到http://localhost:8080/download/private/访问即可重定向到文件链接地址查看文件:

由于callbackUrl需要公网能访问的地址,要通过回调访问到本地运行的项目,这里需要用到内网穿透工具,可以使用Ngrok来进行内网穿透,在公网访问本地的项目。在Ngrok官网https://ngrok.com/注册账号并下载。然后找到Authtoken:

然后运行gnrok.exe,执行以下命令:

# Your Authtoken为你的密钥
ngrok authotoken Your Authtoken
ngrok http 8080

执行之后,就可以使用http://83527e8a63ff.ngrok.io在公网访问http://localhost:8080:

代码示例

  • Github:https://github.com/RtxTitanV/springboot-learning/tree/master/springboot2.x-learning/springboot-qiniu-kodo
  • Gitee:https://gitee.com/RtxTitanV/springboot-learning/tree/master/springboot2.x-learning/springboot-qiniu-kodo

SpringBoot2.x 集成 七牛云对象存储Kodo相关推荐

  1. 七牛云对象存储绑定个人域名

    七牛云对象存储绑定个人域名 七牛云介绍 主题说明 过程介绍 登录病创建存储空间 创建完成后 增加域名管理 查看域名解析 chame 域名 去域名管理页面 增加chame解析(我的是阿里云) 七牛云介绍 ...

  2. 一个集成阿里云、腾讯云、七牛云对象存储的SDK

    概述 一个集成阿里云.腾讯云.七牛云对象存储的SDK An SDK integrating Alibaba cloud, Tencent cloud and qiniu cloud object st ...

  3. 七牛云对象存储使用指南

    对象存储服务简介 七牛云海量存储系统(KODO)是自主研发的非结构化数据存储管理平台,支持中心和边缘存储.平台经过多年大规模用户验证已跻身先进技术行列,并广泛应用于海量数据管理的各类场景. 详细介绍参 ...

  4. 七牛云对象存储 Java使用

    文章目录 对象存储服务简介 产品优势 核心功能及服务 创建对象存储空间 上传文件测试 Java SDK简介 使用SDK上传文件 下载文件测试 对象存储服务简介 七牛云海量存储系统(KODO)是自主研发 ...

  5. 使用z-file和七牛云对象存储构建个人网盘

    最近想构建一个个人网盘玩玩,用来存储些资源.这里使用云服务器+zfile+七牛云对象存储进行搭建. 租用云服务器 首先需要在常用的云服务网站买一个云服务器,如阿里云.腾讯云等.这里不说该怎么租用和搭建 ...

  6. WordPress七牛云对象存储免费插件WPQiNiu

    ​​有些站长喜欢使用七牛云来加速 WordPress 站点的访问速度,平时需要手工将需要加速的静态文件或图片上传到七牛云存储空间才行.为了提供效率,boke112 联盟建议大家安装这款 WordPre ...

  7. Java实现将文件(图片)上传到七牛云对象存储,并实现下载和删除功能

    引言:在搭建项目时如果把上传的文件存储在该项目运行的主机上,会导致访问该项目时加载非常缓慢,因此需要对象存储.并且对象存储具有网站数据动静分离,大幅提升网页性能,储存节点多,支持跨地域实时同步,成本低 ...

  8. 微信小程序使用七牛云对象存储保存图片和文件

    先给大家看效果图,如下: 一.开通七牛云对象存储服务(免费的) 官网:https://www.qiniu.com/,实名认证后,创建一个空间,用于保存文件 二.获取 AccessKey和SecretK ...

  9. 一个集成阿里云、腾讯云、七牛云对象存储的工具类

    1.安装composer扩展 composer require china-lishuo/oss-utils 2.这是图片上传到云存储+cdn /*** @param Request $request ...

最新文章

  1. app开发外包的流程、需求、报价,需要知道的细节!
  2. 计算机仿真的过程,计算机仿真的过程与方法.doc
  3. B站《一天学会 MySQL 数据库》学习笔记
  4. java通过url读取远程数据并保持到本地
  5. 2017-11-29 黑盒测试实践(小组作业)小组工作记录
  6. arm开发板上电设置静态ip_与X86/Arm三分天下,RISCV还需几步?
  7. 【Android 逆向】整体加固脱壳 ( DexClassLoader 加载 dex 流程分析 | RawDexFile.cpp 分析 | dvmRawDexFileOpen函数读取 DEX 文件 )
  8. XMLHttpRequest
  9. php转换图片属性a,PHP 提取图片img标记中的任意属性
  10. Bootstrap 3: 菜单居中 Center content in responsive bootstrap navbar
  11. 检索 COM 类工厂中 CLSID 为 {00024500-0000-0000-C000-000000000046} 的组件时失败,原因是出现以下错误: 80070005。...
  12. PHP表单header post get
  13. visio专业版svg图片裁剪
  14. 共阳极管的代码_《手把手教你学FPGA》第三章设计实例
  15. 超市管理系统java_java实现超市管理系统
  16. 181225 Matlab图解峰度kurtosis与偏度skewness
  17. 《资本之王》书中的精髓:黑石公司是如何成长为全球最顶尖的私募股权投资机构的?
  18. win10企业版LTSC-1809无法访问共享文件
  19. 大学生没有项目经验该怎么拿测开岗位的office?来看话梅怎么说
  20. SAP选择屏幕开发(一)

热门文章

  1. 硬盘安装ubuntu 14.04 LTS
  2. 1]解决java.util.concurrent.RejectedExecutionException
  3. ERROR: Failed building wheel for inplace-abn
  4. 小程序和APP谁将主导未来?
  5. 家用服务器 无线路由器,评测六款热门家用Wi-Fi 6路由器
  6. 基于ubuntu 20.04与cri-docker 搭建部署高可用k8s 1.25.3
  7. android关机闹钟设计思路
  8. Linux htop命令
  9. elasticsearch 6.x安装及使用
  10. Synopsys DC 笔记