yumeng il y a 3 semaines
Parent
commit
218c58db99

+ 11 - 2
src/main/java/com/adx/tencent/AppConfiguration.java

@@ -9,6 +9,7 @@ import com.adx.tencent.conversionsync.ConversionSyncService;
 import com.adx.tencent.conversionsync.RetryService;
 import com.adx.tencent.httpapi.MediaPlacement;
 import com.adx.tencent.leader.LeaderElection;
+import com.adx.tencent.tagsync.TagEventResolver;
 import com.adx.tencent.tagsync.TagEventSyncService;
 import com.adx.tencent.tencent.TencentClient;
 import com.adx.tencent.storage.RedisHotStore;
@@ -182,12 +183,20 @@ public class AppConfiguration {
     }
 
     @Bean
+    public TagEventResolver tagEventResolver(@Nullable TiDBColdStore coldStore,
+                                             StringRedisTemplate redisTemplate) {
+        if (coldStore == null) return null;
+        return new TagEventResolver(redisTemplate, coldStore, props.getTagEventSyncRedisPrefix());
+    }
+
+    @Bean
     public ConversionSyncService conversionSyncService(ConversionClient baiduClient,
                                                          RedisHotStore hotStore,
                                                          TencentClient tencentClient,
-                                                         @Nullable TiDBColdStore coldStore) {
+                                                         @Nullable TiDBColdStore coldStore,
+                                                         @Nullable TagEventResolver tagEventResolver) {
         if (coldStore == null) return null;
-        return new ConversionSyncService(baiduClient, hotStore, tencentClient, coldStore);
+        return new ConversionSyncService(baiduClient, hotStore, tencentClient, coldStore, tagEventResolver);
     }
 
     @Bean

+ 15 - 2
src/main/java/com/adx/tencent/conversionsync/ConversionSyncService.java

@@ -8,6 +8,7 @@ import com.adx.tencent.tencent.TencentClient;
 import com.adx.tencent.storage.RedisHotStore;
 import com.adx.tencent.storage.TiDBColdStore;
 import com.adx.tencent.storage.model.*;
+import com.adx.tencent.tagsync.TagEventResolver;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -30,14 +31,17 @@ public class ConversionSyncService {
     private final RedisHotStore hotStore;
     private final TencentClient tencentClient;
     private final TiDBColdStore coldStore;
+    private final TagEventResolver tagEventResolver;
     private final ExecutorService executor;
 
     public ConversionSyncService(ConversionClient baiduClient, RedisHotStore hotStore,
-                                  TencentClient tencentClient, TiDBColdStore coldStore) {
+                                  TencentClient tencentClient, TiDBColdStore coldStore,
+                                  TagEventResolver tagEventResolver) {
         this.baiduClient = baiduClient;
         this.hotStore = hotStore;
         this.tencentClient = tencentClient;
         this.coldStore = coldStore;
+        this.tagEventResolver = tagEventResolver;
         this.executor = Executors.newFixedThreadPool(CONCURRENCY,
                 r -> { Thread t = new Thread(r, "conv-sync-worker"); t.setDaemon(true); return t; });
     }
@@ -133,7 +137,16 @@ public class ConversionSyncService {
 
         // 发送到腾讯
         String platform = resolvePlatform(bid);
-        MediaCallbackRecord callbackRecord = tencentClient.sendConversionEvent(bid, payment, platform);
+        // 根据广告位(tag_id)解析回传方式作为 action_type:Redis 优先,DB 兜底
+        String actionTypeOverride = null;
+        if (tagEventResolver != null) {
+            actionTypeOverride = tagEventResolver.resolveEventType(bid.getTagId());
+        }
+        if (actionTypeOverride == null || actionTypeOverride.isBlank()) {
+            log.warn("[ConversionSync] 广告位回传方式未配置, 回退act-map | qk={} | tagId={} | act={}",
+                    payment.getQk(), bid.getTagId(), payment.getAct());
+        }
+        MediaCallbackRecord callbackRecord = tencentClient.sendConversionEvent(bid, payment, platform, actionTypeOverride);
         callbackRecord.setDedupeKey(callbackKey);
 
         if (callbackRecord.getStatus() == 0 && callbackRecord.getErrorMessage() != null) {

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

@@ -256,6 +256,17 @@ public class TiDBColdStore {
         }
     }
 
+    /**
+     * 根据广告位ID查询回传方式配置(DB 兜底查询)。
+     */
+    public TagEventRecord getTagEvent(String tagId) {
+        if (tagId == null || tagId.isBlank()) return null;
+        try (SqlSession session = sqlSessionFactory.openSession(true)) {
+            TagEventMapper mapper = session.getMapper(TagEventMapper.class);
+            return mapper.selectByTagId(tagId);
+        }
+    }
+
     // ─── 工具方法 ─────────────────────────────────────────────────────────────
 
     private String toJson(Object value) {

+ 6 - 0
src/main/java/com/adx/tencent/storage/mapper/TagEventMapper.java

@@ -2,6 +2,7 @@ package com.adx.tencent.storage.mapper;
 
 import com.adx.tencent.storage.model.TagEventRecord;
 import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
 
 import java.util.List;
 
@@ -12,4 +13,9 @@ public interface TagEventMapper {
      * 查询全部广告位回传方式配置。
      */
     List<TagEventRecord> selectAll();
+
+    /**
+     * 根据广告位ID查询回传方式配置。
+     */
+    TagEventRecord selectByTagId(@Param("tagId") String tagId);
 }

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

@@ -81,6 +81,7 @@ public class BidRecord {
         slim.media = this.media;
         slim.mediaTraceId = this.mediaTraceId;
         slim.platform = this.platform;
+        slim.tagId = this.tagId;
         slim.mediaParams = this.mediaParams; // 已经是瘦身后的设备标识
         return slim;
     }
@@ -97,6 +98,7 @@ public class BidRecord {
         hot.media = this.media;
         hot.mediaTraceId = this.mediaTraceId;
         hot.platform = this.platform;
+        hot.tagId = this.tagId;
         hot.adId = this.adId;
         hot.accountId = this.accountId;
         hot.clickUrls = this.clickUrls;

+ 63 - 0
src/main/java/com/adx/tencent/tagsync/TagEventResolver.java

@@ -0,0 +1,63 @@
+package com.adx.tencent.tagsync;
+
+import com.adx.tencent.storage.TiDBColdStore;
+import com.adx.tencent.storage.model.TagEventRecord;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.data.redis.core.StringRedisTemplate;
+
+/**
+ * 广告位回传方式解析器。
+ * 根据广告位ID(tag_id)解析回传方式(event_type):
+ *   1. 优先从 Redis 读取(key = {prefix}{tagId}),命中直接返回;
+ *   2. Redis 未命中时兜底查询 tencent_tag_event 表。
+ * Redis key 前缀必须与 {@link TagEventSyncService} 保持一致。
+ */
+public class TagEventResolver {
+
+    private static final Logger log = LoggerFactory.getLogger(TagEventResolver.class);
+
+    private final StringRedisTemplate redis;
+    private final TiDBColdStore coldStore;
+    private final String keyPrefix;
+
+    public TagEventResolver(StringRedisTemplate redis, TiDBColdStore coldStore, String keyPrefix) {
+        this.redis = redis;
+        this.coldStore = coldStore;
+        this.keyPrefix = keyPrefix == null ? "" : keyPrefix;
+    }
+
+    /**
+     * 解析广告位回传方式。
+     *
+     * @param tagId 广告位ID
+     * @return 回传方式;未配置时返回 null
+     */
+    public String resolveEventType(String tagId) {
+        if (tagId == null || tagId.isBlank()) return null;
+
+        // 1. Redis 优先
+        try {
+            String cached = redis.opsForValue().get(keyPrefix + tagId);
+            if (cached != null && !cached.isBlank()) {
+                return cached;
+            }
+        } catch (Exception e) {
+            log.warn("[TagEventResolver] 读取 Redis 失败, 回退DB查询 | tagId={} | error={}",
+                    tagId, e.getMessage());
+        }
+
+        // 2. DB 兜底
+        try {
+            TagEventRecord record = coldStore.getTagEvent(tagId);
+            if (record != null && record.getEventType() != null && !record.getEventType().isBlank()) {
+                return record.getEventType();
+            }
+        } catch (Exception e) {
+            log.warn("[TagEventResolver] 查询 tencent_tag_event 失败 | tagId={} | error={}",
+                    tagId, e.getMessage());
+        }
+
+        return null;
+    }
+}

+ 10 - 5
src/main/java/com/adx/tencent/tencent/TencentClient.java

@@ -64,14 +64,19 @@ public class TencentClient {
     /**
      * 通过 callback URL 回传转化事件到腾讯。
      *
-     * @param bid      匹配到的竞价记录(含 callback URL)
-     * @param payment  百度转化支付信息
-     * @param platform 平台标识("android" 或 "ios")
+     * @param bid                匹配到的竞价记录(含 callback URL)
+     * @param payment            百度转化支付信息
+     * @param platform           平台标识("android" 或 "ios")
+     * @param actionTypeOverride 广告位维度的回传方式;非空时优先作为 action_type,
+     *                           为空时回退到 act-map 映射
      * @return MediaCallbackRecord 记录回传结果(含请求体、响应、状态)
      */
-    public MediaCallbackRecord sendConversionEvent(BidRecord bid, PaymentInfo payment, String platform) {
+    public MediaCallbackRecord sendConversionEvent(BidRecord bid, PaymentInfo payment, String platform,
+                                                   String actionTypeOverride) {
         long eventTimeMs = Instant.now().toEpochMilli();
-        String actionType = conversionActionType(payment.getAct());
+        String actionType = (actionTypeOverride != null && !actionTypeOverride.isBlank())
+                ? actionTypeOverride
+                : conversionActionType(payment.getAct());
 
         MediaCallbackRecord record = new MediaCallbackRecord();
         record.setMedia("tencent");

+ 4 - 0
src/main/resources/mapper/TagEventMapper.xml

@@ -10,4 +10,8 @@
     <select id="selectAll" resultMap="tagEventResultMap">
         SELECT tag_id, event_type FROM tencent_tag_event
     </select>
+
+    <select id="selectByTagId" resultMap="tagEventResultMap">
+        SELECT tag_id, event_type FROM tencent_tag_event WHERE tag_id = #{tagId}
+    </select>
 </mapper>