""" 分卷接收模块 (receiver) 模块: 服务端 / 分卷接收 输入: 卷数据 (transfer_id, index) 输出: 落盘卷 + ok/409 逐卷校验 SHA-256 (对照 init 登记的规格); 重复卷直接 ok (幂等, 断点续传重发不炸)。 """ import hashlib import shutil from pathlib import Path from task_manager import TaskManager class ReceiverError(RuntimeError): """卷校验失败 (哈希不一致/规格缺失)""" class Receiver: def __init__(self, tasks: TaskManager, tmp_root: str | Path) -> None: self.tasks = tasks self.tmp_root = Path(tmp_root) def receive(self, transfer_id: str, idx: int, data: bytes) -> None: """接收并校验一卷。失败抛 ReceiverError (api 转 409)。""" if self.tasks.is_chunk_received(transfer_id, idx): return # 幂等: 重复卷直接 ok spec = self.tasks.chunk_spec(transfer_id, idx) if spec is None: raise ReceiverError(f"任务 {transfer_id} 卷 {idx} 规格不存在") # 校验大小 + SHA-256 if len(data) != spec["size"]: raise ReceiverError(f"卷 {idx} 大小不符: 期望 {spec['size']}, 实际 {len(data)}") sha256 = hashlib.sha256(data).hexdigest() if sha256 != spec["sha256"]: raise ReceiverError(f"卷 {idx} SHA-256 不一致 (数据损坏或串卷)") # 落盘临时目录 chunk_dir = self.tmp_root / transfer_id chunk_dir.mkdir(parents=True, exist_ok=True) chunk_path = chunk_dir / f"chunk_{idx:04d}" tmp_path = chunk_path.with_suffix(".tmp") tmp_path.write_bytes(data) shutil.move(str(tmp_path), str(chunk_path)) self.tasks.mark_chunk_received(transfer_id, idx)