|
@@ -16,6 +16,7 @@ import java.net.http.HttpResponse;
|
|
|
import java.nio.charset.StandardCharsets;
|
|
import java.nio.charset.StandardCharsets;
|
|
|
import java.time.Duration;
|
|
import java.time.Duration;
|
|
|
import java.time.Instant;
|
|
import java.time.Instant;
|
|
|
|
|
+import java.util.Arrays;
|
|
|
import java.util.Base64;
|
|
import java.util.Base64;
|
|
|
import java.util.LinkedHashMap;
|
|
import java.util.LinkedHashMap;
|
|
|
import java.util.Locale;
|
|
import java.util.Locale;
|
|
@@ -32,6 +33,7 @@ public class BaiduRtaService {
|
|
|
private final String customerName;
|
|
private final String customerName;
|
|
|
private final String sspid;
|
|
private final String sspid;
|
|
|
private final String eKey;
|
|
private final String eKey;
|
|
|
|
|
+ private final String iKey;
|
|
|
private final String tuPrefix;
|
|
private final String tuPrefix;
|
|
|
private final RtaOrderResolver orderResolver;
|
|
private final RtaOrderResolver orderResolver;
|
|
|
private final HttpClient httpClient;
|
|
private final HttpClient httpClient;
|
|
@@ -43,16 +45,19 @@ public class BaiduRtaService {
|
|
|
this.enabled = props.isRtaEnabled();
|
|
this.enabled = props.isRtaEnabled();
|
|
|
this.endpoint = props.getRtaEndpoint();
|
|
this.endpoint = props.getRtaEndpoint();
|
|
|
this.timeout = props.getRtaTimeout() == null ? Duration.ofMillis(800) : props.getRtaTimeout();
|
|
this.timeout = props.getRtaTimeout() == null ? Duration.ofMillis(800) : props.getRtaTimeout();
|
|
|
- String configuredCustomerName = trimToNull(props.getRtaCustomerName());
|
|
|
|
|
- this.customerName = configuredCustomerName != null ? configuredCustomerName : trimToNull(props.getBaiduCustomerName());
|
|
|
|
|
- String configuredSspid = trimToNull(props.getRtaSspid());
|
|
|
|
|
- this.sspid = configuredSspid != null ? configuredSspid : (props.getBaiduMediaId() > 0 ? String.valueOf(props.getBaiduMediaId()) : null);
|
|
|
|
|
- String configuredEKey = trimToNull(props.getRtaEKey());
|
|
|
|
|
- this.eKey = configuredEKey != null ? configuredEKey : trimToNull(props.getBaiduAuctionPriceEKey());
|
|
|
|
|
|
|
+ this.customerName = trimToNull(props.getBaiduCustomerName());
|
|
|
|
|
+ this.sspid = props.getBaiduMediaId() > 0 ? String.valueOf(props.getBaiduMediaId()) : null;
|
|
|
|
|
+ this.eKey = trimToNull(props.getBaiduAuctionPriceEKey());
|
|
|
|
|
+ this.iKey = trimToNull(props.getBaiduAuctionPriceIKey());
|
|
|
this.tuPrefix = trimToNull(props.getRtaTuPrefix());
|
|
this.tuPrefix = trimToNull(props.getRtaTuPrefix());
|
|
|
this.orderResolver = orderResolver;
|
|
this.orderResolver = orderResolver;
|
|
|
this.httpClient = HttpClient.newBuilder().connectTimeout(this.timeout).build();
|
|
this.httpClient = HttpClient.newBuilder().connectTimeout(this.timeout).build();
|
|
|
this.objectMapper = objectMapper;
|
|
this.objectMapper = objectMapper;
|
|
|
|
|
+ log.info("[RTA][配置] enabled={} endpoint={} timeoutMs={} customerName={} sspid={} eKeyLength={} iKeyLength={} tuPrefix={}",
|
|
|
|
|
+ enabled, endpoint, timeout.toMillis(), customerName, sspid,
|
|
|
|
|
+ eKey == null ? 0 : eKey.getBytes(StandardCharsets.UTF_8).length,
|
|
|
|
|
+ iKey == null ? 0 : iKey.getBytes(StandardCharsets.UTF_8).length,
|
|
|
|
|
+ tuPrefix);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
public BaiduRtaDecision evaluate(String media,
|
|
public BaiduRtaDecision evaluate(String media,
|
|
@@ -63,23 +68,32 @@ public class BaiduRtaService {
|
|
|
String ip,
|
|
String ip,
|
|
|
String userAgent) {
|
|
String userAgent) {
|
|
|
if (!enabled) {
|
|
if (!enabled) {
|
|
|
|
|
+ log.info("[RTA][{}][绕过] reason=开关未启用 matched=true allowBid=true", mediaLabel(media));
|
|
|
return BaiduRtaDecision.bypass();
|
|
return BaiduRtaDecision.bypass();
|
|
|
}
|
|
}
|
|
|
if (!supportsMedia(media) || endpoint == null || endpoint.isBlank() || customerName == null || sspid == null || eKey == null) {
|
|
if (!supportsMedia(media) || endpoint == null || endpoint.isBlank() || customerName == null || sspid == null || eKey == null) {
|
|
|
- log.warn("[RTA][{}] skipped: missing global config", mediaLabel(media));
|
|
|
|
|
|
|
+ log.warn("[RTA][{}][绕过] reason=全局配置缺失 matched=true allowBid=true endpointConfigured={} customerNameConfigured={} sspidConfigured={} eKeyConfigured={}",
|
|
|
|
|
+ mediaLabel(media), endpoint != null && !endpoint.isBlank(), customerName != null, sspid != null, eKey != null);
|
|
|
return BaiduRtaDecision.bypass();
|
|
return BaiduRtaDecision.bypass();
|
|
|
}
|
|
}
|
|
|
String tu = buildTu(tagId);
|
|
String tu = buildTu(tagId);
|
|
|
String requestOrderId = trimToNull(orderResolver == null ? null : orderResolver.resolveOrderId(tagId));
|
|
String requestOrderId = trimToNull(orderResolver == null ? null : orderResolver.resolveOrderId(tagId));
|
|
|
Map<String, Object> requestPreview = buildRequestPreview(appId, tagId, platform, params, ip, userAgent, tu, requestOrderId, null, null);
|
|
Map<String, Object> requestPreview = buildRequestPreview(appId, tagId, platform, params, ip, userAgent, tu, requestOrderId, null, null);
|
|
|
|
|
+ if (requestOrderId == null) {
|
|
|
|
|
+ log.info("[RTA][{}][绕过] reason=Redis未配置orderId matched=true allowBid=true tagId={} redisOnly=true",
|
|
|
|
|
+ mediaLabel(media), tagId);
|
|
|
|
|
+ log.info("[RTA][{}][绕过预览] tagId={} request={}", mediaLabel(media), tagId, toJson(requestPreview));
|
|
|
|
|
+ return BaiduRtaDecision.bypass();
|
|
|
|
|
+ }
|
|
|
if (tu == null || appId == null || appId.isBlank()) {
|
|
if (tu == null || appId == null || appId.isBlank()) {
|
|
|
- log.warn("[RTA][{}] skipped: missing tag config | tagId={} tu={} appId={}",
|
|
|
|
|
|
|
+ log.warn("[RTA][{}][绕过] reason=广告位配置缺失 matched=true allowBid=true tagId={} tu={} appId={}",
|
|
|
mediaLabel(media), tagId, tu, appId);
|
|
mediaLabel(media), tagId, tu, appId);
|
|
|
- log.info("[RTA][{}][request-preview] {}", mediaLabel(media), toJson(requestPreview));
|
|
|
|
|
- log.info("[RTA][{}][response-preview] {}", mediaLabel(media), toJson(Map.of(
|
|
|
|
|
|
|
+ log.info("[RTA][{}][绕过预览] tagId={} request={}", mediaLabel(media), tagId, toJson(requestPreview));
|
|
|
|
|
+ log.info("[RTA][{}][绕过预览] tagId={} response={}", mediaLabel(media), tagId, toJson(Map.of(
|
|
|
"applied", false,
|
|
"applied", false,
|
|
|
"matched", true,
|
|
"matched", true,
|
|
|
- "reason", "missing tag config",
|
|
|
|
|
|
|
+ "allowBid", true,
|
|
|
|
|
+ "reason", "广告位配置缺失",
|
|
|
"latencyMs", 0
|
|
"latencyMs", 0
|
|
|
)));
|
|
)));
|
|
|
return BaiduRtaDecision.bypass();
|
|
return BaiduRtaDecision.bypass();
|
|
@@ -94,7 +108,6 @@ public class BaiduRtaService {
|
|
|
|
|
|
|
|
try {
|
|
try {
|
|
|
String requestBody = objectMapper.writeValueAsString(body);
|
|
String requestBody = objectMapper.writeValueAsString(body);
|
|
|
- log.info("[RTA][{}][request] {}", mediaLabel(media), requestBody);
|
|
|
|
|
HttpRequest request = HttpRequest.newBuilder()
|
|
HttpRequest request = HttpRequest.newBuilder()
|
|
|
.uri(URI.create(endpoint))
|
|
.uri(URI.create(endpoint))
|
|
|
.timeout(timeout)
|
|
.timeout(timeout)
|
|
@@ -103,22 +116,37 @@ public class BaiduRtaService {
|
|
|
.build();
|
|
.build();
|
|
|
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
|
|
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
|
|
|
long latencyMs = (System.nanoTime() - startedAt) / 1_000_000L;
|
|
long latencyMs = (System.nanoTime() - startedAt) / 1_000_000L;
|
|
|
- log.info("[RTA][{}][response] status={} body={}", mediaLabel(media), response.statusCode(), response.body());
|
|
|
|
|
|
|
+ String responseBody = response.body();
|
|
|
if (response.statusCode() != 200) {
|
|
if (response.statusCode() != 200) {
|
|
|
- log.warn("[RTA][{}] httpStatus={} requestId={} body={}", mediaLabel(media), response.statusCode(), requestId, response.body());
|
|
|
|
|
|
|
+ log.info("[RTA][{}][未命中][请求] requestId={} body={}", mediaLabel(media), requestId, requestBody);
|
|
|
|
|
+ log.info("[RTA][{}][未命中][返回] requestId={} status={} headers={} body={}",
|
|
|
|
|
+ mediaLabel(media), requestId, response.statusCode(), response.headers().map(), responseBody);
|
|
|
return BaiduRtaDecision.of(false, null, requestId, tu, requestOrderId, null, latencyMs, "httpStatus=" + response.statusCode());
|
|
return BaiduRtaDecision.of(false, null, requestId, tu, requestOrderId, null, latencyMs, "httpStatus=" + response.statusCode());
|
|
|
}
|
|
}
|
|
|
- JsonNode root = objectMapper.readTree(response.body());
|
|
|
|
|
|
|
+ JsonNode root = objectMapper.readTree(responseBody);
|
|
|
Integer code = root.hasNonNull("code") ? root.get("code").asInt() : null;
|
|
Integer code = root.hasNonNull("code") ? root.get("code").asInt() : null;
|
|
|
String orderIds = root.hasNonNull("order_id") ? trimToNull(root.get("order_id").asText()) : null;
|
|
String orderIds = root.hasNonNull("order_id") ? trimToNull(root.get("order_id").asText()) : null;
|
|
|
boolean matched = code != null && code == 200 && orderIds != null && !orderIds.isBlank();
|
|
boolean matched = code != null && code == 200 && orderIds != null && !orderIds.isBlank();
|
|
|
- log.info("[RTA][{}] requestId={} code={} matched={} orderIds={} latencyMs={}",
|
|
|
|
|
- mediaLabel(media), requestId, code, matched, orderIds, latencyMs);
|
|
|
|
|
|
|
+ if (code != null && code == 4010 && latencyMs > 2_000L) {
|
|
|
|
|
+ log.warn("[RTA][{}][鉴权失败] latencyMs={},RTA timestamp 允许窗口是±2秒,请检查网络耗时以及当前进程是否加载了最新 timeout/sign 配置",
|
|
|
|
|
+ mediaLabel(media), latencyMs);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!matched) {
|
|
|
|
|
+ log.info("[RTA][{}][未命中][请求] requestId={} body={}", mediaLabel(media), requestId, requestBody);
|
|
|
|
|
+ log.info("[RTA][{}][未命中][返回] requestId={} status={} headers={} body={}",
|
|
|
|
|
+ mediaLabel(media), requestId, response.statusCode(), response.headers().map(), responseBody);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ log.info("[RTA][{}][命中] requestId={} matched=true allowBid=true code={} requestOrderIds={} responseOrderIds={} latencyMs={} msg=命中,参与竞价",
|
|
|
|
|
+ mediaLabel(media), requestId, code, requestOrderId, orderIds, latencyMs);
|
|
|
|
|
+ }
|
|
|
return BaiduRtaDecision.of(matched, code, requestId, tu, requestOrderId, orderIds, latencyMs, null);
|
|
return BaiduRtaDecision.of(matched, code, requestId, tu, requestOrderId, orderIds, latencyMs, null);
|
|
|
} catch (Exception e) {
|
|
} catch (Exception e) {
|
|
|
long latencyMs = (System.nanoTime() - startedAt) / 1_000_000L;
|
|
long latencyMs = (System.nanoTime() - startedAt) / 1_000_000L;
|
|
|
- log.warn("[RTA][{}] request failed | requestId={} error={}", mediaLabel(media), requestId, e.getMessage());
|
|
|
|
|
- return BaiduRtaDecision.of(false, null, requestId, tu, requestOrderId, null, latencyMs, e.getMessage());
|
|
|
|
|
|
|
+ String error = e.getClass().getSimpleName() + ": " + (e.getMessage() == null ? "" : e.getMessage());
|
|
|
|
|
+ log.warn("[RTA][{}][异常] requestId={} matched=false allowBid=false code=null requestOrderIds={} responseOrderIds=null latencyMs={} errorType={} errorMessage={}",
|
|
|
|
|
+ mediaLabel(media), requestId, requestOrderId, latencyMs, e.getClass().getName(), e.getMessage(), e);
|
|
|
|
|
+ log.info("[RTA][{}][未命中][请求] requestId={} body={}", mediaLabel(media), requestId, toJson(body));
|
|
|
|
|
+ return BaiduRtaDecision.of(false, null, requestId, tu, requestOrderId, null, latencyMs, error);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -137,8 +165,6 @@ public class BaiduRtaService {
|
|
|
body.put("customerName", customerName);
|
|
body.put("customerName", customerName);
|
|
|
body.put("sspid", sspid);
|
|
body.put("sspid", sspid);
|
|
|
body.put("appsid", trimToNull(appId));
|
|
body.put("appsid", trimToNull(appId));
|
|
|
- body.put("tagId", trimToNull(tagId));
|
|
|
|
|
- body.put("platform", trimToNull(platform));
|
|
|
|
|
body.put("tu", tu);
|
|
body.put("tu", tu);
|
|
|
putIfNotBlank(body, "order_id", requestOrderId);
|
|
putIfNotBlank(body, "order_id", requestOrderId);
|
|
|
body.put("os", os);
|
|
body.put("os", os);
|
|
@@ -175,22 +201,37 @@ public class BaiduRtaService {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private String sign(String timestamp, String requestId) {
|
|
private String sign(String timestamp, String requestId) {
|
|
|
|
|
+ return signZeroPadding(eKey, timestamp, requestId);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static String signZeroPadding(String keyValue, String timestamp, String requestId) {
|
|
|
try {
|
|
try {
|
|
|
- byte[] rawKey = eKey.getBytes(StandardCharsets.UTF_8);
|
|
|
|
|
|
|
+ byte[] rawKey = keyValue.getBytes(StandardCharsets.UTF_8);
|
|
|
if (rawKey.length < 16) {
|
|
if (rawKey.length < 16) {
|
|
|
throw new IllegalStateException("RTA eKey must contain at least 16 bytes");
|
|
throw new IllegalStateException("RTA eKey must contain at least 16 bytes");
|
|
|
}
|
|
}
|
|
|
- byte[] key = new byte[16];
|
|
|
|
|
- System.arraycopy(rawKey, rawKey.length - 16, key, 0, 16);
|
|
|
|
|
- Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
|
|
|
|
|
|
|
+ byte[] key = Arrays.copyOfRange(rawKey, rawKey.length - 16, rawKey.length);
|
|
|
|
|
+ byte[] payload = (timestamp + "\n" + requestId).getBytes(StandardCharsets.US_ASCII);
|
|
|
|
|
+ byte[] padded = zeroPad(payload, 16);
|
|
|
|
|
+ Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
|
|
|
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"));
|
|
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"));
|
|
|
- byte[] encrypted = cipher.doFinal((timestamp + "\n" + requestId).getBytes(StandardCharsets.UTF_8));
|
|
|
|
|
|
|
+ byte[] encrypted = cipher.doFinal(padded);
|
|
|
return Base64.getEncoder().encodeToString(encrypted);
|
|
return Base64.getEncoder().encodeToString(encrypted);
|
|
|
} catch (Exception e) {
|
|
} catch (Exception e) {
|
|
|
throw new RuntimeException("build RTA sign failed", e);
|
|
throw new RuntimeException("build RTA sign failed", e);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ private static byte[] zeroPad(byte[] value, int blockSize) {
|
|
|
|
|
+ int remainder = value.length % blockSize;
|
|
|
|
|
+ if (remainder == 0) {
|
|
|
|
|
+ return value;
|
|
|
|
|
+ }
|
|
|
|
|
+ byte[] padded = new byte[value.length + blockSize - remainder];
|
|
|
|
|
+ System.arraycopy(value, 0, padded, 0, value.length);
|
|
|
|
|
+ return padded;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
private static int rtaOs(String platform) {
|
|
private static int rtaOs(String platform) {
|
|
|
String normalized = trimToNull(platform);
|
|
String normalized = trimToNull(platform);
|
|
|
if (normalized == null) return 0;
|
|
if (normalized == null) return 0;
|