龙空技术网

SpringBoot + Vue 实现文件的上传与下载

佳云分享 169

前言:

现时兄弟们对“java上传文件的进度条”大约比较讲究,各位老铁们都想要了解一些“java上传文件的进度条”的相关资讯。那么小编同时在网上搜集了一些关于“java上传文件的进度条””的相关知识,希望我们能喜欢,你们一起来了解一下吧!

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 页面引入:

<!DOCTYPE html><html> <head>  <meta charset="utf-8" />  <title></title>  <script src="./js/vue.min.js"></script>  <link rel="stylesheet" href="./css/index.css">  <script src="./js/index.js"></script> </head> <body>  <div id="app">   <div style="top:100px;width:300px">    <el-form ref="upload" :model="form" label-width="120px">     <el-form-item label="请输入文件名" required>      <el-input v-model="form.fileName" auto-complete="off" class="el-col-width"></el-input>     </el-form-item>     <el-form-item>      <el-button size="small" type="primary" @click="handleDownLoad">下载</el-button>     </el-form-item>     <el-form-item>      <el-upload class="upload-demo" :action="uploadUrl" :before-upload="handleBeforeUpload" :on-error="handleUploadError"        multiple :limit="5" :on-exceed="handleExceed" :file-list="fileList" :on-success="onSuccess">       <el-button size="small" type="primary">点击上传</el-button>       <div slot="tip" class="el-upload__tip">不超过10Kb</div>      </el-upload>     </el-form-item>    </el-form>   </div>  </div> </body> <script type="application/javascript">  var app = new Vue({   el: '#app',   data: {    form: {     fileName: 'test.txt'    },    // 后台请求url    uploadUrl: ';,    fileList: [],    isUpload: false   },   methods: {    handleExceed(files, fileList) {     this.$message.warning(`当前限制选择 5 个文件,本次选择了 ${files.length} 个文件,共选择了 ${files.length + fileList.length} 个文件`);    },    handleUploadError(error, file) {     this.$notify.error({      title: 'error',      message: '上传出错:' + error,      type: 'error',      position: 'bottom-right'     })    },    handleBeforeUpload(file) {     this.isUpload = file.size / (1024 * 10) <= 1 ? '1' : '0'     if (this.isUpload === '0') {      this.$message({       message: '上传文件大小不能超过10k',       type: 'error'      })     }     return this.isUpload === '1' ? true : false    },    onSuccess() {     this.$message.success(`上传成功!`)    },    handleDownLoad() {     window.location.href = `` + this.form.fileName    }   }  }) </script></html>
uploadUrl:data 中的属性。指的是上传到后台的地址主要是 el-upload 标签,当点击“点击上传”按钮时,选择文件后,会自动提交(auto-upload)到后台,auto-upload 属性默认为 true,可修改。

前台页面:

上传:可以上传多个文件,最多5个,但每次只能上传一个文件,需要上传多次。然后,文件大小不能超过 10 Kb(handleBeforeUpload()方法中有校验)。否则,上传失败。

下载:前端的下载就是通过链接访问后台地址即可。这里的 fileName 是作为一个测试的下载文件,你可以换成其他的文件,只要本地磁盘存在这个文件就行(重点看后台代码逻辑,在下面呢)。

2.3.1.2 后端

后台是一个父子关系的多模块项目。不太熟悉的话,可以参考此博文:

Maven 多模块项目的创建与配置

项目结构图

父 POM 文件

<?xml version="1.0" encoding="UTF-8"?>...  <parent>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-parent</artifactId>    <version>2.0.2.RELEASE</version>  </parent>  <properties>    <!-- 在properties中统一控制依赖包的版本,更清晰-->    <lombok.version>1.18.8</lombok.version>    <junit.test.version>4.11</junit.test.version>  </properties>  <dependencyManagement>    <dependencies>      <dependency>        <groupId>org.projectlombok</groupId>        <artifactId>lombok</artifactId>        <version>${lombok.version}</version>      </dependency>      <dependency>        <groupId>junit</groupId>        <artifactId>junit</artifactId>        <version>${junit.test.version}</version>        <scope>test</scope>      </dependency>    </dependencies>  </dependencyManagement></project>

sb_vue(此项目) POM 文件

<dependencies>    <dependency>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-web</artifactId>    </dependency>    <dependency>        <groupId>org.projectlombok</groupId>        <artifactId>lombok</artifactId>    </dependency>    <!--父模块pom中使用dependencyManagement来管理依赖版本号,子模块pom中不需要再写版本号-->    <dependency>        <groupId>junit</groupId>        <artifactId>junit</artifactId>        <scope>test</scope>    </dependency></dependencies>

application.yml

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 前台

前台代码

<!DOCTYPE html><html> <head>  <meta charset="utf-8" />  <title></title>  <script src="./js/vue.min.js"></script>  <!-- 引入样式 -->  <link rel="stylesheet" href="./css/index.css">  <!-- 引入组件库 -->  <script src="./js/index.js"></script>  <!-- 引入axios -->  <script src="js/axios.min.js"></script>  <style>   .app {    margin-left: 200px;    margin-top: 200px;   }  </style> </head> <body>  <div id="app" class="app">   <el-upload     class="upload-demo"     ref="upload"     action=""     :http-request="submitUpload"     :before-upload="beaforeUpload"    :limit="1"    :on-exceed="onExceed"    :auto-upload="true">    <el-button slot="trigger" size="small" type="primary">选取文件</el-button>    <div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过500kb</div>   </el-upload>  </div>  </body> <script type="application/javascript">  var app = new Vue({   el: '#app',   data: {       },   methods: {    onExceed(files, fileList) {     this.$message.warning(`当前限制选择 1 个文件,本次选择了 ${files.length} 个文件,共选择了 ${files.length + fileList.length} 个文件`);    },        // 上传文件    submitUpload(content) {     console.log(content)     let file = content.file;     if (file != null && file != '') {      let isJPG = file.type === 'image/jpeg'      let isPNG = file.type === 'image/png'      let isLt2M = file.size / 1024 / 1024 < 0.5      if (!isPNG && !isJPG) {       this.$message.error('上传图片只能是 JPG/PNG 格式!')       return false      } else if (!isLt2M) {       this.$message.error('上传图片大小不能超过 200kb!')       return false      } else if (isLt2M && (isPNG || isJPG)) {       let data = new FormData();       data.append("file", file)       //console.log(data.get('file'))       let url = ';       let headers = {         'Content-Type': 'multipart/form-data'        }       /* axios({        method: 'post',        url: ';,        data: data,        headers: {         'Content-Type': 'multipart/form-data'        } */              axios.post(url, data, headers)       .then(res => {        if (res.data.code === 200) {         this.$message({          type: 'success',          message: res.data.msg         })        } else {         this.$message({          type: 'warning',          message: res.data.msg         })        }       }).catch(error => {        this.$message({         type: 'error',         message: error        })       })      }     }    }   }  }) </script></html>

在 submitUpload() 方法中,先对上传的文件进行校验,只有校验通过的文件才能去请求后台。

3.2 后台代码

后台编码不变

前后端项目启动,发现依旧能交互哈。

总结

以上就是今天要讲的内容,本文仅仅简单地介绍了使用 SpringBoot 和 Vue 实现文件的上传与下载。主要考虑到公司中有使用到 Vue 中的 Upload 组件上传文件,所以,自己也就接触了下 它。奈何自己对 Vue 的造诣不深,使用 axios 进行文件上传的方法也就找到了那一个,但我总感觉不是很理想,如果,有读者有更好的想法可以分享一下。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

本文链接:

标签: #java上传文件的进度条 #netinputfile上传文件 #如何用axios发送数据给后端 #vue3中文文档下载 #vue实现文件上传到服务器