health-blockchain实战:链上存证与IPFS存储的完整Demo
简介这份资源面向区块链初学者与医疗信息化方向的开发者展示区块链与IPFS集成的基础实现思路。项目基于以太坊、Truffle、Ganache、MetaMask与MyEtherWallet构建通过Solidity合约让医生从去中心化服务器检索健康记录的IPFS ID并支持患者下载对应记录适合作为去中心化健康记录追踪的入门实践参考。压缩包共30个文件约1.15MB包含16张png操作截图、4个js脚本、3个sol智能合约、3个json配置及license、md说明等覆盖合约编写、部署配置与前端交互各环节。目前已有176人学习浏览。读者可借此了解IPFS与链上合约的协作方式、Truffle项目目录结构及Ganache本地测试流程并参考截图完成环境搭建与合约部署为后续补充加密与安全机制打下基础。1. 从一份健康数据上链 Demo 说起health-blockchain 到底能跑通什么如果你正在找一个能把「链上存证 链下存储」讲清楚的练手项目health-blockchain 这个仓库值得花一个下午拆一遍。它做的事情很具体把一份健康记录文件的哈希写进以太坊合约文件本体丢给 IPFS前端用 MetaMask 签名发起交易再用 MyEtherWallet 验证合约调用结果。整套流程不依赖任何中心化后端合约、存储、钱包三层各司其职。它适合两类人一是刚学完 Solidity 语法、想找个完整链路练手的开发者二是需要给现有系统加「数据不可篡改」能力、但还没想清楚 IPFS 和链上到底怎么分工的工程师。仓库本身不复杂但麻雀虽小Truffle 编译部署、Ganache 本地链、IPFS 节点接入、前端合约实例化这几块都齐了。跑通一遍你对「什么该上链、什么不该上链」会有比看十篇科普更实在的判断。2. 环境搭建与合约部署Truffle Ganache 的最小闭环2.1 为什么选 Truffle 而不是 Hardhat这个仓库用的是 Truffle 工具链不是现在更流行的 Hardhat。原因很直接项目成型时间较早Truffle 的truffle migrate和truffle console对新手更友好配置文件truffle-config.js结构扁平改网络、改编译器版本一目了然。Hardhat 的插件生态确实更强但如果你只是想快速验证「合约能不能存哈希、能不能读回来」Truffle 的认知负担更低。常见做法是本地开发用 Ganache 起一条内存链它默认给你 10 个带 100 ETH 的测试账户私钥直接暴露在终端里方便导入 MetaMask。注意 Ganache 的 RPC 地址默认是http://127.0.0.1:7545而 Truffle 默认连的是8545这两个端口不一致是新手第一个翻车点。2.2 从零把合约跑起来先确认 Node.js 版本Truffle 对 Node 16 以上支持较好Node 18 也能跑但部分老版本 Truffle 在 Node 20 上会有ERR_OSSL_EVP_UNSUPPORTED报错。我一般会锁 Node 16 或 18。# 全局安装 Truffle 和 Ganache CLI npm install -g truffle npm install -g ganache # 启动本地链指定端口和网络ID ganache --port 7545 --networkId 5777 --deterministic--deterministic这个参数值得说一句它让每次启动生成的账户和私钥完全一致省得你每次重启链都要重新往 MetaMask 里导账户。--networkId 5777是 Ganache 的惯用网络 IDMetaMask 添加自定义网络时填这个值。// truffle-config.js 关键片段 module.exports { networks: { development: { host: 127.0.0.1, port: 7545, // 必须和 Ganache 启动端口一致 network_id: 5777, // 对应 Ganache 的 networkId }, }, compilers: { solc: { version: 0.8.19, // 按合约 pragma 声明调整 }, }, };配置里port和network_id是最容易写错的两个参数。端口写错truffle migrate会直接报连接超时network_id 写错MetaMask 会提示「无法连接到该网络」。改完配置后先跑truffle compile确认合约能编译通过再跑truffle migrate --reset部署。# 编译并部署到本地 Ganache truffle compile truffle migrate --reset --network development # 进入控制台验证合约方法 truffle console --network development进入 console 后可以手动调一下存哈希和读哈希的方法确认合约逻辑没问题。这一步很多人跳过结果前端调不通时不知道是合约问题还是前端问题。先在这里把set和get走一遍后面排错会省很多时间。2.3 合约里到底存了什么这个项目的合约核心就两个动作存一个字符串文件哈希按地址或 ID 读回来。它不会把健康记录原文上链因为链上存储成本极高而且一旦写入无法删除涉及隐私的数据绝不能直接上链。合约里通常是一个mapping结构key 是记录编号或用户地址value 是 IPFS 返回的 CID 或文件哈希。参数设置上哈希用string类型存即可长度固定的话也可以用bytes32省 gas。但bytes32对前端不友好需要额外做转换练手项目用string更直观。如果你要改成bytes32记得在合约里加require(bytes(_hash).length 32)做长度校验否则超长字符串会被截断这是血泪经验。3. IPFS 接入与文件上传CID 怎么和链上记录对上3.1 IPFS 节点的两种接法IPFS 接入有两种常见方式一是本地跑一个 IPFS 节点通过ipfs daemon启动API 默认在5001端口二是用公共网关或第三方 Pin 服务。本地节点的好处是数据完全自己掌控坏处是节点下线后文件可能被垃圾回收。练手阶段我建议本地节点因为你能看到完整的add和cat过程。启动本地节点# 初始化 IPFS 仓库只需一次 ipfs init # 启动守护进程开放 API 和网关 ipfs daemon启动后终端会显示 API 地址http://127.0.0.1:5001和网关地址http://127.0.0.1:8080。前端通过ipfs-http-client连接这个 API 端口注意不是网关端口。这两个端口搞混是第二个高频翻车点API 用于上传和查询网关用于浏览器直接访问文件。3.2 上传文件并拿到 CID// 使用 ipfs-http-client 上传文件 const { create } require(ipfs-http-client); // 连接本地 IPFS 节点的 API 端口 const ipfs create({ url: http://127.0.0.1:5001/api/v0 }); async function uploadToIPFS(fileBuffer) { // add 方法返回一个异步迭代器取第一个结果 const result await ipfs.add(fileBuffer); // result.path 就是 CID形如 QmXxx... console.log(CID:, result.path); return result.path; }ipfs.add接收 Buffer、字符串或文件流。返回的result.path是 CID v0 格式以Qm开头。如果你用ipfs.add的cidVersion: 1选项会得到以b开头的 CID v1两者在网关访问时路径格式略有不同。练手项目保持默认 v0 即可兼容性更好。拿到 CID 后把它传给合约的存储方法链上就留下了一条「某文件哈希对应某 CID」的记录。验证时用ipfs.cat(cid)能把文件内容读回来再算一次哈希和链上存的对比一致就说明整个链路没被篡改。3.3 前端怎么把 IPFS 和合约串起来前端通常用web3.js或ethers.js实例化合约。这个仓库用的是 web3.js配合 MetaMask 注入的window.ethereum。关键步骤是先请求账户授权再用账户实例化合约最后调方法发交易。// 前端连接 MetaMask 并调用合约 async function storeHash(cid) { // 请求账户授权 const accounts await window.ethereum.request({ method: eth_requestAccounts, }); const web3 new Web3(window.ethereum); const contract new web3.eth.Contract(abi, contractAddress); // 发交易from 必须是已授权账户 await contract.methods.setHash(cid).send({ from: accounts[0] }); }send({ from: accounts[0] })里的from不能省也不能填一个没授权的地址否则 MetaMask 会弹窗报错。交易发出后MetaMask 会弹出确认框确认后等几秒链上打包receipt里能看到交易哈希。如果一直 pending检查 Ganache 是否还在运行以及 MetaMask 网络是否切到了本地链。4. 避坑与排查五个让 Demo 跑不起来的典型问题4.1 MetaMask 连不上本地链现象是 MetaMask 添加自定义网络后一直转圈或者提示「无法获取账户」。原因通常是 RPC 地址填成了http://localhost:7545而 Ganache 只监听了127.0.0.1或者 chainId 和 networkId 填反了。解决方法是 RPC 填http://127.0.0.1:7545chainId 填1337Ganache 默认networkId 填5777。如果还不行重启 Ganache 和浏览器。4.2 合约部署后地址对不上现象是前端调合约报「返回地址没有合约代码」。原因是truffle migrate每次--reset都会重新部署合约地址变了但前端里写死的地址没更新。解决办法是不要在前端硬编码地址而是从build/contracts/YourContract.json里读networks字段或者每次部署后手动同步一次。我一般会在部署脚本里把地址写到一个config.js前端引这个文件。4.3 IPFS 上传成功但网关访问 404现象是ipfs.add返回了 CID但浏览器打开http://127.0.0.1:8080/ipfs/QmXxx显示 404。原因是本地节点默认不自动 Pin 新文件垃圾回收可能已经把它清了或者网关端口被占用。解决方法是上传后显式调ipfs.pin.add(cid)并确认ipfs daemon终端没有报错。如果端口冲突用ipfs config Addresses.Gateway改端口。4.4 交易一直 pending 不打包现象是 MetaMask 显示交易已提交但 Ganache 终端没有新块。原因是 Ganache 默认是即时出块但如果之前手动改过blockTime或者用了--miner.blockTime参数就会变成定时出块。解决方法是重启 Ganache 不加额外参数或者用ganache --miner.blockTime 0恢复即时出块。另外检查 MetaMask 的 gas 费是否设得太低本地链一般用默认值即可。4.5 合约编译报 solc 版本不匹配现象是truffle compile报「Source file requires different compiler version」。原因是合约头部的pragma solidity ^0.8.0和truffle-config.js里指定的solc.version不一致。解决方法是把配置里的版本改成合约 pragma 允许的范围比如0.8.19。如果合约用了^0.8.0配置写0.8.19没问题如果合约写死了0.6.0配置也得跟着改。改完记得删掉build目录重新编译。5. 进阶技巧用脚本批量验证链上记录与 IPFS 文件的一致性跑通单条记录后真正有价值的是批量校验。我一般会写一个 Node 脚本遍历合约里存过的所有 CID逐个从 IPFS 拉回文件、算哈希、和链上记录比对。这个脚本能当回归测试用每次改完合约或前端跑一遍确认没有破坏已有记录。// verify.js 批量校验链上哈希与 IPFS 文件 const Web3 require(web3); const { create } require(ipfs-http-client); const crypto require(crypto); const ipfs create({ url: http://127.0.0.1:5001/api/v0 }); const web3 new Web3(http://127.0.0.1:7545); async function verifyAll(contractAddress, abi, totalCount) { const contract new web3.eth.Contract(abi, contractAddress); for (let i 0; i totalCount; i) { // 假设合约有 getHash(index) 方法 const onChainHash await contract.methods.getHash(i).call(); // 从 IPFS 拉回文件内容 const chunks []; for await (const chunk of ipfs.cat(onChainHash)) { chunks.push(chunk); } const fileBuffer Buffer.concat(chunks); // 重新计算哈希 const localHash crypto .createHash(sha256) .update(fileBuffer) .digest(hex); console.log(记录 ${i}: 链上 ${onChainHash} | 本地 ${localHash}); } } verifyAll(0xYourContractAddress, abi, 10);脚本里totalCount需要你根据实际存了多少条记录来传合约如果没提供计数方法可以加一个recordCount状态变量每次setHash时自增。ipfs.cat返回的是异步迭代器必须用for await收集直接await会拿到迭代器对象而不是内容这是第三个容易翻车的地方。参数上crypto.createHash(sha256)的算法要和上传时保持一致。如果上传时用的是sha256校验也用sha256如果用的是keccak256Node 原生 crypto 不支持得用ethers.utils.keccak256。这个细节不注意校验永远对不上但你又找不到原因属于典型的玄学问题。还有一个实用技巧把校验结果写进一个 CSV方便对比。我习惯在脚本里加fs.appendFileSync(verify-log.csv, ...)每次跑完看一眼哪些记录不一致。不一致的记录优先查 IPFS 节点是否被清理过其次查合约是否被重新部署过导致索引错位。从那以后我每次改完合约或前端都强制走一遍这个校验脚本确认链上链下数据还对得上。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

EdgeDriver 92.0.902.67 win64包下载配置与版本匹配实战

EdgeDriver 92.0.902.67 win64包下载配置与版本匹配实战

简介:EdgeDriver 92.0.902.67 的 win64 版本是面向 64 位 Windows 系统上 Microsoft Edge 浏览器的自动化测试驱动组件,主要服务于使用 Selenium WebDriver 进行 Web 应用功能测试与回归测试的开发者和测试工程师。该驱动作为 Selenium 命令与 Edge 浏览…

2026/10/10 4:05:34 阅读更多 →
重庆家具公司怎么选?四类靠谱类型与避坑实操指南

重庆家具公司怎么选?四类靠谱类型与避坑实操指南

上个月陪朋友在重庆跑了整整三周家具市场,从主城的大卖场到周边工厂店,把定制、成品、软体都过了一遍。他一开始的心态很典型:装修不想多花钱,又怕选错公司后面扯皮,天天问我“重庆靠谱家具公司到底有哪些”。这个问题…

2026/10/10 4:05:34 阅读更多 →
技术博客写不出来?先补齐项目标题、关键词和摘要

技术博客写不出来?先补齐项目标题、关键词和摘要

我没法基于“【无标题】”这个输入来生成一篇真正的博文——因为标题是空的,围绕它的关键词、场景、领域信息也都是空的,我没有任何可以拆解的实义内容。请补充以下信息,我就能立刻为你写出一篇结构完整、可直接发布的从业者风格博文&#xf…

2026/10/10 4:05:34 阅读更多 →

最新新闻

Java进阶核心:JVM内存、并发机制与性能排查实战指南

Java进阶核心:JVM内存、并发机制与性能排查实战指南

说实话,身边很多工作了两三年的Java开发者,都会陷入一种“会写但不会查”的尴尬状态:CRUD写得飞起,Spring Boot玩得贼溜,可一旦线上接口变慢、CPU飙高、内存疯狂上涨,就完全没了方向。这其实就是“Java学习…

2026/10/10 4:49:20 阅读更多 →
C#视频流解析实战:从RTSP到GPU渲染的全链路实现

C#视频流解析实战:从RTSP到GPU渲染的全链路实现

1. 这不是“放视频”,而是把视频流从字节里“揪出来”的硬功夫很多人看到“C# 解析视频流播放”这个标题,第一反应是:“不就是用Windows Media Player控件或者WPF的MediaElement拖个控件、设个Source属性完事?”——这确实能播&am…

2026/10/10 4:49:20 阅读更多 →
群聊遥控Claude Code:团队AI协作工作流实践

群聊遥控Claude Code:团队AI协作工作流实践

国庆假期,外面人山人海,我在家宅了整整七天,顺手搞了个有点「怪」但确实能用的东西:在飞书群里,我可以直接 一个特殊身份,队友电脑上的 Claude Code 就会收到任务,自己打开代码、写改动、跑测试…

2026/10/10 4:49:20 阅读更多 →
小模型边缘突破:难例样本动态挖掘与特定边界能力蒸馏实践

小模型边缘突破:难例样本动态挖掘与特定边界能力蒸馏实践

在模型轻量化与边缘端部署的浪潮中,通过知识蒸馏(Knowledge Distillation)将 70B 级别大模型的推理能力迁移至 1.5B 或 3B 级别的小模型,是实现低延迟低成本落地的核心路径。然而,很多算法工程师在依照传统 Hinton 蒸馏…

2026/10/10 4:49:20 阅读更多 →
PHP+MySQL图书管理系统:从环境部署到核心功能拆解

PHP+MySQL图书管理系统:从环境部署到核心功能拆解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 4:49:20 阅读更多 →
裂隙注浆模拟实战:注浆压力、扩散半径与浆液用量深度解析

裂隙注浆模拟实战:注浆压力、扩散半径与浆液用量深度解析

早上九点半,我蹲在隧洞边上看现场注浆,一个孔打下去,水泥浆像被裂隙吞了一样,连打了三罐都没见压力起来。旁边的老师傅把手套一摘,说了句让我记到现在的话:“你连浆往哪跑都不知道,光加压力有什…

2026/10/10 4:48:20 阅读更多 →

日新闻

卫星轨道分类全解析:从LEO到GEO的选型逻辑与工程实践

卫星轨道分类全解析:从LEO到GEO的选型逻辑与工程实践

1. 从“卫星轨道分类”这个标题说起:为什么值得花时间搞懂第一次接触“卫星轨道分类”这个概念,很多人会觉得它离自己很远——不就是天上的星星怎么转吗?但如果你正在做航天任务规划、遥感数据接收、星座设计,甚至只是准备一场航天…

2026/10/10 0:00:39 阅读更多 →
Spring AOP 核心原理与实战:从概念到日志切面落地

Spring AOP 核心原理与实战:从概念到日志切面落地

1. 从一个真实痛点说起:为什么你的代码里到处都是重复逻辑刚入行那会儿,我写过一个用户管理模块,注册、登录、改密码、注销四个接口。每个接口里都塞了几乎一样的日志打印、参数校验、事务开启和提交。当时觉得没什么,能跑就行。直…

2026/10/10 0:00:40 阅读更多 →
Python招聘数据采集与分析可视化:从采集清洗到薪资技能城市可视化全链路

Python招聘数据采集与分析可视化:从采集清洗到薪资技能城市可视化全链路

简介:这是一套面向计算机相关专业学生与项目实战学习者的Python数据采集与分析可视化完整项目,以Boss直聘岗位数据为对象,适合用作毕业设计、课程设计或期末大作业。资源包共38个文件,约246KB,以13个py源码文件为核心&…

2026/10/10 0:00:40 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 15:26:32 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 1:36:08 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 10:11:06 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 21:13:17 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 21:32:20 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 6:17:20 阅读更多 →