一、实际测试及配置
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/