FileCodeBox 文件上传完全指南:普通上传、分片上传与断点续传实战
FileCodeBox 文件上传完全指南:普通上传、分片上传与断点续传实战
FileCodeBox 是开源的匿名口令文件分享服务("像取快递一样取文件"),本文以其 上传指南 为主体,结合仓库源码深入讲解其完整的文件上传体系:网页端的拖拽 / 点击 / 粘贴三种交互方式、普通上传与文本分享的 REST API、面向大文件的分片上传与断点续传全流程、上传相关的全部配置项、限流与错误处理机制。读完本文,你将能独立完成从"修改上传限制"到"基于 API 接入分片上传"的完整实战。
一、上传方式总览
FileCodeBox 把"分享文件/文本"作为核心体验,前端提供三种上传入口,覆盖绝大多数使用场景:
- 拖拽上传:打开 FileCodeBox 首页,把文件从系统文件管理器直接拖入上传区域,松开鼠标即开始上传(支持同时拖拽多个文件,具体取决于当前主题)。
- 点击上传:点击上传区域的「选择文件」按钮,通过系统文件选择器选取文件,确认后即开始上传。
- 粘贴上传:先把图片复制到剪贴板(截图或复制图片),在上传区域按
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 断点续传
分片上传天然支持断点续传。当上传中断后:
- 使用相同的
file_hash重新调用初始化接口 - 服务器返回
uploaded_chunks列表,包含已上传的分片索引 - 客户端只需上传不在列表中的分片
- 所有分片上传完成后调用完成接口
完整 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,适合有独立前端 / 客户端接入诉求的开发者。