yumeng hace 2 días
padre
commit
4a75dc1aa6

+ 0 - 9
src/main/java/com/adx/tencent/config/AppProperties.java

@@ -64,7 +64,6 @@ public class AppProperties {
 
     // Tencent Act Mapping: baiduAct -> tencentActionType
     private Map<Integer, String> tencentActMap = new HashMap<>();
-    private int tencentNewLinkDeductionRate = 20;
 
     // Redis
     private String redisAddr = "127.0.0.1:6379";
@@ -433,14 +432,6 @@ public class AppProperties {
         this.tencentActMap = v;
     }
 
-    public int getTencentNewLinkDeductionRate() {
-        return tencentNewLinkDeductionRate;
-    }
-
-    public void setTencentNewLinkDeductionRate(int v) {
-        this.tencentNewLinkDeductionRate = v;
-    }
-
     public String getRedisAddr() {
         return redisAddr;
     }

+ 21 - 8
src/main/java/com/adx/tencent/conversionsync/ConversionSyncService.java

@@ -60,7 +60,6 @@ public class ConversionSyncService {
     private final HonorTagEventResolver honorTagEventResolver;
     private final ExecutorService realtimeExecutor;
     private final ExecutorService backfillExecutor;
-    private final int tencentNewLinkDeductionRate;
 
     public enum SyncMode {
         REALTIME,
@@ -86,8 +85,6 @@ 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 -> {
@@ -584,13 +581,14 @@ public class ConversionSyncService {
         if (!task.isTencent() || task.tencentBid == null) {
             return false;
         }
-        if (task.payment.getAct() == 2001 || tencentNewLinkDeductionRate <= 0) {
+        int deductionRate = resolveDeductionRate(task.tencentBid);
+        if (task.payment.getAct() == 2001 || deductionRate <= 0) {
             return false;
         }
         if (!isNewTrackingLink(task.tencentBid)) {
             return false;
         }
-        return bucketOf(task) < deductionCountPerTen();
+        return bucketOf(task) < deductionCountPerTen(deductionRate);
     }
 
     private static boolean isNewTrackingLink(BidRecord bid) {
@@ -638,20 +636,35 @@ public class ConversionSyncService {
         record.setDispatchStatus(MediaCallbackRecord.DISPATCH_STATUS_DEDUCTED);
         record.setTrackingVersion(isNewTrackingLink(task.tencentBid) ? "v2" : "v1");
         record.setCreatedAt(now);
+        int deductionRate = resolveDeductionRate(task.tencentBid);
         log.info("[ConversionSync] 腾讯转化扣量 | qk={} | act={} | rate={} | tagId={} | bucket={} | callbackKey={}",
-                task.payment.getQk(), task.payment.getAct(), tencentNewLinkDeductionRate,
+                task.payment.getQk(), task.payment.getAct(), deductionRate,
                 task.tencentBid.getTagId(), bucketOf(task), task.callbackKey);
         return record;
     }
 
-    private int deductionCountPerTen() {
-        int count = tencentNewLinkDeductionRate / 10;
+    private int deductionCountPerTen(int deductionRate) {
+        int count = deductionRate / 10;
         if (count > 10) {
             return 10;
         }
         return Math.max(count, 0);
     }
 
+    private int resolveDeductionRate(BidRecord bid) {
+        if (bid == null || bid.getMediaParams() == null) {
+            return 0;
+        }
+        String rawRate = bid.getMediaParams().get(BidRecord.DEDUCTION_RATE_KEY);
+        if (rawRate != null && !rawRate.isBlank()) {
+            try {
+                return sanitizeDeductionRate(Integer.parseInt(rawRate.trim()));
+            } catch (NumberFormatException ignored) {
+            }
+        }
+        return 0;
+    }
+
     private static String safe(String value) {
         return value == null ? "" : value;
     }

+ 39 - 6
src/main/java/com/adx/tencent/httpapi/TrackingController.java

@@ -79,15 +79,29 @@ public class TrackingController {
     public Object tencentImpressionV2(HttpServletRequest request) throws Exception {
         impressionSemaphore.acquire();
         try {
-            return doImpression(request, true);
+            return doImpression(request, "v2", 20);
+        } finally {
+            impressionSemaphore.release();
+        }
+    }
+
+    @GetMapping("/tencent/v3/impression")
+    public Object tencentImpressionV3(HttpServletRequest request) throws Exception {
+        impressionSemaphore.acquire();
+        try {
+            return doImpression(request, "v3", 50);
         } finally {
             impressionSemaphore.release();
         }
     }
 
     private Object doImpression(HttpServletRequest request, boolean isNewLink) throws Exception {
+        return doImpression(request, isNewLink ? "v2" : "v1", isNewLink ? 20 : 0);
+    }
+
+    private Object doImpression(HttpServletRequest request, String trackingVersion, int deductionRate) throws Exception {
         Map<String, String> params = flatQueryParams(request);
-        markTrackingVersion(params, isNewLink);
+        markTrackingVersion(params, trackingVersion, deductionRate);
         String traceId = mediaTraceId(params);
         if (traceId == null || traceId.isBlank()) {
             log.warn("[腾讯][曝光] 缺少 traceId, params={}", params.keySet());
@@ -151,7 +165,18 @@ public class TrackingController {
             throws Exception {
         clickSemaphore.acquire();
         try {
-            return doClick(request, response, true);
+            return doClick(request, response, "v2", 20);
+        } finally {
+            clickSemaphore.release();
+        }
+    }
+
+    @GetMapping("/tencent/v3/click")
+    public Object tencentClickV3(HttpServletRequest request, HttpServletResponse response)
+            throws Exception {
+        clickSemaphore.acquire();
+        try {
+            return doClick(request, response, "v3", 50);
         } finally {
             clickSemaphore.release();
         }
@@ -164,12 +189,18 @@ public class TrackingController {
 
     private Object doClick(HttpServletRequest request, HttpServletResponse response, boolean isNewLink)
             throws Exception {
+        return doClick(request, response, isNewLink ? "v2" : "v1", isNewLink ? 20 : 0);
+    }
+
+    private Object doClick(HttpServletRequest request, HttpServletResponse response,
+                           String trackingVersion, int deductionRate)
+            throws Exception {
         if (tencentPlacement == null) {
             throw new ResponseStatusException(HttpStatus.SERVICE_UNAVAILABLE, "tencent placement not configured");
         }
 
         Map<String, String> params = flatQueryParams(request);
-        markTrackingVersion(params, isNewLink);
+        markTrackingVersion(params, trackingVersion, deductionRate);
         String traceId = mediaTraceId(params);
         if (traceId == null || traceId.isBlank()) {
             log.warn("[腾讯][点击] 缺少 traceId, params={}", params.keySet());
@@ -225,9 +256,11 @@ public class TrackingController {
         return Map.of("qk", nvl(record.getQk()), "traceId", traceId);
     }
 
-    private static void markTrackingVersion(Map<String, String> params, boolean isNewLink) {
+    private static void markTrackingVersion(Map<String, String> params, String trackingVersion, int deductionRate) {
+        boolean isNewLink = !"v1".equalsIgnoreCase(trackingVersion);
         params.put(BidRecord.NEW_LINK_FLAG_KEY, Boolean.toString(isNewLink));
-        params.put(BidRecord.TRACKING_VERSION_KEY, isNewLink ? "v2" : "v1");
+        params.put(BidRecord.TRACKING_VERSION_KEY, trackingVersion);
+        params.put(BidRecord.DEDUCTION_RATE_KEY, String.valueOf(Math.max(deductionRate, 0)));
     }
 
     // ─── 私有工具方法 ────────────────────────────────────────────────────────

+ 2 - 1
src/main/java/com/adx/tencent/storage/model/BidRecord.java

@@ -11,6 +11,7 @@ import java.util.Map;
 public class BidRecord {
     public static final String NEW_LINK_FLAG_KEY = "is_new_link";
     public static final String TRACKING_VERSION_KEY = "tracking_version";
+    public static final String DEDUCTION_RATE_KEY = "deduction_rate";
 
     @JsonProperty("qk")           private String qk;
     @JsonProperty("media")        private String media;
@@ -139,7 +140,7 @@ public class BidRecord {
                 "android_id", "android_id_md5",
                 "hash_oaid", "hash_android_id",
                 "callback", "click_id", "account_id",
-                NEW_LINK_FLAG_KEY, TRACKING_VERSION_KEY
+                NEW_LINK_FLAG_KEY, TRACKING_VERSION_KEY, DEDUCTION_RATE_KEY
         );
         Map<String, String> slim = new LinkedHashMap<>();
         for (String key : deviceKeys) {

+ 0 - 1
src/main/resources/application-dev.yml

@@ -30,7 +30,6 @@ adx:
   tencent-access-token-redis-key: "adq:access_token:78578478"
   tencent-android-user-action-set-id: 1222435591
   tencent-ios-user-action-set-id: 1222435768
-  tencent-new-link-deduction-rate: 20
   tencent-act-map:
     "1": ACTIVATE_APP      # 激活
     "2": PURCHASE          # 付费

+ 0 - 1
src/main/resources/application-prod.yml

@@ -31,7 +31,6 @@ adx:
   tencent-api-base-url: "https://api.e.qq.com/v3.0"
   tencent-android-user-action-set-id: 1222435591
   tencent-ios-user-action-set-id: 1222435768
-  tencent-new-link-deduction-rate: 20
   tencent-act-map:
     "1": ACTIVATE_APP          # 激活
     "2": PURCHASE              # 付费

+ 0 - 1
src/main/resources/application-test.yml

@@ -30,7 +30,6 @@ adx:
   tencent-access-token: "YOUR_TEST_ACCESS_TOKEN"
   tencent-android-user-action-set-id: 0
   tencent-ios-user-action-set-id: 0
-  tencent-new-link-deduction-rate: 20
   tencent-act-map:
     "1": ACTIVATE_APP
     "2": PURCHASE

+ 0 - 1
src/main/resources/application.yml

@@ -25,7 +25,6 @@ adx:
   baidu-android-tag-ids: jlt52,jlt53,jlt54,jlt55,jlt56,jlt57,jlt58,jlt59,jlt60,jlt61,jlt62,jlt63,jlt64,jlt65,jlt66,jlt67,jlt68,jlt69,jlt70,jlt71,jlt72,jlt73,jlt74,jlt75,jlt76,jlt77,jlt78,jlt79,jlt80,jlt81,jlt82,jlt83,jlt84,jlt85,jlt86,jlt87,jlt88,jlt89,jlt90,jlt91,jlt92,jlt93,jlt94,jlt95,jlt96,jlt97,jlt98,jlt99,jlt100,jlt101
   baidu-ios-app-id: efff0562
   baidu-ios-tag-ids: jlt01,jlt03,jlt04,jlt05,jlt06,jlt07,jlt08,jlt09,jlt10,jlt11,jlt12,jlt13,jlt14,jlt15,jlt16,jlt17,jlt18,jlt19,jlt20,jlt21,jlt22,jlt23,jlt24,jlt25,jlt26,jlt27,jlt28,jlt29,jlt30,jlt31,jlt32,jlt33,jlt34,jlt35,jlt36,jlt37,jlt38,jlt39,jlt40,jlt41,jlt42,jlt43,jlt44,jlt45,jlt46,jlt47,jlt48,jlt49,jlt50,jlt51
-  tencent-new-link-deduction-rate: 20
 
 baidu:
   clue:

+ 1 - 1
src/main/resources/schema.sql

@@ -87,7 +87,7 @@ CREATE TABLE IF NOT EXISTS tencent_media_callbacks (
     error_message TEXT COMMENT '错误信息',
     request_body TEXT COMMENT '请求体',
     dispatch_status VARCHAR(32) NOT NULL DEFAULT 'SENT' COMMENT '回传处理状态(SENT/DEDUCTED)',
-    tracking_version VARCHAR(16) NOT NULL DEFAULT 'v1' COMMENT '监测链接版本(v1老链接/v2新链接)',
+    tracking_version VARCHAR(16) NOT NULL DEFAULT 'v1' COMMENT '监测链接版本(v1/v2/v3)',
     created_at TIMESTAMP(3) NOT NULL COMMENT '创建时间',
     KEY idx_tencent_media_callbacks_dedupe_ok_created_at (dedupe_key, ok, created_at),
     KEY idx_tencent_media_callbacks_qk_created_at (qk, created_at),