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

举报
长路 发表于 2022/11/28 19:48:38 2022/11/28
【摘要】 01、Springboot实战:实现Gitee图床上 引言 个人项目中由于要对图片进行存储,想到做笔记图片都是存储到图床上所以就想调用Gitee的接口来实现,最后方案还是因为网络延时太大没有选择图床存储,而是直接存储到至本地服务器。 在这里给出上传Gitee图床案例de 实操 依赖版本 spring-boot-starter-web:2.6.1 hutool-all:5.5.8 lombok

@[toc]

引言

个人项目中由于要对图片进行存储,想到做笔记图片都是存储到图床上所以就想调用Gitee的接口来实现,最后方案还是因为网络延时太大没有选择图床存储,而是直接存储到至本地服务器。

在这里给出上传Gitee图床案例demo的项目地址及使用说明,若是好用请给个star:



一、实际测试及配置

1.1、接口测试

测试工具Api-Post

包含两个接口:上传图片、删除图片。

上传图片:POST请求,body请求体使用multpart/form-data格式,图片文件名称为file。响应result结果为资源下载路径。

GIF

删除图片:POST请求,body使用application/json格式,携带url参数为待删除的图片地址。

GIF



1.2、项目配置及使用

核心修改配置点

只需要关注以下配置信息即可:

image-20211212003355504

配置文件中配置的文件路径可覆盖Util中的文件路径,格式为xxx/形式

image-20211212003305680

上面的配置好之后即可启动项目,默认端口为8081,对应的两个接口写在controller/GiteeController.java中:

image-20211212003955170


配置点说明

我们需要提前准备好请求地址中的owner、repo以及请求体中access_token

  • owner·:指的是个人空间,如下我的空间地址。
    • image-20210801205923215
  • repo:指的是你要上传的仓库名称,如下图:
    • image-20210801210149547
  • access_token:用来表示你身份的一串字符串,通过该token就能够确定你的身份!该token生成时只会显示一次,妥善保存好,我的已经生成好了:
    • image-20210801210413439

关于目录:

image-20211212003737589



二、Gitee的接口API

Gitee为开发者也提供了API,见:Gitee—API文档

对于上传图床我们只需要关注新建文件部分:请求url中{xxx}是我们需要自己填充的部分,下面Response Class是向gitee发出请求后得到的响应实体类结构,得到响应内容中我们关心的就是download_url属性,其是可供我们下载的图片地址。

image-20210801205457832

image-20211212092311486

下面是需要携带的请求体参数(封装到body中):access_token是每次请求必带的,其他的根据指定的接口携带

image-20210801205756734

上传文件接口:POST请求,直接发送请求上传即可。请求体携带access_tokencontent(文件内容经过base64编码);最后在请求成功后响应对象里属性名为download_url的就是下载地址。

删除文件接口:①GET请求,调用获取仓库具体路径内容接口,取到sha属性。②DELETE请求,携带sha、access_token等其他请求体参数来调用删除接口。

获取sha的接口如下:

image-20211212092844930



三、实现说明

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、项目结构

image-20211212005104536



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文档

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。