从0开始搭建一个区块链Demo
主要以比特币区块链为基础,做一个法院案件记录上链及修改的样例。结合上一篇的介绍
https://blog.csdn.net/NEU_LightBulb/article/details/103475390
假设区块链服务用的库表和数据库表在一个数据库里。整个工程是一个Springboot2工程
https://github.com/zjw271208550/learn/tree/master/blockchain-client-1
-
数据存储
CREATE TABLE "db_data"."t_data" (
"c_bh" varchar NOT NULL, --案件编号
"c_ah" varchar, --案件案号
"dt_jarq" timestamp(6), --结案日期
"dt_update" timestamp(6), --数据更新日期
CONSTRAINT "t_data_pkey" PRIMARY KEY ("c_bh")
)
-
数据实体——继承BlockData
public class Data extends BlockData {
private String bh;
private String ah;
private Date jarq;
private Date update;
public Data(String bh, String ah, Date jarq,Date update) {
super(bh, bh + ah + (null==jarq?"":date2String(jarq)) + date2String(update));
this.bh = bh;
this.ah = ah;
this.jarq = jarq;
this.update = update;
}
private static String date2String(Date date){
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
String rel = sdf.format(date.getTime());
return rel;
}
public String getBh() {
return bh;
}
public void setBh(String bh) {
this.bh = bh;
}
public String getAh() {
return ah;
}
public void setAh(String ah) {
this.ah = ah;
}
public Date getJarq() {
return jarq;
}
public void setJarq(Date jarq) {
this.jarq = jarq;
}
public Date getUpdate() {
return update;
}
public void setUpdate(Date update) {
this.update = update;
}
}
-
Mapper——继承BCMapper
@Repository
@Mapper
public interface DBMapper extends BCMapper {
@Select("SELECT * FROM db_data.t_data WHERE c_bh=#{id}")
@Results({
@Result(property = "bh", column = "c_bh"),
@Result(property = "ah", column = "c_ah"),
@Result(property = "jarq",column = "dt_jarq"),
@Result(property = "update",column = "dt_update")
})
Data getDataById(@Param("id") String id);
@Insert("INSERT INTO db_data.t_data VALUES(#{bh},#{ah},#{jarq},#{update}) ")
void addData(Data data);
@Update("UPDATE db_data.t_data SET dt_jarq=#{jarq} WHERE c_bh=#{id}")
void updateJarqById(@Param("jarq") Date jarq, @Param("id") String id);
@Delete("DELETE FROM db_data.t_data WHERE c_bh=#{id}")
void deleteDataById(@Param("id") String id);
}
-
Service——调用ServiceFunction中的方法3
@Service
public class BaseService {
private final Logger logger = LoggerFactory.getLogger(BaseService.class);
@Autowired
private DBMapper dbMapper;
@Autowired
private ClientKey clientKey;
/**
* 根据 id获得数据
*/
public Data getDataById(String id){
Data data = dbMapper.getDataById(id);
if(checkDataById(data)){
return data;
}else{
logger.error("账目中这个编号的数据与数据库中的不符");
return null;
}
}
/**
* 创世
*/
public void init(){
if(!ServiceFunction.hasInit(dbMapper)) {
ServiceFunction.initBlock(dbMapper);
}
}
/**
* 添加一个新数据,数据准备上链
*/
public String addOneData(Data data) throws Exception{
if(!ServiceFunction.hasInit(dbMapper)){
String message = "尚未创世或创世失败";
logger.error(message);
return message;
}
if(checkDataById(data)){
String message = "账目中已存在这个编号的数据";
logger.error(message);
return message;
}else {
boolean rel = ServiceFunction.addNewData(dbMapper,clientKey.getPublicKey(),clientKey.getPrivateKey(),data);
if(rel) {
dbMapper.addData(data);
return "成功";
}else {
String message = "添加新数据失败";
logger.error(message);
return message;
}
}
}
/**
* 挖掘,数据上链
*/
public boolean mine(){
boolean rel = false;
try {
rel = ServiceFunction.mineTransactions(dbMapper,clientKey.getPublicKey());
}catch (Exception e){
logger.error("挖掘失败",e);
return false;
}
return rel;
}
private boolean checkDataById(Data dataInDB){
return ServiceFunction.checkDataById(dbMapper,dataInDB);
}
}
-
Controller
@RestController
public class Controller {
private final Logger logger = LoggerFactory.getLogger(BaseService.class);
@Autowired
BaseService service;
/**
* 初始化区块链的创世块
* ===> Block
* ===> 是否创世 >> 是否合法
*/
@RequestMapping("/init")
public String init(){
service.init();
return "OK";
}
/**
* 添加一条数据
* ===> 原始数据与 BC的关系
* ===> Tx机制
* ===> 检查数据唯一性 >> 创建 txIn、txOut >> 创建tx >> 签名 >> Tx信息入库 >> data入库
*/
@RequestMapping("/add")
public String add(){
Data data = new Data("abcd1234","xxxxxx",null,new Date(new java.util.Date().getTime()));
try {
return service.addOneData(data);
}catch (Exception e){
logger.error("添加异常",e);
return e.getMessage();
}
}
/**
* 挖掘区块,正式上链
* ===> UTxOut
* ===> 获取未上链 Tx(并广播) >> 验证 Tx合法性 >> 验证 TxIn合法性 >> 生成区块(并广播) >> 生成 UTxOut(更新 Tx)
*/
@RequestMapping("/mine")
public String mine(){
try {
if(service.mine()) {
return "OK";
}else {
return "失败";
}
}catch (Exception e){
logger.error("挖掘异常",e);
return e.getMessage();
}
}
/**
* 在整个网络中查询(不过滤地址公钥)
* ===> UTxOut中是否存在 >> 是否一致
*/
@RequestMapping("/quaryall")
public Data quaryInAll(){
String bh = "abcd1234";
Data data = service.getDataById(bh);
return data;
}
}
-
Client Key
@Component
public class ClientKey {
private final Logger logger = LoggerFactory.getLogger(ClientKey.class);
private String realAddress;
private String publicKey;
private String privateKey;
public ClientKey() {
try {
this.realAddress = InetAddress.getLocalHost().getHostAddress();
}catch (UnknownHostException e){
this.realAddress = "172.16.192.90";
logger.error(e.getMessage());
}
try {
byte[] privateKeyBytes = Crypt.generateECPrivateKeyFromRandom(this.realAddress.getBytes());
this.privateKey = Crypt.bytes2Base64String(privateKeyBytes);
byte[] publicKeyBytes = Crypt.getECPublicKeyFromPrivateKey(privateKeyBytes);
this.publicKey = Crypt.bytes2Base64String(publicKeyBytes);
}catch (Exception e){
logger.error(e.getMessage());
this.privateKey = null;
this.publicKey = null;
}
}
public String getRealAddress() {
return realAddress;
}
public void setRealAddress(String realAddress) {
this.realAddress = realAddress;
}
public String getPublicKey() {
return publicKey;
}
public void setPublicKey(String publicKey) {
this.publicKey = publicKey;
}
public String getPrivateKey() {
return privateKey;
}
public void setPrivateKey(String privateKey) {
this.privateKey = privateKey;
}
}
-
演示
- 直接ADD
- 执行创世
查看Block表的创世块数据
- 再ADD
查看数据表中插入的数据
查看生成的输入交易与输出交易
查看对应的交易,这时的交易还没有被挖掘到区块链中,所以没有对应的区块编号也没有对应区块
同样的没有上链也就是现在的数据还是不被所有节点承认的,所以为输出的交易中也不会有记录
- 查询
就像刚刚说的,这个数据还没被承认,所以是查询不到的
- 挖掘
挖掘后数据正式上链,挖掘出新的区块记录交易,且交易与这个区块ID关联
同时数据生效出现在为输出的交易记录中
- 再查询