侧边栏壁纸
博主头像
张种恩博主等级

一个能运维的 JPG 搬运工

  • 累计撰写 709 篇文章
  • 累计创建 62 个标签
  • 累计收到 27 条评论

目 录CONTENT

文章目录
Go

使用 Golang 写一个管理腾讯云 COS 对象的命令行程序

张种恩
2022-01-20 / 0 评论 / 0 点赞 / 147 阅读 / 8,820 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2022-01-21,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

背景

由于近期考虑使用 COS 做一些数据备份,所以写了这个程序。

直接看效果:
image.png

源码

  • cos/cos.go
package cos

import (
	"context"
	"encoding/json"
	"fmt"
	"github.com/tencentyun/cos-go-sdk-v5"
	"log"
	"net/http"
	"net/url"
	"os"
	"path"
	"zze/tencent-cloud-cos/utils"
)

func InitCosConfig() {
	config := CosConfig{
		BucketUrl:  "https://<bucket_name>.cos.ap-guangzhou.myqcloud.com",
		ServiceUrl: "https://cos.<region>.myqcloud.com",
		SecretId:   "<secret_id>",
		SecretKey:  "<secret_key>",
	}

	homeDirPath := os.Getenv("HOME")
	configFilePath := path.Join(homeDirPath, ".cos/config.json")
	utils.WriteStringToFile(utils.MarshalObjToJson(config), configFilePath)
}

func getCosConfig() (*CosConfig, error) {
	homeDirPath := os.Getenv("HOME")
	configFilePath := path.Join(homeDirPath, ".cos/config.json")

	if !utils.FileExist(configFilePath) {
		return nil, fmt.Errorf("配置文件 %s 不存在!", configFilePath)
	}
	configStr := utils.ReadFile(configFilePath)
	config := new(CosConfig)
	err := json.Unmarshal([]byte(configStr), config)
	if err != nil {
		return nil, fmt.Errorf("配置文件 %s 内容格式错误!err: %v", configFilePath, err)
	}
	return config, nil
}

type CosConfig struct {
	BucketUrl  string `json:"bucket_url"`
	ServiceUrl string `json:"service_url"`
	SecretId   string `json:"secret_id"`
	SecretKey  string `json:"secret_key"`
}

type CosClient struct {
	client *cos.Client
}

func NewCosClient() (*CosClient, error) {
	var cosClient = new(CosClient)
	config, err := getCosConfig()
	if err != nil {
		return nil, err
	}
	u, _ := url.Parse(config.BucketUrl)
	su, _ := url.Parse(config.ServiceUrl)
	b := &cos.BaseURL{BucketURL: u, ServiceURL: su}
	client := cos.NewClient(b, &http.Client{
		Transport: &cos.AuthorizationTransport{
			SecretID:  config.SecretId,
			SecretKey: config.SecretKey,
		},
	})
	cosClient.client = client
	return cosClient, nil
}

// 上传文件对象到 bucket
func (cosCli CosClient) PutObj(objectKey string, filePath string) {
	_, _, err := cosCli.client.Object.Upload(
		context.Background(), objectKey, filePath, nil,
	)
	if err != nil {
		log.Fatalf("上传文件 [%s] 到 [%s] 失败!err: %v", filePath, objectKey, err)
	}
}

// 从 bucket 删除文件对象
func (cosCli CosClient) DeleteObj(objectKey string) {
	_, err := cosCli.client.Object.Delete(context.Background(), objectKey)
	if err != nil {
		log.Fatalf("删除文件 [%s] 失败!err: %v", objectKey, err)
	}
}

// 从 bucket 下载文件对象
func (cosCli CosClient) DownloadObj(objectKey string, savePath string) {
	opt := &cos.MultiDownloadOptions{
		ThreadPoolSize: 5,
	}
	_, err := cosCli.client.Object.Download(
		context.Background(), objectKey, savePath, opt,
	)
	if err != nil {
		log.Fatalf("下载文件 [%s] 到 [%s] 失败!err: %v", objectKey, savePath, err)
	}
}

// 列出 bucket 中的对象
func (cosCli CosClient) ListObj(prefix string, maxKeys int) {
	opt := &cos.BucketGetOptions{
		Prefix:  prefix,
		MaxKeys: maxKeys,
	}
	v, _, err := cosCli.client.Bucket.Get(context.Background(), opt)
	if err != nil {
		log.Fatalf("查询路径 [%s] 下的对象列表失败!err: %v", prefix, err)
	}
	for _, content := range v.Contents {
		fmt.Printf("%v\n", content.Key)
	}
}
  • utils/file.go
package utils

import (
	"bufio"
	"bytes"
	"encoding/json"
	"io/ioutil"
	"log"
	"os"
)

func FileExist(path string) bool {
	_, err := os.Lstat(path)
	return !os.IsNotExist(err)
}

func ReadFile(path string) string {
	b, err := ioutil.ReadFile(path)
	if err != nil {
		log.Fatalf("读取文件 [%s] 失败!", path)
	}
	return string(b)
}

func WriteStringToFile(msg string, destFilePath string) {
	fileHandle, err := os.OpenFile(destFilePath, os.O_TRUNC|os.O_RDWR|os.O_CREATE, 0600)
	if err != nil {
		log.Fatalf("打开文件 [%s] 失败, err: %v", destFilePath, err)
	}
	defer func() {
		err = fileHandle.Close()
		if err != nil {
			log.Fatalf("关闭文件 [%s] 失败, err: %v", destFilePath, err)
		}
	}()
	buf := bufio.NewWriterSize(fileHandle, len(msg))
	_, err = buf.WriteString(msg)
	if err != nil {
		log.Fatalf("写入字符串到文件 [%s] 失败, err: %v", err)
	}
	err = buf.Flush()
	if err != nil {
		log.Fatalf("刷新缓存到文件 [%s] 失败, err: %v", err)
	}
}

func MarshalObjToJson(obj interface{}) string {
	bf := bytes.NewBuffer([]byte{})
	jsonEncoder := json.NewEncoder(bf)
	jsonEncoder.SetEscapeHTML(false)
	jsonEncoder.SetIndent("   ", "")
	err := jsonEncoder.Encode(obj)
	if err != nil {
		log.Fatalf("序列化对象 [%v] 失败, err: %v", obj)
	}
	return bf.String()
}
  • main.go
package main

import (
	"fmt"
	"github.com/gookit/gcli"
	"log"
	"strconv"
	"zze/tencent-cloud-cos/cos"
)

var (
	client *cos.CosClient
	err    error
)

func listCmd() *gcli.Command {
	cmd := &gcli.Command{
		Name:    "list",
		UseFor:  "查询列表",
		Aliases: []string{"ls"},
		Func: func(cmd *gcli.Command, args []string) int {
			prefixArg := cmd.Arg("prefix")
			maxKeysArg := cmd.Arg("max_keys")
			maxKeys := 100
			if maxKeysArg.HasValue() {
				num, err := strconv.Atoi(fmt.Sprintf("%s", maxKeysArg.Value))
				if err != nil {
					log.Fatalf("参数 <max_keys> 必须是整数类型")
				}
				maxKeys = num
			}
			client.ListObj(fmt.Sprintf("%s", prefixArg.Value), maxKeys)
			return 0
		},
	}
	cmd.AddArg("prefix", "bucket 目录/路径前缀", true)
	cmd.AddArg("max_keys", "最大返回记录条数", false)
	return cmd
}

func downloadCmd() *gcli.Command {
	cmd := &gcli.Command{
		Name:    "download",
		UseFor:  "下载对象",
		Aliases: []string{"get"},
		Func: func(cmd *gcli.Command, args []string) int {
			keyPathArg := cmd.Arg("key_path")
			savePath := cmd.Arg("save_path")
			client.DownloadObj(fmt.Sprintf("%s", keyPathArg.Value), fmt.Sprintf("%s", savePath.Value))
			return 0
		},
	}
	cmd.AddArg("key_path", "对象路径", true)
	cmd.AddArg("save_path", "保存文件的本地目标路径", true)
	return cmd
}

func uploadCmd() *gcli.Command {
	cmd := &gcli.Command{
		Name:    "upload",
		UseFor:  "上传对象",
		Aliases: []string{"put"},
		Func: func(cmd *gcli.Command, args []string) int {
			keyPathArg := cmd.Arg("key_path")
			file_path := cmd.Arg("file_path")
			client.PutObj(fmt.Sprintf("%s", keyPathArg.Value), fmt.Sprintf("%s", file_path.Value))
			return 0
		},
	}
	cmd.AddArg("key_path", "对象路径", true)
	cmd.AddArg("file_path", "本地文件路径", true)
	return cmd
}

func main() {
	client, err = cos.NewCosClient()
	app := gcli.NewApp()
	app.Version = "1.0.0"
	app.Description = "一个支持快速从腾讯云 bucket 上传、下载或删除文件命令行程序"

	// 添加一个自定义的子 Command
	app.Add(&gcli.Command{
		Name:    "init",
		UseFor:  "初始化配置文件 [$HOME/.cos/config.json]",
		Aliases: []string{"i"},
		Func: func(cmd *gcli.Command, args []string) int {
			cos.InitCosConfig()
			return 0
		},
	})

	if err == nil {
		app.Add(listCmd())
		app.Add(downloadCmd())
		app.Add(uploadCmd())
	} else {
		log.Printf("err: %v", err)
	}
	app.Run()
}

使用

第一次执行会有提示信息:

$ cosctl 
2022/01/20 21:04:34 err: 配置文件 /root/.cos/config.json 不存在!
一个支持快速从腾讯云 bucket 上传、下载或删除文件命令行程序 (Version: 1.0.0)
Usage:
  cosctl [Global Options...] {command} [--option ...] [argument ...]

Global Options:
      --verbose     Set error reporting level(quiet 0 - 4 debug)
      --no-color    Disable color when outputting message
  -h, --help        Display the help information
  -V, --version     Display app version information

Available Commands:
  init         初始化配置文件 [$HOME/.cos/config.json] (alias: i)
  help         Display help information

Use "cosctl {command} -h" for more information about a command

如上提示信息,我们需要创建配置目录,然后执行初始化配置操作:

$ mkdir ~/.cos 
$ cosctl init

修改配置:

# bucket_name, cos 存储桶名称
# region, cos 所在区域
# secret_id, api 访问 secret id
# secret_key, api 访问 secret key
$ cat ~/.cos/config.json 
{
   "bucket_url": "https://<bucket_name>.cos.ap-guangzhou.myqcloud.com",
   "service_url": "https://cos.<region>.myqcloud.com",
   "secret_id": "<secret_id>",
   "secret_key": "<secret_key>"
   }

修改好如上配置后就可以对存储桶执行管理操作了:

$ cosctl 
一个支持快速从腾讯云 bucket 上传、下载或删除文件命令行程序 (Version: 1.0.0)
Usage:
  cosctl [Global Options...] {command} [--option ...] [argument ...]

Global Options:
      --verbose     Set error reporting level(quiet 0 - 4 debug)
      --no-color    Disable color when outputting message
  -h, --help        Display the help information
  -V, --version     Display app version information

Available Commands:
  download     下载对象 (alias: get)
  init         初始化配置文件 [$HOME/.cos/config.json] (alias: i)
  list         查询列表 (alias: ls)
  upload       上传对象 (alias: put)
  help         Display help information

Use "cosctl {command} -h" for more information about a command

子命令使用帮助可通过 -h 获取:

$ cosctl list -h 
查询列表

Name: list (alias: ls)
Usage: cosctl [Global Options...] list [--option ...] [argument ...]

Global Options:
      --verbose     Set error reporting level(quiet 0 - 4 debug)
      --no-color    Disable color when outputting message
  -h, --help        Display this help information

Arguments:
  prefix      Bucket 目录/路径前缀*
  max_keys    最大返回记录条数

$ cosctl upload -h
上传对象

Name: upload (alias: put)
Usage: cosctl [Global Options...] upload [--option ...] [argument ...]

Global Options:
      --verbose     Set error reporting level(quiet 0 - 4 debug)
      --no-color    Disable color when outputting message
  -h, --help        Display this help information

Arguments:
  key_path    对象路径*
  file_path   本地文件路径*
 
$ cosctl get -h
下载对象

Name: download (alias: get)
Usage: cosctl [Global Options...] download [--option ...] [argument ...]

Global Options:
      --verbose     Set error reporting level(quiet 0 - 4 debug)
      --no-color    Disable color when outputting message
  -h, --help        Display this help information

Arguments:
  key_path    对象路径*
  save_path   保存文件的本地目标路径*

下载

我已经编译好 linux 和 mac 下的二进制文件,可通过如下网盘链接获取:

当然你如果有足够的动手能力的也可以对上述源码进行定制然后自己编译~

0

评论区