4.命令行工具
截至目前我们区块链的基本逻辑已经构建完毕,现在我们使用命令行工具按照我们想要的目的进行输出
4.1 flag基本使用
在此之前,我们先来看看flag的基本使用方法
/Users/xxx/go/src/publicChain/part21-cli-flag/main.go
package main
import (
"flag"
"fmt"
)
func main() {
flagString := flag.String("printChain", "", "output all details of blocks")
flagInt := flag.Int("number", 6, "output an int")
flagBool := flag.Bool("open", false, "Judging true and false")
flag.Parse()
fmt.Printf("%s\n", *flagString)
fmt.Printf("%d\n", *flagInt)
fmt.Printf("%t\n", *flagBool)
}
在终端构建程序并运行
cd /Users/xxx/go/src/publicChain/part21-cli-flag
go build main.go
./main
输出结果
6
false
通过-输出指定内容,指定的顺序可以变化
./main -printChain "jech" -open -number 10
jech
10
true
4.2 os
我们再来看一个os包下面的变量args
/Users/xxx/go/src/publicChain/part22-cli-osArgs/main.go
func main() {
args := os.Args
fmt.Printf("%v\n", args)
}
go build main.go
./main printChain -data "jech.org"
[./main printChain -data jech.org]
它能够将输入连接成一个数组,这个数组中的元素可以随着输入无限增长
./main how are you
[./main how are you]
当然我们可以对这个动态数组求长度、访问里面的元素等
4.3 命令行解释器
现在我们结合os包下的变量args和flag实现我们自定义的命令行工具。args可以获取输入,通过一定的解析可以实现自定义的命令行工具,我们来看看具体代码实现:
/Users/xxx/go/src/publicChain/part23-cli/main.go
func printUsage() {
fmt.Println("Usage:")
fmt.Println("\taddBlock -data DATA - transaction data")
fmt.Println("\tprintChain -- output block's information")
}
func isValidArgs() {
if len(os.Args) < 2 {
printUsage()
os.Exit(1)
}
}
func main() {
isValidArgs()
//custom command
addBlockCmd := flag.NewFlagSet("addBlock", flag.ExitOnError)
printChainCmd := flag.NewFlagSet("printChain", flag.ExitOnError)
flagAddBlockData := addBlockCmd.String("data", "jech.org", "transaction data...")
switch os.Args[1] {
case "addBlock":
err := addBlockCmd.Parse(os.Args[2:])
if err != nil {
log.Panic(err)
}
case "printChain":
err := printChainCmd.Parse(os.Args[2:])
if err != nil {
log.Panic(err)
}
default:
printUsage()
os.Exit(1)
}
if addBlockCmd.Parsed() {
if *flagAddBlockData == "" {
printUsage()
os.Exit(1)
}
fmt.Println(*flagAddBlockData)
cli.addBlock(*flagAddBlockData)
}
if printChainCmd.Parsed() {
//fmt.Println("output all blocks' information")
cli.printChain()
}
}
4.4 数据持久化
现在我们要实现命令行工具将区块添加至数据库,并且根据需要打印区块链
把/Users/xxx/go/src/publicChain/part16-persistence-iteration复制一份并命名为part24-persistence-cli
我们先把上述代码放到独立文件中
/Users/xxx/go/src/publicChain/part24-persistence-cli/BLC/CLI.go
type CLI struct {
Blockchain *Blockchain
}
func printUsage() {
fmt.Println("Usage:")
fmt.Println("\taddBlock -data DATA - transaction data")
fmt.Println("\tprintChain -- output block's information")
}
func isValidArgs() {
if len(os.Args) < 2 {
printUsage()
os.Exit(1)
}
}
func (cli CLI) addBlock(data string) {
cli.Blockchain.AddBlockToBlockchain(data)
}
func (cli CLI) printChain() {
cli.Blockchain.PrintChain()
}
func (cli CLI) Run() {
isValidArgs()
//custom command
addBlockCmd := flag.NewFlagSet("addBlock", flag.ExitOnError)
printChainCmd := flag.NewFlagSet("printChain", flag.ExitOnError)
flagAddBlockData := addBlockCmd.String("data", "jech.org", "transaction data...")
switch os.Args[1] {
case "addBlock":
err := addBlockCmd.Parse(os.Args[2:])
if err != nil {
log.Panic(err)
}
case "printChain":
err := printChainCmd.Parse(os.Args[2:])
if err != nil {
log.Panic(err)
}
default:
printUsage()
os.Exit(1)
}
if addBlockCmd.Parsed() {
if *flagAddBlockData == "" {
printUsage()
os.Exit(1)
}
fmt.Println(*flagAddBlockData)
cli.addBlock(*flagAddBlockData)
}
if printChainCmd.Parsed() {
//fmt.Println("output all blocks' information")
cli.printChain()
}
}
main函数当然也得改一下
/Users/xxx/go/src/publicChain/part24-persistence-cli/main.go
func main() {
//creat a Genesis Block
Blockchain := BLC.CreatBlockchainWithGenesisBlock()
defer Blockchain.DB.Close()
cli := BLC.CLI{Blockchain: Blockchain}
cli.Run()
}
由于当前每次运行都会从创建创世区块开始,现在我们来重新更新下这里的代码
/Users/xxx/go/src/publicChain/part24-persistence-cli/BLC/Blockchain.go
//1 creat a blockchain with Genesis Block
func CreatBlockchainWithGenesisBlock() *Blockchain {
if dbExists() {
fmt.Println("The genesis block already exists")
//open a database
db, err := bolt.Open(dbName, 0600, nil)
if err != nil {
log.Fatal(err)
}
var blockchain *Blockchain
err = db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(blockTableName))
hash := b.Get([]byte("l"))
blockchain = &Blockchain{Tip: hash, DB: db}
return nil
})
if err != nil {
log.Panic(err)
}
return blockchain
}
//creat/open a database
db, err := bolt.Open(dbName, 0600, nil)
if err != nil {
log.Fatal(err)
}
var blockHash []byte
err = db.Update(func(tx *bolt.Tx) error {
//creat a table
b, err := tx.CreateBucket([]byte(blockTableName))
if err != nil {
log.Panic(err)
}
if b != nil {
genesisBlock := CrateGenesisBlock("Genesis Data......")
//Store the genesis block into a table
err := b.Put(genesisBlock.Hash, genesisBlock.Serialize())
if err != nil {
log.Panic(err)
}
//Store the hash of current block
err = b.Put([]byte("l"), genesisBlock.Hash)
if err != nil {
log.Panic(err)
}
blockHash = genesisBlock.Hash
}
return nil
})
return &Blockchain{blockHash, db}
}
在终端中构建并使用命令行运行
go build -o bc main.go
./bc
0000eef7e34b794745f53c69c83a9b8a6f0462efb7acb55a6b72d5837dabf620
Usage:
addBlock -data DATA - transaction data
printChain -- output block's information
./bc printChain
The genesis block already exists
Height:1
PreBlockHash:0000000000000000000000000000000000000000
Data:Genesis Data......
Timestamp:2022-02-27 12:59:50 PM
Hash:0000eef7e34b794745f53c69c83a9b8a6f0462efb7acb55a6b72d5837dabf620
Nonce:36226
./bc addBlock -data "2022"
The genesis block already exists
2022
0000680e7def40960635ee3fe08fc7a879cea562b5f566f08a5ea28b9c4b51e0
./bc printChain
The genesis block already exists
Height:2
PreBlockHash:0000eef7e34b794745f53c69c83a9b8a6f0462efb7acb55a6b72d5837dabf620
Data:2022
Timestamp:2022-02-27 01:01:55 PM
Hash:0000680e7def40960635ee3fe08fc7a879cea562b5f566f08a5ea28b9c4b51e0
Nonce:8328
Height:1
PreBlockHash:0000000000000000000000000000000000000000
Data:Genesis Data......
Timestamp:2022-02-27 12:59:50 PM
Hash:0000eef7e34b794745f53c69c83a9b8a6f0462efb7acb55a6b72d5837dabf620
Nonce:36226
非常完美,我们可以在终端中添加输入data来生成区块,并且输出