Java SpringBoot 实现 TRC20 USDT 自动充值监听系统

2次阅读
没有评论

共计 9867 个字符,预计需要花费 25 分钟才能阅读完成。

Java SpringBoot 实现 TRC20 USDT 自动充值监听系统

本文分享一套基于 Java SpringBoot 开发的 TRC20 USDT 自动充值监听系统,支持自动扫描 TRON 区块、识别 USDT 转账、匹配充值订单、商户回调、Telegram 通知、漏块补扫以及 API Key 池限流。

一、系统主要功能

  • 自动扫描 TRON 最新区块
  • 自动识别 TRC20 USDT 转账
  • 根据收款地址和金额匹配订单
  • 充值成功后自动更新订单状态
  • 自动发送商户 callback 回调
  • 支持 Telegram 成功、失败、超时通知
  • 支持漏块补扫,防止漏单
  • 支持 API Key 池和请求限流

二、核心类配置

首先创建一个定时扫描类,并开启 SpringBoot 定时任务。

@Configuration
@EnableScheduling
public class Trc20OrderScanner {}

@EnableScheduling 用于开启定时任务,后续可以通过 @Scheduled 定时扫描区块。

三、TRON 节点和 USDT 合约地址

private static final String NODE_URL = "https://api.trongrid.io";

private static final String USDT_CONTRACT = "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t";

NODE_URL 是 TronGrid 官方接口地址,USDT_CONTRACT 是 TRON 链上 USDT 的合约地址。

四、订单监听 Map 设计

系统使用一个全局 Map 来保存当前需要监听的充值订单。

public static final Map<String, Set<UsdtDepositOrder>> orderMap = new ConcurrentHashMap<>();

这里的结构是:

 收款地址 => 该地址下的订单集合 

这样设计的好处是,可以快速通过收款地址找到对应订单,同时支持同一个地址挂多个订单。

五、API Key 池设计

为了避免单个 TronGrid API Key 请求过多导致限流,系统设计了 API Key 池。

private final BlockingQueue<String> apiKeyPool = new LinkedBlockingQueue<>();

初始化 API Key:

private void initApiKeyPool() {List<String> apiKeys = myAppConfig.getApiKeys();

    if (apiKeys == null || apiKeys.isEmpty()) {LogUtils.error("API Keys 为空,请检查 application.yml 配置!");
        return;
    }

    for (String apiKey : apiKeys) {apiKeyPool.offer(apiKey);
    }

    LogUtils.info("成功加载 {} 个 API Keys", apiKeyPool.size());
}

六、接口请求限流

系统使用 Guava 的 RateLimiter 控制请求频率。

private final RateLimiter apiRateLimiter = RateLimiter.create(15.0);

表示每秒最多请求 15 次,防止接口调用过快。

private String getApiKeyForTask() {if (!apiRateLimiter.tryAcquire()) {LogUtils.warn("API 请求超限,等待令牌...");
        apiRateLimiter.acquire();}

    try {return apiKeyPool.poll(3, TimeUnit.SECONDS);
    } catch (InterruptedException e) {Thread.currentThread().interrupt();
        return null;
    }
}

七、主扫描任务

主扫描任务每 3 秒执行一次。

@Scheduled(fixedRate = 3000)
public void runTask() {
    try {if (orderMap.isEmpty()) {
            lastScannedBlock = -1;
            return;
        }

        clearExpiredOrders();

        int latestBlock = getLatestBlockNumber();

        if (latestBlock == -1) {return;}

        if (lastScannedBlock == -1) {
            lastScannedBlock = latestBlock;
            LogUtils.info("初始化扫描区块为: {}", latestBlock);
            return;
        }

        for (int i = lastScannedBlock + 1; i < latestBlock; i++) {LogUtils.warn("发现遗漏区块: {}", i);
            missingBlocksList.add(i);
        }

        scanBlockTransactions(latestBlock);

        lastScannedBlock = latestBlock;

    } catch (Exception e) {LogUtils.error("runTask 执行异常: {}", e.getMessage(), e);
    }
}

这段逻辑会先检查是否有待监听订单,如果没有订单,就重置区块游标,避免无意义扫描。

八、获取最新区块号

private int getLatestBlockNumber() {String apiKey = getApiKeyForTask();

    if (apiKey == null) {LogUtils.error("无法获取 API Key,跳过最新区块查询");
        return -1;
    }

    JSONObject response = null;

    try {
        String url = NODE_URL + "/wallet/getblock";

        Map<String, String> headers = new HashMap<>();
        headers.put("TRON-PRO-API-KEY", apiKey);
        headers.put("Content-Type", "application/json");

        response = sendPostRequest(url, "{}", headers);

    } catch (Exception e) {LogUtils.error("获取最新区块失败: {}", e.getMessage(), e);
        return -1;
    } finally {returnApiKey(apiKey);
    }

    try {
        return response == null ? -1 :
                response.getJSONObject("block_header")
                        .getJSONObject("raw_data")
                        .getInt("number");
    } catch (Exception e) {LogUtils.error("解析最新区块号失败: {}", e.getMessage(), e);
        return -1;
    }
}

九、扫描指定区块交易

private void scanBlockTransactions(int blockNumber) {long startTime = System.currentTimeMillis();

    String url = NODE_URL + "/wallet/getblockbynum";
    JSONObject requestBody = new JSONObject().put("num", blockNumber);

    String apiKey = getApiKeyForTask();

    if (apiKey == null) {LogUtils.error("无法获取 API Key,跳过区块: {}", blockNumber);
        return;
    }

    Map<String, String> headers = new HashMap<>();
    headers.put("TRON-PRO-API-KEY", apiKey);
    headers.put("Content-Type", "application/json");

    JSONObject response = null;

    try {response = sendPostRequest(url, requestBody.toString(), headers);
    } catch (Exception e) {LogUtils.error("获取区块 {} 信息失败: {}", blockNumber, e.getMessage(), e);
        missingBlocksList.add(blockNumber);
        return;
    } finally {returnApiKey(apiKey);
    }

    parseBlockTransactions(blockNumber, response);

    int txCount = response != null && response.has("transactions")
            ? response.getJSONArray("transactions").length()
            : 0;

    LogUtils.info("区块 {} 扫描完成,耗时: {}ms, 交易数: {}",
            blockNumber,
            System.currentTimeMillis() - startTime,
            txCount);
}

十、解析 TRC20 USDT 转账

系统只处理 TRC20 的 transfer 方法。

String methodId = data.substring(0, 8);

if (!"a9059cbb".equals(methodId)) {continue;}

a9059cbb 是 TRC20/ERC20 的 transfer(address,uint256) 方法 ID。

十一、过滤 USDT 合约

if (!USDT_CONTRACT.equals(TronTransactionUtils.hexToTronAddress(contractAddress))) {continue;}

通过合约地址判断当前交易是否为 USDT 转账,避免误处理其它 TRC20 代币。

十二、交易去重机制

为了防止同一笔交易重复推送或重复回调,系统使用交易哈希进行去重。

private final Set<String> pushedTxHashes = Collections.synchronizedSet(new LinkedHashSet<String>() {
    @Override
    public boolean add(String e) {if (size() >= 1000) {Iterator<String> it = iterator();
            if (it.hasNext()) {it.next();
                it.remove();}
        }
        return super.add(e);
    }
});

这里最多保留 1000 条交易哈希,避免集合无限增长。

十三、订单匹配逻辑

系统会先解析接收地址,然后通过地址查找监听订单。

String recipient = TronTransactionUtils.getRecipientAddress(transactionJson);

Set<UsdtDepositOrder> orders = orderMap.get(recipient);

if (orders == null || orders.isEmpty()) {return;}

如果接收地址不是系统监听的地址,则直接跳过,减少不必要的解析计算。

确认是监听地址后,再解析发送方和金额。

String sender = TronTransactionUtils.getSenderAddress(transactionJson);

BigDecimal amount = TronTransactionUtils.getAmount(transactionJson);

然后根据金额进行二次匹配。

if (order.getAmountUsdt().compareTo(amount) != 0) {continue;}

十四、处理匹配成功的订单

processMatchedOrder(blockNumber, order, sender, recipient, txID, txTime);

匹配成功后,系统会执行以下步骤:

  1. 更新本地订单状态为成功
  2. 从监听集合中移除订单
  3. 发送商户 callback 回调
  4. 写入本地 callback 记录
  5. 发送 Telegram 通知

十五、更新订单状态

usdtDepositOrderService.updateDepositOrderStatus(order.getOrderNo(), 1);

这里的状态 1 表示订单充值成功。

十六、移除已完成订单

Set<UsdtDepositOrder> orders = orderMap.get(recipient);

if (orders != null) {orders.removeIf(o -> o.getOrderNo().equals(order.getOrderNo()));

    if (orders.isEmpty()) {orderMap.remove(recipient);
    }
}

订单成功后必须从监听集合中移除,否则后续可能重复匹配。

十七、商户 Callback 回调

系统会把订单号、金额、协议、地址、状态和交易哈希回调给商户系统。

JSONObject jsonPayload = new JSONObject();

jsonPayload.put("OrderId", order.getOrderNo());
jsonPayload.put("amountUsdt", order.getAmountUsdt());
jsonPayload.put("Protocol", order.getProtocol());
jsonPayload.put("address", usdtDepositCallback.getRecipientAddress());
jsonPayload.put("Status", 1);
jsonPayload.put("TxID", usdtDepositCallback.getTransactionId());

示例回调参数如下:

{
    "OrderId": "订单号",
    "amountUsdt": "充值金额",
    "Protocol": "TRC20",
    "address": "收款地址",
    "Status": 1,
    "TxID": "交易哈希"
}

十八、发送 HTTP 回调请求

public static JSONObject callbacksendPostRequest(String urlString, String jsonBody) {JSONObject result = new JSONObject();

    try {Connection.Response response = Jsoup.connect(urlString)
                .method(Connection.Method.POST)
                .ignoreContentType(true)
                .ignoreHttpErrors(true)
                .timeout(10000)
                .header("Content-Type", "application/json")
                .requestBody(jsonBody)
                .execute();

        int status = response.statusCode();
        String body = response.body();

        result.put("status", status);
        result.put("response", body);

        if (status >= 400) {result.put("error", body);
        }

    } catch (Exception e) {result.put("status", 500);
        result.put("error", e.getClass().getName() + ":" + e.getMessage());
    }

    return result;
}

十九、Telegram 成功通知

当商户回调成功后,系统会发送 Telegram 通知。

String text = "✅<b>【回调成功】</b>\n\n" +
        "<b> 订单号:</b> <code>" + order.getOrderNo() + "</code>\n\n" +
        "<b>【金额】:</b> +" + order.getAmountUsdt() + "USDT\n" +
        "<b>【状态】:</b> #回调成功 \n" +
        "<b>【币种】:</b> #USDT\n\n" +
        "<b> 交易哈希:</b>\n<code>" + txID + "</code>\n" +
        "\n\n" +
        "<a href='https://tronscan.org/#/transaction/"+ txID +"'>【查看链上详情】</a>";

myBot.sendMessageToChannel(text);

二十、Telegram 失败通知

如果商户回调失败,也会发送失败通知,方便人工补单。

String text = "❌<b>【回调失败】</b>\n\n" +
        "<b> 订单号:</b> <code>" + order.getOrderNo() + "</code>\n\n" +
        "<b>【金额】:</b> +" + order.getAmountUsdt() + "USDT\n" +
        "<b>【状态】:</b> #回调失败 \n" +
        "<b>HTTP 状态码:</b>" + status + "\n" +
        "<b> 错误信息:</b>\n<code>" + shortError + "</code>" +
        "\n\n" +
        "⚠️<b> 处理建议:</b>\n" +
        "请查询订单或手动补发金额";

myBot.sendMessageToChannel(text);

二十一、订单超时处理

系统会自动清理超过 15 分钟未支付的订单。

LocalDateTime expireTime = order.getCreationTime().plusMinutes(15);

if (expireTime.isBefore(now)) {usdtDepositOrderService.updateDepositOrderStatus(order.getOrderNo(), 2);
    orderIterator.remove();}

这里的状态 2 表示订单超时。

二十二、漏块补扫机制

由于网络波动、接口异常或服务器压力等原因,扫描过程中可能会出现漏块,所以需要补扫机制。

private static final Set<Integer> missingBlocksList = Collections.synchronizedSet(new LinkedHashSet<Integer>() {
    @Override
    public boolean add(Integer e) {if (size() >= 100) {Iterator<Integer> it = iterator();
            if (it.hasNext()) {it.next();
                it.remove();}
        }
        return super.add(e);
    }
});

最多保留 100 个待补扫区块,避免内存无限增长。

二十三、定时补扫遗漏区块

@Scheduled(fixedRate = 5000)
public void processMissingBlocks() {if (missingBlocksList.isEmpty()) {return;}

    int maxBlocks = 3;
    int count = 0;

    Iterator<Integer> iterator = missingBlocksList.iterator();

    while (iterator.hasNext() && count++ < maxBlocks) {Integer blockNumber = iterator.next();

        try {LogUtils.info("开始补扫遗漏区块: {}, 当前剩余: {}", blockNumber, missingBlocksList.size());
            scanBlockTransactions(blockNumber);
            iterator.remove();} catch (Exception e) {LogUtils.error("补扫区块 {} 失败: {}", blockNumber, e.getMessage(), e);
        }
    }
}

这里每 5 秒执行一次补扫任务,每次最多补扫 3 个区块。

二十四、TronGrid POST 请求工具

public static JSONObject sendPostRequest(String urlString, String jsonBody, Map<String, String> headers) throws Exception {HttpURLConnection conn = (HttpURLConnection) new URL(urlString).openConnection();

    conn.setRequestMethod("POST");
    conn.setDoOutput(true);
    conn.setConnectTimeout(5000);
    conn.setReadTimeout(10000);

    if (headers != null) {headers.forEach(conn::setRequestProperty);
    }

    try (OutputStream os = conn.getOutputStream()) {os.write(jsonBody.getBytes("utf-8"));
    }

    int maxSize = 5 * 1024 * 1024;

    try (InputStream is = conn.getInputStream();
         ByteArrayOutputStream baos = new ByteArrayOutputStream()) {byte[] buffer = new byte[8192];
        int totalRead = 0;
        int read;

        while ((read = is.read(buffer)) != -1) {
            totalRead += read;

            if (totalRead > maxSize) {throw new IOException("响应数据过大,超过" + maxSize + "字节");
            }

            baos.write(buffer, 0, read);
        }

        String responseBody = baos.toString("utf-8");

        return new JSONObject(responseBody);
    }
}

这里设置了连接超时、读取超时以及最大响应大小,避免接口异常导致程序阻塞或内存溢出。

二十五、系统运行状态监控

系统每分钟输出一次当前运行状态。

@Scheduled(fixedRate = 60000)
public void logSystemStatus() {int listeningOrders = orderMap.values()
            .stream()
            .mapToInt(Set::size)
            .sum();

    LogUtils.info("系统运行状态 | 监听订单数 ={} | 成功 ={} | 待补扫区块 ={} | 当前扫描区块 ={}",
            listeningOrders,
            pushedTxHashes.size(),
            missingBlocksList.size(),
            lastScannedBlock
    );
}

二十六、系统完整流程

 用户创建充值订单
        ↓
订单加入监听 Map
        ↓
定时扫描 TRON 最新区块
        ↓
解析区块中的 TriggerSmartContract
        ↓
判断是否为 USDT 合约
        ↓
判断是否为 transfer 方法
        ↓
解析收款地址和金额
        ↓
匹配系统订单
        ↓
更新订单状态
        ↓
发送商户 callback
        ↓
发送 Telegram 通知
        ↓
写入 callback 记录 

二十七、系统优点

  • 不依赖第三方支付平台
  • 可自动识别链上充值
  • 支持多 API Key 防限流
  • 支持漏块补扫,降低漏单风险
  • 支持 Telegram 实时通知
  • 支持商户自动回调
  • 适合高并发充值系统
  • 可扩展为多币种监听系统

二十八、适用场景

  • USDT 自动充值系统
  • TRC20 支付系统
  • 数字货币钱包系统
  • 商户收款系统
  • 交易平台入金系统
  • 自动到账系统
  • 链上订单监听系统

二十九、总结

通过这套 Java SpringBoot 实现的 TRC20 USDT 自动充值监听系统,可以实现用户充值后自动识别链上交易、自动匹配订单、自动回调商户系统以及自动发送 Telegram 通知。

核心重点在于区块扫描、TRC20 transfer 解析、订单匹配、交易去重、漏块补扫和回调通知。只要这些环节处理稳定,就可以搭建出一套完整的 USDT 自动充值到账系统。

正文完
0
admin
版权声明:本站原创文章,由 admin 于2026-05-06发表,共计9867字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
评论(没有评论)
验证码