在上一篇文章中 [《Hyperledger Fabric 2.x 自定义智能合约》] 分享了智能合约的安装并使用 cli
客户端进行合约的调用;本文将使用 Java
代码基于 fabric-gateway-java
进行区块链网络的访问与交易,并集成 SpringBoot
框架。
Fabric Gateway SDK
实现Fabric的编程模型,提供了一系列简单的API给应用程序与Fabric区块链网络进行交互;
网络拓扑图:
应用程序将各自的网络交互委托给其网关,每个网关都了解网络信道拓扑,包括组织的多个Peer节点和排序节点,使应用程序专注于业务逻辑;Peer节点可以使用gossip协议在组织内部和组织之间相互通信。
添加网关sdk的依赖:
<dependency> <groupId>org.hyperledger.fabric</groupId> <artifactId>fabric-gateway-java</artifactId> <version>2.2.3</version> </dependency>
工程的目录结构如下图所示:
创建目录 crypto-config
把 orderer
和 peer
节点的证书文件复制进来。
证书文件从 fabric-samples
的 test-network
目录中复制 ordererOrganizations
与 peerOrganizations
文件夹:
创建文件 connection.json
内容如下:
{ "name": "basic-network", "version": "1.0.0", "client": { "organization": "Org1", "connection": { "timeout": { "peer": { "endorser": "300" }, "orderer": "300" } } }, "channels": { "mychannel": { "orderers": [ "orderer.example.com" ], "peers": { "peer0.org1.example.com": { "endorsingPeer": true, "chaincodeQuery": true, "ledgerQuery": true, "eventSource": true }, "peer0.org2.example.com": { "endorsingPeer": true, "chaincodeQuery": true, "ledgerQuery": true, "eventSource": true } } } }, "organizations": { "Org1": { "mspid": "Org1MSP", "peers": [ "peer0.org1.example.com" ], "certificateAuthorities": [ "ca-org1" ], "adminPrivateKeyPEM": { "path": "src/main/resources/crypto-config/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/keystore/priv_sk" }, "signedCertPEM": { "path": "src/main/resources/crypto-config/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/signcerts/Admin@org1.example.com-cert.pem" } }, "Org2": { "mspid": "Org2MSP", "peers": [ "peer0.org2.example.com" ], "certificateAuthorities": [ "ca-org2" ], "adminPrivateKeyPEM": { "path": "src/main/resources/crypto-config/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp/keystore/priv_sk" }, "signedCertPEM": { "path": "src/main/resources/crypto-config/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp/signcerts/Admin@org2.example.com-cert.pem" } } }, "orderers": { "orderer.example.com": { "url": "grpcs://192.168.28.134:7050", "mspid": "OrdererMSP", "grpcOptions": { "ssl-target-name-override": "orderer.example.com", "hostnameOverride": "orderer.example.com" }, "tlsCACerts": { "path": "src/main/resources/crypto-config/ordererOrganizations/example.com/orderers/orderer.example.com/tls/ca.crt" }, "adminPrivateKeyPEM": { "path": "src/main/resources/crypto-config/ordererOrganizations/example.com/users/Admin@example.com/msp/keystore/priv_sk" }, "signedCertPEM": { "path": "src/main/resources/crypto-config/ordererOrganizations/example.com/users/Admin@example.com/msp/signcerts/Admin@example.com-cert.pem" } } }, "peers": { "peer0.org1.example.com": { "url": "grpcs://192.168.28.134:7051", "grpcOptions": { "ssl-target-name-override": "peer0.org1.example.com", "hostnameOverride": "peer0.org1.example.com", "request-timeout": 120001 }, "tlsCACerts": { "path": "src/main/resources/crypto-config/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt" } }, "peer0.org2.example.com": { "url": "grpcs://192.168.28.134:9051", "grpcOptions": { "ssl-target-name-override": "peer0.org2.example.com", "hostnameOverride": "peer0.org2.example.com", "request-timeout": 120001 }, "tlsCACerts": { "path": "src/main/resources/crypto-config/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt" } } }, "certificateAuthorities": { "ca-org1": { "url": "https://192.168.28.134:7054", "grpcOptions": { "verify": true }, "tlsCACerts": { "path": "src/main/resources/crypto-config/peerOrganizations/org1.example.com/ca/ca.org1.example.com-cert.pem" }, "registrar": [ { "enrollId": "admin", "enrollSecret": "adminpw" } ] }, "ca-org2": { "url": "https://192.168.28.134:8054", "grpcOptions": { "verify": true }, "tlsCACerts": { "path": "src/main/resources/crypto-config/peerOrganizations/org2.example.com/ca/ca.org2.example.com-cert.pem" }, "registrar": [ { "enrollId": "admin", "enrollSecret": "adminpw" } ] } } }
需按实际情况修改url中的地址,内容中分别包含了
channels
、organizations
、orderers
、peers
、ca
的配置
在 application.yml
中添加以下内容,用于访问网关的相关配置:
fabric: # wallet文件夹路径(自动创建) walletDirectory: wallet # 网络配置文件路径 networkConfigPath: connection.json # 用户证书路径 certificatePath: crypto-config/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp/signcerts/User1@org1.example.com-cert.pem # 用户私钥路径 privateKeyPath: crypto-config/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp/keystore/priv_sk # 访问的组织名 mspid: Org1MSP # 用户名 username: user1 # 通道名字 channelName: mychannel # 链码名字 contractName: mycc
分别构建网关、通道和合约的Bean对象,代码如下:
/** * 连接网关 */ @Bean public Gateway connectGateway() throws IOException, InvalidKeyException, CertificateException { //使用org1中的user1初始化一个网关wallet账户用于连接网络 Wallet wallet = Wallets.newFileSystemWallet(Paths.get(this.walletDirectory)); X509Certificate certificate = readX509Certificate(Paths.get(this.certificatePath)); PrivateKey privateKey = getPrivateKey(Paths.get(this.privateKeyPath)); wallet.put(username, Identities.newX509Identity(this.mspid, certificate, privateKey)); //根据connection.json 获取Fabric网络连接对象 Gateway.Builder builder = Gateway.createBuilder() .identity(wallet, username) .networkConfig(Paths.get(this.networkConfigPath)); //连接网关 return builder.connect(); } /** * 获取通道 */ @Bean public Network network(Gateway gateway) { return gateway.getNetwork(this.channelName); } /** * 获取合约 */ @Bean public Contract contract(Network network) { return network.getContract(this.contractName); }
创建controller类,注入Contract对象调用合约方法:
@Resource private Contract contract; @Resource private Network network; @GetMapping("/getUser") public String getUser(String userId) throws ContractException { byte[] queryAResultBefore = contract.evaluateTransaction("getUser",userId); return new String(queryAResultBefore, StandardCharsets.UTF_8); } @GetMapping("/addUser") public String addUser(String userId, String userName, String money) throws ContractException, InterruptedException, TimeoutException { byte[] invokeResult = contract.createTransaction("addUser") .setEndorsingPeers(network.getChannel().getPeers(EnumSet.of(Peer.PeerRole.ENDORSING_PEER))) .submit(userId, userName, money); String txId = new String(invokeResult, StandardCharsets.UTF_8); return txId; }
调用接口 getUser
:
http://127.0.0.1:9001/getUser?userId=1
返回:
{ "money": 300, "name": "zlt", "userId": "1" }
调用接口 addUser
:
http://127.0.0.1:9001/addUser?userId=6&userName=test6&money=600
返回:
2ae291bb6a366b5ba01ad49e4237da8def9e9828cc2c982e8c49d4b763af0157
gitee:https://gitee.com/zlt2000/my-fabric-application-java
github:https://github.com/zlt2000/my-fabric-application-java