yumeng hai 1 mes
pai
achega
dc572695c6

+ 51 - 0
src/main/java/com/adx/tencent/honor/service/HonorTagEventProfileResolver.java

@@ -0,0 +1,51 @@
+package com.adx.tencent.honor.service;
+
+import com.adx.tencent.honor.model.HonorTagEventRecord;
+import com.adx.tencent.honor.store.HonorColdStore;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.data.redis.core.StringRedisTemplate;
+
+public class HonorTagEventProfileResolver {
+    private static final Logger log = LoggerFactory.getLogger(HonorTagEventProfileResolver.class);
+
+    private final StringRedisTemplate redis;
+    private final HonorColdStore coldStore;
+    private final String keyPrefix;
+
+    public HonorTagEventProfileResolver(StringRedisTemplate redis, HonorColdStore coldStore, String keyPrefix) {
+        this.redis = redis;
+        this.coldStore = coldStore;
+        this.keyPrefix = keyPrefix == null ? "" : keyPrefix;
+    }
+
+    public HonorTagEventRecord resolve(String tagId, String profile, int baiduAct) {
+        if (tagId == null || tagId.isBlank() || profile == null || profile.isBlank()) {
+            return null;
+        }
+        try {
+            Integer conversionId = parseInt(redis.opsForValue().get(buildRedisKey(tagId, profile, baiduAct)));
+            if (conversionId != null) {
+                HonorTagEventRecord record = new HonorTagEventRecord();
+                record.setTagId(tagId);
+                record.setCallbackProfile(profile);
+                record.setBaiduAct(baiduAct);
+                record.setConversionId(conversionId);
+                return record;
+            }
+        } catch (Exception e) {
+            log.warn("[HonorTagEventProfileResolver] 读取 Redis 失败, 回退DB查询 | tagId={} | profile={} | act={} | error={}",
+                    tagId, profile, baiduAct, e.getMessage());
+        }
+        return coldStore.getTagEventProfile(tagId, profile, baiduAct);
+    }
+
+    private String buildRedisKey(String tagId, String profile, int baiduAct) {
+        return keyPrefix + "profile:" + tagId + ":" + profile + ":" + baiduAct;
+    }
+
+    private static Integer parseInt(String value) {
+        if (value == null || value.isBlank()) return null;
+        try { return Integer.parseInt(value.trim()); } catch (NumberFormatException e) { return null; }
+    }
+}

+ 64 - 0
src/main/java/com/adx/tencent/honor/service/HonorTagEventProfileSyncService.java

@@ -0,0 +1,64 @@
+package com.adx.tencent.honor.service;
+
+import com.adx.tencent.honor.model.HonorTagEventRecord;
+import com.adx.tencent.honor.store.HonorColdStore;
+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;
+
+public class HonorTagEventProfileSyncService {
+    private static final Logger log = LoggerFactory.getLogger(HonorTagEventProfileSyncService.class);
+
+    private final HonorColdStore coldStore;
+    private final StringRedisTemplate redis;
+    private final String keyPrefix;
+    private final Duration ttl;
+
+    public HonorTagEventProfileSyncService(HonorColdStore coldStore, StringRedisTemplate redis,
+                                           String keyPrefix, Duration ttl) {
+        this.coldStore = coldStore;
+        this.redis = redis;
+        this.keyPrefix = keyPrefix == null ? "" : keyPrefix;
+        this.ttl = ttl;
+    }
+
+    public int syncOnce() {
+        List<HonorTagEventRecord> rows = coldStore.listAllTagEventProfiles();
+        if (rows == null || rows.isEmpty()) {
+            log.info("[HonorTagEventProfileSync] 无数据可同步");
+            return 0;
+        }
+        long ttlMs = (ttl != null && !ttl.isZero()) ? ttl.toMillis() : 0;
+        redis.executePipelined((RedisConnection conn) -> {
+            for (HonorTagEventRecord row : rows) {
+                if (row.getTagId() == null || row.getTagId().isBlank()
+                        || row.getCallbackProfile() == null || row.getCallbackProfile().isBlank()) {
+                    continue;
+                }
+                byte[] key = buildRedisKey(row.getTagId(), row.getCallbackProfile(), row.getBaiduAct())
+                        .getBytes(StandardCharsets.UTF_8);
+                byte[] val = String.valueOf(row.getConversionId()).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("[HonorTagEventProfileSync] 同步完成,共 {} 条荣耀 profile 回传方式写入 Redis", rows.size());
+        return rows.size();
+    }
+
+    private String buildRedisKey(String tagId, String profile, int baiduAct) {
+        return keyPrefix + "profile:" + tagId + ":" + profile + ":" + baiduAct;
+    }
+}

+ 66 - 0
src/main/java/com/adx/tencent/kuaishou/service/KuaishouTagEventProfileResolver.java

@@ -0,0 +1,66 @@
+package com.adx.tencent.kuaishou.service;
+
+import com.adx.tencent.kuaishou.model.KuaishouTagEventRecord;
+import com.adx.tencent.kuaishou.store.KuaishouColdStore;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.data.redis.core.StringRedisTemplate;
+
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+public class KuaishouTagEventProfileResolver {
+    private static final Logger log = LoggerFactory.getLogger(KuaishouTagEventProfileResolver.class);
+
+    private final StringRedisTemplate redis;
+    private final KuaishouColdStore coldStore;
+    private final String keyPrefix;
+
+    public KuaishouTagEventProfileResolver(StringRedisTemplate redis, KuaishouColdStore coldStore, String keyPrefix) {
+        this.redis = redis;
+        this.coldStore = coldStore;
+        this.keyPrefix = keyPrefix == null ? "" : keyPrefix;
+    }
+
+    public List<KuaishouTagEventRecord> resolve(String tagId, String profile, int baiduAct) {
+        if (tagId == null || tagId.isBlank() || profile == null || profile.isBlank()) {
+            return List.of();
+        }
+        try {
+            List<KuaishouTagEventRecord> cached = parse(tagId, profile, baiduAct,
+                    redis.opsForValue().get(buildRedisKey(tagId, profile, baiduAct)));
+            if (!cached.isEmpty()) {
+                return cached;
+            }
+        } catch (Exception e) {
+            log.warn("[KuaishouTagEventProfileResolver] 读取 Redis 失败, 回退DB查询 | tagId={} | profile={} | act={} | error={}",
+                    tagId, profile, baiduAct, e.getMessage());
+        }
+        return coldStore.getTagEventProfiles(tagId, profile, baiduAct);
+    }
+
+    private String buildRedisKey(String tagId, String profile, int baiduAct) {
+        return keyPrefix + "profile:" + tagId + ":" + profile + ":" + baiduAct;
+    }
+
+    private static List<KuaishouTagEventRecord> parse(String tagId, String profile, int baiduAct, String value) {
+        if (value == null || value.isBlank()) return List.of();
+        Set<Integer> eventTypes = new LinkedHashSet<>();
+        for (String part : value.split(",")) {
+            if (part == null || part.isBlank()) continue;
+            try { eventTypes.add(Integer.parseInt(part.trim())); } catch (NumberFormatException ignored) {}
+        }
+        List<KuaishouTagEventRecord> records = new ArrayList<>(eventTypes.size());
+        for (Integer eventType : eventTypes) {
+            KuaishouTagEventRecord record = new KuaishouTagEventRecord();
+            record.setTagId(tagId);
+            record.setCallbackProfile(profile);
+            record.setBaiduAct(baiduAct);
+            record.setEventType(eventType);
+            records.add(record);
+        }
+        return records;
+    }
+}

+ 75 - 0
src/main/java/com/adx/tencent/kuaishou/service/KuaishouTagEventProfileSyncService.java

@@ -0,0 +1,75 @@
+package com.adx.tencent.kuaishou.service;
+
+import com.adx.tencent.kuaishou.model.KuaishouTagEventRecord;
+import com.adx.tencent.kuaishou.store.KuaishouColdStore;
+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.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public class KuaishouTagEventProfileSyncService {
+    private static final Logger log = LoggerFactory.getLogger(KuaishouTagEventProfileSyncService.class);
+
+    private final KuaishouColdStore coldStore;
+    private final StringRedisTemplate redis;
+    private final String keyPrefix;
+    private final Duration ttl;
+
+    public KuaishouTagEventProfileSyncService(KuaishouColdStore coldStore, StringRedisTemplate redis,
+                                              String keyPrefix, Duration ttl) {
+        this.coldStore = coldStore;
+        this.redis = redis;
+        this.keyPrefix = keyPrefix == null ? "" : keyPrefix;
+        this.ttl = ttl;
+    }
+
+    public int syncOnce() {
+        List<KuaishouTagEventRecord> rows = coldStore.listAllTagEventProfiles();
+        if (rows == null || rows.isEmpty()) {
+            log.info("[KuaishouTagEventProfileSync] 无数据可同步");
+            return 0;
+        }
+        Map<String, Set<Integer>> grouped = new LinkedHashMap<>();
+        for (KuaishouTagEventRecord row : rows) {
+            if (row.getTagId() == null || row.getTagId().isBlank()
+                    || row.getCallbackProfile() == null || row.getCallbackProfile().isBlank()) {
+                continue;
+            }
+            grouped.computeIfAbsent(buildRedisKey(row.getTagId(), row.getCallbackProfile(), row.getBaiduAct()),
+                    ignored -> new LinkedHashSet<>()).add(row.getEventType());
+        }
+        long ttlMs = (ttl != null && !ttl.isZero()) ? ttl.toMillis() : 0;
+        redis.executePipelined((RedisConnection conn) -> {
+            for (Map.Entry<String, Set<Integer>> entry : grouped.entrySet()) {
+                byte[] key = entry.getKey().getBytes(StandardCharsets.UTF_8);
+                byte[] val = entry.getValue().stream().map(String::valueOf).collect(Collectors.joining(","))
+                        .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("[KuaishouTagEventProfileSync] 同步完成,共 {} 条快手 profile 回传方式写入 Redis, 聚合后 {} 个键",
+                rows.size(), grouped.size());
+        return rows.size();
+    }
+
+    private String buildRedisKey(String tagId, String profile, int baiduAct) {
+        return keyPrefix + "profile:" + tagId + ":" + profile + ":" + baiduAct;
+    }
+}

+ 51 - 0
src/main/java/com/adx/tencent/oppo/service/OppoTagEventProfileResolver.java

@@ -0,0 +1,51 @@
+package com.adx.tencent.oppo.service;
+
+import com.adx.tencent.oppo.model.OppoTagEventRecord;
+import com.adx.tencent.oppo.store.OppoColdStore;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.data.redis.core.StringRedisTemplate;
+
+public class OppoTagEventProfileResolver {
+    private static final Logger log = LoggerFactory.getLogger(OppoTagEventProfileResolver.class);
+
+    private final StringRedisTemplate redis;
+    private final OppoColdStore coldStore;
+    private final String keyPrefix;
+
+    public OppoTagEventProfileResolver(StringRedisTemplate redis, OppoColdStore coldStore, String keyPrefix) {
+        this.redis = redis;
+        this.coldStore = coldStore;
+        this.keyPrefix = keyPrefix == null ? "" : keyPrefix;
+    }
+
+    public OppoTagEventRecord resolve(String tagId, String profile, int baiduAct) {
+        if (tagId == null || tagId.isBlank() || profile == null || profile.isBlank()) {
+            return null;
+        }
+        try {
+            Integer dataType = parseInt(redis.opsForValue().get(buildRedisKey(tagId, profile, baiduAct)));
+            if (dataType != null) {
+                OppoTagEventRecord record = new OppoTagEventRecord();
+                record.setTagId(tagId);
+                record.setCallbackProfile(profile);
+                record.setBaiduAct(baiduAct);
+                record.setDataType(dataType);
+                return record;
+            }
+        } catch (Exception e) {
+            log.warn("[OppoTagEventProfileResolver] 读取 Redis 失败, 回退DB查询 | tagId={} | profile={} | act={} | error={}",
+                    tagId, profile, baiduAct, e.getMessage());
+        }
+        return coldStore.getTagEventProfile(tagId, profile, baiduAct);
+    }
+
+    private String buildRedisKey(String tagId, String profile, int baiduAct) {
+        return keyPrefix + "profile:" + tagId + ":" + profile + ":" + baiduAct;
+    }
+
+    private static Integer parseInt(String value) {
+        if (value == null || value.isBlank()) return null;
+        try { return Integer.parseInt(value.trim()); } catch (NumberFormatException e) { return null; }
+    }
+}

+ 64 - 0
src/main/java/com/adx/tencent/oppo/service/OppoTagEventProfileSyncService.java

@@ -0,0 +1,64 @@
+package com.adx.tencent.oppo.service;
+
+import com.adx.tencent.oppo.model.OppoTagEventRecord;
+import com.adx.tencent.oppo.store.OppoColdStore;
+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;
+
+public class OppoTagEventProfileSyncService {
+    private static final Logger log = LoggerFactory.getLogger(OppoTagEventProfileSyncService.class);
+
+    private final OppoColdStore coldStore;
+    private final StringRedisTemplate redis;
+    private final String keyPrefix;
+    private final Duration ttl;
+
+    public OppoTagEventProfileSyncService(OppoColdStore coldStore, StringRedisTemplate redis,
+                                          String keyPrefix, Duration ttl) {
+        this.coldStore = coldStore;
+        this.redis = redis;
+        this.keyPrefix = keyPrefix == null ? "" : keyPrefix;
+        this.ttl = ttl;
+    }
+
+    public int syncOnce() {
+        List<OppoTagEventRecord> rows = coldStore.listAllTagEventProfiles();
+        if (rows == null || rows.isEmpty()) {
+            log.info("[OppoTagEventProfileSync] 无数据可同步");
+            return 0;
+        }
+        long ttlMs = (ttl != null && !ttl.isZero()) ? ttl.toMillis() : 0;
+        redis.executePipelined((RedisConnection conn) -> {
+            for (OppoTagEventRecord row : rows) {
+                if (row.getTagId() == null || row.getTagId().isBlank()
+                        || row.getCallbackProfile() == null || row.getCallbackProfile().isBlank()) {
+                    continue;
+                }
+                byte[] key = buildRedisKey(row.getTagId(), row.getCallbackProfile(), row.getBaiduAct())
+                        .getBytes(StandardCharsets.UTF_8);
+                byte[] val = String.valueOf(row.getDataType()).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("[OppoTagEventProfileSync] 同步完成,共 {} 条 OPPO profile 回传方式写入 Redis", rows.size());
+        return rows.size();
+    }
+
+    private String buildRedisKey(String tagId, String profile, int baiduAct) {
+        return keyPrefix + "profile:" + tagId + ":" + profile + ":" + baiduAct;
+    }
+}

+ 69 - 0
src/main/java/com/adx/tencent/tagsync/TagEventProfileResolver.java

@@ -0,0 +1,69 @@
+package com.adx.tencent.tagsync;
+
+import com.adx.tencent.storage.TiDBColdStore;
+import com.adx.tencent.storage.model.TencentTagEventProfileRecord;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.data.redis.core.StringRedisTemplate;
+
+/**
+ * 腾讯 profile 级广告位回传方式解析器。只做精确 profile + act 命中,不使用 act=0 兜底。
+ */
+public class TagEventProfileResolver {
+
+    private static final Logger log = LoggerFactory.getLogger(TagEventProfileResolver.class);
+
+    private final StringRedisTemplate redis;
+    private final TiDBColdStore coldStore;
+    private final String keyPrefix;
+
+    public TagEventProfileResolver(StringRedisTemplate redis, TiDBColdStore coldStore, String keyPrefix) {
+        this.redis = redis;
+        this.coldStore = coldStore;
+        this.keyPrefix = keyPrefix == null ? "" : keyPrefix;
+    }
+
+    public TencentTagEventProfileRecord resolve(String tagId, String profile, int baiduAct) {
+        if (tagId == null || tagId.isBlank() || profile == null || profile.isBlank()) {
+            return null;
+        }
+        try {
+            TencentTagEventProfileRecord cached = parse(tagId, profile, baiduAct,
+                    redis.opsForValue().get(buildRedisKey(tagId, profile, baiduAct)));
+            if (cached != null) {
+                return cached;
+            }
+        } catch (Exception e) {
+            log.warn("[TagEventProfileResolver] 读取 Redis 失败, 回退DB查询 | tagId={} | profile={} | act={} | error={}",
+                    tagId, profile, baiduAct, e.getMessage());
+        }
+        try {
+            return coldStore.getTagEventProfile(tagId, profile, baiduAct);
+        } catch (Exception e) {
+            log.warn("[TagEventProfileResolver] 查询 tencent_tag_event_profile 失败 | tagId={} | profile={} | act={} | error={}",
+                    tagId, profile, baiduAct, e.getMessage());
+            return null;
+        }
+    }
+
+    private String buildRedisKey(String tagId, String profile, int baiduAct) {
+        return keyPrefix + "profile:" + tagId + ":" + profile + ":" + baiduAct;
+    }
+
+    private static TencentTagEventProfileRecord parse(String tagId, String profile, int baiduAct, String value) {
+        if (value == null || value.isBlank()) {
+            return null;
+        }
+        String[] parts = value.split("\t", 2);
+        if (parts.length == 0 || parts[0].isBlank()) {
+            return null;
+        }
+        TencentTagEventProfileRecord record = new TencentTagEventProfileRecord();
+        record.setTagId(tagId);
+        record.setCallbackProfile(profile);
+        record.setBaiduAct(baiduAct);
+        record.setTencentActionType(parts[0]);
+        record.setTencentCustomAction(parts.length > 1 ? parts[1] : null);
+        return record;
+    }
+}

+ 76 - 0
src/main/java/com/adx/tencent/tagsync/TagEventProfileSyncService.java

@@ -0,0 +1,76 @@
+package com.adx.tencent.tagsync;
+
+import com.adx.tencent.storage.TiDBColdStore;
+import com.adx.tencent.storage.model.TencentTagEventProfileRecord;
+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;
+
+/**
+ * 腾讯 profile 级广告位回传方式同步服务。
+ * key = {prefix}profile:{tag_id}:{callback_profile}:{baidu_act}
+ * value = tencent_action_type + tab + tencent_custom_action
+ */
+public class TagEventProfileSyncService {
+
+    private static final Logger log = LoggerFactory.getLogger(TagEventProfileSyncService.class);
+
+    private final TiDBColdStore coldStore;
+    private final StringRedisTemplate redis;
+    private final String keyPrefix;
+    private final Duration ttl;
+
+    public TagEventProfileSyncService(TiDBColdStore coldStore, StringRedisTemplate redis,
+                                      String keyPrefix, Duration ttl) {
+        this.coldStore = coldStore;
+        this.redis = redis;
+        this.keyPrefix = keyPrefix == null ? "" : keyPrefix;
+        this.ttl = ttl;
+    }
+
+    public int syncOnce() {
+        List<TencentTagEventProfileRecord> rows = coldStore.listAllTagEventProfiles();
+        if (rows == null || rows.isEmpty()) {
+            log.info("[TagEventProfileSync] 无数据可同步");
+            return 0;
+        }
+        long ttlMs = (ttl != null && !ttl.isZero()) ? ttl.toMillis() : 0;
+        redis.executePipelined((RedisConnection conn) -> {
+            for (TencentTagEventProfileRecord row : rows) {
+                if (row.getTagId() == null || row.getTagId().isBlank()
+                        || row.getCallbackProfile() == null || row.getCallbackProfile().isBlank()) {
+                    continue;
+                }
+                byte[] key = buildRedisKey(row.getTagId(), row.getCallbackProfile(), row.getBaiduAct())
+                        .getBytes(StandardCharsets.UTF_8);
+                byte[] val = encode(row).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("[TagEventProfileSync] 同步完成,共 {} 条腾讯 profile 回传方式写入 Redis", rows.size());
+        return rows.size();
+    }
+
+    private String buildRedisKey(String tagId, String profile, int baiduAct) {
+        return keyPrefix + "profile:" + tagId + ":" + profile + ":" + baiduAct;
+    }
+
+    static String encode(TencentTagEventProfileRecord row) {
+        String actionType = row.getTencentActionType() == null ? "" : row.getTencentActionType();
+        String customAction = row.getTencentCustomAction() == null ? "" : row.getTencentCustomAction();
+        return actionType + "\t" + customAction;
+    }
+}

+ 65 - 0
src/main/java/com/adx/tencent/vivo/service/VivoTagEventProfileResolver.java

@@ -0,0 +1,65 @@
+package com.adx.tencent.vivo.service;
+
+import com.adx.tencent.vivo.model.VivoTagEventRecord;
+import com.adx.tencent.vivo.store.VivoColdStore;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.data.redis.core.StringRedisTemplate;
+
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+public class VivoTagEventProfileResolver {
+    private static final Logger log = LoggerFactory.getLogger(VivoTagEventProfileResolver.class);
+
+    private final StringRedisTemplate redis;
+    private final VivoColdStore coldStore;
+    private final String keyPrefix;
+
+    public VivoTagEventProfileResolver(StringRedisTemplate redis, VivoColdStore coldStore, String keyPrefix) {
+        this.redis = redis;
+        this.coldStore = coldStore;
+        this.keyPrefix = keyPrefix == null ? "" : keyPrefix;
+    }
+
+    public List<VivoTagEventRecord> resolve(String tagId, String profile, int baiduAct) {
+        if (tagId == null || tagId.isBlank() || profile == null || profile.isBlank()) {
+            return List.of();
+        }
+        try {
+            List<VivoTagEventRecord> cached = parse(tagId, profile, baiduAct,
+                    redis.opsForValue().get(buildRedisKey(tagId, profile, baiduAct)));
+            if (!cached.isEmpty()) {
+                return cached;
+            }
+        } catch (Exception e) {
+            log.warn("[VivoTagEventProfileResolver] 读取 Redis 失败, 回退DB查询 | tagId={} | profile={} | act={} | error={}",
+                    tagId, profile, baiduAct, e.getMessage());
+        }
+        return coldStore.getTagEventProfiles(tagId, profile, baiduAct);
+    }
+
+    private String buildRedisKey(String tagId, String profile, int baiduAct) {
+        return keyPrefix + "profile:" + tagId + ":" + profile + ":" + baiduAct;
+    }
+
+    private static List<VivoTagEventRecord> parse(String tagId, String profile, int baiduAct, String value) {
+        if (value == null || value.isBlank()) return List.of();
+        Set<String> eventTypes = new LinkedHashSet<>();
+        for (String part : value.split(",")) {
+            if (part != null && !part.isBlank()) eventTypes.add(part.trim());
+        }
+        List<VivoTagEventRecord> records = new ArrayList<>(eventTypes.size());
+        for (String eventType : eventTypes) {
+            VivoTagEventRecord record = new VivoTagEventRecord();
+            record.setTagId(tagId);
+            record.setCallbackProfile(profile);
+            record.setBaiduAct(baiduAct);
+            record.setEventType(eventType);
+            records.add(record);
+        }
+        return records;
+    }
+}

+ 76 - 0
src/main/java/com/adx/tencent/vivo/service/VivoTagEventProfileSyncService.java

@@ -0,0 +1,76 @@
+package com.adx.tencent.vivo.service;
+
+import com.adx.tencent.vivo.model.VivoTagEventRecord;
+import com.adx.tencent.vivo.store.VivoColdStore;
+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.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public class VivoTagEventProfileSyncService {
+    private static final Logger log = LoggerFactory.getLogger(VivoTagEventProfileSyncService.class);
+
+    private final VivoColdStore coldStore;
+    private final StringRedisTemplate redis;
+    private final String keyPrefix;
+    private final Duration ttl;
+
+    public VivoTagEventProfileSyncService(VivoColdStore coldStore, StringRedisTemplate redis,
+                                          String keyPrefix, Duration ttl) {
+        this.coldStore = coldStore;
+        this.redis = redis;
+        this.keyPrefix = keyPrefix == null ? "" : keyPrefix;
+        this.ttl = ttl;
+    }
+
+    public int syncOnce() {
+        List<VivoTagEventRecord> rows = coldStore.listAllTagEventProfiles();
+        if (rows == null || rows.isEmpty()) {
+            log.info("[VivoTagEventProfileSync] 无数据可同步");
+            return 0;
+        }
+        Map<String, Set<String>> grouped = new LinkedHashMap<>();
+        for (VivoTagEventRecord row : rows) {
+            if (row.getTagId() == null || row.getTagId().isBlank()
+                    || row.getCallbackProfile() == null || row.getCallbackProfile().isBlank()
+                    || row.getEventType() == null || row.getEventType().isBlank()) {
+                continue;
+            }
+            grouped.computeIfAbsent(buildRedisKey(row.getTagId(), row.getCallbackProfile(), row.getBaiduAct()),
+                    ignored -> new LinkedHashSet<>()).add(row.getEventType().trim());
+        }
+        long ttlMs = (ttl != null && !ttl.isZero()) ? ttl.toMillis() : 0;
+        redis.executePipelined((RedisConnection conn) -> {
+            for (Map.Entry<String, Set<String>> entry : grouped.entrySet()) {
+                byte[] key = entry.getKey().getBytes(StandardCharsets.UTF_8);
+                byte[] val = entry.getValue().stream().collect(Collectors.joining(","))
+                        .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("[VivoTagEventProfileSync] 同步完成,共 {} 条 vivo profile 回传方式写入 Redis, 聚合后 {} 个键",
+                rows.size(), grouped.size());
+        return rows.size();
+    }
+
+    private String buildRedisKey(String tagId, String profile, int baiduAct) {
+        return keyPrefix + "profile:" + tagId + ":" + profile + ":" + baiduAct;
+    }
+}