|
|
@@ -4,6 +4,7 @@ import com.adx.tencent.baidu.ConversionClient;
|
|
|
import com.adx.tencent.baidu.model.ConversionQuery;
|
|
|
import com.adx.tencent.baidu.model.ConversionResponse;
|
|
|
import com.adx.tencent.baidu.model.PaymentInfo;
|
|
|
+import com.adx.tencent.config.AppProperties;
|
|
|
import com.adx.tencent.honor.client.HonorClient;
|
|
|
import com.adx.tencent.honor.model.HonorBidRecord;
|
|
|
import com.adx.tencent.honor.model.HonorMediaCallbackRecord;
|
|
|
@@ -21,6 +22,7 @@ import org.slf4j.Logger;
|
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
|
|
import java.time.Instant;
|
|
|
+import java.nio.charset.StandardCharsets;
|
|
|
import java.util.ArrayList;
|
|
|
import java.util.HashSet;
|
|
|
import java.util.LinkedHashSet;
|
|
|
@@ -32,6 +34,7 @@ import java.util.concurrent.ExecutorService;
|
|
|
import java.util.concurrent.Executors;
|
|
|
import java.util.concurrent.TimeUnit;
|
|
|
import java.util.concurrent.atomic.AtomicInteger;
|
|
|
+import java.util.zip.CRC32;
|
|
|
|
|
|
/**
|
|
|
* 统一转化同步:
|
|
|
@@ -57,6 +60,7 @@ public class ConversionSyncService {
|
|
|
private final HonorTagEventResolver honorTagEventResolver;
|
|
|
private final ExecutorService realtimeExecutor;
|
|
|
private final ExecutorService backfillExecutor;
|
|
|
+ private final int tencentNewLinkDeductionRate;
|
|
|
|
|
|
public enum SyncMode {
|
|
|
REALTIME,
|
|
|
@@ -71,7 +75,8 @@ public class ConversionSyncService {
|
|
|
HonorTagEventResolver honorTagEventResolver,
|
|
|
HonorHotStore honorHotStore,
|
|
|
HonorColdStore honorColdStore,
|
|
|
- HonorClient honorClient) {
|
|
|
+ HonorClient honorClient,
|
|
|
+ AppProperties props) {
|
|
|
this.baiduClient = baiduClient;
|
|
|
this.hotStore = hotStore;
|
|
|
this.tencentClient = tencentClient;
|
|
|
@@ -81,6 +86,8 @@ public class ConversionSyncService {
|
|
|
this.honorHotStore = honorHotStore;
|
|
|
this.honorColdStore = honorColdStore;
|
|
|
this.honorClient = honorClient;
|
|
|
+ this.tencentNewLinkDeductionRate = sanitizeDeductionRate(
|
|
|
+ props != null ? props.getTencentNewLinkDeductionRate() : 20);
|
|
|
this.realtimeExecutor = Executors.newFixedThreadPool(
|
|
|
REALTIME_CONCURRENCY,
|
|
|
r -> {
|
|
|
@@ -420,6 +427,10 @@ public class ConversionSyncService {
|
|
|
|
|
|
private CallbackDispatchResult sendCallback(CallbackTask task) {
|
|
|
if (task.isTencent()) {
|
|
|
+ if (shouldDeductTencentCallback(task)) {
|
|
|
+ MediaCallbackRecord deductedRecord = buildDeductedCallbackRecord(task);
|
|
|
+ return CallbackDispatchResult.tencent(task, deductedRecord);
|
|
|
+ }
|
|
|
String platform = resolvePlatform(task.tencentBid);
|
|
|
MediaCallbackRecord callbackRecord = tencentClient.sendConversionEvent(
|
|
|
task.tencentBid, task.payment, platform, task.tencentActionType);
|
|
|
@@ -569,6 +580,89 @@ public class ConversionSyncService {
|
|
|
conversionId);
|
|
|
}
|
|
|
|
|
|
+ private boolean shouldDeductTencentCallback(CallbackTask task) {
|
|
|
+ if (!task.isTencent() || task.tencentBid == null) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ if (task.payment.getAct() == 2001 || tencentNewLinkDeductionRate <= 0) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ if (!isNewTrackingLink(task.tencentBid)) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ return bucketOf(task) < deductionCountPerTen();
|
|
|
+ }
|
|
|
+
|
|
|
+ private static boolean isNewTrackingLink(BidRecord bid) {
|
|
|
+ if (bid == null || bid.getMediaParams() == null) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ String flag = bid.getMediaParams().get(BidRecord.NEW_LINK_FLAG_KEY);
|
|
|
+ if (flag != null && Boolean.parseBoolean(flag)) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ String version = bid.getMediaParams().get(BidRecord.TRACKING_VERSION_KEY);
|
|
|
+ return "v2".equalsIgnoreCase(version);
|
|
|
+ }
|
|
|
+
|
|
|
+ private int bucketOf(CallbackTask task) {
|
|
|
+ CRC32 crc32 = new CRC32();
|
|
|
+ crc32.update(String.join("|",
|
|
|
+ safe(task.tencentBid.getMedia()),
|
|
|
+ safe(task.payment.getQk()),
|
|
|
+ safe(task.payment.getDate()),
|
|
|
+ safe(task.payment.getDeviceId()),
|
|
|
+ safe(task.tencentBid.getTagId()),
|
|
|
+ String.valueOf(task.payment.getAct()),
|
|
|
+ safe(task.tencentActionType)).getBytes(StandardCharsets.UTF_8));
|
|
|
+ return (int) (crc32.getValue() % 10);
|
|
|
+ }
|
|
|
+
|
|
|
+ private MediaCallbackRecord buildDeductedCallbackRecord(CallbackTask task) {
|
|
|
+ MediaCallbackRecord record = new MediaCallbackRecord();
|
|
|
+ Instant now = Instant.now();
|
|
|
+ record.setDedupeKey(task.callbackKey);
|
|
|
+ record.setMedia("tencent");
|
|
|
+ record.setQk(task.payment.getQk());
|
|
|
+ String callbackUrl = task.tencentBid.getMediaParams() != null
|
|
|
+ ? task.tencentBid.getMediaParams().get("callback")
|
|
|
+ : null;
|
|
|
+ record.setCallbackUrl(callbackUrl != null ? callbackUrl : "");
|
|
|
+ record.setEventType(task.payment.getAct());
|
|
|
+ record.setEventTimeMs(now.toEpochMilli());
|
|
|
+ record.setPurchase(task.payment.getPayment());
|
|
|
+ record.setStatus(0);
|
|
|
+ record.setOk(false);
|
|
|
+ record.setAttempt(1);
|
|
|
+ record.setErrorMessage("deducted_by_new_link_rate");
|
|
|
+ record.setDispatchStatus(MediaCallbackRecord.DISPATCH_STATUS_DEDUCTED);
|
|
|
+ record.setTrackingVersion(isNewTrackingLink(task.tencentBid) ? "v2" : "v1");
|
|
|
+ record.setCreatedAt(now);
|
|
|
+ log.info("[ConversionSync] 腾讯转化扣量 | qk={} | act={} | rate={} | tagId={} | bucket={} | callbackKey={}",
|
|
|
+ task.payment.getQk(), task.payment.getAct(), tencentNewLinkDeductionRate,
|
|
|
+ task.tencentBid.getTagId(), bucketOf(task), task.callbackKey);
|
|
|
+ return record;
|
|
|
+ }
|
|
|
+
|
|
|
+ private int deductionCountPerTen() {
|
|
|
+ int count = tencentNewLinkDeductionRate / 10;
|
|
|
+ if (count > 10) {
|
|
|
+ return 10;
|
|
|
+ }
|
|
|
+ return Math.max(count, 0);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static String safe(String value) {
|
|
|
+ return value == null ? "" : value;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static int sanitizeDeductionRate(int rate) {
|
|
|
+ if (rate < 0) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ return Math.min(rate, 100);
|
|
|
+ }
|
|
|
+
|
|
|
private static HonorMediaCallbackRecord toHonorCallbackRecord(CallbackTask task, MediaCallbackRecord record) {
|
|
|
HonorMediaCallbackRecord callback = new HonorMediaCallbackRecord();
|
|
|
callback.setDedupeKey(record.getDedupeKey());
|
|
|
@@ -603,6 +697,7 @@ public class ConversionSyncService {
|
|
|
callbackRecord.setResponseBody(honorRecord.getResponseBody());
|
|
|
callbackRecord.setErrorMessage(honorRecord.getErrorMessage());
|
|
|
callbackRecord.setRequestBody(honorRecord.getRequestBody());
|
|
|
+ callbackRecord.setTrackingVersion("v1");
|
|
|
callbackRecord.setCreatedAt(honorRecord.getCreatedAt());
|
|
|
return callbackRecord;
|
|
|
}
|