本文小编为大家详细介绍“基于gitee如何实现上传下载文件的功能”,内容详细,步骤清晰,细节处理妥当,希望这篇“基于gitee如何实现上传下载文件的功能”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。
方案的选择
文件的上传和下载是我们这个项目的核心功能,也是整合优化了一下以前的boot项目来实现这个功能。
对于文件的上传和下载一般是使用阿里云OSS、华为云OSS这些,很好用而且官方提供了图形界面,但是这些方式都需要按量储存收费并且和gitee相似都是去调用官方接口实现功能,因为我是在学习阶段,所以选择了在gitee搭建了一个仓库,利用官方的api向仓库发起文件的上传、删除功能,并且利用数据库储存的文件地址实现将文件下载到浏览器客户端。
数据库表的设计
数据库的表暂时只用了两张来实现基本功能,一个是文件表,一个是文件夹表
public class File { @TableId(type = IdType.AUTO) private Long id; private String fileName; private String filePath; private String fileSize; @TableField(fill = FieldFill.INSERT) @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private Date createTime; private Long userId; private Long folderId; //分享次数 private Integer shareTimes; //文件描述 private String fileCover; }
public class Folder { @TableId(type = IdType.AUTO) private Long id; private String folderName; private Long fatherId; //父文件夹id,为0表示没有最上层文件夹 private Long userId; private String folderCover; private Boolean folderPermissions; @TableField(fill = FieldFill.INSERT) @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private Date createTime; }
搭建gitee仓库
首先打开自己的gitee新建一个仓库,填写名称,勾选初始化仓库,创建好之后设置为开源
仓库创建好之后打开“个人主页”—>“个人设置”---->“私人令牌”为自己生成一个新的私人令牌用于调用官方接口时的认证。
生成时不用管选项直接生成,注意保存自己的令牌,因为只会展示一次。
gitee图床工具类的编写
这里用的别人写好的直接copy就行,注意里面的私人令牌、个人空间、仓库名、默认存储地址要改成自己的,这个工具类也就是利用这些信息通过HttpUtil工具类向gitee仓库发起上传请求。
package com.ityz.file.util; 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 ityz * @Date 2022/11/23 16:38 * @Description Gitee图床工具类 */ public class GiteeImgBedUtil { /** * 码云私人令牌 */ private static final String ACCESS_TOKEN = "0616f0e894e3c264bac45591e34a43bc"; //这里不展示我自己的了,需要你自己补充 /** * 码云个人空间名 */ private static final String OWNER = "procedure-yuan-yanzu"; /** * 上传指定仓库 */ private static final String REPO = "files"; /** * 默认上传时指定存放图片路径 */ public static final String PATH = "files/"; //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(); } }
文件上传接口
文件上传的过程为前端传入文件对象和相关信息然后我们向gitee发起请求将文件传到仓库,上传成功之后在将返回的下载地址和相关信息存入数据库当中。
中间工具类
这里我们先来编写一个中间工具类来发起请求和拿到结果,这个代码也写在服务当中的话会显得代码太长太复杂所以我单独拿出来写。
import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; import com.ityz.common.constants.GiteeConstant; import lombok.extern.slf4j.Slf4j; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; /** * gitee文件操作api */ @Slf4j public class GiteeApi { /** * 上传文件api * @param multipartFile 前端传入的文件对象 * @param folder 文件所存文件夹名称 * @return gitee的api上传返回值的json对象 * @throws IOException */ public static JSONObject upload(MultipartFile multipartFile,String folder) throws IOException { log.info("uploadFile()请求已来临..."); //根据文件名生成指定的请求url String originalFilename = multipartFile.getOriginalFilename(); if (originalFilename == null) { log.info("服务器接收文件失败!"); } //Gitee请求:发送上传文件请求 String JSONResult = GiteeImgBedUtil.uploadFile(folder, originalFilename, multipartFile.getBytes()); //解析响应JSON字符串 //上传txt文件时会出问题,没解决 JSONObject jsonObj = JSONUtil.parseObj(JSONResult); //请求失败 if (jsonObj.getObj(GiteeConstant.RESULT_BODY_COMMIT) == null) { log.info("上传文件失败!"); } //请求成功:返回下载地址 JSONObject content = JSONUtil.parseObj(jsonObj.getObj(GiteeConstant.RESULT_BODY_CONTENT)); log.info("上传成功,下载地址为:" + content.getStr(GiteeConstant.RESULT_BODY_DOWNLOAD_URL)); return content; } }
Controller
controller拿到文件和信息后调用中间工具类上传文件,然后从请求头中拿到用户id,从返回对象中拿到文件信息存入File类当中然后使用mybatisplus将对象存入数据库就行。
@RestController @Slf4j @RequestMapping("/file") public class FileController { @Autowired private FileService fileService; @Autowired private HttpServletRequest request; /** * 文件上传接口 * @param multipartFile 上传的文件对象 * @param fileCover 文件的描述信息 * @param folderId 文件所属的文件夹id * @return 用户文件地址 * @throws IOException */ @PostMapping("/upload") public Result<String> uploadFile(@RequestParam("file") MultipartFile multipartFile , @RequestParam("fileCover") String fileCover , @RequestParam("folderId") Long folderId) throws IOException { JSONObject content = GiteeApi.upload(multipartFile, "test/"); //获取userId Long userId = Long.valueOf(TokenUtil.parseToken(request.getHeader("token"))); File file = new File(); file.setFileCover(fileCover); file.setUserId(userId); file.setFolderId(folderId); file.setFileName(content.getStr(GiteeConstant.RESULT_BODY_NAME));; file.setFileSize(content.getStr(GiteeConstant.RESULT_BODY_SIZE)); file.setFilePath(content.getStr(GiteeConstant.RESULT_BODY_DOWNLOAD_URL)); fileService.fileUpload(file); return new Result<>(ResultCode.SUCCESS,ResultCode.UP_FILE_SUCCESS,content.getStr(GiteeConstant.RESULT_BODY_DOWNLOAD_URL)); } }
service比较简单,设置一个储存时间调用mapper存入就行
/** * 上传文件 * * @param file 用于向数据库储存的文件对象 */ @Override public void fileUpload(File file) { file.setCreateTime(new Date()); fileMapper.insert(file); }
接口测试
我们随便上传一个文件
查看gitee和数据库是否有相应结果
这里id为2是因为之前删除了一条数据,想要从头开始截断表就行。
文件下载接口
文件下载最初比较困扰我,有两种方式,第一种是直接将文件对象写入到用户提供的本地文件夹地址,第二种是将文件下载到浏览器。最终选择了第二种方式,因为用户不用设置下载地址而且在浏览器下载可以看到下载过程,整个流程是利用下载地址将文件转换为文件流写入字节数组,在利用浏览器通过下载的方式写出字节数组到输出流。
controller
使用文件id先从数据库查到相应地址然后利用地址完成后续操作。
/** * 文件下载接口 * @param fileId 文件id * @param response http响应对象 * @return 下载结果 */ @GetMapping("/download") public Result<String> download(Long fileId,HttpServletResponse response) { try { String downloadUrl = fileService.downloadFile(fileId); URL url = new URL(downloadUrl); URLConnection conn = url.openConnection(); InputStream bis = conn.getInputStream(); byte[] bytes = new byte[bis.available()]; OutputStream os = response.getOutputStream(); // 从文件流读取字节到字节数组中 while (bis.read(bytes) != -1) { // 重置 response response.reset(); // 设置 response 的下载响应头 response.setContentType("application/x-download"); response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode(FileUtil.getFileName(downloadUrl), "UTF-8")); // 注意,这里要设置文件名的编码,否则中文的文件名下载后不显示 // 写出字节数组到输出流 os.write(bytes); // 刷新输出流 os.flush(); } return new Result<>(ResultCode.SUCCESS,ResultCode.DOWN_FILE_SUCCESS); }catch (Exception e){ e.printStackTrace(); return new Result<>(ResultCode.FAIL,ResultCode.DOWN_FILE_FAIL); } }
这里需要一个编写获取文件名的工具类,不然下载之后浏览器不知道文件类型
/** * 获取url中的文件名(取到最后一个/后面的就是文件名) * @param url 文件地址 * @return */ public static String getFileName(String url) { if(!url.equals("")){ return url.substring(url.lastIndexOf("/")+1); } else return "文件地址错误"; }
接口测试
发送请求完成下载
文件删除接口
中间工具类
/** * 删除文件api * @param url 文件地址 */ public static void del(String url){ if (!url.equals("") && !url.contains("master/")) { log.info("url:" + url + " 无法解析路径!"); } String path = url.substring(url.indexOf("master/") + 7); log.info("解析取得待删除路径:" + path); String shaResult = GiteeImgBedUtil.getSha(path); JSONObject jsonObj = JSONUtil.parseObj(shaResult); String sha = jsonObj.getStr(GiteeConstant.RESULT_BODY_SHA); //3、Gitee请求:发送删除请求 String JSONResult = GiteeImgBedUtil.deleteFile(path, sha); jsonObj = JSONUtil.parseObj(JSONResult); if (jsonObj.getObj(GiteeConstant.RESULT_BODY_COMMIT) == null) { log.info("删除文件失败!"); } log.info("文件路径为:" + path + " 删除成功!"); }
service
获取文件地址并删除数据库数据
/** * 删除文件 * @param fileId 文件id * @return */ @Override public String delFile(Long fileId) { String url = fileMapper.selectById(fileId).getFilePath(); fileMapper.deleteById(fileId); return url; }
controller
** * 删除文件接口 * @param fileId 前端传入的文件id * @return */ @GetMapping("/del") public Result<String> delFile(Long fileId) { try { String url = fileService.delFile(fileId); GiteeApi.del(url); return new Result<>(ResultCode.SUCCESS,ResultCode.DEL_FILE_SUCCESS); } catch (Exception e){ e.printStackTrace(); return new Result<>(ResultCode.FAIL,ResultCode.DEL_FILE_FAIL); } }
接口测试
发送请求删除文件
查看数据库和gitee仓库