简介这是一套面向计算机相关专业在校学生与教师的区块链课程设计、毕业设计完整源码包基于以太坊实现数据共享与访问控制适合作为毕设项目、课程大作业或项目初期立项演示也便于基础较好的学习者在此基础上二次修改扩展功能。压缩包共27个文件约352KB以JavaScript脚本、HTML页面、文本数据与CSS样式为主另含一份Solidity智能合约与README说明文档前端页面、后端逻辑与链上合约相互配合构成可运行的完整系统。项目代码均经测试运行成功后才上传答辩评审平均分达到96分已有35人学习关注。读者可从中获得一套结构清晰的区块链数据共享实现方案涵盖用户注册登录、数据上传与详情查看、访问控制合约等模块便于理解前后端与智能合约的交互流程并参考其目录组织与排错思路完成自己的设计任务。1. 从一份能跑通的以太坊数据共享系统说起如果你正在为毕业设计或课程设计发愁尤其是选题落在区块链方向大概率会遇到一个尴尬局面概念听了一堆真到动手时连一个能跑通的前后端加合约的完整项目都找不到。这份 BlockChain-Data-Sharing-System 就是冲着这个痛点来的。它把数据上传、链上存证、基于以太坊的访问控制、用户注册登录、文件详情查看这一整条链路都做完了前端是原生 HTML/CSS/JS后端逻辑通过 web3.js 与合约交互智能合约用 Solidity 写核心文件是 AccessControl.sol。换句话说这不是一个只讲理论的 PPT 项目而是一个你解压后配好环境就能在本地跑起来的完整工程。适合计算机、通信、自动化等专业的在校生拿来做毕设或课设底座也适合想入门以太坊访问控制的小白顺着代码摸一遍真实流程。2. 拆开压缩包目录结构与技术栈的真实分工2.1 前端页面与脚本的对应关系拿到压缩包后别急着改代码先把目录结构看清楚。这个项目的文件组织方式很直白没有用任何前端框架所有页面都是独立的 HTML配套的 JS 负责调合约和渲染数据。我一般会先画一张页面与脚本的对应表避免改错文件。页面文件配套脚本主要职责register.htmlregister.js用户注册写入链上用户信息login.htmllogin.js登录校验获取当前账户身份main.htmlmain.js主入口展示数据列表与导航dataUpload.htmldataUpload.js上传数据调用合约存证dataDetail.htmldataDetail.js查看单条数据详情与访问权限userDetail.htmluserDetail.js查看用户信息与授权记录helloworld.htmlcontrol.js / controlAbi.js合约连通性测试与 ABI 加载这张表的价值在于当你发现某个功能异常时能立刻定位到是页面逻辑问题还是合约调用问题。比如上传后列表不刷新先看 dataUpload.js 里回调有没有重新拉取数据而不是一头扎进合约里查。2.2 智能合约 AccessControl.sol 的职责边界整个系统的信任锚点就在 AccessControl.sol 这个文件里。它承担了三件事第一维护用户注册信息把地址和基本身份映射起来第二记录数据文件的元信息比如文件名、上传者地址、时间戳第三实现访问控制逻辑判断某个地址有没有权限读取某条数据。这里要特别注意链上只适合存元数据和权限标记真正的文件内容不应该直接写进合约否则 gas 消耗会让你怀疑人生。项目里 testData 目录下的 testdata1.txt 到 testdata8.txt 就是用来模拟文件内容的实际运行时文件本身走本地或传统存储链上只留指纹和授权关系。2.3 web3.min.js 与 controlAbi.js 的配合方式controlAbi.js 里放的是合约编译后的 ABIcontrol.js 和各个业务脚本通过它来实例化合约对象。web3.min.js 则是与以太坊节点通信的桥梁。常见做法是本地用 Ganache 起一条私有链把合约部署上去拿到合约地址后填进前端脚本的初始化位置。这里有个容易忽略的点ABI 必须和你实际部署的合约版本一致如果你改了 Solidity 源码却忘了重新编译并替换 controlAbi.js调用就会各种报错而且报错信息往往不直接指向 ABI 不匹配排查起来很费时间。3. 本地跑通全流程从 Ganache 到页面交互3.1 环境准备与合约部署先确保本机有 Node.js 和 npm然后安装 Ganache 作为本地测试链。Ganache 的好处是启动后直接给你十个带余额的账户省去挖矿和领测试币的麻烦。安装命令如下# 全局安装 ganache-cli也可以直接用 Ganache 图形界面 npm install -g ganache-cli # 启动本地链指定端口和网络ID ganache-cli -p 7545 -i 5777 -d参数说明-p 7545指定 RPC 监听端口后面前端 web3 要连这个端口-i 5777是网络 IDMetaMask 里添加自定义网络时保持一致-d表示每次启动用固定的助记词方便你反复调试时账户地址不变。启动后终端会打印十个账户地址和私钥先记下来。接下来部署合约。如果你有 Truffle 环境可以直接用 truffle migrate如果没有用 Remix 在线编译后把 ABI 和字节码复制出来通过 web3 脚本部署也行。我一般图省事直接在 Remix 里选 Injected Web3 连到 Ganache点部署然后把合约地址抄下来。// deploy.js 片段用 web3 部署合约的常见写法 const Web3 require(web3); const web3 new Web3(http://127.0.0.1:7545); const abi require(./controlAbi).abi; const bytecode 0x...; // 编译后得到的字节码 const contract new web3.eth.Contract(abi); contract.deploy({ data: bytecode }) .send({ from: 0x你的第一个账户地址, gas: 3000000 }) .then(instance { console.log(合约地址:, instance.options.address); });逻辑说明new web3.eth.Contract(abi)先用 ABI 创建合约抽象对象deploy时传入字节码send里指定部署账户和 gas 上限。部署成功后打印出的地址要填回前端脚本里。参数方面gas 给 3000000 对这个小合约足够如果报 out of gas 再往上加。3.2 前端脚本里改哪几个地方合约部署完前端要改的其实就两处web3 提供者地址和合约地址。打开 control.js 或 main.js找到初始化 web3 的那一行把http://127.0.0.1:7545填进去再把合约地址替换成你刚部署得到的。有些脚本里合约地址是硬编码的搜索0x开头的长串就能定位。// 前端初始化片段 if (typeof web3 ! undefined) { web3 new Web3(web3.currentProvider); } else { web3 new Web3(new Web3.providers.HttpProvider(http://127.0.0.1:7545)); } const contractAddress 0x你部署的合约地址; const contract new web3.eth.Contract(abi, contractAddress);这段代码先判断浏览器有没有注入 web3比如 MetaMask有就用它没有就回退到本地 HTTP 提供者。这样做的好处是既能在装了 MetaMask 的浏览器里跑也能在纯本地环境跑。注意合约地址一定要换成你自己的用别人的地址调用会失败。3.3 注册、上传、授权的完整操作链环境配好后按注册、登录、上传、授权的顺序走一遍。注册时 register.js 会调用合约的注册方法把当前账户地址和用户名写进去。上传时 dataUpload.js 读取文件内容计算一个简单哈希或直接取文件名加时间戳调用合约存证。授权环节是访问控制的核心数据拥有者调用合约方法把访问权限授予某个地址被授权地址在 dataDetail 页面就能看到数据详情。// 授权调用的典型写法 contract.methods.grantAccess(dataId, userAddress).send({ from: ownerAddress }) .on(receipt, receipt { console.log(授权成功交易哈希:, receipt.transactionHash); }) .on(error, err { console.error(授权失败:, err.message); });参数说明dataId是数据在合约里的索引或 IDuserAddress是被授权者地址from必须是数据拥有者否则合约里的权限判断会拒绝。事件监听里 receipt 能拿到交易哈希方便你去 Ganache 日志里核对。如果授权后对方还是看不到数据先检查 dataId 传对没有再检查合约里权限判断的条件是不是要求拥有者亲自调用。4. 访问控制逻辑的合约实现与常见坑4.1 Solidity 里怎么表达「谁能看什么」AccessControl.sol 的访问控制模型不复杂常见做法是用 mapping 做两层映射一层从数据 ID 映射到拥有者地址另一层从数据 ID 映射到被授权地址列表或布尔值。读取时先查拥有者是不是调用者再查调用者有没有在授权名单里。这种设计的好处是逻辑清晰gas 消耗可控缺点是授权列表如果太长遍历查询会越来越贵。对于课设和毕设规模的数据量完全够用。// 简化示意非项目原文件 mapping(uint address) public dataOwner; mapping(uint mapping(address bool)) public accessGranted; function grantAccess(uint dataId, address user) public { require(msg.sender dataOwner[dataId], 只有拥有者可以授权); accessGranted[dataId][user] true; } function checkAccess(uint dataId, address user) public view returns (bool) { return dataOwner[dataId] user || accessGranted[dataId][user]; }逻辑说明grantAccess里用require卡住非拥有者调用这是合约层面最基本的安全边界。checkAccess是 view 函数不消耗 gas前端可以随意调用来决定是否展示数据。参数上dataId 用 uint 比用字符串省 gasuser 直接传地址。4.2 避坑五个真实调试中会撞上的问题现象一页面一直转圈控制台报 CORS 或连接拒绝。原因通常是 Ganache 没启动或者端口对不上。解决方法是确认 ganache-cli 终端还在运行且前端里写的端口和启动参数一致。如果浏览器装了 MetaMask它可能劫持了 web3 对象试着在无痕窗口里跑或者临时禁用 MetaMask。现象二合约调用返回 revert但不知道哪一步挂了。原因多半是 require 条件没满足比如非拥有者调了授权方法或者 dataId 根本不存在。解决办法是在 Ganache 的交易日志里找到失败的那笔看 revert reason如果没显示就在合约里给每个 require 加上明确的错误字符串重新编译部署。现象三改了 Solidity 源码后前端行为没变化。这是血泪经验里最常见的一条ABI 和字节码没重新编译替换。Solidity 改一行ABI 可能就变了前端拿旧 ABI 调新合约参数编码对不上自然各种异常。解决方法是每次改合约后重新编译把新的 ABI 覆盖到 controlAbi.js并重新部署拿到新地址。现象四上传文件后列表不显示新数据。原因通常是前端没有在交易确认后重新拉取列表或者拉取时用的账户不对。解决方法是把拉取逻辑放在 send 的 receipt 回调里确保交易上链后再刷新同时检查拉取时用的 from 地址是不是当前登录账户。现象五MetaMask 弹窗显示 gas 费过高或交易一直 pending。本地 Ganache 一般不会 pending如果出现多半是 MetaMask 连到了主网或测试网而不是本地 7545。解决方法是手动在 MetaMask 里添加自定义网络RPC URL 填http://127.0.0.1:7545链 ID 填 5777然后切换过去。5. 进阶用法把访问控制改成多级授权与事件追踪5.1 从单级授权扩展到角色分级原项目的访问控制是「拥有者—被授权者」两级模型够用但不够灵活。如果你想在毕设里做出区分度可以加一层角色映射比如把用户分成普通用户、审核员、管理员不同角色对数据的读权限不同。实现方式是在合约里加一个mapping(address uint) public roles然后在 checkAccess 里根据角色做分支判断。这样改的好处是答辩时能讲出「基于角色的访问控制」这个点比单纯的两级模型更有层次。// 角色分级示意 mapping(address uint) public roles; // 0 普通1 审核员2 管理员 function checkAccessWithRole(uint dataId, address user) public view returns (bool) { if (roles[user] 2) return true; // 管理员全通 if (roles[user] 1) return accessGranted[dataId][user]; return dataOwner[dataId] user; }参数说明roles 用 uint 存角色等级比字符串省空间。checkAccessWithRole 里先判管理员再判审核员最后回落到拥有者判断。这样前端可以根据返回值决定展示哪些按钮。5.2 用事件做操作审计合约里加 event 是低成本高回报的改动。每次授权、上传、注册都 emit 一个事件前端可以监听事件来实时更新界面而不是反复轮询。更重要的是事件日志本身就是一份链上审计记录答辩时演示「谁在什么时候授权了谁」会很有说服力。event AccessGranted(uint indexed dataId, address indexed owner, address indexed user); function grantAccess(uint dataId, address user) public { require(msg.sender dataOwner[dataId], not owner); accessGranted[dataId][user] true; emit AccessGranted(dataId, msg.sender, user); }前端监听写法contract.events.AccessGranted({ fromBlock: latest }) .on(data, event { console.log(新授权:, event.returnValues); // 在这里刷新列表或弹提示 });逻辑说明indexed关键字让参数可以被过滤查询前端可以只监听某个 dataId 的授权事件。fromBlock: latest表示只监听新事件如果想回溯历史改成 0 或部署时的区块号。5.3 验证合约逻辑是否真的生效改完合约别只看页面能不能点要做一次完整的验证。我一般会走这三步第一步用 Ganache 里的账户 A 注册并上传数据第二步用账户 B 尝试读取确认被拒绝第三步账户 A 授权给 B再用 B 读取确认成功。每一步都在 Ganache 日志里核对交易和事件。这套流程走下来合约的访问控制逻辑才算真正验证过而不是「看起来能跑」。从那以后我每次改完合约都强制走一遍「换账户验证权限」的流程因为页面上的成功提示可能只是前端没报错不代表链上逻辑真的按预期执行。希望这份拆解能帮你少走点弯路把这份资源真正用起来。本文还有配套的精品资源点击获取
