首頁>技術>

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

22

HTML

CSS

最新評論
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • 知乎千贊:位元組跳動整理Android Framework筆記