57e6837fe706251a1ad1cbae7964671a.png

1 准备条件

  • 2 操作流程
    • 2.1 添加依赖
    • 2.2 将需要的文件放到合适的位置
    • 2.3 修改文件
    • 2.4 编写代码
    • 2.5 提取contract对象
    • 2.6 方法调用
  • 3 常见错误
  • 4 注意

背景:目前我们的fabric服务使用node搭建的。

目的:在spring-boot中实现与使用node-sdk搭建fabric服务一样的功能,其中wallet是使用node脚本生成的,当然应该可以直接使用java程序生成,后面有时间尝试一下。

环境:测试的时候使用的是单机,docker,fabric1.4.4,solo模式

1 准备条件

  • 确保fabric安装成功,/home/go/src/github.com/hyperledger/fabric-samples/下的first-network例子运行没问题
  • 确保你自己的fabric区块链启动成功
  • 确保spring-boot项目可用

2 操作流程

2.1 添加依赖

修改pom.xml文件,添加

<dependency>
    <groupId>org.hyperledger.fabric</groupId>
    <artifactId>fabric-gateway-java</artifactId>
    <version>1.4.4</version>
</dependency>

2.2 将需要的文件放到合适的位置

spring-boot项目中controller目录下新增一个目录sdk(随便)

  • 将服务器上的connection-org1.json文件复制到sdk目录中
  • 将与connection-org1.json同目录的crypto-config目录下的crypto-config/peerOrganizations/org1.example.com/ca/ca.org1.example.com-cert.pem文件复制到sdk目录中
  • /home/go/src/github.com/hyperledger/fabric-samples/fabcar/java/src/main/java/org/example目录下的三个文件复制到sdk目录

8927e6bd00c388a46aee1f34031ad77d.png

2.3 修改文件

  • 修改connection-org1.json文件,将其中的localhost,和grpcspeer*.org*.example.com等字段改为服务器的ip地址
  • 修改EnrollAdmin.javaRegisterUser.java文件
// 证书相关文件
props.put("pemFile","src/main/java/com/***/web/controller/sdk/ca.org1.example.com-cert.pem");

// ca客户端
HFCAClient caClient = HFCAClient.createNewInstance("https://ip地址:7054", props);

// wallet目录,不修改的话会在项目根目录
Wallet wallet = Wallet.createFileSystemWallet(Paths.get("src/main/java/com/***/web/controller/sdk/wallet"));

// ip地址
enrollmentRequestTLS.addHost("ip地址");

两个文件修改方法一样,其他的不用改

  • 修改完成后分别运行EnrollAdmin.javaRegisterUser.java不要忘了!

感觉sui kou yi shuo fabric1.4.4的java版本的SDK有点捞,注册管理员和用户的时候还需要传入ca.org1.example.com-cert.pem,而node版本的SDK只需要传入connection-org1.json,会自动读取其中的ca.org1.example.com字段。

2.4 编写代码

先看一下官方给的案例: fabcar sample[1]

1.4版本官方文档

sdk目录中建一个SdkControllerjava文件(可以直接修改ClientApp.java

主要修改内容:

// path
Path walletDirectory = Paths.get("src/main/java/com/***/web/controller/sdk/wallet");
Path networkConfigFile = Paths.get("src/main/java/com/***/web/controller/sdk/connection-org1.json");

// your channel name and contract name
Network network = gateway.getNetwork("mychannel");
Contract contract = network.getContract("mycontract");

最主要的还是调用你的链码中的方法

2.5 提取contract对象

修改完上面的内容基本就可以进行方法的调用了,如果有多个方法的话总不能在每个方法中都读取连接文件和认证文件吧。

所有我们需要把contract对象提取出来,当然也可以提取gatewaynetwork对象,不过这样就比较麻烦,没必要。

// 返回contract对象
public Contract getContract() throws IOException{
    // Load an existing wallet holding identities used to access the network.
    Path walletDirectory = Paths.get("src/main/java/com/***/web/controller/sdk/wallet");
    Wallet wallet =Wallet.createFileSystemWallet(walletDirectory);
    System.out.println(wallet.get("user1"));

    // Path to a common connection profile describing the network.
    System.out.println("读取连接文件开始");
    Path networkConfigFile = Paths.get("src/main/java/com/***/web/controller/sdk/connection-org1.json");
    System.out.println(networkConfigFile);
    System.out.println("读取连接文件结束");

    // Configure the gateway connection used to access the network.
    Gateway.Builder builder = Gateway.createBuilder()
            .identity(wallet,"user1")
            .networkConfig(networkConfigFile)
            .discovery(true);

    // Create a gateway connection
    Gateway gateway = builder.connect();
    // Obtain a smart contract deployed on the network.
    Network network = gateway.getNetwork("mychannel");
    System.out.println(network.getChannel());
    Contract contract1 = network.getContract("mycontract");
    System.out.println(contract1);
    return contract1;
}

为什么我在这儿定义一个contract1,因为我已经定义了一个全局类属性Contract contract=this.getContract();

2.6 方法调用

import com.alibaba.fastjson.JSON;
import com.bicon.web.controller.SuperController;

import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

//fabric-java-sdk
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.UUID;
import java.util.concurrent.TimeoutException;

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.gateway.Wallet

@RestController
@RequestMapping(path = "/", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public class SdkController extends SuperController{ //这个继承和下面的getParameterMap有关
    Contract contract=this.getContract();

    public SdkController() throws IOException {
    }

    // 方法说明
    @RequestMapping("/test")
    public Object test(HttpServletRequest request) throws IOException, InterruptedException, TimeoutException, ContractException {
        Map<String, Object> params = getParameterMap(request);
        // 去掉“-”
        String Id= UUID.randomUUID().toString().replaceAll("-","");
        String param=JSON.toJSONString(params);
        System.out.println(param);
        // 提交交易  “test”为链码中的方法,紧接着的为链码中需要的参数
        byte[] result=contract.submitTransaction("test", param);
        String resultStr=new String(result, StandardCharsets.UTF_8);
        System.out.println(resultStr);
        HashMap resultMap=JSON.parseObject(resultStr,HashMap.class);
        return resultMap;
    }
}

然后就可以发送请求进行测试了

http://127.0.0.1:8080/test

//application/json
{
  ...
}

3 常见错误

错误1 validatePeerResponses: invalid response from peer grpcs://localhost:10051

解决方法:修改hostsC:WINDOWSSystem32driversetchosts)文件,添加映射如下:

*.*.*.* peer1.org1.example.com
*.*.*.* peer0.org1.example.com
*.*.*.* ca_peerOrg1
*.*.*.* ca_peerOrg2
*.*.*.* peer1.org2.example.com
*.*.*.* peer0.org2.example.com
*.*.*.* orderer.example.com

其中*.*.*.*为服务器的ip地址

错误2

97d749a90352cdc2b23be2f5a5cb9ead.png

解决方法:

  1. 修改hosts文件
  2. 删掉下面的代码
static {
    System.setProperty("org.hyperledger.fabric.sdk.service_discovery.as_localhost", "true");
}

错误3

09:06:51.004 [http-nio-18090-exec-1] ERROR WEB-RESPONSE-MONITOR - 
org.hyperledger.fabric.gateway.GatewayRuntimeException: org.hyperledger.fabric.sdk.exception.InvalidArgumentException: Channel mychannel has been shutdown.

启发[2]

try (Gateway gateway = builder.connect()) {
    ...
 } catch (ContractException | TimeoutException e) {
    e.printStackTrace();
}

发现try-catch里面的代码可以正常执行,而其他方法中的代码却不能执行。删除try-catch即可

4 注意

  • 不能多次执行注册管理员和用户的代码
  • 每次重启区块链后connection-org1.json,crypto-config目录都会发生变化,需要重新注册管理员和用户,也就是生成wallet目录
  • 注意我的目录结构,大家的可能会有些区别
  • 注意其中的*** 符号
  • 项目目录结构

97e6d48f8de0578ad751513f46a3d356.png

参考资料

[1] Fabric Gateway SDK for Java: https://github.com/hyperledger/fabric-gateway-java/

[2] Channel mychannel has been shutdown: https://stackoverflow.com/questions/58861348/hyperledger-fabric-async-parallel-transaction-using-java-gateway-sdk

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐