Spring Boot项目中集成Geth与以太坊区块链进行交互操作实例_springboot以太坊
前置条件已经安装Geth并启动。
现在我们讲一下Spring Boot项目中集成Geth,然后怎么以太坊区块链进行交互操作。
1、添加依赖到工程pom.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文件
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
/** * @author deray.wang * @date 2024/04/20 17:18 */@Configurationpublic 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
/** * 转账ETH参数类 * @author */@Data@ApiModel@ToStringpublic 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;}
/** * @author deray.wang * @date 2024/04/20 13:44 */@Datapublic class BlockchainTransaction { private String id; //发送发件人ID private Integer fromId; //交易金额 private long value; //收件人ID private Integer toId; private Boolean accepted;}
5、封装操作区块链的方法 BlockchainService.java
BlockchainService.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
/** * @author deray.wang * @date 2024/11/20 13:52 */@Servicepublic 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
/** * @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); }}
大部分操作已经实现。有需要的可以联系我沟通。剩下的操作,给区块链处理,比如转账确认。