Java区块链投票系统开发实战分享

文章目录

        • 一、前言
        • 二、背景
        • 三、功能设计
          • 1.设计思路
          • 2.开发思路
        • 四、工程架构
        • 五、技术选型
          • (1)后端技术
          • (2)前端技术
          • (4)开发环境
          • (5)资源下载
          • (6)附:redis windows版本安装流程
        • 六、核心代码
          • 1.区块结构
          • 2.交易模型
          • 3.构建交易过程
          • 4.工作量证明算法判断
          • 5. 计算挖矿的难度
          • 6.区块链总账户(俗称钱包)模型
        • 七、数据库模型
        • 八、界面风格
        • 九、系统架构
        • 十、技术交流
          • 1、作者其他文章
          • 2、技术交流群二维码

Tips: 有需要区块链资料或者讨论技术问题的,可以扫描进QQ群(炒币和发广告的敬请绕道)
Java区块链投票系统开发实战分享_第1张图片

一、前言

基于Java语言以及目前市面上流行的Springboot+Mybatis等框架开发的区块链投票系统,可用于企业集团、政府机构、组织联盟内的投票平台,系统分为三层架构,分别是自研区块链作为底层、投票系统作为业务层、前端操作界面作为视图层,功能包括完整的前后端权限管理模块、投票应用功能和自研区块链系统,扩展性强。系统整体采用springboot框架开发,简单易懂,包含完整的投票功能、权限管理功能、区块链模型、区块链钱包、转账和交易、UTXO模型、数据的加密解密、挖矿、区块数据的数据库持久化,以及基于netty框架的P2P分布式网络。

二、背景

随着社会的进步,经济的快速发展,投票调查、意见采集在社会生活中起了越来越重要的作用。网上投票系统以高效率、节省资源、易传播等特性替代了线下投票活动。但是由于网上投票系统的数据存储在中心化服务器中,存在许多弊端。例如:
(1)用户的投票信息有被泄漏的风险;
(2)选民投票以后,普通的选民无法验证投票结果是否正确;
(3)投票数据、结果可能会被恶意篡改等
而如果投票系统利用区块链技术的去中心化、信息不可篡改、公开透明等特性进行系统建设的话,那么就可以轻松构建一套公平、公正、公开透明的投票系统。
本系统旨在通过利用区块链中的转账交易替代投票过程,以解决现有网上投票系统存在的问题。系统为每个用户和投票选项生成一个区块链账号,并且拥有代表投票凭证的token,投票过程也是投票人员把自己拥有的投票token转移给投票选项的虚拟区块链账号,最后,系统统计各投票选项拥有的token数量,把它记为投票选项所得票数,公示投票结果,同时投票结果保存在区块链网络各个节点中,无法篡改。

三、功能设计

1.设计思路

系统分为三层组织结构,分别是自研区块链作为底层、投票系统作为业务层、前端界面作为视图层,方便系统后台业务灵活调用底层区块链,完成投票功能的实现以及业务逻辑的上链操作。

2.开发思路
  • 首先,基于SpringBoot+Netty实现底层区块链的开发实现,测试环境可以单节点运行,也可以多节点达成共识,保证底层区块链正常运行,一直接收投票应用提交的数据。
  • 其次,按照设计完成数据库的选型与设计,以保证区块链系统以及投票系统的上层业务实现,包括表结构的设计,Dao层的开发。
  • 再次,完成投票系统的代码实现,采用SpringBoot+SpringMVC+Mybatis框架开发实现基于区块链的投票系统的业务功能,包括投票系统应用的RBAC权限模型设计,用户的管理,投票主题的管理,投票选项的管理等

四、工程架构

Java区块链投票系统开发实战分享_第2张图片

五、技术选型

(1)后端技术
技术 名称 官网 备注
springboot springboot框架
Apache Shiro 权限框架
MyBatis Generator 代码生成
PageHelper MyBatis物理分页插件
hikari 数据库连接池
Thymeleaf 模板引擎
Log4J 日志组件
Swagger2 接口测试框架
Maven 项目构建管理
Netty websocket消息通知
kaptcha google验证码
devtools 热部署
GSON 谷歌json
druid 阿里连接池
(2)前端技术
技术 名称 官网 备注
jQuery 函式库
bootstrap 前端页面框架
Font-awesome 字体图标
jquery.validate jquery验证插件
ladda.min.js 按钮加载js
bootstrap-table 表格组件
layer.js 弹窗组件
jquery.blockUI.js 遮蔽层组件
bootstrap-table-export.js 前台导出组件
bootstrap-treeview 树结构组件
bootstrap-colorpicker 颜色组件
dropzone 文件上传
bootstrap-wysihtml5 富文本
bootstrap-switch 开关按钮
(4)开发环境
  • JDK8.0
  • mysql5.7以上
  • eclipse或者Idea
  • redis
(5)资源下载
JDK8:
http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html
Maven:
http://maven.apache.org/download.cgi
Redis:
https://pan.baidu.com/s/1z1_OdNVbtgyEjiktqgB83g  密码:kdfq
(6)附:redis windows版本安装流程
1. 首先先把下载的压缩包解压到一个文件夹中,例如C:\Program Files\Redis-x64-3.2.100
2. 打开cmd指令窗口,进入刚才解压的文件路径
3. 执行命令:redis-server --service-install redis.windows.conf
4. 随后在桌面,右键我的电脑→选择管理→进入服务和应用程序→服务 右键启动服务
5. 切换回cmd窗口,执行redis-cli启动客户端,连接服务进行测试
6. 执行命令:set a 123 然后再执行get a ,这时输出123

六、核心代码

1.区块结构
/**
 * 区块
 *
 * @author Jared [email protected]
 */
public class Block implements Serializable {
     

    /**
     * 区块产生的时间戳
     */
    private long timestamp;
    /**
     * 区块高度
     * 冗余字段,这个值可以由区块链系统推算出来
     */
    private long height;
    /**
     * 上一个区块的哈希
     */
    private String previousBlockHash;
    /**
     * 区块里的交易
     */
    private List<Transaction> transactions;
    /**
     * 默克尔树根
     * 由transactions生成。
     */
    private String merkleTreeRoot;
    /**
     * 随机数
     */
    private String nonce;
    /**
     * 区块哈希:由timestamp、previousBlockHash、nonce、merkleRoot共同作用使用Hash算法生成。
     */
    private String hash;

    /**
     * 挖矿难度
     */
    private String difficulty;

    /**
     * 区块中的交易总笔数
     */
    private long transactionCount;

    /**
     * 区块中第一笔交易的序列号。
     */
    private long startTransactionIndexInBlockchain;

   /**
   * 省略set get方法
   */
}
2.交易模型
/**
 * 交易
 *
 * @author Jared [email protected]
 */
public class Transaction implements Serializable {
     

    /**
     * 交易哈希
     */
    private String transactionHash;
    /**
     * 交易类型
     */
    private TransactionType transactionType;
    /**
     * 交易输入
     */
    private List<TransactionInput> inputs;
    /**
     * 交易输出
     */
    private List<TransactionOutput> outputs;
    /**
     * 交易在区块中的序列号
     */
    private long transactionIndexInBlock;
    /**
     * 交易在区块链中的序列号
     */
    private long transactionIndexInBlockchain;
    /**
     * 交易所在区块的区块高度
     */
    private long blockHeight;
}
3.构建交易过程
public BuildTransactionResponse buildTransactionDTO(List<String> payerPrivateKeyList,String payerChangeAddress,List<Recipient> recipientList) {
     
        //需要转移的token总的数量
        long outputValues = 0;
        if(recipientList != null){
     
            for(Recipient recipient : recipientList){
     
                outputValues += recipient.getValue();
            }
        }
        //创建交易输出
        List<TransactionOutputDTO> transactionOutputDtoList = new ArrayList<>();
        //创建交易输出(不包含找零交易输出)
        List<BuildTransactionResponse.InnerTransactionOutput> innerTransactionOutputList = new ArrayList<>();

        if(recipientList != null){
     
            for(Recipient recipient : recipientList){
     
                TransactionOutputDTO transactionOutputDTO = new TransactionOutputDTO();
                transactionOutputDTO.setValue(recipient.getValue());
                OutputScript outputScript = StackBasedVirtualMachine.createPayToPublicKeyHashOutputScript(recipient.getAddress());
                transactionOutputDTO.setOutputScriptDTO(Model2DtoTool.outputScript2OutputScriptDTO(outputScript));
                transactionOutputDtoList.add(transactionOutputDTO);

                BuildTransactionResponse.InnerTransactionOutput innerTransactionOutput = new BuildTransactionResponse.InnerTransactionOutput();
                innerTransactionOutput.setAddress(recipient.getAddress());
                innerTransactionOutput.setValue(recipient.getValue());
                innerTransactionOutput.setOutputScript(outputScript);
                innerTransactionOutputList.add(innerTransactionOutput);
            }
        }

        //获取足够的token数量
        //交易输入列表
        List<TransactionOutput> inputs = new ArrayList<>();
        List<String> inputPrivateKeyList = new ArrayList<>();
        //交易输入总token数量
        long inputValues = 0;
        long feeValues = 0;
        boolean haveEnoughMoneyToPay = false;
        for(String privateKey : payerPrivateKeyList){
     
            if(haveEnoughMoneyToPay){
     
                break;
            }
            String address = AccountUtil.accountFromPrivateKey(privateKey).getAddress();
            long from = 0;
            long size = 100;
            while (true){
     
                if(haveEnoughMoneyToPay){
     
                    break;
                }
                //查询当前投票方地址下token数量
                List<TransactionOutput> utxoList = blockchainDataBase.queryUnspendTransactionOutputListByAddress(address,from,size);
                if(utxoList == null || utxoList.isEmpty()){
     
                    break;
                }
                for(TransactionOutput transactionOutput:utxoList){
     
                    inputValues += transactionOutput.getValue();
                    //交易输入
                    inputs.add(transactionOutput);
                    inputPrivateKeyList.add(privateKey);
                    if(inputValues >= outputValues){
     
                        haveEnoughMoneyToPay = true;
                        break;
                    }
                }
                from += size;
            }
        }

        if(!haveEnoughMoneyToPay){
     
            BuildTransactionResponse buildTransactionResponse = new BuildTransactionResponse();
            buildTransactionResponse.setBuildTransactionSuccess(false);
            buildTransactionResponse.setMessage("账户没有足够的token进行转移");
            return buildTransactionResponse;
        }

        //构建交易输入
        List<TransactionInputDTO> transactionInputDtoList = new ArrayList<>();
        for(TransactionOutput input:inputs){
     
            UnspendTransactionOutputDTO unspendTransactionOutputDto = Model2DtoTool.transactionOutput2UnspendTransactionOutputDto(input);
            TransactionInputDTO transactionInputDTO = new TransactionInputDTO();
            transactionInputDTO.setUnspendTransactionOutputDTO(unspendTransactionOutputDto);
            transactionInputDtoList.add(transactionInputDTO);
        }

        //构建交易
        TransactionDTO transactionDTO = new TransactionDTO();
        transactionDTO.setTransactionInputDtoList(transactionInputDtoList);
        transactionDTO.setTransactionOutputDtoList(transactionOutputDtoList);

        //找零
        long change = inputValues - outputValues - feeValues;
        BuildTransactionResponse.InnerTransactionOutput payerChange = null;
        if(change > 0){
     
            TransactionOutputDTO transactionOutputDTO = new TransactionOutputDTO();
            transactionOutputDTO.setValue(change);
            OutputScript outputScript = StackBasedVirtualMachine.createPayToPublicKeyHashOutputScript(payerChangeAddress);
            transactionOutputDTO.setOutputScriptDTO(Model2DtoTool.outputScript2OutputScriptDTO(outputScript));
            transactionOutputDtoList.add(transactionOutputDTO);

            payerChange = new BuildTransactionResponse.InnerTransactionOutput();
            payerChange.setAddress(payerChangeAddress);
            payerChange.setValue(change);
            payerChange.setOutputScript(outputScript);
        }

        //签名
        for(int i=0;i<transactionInputDtoList.size();i++){
     
            String privateKey = inputPrivateKeyList.get(i);
            String publicKey = AccountUtil.accountFromPrivateKey(privateKey).getPublicKey();
            TransactionInputDTO transactionInputDTO = transactionInputDtoList.get(i);
            String signature = Model2DtoTool.signature(transactionDTO,privateKey);
            InputScript inputScript = StackBasedVirtualMachine.createPayToPublicKeyHashInputScript(signature, publicKey);
            transactionInputDTO.setInputScriptDTO(Model2DtoTool.inputScript2InputScriptDTO(inputScript));
        }


        BuildTransactionResponse buildTransactionResponse = new BuildTransactionResponse();
        buildTransactionResponse.setBuildTransactionSuccess(true);
        buildTransactionResponse.setMessage("构建交易成功");
        buildTransactionResponse.setTransactionHash(TransactionTool.calculateTransactionHash(transactionDTO));
        buildTransactionResponse.setFee(feeValues);
        buildTransactionResponse.setPayerChange(payerChange);
        buildTransactionResponse.setTransactionInputList(inputs);
        buildTransactionResponse.setTransactionOutputList(innerTransactionOutputList);
        buildTransactionResponse.setTransactionDTO(transactionDTO);
        return buildTransactionResponse;
    }
4.工作量证明算法判断

如果本地电脑算力太差,可以更改算法的逻辑,只要区块的哈希值前四位都为0即认为满足计算的结果:

public boolean isReachConsensus(BlockchainDatabase blockchainDataBase, Block block) {
     
        String difficulty = block.getDifficulty();
        if(StringUtil.isNullOrEmpty(difficulty)){
     
            difficulty = calculateDifficult(blockchainDataBase,block);
            block.setDifficulty(difficulty);
        }

        //区块Hash
        String hash = block.getHash();
        if(hash == null){
     
            hash = BlockTool.calculateBlockHash(block);
        }
        System.out.println("正在挖矿中,区块hash:"+hash);
        return new BigInteger(difficulty,16).compareTo(new BigInteger(hash,16)) > 0;
        //return hash.startsWith("0000");
    }
5. 计算挖矿的难度
public String calculateDifficult(BlockchainDatabase blockchainDataBase, Block block) {
     
        long intervalBlockCount = GlobalSetting.MinerConstant.INTERVAL_TIME / GlobalSetting.MinerConstant.BLOCK_TIME;

        String targetDifficult;
        long blockHeight = block.getHeight();
        if(blockHeight == 1){
     
            targetDifficult = GlobalSetting.GenesisBlock.DIFFICULTY;
            return targetDifficult;
        }
        Block lastBlock = blockchainDataBase.queryBlockByBlockHeight(blockHeight-1);
        long lastBlockHeight = lastBlock.getHeight();
        if (lastBlockHeight % intervalBlockCount != 0){
     
            targetDifficult = lastBlock.getDifficulty();
            return targetDifficult;
        }
        //此时,最后一个区块是上一个周期的最后一个区块。
        Block intervalLastBlock = lastBlock;
        Block intervalFirstBlock = blockchainDataBase.queryBlockByBlockHeight(lastBlockHeight-intervalBlockCount+1);
        long actualTimespan = intervalLastBlock.getTimestamp() - intervalFirstBlock.getTimestamp();
        if (actualTimespan < GlobalSetting.MinerConstant.INTERVAL_TIME /4){
     
            actualTimespan = GlobalSetting.MinerConstant.INTERVAL_TIME /4;
        }
        if (actualTimespan > GlobalSetting.MinerConstant.INTERVAL_TIME *4){
     
            actualTimespan = GlobalSetting.MinerConstant.INTERVAL_TIME *4;
        }
        BigInteger bigIntegerTargetDifficult = new BigInteger(intervalLastBlock.getDifficulty(),16).multiply(new BigInteger(String.valueOf(actualTimespan))).divide(new BigInteger(String.valueOf(GlobalSetting.MinerConstant.INTERVAL_TIME)));
        return bigIntegerTargetDifficult.toString(16);
    }
6.区块链总账户(俗称钱包)模型

用来管理区块链平台拥有的区块链账号,并进行增加账号、删除账号、查询账号等操作:

public abstract class Wallet {
     

    public abstract List<Account> queryAllAccount();

    public abstract Account createAccount();

    public abstract void addAccount(Account account);

    public abstract void deleteAccountByAddress(String address);
}

七、数据库模型

Java区块链投票系统开发实战分享_第3张图片

八、界面风格

Java区块链投票系统开发实战分享_第4张图片
Java区块链投票系统开发实战分享_第5张图片

Java区块链投票系统开发实战分享_第6张图片

九、系统架构

Java区块链投票系统开发实战分享_第7张图片

十、技术交流

1、作者其他文章

(1)Java区块链技术开发实战入门

2、技术交流群二维码

欢迎区块链技术爱好者一起沟通交流,Java区块链技术交流03群已满了,加04群吧(需要源码的可以进群私信群主):
Java区块链投票系统开发实战分享_第8张图片

你可能感兴趣的:(区块链技术,区块链,java,投票系统,Springboot)