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);
    }
}

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

相关推荐
李南想做条咸鱼18 分钟前
vscode spring boot项目编辑yaml不自动提示补全如何解决
ide·spring boot·vscode
机智的小神仙儿2 小时前
我是Redis,请看我和mysql是如何交互的吧~
redis·mysql·缓存·交互
程序员大金2 小时前
基于SpringBoot+Vue+MySQL的垃圾分类回收管理系统
java·vue.js·spring boot·后端·mysql·mybatis
coding侠客2 小时前
Spring Boot 注解探秘:常用配置值读取注解的魔力
java·spring boot·后端·spring·spring cloud
程序员大金2 小时前
基于SpringBoot+Vue+MySQL的影院购票系统
java·vue.js·spring boot·后端·mysql·mybatis
bighu3 小时前
chainLink vrf实验
区块链·l2·chainlink·vrf
工业甲酰苯胺5 小时前
Springboot中自定义监听器
java·spring boot·spring
一粟1026 小时前
Spring Boot:现代化Java应用开发的艺术
java·spring boot·后端
IT研究室6 小时前
计算机毕业设计选题推荐-校园车辆管理系统-Java/Python项目实战(亮点:数据可视化分析、账号锁定)
java·spring boot·python·django·毕业设计·源码·课程设计
coffee_baby6 小时前
桥接模式详解和分析JDBC中的应用
java·数据库·spring boot·java-ee·桥接模式