1. 程式人生 > 其它 >SpringBoot+Vue+kkFileView實現文件管理(文件上傳、下載、線上預覽)

SpringBoot+Vue+kkFileView實現文件管理(文件上傳、下載、線上預覽)

場景

SpringBoot+Vue+OpenOffice實現文件管理(文件上傳、下載、線上預覽):

https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/121363504

上面在使用OpenOffice實現doc、excel、ppt等文件的管理和預覽。

除此之外可用kkFileView實現包括且更多文件的預覽。

kkFileView

https://kkfileview.keking.cn/zh-cn/index.html

kkFileView為檔案文件線上預覽解決方案,該專案使用流行的spring boot搭建,易上手和部署,

基本支援主流辦公文件的線上預覽,如doc,docx,xls,xlsx,ppt,pptx,pdf,txt,zip,rar,圖片,視訊,音訊等等。

gitee地址:

https://gitee.com/kekingcn/file-online-preview

kkFileView部署和SpringBoot接入指南

具體參考文件:

https://gitee.com/kekingcn/file-online-preview/wikis/pages

這裡是windows電腦,所以直接下載發行版本並解壓執行即可

https://gitee.com/kekingcn/file-online-preview/releases

解壓之後找到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)));

注:

部落格:
https://blog.csdn.net/badao_liumang_qizhi
關注公眾號
霸道的程式猿
獲取程式設計相關電子書、教程推送與免費下載。

實現

建表與後臺SpringBoot程式碼生成

若依前後端分離版本地搭建開發環境並執行專案的教程:

https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/108465662

基於上面搭建起來架構的基礎上。

設計資料庫表

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:

https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/115466945

這裡後臺將預覽檔案的網路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 fiele file 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

修改位置