首页 文章 精选 留言 我的

精选列表

搜索[网站开发],共10000篇文章
优秀的个人博客,低调大师

Android开发教程 - 使用Data Binding Android Studio不能正常生成相关类/方法的解决办法

本系列目录 使用Data Binding(一)介绍 使用Data Binding(二)集成与配置 使用Data Binding(三)在Activity中的使用 使用Data Binding(四)在Fragment中的使用 使用Data Binding(五)数据绑定 使用Data Binding(六)RecyclerView Adapter中的使用 使用Data Binding(七)使用BindingAdapter简化图片加载 使用Data Binding(八)使用自定义Interface 使用Data Binding Android Studio不能正常生成相关类/方法的解决办法 有时候新建布局文件时或者定义布局中的变量时,不能即时生成相关Binding类或方法(其实是不能正确索引),也许是IDE支持的一些BUG,不过Android Studio对Data Binding的支持问题也仅限于这些,并且可以比较简单地解决这些问题。 方法一(经常不起作用): 菜单 -> Build -> Clean Project -> Rebuild Project 方法二(推荐): 关闭Android Studio,重新打开。 此时工程会重新索引,大部分情况会正常。 方法三: 菜单 -> File -> Invalidate Caches / Restart… 这种方法是最终的解决方案,一般都会解决IDE的索引问题,但是会导致所有缓存被删除,再次打开后第一次索引较慢。

优秀的个人博客,低调大师

【JavaScript游戏开发】JavaScript+HTML5封装的苏拉卡尔塔游戏(包含源码)

版权声明:本文为博主原创文章,未经博主允许不得转载。更多学习资料请访问我爱科技论坛:www.52tech.tech https://blog.csdn.net/m0_37981569/article/details/81978903 /** 苏拉克尔塔游戏 * 思路: * 1.棋盘设置:使用HTML5的canvas标签绘制整个棋盘 * 2.点击事件:当页面被点击时,获取点击的x,y像素点,根据此像素点进行判断,再在合适位置绘制黑红棋子,棋子均是使用canvas绘制的 * 3.保存落子记录:将数据存入一个二维数组,x和y表是落子坐标,1为白棋,2为黑棋,0代表此处无棋子,只有没有棋子的才能落子 * 4.判断输赢:每次落子后以此坐标分别向左右,上下,右下,右上进行判断,设参数count,遇到同色棋子+1,遇到空格或不同色棋子终止,当count=5时,游戏结束 * 20180823 */ 完整项目源码已经开源:https://github.com/xiugangzhang/SularaGame 项目在线预览地址: http://htmlpreview.github.io/?https://github.com/xiugangzhang/SularaGame/blob/master/SularaGame.html 主要功能: 1.棋盘棋子的初始化 2.走棋规则的实现 3.输赢的判定 4.自动走棋的实现 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>SuraKarta Game</title> </head> <body onload="startLoad()" style="padding:0px;margin:0px"> <canvas width="700" id="canvas" onmousedown="play(event)" height="600"> </canvas> <div style=" border: solid 2px purple; display: block; position:absolute; left:750px; top:50px; width: 100px; height: 500px; text-align: center; margin:auto; line-height: 45px;"> <input type="button" value="START" id="begin" onclick="isNewGame()"/> <input type="button" value="STOP" id="replay" onclick="isExitGame()"/> </div> </body> <script type="text/javascript"> /** 苏拉克尔塔游戏 * 思路: * 1.棋盘设置:使用HTML5的canvas标签绘制整个棋盘 * 2.点击事件:当页面被点击时,获取点击的x,y像素点,根据此像素点进行判断,再在合适位置绘制黑红棋子,棋子均是使用canvas绘制的 * 3.保存落子记录:将数据存入一个二维数组,x和y表是落子坐标,1为白棋,2为黑棋,0代表此处无棋子,只有没有棋子的才能落子 * 4.判断输赢:每次落子后以此坐标分别向左右,上下,右下,右上进行判断,设参数count,遇到同色棋子+1,遇到空格或不同色棋子终止,当count=5时,游戏结束 * 20180823 */ /**全局参数初始化 * */ var canvas; //html5画布 var context; var isRed = false; //设置是否该轮到白棋,黑棋先手 var winner = ''; //赢家初始化为空 var step = 225;//总步数 // 记录棋盘的位置坐标 var chessData = new Array(6); //二维数组存储棋盘落子信息,初始化数组chessData值为0即此处没有棋子,1为红棋,2为黑棋 for (var x = 0; x < 6; x++) { chessData[x] = new Array(6); for (var y = 0; y < 6; y++) { chessData[x][y] = 0; } } /**每次打开网页加载棋盘和相关游戏信息 * */ function startLoad() { drawRect(); // 会加载游戏上一局保存的记录 //loadGame(); } /**棋盘样式信息 * */ function drawRect() { /*//创建棋盘背景 canvas = document.getElementById("canvas"); context = canvas.getContext("2d"); context.fillStyle = '#FFFFFF'; context.fillRect(0, 0, 1024, 768); //标题 context.fillStyle = '#00f'; context.font = '20px Consols'; context.strokeText('SuraKarta Game Board', 750, 150); context.strokeRect(745, 120, 230, 250);*/ //新游戏 context.strokeRect(790, 180, 120, 30); context.fillStyle = '#00f'; context.font = '25px Consols'; context.strokeText('START', 809, 205); //结束游戏 context.strokeRect(790, 250, 120, 30); context.fillStyle = '#00f'; context.font = '25px Consols'; context.strokeText('STOP', 812, 275); //游戏说明 /*context.fillStyle = '#00f'; context.font = '15px Arial'; context.strokeText('游戏规则:玩家执黑色', 780, 200); context.strokeText('棋子先手,电脑执白色棋子', 750, 220); context.strokeText('后手,任何一方形成五子连', 750, 240); context.strokeText('珠,游戏终止。', 750, 260);*/ //棋盘纵横线 for (var i = 1; i < 7; i++) { if (i == 1 || i == 6) { context.lineWidth = 8; context.strokeStyle = '#FEFE02'; } else if (i == 2 || i == 5) { context.lineWidth = 8; context.strokeStyle = '#02CFFF'; } else { context.lineWidth = 8; context.strokeStyle = '#028202'; } context.beginPath(); context.moveTo(57 * i + 160, 150); context.lineTo(57 * i + 160, 437); context.closePath(); context.stroke(); context.beginPath(); context.moveTo(216, 57 * i + 94); context.lineTo(501, 57 * i + 94); context.closePath(); context.stroke(); } // 圆弧的绘制 //弧度是以x轴正方向为基准、进行顺时针旋转的角度来计算 // true 表示逆时针, false表示顺时针 var x = 0; var y = 0; context.beginPath(); context.lineWidth = 8; context.strokeStyle = '#02CFFF'; context.arc(217, 151, 57, 0, getRads(90), true); context.stroke(); context.beginPath(); context.lineWidth = 8; context.strokeStyle = '#028202'; context.arc(217, 151, 57 * 2, 0, getRads(90), true); context.stroke(); context.beginPath(); context.lineWidth = 8; context.strokeStyle = '#02CFFF'; context.arc(502, 150, 57, getRads(90), getRads(180), true); context.stroke(); context.beginPath(); context.lineWidth = 8; context.strokeStyle = '#028202'; context.arc(502, 150, 57 * 2, getRads(90), getRads(180), true); context.stroke(); context.beginPath(); context.lineWidth = 8; context.strokeStyle = '#02CFFF'; context.arc(217, 436, 57, getRads(0), getRads(270), false); context.stroke(); context.beginPath(); context.lineWidth = 8; context.strokeStyle = '#028202'; context.arc(217, 436, 57 * 2, getRads(0), getRads(270), false); context.stroke(); context.beginPath(); context.lineWidth = 8; context.strokeStyle = '#02CFFF'; context.arc(503, 436, 57, getRads(-90), getRads(180), false); context.stroke(); context.beginPath(); context.lineWidth = 8; context.strokeStyle = '#028202'; context.arc(503, 436, 57 * 2, getRads(-90), getRads(180), false); context.stroke(); } // 重绘棋盘 function update() { context.clearRect(0, 0, 700, 600); drawRect(); } /**加载游戏记录 * 通过cookie查询是否存在游戏记录,有则加载 */ function loadGame() { var we = getCookie("red"); console.log("获取白色棋子的cookie" + we); loadChessByCookie("white", we); var bk = getCookie("black"); console.log("获取黑色棋子的cookie" + bk); loadChessByCookie("black", bk); winner = getCookie("winner"); //如果没有winner的cookie存在的话此处winner的值会被设为null //判断是否该电脑走 // var temp=getCookie("isRed"); // if(temp!=null){ // if(temp=="true"){ // AIplay(); // } // } } /**分割cookie中获取的字符串,从而加载棋子 * @param {[type]} color [description] * @param {[type]} record [description] * @return {[type]} [description] */ function loadChessByCookie(color, record) { if (record == null) { console.log(color + "棋子没有游戏记录"); } else { var a = record.split(";"); console.log(color + "第一次分割字符串:" + a) for (var i = 0; i < a.length; i++) { var b = a[i].split(","); console.log("第" + (i + 1) + "个" + color + "棋子坐标:" + parseInt(b[0]) + "," + parseInt(b[1])); chess(color, b[0], b[1]); } } } /** 落子 * @param {[type]} turn [description] * @param {[type]} x [description] * @param {[type]} y [description] * @return {[type]} [description] */ function drawChess(color, x, y) { //参数为,棋(1为白棋,2为黑棋),数组位置 if (x >= 0 && x < 6 && y >= 0 && y < 6) { if (color == "red") { chess("red", x, y); } else if (color == "black") { chess("black", x, y); } else if (color == "blue" && isSelected) { chess("blue", x, y); } } /*if (--step == 0) { winner = "和局"; setCookie("winner", winner); alert(winner); }*/ } /**绘制棋子,每次绘制棋子的时候刷新cookie信息 * */ function chess(color, x, y) { if (x > 6 || y > 6 || x < 0 || y < 0 || x == 6 || y == 6){ return; } context.fillStyle = color; context.beginPath(); // 绘制棋子(注意要把棋子的初始位置复位) context.arc(x * 56 + 217, y * 56 + 150, 20, 0, Math.PI * 2, true); context.closePath(); context.fill(); if (color == "red") { console.log("电脑在" + x + "," + y + "画了个红棋"); // 1为红棋子 chessData[x][y] = 1; } else if (color == "black") { // 2为黑棋子 console.log("电脑在" + x + "," + y + "画了个黑棋"); chessData[x][y] = 2; } /*var a = getCookie(color); if (a != null) { delCookie(color); setCookie(color, a + ";" + x + "," + y, 30); } else { setCookie(color, x + "," + y, 30); }*/ } /**鼠标点击事件 * 在这里开始完成棋子的博弈操作 * @param {[type]} e [description] * @return {[type]} [description] */ function play(e) { //鼠标点击时发生 var color; var e = e || event; console.log("(e.x, e.y) = " + "(" + e.clientX + ", " + e.clientY + ")"); // 用这个棋盘的初始位置为坐标的原点(217, 150) var px = e.clientX - 217; var py = e.clientY - 150; console.log("(px, py) = " + "(" + px + ", " + py + ")"); // 棋子的大小 var x = parseInt(px / 57); var y = parseInt(py / 57); console.log("(x, y) = " + "(" + x + ", " + y + ")"); //isNewGame(e.clientX, e.clientY); //是否点击了newgame //isExitGame(e.clientX, e.clientY); //是否点击了exitGame // 划定棋盘的位置范围 if (px < 0 || py < 0 || x > 5 || y > 5) { //鼠标点击棋盘外的区域不响应 return; } //chess('red', 2, 3); doCheck(x, y); } // 检查鼠标当前点击的位置上有没有棋子 var isSelected = false; var selectedX = -1; var selectedY = -1; var movetoX =-1; var movetoY =-1; // 选中的棋子ID //var selectedChess; var chessId = 1; var col; var row; for (var i=0; i<24; i++){ drawChessById(i); } var stone = new Array(24); function drawChessById(id, x, y) { /* // 黑色 if (id == "00"){ chess("red", 0, 0); }if (id == "10"){ }if (id == "20"){ }if (id == "30"){ }if (id == "40"){ }if (id == "50"){ }if (id == "01"){ }if (id == "11"){ }if (id == "21"){ }if (id == "31"){ }if (id == "41"){ }if (id == "51"){ } // 红色 if (id == "04"){ }if (id == "14"){ }if (id == "24"){ }if (id == "34"){ }if (id == "44"){ }if (id == "54"){ }if (id == "05"){ }if (id == "15"){ }if (id == "25"){ }if (id == "35"){ }if (id == "45"){ }if (id == "55"){ }*/ // //chess(stone[id], x, y); } // 记录选中的棋子的ID function doCheck(x, y) { if (winner != '' && winner != null) { //已经结束的游戏只能点击new game //alert(winner); //return; } // 判断是黑棋子还是红棋子(注意在判断的时候要使用双等号, 不要使用一个等号哈) var chessColor; // 红色棋子被选中 // 每次点击一次, 都要把键盘的所有棋子重新绘制一次, 而不是去覆盖 // 判断点击的位置有没有棋子被选中 if (chessData[x][y] != 0) { //alert("有棋子"); isSelected = true; } else { alert("没有棋子"); isSelected = false; } //chess("blue", 1, 2); // 如果有棋子被选中了 if (isSelected) { // 开始绘制棋子 chessColor = "blue"; // 把选中的棋子的坐标记录下来 selectedX = x; selectedY = y; if (chessData[x][y] == 1) { /*alert("你选择的是红色棋子" + "这个棋子的ID是" + selectedX + selectedY);*/ chessId = selectedX+","+selectedY; alert("chessId="+chessId); } else { /*alert("你选择的是黑色棋子" + "这个棋子的ID是" + selectedX + selectedY);*/ chessId = selectedX+","+selectedY; alert("chessId="+chessId); } // 重新绘制 //drawRedAndBlackChess(chessColor, selectedX, selectedY); // 棋盘上面任意位置有棋子被选中就变颜色 update(); // 棋子选中的位置变颜色 if (chessData[selectedX][selectedY] != 0) { chess("blue", selectedX, selectedY); // 记录这个选中的棋子的id var id = selectedX + "" + selectedY; alert("ID" + id); // 只要这个棋子选中了 drawRedAndBlackChess("blue", selectedX, selectedY); } } // 第一次选择了棋子, 第二次点击其他位置 if (!isSelected){ alert("开始移动"); // 记录鼠标将要移动到的位置 movetoX = x; movetoY = y; //移动棋子到新的位置 //alert("movetoX, movetoY"+x+", "+y); // 强制加载当前的文档 //location.reload(); // 移动当前的棋子(开始重绘) //update(); // 移动棋子 chessMove(selectedX, selectedY, movetoX, movetoY); //drawRedAndBlackChess(chessColor, selectedX, selectedY); } } // 根据棋子的ID来绘制棋子 /*var row; var col; var type; var arr = new Array(24); function drawChessById(id) { var chess = arr[id]; // 根据行列号来 }*/ // 对绘制好的棋子赋值id var arr = new Array(24); function getChessidByRowCol(row, col) { for(var i=0; i<6; i++){ for (var j=0; j<6; j++){ //alert(); arr.push(); } } } // 移动棋子(源位置移动到目标位置) function chessMove(selectedX, selectedY, movetoX, movetoY) { //alert(selectedX+" "+selectedY+","+movetoX+" "+movetoY); // 从源位置移动到目标位置 // 红色棋子在移动 // 原来的位置不画 //update(); if (chessData[selectedX][selectedY] == 1) { alert("SeID"+selectedX+selectedY+"选择的"+chessId); update(); //location.reload(); //alert("红色"); // 红色棋子移动到的位置 chess("red", movetoX, movetoY); // 绘制红色棋子(移动的位置不用画, 其他的位置都要画出来) // 这个是在最下面两行绘制的红色棋子的方式 for (var j = 0; j < 6; j++) { for (var i = 4; i < 6; i++) { // 开始绘制更新后的红色棋子 if (j == selectedX && i == selectedY) { // 这个位置就是那个之前移动棋子的位置 continue; } else { chess("red", j, i); } } } /*for (var i=0; i<6; i++){ for (var j=0; j<6; j++){ if (j == selectedX && i == selectedY){ //chess("red", selectedX, selectedY); continue; } chess("red", selectedX, selectedY); } }*/ for (var j = 0; j < 6; j++) { for (var i = 0; i < 2; i++) { chess("black", j, i); } } } // 黑色棋子在移动 if (chessData[selectedX][selectedY] == 2) { // 绘制红色棋子(移动的位置不用画, 其他的位置都要画出来) for (var j = 0; j < 6; j++) { for (var i = 4; i < 6; i++) { chess("red", j, i); } } //alert("黑色"); // 绘制黑色棋子 //alert("chess move"); chess("black", movetoX, movetoY); for (var j = 0; j < 6; j++) { for (var i = 0; i < 2; i++) { // 把选中的棋子设为蓝色 // 除了原来的位置不画出来,其他位置都要画 if (selectedX == j && selectedY == i) { // 原来的这个位置不画出来 continue; } chess("black", j, i); } } } } /**新游戏按钮 * */ function isNewGame(x, y) { if (confirm("Arr you sure to play new game now?")) { update(); // 从新摆棋子 drawRedAndBlackChess(); //chess("red", 1, 2); //chess("black", 2, 2); // 创建24个棋子对象 /*for (var i=0; i<24; i++){ drawChessById(i, x, y); }*/ } } // 主要用于绘制24个棋子 function drawRedAndBlackChess(chesscolor, x, y) { // 绘制黑色棋子12 个 for (var j = 0; j < 6; j++) { for (var i = 0; i < 2; i++) { // 把选中的棋子设为蓝色 if (isSelected && j == x && i == y) { //alert("isSelected && j==x && i==y"); //chess(chesscolor, j, i); } else { chess("black", j, i); } } } // 绘制红色棋子 12个 for (var j = 0; j < 6; j++) { for (var i = 4; i < 6; i++) { if (isSelected && j == x && i == y) { //alert("isSelected && j==x && i==y"); //chess(chesscolor, j, i); } else { chess("red", j, i); } } } } // 退出游戏 function isExitGame(x, y) { if (confirm("Are you sure to exit game now?")) { //退出游戏 window.close(); } } /**判断此局游戏是否已有结果 * 每次落子判断游戏是否胜利 * */ function isWin(color, x, y) { console.log("判断" + color + "(" + x + "," + y + ")是否胜利"); var temp = 2; //默认为黑色 if (color == "white") { temp = 1; } //白色 console.log("temp=" + temp); lrCount(temp, x, y); tbCount(temp, x, y); rtCount(temp, x, y); rbCount(temp, x, y); } function lrCount(temp, x, y) { var line = new Array(4); var count = 0; for (var i = x; i >= 0; i--) { line[0] = i; line[1] = y; if (chessData[i][y] == temp) { ++count; } else { i = -1; } } for (var i = x; i <= 6; i++) { line[2] = i; line[3] = y; if (chessData[i][y] == temp) { ++count; } else { i = 100; } } success(line[0], line[1], line[2], line[3], temp, --count); } function tbCount(temp, x, y) { var line = new Array(4); var count = 0; for (var i = y; i >= 0; i--) { line[0] = x; line[1] = i; if (chessData[x][i] == temp) { ++count; } else { i = -1; } } for (var i = y; i <= 14; i++) { line[2] = x; line[3] = i; if (chessData[x][i] == temp) { ++count; } else { i = 100; } } success(line[0], line[1], line[2], line[3], temp, --count); } function rtCount(temp, x, y) { var line = new Array(4); var count = 0; for (var i = x, j = y; i <= 14 && j >= 0;) { line[0] = i; line[1] = j; if (chessData[i][j] == temp) { ++count; } else { i = 100; } i++; j--; } for (var i = x, j = y; i >= 0 && j <= 14;) { line[2] = i; line[3] = j; if (chessData[i][j] == temp) { ++count; } else { i = -1; } i--; j++; } success(line[0], line[1], line[2], line[3], temp, --count); } function rbCount(temp, x, y) { //右下斜判断 var line = new Array(4); var count = 0; for (var i = x, j = y; i >= 0 && j >= 0;) { line[0] = i; line[1] = j; if (chessData[i][j] == temp) { ++count; } else { i = -1; } i--; j--; } for (var i = x, j = y; i <= 14 && j <= 14;) { line[2] = i; line[3] = j; if (chessData[i][j] == temp) { ++count; } else { i = 100; } i++; j++; } success(line[0], line[1], line[2], line[3], temp, --count); } /**判断是否胜利及胜利之后的操作 * @param {[type]} turn [description] * @param {[type]} count [description] * @return {[type]} [description] */ function success(a, b, c, d, temp, count) { if (count == 5) { //因为落子点重复计算了一次 console.log("此局游戏结束啦"); console.log("(" + a + "," + b + ")" + "到" + "(" + c + "," + d + ")"); context.beginPath(); context.lineWidth = 5; context.strokeStyle = 'purple'; context.moveTo(40 * a + 180, 40 * b + 80); context.lineTo(40 * c + 180, 40 * d + 80); context.closePath(); context.stroke(); winner = "黑棋胜利!"; if (temp == 1) { winner = "白棋胜利!"; } setCookie("winner", winner); alert(winner); } } /**使用cookie保存棋盘信息,防止不小心关闭网页 * @param {[type]} name [description] * @param {[type]} value [description] * @param {[type]} time [description] */ function setCookie(name, value, time) { var exp = new Date(); exp.setTime(exp.getTime() + time * 24 * 60 * 60 * 1000); document.cookie = name + "=" + escape(value) + ";expires=" + exp.toGMTString(); } /**获取cookie,初始化棋盘 *cookie */ function getCookie(name) { var arr, reg = new RegExp("(^| )" + name + "=([^;]*)(;|$)"); if (arr = document.cookie.match(reg)) return unescape(arr[2]); else return null; } /**删除cookie * */ function delCookie(name) { var exp = new Date(); exp.setTime(exp.getTime() - 1); var cval = getCookie(name); if (cval != null) document.cookie = name + "=" + cval + ";expires=" + exp.toGMTString(); } /** * 禁止页面滚动事件 * @return {[type]} [description] */ var pageScroll = 0; window.onscroll = function () { pageScroll++; scrollTo(0, 0); if (pageScroll > 100) { //每当玩家滚动页面滚动条100次提醒 pageScroll = 0; } } /** * 工具函数 */ function getRads(degrees) { return (Math.PI * degrees) / 180; } function getDegrees(rads) { return (rads * 180) / Math.PI; } </script> </html>

优秀的个人博客,低调大师

构造万物互联的区块链开发公司 区块链与物联网的发展

我们知道在21世纪,对我们生活影响最大的莫过于互联网的发展和应用了,随着互联网的逐渐发展,大家已经逐渐不再满足于网络之间的联系,开始逐渐往实物当中发展联系,这也就是物联网的诞生。 物联网作为新一代信息技术的重要领域,逐渐由工业应用拓展到家用领域,通过打通“物与物”之间的连接,构造起一个万物互联的时代。 简而言之,物联网通过传感器等外接设备将工业设备、汽车和家用电器等物品连接在一起,记录工艺流程、生活习惯等数据,然后再通过后端强大的数据分析能力服务于生产、生活方式。 虽然物联网运营商一直宣称能够有效保护用户的数据安全和隐私,但是一系列的安全漏洞和隐私泄露事件的发生使用户无法真正信任运营服务提供商能够实现他们的承诺。但是结合区块链的物联网就恰好可以解决这一问题。 目前物联网在网络安全上还是存在着不信任的因素,但是区块链可以使机器节点之间互相形成自治,形成有价值的自治物联网,由于区块链本身不可篡改的特性摆在那里,所以在前端可以通过区块链技术进行滤化,记录有效信息,这样的情况下,执行会更加准确。 区块链技术可以为物联网提供点对点直接互联的方式来传输数据,而不是通过中央处理器,这样分布式的计算就可以处理数以亿计的交易了。同时,还可以充分利用分布在不同位置的数以亿计闲置设备的计算力、存储容量和带宽,用于交易处理,大幅度降低计算和储存的成本。 另外,区块链技术叠加智能合约可将每个智能设备变成可以自我维护调节的独立的网络节点,这些节点可在事先规定或植入的规则基础上执行与其他节点交换信息或核实身份等功能。这样无论设备生命周期有多长,物联网产品都不会过时,节省了大量的设备维护成本。 物联网面向实体经济,对实体经济进行追踪,囊括物品从生产者到消费者之间所有环节,实现资金流、信息流、实体流三流合一,不可避免要涉及个体之间融资、交易、保险等金融领域问题,让虚拟经济从时间与空间两维全面感知实体经济行为,加速虚拟经济与实体经济二者有机融合。

优秀的个人博客,低调大师

从0到1简易区块链开发手册V0.3-数据持久化与创世区块

Author: brucefeng Email: brucefeng@brucefeng.com 编程语言:Golang 1.BoltDB简介 Bolt是一个纯粹Key/Value模型的程序。该项目的目标是为不需要完整数据库服务器(如Postgres或MySQL)的项目提供一个简单,快速,可靠的数据库。 BoltDB只需要将其链接到你的应用程序代码中即可使用BoltDB提供的API来高效的存取数据。而且BoltDB支持完全可序列化的ACID事务,让应用程序可以更简单的处理复杂操作。 其源码地址为:https://github.com/boltdb/bolt 2.BoltDB特性 BoltDB设计源于LMDB,具有以下特点: 使用Go语言编写 不需要服务器即可运行 支持数据结构 直接使用API存取数据,没有查询语句; 支持完全可序列化的ACID事务,这个特性比LevelDB强; 数据保存在内存映射的文件里。没有wal、线程压缩和垃圾回收; 通过COW技术,可实现无锁的读写并发,但是无法实现无锁的写写并发,这就注定了读性能超高,但写性能一般,适合与读多写少的场景。 BoltDB是一个Key/Value(键/值)存储,这意味着没有像SQL RDBMS(MySQL,PostgreSQL等)中的表,没有行,没有列。相反,数据作为键值对存储(如在Golang Maps中)。键值对存储在Buckets中,它们旨在对相似的对进行分组(这与RDBMS中的表类似)。因此,为了获得Value(值),需要知道该Value所在的桶和钥匙。 3.BoltDB简单使用 //通过go get下载并import import "github.com/boltdb/bolt" 3.1 打开或创建数据库 db, err := bolt.Open("my.db", 0600, nil) if err != nil { log.Fatal(err) } defer db.Close() 执行注意点 如果通过goland程序运行创建的my.db会保存在 GOPATH /src/Project目录下 如果通过go build main.go ; ./main 执行生成的my.db,会保存在当前目录GOPATH /src/Project/package下 3.2 数据库操作 (1) 创建数据库表与数据写入操作 //1. 调用Update方法进行数据的写入 err = db.Update(func(tx *bolt.Tx) error { //2.通过CreateBucket()方法创建BlockBucket(表),初次使用创建 b, err := tx.CreateBucket([]byte("BlockBucket")) if err != nil { return fmt.Errorf("Create bucket :%s", err) } //3.通过Put()方法往表里面存储一条数据(key,value),注意类型必须为[]byte if b != nil { err := b.Put([]byte("l"), []byte("Send $100 TO Bruce")) if err != nil { log.Panic("数据存储失败..") } } return nil }) //数据Update失败,退出程序 if err != nil { log.Panic(err) } (2) 数据写入 //1.打开数据库 db, err := bolt.Open("my.db", 0600, nil) if err != nil { log.Fatal(err) } defer db.Close() err = db.Update(func(tx *bolt.Tx) error { //2.通过Bucket()方法打开BlockBucket表 b := tx.Bucket([]byte("BlockBucket")) //3.通过Put()方法往表里面存储数据 if b != nil { err := b.Put([]byte("l"), []byte("Send $200 TO Fengyingcong")) err = b.Put([]byte("ll"), []byte("Send $100 TO Bruce")) if err != nil { log.Panic("数据存储失败..") } } return nil }) //更新失败 if err != nil { log.Panic(err) } (3) 数据读取 //1.打开数据库 db, err := bolt.Open("my.db", 0600, nil) if err != nil { log.Fatal(err) } defer db.Close() //2.通过View方法获取数据 err = db.View(func(tx *bolt.Tx) error { //3.打开BlockBucket表,获取表对象 b := tx.Bucket([]byte("BlockBucket")) //4.Get()方法通过key读取value if b != nil { data := b.Get([]byte("l")) fmt.Printf("%s\n", data) data = b.Get([]byte("ll")) fmt.Printf("%s\n", data) } return nil }) if err != nil { log.Panic(err) } 4.通过BoltDB存储区块 该代码包含对BoltDB的数据库创建,表创建,区块添加,区块查询操作 //1.创建一个区块对象block block := BLC.NewBlock("Send $500 to Tom", 1, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}) //2. 打印区块对象相关信息 fmt.Printf("区块的Hash信息为:\t%x\n", block.Hash) fmt.Printf("区块的数据信息为:\t%v\n", string(block.Data)) fmt.Printf("区块的随机数为:\t%d\n", block.Nonce) //3. 打开数据库 db, err := bolt.Open("my.db", 0600, nil) if err != nil { log.Fatal(err) } defer db.Close() //4. 更新数据 err = db.Update(func(tx *bolt.Tx) error { //4.1 打开BlockBucket表对象 b := tx.Bucket([]byte("blocks")) //4.2 如果表对象不存在,创建表对象 if b == nil { b, err = tx.CreateBucket([]byte("blocks")) if err != nil { log.Panic("Block Table Create Failed") } } //4.3 往表里面存储一条数据(key,value) err = b.Put([]byte("l"), block.Serialize()) if err != nil { log.Panic("数据存储失败..") } return nil }) //更新失败,返回错误 if err != nil { log.Panic("数据更新失败") } //5. 查看数据 err = db.View(func(tx *bolt.Tx) error { //5.1打开BlockBucket表对象 b := tx.Bucket([]byte("blocks")) if b != nil { //5.2 取出key=“l”对应的value blockData := b.Get([]byte("l")) //5.3反序列化 block := BLC.DeserializeBlock(blockData) //6. 打印区块对象相关信息 fmt.Printf("区块的Hash信息为:\t%x\n", block.Hash) fmt.Printf("区块的数据信息为:\t%v\n", string(block.Data)) fmt.Printf("区块的随机数为:\t%d\n", block.Nonce) } return nil }) //数据查看失败 if err != nil { log.Panic("数据更新失败") } 五.创建创世区块 1.概念 北京时间2009年1月4日2时15分5秒,比特币的第一个区块诞生了。随着时间往后推移,不断有新的区块被添加到链上,所有后续区块都可以追溯到第一个区块。第一个区块就被人们称为创世区块。 2. 工作量证明 在比特币世界中,获取区块记账权的过程称之为挖矿,一个矿工成功后,他会把之前打包好的网络上的交易记录到一页账本上,同步给其他人。因为这个矿工能够最先计算出超难数学题的正确答案,说明这个矿工付出了工作量,是一个有权利记账的人,因此其他人也会同意这一页账单。这种依靠工作量来证明记账权,大家来达成共识的机制叫做“工作量证明”,简而言之结果可以证明你付出了多少工作量。Proof Of Work简称“PoW”,关于其原理跟代码实现,我们在后面的代码分析中进行讲解说明。 2.1 定义结构体 type ProofOfWork struct { Block *Block //要验证的block Target *big.Int //目标hash } 2.2 创建工作量证明对象 const TargetBit = 16 //目标哈希的0个个数,16,20,24,28 func NewProofOfWork(block *Block) *ProofOfWork { //1.创建pow对象 pow := &ProofOfWork{} //2.设置属性值 pow.Block = block target := big.NewInt(1) // 目标hash,初始值为1 target.Lsh(target, 256-TargetBit) //左移256-16 pow.Target = target return pow } 我们首先设定一个难度系数值为16,即目标哈希前导0的个数,0的个数越多,挖矿难度越大,此处我们创建一个函数NewProofOfWork用于返回Pow对象。 目标Hash的长度为256bit,通过64个16进制byte进行展示,如下所示为前导0为16/4=4的哈希 0000c01d342fc51cb030f93979343de70ab771855dd8ca28e6f5888737759747 通过big.NewInt创建一个BigInt对象target 对target进行通过左移(256-TargetBit)位操作 2.3 将int64类型转[]byte func IntToHex(num int64) []byte { buff := new(bytes.Buffer) //将二进制数据写入w // err := binary.Write(buff, binary.BigEndian, num) if err != nil { log.Panic(err) } //转为[]byte并返回 return buff.Bytes() } 通过func Write(w io.Writer, order ByteOrder, data interface{}) error方法将一个int64的整数转为二进制后,每8bit一个byte,转为[]byte 2.4 拼接区块属性数据 func (pow *ProofOfWork) prepareData(nonce int64) []byte { data := bytes.Join([][]byte{ IntToHex(pow.Block.Height), pow.Block.PrevBlockHash, IntToHex(pow.Block.TimeStamp), pow.Block.HashTransactions(), IntToHex(nonce), IntToHex(TargetBit), }, []byte{}) return data } 通过bytes.Join方法将区块相关属性进行拼接成字节数组 2.5 "挖矿"方法 func (pow *ProofOfWork) Run() ([]byte, int64) { var nonce int64 = 0 var hash [32]byte for { //1.根据nonce获取数据 data := pow.prepareData(nonce) //2.生成hash hash = sha256.Sum256(data) //[32]byte fmt.Printf("\r%d,%x", nonce, hash) //3.验证:和目标hash比较 /* func (x *Int) Cmp(y *Int) (r int) Cmp compares x and y and returns: -1 if x < y 0 if x == y +1 if x > y 目的:target > hashInt,成功 */ hashInt := new(big.Int) hashInt.SetBytes(hash[:]) if pow.Target.Cmp(hashInt) == 1 { break } nonce++ } fmt.Println() return hash[:], nonce } 代码思路 设置nonce值:0,1,2....... block-->拼接数组,产生hash 比较实际hash和pow的目标hash 不断更改nonce的值,计算hash,直到小于目标hash。 2.6 验证区块 func (pow *ProofOfWork) IsValid() bool { hashInt := new(big.Int) hashInt.SetBytes(pow.Block.Hash) return pow.Target.Cmp(hashInt) == 1 } 判断方式同挖矿中的策略 3.区块创建 3.1 定义结构体 type Block struct { //字段属性 //1.高度:区块在区块链中的编号,第一个区块也叫创世区块,一般设定为0 Height int64 //2.上一个区块的Hash值 PrevBlockHash []byte //3.数据:Txs,交易数据 Txs []*Transaction //4.时间戳 TimeStamp int64 //5.自己的hash Hash []byte //6.Nonce Nonce int64 } 关于属性的定义,在代码的注释中比较清晰了,需要提一下的就是创世区块的PrevBlockHash一般设定为0 ,高度也一般设定为0 3.2 创建创世区块 func CreateGenesisBlock(txs []*Transaction) *Block{ return NewBlock(txs,make([]byte,32,32),0) } 设定创世区块的PrevBlockHash为0,区块高度为0 3.3 序列化区块对象 func (block *Block) Serialize()[]byte{ //1.创建一个buff var buf bytes.Buffer //2.创建一个编码器 encoder:=gob.NewEncoder(&buf) //3.编码 err:=encoder.Encode(block) if err != nil{ log.Panic(err) } return buf.Bytes() } 通过gob库的Encode方法将Block对象序列化成字节数组,用于持久化存储 3.4 字节数组反序列化 func DeserializeBlock(blockBytes [] byte) *Block{ var block Block //1.先创建一个reader reader:=bytes.NewReader(blockBytes) //2.创建××× decoder:=gob.NewDecoder(reader) //3.解码 err:=decoder.Decode(&block) if err != nil{ log.Panic(err) } return &block } 定义一个函数,用于将[]byte反序列化为block对象 4.区块链创建 4.1 定义结构体 type BlockChain struct { DB *bolt.DB //对应的数据库对象 Tip [] byte //存储区块中最后一个块的hash值 } 定义区块链结构体属性DB用于存储对应的数据库对象,Tip用于存储区块中最后一个块的Hash值 4.2 判断数据库是否存在 const DBName = "blockchain.db" //数据库的名字 const BlockBucketName = "blocks" //定义bucket 定义数据库名字以及定义用于存储区块数据的bucket(表)名 func dbExists() bool { if _, err := os.Stat(DBName); os.IsNotExist(err) { return false //表示文件不存在 } return true //表示文件存在 } 需要注意IsNotExist返回true,则表示不存在成立,返回值为true,则dbExists函数的返回值则需要返回false,否则,返回true 4.3 创建带有创世区块的区块链 func CreateBlockChainWithGenesisBlock(address string) { /* 1.判断数据库如果存在,直接结束方法 2.数据库不存在,创建创世区块,并存入到数据库中 */ if dbExists() { fmt.Println("数据库已经存在,无法创建创世区块") return } //数据库不存在 fmt.Println("数据库不存在") fmt.Println("正在创建创世区块") /* 1.创建创世区块 2.存入到数据库中 */ //创建一个txs--->CoinBase txCoinBase := NewCoinBaseTransaction(address) genesisBlock := CreateGenesisBlock([]*Transaction{txCoinBase}) db, err := bolt.Open(DBName, 0600, nil) if err != nil { log.Panic(err) } defer db.Close() err = db.Update(func(tx *bolt.Tx) error { //创世区块序列化后,存入到数据库中 b, err := tx.CreateBucketIfNotExists([]byte(BlockBucketName)) if err != nil { log.Panic(err) } if b != nil { err = b.Put(genesisBlock.Hash, genesisBlock.Serialize()) if err != nil { log.Panic(err) } b.Put([]byte("l"), genesisBlock.Hash) } return nil }) if err != nil { log.Panic(err) } //return &BlockChain{db, genesisBlock.Hash} } 代码分析 (1) 判断数据库是否存在,如果不存在,证明还没有创建创世区块,如果存在,则提示创世区块已存在,直接返回 if dbExists() { fmt.Println("数据库已经存在,无法创建创世区块") return } (2) 如果数据库不存在,则提示开始调用相关函数跟方法创建创世区块 fmt.Println("数据库不存在") fmt.Println("正在创建创世区块") (3) 创建一个交易数组Txs 关于交易这一部分内容,将在后面一个章节中进行详细说明,篇幅会非常长,这也是整个课程体系中最为繁琐,知识点最广的地方,届时慢慢分析 txCoinBase := NewCoinBaseTransaction(address) 通过函数NewCoinBaseTransaction创建一个CoinBase交易 func NewCoinBaseTransaction(address string) *Transaction { txInput := &TxInput{[]byte{}, -1, nil, nil} txOutput := NewTxOutput(10, address) txCoinBaseTransaction := &Transaction{[]byte{}, []*TxInput{txInput}, []*TxOutput{txOutput}} //设置交易ID txCoinBaseTransaction.SetID() return txCoinBaseTransaction } (4) 生成创世区块 genesisBlock := CreateGenesisBlock([]*Transaction{txCoinBase}) (5) 打开/创建数据库 db, err := bolt.Open(DBName, 0600, nil) if err != nil { log.Panic(err) } defer db.Close() 通过bolt.Open方法打开(如果不存在则创建)数据库文件,注意数据库关闭操作不能少,用defer实现延迟关闭。 (6) 将数据写入数据库 err = db.Update(func(tx *bolt.Tx) error { b, err := tx.CreateBucketIfNotExists([]byte(BlockBucketName)) if err != nil { log.Panic(err) } if b != nil { err = b.Put(genesisBlock.Hash, genesisBlock.Serialize()) if err != nil { log.Panic(err) } b.Put([]byte("l"), genesisBlock.Hash) } return nil }) if err != nil { log.Panic(err) } 通过db.Upadate方法进行数据更新操作 创建/打开存储区块的Bucket:BlockBucketName 将创世区块序列化后存入Bucket中 通过Put方法更新K/V值(Key:区块哈希,Value:区块序列化后的字节数组) 通过Put方法更新Key为“l”的Value为最新区块哈希值,此处即genesisBlock.Hash 5.命令行调用 func (cli *CLI) CreateBlockChain(address string) { CreateBlockChainWithGenesisBlock(address) } 测试命令 $ ./mybtc createblockchain -address 1DHPNHKfk9uUdog2f2xBvx9dq4NxpF5Q4Q 返回结果 数据库不存在 正在创建创世区块 32325,00005c7b4246aa88bd1f9664c665d6424d1522f569d981691ac2b5b5d15dd8d9 本章节介绍了如何创建一个带有创世区块的区块链,并持久化存储至数据库blockchain.db $ ls BLC Wallets.dat blockchain.db main.go mybtc

优秀的个人博客,低调大师

收藏 | 90后开发者的11套关于服务调用和消息发送的解题思路

阿里中间件性能挑战赛至今为止已经成功举办四届,大赛是由阿里巴巴集团发起,阿里巴巴中间(Aliware)、阿里云天池联合举办,是集团唯一的工程性品牌赛事。大赛的初衷是为热爱技术的年轻人提供一个挑战世界级技术问题的舞台,希望选手在追求性能极致的同时,能深刻体会技术人的匠心精神,用技术为全社会创造更大的价值。 第四届阿里中间件性能挑战赛以“挑战双十一万亿级服务调用”为主题,以开源项目为背景,基于Apache Dubbo 和 Apache RocketMQ这两个开源项目,分别实现一个高性能Service Mesh Agent组件完成服务注册与发现、协议转化、负责均衡的功能,以及实现一个进程内的队列引擎,完成单机可支持100万队列以上的性能。 »»关注 “阿里巴巴中间件” 公众号,回复“挑战赛”,获得Top11解题思路。(持续更新) ** -变化一

资源下载

更多资源
腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册