huawei

package module
v1.7.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 12, 2026 License: MIT Imports: 10 Imported by: 0

README

gouploader/huawei

华为云 OBS 的 Storage + MultipartStorage 实现。已完整对接 huaweicloud-sdk-go-obs,可直接生产使用。

安装

go get github.com/gtkit/gouploader
go get github.com/gtkit/gouploader/huawei

主 module 不会引入华为云 SDK 依赖——只有显式 go get .../huawei 的项目才会拉 huaweicloud-sdk-go-obs

使用

import (
    "time"
    "github.com/gtkit/gouploader"
    "github.com/gtkit/gouploader/huawei"
)

storage, err := huawei.NewStorage(huawei.Config{
    Endpoint:  "obs.cn-north-4.myhuaweicloud.com",
    AccessKey: "...",
    SecretKey: "...",
    Bucket:    "my-bucket",
    Region:    "cn-north-4",
    UseSSL:    true,
    // 可选
    // SecurityToken:  "temp-token-from-iam-sts",
    // ConnectTimeout: 10,  // 秒,<=0 使用 SDK 默认
    // SocketTimeout:  60,
    // CDNHost:        "cdn.example.com",  // 也接受 "https://cdn.example.com" 等带 scheme 的形式
})
if err != nil {
    log.Fatal(err)
}
defer storage.Close()

uploader, _ := gouploader.New(
    gouploader.WithStorage(storage),                    // 自动启用 Multipart 直传
    gouploader.WithHashStore(myHashStore),               // 秒传
    gouploader.WithMultipartThreshold(100 << 20),        // 大于 100MB 走 Multipart
    gouploader.WithPartSignTTL(15 * time.Minute),
    gouploader.WithOrphanCleanup(24*time.Hour, 1*time.Hour),
)
defer uploader.Close()

实现概览

接口方法 SDK 调用
Save obsClient.PutObject(PutObjectInput{Body, ContentLength})
Get obsClient.GetObject(GetObjectInput) → out.Body
Delete obsClient.DeleteObject(NoSuchKey 幂等)
Exists obsClient.HeadObject(404/NoSuchKey → false)
InitMultipart obsClient.InitiateMultipartUpload
PresignPart obsClient.CreateSignedUrl(HttpMethodPut, QueryParams={partNumber, uploadId}, Headers={Content-Length})
ListParts obsClient.ListParts + 分页
CompleteMultipart obsClient.CompleteMultipartUpload + GetObjectMetadata 补 Size
AbortMultipart obsClient.AbortMultipartUpload(NoSuchUpload 幂等)
ListOrphans obsClient.ListMultipartUploads + 分页 + 时间过滤
GetSignedGetURL obsClient.CreateSignedUrl(HttpMethodGet, Bucket, Key, Expires) + CDN 重写(私有 bucket 临时访问)

签名说明

OBS SDK 使用自家签名算法,与 AWS S3 V2/V4 不同,也与阿里/腾讯不同。 这是本包必须独立实现而不能复用 minio-go 的核心原因之一——minio-go 强制使用 S3 V4,在部分 OBS region 会认证失败。

本包始终使用 obs.CreateSignedUrl,由 SDK 自动选择正确的签名版本。

安全要点

预签名 URL 签入的参数(客户端无法篡改):

  • partNumber / uploadId:写入 CreateSignedUrlInput.QueryParams
  • Content-Length:写入 CreateSignedUrlInput.Headers(req.ContentLength>0 时生效)

签入的 header 会进 OBS 签名算法的摘要;客户端 PUT 时 header 不匹配会 403。

默认 TTL 15 分钟,可通过 WithPartSignTTL() 覆盖。

悬挂 Multipart 清理

应用内通过 WithOrphanCleanup(24h, 1h) 启动 worker 自动扫描 + Abort。

强烈建议同时配置 Bucket 层面的生命周期规则做双保险

控制台 → OBS → 对象存储服务 → 桶列表 → 基础配置 → 生命周期规则 → 新建
规则类型:删除未完成的分段上传任务
触发时间:N 天(建议 7 天)

测试

单元测试(默认)
go test ./...

覆盖:参数校验、Endpoint scheme 推导、URL 签名格式、CDN 重写、错误判定(StatusCode + Code 双路径)、接口合规性等无网络路径。

E2E 集成测试(可选)

需要真实的华为云 OBS 测试 Bucket:

export OBS_ENDPOINT=obs.cn-north-4.myhuaweicloud.com
export OBS_BUCKET=your-test-bucket
export OBS_AK=...
export OBS_SK=...

go test -v -run TestIntegration ./...

集成测试覆盖:

  • TestIntegration_StorageRoundTrip — Save/Get/Delete/Exists 完整闭环
  • TestIntegration_MultipartFullFlow — Init → PresignPart → HTTP PUT 直传 → ListParts → Complete
  • TestIntegration_AbortMultipart_Idempotent — Abort 幂等性
  • TestIntegration_ListOrphans — 悬挂 Multipart 扫描

所有测试对象使用 gouploader-e2e/ 前缀,结束自动清理。未设置环境变量时跳过。

注意事项

partSize 限制

华为云 OBS Multipart 规则:

  • 最小 100 KB(最后一片可更小)
  • 最大 5 GB
  • 最多 10000 个 parts

主包 gouploader.WithChunkSize() 默认 1-20 MB 在 OBS 范围内。

ETag 说明

OBS Multipart 合并后对象的 ETag 不是 完整文件的 MD5(是内部哈希算法,与阿里 OSS 一致)。业务若依赖完整文件 hash 做校验,应使用 gouploader.WithChecksum() 让前端上报 SHA256 并在 OnUploadComplete 验证。

临时凭证(IAM STS)

华为云 IAM 临时凭证(CreateTemporaryAccessKeyByAgency)返回的 AK/SK/SecurityToken 可直接用于 Config:

storage, _ := huawei.NewStorage(huawei.Config{
    Endpoint:      "obs.cn-north-4.myhuaweicloud.com",
    AccessKey:     stsCred.Access,
    SecretKey:     stsCred.Secret,
    SecurityToken: stsCred.SecurityToken,
    Bucket:        "my-bucket",
    Region:        "cn-north-4",
})
Close 的必要性

华为云 OBS SDK 内部维护 HTTP 连接池,长生命周期的 Storage 实例建议在应用退出时调用 Storage.Close() 主动释放连接。短生命周期(请求内创建)可不调用,连接会随 GC 释放。

Documentation

Overview

Package huawei 提供华为云 OBS 的 Storage + MultipartStorage 实现。

本包位于独立子 module,用户按需引入:

go get github.com/gtkit/gouploader/huawei

使用示例:

import (
    "github.com/gtkit/gouploader"
    "github.com/gtkit/gouploader/huawei"
)

storage, err := huawei.NewStorage(huawei.Config{
    Endpoint:  "obs.cn-north-4.myhuaweicloud.com",
    AccessKey: "...",
    SecretKey: "...",
    Bucket:    "my-bucket",
})
uploader, _ := gouploader.New(
    gouploader.WithStorage(storage),
    gouploader.WithMultipartThreshold(100 << 20),
)

实现接口:

  • gouploader.Storage —— Save / Get / Delete / Exists
  • gouploader.MultipartStorage —— InitMultipart / PresignPart / ListParts / CompleteMultipart / AbortMultipart / ListOrphans

华为 OBS 签名说明: OBS SDK 默认使用 OBS 自家签名算法(与 AWS S3 V2/V4 不同)。 本包使用 SDK 默认签名,与 minio-go 强制 V4 签名相比兼容性更好,尤其是某些 region 只接受 OBS 签名。

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Config

type Config struct {
	// Endpoint 访问端点(如 "obs.cn-north-4.myhuaweicloud.com")。
	// 可带 scheme;不带时按 UseSSL 决定。
	Endpoint string

	// AccessKey / SecretKey 华为云 AK/SK。
	AccessKey string
	SecretKey string

	// SecurityToken 临时凭证时使用(可选)。
	SecurityToken string

	// Bucket 对象桶名。
	Bucket string

	// Region 区域(如 "cn-north-4")。部分高级特性签名时需要。
	Region string

	// UseSSL 是否使用 HTTPS。默认 true(未设置 Endpoint scheme 时生效)。
	UseSSL bool

	// ConnectTimeout / SocketTimeout 单位秒。<=0 使用 SDK 默认值。
	ConnectTimeout int
	SocketTimeout  int

	// CDNHost CDN 加速域名,预签名 URL 使用此域名替换 Endpoint。不影响服务端到 OBS 的通信。
	//
	// 接受以下三种输入格式(自动 normalize):
	//   - 纯 host:               "static.example.com"
	//   - 带 scheme:              "https://static.example.com"
	//   - 带 scheme + 多余 path:   "https://static.example.com/ignored-path"
	CDNHost string
}

Config 华为云 OBS 连接参数。

type Storage

type Storage struct {
	// contains filtered or unexported fields
}

Storage 实现 gouploader.Storage + gouploader.MultipartStorage。

func NewStorage

func NewStorage(cfg Config) (*Storage, error)

NewStorage 创建华为云 OBS Storage 实例。

func (*Storage) AbortMultipart

func (s *Storage) AbortMultipart(_ context.Context, key, cloudUploadID string) error

AbortMultipart 中止云端 Multipart。幂等:NoSuchUpload 视为成功。

func (*Storage) Close

func (s *Storage) Close()

Close 释放 OBS Client 内部 HTTP 连接池。 可选调用;不调用也会随进程退出释放。

func (*Storage) CompleteMultipart

func (s *Storage) CompleteMultipart(_ context.Context, key, cloudUploadID string, parts []gouploader.PartETag) (*gouploader.CompleteResult, error)

CompleteMultipart 合并分片为最终对象。

func (*Storage) CopyObject added in v1.5.0

CopyObject 在 OBS 内复制对象。

func (*Storage) Delete

func (s *Storage) Delete(_ context.Context, key string) error

Delete 删除对象。幂等:NoSuchKey 返回 nil。

func (*Storage) DeleteObjects added in v1.5.0

func (s *Storage) DeleteObjects(_ context.Context, keys []string) error

DeleteObjects 批量删除对象。幂等:不存在的 key 由 OBS 按成功处理。

func (*Storage) Exists

func (s *Storage) Exists(_ context.Context, key string) (bool, error)

Exists 检查对象是否存在。

func (*Storage) Get

func (s *Storage) Get(_ context.Context, key string) (io.ReadCloser, error)

Get 读取对象。返回值必须由调用方 Close。

func (*Storage) GetSignedGetURL added in v1.4.0

func (s *Storage) GetSignedGetURL(_ context.Context, key string, ttl time.Duration) (string, error)

GetSignedGetURL 生成对象的预签名 GET URL。

实现:obs-sdk-go 的 CreateSignedUrl 是本地 HMAC 计算无网络调用, 性能适合在 PublicURLBuilder 中按请求生成。

配置了 CDNHost 时,返回的 URL 会被替换为 CDN 域名(保留 scheme/path/query)。

func (*Storage) InitMultipart

func (s *Storage) InitMultipart(_ context.Context, key string, meta gouploader.MultipartMeta) (string, error)

InitMultipart 创建 Multipart 上传会话,返回 OBS 的 UploadId。

func (*Storage) ListOrphans

func (s *Storage) ListOrphans(_ context.Context, olderThan time.Duration) ([]gouploader.OrphanMultipart, error)

ListOrphans 列出超过 olderThan 时长未完成的悬挂 Multipart。

单次最多返回 1000,通过 (KeyMarker, UploadIdMarker) 翻页。 客户端测时过滤:Initiated < (now - olderThan)。

func (*Storage) ListParts

func (s *Storage) ListParts(_ context.Context, key, cloudUploadID string) ([]gouploader.PartInfo, error)

ListParts 列出云端已上传的 parts。处理分页,返回完整列表。

OBS 单次最多返回 1000 parts,通过 PartNumberMarker 翻页直到 IsTruncated=false。

func (*Storage) PresignPart

func (s *Storage) PresignPart(_ context.Context, req gouploader.PresignPartRequest) (string, error)

PresignPart 为单个分片生成预签名 PUT URL。

锁死参数:

  • partNumber / uploadId:签入 URL query
  • Content-Length:签入 header 摘要(req.ContentLength>0 时生效)

重要:使用 OBS 自家签名(非 AWS V4)。某些 region 只接受 OBS 签名, 这是 minio-go 通用兼容层失败的根本原因之一。

func (*Storage) RawClient added in v1.5.0

func (s *Storage) RawClient() *obs.ObsClient

RawClient returns the underlying Huawei OBS SDK client used by this storage.

func (*Storage) Save

func (s *Storage) Save(_ context.Context, key string, reader io.Reader, size int64) error

Save 将流式数据持久化到 OBS。

func (*Storage) SaveObject added in v1.5.0

func (s *Storage) SaveObject(_ context.Context, req gouploader.SaveObjectRequest) error

SaveObject 将流式数据和对象元数据一并持久化到 OBS。

func (*Storage) SaveObjectWithResult added in v1.7.0

SaveObjectWithResult 将流式数据和对象元数据一并持久化到 OBS,并返回保存结果。

func (*Storage) StatObject added in v1.5.0

func (s *Storage) StatObject(_ context.Context, key string) (*gouploader.ObjectStat, error)

StatObject 查询对象的通用元数据。

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL