Snapan项目--预览文件梳理

整体思路

前端触发预览请求--->后端生成安全预览链接--->前端按文件类型适配渲染--->关闭预览

触发预览、前端请求预览链接

功能说明

用户点击文件预览按钮后,前端展示预览模态框并初始化加载状态。

核心代码【home.html】

动态生成文件列表时:

java 复制代码
 <div class="action-item" onclick="previewFile('${file.filePath}')" data-tooltip="预览"><button class="action-icon preview">👁️</button></div>
html 复制代码
    <!-- 预览模态框 -->
    <div id="previewModal" class="modal" style="display: none;">
        <div class="modal-content" style="width: 90%; max-width: 1200px; height: 90vh; max-height: 900px; padding: 0;">
            <div class="modal-header">
                <h3 id="previewModalTitle">文件预览</h3>
                <span class="close" onclick="closePreviewModal()">&times;</span>
            </div>
            <!-- 预览容器(放在模态框内) -->
            <div id="previewContainer" style="width: 100%; height: calc(100% - 60px); border: none; border-radius: 0;">
                <div class="loading">
                    <span>正在生成预览链接,请稍候...</span>
                </div>
            </div>
        </div>
    </div>

前端调用/api/filebase/getPreviewUrl接口,传递文件路径参数,处理成功/失败响应;

java 复制代码
        // 1. 预览函数(点击预览按钮时调用)
        function previewFile(filePath) {
            // 显示预览模态框
            document.getElementById("previewModal").style.display = "flex";
            // 更新模态框标题(显示当前预览文件名)
            document.getElementById("previewModalTitle").textContent = `文件预览:${filePath.split("/").pop()}`;
            // 调用后端接口获取签名预览URL(使用你的项目基础路径)
            const baseUrl = "http://localhost:8080"; // 与你的后端地址一致
            $.ajax({
                url: `${baseUrl}/api/filebase/getPreviewUrl`,
                type: "GET",
                data: { filePath: filePath }, // 传递当前文件的OSS路径
                dataType: "json",
                success: function (res) {
                    if (res.code === "200") {
                        const previewUrl = res.previewUrl;
                        const fileType = res.fileType;
                        // 渲染预览内容
                        renderPreview(previewUrl, fileType);
                        console.log(filePath,fileType,previewUrl);
                    } else {
                        const container = document.getElementById("previewContainer");
                        container.innerHTML = `
                    <div class="error">
                        <span>${res.msg || "生成预览链接失败,请重试"}</span>
                    </div>
                `;
                    }
                },
                error: function (xhr, status, error) {
                    const container = document.getElementById("previewContainer");
                    container.innerHTML = `
                <div class="error">
                    <span>预览接口访问失败:${error}</span>
                    <br>
                    <span style="margin-top:5px;display:inline-block;">请检查接口地址是否正确,或是否存在跨域问题</span>
                </div>
            `;
                }
            });
        }

后端生成预览链接、前端渲染预览内容

功能说明

后端接收文件路径,处理oss域名,生成带有有效期的签名预览url,并提取文件类型。

核心代码

java 复制代码
 /**
     * 获取文件预览的签名url
     * @param filePath oss文件中的路径
     * @return 包含url和文件类型的json
     */
    @GetMapping("/getPreviewUrl")
    @ResponseBody
    public Map<String,String> getPreviewUrl(String filePath){
        Map<String,String> result=new HashMap<>();
        try{
            //生成1小时有效的签名url
            // 手动去除URL中的域名,只保留相对路径
            String ossDomain = "https://snapan.oss-cn-beijing.aliyuncs.com/";
            if (filePath.startsWith(ossDomain)) {
                filePath = filePath.substring(ossDomain.length()); // 截取后得到 "1761277048722.jpg"
            }
            // 生成签名URL(此时filePath是相对路径)
            String previewUrl=ossService.generatePreviewUrl(filePath,3600);
            System.out.println(previewUrl);
            System.out.println(filePath);
            //获取文件后缀
            String fileType=getFileSuffix(filePath);
            result.put("code","200");
            result.put("previewUrl",previewUrl);
            result.put("fileType",fileType);
        }catch (Exception e){
            result.put("code","500");
            result.put("msg","生成预览链接失败:"+e.getMessage());
        }
        return result;
    }

    //提取文件后缀
    private String getFileSuffix(String fileName){
        if(fileName.lastIndexOf(".")==-1){
            return "";//无后缀
        }
        return fileName.substring(fileName.lastIndexOf(".")+1).toLowerCase();
    }
java 复制代码
/**
     * 生成带签名的预览URL(用于在线预览)
     */
    public String generatePreviewUrl(String fileName, int expirationMinutes) {
        try {
            // 设置URL过期时间
            java.util.Date expiration = new java.util.Date();
            long expTimeMillis = expiration.getTime() + expirationMinutes * 60 * 1000;
            expiration.setTime(expTimeMillis);
            //创建响应头配置对象,控制浏览器是否预览
            ResponseHeaderOverrides headers=new ResponseHeaderOverrides();
            // 判断文件是否为PDF或Office文件,强制预览
            String lowerFileName = fileName.toLowerCase();
            if (lowerFileName.endsWith(".pdf") ||
                    lowerFileName.endsWith(".doc") ||
                    lowerFileName.endsWith(".docx") ||
                    lowerFileName.endsWith(".xls") ||
                    lowerFileName.endsWith(".xlsx") ||
                    lowerFileName.endsWith(".ppt") ||
                    lowerFileName.endsWith(".pptx")) {

                String fileNameOnly = fileName.substring(fileName.lastIndexOf("/") + 1);
                // 设置Content-Disposition为inline,允许浏览器预览
                headers.setContentDisposition("inline;filename=\"" + fileNameOnly + "\"");

            }
            //生成带响应头配置的签名url
            com.aliyun.oss.model.GeneratePresignedUrlRequest request=
                    new com.aliyun.oss.model.GeneratePresignedUrlRequest(bucketName,fileName);
            request.setExpiration(expiration);
            request.setMethod(HttpMethod.GET);
            request.setResponseHeaders(headers);
            // 生成带签名的URL,用于预览(GET请求)
            java.net.URL signedUrl = ossClient.generatePresignedUrl(request);
            String urlStr=signedUrl.toString();
            return urlStr;
        } catch (Exception e) {
            throw new RuntimeException("生成预览URL失败", e);
        }
    }

前端根据文件类型(图片、视频、Office 文档等),生成对应的 HTML 标签渲染预览内容。

java 复制代码
  /**
         * 4. 根据文件类型渲染不同的预览组件
         * @param {string} url - 后端返回的签名预览URL
         * @param {string} type - 后端返回的文件后缀(小写)
         */
        function renderPreview(url, type) {
            const container = document.getElementById("previewContainer");
            container.innerHTML = ""; // 清空加载状态

            switch (type) {
                // 图片类型
                case "jpg": case "jpeg": case "png": case "gif": case "webp": case "bmp":
                    container.innerHTML = `<img src="${url}" alt="图片预览" title="点击可查看原图" style="width:100%;height:100%;object-fit:contain;">`;
                    break;
                // PDF类型
                /*case "pdf":
                    container.innerHTML = `<iframe src="${url}" title="PDF预览" style="width:100%;height:100%;border:none;"></iframe>`;
                    break;*/
                // 视频类型
                case "mp4":
                case "webm":
                case "mov":
                case "avi":
                case "mkv":
                    container.innerHTML = `
                <video controls autoplay muted playsinline style="width:100%;height:100%;padding:20px;">
                    <source src="${url}" type="video/${type}">
                    您的浏览器不支持该视频格式,请下载后查看
                </video>
            `;
                    break;
                // 音频类型
                case "mp3": case "wav": case "ogg": case "flac":
                    container.innerHTML = `
                <audio controls autoplay style="width:100%;padding:20px;">
                    <source src="${url}" type="audio/${type}">
                    您的浏览器不支持该音频格式,请下载后查看
                 </audio>
            `;
                    break;
                // Office文档(依赖kkFileView)
                case "doc": case "docx": case "xls": case "xlsx": case "ppt": case "pptx":case "pdf":
                case "txt": case "md": case "json": case "xml": case "html": case "css": case "js":
                case "xmind":case "vsd":case "py":case "eml":case "psd":case "csv":
                    const kkFileViewUrl = "http://101.42.40.144:8012";
                    //const encodedUrl = encodeURIComponent(Base64.encode(url));
                    const encodedUrl = encodeURIComponent(Base64.encode(url).replace(/[\r\n]/g, '')).replace(/%0A/g, '');
                    const previewUrl = `${kkFileViewUrl}/onlinePreview?url=${encodedUrl}`;
                    console.log("office预览地址:",previewUrl);
                    container.innerHTML = `
                    <iframe
                        src="${previewUrl}"
                        title="Office文档预览"
                        style="width:100%;height:100%;border:none;"
                       onload="document.getElementById('officeLoading').style.display='none';this.style.display='block'">
                    </iframe>
                `;
                    break;
                // 其他类型
                default:
                    showDownloadTip(container, url, type);
            }
        }


        function showDownloadTip(container, url, fileType) {
            container.innerHTML = `
        <div class="download-tip" style="position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);text-align:center;">
            <span>当前文件类型(.${fileType || "未知"})不支持在线预览</span>
            <br>
        </div>
    `;
        }
  • 图片直接用img标签,通过object-fit:contain保持比例;
  • 视频 / 音频使用原生video/audio标签,支持控制、自动播放(视频默认静音);
  • Office 文档等通过iframe嵌入第三方服务(kkFileView),需对 URL 进行 Base64 编码避免格式错误;

关闭预览

java 复制代码
// 2. 新增:关闭预览模态框
        function closePreviewModal() {
            document.getElementById("previewModal").style.display = "none";
            // 清空预览容器,避免下次打开有残留
            document.getElementById("previewContainer").innerHTML = `
        <div class="loading">
            <span>正在生成预览链接,请稍候...</span>
        </div>
    `;
        }

关于kkfileview安装及使用

参考了以下博客,感谢这位大佬

https://blog.csdn.net/qq_33697094/article/details/126076565?fromshare=blogdetail&sharetype=blogdetail&sharerId=126076565&sharerefer=PC&sharesource=2301_80391652&sharefrom=from_link

相关推荐
无敌最俊朗@2 天前
后端开发 DTO-Entity-VO 转换模式详解
状态模式
qq_420362033 天前
PDF导出服务
前端·pdf·状态模式·node·puppeteer
芙蓉王真的好14 天前
NestJS API 提示信息规范:让日志与前端提示保持一致的方法
前端·状态模式
我血条子呢4 天前
【预览PDF】前端预览pdf
前端·pdf·状态模式
紫荆鱼6 天前
设计模式-状态模式(State)
c++·后端·设计模式·状态模式
尘似鹤7 天前
c++注意点(15)----状态模式
状态模式
shi57837 天前
设计模式之 状态机 C#范例
设计模式·状态模式
顾漂亮9 天前
Token快过期的三种续期方案
java·spring·状态模式
造价女工9 天前
视频监控系统原理与计量
网络·音视频·状态模式·消防·工程造价