Sign inSign up

muziyuyang/nms-files

By muziyuyang

•Updated 2 months ago

Image
0

108

muziyuyang/nms-files repository overview

⁠nms-files

nms-files 是一个面向个人项目和内部系统的文件中间件服务,设计初衷是“后端服务调用后端服务”。它不直接承担业务权限、用户体系或公网匿名文件站点职责,而是为业务后端提供统一的文件上传、存储、下载、预览、回收站和清理能力。

⁠设计初衷

本项目适合作为内网文件能力后端接入,推荐部署在业务系统可访问的内部网络中。

核心定位:

  • 文件系统负责文件存储、文件元数据、固定链接、下载、预览、回收站和后台清理。
  • 接入方业务系统负责用户登录、业务权限、业务对象关联、审计和对前端暴露的业务接口。
  • 前端不直接调用本服务,不保存、不传递、不暴露 APP_API_KEY。
  • 所有文件访问都通过接口完成,接入方不应依赖或暴露物理文件路径。

不适用场景:

  • 公网匿名文件访问。
  • 浏览器直接持有 API Key 调用文件服务。
  • 多租户、复杂权限协作、独立网盘类系统。

⁠技术栈

  • Python 3.11+
  • FastAPI
  • SQLAlchemy 2.x
  • Alembic
  • MySQL 8.0
  • APScheduler
  • Docker / docker-compose

⁠核心能力

  • API Key 统一鉴权。
  • 文件上传,支持上传大小限制。
  • 基于 SHA-256 的物理文件去重。
  • 文件列表、详情、重命名、下载。
  • 固定链接访问,固定链接仍需要鉴权。
  • 图片、PDF、文本文件预览。
  • 删除到回收站和恢复。
  • 回收站超期清理。
  • 无引用物理文件清理。
  • 文件引用计数修复。
  • 下载和预览支持 Range 请求。

⁠接入方式

推荐接入链路:

浏览器 / 客户端
    -> 接入方业务后端
        -> nms-files

接入方后端需要持有 APP_API_KEY,调用本服务时统一携带:

X-API-Key: <your-api-key>

典型上传流程:

  1. 前端向接入方业务后端提交上传请求。
  2. 接入方业务后端调用 POST /api/files/upload。
  3. 本服务返回文件记录和固定链接。
  4. 接入方保存 id、file_key、fixed_link、display_name、mime_type、size 等字段。
  5. 接入方将文件记录与自己的业务对象绑定。

典型下载流程:

  1. 前端请求接入方业务后端的业务下载接口。
  2. 接入方校验业务权限。
  3. 接入方后端调用本服务下载接口。
  4. 接入方尽量透传响应体和文件响应头。

建议透传的响应头:

  • Content-Type
  • Content-Disposition
  • Content-Length
  • Content-Range
  • Accept-Ranges

⁠配置说明

基于 .env.example 创建 .env:

Copy-Item ".env.example" ".env"

关键配置:

APP_NAME=nms-files
APP_ENV=local
APP_HOST=0.0.0.0
APP_PORT=8000
APP_API_KEY=change-me

MYSQL_HOST=mysql
MYSQL_PORT=3306
MYSQL_DB=nms_files
MYSQL_USER=nms_files
MYSQL_PASSWORD=nms_files

FILE_ROOT=/data/files
MAX_UPLOAD_SIZE_MB=100
RECYCLE_RETENTION_DAYS=180
RECYCLE_CLEANUP_CRON=0 3 * * *
BLOB_CLEANUP_CRON=30 3 * * *
REF_COUNT_REPAIR_CRON=0 4 * * *

说明:

  • APP_API_KEY 是服务间调用密钥,生产环境必须修改。
  • FILE_ROOT 是容器内文件根目录,docker-compose 默认挂载到宿主机 ./data/files。
  • MySQL 数据默认挂载到宿主机 ./data/mysql。
  • 清理任务使用 Asia/Shanghai 时区。

⁠部署与初始化

使用 Docker 启动:

docker-compose up --build

执行数据库迁移:

alembic upgrade head

默认端口:

  • 应用服务:8000
  • MySQL:3306

健康检查:

curl -X GET "http://127.0.0.1:8000/api/system/health" `
  -H "X-API-Key: change-me"

⁠通用接口规则

所有接口都需要鉴权请求头:

X-API-Key: <your-api-key>

固定链接接口也需要鉴权。固定链接是稳定资源地址,不是匿名外链。

常见状态码:

  • 200:请求成功。
  • 206:Range 分段请求成功。
  • 400:请求参数错误,或文件类型不支持预览。
  • 401:API Key 缺失或错误。
  • 404:文件不存在、文件在回收站、或物理文件缺失。
  • 413:上传文件超过大小限制。
  • 416:Range 请求不合法。
  • 500:服务内部错误。

⁠接口清单

模块方法路径说明
系统GET/api/system/health健康检查
文件POST/api/files/upload上传文件
文件GET/api/files查询文件列表
文件GET/api/files/{record_id}查询文件详情
文件PATCH/api/files/{record_id}重命名文件
文件GET/api/files/{record_id}/download按记录 ID 下载文件
文件DELETE/api/files/{record_id}删除到回收站
文件POST/api/files/{record_id}/restore从回收站恢复
文件GET/api/files/key/{file_key}按固定链接标识下载文件
预览GET/api/files/preview/{record_id}预览文件
回收站GET/api/recycle查询回收站列表
运维任务POST/api/tasks/recycle-cleanup手动清理超期回收站记录
运维任务POST/api/tasks/blob-cleanup手动清理无引用物理文件
运维任务POST/api/tasks/ref-count-repair手动修复引用计数

⁠详细请求方式

以下示例假设:

  • 服务地址:http://127.0.0.1:8000
  • API Key:change-me
⁠健康检查
GET /api/system/health
X-API-Key: change-me
curl -X GET "http://127.0.0.1:8000/api/system/health" `
  -H "X-API-Key: change-me"

响应示例:

{
  "status": "ok",
  "database": "ok"
}
⁠上传文件
POST /api/files/upload
X-API-Key: change-me
Content-Type: multipart/form-data

表单字段:

字段类型必填说明
upload_filefile是上传文件
curl -X POST "http://127.0.0.1:8000/api/files/upload" `
  -H "X-API-Key: change-me" `
  -F "upload_file=@D:/tmp/demo.pdf"

响应示例:

{
  "file": {
    "id": 1,
    "file_key": "8d2d4f4b2a7a4e01b4e9c9ed6a9b34c1",
    "fixed_link": "/api/files/key/8d2d4f4b2a7a4e01b4e9c9ed6a9b34c1",
    "original_file_name": "demo.pdf",
    "display_name": "demo.pdf",
    "file_ext": "pdf",
    "size": 1024,
    "mime_type": "application/pdf",
    "is_deleted": false,
    "deleted_at": null,
    "created_at": "2026-05-23T10:00:00",
    "updated_at": "2026-05-23T10:00:00"
  },
  "fixed_link": "/api/files/key/8d2d4f4b2a7a4e01b4e9c9ed6a9b34c1"
}
⁠查询文件列表
GET /api/files?query=&mime_type=&is_deleted=false&page=1&page_size=20
X-API-Key: change-me

查询参数:

参数类型必填默认值说明
querystring否无按展示文件名或原始文件名模糊搜索
mime_typestring否无按 MIME 类型精确过滤
is_deletedboolean否false是否查询回收站文件
pageinteger否1页码,最小为 1
page_sizeinteger否20每页数量,范围 1 到 100
curl -X GET "http://127.0.0.1:8000/api/files?page=1&page_size=20" `
  -H "X-API-Key: change-me"

响应示例:

{
  "items": [
    {
      "id": 1,
      "file_key": "8d2d4f4b2a7a4e01b4e9c9ed6a9b34c1",
      "fixed_link": "/api/files/key/8d2d4f4b2a7a4e01b4e9c9ed6a9b34c1",
      "original_file_name": "demo.pdf",
      "display_name": "demo.pdf",
      "file_ext": "pdf",
      "size": 1024,
      "mime_type": "application/pdf",
      "is_deleted": false,
      "deleted_at": null,
      "created_at": "2026-05-23T10:00:00",
      "updated_at": "2026-05-23T10:00:00"
    }
  ],
  "page": 1,
  "page_size": 20
}
⁠查询文件详情
GET /api/files/{record_id}
X-API-Key: change-me
curl -X GET "http://127.0.0.1:8000/api/files/1" `
  -H "X-API-Key: change-me"

响应为单个文件记录,字段同列表中的 items[]。

⁠重命名文件
PATCH /api/files/{record_id}
X-API-Key: change-me
Content-Type: application/json

请求体:

{
  "display_name": "new-name.pdf"
}
curl -X PATCH "http://127.0.0.1:8000/api/files/1" `
  -H "X-API-Key: change-me" `
  -H "Content-Type: application/json" `
  -d "{\"display_name\":\"new-name.pdf\"}"

说明:

  • 只修改展示名称。
  • 不影响 file_key 和 fixed_link。
  • 回收站文件不可重命名。
⁠下载文件
GET /api/files/{record_id}/download
X-API-Key: change-me
Range: bytes=0-1023

Range 请求头可选。

curl -X GET "http://127.0.0.1:8000/api/files/1/download" `
  -H "X-API-Key: change-me" `
  -o "demo.pdf"

分段下载示例:

curl -X GET "http://127.0.0.1:8000/api/files/1/download" `
  -H "X-API-Key: change-me" `
  -H "Range: bytes=0-1023" `
  -o "demo.part"

说明:

  • 正常下载返回 200。
  • 分段下载返回 206。
  • 回收站文件不可下载。
⁠按固定链接标识下载
GET /api/files/key/{file_key}
X-API-Key: change-me
Range: bytes=0-1023
curl -X GET "http://127.0.0.1:8000/api/files/key/8d2d4f4b2a7a4e01b4e9c9ed6a9b34c1" `
  -H "X-API-Key: change-me" `
  -o "demo.pdf"

说明:

  • fixed_link 字段返回的是相对路径。
  • 接入方可以保存完整访问地址,也可以保存相对路径并按环境拼接域名。
  • 固定链接不是匿名链接,仍需 X-API-Key。
⁠删除到回收站
DELETE /api/files/{record_id}
X-API-Key: change-me
curl -X DELETE "http://127.0.0.1:8000/api/files/1" `
  -H "X-API-Key: change-me"

说明:

  • 删除是软删除,只进入回收站。
  • 物理文件不会立即删除。
  • 超过 RECYCLE_RETENTION_DAYS 后,定时任务会清理回收站记录。
⁠从回收站恢复
POST /api/files/{record_id}/restore
X-API-Key: change-me
curl -X POST "http://127.0.0.1:8000/api/files/1/restore" `
  -H "X-API-Key: change-me"

说明:

  • 恢复后继续使用原来的 file_key 和 fixed_link。
  • 未删除文件调用恢复接口会返回 400。
⁠预览文件
GET /api/files/preview/{record_id}
X-API-Key: change-me
Range: bytes=0-1023

Range 请求头可选。

curl -X GET "http://127.0.0.1:8000/api/files/preview/1" `
  -H "X-API-Key: change-me"

说明:

  • 支持图片、PDF、文本。
  • 返回 Content-Disposition: inline。
  • 不支持的文件类型返回 400。
  • 回收站文件不可预览。
⁠查询回收站列表
GET /api/recycle?query=&mime_type=&page=1&page_size=20
X-API-Key: change-me

查询参数:

参数类型必填默认值说明
querystring否无按展示文件名或原始文件名模糊搜索
mime_typestring否无按 MIME 类型精确过滤
pageinteger否1页码,最小为 1
page_sizeinteger否20每页数量,范围 1 到 100
curl -X GET "http://127.0.0.1:8000/api/recycle?page=1&page_size=20" `
  -H "X-API-Key: change-me"

响应结构同文件列表。

⁠手动清理超期回收站记录
POST /api/tasks/recycle-cleanup
X-API-Key: change-me
curl -X POST "http://127.0.0.1:8000/api/tasks/recycle-cleanup" `
  -H "X-API-Key: change-me"

响应示例:

{
  "result": 3
}

说明:

  • 返回清理的文件记录数量。
  • 清理记录时会递减物理文件引用计数。
  • 引用计数归零的物理文件会被标记为待删除。
⁠手动清理无引用物理文件
POST /api/tasks/blob-cleanup
X-API-Key: change-me
curl -X POST "http://127.0.0.1:8000/api/tasks/blob-cleanup" `
  -H "X-API-Key: change-me"

响应示例:

{
  "result": 1
}

说明:

  • 清理状态为 PENDING_DELETE 且引用计数为 0 的物理文件。
  • 普通业务流程不应频繁调用该接口。
⁠手动修复引用计数
POST /api/tasks/ref-count-repair
X-API-Key: change-me
curl -X POST "http://127.0.0.1:8000/api/tasks/ref-count-repair" `
  -H "X-API-Key: change-me"

响应示例:

{
  "result": 2
}

说明:

  • 根据 file_record 重新计算 file_blob.ref_count。
  • 主要用于运维排查或异常补偿。

⁠数据保存建议

接入方建议至少保存以下字段:

  • id:适合管理接口操作,例如重命名、删除、恢复、详情查询。
  • file_key:稳定文件标识,可用于拼装固定链接。
  • fixed_link:稳定访问路径,适合长期保存。
  • display_name:展示文件名。
  • mime_type:文件类型。
  • size:文件大小,单位为字节。

不建议保存或暴露:

  • 宿主机物理文件路径。
  • 容器内物理文件路径。
  • APP_API_KEY。

⁠运维任务

应用启动时会注册以下定时任务:

  • RECYCLE_CLEANUP_CRON:清理超期回收站记录。
  • BLOB_CLEANUP_CRON:清理无引用物理文件。
  • REF_COUNT_REPAIR_CRON:修复引用计数。

任务接口主要用于手动补偿和排查,不建议纳入普通业务请求链路。

⁠升级与数据持久化

升级或迁移前需要保护:

  • .env
  • ./data/files
  • ./data/mysql

推荐升级步骤:

  1. 备份 .env、文件目录和 MySQL 数据目录。
  2. 更新应用镜像或代码。
  3. 执行 alembic upgrade head。
  4. 重启服务。
  5. 调用 /api/system/health。
  6. 验证历史文件下载和固定链接访问。

Tag summary

Content type

Image

Digest

sha256:5f346ccbe…

Size

64 MB

Last updated

2 months ago

docker pull muziyuyang/nms-files