From 39f6361e29f48a1e302486fb1d27a42181e199e5 Mon Sep 17 00:00:00 2001
From: Administrator <15274802129@163.com>
Date: Thu, 18 Jun 2026 11:54:45 +0800
Subject: [PATCH] feat(mall): 集成 Tokenview Webhook 服务实现 TRC20 充值处理

---
 src/main/java/cc/mrbird/febs/mall/controller/dependentStation/TokenviewWebhookController.java |  187 ++++++++++++++++++++
 src/main/java/cc/mrbird/febs/mall/quartz/ChatTrc20ChargeOkLinkTask.java                       |  143 ++++-----------
 src/main/java/cc/mrbird/febs/mall/controller/dependentStation/TokenviewWebhookService.java    |  196 +++++++++++++++++++++
 src/main/java/cc/mrbird/febs/common/configure/WebMvcConfigure.java                            |    1 
 4 files changed, 427 insertions(+), 100 deletions(-)

diff --git a/src/main/java/cc/mrbird/febs/common/configure/WebMvcConfigure.java b/src/main/java/cc/mrbird/febs/common/configure/WebMvcConfigure.java
index f729211..d7d19e8 100644
--- a/src/main/java/cc/mrbird/febs/common/configure/WebMvcConfigure.java
+++ b/src/main/java/cc/mrbird/febs/common/configure/WebMvcConfigure.java
@@ -36,6 +36,7 @@
         registration.excludePathPatterns("/api/fuPayReturn/callback");
         registration.excludePathPatterns("/api/fuPayReturn/payment/callback");
         registration.excludePathPatterns("/api/fuPay/notify");
+        registration.excludePathPatterns("/api/tokenview/**");
 
         // 添加Swagger UI相关路径
         registration.excludePathPatterns("/api/swagger-ui.html");
diff --git a/src/main/java/cc/mrbird/febs/mall/controller/dependentStation/TokenviewWebhookController.java b/src/main/java/cc/mrbird/febs/mall/controller/dependentStation/TokenviewWebhookController.java
new file mode 100644
index 0000000..e1d0498
--- /dev/null
+++ b/src/main/java/cc/mrbird/febs/mall/controller/dependentStation/TokenviewWebhookController.java
@@ -0,0 +1,187 @@
+package cc.mrbird.febs.mall.controller.dependentStation;
+
+import cn.hutool.core.util.StrUtil;
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.TypeReference;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import javax.servlet.http.HttpServletRequest;
+import java.io.BufferedReader;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Tokenview 地址监控 Webhook 接收端点
+ * 接收 Tokenview 推送的链上交易通知,完成充值匹配
+ *
+ * @author auto-generated
+ */
+@Slf4j
+@RestController
+@RequestMapping("/api/tokenview")
+public class TokenviewWebhookController {
+
+    @Resource
+    private TokenviewWebhookService tokenviewWebhookService;
+
+    /**
+     * Webhook 签名密钥(HMAC-SHA256)
+     * 需与 Tokenview 后台配置的 Secret Key 保持一致
+     * TODO: 移至配置文件
+     */
+    private static final String WEBHOOK_SECRET = "your-webhook-secret-key-here";
+
+    /**
+     * 接收 Tokenview 地址监控推送
+     * <p>
+     * Tokenview 在监测到地址有交易时,会 POST JSON 到此端点。
+     * 需要在 Tokenview 后台配置此 URL 和 Secret Key。
+     * <p>
+     * 配置步骤:
+     * 1. 登录 Tokenview 开发者后台
+     * 2. 进入「地址监控」模块
+     * 3. 添加要监控的 TRC20 地址
+     * 4. 配置 Webhook URL: https://your-domain/api/tokenview/webhook
+     * 5. 设置 Secret Key(与此处 WEBHOOK_SECRET 一致)
+     */
+    @PostMapping("/webhook")
+    public Map<String, Object> receiveWebhook(HttpServletRequest request) {
+        Map<String, Object> result = new HashMap<>();
+        result.put("code", 0);
+        result.put("msg", "ok");
+
+        try {
+            // 1. 读取原始请求体
+            String rawBody = readRequestBody(request);
+            if (StrUtil.isBlank(rawBody)) {
+                result.put("code", 1);
+                result.put("msg", "请求体为空");
+                return result;
+            }
+
+            // 2. 验证签名
+            String signature = request.getHeader("X-Tokenview-Signature");
+            if (StrUtil.isNotBlank(signature)) {
+                if (!verifySignature(rawBody, signature)) {
+                    log.error("Webhook 签名验证失败: signature={}", signature);
+                    result.put("code", 2);
+                    result.put("msg", "签名验证失败");
+                    return result;
+                }
+            } else {
+                // 也尝试兼容其他常见签名头
+                signature = request.getHeader("Tokenview-Signature");
+                if (StrUtil.isNotBlank(signature) && !verifySignature(rawBody, signature)) {
+                    log.error("Webhook 签名验证失败: signature={}", signature);
+                    result.put("code", 2);
+                    result.put("msg", "签名验证失败");
+                    return result;
+                }
+            }
+
+            log.info("收到 Tokenview Webhook 推送: {}", rawBody);
+
+            // 3. 解析 JSON
+            Map<String, Object> payload = JSON.parseObject(
+                    rawBody,
+                    new TypeReference<Map<String, Object>>() {}
+            );
+            if (payload == null || payload.isEmpty()) {
+                result.put("code", 3);
+                result.put("msg", "JSON 解析失败");
+                return result;
+            }
+
+            // 4. 处理充值匹配
+            String processResult = tokenviewWebhookService.processWebhookTransaction(payload);
+            log.info("Webhook 处理结果: {}", processResult);
+
+            if (processResult != null && processResult.startsWith("SUCCESS")) {
+                result.put("msg", processResult);
+            } else if (processResult != null && !processResult.startsWith("SUCCESS")) {
+                // 非致命错误(如金额未匹配、哈希已使用等),仍返回 200,避免 Tokenview 重复推送
+                result.put("msg", processResult);
+            }
+
+        } catch (Exception e) {
+            log.error("Webhook 处理异常", e);
+            result.put("code", 500);
+            result.put("msg", "处理异常: " + e.getMessage());
+        }
+
+        return result;
+    }
+
+    /**
+     * 读取请求体原始字符串
+     */
+    private String readRequestBody(HttpServletRequest request) {
+        try {
+            StringBuilder sb = new StringBuilder();
+            BufferedReader reader = request.getReader();
+            String line;
+            while ((line = reader.readLine()) != null) {
+                sb.append(line);
+            }
+            return sb.toString();
+        } catch (Exception e) {
+            log.error("读取请求体失败", e);
+            return null;
+        }
+    }
+
+    /**
+     * 验证 HMAC-SHA256 签名
+     *
+     * @param payload   原始请求体
+     * @param signature 请求头中的签名值
+     * @return true-验证通过, false-验证失败
+     */
+    private boolean verifySignature(String payload, String signature) {
+        try {
+            Mac sha256Hmac = Mac.getInstance("HmacSHA256");
+            SecretKeySpec secretKey = new SecretKeySpec(
+                    WEBHOOK_SECRET.getBytes(StandardCharsets.UTF_8),
+                    "HmacSHA256"
+            );
+            sha256Hmac.init(secretKey);
+            byte[] signedBytes = sha256Hmac.doFinal(payload.getBytes(StandardCharsets.UTF_8));
+            String expectedSignature = bytesToHex(signedBytes);
+
+            // 同时尝试 Base64 编码的签名(兼容不同 Tokenview 版本)
+            String expectedBase64 = Base64.getEncoder().encodeToString(signedBytes);
+
+            boolean hexMatch = expectedSignature.equalsIgnoreCase(signature);
+            boolean base64Match = expectedBase64.equals(signature);
+
+            if (!hexMatch && !base64Match) {
+                log.warn("签名不匹配: expected_hex={}, expected_base64={}, actual={}",
+                        expectedSignature, expectedBase64, signature);
+                return false;
+            }
+            return true;
+        } catch (Exception e) {
+            log.error("签名验证计算异常", e);
+            return false;
+        }
+    }
+
+    /**
+     * 字节数组转十六进制字符串
+     */
+    private String bytesToHex(byte[] bytes) {
+        StringBuilder sb = new StringBuilder();
+        for (byte b : bytes) {
+            sb.append(String.format("%02x", b));
+        }
+        return sb.toString();
+    }
+}
diff --git a/src/main/java/cc/mrbird/febs/mall/controller/dependentStation/TokenviewWebhookService.java b/src/main/java/cc/mrbird/febs/mall/controller/dependentStation/TokenviewWebhookService.java
new file mode 100644
index 0000000..155064f
--- /dev/null
+++ b/src/main/java/cc/mrbird/febs/mall/controller/dependentStation/TokenviewWebhookService.java
@@ -0,0 +1,196 @@
+package cc.mrbird.febs.mall.controller.dependentStation;
+
+import cc.mrbird.febs.common.enumerates.OrderStatusEnum;
+import cc.mrbird.febs.common.utils.RedisUtils;
+import cc.mrbird.febs.mall.controller.dependentStation.constant.OrderConstants;
+import cc.mrbird.febs.mall.controller.dependentStation.enums.SalesServiceEnums;
+import cc.mrbird.febs.mall.entity.DataDictionaryCustom;
+import cc.mrbird.febs.mall.entity.MallOrderInfo;
+import cc.mrbird.febs.mall.mapper.DataDictionaryCustomMapper;
+import cc.mrbird.febs.mall.mapper.MallOrderInfoMapper;
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Tokenview Webhook 充值处理服务
+ * 统一处理来自 webhook 推送和轮询任务的充值匹配逻辑
+ *
+ * @author auto-generated
+ */
+@Slf4j
+@Service
+public class TokenviewWebhookService {
+
+    @Resource
+    private RedisUtils redisUtils;
+
+    @Resource
+    private MallOrderInfoMapper mallOrderInfoMapper;
+
+    @Resource
+    private DataDictionaryCustomMapper dataDictionaryCustomMapper;
+
+    /**
+     * 获取系统配置的 TRC20 收款地址
+     */
+    public String getReceiveAddress() {
+        DataDictionaryCustom data = dataDictionaryCustomMapper.selectDicDataByTypeAndCode(
+                SalesServiceEnums.TRC_ADDRESS.getType(),
+                SalesServiceEnums.TRC_ADDRESS.getCode()
+        );
+        if (data == null || StrUtil.isBlank(data.getValue())) {
+            log.error("系统 TRC20 收款地址未配置");
+            return null;
+        }
+        return data.getValue();
+    }
+
+    /**
+     * 处理单笔 webhook 推送的充值交易
+     *
+     * @param payload webhook 推送的 JSON 数据(已解析为 Map)
+     * @return 处理结果描述
+     */
+    public String processWebhookTransaction(Map<String, Object> payload) {
+        String receiveAddress = getReceiveAddress();
+        if (receiveAddress == null) {
+            return "系统地址未配置,跳过处理";
+        }
+
+        // 从 payload 提取关键字段(兼容多种 key 命名)
+        String txid = getField(payload, "txid", "hash", "transactionHash");
+        String toAddress = getField(payload, "to", "toAddress");
+        String value = getField(payload, "value", "amount");
+        String tokenAddr = getField(payload, "token", "tokenAddr", "contractAddress");
+        String symbol = getField(payload, "symbol", "tokenSymbol");
+
+        if (StrUtil.isBlank(txid)) {
+            log.warn("webhook 推送数据缺少交易哈希 txid: {}", payload);
+            return "缺少 txid,跳过处理";
+        }
+
+        // 验证收款地址是否匹配
+        if (StrUtil.isNotBlank(toAddress) && !receiveAddress.equalsIgnoreCase(toAddress)) {
+            log.info("交易收款地址不匹配,期望: {}, 实际: {}, txid: {}", receiveAddress, toAddress, txid);
+            return "收款地址不匹配,跳过处理";
+        }
+
+        // 验证 token 合约地址(USDT: TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t)
+        if (StrUtil.isNotBlank(tokenAddr) && !"TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t".equalsIgnoreCase(tokenAddr)) {
+            log.info("非 USDT 代币交易,跳过处理: token={}, symbol={}, txid={}", tokenAddr, symbol, txid);
+            return "非 USDT 交易,跳过处理";
+        }
+
+        // 执行充值匹配
+        return matchAndUpdateOrder(txid, value, receiveAddress);
+    }
+
+    /**
+     * 处理轮询模式下批量交易记录中的单笔
+     *
+     * @param txid   交易哈希
+     * @param value  原始金额(需除以 1000000)
+     * @param toAddr 收款地址(用于日志)
+     * @return 处理结果
+     */
+    public String processPollTransaction(String txid, String value, String toAddr) {
+        return matchAndUpdateOrder(txid, value, toAddr);
+    }
+
+    /**
+     * 核心充值匹配逻辑:
+     * 1. 交易哈希去重
+     * 2. 金额换算(除以 1000000)
+     * 3. Redis 金额→订单映射匹配
+     * 4. 订单状态校验
+     * 5. 更新订单为待发货
+     */
+    private String matchAndUpdateOrder(String txid, String rawValue, String receiveAddress) {
+        // 1. 交易哈希去重
+        List<MallOrderInfo> existOrders = mallOrderInfoMapper.selectList(
+                Wrappers.lambdaQuery(MallOrderInfo.class)
+                        .eq(MallOrderInfo::getTradeHash, txid)
+        );
+        if (CollUtil.isNotEmpty(existOrders)) {
+            log.info("交易哈希已使用,跳过: txid={}", txid);
+            return "HASH 已使用: " + txid;
+        }
+
+        // 2. 金额换算:除以 1000000(USDT 精度为 6)
+        if (StrUtil.isBlank(rawValue)) {
+            log.warn("交易金额为空: txid={}", txid);
+            return "金额为空: " + txid;
+        }
+        BigDecimal amount;
+        try {
+            amount = new BigDecimal(rawValue)
+                    .divide(new BigDecimal("1000000"), 2, RoundingMode.DOWN);
+        } catch (NumberFormatException e) {
+            log.error("金额解析失败: txid={}, value={}", txid, rawValue, e);
+            return "金额解析失败: " + txid;
+        }
+
+        // 3. Redis 金额匹配订单
+        String amountKey = OrderConstants.TRC20_ORDER_KEY + amount;
+        String orderCode = redisUtils.getString(amountKey);
+        if (StrUtil.isBlank(orderCode)) {
+            log.info("Redis 未匹配到充值金额 {}=>{}: txid={}", amountKey, amount, txid);
+            return "Redis 未匹配到订单: amount=" + amount + ", txid=" + txid;
+        }
+
+        // 4. 查询并校验订单
+        MallOrderInfo order = mallOrderInfoMapper.selectOne(
+                Wrappers.lambdaQuery(MallOrderInfo.class)
+                        .eq(MallOrderInfo::getOrderNo, orderCode)
+        );
+        if (order == null) {
+            log.error("未找到订单: orderNo={}, txid={}", orderCode, txid);
+            return "订单不存在: " + orderCode;
+        }
+        if (OrderStatusEnum.WAIT_PAY.getValue() != order.getStatus()) {
+            log.error("订单非待支付状态: status={}, orderNo={}, txid={}",
+                    order.getStatus(), orderCode, txid);
+            return "订单状态异常(" + order.getStatus() + "): " + orderCode;
+        }
+
+        // 5. 更新订单状态为待发货
+        mallOrderInfoMapper.update(
+                null,
+                Wrappers.lambdaUpdate(MallOrderInfo.class)
+                        .set(MallOrderInfo::getStatus, OrderStatusEnum.WAIT_SHIPPING.getValue())
+                        .set(MallOrderInfo::getTradeHash, txid)
+                        .set(MallOrderInfo::getPayTime, new Date())
+                        .set(MallOrderInfo::getPayResult, "1")
+                        .eq(MallOrderInfo::getId, order.getId())
+        );
+
+        // 删除已使用的 Redis key
+        redisUtils.del(amountKey);
+
+        log.info("Webhook 充值匹配成功: txid={}, orderNo={}, amount={}", txid, orderCode, amount);
+        return "SUCCESS: orderNo=" + orderCode + ", amount=" + amount + ", txid=" + txid;
+    }
+
+    /**
+     * 从 Map 中灵活获取字段值,兼容多种 key 命名
+     */
+    private String getField(Map<String, Object> map, String... keys) {
+        for (String key : keys) {
+            Object val = map.get(key);
+            if (val != null) {
+                return val.toString();
+            }
+        }
+        return null;
+    }
+}
diff --git a/src/main/java/cc/mrbird/febs/mall/quartz/ChatTrc20ChargeOkLinkTask.java b/src/main/java/cc/mrbird/febs/mall/quartz/ChatTrc20ChargeOkLinkTask.java
index 329b4c4..c15c01e 100644
--- a/src/main/java/cc/mrbird/febs/mall/quartz/ChatTrc20ChargeOkLinkTask.java
+++ b/src/main/java/cc/mrbird/febs/mall/quartz/ChatTrc20ChargeOkLinkTask.java
@@ -1,51 +1,36 @@
 package cc.mrbird.febs.mall.quartz;
 
-import cc.mrbird.febs.common.enumerates.OrderStatusEnum;
-import cc.mrbird.febs.common.utils.RedisUtils;
-import cc.mrbird.febs.mall.controller.dependentStation.constant.OrderConstants;
-import cc.mrbird.febs.mall.controller.dependentStation.enums.SalesServiceEnums;
+import cc.mrbird.febs.mall.controller.dependentStation.TokenviewWebhookService;
 import cc.mrbird.febs.mall.controller.dependentStation.utils.OkHttpUtil2;
 import cc.mrbird.febs.mall.controller.dependentStation.utils.Trc20TokenviewContentModel;
 import cc.mrbird.febs.mall.controller.dependentStation.utils.Trc20TokenviewModel;
-import cc.mrbird.febs.mall.entity.DataDictionaryCustom;
-import cc.mrbird.febs.mall.entity.MallOrderInfo;
-import cc.mrbird.febs.mall.mapper.DataDictionaryCustomMapper;
-import cc.mrbird.febs.mall.mapper.MallOrderInfoMapper;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.ObjectUtil;
-import cn.hutool.core.util.StrUtil;
 import com.alibaba.fastjson.JSONObject;
-import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Component;
 
 import javax.annotation.Resource;
-import java.math.BigDecimal;
-import java.math.RoundingMode;
 import java.nio.charset.StandardCharsets;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+
 @Slf4j
 @Component
 @ConditionalOnProperty(prefix = "system", name = "job", havingValue = "true")
 public class ChatTrc20ChargeOkLinkTask {
 
+    @Resource
+    private TokenviewWebhookService tokenviewWebhookService;
 
-    @Resource
-    private RedisUtils redisUtils;
-    @Resource
-    private MallOrderInfoMapper mallOrderInfoMapper;
-    @Resource
-    private DataDictionaryCustomMapper dataDictionaryCustomMapper;
     /**
-     * 五分钟  毫秒
+     * 30分钟(毫秒)
      */
-    private final static long TIME_INTERVAL = 300000*6;
-
+    private final static long TIME_INTERVAL = 300000 * 6;
 
     private final static String TRON_API_KEY = "X8Np5zbDuhmG6cntbhLu";
 
@@ -54,125 +39,83 @@
         REQUEST_HEADER.put("TRON-PRO-API-KEY", TRON_API_KEY);
     }
 
+    /**
+     * 每分钟轮询 Tokenview API,查询 TRC20-USDT 充值记录
+     * 充值匹配逻辑委托给 {@link TokenviewWebhookService}
+     */
     @Scheduled(cron = "0 0/1 * * * ? ")
     public void recharge() {
-        // 查询过去5分钟的记录
-
-        DataDictionaryCustom dataDictionaryCustom = dataDictionaryCustomMapper.selectDicDataByTypeAndCode(
-                SalesServiceEnums.TRC_ADDRESS.getType(),
-                SalesServiceEnums.TRC_ADDRESS.getCode()
-        );
-        String receiveAddress = dataDictionaryCustom.getValue();
+        String receiveAddress = tokenviewWebhookService.getReceiveAddress();
         if (receiveAddress == null) {
             log.error("请先配置系统地址");
             return;
         }
-        //https://services.tokenview.io/vipapi/trx/token/TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t
-        String url = "https://services.tokenview.io/vipapi/trx/address/tokentrans/"+receiveAddress+"/TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t/1/10";
-        // 定时任务的执行时间和转账时间区间间隔5分钟
 
-        // 每次将上次的时间存入redis,第一次使用默认的时间
+        String url = "https://services.tokenview.io/vipapi/trx/address/tokentrans/"
+                + receiveAddress + "/TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t/1/10";
+
         long endTime = System.currentTimeMillis();
-        long startTime = endTime-TIME_INTERVAL;
+        long startTime = endTime - TIME_INTERVAL;
 
-        System.out.println(new Date()+" 自动充值定时任务 "+startTime+" "+endTime);
-        // 当前的充值地址 TRC20USDT_ADDRESS
+        log.info("自动充值定时任务 startTime={}, endTime={}", startTime, endTime);
+
         Map<String, String> param = new HashMap<>();
         param.put("timestampStart", startTime + "");
         param.put("timestampEnd", endTime + "");
         param.put("toAddress", receiveAddress);
         param.put("apikey", TRON_API_KEY);
-        long l = System.currentTimeMillis();
+
+        long start = System.currentTimeMillis();
         byte[] bytes = OkHttpUtil2.doGetSingle(url, REQUEST_HEADER, param, "application/json");
 
-        if (ObjectUtil.isEmpty( bytes )) {
-            log.error("查询链上数据返回为空,传参:{}",param);
+        if (ObjectUtil.isEmpty(bytes)) {
+            log.error("查询链上数据返回为空, param={}", param);
             return;
         }
 
-        String s = new String(bytes, StandardCharsets.UTF_8);
-        log.info("查询到的充值记录:{}", s);
-        Trc20TokenviewModel trc20TransfersModel = JSONObject.parseObject(s, Trc20TokenviewModel.class);
-        List<Trc20TokenviewContentModel> tokenTransfers = trc20TransfersModel.getData();
+        String response = new String(bytes, StandardCharsets.UTF_8);
+        log.info("查询到的充值记录:{}", response);
+
+        Trc20TokenviewModel model = JSONObject.parseObject(response, Trc20TokenviewModel.class);
+        List<Trc20TokenviewContentModel> tokenTransfers = model.getData();
         if (CollUtil.isEmpty(tokenTransfers)) {
-            //logger.error("FyTrc20RechargeTask查询链上数据返回没有token转账,返回结果:{}",s);
             return;
         }
-        long l1 = System.currentTimeMillis();
-        log.info("查询trc耗时:{}", (l1 - l));
-        log.info("查询到的充值记录:{}", JSONObject.toJSONString(trc20TransfersModel));
-        log.info("时间区间,start:{},end:{}", startTime, endTime);
-        // 有记录
-        for (Trc20TokenviewContentModel tokenTransfer : tokenTransfers) {
-            log.info("链上时间:{}", tokenTransfer.getTime());
-            // 从数据库
-            String transactionId = tokenTransfer.getTxid();
-            List<MallOrderInfo> chatOrders = mallOrderInfoMapper.selectList(
-                    Wrappers.lambdaQuery(MallOrderInfo.class)
-                    .eq(MallOrderInfo::getTradeHash, transactionId)
-            );
-            if(CollUtil.isNotEmpty(chatOrders)){
-                log.info("扫描到HASH已使用:{}",transactionId);
-                continue;
-            }
-            // 金额
-            String quant = tokenTransfer.getValue();
-            BigDecimal amount = new BigDecimal(quant).divide(new BigDecimal("1000000")).setScale(2, RoundingMode.DOWN);
-            String amountKey = OrderConstants.TRC20_ORDER_KEY + amount;
-            String orderCode = redisUtils.getString(amountKey);
-            if (StrUtil.isBlank(orderCode)) {
-                log.info("Redis未扫描到充值金额:{}",transactionId);
-                continue;
-            }
-            MallOrderInfo chatOrder = mallOrderInfoMapper.selectOne(
-                    Wrappers.lambdaQuery(MallOrderInfo.class)
-                            .eq(MallOrderInfo::getOrderNo, orderCode)
-            );
-            if(chatOrder==null){
-                log.error("未找到订单:{}",orderCode);
-                continue;
-            }
-            if(OrderStatusEnum.WAIT_PAY.getValue() != chatOrder.getStatus()){
-                log.error("订单不是待充值状态: {},订单编号:{}",transactionId,orderCode);
-                continue;
-            }
 
-            mallOrderInfoMapper.update(
-                    null,
-                    Wrappers.lambdaUpdate(MallOrderInfo.class)
-                            .set(MallOrderInfo::getStatus, OrderStatusEnum.WAIT_SHIPPING.getValue())
-                            .set(MallOrderInfo::getTradeHash, transactionId)
-                            .set(MallOrderInfo::getPayTime, new Date())
-                            .set(MallOrderInfo::getPayResult, "1")
-                            .eq(MallOrderInfo::getId, chatOrder.getId())
+        log.info("查询耗时:{}ms, 记录数:{}, 时间区间 [{}, {}]",
+                System.currentTimeMillis() - start, tokenTransfers.size(), startTime, endTime);
+
+        for (Trc20TokenviewContentModel transfer : tokenTransfers) {
+            log.info("链上时间: {}", transfer.getTime());
+            String result = tokenviewWebhookService.processPollTransaction(
+                    transfer.getTxid(),
+                    transfer.getValue(),
+                    receiveAddress
             );
-            redisUtils.del(amountKey);
-            log.info("Redis扫描到充值记录:{},订单编号:{}",transactionId,orderCode);
+            log.info("轮询处理结果: {}", result);
         }
     }
 
-
     public static void main(String[] args) {
         String receiveAddress = "TExto1UjtFcXKw5QdJDRqtx7wTQ15D37GD";
-        String url = "https://services.tokenview.io/vipapi/trx/address/tokentrans/"+receiveAddress+"/TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t/1/10";
-        // 定时任务的执行时间和转账时间区间间隔5分钟
+        String url = "https://services.tokenview.io/vipapi/trx/address/tokentrans/"
+                + receiveAddress + "/TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t/1/10";
 
-        // 每次将上次的时间存入redis,第一次使用默认的时间
         long endTime = System.currentTimeMillis();
-        long startTime = endTime-TIME_INTERVAL;
+        long startTime = endTime - TIME_INTERVAL;
 
-        System.out.println(new Date()+" 自动充值定时任务fyTrc20RechargeTokenviewTask "+startTime+" "+endTime);
-        // 当前的充值地址 TRC20USDT_ADDRESS
+        System.out.println(new Date() + " 自动充值定时任务 " + startTime + " " + endTime);
+
         Map<String, String> param = new HashMap<>();
         param.put("timestampStart", startTime + "");
         param.put("timestampEnd", endTime + "");
         param.put("toAddress", receiveAddress);
         param.put("apikey", TRON_API_KEY);
-        long l = System.currentTimeMillis();
+
         byte[] bytes = OkHttpUtil2.doGetSingle(url, REQUEST_HEADER, param, "application/json");
 
         if (bytes == null) {
-            log.error("FyTrc20RechargeTask查询链上数据返回为空,传参:{}",param);
+            System.out.println("查询链上数据返回为空, param=" + param);
             return;
         }
 

--
Gitblit v1.9.1