添加更多的命令
对于添加更多的命令,使用 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)
}
经历一番挫折后,该库的简单使用已掌握,解下来先完成上传文章。