Skip to content

文件与存储

Zenith Admin 提供统一的文件管理、文件存储配置与业务附件能力。文件上传后由默认存储服务写入后端存储,元数据落库到 managed_files,业务模块通过 business_files 关联文件记录,前端统一使用文件预览组件完成预览、下载与附件展示。


能力总览

能力当前实现
存储后端核心适配层支持 local / oss / s3 / cos / obs / kodo / bos / azure / sftp 9 种 provider
默认存储file_storage_configs.is_default 标记默认服务;上传时只使用启用状态的默认配置
上传安全通过 Magic Bytes 检测真实 MIME 类型,结合系统配置白名单拦截不允许的文件
文件管理上传、分页列表、详情、内容读取、预览、下载、删除、批量删除、批量 ZIP 下载
存储浏览按存储配置与对象键前缀浏览目录,支持列表 / 网格视图
文件统计总数、总大小、类型分布、provider 分布、近 12 个月趋势、上传人 Top、大小区间
业务附件business_files 将文件与业务实体关联,当前业务类型枚举为 announcement
前端预览图片、PDF、音视频、Excel/CSV、Word、Markdown、文本、JSON、SVG、代码、ZIP 等格式由统一组件处理

存储后端

核心实现位于 packages/server/src/lib/file-storage.ts。对象键由 basePath + YYYY/MM/DD + 时间戳-随机串 + 原扩展名 组成,例如 uploads/2026/06/20/1780000000000-ab12cd34.png

流式 I/O

上传与下载全程采用流式传输,不再把整个文件读入内存,避免大文件导致的 OOM:

  • 上传:所有 provider 直接消费请求体的可读流(localpipelineoss/s3/cos 用原生 stream API,obs/kodo/bos/azure/sftp 分别用 putObject(Body:Readable) / putStream / putObject(stream) / uploadStream / put(stream))。
  • 下载:local/oss/s3/azure/kodo 返回原生流;cosgetObjectStreamobsgetObject({ SaveAsStream:true })bos 用预签名 URL + fetchsftp 用独立连接 get(path, PassThrough) 并在流读取完成后再关闭连接。
  • 批量 ZIP 下载逐个文件流式写入压缩包,等待上一个条目处理完再打开下一个远端连接,同一时刻仅持有一个源流。

通用字段:

字段说明
name存储配置名称
provider存储类型:local / oss / s3 / cos / obs / kodo / bos / azure / sftp
statusenableddisabled
is_default / isDefault是否为默认文件服务
base_path / basePath对象键基础路径
remark备注

本地磁盘:local

适用于开发环境、内网部署或单机文件存储。

配置项说明
localRootPath本地存储根目录;相对路径按服务端 process.cwd() 解析

代码默认根目录常量为 storage/local。读取、删除时按 objectKey 拼接实际文件路径。

阿里云 OSS:oss

通过 ali-oss SDK 写入、读取和删除对象。

配置项说明
ossRegionOSS Region
ossEndpointOSS Endpoint
ossBucketBucket
ossAccessKeyIdAccessKey ID
ossAccessKeySecretAccessKey Secret

S3 兼容存储:s3

通过 AWS SDK v3 访问 AWS S3、MinIO、Cloudflare R2 等 S3 兼容服务。

配置项说明
s3RegionRegion
s3Endpoint自定义 Endpoint,可用于兼容存储
s3BucketBucket
s3AccessKeyIdAccess Key ID
s3SecretAccessKeySecret Access Key
s3ForcePathStyle是否启用 path-style 访问,默认 false

腾讯云 COS:cos

通过 cos-nodejs-sdk-v5 访问腾讯云 COS。

配置项说明
cosRegionCOS Region
cosBucketBucket
cosSecretIdSecretId
cosSecretKeySecretKey

华为云 OBS:obs

通过 esdk-obs-nodejs 访问华为云 OBS。

配置项说明
obsEndpointOBS Endpoint
obsBucketBucket
obsAccessKeyIdAccess Key ID
obsSecretAccessKeySecret Access Key

七牛云 Kodo:kodo

通过 qiniu SDK 上传、删除对象;读取时使用 BucketManager.privateDownloadUrl() 生成临时下载地址。

配置项说明
kodoAccessKeyAccess Key
kodoSecretKeySecret Key
kodoBucketBucket
kodoRegion七牛 Zone 名称,可选
kodoEndpoint下载域名,用于生成私有下载 URL

百度云 BOS:bos

通过 @baiducloud/sdk 访问百度云 BOS。

配置项说明
bosEndpointBOS Endpoint
bosBucketBucket
bosAccessKeyIdAccess Key ID
bosSecretAccessKeySecret Access Key

Azure Blob Storage:azure

通过 @azure/storage-blob 访问 Azure Blob Storage。

配置项说明
azureAccountNameStorage Account 名称
azureAccountKeyStorage Account Key
azureContainerNameBlob Container 名称
azureEndpoint自定义服务地址;为空时使用 https://{accountName}.blob.core.windows.net

SFTP:sftp

通过 ssh2-sftp-client 连接远端服务器并写入文件。

配置项说明
sftpHostSFTP 主机
sftpPort端口,默认 22
sftpUsername用户名
sftpPassword密码
sftpPrivateKeySSH 私钥;存在时优先使用私钥登录
sftpRootPath远端根目录
sftpBaseUrl访问 Base URL 字段;文件内容仍统一经 /api/files/{id}/content 读取

默认存储切换

  • 上传时查询 file_storage_configsis_default = truestatus = 'enabled' 的配置。
  • 创建配置时,如果当前没有默认配置且新配置为启用状态,会自动成为默认配置。
  • 默认配置不能被禁用或删除;切换默认服务会先清除其它配置的 is_default
  • managed_files.bucket_name 会快照对象存储的 Bucket / Container 名称,避免后续修改配置导致历史文件无法读取。

密钥脱敏(write-only)

  • 存储配置中的密钥字段(ossAccessKeySecrets3SecretAccessKeycosSecretKeyobsSecretAccessKeykodoSecretKeybosSecretAccessKeyazureAccountKeysftpPasswordsftpPrivateKey不会在列表 / 详情接口中返回
  • 编辑配置时密钥框默认留空,留空表示沿用原密钥,仅在填入新值时才覆盖(write-only)。
  • AccessKeyId / SecretId / AccountName 等标识类字段仍会返回,用于识别当前配置。

对象读写权限(objectAcl)

存储配置支持设置上传对象的读写权限(canned ACL),后续上传的文件将自动带上该权限;default(继承 Bucket)表示不单独指定、跟随 Bucket 权限(上传时不发送 ACL 参数)。简单上传与分片上传(multipart init)均生效。

各 provider 支持矩阵(由 @zenith/sharedFILE_OBJECT_ACL_SUPPORT 统一定义,前端表单选项、Zod 校验、上传链路三处共用):

Providerdefaultprivatepublic-readpublic-read-write实现方式
ossx-oss-object-acl 请求头
s3ACL 参数
cosACL 参数(对象级不支持公共读写)
obsACL 参数
bosx-bce-acl 请求头(对象级不支持公共读写)
local / kodo / azure / sftp不支持对象级 ACL

注意事项:

  • S3:AWS 新建桶默认启用「Bucket owner enforced」(禁用 ACL),带 ACL 上传会报 AccessControlListNotSupported,需先在桶设置中开启 ACL;MinIO / Cloudflare R2 不支持对象 ACL,请保持 default
  • 公共读 / 公共读写存在数据泄露风险,前端表单已作提示,请谨慎选择。
  • 该权限只影响对象在云端的直接访问;管理后台内的预览 / 下载统一经服务端代理(AK 鉴权),不受此设置影响。
  • 更新接口为 partial schema,service 层会按支持矩阵对非法组合兜底回落为 default(如切换 provider 后残留不支持的取值)。

上传安全

上传逻辑位于 packages/server/src/services/files/

Magic Bytes 真实类型校验

上传文件时默认启用真实类型校验:

  1. 读取文件前 4100 字节。
  2. 使用 file-typefileTypeFromBuffer() 识别真实 MIME 类型。
  3. 若无法识别(如纯文本),回退使用上传请求中的 file.type
  4. 将实际 MIME 与允许类型白名单匹配;支持 image/* 这类通配符。
  5. 不匹配时返回 400,提示检测到的 MIME 不在允许列表中。

系统配置项

系统配置种子位于 packages/shared/src/seed-data.ts

Key类型默认值说明
file_upload_validate_typebooleantrue是否启用 Magic Bytes 真实类型校验
file_upload_allowed_typesstringimage/*,video/*,audio/*,application/pdf,text/plain,application/zip,application/x-zip-compressed,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/vnd.openxmlformats-officedocument.presentationml.presentation,application/vnd.ms-excel,application/msword,application/vnd.ms-powerpoint允许上传的 MIME 类型,逗号分隔,支持 **/*type/*
file_upload_max_size_mbnumber0单个文件上传大小上限(MB),0 表示不限制;普通上传与分片上传初始化均会校验
upload_session_ttl_hoursnumber24分片上传会话保留时长(小时);超过仍未完成的会话及其临时分片由定时任务清理

普通上传与分片上传在服务端均按 file_upload_max_size_mb 校验大小;另有运行时请求体限制、反向代理限制等共同生效,全局请求体限制见安全体系

内容读取安全头

GET /api/files/{id}/content 返回文件内容时:

  • Content-Type 使用文件记录的 mimeType,缺省为 application/octet-stream
  • 仅图片、音视频和 PDF 使用 inline;其它 MIME 统一使用 attachment,避免 HTML、SVG、XML、JS 等内容被浏览器直接执行。
  • 响应头包含 X-Content-Type-Options: nosniff
  • 响应头包含 ETagLast-ModifiedCache-Control: private, max-age=3600,支持 If-None-Match / If-Modified-Since 条件请求返回 304
  • locals3 provider 支持 Range 请求并返回 206 Partial Content,用于本地文件和 S3 兼容存储的视频拖动、大文件断点下载;非法 Range 返回 416Content-Range: bytes */{size}。其它 provider 当前仍优雅回退为完整 200 流式响应。

文件管理能力

上传

  • POST /api/files/upload 支持 multipart 多文件上传,权限为 system:file:upload
  • POST /api/files/upload-one 支持单文件上传,使用登录态鉴权。
  • 前端文件管理页使用 XMLHttpRequest 上传,以便展示每个文件的上传进度。
  • 上传成功后写入 managed_files,返回统一访问 URL:/api/files/{id}/content,其中 {id}managed_files.id(UUIDv7 字符串),不是自增数字。

分片上传与断点续传

大文件(前端默认 > 5MB)走分片上传,状态持久化在 upload_sessions / upload_chunks 两张表,支持并发上传、失败重试与断点续传:

  • POST /api/files/upload/init 初始化会话:校验大小上限、快照默认存储与 objectKey,返回 uploadId / chunkSize / totalChunks / received
  • POST /api/files/upload/chunk 上传单个分片(multipart:uploadId / index / chunk);分片以 unique(upload_session_id, index) 幂等记录,天然并发安全。
  • POST /api/files/upload/complete 校验分片完整 + Magic Bytes 类型校验后,按序流式合并并写入存储,落库 managed_files
  • GET /api/files/upload/{uploadId}/status 返回已接收分片序号,用于断点续传。
  • DELETE /api/files/upload/{uploadId} 中止会话并清理临时分片。
  • 存储路径:OSS / S3(含 MinIO / Cloudflare R2 等 S3 兼容)/ COS / OBS / Azure Blob / BOS 已接入云原生 multipart / block upload——分片直传云端、记录 ETag(Azure 记录 blockId),complete 时由云端合并,服务器不暂存整文件;local / sftp / kodo 走本地临时目录暂存 + 流式合并。Kodo 的 Node SDK 不暴露可外部控制的 uploadId / part / etag,暂保留回退路径。
  • 前端工具位于 packages/web/src/utils/chunked-upload.tsuploadId 持久化到 localStorage,刷新或重新选择同一文件可续传。
  • 临时分片(仅暂存路径)存于 packages/server/storage/tmp/uploads/{uploadId}/{index}。完成(complete)或中止(abort)时立即删除会话目录;云原生路径在 abort 时调用云端 AbortMultipartUpload。对中断未完成的会话,由定时任务 cleanupUploadSessions(默认每天 4:30)按 upload_session_ttl_hours(默认 24 小时)清理过期会话、临时分片、孤儿目录并中止残留的云端 multipart(孤儿目录额外做 mtime 校验,避免误删进行中的上传)。

存储连接测试

  • POST /api/file-storage-configs/test 用于测试新增表单中的配置,会写入并删除一个 .zenith-test/*.txt 临时对象,验证写入、路径 / bucket 权限与删除权限。
  • POST /api/file-storage-configs/{id}/test 用于测试已保存配置;编辑模式下密钥字段留空时沿用数据库原密钥(write-only),避免为测试而回显密钥。
  • 前端文件配置页提供列表行「测试」按钮和弹窗内「测试连接」按钮;测试接口开启审计但 recordBody:false,避免密钥进入审计日志。

列表与筛选

GET /api/files 返回分页列表,支持:

  • keyword:匹配文件名、对象键、存储服务名称。
  • provider:存储类型,支持 local / oss / s3 / cos / obs / kodo / bos / azure / sftp
  • fileTypeimage / video / audio / document
  • startTimeendTime:按创建时间筛选,入参使用 YYYY-MM-DD HH:mm:ss

预览与下载

  • 图片通过 Semi Design ImagePreview 预览,并按当前页图片列表支持切换。
  • 非图片预览交给 FilePreviewModal,详见文件预览组件
  • 下载通过 fetchProtectedFile() 拉取 Blob 后触发浏览器下载。
  • 文件详情展示文件名、存储服务、MIME 类型、大小、上传人、对象键、访问链接、上传时间;图片详情会尝试读取分辨率。

删除与批量操作

  • DELETE /api/files/{id} 删除单个文件记录,并同步尝试删除实际存储对象。
  • DELETE /api/files/batch 批量删除文件;单个对象删除失败不会阻断整体数据库记录删除。
  • POST /api/files/batch-download 将选中文件打包为 ZIP 流式响应;读取失败的单个文件会被跳过。

存储浏览

GET /api/files/browse 根据 storageConfigIdpath 浏览存储目录。当前实现基于 managed_files.object_key 前缀构造目录树,不直接枚举远端 Bucket。路径会拒绝 ... 片段,避免目录穿越。

文件统计

GET /api/files/stats 返回:

  • 汇总:文件总数、总大小、图片 / 文档 / 视频 / 音频数量、今日上传、本月上传。
  • 分布:文件类型、存储 provider、文件大小区间。
  • 趋势:近 12 个月每月上传数量。
  • 上传人:按文件数统计 Top 上传人。

业务文件

业务文件由 business_files 表维护文件与业务实体的多态关联。

当前业务类型枚举为:

businessType说明
announcement通知公告附件

能力:

  • GET /api/business-files/{businessType}/{businessId} 查询业务记录附件。
  • DELETE /api/business-files/{businessType}/{businessId}/{fileId} 移除业务附件关联。
  • saveBusinessFiles() 会先删除原有关联,再校验文件存在且属于当前租户,最后按 fileIds 顺序写入新关联。
  • business_files.file_id 外键指向 managed_files.id,文件删除时关联记录级联删除。

通知公告服务也直接使用 business_files 保存和读取公告附件,前端通过 FileAttachment 组件在编辑与只读模式下复用上传、预览、下载与移除能力。


接口一览

文件接口

方法路径权限说明
GET/api/files/{id}/content公开内容读取路由读取文件内容
GET/api/filessystem:file:list文件分页列表
GET/api/files/statssystem:file:list文件统计分析
GET/api/files/browsesystem:file:list按存储配置浏览目录
GET/api/files/{id}system:file:list获取文件详情
GET/api/files/{id}/sheet-previewsystem:file:list获取 Excel / CSV 表格预览数据
POST/api/files/uploadsystem:file:upload上传一个或多个文件
POST/api/files/upload-one登录态上传单个文件
POST/api/files/upload/init登录态初始化分片上传
POST/api/files/upload/chunk登录态上传单个分片
POST/api/files/upload/complete登录态完成并合并分片
GET/api/files/upload/{uploadId}/status登录态查询分片进度(断点续传)
DELETE/api/files/upload/{uploadId}登录态中止分片上传
DELETE/api/files/{id}system:file:delete删除文件
DELETE/api/files/batchsystem:file:delete批量删除文件
POST/api/files/batch-downloadsystem:file:list批量下载 ZIP

存储配置接口

方法路径权限说明
GET/api/file-storage-configssystem:file:config存储配置分页列表
GET/api/file-storage-configs/defaultsystem:file:config获取默认配置
GET/api/file-storage-configs/{id}system:file:config获取配置详情
POST/api/file-storage-configssystem:file:config:create创建配置
PUT/api/file-storage-configs/{id}system:file:config:update更新配置
PUT/api/file-storage-configs/{id}/defaultsystem:file:config:default设为默认
DELETE/api/file-storage-configs/{id}system:file:config:delete删除配置

业务附件接口

方法路径权限说明
GET/api/business-files/{businessType}/{businessId}登录态获取业务附件列表
DELETE/api/business-files/{businessType}/{businessId}/{fileId}system:file:delete移除业务附件关联

数据模型

file_storage_configs

文件存储配置表。

字段说明
id主键(数字 ID,存储配置仍使用自增 ID)
name配置名称
providerfile_storage_provider 枚举:local / oss / s3 / cos / obs / kodo / bos / azure / sftp
statusenabled / disabled
is_default是否默认
base_path对象键基础路径
local_root_path本地存储根目录
oss_regionoss_endpointoss_bucketoss_access_key_idoss_access_key_secretOSS 配置
s3_regions3_endpoints3_buckets3_access_key_ids3_secret_access_keys3_force_path_styleS3 配置
cos_regioncos_bucketcos_secret_idcos_secret_keyCOS 配置
obs_endpointobs_bucketobs_access_key_idobs_secret_access_keyOBS 配置
kodo_access_keykodo_secret_keykodo_bucketkodo_regionkodo_endpointKodo 配置
bos_endpointbos_bucketbos_access_key_idbos_secret_access_keyBOS 配置
azure_account_nameazure_account_keyazure_container_nameazure_endpointAzure Blob 配置
sftp_hostsftp_portsftp_usernamesftp_passwordsftp_private_keysftp_root_pathsftp_base_urlSFTP 配置
remark备注
审计字段created_byupdated_by 等通用审计字段
created_atupdated_at创建与更新时间

managed_files

托管文件记录表。

字段说明
idUUIDv7 主键(应用层生成,对外 URL 使用该值,避免自增 ID 可枚举)
storage_config_id关联 file_storage_configs.id,删除策略为 restrict
storage_name上传时的存储配置名称快照
provider上传时的存储 provider
original_name原始文件名
object_key存储对象键
bucket_nameBucket / Container 快照;localsftp 为空
size文件大小,单位字节
mime_typeMIME 类型
extension扩展名
tenant_id租户 ID
审计字段上传人与更新人等审计字段
created_atupdated_at创建与更新时间

business_files

业务文件关联表。

字段说明
id主键
business_type业务类型枚举,当前为 announcement
business_id业务记录 ID
file_idUUID,关联 managed_files.id,删除文件时级联删除
name业务侧展示名称
category业务分类
sort_order排序值
tenant_id租户 ID
created_at创建时间

唯一约束:business_type + business_id + file_id


前端

文件配置页

文件配置页菜单路径为 /system/file-configs

能力:

  • 按状态、更新时间筛选配置。
  • 创建、编辑、删除、启用 / 禁用配置。
  • 设置默认文件服务。
  • 打开 StorageFileBrowser 浏览指定配置下的文件目录。
  • 表单提供 9 种 provider 配置项。

文件列表页

文件列表页菜单路径为 /system/files

能力:

  • 文件列表 / 网格双视图。
  • 按关键字、存储类型、文件类型、上传时间筛选。
  • 多文件上传与进度展示。
  • 文件预览、下载、详情、复制链接、删除。
  • 批量下载 ZIP、批量删除、取消选择。
  • 统计 Tab 通过 FileStatsPanel 展示文件统计图表。

业务附件组件

FileAttachment 组件提供业务附件能力:

  • edit 模式:上传、删除、进度、数量限制、大小限制。
  • view 模式:只读展示附件。
  • 复用统一文件预览组件和图片预览能力。
  • 上传接口使用 /api/files/upload-one

文件预览组件

统一预览组件为 FilePreviewModal,详细能力见文件预览组件


相关文档

Built with VitePress for local documentation preview.