1. 前言
簡要地記錄下 SpringBoot 與 Vue 實現檔案的上傳與下載
2. 簡單案例2.1 功能需求前臺使用 ElementUI 的 Upload 元件或者是 Axios,後臺使用 SpringBoot 來實現檔案的上傳與下載
2.2 開發環境IDEA-2019.1SpringBoot-2.0.2.RELEASEMaven-3.5.3HBuilderX2.3 編寫程式碼2.3.1 上傳、下載2.3.1.1 前端使用 ElementUI 的 Upload 元件我這裡在 html 頁面引入:
上傳:可以上傳多個檔案,最多5個,但每次只能上傳一個檔案,需要上傳多次。然後,檔案大小不能超過 10 Kb(handleBeforeUpload()方法中有校驗)。否則,上傳失敗。
下載:前端的下載就是通過連結訪問後臺地址即可。這裡的 fileName 是作為一個測試的下載檔案,你可以換成其他的檔案,只要本地磁碟存在這個檔案就行(重點看後臺程式碼邏輯,在下面呢)。
2.3.1.2 後端後臺是一個父子關係的多模組專案。不太熟悉的話,可以參考此博文:Maven 多模組專案的建立與配置
專案結構圖
父 POM 檔案
spring: servlet: multipart: enabled: true max-file-size: 10MB # 單個檔案上傳的最大上限 max-request-size: 10MB # 一次請求總大小上限
Controller 層
@RestController@RequestMapping("/file")@CrossOrigin // 跨域public class FileController { @Autowired private FileService fileService; // 檔案上傳 @RequestMapping("/upload") public ResultVo<String> uploadFile(@RequestParam("file") MultipartFile file) { return fileService.uploadFile(file); } // 檔案下載 @RequestMapping("/download") public ResultVo<String> downloadFile(@RequestParam("fileName") String fileName, final HttpServletResponse response) { return fileService.downloadFile(fileName, response); }}
Service 層這裡只貼出實現類的程式碼了
@Slf4j // 日誌註解@Servicepublic class FileServiceImpl implements FileService { @Override public ResultVo<String> uploadFile(MultipartFile file) { if (file.isEmpty()) { log.error("上傳的檔案為空"); throw new ParameterValidateExcepiton(ResultCodeEnum.PARAMETER_ERROR.getCode(), "上傳的檔案為空"); } try { FileUtil.uploadFile(file); } catch (IOException e) { log.error("檔案{}上傳失敗", file); return ResultUtil.error("上傳失敗"); } log.info("檔案上傳成功"); return ResultUtil.success("上傳成功!"); } @Override public ResultVo<String> downloadFile(String fileName, HttpServletResponse response) { if (fileName.isEmpty()) { log.error("檔名為空"); throw new ParameterValidateExcepiton(ResultCodeEnum.PARAMETER_ERROR.getCode(), "檔名為空"); } return FileUtil.downloadFile(fileName, response); }}
FileUtil檔案工具類
@Slf4jpublic class FileUtil { // 檔案上傳路徑 private static final String FILE_UPLOAD_PATH = "upload" + File.separator; // 檔案下載路徑 private static final String FILE_DOWNLOAD_PATH = "download" + File.separator; // 日期路徑 private static final String DATE_PATH = DateUtil.getNowStr() + File.separator; // 根路徑 private static final String ROOT_PATH = "E:" + File.separator; // 下劃線 private static final String UNDER_LINE = "_"; // 預設字符集 private static final String DEFAULT_CHARSET = "utf-8"; // 上傳檔案 public static String uploadFile(MultipartFile file) throws IOException{ // 獲取上傳的檔名稱(包含字尾名) String oldFileName = file.getOriginalFilename(); // 獲取檔案字尾名,將小數點“.” 進行轉譯 String[] split = oldFileName.split("\\\\."); // 檔名 String fileName = null; StringBuilder builder = new StringBuilder(); if (split.length > 0) { String suffix = split[split.length - 1]; for (int i = 0; i < split.length -1; i++) { builder.append(split[i]).append(UNDER_LINE); } // 防止檔名重複 fileName = builder.append(System.nanoTime()).append(".").append(suffix).toString(); } else { fileName = builder.append(oldFileName).append(UNDER_LINE).append(System.nanoTime()).toString(); } // 上傳檔案的儲存路徑 String filePath = ROOT_PATH + FILE_UPLOAD_PATH + DATE_PATH; // 生成資料夾 mkdirs(filePath); // 檔案全路徑 String fileFullPath = filePath + fileName; log.info("上傳的檔案:" + file.getName() + "," + file.getContentType() + ",儲存的路徑為:" + fileFullPath); // 轉存檔案 Streams.copy(file.getInputStream(), new FileOutputStream(fileFullPath), true); //file.transferTo(new File(fileFullPath)); //Path path = Paths.get(fileFullPath); //Files.write(path,file.getBytes()); return fileFullPath; } // 根據檔名下載檔案 public static ResultVo<String> downloadFile(String fileName, HttpServletResponse response) { InputStream in = null; OutputStream out = null; try { // 獲取輸出流 out = response.getOutputStream(); setResponse(fileName, response); String downloadPath = new StringBuilder().append(ROOT_PATH).append(FILE_DOWNLOAD_PATH).append(fileName).toString(); File file = new File(downloadPath); if (!file.exists()) { log.error("下載附件失敗,請檢查檔案" + downloadPath + "是否存在"); return ResultUtil.error("下載附件失敗,請檢查檔案" + downloadPath + "是否存在"); } // 獲取輸入流 in = new FileInputStream(file); if (null == in) { log.error("下載附件失敗,請檢查檔案" + fileName + "是否存在"); throw new FileNotFoundException("下載附件失敗,請檢查檔案" + fileName + "是否存在"); } // 複製 IOUtils.copy(in, response.getOutputStream()); response.getOutputStream().flush(); try { close(in, out); } catch (IOException e) { log.error("關閉流失敗"); return ResultUtil.error(ResultCodeEnum.CLOSE_FAILD.getCode(), "關閉流失敗"); } } catch (IOException e) { log.error("響應物件response獲取輸出流錯誤"); return ResultUtil.error("響應物件response獲取輸出流錯誤"); } return ResultUtil.success("檔案下載成功"); } // 設定響應頭 public static void setResponse(String fileName, HttpServletResponse response) { // 清空輸出流 response.reset(); response.setContentType("application/x-download;charset=GBK"); try { response.setHeader("Content-Disposition", "attachment;filename=" + new String(fileName.getBytes(DEFAULT_CHARSET), "iso-8859-1")); } catch (UnsupportedEncodingException e) { log.error("檔名{}不支援轉換為字符集{}", fileName, DEFAULT_CHARSET); } } // 關閉流 public static void close(InputStream in, OutputStream out) throws IOException{ if (null != in) { in.close(); } if (null != out) { out.close(); } } // 根據目錄路徑生成資料夾 public static void mkdirs(String path) { File file = new File(path); if(!file.exists() || !file.isDirectory()) { file.mkdirs(); } }}
DateUtil日期工具類
public class DateUtil { // 預設日期字串格式 "yyyy-MM-dd" public final static String DATE_DEFAULT = "yyyy-MM-dd"; // 日期字串格式 "yyyyMMdd" public final static String DATE_YYYYMMDD = "yyyyMMdd"; // 格式 map private static Map<String, SimpleDateFormat> formatMap; // 通過格式獲取 SimpleDateFormat 物件 private static SimpleDateFormat getFormat(String pattern) { if (formatMap == null) { formatMap = new HashMap<>(); } SimpleDateFormat format = formatMap.get(pattern); if (format == null) { format = new SimpleDateFormat(pattern); formatMap.put(pattern, format); } return format; } // 將當前時間轉換為字串 public static String getNowStr() { return LocalDate.now().format(DateTimeFormatter.ofPattern(DATE_YYYYMMDD)); }}
ResultVo統一介面的返回值
@Datapublic class ResultVo<T> { // 錯誤碼. private Integer code; // 提示資訊. private String msg; // 具體的內容. private T data;}
ResultUtil返回介面的工具類
public class ResultUtil { public static ResultVo success() { return success(null); } public static ResultVo success(Object object) { ResultVo result = new ResultVo(); result.setCode(ResultCodeEnum.SUCCESS.getCode()); result.setMsg("成功"); result.setData(object); return result; } public static ResultVo success(Integer code, Object object) { return success(code, null, object); } public static ResultVo success(Integer code, String msg, Object object) { ResultVo result = new ResultVo(); result.setCode(code); result.setMsg(msg); result.setData(object); return result; } public static ResultVo error( String msg) { ResultVo result = new ResultVo(); result.setCode(ResultCodeEnum.ERROR.getCode()); result.setMsg(msg); return result; } public static ResultVo error(Integer code, String msg) { ResultVo result = new ResultVo(); result.setCode(code); result.setMsg(msg); return result; }}
ParameterValidateExcepiton自定義異常
@Getterpublic class ParameterValidateExcepiton extends RuntimeException { // 錯誤碼 private Integer code; // 錯誤訊息 private String msg; public ParameterValidateExcepiton() { this(ResultCodeEnum.PARAMETER_ERROR.getCode(), ResultCodeEnum.PARAMETER_ERROR.getMessage()); } public ParameterValidateExcepiton(String msg) { this(ResultCodeEnum.PARAMETER_ERROR.getCode(), msg); } public ParameterValidateExcepiton(Integer code, String msg) { super(msg); this.code = code; this.msg = msg; }}
ExceptionControllerAdvice統一異常處理類
@RestControllerAdvicepublic class ExceptionControllerAdvice { // 處理檔案為空的異常 @ExceptionHandler(ParameterValidateExcepiton.class) public ResultVo<String> fileExceptionHandler(ParameterValidateExcepiton excepiton) { return ResultUtil.error(ResultCodeEnum.PARAMETER_ERROR.getCode(), excepiton.getMsg()); } // 檔案不存在異常 @ExceptionHandler(FileNotFoundException.class) public ResultVo<String> fileNotFoundExceptionHandler(FileNotFoundException exception) { return ResultUtil.error(ResultCodeEnum.FILE_NOT_EXIST.getCode(), exception.getMessage()); }}
ResultCodeEnum統一響應碼
@Getterpublic enum ResultCodeEnum { SUCCESS(200, "成功") , ERROR(301, "錯誤") , UNKNOWERROR(302, "未知錯誤") , PARAMETER_ERROR(303, "引數錯誤") , FILE_NOT_EXIST(304, "檔案不存在") , CLOSE_FAILD(305, "關閉流失敗") ; private Integer code; private String message; ResultCodeEnum(Integer code, String message) { this.code = code; this.message = message; }}
總體上看,程式碼量有點大哈(主要是程式碼寫得比較優雅),各位就將就點吧。本來是想著上傳到 github 上面,但公司電腦用的是內網,無法訪問到網路。即使配置了代理,但 IDEA 也無法連線到 github 上面。但又不想殘忍地只貼出部分程式碼(以免部分讀者很迷惑),所以,這裡就全貼出來了哈。
3. 使用 Axios看看上面的前端上傳程式碼,使用 Upload 元件自動地上傳到後臺,無法接收後臺介面傳過來的值。這就會導致一個問題:如果上傳過程中,遇見什麼錯誤,導致上傳失敗,那麼就需要提示給使用者了。但這種做法是無法實現的。看了多篇部落格,大多是使用了 http-request() 方法。在這個方法裡,通過 axios 請求後臺,並能獲取後臺的返回值。
3.1 前臺前臺程式碼
3.2 後臺程式碼後臺編碼不變
前後端專案啟動,發現依舊能互動哈。
總結以上就是今天要講的內容,本文僅僅簡單地介紹了使用 SpringBoot 和 Vue 實現檔案的上傳與下載。主要考慮到公司中有使用到 Vue 中的 Upload 元件上傳檔案,所以,自己也就接觸了下 它。奈何自己對 Vue 的造詣不深,使用 axios 進行檔案上傳的方法也就找到了那一個,但我總感覺不是很理想,如果,有讀者有更好的想法可以分享一下。
本文連結:
http://blog.csdn.net/Lucky_Boy_Luck/article/details/108501419