yumeng 3 týždňov pred
rodič
commit
cd1dbce5ab

+ 58 - 1
src/main/java/com/adx/tencent/AppConfiguration.java

@@ -28,6 +28,8 @@ import com.adx.tencent.kuaishou.store.KuaishouColdStore;
 import com.adx.tencent.kuaishou.store.KuaishouHotStore;
 import com.adx.tencent.leader.LeaderElection;
 import com.adx.tencent.report.AdBidReportStore;
+import com.adx.tencent.tagsync.AccountTagEventResolver;
+import com.adx.tencent.tagsync.AccountTagEventSyncService;
 import com.adx.tencent.tagsync.TagEventResolver;
 import com.adx.tencent.tagsync.TagEventSyncService;
 import com.adx.tencent.tencent.TencentClient;
@@ -510,6 +512,13 @@ public class AppConfiguration {
     }
 
     @Bean
+    public AccountTagEventResolver accountTagEventResolver(@Nullable TiDBColdStore coldStore,
+                                                           StringRedisTemplate redisTemplate) {
+        if (coldStore == null) return null;
+        return new AccountTagEventResolver(redisTemplate, coldStore, props.getAccountTagEventSyncRedisPrefix());
+    }
+
+    @Bean
     public HonorTagEventResolver honorTagEventResolver(@Nullable HonorColdStore honorColdStore,
                                                        StringRedisTemplate redisTemplate) {
         if (honorColdStore == null) return null;
@@ -536,6 +545,7 @@ public class AppConfiguration {
                                                          TencentClient tencentClient,
                                                          @Nullable TiDBColdStore coldStore,
                                                          @Nullable TagEventResolver tagEventResolver,
+                                                         @Nullable AccountTagEventResolver accountTagEventResolver,
                                                          @Nullable HonorTagEventResolver honorTagEventResolver,
                                                          @Nullable KuaishouTagEventResolver kuaishouTagEventResolver,
                                                          @Nullable VivoTagEventResolver vivoTagEventResolver,
@@ -550,7 +560,7 @@ public class AppConfiguration {
                                                          @Nullable VivoClient vivoClient) {
         if (coldStore == null) return null;
         return new ConversionSyncService(baiduClient, hotStore, tencentClient, coldStore, tagEventResolver,
-                honorTagEventResolver, kuaishouTagEventResolver, vivoTagEventResolver,
+                accountTagEventResolver, honorTagEventResolver, kuaishouTagEventResolver, vivoTagEventResolver,
                 honorHotStore, honorColdStore, kuaishouHotStore, kuaishouColdStore, vivoHotStore, vivoColdStore,
                 honorClient, kuaishouClient, vivoClient, props);
     }
@@ -595,6 +605,15 @@ public class AppConfiguration {
     }
 
     @Bean
+    public AccountTagEventSyncService accountTagEventSyncService(@Nullable TiDBColdStore coldStore,
+                                                                 StringRedisTemplate redisTemplate) {
+        if (coldStore == null) return null;
+        Duration ttl = props.getAccountTagEventSyncInterval().multipliedBy(3);
+        return new AccountTagEventSyncService(coldStore, redisTemplate,
+                props.getAccountTagEventSyncRedisPrefix(), ttl);
+    }
+
+    @Bean
     public HonorTagEventSyncService honorTagEventSyncService(@Nullable HonorColdStore honorColdStore,
                                                              StringRedisTemplate redisTemplate) {
         if (honorColdStore == null) return null;
@@ -734,6 +753,7 @@ public class AppConfiguration {
         @Autowired(required = false) private ConversionSyncRunner conversionSyncRunner;
         @Autowired(required = false) private RetryService retryService;
         @Autowired(required = false) private TagEventSyncService tagEventSyncService;
+        @Autowired(required = false) private AccountTagEventSyncService accountTagEventSyncService;
         @Autowired(required = false) private LeaderElection leaderElection;
 
         private final AtomicBoolean stopped = new AtomicBoolean(false);
@@ -825,6 +845,28 @@ public class AppConfiguration {
                 taskLog.warn("Tag event sync NOT started: service={}, enabled={}",
                         tagEventSyncService != null, props.isTagEventSyncEnabled());
             }
+
+            // 腾讯账户级广告位回传方式同步
+            if (accountTagEventSyncService != null && props.isAccountTagEventSyncEnabled()) {
+                if (props.isSkipLeaderElection()) {
+                    executor.submit(() -> runAccountTagEventSync(() -> stopped.get()));
+                    taskLog.info("Account tag event sync started (skip leader election)");
+                } else if (leaderElection != null) {
+                    executor.submit(() ->
+                        leaderElection.run(
+                            "adx:lock:tencent:account-tag-event-sync",
+                            props.getTaskLockTtl(), props.getTaskLockRenewInterval(), props.getTaskLockRetryInterval(),
+                            stopped::get,
+                            (jobStop) -> runAccountTagEventSync(jobStop),
+                            e -> taskLog.error("tencent account tag event sync leader: {}", e.getMessage(), e)
+                        )
+                    );
+                    taskLog.info("Account tag event sync leader election started");
+                }
+            } else {
+                taskLog.warn("Account tag event sync NOT started: service={}, enabled={}",
+                        accountTagEventSyncService != null, props.isAccountTagEventSyncEnabled());
+            }
         }
 
         private void runConversionSync(LeaderElection.StopSignal jobStop) {
@@ -918,6 +960,21 @@ public class AppConfiguration {
             taskLog.info("[TagEventSync] task stopped");
         }
 
+        private void runAccountTagEventSync(LeaderElection.StopSignal jobStop) {
+            long intervalMs = props.getAccountTagEventSyncInterval().toMillis();
+            taskLog.info("[AccountTagEventSync] task started, interval={}ms", intervalMs);
+            while (!jobStop.isStopped()) {
+                try {
+                    int n = accountTagEventSyncService.syncOnce();
+                    taskLog.info("[AccountTagEventSync] done: synced={}", n);
+                } catch (Exception e) {
+                    taskLog.error("[AccountTagEventSync] error: {}", e.getMessage(), e);
+                }
+                sleepResponsive(intervalMs, jobStop);
+            }
+            taskLog.info("[AccountTagEventSync] task stopped");
+        }
+
         private static void sleepResponsive(long ms, LeaderElection.StopSignal jobStop) {
             long deadline = System.currentTimeMillis() + ms;
             while (!jobStop.isStopped()) {

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

@@ -157,6 +157,9 @@ public class AppProperties {
     private boolean tagEventSyncEnabled = false;
     private Duration tagEventSyncInterval = Duration.ofMinutes(5);
     private String tagEventSyncRedisPrefix = "";
+    private boolean accountTagEventSyncEnabled = false;
+    private Duration accountTagEventSyncInterval = Duration.ofMinutes(5);
+    private String accountTagEventSyncRedisPrefix = "adx:tencent:account-tag-event:";
 
     private static String defaultHostname() {
         try {
@@ -1071,4 +1074,28 @@ public class AppProperties {
     public void setTagEventSyncRedisPrefix(String v) {
         this.tagEventSyncRedisPrefix = v;
     }
+
+    public boolean isAccountTagEventSyncEnabled() {
+        return accountTagEventSyncEnabled;
+    }
+
+    public void setAccountTagEventSyncEnabled(boolean v) {
+        this.accountTagEventSyncEnabled = v;
+    }
+
+    public Duration getAccountTagEventSyncInterval() {
+        return accountTagEventSyncInterval;
+    }
+
+    public void setAccountTagEventSyncInterval(Duration v) {
+        this.accountTagEventSyncInterval = v;
+    }
+
+    public String getAccountTagEventSyncRedisPrefix() {
+        return accountTagEventSyncRedisPrefix;
+    }
+
+    public void setAccountTagEventSyncRedisPrefix(String v) {
+        this.accountTagEventSyncRedisPrefix = v;
+    }
 }

+ 14 - 5
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.tagsync.AccountTagEventResolver;
 import com.adx.tencent.tagsync.TagEventResolver;
 import com.adx.tencent.tencent.TencentClient;
 import com.adx.tencent.vivo.client.VivoClient;
@@ -91,6 +92,7 @@ public class ConversionSyncService {
     private final KuaishouClient kuaishouClient;
     private final VivoClient vivoClient;
     private final TagEventResolver tagEventResolver;
+    private final AccountTagEventResolver accountTagEventResolver;
     private final HonorTagEventResolver honorTagEventResolver;
     private final KuaishouTagEventResolver kuaishouTagEventResolver;
     private final VivoTagEventResolver vivoTagEventResolver;
@@ -107,6 +109,7 @@ public class ConversionSyncService {
                                  TencentClient tencentClient,
                                  TiDBColdStore coldStore,
                                  TagEventResolver tagEventResolver,
+                                 AccountTagEventResolver accountTagEventResolver,
                                  HonorTagEventResolver honorTagEventResolver,
                                  KuaishouTagEventResolver kuaishouTagEventResolver,
                                  VivoTagEventResolver vivoTagEventResolver,
@@ -125,6 +128,7 @@ public class ConversionSyncService {
         this.tencentClient = tencentClient;
         this.coldStore = coldStore;
         this.tagEventResolver = tagEventResolver;
+        this.accountTagEventResolver = accountTagEventResolver;
         this.honorTagEventResolver = honorTagEventResolver;
         this.kuaishouTagEventResolver = kuaishouTagEventResolver;
         this.vivoTagEventResolver = vivoTagEventResolver;
@@ -406,18 +410,23 @@ public class ConversionSyncService {
 
     private CallbackTask buildTencentCallbackTask(PaymentInfo payment, BidRecord bid) {
         String actionType = null;
+        if (accountTagEventResolver != null) {
+            actionType = accountTagEventResolver.resolveActionType(bid.getTagId(), bid.getAccountId(), payment.getAct());
+        }
         if (tagEventResolver != null) {
-            actionType = tagEventResolver.resolveActionType(bid.getTagId(), payment.getAct());
+            if (actionType == null || actionType.isBlank()) {
+                actionType = tagEventResolver.resolveActionType(bid.getTagId(), payment.getAct());
+            }
         }
         if (actionType == null || actionType.isBlank()) {
             try {
                 actionType = tencentClient.conversionActionType(payment.getAct());
-                log.warn("[ConversionSync] 广告位回传方式未配置, 回退act-map | qk={} | tagId={} | act={} | actionType={}",
-                        payment.getQk(), bid.getTagId(), payment.getAct(), actionType);
+                log.warn("[ConversionSync] 账户级/广告位回传方式未配置, 回退act-map | qk={} | tagId={} | accountId={} | act={} | actionType={}",
+                        payment.getQk(), bid.getTagId(), bid.getAccountId(), payment.getAct(), actionType);
             } catch (IllegalArgumentException e) {
                 // act 既没配广告位回传方式,也没配 act-map:只落库不回传,避免整个任务中断
-                log.warn("[ConversionSync] 腾讯回传方式未配置, 跳过回传 | qk={} | tagId={} | act={} | date={} | error={}",
-                        payment.getQk(), bid.getTagId(), payment.getAct(), payment.getDate(), e.getMessage());
+                log.warn("[ConversionSync] 腾讯回传方式未配置, 跳过回传 | qk={} | tagId={} | accountId={} | act={} | date={} | error={}",
+                        payment.getQk(), bid.getTagId(), bid.getAccountId(), payment.getAct(), payment.getDate(), e.getMessage());
                 return null;
             }
         }

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

@@ -133,6 +133,18 @@ public class TiDBColdStore {
                 tencent_action_type VARCHAR(128) NOT NULL COMMENT '腾讯回传行为',
                 PRIMARY KEY (tag_id, baidu_act)
             ) COMMENT '竞价事件记录'
+            """,
+            """
+            CREATE TABLE IF NOT EXISTS tencent_account_tag_event (
+                tag_id VARCHAR(128) NOT NULL COMMENT '百度广告位ID',
+                account_id VARCHAR(64) NOT NULL COMMENT '腾讯广告主ID',
+                baidu_act INT NOT NULL COMMENT '百度转化行为',
+                tencent_action_type VARCHAR(128) NOT NULL COMMENT '腾讯回传行为',
+                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),
+                KEY idx_tencent_account_tag_event_account_act (account_id, baidu_act)
+            ) COMMENT '腾讯账户级广告位回传方式配置'
             """
         };
         try (SqlSession session = sqlSessionFactory.openSession(true)) {
@@ -433,6 +445,27 @@ public class TiDBColdStore {
         }
     }
 
+    // ─── 查询:腾讯账户级广告位回传方式配置 ───────────────────────────────────
+
+    public List<AccountTagEventRecord> listAllAccountTagEvents() {
+        try (SqlSession session = sqlSessionFactory.openSession(true)) {
+            AccountTagEventMapper mapper = session.getMapper(AccountTagEventMapper.class);
+            return mapper.selectAll();
+        }
+    }
+
+    /**
+     * 根据广告位ID、腾讯广告主ID和百度行为查询账户级回传方式配置(DB 兜底查询)。
+     */
+    public AccountTagEventRecord getAccountTagEvent(String tagId, String accountId, int baiduAct) {
+        if (tagId == null || tagId.isBlank()) return null;
+        if (accountId == null || accountId.isBlank()) return null;
+        try (SqlSession session = sqlSessionFactory.openSession(true)) {
+            AccountTagEventMapper mapper = session.getMapper(AccountTagEventMapper.class);
+            return mapper.selectByTagIdAndAccountIdAndAct(tagId, accountId, baiduAct);
+        }
+    }
+
     // ─── 工具方法 ─────────────────────────────────────────────────────────────
 
     private String toJson(Object value) {

+ 23 - 0
src/main/java/com/adx/tencent/storage/mapper/AccountTagEventMapper.java

@@ -0,0 +1,23 @@
+package com.adx.tencent.storage.mapper;
+
+import com.adx.tencent.storage.model.AccountTagEventRecord;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+@Mapper
+public interface AccountTagEventMapper {
+
+    /**
+     * 查询全部腾讯账户级广告位回传方式配置。
+     */
+    List<AccountTagEventRecord> selectAll();
+
+    /**
+     * 根据广告位ID、腾讯广告主ID和百度行为查询账户级回传方式配置。
+     */
+    AccountTagEventRecord selectByTagIdAndAccountIdAndAct(@Param("tagId") String tagId,
+                                                          @Param("accountId") String accountId,
+                                                          @Param("baiduAct") int baiduAct);
+}

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

@@ -0,0 +1,28 @@
+package com.adx.tencent.storage.model;
+
+/**
+ * 腾讯账户级广告位回传方式记录,对应 tencent_account_tag_event 表。
+ *   tag_id              百度广告位ID
+ *   account_id          腾讯广告主ID
+ *   baidu_act           百度转化行为
+ *   tencent_action_type 腾讯回传行为
+ */
+public class AccountTagEventRecord {
+
+    private String tagId;
+    private String accountId;
+    private int baiduAct;
+    private String tencentActionType;
+
+    public String getTagId() { return tagId; }
+    public void setTagId(String v) { this.tagId = v; }
+
+    public String getAccountId() { return accountId; }
+    public void setAccountId(String v) { this.accountId = v; }
+
+    public int getBaiduAct() { return baiduAct; }
+    public void setBaiduAct(int v) { this.baiduAct = v; }
+
+    public String getTencentActionType() { return tencentActionType; }
+    public void setTencentActionType(String v) { this.tencentActionType = v; }
+}

+ 108 - 0
src/main/java/com/adx/tencent/tagsync/AccountTagEventResolver.java

@@ -0,0 +1,108 @@
+package com.adx.tencent.tagsync;
+
+import com.adx.tencent.storage.TiDBColdStore;
+import com.adx.tencent.storage.model.AccountTagEventRecord;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.data.redis.core.StringRedisTemplate;
+
+import java.time.Duration;
+
+/**
+ * 腾讯账户级广告位回传方式解析器。
+ * 按 tag_id + account_id + baidu_act 精确命中账户级覆盖配置。
+ */
+public class AccountTagEventResolver {
+
+    private static final Logger log = LoggerFactory.getLogger(AccountTagEventResolver.class);
+    private static final String NO_CONFIG_VALUE = "__NO_ACCOUNT_TAG_EVENT__";
+    private static final Duration CONFIG_CACHE_TTL = Duration.ofMinutes(5);
+    private static final Duration NEGATIVE_CACHE_TTL = Duration.ofMinutes(1);
+
+    private final StringRedisTemplate redis;
+    private final TiDBColdStore coldStore;
+    private final String keyPrefix;
+
+    public AccountTagEventResolver(StringRedisTemplate redis, TiDBColdStore coldStore, String keyPrefix) {
+        this.redis = redis;
+        this.coldStore = coldStore;
+        this.keyPrefix = keyPrefix == null ? "" : keyPrefix;
+    }
+
+    /**
+     * 解析腾讯账户级广告位回传方式。
+     *
+     * @param tagId     百度广告位ID
+     * @param accountId 腾讯广告主ID
+     * @param baiduAct  百度转化行为
+     * @return 回传方式;未配置时返回 null,让调用方继续走 tencent_tag_event
+     */
+    public String resolveActionType(String tagId, String accountId, int baiduAct) {
+        if (tagId == null || tagId.isBlank() || accountId == null || accountId.isBlank()) {
+            return null;
+        }
+
+        String key = buildRedisKey(tagId, accountId, baiduAct);
+        boolean negativeCached = false;
+
+        try {
+            String cached = redis.opsForValue().get(key);
+            if (isConfiguredValue(cached)) {
+                return cached;
+            }
+            negativeCached = NO_CONFIG_VALUE.equals(cached);
+            if (negativeCached) {
+                return null;
+            }
+        } catch (Exception e) {
+            log.warn("[AccountTagEventResolver] 读取 Redis 失败, 回退DB查询 | tagId={} | accountId={} | act={} | error={}",
+                    tagId, accountId, baiduAct, e.getMessage());
+        }
+
+        try {
+            if (!negativeCached) {
+                AccountTagEventRecord record = coldStore.getAccountTagEvent(tagId, accountId, baiduAct);
+                if (hasActionType(record)) {
+                    cacheConfigured(key, record.getTencentActionType());
+                    return record.getTencentActionType();
+                }
+                cacheNegative(key);
+            }
+        } catch (Exception e) {
+            log.warn("[AccountTagEventResolver] 查询 tencent_account_tag_event 失败 | tagId={} | accountId={} | act={} | error={}",
+                    tagId, accountId, baiduAct, e.getMessage());
+        }
+
+        return null;
+    }
+
+    private String buildRedisKey(String tagId, String accountId, int baiduAct) {
+        return keyPrefix + tagId + ":" + accountId + ":" + baiduAct;
+    }
+
+    private static boolean isConfiguredValue(String value) {
+        return value != null && !value.isBlank() && !NO_CONFIG_VALUE.equals(value);
+    }
+
+    private static boolean hasActionType(AccountTagEventRecord record) {
+        return record != null
+                && record.getTencentActionType() != null
+                && !record.getTencentActionType().isBlank();
+    }
+
+    private void cacheConfigured(String key, String actionType) {
+        try {
+            redis.opsForValue().set(key, actionType, CONFIG_CACHE_TTL);
+        } catch (Exception e) {
+            log.warn("[AccountTagEventResolver] 写入 Redis 配置缓存失败 | key={} | error={}", key, e.getMessage());
+        }
+    }
+
+    private void cacheNegative(String key) {
+        try {
+            redis.opsForValue().set(key, NO_CONFIG_VALUE, NEGATIVE_CACHE_TTL);
+        } catch (Exception e) {
+            log.warn("[AccountTagEventResolver] 写入 Redis 未配置缓存失败 | key={} | error={}", key, e.getMessage());
+        }
+    }
+}

+ 75 - 0
src/main/java/com/adx/tencent/tagsync/AccountTagEventSyncService.java

@@ -0,0 +1,75 @@
+package com.adx.tencent.tagsync;
+
+import com.adx.tencent.storage.TiDBColdStore;
+import com.adx.tencent.storage.model.AccountTagEventRecord;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.data.redis.connection.RedisConnection;
+import org.springframework.data.redis.connection.RedisStringCommands;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.data.redis.core.types.Expiration;
+
+import java.nio.charset.StandardCharsets;
+import java.time.Duration;
+import java.util.List;
+
+/**
+ * 腾讯账户级广告位回传方式同步服务。
+ * 定时从 tencent_account_tag_event 表读取全部记录,同步到 Redis:
+ *   key   = {prefix}{tag_id}:{account_id}:{baidu_act}
+ *   value = tencent_action_type
+ */
+public class AccountTagEventSyncService {
+
+    private static final Logger log = LoggerFactory.getLogger(AccountTagEventSyncService.class);
+
+    private final TiDBColdStore coldStore;
+    private final StringRedisTemplate redis;
+    private final String keyPrefix;
+    private final Duration ttl;
+
+    public AccountTagEventSyncService(TiDBColdStore coldStore, StringRedisTemplate redis,
+                                      String keyPrefix, Duration ttl) {
+        this.coldStore = coldStore;
+        this.redis = redis;
+        this.keyPrefix = keyPrefix == null ? "" : keyPrefix;
+        this.ttl = ttl;
+    }
+
+    /**
+     * 执行一次同步,返回写入 Redis 的记录数。
+     */
+    public int syncOnce() {
+        List<AccountTagEventRecord> rows = coldStore.listAllAccountTagEvents();
+        if (rows == null || rows.isEmpty()) {
+            log.info("[AccountTagEventSync] 无数据可同步");
+            return 0;
+        }
+        long ttlMs = (ttl != null && !ttl.isZero()) ? ttl.toMillis() : 0;
+
+        redis.executePipelined((RedisConnection conn) -> {
+            for (AccountTagEventRecord row : rows) {
+                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())
+                        .getBytes(StandardCharsets.UTF_8);
+                if (ttlMs > 0) {
+                    conn.stringCommands().set(key, val,
+                            Expiration.milliseconds(ttlMs),
+                            RedisStringCommands.SetOption.UPSERT);
+                } else {
+                    conn.stringCommands().set(key, val);
+                }
+            }
+            return null;
+        });
+
+        log.info("[AccountTagEventSync] 同步完成,共 {} 条腾讯账户级广告位回传方式写入 Redis", rows.size());
+        return rows.size();
+    }
+
+    private String buildRedisKey(String tagId, String accountId, int baiduAct) {
+        return keyPrefix + tagId + ":" + accountId + ":" + baiduAct;
+    }
+}

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

@@ -86,6 +86,9 @@ adx:
   # --- Tag Event Sync(广告位回传方式 -> Redis)---
   tag-event-sync-enabled: false
   tag-event-sync-interval: 300s
+  account-tag-event-sync-enabled: false
+  account-tag-event-sync-interval: 300s
+  account-tag-event-sync-redis-prefix: "adx:tencent:account-tag-event:"
 
   # --- Honor Tag Event Sync(荣耀广告位回传方式 -> Redis)---
   honor-tag-event-sync-enabled: false

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

@@ -91,6 +91,9 @@ adx:
   # --- Tag Event Sync(广告位回传方式 -> Redis)---
   tag-event-sync-enabled: true
   tag-event-sync-interval: 300s
+  account-tag-event-sync-enabled: true
+  account-tag-event-sync-interval: 300s
+  account-tag-event-sync-redis-prefix: "adx:tencent:account-tag-event:"
 
   # --- Honor Tag Event Sync(荣耀广告位回传方式 -> Redis)---
   honor-tag-event-sync-enabled: true

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

@@ -90,6 +90,9 @@ adx:
   # --- Tag Event Sync(广告位回传方式 -> Redis)---
   tag-event-sync-enabled: true
   tag-event-sync-interval: 300s
+  account-tag-event-sync-enabled: true
+  account-tag-event-sync-interval: 300s
+  account-tag-event-sync-redis-prefix: "adx:tencent:account-tag-event:"
 
   # --- Honor Tag Event Sync(荣耀广告位回传方式 -> Redis)---
   honor-tag-event-sync-enabled: false

+ 22 - 0
src/main/resources/mapper/AccountTagEventMapper.xml

@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.adx.tencent.storage.mapper.AccountTagEventMapper">
+    <resultMap id="accountTagEventResultMap" type="com.adx.tencent.storage.model.AccountTagEventRecord">
+        <result property="tagId" column="tag_id"/>
+        <result property="accountId" column="account_id"/>
+        <result property="baiduAct" column="baidu_act"/>
+        <result property="tencentActionType" column="tencent_action_type"/>
+    </resultMap>
+
+    <select id="selectAll" resultMap="accountTagEventResultMap">
+        SELECT tag_id, account_id, baidu_act, tencent_action_type
+        FROM tencent_account_tag_event
+    </select>
+
+    <select id="selectByTagIdAndAccountIdAndAct" resultMap="accountTagEventResultMap">
+        SELECT tag_id, account_id, baidu_act, tencent_action_type
+        FROM tencent_account_tag_event
+        WHERE tag_id = #{tagId} AND account_id = #{accountId} AND baidu_act = #{baiduAct}
+    </select>
+</mapper>

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

@@ -106,6 +106,20 @@ CREATE TABLE IF NOT EXISTS tencent_tag_event (
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='广告位回传方式配置';
 
 -- ------------------------------------------------------------
+-- 6. 腾讯账户级广告位回传方式配置表
+-- ------------------------------------------------------------
+CREATE TABLE IF NOT EXISTS tencent_account_tag_event (
+    tag_id VARCHAR(128) NOT NULL COMMENT '百度广告位ID',
+    account_id VARCHAR(64) NOT NULL COMMENT '腾讯广告主ID',
+    baidu_act INT NOT NULL COMMENT '百度转化行为',
+    tencent_action_type VARCHAR(128) NOT NULL COMMENT '腾讯回传行为',
+    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),
+    KEY idx_tencent_account_tag_event_account_act (account_id, baidu_act)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='腾讯账户级广告位回传方式配置';
+
+-- ------------------------------------------------------------
 -- 字段补丁(兼容旧表升级)
 -- ------------------------------------------------------------
 -- 如果 req_id 原来是 VARCHAR(128),扩展为 512