Java在线预览案例怎么实操

wen java案例 25

本文目录导读:

Java在线预览案例怎么实操

  1. 技术选型方案
  2. 实操案例1:使用Apache PDFBox实现PDF预览
  3. 实操案例2:使用JODConverter(OpenOffice)实现多格式转换
  4. 前端JavaScript实现(轻量级方案)
  5. 完整实操步骤
  6. 常见问题与优化

我来详细讲解Java在线预览的实操案例,以最常见的PDF、Word、Excel在线预览为例,我会提供完整的实操步骤和代码。

技术选型方案

常用方案对比

方案 优缺点 适用场景
Apache POI + iText 功能强大,但开发量大 需要复杂处理的场景
OpenOffice/LibreOffice 转换格式通用,但需安装软件 多种文档格式转换
Aspose 商业级,效果好 企业级应用
前端JS库 轻量级,无需后端 简单预览需求
阿里巴巴jw-project PDF预览效果好 纯Java解决方案

实操案例1:使用Apache PDFBox实现PDF预览

添加依赖(Maven)

<!-- PDF处理 -->
<dependency>
    <groupId>org.apache.pdfbox</groupId>
    <artifactId>pdfbox</artifactId>
    <version>2.0.27</version>
</dependency>
<!-- 图片处理 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-imaging</artifactId>
    <version>1.0-alpha2</version>
</dependency>

PDF转图片核心代码

import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.rendering.PDFRenderer;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
public class PdfPreviewUtil {
    /**
     * PDF转图片(字节数组)
     */
    public static byte[] pdfToImage(File pdfFile, int pageNumber, int dpi) throws IOException {
        try (PDDocument document = PDDocument.load(pdfFile)) {
            PDFRenderer renderer = new PDFRenderer(document);
            // 渲染指定页面
            BufferedImage image = renderer.renderImageWithDPI(pageNumber, dpi);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ImageIO.write(image, "png", baos);
            return baos.toByteArray();
        }
    }
    /**
     * 将PDF所有页面转为图片(用于分页预览)
     */
    public static List<byte[]> pdfToImages(File pdfFile, int dpi) throws IOException {
        List<byte[]> imageList = new ArrayList<>();
        try (PDDocument document = PDDocument.load(pdfFile)) {
            PDFRenderer renderer = new PDFRenderer(document);
            for (int i = 0; i < document.getNumberOfPages(); i++) {
                BufferedImage image = renderer.renderImageWithDPI(i, dpi);
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                ImageIO.write(image, "png", baos);
                imageList.add(baos.toByteArray());
            }
        }
        return imageList;
    }
}

Spring Boot控制器实现

import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
@Controller
public class PdfPreviewController {
    // 文件存储路径
    private final String filePath = "/data/files/";
    /**
     * 返回PDF预览页面
     */
    @GetMapping("/preview/pdf/{fileId}")
    public String previewPdf(@PathVariable String fileId, Model model) {
        model.addAttribute("fileId", fileId);
        return "pdf-preview"; // 返回模板页面
    }
    /**
     * 获取PDF某一页的图片
     */
    @GetMapping("/api/pdf/{fileId}/page/{pageNum}")
    public ResponseEntity<Resource> getPdfPage(
            @PathVariable String fileId,
            @PathVariable int pageNum,
            @RequestParam(defaultValue = "150") int dpi) {
        try {
            File pdfFile = new File(filePath + fileId + ".pdf");
            if (!pdfFile.exists()) {
                return ResponseEntity.notFound().build();
            }
            byte[] imageBytes = PdfPreviewUtil.pdfToImage(pdfFile, pageNum - 1, dpi);
            ByteArrayResource resource = new ByteArrayResource(imageBytes);
            return ResponseEntity.ok()
                    .contentType(MediaType.IMAGE_PNG)
                    .contentLength(imageBytes.length)
                    .body(resource);
        } catch (Exception e) {
            return ResponseEntity.status(500).build();
        }
    }
    /**
     * 获取PDF总页数
     */
    @GetMapping("/api/pdf/{fileId}/pages")
    public ResponseEntity<Map<String, Object>> getPdfPages(@PathVariable String fileId) {
        try {
            File pdfFile = new File(filePath + fileId + ".pdf");
            if (!pdfFile.exists()) {
                return ResponseEntity.notFound().build();
            }
            try (PDDocument document = PDDocument.load(pdfFile)) {
                Map<String, Object> result = new HashMap<>();
                result.put("totalPages", document.getNumberOfPages());
                return ResponseEntity.ok(result);
            }
        } catch (Exception e) {
            return ResponseEntity.status(500).build();
        }
    }
}

前端预览界面(Thymeleaf模板)

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>PDF在线预览</title>
    <style>
        #pdfContainer {
            display: flex;
            flex-direction: column;
            align-items: center;
        }
        .page-image {
            max-width: 100%;
            margin-bottom: 10px;
            border: 1px solid #ddd;
            box-shadow: 0 0 10px rgba(0,0,0,0.1);
        }
        .controls {
            position: fixed;
            bottom: 0;
            width: 100%;
            background: white;
            padding: 10px;
            text-align: center;
            box-shadow: 0 -2px 5px rgba(0,0,0,0.1);
        }
    </style>
</head>
<body>
    <h1>PDF在线预览</h1>
    <div id="pdfContainer">
        <!-- 动态加载的页面图片 -->
    </div>
    <div class="controls">
        <button onclick="prevPage()">上一页</button>
        <span id="pageInfo">第 <span id="currentPage">1</span> / <span id="totalPages">0</span> 页</span>
        <button onclick="nextPage()">下一页</button>
        <input type="number" id="jumpPage" min="1" placeholder="跳转页">
        <button onclick="jumpToPage()">跳转</button>
    </div>
    <script>
        const fileId = [[${fileId}]];
        let currentPage = 1;
        let totalPages = 0;
        // 获取总页数
        async function getTotalPages() {
            const response = await fetch(`/api/pdf/${fileId}/pages`);
            const data = await response.json();
            totalPages = data.totalPages;
            document.getElementById('totalPages').textContent = totalPages;
        }
        // 加载页面
        async function loadPage(pageNum) {
            const response = await fetch(`/api/pdf/${fileId}/page/${pageNum}?dpi=150`);
            const blob = await response.blob();
            const imageUrl = URL.createObjectURL(blob);
            const container = document.getElementById('pdfContainer');
            container.innerHTML = `
                <img src="${imageUrl}" class="page-image" alt="第${pageNum}页">
            `;
            document.getElementById('currentPage').textContent = pageNum;
        }
        // 上一页
        function prevPage() {
            if (currentPage > 1) {
                currentPage--;
                loadPage(currentPage);
            }
        }
        // 下一页
        function nextPage() {
            if (currentPage < totalPages) {
                currentPage++;
                loadPage(currentPage);
            }
        }
        // 跳转到指定页
        function jumpToPage() {
            const page = parseInt(document.getElementById('jumpPage').value);
            if (page >= 1 && page <= totalPages) {
                currentPage = page;
                loadPage(currentPage);
            }
        }
        // 初始化
        getTotalPages().then(() => loadPage(1));
    </script>
</body>
</html>

实操案例2:使用JODConverter(OpenOffice)实现多格式转换

这种方式可以处理Word、Excel、PPT等多种格式转换为PDF或图片。

安装OpenOffice/LibreOffice

# Ubuntu/Debian
sudo apt-get install libreoffice
# CentOS
sudo yum install libreoffice
# macOS
brew install --cask libreoffice

添加依赖

<!-- JODConverter -->
<dependency>
    <groupId>com.github.jodconverter</groupId>
    <artifactId>jodconverter-spring-boot-starter</artifactId>
    <version>4.4.6</version>
</dependency>
<dependency>
    <groupId>com.github.jodconverter</groupId>
    <artifactId>jodconverter-local</artifactId>
    <version>4.4.6</version>
</dependency>

application.yml配置

jodconverter:
  local:
    enabled: true
    office-home: /usr/lib/libreoffice # 根据实际安装路径修改
    max-tasks-per-process: 10
    port-numbers: 2002
    task-execution-timeout: 60000
    task-queue-timeout: 60000

文件转换服务

import com.github.jodconverter.core.DocumentConverter;
import com.github.jodconverter.core.document.DefaultDocumentFormatRegistry;
import com.github.jodconverter.core.document.DocumentFormat;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
@Service
public class FileConversionService {
    @Autowired
    private DocumentConverter converter;
    /**
     * Word/PPT/Excel转为PDF
     */
    public File convertToPdf(File inputFile) throws Exception {
        // 创建临时文件
        File outputFile = File.createTempFile("converted_", ".pdf");
        try (InputStream inputStream = new FileInputStream(inputFile);
             OutputStream outputStream = new FileOutputStream(outputFile)) {
            // 自动检测文件格式并转换
            DocumentFormat inputFormat = getDocumentFormat(inputFile.getName());
            DocumentFormat outputFormat = DefaultDocumentFormatRegistry.PDF;
            converter.convert(inputStream, inputFormat, outputStream, outputFormat);
        }
        return outputFile;
    }
    /**
     * 根据文件扩展名获取文档格式
     */
    private DocumentFormat getDocumentFormat(String fileName) {
        String extension = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();
        switch (extension) {
            case "doc":
                return DefaultDocumentFormatRegistry.DOC;
            case "docx":
                return DefaultDocumentFormatRegistry.DOCX;
            case "xls":
                return DefaultDocumentFormatRegistry.XLS;
            case "xlsx":
                return DefaultDocumentFormatRegistry.XLSX;
            case "ppt":
                return DefaultDocumentFormatRegistry.PPT;
            case "pptx":
                return DefaultDocumentFormatRegistry.PPTX;
            case "txt":
                return DefaultDocumentFormatRegistry.TXT;
            default:
                throw new IllegalArgumentException("不支持的文件格式: " + extension);
        }
    }
    /**
     * 获取文件预览URL
     */
    public String getPreviewUrl(String fileId, String fileName) {
        // 返回预览API地址
        return "/preview/" + fileId + "?fileName=" + fileName;
    }
}

统一预览控制器

@RestController
@RequestMapping("/preview")
public class UnifiedPreviewController {
    @Autowired
    private FileConversionService conversionService;
    @Autowired
    private PdfPreviewUtil pdfPreviewUtil;
    private final String filePath = "/data/uploads/";
    /**
     * 文档预览(自动转换并预览)
     */
    @GetMapping("/{fileId}")
    public ResponseEntity<?> previewDocument(
            @PathVariable String fileId,
            @RequestParam String fileName) {
        try {
            File inputFile = new File(filePath + fileName);
            if (!inputFile.exists()) {
                return ResponseEntity.notFound().build();
            }
            String extension = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();
            // 如果是PDF直接预览
            if ("pdf".equals(extension)) {
                return previewPdf(fileId, fileName);
            }
            // 其他格式先转为PDF
            File pdfFile = conversionService.convertToPdf(inputFile);
            // 返回转换后的PDF预览URL
            Map<String, Object> result = new HashMap<>();
            result.put("status", "success");
            result.put("previewUrl", "/preview/pdf/" + pdfFile.getName());
            result.put("downloadUrl", "/download/" + fileName);
            result.put("originalName", fileName);
            return ResponseEntity.ok(result);
        } catch (Exception e) {
            return ResponseEntity.status(500).body("预览失败: " + e.getMessage());
        }
    }
    private ResponseEntity<?> previewPdf(String fileId, String fileName) {
        Map<String, Object> result = new HashMap<>();
        result.put("status", "success");
        result.put("previewType", "pdf");
        result.put("pages", "/api/pdf/" + fileId + "/pages");
        result.put("pageUrl", "/api/pdf/" + fileId + "/page/{pageNum}");
        return ResponseEntity.ok(result);
    }
}

前端JavaScript实现(轻量级方案)

如果不想后端处理太复杂,也可以使用纯前端库:

PDF.js实现

<!DOCTYPE html>
<html>
<head>PDF预览 - PDF.js</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.12.313/pdf.min.js"></script>
    <style>
        canvas {
            border: 1px solid #ddd;
            margin: 10px auto;
            display: block;
        }
    </style>
</head>
<body>
    <div class="controls">
        <button onclick="prevPage()">上一页</button>
        <span>第 <span id="currentPage">1</span> / <span id="totalPages">0</span> 页</span>
        <button onclick="nextPage()">下一页</button>
    </div>
    <div id="pdfContainer">
        <canvas id="pdfCanvas"></canvas>
    </div>
    <script>
        let pdfDoc = null;
        let pageNum = 1;
        let pageRendering = false;
        let pageNumPending = null;
        const scale = 1.5;
        const canvas = document.getElementById('pdfCanvas');
        const ctx = canvas.getContext('2d');
        // 加载PDF
        pdfjsLib.getDocument('/api/pdf/sample.pdf').promise.then(function(pdfDoc_) {
            pdfDoc = pdfDoc_;
            document.getElementById('totalPages').textContent = pdfDoc.numPages;
            renderPage(pageNum);
        });
        // 渲染页面
        function renderPage(num) {
            pageRendering = true;
            pdfDoc.getPage(num).then(function(page) {
                const viewport = page.getViewport({scale: scale});
                canvas.height = viewport.height;
                canvas.width = viewport.width;
                const renderContext = {
                    canvasContext: ctx,
                    viewport: viewport
                };
                const renderTask = page.render(renderContext);
                renderTask.promise.then(function() {
                    pageRendering = false;
                    if (pageNumPending !== null) {
                        renderPage(pageNumPending);
                        pageNumPending = null;
                    }
                });
            });
            document.getElementById('currentPage').textContent = num;
        }
        function queueRenderPage(num) {
            if (pageRendering) {
                pageNumPending = num;
            } else {
                renderPage(num);
            }
        }
        function prevPage() {
            if (pageNum <= 1) return;
            pageNum--;
            queueRenderPage(pageNum);
        }
        function nextPage() {
            if (pageNum >= pdfDoc.numPages) return;
            pageNum++;
            queueRenderPage(pageNum);
        }
    </script>
</body>
</html>

使用Viewer.js实现Office文档预览

<!DOCTYPE html>
<html>
<head>Office文档预览</title>
    <script src="https://cdn.jsdelivr.net/npm/docsify/lib/docsify.min.js"></script>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/docsify/themes/vue.css">
</head>
<body>
    <div id="app">文档加载中...</div>
    <script>
        // 使用微软Office Online预览
        function previewOfficeDoc(url, type) {
            if (type === 'docx' || type === 'xlsx' || type === 'pptx') {
                // 使用微软Office Online Viewer
                const viewerUrl = `https://view.officeapps.live.com/op/embed.aspx?src=${encodeURIComponent(url)}`;
                window.open(viewerUrl, '_blank');
            } else if (type === 'pdf') {
                // 使用PDF.js
                window.open('/pdf-viewer?file=' + encodeURIComponent(url), '_blank');
            }
        }
    </script>
</body>
</html>

完整实操步骤

步骤1:创建Spring Boot项目

# 使用Spring Initializr或手动创建
mvn archetype:generate -DgroupId=com.example -DartifactId=file-preview -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false

步骤2:添加依赖(pom.xml)

<!-- 基础依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- PDF处理 -->
<dependency>
    <groupId>org.apache.pdfbox</groupId>
    <artifactId>pdfbox</artifactId>
    <version>2.0.27</version>
</dependency>
<!-- 文档转换 -->
<dependency>
    <groupId>com.github.jodconverter</groupId>
    <artifactId>jodconverter-spring-boot-starter</artifactId>
    <version>4.4.6</version>
</dependency>
<dependency>
    <groupId>com.github.jodconverter</groupId>
    <artifactId>jodconverter-local</artifactId>
    <version>4.4.6</version>
</dependency>

步骤3:核心配置

server:
  port: 8080
spring:
  servlet:
    multipart:
      enabled: true
      max-file-size: 50MB
      max-request-size: 50MB
file:
  upload-dir: ./uploads
  preview-cache: ./preview-cache
jodconverter:
  local:
    enabled: true
    office-home: /usr/lib/libreoffice

步骤4:启动测试

# 1. 启动Spring Boot应用
mvn spring-boot:run
# 2. 上传文件测试
curl -X POST -F "file=@/path/to/test.pdf" http://localhost:8080/upload
# 3. 访问预览页面
http://localhost:8080/preview/pdf/{fileId}

常见问题与优化

性能优化

// 使用缓存避免重复转换
@Cacheable(value = "previewCache", key = "#fileId")
public byte[] getPreviewImage(String fileId, int pageNum) {
    // ... 转换逻辑
}
// 异步转换
@Async
public CompletableFuture<File> convertAsync(File inputFile) {
    File pdfFile = convertToPdf(inputFile);
    return CompletableFuture.completedFuture(pdfFile);
}

安全考虑

// 文件路径安全
public String validateFilePath(String fileName) {
    // 防止路径穿越攻击
    if (fileName.contains("..") || fileName.contains("/")) {
        throw new SecurityException("非法文件名");
    }
    return fileName;
}
// 预览权限控制
@PreAuthorize("hasPermission(#fileId, 'preview')")
public ResponseEntity<?> previewDocument(String fileId) {
    // ... 实现
}

大文件处理

// 限制转换页数
public byte[] pdfToImage(File pdfFile, int maxPages) throws IOException {
    try (PDDocument document = PDDocument.load(pdfFile)) {
        int pagesToRender = Math.min(document.getNumberOfPages(), maxPages);
        // 只渲染前几页
    }
}
// 流式处理
public void streamPreview(File file, HttpServletResponse response) {
    response.setContentType("application/pdf");
    try (InputStream is = new FileInputStream(file);
         OutputStream os = response.getOutputStream()) {
        IOUtils.copy(is, os);
    }
}

在线预览的核心思路是:

  1. 后端解析:将文档转换为前端可显示格式(图片/PDF)
  2. 前端展示:使用HTML/CSS/JavaScript渲染显示
  3. 分页加载:按需加载,避免一次性加载大文件

选择方案时考虑:

  • 简单场景:使用PDF.js或前端JS库
  • 企业级场景:使用OpenOffice/LibreOffice转换
  • 高性能场景:使用Aspose等商业库

记得添加缓存机制和并发控制,特别是对于频繁预览的文档!

抱歉,评论功能暂时关闭!