添加更多的命令

对于添加更多的命令,使用 flag,就有点麻烦了,这次我们使用一个更高级的库 cobra。同时,我们使用 viper 替换 ini 库,这个库可以读取多种格式的配置文件,可以读取环境变量。

要实现的功能如下:

  • 上传文章
  • 文章删除
  • 更新文章标题
  • 更新文章关键字
  • 更新文章内容
  • 文章公开开关
  • 文章加锁开关
  • 根据标题查找文章
  • 根据关键字查询文章

现在使用 cobra 实现上面的命令。

首先,我想要的效果如下:

  • 上传文章 gart upload title keyword filename ispub islock
  • 删除文章 gart remove uuid
  • 更新文章标题 gart updatetitle uuid title
  • 更新文章关键字 gart updatekeyword uuid keyword
  • 更新文章内容 gart updatecontent uuid filename
  • 更新文章公开或不公开 gart updatepub uuid ispub
  • 更新文章加锁或不加锁 gart updatelock uuid islock
  • 根据标题或关键字查找文章 gart search content

上面的 upload,remove,updatetitle,updatekeyword 等,在 cobra 中都是命令。title keyword 等都是参数。

我在读 cobra 文档后,最疑惑的地方就是标志和参数,这两者最大的区别就是标志需要在命令中添加--flag 然后是值,而参数是直接跟在命令后,直接就是内容的。标志可以更改程序的行为。

我在这个程序中,配置了标志 config,就是获取配置文件。当用户在命令行中配置--config xxx.file 就从命令中读取配置文件,否则就在用户的 HOME 目录查找配置文件。获取 icode 和 isecret。

详细阅读 cobra 文档后,开始撸代码。参考样例文档,我们也先改造 go 入口文件 main.go。

package main

import "gart/cmd"

func main() {
	cmd.Execute()
}

就是这样的简单,只有一个执行函数。它会加载 cmd 文件夹下的 root 命令。

cmd 文件夹组织如下:

cmd
  root.go
  upload.go
  remove.go
  ...

我们看下 root.go,这个文件还是很重要的。


var (
	cfgFile string
	token   string
)

func init() {
	cobra.OnInitialize(initConfig)
	rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/conf.toml)")
}

func initConfig() {
	if cfgFile != "" {
		viper.SetConfigFile(cfgFile)
	} else {
		home, err := homedir.Dir()
		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		}
		viper.AddConfigPath(home)
		viper.AddConfigPath(".")
		viper.SetConfigName("conf")
		viper.SetConfigType("toml")
	}

	if err := viper.ReadInConfig(); err != nil {
		fmt.Println("Cant't read config:", err)
		os.Exit(1)
	}
	// 每次启动都调用token
	getToken()

}

// 根命令
var rootCmd = &cobra.Command{
	Use:   "gart",
	Short: "gart 是文章管理命令行工具。",
	Long:  `gart 是文章管理命令行工具,主要用来管理豆子碎片小程序中的文章。`,
	Run: func(cmd *cobra.Command, args []string) {
		fmt.Println("该命令行工具用来管理豆子碎片小程序的文章。")
	},
}
// main函数中调用的该函数
func Execute() {
	if err := rootCmd.Execute(); err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
}

首先,需要定义根命令 rootCmd,然后实现 Execute()函数。

在初始化 init()函数中,定义了 cobra 初始化回调函数,进行了全局标志的配置以及配置文件的加载。

我这里还添加了 token 的获取,这个花费了我一些时间,琢磨它的调用顺序。它的调用顺序如下: init()->initConfig()->getToken()

这之后,token 的变量就是有效的。在其它的命令中就可以直接使用。

getToken 函数定义如下:


func getToken() {
	var err error
	icode := viper.GetString("icode")
	isecret := viper.GetString("isecret")
	expire := viper.GetString("expire_at")
	expireAt := utils.Str2Int64(expire)
	now := time.Now().Unix()
	if now > expireAt {
		token, err = service.GetToken(icode, isecret)
		if err != nil {
			fmt.Println("获取token错误,", err)
			os.Exit(1)
		}
		expireAtStr := strconv.FormatInt(now+7000, 10)
		viper.Set("expire_at", expireAtStr)
		viper.Set("token", token)
		err = viper.WriteConfig()
		if err != nil {
			fmt.Println("写入配置文件错误,", err)
			os.Exit(1)
		}
	} else {
		token = viper.GetString("token")
	}
}

在 Viper 这回写到文件中,遇到了点问题,刚开始获取 token 后,无法写入到配置文件中。参考文档,已经设置了新的有效时间和 token。就是这两句代码:

viper.Set("expire_at", expireAtStr)
viper.Set("token", token)

排查后,发现没有调用下面的函数原因:

err = viper.WriteConfig()
if err != nil {
			fmt.Println("写入配置文件错误,", err)
			os.Exit(1)
		}

经历一番挫折后,该库的简单使用已掌握,解下来先完成上传文章。