Go追加上传

追加上传是指在已上传的追加类型文件(Appendable Object)末尾直接追加内容。本文介绍如何使用OSS Go SDK进行追加上传。

注意事项

  • 本文示例代码以华东1(杭州)的地域IDcn-hangzhou为例,默认使用外网Endpoint,如果您希望通过与OSS同地域的其他阿里云产品访问OSS,请使用内网Endpoint。关于OSS支持的RegionEndpoint的对应关系,请参见OSS地域和访问域名

  • 本文以从环境变量读取访问凭证为例。如何配置访问凭证,请参见配置访问凭证

  • 要进行简单上传,您必须有oss:PutObject权限。具体操作,请参见RAM用户授权自定义的权限策略

  • 当文件不存在时,调用追加上传接口会创建一个追加类型文件。

  • 当文件已存在时:

    • 如果文件为追加类型文件,且设置的追加位置和文件当前长度相等,则直接在该文件末尾追加内容。

    • 如果文件为追加类型文件,但是设置的追加位置和文件当前长度不相等,则抛出PositionNotEqualToLength异常。

    • 如果文件为非追加类型文件,例如通过简单上传的文件类型为Normal的文件,则抛出ObjectNotAppendable异常。

方法定义

针对文件追加上传的场景,Go SDK V2新增了AppendFile接口以模仿文件的读写行为,用于操作存储空间里的对象,以下列举了AppendFileAppendObject接口的具体说明:

接口名

说明

Client.AppendObject

追加上传, 最终文件最大支持5GiB

支持CRC64数据校验(默认启用)

支持进度条

请求body类型为io.Reader, 当支持io.Seeker类型时,具备失败重传(该接口为非幂等接口,重传时可能出现失败)

Client.AppendFile

Client.AppendObject接口能力一致

优化了重传时失败后容错处理

包含AppendOnlyFile接口

AppendOnlyFile.Write

AppendOnlyFile.WriteFrom

(推荐)高级版追加上传API:AppendFile

调用AppendFile接口以追加写的方式上传数据。如果对象不存在,则创建追加类型的对象。如果对象存在,并且不为追加类型的对象,则返回错误。

AppendFile接口定义如下。

type AppendOnlyFile struct {
...
}

func (c *Client) AppendFile(ctx context.Context, bucket string, key string, optFns ...func(*AppendOptions)) (*AppendOnlyFile, error)

请求参数列表

参数名

类型

说明

ctx

context.Context

请求的上下文

bucket

string

设置存储空间名

key

string

设置对象名

optFns

...func(*AppendOptions)

(可选)追加文件时的配置选项

其中,AppendOptions的参数说明列举如下:

参数

类型

说明

RequestPayer

*string

启用了请求者付费模式时,需要设置为'requester'

CreateParameter

*AppendObjectRequest

用于首次上传时,设置对象的元信息,包括ContentType,Metadata,权限,存储类型等,具体请参见AppendObjectRequest

返回值列表

返回值名

类型

说明

file

*AppendOnlyFile

追加文件的实例,当 err 为nil 时有效,具体请参见AppendOnlyFile

err

error

打开追加文件时的状态,当失败时,err 不为 nil

其中,AppendOnlyFile接口说明如下:

接口名

说明

Close() error

关闭文件句柄,释放资源

Write(b []byte) (int, error)

b中的数据写入到数据流中,返回写入的字节数和遇到的错误

WriteFrom(r io.Reader) (int64, error)

r中的数据写入到数据流中,返回写入的字节数和遇到的错误

Stat() (os.FileInfo, error)

获取对象的信息,包括 对象大小,最后修改时间 以及元信息

基础版追加上传API:AppendObject

func (c *Client) AppendObject(ctx context.Context, request *AppendObjectRequest, optFns ...func(*Options)) (*AppendObjectResult, error)

请求参数列表

参数名

类型

说明

ctx

context.Context

请求的上下文,可以用来设置请求的总时限

request

*AppendObjectRequest

设置具体接口的请求参数,具体请参见AppendObjectRequest

optFns

...func(*Options)

(可选)接口级的配置参数, 具体请参见Options

返回值列表

返回值名

类型

说明

result

*AppendObjectResult

接口返回值,当 err 为nil 时有效,具体请参见AppendObjectResult

err

error

请求的状态,当请求失败时,err 不为 nil

示例代码

(推荐)使用AppendFile追加上传

package main

import (
	"context"
	"flag"
	"log"
	"os"

	"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss"
	"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss/credentials"
)

// 定义全局变量
var (
	region     string // 存储区域
	bucketName string // 存储空间名称
	objectName string // 对象名称
)

// init函数用于初始化命令行参数
func init() {
	flag.StringVar(&region, "region", "", "The region in which the bucket is located.")
	flag.StringVar(&bucketName, "bucket", "", "The name of the bucket.")
	flag.StringVar(&objectName, "object", "", "The name of the object.")
}

func main() {
	// 解析命令行参数
	flag.Parse()

	// 检查存储空间名称是否为空
	if len(bucketName) == 0 {
		flag.PrintDefaults()
		log.Fatalf("invalid parameters, bucket name required")
	}

	// 检查存储区域是否为空
	if len(region) == 0 {
		flag.PrintDefaults()
		log.Fatalf("invalid parameters, region required")
	}

	// 检查对象名称是否为空
	if len(objectName) == 0 {
		flag.PrintDefaults()
		log.Fatalf("invalid parameters, object name required")
	}

	// 配置OSS客户端
	cfg := oss.LoadDefaultConfig().
		WithCredentialsProvider(credentials.NewEnvironmentVariableCredentialsProvider()).
		WithRegion(region)

	// 创建OSS客户端
	client := oss.NewClient(cfg)

	// 创建一个追加文件的实例
	f, err := client.AppendFile(context.TODO(),
		bucketName,
		objectName,
		func(ao *oss.AppendOptions) {
			ao.CreateParameter = &oss.AppendObjectRequest{
				Acl: oss.ObjectACLPrivate, // 设置对象的访问权限为私有
				Metadata: map[string]string{
					"user": "jack", // 设置对象的元数据
				},
				Tagging: oss.Ptr("key=value"), // 设置对象的标签
			}
		})

	if err != nil {
		log.Fatalf("failed to append file %v", err)
	}
	defer f.Close() // 确保文件流在函数结束时关闭

	// 打开本地文件 example1.txt
	lf, err := os.Open("/local/dir/example1.txt")
	if err != nil {
		log.Fatalf("failed to open local file %v", err)
	}

	// 将 example1.txt 的内容追加到 OSS 对象
	_, err = f.WriteFrom(lf)
	if err != nil {
		log.Fatalf("failed to append file %v", err)
	}
	lf.Close() // 关闭本地文件

	// 打开本地文件 example2.txt
	lf, err = os.Open("/local/dir/example2.txt")
	if err != nil {
		log.Fatalf("failed to open local file %v", err)
	}

	// 将 example2.txt 的内容追加到 OSS 对象
	_, err = f.WriteFrom(lf)
	if err != nil {
		log.Fatalf("failed to append file %v", err)
	}
	lf.Close() // 关闭本地文件

	// 读取本地文件 example3.txt 的内容
	lb, err := os.ReadFile("/local/dir/example3.txt")
	if err != nil {
		log.Fatalf("failed to read local file %v", err)
	}

	// 将 example3.txt 的内容追加到 OSS 对象
	_, err = f.Write(lb)
	if err != nil {
		log.Fatalf("failed to append file %v", err)
	}

	// 打印成功追加文件的日志信息
	log.Printf("append file successfully")
}

使用AppendObject追加上传

package main

import (
	"context"
	"flag"
	"log"
	"strings"

	"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss"
	"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss/credentials"
)

// 定义全局变量
var (
	region     string
	bucketName string
	objectName string
)

// init函数用于初始化命令行参数
func init() {
	flag.StringVar(&region, "region", "", "The region in which the bucket is located.")
	flag.StringVar(&bucketName, "bucket", "", "The name of the bucket.")
	flag.StringVar(&objectName, "object", "", "The name of the object.")
}

func main() {
	// 解析命令行参数
	flag.Parse()

	// 定义追加文件的初始位置
	var (
		position = int64(0)
	)

	// 检查bucket名称是否为空
	if len(bucketName) == 0 {
		flag.PrintDefaults()
		log.Fatalf("invalid parameters, bucket name required")
	}

	// 检查region是否为空
	if len(region) == 0 {
		flag.PrintDefaults()
		log.Fatalf("invalid parameters, region required")
	}

	// 检查object名称是否为空
	if len(objectName) == 0 {
		flag.PrintDefaults()
		log.Fatalf("invalid parameters, object name required")
	}

	// 加载默认配置并设置凭证提供者和region
	cfg := oss.LoadDefaultConfig().
		WithCredentialsProvider(credentials.NewEnvironmentVariableCredentialsProvider()).
		WithRegion(region)

	// 创建OSS客户端
	client := oss.NewClient(cfg)

	// 定义要追加的内容
	content := "hi append object"

	// 创建AppendObject请求
	request := &oss.AppendObjectRequest{
		Bucket:   oss.Ptr(bucketName),
		Key:      oss.Ptr(objectName),
		Position: oss.Ptr(position),
		Body:     strings.NewReader(content),
	}

	// 执行AppendObject请求并处理结果
	// 第一次追加上传的位置是0,返回值中包含下一次追加的位置
	result, err := client.AppendObject(context.TODO(), request)
	if err != nil {
		log.Fatalf("failed to append object %v", err)
	}

	// 创建第二次AppendObject请求
	request = &oss.AppendObjectRequest{
		Bucket:   oss.Ptr(bucketName),
		Key:      oss.Ptr(objectName),
		Position: oss.Ptr(result.NextPosition), //从第一次AppendObject返回值中获取NextPosition
		Body:     strings.NewReader("hi append object"),
	}

	// 执行第二次AppendObject请求并处理结果
	result, err = client.AppendObject(context.TODO(), request)
	if err != nil {
		log.Fatalf("failed to append object %v", err)
	}

	log.Printf("append object result:%#v\n", result)
}

相关文档