到目前为止,我们的实现还没有提供一个与程序交互的接口:目前只是在 main
函数中简单执行了 CreateBlockChainWithGenesisBlock()
和 AddBlockToBlockChain()
。是时候改变了!
了解什么是CLI
学会使用flag包的语法
学会在项目中添加cli命令
项目打包:
通过jar包直接运行:
也可以编写一个sh脚本文件,运行程序:
打开IntelliJ IDEA的工作空间,将上一个项目代码目录part3_Persistence
,复制为part4_CLI
。
然后打开IntelliJ IDEA开发工具。
打开工程:part4_CLI
,并删除target目录。然后进行以下修改:
step1:先将项目重新命名为:part4_CLI。
step2:修改pom.xml配置文件。
改为:<artifactId>part4_CLI</artifactId>标签
改为:<name>part4_CLI Maven Webapp</name>
说明:我们每一章节的项目代码,都是在上一个章节上进行添加。所以拷贝上一次的项目代码,然后进行新内容的添加或修改。
CLI.java
新建cldy.hanru.blockchain.cli包,并新建CLI.java文件,编写代码如下:
package cldy.hanru.blockchain.cli;
import cldy.hanru.blockchain.block.Block;
import cldy.hanru.blockchain.block.Blockchain;
import cldy.hanru.blockchain.pow.ProofOfWork;
import cldy.hanru.blockchain.store.RocksDBUtils;
import org.apache.commons.cli.*;
import java.text.SimpleDateFormat;
import java.util.Date;
public class CLI {
private String[] args;
private Options options = new Options();
public CLI(String[] args) {
this.args = args;
Option helpCmd = Option.builder("h").desc("show help").build();
options.addOption(helpCmd);
Option data = Option.builder("data").hasArg(true).desc("add block").build();
options.addOption(data);
}
/**
* 打印帮助信息
*/
private void help() {
System.out.println("Usage:");
System.out.println(" createblockchain -address ADDRESS - Create a blockchain and send genesis block reward to ADDRESS");
System.out.println(" addblock -data DATA - Get balance of ADDRESS");
System.out.println(" printchain - Print all the blocks of the blockchain");
System.exit(0);
}
/**
* 验证入参
*
* @param args
*/
private void validateArgs(String[] args) {
if (args == null || args.length < 1) {
help();
}
}
/**
* 命令行解析入口
*/
public void run() {
this.validateArgs(args);
try {
CommandLineParser parser = new DefaultParser();
CommandLine cmd = parser.parse(options, args);
switch (args[0]) {
case "createblockchain":
createBlockchainWithGenesisBlock();
break;
case "addblock":
String data = cmd.getOptionValue("data");
addBlock(data);
break;
case "printchain":
this.printChain();
break;
case "h":
this.help();
break;
default:
this.help();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
RocksDBUtils.getInstance().closeDB();
}
}
/**
* 创建创世块
*/
private void createBlockchainWithGenesisBlock(){
Blockchain.newBlockchain();
}
/**
* 添加区块
*
* @param data
*/
private void addBlock(String data) throws Exception {
Blockchain blockchain = Blockchain.newBlockchain();
blockchain.addBlock(data);
}
/**
* 打印出区块链中的所有区块
*/
private void printChain() {
Blockchain blockchain = Blockchain.newBlockchain();
Blockchain.BlockchainIterator iterator = blockchain.getBlockchainIterator();
long index = 0;
while (iterator.hashNext()) {
Block block = iterator.next();
System.out.println("第" + block.getHeight() + "个区块信息:");
if (block != null){
boolean validate = ProofOfWork.newProofOfWork(block).validate();
System.out.println("validate = " + validate);
System.out.println("\tprevBlockHash: " + block.getPrevBlockHash());
System.out.println("\tData: " + block.getData());
System.out.println("\tHash: " + block.getHash());
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String date = sdf.format(new Date(block.getTimeStamp() * 1000L));
System.out.println("\ttimeStamp:" + date);
System.out.println();
}
}
}
}
main.java
在main.java
中修改测试代码
package cldy.hanru.blockchain;
import cldy.hanru.blockchain.block.Block;
import cldy.hanru.blockchain.block.Blockchain;
import cldy.hanru.blockchain.cli.CLI;
import cldy.hanru.blockchain.store.RocksDBUtils;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 测试
*
* @author hanru
*/
public class Main {
public static void main(String[] args) {
// // 1.创建创世区块
// Block genesisBlock = Block.newGenesisBlock();
// System.out.println("创世区块的信息:");
// System.out.println("\thash:" + genesisBlock.getHash());
// System.out.println("\tprevBlockHash:" + genesisBlock.getPrevBlockHash());
// System.out.println("\tdata:" + genesisBlock.getData());
// SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// String date = sdf.format(new Date(genesisBlock.getTimeStamp()*1000L));
//
// System.out.println("\ttimeStamp:" + date);
//
// //2.创建第二个区块
// Block block2 = Block.newBlock(genesisBlock.getHash(), "I am hanru",1);
// System.out.println("第二个区块的信息:");
// System.out.println("\thash:" + block2.getHash());
// System.out.println("\tprevBlockHash:" + block2.getPrevBlockHash());
// System.out.println("\tdata:" + block2.getData());
// String date2 = sdf.format(new Date(block2.getTimeStamp()*1000L));
// System.out.println("\ttimeStamp:" + date2);
//3.测试Blockchain
// Blockchain blockchain = Blockchain.newBlockchain();
//
//
// System.out.println("创世链的信息:");
// System.out.println("区块的长度:" + blockchain.getBlockList().size());
//
// //4.添加区块
// blockchain.addBlock("Send 1 BTC to 韩茹");
// blockchain.addBlock("Send 2 more BTC to ruby");
// blockchain.addBlock("Send 4 more BTC to 王二狗");
//
// for (int i = 0; i < blockchain.getBlockList().size(); i++) {
// Block block = blockchain.getBlockList().get(i);
// System.out.println("第" + block.getHeight() + "个区块信息:");
// System.out.println("\tprevBlockHash: " + block.getPrevBlockHash());
// System.out.println("\tData: " + block.getData());
// System.out.println("\tHash: " + block.getHash());
// SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// String date2 = sdf.format(new Date(block.getTimeStamp() * 1000L));
// System.out.println("\ttimeStamp:" + date2);
//
//
// ProofOfWork pow = ProofOfWork.newProofOfWork(block);
// System.out.println("是否有效: " + pow.validate() + "\n");
// System.out.println();
// }
/*
// 5.检测pow
//1.创建一个big对象 0000000.....00001
BigInteger target = BigInteger.ONE;
System.out.printf("0x%x\n",target); //0x1
//2.左移256-bits位
target = target.shiftLeft((256 - ProofOfWork.TARGET_BITS));
System.out.printf("0x%x\n",target); //61
//61位:0x1000000000000000000000000000000000000000000000000000000000000
//64位:0x0001000000000000000000000000000000000000000000000000000000000000
//检测hash
System.out.println();
String s1="HelloWorld";
String hash = DigestUtils.sha256Hex(s1);
System.out.printf("0x%s\n",hash);
*/
/*
//5.测试持久化
Blockchain blockchain = Blockchain.newBlockchain();
System.out.println(blockchain);
// RocksDBUtils.getInstance().closeDB();
//6.添加区块
try {
blockchain.addBlock("Send 1 BTC to 韩茹");
blockchain.addBlock("Send 2 more BTC to ruby");
blockchain.addBlock("Send 4 more BTC to 王二狗");
} catch (Exception e) {
e.printStackTrace();
} finally {
RocksDBUtils.getInstance().closeDB();
}
//7.遍历区块
// Blockchain blockchain = Blockchain.newBlockchain();
Blockchain.BlockchainIterator iterator = blockchain.getBlockchainIterator();
long index = 0;
while (iterator.hashNext()) {
Block block = iterator.next();
System.out.println("第" + block.getHeight() + "个区块信息:");
System.out.println("\tprevBlockHash: " + block.getPrevBlockHash());
System.out.println("\tData: " + block.getData());
System.out.println("\tHash: " + block.getHash());
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String date = sdf.format(new Date(block.getTimeStamp() * 1000L));
System.out.println("\ttimeStamp:" + date);
System.out.println();
}
RocksDBUtils.getInstance().closeDB();
*/
CLI cli = new CLI(args);
cli.run();
}
}
pom.xml
配置文件打开pom.xml
配置文件,并修改如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cldy.hanru.blockchain</groupId>
<artifactId>part4_CLI</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>part4_CLI Maven Webapp</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!-- lombok是一个可以通过简单的注解的形式来帮助我们简化消除一些必须有但显得很臃肿的 Java 代码的工具 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.20</version>
</dependency>
<!--处理Java基本对象方法的工具类包,该类包提供对字符、数组等基本对象的操作,弥补了java.lang api基本处理方法上的不足。 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.7</version>
</dependency>
<!-- commons-codec是Apache开源组织提供的用于摘要运算、编码的包。在该包中主要分为四类加密:BinaryEncoders、DigestEncoders、LanguageEncoders、NetworkEncoders。 -->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.11</version>
</dependency>
<!-- 对象序列化/反序列化框架 -->
<dependency>
<groupId>com.esotericsoftware</groupId>
<artifactId>kryo</artifactId>
<version>4.0.1</version>
</dependency>
<!-- rocksdb -->
<dependency>
<groupId>org.rocksdb</groupId>
<artifactId>rocksdbjni</artifactId>
<version>5.9.2</version>
</dependency>
<!--CLI命令行-->
<dependency>
<groupId>commons-cli</groupId>
<artifactId>commons-cli</artifactId>
<version>1.4</version>
</dependency>
</dependencies>
<build>
<finalName>part4_CLI</finalName>
<plugins>
<!--
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.0.0</version>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.20.1</version>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.0</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
<mainClass>cldy.hanru.blockchain.Main</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<!-- this is used for inheritance merges -->
<phase>package</phase>
<!-- 指定在打包节点执行jar包合并操作 -->
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
blockchain.sh
脚本文件在项目下新建一个sh脚本文件,命名为:blockchain.sh
,并编写内容如下:
#!/bin/bash
set -e
# Check if the jar has been built.
if [ ! -e target/part4_CLI-jar-with-dependencies.jar ]; then
echo "Compiling blockchain project to a JAR"
mvn package -DskipTests
fi
java -jar target/part4_CLI-jar-with-dependencies.jar "$@"
目前只是在 main
函数中简单执行了 newBlockchain
和 addBlock
。是时候改变了!现在我们想要拥有这些命令,就需要通过cli实现。
hanru:part4_CLI ruby$ ./blockchain.sh h
hanru:part4_CLI ruby$ ./blockchain.sh createblockchain
hanru:part4_CLI ruby$ ./blockchain.sh addblock -data "send 1 BTC to hanru"
hanru:part4_CLI ruby$ ./blockchain.sh printchain
命令行界面(英语:command-line interface,缩写:CLI)是在图形用户界面得到普及之前使用最为广泛的用户界面,它通常不支持鼠标,用户通过键盘输入指令,计算机接收到指令后,予以执行。
要想使用CLI命令行工具,需要导入org.apache.commons.cli包,所以我们最先要做的就是修改pom.xml配置文件,添加依赖包
<!--CLI命令行-->
<dependency>
<groupId>commons-cli</groupId>
<artifactId>commons-cli</artifactId>
<version>1.4</version>
</dependency>
接下来,我们创建一个包:cldy.hanru.blockchain.cli,并新建CLI.java文件,创建CLI类。所有命令行相关的操作都会通过 CLI
类的对象进行处理:
public class CLI {
private String[] args;
private Options options = new Options();
}
它的 “入口” 是 run()
函数:
/**
* 命令行解析入口
*/
public void run() {
this.validateArgs(args);
try {
CommandLineParser parser = new DefaultParser();
CommandLine cmd = parser.parse(options, args);
switch (args[0]) {
case "createblockchain":
createBlockchainWithGenesisBlock();
break;
case "addblock":
String data = cmd.getOptionValue("data");
addBlock(data);
break;
case "printchain":
this.printChain();
break;
case "h":
this.help();
break;
default:
this.help();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
RocksDBUtils.getInstance().closeDB();
}
}
Apache Commons CLI是开源的命令行解析工具,它可以帮助开发者快速构建启动命令,并且帮助你组织命令的参数、以及输出列表等。
CLI分为三个过程:
定义阶段
调用的方法:
public Options addOption(String opt, String longOpt, boolean hasArg, String description)
{
addOption(new Option(opt, longOpt, hasArg, description));
return this;
}
其中Option的参数:
- 第一个参数:参数的简单形式
- 第二个参数:参数的复杂形式
- 第三个参数:是否需要额外的输入
- 第四个参数:对参数的描述信息
我们在CLI的构造函数中,通过向options中添加Option对象,用于表示不同的命令行参数:
public CLI(String[] args) {
this.args = args;
Option helpCmd = Option.builder("h").desc("show help").build();
options.addOption(helpCmd);
Option data = Option.builder("data").hasArg(true).desc("add block").build();
options.addOption(data);
}
首先,我们利用终端的参数来表示4个命令::h
,createblockchain
,addblock
,printchain
。 其中addblock
,需要接收额外的输入,所以定义Option接接收:
我们希望当程序运行时,命令行提示信息如下:
解析阶段
通过解析器解析参数
首先,创建一个CommandLineParser解析器对象,然后调用parse()方法解析options和args:
try {
CommandLineParser parser = new DefaultParser();
CommandLine cmd = parser.parse(options, args);
}catch(Exception e){
//TODO xxx
}
询问阶段
根据commandLine查询参数,提供服务,此处配合分支语句:
switch (args[0]) {
case "createblockchain":
createBlockchainWithGenesisBlock();
break;
case "addblock":
String data = cmd.getOptionValue("data");
addBlock(data);
break;
case "printchain":
this.printChain();
break;
case "h":
this.help();
break;
default:
this.help();
}
如何终端输入的命令是h,那么现实帮助信息:
/**
* 打印帮助信息
*/
private void help() {
System.out.println("Usage:");
System.out.println(" createblockchain -address ADDRESS - Create a blockchain and send genesis block reward to ADDRESS");
System.out.println(" addblock -data DATA - Get balance of ADDRESS");
System.out.println(" printchain - Print all the blocks of the blockchain");
System.exit(0);
}
如果终端输入的命令是createblockchain,表示创建创世区块:
/**
* 创建创世块
*/
private void createBlockchainWithGenesisBlock(){
Blockchain.newBlockchain();
}
如果终端输入的命令是addblock,表示挖掘新的区块,并添加到区块链中:
/**
* 添加区块
*
* @param data
*/
private void addBlock(String data) throws Exception {
Blockchain blockchain = Blockchain.newBlockchain();
blockchain.addBlock(data);
}
如果终端输入的命令是print,表示整个区块链中的所有的区块:
/**
* 打印出区块链中的所有区块
*/
private void printChain() {
Blockchain blockchain = Blockchain.newBlockchain();
Blockchain.BlockchainIterator iterator = blockchain.getBlockchainIterator();
long index = 0;
while (iterator.hashNext()) {
Block block = iterator.next();
System.out.println("第" + block.getHeight() + "个区块信息:");
if (block != null){
boolean validate = ProofOfWork.newProofOfWork(block).validate();
System.out.println("validate = " + validate);
System.out.println("\tprevBlockHash: " + block.getPrevBlockHash());
System.out.println("\tData: " + block.getData());
System.out.println("\tHash: " + block.getHash());
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String date = sdf.format(new Date(block.getTimeStamp() * 1000L));
System.out.println("\ttimeStamp:" + date);
System.out.println();
}
}
最后,在main.go
中修改代码:
package cldy.hanru.blockchain;
import cldy.hanru.blockchain.block.Block;
import cldy.hanru.blockchain.block.Blockchain;
import cldy.hanru.blockchain.cli.CLI;
import cldy.hanru.blockchain.store.RocksDBUtils;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 测试
*
* @author hanru
*/
public class Main {
public static void main(String[] args) {
CLI cli = new CLI(args);
cli.run();
}
}
现在因为我们需要通过终端命令执行程序,所以不能像以前那样,直接选择右键Run执行程序。
首先我们需要修改pom.xml文件,修改配置信息,指定打包的配置信息以及程序的入口程序:
<build>
<finalName>part4_CLI</finalName>
<plugins>
<!--
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.0.0</version>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.20.1</version>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.0</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
<mainClass>cldy.hanru.blockchain.Main</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<!-- this is used for inheritance merges -->
<phase>package</phase>
<!-- 指定在打包节点执行jar包合并操作 -->
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
接下来,我们需要将程序打包:
打开终端进入到项目所在的目录,输入以下命令将项目进行打包:
hanru:part4_CLI ruby$ mvn package
效果如下:
编译打包后会在项目根目录下生成一个target文件,里面有打包生成的jar文件:part4_CLI-jar-with-dependencies.jar。
然后我们可以通过cd命令进入target目录,执行jar文件运行程序:
hanru:target ruby$ java -jar part4_CLI-jar-with-dependencies.jar h
hanru:target ruby$ java -jar part4_CLI-jar-with-dependencies.jar createblockchain
运行效果如下:
也可以编写一个sh脚本文件,简化运行命令。在项目根目录下创建sh文件:blockchain.sh,编写脚本内容如下:
#!/bin/bash
set -e
# Check if the jar has been built.
if [ ! -e target/part4_CLI-jar-with-dependencies.jar ]; then
echo "Compiling blockchain project to a JAR"
mvn package -DskipTests
fi
java -jar target/part4_CLI-jar-with-dependencies.jar "$@"
然后再终端执行运行命令:
hanru:part4_CLI ruby$ ./blockchain.sh h
hanru:part4_CLI ruby$ ./blockchain.sh createblockchain
hanru:part4_CLI ruby$ ./blockchain.sh addblock -data "send 1.5 BTC to hanru"
hanru:part4_CLI ruby$ ./blockchain.sh addblock -data "send 3 BTC to wangergou"
hanru:part4_CLI ruby$ ./blockchain.sh printchain
执行程序,运行结果如下:
创建创世区块
添加新的区块:
遍历打印区块:
通过本章节的学习,我们知道了什么是CLI,并通过CLI命令执行程序。通过Apache Commons CLI包设置终端命令,通过命令配合命令参数执行对应的功能。本章节中我们并没有新增功能,项目功能目前为止还是3个,创建创世区块:creategenesis
,添加新区块:add
,以及打印区块:print
。