上传中断时应通过 io.errunexpectedeof 和 net.errclosed 判断客户端断开,开头检查 r.body 是否为 nil 并 defer close;用 io.readfull 替代 io.read,返回 499 或 408;避免 os.o_append 导致偏移错乱,优先用 writeat;状态须持久化(如 redis set nx),写完先 f.sync() 再更新状态;并发锁仅限状态操作;需调用 r.parsemultipartform(0) 防止 body 被提前消费。

上传中断时 io.ErrUnexpectedEOF 怎么判断和响应
客户端断开连接(关浏览器、网络闪断、Nginx 代理超时)后,request.Body.Read 会立刻返回 io.ErrUnexpectedEOF 或 net.ErrClosed,这不是 bug,是正常信号。关键不是“捕获 panic”,而是提前识别并优雅退出。
- 必须在 handler 开头就检查
r.Body是否为 nil,再加defer r.Body.Close(),否则后续Read可能 panic - 读取分片时用
io.ReadFull替代io.Read:它要求读满预期字节数,若中途断开就明确报错,避免写入不完整块 - 遇到
io.ErrUnexpectedEOF,返回 HTTP 499(Client Closed Request)或 408(Request Timeout),别返回 500 —— 这是客户端问题,不是服务端故障 - 别依赖
Content-Length做校验,它可能为空或被中间代理篡改;真正可信的是实际读取字节数
os.OpenFile 写入偏移错位的典型坑
想从断点继续写,却把数据写到文件末尾甚至覆盖开头,基本都是 os.OpenFile 标志位和定位方式用错了。
- 错误写法:
os.O_WRONLY | os.O_CREATE | os.O_APPEND——os.O_APPEND会让所有Write忽略Seek,强制追加到末尾,导致偏移错乱 - 正确写法:
os.O_WRONLY | os.O_CREATE | os.O_RDWR,然后显式调用f.Seek(offset, io.SeekStart)定位 - 更安全的是用
f.WriteAt(data, offset),它不依赖当前文件光标位置,避免因并发或异常导致 Seek 失效 - 每次
Seek后必须检查返回值是否等于期望offset,否则说明文件已被截断或移动过,不能继续写
分片状态怎么存才不会丢、不冲突
重启后找不到上次传到哪了,或者两个 goroutine 同时写同一块,本质是状态没落盘或没锁好。
- 绝对不要只存在内存 map 中 —— 进程一挂,进度全丢
- 本地开发可用 SQLite(开启 WAL 模式)或 Redis:Redis 的
SET key value EX 3600 NX能天然实现带过期的原子写入 - 每次写完一块,必须先
f.Sync()刷盘临时文件,再更新状态;顺序反了,崩溃后状态和文件不一致 - 并发写同一
file_id时,锁粒度要小:只锁“读/写状态”那几行代码,绝不包裹io.Copy这类可能阻塞的 I/O 操作 - 客户端传来的
chunk_index不能直接信 —— 服务端得自己扫描磁盘或查 Redis,确认该块是否真实存在且哈希匹配
为什么 r.ParseMultipartForm(0) 是分片上传的前提
不加这句,后续所有分片读取都会失败,因为 Go 默认会偷偷消费整个 Body。
-
r.ParseMultipartForm在首次访问r.MultipartForm时自动触发,默认参数是32 (32MB),哪怕你根本不需要解析 multipart - 一旦触发,
r.Body就被读空,后面io.CopyN(r.Body, ...)只能得到 0 字节 - 解决方案:handler 第一行就写
r.ParseMultipartForm(0),强制禁用自动解析,保留原始流 - 元信息(如
file_id、chunk_index)必须从 URL query 或r.FormValue()获取,别碰r.MultipartForm.Value - 如果真要解析表单字段,改用
r.MultipartReader()流式处理,手动提取 part,不加载全文
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











