# 文件预览
**Repository Path**: wb04307201/file-view
## Basic Information
- **Project Name**: 文件预览
- **Description**: 一个 Spring Boot 的轻量级文件在线预览 Starter 组件,通过简单的 Maven 依赖引入即可为项目提供 docx/xlsx/pptx/PDF/BPMN/图片/视频/代码/3D 模型/CAD 等 20+ 种文件格式的在线预览能力。
- **Primary Language**: Java
- **License**: Apache-2.0
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 186
- **Forks**: 52
- **Created**: 2022-11-03
- **Last Updated**: 2026-06-24
## Categories & Tags
**Categories**: Uncategorized
**Tags**: 预览, 文档, Spring, OnlyOffice, minio
## README
# File View 文件预览
> 一个 Spring Boot 的轻量级文件在线预览 Starter 组件,通过简单的 Maven 依赖引入即可为项目提供 docx/xlsx/pptx/PDF/BPMN/图片/视频/代码/3D 模型/CAD 等 20+ 种文件格式的在线预览能力。

[](https://gitee.com/wb04307201/file-view)
[](https://gitee.com/wb04307201/file-view)
[](https://github.com/wb04307201/file-view)
[](https://github.com/wb04307201/file-view)
  
---
## 支持的文件类型
- office文件(docx,xlsx,pptx)
- 业务流程管理文件(bpmn,dmn,cmmn)
- 图片文件
- 视频文件
- 音频文件
- 文档文件(pdf,epub,ofd)
- TIFF图像文件
- 文本文件/代码文件(c, cpp, cs, css, diff, go, graphql, html, ini, java, js, json, kt, less, lua, m, mk, pl, php, phtml, py, pyrepl, r, rb, rs, scss, sh, sql, swift, ts, vb, wasm, xml, yaml, yml)
- Markdown文档文件
- 3D模型文件(3dm,3ds,3mf,amf,bim,brep,dae,fbx,fcstd,gltf,ifc,iges,step,stl,obj,off,ply,wrl)
- 思维导图文件(xmind)
- 压缩文件(zip)
- CAD(dwg,dxf)
## 引入
### 添加Maven依赖
> **1.4.0 及更高版本**:基于 JDK 25 + Spring Boot 4.x(当前分支)
> **1.4.0-sb3**:基于 JDK 17 + Spring Boot 3.x
#### JDK 25 + Spring Boot 4.x(推荐)
```xml
io.github.wb04307201
file-view-spring-boot-starter
1.4.2
```
#### JDK 17 + Spring Boot 3.x
```xml
io.github.wb04307201
file-view-spring-boot-starter
1.4.2-sb3
```
### 配置文件
```yaml
file:
view:
## 以下为默认的处理器,默认无需配置
bpmn:
enable: true
dmn:
enable: true
cmmn:
enable: true
code:
enable: true
epub:
enable: true
image:
enable: true
markdown:
enable: true
pdf:
enable: true
xmind:
enable: true
docx:
enable: true
excel:
enable: true
pptx:
enable: true
o3d:
enable: true
zip:
enable: true
cad:
enable: true
csv:
enable: true
tiff:
enable: true
ofd:
enable: true
## 以下为默认的文件名和处理器匹配规则,默认无需配置
strategies:
- syntaxAndPattern: glob:*.bpmn
serviceName: bpmn
- syntaxAndPattern: glob:*.dmn
serviceName: dmn
- syntaxAndPattern: glob:*.cmmn
serviceName: cmmn
- syntaxAndPattern: glob:*.{c,cpp,cs,css,diff,go,graphql,html,ini,java,js,json,kt,less,lua,m,mk,pl,php,phtml,py,pyrepl,r,rb,rs,scss,sh,sql,swift,ts,vb,wasm,xml,yaml,yml}
serviceName: code
- syntaxAndPattern: glob:*.epub
serviceName: epub
- syntaxAndPattern: glob:*.{jpg,png,bmp,gif,webp,svg,raw,heic,cr2,nef,orf,sr2}
serviceName: image
- syntaxAndPattern: glob:*.md
serviceName: markdown
- syntaxAndPattern: glob:*.pdf
serviceName: pdf
- syntaxAndPattern: glob:*.xmind
serviceName: xmind
- syntaxAndPattern: glob:*.docx
serviceName: docx
- syntaxAndPattern: glob:*.{xlsx,xls}
serviceName: excel
- syntaxAndPattern: glob:*.pptx
serviceName: pptx
- syntaxAndPattern: glob:*.{3dm,3ds,3mf,amf,bim,brep,dae,fbx,fcstd,gltf,ifc,iges,step,stl,obj,off,ply,wrl}
serviceName: o3d
- syntaxAndPattern: glob:*.zip
serviceName: zip
- syntaxAndPattern: glob:*.csv
serviceName: csv
- syntaxAndPattern: glob:*.{tif,tiff}
serviceName: tiff
- syntaxAndPattern: glob:*.ofd
serviceName: ofd
- syntaxAndPattern: glob:*.{dwg,dxf}
serviceName: cad
```
`syntaxAndPattern`通过指定语法(如 glob 或 regex)对文件名进行匹配:
- glob:`*.txt`
- regex:`(.*)\.txt`
## 使用
### 静态资源库
部分文件类型使用内置渲染器,如:pdf、epub、xmind、zip、image、code、markdown、cmmn、dmn、bpmn等
使用的js库资源从jsDelivr加载,如无法从jsDelivr获取资源,可以添加`file-view-static`将js库本地化
```xml
io.github.wb04307201
file-view-static
1.0.0
```
### 访问内置界面进行文件上传和预览
访问 `http://localhost:8080/file/view`
你也可以直接从文件管理器拖动文件到页面任意位置完成上传。


> 所有预览页面右上角均内置悬浮下载按钮,鼠标靠近时淡入显示,点击即可下载原始文件。
### REST API
| 方法 | 路径 | 说明 |
|------|------|------|
| POST | `/file/view/upload` | 上传文件(支持多文件上传,返回 `List` 数组) |
| GET | `/file/view/list` | 获取已上传的文件列表 |
| GET | `/file/view/{id}` | 预览指定 ID 的文件 |
| POST | `/file/view/deleteById` | 删除指定 ID 的文件(请求体 JSON:`{"id": "文件ID"}`) |
| GET | `/wopi/files/{id}` | WOPI 接口:获取文件元信息 |
| GET | `/wopi/files/{id}/contents` | WOPI 接口:获取文件内容 |
### 预览扩展
下面以OnlyOffice为例说明如何扩展预览:
1. 使用docker安装[OnlyOffice文档开发者版](https://api.onlyoffice.com/docs/docs-api/get-started/basic-concepts/)
```bash
docker run --name onlyoffice -i -t -d -p 80:80 -e JWT_ENABLED=false -e ALLOW_PRIVATE_IP_ADDRESS=true onlyoffice/documentserver
```
2. 文件预览渲染器扩展
编写`IView.java`接口的实现`OnlyOfficeView.java`:
```java
package cn.wubo.file.view.test;
import cn.wubo.file.view.preview.IView;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.function.ServerRequest;
import org.springframework.web.servlet.function.ServerResponse;
import java.net.URI;
@Service
public class OnlyOfficeView implements IView {
@Override
public String getServiceName() {
return "onlyoffice";
}
@Override
public ServerResponse handle(ServerRequest request) {
String id = request.pathVariable("id");
return ServerResponse.temporaryRedirect(URI.create(String.format("/onlyoffice.html?id=%s",id))).build();
}
}
```
编写页面`onlyoffice.html`:
```html
onlyoffice
```
3. 修改配置,关闭重复的渲染器,重定义文件匹配规则`application.yml`:
```yaml
file:
view:
docx:
enable: false
excel:
enable: false
pptx:
enable: false
strategies:
- ...
# - syntaxAndPattern: glob:*.docx
# serviceName: docx
# - syntaxAndPattern: glob:*.xlsx
# serviceName: excel
# - syntaxAndPattern: glob:*.pptx
# serviceName: pptx
- syntaxAndPattern: glob:*.{docx,doc,xlsx,xls,pptx,ppt}
serviceName: onlyoffice
```
预览效果如下:

### 文件存储扩展
下面以MinIO为例说明如何扩展文件存储:
1. 使用docker安装MinIO:
```bash
docker run -p 9000:9000 -p 9001:9001 --name minio -e "MINIO_ROOT_USER=ROOTUSER" -e "MINIO_ROOT_PASSWORD=CHANGEME123" quay.io/minio/minio server /data --console-address ":9001"
```
2. 添加MinIO依赖:
```xml
io.minio
minio
8.6.0
```
3. 编写接口`IFileStorage.java`的实现`MinioFileStorageImpl.java`:
```java
package cn.wubo.file.view.test;
import cn.wubo.file.view.exception.LocalFileStorageException;
import cn.wubo.file.view.storage.IFileStorage;
import cn.wubo.file.view.storage.dto.FileStorageInfo;
import cn.wubo.file.view.utils.VersionUtls;
import io.minio.GetObjectArgs;
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import io.minio.RemoveObjectArgs;
import org.springframework.stereotype.Service;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@Service
public class MinioFileStorageImpl implements IFileStorage {
private final MinioClient minioClient;
private static final String BUCKET_NAME = "temp";
private static List fileStorageInfos = new ArrayList<>();
public MinioFileStorageImpl() {
this.minioClient = new MinioClient.Builder()
.endpoint("http://127.0.0.1:9000")
.credentials("ROOTUSER", "CHANGEME123")
.build();
}
@Override
public FileStorageInfo upload(String fileName, byte[] content, String mimeType) {
try {
String id = UUID.randomUUID().toString();
String version = VersionUtls.generateContentVersion(content, id);
Path filePath = Paths.get(version, fileName);
minioClient.putObject(
PutObjectArgs.builder()
.bucket(BUCKET_NAME)
.object(filePath.toString())
.stream(new ByteArrayInputStream(content), content.length, -1)
.contentType(mimeType)
.build()
);
FileStorageInfo fpi = new FileStorageInfo(id, fileName, content.length, mimeType, filePath.toString(), version);
fileStorageInfos.add(fpi);
return fpi;
} catch (NoSuchAlgorithmException | IOException | InvalidKeyException | InvalidResponseException | InsufficientDataException | InternalException | ErrorResponseException | XmlParserException | ServerException e) {
throw new LocalFileStorageException(e.getMessage(), e);
}
}
@Override
public FileStorageInfo findById(String id) {
return fileStorageInfos.stream()
.filter(fpi -> fpi.getId().equals(id))
.findAny()
.orElseThrow(() -> new LocalFileStorageException("File info not found for id: " + id));
}
@Override
public List list() {
return fileStorageInfos;
}
@Override
public byte[] getContentByLocation(String location) {
try {
InputStream is = minioClient.getObject(
GetObjectArgs.builder()
.bucket(BUCKET_NAME)
.object(location)
.build()
);
return is.readAllBytes();
} catch (IOException | InvalidKeyException | InvalidResponseException | NoSuchAlgorithmException |
InsufficientDataException | InternalException | ErrorResponseException | XmlParserException | ServerException e) {
throw new LocalFileStorageException(e.getMessage(), e);
}
}
@Override
public Boolean deleteById(String id) {
FileStorageInfo fsi = findById(id);
if (fsi != null) {
try {
minioClient.removeObject(
RemoveObjectArgs.builder()
.bucket(BUCKET_NAME)
.object(fsi.getLocation())
.build()
);
fileStorageInfos.remove(fsi);
} catch (IOException | InvalidKeyException | InvalidResponseException | NoSuchAlgorithmException |
InsufficientDataException | InternalException | ErrorResponseException | XmlParserException | ServerException e) {
throw new LocalFileStorageException(e.getMessage(), e);
}
}
return true;
}
}
```
### 请求鉴权
默认情况下,`/file/view/*` 和 `/wopi/*` 端点允许未鉴权的访问。如需添加鉴权,只需实现 `IAuth` 接口并注册为 Spring Bean,默认的放行实现将被自动替换。
#### API 鉴权模式(返回 403)
```java
@Service
public class TokenAuth implements IAuth {
@Override
public AuthResult check(HttpServletRequest request, String path) {
String token = request.getHeader("X-Api-Token");
if (!"your-secret-token".equals(token)) {
return AuthResult.deny("无效的 Token");
}
return AuthResult.allow();
}
}
```
#### BFF 鉴权模式(302 重定向到登录页)
```java
@Service
public class BffAuth implements IAuth {
@Override
public AuthResult check(HttpServletRequest request, String path) {
HttpSession session = request.getSession(false);
if (session == null || session.getAttribute("user") == null) {
// 浏览器未登录,重定向到登录页
return AuthResult.redirect("/login?from=" + path);
}
return AuthResult.allow();
}
}
```
#### AuthResult 的三种返回结果
| 返回 | HTTP 行为 | 适用场景 |
|------|----------|---------|
| `AuthResult.allow()` | 放行请求 | 已登录 / 无需鉴权 |
| `AuthResult.deny("原因")` | 403 + JSON `{"error":"原因"}` | API 客户端(前端 fetch、OnlyOffice 回调等) |
| `AuthResult.redirect("/login")` | 302 浏览器重定向 | BFF 模式 — 未登录时跳转到登录页 |
#### 需要鉴权的路径
默认仅对以 `/file/view` 和 `/wopi` 开头的路径进行鉴权。你可以通过 `application.yml` 自定义需要鉴权的路径:
```yaml
file:
view:
auth:
path-patterns:
- /file/view/** # 所有子路径
- /wopi/**
- /api/** # 也保护自定义接口
# - /** # 拦截所有请求
```
支持的路径匹配模式:
| 模式 | 含义 |
|------|------|
| `/file/view` | 精确匹配 |
| `/file/*` | 直接子路径(一级) |
| `/file/**` | 所有递归子路径 |
> **注意**: 如果配置了 `/**`,请确保在 `IAuth` 实现中对登录接口放行,避免重定向死循环。
## 使用的第三方库
| 文件类型 | 第三方库 |
|----------|----------|
| DOCX 文件 | [docx-preview](https://github.com/VolodymyrBaydak/docx-preview) + [JSZip](https://github.com/Stuk/jszip) |
| XLSX 文件 | [SheetJS](https://github.com/SheetJS/sheetjs) |
| PPTX 文件 | [pptxviewjs](https://github.com/nicktomach/pptxviewjs) + [Chart.js](https://github.com/chartjs/Chart.js) |
| 业务流程管理文件 | [bpmn-js](https://github.com/bpmn-io/bpmn-js) / [cmmn-js](https://github.com/bpmn-io/cmmn-js) / [dmn-js](https://github.com/bpmn-io/dmn-js) |
| 图片文件 | [Viewer.js](https://github.com/fengyuanchen/viewerjs) |
| 文档文件(PDF) | [PDFObject](https://github.com/pipwerks/PDFObject) |
| 文档文件(EPUB) | [epub.js](https://github.com/futurepress/epub.js) + [JSZip](https://github.com/Stuk/jszip) |
| 文本文件/代码文件 | [highlight.js](https://github.com/highlightjs/highlight.js) |
| Markdown文档文件 | [Vditor](https://github.com/Vanessa219/vditor) |
| CSV 文件 | [Papa Parse](https://github.com/mholt/PapaParse) |
| 3D 模型文件 | [Online 3D Viewer](https://github.com/kovacsv/Online3DViewer) + [Three.js](https://github.com/mrdoob/three.js) |
| 思维导图文件 | [xmind-embed-viewer](https://github.com/xmindltd/xmind-embed-viewer) |
| 压缩文件 | [JSZip](https://github.com/Stuk/jszip) |
| CAD 文件(DWG/DXF) | [@mlightcad/cad-simple-viewer](https://github.com/mlightcad/cad-viewer) + [Three.js](https://github.com/mrdoob/three.js) |
| TIFF 图像文件 | [UTIF.js](https://github.com/photopea/UTIF.js) + [pako](https://github.com/nodeca/pako) |
| OFD 文件 | [xq-doc-viewer](https://www.npmjs.com/package/xq-doc-viewer) |