SpringBoot实战:基于 SpringBoot3.3 支持任意文件在线预览功能

科技   2024-09-18 08:00   河北  


前言

在现代Web应用的开发过程中,实现文件的在线预览功能是一项关键需求,极大地提升了用户体验。该功能允许用户直接在浏览器界面内浏览文件内容,省去了传统上需要先将文件下载到本地再打开的步骤。本文旨在详尽阐述如何在基于SpringBoot的项目中集成kkFileView这一强大工具,从而轻松实现多种类型文件的在线预览功能。

kFileView作为一款开源的文件在线预览解决方案,专注于为Web应用提供无缝的文件预览体验。该工具建立在Spring Boot框架之上,广泛支持多种文件格式的直接预览,使得用户能够在浏览器中即时查看文件内容,无需进行繁琐的下载步骤。这种设计极大地提升了用户交互的便捷性和效率。

运行效果:

二、kkFileView 安装

在开始 SpringBoot 项目之前,我们首先需要在服务器上安装 kkFileView。以下是详细的安装步骤:

下载 kkFileView 安装包

wget https://kkfileview.keking.cn/kkFileView-4.0.0.tar.gz

2.解压安装包

tar -zxvf kkFileView-4.0.0.tar.gz

3.修改配置文件

cd kkFileView-4.0.0/config
vi application.properties

4.启动 kkFileView

cd kkFileView-4.0.0/bin
sh startup.sh

启动后,可以通过以下命令查看日志,确认是否启动成功:

sh showlog.sh
项目结构

项目的基本结构如下:

├── src
│ ├── main
│ │ ├── java
│ │ │ └── com.icoderoad.filepreview
│ │ │ ├── controller
│ │ │ │ └── FileController.java
│ │ │ ├── config
│ │ │ │ └── WebConfig.java
│ │ │ ├── service
│ │ │ │ └── FileService.java
│ │ │ └── FilepreviewApplication.java
│ │ ├── resources
│ │ │ ├── templates
│ │ │ │ └── upload.html
│ │ │ │ └── preview.html
│ │ │ └── application.yml
│ └── pom.xml
引入依赖

首先,在 pom.xml 中添加kkFileView的依赖以及其他必要的依赖:

<dependencies>
    <!-- Spring Boot 核心依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
 
    <!-- Thymeleaf 模板引擎 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
 
    <!-- 其他依赖 -->
</dependencies>
配置文件

在 application.yml 文件中,添加kkFileView相关配置:

server:
  port: 8080
 
file-preview:
  file-save-dir: /Users/icoderoad/code/java/filepreview/src/main/resources/uploadfile/ # 本地文件保存路径
  base-url: http://localhost:8080/files  # 文件的 HTTP 访问路径
后端代码实现

通过自定义 WebMvcConfigurer 来手动映射静态资源路径:

package com.icoderoad.filepreview.config;
 
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
 
@Configuration
public class WebConfig implements WebMvcConfigurer {
 
    // 从配置文件中读取静态资源路径
    @Value("${file-preview.file-save-dir}")
    private String fileStoragePath;
 
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        // 映射资源路径
        registry.addResourceHandler("/files/**")
                .addResourceLocations("file:" + fileStoragePath);
    }
}

这样就可以通过 http://localhost:8080/files/example.txt 来访问文件了。

主应用程序类 (FilepreviewApplication.java):

package com.icoderoad.filepreview;
 
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
 
@SpringBootApplication
public class FilepreviewApplication {
 
  public static void main(String[] args) {
    SpringApplication.run(FilepreviewApplication.class, args);
  }
 
}

文件服务类 (FileService.java):

package com.icoderoad.filepreview.service;
 
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
 
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
 
@Service
public class FileService {
 
    @Value("${file-preview.file-save-dir}")
    private String fileSaveDir;
 
    @Value("${file-preview.base-url}")
    private String baseUrl;
 
    public String saveFile(MultipartFile file) throws IOException {
        // 创建文件保存路径
        Path path = Paths.get(fileSaveDir, file.getOriginalFilename());
        Files.write(path, file.getBytes());
 
        // 生成文件的 HTTP URL
        return baseUrl + "/" + file.getOriginalFilename();
    }
}

文件预览控制器 (FileController.java):

package com.icoderoad.filepreview.controller;
 
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
 
import com.icoderoad.filepreview.service.FileService;
 
@Controller
public class FileController {
 
    @Autowired
    private FileService fileService;
    
    @GetMapping("/")
    public String index(Model model) {
        return "upload";
    }
 
    
    @PostMapping("/upload")
    public ResponseEntity<Map<String, String>> handleFileUpload(@RequestParam("file") MultipartFile file) {
        Map<String, String> response = new HashMap<>();
        try {
            if (file.isEmpty()) {
                throw new IllegalArgumentException("上传的文件为空");
            }
 
            // 保存文件并获取 HTTP 访问路径
            String fileUrl = fileService.saveFile(file);
            response.put("fileUrl", fileUrl);
 
            return ResponseEntity.ok(response);
        } catch (IOException e) {
            e.printStackTrace();
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(Map.of("error", "文件上传失败"));
        }
    }
    
 
    @GetMapping("/preview")
    public String previewFile(@RequestParam("filePath") String filePath, Model model) {
        // 将文件路径传递给前端页面
        model.addAttribute("filePath", filePath);
        return "preview";
    }
}
前端页面

文件上传页面 (upload.html):

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>文件上传</title>
    <!-- Bootstrap CSS -->
    <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
    <style>
        body {
            padding-top: 20px;
        }
        .container {
            max-width: 900px;
        }
        .upload-box {
            border: 1px solid #ddd;
            padding: 20px;
            border-radius: 5px;
            box-shadow: 0 4px 8px rgba(0,0,0,0.1);
        }
        .progress {
            margin-top: 10px;
        }
        .alert {
            display: none; /* 默认隐藏提示信息 */
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="row">
            <div class="col-md-12">
                <h1 class="text-center">文件上传</h1>
                <div class="upload-box">
                    <form id="uploadForm" enctype="multipart/form-data">
                        <div class="form-group">
                            <label for="fileInput">选择文件</label>
                            <input type="file" class="form-control-file" id="fileInput" name="file">
                        </div>
                        <button type="submit" class="btn btn-primary">上传</button>
                    </form>
                    <div class="progress mt-3">
                        <div id="progressBar" class="progress-bar" role="progressbar" style="width: 0%;" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>
                    </div>
                    <div id="uploadStatus" class="alert alert-success mt-3"></div>
                    <div id="uploadError" class="alert alert-danger mt-3"></div>
                    <div id="previewSection" class="mt-3" style="display: none;">
                        <button id="previewButton" class="btn btn-success">查看预览</button>
                    </div>
                </div>
            </div>
        </div>
    </div>
 
    <!-- Bootstrap JS and dependencies -->
    <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.5.2/dist/umd/popper.min.js"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
    <script>
        $(document).ready(function() {
            $('#uploadForm').on('submit', function(event) {
                event.preventDefault(); // 防止默认提交
 
                var formData = new FormData(this);
 
                $.ajax({
                    url: '/upload', // 后端处理上传的接口
                    type: 'POST',
                    data: formData,
                    contentType: false,
                    processData: false,
                    xhr: function() {
                        var xhr = new XMLHttpRequest();
                        xhr.upload.addEventListener('progress', function(e) {
                            if (e.lengthComputable) {
                                var percentComplete = Math.round((e.loaded / e.total) * 100);
                                $('#progressBar').css('width', percentComplete + '%');
                                $('#progressBar').attr('aria-valuenow', percentComplete);
                            }
                        }, false);
                        return xhr;
                    },
                    success: function(response) {
                        // 假设响应中包含文件的 URL
                        $('#uploadStatus').text('文件上传成功!').show();
                        $('#uploadError').hide();
                        $('#progressBar').css('width', '100%');
 
                        // 显示预览按钮
                        var fileUrl = response.fileUrl; // 从响应中获取文件 URL
                        console.log("fileUrl:", fileUrl);
                        $('#previewButton').data('file-url', fileUrl);
                        $('#previewSection').show();
                    },
                    error: function(xhr) {
                        $('#uploadError').text('文件上传失败!').show();
                        $('#uploadStatus').hide();
                    }
                });
 
                // 预览按钮点击事件
                $('#previewButton').on('click', function() {
                    var fileUrl = $(this).data('file-url');
                    console.log("fileUrl:", fileUrl);
                    if (fileUrl) {
                        var previewUrl = 'http://localhost:8080/preview?filePath=' + encodeURIComponent(fileUrl);
                        window.open(previewUrl, '_blank');
                    }
                });
            });
        });
    </script>
</body>
</html>

文件预览页面 (preview.html):

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>File Preview</title>
    <!-- Bootstrap CSS -->
    <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
    <script type="text/javascript">
    function base64Encode(str) {
        return btoa(unescape(encodeURIComponent(str)));
    }
 
    </script>
    <style>
        body {
            padding-top: 20px;
        }
        .container {
            max-width: 900px;
        }
        .preview-box {
            border: 1px solid #ddd;
            padding: 20px;
            border-radius: 5px;
            box-shadow: 0 4px 8px rgba(0,0,0,0.1);
        }
        .iframe-container {
            position: relative;
            padding-top: 56.25%; /* 16:9 aspect ratio */
            height: 0;
            overflow: hidden;
        }
        .iframe-container iframe {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
        }
    </style>
</head>
<body>
    
    <div class="container">
        <div class="row">
            <div class="col-md-12">
                <h1 class="text-center">文件在线预览</h1>
                <div class="preview-box">
                    <div class="iframe-container">
                        <!-- 使用 iframe 显示文件预览 -->
                       <script th:inline="javascript">
                  // 获取服务器传递的文件 HTTP 访问路径
                  var filePath = /*[[${filePath}]]*/ 'default.txt';
                  // 对文件路径进行 Base64 编码并进行 URL 编码
                  var encodedUrl =encodeURIComponent(base64Encode(filePath));
                  // 生成预览的 URL
                  var previewUrl = 'http://localhost:8012/onlinePreview?url=' + encodedUrl;
                  // 动态插入 iframe 用于文件预览
                  document.write('<iframe lay-src="' + previewUrl + '" width="100%" height="600px"></iframe>');
              </script>
                    </div>
                </div>
            </div>
        </div>
    </div>
    
</body>
</html>
启动项目

完成上述步骤后,启动Spring Boot项目,访问 http://localhost:8080,即可体验文件在线预览功能





Java技术前沿
专注分享Java技术,包括但不限于 SpringBoot,SpringCloud,Docker,消息中间件等。
 最新文章