基于区块链的安全文件共享系统:哈希上链、权限管理与密钥信封实践 简介基于区块链的安全文件共享系统毕业设计完整源码包面向软件工程、计科、人工智能等计算机相关专业学生、教师或开发者。项目聚焦文件共享中的可信存储与权限审计实现节点身份认证、加密通信与文件分发逻辑源码获导师认可答辩评审95分以上可本地编译运行适合课设、毕设或初期立项演示。资源共286个文件以Go源码为主112个go文件覆盖区块链核心与网络模块配套66个PEM证书、28个CRT证书及14个Key密钥用于节点身份与安全通信另有19个YAML配置文件、地址与合约说明等整包仅349KB结构清晰。当前已有86人浏览学习。除完整源码外还附带详细文档和全部配套资料便于理解项目架构和实现细节可按模块进阶学习也可在现有基础上二次开发搭建自己的区块链文件共享项目。1. 基于区块链的安全文件共享系统到底在做什么先把答题方向定对“基于区块链的安全文件共享系统”这个毕业设计题目在高校里出现频率很高但大多数同学最后交出来的东西是一个能上传下载文件的普通网站区块链只是挂了个名字。真正能拿高分、能扛住答辩追问的设计核心思路其实很简单文件本体离链文件指纹与共享权限记录上链。链上存哈希和访问控制记录链下用加密存储托管文件内容这样既满足“基于区块链”的硬性要求又用最小的成本获得了可审计、难篡改的文件流转记录。这篇文章面向两类人一是需要把这个课题做成可演示作品的学生二是想知道文件共享类 DApp 权限模型和存证链路怎么落地的开发者。我会把链选型、链码设计、加解密流程、SDK 接入和五个高频翻车点一次讲透照着走就能跑出一套拿得出手的演示系统。2. 系统架构与链选型联盟链还是公链文件本体到底放哪这个课题的第一个决策点不是写代码而是定架构。很多同学一上来就研究 IPFS 集群、分布式存储结果一个月过去连一条交易都没上过链。先想清楚“链上存什么、链下存什么”后面的开发才有方向。2.1 文件指纹上链、文件本体离链这个取舍决定了答辩能不能过区块链不适合存大文件这是一个工程事实不是偷懒借口。Fabric 的区块有大小限制一个区块塞几个 MB 的文件就会拖慢整个网络的排序和背书过程以太坊上存大文件更是需要支付高昂的 gas。更关键的其实是隐私问题文件内容一旦上链所有通道内的节点都能看到这和“安全共享”的目标直接矛盾。所以安全文件共享系统的标准解法是把数据分成两层。链上只保存文件的 SHA-256 哈希、文件元数据、上传者身份、时间戳和访问控制列表链下保存密文本身可以放在服务器磁盘、对象存储或者 IPFS。文件在加密后存储密钥再做一层封装。我把三种方案放在一起对比设计方案链上存放内容优点代价答辩风险全文上链文件完整内容不可篡改性最强性能差、隐私差、成本高被问“这还能叫安全共享”哈希元数据上链文件哈希、大小、时间戳、上传者查询快、成本低、能验证完整性无法从链上恢复文件被问“文件丢了怎么办”哈希ACL密钥信封上链哈希、权限记录、加密后的会话密钥兼顾完整性校验和访问控制是完整方案需要设计加解密链路防御完整最稳我一般会推荐第三种。它把区块链的核心价值用在了刀刃上哈希用来做防篡改校验ACL 用来记录谁有权访问密钥信封用来让密文只能被指定接收方解开。文件内容始终以密文形态保存在链下即使有人拖走数据库也拿不到原文。顺带说一句很多毕设论文会把“去中心化存储”写进摘要但实现里根本没有 IPFS。这本身不是大问题只要论文里写清楚“文件密文存储于可信服务器链上存哈希与权限记录”架构自洽就行。答辩老师更看重的是你有没有想清楚为什么这么分层。2.2 链选型对比Fabric、Ganache 模拟链、自研轻量链别再纠结链上存什么是架构决策选哪条链是技术决策。毕业设计里最常见的选项有三个Hyperledger Fabric、以太坊本地用 Ganache 模拟、自研轻量链。我用一个实际项目里总结过的对比表说话对比维度Hyperledger Fabric以太坊 GanachePython 自研轻量链环境搭建成本高需要 Docker、证书体系低一条命令起本地链最低无外部依赖语言生态Go/Node 链码 Python SDKSolidity web3.pyPython 自写权限模型原生支持 MSP、通道隔离需要自己实现 ACL全部自己写与“安全文件共享”贴合度高联盟链天生适合授权场景中公链语义偏开放取决于实现答辩说服力强行业标准中弱容易被追问一致性翻车概率高依赖和版本坑多低低但真实性存疑如果时间和精力允许首选 Fabric。原因很直接文件共享系统的核心诉求是“谁能访问什么”Fabric 的 MSP成员服务提供者和通道机制本身就是干这个的你在答辩时可以说“权限由链的成员管理机制保证”这句话是有分量的。如果只剩两三个星期Ganache web3.py 是更稳妥的选择Solidity 合约写 ACL 也不复杂。至于自研轻量链可以作为“区块链原理展示”的加分项但不建议作为系统的唯一链——因为答辩老师一句“你的链和 MySQL 有什么区别”很难回答。我自己做这个课题时选的是 Fabric 2.2 Go 链码 Python 后端原因是 Fabric 的背书、排序、通道机制凑齐了一整套“企业级”术语论文和答辩 PPT 都会好写很多。下面这个最小网络就是我当时精简出来的。2.3 用 Fabric 2.x 起一个最小网络三个容器的 docker-compose 就能跑通Fabric 官方提供 test-network 脚本但那个脚本对毕设来说太啰嗦里面塞了 raft 集群、双组织、CA 服务一大堆东西。我习惯把手头网络的 docker-compose 精简到三个服务一个 CA、一个 Orderer、一个 Peer单组织单通道足够跑通上链和查询。version: 2.4 services: ca: image: hyperledger/fabric-ca:1.5.2 environment: - FABRIC_CA_HOME/etc/hyperledger/fabric-ca-server - FABRIC_CA_SERVER_CA_NAMEca.org1.example.com - FABRIC_CA_SERVER_PORT7054 ports: - 7054:7054 command: sh -c fabric-ca-server start -b admin:adminpw -d orderer: image: hyperledger/fabric-orderer:2.2.2 environment: - FABRIC_CFG_PATH/etc/hyperledger/fabric - ORDERER_GENERAL_LISTENADDRESS0.0.0.0 - ORDERER_GENERAL_LISTENPORT7050 - ORDERER_GENERAL_LOCALMSPIDOrdererMSP - ORDERER_GENERAL_TLS_ENABLEDfalse ports: - 7050:7050 peer: image: hyperledger/fabric-peer:2.2.2 environment: - CORE_PEER_IDpeer0.org1.example.com - CORE_PEER_ADDRESSpeer0.org1.example.com:7051 - CORE_PEER_LOCALMSPIDOrg1MSP - CORE_PEER_TLS_ENABLEDfalse - CORE_PEER_CHAINCODEADDRESSpeer0.org1.example.com:7052 ports: - 7051:7051 - 7052:7052这里有一个容易翻车的地方Fabric 2.x 的 peer 和 chaincode 是分离的CORE_PEER_CHAINCODEADDRESS指向的是链码容器的地址。如果这个配置不对启动链码时你会在 peer 日志里看到Error starting container一类的报错后面第 5 章会展开讲。这个精简网络的启动顺序是固定的先生成证书和创世区块用cryptogen和configtxgen然后通过configtxlator创建通道再依次启动 CA、Orderer、Peer。证书和创世区块的生成命令属于 Fabric 环境里的固定动作在这里不展开但有一点要记住整套 docker-compose 里的image标签必须和证书生成的版本严格一致比如都是 2.2.x混用 2.2 和 2.5 的镜像基本起不来。启动完之后用docker ps看三个容器是不是都在运行再用docker exec peer peer channel list确认 Peer 已经加入了通道。这一步通了才轮到写链码和 SDK。3. 核心链路落地上传、加密、上链一条命令走完的完整流程网络跑通之后开始写业务链路。整个系统的核心流程是用户上传文件 → 后端加密文件 → 计算文件哈希 → 调用链码把元数据写入区块链 → 返回结果给前端。这一章把每一步的关键代码和参数讲透你拿到手可以直接抄。3.1 文件分片与加密大文件用 AES-CTR 而不是一次性读进内存文件加密我推荐用 AES-256-CTR 模式搭配独立校验。为什么不直接用 GCMGCM 虽然自带认证但要求整个加密过程使用同一个 nonce大文件一旦分片处理每个分片用同一个 nonce 会产生严重的安全问题。CTR 模式没有这个限制同一个加密器对象可以连续 update 多个分片内部会维护计数器。完整性校验交给 SHA-256反正哈希本来也要上链一举两得。import os import hashlib from Crypto.Cipher import AES from Crypto.Util import Counter def encrypt_file_ctr(in_path: str, out_path: str, aes_key: bytes) - str: # aes_key 长度为 32 字节对应 AES-256 iv_int int.from_bytes(os.urandom(16), big) ctr Counter.new(128, initial_valueiv_int) cipher AES.new(aes_key, AES.MODE_CTR, counterctr) sha256 hashlib.sha256() chunk_size 4 * 1024 * 1024 # 4MB 分片避免大文件占满内存 with open(in_path, rb) as f_in, open(out_path, wb) as f_out: # IV 写入文件头部解密时需要用到 f_out.write(iv_int.to_bytes(16, big)) while True: chunk f_in.read(chunk_size) if not chunk: break sha256.update(chunk) f_out.write(cipher.encrypt(chunk)) return sha256.hexdigest()代码里三个关键决策值得说明。第一iv_int是 16 字节随机数写入密文文件头部解密时读取前 16 字节即可恢复计数器初始值不需要额外传参。第二Counter.new(128, initial_valueiv_int)的 initial_value 必须按 128 位计数器来理解如果和 nonce 混用会很麻烦。第三SHA-256 是对明文分片计算的这个哈希值稍后要作为文件指纹上链解密后重新计算一次比对一致就说明文件没有被篡改。加密完成后aes_key 不能直接落库。这个密钥是会话级的每个文件生成一个使用完要做密钥封装第 4 章专门讲。这里先记住一点文件密文放在链下存储目录任何接口都不要直接返回明文路径统一走解密接口。3.2 链码设计文件注册、权限授予、共享记录查询链码是整个系统的合约层。用 Go 写链码在 Fabric 里是最主流的选择部署简单、性能好。下面这个链码覆盖了文件注册、授权访问、按文件 ID 查询记录三个核心方法足够支撑整个 demo。package main import ( encoding/json fmt time github.com/hyperledger/fabric-contract-api-go/contractapi ) type FileMeta struct { FileID string json:file_id Owner string json:owner Hash string json:hash Size int64 json:size EncRef string json:enc_ref // 链下密文存储地址 Envelope string json:envelope // 密钥信封base64 Timestamp int64 json:timestamp } type FileContract struct { contractapi.Contract } // 上传文件写入文件元数据 func (fc *FileContract) UploadFile(ctx contractapi.TransactionContextInterface, fileID string, owner string, hash string, size int64, encRef string, envelope string) error { ts : time.Now().Unix() meta : FileMeta{ FileID: fileID, Owner: owner, Hash: hash, Size: size, EncRef: encRef, Envelope: envelope, Timestamp: ts, } metaBytes, _ : json.Marshal(meta) // 复合主键owner fileID避免不同用户上传同名文件互相覆盖 key, _ : ctx.GetStub().CreateCompositeKey(file, []string{owner, fileID}) return ctx.GetStub().PutState(key, metaBytes) } // 授予访问权限允许 receiver 读取 fileID 指向的文件 func (fc *FileContract) GrantAccess(ctx contractapi.TransactionContextInterface, owner string, fileID string, receiver string) error { // 权限记录的主键是 owner fileID receiver谁授权给谁一目了然 aclKey, _ : ctx.GetStub().CreateCompositeKey(acl, []string{owner, fileID, receiver}) return ctx.GetStub().PutState(aclKey, []byte(granted)) } // 查询文件元数据接收方在下载前调用 func (fc *FileContract) QueryFile(ctx contractapi.TransactionContextInterface, owner string, fileID string) (*FileMeta, error) { key, _ : ctx.GetStub().CreateCompositeKey(file, []string{owner, fileID}) data, err : ctx.GetStub().GetState(key) if err ! nil || data nil { return nil, fmt.Errorf(file not found) } var meta FileMeta json.Unmarshal(data, meta) return meta, nil }三个函数对应三种链上操作设计上有一个容易被忽略的细节CreateCompositeKey的用法。所有键都用“owner 开头”保证同一个人名下的文件在 LevelDBFabric 默认状态数据库里是相邻排布的将来做“查询我上传的所有文件”时可以直接用范围查询不需要遍历全网状态。权限记录用granted字符串做值虽然没有业务数据要存但保持这个字段存在后续审计和溯源时就可以通过查询历史来还原“谁在什么时候授予了谁权限”——这就是第 4 章的审计链路基础。部署链码时安装和实例化各有一条命令其中最需要注意的是背书策略。默认策略是AND(Org1MSP.peer)对于单组织网络就够了。如果将来扩展了组织记得改成AND(Org1MSP.peer,Org2MSP.peer)否则只有单个 peer 背书链码依然能跑但“多组织背书”这句论文表述就不成立了。3.3 用 fabric-sdk-py 把上传接口接到链上Fabric 2.x 的官方 Python SDK 是fabric-sdk-py它依赖 gRPC 调用 peer 和 orderer整个调用链要先配置网关再提交交易。下面是我常用的最小调用代码。from hfc.fabric import Client def submit_upload(file_id: str, owner: str, file_hash: str, file_size: int, enc_ref: str, envelope: str) - str: # 读取网络配置net_profile.yaml 里包含 channel 名、peer 地址、MSP 信息 client Client(net_profile./config/net_profile.yaml) channel client.get_channel(mychannel) request { chaincode_id: fileshare, fcn: UploadFile, args: [file_id, owner, file_hash, str(file_size), enc_ref, envelope], } # 提交后等待交易被打包进区块返回交易哈希 tx_id channel.send_transaction(request, peers[peer0.org1.example.com:7051]) return tx_id这段代码有个前提调用方必须已经通过 CA 注册并被加入组织MSP身份信息需要提前导入 wallet。很多同学在这一步卡住报错信息是Failed to connect或getting endorser client connection原因往往不是代码问题而是 wallet 里的证书和当前 peer 的 MSP ID 对不上。调试时先看net_profile.yaml里的msp_id是不是Org1MSP再看 wallet 目录下的身份文件是不是从同一个 CA 签发的。SDK 提交交易是异步的send_transaction返回的交易 ID 只代表交易已提交不代表已上链。要做“确认上链”的反馈需要监听区块事件或者在返回后主动查询一次QueryFile来确认状态。毕设演示时前端展示“上传成功”的时机应该放在查询确认之后否则会出现文件上传成功但链上查不到的尴尬场景。4. 共享与权限校验接收方如何拿文件、系统如何证明他有权文件上传只是前半段这个系统的真正难点在共享。你把自己的文件授权给别人接收方下载时系统要能证明两件事第一这个人确实被授权了第二他拿到的密文确实能解开、没被篡改。这一章讲完整的密钥信封和下载链路。4.1 密钥信封用接收方公钥加密 AES 会话密钥我在 3.1 里留下了一个伏笔每个文件都有一个独立的 AES 会话密钥。那这个密钥怎么安全地交给接收方直接明文发送等于把门锁钥匙贴在门上。标准做法是密钥信封上传时用接收方或所有被授权者的公钥加密 AES 密钥把加密结果存到链上或随共享记录分发。接收方用自己的私钥解开信封拿到 AES 密钥才能解文件密文。from Crypto.Cipher import PKCS1_OAEP from Crypto.PublicKey import RSA def build_envelope(aes_key: bytes, receiver_public_key_pem: str) - str: pub_key RSA.import_key(receiver_public_key_pem) cipher PKCS1_OAEP.new(pub_key) # aes_key 是 32 字节PKCS1_OAEP 一次可以封装不需要分片 envelope cipher.encrypt(aes_key) return base64.b64encode(envelope).decode()注意两个参数RSA 公钥建议使用 2048 位以上密钥信封是一次性的——同一个文件的 AES 密钥如果需要共享给多个接收方就要为每个接收方分别生成信封而不是所有人用同一个。这样权限撤销时只需要删除链上对应的 ACL 记录不影响其他接收方。信封放在哪里有两种常见做法。一种是把 envelope 直接写进FileMeta意味着每个文件只有一个信封只服务上传者自己另一种是把 envelope 和 ACL 绑定每个被授权者一个信封存在链上的扩展记录里。后者更合理但也意味着链码要加一个StoreEnvelope方法。毕设阶段如果不想加复杂度可以把 envelope 设计成“每个接收方动态生成通过链码的共享记录存入”代码量增加不大但论文里可以多写一节“基于属性的密钥分发”。4.2 下载链路先查链上 ACL再解密文件最后校验哈希下载接口的逻辑是四条顺序执行的步骤查链上 ACL 确认权限 → 读取链下密文 → 用接收方私钥解信封拿 AES 密钥 → 解密密文并重新计算 SHA-256 与链上哈希比对。任何一步失败都直接拒绝返回明文。def download_file(file_id: str, owner: str, receiver: str, receiver_private_key_pem: str) - bytes: # 步骤 1查询链上权限记录 acl_valid query_acl(ownerowner, file_idfile_id, receiverreceiver) if not acl_valid: raise PermissionError(no access right on chain) # 步骤 2读取文件元数据和密文 meta query_file_meta(ownerowner, file_idfile_id) ciphertext_path resolve_path(meta[enc_ref]) iv read_iv_from_head(ciphertext_path) aes_key decrypt_envelope(meta[envelope], receiver_private_key_pem) # 步骤 3AES-CTR 解密同时重新计算明文哈希 sha256 hashlib.sha256() ctr Counter.new(128, initial_valueint.from_bytes(iv, big)) cipher AES.new(aes_key, AES.MODE_CTR, counterctr) plain_chunks [] with open(ciphertext_path, rb) as f: f.read(16) # 跳过 IV 头 while True: chunk f.read(4 * 1024 * 1024) if not chunk: break plain cipher.decrypt(chunk) sha256.update(plain) plain_chunks.append(plain) # 步骤 4哈希比对不一致说明密文或密钥不对 if sha256.hexdigest() ! meta[hash]: raise ValueError(file hash mismatch, file may be tampered) return b.join(plain_chunks)这段代码把“链上验证”和“链下解密”串成了一条完整的证据链。其中最容易做错的点是meta[envelope]的解密时机不要在查询链码时就解密一定要等 ACL 验证通过之后再解否则密钥信封就失去意义了。哈希校验失败时抛出的异常要返回给前端展示“校验失败”的状态这正是答辩演示里最有冲击力的环节。4.3 审计存证共享记录查询接口文件共享系统的加分项在审计。用 Fabric 的GetHistoryForKey可以拿到某个键的全部历史值包括每次写入的交易 ID、写入时间、是否是删除标记。这意味着可以完整还原一个文件的全部生命周期。func (fc *FileContract) QueryFileHistory(ctx contractapi.TransactionContextInterface, owner string, fileID string) ([]HistoryRecord, error) { key, _ : ctx.GetStub().CreateCompositeKey(file, []string{owner, fileID}) it, err : ctx.GetStub().GetHistoryForKey(key) if err ! nil { return nil, err } defer it.Close() var records []HistoryRecord for it.HasNext() { mod, _ : it.Next() records append(records, HistoryRecord{ TxID: mod.TxId, Value: string(mod.Value), IsDelete: mod.IsDelete, Time: mod.Timestamp.String(), }) } return records, nil }这个接口的应用场景是做前端审计页把每个文件的“创建时间、授权操作、哈希变更”按时间线展示出来。论文里的“基于区块链的文件共享审计机制”这一节用一个截图就能说清楚。注意GetHistoryForKey的返回值是二进制的大端格式Time字段需要转换成可读字符串否则页面展示出来是一串乱码。Fabric 2.x 默认会保留所有历史状态不需要额外开配置这也是它比自研轻量链更适合审计展示的原因。5. 避坑指南开发区块链文件共享系统最常见的五个翻车点这五个坑是我自己从零跑通这个课题时真实踩过的每一条都花掉了至少一个晚上排查。写出来希望你能绕开。5.1 Fabric 网络起不来先查版本对齐别怀疑人品现象docker-compose up之后 Peer 容器不断重启日志刷Failed to reach consensus或者channel config mismatchOrderer 容器正常但 Peer 就是加入不了通道。原因几乎都是版本错配。Fabric 的 Peer、Orderer、CA、SDK、链码编译器的版本必须严格一致。最常见的是 docker-compose 里写了hyperledger/fabric-peer:latest而本地的 configtxlator 是 2.2.x 生成的创世区块双方不兼容另一个常见问题是 Go 链码的依赖版本和 Fabric 的链码接口版本对不上。解决把 compose 里所有镜像锁定到具体版本号比如2.2.2生成证书和创世区块的工具也用同一版本。确认无误后删掉全部容器和旧卷docker-compose down -v重新生成创世区块再启动。Fabric 的清理流程里-v必须带上否则旧的状态数据库残留会让新网络起不来。5.2 fabric-sdk-py 连接失败MSP、通道名、peer 端口三个参数缺一不可现象调用send_transaction时报Failed to connect或EndorseError但 docker 里 peer 明明活着。原因net_profile.yaml里的msp_id配置错误是最常见的一个点。Peer 的 MSP 是Org1MSPSDK 里对应的却是Org1MSP的别名或者拼写错误另一个高发原因是 SDK 默认走 7053 端口旧版 gossip 端口而 Fabric 2.x 的 peer 监听是 7051。还有一个坑wallet 里导入的身份证书必须来自 peer 所属的同一个 CA否则 TLS 握手直接失败。解决先单独写一个简单的query脚本只调QueryFile不提交交易测试连接确认连接成功后再写提交交易的逻辑。这样能把网络问题和业务问题分开排查。端口检查用docker port peer命令看真实映射不要相信 yaml 里写的端口。5.3 把整个文件塞进链码交易交易过大直接翻车现象上传一个小视频调用链码后send_transaction长时间无响应最后返回REQUEST_TIMEOUT或tx too largePeer 日志里出现Message size too large。原因代码里把文件读出来 base64 编码后塞进了链码参数。Fabric 对交易消息有默认大小限制默认约 100MB 是网络层限制但实际背书和排序性能在几 MB 时就会明显劣化而且链码会把参数复制到状态数据库几十 MB 的数据写入会让区块几乎无法同步。解决回到 2.1 的设计链上只传哈希和元数据文件本体进链下存储目录。如果确实需要链上证明“某人在某时刻上传了某文件”哈希就是足够的证据——别人在链下拿到文件重新计算哈希一比对真伪立现。这也是答辩时最有力的“区块链价值”展示点。5.4 链上的“权限记录”被覆盖把链码当成 MySQL 表在写现象GrantAccess 之后再调用一次 GrantAccess旧记录“消失”了查历史能看到两条记录但查当前状态只有第二条更有同学直接在链码里写PutState更新文件哈希历史里能看到旧哈希但系统状态只保留最新值。原因对区块链不可篡改的理解有偏差。区块链的不可篡改指的是“全局账本的历史记录不可篡改”但 Fabric 的 World State世界状态是可覆盖的PutState同一个键就是覆盖写入。解决把链码设计成“只追加”的权限授予用新增的复合键每个 receiver 一个键文件元数据一旦创建就不允许更新哈希字段。如果业务上必须更新比如重新上传新版本文件不要改旧键而是创建一个新版本键file_v2让历史记录自然保留。这样审计查询才能还原完整变更过程。5.5 答辩被问“这和 MySQL 加文件系统有什么区别”没提前准备对比现象答辩演示很顺利但老师问了一句“你这个系统的安全性用 MySQL 存哈希和权限记录也能实现吧”当场卡住。原因论文里写了区块链但功能设计和普通 Web 系统没有本质区别没有突出“多方共识 全局审计”的价值。解决提前准备两个对比维度。第一篡改检测MySQL 里管理员可以直接 UPDATE 哈希字段系统没有任何感知Fabric 里修改一个键需要经过背书、排序、提交且历史版本全部保留篡改行为会被审计记录暴露。第二溯源能力一次文件共享记录在 MySQL 里是普通的一行数据删了就没了在 Fabric 里GetHistoryForKey能还原每一次操作。答辩演示时现场演示“篡改文件哈希后下载校验失败”和“查看历史记录还原授权过程”比背十页论文都管用。6. 从“跑通”到“能答辩”怎么证明你的系统真的安全系统功能都实现之后还要过最后一关用数据证明系统可用、安全。我习惯在答辩前做一轮并发上传验证和篡改检测验证把结果整理成一张表格放进论文附录。import threading import time import requests def upload_worker(url: str, stop_event: threading.Event, latencies: list): while not stop_event.is_set(): t0 time.time() # 实际请求体按你的上传接口调整这里省略文件数据 resp requests.post(url, files{file: (test.bin, b0 * 1024 * 1024)}) latencies.append(time.time() - t0) stop threading.Event() lats [] threads [threading.Thread(targetupload_worker, args(http://localhost:8080/api/upload, stop, lats)) for _ in range(10)] for t in threads: t.start() time.sleep(60) # 压测 60 秒 stop.set() for t in threads: t.join() success len([x for x in lats if x 10]) # 10 秒内返回视为成功 print(ftotal requests: {len(lats)}, success: {success}, avg latency: {sum(lats) / len(lats):.2f}s)这个脚本用 10 个线程并发上传 1MB 文件跑 60 秒统计成功率和平均时延。Fabric 这种单 peer 网络的 TPS 本来就不会很高平均时延在 2~5 秒都属正常关键是表现“稳定不报错”。把结果整理成“并发数、请求总数、成功率、平均时延”四列表格写进论文的性能分析一节比“系统性能良好”六个字有说服力得多。篡改检测的演示步骤我建议固定成三条上传一个文件并记录链上哈希 → 在链下存储目录里手工修改密文文件的某个字节 → 重新触发下载接口观察哈希校验失败的系统提示。这一套流程走完整个系统的“安全”就不再是概念而是可以当场复现的事实。最后说一个我自己的习惯做这类毕设系统我会先在本地把“篡改检测失败”的截图存好因为答辩现场的突发状况很多——有时候网络卡了、有时候 Docker 没起来、有时候链码容器被系统回收。提前把关键环节的截图放进 PPT现场演示失败时也有退路。这套系统的价值不在于用了多前沿的技术而在于你让“不可篡改”和“授权审计”变成了可验证的交互体验。如果你正卡在这个课题上先把文件离链、哈希上链、密钥信封这三件事想清楚再做代码路会顺很多。希望帮到你。本文还有配套的精品资源点击获取