package cc.mrbird.febs.mall.controller.dependentStation;
import cc.mrbird.febs.mall.entity.DataDictionaryCustom;
import cc.mrbird.febs.mall.mapper.DataDictionaryCustomMapper;
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;
@Resource
private DataDictionaryCustomMapper dataDictionaryCustomMapper;
/** 默认 Webhook 签名密钥(数据库未配置时回退使用) */
private static final String DEFAULT_WEBHOOK_SECRET = "dd12521274e434115df5c4277755839766349007fb57936d9d5be0a7a4f0e42f";
/** Webhook 配置字典 type */
private static final String TOKENVIEW_DICT_TYPE = "TOKENVIEW_CONFIG";
private static final String TOKENVIEW_DICT_CODE = "WEBHOOK_SECRET";
/**
* 获取 Webhook 签名密钥(HMAC-SHA256)
* 优先从数据库 data_dictionary_custom 读取,未配置则回退默认值
*/
private String getWebhookSecret() {
DataDictionaryCustom dict = dataDictionaryCustomMapper.selectDicDataByTypeAndCode(
TOKENVIEW_DICT_TYPE, TOKENVIEW_DICT_CODE);
if (dict != null && StrUtil.isNotBlank(dict.getValue())) {
return dict.getValue();
}
return DEFAULT_WEBHOOK_SECRET;
}
/**
* 接收 Tokenview 地址监控推送
*
* Tokenview 在监测到地址有交易时,会 POST JSON 到此端点。
* 需要在 Tokenview 后台配置此 URL 和 Secret Key。
*
* 配置步骤:
* 1. 登录 Tokenview 开发者后台
* 2. 进入「地址监控」模块
* 3. 添加要监控的 TRC20 地址
* 4. 配置 Webhook URL: https://your-domain/api/tokenview/webhook
* 5. 设置 Secret Key(与此处 WEBHOOK_SECRET 一致)
*/
@PostMapping("/webhook")
public Map receiveWebhook(HttpServletRequest request) {
Map 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 payload = JSON.parseObject(
rawBody,
new TypeReference