四、进阶开发
采用springboot方式构建app测试
4.1、阶段一
最简单的目录结构如下,其中配置文件的组织来源fabric的test-network
resource目录下的配置文件如下:
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>app-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>app-demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.3.7.RELEASE</spring-boot.version>
</properties>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.hyperledger.fabric-sdk-java/fabric-sdk-java -->
<dependency>
<groupId>org.hyperledger.fabric-sdk-java</groupId>
<artifactId>fabric-sdk-java</artifactId>
<version>2.2.12</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.hyperledger.fabric/fabric-gateway-java-->
<dependency>
<groupId>org.hyperledger.fabric</groupId>
<artifactId>fabric-gateway-java</artifactId>
<version>2.2.3</version>
</dependency>
<dependency>
<groupId>org.hyperledger.fabric</groupId>
<artifactId>fabric-gateway</artifactId>
<version>1.0.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.grpc/grpc-netty-shaded -->
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
<version>1.42.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.79</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-collections4 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-codec/commons-codec -->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.15</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.3.7.RELEASE</version>
<configuration>
<mainClass>org.example.appdemo.Application
</mainClass>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
network.json
{
"name": "test-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/test-network/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/keystore/priv_sk"
},
"signedCertPEM": {
"path": "src/main/resources/crypto-config/test-network/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/test-network/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp/keystore/priv_sk"
},
"signedCertPEM": {
"path": "src/main/resources/crypto-config/test-network/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.20.11:7050",
"mspid": "OrdererMSP",
"grpcOptions": {
"ssl-target-name-override": "orderer.example.com",
"hostnameOverride": "orderer.example.com"
},
"tlsCACerts": {
"path": "src/main/resources/crypto-config/test-network/ordererOrganizations/example.com/orderers/orderer.example.com/tls/ca.crt"
},
"adminPrivateKeyPEM": {
"path": "src/main/resources/crypto-config/test-network/ordererOrganizations/example.com/users/Admin@example.com/msp/keystore/priv_sk"
},
"signedCertPEM": {
"path": "src/main/resources/crypto-config/test-network/ordererOrganizations/example.com/users/Admin@example.com/msp/signcerts/Admin@example.com-cert.pem"
}
}
},
"peers": {
"peer0.org1.example.com": {
"url": "grpcs://192.168.20.11: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/test-network/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt"
}
},
"peer0.org2.example.com": {
"url": "grpcs://192.168.20.11: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/test-network/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt"
}
}
},
"certificateAuthorities": {
"ca-org1": {
"url": "https://192.168.20.11:7054",
"grpcOptions": {
"verify": true
},
"tlsCACerts": {
"path": "src/main/resources/crypto-config/test-network/peerOrganizations/org1.example.com/ca/ca.org1.example.com-cert.pem"
},
"registrar": [
{
"enrollId": "admin",
"enrollSecret": "adminpw"
}
]
},
"ca-org2": {
"url": "https://192.168.20.11:8054",
"grpcOptions": {
"verify": true
},
"tlsCACerts": {
"path": "src/main/resources/crypto-config/test-network/peerOrganizations/org2.example.com/ca/ca.org2.example.com-cert.pem"
},
"registrar": [
{
"enrollId": "admin",
"enrollSecret": "adminpw"
}
]
}
}
}
application.properties
spring.application.name=fabric-java-demo
server.port=8888
fabric.networkConnectionConfigPath=src/main/resources/network.json
#fabric.mspId=Org1MSP
fabric.certificatePath=src/main/resources/crypto-config/test-network/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp/signcerts/User1@org1.example.com-cert.pem
fabric.privateKeyPath=src/main/resources/crypto-config/test-network/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp/keystore/priv_sk
#fabric.tlsCertPath=src/main/resources/crypto-config/prod-network/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
#fabric.channel=mychannel
logging.level.org.hyperledger=trace
java各个类
Application.java主程序入口
package org.example.appdemo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
/**
* @Author: kingtao
* @Date: 2023-04-22-10:52
* @Description:
*/
@SpringBootApplication
@EnableConfigurationProperties
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
CatContractController.java
controller提供的各个maping与fabric交互
package org.example.appdemo;
import com.google.common.collect.Maps;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.StringUtils;
import org.hyperledger.fabric.gateway.Contract;
import org.hyperledger.fabric.gateway.ContractException;
import org.hyperledger.fabric.gateway.Gateway;
import org.hyperledger.fabric.gateway.Network;
import org.hyperledger.fabric.sdk.Peer;
import org.springframework.web.bind.annotation.*;
import java.util.EnumSet;
import java.util.Map;
import java.util.concurrent.TimeoutException;
/**
* @Author: kingtao
* @Date: 2023-04-22-10:52
* @Description:
*/
@RestController
@RequestMapping("/cat")
@Slf4j
@AllArgsConstructor
public class CatContractController {
final Gateway gateway;
@GetMapping("/{key}")
public Map<String, Object> queryCatByKey(@PathVariable String key) throws ContractException {
Map<String, Object> res = Maps.newConcurrentMap();
Contract contract = getContract();
byte[] cat = contract.evaluateTransaction("queryCat", key);
res.put("payload", StringUtils.newStringUtf8(cat));
res.put("status", "ok");
return res;
}
@PutMapping("/")
public Map<String, Object> createCat(@RequestBody CatDTO cat) throws InterruptedException, TimeoutException, ContractException {
Map<String, Object> res = Maps.newConcurrentMap();
Contract contract = getContract();
byte[] bytes = contract.submitTransaction("createCat", cat.getKey(), cat.getName(), String.valueOf(cat.getAge()), cat.getColor(), cat.getBreed());
res.put("payload", StringUtils.newStringUtf8(bytes));
res.put("status", "ok");
return res;
}
@DeleteMapping("/{key}")
public Map<String, Object> deleteCatByKey(@PathVariable String key) throws InterruptedException, TimeoutException, ContractException {
Map<String, Object> res = Maps.newConcurrentMap();
Contract contract = getContract();
// 合约生效规则
byte[] cat = contract.createTransaction("deleteCat")
.setEndorsingPeers(getNetwork().getChannel().getPeers(EnumSet.of(Peer.PeerRole.ENDORSING_PEER)))
.submit(key);
res.put("payload", StringUtils.newStringUtf8(cat));
res.put("status", "ok");
return res;
}
private Network getNetwork() {
// 获取通道
return gateway.getNetwork("mychannel");
}
private Contract getContract() {
// 获取合约
return getNetwork().getContract("demo");
}
@GetMapping("/test")
public String test() {
return "test success";
}
}
CatDTO.java 实体
package org.example.appdemo;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* @Author: kingtao
* @Date: 2023-04-22-10:51
* @Description:
*/
@Data
@Accessors(chain = true)
public class CatDTO {
private String key;
private String name;
private Integer age;
private String color;
private String breed;
}
fabricConfig.java 相关fabric的配置
package org.example.appdemo;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
//import org.hyperledger.fabric.client.Gateway;
//import org.hyperledger.fabric.client.identity.Identities;
import org.hyperledger.fabric.gateway.Gateway;
import org.hyperledger.fabric.gateway.Identities;
import org.hyperledger.fabric.gateway.Wallet;
import org.hyperledger.fabric.gateway.Wallets;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.io.BufferedReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
/**
* @Author: kingtao
* @Date: 2023-04-22-10:50
* @Description:
*/
@Configuration
@AllArgsConstructor
@Slf4j
public class FabricConfig {
final FabricProperties fabricProperties;
@Bean
public Gateway gateway() throws Exception {
BufferedReader certificateReader = Files.newBufferedReader(Paths.get(fabricProperties.getCertificatePath()), StandardCharsets.UTF_8);
X509Certificate certificate = Identities.readX509Certificate(certificateReader);
BufferedReader privateKeyReader = Files.newBufferedReader(Paths.get(fabricProperties.getPrivateKeyPath()), StandardCharsets.UTF_8);
PrivateKey privateKey = Identities.readPrivateKey(privateKeyReader);
Wallet wallet = Wallets.newInMemoryWallet();
wallet.put("user1", Identities.newX509Identity("Org1MSP", certificate, privateKey));
Gateway.Builder builder = org.hyperledger.fabric.gateway.Gateway.createBuilder()
.identity(wallet, "user1")
.networkConfig(Paths.get("src/main/resources/network.json"));
Gateway gateway = builder.connect();
log.info("=================connected fabric gateway {}", gateway );
return gateway;
}
}
FabricProperties.java fabric的相关配置类
package org.example.appdemo;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
* @Author: kingtao
* @Date: 2023-04-22-10:52
* @Description:
*/
@Configuration
@ConfigurationProperties(prefix = "fabric")
@Data
public class FabricProperties {
private String networkConnectionConfigPath;
private String certificatePath;
private String privateKeyPath;
}
然后通过postman测试接口,注意参数的位置与链码的相对应
例如:创建方法