SpringBoot Vue 批量上传附件与打包压缩下载附件

前言:

在传统的管理系统项目一般都遇到有附件的上传与下载业务,我最近参与的项目中用到了附件的上传与下载功能,今天正好有空整理一下。

业务逻辑:

附件先上传到临时目录,业务页面点击保存后临时目录附件移动到正式目录下,并将业务数据和附件相关信息保存入库。

废话不多说,直接上代码!!!

  1. 批量上传附件

前端 vue 代码

前端 使用 Element-UI 的上传控件

<el-card>
            <h1>1.上傳附件</h1>
            <el-upload
                    class="upload-demo"
                    drag
                    :headers="headers"
                    name="files"
                    :on-remove="remove"
                    :on-success="success"
                    :auto-upload="true"
                    action="api/basic/file/uploadFile"
                    multiple>
                <i class="el-icon-upload"></i>
                <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
                <div class="el-upload__tip" slot="tip">只能上传doc docx文件,且不超10MB</div>
            </el-upload>
</el-card>

上图中   :on-success="success"  代表附件上传成功后的钩子

接收后端返回的临时目录

  //文件上传成功时的钩子
  success(res, file, fileList) {
    this.fileList.length = 0
    for (let i = 0; i < fileList.length; i++) {
      if(fileList[i].response)
        this.fileList.push(fileList[i].response.list[0])
    }
    console.log(`已上传文件列表`, this.fileList)
  },

后端 接口

配置文件的上传路径(前缀) 在application-dev.yml 中 ,项目打包发布到对应的服务,附件上传后的位置就是配置的路径

获取application-dev.yml配置路径

/**
 *@Description 文件上传处理方法 临时目录
 *@Param MultipartFile file
 *@Author
 *@Date  2020/4/22  18:24
 *@return R
 */
@PostMapping("/uploadFile")
public R uploadFile(@RequestParam(required = false, value = "files") MultipartFile[] multipartFiles) throws IOException {
    List<AccessoryQueryExt> list = new ArrayList<AccessoryQueryExt>();
    if(multipartFiles.length > 0){
        for (MultipartFile file : multipartFiles){
            R r = fileService.uploadFile(file,filePath);
            String path = (String) r.get("path");
            String fileName = (String) r.get("fileName");
            AccessoryQueryExt accessoryQueryExt = new AccessoryQueryExt();
            accessoryQueryExt.setName(fileName);
            accessoryQueryExt.setTemporaryPath(path);
            list.add(accessoryQueryExt);
        }
        return R.success().put("list",list);
    }
    return R.fail();
}

上传的实现类

@Transactional(rollbackFor = Exception.class)
@Override
public R uploadFile(MultipartFile file,String filePath) throws IOException{
    // 判断文件是否为空
    if(file.isEmpty()){
        return R.fail().setMessage("file is empty");
    }
    String fileName = file.getOriginalFilename();
    // 获取文件名后缀
    String suffixName = fileName.substring(fileName.lastIndexOf("."));
    // 附件重命名 = 生成32位随机码 + 源文件后缀
    String uuid = UUID.randomUUID().toString().trim().replaceAll("-", "");
    String newFileName = uuid.concat(suffixName);
    // 设置文件临时存储路径
    String path = "Temporary".concat("/").concat(newFileName);
    // 全路径
    String fullPath = filePath.concat(path);
    File dest = new File(fullPath);
    // 判断目录是否存在
    if(!dest.getParentFile().exists()){
        dest.getParentFile().mkdirs();
    }
    // 写入文件
    file.transferTo(dest);
    // 将临时目录和原附件名称返回
    return R.success().put("path",path).put("fileName",fileName);
}

2.  移动附件至正式目录

前端 vue

        <el-card>
            <h1>2.保存附件</h1>
            <el-button @click="save()">保存表單</el-button>
        </el-card>
    save(){
        console.log(this.fileList)
        for (let i = 0; i < this.fileList.length; i++){
          this.fileList[i].isDelete = false // 此处为业务逻辑参数
          this.fileList[i].relaId = "1" // 此处为业务逻辑参数
        }

        http
          .post('basic/file/saveFile', this.fileList)
          .then(function (res) {
           console.log(res)
          })
          .catch(function (err) {
            console.log(err);
          });
      }

后端接口及实现

 /**
 *@Description  保存附件至正式目录
 *@Param  accessoryQueryExt 附件接收参数实体
 *@Author 
 *@Date  2020/5/25  11:50
 *@return  R
 */
@PostMapping("/saveFile")
public R saveFile(@RequestBody List<AccessoryQueryExt> accessoryQueryExts){
    for (AccessoryQueryExt accessoryQueryExt : accessoryQueryExts){
        fileService.saveFile(accessoryQueryExt, filePath);
    }
    return R.success();
}
@Override
public R saveFile(AccessoryQueryExt accessoryQueryExt, String filePath) {
    if(accessoryQueryExt.getIsDelete()){
        // 删除附件表中对应的数据并删除附件
        QueryWrapper<Accessory> wrapper = new QueryWrapper<Accessory>();
        wrapper.eq("RELA_ID", accessoryQueryExt.getRelaId());
        List<Accessory> accessories = this.baseMapper.selectList(wrapper);
        if(accessories != null && accessories.size() > 0){
            for (Accessory accessory : accessories){
                // 删除附件
                new File(filePath.concat(accessory.getPath())).delete();
                this.baseMapper.deleteById(accessory.getAccessoryId());
            }
        }
    }
    // 获取附件临时目录
    String temporaryPath = accessoryQueryExt.getTemporaryPath();
    if(StringUtils.isEmpty(temporaryPath)){
        throw new RuntimeException("附件临时目录为空");
    }

    String formatDate = new SimpleDateFormat("yyyyMM").format(new Date());
    // 附件正式目录 /data/uploadFile/业务类型码/yyyymm  业务类型码 从字典表中获取
    String businessTypeName = "common";
    if(StringUtils.isNotBlank(accessoryQueryExt.getBusinessTypeName())){
        businessTypeName = accessoryQueryExt.getBusinessTypeName();
    }
    String savePath =  businessTypeName.concat("/").concat(formatDate).concat("/").concat(temporaryPath.substring(10));
    String formalPath = filePath.concat(savePath);
    File dest = new File(formalPath);
    // 判断目录是否存在
    if(!dest.getParentFile().exists()){
        dest.getParentFile().mkdirs();
    }
    File fileOld = new File(filePath.concat(temporaryPath));
    String sizeUnit = getPrintSize(fileOld.length());
    String[] split = sizeUnit.split(":");
    if(fileOld.renameTo(dest)){
        // 将文件上传信息写入数据库
        Accessory accessory = new Accessory();
        // 关联ID
        accessory.setRelaId(new BigDecimal(accessoryQueryExt.getRelaId()));
        String perfix = accessoryQueryExt.getName().substring(accessoryQueryExt.getName().lastIndexOf(".") + 1);
        if(FileTypeUtil.fileType(perfix)){
            // 文件类型  01 文档 02 图片 03 视频
            accessory.setFileType("01");
        } else if(FileTypeUtil.imageType(perfix)){
            accessory.setFileType("02");
        } else if(FileTypeUtil.videoType(perfix)){
            accessory.setFileType("03");
        }

        if(filePath.indexOf(":") != -1){
            //文件存储类型 01 本地 02 远程
            accessory.setFileStoreType("01");
        } else {
            accessory.setFileStoreType("02");
        }
        //文件名
        accessory.setName(accessoryQueryExt.getName());
        accessory.setPath(savePath);
        // 附件大小
        accessory.setSize(new BigDecimal(split[0]));
        // 附件单位
        accessory.setUnit(split[1]);
        this.baseMapper.insert(accessory);
        return R.success();
    }
    return R.fail().setMessage("移动附件失败");
}


/**
 * 根据文件大小转换为B、KB、MB、GB单位字符串显示
 * @param fileSize 文件的大小(long型)
 * @return 返回 转换后带有单位的字符串
 */
public static String getPrintSize(long fileSize){
    String strFileSize = null;
    if(fileSize < 1024){
        strFileSize = fileSize + ":" +"B";
        return strFileSize;
    }
    DecimalFormat df = new DecimalFormat("######0.00");
    if ((fileSize >= 1024) && (fileSize < 1024*1024)){ //KB
        strFileSize = df.format(((double)fileSize)/1024) + ":" +"KB";
    }else if((fileSize >= 1024*1024)&&(fileSize < 1024*1024*1024)){ //MB
        strFileSize = df.format(((double)fileSize)/(1024*1024)) + ":" +"MB";
    }else{ //GB
        strFileSize = df.format(((double)fileSize)/(1024*1024*1024)) + ":" +"GB";
    }
    return strFileSize;
}
  1. 批量下载并压缩

前端 vue

    <el-card>
        <h1>3.下載附件</h1>
        <el-button @click="download()">下载附件</el-button>
    </el-card>
download:function(){
  http
    ({
      method: 'post',
      url: 'basic/file/downloadFile?relaId=1',
      data: {}, //可选参数,后台用 @RequestBody接收
      responseType: 'blob'
    })
    .then(function(res)
    {
      let data= res.data
      var blob = new Blob([data])
      // 创建下载的链接
      var a = document.createElement('a');
      a.style.display = 'none';
      var href = window.URL.createObjectURL(blob);
      a.href = href;
      //显示下载文件名 获取后端返回的文件名
      let filename= res.headers['content-disposition'].split('=')[1]
      // 文件名中有中文 则对文件名进行转码 decodeURI
      a.download= decodeURI(filename)   
      // 利用a标签做下载       
      document.body.appendChild(a);
      // 点击下载
      a.click();
      // 下载后移除元素
      document.body.removeChild(a);
      // 释放掉blob对象
      window.URL.revokeObjectURL(href);
    })
    .catch(function (error) 
    { 
      console.info('response.error'+error.message)
    });


}

后端 接口

/**
 *@Description  文件下载处理方法
 *@Param relaId 关联ID
 *@Author 
 *@Date  2020/4/23  13:47
 *@return R
 */
@GetMapping("/downloadFile")
public void downloadFile(HttpServletResponse response,@RequestParam("relaId") String relaId){
    fileService.downloadFile(relaId,response,filePath);
}
@Override
public void downloadFile(String  relaId, HttpServletResponse response, String filePath)     
{
    // 从数据库获取文件信息
    QueryWrapper<Accessory> queryWrapper = new QueryWrapper<>();
    queryWrapper.eq("RELA_ID",relaId);
    List<Accessory> accessoryList = this.baseMapper.selectList(queryWrapper);
    if(accessoryList != null && accessoryList.size() > 1){
        // 压缩包名称
        String fileName = String.valueOf(System.currentTimeMillis());
        batchDownload(accessoryList,fileName, response, filePath);
    }
    // 若只有一个文件,则不用压缩
    if(accessoryList != null && accessoryList.size() == 1){
        Accessory accessory = accessoryList.get(0);
        download(accessory,filePath,response);
    }

}


/**
 * 批量下载并压缩
 */
public void batchDownload(List<Accessory> accessoryList,String filename, HttpServletResponse response, String filePath) {
    //创建zip文件并返回zip文件路径
    String zipPath = createZipAndReturnPath(accessoryList, filename, filePath);
    try {
        response.reset();
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/zip;charset=utf-8");
        response.setHeader("content-disposition", "attachment;filename="+filename+".zip");
        // 返回文件名,需要设置下面代码
        response.setHeader("Access-Control-Expose-Headers", "content-disposition,content-length");
        //开始下载
        BufferedInputStream is = new BufferedInputStream(new FileInputStream(new File(zipPath)));
        BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
        byte[] buff = new byte[1024];
        int len = 0;
        while ((len = is.read(buff, 0, buff.length)) != -1) {
            out.write(buff, 0, len);
        }
        out.close();
        out.flush();
        is.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
}


/**
 * 批量打包
 * @param filePath 根目录
 * @param filename 压缩包名称
 * @return zip文件保存绝对路径
 */
public String createZipAndReturnPath(List<Accessory> accessoryList,String filename, String filePath) {
    try {
        //生成打包下载后的zip文件:文件名.zip
        String papersZipName = filename+".zip";
        //zip文件保存路径
        String zipPath = filePath + papersZipName;
        ZipOutputStream out = new ZipOutputStream(new FileOutputStream(zipPath));
        for (Accessory accessory : accessoryList){
            //获得文件相对路径
            String relativePath = accessory.getPath();
            //获得文件名
            String fileName = accessory.getName();
            //获得下载文件完整路径
            String downloadPath = filePath + relativePath;
            FileInputStream fis = new FileInputStream(downloadPath);
            out.putNextEntry(new ZipEntry(fileName));
            //写入压缩包
            int len;
            byte[] buffer = new byte[1024];
            while ((len = fis.read(buffer)) > 0) {
                out.write(buffer, 0, len);
            }
            out.closeEntry();
            fis.close();
        }
        out.close();
        out.flush();
        return zipPath;
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}


/**
 * 下载
 */
public void download(Accessory accessory, String filePath, HttpServletResponse response){
    String fileName  = accessory.getName();
    String path = accessory.getPath();
    File file = new File(filePath.concat(path));
    FileInputStream fis = null;
    BufferedInputStream bis = null;
    try {
        String extension = ext(path);
        //设置response内容的类型
        response = setContextType(extension, response);
        // 设置文件名
        response.addHeader("content-disposition", "attachment;fileName=" + URLEncoder.encode(fileName,"UTF-8" ));
        response.setHeader("content-length", Long.toString(file.length()));
        // 返回文件名,需要设置下面代码
        response.setHeader("Access-Control-Expose-Headers", "content-disposition,content-length");
        byte[] buffer = new byte[1024];
        fis = new FileInputStream(file);
        bis = new BufferedInputStream(fis);
        OutputStream os = response.getOutputStream();
        int i = bis.read(buffer);
        while (i != -1) {
            os.write(buffer, 0, i);
            i = bis.read(buffer);
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        if (bis != null) {
            try {
                bis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if (fis != null) {
            try {
                fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}


/**
 * 设置response内容的类型
 */
public static HttpServletResponse setContextType(String extension, HttpServletResponse response){
    if("JPG".equalsIgnoreCase(extension)){
        response.setContentType("application/x-jpg");
    } else if("GIF".equalsIgnoreCase(extension)){
        response.setContentType("image/gif");
    } else if("mp4".equalsIgnoreCase(extension)){
        response.setContentType("video/mpeg4");
    } else if("avi".equalsIgnoreCase(extension)){
        response.setContentType("video/avi");
    } else if("WMV".equalsIgnoreCase(extension)){
        response.setContentType("video/x-ms-wmv");
    } else if("txt".equalsIgnoreCase(extension)){
        response.setContentType("text/plain");
    } else if("doc".equalsIgnoreCase(extension)){
        response.setContentType("application/msword");
    } else if("pdf".equalsIgnoreCase(extension)){
        response.setContentType("application/pdf");
    } else if("xls".equalsIgnoreCase(extension)){
        response.setContentType("application/vnd.ms-excel");
    }
    return response;
}
原文地址:https://www.cnblogs.com/1204it-ly/p/13877407.html