# 文件分片上传与断点续传示例

这个示例使用 Python 标准库实现上传服务，前端使用浏览器原生 API 实现文件切片、并发上传、暂停继续、失败重试和断点续传。

## 运行方式

```bash
python server.py
```

然后访问：

```text
http://127.0.0.1:8000
```

如果端口被占用：

```powershell
$env:PORT=8010; python server.py
```

## 实现功能

- 文件分片：前端使用 `Blob.slice` 按 1MB 切片。
- 断点续传：上传前调用 `/api/status` 查询已有分片，只补传缺失分片。
- 并发上传：前端最多同时上传 3 个分片。
- 暂停继续：暂停后不再派发新分片，继续后从剩余队列接着传。
- 失败重试：单个分片失败后自动重试 2 次。
- 服务端合并：所有分片上传后调用 `/api/merge`，Python 按分片序号合并。
- 完整性校验：前端计算 SHA-256，服务端合并后再次计算并对比。

## 主要代码

前端分片：

```js
for (let start = 0; start < file.size; start += CHUNK_SIZE) {
  chunks.push(file.slice(start, start + CHUNK_SIZE));
}
```

服务端保存分片：

```python
with chunk_path.open("wb") as file:
    remaining = content_length
    while remaining > 0:
        data = self.rfile.read(min(1024 * 1024, remaining))
        file.write(data)
        remaining -= len(data)
```

服务端合并：

```python
with target_path.open("wb") as target:
    for index in range(total_chunks):
        with (upload_dir / str(index)).open("rb") as chunk:
            shutil.copyfileobj(chunk, target)
```

## 逻辑过程

- 用户选择文件。
- 前端计算文件 SHA-256，并生成 `uploadId`。
- 前端把文件切成多个分片。
- 前端请求 `/api/status`，得到服务端已经存在的分片编号。
- 前端跳过已上传分片，只上传缺失分片。
- 所有分片上传完成后，请求 `/api/merge`。
- 服务端检查分片是否完整，按顺序合并文件。
- 服务端计算合并后文件的 SHA-256，和前端传来的 hash 对比。

## 注意事项

- 示例为了教学只使用 Python 标准库，生产环境建议使用 FastAPI、Django、Flask 或对象存储直传。
- 生产环境必须增加鉴权、文件大小限制、文件类型校验、上传频率限制和临时分片清理。
- 大文件 hash 计算会占用浏览器内存，本示例为简单清晰使用 `file.arrayBuffer()`，生产环境建议使用流式 hash 或 Web Worker。
- 多实例部署时，分片需要放到共享存储，或者用一致性路由保证同一个文件落到同一台机器。
