Ruoyi-vue 多文件上传, 多文件选择打包(zip)下载

本文介绍了一个使用Vue和后端Spring Boot配合的项目,涉及文件上传功能,包括单个文件和多文件上传,以及文件列表展示与批量下载。重点讲解了`FileUploadUtils.upload2`方法在处理文件上传路径和返回文件信息中的关键作用。

需求描述: 多个文件上传,展示已上传文件列表, 对已上传文件列表可以勾选进行多文件下载.
原型:
在这里插入图片描述

1.后端代码

注意:FileUploadUtils.upload2(filePath, file)很关键

@PostMapping("/upload/file")
    @PreAuthorize("@ss.hasPermi('report:upload:file')")
    public AjaxResult uploadFile(@RequestParam("file") MultipartFile file)
    {
        // 获取当前的用户
        LoginUser loginUser = SecurityUtils.getLoginUser();
        try
        {
            // 上传文件路径
            String filePath = RuoYiConfig.getUploadPath();
            // 改造ruoyi基线方法,返回一个map,需要包含现在的本地路径(eg: /home/rms/uploadPath/upload/2022/06/23/中文测试文件_20220623230505A018.docx)
            Map<String, String> pathMap = FileUploadUtils.upload2(filePath, file);

            // 上传并返回新文件名称
            String fileName = pathMap.get("path");
            String url = serverConfig.getUrl() + fileName;

            //保存文件信息
            M06 m06 = new M06();
            m06.setName(file.getOriginalFilename());
            m06.setUrl(url);
            m06.setLocalPath(pathMap.get("localPath"));
            // 返回上传文件的信息
            return AjaxResult.success(m06);
        }
        catch (Exception e)
        {
            return AjaxResult.error(e.getMessage());
        }
    }
 public static final Map<String, String> upload2(String baseDir, MultipartFile file) throws IOException
 {
     try
     {
         return upload2(baseDir, file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION);
     }
     catch (Exception e)
     {
         throw new IOException(e.getMessage(), e);
     }
 }
 
 public static final Map<String, String> upload2(String baseDir, MultipartFile file, String[] allowedExtension)
            throws FileSizeLimitExceededException, IOException, FileNameLengthLimitExceededException,
            InvalidExtensionException
    {
        int fileNamelength = Objects.requireNonNull(file.getOriginalFilename()).length();
        if (fileNamelength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH)
        {
            throw new FileNameLengthLimitExceededException(FileUploadUtils.DEFAULT_FILE_NAME_LENGTH);
    }

    assertAllowed(file, allowedExtension);

    String fileName = extractFilename(file);

    String absPath = getAbsoluteFile(baseDir, fileName).getAbsolutePath();
    file.transferTo(Paths.get(absPath));
    Map<String, String> pathMap = new HashMap<>();
    pathMap.put("path", getPathFileName(baseDir, fileName));
    pathMap.put("localPath", baseDir + "/" + fileName);
    return pathMap;
}
/**
     * 文件打包下载
     * @param response
     * @param files
     */
    @GetMapping ("/zip")
    public void downloadZip(HttpServletResponse response, String files){
        LoginUser loginUser = SecurityUtils.getLoginUser();
        String zipname=loginUser.getUser().getDept().getDeptName();

        String[] filesPsths = Convert.toStrArray(files);

        String fileName = zipname + ".zip";
        response.setContentType("application/zip");
        response.setHeader("content-disposition", "attachment;filename=" + fileName);

        ZipOutputStream zos = null;
        BufferedInputStream bis = null;
        try{
            zos = new ZipOutputStream(response.getOutputStream());
            byte[] buf = new byte[8192];
            int len;
            for (int i = 0; i < filesPsths.length; i++) {
                File file = new File(filesPsths[i]);
                if (!file.isFile()) {
                    continue;
                }
                ZipEntry ze = new ZipEntry(file.getName());
                zos.putNextEntry(ze);
                bis = new BufferedInputStream(new FileInputStream(file));
                while ((len = bis.read(buf)) > 0) {
                    zos.write(buf, 0, len);
                }
                zos.closeEntry();
            }
            zos.closeEntry();
        }catch(Exception ex){
            ex.printStackTrace();
        }finally {
            if(bis != null){
                try{
                    bis.close();
                }catch(Exception e){
                    e.printStackTrace();
                }
            }
            if(zos != null){
                try{
                    zos.close();
                }catch(Exception e){
                    e.printStackTrace();
                }
            }
        }

    }
2. vue代码

注意:this.$download.zip("/report/zip?files=" + files, zipName);方法

<template>
  <div class="app-container">
    <div>
      <el-row :gutter="10" class="mb8">
        <el-col :span="1.5">
          <el-upload
            ref="upload"
            :before-upload="handleBeforeUpload"
            :on-error="handleUploadError"
            :on-success="handleUploadSuccess"
            :show-file-list="false"
            accept=".doc, .docx, .pdf, .xlsx, .xls"
            :action="upload.url"
            :headers="upload.headers"
            :file-list="upload.fileList"
            multiple
          >
            <el-button
              type="success"
              plain
              icon="el-icon-upload"
              size="mini"
            >上传文件</el-button>
          </el-upload>
        </el-col>
        <el-col :span="1.5">
          <div v-cloak>
            <el-button
              type="warning"
              plain
              size="mini"
              @click="downloadZip"
            ><svg-icon icon-class="download2" style="margin-right: 5px;"/>下载文件</el-button>
          </div>
        </el-col>
      </el-row>
      <el-row :gutter="10" class="mb8"  v-if="editEnable">
        <el-col>
          <div class="el-upload__tip">
            请上传
            <template v-if="fileSize"> 大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b> </template>
            <template v-if="fileType"> 格式为 <b style="color: #f56c6c">{{ fileType.join("/") }}</b> </template>
            的文件
          </div>
        </el-col>
      </el-row>
    </div>
    <el-table
      border
      :data="report.reportData"
      @selection-change="handleSelectionChange"
      style="width: 100%">
      <el-table-column
        type="selection"
        width="55">
      </el-table-column>
      <el-table-column
        align="center"
        header-align="center"
        type="index"
        label="序号">
      </el-table-column>
      <el-table-column
        header-align="center"
        prop="name"
        label="文件名称">
        <template slot-scope="scope">
          <el-button type="text" @click="download(scope.row)">{{ scope.row.name }}</el-button>
        </template>
      </el-table-column>
      <el-table-column
        align="center"
        width="100">
        <template slot-scope="scope">
          <el-button type="danger" size="mini" icon="el-icon-delete" plain circle @click="del(scope.$index)"></el-button>
        </template>
      </el-table-column>
    </el-table>
  </div>
</template>

<script>
import { getToken } from "@/utils/auth";
export default {
  name: "M06",
  dicts: [],
  components: { },
  props: {
    report: {
      type: Object
    }
  },
  data() {
    return {
      // 遮罩层
      loading: true,
      reportObject: null,
      fileSize: 5,
      fileType:['doc', 'docx', 'pdf', 'xlsx','xls'],
      fileList: [],
      multipleSelection: [],
      filePaths: [],
      editEnable: true,
      // 用户导入参数
      upload: {
        // 是否显示弹出层(用户导入)
        open: false,
        // 弹出层标题(用户导入)
        title: "",
        // 是否禁用上传
        isUploading: false,
        // 是否更新已经存在的用户数据
        updateSupport: 0,
        // 设置上传的请求头部
        headers: { Authorization: "Bearer " + getToken() },
        // 上传的地址
        url: process.env.VUE_APP_BASE_API + "/report/upload/file"
      },
    };
  },
  computed: {
  },
  created() {
  },
  methods: {
    // 上传前校检格式和大小
    handleBeforeUpload(file) {
      // 校检文件类型
      if (this.fileType) {
        let fileExtension = "";
        if (file.name.lastIndexOf(".") > -1) {
          fileExtension = file.name.slice(file.name.lastIndexOf(".") + 1);
        }
        const isTypeOk = this.fileType.some((type) => {
          if (file.type.indexOf(type) > -1) return true;
          if (fileExtension && fileExtension.indexOf(type) > -1) return true;
          return false;
        });
        if (!isTypeOk) {
          this.$message.error(`文件格式不正确, 请上传${this.fileType.join("/")}格式文件!`);
          return false;
        }
      }
      // 校检文件大小
      if (this.fileSize) {
        const isLt = file.size / 1024 / 1024 < this.fileSize;
        if (!isLt) {
          this.$message.error(`上传文件大小不能超过 ${this.fileSize} MB!`);
          return false;
        }
      }
      return true;
    },
    // 上传成功回调
    handleUploadSuccess(res, file) {
      if(res.code === 500) {
        this.$message.error(res.msg);
      } else {
        console.log(res.data)
        this.report.reportData.push(res.data);
      }
      //this.$refs.upload.clearFiles(); //上传成功之后清除历史记录
    },
    // 上传失败
    handleUploadError(err) {
      this.$message.error("上传失败, 请重试");
    },
    del(index) {
      this.report.reportData.splice(index, 1);
    },
    handleSelectionChange(val) {
      this.multipleSelection = val;
    },
    download(file) {
      let name = file.name;
      let url = file.url;
      let suffix = url.substring(url.lastIndexOf("."), url.length);
      const a = document.createElement('a')
      a.setAttribute('download', name + suffix)
      a.setAttribute('target', '_blank')
      a.setAttribute('href', url)
      a.click()
    },
    downloadZip() {
      if (this.multipleSelection.length < 1) {
        this.$message.warning("请选择要下载的文件!");
      } else {
        let files = this.multipleSelection.map(item => {
          return item.localPath;
        });
        let zipName = this.$store.state.user.dept.deptName;
        this.$download.zip("/report/zip?files=" + files, zipName);
      }
    }
  }
};
</script>
<style scoped>

</style>

src/plugins/download.js

import axios from 'axios'
import { Message } from 'element-ui'
import { saveAs } from 'file-saver'
import { getToken } from '@/utils/auth'
import errorCode from '@/utils/errorCode'
import { blobValidate } from "@/utils/ruoyi";

const baseURL = process.env.VUE_APP_BASE_API

export default {
  name(name, isDelete = true) {
    var url = baseURL + "/common/download?fileName=" + encodeURI(name) + "&delete=" + isDelete
    axios({
      method: 'get',
      url: url,
      responseType: 'blob',
      headers: { 'Authorization': 'Bearer ' + getToken() }
    }).then(async (res) => {
      const isLogin = await blobValidate(res.data);
      if (isLogin) {
        const blob = new Blob([res.data])
        this.saveAs(blob, decodeURI(res.headers['download-filename']))
      } else {
        this.printErrMsg(res.data);
      }
    })
  },
  resource(resource) {
    var url = baseURL + "/common/download/resource?resource=" + encodeURI(resource);
    axios({
      method: 'get',
      url: url,
      responseType: 'blob',
      headers: { 'Authorization': 'Bearer ' + getToken() }
    }).then(async (res) => {
      const isLogin = await blobValidate(res.data);
      if (isLogin) {
        const blob = new Blob([res.data])
        this.saveAs(blob, decodeURI(res.headers['download-filename']))
      } else {
        this.printErrMsg(res.data);
      }
    })
  },
  zip(url, name) {
    var url = baseURL + url
    axios({
      method: 'get',
      url: url,
      responseType: 'blob',
      headers: { 'Authorization': 'Bearer ' + getToken() }
    }).then(async (res) => {
      const isLogin = await blobValidate(res.data);
      if (isLogin) {
        const blob = new Blob([res.data], { type: 'application/zip' })
        this.saveAs(blob, name)
      } else {
        this.printErrMsg(res.data);
      }
    })
  },
  saveAs(text, name, opts) {
    saveAs(text, name, opts);
  },
  async printErrMsg(data) {
    const resText = await data.text();
    const rspObj = JSON.parse(resText);
    const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default']
    Message.error(errMsg);
  }
}


3. 验证

在这里插入图片描述
在这里插入图片描述
撒花~~~

评论 8
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值