怎么實(shí)現(xiàn)文件的上傳下載功能?下面本篇文章給大家介紹一下基于gitee實(shí)現(xiàn)文件上傳和下載功能的方法,希望對大家有所幫助!
方案的選擇
? 文件的上傳和下載是我們這個(gè)項(xiàng)目的核心功能,也是整合優(yōu)化了一下以前的boot項(xiàng)目來實(shí)現(xiàn)這個(gè)功能。
? 對于文件的上傳和下載一般是使用阿里云OSS、華為云OSS這些,很好用而且官方提供了圖形界面,但是這些方式都需要按量儲存收費(fèi)并且和gitee相似都是去調(diào)用官方接口實(shí)現(xiàn)功能,因?yàn)槲沂窃趯W(xué)習(xí)階段,所以選擇了在gitee搭建了一個(gè)倉庫,利用官方的api向倉庫發(fā)起文件的上傳、刪除功能,并且利用數(shù)據(jù)庫儲存的文件地址實(shí)現(xiàn)將文件下載到瀏覽器客戶端。
數(shù)據(jù)庫表的設(shè)計(jì)
? 數(shù)據(jù)庫的表暫時(shí)只用了兩張來實(shí)現(xiàn)基本功能,一個(gè)是文件表,一個(gè)是文件夾表
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; //分享次數(shù) 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新建一個(gè)倉庫,填寫名稱,勾選初始化倉庫,創(chuàng)建好之后設(shè)置為開源
? 倉庫創(chuàng)建好之后打開“個(gè)人主頁”—>“個(gè)人設(shè)置”—->“私人令牌”為自己生成一個(gè)新的私人令牌用于調(diào)用官方接口時(shí)的認(rèn)證。
? 生成時(shí)不用管選項(xiàng)直接生成,注意保存自己的令牌,因?yàn)橹粫?huì)展示一次。
gitee圖床工具類的編寫
? 這里用的別人寫好的直接copy就行,注意里面的私人令牌、個(gè)人空間、倉庫名、默認(rèn)存儲地址要改成自己的,這個(gè)工具類也就是利用這些信息通過HttpUtil工具類向gitee倉庫發(fā)起上傳請求。
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"; //這里不展示我自己的了,需要你自己補(bǔ)充 /** * 碼云個(gè)人空間名 */ private static final String OWNER = "procedure-yuan-yanzu"; /** * 上傳指定倉庫 */ private static final String REPO = "files"; /** * 默認(rèn)上傳時(shí)指定存放圖片路徑 */ public static final String PATH = "files/"; //API /** * 新建(POST)、獲取(GET)、刪除(DELETE)文件:()中指的是使用對應(yīng)的請求方式 * %s =>倉庫所屬空間地址(企業(yè)、組織或個(gè)人的地址path) (owner) * %s => 倉庫路徑(repo) * %s => 文件的路徑(path) */ private static final String API_CREATE_POST = "https://gitee.com/api/v5/repos/%s/%s/contents/%s"; /** * 生成創(chuàng)建(獲取、刪除)的指定文件路徑 * @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; } /** * 獲取創(chuàng)建文件的請求體map集合:access_token、message、content * @param multipartFile 文件字節(jié)數(shù)組 * @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; } /** * 創(chuàng)建普通攜帶請求體集合內(nèi)容 * @param map 額外參數(shù) * @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; } /** * **********封裝好的實(shí)際調(diào)用方法******************* */ //超時(shí) private static int TIMEOUT = 10 * 1000; /** * 上傳文件 * @param filename 文件名稱 * @param path 路徑 * @param sha 必備參數(shù)from 獲取倉庫具體路徑下的內(nèi)容 * @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 必備參數(shù)from 獲取倉庫具體路徑下的內(nèi)容 * @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參數(shù) return HttpUtil.createRequest(Method.DELETE, delFileUrl) .form(getCommonBodyMap(needMap,"del file!")) //構(gòu)建請求表單 .timeout(TIMEOUT) .execute().body(); } /** * 獲取倉庫具體路徑下的內(nèi)容,主要是獲取 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(); } }
文件上傳接口?
? 文件上傳的過程為前端傳入文件對象和相關(guān)信息然后我們向gitee發(fā)起請求將文件傳到倉庫,上傳成功之后在將返回的下載地址和相關(guān)信息存入數(shù)據(jù)庫當(dāng)中。
中間工具類
? 這里我們先來編寫一個(gè)中間工具類來發(fā)起請求和拿到結(jié)果,這個(gè)代碼也寫在服務(wù)當(dāng)中的話會(huì)顯得代碼太長太復(fù)雜所以我單獨(dú)拿出來寫。
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()請求已來臨..."); //根據(jù)文件名生成指定的請求url String originalFilename = multipartFile.getOriginalFilename(); if (originalFilename == null) { log.info("服務(wù)器接收文件失??!"); } //Gitee請求:發(fā)送上傳文件請求 String JSONResult = GiteeImgBedUtil.uploadFile(folder, originalFilename, multipartFile.getBytes()); //解析響應(yīng)JSON字符串 //上傳txt文件時(shí)會(huì)出問題,沒解決 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拿到文件和信息后調(diào)用中間工具類上傳文件,然后從請求頭中拿到用戶id,從返回對象中拿到文件信息存入File類當(dāng)中然后使用mybatisplus將對象存入數(shù)據(jù)庫就行。
@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比較簡單,設(shè)置一個(gè)儲存時(shí)間調(diào)用mapper存入就行
/** * 上傳文件 * * @param file 用于向數(shù)據(jù)庫儲存的文件對象 */ @Override public void fileUpload(File file) { file.setCreateTime(new Date()); fileMapper.insert(file); }
接口測試
? 我們隨便上傳一個(gè)文件
? ?查看gitee和數(shù)據(jù)庫是否有相應(yīng)結(jié)果
?
? ?這里id為2是因?yàn)橹皠h除了一條數(shù)據(jù),想要從頭開始截?cái)啾砭托小?/p>
文件下載接口
? 文件下載最初比較困擾我,有兩種方式,第一種是直接將文件對象寫入到用戶提供的本地文件夾地址,第二種是將文件下載到瀏覽器。最終選擇了第二種方式,因?yàn)橛脩舨挥迷O(shè)置下載地址而且在瀏覽器下載可以看到下載過程,整個(gè)流程是利用下載地址將文件轉(zhuǎn)換為文件流寫入字節(jié)數(shù)組,在利用瀏覽器通過下載的方式寫出字節(jié)數(shù)組到輸出流。
controller
? 使用文件id先從數(shù)據(jù)庫查到相應(yīng)地址然后利用地址完成后續(xù)操作。
/** * 文件下載接口 * @param fileId 文件id * @param response http響應(yīng)對象 * @return 下載結(jié)果 */ @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(); // 從文件流讀取字節(jié)到字節(jié)數(shù)組中 while (bis.read(bytes) != -1) { // 重置 response response.reset(); // 設(shè)置 response 的下載響應(yīng)頭 response.setContentType("application/x-download"); response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode(FileUtil.getFileName(downloadUrl), "UTF-8")); // 注意,這里要設(shè)置文件名的編碼,否則中文的文件名下載后不顯示 // 寫出字節(jié)數(shù)組到輸出流 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); } }
? 這里需要一個(gè)編寫獲取文件名的工具類,不然下載之后瀏覽器不知道文件類型
/** * 獲取url中的文件名(取到最后一個(gè)/后面的就是文件名) * @param url 文件地址 * @return */ public static String getFileName(String url) { if(!url.equals("")){ return url.substring(url.lastIndexOf("/")+1); } else return "文件地址錯(cuò)誤"; }
接口測試?
? 發(fā)送請求完成下載
文件刪除接口
中間工具類
/** * 刪除文件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請求:發(fā)送刪除請求 String JSONResult = GiteeImgBedUtil.deleteFile(path, sha); jsonObj = JSONUtil.parseObj(JSONResult); if (jsonObj.getObj(GiteeConstant.RESULT_BODY_COMMIT) == null) { log.info("刪除文件失?。?quot;); } log.info("文件路徑為:" + path + " 刪除成功!"); }
service
? 獲取文件地址并刪除數(shù)據(jù)庫數(shù)據(jù)
/** * 刪除文件 * @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); } }
接口測試
發(fā)送請求刪除文件
?查看數(shù)據(jù)庫和gitee倉庫
? ?自此完成了文件的上傳和下載功能,大家有需要的可以參考。
(學(xué)習(xí)視頻分享:編程基礎(chǔ)視頻)