|
|
@@ -19,6 +19,7 @@ import java.security.MessageDigest;
|
|
|
import java.time.Duration;
|
|
|
import java.time.Instant;
|
|
|
import java.util.*;
|
|
|
+import java.util.function.Supplier;
|
|
|
|
|
|
/**
|
|
|
* 腾讯行为回传客户端。
|
|
|
@@ -31,21 +32,19 @@ public class TencentClient {
|
|
|
private static final Logger log = LoggerFactory.getLogger(TencentClient.class);
|
|
|
|
|
|
private final String apiBaseUrl;
|
|
|
- private final String accessToken;
|
|
|
- private final long accountId;
|
|
|
+ private final Supplier<String> accessTokenSupplier;
|
|
|
private final long androidUserActionSetId;
|
|
|
private final long iosUserActionSetId;
|
|
|
private final Map<Integer, String> actMap; // baiduAct -> tencentActionType
|
|
|
private final HttpClient httpClient;
|
|
|
private final ObjectMapper objectMapper;
|
|
|
|
|
|
- public TencentClient(String apiBaseUrl, String accessToken, long accountId,
|
|
|
+ public TencentClient(String apiBaseUrl, Supplier<String> accessTokenSupplier,
|
|
|
long androidUserActionSetId, long iosUserActionSetId,
|
|
|
Map<Integer, String> actMapOverride,
|
|
|
ObjectMapper objectMapper) {
|
|
|
this.apiBaseUrl = apiBaseUrl.replaceAll("/+$", "");
|
|
|
- this.accessToken = accessToken;
|
|
|
- this.accountId = accountId;
|
|
|
+ this.accessTokenSupplier = accessTokenSupplier;
|
|
|
this.androidUserActionSetId = androidUserActionSetId;
|
|
|
this.iosUserActionSetId = iosUserActionSetId;
|
|
|
this.objectMapper = objectMapper;
|
|
|
@@ -105,23 +104,45 @@ public class TencentClient {
|
|
|
// 构造请求 URL
|
|
|
long timestamp = Instant.now().getEpochSecond();
|
|
|
String nonce = UUID.randomUUID().toString().replace("-", "").substring(0, 32);
|
|
|
+ String currentToken = accessTokenSupplier.get();
|
|
|
+ if (currentToken == null || currentToken.isBlank()) {
|
|
|
+ record.setOk(false);
|
|
|
+ record.setErrorMessage("access_token is empty (check Redis key)");
|
|
|
+ log.warn("[Tencent] access_token 为空, qk={}", payment.getQk());
|
|
|
+ return record;
|
|
|
+ }
|
|
|
String url = String.format("%s/user_actions/add?access_token=%s×tamp=%d&nonce=%s",
|
|
|
- apiBaseUrl, accessToken, timestamp, nonce);
|
|
|
+ apiBaseUrl, currentToken, timestamp, nonce);
|
|
|
record.setCallbackUrl(url);
|
|
|
|
|
|
// 构造请求 Body
|
|
|
UserAction action = buildUserAction(bid, payment, actionType, eventTimeMs);
|
|
|
|
|
|
long actionSetId = getUserActionSetId(platform);
|
|
|
+ // account_id: 从监测链接传入
|
|
|
+ long finalAccountId = 0;
|
|
|
+ if (bid.getAccountId() != null && !bid.getAccountId().isBlank()) {
|
|
|
+ try {
|
|
|
+ finalAccountId = Long.parseLong(bid.getAccountId());
|
|
|
+ } catch (NumberFormatException ignored) {}
|
|
|
+ }
|
|
|
+ if (finalAccountId == 0) {
|
|
|
+ log.warn("[Tencent] account_id 缺失, qk={}, 监测链接未传入account_id", payment.getQk());
|
|
|
+ }
|
|
|
Map<String, Object> body = new LinkedHashMap<>();
|
|
|
- body.put("account_id", accountId);
|
|
|
+ body.put("account_id", finalAccountId);
|
|
|
body.put("user_action_set_id", actionSetId);
|
|
|
body.put("actions", List.of(action));
|
|
|
+ // ad_id: 传入广告ID用于归因匹配
|
|
|
+ if (bid.getAdId() != null && !bid.getAdId().isBlank()) {
|
|
|
+ body.put("ad_id", bid.getAdId());
|
|
|
+ }
|
|
|
|
|
|
try {
|
|
|
byte[] payload = objectMapper.writeValueAsBytes(body);
|
|
|
- log.info("[Tencent] sending user_action: qk={}, actionType={}, baiduAct={}",
|
|
|
- payment.getQk(), actionType, payment.getAct());
|
|
|
+ String payloadStr = new String(payload, java.nio.charset.StandardCharsets.UTF_8);
|
|
|
+ log.info("[Tencent] sending user_action: qk={}, actionType={}, baiduAct={}, url={}, body={}",
|
|
|
+ payment.getQk(), actionType, payment.getAct(), url, payloadStr);
|
|
|
|
|
|
HttpRequest request = HttpRequest.newBuilder()
|
|
|
.uri(URI.create(url))
|
|
|
@@ -149,8 +170,8 @@ public class TencentClient {
|
|
|
record.setErrorMessage("HTTP status: " + status);
|
|
|
}
|
|
|
|
|
|
- log.info("[Tencent] callback result: qk={}, actionType={}, status={}, ok={}",
|
|
|
- payment.getQk(), actionType, status, record.isOk());
|
|
|
+ log.info("[Tencent] callback result: qk={}, actionType={}, status={}, ok={}, response={}",
|
|
|
+ payment.getQk(), actionType, status, record.isOk(), responseBody);
|
|
|
} catch (Exception e) {
|
|
|
record.setOk(false);
|
|
|
record.setErrorMessage(e.getMessage());
|
|
|
@@ -176,8 +197,14 @@ public class TencentClient {
|
|
|
// 重新构造请求(使用新的 timestamp 和 nonce)
|
|
|
long timestamp = Instant.now().getEpochSecond();
|
|
|
String nonce = UUID.randomUUID().toString().replace("-", "").substring(0, 32);
|
|
|
+ String currentToken = accessTokenSupplier.get();
|
|
|
+ if (currentToken == null || currentToken.isBlank()) {
|
|
|
+ retry.setOk(false);
|
|
|
+ retry.setErrorMessage("access_token is empty (check Redis key)");
|
|
|
+ return retry;
|
|
|
+ }
|
|
|
String url = String.format("%s/user_actions/add?access_token=%s×tamp=%d&nonce=%s",
|
|
|
- apiBaseUrl, accessToken, timestamp, nonce);
|
|
|
+ apiBaseUrl, currentToken, timestamp, nonce);
|
|
|
|
|
|
// 需要从 original 解析出原始 body 重发
|
|
|
// 简化处理:直接用原始的 callbackUrl 重新发送一个空 body 触发
|
|
|
@@ -291,7 +318,6 @@ public class TencentClient {
|
|
|
m.put(1, "ACTIVATE_APP");
|
|
|
m.put(2, "PURCHASE");
|
|
|
m.put(3, "REGISTER");
|
|
|
- m.put(4, "ONE_DAY_RETENTION");
|
|
|
m.put(5, "COMPLETE_ORDER");
|
|
|
m.put(6, "START_APP");
|
|
|
m.put(7, "CUSTOM");
|