MuYunFileServer 是一个基于 Quarkus 的轻量文件资产服务,当前提供文件上传、元数据查询、统一查看、下载、删除和健康检查能力。
它适合这类场景:
- 业务系统需要一个独立的文件服务
- 需要基础的多租户隔离
- 需要本地文件系统或
MinIO对象存储 - 需要清晰、可控的
SQLite + JDBC实现,而不是重型基础设施
当前已经支持:
- 单次多文件上传
- 临时文件上传与批量转正
- 整单成功 / 整单失败语义
- 单文件元数据查询、下载和统一查看
- 文件软删
- 定时物理清理
liveness / readiness健康检查local和minio两种存储模式
当前明确不包含:
- 分片上传
- 断点续传
- 列表搜索
- 业务对象引用治理
- 对象存储直传
查看能力当前覆盖 PDF、Office -> PDF、主流图片、纯文本、原始音频和原始视频。Office 预览依赖服务端 LibreOffice。
如果你只是想部署或试用服务,优先使用 GitHub Releases,而不是源码构建。
从 GitHub Releases 下载以下任一文件:
MuYunFileServer-<version>.zipMuYunFileServer-<version>.tar.gz
解压后目录中会包含:
quarkus-app/application.ymlapplication.local.example.ymlapplication.minio.example.ymlcompose.yamlREADME.mdRUN.md
默认配置文件是 application.yml。
如果你使用 release 包,则编辑解压目录里的同名 application.yml。
如果你不想从零开始写配置,可以直接参考:
application.local.example.ymlapplication.minio.example.yml
先准备目录:
mkdir -p var/storage var/tmp var/data然后在 release 目录内启动:
java -jar quarkus-app/quarkus-run.jar健康检查:
curl http://127.0.0.1:8080/q/health/ready期望看到的关键结果是:
{"status":"UP"}上传文件并取回 fileId:
curl -X POST http://127.0.0.1:8080/api/v1/files \
-H 'X-Tenant-Id: tenant-a' \
-H 'X-User-Id: u123' \
-F 'files=@/path/to/contract.pdf'成功响应至少会包含:
{
"success": true,
"data": {
"items": [
{
"id": "01..."
}
]
}
}取统一 view descriptor:
curl http://127.0.0.1:8080/api/v1/files/<fileId>/view \
-H 'X-Tenant-Id: tenant-a' \
-H 'X-User-Id: u123'打开内置 viewer 页面:
open http://127.0.0.1:8080/view/files/<fileId>当前 Docker 交付采用单容器模式,镜像内已包含:
- Quarkus 应用
- viewer 静态资源
LibreOffice- 中文字体
fonts-noto-cjk
推荐构建路径:
./gradlew quarkusBuild -x test
docker build -f src/main/docker/Dockerfile.jvm -t muyun-fileserver:latest .推荐验收路径:
./scripts/docker-verify.sh muyun-fileserver:latest这条路径会验证:
- 容器 readiness
soffice可执行- 文本 viewer
docx -> pdf查看链路
如果你只想快速验证项目,到这里就够了。接口接入、Office 预览和排障说明见后文。
默认配置文件是 application.yml。
推荐第一次先用默认 local 模式验证服务是否可用。
- 准备目录:
mkdir -p var/storage var/tmp var/data- 启动服务:
./gradlew quarkusDev- 健康检查:
curl http://127.0.0.1:8080/q/health/ready- 试传一个文件:
curl -X POST http://127.0.0.1:8080/api/v1/files \
-H 'X-Tenant-Id: tenant-a' \
-H 'X-User-Id: u123' \
-F 'files=@/path/to/contract.pdf'如果仓库根目录下有 demo-files/,可以直接用脚本拉起一个独立的本地 demo 环境,并批量输出每个文件的 /view/public/... 链接:
./scripts/demo-view.sh默认行为:
- 使用独立 demo 数据目录,不影响常规本地库和存储
- 自动打开 token 模式
- 自动使用本机
soffice做 Office PDF 渲染 - 批量上传
demo-files/里的非隐藏文件 - 输出
filename / mimeType / viewerType / viewUrl
如果 demo 目录不在默认位置,可以显式传路径:
./scripts/demo-view.sh /absolute/path/to/demo-files如果本机 soffice 不在 PATH 中,可以显式指定:
SOFFICE_COMMAND=/opt/homebrew/bin/soffice ./scripts/demo-view.sh停止这个 demo 环境:
./scripts/demo-view-stop.sh如果需要实时调试 viewer 前端样式和交互,推荐双终端方式:
终端 A:
./scripts/demo-view.sh终端 B:
cd frontend/viewer
npm run dev然后将 demo-view.sh 输出的链接中的 host 改成 http://127.0.0.1:5173 再打开,例如:
http://127.0.0.1:5173/view/public/files/{fileId}?access_token=...
此时页面始终使用最新前端源码,/api/... 请求会自动代理到本地后端 http://127.0.0.1:8080。
其他常用命令:
运行测试:
./gradlew test构建:
./gradlew build打包后运行:
java -jar build/quarkus-app/quarkus-run.jar默认模式为 local,适合本地开发和单机部署。首次运行最少只需要保证这 3 个路径可写:
mfs:
storage:
type: local
root-dir: ${user.dir}/var/storage
temp-dir: ${user.dir}/var/tmp切换到 MinIO 时,服务仍使用本地临时目录做上传预处理,正式文件写入对象存储。最小配置如下:
mfs:
storage:
type: minio
temp-dir: ${user.dir}/var/tmp
minio:
endpoint: http://127.0.0.1:9000
access-key: minioadmin
secret-key: minioadmin
bucket: muyun-files
auto-create-bucket: true本地启动 MinIO:
docker run -d \
--name muyun-minio \
-p 9000:9000 \
-p 9001:9001 \
-e MINIO_ROOT_USER=minioadmin \
-e MINIO_ROOT_PASSWORD=minioadmin \
-v $(pwd)/.data/minio:/data \
minio/minio:RELEASE.2023-09-04T19-57-37Z \
server /data --console-address ":9001"如果你想直接用环境变量启动服务:
MFS_STORAGE_TYPE=minio \
MFS_STORAGE_TEMP_DIR=$(pwd)/var/tmp \
MFS_STORAGE_MINIO_ENDPOINT=http://127.0.0.1:9000 \
MFS_STORAGE_MINIO_ACCESS_KEY=minioadmin \
MFS_STORAGE_MINIO_SECRET_KEY=minioadmin \
MFS_STORAGE_MINIO_BUCKET=muyun-files \
./gradlew quarkusDevminioadmin / minioadmin 仅用于本地开发示例,不应直接用于生产环境。
Readiness 在两种模式下的行为:
local:检查数据库、正式目录和临时目录minio:检查数据库、临时目录和对象桶可访问性
首次运行通常只需要关注这些配置:
mfs.storage.typemfs.storage.root-dirmfs.storage.temp-dirmfs.database.pathmfs.upload.max-file-size-bytesmfs.viewer.pdf-rendering.enabledmfs.viewer.pdf-rendering.office-enabled
切换到 minio 时,再额外配置:
mfs.storage.minio.endpointmfs.storage.minio.access-keymfs.storage.minio.secret-keymfs.storage.minio.bucketmfs.storage.minio.auto-create-bucket
完整默认值见 application.yml。
文件上传与统一查看的类型支持矩阵由系统内建维护,运行时不需要配置 MIME 白名单。常见 MIME 别名也由系统内部兼容;后续新增文件类型支持时,通常通过升级服务版本获得。
服务端接入默认使用可信身份头模式。调用 /api/v1/files... 接口时需要传:
X-Tenant-IdX-User-IdX-Request-Id可选X-Client-Id可选
浏览器前端不应直接暴露这些身份头。推荐链路是:浏览器 -> 业务网关 / BFF -> MuYunFileServer,由网关、BFF 或受控后端注入身份头。
同一套文件能力支持两种访问模式:
| 能力 | 可信身份头模式 | 短时 token 模式 |
|---|---|---|
| 上传 | POST /api/v1/files |
POST /api/v1/public/files?access_token=... |
| 转正 | POST /api/v1/files/promote |
暂不支持 |
| 元数据 | GET /api/v1/files/{fileId} |
GET /api/v1/public/files/{fileId}?access_token=... |
| 下载 | GET /api/v1/files/{fileId}/download |
GET /api/v1/public/files/{fileId}/download?access_token=... |
| 展示描述 | GET /api/v1/files/{fileId}/view |
GET /api/v1/public/files/{fileId}/view?access_token=... |
| viewer 内容 | GET /api/v1/files/{fileId}/view/content |
GET /api/v1/public/files/{fileId}/view/content/{accessToken} |
| 删除 | DELETE /api/v1/files/{fileId} |
DELETE /api/v1/public/files/{fileId}?access_token=... |
短时 token 模式适合业务后端先完成权限校验,再给前端一个临时上传、查看、下载或删除地址。token 模式默认关闭,需要显式开启 mfs.token.enabled=true。
token 约束:
- 当前只支持
HMAC-SHA256 - 上传 token 至少携带
tenant_id、sub、purpose=upload、exp - 查询 / 下载 / 查看 token 至少携带
tenant_id、file_id、exp - 删除 token 必须单独签发,并携带
purpose=delete - 公开 token 上传支持多文件和
remark,不支持显式file_ids
上传并拿到 fileId:
FILE_ID=$(
curl -s -X POST http://127.0.0.1:8080/api/v1/files \
-H 'X-Tenant-Id: tenant-a' \
-H 'X-User-Id: u123' \
-F 'files=@/path/to/contract.pdf' \
-F 'remark=crm upload' | jq -r '.data.items[0].id'
)
echo "$FILE_ID"查询、下载、删除:
curl http://127.0.0.1:8080/api/v1/files/$FILE_ID \
-H 'X-Tenant-Id: tenant-a' \
-H 'X-User-Id: u123'
curl -OJ http://127.0.0.1:8080/api/v1/files/$FILE_ID/download \
-H 'X-Tenant-Id: tenant-a' \
-H 'X-User-Id: u123'
curl -X DELETE http://127.0.0.1:8080/api/v1/files/$FILE_ID \
-H 'X-Tenant-Id: tenant-a' \
-H 'X-User-Id: u123'临时文件转正:
curl -X POST http://127.0.0.1:8080/api/v1/files/promote \
-H 'X-Tenant-Id: tenant-a' \
-H 'X-User-Id: u123' \
-H 'Content-Type: application/json' \
-d '{"fileIds":["01...","01..."]}'多文件上传采用整单成功 / 整单失败语义,不会返回部分成功结果。当前默认限制是单次最多 10 个文件,单文件最大 500 MB。
前端如果要从响应里读取下载文件名,网关需要转发并暴露这些响应头:
Content-Disposition, Content-Length, Content-Type
错误处理建议按 HTTP 状态码分支,message 用于展示或日志,request_id 用于关联服务端日志。
内置 viewer 页面入口:
GET /view/files/{fileId}GET /view/public/files/{fileId}?access_token=...
viewer 页面支持这些 URL 参数:
| 参数 | 默认值 | 说明 |
|---|---|---|
showHeader |
true |
是否展示 MuYun viewer 自带顶部 header 区域 |
showDownloadButton |
true |
是否展示下载按钮 |
watermark |
空 | 若非空,则在预览主区域叠加重复斜向水印 |
布尔值推荐使用 true/false,同时兼容 on/off、1/0、yes/no。这些参数只作用于 /view/... 页面,不改变 /api/v1/.../view 返回的 descriptor。
当前 viewer 支持:
application/pdf直接 inline 查看doc/docx/xls/xlsx/ppt/pptx/odt/ods/odp转 PDF 后查看- 主流图片直接查看
txt / md / json / xml / csv / log等 UTF-8 文本内联查看- 音频和视频使用浏览器原生能力播放原始媒体流
Office 预览采用 Office -> LibreOffice -> PDF -> PDF.js 链路。首次访问时懒生成 PDF,成功后缓存预览产物。
默认配置已经开启 Office 预览:
mfs:
viewer:
pdf-rendering:
enabled: true
office-enabled: true
renderer: libreoffice
libreoffice:
command: soffice
timeout: 60S
max-concurrency: 1
retry-failure-after: 5M
profile-root: ${user.dir}/var/tmp/libreoffice-profile关键配置:
| 配置 | 说明 |
|---|---|
mfs.viewer.pdf-rendering.enabled |
控制 PDF viewer;关闭后 PDF 和 Office 都不走内置 PDF 预览链路 |
mfs.viewer.pdf-rendering.office-enabled |
控制 Office 转 PDF;关闭后 Office 文件只能下载 |
mfs.viewer.pdf-rendering.libreoffice.command |
soffice 可执行文件名或绝对路径 |
mfs.viewer.pdf-rendering.libreoffice.timeout |
单个 Office 文件转换超时时间 |
mfs.viewer.pdf-rendering.libreoffice.max-concurrency |
单实例同时转换的 Office 文件数 |
mfs.viewer.pdf-rendering.libreoffice.profile-root |
LibreOffice 临时用户配置目录,运行用户必须可写 |
部署注意事项:
- JVM Docker 镜像内已包含
LibreOffice和中文字体fonts-noto-cjk。 - release 包或裸机部署需要自行安装
LibreOffice,并确保服务进程能执行soffice。 - 如果
soffice不在PATH中,把libreoffice.command配成绝对路径。 mfs.storage.temp-dir和profile-root都需要给服务运行用户写权限。- 生产环境先保持
max-concurrency=1,再结合 CPU、内存和转换耗时逐步调大。
上线前验证:
soffice --headless --version
curl http://127.0.0.1:8080/q/health/ready上传一个 docx/xlsx/pptx 文件后访问:
curl http://127.0.0.1:8080/api/v1/files/<fileId>/view \
-H 'X-Tenant-Id: tenant-a' \
-H 'X-User-Id: u123'期望响应中的 viewerType 为 pdf,contentMimeType 为 application/pdf。浏览器打开 http://127.0.0.1:8080/view/files/<fileId> 应能看到转换后的 PDF 预览。
健康检查接口:
GET /q/health/liveGET /q/health/ready
最常见的启动或访问失败,可以先按这个顺序排查:
local模式先看var/storage、var/tmp、var/data是否存在且可写- 检查
mfs.database.path指向的目录是否可写 minio模式先确认docker compose up -d minio已成功启动- 检查
mfs.storage.minio.endpoint、access-key、secret-key、bucket是否匹配 - 若
readiness为DOWN,先看/q/health/ready返回的具体字段 - 若上传失败,优先查看服务日志中的
operation=upload
Office 预览常见错误:
- readiness
DOWN且包含pdfRenderer:检查soffice是否存在、是否可执行、profile-root是否可写 415:通常是office-enabled=false或文件 MIME 不在当前支持列表中503:通常是soffice不可用或路径配置错误504:转换超时,可以先调大timeout,同时排查文件大小、字体缺失或机器负载422:LibreOffice 没有产出有效 PDF,优先在同一台机器上用soffice手工转换该文件复现
关键业务日志已统一为 key=value 风格,便于 grep 和日志平台采集。常见字段包括:
operationresultfile_idtenant_iduser_idrequest_idstorage_providerreason
如果你只是评估或运行服务,到上一节为止已经足够。下面这部分面向仓库维护者和后续开发者。
当前技术栈:
Quarkus RESTSQLite + FlywayJDBC + 手写 SQLStorageProvider抽象- 本地文件系统 /
MinIO Apache TikaTestcontainers
当前实现特点:
- 上传链路已完成第一轮职责拆分
- 默认存储为
local,minio通过配置切换 - 上传在两种模式下都先走本地临时目录
storageKey规则统一为{tenantId}/yyyy/MM/{ulid}
当前测试已覆盖:
- 上传、查询、下载、删除主流程
- 租户不匹配
403 - 缺少身份头
401 - 非法
fileId400 - MIME 拒绝
415 - 显式
file_id冲突409 - 空文件上传
- 超大文件拦截
file_ids部分显式部分自动生成- 下载物理缺失场景
MinIOprovider 的上传、读取、删除和自动建桶验证minio模式下的端到端 Quarkus 集成测试
- 文档导航
- Overview Design
- API Design
- Server Integration Guide
- Technical Solution
- Development Plan
- Risks And Next Steps
- 设计边界已确认
- 主链路已接通
MinIO存储支持已落地- 配置文件已切换为
application.yml - 仓库已配置 GitHub Actions,在
main和 PR 上执行构建与测试
本项目采用 MIT License。