FileCodeBox 文件上传完全指南:普通上传、分片上传与断点续传实战

原创2026-09-15 22:43:501,648 阅读
文章标签:后端存储MCP 服务

FileCodeBox 文件上传完全指南:普通上传、分片上传与断点续传实战

FileCodeBox 是开源的匿名口令文件分享服务("像取快递一样取文件"),本文以其 上传指南 为主体,结合仓库源码深入讲解其完整的文件上传体系:网页端的拖拽 / 点击 / 粘贴三种交互方式、普通上传与文本分享的 REST API、面向大文件的分片上传与断点续传全流程、上传相关的全部配置项、限流与错误处理机制。读完本文,你将能独立完成从"修改上传限制"到"基于 API 接入分片上传"的完整实战。

一、上传方式总览

FileCodeBox 把"分享文件/文本"作为核心体验,前端提供三种上传入口,覆盖绝大多数使用场景:

  1. 拖拽上传:打开 FileCodeBox 首页,把文件从系统文件管理器直接拖入上传区域,松开鼠标即开始上传(支持同时拖拽多个文件,具体取决于当前主题)。
  2. 点击上传:点击上传区域的「选择文件」按钮,通过系统文件选择器选取文件,确认后即开始上传。
  3. 粘贴上传:先把图片复制到剪贴板(截图或复制图片),在上传区域按 Ctrl+V(Windows/Linux)或 Cmd+V(macOS)粘贴,图片自动开始上传。注意:粘贴上传仅支持图片格式,且是否支持取决于所用主题。

无论走哪种方式,上传完成后页面都会返回一个提取码(取件码),分享对象凭该口令即可取回文件。提取码的生成策略由配置项 code_generate_type 决定(默认 secret 为随机字符串,可改为数字型),对应实现位于 apps/base/utils.py 的 get_random_code。

二、上传大小限制与配置

2.1 默认限制与单位换算

配置项 默认值 说明
upload_size 10MB(10485760 字节) 单文件最大上传大小

upload_size 的单位是字节,常用换算速查:

  • 10MB = 10485760
  • 50MB = 52428800
  • 100MB = 104857600
  • 500MB = 524288000
  • 1GB = 1073741824

在源码中,默认值定义于 core/settings.py("upload_size": 1024 * 1024 * 10),运行时校验逻辑位于 apps/base/services.py 的 validate_file_size:若上传大小超过 max_size,直接抛出 HTTP 403。

2.2 修改上传限制

管理员可通过管理面板(推荐,避免直接编辑数据库造成类型错误或安全配置失效)修改:

# 设置最大上传大小为 100MB
upload_size = 104857600  # 100 * 1024 * 1024

配置项的运行时读取、类型校验与进程内 TTL 缓存逻辑位于 apps/base/config.py,写入配置的代码路径必须调用 refresh_settings(force=True) 才会立即生效。

2.3 超出限制的处理

当上传文件超过大小限制时,系统返回 403:

{
  "detail": "大小超过限制,最大为10.00 MB"
}

该错误文案与 apps/base/services.py 中 validate_file_size 的抛出逻辑完全一致。

2.4 类型白名单与内容伪造校验

除了大小,上传还会经过 apps/base/file_validation.py 的双重类型校验:

  • allowed_file_types 扩展名 / MIME 白名单(默认 ["*"] 表示不限制),对应 validate_file_type;
  • magic bytes 校验:读取文件头部(默认 64 字节)与扩展名 / Content-Type 比对,防止伪造类型(如把脚本改成 .jpg 上传),对应 validate_file_magic / validate_header_bytes。

分片上传的首个分片(chunk_index == 0)也会执行头部字节校验,见 apps/base/views.py。

三、普通上传 API

3.1 文件上传接口

POST /share/file/

Content-Type: multipart/form-data

请求参数:

参数 类型 必填 说明
file file 是 要上传的文件
expire_value int 否 过期数值,默认 1
expire_style string 否 过期方式,默认 day

过期方式选项:

值 说明
day 按天过期
hour 按小时过期
minute 按分钟过期
forever 永不过期
count 按下载次数过期

expire_style 的合法值不是写死的,而是由配置项 expire_style(默认 <a href="https://link.gitcode.com/i/4a479adf4b87323bc2ad915599073e6e" target="_blank">"day","hour","minute","forever","count"])动态控制。validate_expire_style 会校验请求值是否在管理员配置的白名单内,否则返回 400「过期时间类型错误」([apps/base/utils.py)。

响应示例:

{
  "code": 200,
  "detail": {
    "code": "654321",
    "name": "example.pdf"
  }
}

cURL 示例:

# 上传文件(默认1天有效期)
curl -X POST "http://localhost:12345/share/file/" \
  -F "file=@/path/to/file.pdf"

# 上传文件并指定有效期(7天)
curl -X POST "http://localhost:12345/share/file/" \
  -F "file=@/path/to/file.pdf" \
  -F "expire_value=7" \
  -F "expire_style=day"

# 上传文件并指定有效期(可下载10次)
curl -X POST "http://localhost:12345/share/file/" \
  -F "file=@/path/to/file.pdf" \
  -F "expire_value=10" \
  -F "expire_style=count"

# 分享文本
curl -X POST "http://localhost:12345/share/text/" \
  -F "text=这是要分享的文本内容"

# 通过取件码下载文件
curl -L "http://localhost:12345/share/select/?code=取件码" -o downloaded_file

需要认证时: 如果管理面板关闭了游客上传(open_upload=0),需要先登录获取 token:

# 1. 登录获取 token
curl -X POST "http://localhost:12345/admin/login" \
  -H "Content-Type: application/json" \
  -d '{"password": "<初始化时设置的管理员密码>"}'

# 返回: {"code":200,"msg":"success","detail":{"token":"xxx.xxx.xxx","token_type":"Bearer"}}

# 2. 使用 token 上传文件
curl -X POST "http://localhost:12345/share/file/" \
  -H "Authorization: Bearer xxx.xxx.xxx" \
  -F "file=@/path/to/file.pdf"

# 3. 使用 token 分享文本
curl -X POST "http://localhost:12345/share/text/" \
  -H "Authorization: Bearer xxx.xxx.xxx" \
  -F "text=这是要分享的文本内容"

3.2 源码级调用链

/share/file/ 的处理函数位于 apps/base/views.py:先做大小校验(validate_file_size)→ 类型校验(validate_upload_file)→ 过期方式校验(validate_expire_style),随后交给 FileUploadService.create_file_share 完成"生成存储路径 → 配额预留 → 写入存储 → 创建分享记录"的完整事务;任一环节失败都会回滚已保存文件并释放配额(apps/base/services.py)。注意:普通上传与文本分享都会经过 ip_limit["upload"] 依赖注入做 IP 限流。

文本分享接口 /share/text/ 有独立的大小限制:文本按 UTF-8 编码后不得超过 222KB(约 227KB),超出返回 403「内容过多,建议采用文件形式」(apps/base/views.py)。

3.3 文件路径与配额机制

所有上传(普通 / 分片 / 预签名)共用 build_file_path 生成存储路径:share/data/{年}/{月}/{日}/{uuid}/{文件名},日期使用 UTC+8(get_now),文件名经过 sanitize_filename 清洗(apps/base/utils.py)。

若管理员设置了 storage_limit 总存储配额,上传前会通过 reserve_storage 做原子配额预留(SQL 条件插入防止并发超卖,见 apps/base/quota.py),上传完成后由 release_storage 释放。

四、分片上传 API

对于大文件,FileCodeBox 支持分片上传:把大文件切割成多个小块分别上传,天然支持断点续传,并能绕过代理 / 网关对单请求体积的限制。

::: warning 前提条件 分片上传功能需要管理员启用:enable_chunk=1 :::

4.1 分片上传流程

┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│  初始化上传  │ ──▶ │  上传分片   │ ──▶ │  完成上传   │
│   /init/    │     │  /chunk/    │     │ /complete/  │
└─────────────┘     └─────────────┘     └─────────────┘
                          │
                          ▼
                    ┌───────────┐
                    │ 循环上传  │
                    │ 每个分片  │
                    └───────────┘

三个核心接口都注册在 chunk_api(前缀 /chunk,apps/base/views.py)。分片上传还需要满足一个隐性约束:分片理论上限受 upload_size 约束——初始化时服务端会按 total_chunks * chunk_size 估算最大上传量,超过 upload_size 直接返回 403;上传每个分片与最终合并时也会再次以"分片数 × 分片大小"复核累计大小。

4.2 1. 初始化上传

POST /chunk/upload/init/

请求体(JSON 或表单):

{
  "file_name": "large_file.zip",
  "file_size": 104857600,
  "chunk_size": 5242880,
  "file_hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
}
参数 类型 必填 默认值 说明
file_name string 是 - 文件名
file_size int 是 - 文件总大小(字节)
chunk_size int 否 5MB 分片大小(字节)
file_hash string 是 - 文件的 SHA256 哈希值

请求模型定义于 apps/base/schemas.py(InitChunkUploadModel,chunk_size 默认 5 * 1024 * 1024)。

响应示例:

{
  "code": 200,
  "detail": {
    "existed": false,
    "upload_id": "abc123def456789",
    "chunk_size": 5242880,
    "total_chunks": 20,
    "uploaded_chunks": []
  }
}
字段 说明
existed 文件是否已存在(秒传)
upload_id 上传会话 ID
chunk_size 分片大小
total_chunks 总分片数
uploaded_chunks 已上传的分片索引列表

源码实现(apps/base/views.py):服务端以 file_hash + file_size + file_name 为维度查找历史会话——命中则复用会话并返回已上传分片(断点续传);未命中则生成 upload_id、预留存储配额,并写入一个 chunk_index=-1 的会话标记记录。初始化同时还会校验文件名类型(validate_file_type)。

4.3 2. 上传分片

POST /chunk/upload/chunk/{upload_id}/{chunk_index}

路径参数:

参数 说明
upload_id 初始化时返回的上传会话 ID
chunk_index 分片索引,从 0 开始

请求体: Content-Type: multipart/form-data

参数 类型 说明
chunk file 分片数据

响应示例:

{
  "code": 200,
  "detail": {
    "chunk_hash": "a1b2c3d4e5f6..."
  }
}

cURL 示例:

# 上传第一个分片(索引为 0)
curl -X POST "http://localhost:12345/chunk/upload/chunk/abc123def456789/0" \
  -F "chunk=@/path/to/chunk_0"

源码实现(apps/base/views.py)的关键校验:会话必须存在(否则 404「上传会话不存在」)、chunk_index 必须在 [0, total_chunks) 内(否则 400「无效的分片索引」)、分片实际大小不得超过声明的 chunk_size(否则 400)。若某个分片已上传过(completed=True),接口直接返回已保存的 chunk_hash(skipped: true),这是断点续传"跳过已完成分片"的服务端基础。分片数据会计算 SHA256 后交给存储层 save_chunk 保存。

4.4 3. 完成上传

POST /chunk/upload/complete/{upload_id}

路径参数:

参数 说明
upload_id 上传会话 ID

请求体(JSON 或表单):

{
  "expire_value": 7,
  "expire_style": "day"
}
参数 类型 必填 说明
expire_value int 是 过期数值
expire_style string 是 过期方式

响应示例:

{
  "code": 200,
  "detail": {
    "code": "789012",
    "name": "large_file.zip"
  }
}

源码实现(apps/base/views.py → apps/base/services.py complete_chunked_upload):先校验已上传分片数量是否等于 total_chunks(不足则 400「分片不完整」),再复核累计大小是否超限,然后调用存储层 merge_chunks 把分片按索引顺序合并为完整文件并计算合并后的 SHA256,创建 FileCodes 分享记录(标记 is_chunked=True),最后清理临时分片并释放配额。合并失败会清理分片并返回 500「文件合并失败」。

4.5 断点续传

分片上传天然支持断点续传。当上传中断后:

  1. 使用相同的 file_hash 重新调用初始化接口
  2. 服务器返回 uploaded_chunks 列表,包含已上传的分片索引
  3. 客户端只需上传不在列表中的分片
  4. 所有分片上传完成后调用完成接口

完整 JavaScript 示例:

// 1. 初始化上传
const initResponse = await fetch('/chunk/upload/init/', {
  method: 'POST',
  body: JSON.stringify({
    file_name: 'large_file.zip',
    file_size: fileSize,
    chunk_size: 5 * 1024 * 1024,
    file_hash: fileHash
  })
});
const { upload_id, uploaded_chunks, total_chunks } = await initResponse.json();

// 2. 上传未完成的分片
for (let i = 0; i < total_chunks; i++) {
  if (!uploaded_chunks.includes(i)) {
    const chunk = file.slice(i * chunkSize, (i + 1) * chunkSize);
    await fetch(`/chunk/upload/chunk/${upload_id}/${i}`, {
      method: 'POST',
      body: chunk
    });
  }
}

// 3. 完成上传
await fetch(`/chunk/upload/complete/${upload_id}`, {
  method: 'POST',
  body: JSON.stringify({
    expire_value: 7,
    expire_style: 'day'
  })
});

除了文档中的三接口流程,仓库还提供两个辅助接口便于客户端掌控会话:

  • GET /chunk/upload/status/{upload_id}:查询会话状态,返回 uploaded_chunks 与 progress(已上传分片占比百分比),见 apps/base/views.py;
  • DELETE /chunk/upload/{upload_id}:取消上传,清理存储中的临时分片文件与数据库记录并释放配额,见 apps/base/views.py。

分片会话的有效期由配置项 chunk_expire_hours(默认 24 小时)控制,超时后由过期清理任务回收。分片合并的失败兜底逻辑(分片记录缺失、哈希不匹配、文件不存在)有专门的单元测试覆盖,见 tests/test_merge_chunks.py。

五、错误处理

5.1 常见错误

HTTP 状态码 错误信息 原因 解决方案
403 大小超过限制 文件超过 upload_size 限制 减小文件大小或联系管理员调整限制
403 上传频率限制 超过 IP 上传频率限制 等待限制时间窗口后重试
400 过期时间类型错误 expire_style 值不在允许列表中 使用有效的过期方式
404 上传会话不存在 upload_id 无效或已过期 重新初始化上传
400 无效的分片索引 chunk_index 超出范围 检查分片索引是否正确
400 分片不完整 完成上传时分片数量不足 确保所有分片都已上传

所有错误响应统一采用 {"detail": "错误信息描述"} 格式(封装于 core/response.py 的 APIResponse 与 FastAPI 的 HTTPException)。

5.2 频率限制

系统对上传操作有 IP 级频率限制,防止滥用:

配置项 默认值 说明
upload_minute 1 限制时间窗口(分钟)
upload_count 10 时间窗口内最大上传次数

默认配置表示"每 1 分钟内最多上传 10 次",超过后返回 423「请求次数过多,请稍后再试」。实现为基于内存字典的滑动窗口限流器 IPRateLimit(apps/base/dependencies.py),每个进程独立计数;限流的窗口与次数由 refresh_settings 在每次配置刷新时同步(apps/base/config.py)。此外取件码错误(error_count/error_minute)与登录(login_count/login_minute)也使用同类机制。

六、上传配置速查

6.1 相关配置项

配置项 类型 默认值 说明
open_upload int 1 是否开放上传(1=开放,0=关闭;关闭后需管理员 token)
upload_size int 10485760 最大上传大小(字节)
enable_chunk int 0 是否启用分片上传(1=启用,0=禁用)
upload_minute int 1 上传频率限制时间窗口(分钟)
upload_count int 10 时间窗口内最大上传次数
expire_style list ["day","hour","minute","forever","count"] 允许的过期方式
chunk_expire_hours int 24 分片上传会话有效期(小时)
allowed_file_types list ["*"] 允许上传的扩展名 / MIME 白名单,* 表示不限制

6.2 配置示例

# 允许上传 100MB 文件,启用分片上传
upload_size = 104857600
enable_chunk = 1

# 放宽上传频率限制:每 5 分钟最多 50 次
upload_minute = 5
upload_count = 50

# 只允许按天和按次数过期
expire_style = ["day", "count"]

这些配置项都定义于 core/settings.py 的 DEFAULT_CONFIG,运行时统一存于数据库 KeyValue 表,建议通过 /admin 管理面板修改;配置示例的组合在 docs/guide/configuration.md 中有个人使用 / 公开服务 / 企业三种典型场景(含 1GB 大文件 + 分片上传组合)。

七、进阶:预签名上传(大文件直传)

在普通上传与分片上传之外,仓库还提供了一套预签名上传接口(/presign,见 apps/base/views.py),用于"初始化 → 客户端直传 / 代理转存 → 确认分享码"的异步流程:S3 存储返回直传 URL(mode=direct),其他存储走代理模式(mode=proxy);会话有效期固定为 900 秒(15 分钟)。其完整协议说明见 docs/api/presign-upload.md,适合有独立前端 / 客户端接入诉求的开发者。

下一步

登录后查看全文
FileCodeBox