一丶区块链V1保存字符串
本模块实现简单功能, 使用传统技术实现一个账本功能
1.实现添加和查询功能
// 生活中的账本 = 区块链
public class NoteBook {
// 用于保存数据的集合
private ArrayList
// 添加封面 = 创世区块
// 添加封面的时候,必须保证账本是新的
private void addGenesis(String genesis) {
if (list.size() > 0) {
throw new RuntimeException("添加封面的时候,必须保证账本是新的");
}
list.add(genesis);
}
// 添加交易记录 = 普通区块
// 添加交易记录的时候,必须保证账本已经有封面了
private void addNote(String note) {
if (list.size() < 1) {
throw new RuntimeException("添加交易记录的时候,必须保证账本已经有封面了");
}
list.add(note);
}
// 展示数据
private void showlist() {
for (String s : list) {
System.out.println(s);
}
}
// 保存到本地硬盘
private void save2Disk() {
}
public static void main(String[] args) {
NoteBook noteBook = new NoteBook();
noteBook.addGenesis("封面");
noteBook.addNote("222给2转账了一百块钱");
noteBook.showlist();
}
}
2.实现保存功能
增加加载本地数据的方法
private void loadFile() {
try {
ObjectMapper objectMapper = new ObjectMapper();
File file = new File("a.json");
// 判断文件是否存在
if (file.exists() && file.length() > 0) {
// 如果文件存在,读取之前的数据
JavaType javatype = objectMapper.getTypeFactory().constructParametricType(ArrayList.class, String.class);
list = objectMapper.readValue(file, javatype);
}
} catch (IOException e) {
e.printStackTrace();
}
}
在构造函数中加载本地数据
public NoteBook() {
loadFile();
}
增加保存数据到本地的方法
// 保存数据到本地硬盘
private void save2Disk() {
try {
ObjectMapper objectMapper = new ObjectMapper();
File file = new File("a.json");
objectMapper.writeValue(file, list);
} catch (IOException e) {
e.printStackTrace();
}
}
在添加数据时, 调用保存方法
private void addGenesis(String genesis) {
if (list.size() > 0) {
throw new RuntimeException("添加封面的时候,必须保证账本是新的");
}
list.add(genesis);
save2Disk();
}
private void addNote(String note) {
if (list.size() < 1) {
throw new RuntimeException("添加交易记录的时候,必须保证账本已经有封面了");
}
list.add(note);
save2Disk();
}
二丶区块链V2增加Http访问接口
本模块为账本功能提供网络接口, 使用户可以通过页面对账本进行增删改查的操作
1.增加网络访问接口
@RestController
public class BlockController {
private NoteBook book = new NoteBook();
@RequestMapping(value = "/addGenesis", method = RequestMethod.POST)
public String addGenesis(String genesis) {
try {
book.addGenesis(genesis);
return "success";
} catch (Exception e) {
return "fail:" + e.getMessage();
}
}
@RequestMapping(value = "/addNote", method = RequestMethod.POST)
public String addNote(String note) {
try {
book.addNote(note);
return "success";
} catch (Exception e) {
return "fail:" + e.getMessage();
}
}
@RequestMapping(value = "/showlist", method = RequestMethod.GET)
public ArrayList
return book.showlist();
}
}
2.增加前台页面
引入资料中的静态资源
body {
margin: 30px;
}
#result {
padding: 15px;
}
// 添加封面
function addGenesis() {
// 显示进度条
loading.baosight.showPageLoadingMsg(false)
// 用户输入的内容
var content = $("#inputCtr").val();
// 发起请求
$.post("/addGenesis", "genesis=" + content, function (data) {
//清空输入框
$("#inputCtr").val();
// 展示结果
$("#result").html(data)
// 隐藏进度条
loading.baosight.hidePageLoadingMsg()
// 查询最新的数据
showlist();
})
}
// 添加记录
function addNote() {
// 显示进度条
loading.baosight.showPageLoadingMsg(false)
// 用户输入的内容
var content = $("#inputCtr").val();
// 发起请求
$.post("/addNote", "note=" + content, function (data) {
//清空输入框
$("#inputCtr").val();
// 展示结果
$("#result").html(data)
// 隐藏进度条
loading.baosight.hidePageLoadingMsg()
// 查询最新的数据
showlist();
})
}
// 展示数据
function showlist() {
// 显示进度条
loading.baosight.showPageLoadingMsg(false)
// 发起请求
$.get("/showlist", function (data) {
// 展示数据
$("#result").html(data)
// 隐藏进度条
loading.baosight.hidePageLoadingMsg()
})
}
// 页面加载成功后,查询已有数据
$(function () {
showlist();
})
3.进度条控件的使用
loading.baosight.showPageLoadingMsg(false) // 显示进度条
loading.baosight.hidePageLoadingMsg() // 隐藏进度条
三丶区块链V3增加Hash校验
上一小节实现的功能,很容易导致黑客攻击,篡改数据,这一小节,为数据增加Hash校验功能,增加黑客攻击的难度.
1.创建Block实体类
public class Block {
public int id;
public String content;
public String hash;
public Block() {
}
public Block(int id, String content, String hash) {
this.id = id;
this.content = content;
this.hash = hash;
}
}
2.Notebook中增加校验方法
// 校验数据
public String check() {
StringBuilder sb = new StringBuilder();
for (Block block : list) {
// 获取内容
String content = block.content;
// 生成Hash
String hashNew = HashUtils.sha256(content);
// 比对hash,如果不一样说明数据内篡改
if (!hashNew.equals(block.hash)) {
sb.append("编号为" + block.id + "的数据有可能被篡改了,请注意防范黑客
");
}
}
return sb.toString();
}
3.Controller中增加方法
@RequestMapping(value = "/check", method = RequestMethod.GET)
public String check() {
String check = book.check();
if (StringUtils.isEmpty(check)) {
return "数据是安全的";
}
return check;
}
4.页面中增加表格用于展示数据
编号 | 内容 | 哈希值 |
---|
5.修改页面显示数据的逻辑
function showlist() {
// 获取用户在输入框中输入的内容
var content = $("#idInput").val();
// 打开进度条
loading.baosight.showPageLoadingMsg(false);
// 发起请求
$.get("showlist", function (data) {
// 隐藏进度条
loading.baosight.hidePageLoadingMsg()
// 清空数据
$("#idTbody").html("")
// 遍历数据,添加数据
for (var i = 0; i < data.length; i++) {
var id = data[i].id
var content = data[i].content
var hash = data[i].hash
$("#idTbody").append("
}
})
}
6.页面中增加校验数据的逻辑
// 校验数据
function check() {
// 打开进度条
loading.baosight.showPageLoadingMsg(false);
// 发起请求
$.get("check", function (data) {
// 隐藏进度条
loading.baosight.hidePageLoadingMsg()
$("#result").html(data)
})
}
四丶区块链V4增加工作量证明
上一小节增加Hash校验后,如果黑客在篡改数据的同时,又修改了Hash,就无法达到保护数据的目的.因此本节,增加工作量证明,即生成的Hash必须符合特定的规则,如必须以0000开头.这个获取特定Hash的过程就是比特币中的挖矿.
1.修改实体类,增加工作量证明字段
public class Block {
public int id;
public String content;
public String hash;
public int nonce;
public Block() {
}
public Block(int id, String content, String hash, int nonce) {
this.id = id;
this.content = content;
this.hash = hash;
this.nonce = nonce;
}
}
2.Notebook中增加挖矿方法
// 挖矿
private int mine(String content) {
// 求取一个符合特定规则的hash值,并将运算次数返回
for (int i = 0; i < Integer.MAX_VALUE; i++) {
String s = HashUtils.sha256(i + content);
if (s.startsWith("0000")) {
System.out.println("挖矿成功:" + i);
return i;
} else {
System.out.println("挖矿失败:" + i);
}
}
throw new RuntimeException("挖矿失败");
}
3.Notebook中修改添加的逻辑
int nonce = mine(genesis);
Block block = new Block(list.size() + 1, genesis, HashUtils.sha256(nonce + genesis), nonce);
4.Notebook中修改校验的逻辑
// 校验数据
public String check() {
StringBuilder sb = new StringBuilder();
for (Block block : list) {
// 获取内容
String content = block.content;
// 工作量证明
int nonce = block.nonce;
// 生成Hash
String hashNew = HashUtils.sha256(nonce + content);
// 比对hash,如果不一样说明数据内篡改
if (!hashNew.equals(block.hash)) {
sb.append("编号为" + block.id + "的数据有可能被篡改了,请注意防范黑客
");
}
}
return sb.toString();
}
5.修改页面,表格增加列
编号 | 内容 | 哈希值 | 工作量 |
---|
6.修改页面,展示数据的逻辑
$("#idTbody").append("
五丶区块链V4形成区块链
本节继续增加黑客攻击的难度, 每个区块都持有上个区块的Hash值,互相咬合,形成一个链条.这样当链条足够长的时候,会大大增加黑客攻击的难度.
1.修改实体类,增加preHash字段
public class Block {
public int id;
public String content;
public String hash;
public int nonce;
public String preHash;
public Block() {
}
public Block(int id, String content, String hash, int nonce, String preHash) {
this.id = id;
this.content = content;
this.hash = hash;
this.nonce = nonce;
this.preHash = preHash;
}
}
2.Notebook中修改添加的逻辑
public void addGenesis(String genesis) {
if (list.size() > 0) {
throw new RuntimeException("添加封面的时候,必须保证账本是新的");
}
String preHash = "0000000000000000000000000000000000000000000000000000000000000000";
int nonce = mine(preHash + genesis);
Block block = new Block(list.size() + 1, genesis, HashUtils.sha256(nonce + preHash + genesis), nonce, preHash);
list.add(block);
save2Disk();
}
public void addNote(String note) {
if (list.size() < 1) {
throw new RuntimeException("添加交易记录的时候,必须保证账本已经有封面了");
}
Block preBlock = list.get(list.size() - 1);
String preHash = preBlock.hash;
int nonce = mine(preHash + note);
Block block = new Block(list.size() + 1, note, HashUtils.sha256(nonce + preHash + note), nonce, preHash);
list.add(block);
save2Disk();
}
3.Notebook中修改校验的逻辑
public String check() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < list.size(); i++) {
Block currentBlock = list.get(i);
String savedHash = currentBlock.hash;
String content = currentBlock.content;
int nonce = currentBlock.nonce;
String preHash = currentBlock.preHash;
int id = currentBlock.id;
if (i == 0) {
// 创世区块,校验hash
preHash = "0000000000000000000000000000000000000000000000000000000000000000";
String caculatedHash = HashUtils.sha256(nonce + preHash + content);
if (!savedHash.equals(caculatedHash)) {
sb.append("编号为" + id + "的数据有可能被篡改了,请注意防范黑客
");
}
} else {
// 其他区块,校验hash,preHash
String caculatedHash = HashUtils.sha256(nonce + preHash + content);
if (!savedHash.equals(caculatedHash)) {
sb.append("编号为" + id + "的hash有问题,请注意防范黑客
");
}
Block preBlock = list.get(i - 1);
String preBlockHash = preBlock.hash;
if (!preBlockHash.equals(preHash)) {
sb.append("编号为" + id + "的preHash有问题,请注意防范黑客
");
}
}
}
return sb.toString();
}
4.修改页面,表格增加列
5.修改页面,展示数据的逻辑
$("#idTbody").append("
六丶钱包功能
前面的章节,存储数据时,直接可见转账双方的信息,本节通过非对称加密技术和签名技术隐藏交易双方的真实信息.
可以简单理解为 非对称加密中的公钥就是钱包地址,非对称加密中的私钥就是钱包的密码.
1.钱包实体类
public class Wallet {
// 公钥
public PublicKey publicKey;
// 私钥
public PrivateKey privateKey;
public Wallet(String name) {
// 保存公私钥的文件
File pubFile = new File(name + ".pub");
File priFile = new File(name + ".pri");
// 如果文件不存在,说明没有公私钥,就创建公私钥文件
if (!pubFile.exists() || pubFile.length() == 0 || !priFile.exists() || priFile.length() == 0) {
RSAUtils.generateKeys("RSA", name + ".pri", name + ".pub");
}
// 从文件中读取公私钥
publicKey = RSAUtils.getPublicKeyFromFile("RSA", name + ".pub");
privateKey = RSAUtils.getPrivateKey("RSA", name + ".pri");
}
// 转账
public Transaction sendMoney(String receiverPublickKey, String content) {
// 将公钥转为字符串
String publicKeyEncode = Base64.encode(publicKey.getEncoded());
// 生成签名
String signature = RSAUtils.getSignature("SHA256withRSA", privateKey, content);
// 生成交易对象
Transaction transaction = new Transaction(publicKeyEncode, receiverPublickKey, signature, content);
return transaction;
}
}
2.交易实体类
public class Transaction {
// 付款方的公钥
public String senderPublickKey;
// 收款方的公钥
public String receiverPublickKey;
// 签名
public String signature;
// 转账信息
public String content;
public Transaction() {
}
public Transaction(String senderPublickKey, String receiverPublickKey, String signature, String content) {
this.senderPublickKey = senderPublickKey;
this.receiverPublickKey = receiverPublickKey;
this.signature = signature;
this.content = content;
}
// 校验交易是否正确
public boolean verify() {
PublicKey sender = RSAUtils.getPublicKeyFromString("RSA", senderPublickKey);
return RSAUtils.verify("SHA256withRSA", sender, content, signature);
}
public String getSenderPublickKey() {
return senderPublickKey;
}
public void setSenderPublickKey(String senderPublickKey) {
this.senderPublickKey = senderPublickKey;
}
public String getReceiverPublickKey() {
return receiverPublickKey;
}
public void setReceiverPublickKey(String receiverPublickKey) {
this.receiverPublickKey = receiverPublickKey;
}
public String getSignature() {
return signature;
}
public void setSignature(String signature) {
this.signature = signature;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
@Override
public String toString() {
return "Transaction{" +
"senderPublickKey='" + senderPublickKey + '\'' +
", receiverPublickKey='" + receiverPublickKey + '\'' +
", signature='" + signature + '\'' +
", content='" + content + '\'' +
'}';
}
}
七丶区块链V5增加转账功能
本小节实现网页转账的功能
jsrsasign插件 : https://github.com/kjur/jsrsasign
1.在页面引入插件
2.修改页面,增加输入key的文本域
发送方的私钥
发送方的公钥
接收方的公钥
转账信息
3.修改页面添加记录的逻辑
function addNote() {
// 获取输入框的内容
var senderPrivateKey = $("#senderPrivateKey").val();
var senderPublicKey = $("#senderPublicKey").val();
var receiverPublicKey = $("#receiverPublicKey").val();
var content = $("#idInput").val()
// 生成私钥
var prvKey = KEYUTIL.getKey(senderPrivateKey);
// 指定生成签名使用的算法
var sig = new KJUR.crypto.Signature({"alg": "SHA256withRSA"});
// 初始化私钥
sig.init(prvKey);
// 传入原文
sig.updateString(content)
// 生成签名数据
var sigValueHex = sig.sign()
// 打开进度条
loading.baosight.showPageLoadingMsg(false);
// 发起请求
$.post("addNote", {
senderPublickKey: senderPublicKey,
receiverPublickKey: receiverPublicKey,
signature: sigValueHex,
content: content,
}, function (data) {
// 清空输入框
$("#idInput").val("");
// 隐藏进度条
loading.baosight.hidePageLoadingMsg()
$("#result").html(data)
// 展示最新的数据
showlist()
})
}
4.修改RsaUtils,增加js生成签名,验证签名的方法
/**
* 生成公私钥,并保存在文件中,JS版本
*
* @param algorithm : 算法
* @param privateKeyPath : 保存私钥的文件路径
* @param publicKeyPath : 保存公钥的文件路径
*/
public static void generateKeysJS(String algorithm, String privateKeyPath, String publicKeyPath) {
try {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(algorithm);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
PrivateKey privateKey = keyPair.getPrivate();
PublicKey publicKey = keyPair.getPublic();
byte[] privateKeyEncoded = privateKey.getEncoded();
byte[] publicKeyEncoded = publicKey.getEncoded();
String encodePrivateKey = Base64.encode(privateKeyEncoded);
String encodePublicKey = Base64.encode(publicKeyEncoded);
FileUtils.writeStringToFile(new File(privateKeyPath), "-----BEGIN PRIVATE KEY-----\n" + encodePrivateKey + "\n-----END PRIVATE KEY-----", "UTF-8");
FileUtils.writeStringToFile(new File(publicKeyPath), encodePublicKey, "UTF-8");
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 校验签名,JS版本
*
* @param algorithm : 加密算法(SHA256withRSA)
* @param publicKey : 公钥
* @param originalData : 原文
* @param signaturedData : 签名
* @return : 签名是否正确
*/
public static boolean verifyDataJS(String algorithm, PublicKey publicKey, String originalData, String signaturedData) {
try {
// 获取签名对象
Signature signature = Signature.getInstance(algorithm);
// 传入公钥
signature.initVerify(publicKey);
// 传入原文
signature.update(originalData.getBytes());
// 校验数据
return signature.verify(toBytes(signaturedData));
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
//转换方法
public static byte[] toBytes(String str) {
if (str == null || trim(str) == "") {
return new byte[0];
}
byte[] bytes = new byte[str.length() / 2];
for (int i = 0; i < str.length() / 2; i++) {
String subStr = str.substring(i * 2, i * 2 + 2);
bytes[i] = (byte) Integer.parseInt(subStr, 16);
}
return bytes;
}
public static String trim(String str) {
int startIndex = 0;
int endIndex = str.length() - 1;
boolean startFound = false;
while (startIndex <= endIndex) {
int index;
if (!startFound) {
index = startIndex;
} else {
index = endIndex;
}
char it = str.charAt(index);
boolean match = it <= ' ';
if (!startFound) {
if (!match)
startFound = true;
else
startIndex += 1;
} else {
if (!match)
break;
else
endIndex -= 1;
}
}
return str.substring(startIndex, endIndex + 1);
}
修改Controller,添加记录的逻辑
@RequestMapping(value = "/addNote", method = RequestMethod.POST)
public String addNote(Transaction transaction) {
try {
// 校验交易数据
if (transaction.verify()) {
// 将交易数据转为字符串
ObjectMapper objectMapper = new ObjectMapper();
String note = objectMapper.writeValueAsString(transaction);
// 添加数据
book.addNote(note);
return "success";
} else {
throw new RuntimeException("交易数据校验失败");
}
} catch (Exception e) {
return "fail:" + e.getMessage();
}
}
八丶WebSocket简介
前面的章节章节中,我们的转账信息和生成的区块只是保存在本地,事实上我们需要把这些信息广播出去,告知其他节点.其他节点上的转账信息和生成的区块也要告知我们,这样我们就需要通过WebSocket和其他节点之间进行通信.
WebSocket是一种通信协议,可以实现双向通信.
具体介绍 : http://www.ruanyifeng.com/blog/2017/05/websocket.html
本项目使用Java WebSockets框架. https://github.com/TooTallNate/Java-WebSocket
快速入门案例
1.引入坐标
compile "org.java-websocket:Java-WebSocket:1.3.8"
2.创建Server
public class MyServer extends WebSocketServer {
// 服务器端口
private int port;
public MyServer(int port) {
super(new InetSocketAddress(port));
this.port = port;
}
@Override
public void onOpen(WebSocket conn, ClientHandshake handshake) {
System.out.println("WebSocket服务器__" + port + "__打开了一个连接,对方是:" + conn.getRemoteSocketAddress().toString());
}
@Override
public void onClose(WebSocket conn, int code, String reason, boolean remote) {
System.out.println("WebSocket服务器__" + port + "__关闭了一个连接,对方是:" + conn.getRemoteSocketAddress().toString());
}
@Override
public void onMessage(WebSocket conn, String message) {
System.out.println("WebSocket服务器__" + port + "__收到了消息,对方是:" + conn.getRemoteSocketAddress().toString() + "__消息的内容是:" + message);
}
@Override
public void onError(WebSocket conn, Exception ex) {
System.out.println("WebSocket服务器__" + port + "__发生了错误__原因:" + ex.getMessage());
}
@Override
public void onStart() {
System.out.println("WebSocket服务器__" + port + "__启动成功");
}
// 开启服务器
public void startServer() {
new Thread(this).start();
}
}
3.创建Client
public class MyClient extends WebSocketClient {
private String name;
/**
* @param serverUri : 要连接的服务器的地址
* @param name : 本客户端的名字
*/
public MyClient(URI serverUri, String name) {
super(serverUri);
this.name = name;
}
@Override
public void onOpen(ServerHandshake handshakedata) {
System.out.println("WebSocket客户端__" + name + "_连接成功");
}
@Override
public void onMessage(String message) {
System.out.println("WebSocket客户端__" + name + "_收到消息,内容是:" + message);
}
@Override
public void onClose(int code, String reason, boolean remote) {
System.out.println("WebSocket客户端__" + name + "_连接关闭");
}
@Override
public void onError(Exception ex) {
System.out.println("WebSocket客户端__" + name + "_发生错误");
}
}
4.创建测试类
public class MyTest {
public static void main(String[] args) {
try {
// 创建并开启服务器
MyServer server = new MyServer(8000);
server.startServer();
// 指定服务器地址
URI uri = new URI("ws://localhost:8000");
// 创建客户端
MyClient client1 = new MyClient(uri, "1111");
MyClient client2 = new MyClient(uri, "2222");
// 客户端连接服务器
client1.connect();
client2.connect();
// 避免连接尚未成功,就发送消息,导致的发送失败
Thread.sleep(1000);
// 服务器发送广播
// server.broadcast("这是来自服务器的广播");
// 客户端发送消息给服务器
client1.send("这是一号客户端发送的消息");
} catch (Exception e) {
e.printStackTrace();
}
}
}
九丶SpringBoot集成WebSocket
1.修改Application类,可以手动指定启动端口号
public class WsdemoApplication {
public static String port;
public static void main(String[] args) {
// 获取用户输入的内容,作为端口号
Scanner scanner = new Scanner(System.in);
port = scanner.nextLine();
// 启动应用
new SpringApplicationBuilder(WsdemoApplication.class).properties("server.port=" + port).run(args);
}
}
2.修改Springboot配置,使其可以开启多实例
编辑配置
找到对应的Application类,修改配置
3.创建Controller,实现注册节点 / 连接 / 广播功能
@RestController
public class DemoController {
private MyServer server;
// 创建服务器,开启服务器
@PostConstruct// 创建Controller后调用该方法
public void init() {
// webSocket服务器占用的端口号 = SpringBoot占用的端口号 + 1
server = new MyServer(Integer.parseInt(WsdemoApplication.port) + 1);
server.startServer();
}
private HashSet
// 注册节点
@RequestMapping("/regist")
public String regist(String port) {
set.add(port);
return "节点:" + port + "注册成功";
}
// 连接
@RequestMapping("/conn")
public String conn() {
try {
// 遍历集合,连接其他WebSocket服务器
for (String port : set) {
URI uri = new URI("ws://localhost:" + port);
MyClient client = new MyClient(uri, "连接到__"+port+"__服务器的客户端");
client.connect();
}
return "连接成功";
} catch (URISyntaxException e) {
return "连接失败:" + e.getMessage();
}
}
// 广播
@RequestMapping("/broadcast")
public String broadcast(String msg) {
server.broadcast(msg);
return "发送消息成功";
}
}
十丶本项目集成WebSocket
1.参考上例将WebSocket集成进本项目
2.修改页面,增加对应的按钮
3.修改页面,增加对应的点击事件
function regist() {
// 获取用户在输入框中输入的内容
var content = $("#idNode").val();
// 打开进度条
loading.baosight.showPageLoadingMsg(false);
// 发起请求
$.post("regist", "port=" + content, function (data) {
// 清空输入框
$("#idNode").val("");
// 隐藏进度条
loading.baosight.hidePageLoadingMsg()
$("#result").html(data)
})
}
function conn() {
// 打开进度条
loading.baosight.showPageLoadingMsg(false);
// 发起请求
$.post("conn", function (data) {
// 隐藏进度条
loading.baosight.hidePageLoadingMsg()
$("#result").html(data)
})
}
function broadcast() {
// 获取用户在输入框中输入的内容
var content = $("#idNode").val();
// 打开进度条
loading.baosight.showPageLoadingMsg(false);
// 发起请求
$.post("broadcast", "msg=" + content, function (data) {
// 清空输入框
$("#idNode").val("");
// 隐藏进度条
loading.baosight.hidePageLoadingMsg()
$("#result").html(data)
})
}
十一丶同步区块链数据
当一个新节点启动的时候,是没有任何数据的,此时可以发送同步请求,从其他节点获取区块链数据.本节实现垓功能.
1.页面上增加同步按钮,并实现点击事件
function syncData() {
// 打开进度条
loading.baosight.showPageLoadingMsg(false);
// 发起请求
$.post("syncData", function (data) {
// 隐藏进度条
loading.baosight.hidePageLoadingMsg()
$("#result").html(data)
})
}
2.修改Notebook,使其为单例模式
private NoteBook() {
loadFile();
}
private static volatile NoteBook instance;
public static NoteBook getInstance() {
if (instance == null) {
synchronized (NoteBook.class) {
if (instance == null) {
instance = new NoteBook();
}
}
}
return instance;
}
3.修改Controller,增加同步方法
// 向其他节点发起请求,要求获取区块链的数据
@RequestMapping("/syncData")
public String syncData() {
for (MyClient client : clients) {
client.send("请把你最新的区块链数据发给我一份");
}
return "发送消息成功";
}
4.增加MessageBean,用于传递数据
public class MessageBean {
// 1 ,服务器发送给客户端的最新的区块链数据
public int type;
public String msg;
public MessageBean() {
}
public MessageBean(int type, String msg) {
this.type = type;
this.msg = msg;
}
}
5.修改Server接收消息的方法
public void onMessage(WebSocket conn, String message) {
try {
System.out.println("WebSocket服务器__" + port + "__收到了消息,对方是:" + conn.getRemoteSocketAddress().toString() + "__消息的内容是:" + message);
if ("请把你最新的区块链数据发给我一份".equals(message)) {
// 获取本地区块链数据
NoteBook noteBook = NoteBook.getInstance();
ArrayList
// 封装数据
ObjectMapper objectMapper = new ObjectMapper();
String chain = objectMapper.writeValueAsString(list);
MessageBean bean = new MessageBean(1, chain);
String msg = objectMapper.writeValueAsString(bean);
// 广播数据
broadcast(msg);
}
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
6.修改Client接收消息的方法
public void onMessage(String message) {
try {
System.out.println("WebSocket客户端__" + name + "_收到消息,内容是:" + message);
if (!StringUtils.isEmpty(message)) {
// 解析消息对象
ObjectMapper objectMapper = new ObjectMapper();
MessageBean bean = objectMapper.readValue(message, MessageBean.class);
// 判断消息类型
if (bean.type == 1) {
// 获取数据
JavaType javaType = objectMapper.getTypeFactory().constructParametricType(ArrayList.class, Block.class);
ArrayList
// 对数据进行比对
NoteBook book = NoteBook.getInstance();
book.compareList(list);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
7.修改Notebook,增加比对数据的方法
// 比对数据,如果新的数据长度更长,就替换掉本地数据
public void compareList(ArrayList
System.out.println("参数:" + newList.size());
if (newList.size() > list.size()) {
list = newList;
}
System.out.println("list:" + list.size());
}
十二丶广播交易数据
我们添加完交易后,将交易数据广播给其他节点
1.修改Controller,添加记录的方法
@RequestMapping(value = "/addNote", method = RequestMethod.POST)
public String addNote(Transaction transaction) {
try {
// 校验交易数据
if (transaction.verify()) {
// 将交易数据转为字符串
ObjectMapper objectMapper = new ObjectMapper();
String note = objectMapper.writeValueAsString(transaction);
// 封装消息Bean
MessageBean messageBean = new MessageBean(2, note);
String bean = objectMapper.writeValueAsString(messageBean);
// 广播交易数据
server.broadcast(bean);
// 添加数据
book.addNote(note);
return "success";
} else {
throw new RuntimeException("交易数据校验失败");
}
} catch (Exception e) {
return "fail:" + e.getMessage();
}
}
2.修改Client,接收消息的方法
public void onMessage(String message) {
try {
System.out.println("WebSocket客户端__" + name + "_收到消息,内容是:" + message);
if (!StringUtils.isEmpty(message)) {
NoteBook book = NoteBook.getInstance();
// 解析消息对象
ObjectMapper objectMapper = new ObjectMapper();
MessageBean bean = objectMapper.readValue(message, MessageBean.class);
// 判断消息类型
if (bean.type == 1) {
// 获取数据
JavaType javaType = objectMapper.getTypeFactory().constructParametricType(ArrayList.class, Block.class);
ArrayList
// 对数据进行比对
book.compareList(list);
} else if (bean.type == 2) {
// 获取交易数据
Transaction transaction = objectMapper.readValue(bean.msg, Transaction.class);
// 验证交易数据
if (transaction.verify()) {
// 添加到区块
book.addNote(bean.msg);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
以上实现了比特币的简单实现,如果对你有帮助请帮我点赞谢谢.