SpringBoot业务开发 01、Springboot实战:实现Gitee图床上传及删除(含完整代码)
@[toc]
引言
个人项目中由于要对图片进行存储,想到做笔记图片都是存储到图床上所以就想调用Gitee的接口来实现,最后方案还是因为网络延时太大没有选择图床存储,而是直接存储到至本地服务器。
在这里给出上传Gitee图床案例demo的项目地址及使用说明,若是好用请给个star:
- Github地址:https://github.com/changlua/CL-SpringbootDemos,对应仓库项目文件
01、springboot-Gitee
一、实际测试及配置
1.1、接口测试
测试工具Api-Post
包含两个接口:上传图片、删除图片。
上传图片:POST请求,body请求体使用multpart/form-data
格式,图片文件名称为file。响应result结果为资源下载路径。
删除图片:POST请求,body使用application/json
格式,携带url参数为待删除的图片地址。
1.2、项目配置及使用
核心修改配置点
只需要关注以下配置信息即可:
配置文件中配置的文件路径可覆盖Util中的文件路径,格式为xxx/形式
上面的配置好之后即可启动项目,默认端口为8081,对应的两个接口写在controller/GiteeController.java
中:
配置点说明
我们需要提前准备好请求地址中的owner、repo
以及请求体中access_token
:
owner·
:指的是个人空间,如下我的空间地址。repo
:指的是你要上传的仓库名称,如下图:access_token
:用来表示你身份的一串字符串,通过该token就能够确定你的身份!该token生成时只会显示一次,妥善保存好,我的已经生成好了:
关于目录:
二、Gitee的接口API
Gitee为开发者也提供了API,见:Gitee—API文档
对于上传图床我们只需要关注新建文件部分:请求url中{xxx}
是我们需要自己填充的部分,下面Response Class
是向gitee发出请求后得到的响应实体类结构,得到响应内容中我们关心的就是download_url
属性,其是可供我们下载的图片地址。
下面是需要携带的请求体参数(封装到body中):access_token
是每次请求必带的,其他的根据指定的接口携带
上传文件接口
:POST请求,直接发送请求上传即可。请求体携带access_token
、content
(文件内容经过base64编码);最后在请求成功后响应对象里属性名为download_url的就是下载地址。
删除文件接口
:①GET请求,调用获取仓库具体路径内容接口,取到sha属性。②DELETE请求,携带sha、access_token等其他请求体参数来调用删除接口。
获取sha的接口如下:
三、实现说明
3.1、依赖版本
spring-boot-starter-web
:2.6.1
hutool-all
:5.5.8
lombok
:1.18.20
<!-- 额外引入工具类 -->
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</dependency>
<!-- Hutool工具类 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.10</version>
</dependency>
本项目里使用hutool中的HttpUtil
来发起请求调用。
3.2、项目结构
3.3、代码实现
config
定义一些调用Gitee的API响应属性:
/**
* @ClassName Constant
* @Author ChangLu
* @Date 2021/12/11 0:36
* @Description Gitee相关常量
*/
public class GiteeConstant {
public static String RESULT_BODY_COMMIT = "commit";
public static String RESULT_BODY_CONTENT = "content";
public static String RESULT_BODY_DOWNLOAD_URL = "download_url";
public static String RESULT_BODY_SHA = "sha";
}
controller
控制器:包含封装好的上传、删除接口
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.changlu.springboot.Exception.CommonEnum;
import com.changlu.springboot.Exception.OwnException;
import com.changlu.springboot.config.GiteeConstant;
import com.changlu.springboot.domain.Basic.ResultBody;
import com.changlu.springboot.domain.Request.BasicRequest;
import com.changlu.springboot.utils.GiteeImgBedUtil;
import com.changlu.springboot.utils.WebTools;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
/**
* @ClassName GiteeController
* @Author ChangLu
* @Date 2021/12/1 0:05
* @Description Gitee控制器
*/
@RestController
@Slf4j
public class GiteeController {
@Value("${gitee.upload.path}")
private String MEMBERS_UPLOAD_PATH;
/**
* 上传文件
*
* @param multipartFile 文件对象
* @return
* @throws IOException
*/
@PostMapping("/gitee/upload")
public ResultBody uploadFile(@RequestParam("file") MultipartFile multipartFile) throws IOException {
log.info("uploadFile()请求已来临...");
//根据文件名生成指定的请求url
String originalFilename = multipartFile.getOriginalFilename();
if (originalFilename == null) {
log.info("服务器接收文件失败!");
throw new OwnException(CommonEnum.IMAGE_EXIST_ERROR);
}
//Gitee请求:发送上传文件请求
String JSONResult = GiteeImgBedUtil.uploadFile(MEMBERS_UPLOAD_PATH, originalFilename, multipartFile.getBytes());
//解析响应JSON字符串
JSONObject jsonObj = JSONUtil.parseObj(JSONResult);
//请求失败
if (jsonObj == null || jsonObj.getObj(GiteeConstant.RESULT_BODY_COMMIT) == null) {
log.info("上传文件失败!");
return ResultBody.fail(CommonEnum.IMAGE_UPLOAD_ERROR);
}
//请求成功:返回下载地址
JSONObject content = JSONUtil.parseObj(jsonObj.getObj(GiteeConstant.RESULT_BODY_CONTENT));
log.info("上传成功,下载地址为:" + content.getObj(GiteeConstant.RESULT_BODY_DOWNLOAD_URL));
return ResultBody.success(content.getObj(GiteeConstant.RESULT_BODY_DOWNLOAD_URL));
}
/**
* 删除文件
*
* @param request
* @return
*/
@PostMapping("/gitee/del")
public ResultBody delFile(@RequestBody BasicRequest request) {
//1、解析取得原始上传路径
String url = request.getUrl();
if (WebTools.isNotEmpty(url) && !url.contains("master/")) {
log.info("url:" + url + " 无法解析路径!");
throw new OwnException(CommonEnum.URL_PARSE_FAILED);
}
String path = url.substring(url.indexOf("master/") + 7);
log.info("解析取得待删除路径:" + path);
if (!WebTools.isEmpty(path)) {
//2、Gitee请求:获取sha
String shaResult = GiteeImgBedUtil.getSha(path);
JSONObject jsonObj = JSONUtil.parseObj(shaResult);
if (jsonObj == null) {
log.info("delFile中获取sha失败!");
return ResultBody.fail(CommonEnum.DEL_FILE_FAILED);
}
String sha = jsonObj.getStr(GiteeConstant.RESULT_BODY_SHA);
//3、Gitee请求:发送删除请求
String JSONResult = GiteeImgBedUtil.deleteFile(path, sha);
jsonObj = JSONUtil.parseObj(JSONResult);
if (jsonObj == null || jsonObj.getObj(GiteeConstant.RESULT_BODY_COMMIT) == null) {
log.info("删除文件失败!");
return ResultBody.fail(CommonEnum.DEL_FILE_FAILED);
}
}
log.info("文件路径为:" + path + " 删除成功!");
return ResultBody.success("删除成功!");
}
}
domain
Basic
BaseExceptionInfoInterface.java
:异常信息接口,之后枚举类会实现该接口
/**
* 基本异常接口
* @author changlu
* @date 2021/07/22 17:03
**/
public interface BaseExceptionInfoInterface {
/**
* 得到错误码
* @date 2021/07/22 17:04
* @return java.lang.String
*/
Integer getResultCode();
/**
* 得到错误信息
* @date 2021/07/22 17:05
* @return java.lang.String
*/
String getResultMsg();
}
ResultBody.java
:响应体封装,统一接口返回对象
import com.changlu.springboot.Exception.CommonEnum;
import lombok.Data;
/**
* @ClassName ResultBody
* @Author ChangLu
* @Date 2021/9/21 9:55
* @Description 响应体封装
*/
@Data
public class ResultBody {
/**
* 响应码
*/
private Integer code;
/**
* 响应消息
*/
private String message;
/**
* 响应结果
*/
private Object result;
public ResultBody() {
}
/**
* 内部封装
* @param code
* @param message
*/
private ResultBody(Integer code, String message) {
this.code = code;
this.message = message;
}
/**
* 响应码与响应结果封装
*/
public ResultBody(BaseExceptionInfoInterface baseErrorInfoInterface) {
this.code = baseErrorInfoInterface.getResultCode();
this.message = baseErrorInfoInterface.getResultMsg();
}
/**
* 成功
* @param data 数据
* @return ResultBody
*/
public static ResultBody success(Object data){
ResultBody resultBody = new ResultBody(CommonEnum.SUCCESS);
resultBody.setResult(data);
return resultBody;
}
/**
* 错误
* @param baseErrorInfoInterface 枚举类
* @return ResultBody
*/
public static ResultBody fail(BaseExceptionInfoInterface baseErrorInfoInterface){
ResultBody resultBody = new ResultBody(baseErrorInfoInterface);
resultBody.setResult(null);
return resultBody;
}
/**
* 可自定义错误描述,
* @param baseErrorInfoInterface
* @param errMsg
* @return
*/
public static ResultBody fail(BaseExceptionInfoInterface baseErrorInfoInterface,String errMsg){
ResultBody resultBody = fail(baseErrorInfoInterface);
resultBody.setMessage(errMsg);
return resultBody;
}
/**
* 错误
* @param code 状态码
* @param message 描述信息
* @return ResultBody
*/
public static ResultBody fail(Integer code,String message){
ResultBody resultBody = new ResultBody(code,message);
resultBody.setResult(null);
return resultBody;
}
}
BasicRequest
用于取得对象体中的url属性,应用在删除接口中:
import lombok.Data;
import java.io.Serializable;
/**
* @ClassName BasicRequest
* @Author ChangLu
* @Date 2021/12/10 23:53
* @Description 请求对象封装
*/
@Data
public class BasicRequest implements Serializable {
private static final long serialVersionUID = 1L;
private String url;
}
Exception
CommonEnum.java
:响应状态码及描述枚举类
import com.changlu.springboot.domain.Basic.BaseExceptionInfoInterface;
/**
* 常见异常枚举类
* @author changlu
* @date 2021/07/22 17:05
**/
public enum CommonEnum implements BaseExceptionInfoInterface {
//成功情况
SUCCESS(200,"成功!"),
//图床异常处理
IMAGE_EXIST_ERROR(1001, "服务器未接收到上传资源"),
IMAGE_UPLOAD_ERROR(1002,"上传Gitee图床失败"),
DEL_FILE_FAILED(1003,"图片删除失败"),
URL_PARSE_FAILED(1004,"Gitee图片url无法解析"),
//系统异常
SYSTEM_ERROR(2000, "系统异常,请从控制台或日志中查看具体错误信息");
//错误码
private final Integer resultCode;
//描述信息
private final String resultMsg;
CommonEnum(Integer resultCode, String resultMsg) {
this.resultCode = resultCode;
this.resultMsg = resultMsg;
}
@Override
public Integer getResultCode() {
return resultCode;
}
@Override
public String getResultMsg() {
return resultMsg;
}
}
OwnException.java
:自定义异常类,用于抛出一些自定义的异常信息以及用于全局异常处理器捕获
/**
* @ClassName OwnException
* @Author ChangLu
* @Date 2021/7/29 23:40
* @Description 自定义异常类
*/
public class OwnException extends RuntimeException{
private final Integer code;
private final String message;
public OwnException(Integer code, String message){
this.code = code;
this.message = message;
}
public OwnException(CommonEnum ex){
this(ex.getResultCode(),ex.getResultMsg());
}
public OwnException(CommonEnum ex, String msg){
this(ex.getResultCode(),msg);
}
public Integer getCode() {
return code;
}
@Override
public String getMessage() {
return message;
}
}
handler
GlobalExceptionHandler.java
:全局异常处理器,这里对于Exception以及自定义异常进行异常捕捉
import com.changlu.springboot.Exception.CommonEnum;
import com.changlu.springboot.Exception.OwnException;
import com.changlu.springboot.domain.Basic.ResultBody;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.servlet.http.HttpServletRequest;
/**
* @ClassName GlobalExceptionHandler
* @Author ChangLu
* @Date 2021/12/11 0:04
* @Description 全局异常捕获器
*/
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
/**
* 处理全局异常(Exception)
* @param e
* @return
*/
@ExceptionHandler(Exception.class)
public ResultBody handleException(HttpServletRequest request, Exception e){
log.error("Exception:",e);
return ResultBody.fail(CommonEnum.SYSTEM_ERROR);
}
/**
* 自定义异常(可自行抛出针对于一些受检类型,继承RuntimeException)
* @param ex 自定义抛出异常
* @return xyz.changlu.util.ResultBody
*/
@ExceptionHandler(value = OwnException.class)
public ResultBody msgExceptionHandler(HttpServletRequest request, OwnException ex){
log.error("OwnException:",ex);
return ResultBody.fail(ex.getCode(),ex.getMessage());
}
}
utils
FileUtil.java
:文件工具类
/**
* @ClassName FileUtils
* @Author ChangLu
* @Date 2021/8/1 18:18
* @Description 文件工具类
*/
public class FileUtil {
/**
* 获取文件名的后缀,如:changlu.jpg => .jpg
* @return 文件后缀名
*/
public static String getFileSuffix(String fileName) {
return fileName.contains(".") ? fileName.substring(fileName.indexOf('.')) : null;
}
}
GiteeImgBedUtil
:图床工具类,对外公开了三个API方法用来向Gitee发送请求
import cn.hutool.core.codec.Base64;
import cn.hutool.http.HttpUtil;
import cn.hutool.http.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* @ClassName UploadGiteeImgBedUtil
* @Author ChangLu
* @Date 2021/12/10 23:41
* @Description Gitee图床工具类
*/
public class GiteeImgBedUtil {
/**
* 码云私人令牌
*/
private static final String ACCESS_TOKEN = ""; //这里不展示我自己的了,需要你自己补充
/**
* 码云个人空间名
*/
private static final String OWNER = "";
/**
* 上传指定仓库
*/
private static final String REPO = "";
/**
* 默认上传时指定存放图片路径
*/
public static final String PATH = "test1/";
//API
/**
* 新建(POST)、获取(GET)、删除(DELETE)文件:()中指的是使用对应的请求方式
* %s =>仓库所属空间地址(企业、组织或个人的地址path) (owner)
* %s => 仓库路径(repo)
* %s => 文件的路径(path)
*/
private static final String API_CREATE_POST = "https://gitee.com/api/v5/repos/%s/%s/contents/%s";
/**
* 生成创建(获取、删除)的指定文件路径
* @param originalFilename 原文件名
* @param path 存储文件路径
* @return
*/
private static String createUploadFileUrl(String originalFilename,String path){
String targetPath = path == null ? GiteeImgBedUtil.PATH : path;
//获取文件后缀
String suffix = FileUtil.getFileSuffix(originalFilename);
//拼接存储的图片名称
String fileName = System.currentTimeMillis()+"_"+ UUID.randomUUID().toString()+suffix;
//填充请求路径
String url = String.format(GiteeImgBedUtil.API_CREATE_POST,
GiteeImgBedUtil.OWNER,
GiteeImgBedUtil.REPO,
targetPath + fileName);
return url;
}
private static String createDelFileUrl(String path){
//填充请求路径
String url = String.format(GiteeImgBedUtil.API_CREATE_POST,
GiteeImgBedUtil.OWNER,
GiteeImgBedUtil.REPO,
path);
return url;
}
private static String createGetUrl(String path){
String targetPath = path == null ? GiteeImgBedUtil.PATH : path;
//填充请求路径
String url = String.format(GiteeImgBedUtil.API_CREATE_POST,
GiteeImgBedUtil.OWNER,
GiteeImgBedUtil.REPO,
targetPath);
return url;
}
/**
* 获取创建文件的请求体map集合:access_token、message、content
* @param multipartFile 文件字节数组
* @return 封装成map的请求体集合
*/
private static Map<String,Object> getUploadBodyMap(byte[] multipartFile){
HashMap<String, Object> bodyMap = new HashMap<>(3);
bodyMap.put("access_token", GiteeImgBedUtil.ACCESS_TOKEN);
bodyMap.put("message", "add file!");
bodyMap.put("content", Base64.encode(multipartFile));
return bodyMap;
}
/**
* 创建普通携带请求体集合内容
* @param map 额外参数
* @param message 请求信息
* @return
*/
private static Map<String,Object> getCommonBodyMap(HashMap map, String message){
HashMap<String, Object> bodyMap = new HashMap<>(2);
bodyMap.put("access_token", GiteeImgBedUtil.ACCESS_TOKEN);
bodyMap.put("message", message);
if (map != null){
bodyMap.putAll(map);
}
return bodyMap;
}
/**
* **********封装好的实际调用方法*******************
*/
//超时
private static int TIMEOUT = 10 * 1000;
/**
* 上传文件
* @param filename 文件名称
* @param path 路径
* @param sha 必备参数from 获取仓库具体路径下的内容
* @return
*/
public static String uploadFile(String path, String originalFilename, byte[] data){
String targetURL = GiteeImgBedUtil.createUploadFileUrl(originalFilename,path);
//请求体封装
Map<String, Object> uploadBodyMap = GiteeImgBedUtil.getUploadBodyMap(data);
return HttpUtil.post(targetURL, uploadBodyMap);
}
/**
* 删除指定path路径下的文件
* @param filename 文件名称
* @param path 路径
* @param sha 必备参数from 获取仓库具体路径下的内容
* @return
*/
public static String deleteFile(String path,String sha){
String delFileUrl = createDelFileUrl(path);
HashMap<String, Object> needMap = new HashMap<>(1);
needMap.put("sha",sha);//添加sha参数
return HttpUtil.createRequest(Method.DELETE, delFileUrl)
.form(getCommonBodyMap(needMap,"del file!")) //构建请求表单
.timeout(TIMEOUT)
.execute().body();
}
/**
* 获取仓库具体路径下的内容,主要是获取 sha
* @param path
* @return
*/
public static String getSha(String path){
String getShaUrl = createDelFileUrl(path);
return HttpUtil.createRequest(Method.GET, getShaUrl)
.form(getCommonBodyMap(null, "get sha!"))
.timeout(TIMEOUT)
.execute().body();
}
}
WebTools.java:常用工具类
/**
* @ClassName WebTools
* @Author ChangLu
* @Date 2021/10/5 23:35
* @Description 常用工具
*/
public class WebTools {
/** 空字符串 */
private static final String NULLSTR = "";
/**
* * 判断一个对象是否为空
*
* @param object Object
* @return true:为空 false:非空
*/
public static boolean isNull(Object object)
{
return object == null;
}
/**
* * 判断一个字符串是否为空串
*
* @param str String
* @return true:为空 false:非空
*/
public static boolean isEmpty(String str)
{
return isNull(str) || NULLSTR.equals(str.trim());
}
/**
* * 判断多个字符串是否为空串
*
* @param str String
* @return true:为空 false:非空
*/
public static boolean areEmpty(String...strs)
{
for (String str : strs) {
if(isEmpty(str)){
return true;
}
}
return false;
}
/**
* * 判断一个字符串是否为非空串
*
* @param str String
* @return true:非空串 false:空串
*/
public static boolean isNotEmpty(String str)
{
return !isEmpty(str);
}
}
Application.yaml
这里要注意配置接口文件的大小,否则一些较大图片上传不了:
# 设置默认运行端口
server:
port: 8081
# max-file-size:servlet每次接收单个文件的最大容量;max-request-size:指的是单次请求接收的文件最大容量
spring:
servlet:
multipart:
max-file-size: 10MB
max-request-size: 100MB
# 自定义
# gitee上传的文件路径(可覆盖图床工具类中的默认存储文件路径)
gitee:
upload:
path: test1/
参考资料
[1]. Vue+Springboot使用Gitee图床上传图片
[2]. Gitee—API文档
- 点赞
- 收藏
- 关注作者
评论(0)