问题 Link to heading

腾讯云的对象存储可以看做是一个线上的KV,因为最近有需求所以试着用了一下。在使用的时候遇到了一些问题,具体来说是cos.BucketGetOptions中的Delimiter的使用问题。

按官方的说法,delimiter表示列出当前目录下的object,设置为空的时候列出所有的object。我不太清楚这个所有具体是什么意思,因此还是实践了一下。

实践与代码 Link to heading

代码与腾讯cos Go SDK使用学习比较类似,本身应该没有难度。

代码包含以下内容:

  1. 环境构造:通过批量上传文件来构建复杂的文件环境,为后续获取与下载文件提供基础
  2. 文件下载:测试文件内容是否符合预期
  3. 文件批量下载:测试delimiter选项在下载中的影响
  4. 文件列出:测试delimiter选项在文件列出中的影响

环境构造 Link to heading

使用以下函数构造环境,注意:腾讯云对象存储不是免费服务,使用时注意费用情况。本文示例的文件操作理论上不会超过免费限制,但在修改代码的时候请注意。

func main(){
    u, _ := url.Parse("https://wtytest-1252789333.cos.ap-guangzhou.myqcloud.com")
	b := &cos.BaseURL{BucketURL: u}
	c := cos.NewClient(b, &http.Client{
		Transport: &cos.AuthorizationTransport{
			// 通过环境变量获取密钥
			// 环境变量 COS_SECRETID 表示用户的 SecretId,登录访问管理控制台查看密钥,https://console.cloud.tencent.com/cam/capi
			SecretID: "secretID",
			// 环境变量 COS_SECRETKEY 表示用户的 SecretKey,登录访问管理控制台查看密钥,https://console.cloud.tencent.com/cam/capi
			SecretKey: "secret",
			// Debug 模式,把对应 请求头部、请求内容、响应头部、响应内容 输出到标准输出
			Transport: &debug.DebugRequestTransport{
				RequestHeader:  true,
				RequestBody:    true,
				ResponseHeader: true,
				ResponseBody:   false,
			},
		},
	})
	// 1. 批量上传文件,构建测试集合
	uploadFileToCos(0, "", c)
}

func uploadFileToCos(depth int, prefix string, c *cos.Client) {
	if depth > 5 {
		return
	}
	for i := 0; i < 3; i++ {
		s := rand.Intn(100)
		filePath := prefix + "file" + strconv.Itoa(s)
		err := uploadFileByName(filePath, "file"+strconv.Itoa(s), c)
		if err != nil {
			log_status(err)
		}
	}

	for i := 0; i < 2; i++ {
		s := rand.Intn(100)
		filePrefix := prefix + "path" + strconv.Itoa(s) + "/"
		uploadFileToCos(depth+1, filePrefix, c)
	}
}

文件下载 Link to heading


// 随便选一个文件名,测试一下是否成功写入
//getFileByName("path59/file81", c)
// 结果为content: file81
func getFileByName(name string, c *cos.Client) {
	resp, err := c.Object.Get(context.Background(), name, nil)
	log_status(err)
	bs, _ := ioutil.ReadAll(resp.Body)
	resp.Body.Close()
	fmt.Println("content:", string(bs))
}

文件批量下载 Link to heading

func main(){
	const NoDelimiter = ""
	const FolderDelimiter = "/"
	const path1 = "path59/path33/path43/" // 测试末尾为/
    const path2 = "path59/path33/path43" // 测试末尾不为/
	getAllFileInPrefix(path1, NoDelimiter, c)
	getAllFileInPrefix(path1, FolderDelimiter, c)
}

func getAllFileInPrefix(prefix string, delimiter string, c *cos.Client) {
	totalFileCount := 0
	isTruncated := true
	marker := ""
	count := 0
	for isTruncated {
		fmt.Printf("count:%d marker:%s isTruncated:%v", count, marker, isTruncated)
		count++

		opt := &cos.BucketGetOptions{
			Prefix:    prefix,
			Marker:    marker,
			Delimiter: delimiter,
		}
		v, _, err := c.Bucket.Get(context.Background(), opt)
		if err != nil {
			log_status(err)
			return
		}
		for _, content := range v.Contents {
			resp, err := c.Object.Get(context.Background(), content.Key, nil)
			if err != nil {
				log_status(err)
				return
			}
			bs, _ := ioutil.ReadAll(resp.Body)
			resp.Body.Close()
			fmt.Printf("key:%s content:%s\n", content.Key, string(bs))
			totalFileCount++
		}
		marker = v.NextMarker
		isTruncated = v.IsTruncated
	}
	fmt.Println("total file count is ", totalFileCount, " with count:", count)
}

当cos的键的前缀末尾有"/“时,采用NoDelimiter能获得21个文件,采用FolderDelimiter能获得3个文件。 当cos的键的前缀末尾没有”/“时,采用NoDelimiter能获得21个文件,采用FolderDelimiter能获得0个文件。

这说明delimiter为”/“的时候可以理解为文件夹模式,文件夹模式只对模拟出的文件夹(即前缀末尾有”/")起作用。 如果delimiter为空字符串的时候则会尝试匹配前缀相同的对象。

文件列出 Link to heading

文件列出与文件下载开始的一段逻辑有点类似,不过加上了commonPrefix。测试代码与此前相同,此处不再赘述。

func listAllFileInPrefix(prefix string, delimiter string, c *cos.Client) {
	var marker string
	opt := &cos.BucketGetOptions{
		Prefix:    prefix,
		Delimiter: delimiter,
		MaxKeys:   1000,
	}
	totalFileCount := 0
	commonPrefixCount := 0
	isTruncated := true
	for isTruncated {
		opt.Marker = marker
		v, _, err := c.Bucket.Get(context.Background(), opt)
		if err != nil {
			fmt.Println(err)
			break
		}
		for _, content := range v.Contents {
			fmt.Printf("Object: %v\n", content.Key)
		}
		totalFileCount += len(v.Contents)
		// common prefix表示表示被delimiter截断的路径, 如delimter设置为/, common prefix则表示所有子目录的路径
		for _, commonPrefix := range v.CommonPrefixes {
			fmt.Printf("CommonPrefixes: %v\n", commonPrefix)
		}
		commonPrefixCount += len(v.CommonPrefixes)
		isTruncated = v.IsTruncated // 是否还有数据
		marker = v.NextMarker       // 设置下次请求的起始 key
	}
	fmt.Println("total file count", totalFileCount, " commonPrefix:", commonPrefixCount)
}

当cos的键的前缀末尾有"/“时,采用NoDelimiter能获得21个文件且没有截断路径,采用FolderDelimiter能获得3个文件并截断了2个路径(当前目录下的两个子目录)。 当cos的键的前缀末尾没有”/“时,采用NoDelimiter能获得21个文件且没有截断路径,采用FolderDelimiter能获得0个文件,但是截断了一个路径(path59/path33/path43/)。

总结 Link to heading

对象存储的delimiter可以开启文件夹模式来模拟一般操作系统中的文件夹,总体来说还是比较有用的。

平时不看评论,如果希望和我交流可以发邮件到wtysos11@gmail.com。