? ? ? ? 我们可以直接使用下面代码引入redis的Bean
package com.example.demo;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
@Component
@Lazy(false)
public class SpringContextHolder implements ApplicationContextAware, DisposableBean {
private static ApplicationContext applicationContext = null;
/**
* 取得存储在静态变量中的ApplicationContext.
*/
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
/**
* 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
*/
@SuppressWarnings("unchecked")
public static <T> T getBean(String name) {
return (T) applicationContext.getBean(name);
}
/**
* 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
*/
public static <T> T getBean(Class<T> requiredType) {
return applicationContext.getBean(requiredType);
}
public static <T> T getBean(String name, Class<T> clazz) {
return getApplicationContext().getBean(name, clazz);
}
/**
* 清除SpringContextHolder中的ApplicationContext为Null.
*/
public static void clearHolder() {
applicationContext = null;
}
/**
* 实现ApplicationContextAware接口, 注入Context到静态变量中.
*/
@Override
public void setApplicationContext(ApplicationContext appContext) {
applicationContext = appContext;
}
/**
* 实现DisposableBean接口, 在Context关闭时清理静态变量.
*/
@Override
public void destroy() {
SpringContextHolder.clearHolder();
}
}
Maven配置如下:
<dependency>
<groupId>org.web3j</groupId>
<artifactId>core</artifactId>
<version>3.4.0</version>
</dependency>
com.odcchina:
# 区块链节点地址
bscChainUrl: 'https://data-seed-prebsc-1-s1.binance.org:8545/'
contracts:
# 合约地址
bscContractUrl: '0x165fa14332d0ac163513d65D415aD2D692296B4d'
????????下面的每一段代码写的非常详细,我们需要注意的是我们合约日志如果加了indexed的话请使用:? logObject.getTopics();
? ? ? 如果没有加indexed的话可以使用下面代码获取,需要注意的是没有加indexed的日志他是将你那一条所有没有加indexed的参数拼接成字符串的,所以我们需要进行拆分,代码里面也写的非常详细了:
????????????????logObject.getData().substring(2, logObject.getData().length());
package com.example.demo.web3jLog;
import com.example.demo.util.RedisUtil;
import io.micrometer.core.instrument.util.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.web3j.abi.EventEncoder;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.core.DefaultBlockParameter;
import org.web3j.protocol.core.methods.request.EthFilter;
import org.web3j.protocol.core.methods.response.EthLog;
import org.web3j.protocol.http.HttpService;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@Component
public class Web3jTest {
//第一步操作,完成Redis的配置。
//第二步操作,完成web3j的maven配置
/**
* <dependency>
* <groupId>org.web3j</groupId>
* <artifactId>core</artifactId>
* <version>3.4.0</version>
* </dependency>
*/
//第三步,配置好数据库的定时任务
/**
* 启动类上面加上下面注解
*
* @EnableCaching // 启用缓存功能
* @EnableScheduling // 开启定时任务功能
*/
//定时任务类加上 @Component注解,方法上面加上 @Scheduled(cron = "*/15 * * * * ?"),里面的表达式自定义设置
/**
* 需要同步的合约地址
*/
@Value("${com.odcchina.contracts.bscContractUrl}")
private String bscContractUrl;
/**
* 完成web3的初始化 下面的地址引入区块链节点地址 BNB或者ETH的
*/
private Web3j bscWeb3j = Web3j.build(new HttpService("https://data-seed-prebsc-1-s1.binance.org:8545/"));
/**
* 每分钟同步一次,获取区块链最新区块号
*
* @throws IOException
*/
@Scheduled(initialDelay = 1 * 1000, fixedDelay = 1 * 1000)
public void latestBlockTask() throws IOException {
//同步最新区块
BigInteger latestBlock = bscWeb3j.ethBlockNumber().send().getBlockNumber();
RedisUtil.set("end-wen3jLogs", latestBlock.toString());
//第一次执行将退20000块开始扫描
String start = RedisUtil.get("start-wen3jLogs");
if (start == null || start.isEmpty()) {
RedisUtil.set("start-wen3jLogs", latestBlock.subtract(BigInteger.valueOf(20000)).toString());
}
}
/**
* 开始扫描日志
*/
@Scheduled(initialDelay = 1 * 1000, fixedDelay = 1 * 1000)
public void bscScanTask() {
String start = RedisUtil.get("start-wen3jLogs");
if (StringUtils.isEmpty(start)) {
try {
//如果没有块的话将获取块在进行扫描日志
latestBlockTask();
} catch (IOException e) {
return;
}
}
BigInteger scannedBlock = new BigInteger(start);
//最新区块
BigInteger latestBlock = new BigInteger(RedisUtil.get("end-wen3jLogs"));
//一次性同步多少块
BigInteger offset;
int times = 0;
//如果有1块的话就执行 并且 这里考虑性能一次性任务只跑60次 大于2是因为开始块要加1,比如上次同步到了区块5,那么这次要从第6块开始同步。
while (latestBlock.subtract(scannedBlock).longValue() > 2 && times++ < 60) {
if (latestBlock.longValue() - scannedBlock.longValue() > 10000) {
//如果大于1万块的话每次同步500块数据
offset = BigInteger.valueOf(500);
} else if (latestBlock.longValue() - scannedBlock.longValue() > 1000) {
//如果大于1000块的话每次同步200块数据
offset = BigInteger.valueOf(200);
} else if (latestBlock.longValue() - scannedBlock.longValue() > 100) {
//如果大于100块的话每次同步80块数据
offset = BigInteger.valueOf(80);
} else if (latestBlock.longValue() - scannedBlock.longValue() > 10) {
//如果大于10块的话每次同步8块数据
offset = BigInteger.valueOf(8);
} else if (latestBlock.longValue() - scannedBlock.longValue() > 4) {
offset = BigInteger.valueOf(3);
} else {
//最后一条一条数据同步
offset = BigInteger.valueOf(1);
}
if (latestBlock.subtract(scannedBlock).longValue() < offset.longValue()) {
break;
}
//根据区块值查询日志 如果原来同步到区块5的话 那么现在就从区块6开始同步
List<EthLog.LogResult> resp = scanBlock(scannedBlock.add(BigInteger.ONE), scannedBlock.add(offset));
for (EthLog.LogResult logItem : resp) {
//循环同步下来的地址,筛选我们需要获取的日志
handleLogEvent((EthLog.LogObject) logItem.get());
}
//日志同步完毕,将本次同步的区块数量加到初始区块号上面进行缓存
scannedBlock = scannedBlock.add(offset);
RedisUtil.set("start-wen3jLogs", scannedBlock.toString());
}
}
/**
* 根据区块号查询日志
*
* @param startBlock
* @param endBlock
* @return
*/
private List<EthLog.LogResult> scanBlock(BigInteger startBlock, BigInteger endBlock) {
EthFilter filter = new EthFilter(DefaultBlockParameter.valueOf(startBlock),
DefaultBlockParameter.valueOf(endBlock),
Arrays.asList(bscContractUrl));
EthLog send = null;
try {
send = bscWeb3j.ethGetLogs(filter).send();
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage());
}
return send.getLogs();
}
@Transactional
void handleLogEvent(EthLog.LogObject logObject) {
{
//这段代码会将日志没有加indexed的打印出来,并且组成数组
//event TestWeb3jToken2(address indexed userAddress, uint256 indexed order,address userAddress1, uint256 order1);
//比如上面这段合约就会将第三个地址userAddress1和第四个order1取出来,我们可以处理一些业务逻辑
String data = logObject.getData().substring(2);
List<String> dataList = new ArrayList<>(data.length() / 64);
int beginIndex = 0;
while (beginIndex < data.length()) {
dataList.add(data.substring(beginIndex, beginIndex + 64));
beginIndex += 64;
}
}
//这就是获取出来的数据,数据是日志加了indexed参数的
List<String> topList = logObject.getTopics();
String code = handle(logObject);
//对TestWeb3jToken1的日志开始处理
if (code != null && code.equals("TestWeb3jToken2")) {
//下面将进行数据的解析和存储
//第0个参数一般是固定 hash直接取
String topHash = topList.get(0);
//我们第1个参数为地址,需要截取取出 数据状态格式为 0x0000000000000000000000009175f9ecbbddc078e40c5e037ad31f4abf36628a
String addsser = topList.get(1);
addsser = "0x" + addsser.substring(26, addsser.length());
//我们日志里面的第二个参数为订单号,我们截取出来为16进制,需要我们转换成10进制
//一般数据格式为 0x00000000000000000000000000000000000000000000000004380663b7c5ec3c
String order = topList.get(2);
order = order.substring(2, order.length());
order = getBin16By10(order) + "" ;
{
//下面这段代码也是可以处理没有加indexed参数的日志
//event TestWeb3jToken2(address indexed userAddress, uint256 indexed order,address userAddress1, uint256 order1);
String date = logObject.getData().substring(2, logObject.getData().length());
String address = "0x" + date.substring(0, 64).substring(26, addsser.length());
String order1 = date.substring(64, date.length());
BigInteger typePas = getBin16By10(order1);
//上面的参数我们可以进行存储和业务逻辑处理
}
}
//记录事件
/*ChainEventLogRecord eventRecord = dslContext.newRecord(CHAIN_EVENT_LOG);
eventRecord.setContract(logObject.getAddress());
eventRecord.setTxHash(logObject.getTransactionHash());
eventRecord.setBlockHash(logObject.getBlockHash());
eventRecord.setBlockNum(logObject.getBlockNumber().longValue());
eventRecord.setEvent("ignore");
eventRecord.setToppics(String.join(",", logObject.getTopics()));
eventRecord.setData(logObject.getData());
eventRecord.setMd5(Md5Util.md5(eventRecord.getTxHash() + eventRecord.getToppics() + eventRecord.getData()));
RedisUtil.lock("MTXM-UMTP:txLock:" + eventRecord.getMd5(), 5);
int count = dslContext.selectCount().from(CHAIN_EVENT_LOG).where(
CHAIN_EVENT_LOG.MD5.eq(eventRecord.getMd5())
.and(CHAIN_EVENT_LOG.TX_HASH.eq(eventRecord.getTxHash()))).fetchOneInto(Integer.class);
//下面是为了防止存入重复日志
if (count > 0) {
log.info("tx_hash = {} 已录入,跳过", eventRecord.getTxHash());
RedisUtil.del("MTXM-UMTP:txLock:" + eventRecord.getMd5());
return;
}
if (logObject.getAddress().equalsIgnoreCase(smtAddress)) {
smTokenService.handle(logObject, dataList, eventRecord);
}
eventRecord.store();*/
}
/**
* 需要获取的合约日志
* 合约日志代码
* TODO TestWeb3jToken(address,uint256) 中间一定不能留空格
* event TestWeb3jToken(address indexed userAddress, uint256 indexed order);
* event TestWeb3jToken1(address userAddress, uint256 order);
* event TestWeb3jToken2(address indexed userAddress, uint256 indexed order,address userAddress1, uint256 order1);
*/
final String TESTWEB3JTOKEN = EventEncoder.buildEventSignature("TestWeb3jToken(address,uint256)");
final String TestWeb3jToken1 = EventEncoder.buildEventSignature("TestWeb3jToken1(address,uint256)");
final String TESTWEB3JTOKEN2 = EventEncoder.buildEventSignature("TestWeb3jToken2(address,uint256,address,uint256)");
/**
* 如果该日志是这个方法打印出来的将该方法返回
*
* @param logObject
* @return
*/
public String handle(EthLog.LogObject logObject) {
if (logObject.getTopics().get(0).equalsIgnoreCase(TESTWEB3JTOKEN)) {
return "TestWeb3jToken" ;
} else if (logObject.getTopics().get(0).equalsIgnoreCase(TestWeb3jToken1)) {
return "TestWeb3jToken1" ;
} else if (logObject.getTopics().get(0).equalsIgnoreCase(TESTWEB3JTOKEN2)) {
return "TestWeb3jToken2" ;
}
return null;
}
/**
* 16进制转10进制
*
* @param code
* @return
*/
public static BigInteger getBin16By10(String code) {
return new BigInteger(code, 16);
}
}
下面为大家准备了两份合约代码,可以直接运行的,配置上面Java代码可以直接呈现和测试,第一份合约就是一个普通的方法,里面打印了3条不同的日志,第二个合约是日志的定义。
pragma solidity ^0.5.1;
import "./web3j_tokenBasic.sol";
contract business_token is web3j_tokenBasic{
function orderPayBnb() public returns (bool success) {
//不做任何业务处理,直接打印3种不同的日志
address tsc = 0xa7B049d3A19796B195B0e976ec43EB7a12f07Bf9;
emit TestWeb3jToken(msg.sender, 2555);
emit TestWeb3jToken1(msg.sender, 3000);
emit TestWeb3jToken2(msg.sender, 7000,tsc, 8000);
return true;
}
}
pragma solidity ^0.5.1;
contract web3j_tokenBasic {
event TestWeb3jToken(address indexed userAddress, uint256 indexed order);
event TestWeb3jToken1(address userAddress, uint256 order);
event TestWeb3jToken2(address indexed userAddress, uint256 indexed order,address userAddress1, uint256 order1);
}
|