《 GO语言公链开发实战》 —2.2.3 bytomcli运行案例
2.2.3 bytomcli运行案例
本小节我们以net-info命令为例对bytomcli的源码进行剖析,其他命令参数与net-info参数执行的过程大同小异。
bytomcli相关代码文件结构如下所示:
$ tree cmd/bytomcli/
cmd/bytomcli/
├── bytomcli
├── commands commands目录存放bytomcli所有参数的实现
│ ├── accesstoken.go token参数相关
│ ├── account.go 账户参数相关
│ ├── asset.go 资产参数相关
│ ├── block.go 块参数相关
│ ├── bytomcli.go bytomcli flag解析相关
│ ├── key.go key参数相关
│ ├── mining.go 挖矿参数相关
│ ├── net.go 节点状态参数相关
│ ├── program.go 合约参数相关
│ ├── template.go 交易模板参数相关
│ ├── transaction.go 交易参数相关
│ ├── txfeed.go 目前版本该功能未使用
│ ├── util.go bytomcli相关输出结构体
│ ├── version.go 节点版本参数相关
│ └── wallet.go 本地钱包参数相关
└── main.go bytomcli入口函数
1. Cobra库介绍
bytomcli命令行工具是基于Cobra实现的。Cobra既可以用来创建强大的现代CLI应用程序库,也可以用来生成应用和程序文件。很多知名的开源软件都使用Cobra实现其CLI部分,例如kubernetes、docker、etcd等。
Cobra基于三个基本的概念—commands、arguments和flags,实现对命令参数的解析和行为控制。commands是应用程序的中心,应用程序支持的每个交互都包含在命令中,命令可以具有子命令并可选地运行子命令。flags是修改命令行为的方法,Cobra支持POSIX标准和GO的flags包,并可以同时作用于父命令和子命令的参数,也支持只在父命令或者子命令有效的参数。
如何使用Cobra库来构建自己的CLI程序呢?下面我们简单介绍Cobra库的使用。
(1)安装Cobra库
Cobra是非常容易使用的,使用go get来安装最新版本的库。Cobra库相对比较大,安装它可能需要花费一些时间。安装完成后,在GOPATH/bin目录下应该有已经编译好的Cobra程序。
$ go get -v github.com/spf13/cobra/cobra
(2)使用cobra命令生成应用程序
假设现在我们要开发一个基于CLI的命令程序,名字为demo。首先打开cmd,切换到GOPATH的src目录下,执行如下命令:
$ cobra init demo
在src目录下会生成一个demo的文件夹,如下:
$ tree demo/
demo/
├── LICENSE
├── cmd
│ └── root.go
└── main.go
如果此时应用程序不需要子命令,那么Cobra生成应用程序的操作就结束了。这里我们先实现一个没有子命令的CLI程序,之后再为程序添加子命令。
接下来继续为demo设计功能。我们打开Cobra自动生成的main.go文件查看,如下:
package main
import "demo/cmd"
func main() {
cmd.Execute()
}
main函数执行cmd包的Execute()方法,打开cmd包下的root.go文件查看,发现里面进行了一些初始化操作,并提供了Execute接口。其实,Cobra自动生成的root.go文件中有很多初始化操作是不需要的,其中viper是Cobra集成的配置文件读取的库,这里不需要使用,可以注释掉,如不注释掉则生成的应用程序会大10M左右。
在demo下面新建一个imp包,imp.go内容如下:
package imp
import(
"fmt"
)
func Show(name string, age int) {
fmt.Printf("My Name is %s, My age is %d\n", name, age)
}
在imp.go文件中,Show函数接收两个参数—name和age,使用fmt打印出来。此时,整个demo项目目录结构如下:
$ tree demo/
demo/
├── LICENSE
├── cmd
│ └── root.go
├── imp
│ ├── imp.go
└── main.go
Cobra的所有命令都是通过cobra.Command结构体实现的。为了实现demo功能,我们需要修改RootCmd。修改demo/cmd/root.go文件,如下所示:
var RootCmd = &cobra.Command{
Use: "demo",
Short: "A test demo",
Long: `Demo is a test appcation for print things`,
// Uncomment the following line if your bare application
// has an action associated with it:
Run: func(cmd *cobra.Command, args []string) {
if len(name) == 0 {
cmd.Help()
return
}
imp.Show(name, age)
},
}
虽然我们已经定义了command结构,也能通过Execute接口调用RootCmd定义的回调方法。但是若要实现demo的功能还需从命令行解析传入的参数,这部分应该如何实现?这部分可以在cmd包的init方法中实现。init()函数会在每个包完成初始化后自动执行,并且执行优先级比main函数高。init()函数通常被用来对变量进行初始化操作。这里我们使用init()函数在main函数执行之前解析命令行参数。修改demo/cmd/root.go文件,如下所示:
var (
name string
age int
)
func init() {
RootCmd.Flags().StringVarP(&name, "name", "n", "", "person's name")
RootCmd.Flags().IntVarP(&age, "age", "a", 0, "person's age")
}
至此,demo的功能已经实现了,我们编译运行一下看看实际效果,如下所示:
$ go run main.go
Usage:
demo [flags]
Flags:
-a, --age int person's age
--config string config file (default is $HOME/.demo.yaml)
-h, --help help for demo
-n, --name string person's name
-t, --toggle Help message for toggle
如果我们想实现一个带有子命令的CLI程序,只需要再执行cobra add为程序新增子命令。下面我们为demo程序新增一个server的子命令,如下所示:
$ cd demo
$ cobra add server
在demo目录下生成了一个cmd/server.go文件,如下所示:
$ tree demo/
demo/
├── LICENSE
├── cmd
│ ├── root.go
│ └── server.go
├── imp
│ └── imp.go
└── main.go
接下来配置server子命令,此操作与前文中修改root.go文件类似效果如下:
$ go run main.go
Usage:
demo [flags]
demo [command]
Available Commands:
help Help about any command
server A brief description of your command
Flags:
-a, --age int person's age
--config string config file (default is $HOME/.demo.yaml)
-h, --help help for demo
-n, --name string person's name
-t, --toggle Help message for toggle
Use "demo [command] --help" for more information about a command.
2. bytomcli运行流程与原理
对Cobra库有了了解之后,我们再来学习bytomcli的代码时会感觉比较容易,其运行原理如图2-1所示。同上一节中的demo程序一样,bytomcli在main函数中使用cmd.Execute()来启动应用程序,cmd.Execute()是调用commands包的Execute()方法。在mian.go中引入commands包的时候,给它起了一个别名cmd。
cmd/bytomcli/main.go
func main() {
runtime.GOMAXPROCS(runtime.NumCPU())
cmd.Execute()
}
图2-1 bytomcli运行原理
当程序调用commands包的Execute()方法时,会先执行commands包中所有的init()方法,这些init()主要用来解析命令行参数。当执行完commands包中所有的init()方法之后,才会执行Execute()。在Execute()方法中,首先执行AddCommands()方法,该方法主要用来给BytomcliCmd命令添加子命令;而AddTemplateFunc()方法则是为子命令添加使用模板的。在解析flags参数、添加子命令和添加使用模板这些初始化操作完成之后,才真正进入命令的执行过程。在BytomcliCmd.ExecuteC()方法中,Cobra库会根据用户输入的命令跳转到相应的子命令,同时执行命令定义的Run方法。
cmd/bytomcli/commands/bytomcli.go
func Execute() {
AddCommands()
AddTemplateFunc()
if _, err := BytomcliCmd.ExecuteC(); err != nil {
os.Exit(util.ErrLocalExe)
}
}
下面我们以create-access-token命令为例,看看这个命令的Run方法都执行了哪些操作。首先会取args参数的第一位作为tokenID,然后调用util包下的ClientCall方法请求/create-access-token路径,并将tokenID传入,创建token。
bytom/cmd/bytomcli/commands/accesstoken.go
var createAccessTokenCmd = &cobra.Command{
Use: "create-access-token <tokenID>",
Short: "Create a new access token",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
var token accessToken
token.ID = args[0]
data, exitCode := util.ClientCall("/create-access-token", &token)
if exitCode != util.Success {
os.Exit(exitCode)
}
printJSON(data)
},
}
ClientCall方法封装了一个RPC的client,根据用户传入的路径和参数发送请求,并解析RPC server返回的内容。
bytom/util/util.go
func ClientCall(path string, req ...interface{}) (interface{}, int) {
var response = &api.Response{}
var request interface{}
if req != nil {
request = req[0]
}
client := MustRPCClient()
client.Call(context.Background(), path, request, response)
switch response.Status {
case api.FAIL:
jww.ERROR.Println(response.Msg)
return nil, ErrRemote
case "":
jww.ERROR.Println("Unable to connect to the bytomd")
return nil, ErrConnect
}
return response.Data, Success
}
至此,create-access-token命令执行完成,其生命周期终止。
- 点赞
- 收藏
- 关注作者
评论(0)