资讯动态

SpringBoot+以太坊的项目评审系统:智能合约存证开发实战

发布时间:2026/10/18 12:06:06 来源:尧图企业网站定制
简介一份面向计算机相关专业软件工程、计科、区块链、人工智能等学生的基于SpringBoot与以太坊的项目评审系统毕业设计资源包覆盖课题设计、服务端开发、智能合约编写与部署交付全流程既适合作为毕业设计、课程设计或实训项目的完整参考也方便入门者按文档逐步复现。压缩包共208个文件、约771KB内含75个GIF过程演示、51个Java类、23个JS脚本、22个FTL模板与9个CSS样式并辅以以太坊智能合约.sol与编译产物、SQL初始化脚本和部署文档便于快速还原项目环境、理清前后端与合约交互。当前已有110人学习下载属于小范围流通的高分毕业设计资源。项目源码经过完整功能测试答辩评审得分95分结构清晰、扩展性强既可直接用于课题交付也可按需做二次开发配套的过程演示、数据脚本与部署说明能显著降低环境搭建与排错成本帮助读者高效完成项目。1. 为什么项目评审系统要碰以太坊毕设选题值不值得做做毕设选“基于SpringBoot以太坊的项目评审系统”这个题目很多人其实是冲着区块链三个字来的。框架学习和页面开发都不难难的是把以太坊真正接进业务里让答辩的时候有东西可讲、有数据可验证。这套系统把评审业务搬上链项目的申报、专家打分、最终评审结论都会生成一条存证交易保证数据一旦落定不可篡改。它解决的问题不是“做一个系统”而是“证明评审过程可信”。适合三类人想用最少成本把区块链写进毕业设计的人、想给公司内部评审流程加审计能力的开发者、以及需要在简历里同时展示SpringBoot后端和智能合约两项技能的人。我帮你把这条技术路线拆到底。从架构选型、智能合约编写、SpringBoot集成到部署顺序和常见问题全部按可复现的标准写清楚。你照着做完手里会有一套能跑、能演示、能回答追问的完整作品。2. 分层架构与数据模型链上存证、链下业务怎么配合2.1 为什么让SpringBoot管业务、让以太坊管存证项目评审系统如果所有数据都上链会非常难用。区块链的每一次写入都要消耗Gas测试网虽然免费但网络确认有延迟区块浏览器的查询速度也远不如关系型数据库。常见的正确做法是“链下业务、链上存证”SpringBoot负责管理项目信息、专家名单、评审打分这些日常操作数据落在MySQL里等到一条评审记录被最终确认系统把这条记录的摘要哈希写到以太坊上拿到交易哈希和区块号。这样的分层有几个实际好处。第一业务功能开发和普通Java Web项目没有区别学习成本低。第二上链的数据量很小一辈子也就几千条Gas费用在测试网可以忽略。第三答辩时你可以清楚地说出“链上存的是指纹链下存的是原文件”这句话本身就展示了设计思考。链上存证的核心逻辑是原数据放在MySQL把原数据的SHA-256摘要写进合约然后用合约里的摘要和MySQL里的摘要做比对两边一致就说明没有被篡改。2.2 评审上链的系统模块划分与核心表结构系统按角色划分的话典型模块有四个项目管理、专家管理、评审打分、存证查询。项目管理负责申报项目的基本资料维护专家管理负责维护评审专家名单和分配关系评审打分是各类评审系统最复杂的部分要支持打分项、权重、总分计算存证查询是区块链部分最直观的功能输入一个项目编号或者一次评审的ID就能看到链上的交易信息。表结构设计时我给了一张“评审记录”表做主表另外两张基础表是项目和专家。评审记录表里的关键字段包括评审编号、项目ID、专家ID、各项得分、总分、评审时间以及两个区块链相关字段交易哈希和区块高度。CREATE TABLE review_record ( id BIGINT PRIMARY KEY AUTO_INCREMENT, project_id BIGINT NOT NULL, expert_id BIGINT NOT NULL, score_tech INT, score_demo INT, score_innovation INT, total_score DECIMAL(6,2), data_hash VARCHAR(64) NOT NULL COMMENT 评审明细的SHA-256摘要, tx_hash VARCHAR(66) COMMENT 上链后的交易哈希, block_number BIGINT COMMENT 上链后的区块高度, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_review (tx_hash), UNIQUE KEY uk_project_expert (project_id, expert_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;字段的先后顺序是有讲究的。data_hash必须先算好才去上链所以它在tx_hash之前出现。uk_review是交易哈希的唯一约束防止同一条交易被重复接进库里。uk_project_expert是业务上的唯一约束同一个专家对同一个项目只能有一次评审记录这是后面做幂等控制的基础。2.3 接入以太坊的两种方式本地模拟链与测试网开发阶段我强烈建议先用本地模拟链。本地模拟链是跑在自己机器上的以太坊开发环境启动速度快出块间隔可以调短到秒级挖矿节点不会无缘无故断连。配合SpringBoot调试时send()操作几乎瞬间返回不用等真实的网络确认这对反复改代码的人非常重要。等本地跑通了再切换到测试网。测试网的切换成本很低只需要改一个RPC端点和账户私钥。这里有个常见误区有些人一上来就接测试网结果网络本身不稳定一直超时最后分不清是代码问题还是网络问题。我的习惯是“先本地把业务逻辑验完再上测试网验证一次部署链路”两步分开排错。测试网上的地址和本地模拟链的地址格式完全一样代码里不需要有分支判断唯一要改的是配置项。2.4 部署文档里必须写清楚的四个部分标题里强调“部署文档”这一点值得多说几句。一份合格的部署文档至少要回答四件事网络环境需要什么、依赖服务怎么启动、智能合约怎么部署、SpringBoot应用怎么配置。我见过不少项目资料齐全但部署文档写得像流水账把启动命令抄一遍就算完事。真正的部署文档应该让人在断网状态下照着你的步骤一步步操作也能把系统跑起来。网络环境部分要写明节点服务地址、端口开放情况依赖服务部分写清楚MySQL的初始化脚本和默认账号密码智能合约部分写清楚编译命令、部署命令、部署后的合约地址该粘贴到哪里SpringBoot部分写清楚配置文件里的每一项作用。这四部分我会在后文逐步展开每一处都会给你现成的命令和检查点。3. 智能合约开发与部署从Solidity代码到链上地址3.1 合约目录结构与Solidity版本选择以太坊智能合约的官方语言是Solidity你的SpringBoot工程里可以开一个contracts目录专门放合约文件。我常用的目录结构是这样的src/main/resources/contracts/ ├── ProjectReviewStorage.sol └── README.mdSolidity版本方面建议用0.8.x这个区间。0.8版本自带整数溢出检查和require的常用写法对初学者来说能少踩很多坑。如果你在本地装了编译工具把版本号写进pragma里就行。// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract ProjectReviewStorage { address public immutable owner; address public manager; mapping(uint256 ReviewRecord) private records; mapping(uint256 bool) private exists; struct ReviewRecord { string dataHash; address operator; uint256 timestamp; uint256 blockNumber; } event ReviewStored(uint256 indexed reviewId, string dataHash, address indexed operator, uint256 blockNumber); constructor(address _manager) { owner msg.sender; manager _manager; } function setManager(address _newManager) external { require(msg.sender owner, only owner can set manager); manager _newManager; } function storeReview(uint256 _reviewId, string calldata _dataHash) external returns (uint256 blockNumber) { require(msg.sender manager, only manager can store review); require(!exists[_reviewId], review already stored); require(bytes(_dataHash).length 64, dataHash must be 64 hex chars); ReviewRecord storage r records[_reviewId]; r.dataHash _dataHash; r.operator msg.sender; r.timestamp block.timestamp; r.blockNumber block.number; exists[_reviewId] true; emit ReviewStored(_reviewId, _dataHash, msg.sender, block.number); return block.number; } function getStoredHash(uint256 _reviewId) external view returns (string memory) { require(exists[_reviewId], review not exists); return records[_reviewId].dataHash; } function isStored(uint256 _reviewId) external view returns (bool) { return exists[_reviewId]; } }这段合约的核心逻辑并不复杂。storeReview要求调用者必须是manager地址这是权限控制exists这个映射用来防止同一条评审记录重复上链dataHash要求是64位十六进制字符串对应一个SHA-256摘要。getStoredHash是查询接口SpringBoot后端拿到reviewId之后可以通过它把链上的哈希取回来比对。3.2 部署合约前的准备私钥、Gas参数与编译产物Solidity合约不能直接交给SpringBoot运行必须经过编译成字节码再部署到区块链网络上拿到一个合约地址。整个过程分三步编译、部署、记录地址。很多人在这里翻车把编译和部署混在一起出错后不知道是编译器问题还是网络问题。我一般会分开执行先在命令行编译成功再写部署工具。如果你用的是Maven或Gradle管理SpringBoot工程可以在build.gradle里添加一个Java类作为部署入口。部署的时候需要三个关键参数节点RPC地址、部署者私钥、manager地址。RPC地址在本地模拟链启动时会显示测试网的RPC地址由节点服务商提供。私钥必须妥善保存它对应的是你的区块链账户泄露等于资金和合约管理权都交出去了。一个常见的设计问题是谁来做manager毕设场景里Manager可以让某个固定地址来担任也可以设计成让系统管理员修改。实际项目里manager通常是后端服务使用的“服务账户”后端持有这个账户的私钥用它来调用storeReview。这样前端用户不需要直接和区块链交互业务逻辑也保持在后端。3.3 用Java工具类部署合约最直接的启动方式我这里提供一段核心的Java部署代码逻辑是连接节点、创建凭证、设置Gas参数、部署合约、打印地址。public class ContractDeployer { public static void main(String[] args) throws Exception { String rpcUrl http://localhost:7545; // 本地模拟链默认RPC String privateKey 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80; Web3j web3j Web3j.build(new HttpService(rpcUrl)); Credentials credentials Credentials.create(privateKey); BigInteger gasPrice Convert.toWei(20, Convert.Unit.GWEI).toBigInteger(); BigInteger gasLimit BigInteger.valueOf(300000L); String managerAddress credentials.getAddress(); ProjectReviewStorage contract ProjectReviewStorage.deploy( web3j, credentials, gasPrice, gasLimit, managerAddress ).send(); System.out.println(合约地址: contract.getContractAddress()); System.out.println(部署区块: contract.getDeployBlock()); web3j.shutdown(); } }这段部署工具里gasPrice设为20 GWEI是测试网常见的起步值本地模拟链对Gas价格并不敏感但写成20 GWEI能模拟真实环境。gasLimit设成300000是给合约构造函数和初始存储留的余量很多合约因为Gas上限设太低部署时直接报“out of gas”。managerAddress这里直接用了部署者地址也就是说部署者自己就是manager但实际系统里最好单独生成一个服务账户地址。3.4 部署成功后的验证交易回执里有哪些信息部署成功后控制台会打印合约地址。此时不要急着写SpringBoot代码先做一轮验证把合约地址拿到区块链浏览器上打开能看到合约代码和交易记录才算真正的部署成功。如果没有区块链浏览器也可以用Java代码直接查询部署交易的回执。TransactionReceipt receipt web3j.ethGetTransactionReceipt(txHash).send().get(); System.out.println(状态: receipt.getStatus()); System.out.println(区块号: receipt.getBlockNumber()); System.out.println(Gas使用: receipt.getGasUsed());交易回执里的值值得解释一下。status为1表示交易成功0表示失败blockNumber说明这笔交易被打包进了哪个区块是把区块链和业务数据关联起来的重要桥梁gasUsed可以帮助你估算后续调用合约的Gas上限比如看到部署用了20万Gas那业务调用可以适当放宽。拿到合约地址后建议把它写进SpringBoot的配置文件中并提交到文档里不然重启后找不回来。4. SpringBoot集成以太坊初始化、上链服务与查询接口4.1 Web3j依赖与节点连接配置SpringBoot和以太坊之间常用Web3j这个库来通信。Web3j相当于一个Java版的区块链客户端它把JSON-RPC请求封装成了对象方法让你不用手动组装请求体。在pom.xml里引入依赖之后配置一个Web3j实例就能开始工作。dependency groupIdorg.web3j/groupId artifactIdcore/artifactId version4.9.8/version /dependency版本号选4.x的稳定版本即可不建议追最新版本因为Web3j的部分API在升级后会有调整。配置方面我习惯把节点地址和私钥放在application.yml里方便不同环境切换。web3j: rpc-url: http://localhost:7545 private-key: 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 contract: review-storage-address: 0x你的合约地址接私钥放在配置文件里只是一种开发阶段的做法。生产环境不应该把私钥明文写在配置文件里可以用环境变量或密钥管理服务注入。毕设场景下放在application.yml没问题但答辩时如果被问到安全性你要能说出“实际部署会用环境变量替换”这个方案。4.2 合约加载与Credentials私钥管理有了RPC地址和私钥下一步就是把合约加载到SpringBoot里。加载合约需要四样东西合约地址、Web3j对象、Credentials凭证、Gas参数。Credentials是从私钥创建出来的它代表你的区块链身份。Configuration public class Web3jConfig { Value(${web3j.rpc-url}) private String rpcUrl; Value(${web3j.private-key}) private String privateKey; Bean public Web3j web3j() { return Web3j.build(new HttpService(rpcUrl)); } Bean public Credentials credentials() { return Credentials.create(privateKey); } Bean public ProjectReviewStorage reviewStorageContract( Web3j web3j, Credentials credentials, Value(${contract.review-storage-address}) String contractAddress) { return ProjectReviewStorage.load( contractAddress, web3j, credentials, DefaultGasProvider.GAS_PRICE, DefaultGasProvider.GAS_LIMIT ); } }注意Credentials.create只接受不带0x前缀的私钥会报错填错了它会直接抛异常。加载合约之后ProjectReviewStorage对象就相当于一个本地代理你调用它的storeReview方法它会自动帮你构造交易并发送到链上。4.3 评审结果上链服务业务数据怎么变成交易核心上链服务要做的事情是接收一条评审记录计算它的摘要调用合约存入链上然后把返回的交易哈希和区块号更新回数据库。这里计算摘要时要注意不要用整个对象去算而是拼接出固定格式的字符串再算。Service public class ReviewChainService { private static final Logger log LoggerFactory.getLogger(ReviewChainService.class); private final ProjectReviewStorage contract; private final ReviewRecordMapper reviewRecordMapper; public ReviewChainService(ProjectReviewStorage contract, ReviewRecordMapper reviewRecordMapper) { this.contract contract; this.reviewRecordMapper reviewRecordMapper; } public void storeReview(ReviewRecord record) { if (StringUtils.hasText(record.getTxHash())) { throw new BusinessException(该评审已上链请勿重复操作); } String hash calcHash(record); try { TransactionReceipt receipt contract.storeReview( record.getId().longValue(), hash ).send(); ReviewRecord update new ReviewRecord(); update.setId(record.getId()); update.setTxHash(receipt.getTransactionHash()); update.setBlockNumber(receipt.getBlockNumber().longValue()); reviewRecordMapper.updateBlockchainInfo(update); log.info(评审上链成功, id{}, txHash{}, record.getId(), receipt.getTransactionHash()); } catch (Exception e) { log.error(评审上链失败, id{}, record.getId(), e); throw new BusinessException(上链失败请稍后重试); } } private String calcHash(ReviewRecord record) { String raw record.getProjectId() - record.getExpertId() - record.getScoreTech() - record.getScoreDemo() - record.getScoreInnovation() - record.getTotalScore(); return DigestUtils.sha256Hex(raw); } }storeReview方法发送的是同步调用会等到交易被打包并返回回执才继续执行。calcHash拼接了业务主键和各项得分顺序不同会产生不同哈希所以要定好规范。这里有一个容易忽略的点storeReview的入参是uint256Java里对应BigInteger直接把Long传进去没问题但如果record.getId()意外为null就会空指针所以调用前要做非空校验。4.4 查询存证接口从tx_hash到评审明细的完整链路查询接口是答辩时最容易演示的功能。用户在页面上输入评审ID后端先查MySQL拿到业务数据再从合约里查链上存的哈希然后将两者比较返回“一致”或“不一致”。RestController RequestMapping(/api/review) public class ReviewVerifyController { private final ReviewRecordMapper reviewRecordMapper; private final ProjectReviewStorage contract; public ReviewVerifyController(ReviewRecordMapper reviewRecordMapper, ProjectReviewStorage contract) { this.reviewRecordMapper reviewRecordMapper; this.contract contract; } GetMapping(/verify/{reviewId}) public Result verify(PathVariable Long reviewId) throws Exception { ReviewRecord record reviewRecordMapper.selectById(reviewId); if (record null) { return Result.fail(评审记录不存在); } if (record.getTxHash() null) { return Result.fail(该评审未上链); } String chainHash contract.getStoredHash(reviewId.longValue()).send(); String localHash calcHash(record); boolean match chainHash.equalsIgnoreCase(localHash); VerifyVO vo new VerifyVO(); vo.setReviewId(reviewId); vo.setLocalHash(localHash); vo.setChainHash(chainHash); vo.setTxHash(record.getTxHash()); vo.setBlockNumber(record.getBlockNumber()); vo.setMatch(match); return Result.ok(vo); } private String calcHash(ReviewRecord record) { // 必须与上链时完全一致 String raw record.getProjectId() - record.getExpertId() - record.getScoreTech() - record.getScoreDemo() - record.getScoreInnovation() - record.getTotalScore(); return DigestUtils.sha256Hex(raw); } }两个calcHash方法是同一套拼接规则这就是链上链下对得上的前提。实际项目里我会把calcHash抽到一个工具类里避免两处写出差异。getStoredHash是合约里的view方法调用它不产生交易也不消耗Gas可以放心反复查询。5. 部署与运行避坑连不上的节点、丢掉的合约地址和重复上链5.1 连接测试网失败节点服务配置与本地兜底现象SpringBoot应用启动时报Connection refused或connect timed outWeb3j无法建立连接。原因节点RPC地址配置错误或本地网络无法访问公共节点服务。解决先检查rpc-url是否能直接访问。用命令行工具直接访问RPC接口能返回JSON就说明网络通。如果网络不通毕设阶段别折腾直接把配置切回本地模拟链业务逻辑不会受影响。测试网在演示当天不稳定是常见的事所以正式答辩前最后一天一定要再用本地模拟链做一次完整预演防止现场翻车。5.2 重启过后合约地址丢了把地址变成可配置项现象系统第一次跑通第二天启动后调用合约一直报合约不存在或调用失败。原因把合约地址硬编码在Service或Controller里换电脑、换测试网后地址失效或者代码重新部署后地址变了。解决把所有合约地址统一放在application.yml里部署合约后把新地址写入配置重启即可生效。我在3.3节里特意让你打印合约地址就是希望你立刻把它落到配置文件中并用Value注入。5.3 调用合约一直超时Gas参数与同步阻塞的排查现象storeReview调用后长时间不返回控制台一直停在等待状态最后超时。原因Gas价格设得太低交易在节点里排队等打包或者同步调用Block住了Tomcat线程多条请求一起超时。解决本地模拟链把Gas Price调低甚至设为0没关系测试网建议不低于20 GWEI。事务提交不要放在请求线程里改成异步任务或MQ队列让用户先看到“已受理”上链完成再回调更新状态。这块不让用户干等体验会好很多。5.4 同一条评审被重复上链唯一约束与幂等控制现象页面重复提交或者后端重试库里出现两条相同业务ID的存证记录合约exists映射也判断不了不同ID的数据。原因接口没有做幂等控制前端也没有禁用按钮。解决数据库里tx_hash和project_id expert_id都建唯一索引代码里先查一次txHash是否为空再做上链操作。同时在前端提交后把按钮置灰双保险。合约里的exists[_reviewId]是链上防重数据库唯一索引是链下防重两层一起兜底才算完整。5.5 评审权限失控把以太坊地址和用户身份绑定现象随便一个人调用storeReview也能成功上链或者后端服务换了一把私钥后调用被拒绝。原因合约里只判断manager地址而后端用户表里没有维护地址和人的绑定关系。解决用户表增加wallet_address字段管理员账号在初始化时把钱包地址登记进去。业务要求每个评审上链操作必须来自管理员那么合约的manager就设置成管理员地址后端持有的私钥和这个地址保持一致。这个设计在答辩时会被问到权限模型值得提前想清楚。6. 答辩前必做的三项验证与二次开发切入点6.1 演示顺序上链前、上链中、上链后答辩演示不要从头登录到退出那太冗长。我建议按“上链前、上链中、上链后”三步走。上链前展示一个评审记录的完整内容各项打分和总分都在MySQL里上链中点击“上链”按钮后端实时打印日志页面显示交易哈希和区块号上链后打开区块链浏览器展示这笔交易再回到系统点击“验真”对比结果一致。这个闭环展示的是完整链路比讲一百页PPT都有说服力。6.2 答辩追问怎么答交易不可篡改、区块浏览器与回执答辩老师大概率会问“凭什么说数据不可篡改”。这时候你别背概念直接操作在数据库里改一条评审分数再点验真系统返回“不一致”。一次操作胜过一套解释。还会问“链上存的到底是什么”你的回答是链上存的是摘要不是原始数据。这既是成本考虑也是隐私考虑。还会问“交易哈希能不能查”你打开浏览器输入tx_hash就能看到交易详情这比口头解释真实性高很多。6.3 值得继续做的三个方向这套系统跑通之后有三条路可以继续深入。第一把上链状态做成定时任务自动推送申请、评审、公示三个阶段全部上链形成完整的评审生命周期存证。第二在管理端增加一个区块浏览器把链上交易列表直接嵌入系统页面用户不用跳出系统。第三引入多签机制重大评审结果需要两个管理员地址共同确认才上链这是区块链应用里很经典的权限进化方向。我在自己做类似需求时第二次迭代就加了多签效果是评审流程的信任感明显提升答辩的时候这也成了核心亮点。做完这套系统后我自己最深的感受是区块链在毕设里不是一个空泛的理念它是可以被精确验证的工程组件。与其让评审变复杂不如只让它更可信。希望这篇笔记能帮你少走一些弯路祝你的项目顺利跑通答辩传来好消息。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑