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

修改位置

完整示例代码下载地址:

https://download.csdn.net/download/BADAO_LIUMANG_QIZHI/88849140

  • 16
    点赞
  • 109
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 27
    评论
下面是使用 Spring Boot + Vue 实现 KKFileView 与 MinIO 文件预览的具体操作步骤: 1. 首先,需要安装并启动 MinIO,可以从官网下载并安装对应平台的版本。 2. 创建一个新的 MinIO 存储桶,并将需要预览的文件上传到该存储桶中。 3. 使用 Spring Boot 创建一个新的 Web 应用程序,并添加 MinIO 的客户端依赖,如下所示: ```xml <dependency> <groupId>io.minio</groupId> <artifactId>minio</artifactId> <version>6.1.0</version> </dependency> ``` 4. 创建一个 MinIO 客户端实例,并使用该实例获取存储桶中的文件 URL,如下所示: ```java import io.minio.MinioClient; import io.minio.errors.MinioException; public class MinioUtils { private static final String ENDPOINT = "your-minio-endpoint"; private static final String ACCESS_KEY = "your-minio-access-key"; private static final String SECRET_KEY = "your-minio-secret-key"; private static final String BUCKET_NAME = "your-bucket-name"; private static final MinioClient minioClient; static { try { minioClient = new MinioClient(ENDPOINT, ACCESS_KEY, SECRET_KEY); } catch (Exception e) { throw new RuntimeException(e); } } public static String getFileUrl(String fileName) { try { return minioClient.presignedGetObject(BUCKET_NAME, fileName); } catch (Exception e) { throw new RuntimeException(e); } } } ``` 其中,getFileUrl 方法用于获取指定文件在 MinIO 存储桶中的 URL。 5. 使用 Vue 创建一个新的 Web 界面,并引入 KKFileViewJavaScript 文件,如下所示: ```html <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>文件预览</title> <script src="https://cdn.koalaxiezi.com/kkfileview/4.0.6/kkfileview.min.js"></script> <link href="https://cdn.koalaxiezi.com/kkfileview/4.0.6/kkfileview.min.css" rel="stylesheet"> </head> <body> <div id="app"></div> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script src="app.js"></script> </body> </html> ``` 6. 在 Vue 的 app.js 文件中,使用 Axios 获取 MinIO 存储桶中的文件 URL,并将其传递给 KKFileView 进行预览,如下所示: ```javascript import axios from 'axios' import KKFileView from 'kkfileview' new Vue({ el: '#app', created() { axios.get('/getFileUrl?fileName=test.pdf').then((response) => { KKFileView.open(response.data) }) } }) ``` 其中,getFileUrl 接口用于返回指定文件在 MinIO 存储桶中的 URL,response.data 是从接口返回的 URL。 7. 在 Spring Boot 应用程序中,创建 getFileUrl 接口,用于返回指定文件在 MinIO 存储桶中的 URL,如下所示: ```java import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController public class FileController { @GetMapping("/getFileUrl") public String getFileUrl(@RequestParam String fileName) { return MinioUtils.getFileUrl(fileName); } } ``` 8. 启动 Spring Boot 应用程序和 Vue Web 界面,访问 Vue Web 界面,即可在线预览 MinIO 存储桶中的文件。 需要注意的是,为了保证文件访问的安全性,需要在 MinIO 中设置适当的访问权限,确保只有授权的用户才能访问存储桶中的文件。同时,KKFileView 也提供了一些安全设置,如设置访问密码等,可以进一步加强文件访问的安全性。
评论 27
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

霸道流氓气质

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值