整体思路
前端触发预览请求--->后端生成安全预览链接--->前端按文件类型适配渲染--->关闭预览
触发预览、前端请求预览链接
功能说明
用户点击文件预览按钮后,前端展示预览模态框并初始化加载状态。
核心代码【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()">×</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安装及使用
参考了以下博客,感谢这位大佬