Documentation
¶
Overview ¶
storage.go — 分片物理操作的存储接缝(chunk 域)。
写片/合并/取流/清理收口于 app 层,HTTP 适配层不直连 storage; 磁盘布局(uploads/YYYY/MM/DD/<uuid><ext>)统一走 utils.UploadObjectRelPath, 与直传分享落盘同源。未注入统一存储实例时(测试/降级路径)懒加载本地后端 兜底,参数与历史 handler 兜底一致(./data)。
token.go 分片会话令牌与归属校验的单一实现。
此前 transport/handler(multi-bind)与 gen/handler/chunk 各持一份逐字孪生 (chunkSessionToken/verifyChunkToken/ownedByCaller,注释自认"同源同语义"), 收口到本域导出函数后两处只留薄委托。
Index ¶
- Constants
- Variables
- func OwnedByCaller(info *model.UploadChunk, clientIP string, sessionToken string, ...) bool
- func SessionToken(uploadID string) string
- func ValidateChunkPlan(fileSize, chunkSize int64, totalChunks int) error
- func VerifySessionToken(uploadID, token string) bool
- type ChunkResp
- type InitiateUploadReq
- type ProgressResp
- type Service
- func (s *Service) CheckQuickUpload(ctx context.Context, fileHash string, fileSize int64) (string, error)
- func (s *Service) CheckUploadProgress(ctx context.Context, uploadID string) (*ProgressResp, error)
- func (s *Service) CleanSessionFiles(ctx context.Context, uploadID string) error
- func (s *Service) CompleteUpload(ctx context.Context, uploadID string) error
- func (s *Service) DeleteMergedFile(ctx context.Context, relPath string)
- func (s *Service) DeleteUpload(ctx context.Context, uploadID string) error
- func (s *Service) GetUploadInfo(ctx context.Context, uploadID string) (*model.UploadChunk, error)
- func (s *Service) GetUploadList(ctx context.Context, page, pageSize int) ([]*ChunkResp, int64, error)
- func (s *Service) GetUploadedChunkIndexes(ctx context.Context, uploadID string) ([]int, error)
- func (s *Service) InitiateUpload(ctx context.Context, req *InitiateUploadReq) (*ChunkResp, error)
- func (s *Service) MergeUpload(ctx context.Context, uploadID string, totalChunks int, fileName string) (string, error)
- func (s *Service) OpenMerged(ctx context.Context, relPath string) (io.ReadCloser, int64, error)
- func (s *Service) SaveChunkData(ctx context.Context, uploadID string, chunkIndex int, data []byte) error
- func (s *Service) SetStorage(st storage.StorageInterface)
- func (s *Service) UploadChunk(ctx context.Context, req *UploadChunkReq) (*ChunkResp, error)
- type UploadChunkReq
Constants ¶
const MaxTotalChunks = 10000
MaxTotalChunks 单会话分片数硬上限(2026-10-08 加固)。TotalChunks 客户端 可控且此前无上限:一次 init 可声明上亿分片刷控制行,后续每个 chunk PUT 还会按 index 逐条建行。10000 片 × 常见 4MB 分片已承载 40GB,远超常规 upload.max_file_size 配置,正常客户端不可能触顶。
Variables ¶
var ErrSessionConflict = errors.New("上传会话已被占用,请刷新上传")
ErrSessionConflict init 撞上他人进行中(已含分片数据)的会话。 2026-10-05 审计 P1:uploadID 可为客户端自报哈希(公开文件哈希可预测), 原"最后写入者 wins"无条件重绑归属让第三方可截胡他人上传——改归属后即可 绑走已传分片(内容窃取)或覆写合并结果(内容替换)。HTTP 层映射 409。
Functions ¶
func OwnedByCaller ¶
func OwnedByCaller(info *model.UploadChunk, clientIP string, sessionToken string, callerUserID *uint) bool
OwnedByCaller 分片会话归属校验(治理):控制记录记有 OwnerIP 时,要求 IP 一致、同一登录用户或持有效会话令牌;老数据(OwnerIP 为空)此前无条件 放行——同网任意调用方可向他人进行中会话写分片/取消/完成(2026-10-08 加固 收紧为 fail-closed:仅令牌或同用户可通过,匿名老会话不可恢复,分片会话是 短生命周期数据,升级窗口外无存量)。 clientIP/callerUserID 由调用方按各路由的身份来源提取后传入(JWT/匿名、 ctx 值/c.Get 两种路径判定口径一致)。
func SessionToken ¶
SessionToken 分片会话令牌:HMAC("chunk-session:"+uploadID, key)。客户端从 init 响应头 X-Upload-Token 取得并在后续请求回传,即可在 IP 漂移(移动网络/ CGNAT)后仍通过归属校验。密钥复用 presign 签名密钥(conf.PresignSigningKey 单源:PB_PRESIGN_SIGNING_KEY 优先,缺省回退 jwt_secret);无密钥返回空串 (令牌通道关闭,仅 IP/用户匹配生效)。
func ValidateChunkPlan ¶ added in v0.14.9
ValidateChunkPlan 校验分片计划自洽性:TotalChunks 必须恰为 ⌈FileSize/ChunkSize⌉ 且不超过 MaxTotalChunks。防两类滥用:① 总数虚高(配合逐片 PUT 刷 DB 行/ 存储写);② 总数不足让 Complete 永不满足的僵尸会话占库。自家前端按 Math.ceil(file.size/chunkSize) 计算,严格相等不误伤。
func VerifySessionToken ¶
VerifySessionToken 客户端回传的会话令牌是否有效。
Types ¶
type ChunkResp ¶
type ChunkResp struct {
ID uint `json:"id"`
UploadID string `json:"upload_id"`
ChunkIndex int `json:"chunk_index"`
ChunkHash string `json:"chunk_hash"`
TotalChunks int `json:"total_chunks"`
FileSize int64 `json:"file_size"`
ChunkSize int `json:"chunk_size"`
FileName string `json:"file_name"`
Completed bool `json:"completed"`
Status string `json:"status"`
}
type InitiateUploadReq ¶
type ProgressResp ¶
type Service ¶
type Service struct {
// contains filtered or unexported fields
}
func NewService ¶
func (*Service) CheckQuickUpload ¶
func (s *Service) CheckQuickUpload(ctx context.Context, fileHash string, fileSize int64) (string, error)
CheckQuickUpload 秒传检查(真实实现,替代原 TODO): 优先按 file_codes.file_hash + size 查既有未过期分享(跨会话秒传), 命中直接返回分享码——客户端无需上传任何分片。
func (*Service) CheckUploadProgress ¶
CheckUploadProgress 检查上传进度
func (*Service) CleanSessionFiles ¶
CleanSessionFiles 清理上传会话的全部临时分片。
func (*Service) CompleteUpload ¶
CompleteUpload 完成上传
func (*Service) DeleteMergedFile ¶
DeleteMergedFile 尽力删除合并对象(校验失败清理路径;删除失败不阻断主流程, 与历史 `_ = DeleteFile` 语义一致)。
func (*Service) DeleteUpload ¶
DeleteUpload 删除上传
func (*Service) GetUploadInfo ¶
GetUploadInfo 获取上传信息(包括文件名、大小等)
func (*Service) GetUploadList ¶
func (s *Service) GetUploadList(ctx context.Context, page, pageSize int) ([]*ChunkResp, int64, error)
GetUploadList 获取上传列表
func (*Service) GetUploadedChunkIndexes ¶
GetUploadedChunkIndexes 获取已上传分片的索引列表
func (*Service) InitiateUpload ¶
InitiateUpload 初始化分片上传
func (*Service) MergeUpload ¶
func (s *Service) MergeUpload(ctx context.Context, uploadID string, totalChunks int, fileName string) (string, error)
MergeUpload 合并全部分片为正式上传对象,返回统一布局下的相对路径。 文件名仅用于扩展名推导,磁盘名由 UploadObjectRelPath 生成(UUID)。
func (*Service) OpenMerged ¶
OpenMerged 打开合并后的对象读取流(哈希计算/魔数复检用),返回读取器与实际大小。
func (*Service) SaveChunkData ¶
func (s *Service) SaveChunkData(ctx context.Context, uploadID string, chunkIndex int, data []byte) error
SaveChunkData 写入单个分片。
func (*Service) SetStorage ¶
func (s *Service) SetStorage(st storage.StorageInterface)
SetStorage 注入统一存储实例(bootstrap 调用)。回归要点:chunk 与 share 必须共用同一实例——此前两个懒加载单例路径基不一致(./data 与 ./data/uploads), 分片合并写入 data/uploads/<rel>、下载却找 data/uploads/uploads/<rel>。
func (*Service) UploadChunk ¶
UploadChunk 上传单个分片