《 GO语言公链开发实战》 —2.2.3 bytomcli运行案例

举报
华章计算机 发表于 2019/07/23 22:22:47 2019/07/23
【摘要】 本节书摘来自华章计算机《GO语言公链开发实战》一书中的第2章,第2.2.3节,作者是郑东旭 杨明珠 潘盈瑜 翟萌 。

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()

}

 

 image.png

图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命令执行完成,其生命周期终止。


【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。