Fabric区块链结合spring boot简单使用

前提:已经看了我上一篇文章并部署好了fabric区块链,那么这篇文章主要聚焦在java项目里怎么去调用区块链的方法进行简单的数据上链及查询

 1.在项目里添加maven依赖

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

2.核心配置文件及相关证书目录

项目结构如下

项目目录结构 (2)

 

在部署fabric时在/vdc/docker/fabric-offline/config/下已经生成了crypto-config,直接把这个目录复制过来替换即可

[root@localhost config]# pwd
/vdc/docker/fabric-offline/config
[root@localhost config]# ls
chaincode  channel-artifacts  configtx.yaml  crypto-config  crypto-config.yaml  docker-compose.yaml
[root@localhost config]# cd crypto-config/
[root@localhost crypto-config]# ls
ordererOrganizations  peerOrganizations
[root@localhost crypto-config]# pwd
/vdc/docker/fabric-offline/config/crypto-config
[root@localhost crypto-config]# 

配置connection.json文件

{
    "name": "basic-network",
    "version": "1.0.0",
    "client": {
        "organization": "Org1MSP",
        "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": {
        "Org1MSP": {
            "mspid": "Org1MSP",
            "peers": [
                "peer0.org1.example.com"
            ],
            "adminPrivateKeyPEM": {
                "path": "crypto-config/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/keystore/priv_sk"
            },
            "signedCertPEM": {
                "path": "crypto-config/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/signcerts/Admin@org1.example.com-cert.pem"
            }
        }

    },
    "orderers": {
        "orderer.example.com": {
            "url": "grpcs://orderer.example.com:7050",
            "mspid": "OrdererMSP",
            "grpcOptions": {
                "ssl-target-name-override": "orderer.example.com",
                "hostnameOverride": "orderer.example.com"
            },
            "tlsCACerts": {
                "path": "crypto-config/ordererOrganizations/example.com/orderers/orderer.example.com/tls/ca.crt"
            },
            "adminPrivateKeyPEM": {
                "path": "crypto-config/ordererOrganizations/example.com/users/Admin@example.com/msp/keystore/priv_sk"
            },
            "signedCertPEM": {
                "path": "crypto-config/ordererOrganizations/example.com/users/Admin@example.com/msp/signcerts/Admin@example.com-cert.pem"
            }
        }
    },
    "peers": {
        "peer0.org1.example.com": {
            "url": "grpcs://peer0.org1.example.com:7051",
            "grpcOptions": {
                "ssl-target-name-override": "peer0.org1.example.com",
                "hostnameOverride": "peer0.org1.example.com",
                "request-timeout": 120001
            },
            "tlsCACerts": {
                "path": "crypto-config/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt"
            }
        }

    }

}

peer和order节点都是读取crypto-config目录下的文件,这块可以不用管,但是文件中的peer0.org1.example.com和orderer.example.com需要配置域名(win10是C:\Windows\System32\drivers\etc\hosts,linux是/etc/hosts)不然访问不了

application.yml配置文件

server:
  port: 9104

spring:
  application:
    name: my-fabric-application-java

otel:
  sdk:
    disabled: true
  traces:
    exporter: none
  metrics:
    exporter: none
  exporter:
    otlp:
      endpoint: ""

fabric:
  # wallet文件夹路径(自动创建)
  walletDirectory: wallet
  # 网络配置文件路径
  networkConfigPath: connection.json
  # 用户证书路径
  certificatePath: crypto-config/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/signcerts/Admin@org1.example.com-cert.pem
  # 用户私钥路径
  privateKeyPath: crypto-config/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/keystore/priv_sk
  # 访问的组织名
  mspid: Org1MSP
  # 用户名
  username: admin
  # 通道名字
  channelName: mychannel
  # 链码名字
  contractName: fabcar

主要是fabric配置, wallet钱包文件夹启动项目时会自己创建并且里面会生成admin.id文件(包含admin证书密钥的一些信息),这里我用的都是admin的一些密钥信息,之前用user普通用户会遇到一些权限问题

GatewayConfig配置文件

package com.chinaoly.config.qkl;

import lombok.extern.slf4j.Slf4j;
import org.hyperledger.fabric.gateway.*;
import org.hyperledger.fabric.sdk.exception.InvalidArgumentException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;

import java.io.IOException;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
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;

/**
 * Fabric Gateway Spring配置类
 * @author ljh
 * @version 1.0
 * @date 2026/2/3
 */
@Slf4j
@Configuration
@Lazy // 懒加载,避免项目启动时过早初始化导致资源冲突
public class GatewayConfig {
    /**
     * wallet文件夹路径
     */
    @Value("${fabric.walletDirectory}")
    private String walletDirectory;
    /**
     * 网络配置文件路径
     */
    @Value("${fabric.networkConfigPath}")
    private String networkConfigPath;
    /**
     * 用户证书路径
     */
    @Value("${fabric.certificatePath}")
    private String certificatePath;
    /**
     * 用户私钥路径
     */
    @Value("${fabric.privateKeyPath}")
    private String privateKeyPath;
    /**
     * 访问的组织名
     */
    @Value("${fabric.mspid}")
    private String mspid;
    /**
     * 用户名
     */
    @Value("${fabric.username}")
    private String username;
    /**
     * 通道名字
     */
    @Value("${fabric.channelName}")
    private String channelName;
    /**
     * 链码名字
     */
    @Value("${fabric.contractName}")
    private String contractName;

    /**
     * 连接网关:增加destroyMethod自动关闭、关闭服务发现、钱包存在性判断
     * @return Gateway 单例Bean
     */
    @Bean(destroyMethod = "close") // 核心:项目关闭时自动调用gateway.close(),释放通道/连接资源
    public Gateway connectGateway() throws IOException, InvalidKeyException, CertificateException {
        log.info("开始初始化Fabric Gateway,用户:{},通道:{}", username, channelName);
        // 1. 初始化文件钱包
        Path walletPath = Paths.get(this.walletDirectory);
        Wallet wallet = Wallets.newFileSystemWallet(walletPath);
        log.info("成功加载钱包,路径:{}", walletDirectory);

        // 2. 仅当钱包中无该用户时,才导入身份(避免每次启动重复put覆盖)
        if (wallet.get(username) == null) {
            X509Certificate certificate = readX509Certificate(Paths.get(this.certificatePath));
            PrivateKey privateKey = getPrivateKey(Paths.get(this.privateKeyPath));
            Identity identity = Identities.newX509Identity(this.mspid, certificate, privateKey);
            wallet.put(username, identity);
            log.info("钱包中无{}用户,已成功导入身份(证书+私钥)", username);
        } else {
            log.info("钱包中已存在{}用户,直接使用现有身份", username);
        }

        // 3. 构建Gateway:关闭服务发现(远程部署核心)、加载网络配置
        Gateway.Builder builder = Gateway.createBuilder()
                .identity(wallet, username)
                .networkConfig(Paths.get(this.networkConfigPath))
                .discovery(false); // 适配远程Fabric,关闭自动服务发现,避免寻址冲突
        log.info("成功构建Gateway Builder,网络配置路径:{}", networkConfigPath);

        // 4. 连接网关
        Gateway gateway = builder.connect();
        log.info("✅ Fabric Gateway连接成功!");
        return gateway;
    }

    /**
     * 获取通道:依赖Gateway单例,仅初始化一次
     */
    @Bean
    public Network network(Gateway gateway) throws InvalidArgumentException {
        try {
            return gateway.getNetwork(this.channelName);
        } catch (Exception e) {
            if (e.getMessage().contains("already exists")) {
                log.warn("Channel {} already exists, attempting to get existing network", this.channelName);
                // 重新获取或使用其他方式获取已存在的网络
                return gateway.getNetwork(this.channelName);
            }
            throw e;
        }
    }

    /**
     * 获取链码合约:依赖Network单例,仅初始化一次
     */
    @Bean
    public Contract contract(Network network) {
        log.info("开始获取Fabric链码合约:{}", contractName);
        Contract contract = network.getContract(this.contractName);
        log.info("✅ 成功获取链码合约:{}", contractName);
        return contract;
    }

    /**
     * 读取X509证书
     */
    private static X509Certificate readX509Certificate(final Path certificatePath) throws IOException, CertificateException {
        try (Reader certificateReader = Files.newBufferedReader(certificatePath, StandardCharsets.UTF_8)) {
            return Identities.readX509Certificate(certificateReader);
        }
    }

    /**
     * 读取私钥(PKCS#8格式)
     */
    private static PrivateKey getPrivateKey(final Path privateKeyPath) throws IOException, InvalidKeyException {
        try (Reader privateKeyReader = Files.newBufferedReader(privateKeyPath, StandardCharsets.UTF_8)) {
            return Identities.readPrivateKey(privateKeyReader);
        }
    }
}

这是启动项目时会加载一次去连接网关获取通道等步骤会返回单例bean可直接操作区块链方法

TestController方法编写(用的是node.js链码方法)

package org.zlt.fabric.controller;

import lombok.extern.slf4j.Slf4j;
import org.hyperledger.fabric.gateway.Contract;
import org.hyperledger.fabric.gateway.ContractException;
import org.hyperledger.fabric.gateway.Network;
import org.hyperledger.fabric.gateway.Transaction;
import org.hyperledger.fabric.sdk.*;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.TimeoutException;


@Slf4j
@RestController
public class TestController {
    @Resource
    private Contract contract;

    @Resource
    private Network network;

    /**
     * 创建KV数据及查询
     */
    @GetMapping("/createKVData")
    public String CreateKVData() throws ContractException, InterruptedException, TimeoutException {
        // 创建 Transaction 实例(绑定链码方法)
        Transaction transaction = contract.createTransaction("createKVData");
        String id = "123456";
        String json = "{\"name\":\"张三\",\"age\":25}";

        log.info("模拟上链KV数据, Key:{}, json:{}", id, json);
        byte[] createResult = transaction.submit(id, json);
        String createMsg = new String(createResult, StandardCharsets.UTF_8);
        log.info("KV数据上链成功, 链码返回:{}", createMsg);

        byte[] queryResult = contract.evaluateTransaction("queryKVData", id);
        String queryJson = new String(queryResult, StandardCharsets.UTF_8);
        System.out.println("KV数据查询成功,结果:" + queryJson);
        return queryJson;
    }
}

 

到这一步已经把主要的配置文件及类都已经弄好了

2.key-value数据上链测试

启动项目报了错,这个报错只有将peer0.org1.example.com和orderer.example.com两个域名(你自己部署的服务器公网ip)配置到自己的hosts即可

项目启动时报错

我是在win10本地配置了域名就可以成功启动了

配置域名

已连接到地址为 ''127.0.0.1:60119',传输: '套接字'' 的目标虚拟机

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.5.9)

2026-02-26 17:34:34.328  INFO 27908 --- [           main] org.zlt.fabric.MyApplication             : Starting MyApplication using Java 1.8.0_201 on DESKTOP-34OQBTS with PID 27908 (D:\workFile\testProject\my-fabric-application-java-master\target\classes started by Administrator in D:\workFile\testProject\my-fabric-application-java-master)
2026-02-26 17:34:34.332  INFO 27908 --- [           main] org.zlt.fabric.MyApplication             : The following profiles are active: jvm.args=-Dotel.traces.exporter=none -Dotel.metrics.exporter=none
2026-02-26 17:34:35.349  INFO 27908 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 9104 (http)
2026-02-26 17:34:35.359  INFO 27908 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2026-02-26 17:34:35.359  INFO 27908 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.56]
2026-02-26 17:34:35.472  INFO 27908 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2026-02-26 17:34:35.473  INFO 27908 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1098 ms
2026-02-26 17:34:35.550  INFO 27908 --- [           main] org.zlt.fabric.config.GatewayConfig      : 开始初始化Fabric Gateway,用户:admin,通道:mychannel
2026-02-26 17:34:35.557  INFO 27908 --- [           main] org.zlt.fabric.config.GatewayConfig      : 成功加载钱包,路径:wallet
2026-02-26 17:34:35.774  INFO 27908 --- [           main] org.zlt.fabric.config.GatewayConfig      : 钱包中已存在admin用户,直接使用现有身份
2026-02-26 17:34:35.798  WARN 27908 --- [           main] o.hyperledger.fabric.sdk.helper.Config   : Failed to load any configuration from: config.properties. Using toolkit defaults
2026-02-26 17:34:36.805  INFO 27908 --- [           main] org.zlt.fabric.config.GatewayConfig      : 成功构建Gateway Builder,网络配置路径:connection.json
2026-02-26 17:34:37.060  INFO 27908 --- [           main] org.zlt.fabric.config.GatewayConfig      : ✅ Fabric Gateway连接成功!
2026-02-26 17:34:37.073  INFO 27908 --- [           main] org.hyperledger.fabric.gateway.Gateway   : Unable to load channel configuration from connection profile: Error constructing channel mychannel. Peer peer0.org2.example.com not defined in configuration
2026-02-26 17:34:37.073  WARN 27908 --- [           main] org.zlt.fabric.config.GatewayConfig      : Channel mychannel already exists, attempting to get existing network
2026-02-26 17:34:39.567  WARN 27908 --- [ault-executor-2] o.h.fabric.sdk.PeerEventServiceClient    : PeerEventServiceClient{id: 5, channel: mychannel, peerName: peer0.org1.example.com, url: grpcs://peer0.org1.example.com:7051} PeerEventServiceClient{id: 5, channel: mychannel, peerName: peer0.org1.example.com, url: grpcs://peer0.org1.example.com:7051} attempts 0 Status returned failure code 400 (BAD_REQUEST) during peer service event registration
2026-02-26 17:34:39.578  INFO 27908 --- [           main] org.hyperledger.fabric.sdk.Channel       : Channel Channel{id: 1, name: mychannel} eventThread started shutdown: false  thread: null 
2026-02-26 17:34:39.587  INFO 27908 --- [           main] org.zlt.fabric.config.GatewayConfig      : 开始获取Fabric链码合约:fabcar
2026-02-26 17:34:39.590  INFO 27908 --- [           main] org.zlt.fabric.config.GatewayConfig      : ✅ 成功获取链码合约:fabcar
2026-02-26 17:34:40.024  INFO 27908 --- [           main] pertySourcedRequestMappingHandlerMapping : Mapped URL path [/v2/api-docs] onto method [springfox.documentation.swagger2.web.Swagger2Controller#getDocumentation(String, HttpServletRequest)]
2026-02-26 17:34:40.655  INFO 27908 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 9104 (http) with context path ''
2026-02-26 17:34:40.658  INFO 27908 --- [           main] d.s.w.p.DocumentationPluginsBootstrapper : Context refreshed
2026-02-26 17:34:40.682  INFO 27908 --- [           main] d.s.w.p.DocumentationPluginsBootstrapper : Found 1 custom documentation plugin(s)
2026-02-26 17:34:40.773  INFO 27908 --- [           main] s.d.s.w.s.ApiListingReferenceScanner     : Scanning for api listing references
2026-02-26 17:34:40.951  INFO 27908 --- [           main] org.zlt.fabric.MyApplication             : Started MyApplication in 7.01 seconds (JVM running for 8.502)

 直接调用TestController里面的CreateKVData()方法查看日志已成功上链及根据key查询链数据

2026-02-26 18:30:00.091  INFO 27820 --- [nio-9104-exec-5] o.zlt.fabric.controller.TestController   : 模拟上链KV数据, Key:123456, json:{"name":"张三","age":25}
2026-02-26 18:30:09.244  INFO 27820 --- [nio-9104-exec-5] o.zlt.fabric.controller.TestController   : KV数据上链成功, 链码返回:KV data create success, key: 123456
KV数据查询成功,结果:{"name":"张三","age":25}

 fabric区块链结合java进行数据上链及查询链测试完成

3.业务进阶

当然如果有更复杂的场景或需求的话可能需要调整链码文件或增加fabric节点及组织,类似分布式集群,我这里只简单分享了fabric区块链部署到结合代码使用,而且我们这块业务上使用区块链可能会更关注区块高度,当前区块hash,上一区块hash,交易hash等,分为区块表及交易表两张表分别存储信息

核心代码展示

//创建 Transaction 实例(绑定链码方法)
Transaction transaction = contract.createTransaction(createKVData);
log.info("开始上链KV数据, Key:{}, json:{}", id, json);
byte[] createResult = transaction.submit(id, json);
String createMsg = new String(createResult, StandardCharsets.UTF_8);
log.info("KV数据上链成功, 链码返回:{}", createMsg);

//获取交易ID和区块信息
txId = transaction.getTransactionId();
//获取通道
Channel channel = network.getChannel();
//获取当前区块信息
BlockInfo blockInfo = channel.queryBlockByTransactionID(txId);
//获取最新区块链信息(存在并发风险)这里面有完整的当前区块hash和上一区块hash
BlockchainInfo blockchainInfo = channel.queryBlockchainInfo();
//提取区块关键信息
long blockHeight = blockInfo.getBlockNumber();
int txCount = blockInfo.getTransactionCount();
int blockSize = blockInfo.getBlock().toByteArray().length;
String blockHash = Hex.encodeHexString(blockchainInfo.getCurrentBlockHash());
String prevBlockHash = Hex.encodeHexString(blockchainInfo.getPreviousBlockHash());

拿到这些关键信息后可以将数据落到区块表及交易hash表里

数据展示

然后区块链的一些展示数据及交易信息可以看这个公司做的区块链项目,整体的样式风格及数据样本展示做的都是比较好的

https://cap-blockchain.shuqinkeji.com/explorerWeb?pageType=1

区块链项目

 

问题思考

1.为啥我自己数据上链的交易数据永远是1

  答: 这是因为在crypto-config.yaml配置文件里对order节点定义的默认配置打包策略, 2s秒最多容纳10笔交易会自动打包成一个区块,这块可以根据自己的需求进行配置(注意这个配置修改需要重新创建创世区块+证书等,相当于重新部署fabric)

Orderer: &OrdererDefaults
    OrdererType: solo
    Addresses:
        - orderer.example.com:7050
    BatchTimeout: 2s
    BatchSize:
        MaxMessageCount: 10
        AbsoluteMaxBytes: 99 MB
        PreferredMaxBytes: 512 KB

2.我第一次上链后的区块高度不是从0开始,可能是6或7开始

  答: 这是因为区块链的区块高度是全局递增的, 那么在前面的创建通道,证书及链码部署等就已经生成了这些系统的区块,所以在我进行数据上链是才会有为啥第一次数据上链区块高度不是0的问题, 且后续我自己也重新模拟了fabric部署全流程并开启另一窗口进行检测最新区块高度的变化, 发现在创建通道,链码时区块高度确实会增加

 

项目的话可以参考这个git项目

https://gitee.com/zlt2000/my-fabric-application-java

把项目拉下来后需要根据我上面贴出的代码进行部分修改调整,直接运行可能跑不起来

posted @ 2026-02-26 17:45  马革皮  阅读(2)  评论(0)    收藏  举报