IPFS 分布式存储,把 文件哈希+元数据 写进 Hyperledger Fabric 区块链里。这个操作具体怎么实现的?
简单来说,这个架构的核心思路是:IPFS 负责“存重物”(大数据),Fabric 负责“记账本”(存指纹)。
这种“链下存储 + 链上存证”的模式解决了区块链无法存储大文件的问题,同时保证了数据的不可篡改性。
核心实现流程
1. 文件上传与哈希生成 (IPFS 侧)
首先,客户端将原始文件上传到 IPFS 节点。
- 分片与计算:IPFS 会将文件切割成块,并通过 Merkle DAG 算法生成一个唯一的 CID (Content Identifier),也就是你说的文件哈希(例如:
Qm...)。 - 持久化:确保文件在 IPFS 网络中被 Pin 住(固定),防止被垃圾回收。
IPFS 的 Pin(固定) 机制就像是电脑里的“防止清空回收站”或者“收藏夹永久保存”功能。
在 IPFS 这种分布式网络中,存储资源是宝贵的。为了不让硬盘塞满,IPFS 节点会定期进行“大扫除”,这就是垃圾回收(Garbage Collection, GC)。
- 什么是垃圾回收 (GC)?
当你通过 IPFS 节点读取或下载一个文件时,该文件会暂时缓存在你的本地节点上。
- 缓存机制:随着时间推移,缓存会占用大量空间。
- 自动清理:当缓存超过设定的阈值(例如 10GB)时,IPFS 会启动垃圾回收,把那些没有被 Pin 住的文件全部删掉。
- 什么是 Pin?
Pin 是一个指令,它告诉 IPFS 节点:“这个文件对我非常重要,无论发生什么,都不要在垃圾回收时把它删掉。”
- 本地性:Pin 是针对具体节点的。如果你在节点 A 上 Pin 了文件,只有节点 A 保证不删;如果节点 B 没 Pin,它依然可能清理掉该文件。
- 默认状态:
- 当你手动
ipfs add一个文件时,IPFS 默认会自动将其 Pin 住。 - 当你只是通过 CID
ipfs cat查看别人的文件时,该文件只是临时缓存,不会被自动 Pin。
- 当你手动
- 为什么在“Fabric + IPFS”架构中,持久化很重要?
在你的场景中,如果你把文件哈希写进了 Fabric 区块链,但忘记在 IPFS 节点上 Pin 住该文件:
- 哈希失效风险:一旦负责存储的 IPFS 节点执行了垃圾回收,而该文件又没有被其他节点缓存。
- 数据孤岛:区块链上依然存着那个哈希(指纹),但当你拿着哈希去 IPFS 寻找原文件时,网络会返回 404。
- 结论:链上的记录永远有效,但对应的链下数据丢失了。 这就是为什么在写入 Fabric 之前,必须确保文件已被持久化(Pinned)。
- 如何实现持久化?
通常有三种方案:
- 本地节点固定:运行自己的 IPFS 节点,执行
ipfs pin add <CID>。 - IPFS Cluster:如果你有多个服务器,可以用 Cluster 工具让多个节点同时 Pin 住同一个文件,实现高可用。
- Pinning Services:使用第三方的服务(如 Pinata, Infura),付钱让他们在全球范围内的节点上帮你永久固定文件。
2. 构建元数据 (Metadata)
在写入区块链之前,你需要定义一个 JSON 结构的元数据,通常包含:
- FileCID: IPFS 返回的哈希值。
- FileName: 文件名。
- Author: 上传者身份。
- Timestamp: 时间戳。
- Size: 文件大小。
3. 调用 Fabric 智能合约 (Chaincode)
这是关键步骤。你需要编写一段 Chaincode(通常用 Go, Node.js 或 Java 编写),定义一个
PutAsset 或 CreateFileRecord 的函数。我这个项目是怎么实现pin操作的?是否需要 IPFS 持久化?
这个项目里 “pin”并不是在前端做的 ,而是在后端把文件 add 到本地 Kubo 节点后,由节点侧来“保留数据”。整体调用链是:
- 前端 POST /api/evidence → 后端 EvidenceController#createEvidence
- 后端上传 IPFS: IpfsService#uploadFile
这份代码库原本没有显式 pin 调用 (只做了 ipfs.add(...) ),但你文档里又写了“要 pin”。我已经把它补上:上传成功拿到 CID 后,立即执行 ipfs.pin.add(...) ,pin 失败会直接抛错,阻止后续入库/上链,避免出现“链上/库里有 CID,但 IPFS 节点重启或 GC 后找不到块”的情况:见 IpfsService.java:L21-L35 。
是否需要 IPFS 持久化?需要(至少生产环境必须)
- pin 只表示“这个节点不要把这些块当垃圾回收”,但 如果节点的数据目录不持久化 (容器没挂卷 / 机器盘没了),pin 也保不住。
- 你当前的 Docker 部署已经做了持久化:把 IPFS repo 挂载到了宿主机目录 ./ipfs-data ,重启容器数据仍在:见 docker-compose.yml:L10-L12 。
- 另外你 compose 里启用了 --enable-gc (会清理未 pin 的块),所以 pin/递归 pin 对“长期可用”是关键:见 docker-compose.yml:L16-L19 。
补充:网关端口对齐 - 你当前 compose 映射的网关是宿主机 9080 -> 容器 8080 ,我已把前端默认网关和文档示例统一为 http://localhost:9080/ipfs :见 frontend/.env 、 DEPLOY.md 、 ipfs-config/README.md 。
如果你打算上生产,除了“单节点持久化 + pin”,还建议至少做一层冗余(IPFS Cluster 或第三方 pinning),否则单机磁盘损坏仍会导致数据不可恢复。