springboot+Bcos智能合约的部署与调用
一、引入java sdk
引入java sdk官方文档说的比较清晰,根据https://fisco-bcos-documentation.readthedocs.io/zh_CN/latest/docs/sdk/java_sdk/quick_start.html的步骤引入即可
二、配置BCOS网络并在JAVA上连接
1.首先进入bcos目录下的console/conf,比如我的目录是 ~/fisco/console/conf
cd ~/fisco/console/conf
修改其中的config.toml文件
vi config.toml
找到[network],将其中的127.0.0.1:20200和另一个 修改为本机ip地址,由于我是在虚拟机上,ip地址是192.168.33.11,所以修改如下
2.进入~/fisco/nodes/127.0.0.1/sdk
cd ~/fisco/nodes/127.0.0.1/sdk
将四个文件复制到项目的main下的resources/conf文件夹下(自己创建一个conf)
之后配置bcos的配置文件,官网上提供了基于spring和springboot的配置方案,但是我测试之后发现在后续的配置中都很麻烦,并且都没有跑通,所以还是选用最简单的xml配置方案,也很方便。
在项目的main/resources下创建applicationContext.xml文件
其完整内容如下
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">
<bean id="defaultConfigProperty" class="org.fisco.bcos.sdk.config.model.ConfigProperty">
<property name="cryptoMaterial">
<map>
<entry key="certPath" value="conf" />
</map>
</property>
<property name="network">
<map>
<entry key="peers">
<list>
<value>192.168.33.11:20200</value>
<value>192.168.33.11:20201</value>
</list>
</entry>
</map>
</property>
<property name="account">
<map>
<entry key="keyStoreDir" value="account" />
<entry key="accountAddress" value="" />
<entry key="accountFileFormat" value="pem" />
<entry key="password" value="" />
<entry key="accountFilePath" value="" />
</map>
</property>
<property name="threadPool">
<map>
<entry key="channelProcessorThreadSize" value="16" />
<entry key="receiptProcessorThreadSize" value="16" />
<entry key="maxBlockingQueueSize" value="102400" />
</map>
</property>
</bean>
<bean id="defaultConfigOption" class="org.fisco.bcos.sdk.config.ConfigOption">
<constructor-arg name="configProperty">
<ref bean="defaultConfigProperty"/>
</constructor-arg>
</bean>
<bean id="bcosSDK" class="org.fisco.bcos.sdk.BcosSDK">
<constructor-arg name="configOption">
<ref bean="defaultConfigOption"/>
</constructor-arg>
</bean>
</beans>
其中peers配置中的节点ip地址和端口要和上面配置的相同
<entry key="peers">
<list>
<value>192.168.33.11:20200</value>
<value>192.168.33.11:20201</value>
</list>
</entry>
三、编写智能合约并在项目中部署
1.bcos支持solidity,所以用remix开发即可
https://ethereum.github.io/browser-solidity/#optimize=false&version=soljson-v0.7.1+commit.f4a555be.js
这里使用自带的HelloWorld.sol演示,这个文件在~/fisco/console/contracts/solidity
pragma solidity ^0.4.2;
contract HelloWorld{
string name;
function set(string n){
emit test(n);
name = n;
}
event test(string a);
function HelloWorld(){
name = "Hello, World!";
}
function get()constant returns(string){
return name;
}
}
这个不涉及到表,实际上bcos的智能合约支持表结构存储,具体可以看官方文档https://fisco-bcos-documentation.readthedocs.io/zh_CN/latest/docs/manual/smart_contract.html#kvtable
需要注意的是,BCOS和mysql等不同的是表设置的主键是可以重复的,而且无论是增删改查,都需要传入主键值作为参数,而且不提供全表查询功能,所以如果需要全表查询,建议主键值可以设置一个全部都相同的,方便查询。
比如设计一个表是
name | number |
---|---|
张三 | 1 |
李四 | 2 |
王五 | 3 |
赵六 | 4 |
如果把name当做主键,搜索时只能使用 table.select(name, table.newCondition());这里的name不是主键列名,而是张三、李四、王五这些名字,此时就无法做到全表查询。
如果按下面这个设计表
type | name | number |
---|---|---|
school | 张三 | 1 |
school | 李四 | 2 |
school | 王五 | 3 |
school | 赵六 | 4 |
查询时可以直接 table.select(“school”, table.newCondition()); 这样查询的就是全部数据了
2.通过bcos自带的脚本将sol文件转化为java文件
进入console目录
cd ~/fisco/console
调用下面的start.sh脚本
./sol2java.sh org.example.demo.contract
这个org.example.demo.contract是你在项目中存放智能合约的包,按实际需求写
之后我们进入contracts/sdk/java/org/example/demo/contract
cd contracts/sdk/java/org/example/demo/contract
可以看到HelloWorld.java文件,将其下载到本机上(sz可以百度一下安装和使用方法,这里不赘述)
sz HelloWorld.java
将其放到项目中的contract目录下
3.接着我们在demo包下创建一个conf目录,在其下创建SdkBeanConfig类,用来配置连接区块链
package org.example.demo.config;
import lombok.extern.slf4j.Slf4j;
import org.fisco.bcos.sdk.BcosSDK;
import org.fisco.bcos.sdk.client.Client;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.math.BigInteger;
@Configuration
@Slf4j
public class SdkBeanConfig {
private BcosSDK bcosSDK;
private Client client;
@Autowired
private ContractConfig contractConfig;
@Bean
public Client client() throws Exception{
//指定配置xml文件的位置
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
bcosSDK = context.getBean(BcosSDK.class);
//选择群组1
client = bcosSDK.getClient(Integer.valueOf(1));
//连接后返回当前区块高度
BigInteger blockNumber = client.getBlockNumber().getBlockNumber();
System.out.println("==========================");
System.out.println(blockNumber);
System.out.println("==========================");
return client;
}
4.之后同级目录创建ContractConfig类,来记录智能合约地址
package org.example.demo.config;
import lombok.Data;
import org.springframework.context.annotation.Configuration;
@Data
@Configuration
public class ContractConfig {
private String HelloWorldAddress;
}
(文件位置别创建错了)
5.创建一个controller类,方便后续的测试
内容如下
package org.example.demo.controller;
import org.example.demo.service.HelloWorldService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("hello")
public class HelloController {
@Autowired private HelloWorldService service;
@GetMapping("set")
public String set(@RequestParam("n") String n) throws Exception {
System.out.println("n = " + n);
return service.set(n);
}
@GetMapping("get")
public String get() throws Exception {
return service.get();
}
}
6.之后再创建一个HelloWorldService类,来实现对智能合约方法的调用
package org.example.demo.service;
import javax.annotation.PostConstruct;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.example.demo.config.ContractConfig;
import org.example.demo.contract.HelloWorld;
import org.fisco.bcos.sdk.client.Client;
import org.fisco.bcos.sdk.model.TransactionReceipt;
import org.fisco.bcos.sdk.transaction.manager.AssembleTransactionProcessor;
import org.fisco.bcos.sdk.transaction.manager.TransactionProcessorFactory;
import org.fisco.bcos.sdk.transaction.model.exception.ContractException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
@NoArgsConstructor
@Data
public class HelloWorldService {
@Autowired private Client client;
@Autowired private ContractConfig contractConfig;
AssembleTransactionProcessor txProcessor;
private HelloWorld helloWorld;
@PostConstruct
//项目部署时运行一次
public void init() throws Exception {
this.txProcessor =
TransactionProcessorFactory.createAssembleTransactionProcessor(
this.client, this.client.getCryptoSuite().getCryptoKeyPair());
//判断在智能合约配置类里,该智能合约是否已经有地址
if(contractConfig.getHelloWorldAddress() == null ||
contractConfig.getHelloWorldAddress().isEmpty()){
//如果没有的话,将其部署到链上
helloWorld = HelloWorld.deploy(client,client.getCryptoSuite().getCryptoKeyPair());
System.out.println("address = " + helloWorld.getContractAddress());
//将该合约的地址填写到智能合约配置类中
contractConfig.setHelloWorldAddress(helloWorld.getContractAddress());
}else{
//如果已有地址,则加载
helloWorld = HelloWorld.load(
contractConfig.getHelloWorldAddress(),
client,
client.getCryptoSuite().getCryptoKeyPair()
);
}
;
}
public String set(String n) throws Exception {
TransactionReceipt set = helloWorld.set(n);
return set.getBlockNumber();
}
public String get() throws Exception {
String mess = helloWorld.get();
return mess;
}
}
7.全部写好后,我们开始测试结果
启动项目后,我们可以看到当前部署后的智能合约地址0x192e21b94ba64a2254380d387dfa40e25b464ed0
打开浏览器,输入localhost:8080/hello/get,可以正确得到返回值
再输入localhost:8080/hello/set?n=testContract,返回了十六进制的高度
再次输入localhost:8080/hello/get,发现修改成功
现在重新运行一下项目
可以看到控制台的智能合约地址已经变成了 0xd8c64830531cafe67125d41dda90efc03fa17ea7
我们打开浏览器,输入localhost:8080/hello/get,发现返回值复原了,证明确实是又重新部署了智能合约
现在我们进入ContractConfig类中,把第一次的智能合约地址赋给HelloWorldAddress
package org.example.demo.config;
import lombok.Data;
import org.springframework.context.annotation.Configuration;
/**
* @Auther sundaohan
* @Package com.neu.config.bcos
* @Title ContractConfig
* @Description TODO
* @Date 2022/1/18 下午9:33
*/
@Data
@Configuration
public class ContractConfig {
private String HelloWorldAddress = "0x192e21b94ba64a2254380d387dfa40e25b464ed0";
}
再次启动项目后,打开浏览器,输入localhost:8080/hello/get,发现返回值是我们当时修改的testContract,证明这次加载的是我们当时部署的那个合约
至此,BCOS智能合约在springboot框架下的java项目中的部署与调用演示完毕。