Vue + SpringBoot 上传 Excel 文件

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Vue + SpringBoot 上传 Excel 文件相关的知识,希望对你有一定的参考价值。

参考技术A

Step2: 引用组件,指定上传方法

这里对 axios 做了封装,可以直接调用 api 中对应的方法:

Controller 层接收请求:

Service 层处理 Excel 文件,取出数据做相应的业务处理。

到此,Vue 上传文件到后端完成,效果如下:

SpringBoot+Vue+kkFileView实现文档管理(文档上传下载在线预览)

场景

SpringBoot+Vue+OpenOffice实现文档管理(文档上传、下载、在线预览):

SpringBoot+Vue+OpenOffice实现文档管理(文档上传、下载、在线预览)_霸道流氓气质的博客-CSDN博客_vue openoffice

上面在使用OpenOffice实现doc、excel、ppt等文档的管理和预览。

除此之外可用kkFileView实现包括且更多文档的预览。

kkFileView

kkFileView - 在线文件预览

kkFileView为文件文档在线预览解决方案,该项目使用流行的spring boot搭建,易上手和部署,

基本支持主流办公文档的在线预览,如doc,docx,xls,xlsx,ppt,pptx,pdf,txt,zip,rar,图片,视频,音频等等。

gitee地址:

kkFileView: 使用spring boot打造文件文档在线预览项目解决方案,支持doc、docx、ppt、pptx、xls、xlsx、zip、rar、mp4、mp3以及众多类文本如txt、html、xml、java、properties、sql、js、md、json、conf、ini、vue、php、py、bat、gitignore等文件在线预览

kkFileView部署和SpringBoot接入指南

具体参考文档:

文档预览 - Gitee.com

这里是windows电脑,所以直接下载发行版本并解压运行即可

kkFileView 发行版 - Gitee.com

 

解压之后找到bin下bat,双击启动即可

 

启动成功之后访问:

http://127.0.0.1:8012/index

出现如下界面则成功

 

 

项目接入使用

当您的项目内需要预览文件时,只需要调用浏览器打开本项目的预览接口,并传入须要预览文件的url

                    var url = 'http://127.0.0.1:8080/file/test.txt'; //要预览文件的访问地址
                    window.open('http://127.0.0.1:8012/onlinePreview?url='+encodeURIComponent(base64Encode(url)));

注:

博客:
霸道流氓气质的博客_CSDN博客-C#,架构之路,SpringBoot领域博主
关注公众号
霸道的程序猿
获取编程相关电子书、教程推送与免费下载。

实现

建表与后台SpringBoot代码生成

若依前后端分离版本地搭建开发环境并运行项目的教程:

若依前后端分离版手把手教你本地搭建环境并运行项目_霸道流氓气质的博客-CSDN博客_运行若依分离版

基于上面搭建起来架构的基础上。

设计数据库表

DROP TABLE IF EXISTS `bus_file_preview`;
CREATE TABLE `bus_file_preview`  (
  `id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `file_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '文件名',
  `preview_server_url` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '预览服务地址',
  `file_path` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '文件磁盘路径',
  `file_url` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '文件url',
  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
  `create_by` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '创建人',
  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间',
  `update_by` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '更新人',
  `remark` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '备注',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 15 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '文件上传与预览' ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;

表结构

 

使用若依自带代码生成工具生成代码,下面展示部分代码

实体类BusFilePreview

import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import com.ruoyi.common.annotation.Excel;
import com.ruoyi.common.core.domain.BaseEntity;

public class BusFilePreview extends BaseEntity

    private static final long serialVersionUID = 1L;

    /** 主键 */
    private Long id;

    /** 文件名 */
    @Excel(name = "文件名")
    private String fileName;

    /** 预览服务地址 */
    @Excel(name = "预览服务地址")
    private String previewServerUrl;

    /** 文件url */
    @Excel(name = "文件url")
    private String fileUrl;

    /** 文件磁盘路径 */
    @Excel(name = "文件磁盘路径")
    private String filePath;

    public void setId(Long id)
    
        this.id = id;
    

    public Long getId()
    
        return id;
    
    public void setFileName(String fileName)
    
        this.fileName = fileName;
    

    public String getFileName()
    
        return fileName;
    
    public void setPreviewServerUrl(String previewServerUrl)
    
        this.previewServerUrl = previewServerUrl;
    

    public String getPreviewServerUrl()
    
        return previewServerUrl;
    
    public void setFileUrl(String fileUrl)
    
        this.fileUrl = fileUrl;
    

    public String getFileUrl()
    
        return fileUrl;
    
    public void setFilePath(String filePath)
    
        this.filePath = filePath;
    

    public String getFilePath()
    
        return filePath;
    


    @Override
    public String toString() 
        return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
            .append("id", getId())
            .append("fileName", getFileName())
            .append("previewServerUrl", getPreviewServerUrl())
            .append("fileUrl", getFileUrl())
            .append("createTime", getCreateTime())
            .append("createBy", getCreateBy())
            .append("updateTime", getUpdateTime())
            .append("updateBy", getUpdateBy())
            .append("remark", getRemark())
            .toString();
    

文件上传功能实现

前端添加el-upload

    <!-- 添加或修改preview对话框 -->
    <el-dialog :title="title" :visible.sync="open" width="35%" append-to-body>
      <el-form ref="form" :model="form" :rules="rules" label-width="110px">
        <el-form-item label="文件名" prop="fileName">
          <el-input
            v-model="form.fileName"
            placeholder="请输入文件名"
            disabled
          />
        </el-form-item>
        <el-form-item label="附件" prop="photoPath">
          <el-upload
            :headers="headers"
            :action="url"
            :multiple="false"
            :file-list="fileList"
            :on-remove="fileRemove"
            :on-success="uploadSuccess"
            :on-error="uploadError"
            :on-progress="uploadProgress"
            :before-upload="beforeUpload"
            :limit="1"
            :on-exceed="beyond"
          >
            <el-button size="small">
              上传
              <i class="el-icon-upload el-icon--right"></i>
            </el-button>
            <div class="el-upload__tip" style="color: red" slot="tip">
              提示:仅允许导入".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx",
              ".pdf", ".mp3",".mp4",".wav"格式文件!
            </div>
          </el-upload>
        </el-form-item>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button type="primary" @click="submitForm">确 定</el-button>
        <el-button @click="cancel">取 消</el-button>
      </div>
    </el-dialog>

并设置各回调事件,事件实现

    // 文件上传失败
    uploadError(err) 
      this.btnLoding = false;
      this.$message.error(res.msg);
    ,
    // 上传中
    uploadProgress(e) 
      this.btnLoding = true;
    ,
    // 文件上传之前
    beforeUpload(file) 
      const fileName = file.name;
      const fileType = fileName.substring(fileName.lastIndexOf("."));
      const whiteList = [
        ".doc",
        ".docx",
        ".xls",
        ".xlsx",
        ".ppt",
        ".pptx",
        ".pdf",
        ".mp3",
        ".mp4",
        ".wav",
      ];
      //array.indexOf此方法判断数组中是否存在某个值,如果存在返回数组元素的下标,否则返回-1。
      if (whiteList.indexOf(fileType) === -1) 
        this.$message.error("只允许如下文件类型:" + whiteList.toString());
        return false;
        // 不处理
       else 
        this.form.fileName = file.name;
      
    ,
    // 文件上传成功
    uploadSuccess(res, file, fileList) 
      this.form.previewServerUrl = res.previewServerUrl;
      this.form.fileUrl = res.fileUrl;
      this.form.filePath = res.filePath;
      this.btnLoding = false;
      this.fileList = fileList;
      this.$message(res.msg);
    ,
    beyond(file, fileList) 
      this.$message(
        message: "最多上传一个文件",
        type: "warning",
      );
    ,
    // 移除选择的文件
    fileRemove(file, fileList) 
      this.btnLoding = false;
      this.reset();
      this.fileList = [];
    ,

重点关注文件上传之前的beforeUpload和文件上传成功的uploadSuccess

上传之前获取文件名和后缀名,然后判断是否为允许的文件类型,如果允许,获取文件名。

文件上传成功之后获取后台接口返回的文件预览服务地址和文件预览url以及磁盘路径三个参数。

其中文件预览服务地址指的是kkFileView的ip和端口号以及预览的url,比如这里是与后台服务放在了一起,

所以previewServerUrl为http://127.0.0.1:8012/onlinePreview?url=

文件预览url为调用kkFileView预览时传递的参数,比如

http://127.0.0.1:9090/profile/upload/2022/12/10/a28ffa19-9982-42d2-8766-1feb274c5bb7.doc

文件磁盘路径映射为网络url可以参考

SpringBoot中通过重写WebMvcConfigurer的方法配置静态资源映射实现图片上传后返回网络Url:

SpringBoot中通过重写WebMvcConfigurer的方法配置静态资源映射实现图片上传后返回网络Url_霸道流氓气质的博客-CSDN博客

这里后台将预览文件的网络url单独返回,是因为在前端进行预览时,需要对url参数进行base64编码

如果不进行编码预览会有问题,会提示

Illegal base64 character 3a

这点官网有说明

 

SpringBoot上传接口实现

代码实现

​
    @PostMapping("/uploadPreviewFile")
    public AjaxResult uploadPreviewFile(@Param("file") MultipartFile file) 
        AjaxResult ajaxResult = AjaxResult.success();
        try 
            //D:/ruoyi/uploadPath/upload/2022/12/10/
            String path = RuoYiConfig.getUploadPath() + "/" + DateUtils.datePath() + "/";
            FileUtils.check_folder(path);
            // 上传后的文件名称
            //423208ab-2171-4631-9e08-382c00aacc43.doc
            String auth_file_name = UploadUtil.save_file_withAllow(file, path ,allowFiles);
            if (StringUtils.isEmpty(auth_file_name))
                return AjaxResult.error(HttpStatus.BAD_REQUEST, "文件格式不合法");
            
            ajaxResult.put("code", 200);
            ajaxResult.put("message", "成功");
            ajaxResult.put("fileName", auth_file_name);
            ajaxResult.put("filePath", path + auth_file_name);
            //serverConfig.getUrl()     http://127.0.0.1:9090
            //Constants.RESOURCE_PREFIX     /profile
            //RuoYiConfig.getUploadPathPre()  /upload
            //File.separator       /
            //DateUtils.datePath()   /2022/12/10
            //auth_file_name    a28ffa19-9982-42d2-8766-1feb274c5bb7.doc
            //url http://127.0.0.1:9090/profile/upload/2022/12/10/a28ffa19-9982-42d2-8766-1feb274c5bb7.doc
            String fileUrl = serverConfig.getUrl()+ Constants.RESOURCE_PREFIX + RuoYiConfig.getUploadPathPre() + File.separator + DateUtils.datePath() + File.separator + auth_file_name;
            String previewServerUrl = Constants.HTTP + previewConfig.getServerIp() +":" +previewConfig.getServerPort() + Constants.PREVIEW_SERVER_URL;
            ajaxResult.put("previewServerUrl", previewServerUrl);
            ajaxResult.put("fileUrl", fileUrl);

         catch (IOException e) 
            ajaxResult.put("code", 400);
            ajaxResult.put("message", "上传失败");
            e.printStackTrace();
        
        return ajaxResult;
    

​

为方便更清晰的调试理解,这里在生成网络url时标注了每一步的参数

首先调用若依的工具类获取并检查文件上传目录,这里的RuoYiConfig.getUploadPath()配置的是D:/ruoyi/uploadPath

 

然后最终path的值为D:/ruoyi/uploadPath/upload/2022/12/10/

然后调用文件上传方法,这里新建了一个重载方法,传递了允许上传的文件类型,若依自带的方法该参数是写死的

 public static String save_file_withAllow(MultipartFile file, String path ,String[] allowFiles) throws IOException 
  String filename=file.getOriginalFilename();
  //后缀名校验
  String suffix = filename.substring(filename.indexOf("."));
  List<String> allowFileList = new ArrayList<>(Arrays.asList(allowFiles));
  if (!allowFileList.contains(suffix))
   return null;
  
  filename = UUID.randomUUID().toString() + suffix;
  File file_temp=new File(path,filename);
  if (!file_temp.getParentFile().exists()) 
   file_temp.getParentFile().mkdir();
  
  if (file_temp.exists()) 
   file_temp.delete();
  
  file_temp.createNewFile();
  file.transferTo(file_temp);
  return file_temp.getName();
 

允许上传的格式需要声明

    /**允许上传的格式*/
    private static String[] allowFiles = ".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx", ".pdf",".mp3",".mp4",".wav"
    ;

上传成功之后获取到文件的文件名,比如423208ab-2171-4631-9e08-382c00aacc43.doc

然后生成文件预览网络url

​
            //serverConfig.getUrl()     http://127.0.0.1:9090
            //Constants.RESOURCE_PREFIX     /profile
            //RuoYiConfig.getUploadPathPre()  /upload
            //File.separator       /
            //DateUtils.datePath()   /2022/12/10
            //auth_file_name    a28ffa19-9982-42d2-8766-1feb274c5bb7.doc
            //url http://127.0.0.1:9090/profile/upload/2022/12/10/a28ffa19-9982-42d2-8766-1feb274c5bb7.doc
            String fileUrl = serverConfig.getUrl()+ Constants.RESOURCE_PREFIX + RuoYiConfig.getUploadPathPre() + File.separator + DateUtils.datePath() + File.separator + auth_file_name;

​

这里的serverConfig是若依自带获取服务相关配置,其它的就是一些常量配置。

RuoYiConfig.getUploadPathPre() 是自己新增的获取上传路径的前缀

    /**
     * 获取上传路径前缀
     */
    public static String getUploadPathPre()
    
        return "/upload";
    

最后返回kkFileView服务预览的ip和端口以及前缀

String previewServerUrl = Constants.HTTP + previewConfig.getServerIp() +":" +previewConfig.getServerPort() + Constants.PREVIEW_SERVER_URL;

这里将ip和端口写在配置文件中

#文件预览相关配置
preview:
  serverIp: 127.0.0.1
  serverPort: 8012

 

然后新增配置文件获取

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;


@Component
@ConfigurationProperties(prefix = "preview")
public class PreviewConfig 

    private String serverIp;

    private String serverPort;

    public String getServerIp() 
        return serverIp;
    

    public void setServerIp(String serverIp) 
        this.serverIp = serverIp;
    

    public String getServerPort() 
        return serverPort;
    

    public void setServerPort(String serverPort) 
        this.serverPort = serverPort;
    

最终返回的previewServerUrl为

http://127.0.0.1:8012/onlinePreview?url=

上传成功之后,前端获取返回参数并赋值到form中,前端点击提交按钮时

    /** 提交按钮 */
    submitForm() 
      this.$refs["form"].validate((valid) => 
        if (valid) 
          if (this.form.id != null) 
            updatePreview(this.form).then((response) => 
              this.msgSuccess("修改成功");
              this.open = false;
              this.fileList = [];
              this.getList();
            );
           else 
            addPreview(this.form).then((response) => 
              this.msgSuccess("新增成功");
              this.open = false;
              this.fileList = [];
              this.getList();
            );
          
        
      );
    ,

将数据存储到数据库中。

文件上传效果实现

 

文件下载实现

前端下载按钮点击事件

    // 下载
    handleDownload(row) 
      var filePath = row.filePath;
      var fileName = row.fileName;
      var url =
        this.downloadUrl + "?filePath=" + filePath + "&fileName=" + fileName;
      const a = document.createElement("a");
      a.setAttribute("download", fileName);
      a.setAttribute("target", "_blank");
      a.setAttribute("href", url);
      a.click();
    ,

获取文件磁盘路径和文件名,并传递参数

后台SpringBoot接口

    @GetMapping("download")
    @ApiOperation("下载")
    public void downloadFile(String filePath,String fileName, HttpServletResponse response) throws IOException 
        File file = new File(filePath);
        // 清空response
        response.reset();
        // 设置response的Header 通知浏览器 已下载的方式打开文件 防止文本图片预览
        response.addHeader("Content-Disposition",
                "attachment;filename=" + new String(fileName.getBytes("gbk"), "iso-8859-1")); // 转码之后下载的文件不会出现中文乱码
        response.addHeader("Content-Length", "" + file.length());
        // 以流的形式下载文件
        InputStream fis = new BufferedInputStream(new FileInputStream(filePath));
        byte[] buffer = new byte[fis.available()];
        fis.read(buffer);
        fis.close();
        OutputStream toClient = new BufferedOutputStream(response.getOutputStream());
        toClient.write(buffer);
        toClient.flush();
        toClient.close();
    

文件下载效果

 

预览实现

前端点击预览的点击事件

    // 预览
    handlePreview(row) 
      var previewServerUrl = row.previewServerUrl;
      var fileUrl = row.fileUrl;
      //分别获取预览服务地址和预览参数的地址然后拼接
      //预览文件url地址需要使用Base64编码URL
      let url =
        previewServerUrl + encodeURIComponent(Base64.encodeURI(fileUrl));
      window.open(url);
    ,

注意这里获取预览服务地址和预览参数文件url,这里需要将文件url进行Base64编码

Vue中使用Base64编码

安装依赖

npm install --save js-base64

引入依赖

import  Base64  from "js-base64";

调用编码方法

Base64.encodeURI(fileUrl)

预览效果

代码完整示例

前端Vue页面完整示例代码

<template>
  <div class="app-container">
    <el-form
      :model="queryParams"
      ref="queryForm"
      :inline="true"
      v-show="showSearch"
      label-width="68px"
    >
      <el-form-item label="文件名" prop="fileName">
        <el-input
          v-model="queryParams.fileName"
          placeholder="请输入文件名"
          clearable
          size="small"
          @keyup.enter.native="handleQuery"
        />
      </el-form-item>

 

      <el-form-item>
        <el-button
          type="cyan"
          icon="el-icon-search"
          size="mini"
          @click="handleQuery"
          >搜索</el-button
        >
        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery"
          >重置</el-button
        >
      </el-form-item>
    </el-form>

    <el-row :gutter="10" class="mb8">
      <el-col :span="1.5">
        <el-button
          type="primary"
          icon="el-icon-plus"
          size="mini"
          @click="handleAdd"
          v-hasPermi="['basicinfomanage:preview:add']"
          >新增</el-button
        >
      </el-col>
      <el-col :span="1.5">
        <el-button
          type="success"
          icon="el-icon-edit"
          size="mini"
          :disabled="single"
          @click="handleUpdate"
          v-hasPermi="['basicinfomanage:preview:edit']"
          >修改</el-button
        >
      </el-col>
      <el-col :span="1.5">
        <el-button
          type="danger"
          icon="el-icon-delete"
          size="mini"
          :disabled="multiple"
          @click="handleDelete"
          v-hasPermi="['basicinfomanage:preview:remove']"
          >删除</el-button
        >
      </el-col>

      <right-toolbar
        :showSearch.sync="showSearch"
        @queryTable="getList"
      ></right-toolbar>
    </el-row>

    <el-table
      v-loading="loading"
      :data="previewList"
      @selection-change="handleSelectionChange"
    >
      <el-table-column type="selection" width="55" align="center" />
      <el-table-column
        show-overflow-tooltip
        label="文件名"
        align="center"
        prop="fileName"
      />
      <el-table-column
        label="操作"
        align="center"
        class-name="small-padding fixed-width"
        width="200"
      >
        <template slot-scope="scope">
          <el-button
            size="mini"
            type="text"
            icon="el-icon-edit"
            @click="handleUpdate(scope.row)"
            v-hasPermi="['basicinfomanage:preview:edit']"
            >修改</el-button
          >
          <el-button
            size="mini"
            type="text"
            icon="el-icon-edit"
            @click="handlePreview(scope.row)"
            >预览</el-button
          >
          <el-button
            size="mini"
            type="text"
            icon="el-icon-edit"
            @click="handleDownload(scope.row)"
            >下载</el-button
          >
          <el-button
            size="mini"
            type="text"
            icon="el-icon-delete"
            @click="handleDelete(scope.row)"
            v-hasPermi="['basicinfomanage:preview:remove']"
            >删除</el-button
          >
        </template>
      </el-table-column>
    </el-table>

    <pagination
      v-show="total > 0"
      :total="total"
      :page.sync="queryParams.pageNum"
      :limit.sync="queryParams.pageSize"
      @pagination="getList"
    />

    <!-- 添加或修改preview对话框 -->
    <el-dialog :title="title" :visible.sync="open" width="35%" append-to-body>
      <el-form ref="form" :model="form" :rules="rules" label-width="110px">
        <el-form-item label="文件名" prop="fileName">
          <el-input
            v-model="form.fileName"
            placeholder="请输入文件名"
            disabled
          />
        </el-form-item>
        <el-form-item label="附件" prop="photoPath">
          <el-upload
            :headers="headers"
            :action="url"
            :multiple="false"
            :file-list="fileList"
            :on-remove="fileRemove"
            :on-success="uploadSuccess"
            :on-error="uploadError"
            :on-progress="uploadProgress"
            :before-upload="beforeUpload"
            :limit="1"
            :on-exceed="beyond"
          >
            <el-button size="small">
              上传
              <i class="el-icon-upload el-icon--right"></i>
            </el-button>
            <div class="el-upload__tip" style="color: red" slot="tip">
              提示:仅允许导入".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx",
              ".pdf", ".mp3",".mp4",".wav"格式文件!
            </div>
          </el-upload>
        </el-form-item>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button type="primary" @click="submitForm">确 定</el-button>
        <el-button @click="cancel">取 消</el-button>
      </div>
    </el-dialog>
  </div>
</template>

<script>
import 
  listPreview,
  getPreview,
  delPreview,
  addPreview,
  updatePreview,
 from "@/api/basicinfomanage/preview";
import  getToken  from "@/utils/auth";
import  Base64  from "js-base64";
export default 
  name: "Preview",
  data() 
    return 
      // 遮罩层
      loading: true,
      // 选中数组
      ids: [],
      // 非单个禁用
      single: true,
      // 非多个禁用
      multiple: true,
      // 显示搜索条件
      showSearch: true,
      // 总条数
      total: 0,
      // preview表格数据
      previewList: [],
      // 弹出层标题
      title: "",
      // 是否显示弹出层
      open: false,
      // 查询参数
      queryParams: 
        pageNum: 1,
        pageSize: 10,
        fileName: null,
      ,
      // 表单参数
      form: ,
      // 表单校验
      rules: 
        fileName: [
          
            required: true,
            message: "文件名称不能为空",
            trigger: "blur",
          ,
        ],
      ,
      // 上传按钮闸口
      btnLoding: false,
      //  请求头
      headers:  Authorization: "Bearer" + " " + getToken() ,
      // 上传地址
      url:
        process.env.VUE_APP_BASE_API +
        "/fzys/basicinfomanage/preview/uploadPreviewFile",
      // 下载地址
      downloadUrl:
        process.env.VUE_APP_BASE_API + "/fzys/basicinfomanage/preview/download",
      // 图片列表
      fileList: [],
    ;
  ,
  created() 
    this.getList();
  ,
  methods: 
    /** 查询preview列表 */
    getList() 
      this.loading = true;
      listPreview(this.queryParams).then((response) => 
        this.previewList = response.rows;
        this.total = response.total;
        this.loading = false;
      );
    ,
    // 取消按钮
    cancel() 
      this.open = false;
      this.reset();
    ,
    // 表单重置
    reset() 
      this.form = 
        id: null,
        fileName: null,
      ;
      this.resetForm("form");
    ,
    /** 搜索按钮操作 */
    handleQuery() 
      this.queryParams.pageNum = 1;
      this.getList();
    ,
    /** 重置按钮操作 */
    resetQuery() 
      this.resetForm("queryForm");
      this.handleQuery();
    ,
    // 多选框选中数据
    handleSelectionChange(selection) 
      this.ids = selection.map((item) => item.id);
      this.single = selection.length !== 1;
      this.multiple = !selection.length;
    ,
    /** 新增按钮操作 */
    handleAdd() 
      this.fileRemove();
      this.open = true;
      this.title = "添加文件";
    ,
    /** 修改按钮操作 */
    handleUpdate(row) 
      this.reset();
      const id = row.id || this.ids;
      getPreview(id).then((response) => 
        this.form = response.data;
        this.open = true;
        this.title = "修改文件";
      );
    ,
    // 预览
    handlePreview(row) 
      var previewServerUrl = row.previewServerUrl;
      var fileUrl = row.fileUrl;
      //分别获取预览服务地址和预览参数的地址然后拼接
      //预览文件url地址需要使用Base64编码URL
      let url =
        previewServerUrl + encodeURIComponent(Base64.encodeURI(fileUrl));
      window.open(url);
    ,
    // 下载
    handleDownload(row) 
      var filePath = row.filePath;
      var fileName = row.fileName;
      var url =
        this.downloadUrl + "?filePath=" + filePath + "&fileName=" + fileName;
      const a = document.createElement("a");
      a.setAttribute("download", fileName);
      a.setAttribute("target", "_blank");
      a.setAttribute("href", url);
      a.click();
    ,

    /** 提交按钮 */
    submitForm() 
      this.$refs["form"].validate((valid) => 
        if (valid) 
          if (this.form.id != null) 
            updatePreview(this.form).then((response) => 
              this.msgSuccess("修改成功");
              this.open = false;
              this.fileList = [];
              this.getList();
            );
           else 
            addPreview(this.form).then((response) => 
              this.msgSuccess("新增成功");
              this.open = false;
              this.fileList = [];
              this.getList();
            );
          
        
      );
    ,
    /** 删除按钮操作 */
    handleDelete(row) 
      const ids = row.id || this.ids;
      this.$confirm('是否确认删除文件编号为"' + ids + '"的数据项?', "警告", 
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning",
      )
        .then(function () 
          return delPreview(ids);
        )
        .then(() => 
          this.getList();
          this.msgSuccess("删除成功");
        );
    ,
    // 文件上传失败
    uploadError(err) 
      this.btnLoding = false;
      this.$message.error(res.msg);
    ,
    // 上传中
    uploadProgress(e) 
      this.btnLoding = true;
    ,
    // 文件上传之前
    beforeUpload(file) 
      const fileName = file.name;
      const fileType = fileName.substring(fileName.lastIndexOf("."));
      const whiteList = [
        ".doc",
        ".docx",
        ".xls",
        ".xlsx",
        ".ppt",
        ".pptx",
        ".pdf",
        ".mp3",
        ".mp4",
        ".wav",
      ];
      //array.indexOf此方法判断数组中是否存在某个值,如果存在返回数组元素的下标,否则返回-1。
      if (whiteList.indexOf(fileType) === -1) 
        this.$message.error("只允许如下文件类型:" + whiteList.toString());
        return false;
        // 不处理
       else 
        this.form.fileName = file.name;
      
    ,
    // 文件上传成功
    uploadSuccess(res, file, fileList) 
      this.form.previewServerUrl = res.previewServerUrl;
      this.form.fileUrl = res.fileUrl;
      this.form.filePath = res.filePath;
      this.btnLoding = false;
      this.fileList = fileList;
      this.$message(res.msg);
    ,
    beyond(file, fileList) 
      this.$message(
        message: "最多上传一个文件",
        type: "warning",
      );
    ,
    // 移除选择的文件
    fileRemove(file, fileList) 
      this.btnLoding = false;
      this.reset();
      this.fileList = [];
    ,
  ,
;
</script>

后台Controller完整代码

package com.ruoyi.web.controller.fzys.basicinfomannager;

import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.config.RuoYiConfig;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.constant.HttpStatus;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.fzys.basicinfomanage.BusFilePreview;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.UploadUtil;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.framework.config.ServerConfig;
import com.ruoyi.fzys.service.basicinfomanageService.IBusFilePreviewService;
import com.ruoyi.system.utils.FileUtils;
import com.ruoyi.web.controller.fzys.config.PreviewConfig;
import io.swagger.annotations.ApiOperation;
import org.apache.ibatis.annotations.Param;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.List;

/**
 * previewController
 *
 * @author ruoyi
 * @date 2021-10-29
 */
@RestController
@RequestMapping("/fzys/basicinfomanage/preview")
public class BusFilePreviewController extends BaseController

    /**允许上传的格式*/
    private static String[] allowFiles = ".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx", ".pdf",".mp3",".mp4",".wav"
    ;
    @Autowired
    private ServerConfig serverConfig;

    @Autowired
    private PreviewConfig previewConfig;

    @Autowired
    private IBusFilePreviewService busFilePreviewService;

    @PostMapping("/uploadPreviewFile")
    public AjaxResult uploadPreviewFile(@Param("file") MultipartFile file) 
        AjaxResult ajaxResult = AjaxResult.success();
        try 
            String path = RuoYiConfig.getUploadPath() + "/" + DateUtils.datePath() + "/";
            FileUtils.check_folder(path);
            String auth_file_name = UploadUtil.save_file_withAllow(file, path ,allowFiles);
            if (StringUtils.isEmpty(auth_file_name))
                return AjaxResult.error(HttpStatus.BAD_REQUEST, "文件格式不合法");
            
            ajaxResult.put("code", 200);
            ajaxResult.put("message", "成功");
            ajaxResult.put("fileName", auth_file_name);
            ajaxResult.put("filePath", path + auth_file_name);
            String fileUrl = serverConfig.getUrl()+ Constants.RESOURCE_PREFIX + RuoYiConfig.getUploadPathPre() + File.separator + DateUtils.datePath() + File.separator + auth_file_name;
            String previewServerUrl = Constants.HTTP + previewConfig.getServerIp() +":" +previewConfig.getServerPort() + Constants.PREVIEW_SERVER_URL;
            ajaxResult.put("previewServerUrl", previewServerUrl);
            ajaxResult.put("fileUrl", fileUrl);

         catch (IOException e) 
            ajaxResult.put("code", 400);
            ajaxResult.put("message", "上传失败");
            e.printStackTrace();
        
        return ajaxResult;
    

    /**
     * 下载文件
     * @param fileName
     * @param response
     * @throws IOException
     */
    @GetMapping("download")
    @ApiOperation("下载")
    public void downloadFile(String filePath,String fileName, HttpServletResponse response) throws IOException 
        File file = new File(filePath);
        // 清空response
        response.reset();
        // 设置response的Header 通知浏览器 已下载的方式打开文件 防止文本图片预览
        response.addHeader("Content-Disposition",
                "attachment;filename=" + new String(fileName.getBytes("gbk"), "iso-8859-1")); // 转码之后下载的文件不会出现中文乱码
        response.addHeader("Content-Length", "" + file.length());
        // 以流的形式下载文件
        InputStream fis = new BufferedInputStream(new FileInputStream(filePath));
        byte[] buffer = new byte[fis.available()];
        fis.read(buffer);
        fis.close();
        OutputStream toClient = new BufferedOutputStream(response.getOutputStream());
        toClient.write(buffer);
        toClient.flush();
        toClient.close();
    

    /**
     * 查询preview列表
     */

    @GetMapping("/list")
    public TableDataInfo list(BusFilePreview busFilePreview)
    
        startPage();
        List<BusFilePreview> list = busFilePreviewService.selectBusFilePreviewList(busFilePreview);
        return getDataTable(list);
    

    /**
     * 导出preview列表
     */

    @Log(title = "preview", businessType = BusinessType.EXPORT)
    @PostMapping("/export")
    public void export(HttpServletResponse response, BusFilePreview busFilePreview) throws IOException
    
        List<BusFilePreview> list = busFilePreviewService.selectBusFilePreviewList(busFilePreview);
        ExcelUtil<BusFilePreview> util = new ExcelUtil<BusFilePreview>(BusFilePreview.class);
        util.exportExcel(response, list, "preview");
    

    /**
     * 获取preview详细信息
     */

    @GetMapping(value = "/id")
    public AjaxResult getInfo(@PathVariable("id") Long id)
    
        return AjaxResult.success(busFilePreviewService.selectBusFilePreviewById(id));
    

    /**
     * 新增preview
     */
    @Log(title = "preview", businessType = BusinessType.INSERT)
    @PostMapping
    public AjaxResult add(@RequestBody BusFilePreview busFilePreview) throws IOException
        if (StringUtils.isNull(busFilePreview.getFileName())) 
            AjaxResult.error("缺少文件名称");
        
        return toAjax(busFilePreviewService.insertBusFilePreview(busFilePreview));
    

    /**
     * 修改preview
     */
    @Log(title = "preview", businessType = BusinessType.UPDATE)
    @PutMapping
    public AjaxResult edit(@RequestBody BusFilePreview busFilePreview)
    
        return toAjax(busFilePreviewService.updateBusFilePreview(busFilePreview));
    

    /**
     * 删除preview
     */
    @Log(title = "preview", businessType = BusinessType.DELETE)
 @DeleteMapping("/ids")
    public AjaxResult remove(@PathVariable Long[] ids)
    
        return toAjax(busFilePreviewService.deleteBusFilePreviewByIds(ids));
    

后台其他各层代码均为根据表结构代码生成。

问题

1、注意后台需要放开下载接口的鉴权

2、如果在预览时页面显示

Whitelabel Error Page

找到kkFileView目录下log下kkFileView.log文件查看具体报错

Illegal base64 character 3a

 

这是因为一开始没将预览文件url进行Base64编码导致。

3、上传文件时提示

Maximum upload size exceeded:nested exception is java.lang.lllegalStateException:

org.apache.tomcat.util.http.fileupload.FileSizeLimitExceeededException:

The fiel filed exceeds its maximum perrmitted size of xxx bytes

 

找到application.yml中修改如下配置

# Spring配置
spring:
  # 资源信息
  messages:
    # 国际化资源文件路径
    basename: i18n/messages
  profiles:
    active: druid
  # 文件上传
  servlet:
     multipart:
       # 单个文件大小
       max-file-size:  100MB
       # 设置总上传的文件大小
       max-request-size:  200MB

修改位置

 

以上是关于Vue + SpringBoot 上传 Excel 文件的主要内容,如果未能解决你的问题,请参考以下文章

Excel 模板下载功能 + 前后端解析 Excel 文件代码封装 + 前端上传 Excel 至数据库(SpringBoot + Vue 版)

Excel 模板下载功能 + 前后端解析 Excel 文件代码封装 + 前端上传 Excel 至数据库(SpringBoot + Vue 版)

SpringBoot+Vue+kkFileView实现文档管理(文档上传下载在线预览)

Spring Boot + Vue Element实现Excel文件上传解析下载(含完整源实现过程)

springboot 实现excel文件上传

springboot整合vue实现上传下载文件