拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于Java+Vue的区块链电子投票防篡改系统实现

基于Java+Vue的区块链电子投票防篡改系统实现

简介:这套基于Java与Vue的区块链电子投票防篡改系统设计实例,面向具备一定Java和Vue基础的软件工程师、全栈开发者及区块链技术爱好者,用于解决传统电子投票中心化存储、数据易篡改、过程不透明等问题。资源压缩包共1个docx文档(约90KB),文档内含完整项目设计说明、系统架构图、数据库设计、前后端核心代码详解、智能合约逻辑及部署方案,可作为二次开发模板。已有80人学习/下载,适合作为课程设计、毕业设计或企业内训参考。内容节选涵盖区块与区块链类结构、SHA-256加密工具、投票交易打包、智能合约判重与自动计票、Java后端身份认证流程以及Vue前端投票组件等关键实现,能帮助读者系统掌握区块链与主流Web技术栈融合的防篡改系统设计思路与落地方法。

1. 为什么电子投票必须上链:一张被改的选票只要 5 分钟就能毁掉整场公投

一场班级表决,管理员登录后台把票数改成自己想要的数字,再清掉操作日志——从数据库层面看,这几乎无法被发现。传统电子投票系统的核心弱点不是界面丑,而是数据和日志可以同时被改。区块链提供的不是“去中心化”这个口号,而是一条无法被静默改写的审计链:每个区块的哈希都压着前一个区块,改动任何一张选票,从那一块开始到最新块全部校验失败。下面拆解一套基于 Java + Vue 的电子投票防篡改系统:Java 写链端逻辑和业务接口,Vue 做投票、管理与验票的 GUI,MySQL 只当业务查询缓存。完整程序、数据库脚本和代码详解会按模块给出可直接复现的落地方案。适合正在做区块链项目、毕设或课设的开发者,也适合想把投票业务做成可审计系统的从业者。

2. Java + Vue 的电子投票系统架构:后端、前端和链上数据是怎么咬合的

2.1 为什么用 Java 做链端、Vue 做 GUI:先想清楚链是谁的

很多人在区块链项目里一上来就纠结“要不要真正去中心化”“要不要多节点共识”,放到电子投票场景里,这个方向其实想偏了。投票系统要解决的核心矛盾是:数据库管理员能改票,日志也能被清掉。区块链在这里承担的角色不是去中心化,而是“不可抵赖的审计账本”。想清楚这一点,技术选型就简单了:链端用 Java 写,业务接口用 Spring Boot 包一层,界面用 Vue。

用 Java 写链端,最现实的理由是 Java 自带完整的密码学库 JCA(Java Cryptography Architecture)。SHA-256、ECDSA 签名、RSA 验签都是原生 API,不需要额外引入 OpenSSL 的复杂封装,出块和验签的逻辑可以写得很干净。另外,Spring Boot + MyBatis 是 Java 服务端最常见的组合,投票业务里的用户管理、候选人管理、投票记录查询,直接复用这套成熟体系,比用 Node.js 写链再跨语言调 Java 业务接口省一大截工作量。

Vue 这边的理由更直接:投票页面的状态多——未投票、投票中、已投、校验中、已出块,Vue 的响应式状态和组件化开发在这种场景下非常顺手。而且 vue 安装及环境配置、路由拆分、Axios 请求封装都有大量现成资料,新手也能快速把 GUI 搭起来。

如果去面 java 开发工程师,这道题也常被拿来问:链上校验和数据库校验谁说了算。我的答案是,链上说了算,数据库只做缓存。整条链路里,Java 链端是唯一的“事实来源”,Vue 界面展示的所有票数、哈希、区块高度,最终都要回到链上验证,而不是信 MySQL 里的汇总数字。

2.2 三条核心数据流:投票、验票、审计分别走哪条路

系统里真正需要跑通的数据流只有三条,先把它们画在脑子里,再动手写代码就不容易乱。

投票流是主链路:选民在 Vue 页面选择候选人,点击提交后,Axios 把请求发到 Spring Boot 的/api/vote接口。后端先做身份鉴权和重复投票检查,通过后组装一条Transaction,放进交易池。出块线程从交易池里批量取交易、计算 Merkle 根(简化实现也可以直接拼字符串)、做 PoW 出块,然后把区块挂到链上。链端确认后,才把投票记录写进 MySQL 的vote_record表,最后把交易哈希作为回执返回给前端。注意这个顺序不能反:先落库再上链的话,一旦出块失败,数据库里就会出现一条“幽灵选票”。

验票流是审计链路:Vue 的验票页面输入候选人编号或投票回执哈希,后端就从创世区块开始,把整条链上的所有交易重放一遍,重新统计每个候选人的票数,再拿这个结果和 MySQL 里的汇总做对比。两边一致,GUI 显示绿色通过;不一致,说明 MySQL 被改过或者链本身出了问题。

审计流面向管理员:管理看板按区块高度拉取列表,展示每个区块的出块时间、前一区块哈希、当前区块哈希、包含的交易数。管理员不需要看密码学细节,只需要能直观看到“链是连续的,哈希是连着的”,这就够了。

数据流入口关键处理落库
投票Vue 投票页鉴权、组装交易、PoW 出块vote_record
验票Vue 验票页链上重放、与 MySQL 对比无
审计Vue 管理看板按高度拉链、展示哈希关系block_info

2.3 模块划分与工程骨架:chain 包独立于 Spring,接口层只做转发

模块划分上,我强烈建议把链端核心逻辑做成一个不依赖 Spring 的普通 Java 包。这样做的直接好处是,写单元测试时不用启动整个 Web 容器,几秒钟就能跑一遍全链校验;以后想把这个链移植到别的项目,直接复制chain包过去就行。

vote-chain/ ├── backend/ │ ├── src/main/java/com/vote/ │ │ ├── chain/ // 区块、交易、PoW、链校验,纯 Java │ │ ├── controller/ // REST 接口,只做参数接收和转发 │ │ ├── service/ // 投票业务、对账任务、验票服务 │ │ ├── mapper/ // MyBatis 数据访问 │ │ └── config/ // 跨域、拦截器、JWT 配置 │ └── src/main/resources/ │ ├── application.yml │ └── db/init.sql └── frontend/ ├── src/ │ ├── router/ // vue-router 路由与守卫 │ ├── views/ // 投票、管理、验票页面 │ └── api/ // axios 实例与接口封装 └── package.json

后端我一般用 Spring Boot 2.7.x 稳定线,搭配 MyBatis Plus;前端 Vite + Vue 3。核心依赖集中在pom.xml里:

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.3</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency>

依赖版本不需要追新,MyBatis Plus 3.5.x 足够稳。REST 接口层只做三件事:收参数、调 service、返回统一结构,不要在 Controller 里写任何链逻辑。接口清单也固定一下,方便前后端并行开发:

接口方法入参出参
/api/votePOSTvoterId, candidateIdvoteHash
/api/verify/tallyGETcandidateId链上票数、MySQL票数、是否一致
/api/chain/blocksGETpage, size区块列表
/api/vote/statusGETvoterId是否已投、回执哈希

提示:chain包内部不要出现@Autowired这类注解,所有依赖通过构造方法传入。这样链逻辑可以在没有 Spring 容器的环境下独立跑,也方便以后加多节点同步。

3. 用 Java 实现最小投票链:区块结构、PoW 难度与逐块校验

3.1 交易与区块实体:为什么存 voteHash 而不是明文

链上的最小数据单元是交易(Transaction)。在投票场景里,一条交易就是“某个选民投给了某个候选人”。但要注意,区块链本身是公开可读的,如果直接把voterId和candidateId明文写进交易,任何人拿到链数据就能知道每个人投了谁,这违背了匿名投票原则。

常见做法是只存voteHash,也就是把“选民 + 候选人 + 时间戳”拼成一个字符串,做一次 SHA-256。验证时不可能从哈希反推出投给了谁,但可以通过重放交易、重新计算哈希来证明某条交易确实存在且未被修改。这个设计叫做“可验证但不可读”,是电子投票系统里非常关键的隐私边界。

public class Transaction { private String txId; // 交易ID,UUID 生成,防重放 private String voterId; // 选民ID,仅用于去重和审计 private String candidateId; // 候选人ID private long timestamp; // 毫秒时间戳 private String voteHash; // SHA-256(voterId + candidateId + timestamp) private String signature; // 选民私钥签名,防止伪造交易 public Transaction(String voterId, String candidateId) { this.txId = UUID.randomUUID().toString(); this.voterId = voterId; this.candidateId = candidateId; this.timestamp = System.currentTimeMillis(); this.voteHash = Sha256.hash(voterId + candidateId + timestamp); } // getter / setter }

这里的签名不是可选项。voteHash 只能防“修改后被发现”,不能防“伪造一条新交易”。如果攻击者知道某个voterId还没投票,他可以直接构造一条交易投给自己。加了signature之后,交易进入交易池前必须先验签,没有选民私钥就无法伪造合法交易。Java 里用Signature.getInstance("SHA256withECDSA")就行,私钥由后端在选民注册时生成并安全保存。小心timestamp的精度,全系统统一用毫秒,别在某个地方又转成秒,否则前后端计算哈希时结果不一样,验票必挂。

区块实体的字段比交易更少:索引、时间戳、交易列表、前一区块哈希、当前区块哈希、随机数 nonce。这里有一个非常容易翻车的细节:calculateHash()里拼接字符串的顺序必须固定。如果把transactions.toString()换成 JSON 序列化,不同 JDK 版本、不同类库对 Map 的排序可能不一样,重算出来的哈希就变了。

public class Block { private int index; private long timestamp; private List<Transaction> transactions; private String previousHash; private String hash; private int nonce; public String calculateHash() { String data = index + previousHash + timestamp + transactions.toString() + nonce; return Sha256.hash(data); } }

3.2 简化 PoW:difficulty 设多少才不会被答辩老师嫌弃

工作量证明在投票系统里的作用不是抢记账权,而是“增加篡改成本”。如果出块不需要算哈希,攻击者改掉一个区块后重新计算后续所有区块的哈希,几毫秒就能完成。加上 PoW 后,改一个区块必须重新计算从该区块到链尾所有区块的 nonce,难度越高,篡改成本越大。

public String mineBlock(int difficulty) { String target = "0".repeat(difficulty); this.nonce = 0; do { this.nonce++; this.hash = calculateHash(); } while (!hash.startsWith(target)); return hash; }

这个循环就是最简 PoW:不断改 nonce,直到区块哈希的前 N 位是 0。difficulty就是target字符串的长度,每增加 1,出块耗时大约翻 16 倍。我在普通笔记本上实测,难度 3 基本瞬出,难度 4 大约 0.1~1 秒,难度 5 约 2~5 秒,难度 6 能拉到 30 秒以上。

给投票系统调参数,别按比特币的标准来。这里没有矿工竞争,只需要达到“演示时有感知、篡改时很痛苦”的程度。我一般设 difficulty = 4,出块打包 100 笔交易。这样一次百人选票的投票活动,链上出块不会卡界面,而攻击者想伪造一个区块,至少要跑几十分钟的哈希。如果是为了答辩演示“防篡改”,把难度临时调到 5 就足够震撼了。

3.3 逐块校验与篡改定位:从创世块重放所有交易

链端的核心方法就一个:从创世块开始,逐块检查哈希连续性和 PoW 合法性。这个方法必须在每次验票、每次对账、每次系统启动时都执行一遍。

public boolean isValidChain(List<Block> chain, int difficulty) { if (chain.isEmpty()) return false; for (int i = 1; i < chain.size(); i++) { Block prev = chain.get(i - 1); Block cur = chain.get(i); // 前一区块哈希必须等于当前区块记录的 previousHash if (!cur.getPreviousHash().equals(prev.getHash())) return false; // 当前区块重算哈希必须等于区块里存的 hash if (!cur.getHash().equals(cur.calculateHash())) return false; // PoW 难度校验 String target = "0".repeat(difficulty); if (!cur.getHash().startsWith(target)) return false; // 同一选民只能投一次 Set<String> voters = new HashSet<>(); for (Transaction tx : cur.getTransactions()) { if (!voters.add(tx.getVoterId())) return false; } } return true; }

当校验失败时,不要只返回 false,最好返回第一个失败的区块索引。这个索引直接告诉排查人员:从这一块开始,后面所有数据都不可信了。定位到具体位置后,可以用第 6 章的模拟篡改接口,故意改掉某一块的交易内容,跑一遍校验,就能直观看到从第 N 块开始“全红”的效果——这是防篡改系统最有力的演示方式。

注意:transactions.toString()在不同 JDK 下可能不稳定,更稳妥的做法是自定义一个规范化序列化方法,例如固定按txId|voterId|candidateId|timestamp|voteHash拼接。排序稳定,哈希才会稳定。

4. Vue 前端与 GUI 落地:选民操作台、管理看板与验票页面

4.1 Vue 初始化与路由:vue-router 的 hash 模式和动态路由

前端的 vue 安装及环境配置从 Vite 脚手架开始。Node 建议 18+,创建项目后只需要装两个核心依赖:vue-router和axios。如果要用图表展示票数趋势,可以再加 echarts,但它不是核心依赖,后面再补也不迟。

npm create vite@latest vote-front -- --template vue cd vote-front npm install vue-router axios

路由设计上,系统只需要四个页面:登录、投票、管理看板、验票。其中投票和管理员页面需要登录,管理员页面还要校验角色。路由表的写法很直接:

// src/router/index.js import { createRouter, createWebHashHistory } from 'vue-router' const routes = [ { path: '/login', component: () => import('../views/LoginView.vue') }, { path: '/vote', component: () => import('../views/VoteView.vue'), meta: { requiresAuth: true } }, { path: '/admin', component: () => import('../views/AdminView.vue'), meta: { requiresAuth: true, role: 'admin' } }, { path: '/verify', component: () => import('../views/VerifyView.vue') } ] const router = createRouter({ history: createWebHashHistory(), routes }) // 全局前置守卫:未登录跳登录页 router.beforeEach((to) => { const token = localStorage.getItem('token') if (to.meta.requiresAuth && !token) { return { path: '/login' } } if (to.meta.role && localStorage.getItem('role') !== 'admin') { return { path: '/vote' } } }) export default router

这里的模式选的是createWebHashHistory()而不是createWebHistory()。区别很现实:hash 模式下 URL 里带着#,刷新页面时不需要后端做任何重定向配置;history 模式虽然好看,但部署到静态服务器时,刷新/admin会直接 404,必须给 Nginx 配 try_files。对课设、毕设这种演示场景,hash 模式是最省心的。

vue 动态路由在投票系统里有一个很好的用途:管理员登录后,再动态注册一个“候选人管理”路由;普通选民登录后永远看不到这个入口。这样路由配置就不需要暴露所有功能,前端权限观感会更干净。实现方式就是把管理员专属路由从routes数组挪到登录成功后的router.addRoute()里。

4.2 Axios 对接投票接口:超时、clientNonce 与防重复

Axios 封装的核心不是把接口路径写短一点,而是统一处理超时和错误码。投票接口的特殊性在于:它背后要跑一次 PoW,出块时间可能达到几百毫秒甚至几秒。如果把默认 timeout 设成 5 秒,出块稍微慢一点,前端就会先报超时,但后端其实已经投成功——用户一看报错,再点一次,就投了两票。

// src/api/vote.js import axios from 'axios' const http = axios.create({ baseURL: '/api', timeout: 20000 }) export function submitVote(data) { return http.post('/vote', { ...data, clientNonce: Date.now() + '-' + Math.random() }) }

clientNonce是前端生成的随机串,每一次点击提交都会生成一个新的。后端拿到这个值后,在缓存里存一份,重复收到相同 nonce 的请求直接拒绝。这是防重复投票的第二道闸,即使前端绕过按钮 loading 直接发请求,也过不了这一关。

// VoteView.vue 关键片段 const loading = ref(false) const result = ref('') async function onConfirm() { if (loading.value) return // 防止连点 loading.value = true try { const res = await submitVote({ voterId: currentUser.voterId, candidateId: selectedCandidate }) result.value = res.data.voteHash // 显示回执哈希 } catch (e) { alert('提交失败:' + e.message) } finally { loading.value = false } }

按钮上的loading只是用户体验层面的防连点,真正的兜底是clientNonce加服务端去重。两件事别混在一起:前者是防手滑,后者才是防攻击。

4.3 GUI 落地:投票回执、管理看板和验票页面的状态设计

GUI 设计上,投票页最重要的是“状态可感知”。选民点击提交后,如果链端正在做 PoW,按钮必须转圈并显示“出块中”,否则用户会以为卡死了。出块完成后,页面显示一个由 64 位十六进制字符组成的voteHash回执,同时提示“请保存此哈希,用于后续验票”。这比直接显示“投票成功”要专业得多——用户真正拿到的是可验证的证据。

管理看板的 GUI 核心是两个数字:链高度和最新区块哈希。链高度让管理员一眼看到出块进度,最新区块哈希则是一个“看起来随机但每块都变”的字符串,直观传达“数据在上链”。区块列表用表格展示即可:高度、出块时间、交易数、哈希前 8 位。不要试图在 GUI 里展示完整哈希,字太长,没有实际阅读价值。

验票页面的交互最简单也最关键:输入候选人编号,后端返回链上统计票数、MySQL 统计票数、两者是否一致。验证通过显示绿灯,不一致显示红灯并标出第一个不一致的区块高度。

// vite.config.js export default { server: { proxy: { '/api': 'http://localhost:8080' } } }

本地联调时,Vite 开发服务器的代理配置解决跨域问题。生产环境部署时,同样把/api反向代理到 Java 后端的 8080 端口。前端代码里不需要硬编码后端地址,统一走相对路径/api,这样换环境只改代理配置,不动业务代码。

5. 数据落库的边界与排查:MySQL 的职责、双写一致性、三个高频故障

5.1 MySQL 表设计与职责划分:链上做证据,库里做缓存

MySQL 在这套系统里扮演的角色极其明确:它是缓存和查询层,不是证据。原因很简单,MySQL 可以被UPDATE直接改掉,所以它永远不能成为防篡改的依据。但业务查询、后台管理、统计展示不能每次都重放整条链,那样太慢,所以需要把链上的摘要同步到库里。

表设计围绕这个原则展开,核心是vote_record表。这张表里存的是“链上交易的业务视图”,每一条记录对应链上的一个交易,字段里有区块高度和交易哈希,可以根据这两列把记录定位到链上具体位置。

CREATE TABLE vote_record ( id BIGINT AUTO_INCREMENT PRIMARY KEY, voter_id VARCHAR(64) NOT NULL, candidate_id VARCHAR(64) NOT NULL, vote_hash VARCHAR(64) NOT NULL, tx_id VARCHAR(64) NOT NULL, block_height INT NOT NULL, status VARCHAR(16) DEFAULT 'PENDING', create_time DATETIME NOT NULL, UNIQUE KEY uk_voter (voter_id), UNIQUE KEY uk_tx (tx_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

两个唯一索引都是有讲究的。uk_voter保证同一个选民在库里只有一条投票记录,这是数据库层面的兜底;uk_tx保证交易 ID 不重复,防止同一笔链上交易被同步两次。投票系统的数据量通常不大,不需要分区,把索引做好就够了。

设计表结构时还要想到 mysql 数据库修改结构常见的问题:后期加字段时,如果表里已经有几十万行,ALTER TABLE会锁表。所以一开始就把status、block_height这些后期对账要用的字段全部建好,宁多勿少。

block_info表和audit_log表可以简单一点。前者存每个区块的摘要信息:高度、哈希、前一哈希、出块时间、交易数,用于管理看板展示;后者存所有对账和恢复操作的操作记录,方便追责。

5.2 双写一致性与对账任务:先上链、再落库、定期回放

投票系统最大的工程坑在双写一致性。链端和 MySQL 是两个独立的存储,不可能用数据库事务包住区块链的出块操作。正确顺序是:先上链拿到block_height和tx_id,再写vote_record,写入时status先标记为PENDING,等待对账任务确认。

对账任务的逻辑就是重放。链端把整条链的交易重新统计一遍,得到voterId -> candidateId的映射,然后和 MySQL 里的vote_record逐条对比。数据库有但链上没有的,标记为INVALID;链上有但数据库没有的,补插一条记录;两边都有但candidateId不一致的,以链上为准修正数据库。

// 对账任务:每 5 分钟执行一次 public void reconcile() { List<Block> chain = chainService.getChain(); Map<String, String> chainVotes = replayService.replay(chain); List<VoteRecord> records = voteRecordMapper.selectList(null); for (VoteRecord r : records) { String chainCandidate = chainVotes.get(r.getVoterId()); if (chainCandidate == null) { r.setStatus("INVALID"); } else if (!chainCandidate.equals(r.getCandidateId())) { r.setCandidateId(chainCandidate); r.setStatus("SYNCED"); auditLogMapper.insert("修正投票记录", r.getId()); } else { r.setStatus("CONFIRMED"); } voteRecordMapper.updateById(r); } }

对账任务不能和正常投票并发跑太频繁,否则数据库会有大量写操作。我的习惯是投票活动期间每 5 分钟跑一次,活动结束后跑一次全量对账,然后锁定数据。对账脚本的每次修正操作都必须写audit_log,这是整个系统里唯一记录“谁改了什么”的地方,自己改自己的时候也要留痕。

5.3 三个高频故障与修复:哈希全红、重复投票、出块超时

第一坑:验票页面哈希全红。现象是 GUI 上每个区块的校验结果都显示失败,连创世块都过不去。原因大多是时间戳单位不统一,或者哈希计算时拼接字符串的顺序发生了变化。系统里有的地方用毫秒,有的地方转成了秒,重算出来的哈希就和存进去的对不上。这是血泪经验换来的教训。解决方法是定一个统一的规范化规则:时间戳全用毫秒,序列化全用固定分隔符拼接,不允许直接依赖toString()或 JSON 类库的默认顺序。

第二坑:同一选民投两票成功。现象是数据库里出现两条相同voter_id的记录,链上也有两笔交易。原因通常是把防重复完全押在了前端按钮的loading上,没有做服务端去重。绕过前端直接调接口就能投两次。解决方法是三层兜底:数据库唯一索引、链上交易voterId去重、clientNonce缓存。任何一层单独都能拦住大部分攻击,三层都在才能拦住绕过前端的直接调用。

第三坑:投票后一直转圈,最后报超时。现象是前端 Axios 报 timeout,但库里其实已经写入了投票记录。原因是出块耗时超过了前端的超时限制,用户以为没投上,再点一次就变成了重复投票。解决方法是把 Axios 的 timeout 调到 20 秒,同时在出块逻辑里做批量打包:交易池攒够 100 笔或者每 2 秒出一次块,避免每次投票都现场跑一遍 PoW。后端可以加一个异步出块线程,投票接口先把交易放进池子、返回“待确认”状态,等出块完成后再通过短轮询或 WebSocket 通知前端更新状态。

6. 部署后的验证技巧:模拟篡改、一键验票与难度参数的调优顺序

部署完成后,真正能说服评审或客户的不是代码量,而是一套看得见的验证方法。我最常演示的是模拟篡改接口:只对 dev 环境开放,把链上某一个已出块交易的候选人改成HACKED,然后重新计算该区块的哈希并调isValidChain。这个接口在正式环境一定要关闭,否则等于主动留了一个后门。

// 仅 dev 环境开启:篡改指定区块,验证防篡改能力 @PostMapping("/dev/tamper/{height}") public Result tamper(@PathVariable int height) { Block block = chainService.getChain().get(height); block.getTransactions().get(0).setCandidateId("HACKED"); block.setHash(block.calculateHash()); // 只重算当前块 boolean valid = chainService.isValidChain(chainService.getChain(), 4); return valid ? Result.fail("篡改未被发现") : Result.success("篡改已拦截"); }

这个接口的演示效果非常直观:只改了一个字段、只重算了一个区块,但校验从被篡改的区块开始一路红到链尾,因为后面每个区块的previousHash都对不上了。真正攻击者面对的难度比这大得多,他要把后面所有区块的 PoW 全部重算,难度调成 4 时,这就是几分钟到几十分钟的算力成本。

一键验票功能也建议做成独立页面。输入候选人编号后,后端同时返回两组数据:链上重放统计的票数、MySQL 汇总的票数。两组数字一致时 GUI 显示绿灯。验收时可以按这个清单走一遍:创世块校验通过、投票后回执哈希能在链上查到、后端重放票数与 MySQL 一致、模拟篡改后校验失败、重启服务后链数据不丢。

难度参数的调优顺序我踩过坑。第一次演示时把难度设成 6,现场一笔选票算了 40 多秒,非常尴尬。后来把难度改回 4,并加了“出块中”的进度提示,体验才正常。参数调优的顺序应该是:先把难度固定为 4,测出单块打包 100 笔交易的出块耗时;再观察交易量翻倍后的耗时变化,决定是否调整批量大小;最后才考虑要不要把难度提到 5。别一上来就追求高难度,投票系统的目标是防篡改在演示中可见,不是模拟比特币的算力竞赛。希望帮到你。

本文还有配套的精品资源,点击获取

返回列表