|
@@ -6,6 +6,7 @@ import com.adx.tencent.baidu.model.TrackingResult;
|
|
|
import com.adx.tencent.storage.RedisHotStore;
|
|
import com.adx.tencent.storage.RedisHotStore;
|
|
|
import com.adx.tencent.storage.model.BidRecord;
|
|
import com.adx.tencent.storage.model.BidRecord;
|
|
|
import com.adx.tencent.storage.model.TrackingRecord;
|
|
import com.adx.tencent.storage.model.TrackingRecord;
|
|
|
|
|
+import com.fasterxml.jackson.databind.ObjectMapper;
|
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.Logger;
|
|
|
import org.slf4j.LoggerFactory;
|
|
import org.slf4j.LoggerFactory;
|
|
|
import org.springframework.beans.factory.annotation.Value;
|
|
import org.springframework.beans.factory.annotation.Value;
|
|
@@ -36,14 +37,17 @@ public class TrackingController {
|
|
|
private final RedisHotStore hotStore;
|
|
private final RedisHotStore hotStore;
|
|
|
private final MediaPlacement tencentPlacement;
|
|
private final MediaPlacement tencentPlacement;
|
|
|
private final boolean allowIpOverride;
|
|
private final boolean allowIpOverride;
|
|
|
|
|
+ private final ObjectMapper objectMapper;
|
|
|
|
|
|
|
|
public TrackingController(AdxClient adxClient,
|
|
public TrackingController(AdxClient adxClient,
|
|
|
RedisHotStore hotStore,
|
|
RedisHotStore hotStore,
|
|
|
@Nullable MediaPlacement tencentPlacement,
|
|
@Nullable MediaPlacement tencentPlacement,
|
|
|
|
|
+ ObjectMapper objectMapper,
|
|
|
@Value("${spring.profiles.active:}") String activeProfile) {
|
|
@Value("${spring.profiles.active:}") String activeProfile) {
|
|
|
this.adxClient = adxClient;
|
|
this.adxClient = adxClient;
|
|
|
this.hotStore = hotStore;
|
|
this.hotStore = hotStore;
|
|
|
this.tencentPlacement = tencentPlacement;
|
|
this.tencentPlacement = tencentPlacement;
|
|
|
|
|
+ this.objectMapper = objectMapper;
|
|
|
this.allowIpOverride = "dev".equals(activeProfile) || "test".equals(activeProfile);
|
|
this.allowIpOverride = "dev".equals(activeProfile) || "test".equals(activeProfile);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -82,31 +86,32 @@ public class TrackingController {
|
|
|
throw new ResponseStatusException(HttpStatus.SERVICE_UNAVAILABLE, "tencent placement not configured");
|
|
throw new ResponseStatusException(HttpStatus.SERVICE_UNAVAILABLE, "tencent placement not configured");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- log.info("[腾讯][曝光] 收到曝光请求 | traceId={} | IP={} | 全部入参: {}", traceId, clientIp(request), params);
|
|
|
|
|
|
|
+ log.info("[腾讯][曝光][入参] traceId={} ip={} params={}", traceId, clientIp(request), params);
|
|
|
|
|
|
|
|
Map<String, Object> bidRequest = mediaBidRequest(request, "tencent", traceId, params, tencentPlacement);
|
|
Map<String, Object> bidRequest = mediaBidRequest(request, "tencent", traceId, params, tencentPlacement);
|
|
|
- NormalizedBidResponse response = adxClient.requestBid(bidRequest);
|
|
|
|
|
|
|
+ NormalizedBidResponse response = adxClient.requestBid(traceId, bidRequest);
|
|
|
if (response == null) {
|
|
if (response == null) {
|
|
|
- log.warn("[腾讯][曝光] 百度ADX返回空响应, traceId={}", traceId);
|
|
|
|
|
|
|
+ log.warn("[腾讯][曝光][百度结果] traceId={} empty=true", traceId);
|
|
|
throw new ResponseStatusException(HttpStatus.BAD_GATEWAY, "empty baidu ADX response");
|
|
throw new ResponseStatusException(HttpStatus.BAD_GATEWAY, "empty baidu ADX response");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- log.info("[腾讯][曝光] 百度ADX返回: qk={}, hasBid={}, showUrls={}, clickUrls={}",
|
|
|
|
|
|
|
+ log.info("[腾讯][曝光][竞价结果] traceId={} qk={} bidId={} success={} price={} clickUrlCount={} showUrlCount={}",
|
|
|
|
|
+ traceId,
|
|
|
response.getQk(),
|
|
response.getQk(),
|
|
|
|
|
+ response.getBidId(),
|
|
|
response.getBid() != null,
|
|
response.getBid() != null,
|
|
|
- response.getBid() != null && response.getBid().getShowUrls() != null ? response.getBid().getShowUrls().size() : 0,
|
|
|
|
|
- response.getBid() != null && response.getBid().getClickUrls() != null ? response.getBid().getClickUrls().size() : 0);
|
|
|
|
|
|
|
+ response.getBid() != null ? response.getBid().getPrice() : 0,
|
|
|
|
|
+ response.getBid() != null && response.getBid().getClickUrls() != null ? response.getBid().getClickUrls().size() : 0,
|
|
|
|
|
+ response.getBid() != null && response.getBid().getShowUrls() != null ? response.getBid().getShowUrls().size() : 0);
|
|
|
|
|
|
|
|
BidRecord record = bidRecordFromMediaResponse(response, "tencent", traceId, "impression", params);
|
|
BidRecord record = bidRecordFromMediaResponse(response, "tencent", traceId, "impression", params);
|
|
|
hotStore.recordBid(record);
|
|
hotStore.recordBid(record);
|
|
|
- log.info("[腾讯][曝光] 已缓存bid, qk={}, traceId={}", record.getQk(), traceId);
|
|
|
|
|
|
|
|
|
|
// 上报百度曝光
|
|
// 上报百度曝光
|
|
|
if (response.getBid() != null && response.getBid().getShowUrls() != null
|
|
if (response.getBid() != null && response.getBid().getShowUrls() != null
|
|
|
&& !response.getBid().getShowUrls().isEmpty()) {
|
|
&& !response.getBid().getShowUrls().isEmpty()) {
|
|
|
List<TrackingResult> trackingResults = adxClient.reportImpression(
|
|
List<TrackingResult> trackingResults = adxClient.reportImpression(
|
|
|
response.getBid().getShowUrls(), response.getBid().getPrice());
|
|
response.getBid().getShowUrls(), response.getBid().getPrice());
|
|
|
- log.info("[腾讯][曝光] 曝光上报完成, qk={}, results={}", response.getQk(), trackingResults);
|
|
|
|
|
recordTrackingResults(response.getQk(), "impression", trackingResults);
|
|
recordTrackingResults(response.getQk(), "impression", trackingResults);
|
|
|
}
|
|
}
|
|
|
return Map.of("qk", nvl(record.getQk()), "traceId", traceId);
|
|
return Map.of("qk", nvl(record.getQk()), "traceId", traceId);
|
|
@@ -143,19 +148,25 @@ public class TrackingController {
|
|
|
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "missing media trace id (click_id/oaid/imei/idfa)");
|
|
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "missing media trace id (click_id/oaid/imei/idfa)");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- log.info("[腾讯][点击] 收到点击请求 | traceId={} | IP={} | 全部入参: {}", traceId, clientIp(request), params);
|
|
|
|
|
|
|
+ log.info("[腾讯][点击][入参] traceId={} ip={} params={}", traceId, clientIp(request), params);
|
|
|
|
|
|
|
|
- // 先从 Redis 按 mediaTrace 查找已有 bid
|
|
|
|
|
BidRecord record = hotStore.findBidByMediaTrace("tencent", traceId);
|
|
BidRecord record = hotStore.findBidByMediaTrace("tencent", traceId);
|
|
|
if (record == null) {
|
|
if (record == null) {
|
|
|
- log.info("[腾讯][点击] Redis未命中, 发起竞价请求, traceId={}", traceId);
|
|
|
|
|
// 未找到,临时发起竞价请求
|
|
// 未找到,临时发起竞价请求
|
|
|
Map<String, Object> bidRequest = mediaBidRequest(request, "tencent", traceId, params, tencentPlacement);
|
|
Map<String, Object> bidRequest = mediaBidRequest(request, "tencent", traceId, params, tencentPlacement);
|
|
|
- NormalizedBidResponse resp = adxClient.requestBid(bidRequest);
|
|
|
|
|
|
|
+ NormalizedBidResponse resp = adxClient.requestBid(traceId, bidRequest);
|
|
|
if (resp == null) {
|
|
if (resp == null) {
|
|
|
- log.warn("[腾讯][点击] 百度ADX返回空响应, traceId={}", traceId);
|
|
|
|
|
|
|
+ log.warn("[腾讯][点击][百度结果] traceId={} empty=true", traceId);
|
|
|
throw new ResponseStatusException(HttpStatus.BAD_GATEWAY, "empty baidu ADX response");
|
|
throw new ResponseStatusException(HttpStatus.BAD_GATEWAY, "empty baidu ADX response");
|
|
|
}
|
|
}
|
|
|
|
|
+ log.info("[腾讯][点击][竞价结果] traceId={} qk={} bidId={} success={} price={} clickUrlCount={} showUrlCount={}",
|
|
|
|
|
+ traceId,
|
|
|
|
|
+ resp.getQk(),
|
|
|
|
|
+ resp.getBidId(),
|
|
|
|
|
+ resp.getBid() != null,
|
|
|
|
|
+ resp.getBid() != null ? resp.getBid().getPrice() : 0,
|
|
|
|
|
+ resp.getBid() != null && resp.getBid().getClickUrls() != null ? resp.getBid().getClickUrls().size() : 0,
|
|
|
|
|
+ resp.getBid() != null && resp.getBid().getShowUrls() != null ? resp.getBid().getShowUrls().size() : 0);
|
|
|
BidRecord newRecord = bidRecordFromMediaResponse(resp, "tencent", traceId, "click", params);
|
|
BidRecord newRecord = bidRecordFromMediaResponse(resp, "tencent", traceId, "click", params);
|
|
|
hotStore.recordBid(newRecord);
|
|
hotStore.recordBid(newRecord);
|
|
|
record = newRecord;
|
|
record = newRecord;
|
|
@@ -163,32 +174,22 @@ public class TrackingController {
|
|
|
&& !resp.getBid().getShowUrls().isEmpty()) {
|
|
&& !resp.getBid().getShowUrls().isEmpty()) {
|
|
|
List<TrackingResult> impressionResults = adxClient.reportImpression(
|
|
List<TrackingResult> impressionResults = adxClient.reportImpression(
|
|
|
resp.getBid().getShowUrls(), resp.getBid().getPrice());
|
|
resp.getBid().getShowUrls(), resp.getBid().getPrice());
|
|
|
- log.info("[腾讯][点击] Redis未命中补竞价后,曝光上报完成, qk={}, results={}", record.getQk(), impressionResults);
|
|
|
|
|
recordTrackingResults(record.getQk(), "impression", impressionResults);
|
|
recordTrackingResults(record.getQk(), "impression", impressionResults);
|
|
|
}
|
|
}
|
|
|
- log.info("[腾讯][点击] 竞价成功, qk={}, traceId={}", record.getQk(), traceId);
|
|
|
|
|
- } else {
|
|
|
|
|
- log.info("[腾讯][点击] Redis命中, qk={}, traceId={}", record.getQk(), traceId);
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 上报百度点击
|
|
|
|
|
List<TrackingResult> trackingResults = adxClient.reportClick(
|
|
List<TrackingResult> trackingResults = adxClient.reportClick(
|
|
|
record.getClickUrls() != null ? record.getClickUrls() : Collections.emptyList());
|
|
record.getClickUrls() != null ? record.getClickUrls() : Collections.emptyList());
|
|
|
- log.info("[腾讯][点击] 点击上报完成, qk={}, results={}", record.getQk(), trackingResults);
|
|
|
|
|
recordTrackingResults(record.getQk(), "click", trackingResults);
|
|
recordTrackingResults(record.getQk(), "click", trackingResults);
|
|
|
|
|
|
|
|
- // 点击处理完成,缩减 Redis 数据(清除 clickUrls/landingPage/appStoreLink)
|
|
|
|
|
String destination = record.getAppStoreLink() != null && !record.getAppStoreLink().isBlank()
|
|
String destination = record.getAppStoreLink() != null && !record.getAppStoreLink().isBlank()
|
|
|
? record.getAppStoreLink() : record.getLandingPage();
|
|
? record.getAppStoreLink() : record.getLandingPage();
|
|
|
try {
|
|
try {
|
|
|
hotStore.shrinkAfterClick(record);
|
|
hotStore.shrinkAfterClick(record);
|
|
|
} catch (Exception e) {
|
|
} catch (Exception e) {
|
|
|
- log.warn("[腾讯][点击] Redis缩减失败(不影响主流程), qk={}, error={}", record.getQk(), e.getMessage());
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 重定向到 landing page 或 app store link
|
|
|
|
|
if (destination != null && !destination.isBlank()) {
|
|
if (destination != null && !destination.isBlank()) {
|
|
|
- log.info("[腾讯][点击] 302重定向, qk={}, destination={}", record.getQk(), destination);
|
|
|
|
|
response.sendRedirect(destination);
|
|
response.sendRedirect(destination);
|
|
|
return null;
|
|
return null;
|
|
|
}
|
|
}
|
|
@@ -298,7 +299,7 @@ public class TrackingController {
|
|
|
List<Integer> appList = parseAppList(firstParam(params, "appList", "app_list", "applist"));
|
|
List<Integer> appList = parseAppList(firstParam(params, "appList", "app_list", "applist"));
|
|
|
if (!appList.isEmpty()) bidReq.put("appList", appList);
|
|
if (!appList.isEmpty()) bidReq.put("appList", appList);
|
|
|
|
|
|
|
|
- log.info("[腾讯][百度ADX] 竞价请求 | traceId={} | body={}", traceId, bidReq);
|
|
|
|
|
|
|
+ log.info("[腾讯][百度请求] traceId={} body={}", traceId, toJson(bidReq));
|
|
|
|
|
|
|
|
return bidReq;
|
|
return bidReq;
|
|
|
}
|
|
}
|
|
@@ -373,7 +374,7 @@ public class TrackingController {
|
|
|
try {
|
|
try {
|
|
|
result.add(Integer.parseInt(item));
|
|
result.add(Integer.parseInt(item));
|
|
|
} catch (NumberFormatException e) {
|
|
} catch (NumberFormatException e) {
|
|
|
- log.warn("[腾讯][百度ADX] 忽略非法 appList 值 | raw={} | item={}", value, item);
|
|
|
|
|
|
|
+ log.warn("[腾讯][百度请求] invalidAppList raw={} item={}", value, item);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
return result;
|
|
return result;
|
|
@@ -409,4 +410,12 @@ public class TrackingController {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private static String nvl(String s) { return s != null ? s : ""; }
|
|
private static String nvl(String s) { return s != null ? s : ""; }
|
|
|
|
|
+
|
|
|
|
|
+ private String toJson(Object value) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ return objectMapper.writeValueAsString(value);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ return String.valueOf(value);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|