Documentation
¶
Index ¶
- Constants
- Variables
- func SanitizeFileName(name string) string
- func SanitizeUploadID(id string) string
- func ValidateStorageKey(key string) bool
- type CallbackVerifier
- type ChunkCompleteRequest
- type ChunkInitRequest
- type ChunkInitResponse
- type ChunkStatusResponse
- type ChunkUploadResponse
- type CompleteResult
- type DirectCompleteRequest
- type DirectTokenRequest
- type DirectTokenResponse
- type DirectUploadResult
- type Error
- type HashStore
- type HashedFile
- type InstantHitEvent
- type MultipartMeta
- type MultipartStorage
- type OrphanMultipart
- type PartETag
- type PartInfo
- type PartSignRequest
- type PartSignResponse
- type PresignPartRequest
- type ResumeRequest
- type ResumeResponse
- type STSCredential
- type STSProvider
- type SignedURLStorage
- type Storage
- type UploadAbortEvent
- type UploadErrorEvent
- type UploadInitEvent
- type UploadLifecycleEvent
- type UploadResult
- type UserScopedHashStore
Constants ¶
const ( CodeFileTooLarge = 10001 // 文件超过大小限制 CodeFileTypeNotAllow = 10002 // 文件类型不允许 CodeChecksumMismatch = 10003 // 校验和不匹配 CodeFileReadError = 10004 // 文件读取错误 CodeInvalidParam = 10005 // 参数无效 CodeMultipartError = 10006 // multipart 解析错误 )
上传错误码 10001-10099.
const ( CodeSessionNotFound = 20001 // 上传会话不存在 CodeSessionExpired = 20002 // 上传会话已过期 CodeChunkIndexOutRange = 20003 // 分片索引越界 CodeChunkDuplicate = 20004 // 分片重复上传 CodeChunkHashMismatch = 20005 // 分片校验和不匹配 CodeMergeIncomplete = 20006 // 分片未全部上传 CodeMergeHashFailed = 20007 // 合并后校验失败 )
分片错误码 20001-20099.
const ( CodeStorageWriteFail = 30001 // 存储写入失败 CodeStorageReadFail = 30002 // 存储读取失败 CodeStorageDeleteFail = 30003 // 存储删除失败 CodeObjectStatUnsupported = 30004 // 当前存储不支持对象 Stat CodeObjectCopyUnsupported = 30005 // 当前存储不支持对象 Copy )
存储错误码 30001-30099.
const ( CodeSTSNotConfigured = 40001 // STS 未配置 CodeSTSIssueFailed = 40002 // STS 凭证签发失败 CodeCallbackVerifyFail = 40003 // 回调验签失败 CodeDirectFileNotFound = 40004 // 直传文件不存在 )
STS 直传错误码 40001-40099.
const ( CodeMultipartNotSupported = 50001 // 当前存储后端不支持 Multipart 直传 CodeMultipartInitFailed = 50002 // 云端 Multipart 初始化失败 CodeMultipartPresignFail = 50003 // 预签名 URL 生成失败 CodeMultipartCompleteFail = 50004 // 云端 Multipart 合并失败 CodeMultipartAbortFail = 50005 // 云端 Multipart 中止失败 CodePartVerifyFail = 50006 // 服务端二次校验 parts 失败 CodePartETagMismatch = 50007 // 客户端上报 ETag 与云端不一致 )
Multipart 直传错误码 50001-50099.
const ( StageInit = "init" // 会话初始化失败:参数校验 / 云端 InitMultipart 等 StagePart = "part" // 分片上传阶段失败:SaveChunkData / 哈希不匹配 / ReportPartETag StageMerge = "merge" // 代理路径合并失败 StageComplete = "complete" // Multipart 直传 Complete 阶段失败 StageAbort = "abort" // 中止阶段失败(本地或云端清理失败) )
上传事件阶段标识。UploadErrorEvent.Stage 字段使用这些常量。 字符串值保证可直接作为 Prometheus label 使用。
const ( LifecyclePartSign = "part_sign" LifecyclePartComplete = "part_complete" LifecycleCompleteSuccess = "complete_success" )
const (
AbortReasonClientRequest = "client_request"
)
中止原因常量。
Variables ¶
var ( ErrFileTooLarge = NewErr(CodeFileTooLarge, http.StatusRequestEntityTooLarge, "文件超过大小限制") ErrFileTypeNotAllow = NewErr(CodeFileTypeNotAllow, http.StatusBadRequest, "文件类型不允许") ErrChecksumMismatch = NewErr(CodeChecksumMismatch, http.StatusBadRequest, "校验和不匹配") ErrInvalidParam = NewErr(CodeInvalidParam, http.StatusBadRequest, "参数无效") ErrMultipartError = NewErr(CodeMultipartError, http.StatusBadRequest, "multipart 解析错误") ErrSessionNotFound = NewErr(CodeSessionNotFound, http.StatusNotFound, "上传会话不存在") ErrSessionExpired = NewErr(CodeSessionExpired, http.StatusGone, "上传会话已过期") ErrChunkIndexOutRange = NewErr(CodeChunkIndexOutRange, http.StatusBadRequest, "分片索引越界") ErrChunkHashMismatch = NewErr(CodeChunkHashMismatch, http.StatusBadRequest, "分片校验和不匹配") ErrMergeIncomplete = NewErr(CodeMergeIncomplete, http.StatusConflict, "分片未全部上传") ErrMergeHashFailed = NewErr(CodeMergeHashFailed, http.StatusInternalServerError, "合并后文件校验失败") ErrStorageWrite = NewErr(CodeStorageWriteFail, http.StatusInternalServerError, "存储写入失败") ErrStorageRead = NewErr(CodeStorageReadFail, http.StatusInternalServerError, "存储读取失败") ErrStorageDelete = NewErr(CodeStorageDeleteFail, http.StatusInternalServerError, "存储删除失败") ErrObjectStatUnsupported = NewErr(CodeObjectStatUnsupported, http.StatusNotImplemented, "当前存储不支持对象 Stat") ErrObjectCopyUnsupported = NewErr(CodeObjectCopyUnsupported, http.StatusNotImplemented, "当前存储不支持对象 Copy") ErrSTSNotConfigured = NewErr(CodeSTSNotConfigured, http.StatusServiceUnavailable, "STS 直传未配置") ErrSTSIssueFailed = NewErr(CodeSTSIssueFailed, http.StatusInternalServerError, "STS 凭证签发失败") ErrCallbackVerifyFail = NewErr(CodeCallbackVerifyFail, http.StatusForbidden, "回调验签失败") ErrDirectFileNotFound = NewErr(CodeDirectFileNotFound, http.StatusNotFound, "直传文件不存在") ErrMultipartNotSupported = NewErr(CodeMultipartNotSupported, http.StatusNotImplemented, "当前存储不支持 Multipart 直传") ErrMultipartInitFailed = NewErr(CodeMultipartInitFailed, http.StatusInternalServerError, "Multipart 初始化失败") ErrMultipartPresignFail = NewErr(CodeMultipartPresignFail, http.StatusInternalServerError, "预签名 URL 生成失败") ErrMultipartCompleteFail = NewErr(CodeMultipartCompleteFail, http.StatusInternalServerError, "Multipart 合并失败") ErrMultipartAbortFail = NewErr(CodeMultipartAbortFail, http.StatusInternalServerError, "Multipart 中止失败") ErrPartVerifyFail = NewErr(CodePartVerifyFail, http.StatusBadRequest, "分片校验失败") ErrPartETagMismatch = NewErr(CodePartETagMismatch, http.StatusBadRequest, "分片 ETag 不匹配") )
预定义错误.
Functions ¶
func SanitizeFileName ¶
SanitizeFileName 去除文件名中的路径组件和不安全字符,防止目录穿越攻击。
func SanitizeUploadID ¶
SanitizeUploadID 确保上传 ID 只包含安全字符,防止通过构造的 ID 进行目录穿越。
func ValidateStorageKey ¶
ValidateStorageKey 校验存储 key 是否安全(无路径穿越)。
Types ¶
type CallbackVerifier ¶
type CallbackVerifier interface {
// VerifyCallback 验证回调签名并解析上传结果。
VerifyCallback(req *http.Request) (*DirectUploadResult, error)
}
CallbackVerifier 验证云存储的上传回调请求(可选接口)。 并非所有 STSProvider 都需要实现此接口。
type ChunkCompleteRequest ¶ added in v1.2.0
type ChunkCompleteRequest struct {
UploadID string `json:"upload_id" binding:"required"`
Parts []PartETag `json:"parts" binding:"required"`
}
ChunkCompleteRequest Multipart 直传路径下,前端通知合并的请求体。 代理路径下沿用 /chunk/merge/:upload_id(无请求体)。
type ChunkInitRequest ¶
type ChunkInitRequest struct {
FileName string `json:"file_name" binding:"required"`
FileSize int64 `json:"file_size" binding:"required,gt=0"`
ChunkSize int64 `json:"chunk_size,omitempty"`
Checksum string `json:"checksum,omitempty"` // 兼容旧字段,等价于 FileHash
FileHash string `json:"file_hash,omitempty"` // 整文件哈希,用于秒传查找
Algorithm string `json:"algorithm,omitempty"`
}
ChunkInitRequest 初始化分片上传的 JSON 请求体。
FileHash 为可选字段:若客户端在上传前计算了整文件哈希,可在 init 时携带, 命中 HashStore 即触发秒传(零流量上传)。算法由 Algorithm 字段指定。
type ChunkInitResponse ¶
type ChunkInitResponse struct {
UploadID string `json:"upload_id"`
ChunkSize int64 `json:"chunk_size"`
TotalChunks int `json:"total_chunks"`
// 秒传命中时填充(Instant=true 时前端直接用 StorageKey 完成流程)
Instant bool `json:"instant,omitempty"`
StorageKey string `json:"storage_key,omitempty"`
FileID string `json:"file_id,omitempty"`
URL string `json:"url,omitempty"` // 配置 WithPublicURLBuilder 时填充,秒传命中可直接用
// UploadPath 指示前端走哪条路径:
// "proxy" — 传统代理模式,前端 POST 到 /chunk
// "multipart" — 云原生 Multipart,前端 PUT 到 PartURLs
// "instant" — 秒传,前端直接提示完成
UploadPath string `json:"upload_path,omitempty"`
// Multipart 直传路径专用字段
// PartURLs: partNum(1-based) -> 预签名 PUT URL。为空时需走按需签名 /chunk/parts/sign。
PartURLs map[int]string `json:"part_urls,omitempty"`
PartSignTTL int `json:"part_sign_ttl,omitempty"` // 建议的单片 URL 有效期(秒)
RecommendConc int `json:"recommend_concurrency,omitempty"` // 建议并发数
}
ChunkInitResponse 分片上传会话创建后的响应。
响应字段按能力分层:
- 基础字段(UploadID / ChunkSize / TotalChunks):所有路径都返回
- Instant / StorageKey / FileID:仅秒传命中时返回;前端识别后直接完成
- UploadPath / PartURLs / PartSignTTL:仅 Multipart 直传路径返回;前端走方案 A 流程
老客户端不识别新字段,行为等价于原代理模式。
type ChunkStatusResponse ¶
type ChunkStatusResponse struct {
UploadID string `json:"upload_id"`
TotalChunks int `json:"total_chunks"`
UploadedChunks []int `json:"uploaded_chunks"`
MissingChunks []int `json:"missing_chunks"`
}
ChunkStatusResponse 查询分片上传状态的响应。
type ChunkUploadResponse ¶
type ChunkUploadResponse struct {
UploadID string `json:"upload_id"`
ChunkIndex int `json:"chunk_index"`
UploadedCount int `json:"uploaded_count"`
TotalChunks int `json:"total_chunks"`
}
ChunkUploadResponse 单个分片上传后的响应。
type CompleteResult ¶ added in v1.2.0
type CompleteResult struct {
Key string
ETag string // 云端合并后的对象 ETag(各家格式不同)
Size int64
Location string // 对象最终 URL(可选)
}
CompleteResult Multipart 合并完成后的结果。
type DirectCompleteRequest ¶
type DirectCompleteRequest struct {
Key string `json:"key" binding:"required"`
KeyPrefix string `json:"key_prefix,omitempty"` // v1.4.0+ 推荐传递;不传 fallback 到 v1.3.0 行为
KeyPrefixSig string `json:"key_prefix_sig,omitempty"` // 启用签名 + 传 KeyPrefix 时必填
ETag string `json:"etag,omitempty"`
}
DirectCompleteRequest 前端确认上传完成的请求体。
字段语义(v1.4.0+):
- Key:必填,云存储对象键
- KeyPrefix:**可选**——v1.3.0 客户端不传时服务端 fallback 到 keyPrefixFn 重算 (保留 v1.3.0 行为兼容),但跨天问题仍存在;建议升级前端在 IssueToken 后 原样回传 IssueToken 响应中的 KeyPrefix 以获得跨天修复
- KeyPrefixSig:仅当业务方配置 WithKeyPrefixSigner 且客户端传了 KeyPrefix 时必填 防止攻击者凭借公开可见的他人 KeyPrefix 构造请求越权读取他人文件
- ETag:可选
type DirectTokenRequest ¶
type DirectTokenRequest struct {
FileName string `json:"file_name" binding:"required"`
FileSize int64 `json:"file_size,omitempty"`
}
DirectTokenRequest 请求 STS 凭证的请求体。
type DirectTokenResponse ¶
type DirectTokenResponse struct {
*STSCredential
Key string `json:"key"` // 建议的对象键,前端可直接使用
}
DirectTokenResponse STS 凭证响应(包含凭证和建议的对象键)。
type DirectUploadResult ¶
type DirectUploadResult struct {
Bucket string `json:"bucket"`
Key string `json:"key"`
Size int64 `json:"size"`
ETag string `json:"etag,omitempty"`
URL string `json:"url,omitempty"`
}
DirectUploadResult 前端直传完成后的结果。
URL 字段在配置了 WithPublicURLBuilder 时填充为业务方期望的对外可见 URL; 未配置时为空字符串,JSON 序列化因 omitempty 不输出。
type Error ¶
type Error struct {
Code int `json:"code"`
Message string `json:"message"`
Details string `json:"details,omitempty"`
HTTPStatus int `json:"-"`
Err error `json:"-"`
}
Error 统一错误类型。
func (*Error) Is ¶ added in v1.4.0
Is 实现 errors.Is 的扩展接口,按 Code 字段比较以支持错误分类。
这让带 Details 的副本(通过 WithDetails 派生)仍能被 sentinel 命中:
err := types.ErrInvalidParam.WithDetails("xxx")
errors.Is(err, types.ErrInvalidParam) // true(v1.3.0 之前为 false)
仅在 target 也是 *Error 类型时按 Code 比较;否则交给 errors.Is 沿 Unwrap 链处理。
func (*Error) WithDetails ¶
WithDetails 返回一个带详情的 Error 副本。
type HashStore ¶ added in v1.2.0
type HashStore interface {
// LookupByHash 按文件哈希查找已存储的文件。
// algorithm: "md5" 或 "sha256"
// 找不到返回 (nil, nil),不要返回错误。
LookupByHash(ctx context.Context, algorithm, hash string) (*HashedFile, error)
// Register 记录一次成功上传的哈希→key 映射。
// 同一 hash 可能被多次 Register(不同用户上传相同内容)——实现方负责幂等或去重。
Register(ctx context.Context, entry *HashedFile) error
}
HashStore 是秒传(Instant Upload)的查找/注册接口。
由业务方实现(常见存储:MySQL / PostgreSQL / Redis / Mongo), 通过 WithHashStore 注入 gouploader。
语义:
- LookupByHash: 文件级哈希到已存储对象的映射。命中即秒传成功。
- Register: 上传完成后注册哈希→key 映射,供后续请求秒传命中。
未注入 HashStore 时,秒传功能自动关闭,所有 /chunk/init 请求走正常上传流程。
⚠️ 多租户安全警告:本接口不含 userID 维度。在多租户私有 bucket 场景下, 用户 B 凭知道的 hash 可命中用户 A 的私有文件 storage_key,构成越权访问漏洞。 多租户场景**必须**改用 UserScopedHashStore 接口(v1.4.0+)。
type HashedFile ¶ added in v1.2.0
type HashedFile struct {
Algorithm string `json:"algorithm"` // "md5" / "sha256"
Hash string `json:"hash"` // 文件完整哈希
StorageKey string `json:"storage_key"` // 存储 key
FileSize int64 `json:"file_size"`
MIMEType string `json:"mime_type,omitempty"`
CreatedAt int64 `json:"created_at"` // Unix 秒
}
HashedFile 秒传索引条目。
type InstantHitEvent ¶ added in v1.2.0
type InstantHitEvent struct {
FileHash string
Algorithm string
StorageKey string
FileSize int64
UserID string
FileName string
}
InstantHitEvent 秒传命中时触发的事件 payload。
type MultipartMeta ¶ added in v1.2.0
type MultipartMeta struct {
ContentType string
ContentDisposition string
CacheControl string
UserMetadata map[string]string
}
MultipartMeta 创建 Multipart 时可设置的对象元数据。
type MultipartStorage ¶ added in v1.2.0
type MultipartStorage interface {
Storage
// InitMultipart 在云端创建一个新的 Multipart 上传会话,返回云厂商的 UploadID。
// key 是最终对象键,meta 可包含 Content-Type、Content-Disposition 等元数据。
InitMultipart(ctx context.Context, key string, meta MultipartMeta) (cloudUploadID string, err error)
// PresignPart 为单个分片生成预签名 PUT URL。
// 签名必须锁死 partNum 和 Content-Length,防止客户端越权/超额。
PresignPart(ctx context.Context, req PresignPartRequest) (url string, err error)
// ListParts 列出指定 UploadID 当前在云端已上传的分片。
// 用于 Complete 前的服务端二次校验,防止客户端伪造 ETag。
ListParts(ctx context.Context, key, cloudUploadID string) ([]PartInfo, error)
// CompleteMultipart 通知云端合并所有分片为最终对象。
// parts 必须按 PartNum 升序排列。
CompleteMultipart(ctx context.Context, key, cloudUploadID string, parts []PartETag) (*CompleteResult, error)
// AbortMultipart 中止云端 Multipart,释放悬挂的分片数据。
// 必须幂等——对已完成或不存在的 uploadID 返回 nil。
AbortMultipart(ctx context.Context, key, cloudUploadID string) error
// ListOrphans 列出超过 olderThan 时长未完成的悬挂 Multipart。
// 供后台 Orphan Cleanup Worker 扫描 + 批量 Abort。
ListOrphans(ctx context.Context, olderThan time.Duration) ([]OrphanMultipart, error)
}
MultipartStorage 是 Storage 的可选扩展接口,提供云原生 Multipart 直传能力。
实现此接口的 Storage 允许客户端绕过服务端,直接把分片 PUT 到云厂商对象存储。 服务端只负责签发预签名 URL、记录 ETag、调用 Init/Complete/Abort。
ChunkUploadService 通过类型断言探测此接口:
if mp, ok := storage.(types.MultipartStorage); ok {
// 走原生 Multipart 直传路径
} else {
// 走本地分片合并路径(LocalStorage 兜底)
}
此接口不内置在主 module,由各厂商 adapter 实现:
github.com/gtkit/gouploader/aliyun github.com/gtkit/gouploader/tencent github.com/gtkit/gouploader/huawei
type OrphanMultipart ¶ added in v1.2.0
OrphanMultipart 悬挂 Multipart 的元信息。
type PartSignRequest ¶ added in v1.2.0
type PartSignRequest struct {
UploadID string `json:"upload_id" binding:"required"`
PartNumbers []int `json:"part_numbers" binding:"required"`
}
PartSignRequest 按需签名预签名 URL 的请求体。
type PartSignResponse ¶ added in v1.2.0
type PartSignResponse struct {
URLs map[int]string `json:"urls"` // partNum -> presigned PUT URL
TTL int `json:"ttl"` // URL 有效期(秒)
}
PartSignResponse 按需签名响应。
type PresignPartRequest ¶ added in v1.2.0
type PresignPartRequest struct {
Key string
CloudUploadID string
PartNum int
ContentLength int64
TTL time.Duration
}
PresignPartRequest 预签名单个分片的请求参数。
签名时务必锁死以下参数,防止客户端越权:
- Key:最终对象键
- PartNum:分片号(1-based)
- ContentLength:该分片精确字节数
- TTL:URL 有效期(建议 15 分钟)
type ResumeRequest ¶ added in v1.2.0
type ResumeRequest struct {
FileHash string `json:"file_hash" binding:"required"`
Algorithm string `json:"algorithm,omitempty"` // 默认 sha256
FileName string `json:"file_name,omitempty"` // 可选,用于未命中秒传时的 init 提示
FileSize int64 `json:"file_size,omitempty"`
}
ResumeRequest 秒传 + 断点续传查询请求体。
type ResumeResponse ¶ added in v1.2.0
type ResumeResponse struct {
Instant bool `json:"instant,omitempty"`
StorageKey string `json:"storage_key,omitempty"`
FileID string `json:"file_id,omitempty"`
URL string `json:"url,omitempty"` // 秒传命中时,配置 WithPublicURLBuilder 后填充
UploadID string `json:"upload_id,omitempty"`
UploadPath string `json:"upload_path,omitempty"` // "proxy" / "multipart"
TotalChunks int `json:"total_chunks,omitempty"`
UploadedParts []int `json:"uploaded_parts,omitempty"`
MissingParts []int `json:"missing_parts,omitempty"`
}
ResumeResponse 秒传 + 断点续传查询响应。
返回值分三种情形:
- Instant=true:秒传命中,前端直接使用 StorageKey 完成
- UploadID != "":找到未完成的上传会话,UploadedParts 列出已传分片,MissingParts 列出待传分片
- 以上都没有:未命中任何已知状态,前端应走正常 /chunk/init 流程
type STSCredential ¶
type STSCredential struct {
AccessKeyID string `json:"access_key_id"`
AccessKeySecret string `json:"access_key_secret"`
SecurityToken string `json:"security_token"`
Expiration string `json:"expiration"` // ISO 8601 格式
Region string `json:"region"`
Bucket string `json:"bucket"`
Endpoint string `json:"endpoint"` // 前端用的公网 endpoint
KeyPrefix string `json:"key_prefix,omitempty"` // 限定的上传路径前缀
KeyPrefixSig string `json:"key_prefix_sig,omitempty"` // KeyPrefix 的 HMAC 签名(v1.4.0+,配置 WithKeyPrefixSigner 时填充)
}
STSCredential 各云厂商 STS 签发的临时凭证(统一输出格式)。 前端使用此凭证直接上传到云存储,无需经过后端中转。
type STSProvider ¶
type STSProvider interface {
// IssueCredential 签发一组临时凭证。
// keyPrefix 限定上传的对象键前缀(如 "user-123/2026/04/"),
// Provider 实现负责将其编入 STS Policy,确保前端只能上传到指定路径。
IssueCredential(ctx context.Context, keyPrefix string) (*STSCredential, error)
}
STSProvider 签发前端直传所需的 STS 临时凭证。
各云厂商分别实现此接口:
- 阿里云 OSS:AssumeRole
- 腾讯云 COS:GetFederationToken
- 华为云 OBS:CreateTemporaryAccessKeyByAgency
- AWS S3:AssumeRole
type SignedURLStorage ¶ added in v1.4.0
type SignedURLStorage interface {
Storage
// GetSignedGetURL 生成对象的预签名 GET URL,TTL 内可被 HTTP GET 直接访问。
//
// 参数:
// - key:对象键
// - ttl:URL 有效期,必须 > 0;上限因厂商而异(普遍 7 天),超出时由 SDK 报错
//
// 返回的 URL 含敏感签名参数,业务方不应持久化存储——每次需要时重新签名。
//
// CDN 域名重写:若 Config.CDNHost 非空,签名 URL 的 host 会被替换为 CDN 域名
// (行为与 PresignPart 一致)。
//
// ctx 取消能力:除阿里云 OSS Go SDK V1 限制外,其他实现支持 ctx 取消。
GetSignedGetURL(ctx context.Context, key string, ttl time.Duration) (string, error)
}
SignedURLStorage 是 Storage 的可选扩展接口,提供"生成预签名 GET URL"能力。
业务场景:私有 bucket(对象未公开读)下,前端无法直接 GET 对象 URL, 需要服务端临时签发一个带签名参数的 URL 让前端 / 浏览器下载。
业务方使用 type assertion 检测底层 Storage 是否实现:
if su, ok := storage.(gouploader.SignedURLStorage); ok {
url, err := su.GetSignedGetURL(ctx, "2026/04/30/x.png", 15*time.Minute)
}
内置实现:
- aliyun.Storage / tencent.Storage / huawei.Storage(详见各子 module)
- internal/store.S3Storage(基于 minio-go)
- LocalStorage 不实现(本地文件无"签名"语义)
与 WithPublicURLBuilder 协作可在私有 bucket 场景下闭环:
gouploader.WithPublicURLBuilder(func(key string) string {
if su, ok := storage.(gouploader.SignedURLStorage); ok {
url, _ := su.GetSignedGetURL(context.Background(), key, 15*time.Minute)
return url
}
return ""
})
type Storage ¶
type Storage interface {
// Save 从流式 Reader 中持久化文件。size 为预期总字节数,未知时传 -1。
Save(ctx context.Context, key string, reader io.Reader, size int64) error
// Get 返回已存储文件的 ReadCloser。调用方必须关闭。
Get(ctx context.Context, key string) (io.ReadCloser, error)
// Delete 按 key 删除文件。
Delete(ctx context.Context, key string) error
// Exists 检查 key 是否存在。
Exists(ctx context.Context, key string) (bool, error)
}
Storage 抽象文件持久化层。 所有方法均使用 io.Reader 流式传输,不会将完整文件缓冲到内存。
type UploadAbortEvent ¶ added in v1.2.0
type UploadAbortEvent struct {
UploadID string
Key string // 可为空(代理路径合并前 abort)
UserID string
UploadPath string // "proxy" | "multipart"
Reason string // 当前固定 "client_request",预留扩展
}
UploadAbortEvent 会话被中止时触发的事件 payload。
type UploadErrorEvent ¶ added in v1.2.0
type UploadErrorEvent struct {
Stage string
UploadID string
Key string
FileHash string
UserID string
Err error
}
UploadErrorEvent 任一上传阶段失败时触发的事件 payload。
Stage 取自 StageInit / StagePart / StageMerge / StageComplete / StageAbort。 空字段含义:
- UploadID 为空:失败发生在 session 创建之前
- Key 为空:上传路径尚未分配存储 key(proxy 路径 merge 前)
- FileHash 为空:请求未携带文件哈希
type UploadInitEvent ¶ added in v1.2.0
type UploadInitEvent struct {
UploadID string
FileName string
FileSize int64
FileHash string
Algorithm string
UserID string
UploadPath string // "proxy" | "multipart"
StorageKey string // 仅 multipart 路径非空
TotalChunks int
ChunkSize int64
}
UploadInitEvent 会话初始化成功时触发的事件 payload。
type UploadLifecycleEvent ¶ added in v1.5.0
type UploadLifecycleEvent struct {
Stage string
UploadID string
Key string
UserID string
UploadPath string
PartNum int
PartNumbers []int
FileID string
FileName string
FileSize int64
Checksum string
CloudUploadID string
}
UploadLifecycleEvent describes successful upload lifecycle milestones.
type UploadResult ¶
type UploadResult struct {
FileID string `json:"file_id"`
FileName string `json:"file_name"`
FileSize int64 `json:"file_size"`
StorageKey string `json:"storage_key"`
Checksum string `json:"checksum,omitempty"`
URL string `json:"url,omitempty"`
}
UploadResult 文件上传完成后返回的结果(普通上传和分片合并)。
URL 字段在配置了 WithPublicURLBuilder 时填充为业务方期望的对外可见 URL; 未配置时为空字符串,JSON 序列化因 omitempty 不输出。
type UserScopedHashStore ¶ added in v1.4.0
type UserScopedHashStore interface {
HashStore
// LookupByHashForUser 按 (userID, algorithm, hash) 三元组查找已存储文件。
// 仅返回属于 userID 的文件——A 的文件不应被 B 的查询命中。
// 找不到返回 (nil, nil),不要返回错误。
LookupByHashForUser(ctx context.Context, userID, algorithm, hash string) (*HashedFile, error)
// RegisterForUser 记录一次成功上传的 (userID, hash, key) 映射。
// 同一 (userID, hash) 可能被多次注册——实现方负责幂等或去重。
RegisterForUser(ctx context.Context, userID string, entry *HashedFile) error
}
UserScopedHashStore 是 HashStore 的可选扩展接口,提供按用户隔离的秒传索引。
多租户私有 bucket 场景下**必须**实现此接口,避免用户 B 凭知道的 hash 命中用户 A 的私有文件。service 层会通过 type assertion 自动选择: 实现了本接口就调用 LookupByHashForUser / RegisterForUser;否则回退旧 HashStore 接口(伴随 Warn 日志提醒)。
典型实现(数据库后端):
func (s *dbHashStore) LookupByHashForUser(ctx, userID, algo, hash string) (*types.HashedFile, error) {
var row struct{...}
err := s.db.WhereRaw("user_id = ? AND algorithm = ? AND hash = ?", userID, algo, hash).First(&row).Error
if errors.Is(err, gorm.ErrRecordNotFound) { return nil, nil }
return &types.HashedFile{...}, err
}
func (s *dbHashStore) RegisterForUser(ctx, userID string, entry *types.HashedFile) error {
return s.db.Create(&model{UserID: userID, ...entry}).Error
}
注入:调用 WithHashStore 即可(type assertion 自动检测扩展接口)。