尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

基于Hyperledger Fabric的农产品区块链溯源系统:联盟链与Go链码实现解析

基于Hyperledger Fabric的农产品区块链溯源系统:联盟链与Go链码实现解析 简介面向计算机相关专业毕业设计与课程设计场景的完整项目包基于区块链Hyperledger Fabric构建农产品、商品等通用溯源系统覆盖智能合约、网络配置、前端页面与部署脚本适用于课题研究、课设作业、初期立项演示及区块链入门进阶。压缩包共1317个文件大小约141MB主要包含Go语言链码、Vue构建的前端页面、证书密钥与配置文件、Shell部署脚本及Markdown说明文档类型覆盖从网络、合约到界面的完整实现路径模块间便于对照和修改。内容涵盖Fabric网络组件、组织与通道配置、证书体系、链码逻辑及Web交互界面代码经过运行测试功能正常并附带详细设计文档与全部项目资料可直接用于毕设演示或课设提交也能在理解接口后扩展其他商品的溯源场景。已有357人浏览学习对需要快速搭建可运行溯源系统原型并理解Fabric落地流程的开发者参考价值较高。1. 溯源系统不是「存个哈希」这套基于 Hyperledger Fabric 的农产品通用溯源源码到底能跑通什么区块链溯源这几年从热点变成了标配尤其在农产品行业「一物一码、全程可查」几乎是答辩评委的标准问题。但多数毕设源码只是把一条哈希记录塞进 MySQL前端贴个标签就当区块链。这套基于 Hyperledger Fabric 的农产品通用溯源系统不一样它把联盟链的完整链路走通了CA 证书签发、Orderer 排序、Peer 背书、Go 链码读写、SDK 调用、Web 查询页每层都有对应的源码和配置详细文档也齐属于拿下来不用大改就能演示、答辩的资源。它适合三类人做毕业设计或课程设计的学生需要一个能讲清「为什么用 Fabric 而不是以太坊」的完整项目刚接触 Fabric 的开发者想找一个能照着复现的工程而不是官方 sample 的零散片段以及要把溯源思路迁移到酒类、药品、工业品场景的从业者。这套项目最大的价值落在「通用」两个字上——商品加流转记录的数据模型是按行业抽象出来的换场景只是改字段和业务流程链码框架不用动。2. 先把网络拓扑看清楚Orderer、Peer、CA 在溯源场景里各干什么2.1 为什么农产品溯源偏偏选 Hyperledger Fabric而不是以太坊先回答一个答辩必问题为什么是 Fabric不是以太坊农产品溯源的业务链条是农户、加工厂、物流、商超、消费者这几个主体之间有上下游协作但谁都不希望自己的经营数据对全网公开。公链的本质是「完全公开 无准入」和这个诉求天然冲突。Fabric 是许可链身份由 CA 签发节点必须持有效证书才能加入网络天然符合「有准入门槛的产销链条」这个前提。第二个原因是成本和性能。Fabric 用 Raft 排序加背书模型不需要 PoW 挖矿没有 gas 消耗。毕设阶段你完全可以在单机 Docker 上跑两个组织各一个 Peer 的网络写入查询的响应都在百毫秒级演示效果足够。以太坊测试网虽然免费但有 gas 限制和出块延迟演示到一半链卡住的情况我见过不止一次。第三个原因是生态和资料密度。Fabric 有 fabric-samples 测试网络、Gateway SDK、Caliper 压测工具社区里教程和踩坑记录远比别的联盟链框架多。对毕设来说能搜到解决方案比什么都重要。这套源码走的就是 fabric-samples 标准工程路线换环境、改配置都有迹可循不会变成一个无人能复现的黑匣子。2.2 一条溯源链的组成通道、组织、背书策略怎么映射到真实业务流程Fabric 里每个组件都能在溯源业务里找到对应角色这张表建议直接写进答辩 PPT组件职责溯源场景对应Orderer 节点交易排序、切割区块、分发账本独立于产销双方的第三方记账方保证流水顺序可信Peer 节点保存账本、执行链码、参与背书农户组织 org1、流通组织 org2 各自持有一份账本副本CA / MSP签发身份证书、管理组织成员为农户、加工厂、质检机构签发可校验的数字身份Channel 通道隔离数据可见范围只有加入同一产销链的组织能查询同一条通道的账本Chaincode 链码定义账本读写逻辑商品登记、流转记录、溯源查询的智能合约对应业务规则通道是理解 Fabric 溯源的关键。一个农产品从产地到货架会经历采收、加工、运输、入库、销售多个环节参与方不同。用通道把「只让这条产销链上的组织看到数据」这件事落地其他组织即使在同一台机器上跑着 Peer也查不到该通道账本。这套源码里默认的 tracechannel 就是干这个的。背书策略对应的是业务确认动作。商品登记这类动作当前操作方自己背书即可策略写成 OR(Org1MSP.member,Org2MSP.member)但「农户发货、加工厂收货」这种涉及双方的动作业务上需要双方确认背书策略就应该收紧成 AND(Org1MSP.member,Org2MSP.member)任何一方单独写入都不合法。查询操作不参与背书任意一个 Peer 返回账本状态即可这也是查询比写入快一个量级的原因。2.3 源码包里关键文件哪些能改、哪些不能动拿到资源先别急着跑把几个核心配置文件认清楚能省后面一大半排错时间。configtx.yaml 定义组织、排序节点和通道的初始配置configtxgen 用它生成创世区块和通道配置交易改了这个文件必须重新生成区块、重新拉起网络否则改动不生效。connection-org1.json 和 connection-org2.json 是后端 SDK 连接网络的入口里面是 Peer、Orderer 的地址端口和证书路径后端连不上网络时优先检查这里。资源包根目录能看到 configtxgen、configtxlator 这类 Fabric 官方配置工具以及后缀为 crt 的 TLS 证书文件这些是网络构件属于正常组成。个别编译产物文件比如带 c 后缀的 go 运行时构建文件和项目运行无关直接忽略就行不影响复现。文档里如果写了「网络启动顺序」严格按它走Fabric 网络对启动顺序敏感顺序错了最容易出现后面第 5 章提到的「链码找不到」类报错。3. 核心链码拆解把「商品登记-流转-溯源查询」写成 Go 链码3.1 数据模型用 Product 加 TraceRecord 两个结构体表达一物一码链码是整个溯源系统的业务核心这套源码用 Go 编写符合 Fabric 官方主推的语言路线。数据模型按「通用溯源」抽象成两张表Product 存商品静态信息和当前状态TraceRecord 存每一次流转动作。一物一码通过 ProductID 实现它是溯源查询的唯一入口。// Product 商品主信息一个商品ID对应一条主记录 type Product struct { ProductID string json:productId // 一物一码全局唯一 Name string json:name // 商品名称 Origin string json:origin // 产地 ProduceAt string json:produceAt // 生产日期 Cert string json:cert // 质检/认证信息建议存对象存储引用而非大字段 Owner string json:owner // 当前持有人 Status string json:status // 状态已登记/流转中/已售出/已召回 } // TraceRecord 流转记录一次动作一行一对多关联到商品 type TraceRecord struct { RecordID string json:recordId // 记录唯一标识业务侧生成 ProductID string json:productId // 关联的商品ID Action string json:action // 动作采收/加工/运输/入库/销售 Operator string json:operator // 操作方ID对应MSP身份 Location string json:location // 流转地点 Timestamp string json:timestamp // 业务时间建议由调用方传入而非节点本地时间 Remark string json:remark // 补充信息如批次号、温度记录 }两个结构体的字段设计有一点值得细说Timestamp 用业务侧传入的时间而不是节点本地时间。原因是一个写入交易会被多个组织背书每个背书节点的本地时钟可能有偏差如果各自写本地时间同一条流转记录在不同组织的账本副本里时间不一致前端溯源时间线就会乱。用调用方统一传入的业务时间配合 RecordID 做次级排序能保证所有 Peer 上看到的数据一致。3.2 三个核心方法登记、流转、溯源查询链码的入口是 CreateProduct、AddTrace、QueryHistory 三个方法分别对应业务里的商品登记、流转录入、溯源查询。创建商品时先查重再写入保证幂等// CreateProduct 登记一个新商品写入世界状态 func (s *TraceContract) CreateProduct(ctx contractapi.ContractTransactionContextInterface, productID, name, origin, produceAt, cert, owner string) error { // 先查重商品ID已存在则拒绝避免覆盖历史数据 exists, err : s.AssetExists(ctx, productID) if err ! nil { return fmt.Errorf(检查商品是否存在失败: %v, err) } if exists { return fmt.Errorf(商品 %s 已存在请勿重复登记, productID) } product : Product{ ProductID: productID, Name: name, Origin: origin, ProduceAt: produceAt, Cert: cert, Owner: owner, Status: 已登记, } productJSON, err : json.Marshal(product) if err ! nil { return err } // 键直接用商品ID保证后续按ID精确查询的O(1)效率 return ctx.GetStub().PutState(productID, productJSON) }这里有个通用准则链码里不要存图片、PDF 这类大字段账本状态数据库会迅速膨胀背书节点之间同步也变慢。常见做法是在 Cert 字段里存对象存储或 IPFS 的引用地址前端拿到地址再加载文件。商品ID直接做世界状态的键查询时 GetState 一次命中比维护索引表简单可靠。流转记录用「商品ID 记录ID」的组合键写入和商品主记录分开存储// AddTrace 为商品追加一条流转记录校验存在性 - 追加记录 - 更新持有人 func (s *TraceContract) AddTrace(ctx contractapi.ContractTransactionContextInterface, productID, recordID, action, operator, location, timestamp, remark string) error { // 先确认主记录存在避免对未登记商品写入流转 productJSON, err : ctx.GetStub().GetState(productID) if err ! nil { return fmt.Errorf(读取商品失败: %v, err) } if productJSON nil { return fmt.Errorf(商品 %s 不存在请先登记, productID) } var product Product if err : json.Unmarshal(productJSON, product); err ! nil { return err } rec : TraceRecord{ RecordID: recordID, ProductID: productID, Action: action, Operator: operator, Location: location, Timestamp: timestamp, Remark: remark, } recJSON, _ : json.Marshal(rec) // 组合键命名空间 trace [productID, recordID]把同一商品的所有记录聚簇存放 traceKey, err : ctx.GetStub().CreateCompositeKey(trace, []string{productID, recordID}) if err ! nil { return err } if err : ctx.GetStub().PutState(traceKey, recJSON); err ! nil { return err } // 同步更新主记录持有人和状态 product.Owner operator product.Status 流转中 productJSON, _ json.Marshal(product) return ctx.GetStub().PutState(productID, productJSON) }组合键是这段代码的关键Fabric 的复合键底层按前缀排序存储用 CreateCompositeKey(trace, []string{productID, recordID}) 生成键后同一个商品的所有流转记录在状态数据库里物理相邻后续按前缀扫描一次 IO 就能取全不用维护额外索引。更新主记录时先 Unmarshal 再改字段再写回保证别把状态字段写丢。溯源查询把主记录和流转记录组装成一个 JSON 返回前端直接渲染// QueryHistory 返回商品主信息 全部流转记录组装成前端可直渲的JSON func (s *TraceContract) QueryHistory(ctx contractapi.ContractTransactionContextInterface, productID string) (string, error) { // 读取商品主记录 productJSON, err : ctx.GetStub().GetState(productID) if err ! nil || productJSON nil { return , fmt.Errorf(商品 %s 不存在或无数据, productID) } // 按组合键前缀扫出该商品全部流转记录 resultIterator, err : ctx.GetStub().GetStateByPartialCompositeKey(trace, []string{productID}) if err ! nil { return , err } defer resultIterator.Close() var records []TraceRecord for resultIterator.HasNext() { kv, err : resultIterator.Next() if err ! nil { return , err } var rec TraceRecord if err : json.Unmarshal(kv.Value, rec); err ! nil { return , err } records append(records, rec) } // 组装结果product 是主记录对象traces 是按写入顺序排列的流转数组 product : json.RawMessage(productJSON) result, _ : json.Marshal(map[string]interface{}{ product: product, traces: records, }) return string(result), nil }这里要和 GetHistoryForKey 做个区分。GetHistoryForKey 返回的是「同一个键每次被修改的版本历史」适合证明某条记录被改过而流转记录是每次插入的新键应该用 GetStateByPartialCompositeKey 加前缀扫描。毕设答辩时经常会问「怎么证明数据没被篡改」答案是两者结合主记录用 GetHistoryForKey 展示版本变化流转记录靠背书签名和区块哈希保证。顺带注意组合键扫出来的记录顺序是按键排序的不完全是业务时间顺序前端要做一次按 Timestamp 的排序。3.3 背书策略与写入校验为什么查询不需要背书链码部署时的背书策略决定了哪些组织的签名是交易合法性的必要条件。源码里默认的策略是 OR(Org1MSP.member,Org2MSP.member)也就是任一组织背书即可提交。如果你想模拟「双方确认」的业务约束把策略改成 AND(Org1MSP.member,Org2MSP.member)那么只有两个组织的 Peer 都执行了链码并签发背书交易才会被排序进区块。这个改动在 deployCC 参数或通道配置里调整不需要改链码代码。查询类方法不走背书也不走排序原因是查询只读世界状态不产生新账本记录一个 Peer 返回的结果就是权威的。但也有边界如果数据刚写入还没在所有 Peer 同步完查询不同的 Peer 可能拿到不同版本这就是 Fabric 的最终一致性。毕设场景一般感知不到这个差异但答辩被问到「查到的数据一定最新吗」时能说出「需要走提交机制或查询指定 Peer」这句话就是加分项。4. 从零把系统跑起来环境准备、网络启动、SDK 调用与前端联调4.1 环境清单与版本匹配先对齐环境版本不匹配是复现失败的第一大来源。Fabric 对版本匹配要求很高Peer、Orderer、CA、SDK、链码编译器任何一个版本不一致都会出现莫名其妙的连接错误。下面是我复现这类项目常用的版本组合组件版本建议说明Docker20.10 以上Fabric 所有节点跑在容器里旧版 Docker 兼容性问题多docker-compose1.29 以上网络编排依赖新版注意 compose v2 命令差异Go1.20 以上编译 Go 链码本地不一定装但建议装方便调试Node.js16 以上后端 SDK 和前端构建用Hyperledger Fabric2.4 LTS 或 2.5毕业设计优先 2.4资料最多docker 镜像稳定CouchDB配套镜像需要富查询时启用纯 LevelDB 可以不用完整装完这些再做一步镜像预拉取。Fabric 链码运行时要拉 fabric-ccenv、fabric-baseos 等镜像首次构建链码容器时如果网络慢经常超时导致「链码容器启动失败」。常见做法是提前手动 docker pull 对应 tag或者给 Docker 配置 registry mirror让后面 network.sh 执行时镜像秒级到位。4.2 启动测试网络并部署链码一条命令和它背后的四个阶段环境就绪后进入 fabric-samples 的 test-network 目录用官方脚本拉起网络。这套源码复现的是标准 Fabric 网络流程# 进入测试网络目录 cd fabric-samples/test-network # 清理可能残留的容器和卷避免端口冲突 ./network.sh down # 拉起网络并创建溯源通道-ca 参数表示使用内置CA服务 ./network.sh up createChannel -c tracechannel -ca # 部署Go链码指定通道、链码名、代码路径和语言 ./network.sh deployCC -c tracechannel -ccn tracecc \ -ccp ../chaincode/trace/go -ccl go \ -cccg OR(Org1MSP.member,Org2MSP.member)参数含义拆开说-c tracechannel 指定通道名通道名必须和后续 SDK 连接配置里的网络名一致-ccn tracecc 是链码名也是 SDK 里 getContract 的第一个参数-ccp 指向链码源码目录-ccl go 声明语言-cccg 是背书策略写成 OR 表示任一组织背书即可写成 AND 则要求双方都背书。network.sh 这条命令把链码生命周期的四个阶段全包了打包package、安装到每个组织的 Peerinstall、各组织批准approveformyorg、提交到通道commit。毕设如果被问到「链码是怎么部署的」手动走一遍这四个阶段能讲得更清楚# 阶段一打包链码 peer lifecycle chaincode package tracecc.tar.gz \ --path ../chaincode/trace/go --lang golang --label tracecc_1.0 # 阶段二安装到 peer0.org1输出会有一个 PACKAGE_ID peer lifecycle chaincode install tracecc.tar.gz # 阶段三org1 批准--package-id 必须填上一步的实际ID peer lifecycle chaincode approveformyorg \ -C tracechannel -n tracecc -v 1.0 \ --package-id tracecc_1.0:xxxxxxxx \ --sequence 1 --signature-policy OR(Org1MSP.member,Org2MSP.member) # 阶段四提交到通道commit 前会检查是否达到组织批准数要求 peer lifecycle chaincode commit -C tracechannel -n tracecc -v 1.0 \ --sequence 1 --peerAddresses peer0.org1.example.com:7051 \ --peerAddresses peer0.org2.example.com:9051四个阶段最常见的翻车点在第 2 到第 3 步之间install 输出的 PACKAGE_ID 必须完整复制给 approveformyorgID 中间是 label 加冒号加哈希少一位都不行。另外 --sequence 默认从 1 开始每次升级链码要递增同一个 sequence 重复提交会直接报错。deployCC 跑完没有报错不代表一定成功最后加一步验证用 peer 命令调用一次链码的查询方法能返回结果才算真部署好。4.3 后端通过 fabric-gateway 调用链码连接配置与代码模板网络跑通后后端要用 SDK 连进去。常用的是 fabric-networkfabric-gateway这个 Node.js 库连接配置读 connection-org1.json// gateway 连接模板Node.js fabric-network const { Wallets, Gateway } require(fabric-network); const path require(path); const fs require(fs); async function submitTrace(org, userId, args) { // 连接配置对应资源里的 connection-org1.json / connection-org2.json const ccpPath path.resolve(__dirname, .., config, connection-${org}.json); const ccp JSON.parse(fs.readFileSync(ccpPath, utf8)); // 钱包目录里面是 CA 签发的用户身份一个用户一个文件夹 const walletPath path.join(process.cwd(), wallet, userId); const wallet await Wallets.newFileSystemWallet(walletPath); const gateway new Gateway(); // asLocalhost: 本地开发环境用生产环境应改为 false 并配域名 await gateway.connect(ccp, { wallet, identity: userId, discovery: { enabled: true, asLocalhost: true } }); const network await gateway.getNetwork(tracechannel); const contract network.getContract(tracecc); try { // 写入类操作走 submitTransaction会走完整背书排序提交 const result await contract.submitTransaction( AddTrace, args.productId, args.recordId, args.action, args.operator, args.location, args.timestamp, args.remark ); return Buffer.from(result).toString(utf8); } finally { // 必须断开连接否则连接池耗尽后 gateway 会卡住 gateway.disconnect(); } }这里有两个关键选择写入用 submitTransaction查询用 evaluateTransaction两者都走 SDK 但语义完全不同。submitTransaction 要等交易排序、背书、提交完成才返回是同步语义evaluateTransaction 只在一个 Peer 上读状态快但可能是旧数据。刚才链码里 QueryHistory 是查询方法后端调用它就应该用 evaluateTransaction。还有 wallet 目录的问题。网关连接的 identity 参数必须是钱包里真实存在的用户这个用户由 fabric-ca-client enroll 生成。毕设复现时最常见的坑是换了环境后 connection json 里证书路径和钱包目录对不上SDK 报 identity not found。解决办法是重新执行一次 enroll 流程把新证书导入钱包。4.4 前端接入扫码查询页面的数据流前端部分相对简单核心是「商品码 - 后端接口 - 链码查询 - 时间线渲染」这条数据流。商品码建议直接把 productId 编码进二维码扫码后跳转查询地址后端接口透传这个 ID// 前端查询商品的简化逻辑Vue axios async function queryTrace(productId) { // 调用后端 REST 接口后端内部走 evaluateTransaction const { data } await axios.get(/api/trace/${productId}); const { product, traces } data.data; // 商品基本信息渲染到卡片 renderProduct(product); // 流转记录按业务时间升序渲染成时间线 const sorted upDown(traces, timestamp); // 升序排列 renderTimeline(sorted); }前端渲染时务必做一次按 Timestamp 的排序原因前面提过组合键扫描返回的记录顺序不是业务时间顺序。如果后端接口直接透传链码返回的 JSON不做排序时间线大概率是乱的。前端这一层不需要任何区块链知识它只是一个标准查询页真正的可信逻辑全部在链码和账本里演示时把这一点讲清楚评委会认可你对架构边界的理解。5. 部署复现避坑记录五个最容易卡住的点5.1 链码部署与背书阶段的坑坑一deployCC 明明没报错调用时却报 endorsement failure现象peer lifecycle chaincode invoke 或后端 submitTransaction 返回背书失败日志提示某个 Peer 上链码未安装。原因network.sh 的 deployCC 在 org1 和 org2 都执行了安装但如果是手动走四阶段流程很可能只 install 了 org1org2 的 Peer 上没有对应链码包或者两个组织安装的包 ID 不一致比如 label 写得不一致。解决在 org2 环境变量下重新执行 install再用 peer lifecycle chaincode queryinstalled 核对两个组织的 PACKAGE_ID 是否完全一致。以后手动部署时我一般会在 approveformyorg 之前加一步校验脚本把两边的 package ID 打出来人工比对。坑二链码容器启动失败日志报 context deadline exceeded现象deployCC 卡在链码容器构建阶段docker logs 显示连接 shim 超时容器一遍遍退避重启。原因Fabric 在部署链码时会用 fabric-ccenv 镜像现场构建链码容器首次构建需要从镜像仓库拉取基础镜像网络慢或镜像源不稳定就超时。解决提前 docker pull hyperledger/fabric-ccenv 和 fabric-baseos 对应 Fabric 版本的 tag或者给 Docker 配置 registry mirror。我现在的习惯是装完 Docker 第一件事就把这两个镜像拉好后面所有部署项目都会用到。5.2 数据、查询与身份的坑坑三CouchDB 富查询不生效或者查询返回慢得离谱现象启用 CouchDB 后链码里写了大字段范围查询但前端查一次要好几秒甚至直接超时。原因CouchDB 的富查询依赖 _design 文档里定义的索引索引文件要用 JSON 形式放在链码包的 META-INF/statedb/couchdb/indexes 目录下随链码一起打包安装。如果只是把索引写在 CouchDB 管理界面里链码查询时用的索引在另一个数据库实例上根本不会生效。解决确认链码目录里存在 META-INF/statedb/couchdb/indexes/xxx-index.json格式为字段名、类型和排序方向声明。重新打包安装链码后索引才会自动创建。如果项目用的默认 LevelDB这个坑不存在但也没有范围查询能力。坑四溯源时间线顺序混乱多条记录时间相同现象前端渲染出来的流转记录忽前忽后同一条商品在上午、下午各录入一条记录但排序结果不对。原因组合键扫描返回的记录按键排序不是按业务时间排序或者录入时用了节点本地时间背书节点间时钟偏差导致同一笔交易在不同副本上时间不一致。解决链码强制 Timestamp 必填且由调用方传入前端渲染时按 Timestamp 升序、相同时间再按 RecordID 排序。我改这类项目时还会在后端接口层加一道排序逻辑不依赖前端自觉。坑五gateway 连接报 signature verification failed 或 identity not found现象后端启动后第一次调用链码就报签名验证失败但连接配置看起来没问题。原因钱包目录里的用户身份和 connection json 指定的 MSP、证书路径不匹配最常见是换了机器后没有重新 enroll或者把测试网络的旧证书复制到了新环境。解决用 fabric-ca-client enroll 重新生成用户证书导入钱包目录核对 connection json 里的 mspid 和证书路径。复现这类资源时每次换环境都强制重走一遍 CA enroll不要图省事复用旧证书。6. 把「通用溯源」改成你自己的毕设亮点验证手段与扩展方向资源跑通只是及格线高分的关键是让评委看到你「理解并能扩展」。这里给三个低成本、高收益的扩展方向。第一个是召回场景。农产品出现质量问题时需要把批次状态改成「已召回」。在链码里加一个 RecallProduct 方法把商品 Status 改为已召回、写入召回原因前端对召回商品显示醒目标记。这个改动只涉及一个方法加一个状态值但能让答辩故事从「能溯源」升级到「能闭环处置」明显更有业务完整度。第二个是防篡改证明。前面提过主记录用 GetHistoryForKey 能拿到每个历史版本做成前端对比视图展示某条商品信息从登记到现在的全部变更。这是评委最爱问的「你怎么证明数据没被改过」的标准答案来源// 用 GetHistoryForKey 取出商品主记录的全部历史版本 resultsIterator, err : ctx.GetStub().GetHistoryForKey(productID) // 迭代返回的每个版本包含 value、txId、timestamp可还原变更时间线第三个是性能数据。用 Hyperledger Caliper 对链码做一轮读写压测把 TPS、延迟数据做成图表放进论文或设计文档。Fabric 2.4 在单机环境跑出每秒几十到上百的写入 TPS 是正常的这个数字在毕设层面足够有说服力而且它证明你不只写了代码还验证过系统的边界。我拿到这种通用溯源资源不会直接交上去而是先跑通基线再改一个字段、加一个流程、跑一轮压测把「搬运」变成「理解后的改造」。从那以后我每次复现 Fabric 毕设资源都强制走一遍「跑通基线 - 改字段 - 加流程 - 压测出数」这四步再进答辩。这套源码和文档够完整按第 4 章的顺序走三天内能把 demo 跑起来剩下两周足够做完扩展和验证。希望帮到你。本文还有配套的精品资源点击获取
返回列表