Browse Source

腾讯配置

yumeng 3 tuần trước cách đây
mục cha
commit
6662f8a8e2

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

@@ -23,6 +23,7 @@ import com.adx.tencent.storage.TiDBColdStore;
 import com.adx.tencent.storage.model.BidRecord;
 import com.adx.tencent.storage.model.ConversionRecord;
 import com.adx.tencent.storage.model.MediaCallbackRecord;
+import com.adx.tencent.storage.model.AccountTagEventRecord;
 import com.adx.tencent.tagsync.AccountTagEventResolver;
 import com.adx.tencent.tagsync.TagEventResolver;
 import com.adx.tencent.tencent.TencentClient;
@@ -410,12 +411,25 @@ public class ConversionSyncService {
 
     private CallbackTask buildTencentCallbackTask(PaymentInfo payment, BidRecord bid) {
         String actionType = null;
+        String customAction = null;
         if (accountTagEventResolver != null) {
-            actionType = accountTagEventResolver.resolveActionType(bid.getTagId(), bid.getAccountId(), payment.getAct());
+            AccountTagEventRecord accountMapping = accountTagEventResolver.resolveAction(
+                    bid.getTagId(), bid.getAccountId(), payment.getAct());
+            if (accountMapping != null) {
+                actionType = accountMapping.getTencentActionType();
+                customAction = accountMapping.getTencentCustomAction();
+                if ("CUSTOM".equals(actionType) && (customAction == null || customAction.isBlank())) {
+                    log.warn("[ConversionSync] 账户级 CUSTOM 回传缺少 custom_action, 忽略账户级配置并回退广告位配置 | qk={} | tagId={} | accountId={} | act={}",
+                            payment.getQk(), bid.getTagId(), bid.getAccountId(), payment.getAct());
+                    actionType = null;
+                    customAction = null;
+                }
+            }
         }
         if (tagEventResolver != null) {
             if (actionType == null || actionType.isBlank()) {
                 actionType = tagEventResolver.resolveActionType(bid.getTagId(), payment.getAct());
+                customAction = null;
             }
         }
         if (actionType == null || actionType.isBlank()) {
@@ -435,7 +449,8 @@ public class ConversionSyncService {
                 payment,
                 bid,
                 callbackDedupeKey(payment, bid, actionType),
-                actionType
+                actionType,
+                customAction
         );
     }
 
@@ -844,7 +859,7 @@ public class ConversionSyncService {
             }
             String platform = resolvePlatform(task.tencentBid);
             MediaCallbackRecord callbackRecord = tencentClient.sendConversionEvent(
-                    task.tencentBid, task.payment, platform, task.tencentActionType);
+                    task.tencentBid, task.payment, platform, task.tencentActionType, task.tencentCustomAction);
             callbackRecord.setDedupeKey(task.callbackKey);
             return CallbackDispatchResult.tencent(task, callbackRecord);
         }
@@ -1525,6 +1540,7 @@ public class ConversionSyncService {
         private final String callbackKey;
         private final BidRecord tencentBid;
         private final String tencentActionType;
+        private final String tencentCustomAction;
         private final HonorBidRecord honorBid;
         private final Integer honorConversionId;
         private final KuaishouBidRecord kuaishouBid;
@@ -1538,6 +1554,7 @@ public class ConversionSyncService {
                              String callbackKey,
                              BidRecord tencentBid,
                              String tencentActionType,
+                             String tencentCustomAction,
                              HonorBidRecord honorBid,
                              Integer honorConversionId,
                              KuaishouBidRecord kuaishouBid,
@@ -1550,6 +1567,7 @@ public class ConversionSyncService {
             this.callbackKey = callbackKey;
             this.tencentBid = tencentBid;
             this.tencentActionType = tencentActionType;
+            this.tencentCustomAction = tencentCustomAction;
             this.honorBid = honorBid;
             this.honorConversionId = honorConversionId;
             this.kuaishouBid = kuaishouBid;
@@ -1561,29 +1579,30 @@ public class ConversionSyncService {
         private static CallbackTask tencent(PaymentInfo payment,
                                             BidRecord bid,
                                             String callbackKey,
-                                            String actionType) {
-            return new CallbackTask("tencent", payment, bid.getTagId(), callbackKey, bid, actionType, null, null, null, null, null, null);
+                                            String actionType,
+                                            String customAction) {
+            return new CallbackTask("tencent", payment, bid.getTagId(), callbackKey, bid, actionType, customAction, null, null, null, null, null, null);
         }
 
         private static CallbackTask honor(PaymentInfo payment,
                                           HonorBidRecord bid,
                                           String callbackKey,
                                           Integer conversionId) {
-            return new CallbackTask("honor", payment, bid.getTagId(), callbackKey, null, null, bid, conversionId, null, null, null, null);
+            return new CallbackTask("honor", payment, bid.getTagId(), callbackKey, null, null, null, bid, conversionId, null, null, null, null);
         }
 
         private static CallbackTask kuaishou(PaymentInfo payment,
                                              KuaishouBidRecord bid,
                                              String callbackKey,
                                              KuaishouTagEventRecord tagEvent) {
-            return new CallbackTask("kuaishou", payment, bid.getTagId(), callbackKey, null, null, null, null, bid, tagEvent, null, null);
+            return new CallbackTask("kuaishou", payment, bid.getTagId(), callbackKey, null, null, null, null, null, bid, tagEvent, null, null);
         }
 
         private static CallbackTask vivo(PaymentInfo payment,
                                          VivoBidRecord bid,
                                          String callbackKey,
                                          VivoTagEventRecord tagEvent) {
-            return new CallbackTask("vivo", payment, bid.getTagId(), callbackKey, null, null, null, null, null, null, bid, tagEvent);
+            return new CallbackTask("vivo", payment, bid.getTagId(), callbackKey, null, null, null, null, null, null, null, bid, tagEvent);
         }
 
         private boolean isTencent() {

+ 4 - 0
src/main/java/com/adx/tencent/storage/TiDBColdStore.java

@@ -140,6 +140,7 @@ public class TiDBColdStore {
                 account_id VARCHAR(64) NOT NULL COMMENT '腾讯广告主ID',
                 baidu_act INT NOT NULL COMMENT '百度转化行为',
                 tencent_action_type VARCHAR(128) NOT NULL COMMENT '腾讯回传行为',
+                tencent_custom_action VARCHAR(128) COMMENT '腾讯自定义行为(action_type=CUSTOM时使用)',
                 created_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
                 updated_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
                 PRIMARY KEY (tag_id, account_id, baidu_act),
@@ -208,6 +209,9 @@ public class TiDBColdStore {
             try {
                 stmt.execute("ALTER TABLE tencent_tag_event ADD PRIMARY KEY (tag_id, baidu_act)");
             } catch (Exception ignored) {}
+            try {
+                stmt.execute("ALTER TABLE tencent_account_tag_event ADD COLUMN tencent_custom_action VARCHAR(128) COMMENT '腾讯自定义行为(action_type=CUSTOM时使用)' AFTER tencent_action_type");
+            } catch (Exception ignored) {}
             stmt.close();
         } catch (Exception e) {
             throw new RuntimeException("migrate failed", e);

+ 5 - 0
src/main/java/com/adx/tencent/storage/model/AccountTagEventRecord.java

@@ -6,6 +6,7 @@ package com.adx.tencent.storage.model;
  *   account_id          腾讯广告主ID
  *   baidu_act           百度转化行为
  *   tencent_action_type 腾讯回传行为
+ *   tencent_custom_action 腾讯自定义行为,当 action_type=CUSTOM 时使用
  */
 public class AccountTagEventRecord {
 
@@ -13,6 +14,7 @@ public class AccountTagEventRecord {
     private String accountId;
     private int baiduAct;
     private String tencentActionType;
+    private String tencentCustomAction;
 
     public String getTagId() { return tagId; }
     public void setTagId(String v) { this.tagId = v; }
@@ -25,4 +27,7 @@ public class AccountTagEventRecord {
 
     public String getTencentActionType() { return tencentActionType; }
     public void setTencentActionType(String v) { this.tencentActionType = v; }
+
+    public String getTencentCustomAction() { return tencentCustomAction; }
+    public void setTencentCustomAction(String v) { this.tencentCustomAction = v; }
 }

+ 26 - 7
src/main/java/com/adx/tencent/tagsync/AccountTagEventResolver.java

@@ -35,9 +35,9 @@ public class AccountTagEventResolver {
      * @param tagId     百度广告位ID
      * @param accountId 腾讯广告主ID
      * @param baiduAct  百度转化行为
-     * @return 回传方式;未配置时返回 null,让调用方继续走 tencent_tag_event
+     * @return 账户级回传配置;未配置时返回 null,让调用方继续走 tencent_tag_event
      */
-    public String resolveActionType(String tagId, String accountId, int baiduAct) {
+    public AccountTagEventRecord resolveAction(String tagId, String accountId, int baiduAct) {
         if (tagId == null || tagId.isBlank() || accountId == null || accountId.isBlank()) {
             return null;
         }
@@ -48,7 +48,7 @@ public class AccountTagEventResolver {
         try {
             String cached = redis.opsForValue().get(key);
             if (isConfiguredValue(cached)) {
-                return cached;
+                return fromCacheValue(tagId, accountId, baiduAct, cached);
             }
             negativeCached = NO_CONFIG_VALUE.equals(cached);
             if (negativeCached) {
@@ -63,8 +63,8 @@ public class AccountTagEventResolver {
             if (!negativeCached) {
                 AccountTagEventRecord record = coldStore.getAccountTagEvent(tagId, accountId, baiduAct);
                 if (hasActionType(record)) {
-                    cacheConfigured(key, record.getTencentActionType());
-                    return record.getTencentActionType();
+                    cacheConfigured(key, record);
+                    return record;
                 }
                 cacheNegative(key);
             }
@@ -90,14 +90,33 @@ public class AccountTagEventResolver {
                 && !record.getTencentActionType().isBlank();
     }
 
-    private void cacheConfigured(String key, String actionType) {
+    private static AccountTagEventRecord fromCacheValue(String tagId, String accountId, int baiduAct, String value) {
+        String[] parts = value.split("\t", -1);
+        AccountTagEventRecord record = new AccountTagEventRecord();
+        record.setTagId(tagId);
+        record.setAccountId(accountId);
+        record.setBaiduAct(baiduAct);
+        record.setTencentActionType(parts.length > 0 ? parts[0] : value);
+        record.setTencentCustomAction(parts.length > 1 ? parts[1] : null);
+        return record;
+    }
+
+    private void cacheConfigured(String key, AccountTagEventRecord record) {
         try {
-            redis.opsForValue().set(key, actionType, CONFIG_CACHE_TTL);
+            redis.opsForValue().set(key,
+                    cacheValue(record.getTencentActionType(), record.getTencentCustomAction()),
+                    CONFIG_CACHE_TTL);
         } catch (Exception e) {
             log.warn("[AccountTagEventResolver] 写入 Redis 配置缓存失败 | key={} | error={}", key, e.getMessage());
         }
     }
 
+    private static String cacheValue(String actionType, String customAction) {
+        String action = actionType == null ? "" : actionType;
+        String custom = customAction == null ? "" : customAction;
+        return action + "\t" + custom;
+    }
+
     private void cacheNegative(String key) {
         try {
             redis.opsForValue().set(key, NO_CONFIG_VALUE, NEGATIVE_CACHE_TTL);

+ 8 - 2
src/main/java/com/adx/tencent/tagsync/AccountTagEventSyncService.java

@@ -17,7 +17,7 @@ import java.util.List;
  * 腾讯账户级广告位回传方式同步服务。
  * 定时从 tencent_account_tag_event 表读取全部记录,同步到 Redis:
  *   key   = {prefix}{tag_id}:{account_id}:{baidu_act}
- *   value = tencent_action_type
+ *   value = tencent_action_type + tab + tencent_custom_action
  */
 public class AccountTagEventSyncService {
 
@@ -52,7 +52,7 @@ public class AccountTagEventSyncService {
                 if (row.getTagId() == null || row.getTagId().isBlank()) continue;
                 if (row.getAccountId() == null || row.getAccountId().isBlank()) continue;
                 byte[] key = buildRedisKey(row.getTagId(), row.getAccountId(), row.getBaiduAct()).getBytes(StandardCharsets.UTF_8);
-                byte[] val = (row.getTencentActionType() == null ? "" : row.getTencentActionType())
+                byte[] val = cacheValue(row.getTencentActionType(), row.getTencentCustomAction())
                         .getBytes(StandardCharsets.UTF_8);
                 if (ttlMs > 0) {
                     conn.stringCommands().set(key, val,
@@ -72,4 +72,10 @@ public class AccountTagEventSyncService {
     private String buildRedisKey(String tagId, String accountId, int baiduAct) {
         return keyPrefix + tagId + ":" + accountId + ":" + baiduAct;
     }
+
+    private static String cacheValue(String actionType, String customAction) {
+        String action = actionType == null ? "" : actionType;
+        String custom = customAction == null ? "" : customAction;
+        return action + "\t" + custom;
+    }
 }

+ 17 - 6
src/main/java/com/adx/tencent/tencent/TencentClient.java

@@ -72,6 +72,11 @@ public class TencentClient {
      */
     public MediaCallbackRecord sendConversionEvent(BidRecord bid, PaymentInfo payment, String platform,
                                                    String actionType) {
+        return sendConversionEvent(bid, payment, platform, actionType, null);
+    }
+
+    public MediaCallbackRecord sendConversionEvent(BidRecord bid, PaymentInfo payment, String platform,
+                                                   String actionType, String customActionOverride) {
         long eventTimeMs = Instant.now().toEpochMilli();
 
         MediaCallbackRecord record = new MediaCallbackRecord();
@@ -96,7 +101,7 @@ public class TencentClient {
         record.setCallbackUrl(callbackUrl);
 
         // 构造 actions body
-        UserAction action = buildUserAction(bid, payment, actionType, eventTimeMs);
+        UserAction action = buildUserAction(bid, payment, actionType, customActionOverride, eventTimeMs);
         Map<String, Object> body = new LinkedHashMap<>();
         body.put("actions", List.of(action));
 
@@ -227,7 +232,7 @@ public class TencentClient {
     // ─── 构造 UserAction ────────────────────────────────────────────────────
 
     private UserAction buildUserAction(BidRecord bid, PaymentInfo payment,
-                                       String actionType, long eventTimeMs) {
+                                       String actionType, String customActionOverride, long eventTimeMs) {
         UserAction action = new UserAction();
         action.setActionTime(eventTimeMs / 1000); // 秒级时间戳
         action.setActionType(actionType);
@@ -263,10 +268,9 @@ public class TencentClient {
             action.setActionParam(param);
         } else if ("CUSTOM".equals(actionType)) {
             UserAction.ActionParam param = new UserAction.ActionParam();
-            String customAction = switch (payment.getAct()) {
-                case 7 -> "UV_CORE_ACTION";  // 关键行为(文档标准值)
-                default -> "custom_" + payment.getAct();
-            };
+            String customAction = customActionOverride != null && !customActionOverride.isBlank()
+                    ? customActionOverride
+                    : defaultCustomAction(payment.getAct());
             param.setCustomAction(customAction);
             action.setActionParam(param);
         }
@@ -274,6 +278,13 @@ public class TencentClient {
         return action;
     }
 
+    private static String defaultCustomAction(int baiduAct) {
+        return switch (baiduAct) {
+            case 7 -> "UV_CORE_ACTION";  // 关键行为(文档标准值)
+            default -> "custom_" + baiduAct;
+        };
+    }
+
     private static String resolveTrackingVersion(BidRecord bid) {
         if (bid == null || bid.getMediaParams() == null) {
             return "v1";

+ 3 - 2
src/main/resources/mapper/AccountTagEventMapper.xml

@@ -7,15 +7,16 @@
         <result property="accountId" column="account_id"/>
         <result property="baiduAct" column="baidu_act"/>
         <result property="tencentActionType" column="tencent_action_type"/>
+        <result property="tencentCustomAction" column="tencent_custom_action"/>
     </resultMap>
 
     <select id="selectAll" resultMap="accountTagEventResultMap">
-        SELECT tag_id, account_id, baidu_act, tencent_action_type
+        SELECT tag_id, account_id, baidu_act, tencent_action_type, tencent_custom_action
         FROM tencent_account_tag_event
     </select>
 
     <select id="selectByTagIdAndAccountIdAndAct" resultMap="accountTagEventResultMap">
-        SELECT tag_id, account_id, baidu_act, tencent_action_type
+        SELECT tag_id, account_id, baidu_act, tencent_action_type, tencent_custom_action
         FROM tencent_account_tag_event
         WHERE tag_id = #{tagId} AND account_id = #{accountId} AND baidu_act = #{baiduAct}
     </select>

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

@@ -113,6 +113,7 @@ CREATE TABLE IF NOT EXISTS tencent_account_tag_event (
     account_id VARCHAR(64) NOT NULL COMMENT '腾讯广告主ID',
     baidu_act INT NOT NULL COMMENT '百度转化行为',
     tencent_action_type VARCHAR(128) NOT NULL COMMENT '腾讯回传行为',
+    tencent_custom_action VARCHAR(128) COMMENT '腾讯自定义行为(action_type=CUSTOM时使用)',
     created_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
     updated_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
     PRIMARY KEY (tag_id, account_id, baidu_act),