SpringBoot业务开发 01、Springboot实战:实现Gitee图床上传及删除(含完整代码)

简介: SpringBoot业务开发 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/
相关文章
|
6月前
|
Java 数据库连接
27SpringBoot之JDBC(完整代码)
27SpringBoot之JDBC(完整代码)
52 0
|
6月前
|
SQL Java 数据库连接
26SpringBoot之JDBC(关键代码)
26SpringBoot之JDBC(关键代码)
47 0
|
7月前
|
Java 应用服务中间件 Maven
解析Spring Boot中的Profile:配置文件与代码的双重掌控
解析Spring Boot中的Profile:配置文件与代码的双重掌控
|
5月前
|
Java 关系型数据库 MySQL
基于springboot的问卷调查管理系统(核心代码文档)。Javaee项目,springboot项目。
基于springboot的问卷调查管理系统(核心代码文档)。Javaee项目,springboot项目。
|
1月前
|
存储 JavaScript 前端开发
基于SpringBoot的医护人员排班系统(代码+数据库+文档)
基于SpringBoot的医护人员排班系统(代码+数据库+文档)
|
1月前
|
前端开发 Java 数据库连接
基于SpringBoot宠物领养系统的设计与实现(代码+数据库+文档)
基于SpringBoot宠物领养系统的设计与实现(代码+数据库+文档)
|
1月前
|
存储 Java 数据库
基于springboot的医院信息管理系统(程序+代码+文档)
基于springboot的医院信息管理系统(程序+代码+文档)
|
1月前
|
存储 前端开发 Java
基于springboot的图书管理系统(代码+数据库+文档)
基于springboot的图书管理系统(代码+数据库+文档)
|
6月前
|
数据挖掘 Java 测试技术
无代码动态表单系统 毕业设计 JAVA+Vue+SpringBoot+MySQL(一)
无代码动态表单系统 毕业设计 JAVA+Vue+SpringBoot+MySQL
|
7月前
|
设计模式 Java Spring
Spring Boot使用责任链模式优化业务逻辑中的if-else代码
在开发过程中,我们经常会遇到需要根据不同的条件执行不同的逻辑的情况。传统的做法是使用if-else语句来进行条件判断,但是随着业务逻辑的复杂化,if-else语句会变得越来越臃肿,难以维护和扩展。这时候,我们可以考虑使用责任链模式来优化代码结构,使得代码更加清晰、可扩展和易于维护。