Spring Boot项目中集成Geth与以太坊区块链进行交互操作实例

前置条件已经安装Geth并启动。

现在我们讲一下Spring Boot项目中集成Geth,然后怎么以太坊区块链进行交互操作。

1、添加依赖到工程pom.xml

xml 复制代码
<dependency>  
    <groupId>org.web3j</groupId>  
    <artifactId>core</artifactId>  
    <version>4.8.7</version>  
</dependency>
<dependency>
    <groupId>org.web3j</groupId>
    <artifactId>geth</artifactId>
    <version>4.8.7</version>
</dependency>

2、添加配置到yml文件

yaml 复制代码
web3j:
#  client-address: http://192.168.99.100:8545
  client-address: http://127.0.0.1:8545
  admin-client: true
  httpTimeoutSeconds: 60000

3、ETH配置类EthConfig.java

java 复制代码
/**
 * @author deray.wang
 * @date 2024/04/20 17:18
 */
@Configuration
public class EthConfig {
    @Value("${web3j.client-address}")
    private String rpc;

    @Bean
    public Web3j web3j() {
        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        builder.connectTimeout(30*1000, TimeUnit.MILLISECONDS);
        builder.writeTimeout(30*1000, TimeUnit.MILLISECONDS);
        builder.readTimeout(30*1000, TimeUnit.MILLISECONDS);
        OkHttpClient httpClient = builder.build();
        Web3j web3j = Web3j.build(new HttpService(rpc,httpClient,false));
        return web3j;
    }

    /**
     * 初始化admin级别操作的对象
     * @return Admin
     */
    @Bean
    public Admin admin() {
        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        builder.connectTimeout(30*1000, TimeUnit.MILLISECONDS);
        builder.writeTimeout(30*1000, TimeUnit.MILLISECONDS);
        builder.readTimeout(30*1000, TimeUnit.MILLISECONDS);
        OkHttpClient httpClient = builder.build();
        Admin admin = Admin.build(new HttpService(rpc,httpClient,false));
        return admin;
    }

    /**
     * 初始化personal级别操作的对象
     * @return Geth
     */
    @Bean
    public Geth geth() {
        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        builder.connectTimeout(30*1000, TimeUnit.MILLISECONDS);
        builder.writeTimeout(30*1000, TimeUnit.MILLISECONDS);
        builder.readTimeout(30*1000, TimeUnit.MILLISECONDS);
        OkHttpClient httpClient = builder.build();
        Geth geth = Geth.build(new HttpService(rpc,httpClient,false));
        return geth;
    }

}

4、封装两个bean:转账ETH参数类 TransferEchBean.java 和BlockchainTransaction.java

java 复制代码
/**
 * 转账ETH参数类
 * @author
 */
@Data
@ApiModel
@ToString
public class TransferEchBean {


    @ApiModelProperty("fromAddr")
    private String fromAddr;

    @ApiModelProperty("密码")
    private String privateKey;

    @ApiModelProperty("toAddr")
    private String toAddr;

    @ApiModelProperty("amount")
    private BigDecimal amount;

    @ApiModelProperty("data")
    private String data;

}
java 复制代码
/**
 * @author deray.wang
 * @date 2024/04/20 13:44
 */
@Data
public class BlockchainTransaction {
    private String id;
    //发送发件人ID
    private Integer fromId;
    //交易金额
    private long value;
    //收件人ID
    private Integer toId;

    private Boolean accepted;
}

5、封装操作区块链的方法 BlockchainService.java

BlockchainService.java类

java 复制代码
/**
 * @author deray.wang
 * @date 2024/04/20 13:36
 */
public interface BlockchainService {

    /**
     * 获取账户的Nonce
     * @param web3j
     * @param addr
     * @return
     */
    BigInteger getAcountNonce(Web3j web3j, String addr);

    /**
     * 获取账户余额
     * @param web3j
     * @param addr
     * @return
     */
    BigDecimal getAccountBalance(Web3j web3j, String addr);

    /**
     * 查询区块内容
     * @param web3j
     * @param blockNumber
     * @return
     */
    EthBlock getBlockEthBlock(Web3j web3j, BigInteger blockNumber);

    /**
     * 创建钱包
     * @param password
     * @return
     */
    ServiceResponse newAccount(String password);

    /**
     * 地址列表
     * @return
     */
    List<String> getAllAccounts();

    /**
     * 转账ETH
     * @param web3j
     * @param fromAddr
     * @param privateKey
     * @param toAddr
     * @param amount
     * @param data
     * @return
     */
    ServiceResponse transferETHD(Web3j web3j, TransferEchBean filterBean);

    /**
     * 普通转账ETH
     * @param web3j
     * @param filterBean
     * @return
     */
    ServiceResponse tranETH(Web3j web3j, TransferEchBean filterBean);

实现类:BlockchainServiceImpl.java

java 复制代码
/**
 * @author deray.wang
 * @date 2024/11/20 13:52
 */
@Service
public class BlockchainServiceImpl implements BlockchainService {
    private static final Logger LOGGER = LoggerFactory.getLogger(BlockchainService.class);

    private static BigInteger gNoce = null;

    @Value("${wallet.file}")
    private String FILE;

    @Autowired
    private Admin admin;

    @Autowired
    private static Web3j web3j;

    @Autowired
    private Geth geth;

    /**
     * 获取账户的Nonce
     * @param web3j
     * @param addr
     * @return
     */
    @Override
    public  BigInteger getAcountNonce(Web3j web3j, String addr) {
        return getNonce(web3j,addr);
    }

    @Override
    public BigDecimal getAccountBalance(Web3j web3j, String addr) {
        return getBalance(web3j,addr);
    }

    /**
     *
     */
    @Override
    public ServiceResponse transferETHD(Web3j web3j, TransferEchBean filterBean){
        //封装业务参数
        Map<String,String> map = new HashMap<String,String>();
        map.put("time", String.valueOf(new Date()));
        map.put("type","info");
        map.put("msg","Web3 Test!!!000000000000000000000000000");

        JSONObject jsonObj=new JSONObject(map);

        //将data转化为hex
        String datahex = null;
        try {
            datahex = HexUtils.toHexString(jsonObj.toString().getBytes("UTF-8"));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }

        return transferETH(web3j,filterBean.getFromAddr(),filterBean.getPrivateKey(),filterBean.getToAddr(),filterBean.getAmount(),datahex);
    }

    /**
     *
     */
    @Override
    public ServiceResponse tranETH(Web3j web3j, TransferEchBean filterBean){
        。。
        return ServiceResponse.createFailResponse("",0,"");

    }
    /**
     * 指定地址发送交易所需nonce获取
     * @param web3j
     * @param addr
     * @return
     */
    public static  BigInteger getNonce(Web3j web3j, String addr){
        EthGetTransactionCount transactionCount = null;
        try {
            transactionCount = web3j.ethGetTransactionCount(addr, DefaultBlockParameterName.LATEST).sendAsync().get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        BigInteger nonce = transactionCount.getTransactionCount();
//        LOGGER.info("Tx hash: {}",  "transfer nonce : " + nonce);
        return nonce;
    }

    /**
     * 获取代币余额
     * @param web3j
     * @param fromAddress
     * @param contractAddress
     * @return
     */
    public static BigInteger getTokenBalance(Web3j web3j, String fromAddress, String contractAddress) {

        String methodName = "balanceOf";
        List<Type> inputParameters = new ArrayList<>();
        List<TypeReference<?>> outputParameters = new ArrayList<>();
        Address address = new Address(fromAddress);
        inputParameters.add(address);

        TypeReference<Uint256> typeReference = new TypeReference<Uint256>() {
        };
        outputParameters.add(typeReference);
        Function function = new Function(methodName, inputParameters, outputParameters);
        String data = FunctionEncoder.encode(function);
        Transaction transaction = Transaction.createEthCallTransaction(fromAddress, contractAddress, data);

        EthCall ethCall;
        BigInteger balanceValue = BigInteger.ZERO;
        try {
            ethCall = web3j.ethCall(transaction, DefaultBlockParameterName.LATEST).send();
            List<Type> results = FunctionReturnDecoder.decode(ethCall.getValue(), function.getOutputParameters());
            balanceValue = (BigInteger) results.get(0).getValue();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return balanceValue;
    }

    /**
     * 转账ETH
     * @param web3j
     * @param fromAddr 发起人钱包地址
     * @param privateKey 钱包私钥
     * @param toAddr 转入的钱包地址
     * @param amount 转账金额,单位是wei
     * @param data  备注的信息
     * @param
     * @return
     */
     ServiceResponse transferETH(Web3j web3j, String fromAddr, String privateKey, String toAddr, BigDecimal amount, String data){
        // 获得nonce
        BigInteger nonce = getNonce(web3j, fromAddr);
        // value转换
        BigInteger value = Convert.toWei(amount, Convert.Unit.ETHER).toBigInteger();
        // gasPrice转账费用
        BigInteger gasPrice;

        gasPrice = Convert.toWei("0", Convert.Unit.GWEI).toBigInteger();
        //注意手续费的设置,这块很容易遇到问题
        BigInteger gasLimit = Convert.toWei("45000", Convert.Unit.WEI).toBigInteger();
        // 查询调用者余额,检测余额是否充足
        BigDecimal ethBalance = getBalance(web3j, fromAddr);
        BigDecimal balance = Convert.toWei(ethBalance, Convert.Unit.ETHER);
        BigDecimal tt = Convert.toWei(String.valueOf(1), Convert.Unit.ETHER);
        checkMoney(String.valueOf(amount),String.valueOf(balance));
        BigInteger val = gasPrice.multiply(gasLimit);
        if (balance.compareTo(tt.add(new BigDecimal(val))) < 0) {
            //throw new RuntimeException("余额不足,请核实");
            return ServiceResponse.createFailResponse("",0,"交易失败:余额不足,请核实");
        }

         //对交易签名,并发送交易
         if(gNoce == null){
             gNoce = nonce;
         }
         LOGGER.info("Tx hash: {}",  "transfer nonce : " + gNoce+" gasPrice:"+gasPrice+" gasLimit:"+gasLimit+" toAddr:"+toAddr+" value:"+value+" data:"+data);

         RawTransaction rawTransaction = RawTransaction.createTransaction(gNoce, gasPrice, gasLimit, toAddr, value, data);
         gNoce = gNoce.add(new BigInteger("1"));
         //RawTransaction.createEtherTransaction(nonce,gasPrice,gasLimit,to,value);
         if (privateKey.startsWith("0x")){
             privateKey = privateKey.substring(2);
         }

         ECKeyPair ecKeyPair = ECKeyPair.create(new BigInteger(privateKey, 16));
         Credentials credentials = Credentials.create(ecKeyPair);
         System.out.println(credentials.getAddress());
         System.out.println("PrivateKey:" + credentials.getEcKeyPair().getPrivateKey());

         //进行签名操作 签名Transaction,这里要对交易做签名
         byte[] signedMessage = TransactionEncoder.signMessage(rawTransaction, credentials);
         String hexValue = Numeric.toHexString(signedMessage);

         //发送交易
         EthSendTransaction ethSendTransaction = null;
         if (!"".equals(hexValue)) {
             try {
                 ethSendTransaction = web3j.ethSendRawTransaction(hexValue).sendAsync().get();
                 if(ethSendTransaction.hasError()) {
                     String message = ethSendTransaction.getError().getMessage();
                     System.out.println("transaction failed,info:" + message);
                     //Utils.writeFile("F:/testErr.txt", "transaction failed,info:" + message);
                     return ServiceResponse.createFailResponse("",0,"交易失败:"+message);
                 }
             } catch (InterruptedException e) {
                 System.out.println("transaction failed,info:" + ethSendTransaction.getError().getMessage());
                 e.printStackTrace();
                 return ServiceResponse.createFailResponse("",0,"交易失败:"+ethSendTransaction.getError().getMessage());
             } catch (ExecutionException e) {
                 System.out.println("transaction failed,info:" + ethSendTransaction.getError().getMessage());
                 e.printStackTrace();
                 return ServiceResponse.createFailResponse("",0,"交易失败:"+ethSendTransaction.getError().getMessage());
             }
         }
         String transactionHash = ethSendTransaction.getTransactionHash();
         if(ethSendTransaction.hasError()){
             String message=ethSendTransaction.getError().getMessage();
             return ServiceResponse.createFailResponse("",0,"交易失败:"+message);
         }else {
             EthGetTransactionReceipt send = null;
             try {
                 send = web3j.ethGetTransactionReceipt(transactionHash).send();
             } catch (IOException e) {
                 e.printStackTrace();
             }
             if (send != null) {
                 System.out.println("交易成功");
                 //System.out.println(send.getTransactionReceipt());
             }
             Map<String,String> mapRes = new HashMap<String,String>();
             mapRes.put("txHash", transactionHash);
             return ServiceResponse.createSuccessResponse("",mapRes,"交易成功,等待记账!");
         }

    }

    /**
     * 获取ETH余额
     * @param web3j
     * @param address
     * @return
     */
    public static BigDecimal getBalance(Web3j web3j, String address) {
        try {
            EthGetBalance ethGetBalance = web3j.ethGetBalance(address, DefaultBlockParameterName.LATEST).send();
            //单位转换
            BigDecimal banlance = Convert.fromWei(new BigDecimal(ethGetBalance.getBalance()),Convert.Unit.ETHER);
            return banlance;
        } catch (IOException e) {
            e.printStackTrace();
            //throw new Exception("查询钱包余额失败");
            return null;
        }
    }


    public static BigInteger getTransactionGasLimit(Web3j web3j, Transaction transaction) {
        try {
            EthEstimateGas ethEstimateGas = web3j.ethEstimateGas(transaction).send();
            if (ethEstimateGas.hasError()){
                throw new RuntimeException(ethEstimateGas.getError().getMessage());
            }
            return ethEstimateGas.getAmountUsed();
        } catch (IOException e) {
            throw new RuntimeException("net error");
        }
    }

    /**
     * generate a random group of mnemonics
     * 生成一组随机的助记词
     */
    private String generateMnemonics() {
        byte[] initialEntropy = new byte[16];
        new SecureRandom().nextBytes(initialEntropy);
        String mnemonic = MnemonicUtils.generateMnemonic(initialEntropy);
        return mnemonic;
    }

    private Map<String,String> createAccount() {

        //创建Map对象
        Map<String, String> map = new HashMap<String,String>();       //数据采用的哈希表结构
        Bip39Wallet wallet = null;
        // 创建一个存放keystore的文件夹
        String path = FILE;
        try {
            // 创建钱包
            wallet = WalletUtils.generateBip39Wallet("", new File(path));
        } catch (Exception e) {
            LOGGER.info("创建钱包失败");
        }
        // 获取keystore的名字
        String keyStoreKey = wallet.getFilename();
        LOGGER.info("keyStoreKey ================ " + keyStoreKey);
        // 获取助记词
        String mnemonic = wallet.getMnemonic();
        LOGGER.info("mnemonic ======================== " + mnemonic);
        // 使用密码和助记词让账户解锁
        Credentials credentials = WalletUtils.loadBip39Credentials("", wallet.getMnemonic());
        // 获取账户地址
        String address = credentials.getAddress();
        LOGGER.info("address ================= " + address);
        // 获取公钥
        String publicKey = credentials.getEcKeyPair().getPublicKey().toString(16);
        LOGGER.info("publicKey ==================== " + publicKey);
        // 获取私钥
        String privateKey = credentials.getEcKeyPair().getPrivateKey().toString(16);
        LOGGER.info("privateKey ================== " + privateKey);
        map.put("address",address);
        map.put("privateKey",privateKey);
        map.put("publicKey",publicKey);
        map.put("createTime", TimeUtil.getNowString());
        return map;
    }

    /**
     * 查询区块内容
     * @param web3j
     * @param blockNumber
     * @return
     */
    @Override
    public EthBlock getBlockEthBlock(Web3j web3j,BigInteger blockNumber){

        DefaultBlockParameter defaultBlockParameter = new DefaultBlockParameterNumber(blockNumber);
        Request<?, EthBlock> request = web3j.ethGetBlockByNumber(defaultBlockParameter, true);
        EthBlock ethBlock = null;
        try {
            ethBlock = request.send();
            //返回值 - 区块对象
            System.out.println(ethBlock.getBlock());
        } catch (IOException e) {
            e.printStackTrace();
        }
        return ethBlock;
    }

    /**
     * 输入密码创建地址
     * @param password 密码(建议同一个平台的地址使用一个相同的,且复杂度较高的密码)
     * @return 地址hash
     */
    @Override
    public ServiceResponse newAccount(String password) {
        return ServiceResponse.createSuccessResponse("",createAccount());

    }

    /**
     * 根据hash值获取交易
     * @param hash
     * @return
     * @throws IOException
     */
    public static EthTransaction getTransactionByHash(String hash) throws IOException {
         Request<?, EthTransaction> request = web3j.ethGetTransactionByHash(hash);
        return request.send();
    }

    /**
     * 账户解锁,使用完成之后需要锁定
     * @param address
     * @return
     * @throws IOException
     */
    public  Boolean lockAccount(String address) throws IOException {

        Request<?, BooleanResponse> request = geth.personalLockAccount(address);
        BooleanResponse response = request.send();
        return response.success();
    }

    /**
     * 解锁账户,发送交易前需要对账户进行解锁
     * @param address 地址
     * @param password 密码
     * @param duration 解锁有效时间,单位秒
     * @return
     * @throws IOException
     */
    public Boolean unlockAccount(String address, String password, BigInteger duration) throws IOException{
        Request<?, PersonalUnlockAccount> request = admin.personalUnlockAccount(address, password, duration);
        PersonalUnlockAccount account = request.send();
        return account.accountUnlocked();
    }

    /**
     * 发送交易并获得交易hash值
     * @param transaction
     * @param password
     * @return
     * @throws IOException
     */
    public  String sendTransaction(Transaction transaction, String password) throws IOException {
        Request<?, EthSendTransaction> request = admin.personalSendTransaction(transaction, password);
        EthSendTransaction ethSendTransaction = request.send();
        return ethSendTransaction.getTransactionHash();
    }

    /**
     *  获取钱包里的所有用户
     * @return
     */
    @Autowired
    public List<String> getAllAccounts() {
        List<String> list = new ArrayList<String>();
        try {
            Request<?, EthAccounts> request = geth.ethAccounts();
            list = request.send().getAccounts();
            System.out.println(list.toString());
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return  list;
    }

    /**
     * 钱包地址余额是否足够转账校验
     * @param bigDecimalValue
     * @param addressBalance
     * @return
     */
    public static String checkMoney(String bigDecimalValue,String addressBalance){
        if(new BigDecimal(addressBalance).subtract(new BigDecimal(bigDecimalValue)).compareTo(new BigDecimal("0")) <= 0){
            System.out.println("转账金额大于钱包地址余额");
            return  "转账金额大于钱包地址余额";
        }else{
            System.out.println("=======================");
            return "";
        }

    }

}

6、Controller类AccountController

java 复制代码
/**
 * @author deray.wang
 * @date 2019/11/27 17:16
 */
@Slf4j
@Api(value = "用户账号接口", tags = "用户账号接口")
@RestController
@RequestMapping(CommonConst.API_PATH_VERSION_1 + "/account")
public class AccountController {
    @Autowired
    private BlockchainService blockchainService;

    @RequestMapping(value = "/newAccount", method = RequestMethod.POST)
    @ApiOperation(httpMethod = "POST", value = "创建地址", produces = MediaType.APPLICATION_JSON_VALUE)
    public ServiceResponse newAccount(@ApiParam(name = "password") @RequestParam(name = "password") String password) {
        return blockchainService.newAccount(password);
    }

    @RequestMapping(value = "/getAccount", method = RequestMethod.GET)
    @ApiOperation(httpMethod = "GET", value = "获取钱包里的所有用户", produces = MediaType.APPLICATION_JSON_VALUE)
    public ServiceResponse getAllAccounts() {

        List<String> accounts = blockchainService.getAllAccounts();

        return ServiceResponse.createSuccessResponse("",accounts);
    }
}

大部分操作已经实现。有需要的可以联系我沟通。剩下的操作,给区块链处理,比如转账确认。

相关推荐
用户908324602732 天前
Spring AI 1.1.2 + Neo4j:用知识图谱增强 RAG 检索(上篇:图谱构建)
java·spring boot
用户8307196840823 天前
Spring Boot 集成 RabbitMQ :8 个最佳实践,杜绝消息丢失与队列阻塞
spring boot·后端·rabbitmq
Java水解3 天前
Spring Boot 视图层与模板引擎
spring boot·后端
Java水解3 天前
一文搞懂 Spring Boot 默认数据库连接池 HikariCP
spring boot·后端
洋洋技术笔记3 天前
Spring Boot Web MVC配置详解
spring boot·后端
初次攀爬者4 天前
Kafka 基础介绍
spring boot·kafka·消息队列
用户8307196840824 天前
spring ai alibaba + nacos +mcp 实现mcp服务负载均衡调用实战
spring boot·spring·mcp
Java水解4 天前
SpringBoot3全栈开发实战:从入门到精通的完整指南
spring boot·后端
初次攀爬者5 天前
RocketMQ在Spring Boot上的基础使用
java·spring boot·rocketmq
花花无缺5 天前
搞懂@Autowired 与@Resuorce
java·spring boot·后端