本文介绍如何通过 Spring Boot 配置和初始化多组织支持的合约实例
1. 修改application.yml
添加fabric多组织配置
首先,在
application.yml
文件中添加Fabric 多组织
的配置信息。这些配置信息包括合约名称、通道名称、默认客户端以及多个组织的详细信息。以下是示例配置:
fabric:
chaincode-name: chaincodeName # 合约名称
channel-name: mychannel
default-client: Org1MSP
clients:
- mspId: Org1MSP
peerEndpoint: 192.168.43.140:7051
override-auth: peer0.org1.example.com
cryptoPath: "./organizations/peerOrganizations/org1.example.com" # 从项目根目录出发的路径
certDirPath: "users/User1@org1.example.com/msp/signcerts"
keyDirPath: "users/User1@org1.example.com/msp/keystore"
tlsCertPath: "peers/peer0.org1.example.com/tls/ca.crt"
- mspId: Org2MSP
peerEndpoint: 192.168.43.140:9051
override-auth: peer0.org2.example.com
cryptoPath: "./organizations/peerOrganizations/org2.example.com"
certDirPath: "users/User1@org2.example.com/msp/signcerts"
keyDirPath: "users/User1@org2.example.com/msp/keystore"
tlsCertPath: "peers/peer0.org2.example.com/tls/ca.crt"
2. Fabric 多组织配置类
为了将
application.yml
中的配置属性绑定到 Java 对象上,我们需要创建一个配置类。该类使用 Spring Boot 的@ConfigurationProperties
注解,绑定配置文件中的属性到 Java 对象中,确保类型安全和简化配置管理。
package com.trace.sys.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
@ConfigurationProperties(prefix = "fabric")
@Data
public class FabricProperties {
private String channelName;
private String chaincodeName;
private String defaultClient;
private List<FabricClientConf> clients;
@Data
public static class FabricClientConf {
private String mspId;
private String peerEndpoint;
private String overrideAuth;
private String cryptoPath;
private String certDirPath;
private String keyDirPath;
private String tlsCertPath;
}
}
主要功能
- 绑定外部属性:将外部配置文件中的属性值绑定到 Java 对象的字段上。
- 类型安全:通过绑定配置属性到 Java Bean 上,确保配置的类型安全。
- 简化配置管理:使配置管理更加简洁和清晰。
3. 初始化多组织合约
在应用启动时,初始化多组织合约实例,通过使用不同的
mspId
切换组织。这部分的核心在于读取配置文件中的组织信息,并根据这些信息创建相应的合约实例。
package com.trace.sys.config;
import io.grpc.ChannelCredentials;
import io.grpc.Grpc;
import io.grpc.ManagedChannel;
import io.grpc.TlsChannelCredentials;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.hyperledger.fabric.client.Contract;
import org.hyperledger.fabric.client.Gateway;
import org.hyperledger.fabric.client.Network;
import org.hyperledger.fabric.client.identity.*;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
import java.io.BufferedReader;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.InvalidKeyException;
import java.security.PrivateKey;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
@Slf4j
@RequiredArgsConstructor
@Configuration
public class FabricConfig {
private static final HashMap<String, Contract> contractMap = new HashMap<>();
private static String DEFAULT_CLIENT;
public static Contract getDefalutContract() {
return contractMap.get(DEFAULT_CLIENT);
}
public static Contract getContract(String mspId) {
return contractMap.get(mspId);
}
private final FabricProperties fabricProperties;
@PostConstruct
public void init() throws IOException, CertificateException, InvalidKeyException {
log.info("FabricConfig init");
DEFAULT_CLIENT = fabricProperties.getDefaultClient();
for (FabricProperties.FabricClientConf client : fabricProperties.getClients()) {
log.info("FabricConfig client init : {}", client);
Path cryptoPath = Paths.get(client.getCryptoPath());
Path certDirPath = cryptoPath.resolve(Paths.get(client.getCertDirPath()));
Path keyDirPath = cryptoPath.resolve(Paths.get(client.getKeyDirPath()));
Path tlsCertPath = cryptoPath.resolve(Paths.get(client.getTlsCertPath()));
ManagedChannel channel = newGrpcConnection(tlsCertPath, client.getPeerEndpoint(), client.getOverrideAuth());
Gateway.Builder builder = Gateway.newInstance()
.identity(newIdentity(certDirPath, client.getMspId()))
.signer(newSigner(keyDirPath))
.connection(channel)
// Default timeouts for different gRPC calls
.evaluateOptions(options -> options.withDeadlineAfter(5, TimeUnit.SECONDS))
.endorseOptions(options -> options.withDeadlineAfter(15, TimeUnit.SECONDS))
.submitOptions(options -> options.withDeadlineAfter(5, TimeUnit.SECONDS))
.commitStatusOptions(options -> options.withDeadlineAfter(1, TimeUnit.MINUTES));
Gateway gateway = builder.connect();
// Get a network instance representing the channel where the smart contract is
// deployed.
Network network = gateway.getNetwork(fabricProperties.getChannelName());
// Get the smart contract from the network.
Contract contract = network.getContract(fabricProperties.getChaincodeName());
contractMap.put(client.getMspId(), contract);
}
log.info("FabricConfig init success");
}
private static ManagedChannel newGrpcConnection(Path tlsCertPath, String peerEndpoint, String overrideAuth) throws IOException {
ChannelCredentials build = TlsChannelCredentials.newBuilder()
.trustManager(tlsCertPath.toFile())
.build();
return Grpc.newChannelBuilder(peerEndpoint, build)
.overrideAuthority(overrideAuth)
.build();
}
private static Identity newIdentity(Path certDirPath, String mspId) throws IOException, CertificateException {
try (BufferedReader bufferedReader = Files.newBufferedReader(getFirstFilePath(certDirPath))) {
X509Certificate certificate = Identities.readX509Certificate(bufferedReader);
return new X509Identity(mspId, certificate);
}
}
private static Signer newSigner(Path keyDirPath) throws IOException, InvalidKeyException {
try (BufferedReader keyReader = Files.newBufferedReader(getFirstFilePath(keyDirPath))) {
PrivateKey privateKey = Identities.readPrivateKey(keyReader);
return Signers.newPrivateKeySigner(privateKey);
}
}
private static Path getFirstFilePath(Path dirPath) throws IOException {
try (Stream<Path> keyFiles = Files.list(dirPath)) {
return keyFiles.findFirst()
.orElseThrow(() -> new IOException("No files found in " + dirPath));
}
}
}
4. 使用示例
在使用时,通过
mspId
获取相应的合约实例。如果mspId·
为空,则返回默认的合约实例。
public static Contract getContract(String mspId) {
if (mspId == null || mspId.isEmpty()){
return FabricConfig.getDefalutContract();
}
return FabricConfig.getContract(mspId);
}
结论
通过本文的步骤,您可以在 Spring Boot 应用中配置和初始化 Hyperledger Fabric 的多组织支持,实现多组织间的合约交互。这样不仅提高了配置的灵活性和类型安全性,还简化了配置管理,使得在实际项目中更加容易维护和扩展。