欢迎访问 生活随笔!

生活随笔

当前位置: 首页 > 前端技术 > javascript >内容正文

javascript

Springboot+web3j(4.7)+实战+填坑

发布时间:2024/1/1 javascript 44 豆豆
生活随笔 收集整理的这篇文章主要介绍了 Springboot+web3j(4.7)+实战+填坑 小编觉得挺不错的,现在分享给大家,帮大家做个参考.

实现功能:获取合约event数据(相当于日志)。


中文文档
目前我找的比较好的文档是 汇智网 的,java以太坊库web3j文档


搭建项目
Springboot版本

<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.3.4.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent>

web3j依赖

<!--Java 操作智能合约 开始--><!--web3j-spring-boot-starter使用的web3j版本为3.x。本项目使用web3j的4.x版本--><!-- <dependency>--><!-- <groupId>org.web3j</groupId>--><!-- <artifactId>web3j-spring-boot-starter</artifactId>--><!-- <version>1.6.0</version>--><!-- </dependency>--><!-- https://mvnrepository.com/artifact/org.jetbrains.kotlin/kotlin-stdlib --><!--springboot 2.3.4 不需要,2.1.7 需要 --><!-- <dependency>--><!-- <groupId>org.jetbrains.kotlin</groupId>--><!-- <artifactId>kotlin-stdlib</artifactId>--><!-- <version>1.3.70</version>--><!-- </dependency>--><!--okhttp与logging-interceptor可根据项目实际情况选择是否添加--><dependency><groupId>com.squareup.okhttp3</groupId><artifactId>okhttp</artifactId><version>4.3.1</version></dependency><dependency><groupId>com.squareup.okhttp3</groupId><artifactId>logging-interceptor</artifactId><version>4.3.1</version></dependency><dependency><groupId>org.web3j</groupId><artifactId>core</artifactId><version>4.7.0</version></dependency><!--Java 操作智能合约 结束-->

fasterxml依赖

<!--这个依赖可根据项目实际情况选择是否添加--><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-core</artifactId><version>2.11.2</version></dependency><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-annotations</artifactId><version>2.11.2</version></dependency><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.11.2</version></dependency>

web3j maven plugin
我们把合约文件(.sol)放在resources目录下,运行插件,即可生成合约对应的Java类。这个插件会根据你的合约版本自动下载对应的solidity编译器,真正实现一键生成合约java类,非常好用,老外就是牛皮。

<!--mvn web3j:generate-sources--><plugin><groupId>org.web3j</groupId><artifactId>web3j-maven-plugin</artifactId><version>4.6.5</version><configuration><packageName>com.contract</packageName><sourceDestination>src/main/java/generated</sourceDestination><nativeJavaType>true</nativeJavaType><outputFormat>java,bin</outputFormat><soliditySourceFiles><directory>src/main/resources</directory><includes><include>**/*.sol</include></includes></soliditySourceFiles><outputDirectory><java>src/java/generated</java><bin>src/bin/generated</bin><abi>src/abi/generated</abi></outputDirectory><contract><!--<includes>--><!-- <include>Chip</include>--><!--</includes>--><!--<excludes>--><!-- <exclude>Hello</exclude>--><!-- <exclude>Chip</exclude>--><!--</excludes>--></contract><pathPrefixes><pathPrefix>dep=../dependencies</pathPrefix></pathPrefixes></configuration></plugin>

创建event过滤器

package com.fc.task.contract.config;import com.fc.task.contract.contract.Chip; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Scope; import org.web3j.crypto.Credentials; import org.web3j.protocol.Web3j; import org.web3j.protocol.core.DefaultBlockParameterName; import org.web3j.protocol.core.methods.request.EthFilter; import org.web3j.protocol.http.HttpService; import org.web3j.tx.gas.DefaultGasProvider;import java.io.IOException;/*** @author ydw* @version 1.0* @date 2020/11/6 10:26*/ @Configuration public class ContractConfigDemo {/*** 为Spring容器注入一个web3j实例。* 方便我们后续用 @Autowired 调用web3j* <p>* web3j Infura 模块提供了一个Infura Http 客户端(InfuraHttpService),* 它为Infura特定的Infura-Ethereum-Preferred-Client提供支持。* 你可以指定geth或Parity客户端响应你的请求,* 并且可以像普通的HTTPClient一样创建客户端。* <p>* 首先,你需要在 infura.io 注册并创建一个project,* 得到你的 https://mainnet.infura.io/v3/xxxxxxxxxxxxxxxxxxxx (主网)* or https://rinkeby.infura.io/v3/xxxxxxxxxxxxxxxxxxx (测试网)** @return*/@Beanpublic Web3j web3j() {String ip = "https://mainnet.infura.io/v3/xxxxxxxxxxxxxxxxxxxx";Web3j web3j = Web3j.build(new HttpService(ip));return web3j;}/*** @param chip 智能合约java类* @param web3j web3j客户端* @return*/@Bean(name = "inviteFilter") // 如果你有多个过滤器,你需要指定每个过滤器的名字@Scope("prototype") // 你可能要同时监听多个事件,那么就不能使用同一个实例,因此这里需要每次都生成一个新的对象public EthFilter ethFilter(Chip chip, Web3j web3j) throws IOException {// 两种方式生成过滤器// 方式一:通过智能合约java类// // 获取当前区块链的区块 // BigInteger startBlockNum = web3j.ethBlockNumber().send().getBlockNumber(); // return new EthFilter( // // 开始区块 // DefaultBlockParameter.valueOf(startBlockNum), // // 直接设置开始区块为初始区块。当这样设置时,我们在后续监听event(智能合约的概念,相当于日志)事件时,会得到初始区块到最新区块之间的所有数据。 // // DefaultBlockParameterName.EARLIEST, // // // 结束区块。这里直接监听最后一个区块,即最新的区块 // DefaultBlockParameterName.LATEST, // // // 智能合约地址 // // 如果监听不到,这里的地址可以把 0x 去掉 // chip.getContractAddress() // );// 方式二:通过智能合约地址。这种方式不需要我们把智能合约转为java类,更加灵活。String contractAddress = "";String eventTopics = "";return new EthFilter(DefaultBlockParameterName.EARLIEST,DefaultBlockParameterName.LATEST,contractAddress)// 这里我在创建过滤器时,就直接指定eventTopics即指定我要监听的event,// 也可以先不指定,例如方式一,在后续使用时再指定。.addSingleTopic(eventTopics);}/*** 往Spring容器注入智能合约Java类** @param web3j* @return* @throws IOException*/@Beanpublic Chip chip(Web3j web3j) throws IOException {// 加载智能合约,线上智能合约与本地java类映射Chip chip;// 链上智能合约地址(部署智能合约后得到的地址)String chipAddress = ""; // // 方式一:通过 org.web3j.tx.transactionManager // String yourMetaMaskAddress = ""; // TransactionManager transactionManager = new ClientTransactionManager(web3j, yourMetaMaskAddress); // Chip chip = Chip.load(chipAddress, web3j, transactionManager, new DefaultGasProvider());// 方式二:通过 org.web3j.crypto.Credentials// MetaMask(小狐狸,谷歌浏览器的一个插件,需要翻墙安装,方便进行智能合约交互) 私钥或其他类似于Metamask 产品的私钥String privateKey = "";Credentials credentials = Credentials.create(privateKey);chip = Chip.load(chipAddress, web3j, credentials, new DefaultGasProvider());return chip;} }

创建监听器

package com.fc.task.contract.listener;import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; import org.springframework.stereotype.Component; import org.web3j.protocol.Web3j; import org.web3j.protocol.core.methods.request.EthFilter;/*** @author ydw* @version 1.0* @date 2020/11/6 11:15*/ @Component public class ContractListenerDemo implements ApplicationRunner {@AutowiredWeb3j web3j;@Autowired@Qualifier("inviteFilter") // 你自己创建的过滤器EthFilter inviteFilter;@Overridepublic void run(ApplicationArguments args) throws Exception {this.inviteFilterHandle();}private void inviteFilterHandle() {// 当你的过滤器没有指定event topic时// 方式一:使用智能合约Java类// inviteFilter.addSingleTopic(EventEncoder.encode(Chip.ADDREWARDCHIP_EVENT));// 方式二:从 etherscan.io 中获取/*** 合约被部署到以太坊后,可通过合约地址查询合约的信息,* 其中就能看到event,而event中就有你想要的topic* */// String eventTopic = "";// inviteFilter.addSingleTopic(eventTopic);// 因为我的 inviteFilter 在创建时就已经指定了topic,所以我在这里就再指定。web3j.ethLogFlowable(inviteFilter).subscribe(log->{System.out.println("收到事件inviteFilter");// 在合约中,当event的emit函数的参数被index修饰,这里表现为log中的topics,// 否则它们将会出现在data中。 });} }

过滤器和监听器结合使即可完成监听合约event功能。


定时任务获取event数据
当我们使用监听器获取数据时,很有可能会漏数据,这时我们需要补救措施,我这里使用的是定时器5秒获取一次数据。
这里我们需要用到 https://etherscan.io/ 的api。首先你要去这个网站注册账号,得到自己的apikey。主网API说明点这里

package com.fc.service.mananger;import com.alibaba.fastjson.JSONObject; import org.apache.http.HttpEntity; import org.apache.http.NameValuePair; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.utils.URIBuilder; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.message.BasicNameValuePair; import org.apache.http.util.EntityUtils; import org.springframework.stereotype.Component;import java.io.IOException; import java.net.URI; import java.util.*;/*** @author ydw* @version 1.0* @date 2020/11/6 11:40*/ @Component public class EtherscanApiDemo {public void getUserRelationship() {String startBlockNumber = "";// 开始区块String inviteContractAddress = "";// 合约地址String apiKey = "";// etherscan.io 中你自己的apikeyString inviteContractTopic = ""; // 合约 event 的 topicString url = "";// url: "https://api-cn.etherscan.com/api"; 主网// url: "https://api-rinkeby.etherscan.io/api" 测试网Map<String, Object> parameters = new HashMap<>(7);parameters.put("module", "logs");parameters.put("action", "getLogs");parameters.put("fromBlock", startBlockNumber);parameters.put("toBlock", "latest");parameters.put("address", inviteContractAddress);parameters.put("topic0", inviteContractTopic);parameters.put("apikey", apiKey);String response = HttpUtil.doGet(url, parameters);TransactionsResponse tr = JSONObject.parseObject(response, TransactionsResponse.class);} }import org.apache.http.HttpEntity; import org.apache.http.NameValuePair; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.utils.URIBuilder; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.message.BasicNameValuePair; import org.apache.http.util.EntityUtils;import java.io.IOException; import java.net.URI; import java.util.ArrayList; import java.util.List; import java.util.Map;/*** httpClient工具类** @author heyi*/ class HttpUtil {private final static int NORMAL_STATUS = 200;/*** get请求处理** @param url* @param args* @return*/public static String doGet(String url, Map<String, Object>... args) {//创建默认的httpClient实例CloseableHttpClient httpClient = HttpClients.createDefault();//httpResponse响应对象CloseableHttpResponse response = null;//响应返回结果String resultString = "";try {//创建uriURIBuilder builder = new URIBuilder(url);if (args.length > 0) {args[0].forEach((k, v) -> builder.addParameter(k, String.valueOf(v)));}URI uri = builder.build();// 创建http GET请求HttpGet httpGet = new HttpGet(uri);// 执行请求response = httpClient.execute(httpGet);// 判断返回状态是否为200if (response != null && response.getStatusLine().getStatusCode() == NORMAL_STATUS) {// 获取响应实体HttpEntity httpEntity = response.getEntity();resultString = EntityUtils.toString(httpEntity, "UTF-8");}} catch (Exception e) {e.printStackTrace();} finally {try {if (response != null) {response.close();}httpClient.close();} catch (IOException e) {e.printStackTrace();}}return resultString;}/*** post请求处理** @param url* @param args args[0] formdata args[1] header* @return*/public static String doPost(String url, Map<String, Object>... args) {//创建默认的httpClient实例CloseableHttpClient httpClient = HttpClients.createDefault();//httpResponse响应对象CloseableHttpResponse response = null;//响应返回结果String resultString = "";try {// 创建Http Post请求HttpPost httpPost = new HttpPost(url);if (args.length > 1) {args[1].forEach((k, v) -> httpPost.setHeader(k, String.valueOf(v)));}// 创建参数列表if (args.length > 0) {List<NameValuePair> formParams = new ArrayList<NameValuePair>();args[0].forEach((k, v) -> formParams.add(new BasicNameValuePair(k, String.valueOf(v))));// 模拟表单UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formParams, "UTF-8");httpPost.setEntity(entity);}//执行http请求response = httpClient.execute(httpPost);//获取响应结果// 判断返回状态是否为200if (response != null && response.getStatusLine().getStatusCode() == NORMAL_STATUS) {resultString = EntityUtils.toString(response.getEntity(), "UTF-8");}} catch (Exception e) {e.printStackTrace();} finally {try {if (response != null) {response.close();}httpClient.close();} catch (IOException e) {e.printStackTrace();}}return resultString;}/*** post请求处理** @param url* @return*/public static String doPostJson(String url, String json) {//创建默认的httpClient实例CloseableHttpClient httpClient = HttpClients.createDefault();//httpResponse响应对象CloseableHttpResponse response = null;//响应返回结果String resultString = "";try {// 创建Http Post请求HttpPost httpPost = new HttpPost(url);// 创建参数列表httpPost.setHeader("Content-Type", "application/json;charset=UTF-8");StringEntity se = new StringEntity(json, "UTF-8");se.setContentType("application/json");httpPost.setEntity(se);//执行http请求response = httpClient.execute(httpPost);//获取响应结果// 判断返回状态是否为200if (response != null && response.getStatusLine().getStatusCode() == NORMAL_STATUS) {resultString = EntityUtils.toString(response.getEntity(), "UTF-8");}} catch (Exception e) {e.printStackTrace();} finally {try {if (response != null) {response.close();}httpClient.close();} catch (IOException e) {e.printStackTrace();}}return resultString;} }class TransactionsResponse {private String status;private String message;private List<Transactions> result = new ArrayList<Transactions>();public TransactionsResponse() {}public String getStatus() {return status;}public void setStatus(String status) {this.status = status;}public String getMessage() {return message;}public void setMessage(String message) {this.message = message;}public List<Transactions> getResult() {return result;}public void setResult(List<Transactions> result) {this.result = result;}@Overridepublic String toString() {return "TransactionsResponse [status=" + status + ", message=" + message + ", result=" + result + "]";}}class Transactions {private String blockNumber;private String timeStamp;private String hash;private String nonce;private String blockHash;private String transactionIndex;private String from;private String to;private String value;private String gas;private String gasPrice;private String isError;private String txreceipt_status;private String input;private String contractAddress;private String cumulativeGasUsed;private String gasUsed;private String confirmations;private String address;private String[] topics;private String data;private String logIndex;private String transactionHash;public String getAddress() {return address;}public void setAddress(String address) {this.address = address;}public String[] getTopics() {return topics;}public void setTopics(String[] topics) {this.topics = topics;}public String getData() {return data;}public void setData(String data) {this.data = data;}public String getLogIndex() {return logIndex;}public void setLogIndex(String logIndex) {this.logIndex = logIndex;}public String getTransactionHash() {return transactionHash;}public void setTransactionHash(String transactionHash) {this.transactionHash = transactionHash;}public Transactions() {}public String getBlockNumber() {return blockNumber;}public void setBlockNumber(String blockNumber) {this.blockNumber = blockNumber;}public String getTimeStamp() {return timeStamp;}public void setTimeStamp(String timeStamp) {this.timeStamp = timeStamp;}public String getHash() {return hash;}public void setHash(String hash) {this.hash = hash;}public String getNonce() {return nonce;}public void setNonce(String nonce) {this.nonce = nonce;}public String getBlockHash() {return blockHash;}public void setBlockHash(String blockHash) {this.blockHash = blockHash;}public String getTransactionIndex() {return transactionIndex;}public void setTransactionIndex(String transactionIndex) {this.transactionIndex = transactionIndex;}public String getFrom() {return from;}public void setFrom(String from) {this.from = from;}public String getTo() {return to;}public void setTo(String to) {this.to = to;}public String getValue() {return value;}public void setValue(String value) {this.value = value;}public String getGas() {return gas;}public void setGas(String gas) {this.gas = gas;}public String getGasPrice() {return gasPrice;}public void setGasPrice(String gasPrice) {this.gasPrice = gasPrice;}public String getIsError() {return isError;}public void setIsError(String isError) {this.isError = isError;}public String getTxreceipt_status() {return txreceipt_status;}public void setTxreceipt_status(String txreceipt_status) {this.txreceipt_status = txreceipt_status;}public String getInput() {return input;}public void setInput(String input) {this.input = input;}public String getContractAddress() {return contractAddress;}public void setContractAddress(String contractAddress) {this.contractAddress = contractAddress;}public String getCumulativeGasUsed() {return cumulativeGasUsed;}public void setCumulativeGasUsed(String cumulativeGasUsed) {this.cumulativeGasUsed = cumulativeGasUsed;}public String getGasUsed() {return gasUsed;}public void setGasUsed(String gasUsed) {this.gasUsed = gasUsed;}public String getConfirmations() {return confirmations;}public void setConfirmations(String confirmations) {this.confirmations = confirmations;}@Overridepublic String toString() {return "Transactions{" +"blockNumber='" + blockNumber + '\'' +", timeStamp='" + timeStamp + '\'' +", hash='" + hash + '\'' +", nonce='" + nonce + '\'' +", blockHash='" + blockHash + '\'' +", transactionIndex='" + transactionIndex + '\'' +", from='" + from + '\'' +", to='" + to + '\'' +", value='" + value + '\'' +", gas='" + gas + '\'' +", gasPrice='" + gasPrice + '\'' +", isError='" + isError + '\'' +", txreceipt_status='" + txreceipt_status + '\'' +", input='" + input + '\'' +", contractAddress='" + contractAddress + '\'' +", cumulativeGasUsed='" + cumulativeGasUsed + '\'' +", gasUsed='" + gasUsed + '\'' +", confirmations='" + confirmations + '\'' +", address='" + address + '\'' +", topics=" + Arrays.toString(topics) +", data='" + data + '\'' +", logIndex='" + logIndex + '\'' +", transactionHash='" + transactionHash + '\'' +'}';} }

本文有引用这篇文章 web3j 的 Infura Http 客户端

总结

以上是生活随笔为你收集整理的Springboot+web3j(4.7)+实战+填坑的全部内容,希望文章能够帮你解决所遇到的问题。

如果觉得生活随笔网站内容还不错,欢迎将生活随笔推荐给好友。