该项目是对hyperledger fabric的sdk的二次封装,简化fabric智能合约的调用方式,同时提供了一些额外的工具包,借助该sdk-wrapper可以帮助尝试fabric开发的人员快速的上手fabric智能合约的java代码部署以及调用,完成简单的业务设计。
spring:
# 如果开启了wallet选项,那么一定要开启数据库配置
datasource:
type: com.alibaba.druid.pool.DruidDataSource
username: 数据库用户名
password: 数据库密码
url: jdbc:mysql://数据库IP:数据库端口/数据库名称?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
driver-class-name: com.mysql.cj.jdbc.Driver
fabric:
wallet:
private-key-path: 钱包使用的私钥的地址
public-key-path: 钱包使用的公钥的地址
wallet-id: 钱包的唯一id,必须是整数
type: 钱包的类型,目前仅支持db类型
chaincode:
ca-config:
# CA证书路径,相对于classpath,在maven中就是resources目录
cert-path: crypto-config/peerOrganizations/org1.example.com/ca/ca.org1.example.com-cert.pem
# tls的路径,如果不开启,默认为空
tls-path:
# CA服务器的地址
url: http://ip地址:端口
msp-config:
affiliation: org1.example.com
msp-id: Org1MSP
chain-code-config:
name: 部署的链码名称
policy-path:
project-name: 链码工程的名称
version: 1.0
gopath: 系统的gopath路径
user-configs:
- username-for-cA: admin的用户名
passwd-for-cA: admin的密码
is-admin: true
cert-path: crypto-config/peerOrganizations/org1.example.com/users/[email protected]/msp/signcerts/[email protected]
private-key-path: crypto-config/peerOrganizations/org1.example.com/users/[email protected]/msp/keystore/admin_sk
orderer-configs:
- name: orderer
tls-path: crypto-config/ordererOrganizations/example.com/orderers/orderer.example.com/tls/server.crt
url: grpcs://ip地址:端口
peer-configs:
- url: grpcs://ip地址:端口
name: peer0
tls-path: crypto-config/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/server.crt
- url: grpcs://ip地址:端口
name: peer1
tls-path: crypto-config/peerOrganizations/org1.example.com/peers/peer1.org1.example.com/tls/server.crt
# 开启日志打印,有助于debug
logging:
level:
com:
github:
xcfyl:
fabric:
sdkwrapper: debug
- 首先将本工程克隆到本地
- 然后使用idea打开
- 使用idea的maven工具栏,install安装
- 安装完毕后,即可在自己的工程里面导入对应的maven坐标即可
本项目支持两种方式的链码调用,基于Java代码的,和基于声明式注解的,下面分别进行演示:
- Invoke链码
public class TestInvoke {
@Test
public void testInvokeByJava() {
InvokeRequest<String> invokeRequest = new InvokeRequest<>(fabricContext, fabricContext.getAdmin(),
String.class, "mychannel", "test", new String[]{"1", "2"},
(result, e) -> {
// 这里编写本次调用的结果处理逻辑
if (e != null) {
// 说明发生了异常
throw new RuntimeException(e.getMessage());
} else {
// 可以正常处理结果
System.out.println(result);
}
}, 1000000);
// 这里需要说明的是,invoke是异步调用,因此他的返回值是没有意义的
// 一般来说不需要接收该返回值
invokeRequest.send();
}
}
- Query链码
public class TestQuery {
@Test
public void testQueryByJava() {
QueryRequest<String> queryRequest = new QueryRequest<>(fabricContext.getAdmin(),
fabricContext, String.class, "mychannel", "test",
new String[]{"1", "2"}, 1000000);
// query是同步调用,因此query方法可以直接接收返回值
String send = queryRequest.send();
System.out.println(send);
}
}
- 初始化链码
public class TestInit {
@Test
public void testInitByJava() {
InitRequest initRequest = new InitRequest(fabricContext, "mychannel", new ResultHandler<String>() {
@Override
public void handle(String result, Throwable e) {
if (e != null) {
throw new RuntimeException(e.getMessage());
} else {
System.out.println(result);
}
}
}, 10000);
// init同invoke也是异步调用,因此不要接收返回值,该返回值始终为null
// 处理结果的逻辑在handle方法中
initRequest.send();
}
}
- 安装链码
public class TestInstall {
@Test
public void testInstallByJava() {
InstallRequest installRequest = new InstallRequest(fabricContext, "mychannel", 10000);
// install是同步调用,因此可以直接接收结果
// install调用的结果始终是bool类型
Boolean send = installRequest.send();
if (send) {
System.out.println("安装成功");
} else {
System.out.println("安装失败");
}
}
}
声明式注解需要遵守以下契约:
- Invoke和Query方法的第一个接口参数必须是User对象
- 接口的方法名和链码的函数名要保持一致,这样才知道调用的是哪一个链码函数
- 对于Invoke方法、Init方法接口的返回值没有意义,建议始终声明为void,最终的结果处理需要借助Handler
- 对于Install方法,接口的返回值始终为Boolean类型,返回true,代表install成功,返回false表示install失败
- 对于Query方法,接口的返回类型就是query的结果,库会自动完成结果内容的填充
下面开始演示基于声明式注解的调用方式:
- 启动声明式注解功能,只需要在启动类加上如下注解
@SpringBootApplication
@EnableChainCodeProxy(scanPackages = "扫描路径,被ChainCodeProxy注解标注的类所在的路径")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
- 创建接口,声明接口方法
// 被该注解标注,说明当前接口需要被代理
@ChainCodeProxy(channelName = "mychannel")
public interface ChainCodeService {
// 使用Init注解标注初始化链码的方法
// @Init注解中包含默认的resultHandler,因此这里没有声明结果处理
// 使用的是默认处理策略,牢记init方法的结果始终是string类型
@Init
void initChainCode();
// 使用@Install注解标注安装链码的方法
@Install
boolean installChainCode();
// 使用@Invoke注解标注Invoke方法,因为Invoke是异步的,因此需要在注解中
// 指定处理结果的结果处理器,第一个参数默认必须是User,第二个参数开始就是该函数
// 对应的链码函数的参数
// 函数名称要和链码声明的函数保持一致
@Invoke(resultHandler = InvokeHandler.class)
void putUserId(User user, String userId);
// 使用@Query注解标注query方法,query方法是同步方法,因此不需要指定
// resultHandler,需要声明query方法的返回值,该返回值要和链码处定义的返回类型
// 保持一致,不然会反解析错误。
@Query(timeout = 100000)
User queryUserById(User user, String id);
}
- 调用接口方法
@SpringBootTest
@Slf4j
public class TestChainCode {
@Autowired
private ChainCodeService chainCodeService;
@Autowired
private FabricContext fabricContext;
@Test
public void testInstallService() throws IOException {
boolean b = chainCodeService.installChainCode();
if (b) {
System.out.println("安装链码成功");
} else {
System.out.println("安装链码失败");
}
}
@Test
public void testInitService() throws IOException {
chainCodeService.initChainCode();
// 这里是所以要read,因为这个调用是异步的,如果不read
// 主线程直接执行结束,然后看不到结果
System.in.read();
}
@Test
public void testInvokeService() throws IOException {
chainCodeService.putUserId(fabricContext.getAdmin(), "1");
System.in.read();
}
@Test
public void testQuery() throws IOException {
System.out.println(chainCodeService.queryUserById(fabricContext.getAdmin(), "1"));
System.in.read();
}
}
该sdkwrapper中实现了基于数据库的简单钱包,所谓的钱包就是一组身份集合,如果需要使用钱包,那么需要在配置文件中声明如下配置,此外还需要在自己的数据库中,提前导入钱包的sql文件,创建钱包的表结构,sql文件在本项目的sql目录下面:
fabric:
wallet:
private-key-path: 私钥路径
public-key-path: 公钥路径
wallet-id: 123
type: db
- private-key-path: 表示的是当前钱包使用的私钥的路径
- public-key-path: 表示的是当前钱包使用的公钥的路径
- wallet-id: 表示当前钱包的唯一标识,用户可以创建多个钱包,只需要保证钱包的id不一样就好了
- type: 表示的是钱包的类型,目前仅支持db形式的钱包,需要注意的是,当前钱包的公私钥采用的均为SM2算法生成的公私钥,sdkwrapper中声明了一组SM2工具集合,可以直接使用该工具集合生成公私钥,并且private-key-path和public-key-path可以为空,如果为空,那么系统会选择一个默认的路径作为公私钥的存储位置
channel应该被共享,进行池化处理