杰瑞科技汇

Java如何实现在线预览Excel文件?

核心流程

无论采用哪种方案,基本流程都大同小异:

Java如何实现在线预览Excel文件?-图1
(图片来源网络,侵删)
  1. 接收文件:通过 Web 接口(如 Spring Boot 的 MultipartFile)接收用户上传的 Excel 文件。
  2. 读取并解析:使用 Java 库(如 Apache POI)读取 Excel 文件内容。
  3. 格式转换:将解析出的内容转换为目标预览格式(如 HTML)。
  4. 前端展示:将转换后的 HTML 返回给前端,前端通过 <iframe> 或直接渲染来展示预览。

使用 Apache POI + 自定义 HTML 转换(最灵活)

这是最核心也是最灵活的方法,你可以完全控制 HTML 的输出样式,但需要自己编写转换逻辑。

添加依赖

在你的 pom.xml 文件中添加 Apache POI 的依赖,建议使用较新的版本以获得更好的性能和兼容性。

<dependencies>
    <!-- Spring Boot Web Starter (用于构建Web服务) -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- Apache POI for Excel -->
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi</artifactId>
        <version>5.2.5</version>
    </dependency>
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-ooxml</artifactId>
        <version>5.2.5</version>
    </dependency>
    <!-- Lombok (简化代码) -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

编写转换工具类

这个工具类负责将 Excel 的 Sheet 转换为 HTML 字符串。

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.Iterator;
public class ExcelToHtmlConverter {
    public static String convert(MultipartFile file) throws IOException {
        // 1. 创建 Workbook 对象
        try (Workbook workbook = WorkbookFactory.create(file.getInputStream())) {
            StringBuilder html = new StringBuilder();
            // 2. 开始构建 HTML
            html.append("<html><head><meta charset=\"UTF-8\"><style>")
                .append("table { border-collapse: collapse; width: 100%; }")
                .append("th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }")
                .append("th { background-color: #f2f2f2; }")
                .append("</style></head><body>")
                .append("<table>");
            // 3. 遍历所有 Sheet
            for (int i = 0; i < workbook.getNumberOfSheets(); i++) {
                Sheet sheet = workbook.getSheetAt(i);
                if (i > 0) {
                    html.append("<br/>"); // 多个 sheet 之间用换行分隔
                }
                // 4. 获取行迭代器
                Iterator<Row> rowIterator = sheet.iterator();
                while (rowIterator.hasNext()) {
                    Row row = rowIterator.next();
                    html.append("<tr>");
                    // 5. 遍历单元格
                    Iterator<Cell> cellIterator = row.iterator();
                    while (cellIterator.hasNext()) {
                        Cell cell = cellIterator.next();
                        // 设置单元格标签 (th 或 td)
                        String cellTag = row.getRowNum() == 0 ? "th" : "td";
                        html.append("<").append(cellTag).append(">");
                        // 获取单元格值
                        switch (cell.getCellType()) {
                            case STRING:
                                html.append(cell.getStringCellValue());
                                break;
                            case NUMERIC:
                                if (DateUtil.isCellDateFormatted(cell)) {
                                    html.append(cell.getDateCellValue());
                                } else {
                                    html.append(cell.getNumericCellValue());
                                }
                                break;
                            case BOOLEAN:
                                html.append(cell.getBooleanCellValue());
                                break;
                            case FORMULA:
                                html.append(cell.getCellFormula());
                                break;
                            default:
                                html.append("");
                        }
                        html.append("</").append(cellTag).append(">");
                    }
                    html.append("</tr>");
                }
            }
            // 6. 结束 HTML
            html.append("</table></body></html>");
            return html.toString();
        }
    }
}

创建 Controller

创建一个 Spring Boot Controller 来处理文件上传和转换。

Java如何实现在线预览Excel文件?-图2
(图片来源网络,侵删)
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
@RestController
@RequestMapping("/api/excel")
public class ExcelPreviewController {
    @PostMapping("/preview")
    public ResponseEntity<String> previewExcel(@RequestParam("file") MultipartFile file) {
        if (file.isEmpty()) {
            return ResponseEntity.badRequest().body("请上传一个有效的文件");
        }
        try {
            // 1. 调用转换工具类
            String htmlContent = ExcelToHtmlConverter.convert(file);
            // 2. 设置响应头,告诉浏览器返回的是 HTML
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.TEXT_HTML);
            headers.setContentLength(htmlContent.getBytes(StandardCharsets.UTF_8).length);
            // 3. 返回转换后的 HTML
            return ResponseEntity.ok()
                    .headers(headers)
                    .body(htmlContent);
        } catch (IOException e) {
            e.printStackTrace();
            return ResponseEntity.internalServerError().body("文件处理失败: " + e.getMessage());
        } catch (Exception e) {
            e.printStackTrace();
            return ResponseEntity.internalServerError().body("发生未知错误: " + e.getMessage());
        }
    }
}

前端调用

前端可以使用一个简单的表单来上传文件,并用 <iframe> 来展示返回的 HTML。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">Excel 在线预览</title>
</head>
<body>
    <h1>上传 Excel 文件进行预览</h1>
    <form id="uploadForm" enctype="multipart/form-data">
        <input type="file" id="fileInput" name="file" accept=".xls,.xlsx" />
        <button type="submit">预览</button>
    </form>
    <hr>
    <h2>预览区域</h2>
    <iframe id="previewFrame" style="width: 100%; height: 600px; border: 1px solid #ccc;"></iframe>
    <script>
        document.getElementById('uploadForm').addEventListener('submit', function(e) {
            e.preventDefault();
            const fileInput = document.getElementById('fileInput');
            const file = fileInput.files[0];
            const previewFrame = document.getElementById('previewFrame');
            if (!file) {
                alert('请先选择一个文件!');
                return;
            }
            const formData = new FormData();
            formData.append('file', file);
            // 使用 fetch API 发送文件
            fetch('/api/excel/preview', {
                method: 'POST',
                body: formData
            })
            .then(response => response.text())
            .then(html => {
                // 将返回的 HTML 写入 iframe
                const blob = new Blob([html], { type: 'text/html' });
                previewFrame.src = URL.createObjectURL(blob);
            })
            .catch(error => {
                console.error('Error:', error);
                alert('预览失败,请检查控制台。');
            });
        });
    </script>
</body>
</html>

使用现成的库(更简单,但依赖第三方)

如果你不想自己写转换逻辑,可以使用一些现成的 Java 库来完成这个任务。

推荐库:easypoi

EasyPoi 是一个功能强大的 Excel 导入导出工具,它内置了将 Excel 转换为 HTML 的功能。

添加依赖

<dependencies>
    <!-- Spring Boot Web Starter -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- EasyPoi 核心包 -->
    <dependency>
        <groupId>cn.afterturn</groupId>
        <artifactId>easypoi-base</artifactId>
        <version>4.4.0</version>
    </dependency>
    <dependency>
        <groupId>cn.afterturn</groupId>
        <artifactId>easypoi-web</artifactId>
        <version>4.4.0</version>
    </dependency>
    <dependency>
        <groupId>cn.afterturn</groupId>
        <artifactId>easypoi-annotation</artifactId>
        <version>4.4.0</version>
    </dependency>
</dependencies>

编写 Controller

使用 EasyPoi 的 ExcelToHtmlUtil 非常简单。

import cn.afterturn.easypoi.excel.ExcelToHtmlUtil;
import cn.afterturn.easypoi.excel.entity.ImportParams;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
@RestController
@RequestMapping("/api/excel")
public class ExcelPreviewControllerEasyPoi {
    @PostMapping("/preview-easypoi")
    public ResponseEntity<String> previewExcelWithEasyPoi(@RequestParam("file") MultipartFile file) {
        if (file.isEmpty()) {
            return ResponseEntity.badRequest().body("请上传一个有效的文件");
        }
        try {
            // 1. 设置导入参数
            ImportParams params = new ImportParams();
            params.setTitleRows(1); // 标题行数
            params.setHeadRows(1);  // 表头行数
            // 2. 使用 EasyPoi 进行转换
            String html = ExcelToHtmlUtil.excelToHtml(file.getInputStream(), params);
            // 3. 返回 HTML
            return ResponseEntity.ok()
                    .contentType(MediaType.TEXT_HTML)
                    .body(html);
        } catch (IOException e) {
            e.printStackTrace();
            return ResponseEntity.internalServerError().body("文件处理失败: " + e.getMessage());
        }
    }
}

优点

  • 极其简单:几行代码就能实现,无需关心底层细节。
  • 样式保留较好:能较好地保留 Excel 的单元格样式(如背景色、字体等)。
  • 功能强大:除了转 HTML,还支持图片、公式等复杂内容。

缺点

  • 依赖第三方:项目引入了新的外部依赖,可能存在版本兼容性或安全风险。
  • 定制性差:如果需要对生成的 HTML 进行深度定制(如添加特定 class、修改 CSS 结构),会比较困难。

纯前端方案(无需后端处理)

Excel 文件已经存在于服务器上(有公开的 URL),可以使用纯前端库来预览,完全无需后端进行转换。

推荐库:SheetJS (xlsx)

这是一个非常流行的 JavaScript 库,可以在浏览器中直接解析和渲染 Excel 文件。

引入库

在 HTML 页面中引入 SheetJS 的 CDN。

<script src="https://cdn.sheetjs.com/xlsx-0.20.1/package/dist/xlsx.full.min.js"></script>

编写前端代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">Excel 在线预览 (纯前端)</title>
    <style>
        #drop-area {
            border: 2px dashed #ccc;
            border-radius: 8px;
            padding: 25px;
            text-align: center;
            font-family: sans-serif;
            margin: 20px;
        }
        #drop-area.highlight {
            border-color: purple;
        }
        #excel-table {
            margin: 20px auto;
            border-collapse: collapse;
        }
        #excel-table th, #excel-table td {
            border: 1px solid #ddd;
            padding: 8px;
        }
        #excel-table th {
            background-color: #f2f2f2;
        }
    </style>
</head>
<body>
    <h1>拖放或点击上传 Excel 文件进行预览</h1>
    <div id="drop-area">
        <p>拖放 Excel 文件到这里,或者 <span id="file-input-label">点击选择文件</span></p>
        <input type="file" id="file-input" accept=".xls,.xlsx" style="display: none;" />
    </div>
    <div id="result-container"></div>
    <script src="https://cdn.sheetjs.com/xlsx-0.20.1/package/dist/xlsx.full.min.js"></script>
    <script>
        const dropArea = document.getElementById('drop-area');
        const fileInput = document.getElementById('file-input');
        const fileInputLabel = document.getElementById('file-input-label');
        const resultContainer = document.getElementById('result-container');
        // 点击标签触发文件选择
        fileInputLabel.addEventListener('click', () => fileInput.click());
        // 监听文件选择
        fileInput.addEventListener('change', handleFiles);
        // 拖放事件
        ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
            dropArea.addEventListener(eventName, preventDefaults, false);
        });
        function preventDefaults(e) {
            e.preventDefault();
            e.stopPropagation();
        }
        ['dragenter', 'dragover'].forEach(eventName => {
            dropArea.addEventListener(eventName, highlight, false);
        });
        ['dragleave', 'drop'].forEach(eventName => {
            dropArea.addEventListener(eventName, unhighlight, false);
        });
        function highlight() {
            dropArea.classList.add('highlight');
        }
        function unhighlight() {
            dropArea.classList.remove('highlight');
        }
        dropArea.addEventListener('drop', handleDrop, false);
        function handleDrop(e) {
            const dt = e.dataTransfer;
            const files = dt.files;
            handleFiles({ target: { files: files } });
        }
        function handleFiles(e) {
            const files = e.target.files;
            if (files.length) {
                const file = files[0];
                const reader = new FileReader();
                reader.onload = function(e) {
                    const data = new Uint8Array(e.target.result);
                    const workbook = XLSX.read(data, { type: 'array' });
                    // 获取第一个 sheet
                    const firstSheetName = workbook.SheetNames[0];
                    const worksheet = workbook.Sheets[firstSheetName];
                    // 将 sheet 转换为 HTML
                    const html = XLSX.utils.sheet_to_html(worksheet);
                    // 显示结果
                    resultContainer.innerHTML = html;
                };
                reader.readAsArrayBuffer(file);
            }
        }
    </script>
</body>
</html>

优点

  • 无后端负担:所有处理都在浏览器完成,减轻了服务器压力。
  • 用户体验好:拖放上传,即时预览,响应迅速。
  • 部署简单:只需要一个静态 HTML 文件即可。

缺点

  • 文件大小限制:浏览器内存有限,大文件(如 50MB+)可能会导致页面卡顿或崩溃。
  • 安全性:直接在浏览器中处理用户文件,虽然现代浏览器有沙箱机制,但仍需注意。
  • 功能限制:对于 Excel 中复杂的公式、宏、图表等支持有限。

方案对比与选择建议

方案 实现复杂度 性能 样式保留 定制性 适用场景
方案一: POI + 自定义 需手动实现 极高 对预览效果有严格要求,需要深度定制样式和结构的场景。
方案二: EasyPoi 较好 快速实现,对样式有一定要求,不想自己编写转换逻辑的项目。
方案三: 纯前端 受限 一般 文件较小,追求极致用户体验,希望减轻服务器负担的场景。

如何选择?

  • 如果你是个人项目或快速原型:推荐 方案二 (EasyPoi),简单快速,效果也不错。
  • 如果你是企业级应用,对预览效果和性能有高要求:推荐 方案一 (POI + 自定义),虽然前期投入大,但可控性最强,可以做出非常完美的预览效果。
  • 如果你的 Excel 文件都较小,且是用户本地预览:推荐 方案三 (纯前端),用户体验最好,也最省心。

希望这个详细的指南能帮助你实现 Java 在线预览 Excel 的功能!

分享:
扫描分享到社交APP
上一篇
下一篇