فهرست منبع

外部渠道素材关联

wangyunpeng 1 ماه پیش
والد
کامیت
bd7143eda8
19فایلهای تغییر یافته به همراه2925 افزوده شده و 4 حذف شده
  1. 17 0
      api-module/src/main/java/com/tzld/piaoquan/api/common/enums/contentplatform/ChannelType.java
  2. 34 0
      api-module/src/main/java/com/tzld/piaoquan/api/common/enums/contentplatform/ExternalChannelStatusEnum.java
  3. 53 0
      api-module/src/main/java/com/tzld/piaoquan/api/component/AdApiService.java
  4. 40 2
      api-module/src/main/java/com/tzld/piaoquan/api/component/AigcApiService.java
  5. 72 0
      api-module/src/main/java/com/tzld/piaoquan/api/component/RecommendApiService.java
  6. 39 0
      api-module/src/main/java/com/tzld/piaoquan/api/controller/ExternalController.java
  7. 30 0
      api-module/src/main/java/com/tzld/piaoquan/api/dao/mapper/contentplatform/ExternalChannelMapper.java
  8. 32 0
      api-module/src/main/java/com/tzld/piaoquan/api/dao/mapper/contentplatform/ExternalChannelMapperExt.java
  9. 787 0
      api-module/src/main/java/com/tzld/piaoquan/api/job/ExternalChannelProcessJob.java
  10. 15 0
      api-module/src/main/java/com/tzld/piaoquan/api/model/bo/AdPutTencentCreative.java
  11. 14 0
      api-module/src/main/java/com/tzld/piaoquan/api/model/bo/AdPutTencentCreativeAnalysis.java
  12. 158 0
      api-module/src/main/java/com/tzld/piaoquan/api/model/po/contentplatform/ExternalChannel.java
  13. 1072 0
      api-module/src/main/java/com/tzld/piaoquan/api/model/po/contentplatform/ExternalChannelExample.java
  14. 26 0
      api-module/src/main/java/com/tzld/piaoquan/api/model/vo/ExternalChannelVO.java
  15. 9 0
      api-module/src/main/java/com/tzld/piaoquan/api/service/ExternalService.java
  16. 135 0
      api-module/src/main/java/com/tzld/piaoquan/api/service/impl/ExternalServiceImpl.java
  17. 341 0
      api-module/src/main/resources/mapper/contentplatform/ExternalChannelMapper.xml
  18. 48 0
      api-module/src/main/resources/mapper/contentplatform/ext/ExternalChannelMapperExt.xml
  19. 3 2
      api-module/src/main/resources/mybatis-generator-config.xml

+ 17 - 0
api-module/src/main/java/com/tzld/piaoquan/api/common/enums/contentplatform/ChannelType.java

@@ -0,0 +1,17 @@
+package com.tzld.piaoquan.api.common.enums.contentplatform;
+
+/**
+ * 渠道类型枚举
+ */
+public enum ChannelType {
+    MINIAPP_TOULIU,      // 1. 小程序投流-稳定
+    GZH_COOPERATE_JIZHUAN, // 2. 公众号合作-即转-稳定
+    QW_COOPERATE,        // 3. 群/企微合作-稳定
+    GZH_TOULIU,          // 4. 公众号投流-稳定
+    FWH_COOPERATE_DAILY, // 5. 服务号合作-Daily-自选
+    FWH_TOULIU_DAILY,    // 6a. 服务号投流-Daily
+    FWH_TOULIU_REPLY,    // 6b. 服务号投流-即转
+    GZH_COOPERATE_DAILY, // 7. 公众号合作-Daily-自选
+    GZH_BUY_ACCOUNT,     // 8. 公众号买号
+    GZH_OPERATION_DAILY, // 9. 公众号代运营-Daily-系统
+}

+ 34 - 0
api-module/src/main/java/com/tzld/piaoquan/api/common/enums/contentplatform/ExternalChannelStatusEnum.java

@@ -0,0 +1,34 @@
+package com.tzld.piaoquan.api.common.enums.contentplatform;
+
+import lombok.Getter;
+
+import java.util.Objects;
+
+@Getter
+public enum ExternalChannelStatusEnum {
+
+    PENDING(0, "待处理"),
+    PROCESSED(1, "已处理"),
+    FAILED(2, "处理失败"),
+    UNKNOWN_CHANNEL(3, "未知渠道"),
+
+    other(999, "其他");
+
+    private final Integer val;
+    private final String description;
+
+    ExternalChannelStatusEnum(Integer val, String description) {
+        this.val = val;
+        this.description = description;
+    }
+
+    public static ExternalChannelStatusEnum from(Integer val) {
+        for (ExternalChannelStatusEnum statusEnum : ExternalChannelStatusEnum.values()) {
+            if (Objects.equals(statusEnum.getVal(), val)) {
+                return statusEnum;
+            }
+        }
+        return other;
+    }
+
+}

+ 53 - 0
api-module/src/main/java/com/tzld/piaoquan/api/component/AdApiService.java

@@ -4,6 +4,8 @@ import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
 import com.tzld.piaoquan.api.model.bo.AdPutCreativeComponentCostData;
 import com.tzld.piaoquan.api.model.bo.AdPutFlowRecordTencent;
+import com.tzld.piaoquan.api.model.bo.AdPutTencentCreative;
+import com.tzld.piaoquan.api.model.bo.AdPutTencentCreativeAnalysis;
 import com.tzld.piaoquan.growth.common.component.HttpPoolClient;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -73,4 +75,55 @@ public class AdApiService {
         }
         return new ArrayList<>();
     }
+
+    /**
+     * 根据rootSourceId获取创意信息
+     */
+    public AdPutTencentCreative getCreativeByRootSourceId(String rootSourceId) {
+        try {
+            String url = adApiHost + "/put/tencent/getCreativeByRootSourceId?rootSourceId=" + rootSourceId;
+            String response = httpPoolClient.get(url);
+            JSONObject res = JSONObject.parseObject(response);
+            if (res.getInteger("code") == 0 && res.getJSONObject("data") != null) {
+                return res.getJSONObject("data").toJavaObject(AdPutTencentCreative.class);
+            }
+        } catch (Exception e) {
+            log.error("获取创意信息失败, rootSourceId={}", rootSourceId, e);
+        }
+        return null;
+    }
+
+    /**
+     * 根据创意ID获取素材分析信息
+     */
+    public AdPutTencentCreativeAnalysis getCreativeAnalysis(Long creativeId) {
+        try {
+            String url = adApiHost + "/put/tencent/getCreativeAnalysis?creativeId=" + creativeId;
+            String response = httpPoolClient.get(url);
+            JSONObject res = JSONObject.parseObject(response);
+            if (res.getInteger("code") == 0 && res.getJSONObject("data") != null) {
+                return res.getJSONObject("data").toJavaObject(AdPutTencentCreativeAnalysis.class);
+            }
+        } catch (Exception e) {
+            log.error("获取素材分析信息失败, creativeId={}", creativeId, e);
+        }
+        return null;
+    }
+
+    /**
+     * 根据创意ID获取人群包ID
+     */
+    public List<Long> getPackageIdByAdId(Long adId) {
+        try {
+            String url = adApiHost + "/put/tencent/getPackageIdByAdId?adId=" + adId;
+            String response = httpPoolClient.get(url);
+            JSONObject res = JSONObject.parseObject(response);
+            if (res.getInteger("code") == 0 && res.getJSONObject("data") != null) {
+                return res.getJSONObject("data").getJSONArray("packageId").toJavaList(Long.class);
+            }
+        } catch (Exception e) {
+            log.error("获取人群包信息失败, adId={}", adId, e);
+        }
+        return null;
+    }
 }

+ 40 - 2
api-module/src/main/java/com/tzld/piaoquan/api/component/AigcApiService.java

@@ -26,6 +26,9 @@ public class AigcApiService {
     @Value("${aigc.api.host:http://aigc-testapi.cybertogether.net/aigc}")
     private String aigcApiHost;
 
+    @Value("${aigc.prod.api.host:http://aigc-api.cybertogether.net/aigc}")
+    private String aigcProdApiHost;
+
     @Value("${aigc.api.token:9ebfcb397e954c41986971f183eb1707}")
     private String aigcApiToken;
 
@@ -299,7 +302,7 @@ public class AigcApiService {
         if (StringUtils.isEmpty(model) || StringUtils.isEmpty(prompt)) {
             return null;
         }
-        String url = "http://aigc-api.cybertogether.net/aigc" + "/dev/test/gpt";
+        String url = aigcProdApiHost + "/dev/test/gpt";
         JSONObject params = new JSONObject();
         params.put("model", model);
         params.put("prompt", prompt);
@@ -324,7 +327,7 @@ public class AigcApiService {
         if (StringUtils.isEmpty(model) || StringUtils.isEmpty(prompt)) {
             return null;
         }
-        String url = "http://aigc-api.cybertogether.net/aigc" + "/infrastructure/gemini/requestWithMedia?model=" + model;
+        String url = aigcProdApiHost + "/infrastructure/gemini/requestWithMedia?model=" + model;
         JSONObject params = new JSONObject();
         params.put("prompt", prompt);
         if (CollectionUtils.isNotEmpty(imageList)) {
@@ -339,4 +342,39 @@ public class AigcApiService {
         }
         return null;
     }
+
+
+    public String getCreativeIdByRootSourceIdAndGhId(String rootSourceId, String ghId) {
+        try {
+            String url = aigcApiHost + "/publish/content/replyMsg/getCreativeIdByRootSourceIdAndGhId?ghId=" + ghId + "&rootSourceId=" + rootSourceId;
+            String response = httpPoolClient.get(url);
+            JSONObject res = JSONObject.parseObject(response);
+            if (res.getInteger("code") == 0 && res.getJSONObject("data") != null) {
+                return res.getString("data");
+            }
+        } catch (Exception e) {
+            log.error("获取创意ID失败, rootSourceId={}, ghId={}", rootSourceId, ghId, e);
+        }
+        return null;
+    }
+
+    /**
+     * 根据ghId获取账号分组来源名称
+     * 用于判断公众号是"买号"还是"代运营"
+     * @param ghId 公众号ID
+     * @return 账号分组来源名称,如"公众号买号"、"公众号代运营"等,失败返回null
+     */
+    public String getAccountGroupSourceName(String ghId) {
+        try {
+            String url = "http://aigc-api.cybertogether.net/aigc/dataStatGroup/getAccountGroupSourceName?ghId=" + ghId;
+            String response = httpPoolClient.get(url);
+            JSONObject res = JSONObject.parseObject(response);
+            if (res != null && res.getInteger("code") == 0) {
+                return res.getString("data");
+            }
+        } catch (Exception e) {
+            log.error("获取账号分组来源名称失败, ghId={}", ghId, e);
+        }
+        return null;
+    }
 }

+ 72 - 0
api-module/src/main/java/com/tzld/piaoquan/api/component/RecommendApiService.java

@@ -0,0 +1,72 @@
+package com.tzld.piaoquan.api.component;
+
+import com.alibaba.fastjson.JSONObject;
+import com.tzld.piaoquan.growth.common.component.HttpPoolClient;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+/**
+ * 推荐API服务
+ */
+@Component
+@Slf4j
+public class RecommendApiService {
+
+    @Autowired
+    private HttpPoolClient httpPoolClient;
+
+    @Value("${recommend.api.host:http://101.37.174.139:80}")
+    private String recommendApiHost;
+
+    /**
+     * 根据rootSourceId获取文章信息
+     */
+    public JSONObject getArticleByRootSourceId(String rootSourceId) {
+        try {
+            String url = recommendApiHost + "/article/getArticleByRootSourceId?rootSourceId=" + rootSourceId;
+            String response = httpPoolClient.get(url);
+            JSONObject res = JSONObject.parseObject(response);
+            if (res.getInteger("code") == 0 && res.getJSONObject("data") != null) {
+                return res.getJSONObject("data");
+            }
+        } catch (Exception e) {
+            log.error("获取文章信息失败, rootSourceId={}", rootSourceId, e);
+        }
+        return null;
+    }
+    /**
+     * 根据rootSourceId获取合作方文章信息
+     */
+    public String getCooperateArticleIdByRootSourceId(String rootSourceId) {
+        try {
+            String url = recommendApiHost + "/article/cooperate/getArticleIdByRootSourceId?rootSourceId=" + rootSourceId;
+            String response = httpPoolClient.get(url);
+            JSONObject res = JSONObject.parseObject(response);
+            if (res.getInteger("code") == 0 && res.getJSONObject("data") != null) {
+                return res.getString("data");
+            }
+        } catch (Exception e) {
+            log.error("获取文章信息失败, rootSourceId={}", rootSourceId, e);
+        }
+        return null;
+    }
+
+    /**
+     * 检查rootSourceId是否存在
+     */
+    public Boolean checkExistRootSourceId(String rootSourceId) {
+        try {
+            String url = recommendApiHost + "/api/checkExistRootSourceId?rootSourceId=" + rootSourceId;
+            String response = httpPoolClient.get(url);
+            JSONObject res = JSONObject.parseObject(response);
+            if (res != null && res.getInteger("code") == 0) {
+                return res.getBoolean("data");
+            }
+        } catch (Exception e) {
+            log.error("检查rootSourceId是否存在失败, rootSourceId={}", rootSourceId, e);
+        }
+        return false;
+    }
+}

+ 39 - 0
api-module/src/main/java/com/tzld/piaoquan/api/controller/ExternalController.java

@@ -0,0 +1,39 @@
+package com.tzld.piaoquan.api.controller;
+
+import com.tzld.piaoquan.api.job.ExternalChannelProcessJob;
+import com.tzld.piaoquan.api.model.vo.ExternalChannelVO;
+import com.tzld.piaoquan.api.service.ExternalService;
+import com.tzld.piaoquan.growth.common.common.base.CommonResponse;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.ibatis.jdbc.Null;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+@Slf4j
+@RestController
+@RequestMapping("/external")
+public class ExternalController {
+
+    @Autowired
+    private ExternalService service;
+    @Autowired
+    private ExternalChannelProcessJob job;
+
+    @ApiOperation(value = "获取外部渠道信息")
+    @GetMapping("/getChannelByRootSourceId")
+    public CommonResponse<ExternalChannelVO> getChannelByRootSourceId(@RequestParam String rootSourceId) {
+        return CommonResponse.success(service.getChannelByRootSourceId(rootSourceId));
+    }
+
+    @ApiOperation(value = "处理外部渠道数据")
+    @GetMapping("/job/processExternalChannel")
+    public CommonResponse<Null> processExternalChannel() {
+        job.processExternalChannel(null);
+        return CommonResponse.success();
+    }
+
+}

+ 30 - 0
api-module/src/main/java/com/tzld/piaoquan/api/dao/mapper/contentplatform/ExternalChannelMapper.java

@@ -0,0 +1,30 @@
+package com.tzld.piaoquan.api.dao.mapper.contentplatform;
+
+import com.tzld.piaoquan.api.model.po.contentplatform.ExternalChannel;
+import com.tzld.piaoquan.api.model.po.contentplatform.ExternalChannelExample;
+import java.util.List;
+import org.apache.ibatis.annotations.Param;
+
+public interface ExternalChannelMapper {
+    long countByExample(ExternalChannelExample example);
+
+    int deleteByExample(ExternalChannelExample example);
+
+    int deleteByPrimaryKey(Long id);
+
+    int insert(ExternalChannel record);
+
+    int insertSelective(ExternalChannel record);
+
+    List<ExternalChannel> selectByExample(ExternalChannelExample example);
+
+    ExternalChannel selectByPrimaryKey(Long id);
+
+    int updateByExampleSelective(@Param("record") ExternalChannel record, @Param("example") ExternalChannelExample example);
+
+    int updateByExample(@Param("record") ExternalChannel record, @Param("example") ExternalChannelExample example);
+
+    int updateByPrimaryKeySelective(ExternalChannel record);
+
+    int updateByPrimaryKey(ExternalChannel record);
+}

+ 32 - 0
api-module/src/main/java/com/tzld/piaoquan/api/dao/mapper/contentplatform/ExternalChannelMapperExt.java

@@ -0,0 +1,32 @@
+package com.tzld.piaoquan.api.dao.mapper.contentplatform;
+
+import com.tzld.piaoquan.api.model.po.contentplatform.ExternalChannel;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * ExternalChannel扩展Mapper
+ */
+@Mapper
+public interface ExternalChannelMapperExt {
+
+    /**
+     * 查询待处理的记录列表(status=0)
+     * 仅查询创建时间在指定时间之后的记录
+     * @param limit 查询条数限制
+     * @param startTime 创建时间起始值
+     * @return 待处理记录列表
+     */
+    List<ExternalChannel> selectPendingList(@Param("limit") int limit, @Param("startTime") Date startTime);
+
+    /**
+     * 将创建时间早于指定时间的待处理记录标记为失败
+     * 用于清理长时间未处理完成的记录
+     * @param timeoutTime 超时时间阈值
+     * @return 更新的记录数
+     */
+    int markFailedForTimeoutRecords(@Param("timeoutTime") Date timeoutTime);
+}

+ 787 - 0
api-module/src/main/java/com/tzld/piaoquan/api/job/ExternalChannelProcessJob.java

@@ -0,0 +1,787 @@
+package com.tzld.piaoquan.api.job;
+
+import com.alibaba.fastjson.JSONObject;
+import com.tzld.piaoquan.api.common.enums.contentplatform.ChannelType;
+import com.tzld.piaoquan.api.common.enums.contentplatform.ExternalChannelStatusEnum;
+import com.tzld.piaoquan.api.component.AdApiService;
+import com.tzld.piaoquan.api.component.AigcApiService;
+import com.tzld.piaoquan.api.component.RecommendApiService;
+import com.tzld.piaoquan.api.dao.mapper.contentplatform.*;
+import com.tzld.piaoquan.api.model.bo.AdPutTencentCreative;
+import com.tzld.piaoquan.api.model.bo.AdPutTencentCreativeAnalysis;
+import com.tzld.piaoquan.api.model.po.contentplatform.*;
+import com.tzld.piaoquan.api.service.CgiReplyService;
+import com.tzld.piaoquan.api.service.contentplatform.ContentPlatformPlanService;
+import com.tzld.piaoquan.growth.common.dao.mapper.CgiReplyBucketDataMapper;
+import com.tzld.piaoquan.growth.common.model.po.CgiReplyBucketData;
+import com.tzld.piaoquan.growth.common.model.po.CgiReplyBucketDataExample;
+import com.tzld.piaoquan.growth.common.utils.RedisUtils;
+import com.xxl.job.core.biz.model.ReturnT;
+import com.xxl.job.core.handler.annotation.XxlJob;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 外部渠道处理定时任务
+ * 处理 external_channel 表中 status=0(待处理)的记录
+ * 根据 rootSourceId 判断渠道类型,获取合作方及其他内容
+ * 仅处理创建时间在 DEFAULT_MAX_RETRY_DAYS 天内的记录
+ */
+@Slf4j
+@Component
+public class ExternalChannelProcessJob {
+
+    @Autowired
+    private ExternalChannelMapper externalChannelMapper;
+
+    @Autowired
+    private ExternalChannelMapperExt externalChannelMapperExt;
+
+    @Autowired
+    private ContentPlatformGzhAccountMapper gzhAccountMapper;
+
+    @Autowired
+    private ContentPlatformQwPlanMapper qwPlanMapper;
+
+    @Autowired
+    private ContentPlatformGzhPlanVideoMapper gzhPlanVideoMapper;
+
+    @Autowired
+    private ContentPlatformGzhPlanMapper gzhPlanMapper;
+
+    @Autowired
+    private CgiReplyBucketDataMapper cgiReplyBucketDataMapper;
+
+    @Autowired
+    private CgiReplyService cgiReplyService;
+
+    @Autowired
+    private ContentPlatformPlanService contentPlatformPlanService;
+
+    @Autowired
+    private AdApiService adApiService;
+
+    @Autowired
+    private AigcApiService aigcApiService;
+
+    @Autowired
+    private RecommendApiService recommendApiService;
+
+    @Autowired
+    private RedisUtils redisUtils;
+
+    // 渠道类型前缀定义
+    private static final String PREFIX_TOULIU_TENCENT = "touliu_tencent_";
+    private static final String PREFIX_DYYJS = "dyyjs_";
+    private static final String PREFIX_DYYQW = "dyyqw_";
+    private static final String PREFIX_TOULIU_TENCENTWBqw = "touliu_tencentwbqw_";
+    private static final String PREFIX_TOULIU_TENCENTGZH = "touliu_tencentgzh_";
+    private static final String PREFIX_TOULIU_TENCENTGZHARTICLE = "touliu_tencentGzhArticle_";
+    private static final String PREFIX_GZHTOULIU_ARTICLES = "GzhTouLiu_Articles_gh";
+    private static final String PREFIX_FWHHZDYy = "fwhhzdyy_";
+    private static final String PREFIX_FWHDYY = "fwhdyy_";
+    private static final String PREFIX_FWHTOULIU_TENCENTGZH = "fwhtouliu_tencentgzh";
+    private static final String PREFIX_LONGARTICLES = "longArticles_";
+    private static final String PREFIX_LONGARTICLES_OUTER = "longArticles_outer";
+    private static final String PREFIX_LONGARTICLES_INNER = "longArticles_inner_";
+
+    /** 默认最大重试天数,超过此天数的待处理记录不再查询 */
+    private static final int DEFAULT_MAX_RETRY_DAYS = 3;
+
+    /** 每次查询的记录数限制 */
+    private static final int QUERY_LIMIT = 100;
+
+    /** 分布式锁前缀 */
+    private static final String LOCK_PREFIX = "external_channel:process:";
+
+    /** 分布式锁过期时间(秒) */
+    private static final long LOCK_EXPIRE_SECONDS = 300;
+
+    /**
+     * 处理外部渠道待处理记录
+     * 1. 先将超过 DEFAULT_MAX_RETRY_DAYS 天的待处理记录标记为 FAILED
+     * 2. 仅处理创建时间在 DEFAULT_MAX_RETRY_DAYS 天内的待处理记录
+     */
+    @XxlJob("processExternalChannelJob")
+    public ReturnT<String> processExternalChannel(String param) {
+        log.info("开始处理外部渠道待处理记录, 最大重试天数={}", DEFAULT_MAX_RETRY_DAYS);
+        
+        try {
+            // 提前计算时间阈值,避免在SQL中进行日期计算
+            Date now = new Date();
+            Date beforeDaysTime = new Date(now.getTime() - TimeUnit.DAYS.toMillis(DEFAULT_MAX_RETRY_DAYS));
+
+            // 0. 先将超过天数的待处理记录标记为失败
+            int failedCount = externalChannelMapperExt.markFailedForTimeoutRecords(beforeDaysTime);
+            if (failedCount > 0) {
+                log.info("已将{}条创建时间早于{}的记录标记为失败", failedCount, beforeDaysTime);
+            }
+            
+            // 1. 查询所有待处理的记录(status=0),仅查询指定时间之后的记录
+            List<ExternalChannel> pendingList = externalChannelMapperExt.selectPendingList(QUERY_LIMIT, beforeDaysTime);
+            
+            if (CollectionUtils.isEmpty(pendingList)) {
+                log.info("没有待处理的外部渠道记录");
+                return ReturnT.SUCCESS;
+            }
+            
+            log.info("找到{}条待处理记录", pendingList.size());
+            
+            // 2. 逐条处理
+            for (ExternalChannel record : pendingList) {
+                try {
+                    processSingleRecord(record);
+                } catch (Exception e) {
+                    log.error("处理记录异常, id={}, rootSourceId={}", record.getId(), record.getRootSourceId(), e);
+                }
+            }
+            
+            log.info("外部渠道处理完成");
+            return ReturnT.SUCCESS;
+        } catch (Exception e) {
+            log.error("处理外部渠道记录时发生异常", e);
+            return ReturnT.FAIL;
+        }
+    }
+
+    /**
+     * 处理单条记录
+     * 根据渠道类型路由到对应的处理方法,使用分布式锁防止并发处理
+     */
+    private void processSingleRecord(ExternalChannel record) {
+        String rootSourceId = record.getRootSourceId();
+        Long recordId = record.getId();
+        
+        if (StringUtils.isBlank(rootSourceId)) {
+            log.warn("rootSourceId为空,标记为未知渠道, id={}", recordId);
+            record.setChannel("未知渠道-rootSourceId为空");
+            updateRecordProcessed(record, ExternalChannelStatusEnum.UNKNOWN_CHANNEL.getVal());
+            return;
+        }
+
+        // 尝试获取分布式锁
+        String lockKey = LOCK_PREFIX + recordId;
+        String lockValue = String.valueOf(System.currentTimeMillis());
+        boolean locked = false;
+        
+        try {
+            locked = redisUtils.tryLock(lockKey, lockValue, LOCK_EXPIRE_SECONDS);
+            if (!locked) {
+                log.info("获取分布式锁失败,跳过处理, id={}, rootSourceId={}", recordId, rootSourceId);
+                return;
+            }
+            
+            // 判断渠道类型并处理
+            ChannelType channelType = determineChannelType(rootSourceId);
+            
+            if (channelType == null) {
+                log.warn("无法识别渠道类型, id={}, rootSourceId={}", recordId, rootSourceId);
+                record.setChannel("未知渠道-前缀不匹配");
+                updateRecordProcessed(record, ExternalChannelStatusEnum.UNKNOWN_CHANNEL.getVal());
+                return;
+            }
+            
+            switch (channelType) {
+                case MINIAPP_TOULIU:
+                    processMiniAppTouliu(record);
+                    break;
+                case GZH_COOPERATE_JIZHUAN:
+                    processGzhCooperateJizhuan(record);
+                    break;
+                case QW_COOPERATE:
+                    processQwCooperate(record);
+                    break;
+                case GZH_TOULIU:
+                    processGzhTouliu(record);
+                    break;
+                case FWH_COOPERATE_DAILY:
+                    processFwhCooperateDaily(record);
+                    break;
+                case FWH_TOULIU_DAILY:
+                    processFwhTouliuDaily(record);
+                    break;
+                case FWH_TOULIU_REPLY:
+                    processFwhTouliuReply(record);
+                    break;
+                case GZH_COOPERATE_DAILY:
+                    processGzhCooperateDaily(record);
+                    break;
+                case GZH_BUY_ACCOUNT:
+                    processGzhBuyAccount(record);
+                    break;
+                case GZH_OPERATION_DAILY:
+                    processGzhOperationDaily(record);
+                    break;
+                default:
+                    log.warn("未处理的渠道类型, id={}, rootSourceId={}, channelType={}", recordId, rootSourceId, channelType);
+                    record.setChannel("未知渠道-" + channelType.name());
+                    updateRecordProcessed(record, ExternalChannelStatusEnum.UNKNOWN_CHANNEL.getVal());
+            }
+        } finally {
+            // 释放分布式锁
+            if (locked) {
+                redisUtils.del(lockKey);
+            }
+        }
+    }
+
+    /**
+     * 判断渠道类型
+     * 根据 rootSourceId 前缀判断所属渠道类型
+     */
+    private ChannelType determineChannelType(String rootSourceId) {
+        if (rootSourceId.startsWith(PREFIX_TOULIU_TENCENT)) {
+            return ChannelType.MINIAPP_TOULIU;
+        } else if (rootSourceId.startsWith(PREFIX_DYYJS)) {
+            return ChannelType.GZH_COOPERATE_JIZHUAN;
+        } else if (rootSourceId.startsWith(PREFIX_DYYQW) || rootSourceId.startsWith(PREFIX_TOULIU_TENCENTWBqw)) {
+            return ChannelType.QW_COOPERATE;
+        } else if (rootSourceId.startsWith(PREFIX_TOULIU_TENCENTGZH) 
+                || rootSourceId.startsWith(PREFIX_TOULIU_TENCENTGZHARTICLE)
+                || rootSourceId.startsWith(PREFIX_GZHTOULIU_ARTICLES)) {
+            return ChannelType.GZH_TOULIU;
+        } else if (rootSourceId.startsWith(PREFIX_FWHHZDYy) || rootSourceId.startsWith(PREFIX_FWHDYY)) {
+            return ChannelType.FWH_COOPERATE_DAILY;
+        } else if (rootSourceId.startsWith(PREFIX_FWHTOULIU_TENCENTGZH)) {
+            // 服务号投流需要通过接口判断是daily还是reply
+            return determineFwhTouliuType(rootSourceId);
+        } else if (rootSourceId.startsWith(PREFIX_LONGARTICLES)) {
+            if (rootSourceId.startsWith(PREFIX_LONGARTICLES_OUTER)) {
+                return ChannelType.GZH_COOPERATE_DAILY;
+            } else {
+                return determineLongArticlesType(rootSourceId);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 判断服务号投流类型(Daily或即转)
+     * 依次调用 recommendApiService 和 cgiReplyService 判断类型
+     * 命中 recommend 接口返回 DAILY,命中 cgiReply 接口返回 REPLY
+     */
+    private ChannelType determineFwhTouliuType(String rootSourceId) {
+        try {
+            Boolean dailyExists = recommendApiService.checkExistRootSourceId(rootSourceId);
+            if (Boolean.TRUE.equals(dailyExists)) {
+                log.info("服务号投流类型判断, rootSourceId={}, dailyExists=true", rootSourceId);
+                return ChannelType.FWH_TOULIU_DAILY;
+            }
+
+            Boolean replyExists = cgiReplyService.checkExistRootSourceId(rootSourceId);
+            if (Boolean.TRUE.equals(replyExists)) {
+                log.info("服务号投流类型判断, rootSourceId={}, replyExists=true", rootSourceId);
+                return ChannelType.FWH_TOULIU_REPLY;
+            }
+
+            log.info("服务号投流类型判断, rootSourceId={}, dailyExists=false, replyExists=false", rootSourceId);
+        } catch (Exception e) {
+            log.error("判断服务号投流类型失败, rootSourceId={}", rootSourceId, e);
+        }
+        // 默认返回daily
+        return ChannelType.FWH_TOULIU_DAILY;
+    }
+
+    /**
+     * 判断 longArticles 类型(inner或outer)
+     * 命名 recommend 接口返回 inner(公众号买号/代运营)
+     * 命名 contentPlatformPlanService 接口返回 outer(公众号合作-Daily)
+     */
+    private ChannelType determineLongArticlesType(String rootSourceId) {
+        try {
+            Boolean innerExists = recommendApiService.checkExistRootSourceId(rootSourceId);
+            if (Boolean.TRUE.equals(innerExists)) {
+                log.info("longArticles类型判断, rootSourceId={}, innerExists=true", rootSourceId);
+                // 区分 公众号买号 与 公众号代运营
+                if (isGzhBuyAccount(rootSourceId)) {
+                    return ChannelType.GZH_BUY_ACCOUNT;
+                } else {
+                    return ChannelType.GZH_OPERATION_DAILY;
+                }
+            }
+
+            Boolean outerExists = contentPlatformPlanService.gzhPushCheckExistRootSourceId(rootSourceId);
+            if (Boolean.TRUE.equals(outerExists)) {
+                log.info("longArticles类型判断, rootSourceId={}, outerExists=true", rootSourceId);
+                return ChannelType.GZH_COOPERATE_DAILY;
+            }
+
+            log.info("longArticles类型判断, rootSourceId={}, innerExists=false, outerExists=false", rootSourceId);
+        } catch (Exception e) {
+            log.error("判断longArticles类型失败, rootSourceId={}", rootSourceId, e);
+        }
+        // 默认返回 公众号代运营-Daily
+        return ChannelType.GZH_OPERATION_DAILY;
+    }
+
+    /**
+     * 判断是否为公众号买号
+     * 调用 AIGC 接口根据 ghId 判断账号类型
+     * 返回值包含 "买号" 则为公众号买号,包含 "代运营" 则为公众号代运营
+     *
+     * @param rootSourceId 根来源ID
+     * @return true-公众号买号, false-公众号代运营
+     */
+    private boolean isGzhBuyAccount(String rootSourceId) {
+        try {
+            // 1. 先通过rootSourceId获取ghId
+            JSONObject article = recommendApiService.getArticleByRootSourceId(rootSourceId);
+            if (article == null || StringUtils.isBlank(article.getString("ghId"))) {
+                log.warn("无法获取ghId, rootSourceId={}", rootSourceId);
+                return false;
+            }
+            String ghId = article.getString("ghId");
+
+            // 2. 调用AIGC接口判断账号类型
+            String accountGroupSourceName = aigcApiService.getAccountGroupSourceName(ghId);
+            log.info("判断公众号买号/代运营, rootSourceId={}, ghId={}, accountGroupSourceName={}", rootSourceId, ghId, accountGroupSourceName);
+
+            if (StringUtils.isBlank(accountGroupSourceName)) {
+                return false;
+            }
+
+            // 3. 根据返回判断账号类型
+            if (accountGroupSourceName.contains("买号")) {
+                log.info("判断为公众号买号, rootSourceId={}, ghId={}, accountGroupSourceName={}", rootSourceId, ghId, accountGroupSourceName);
+                return true;
+            } else if (accountGroupSourceName.contains("代运营")) {
+                log.info("判断为公众号代运营, rootSourceId={}, ghId={}, accountGroupSourceName={}", rootSourceId, ghId, accountGroupSourceName);
+                return false;
+            } else {
+                log.warn("无法识别账号类型, rootSourceId={}, ghId={}, accountGroupSourceName={}", rootSourceId, ghId, accountGroupSourceName);
+                return false;
+            }
+        } catch (Exception e) {
+            log.error("判断公众号买号/代运营失败, rootSourceId={}", rootSourceId, e);
+            // 默认返回false,即公众号代运营
+            return false;
+        }
+    }
+
+    /**
+     * 1. 小程序投流-稳定
+     * 渠道判断:rootSourceId 以 touliu_tencent_ 开头
+     * 处理逻辑:通过 adApiService 获取创意和人群包信息
+     */
+    private void processMiniAppTouliu(ExternalChannel record) {
+        String rootSourceId = record.getRootSourceId();
+        Long recordId = record.getId();
+        log.info("处理小程序投流, id={}, rootSourceId={}", recordId, rootSourceId);
+        
+        record.setChannel("小程序投流-稳定");
+        
+        // 调用AD API获取创意信息
+        try {
+            AdPutTencentCreative creative = adApiService.getCreativeByRootSourceId(rootSourceId);
+            if (creative != null) {
+                record.setCreativeId(String.valueOf(creative.getCreativeId()));
+                // 获取人群包信息
+                List<Long> packageIds = adApiService.getPackageIdByAdId(creative.getAdId());
+                if (packageIds != null && !packageIds.isEmpty()) {
+                    record.setPackageId(JSONObject.toJSONString(packageIds.get(0)));
+                }
+            }
+        } catch (Exception e) {
+            log.error("获取小程序投流信息异常, id={}, rootSourceId={}", recordId, rootSourceId, e);
+        }
+
+        Integer status = ExternalChannelStatusEnum.PENDING.getVal();
+        if (StringUtils.isNotBlank(record.getCreativeId())) {
+            status = ExternalChannelStatusEnum.PROCESSED.getVal();
+        }
+        updateRecordProcessed(record, status);
+    }
+
+    /**
+     * 2. 公众号合作-即转-稳定
+     * 渠道判断:rootSourceId 以 dyyjs_ 开头
+     * 处理逻辑:查询 cgi_reply_bucket_data 获取 ghId,再查询 content_platform_gzh_account 获取合作方
+     */
+    private void processGzhCooperateJizhuan(ExternalChannel record) {
+        String rootSourceId = record.getRootSourceId();
+        Long recordId = record.getId();
+        log.info("处理公众号合作-即转, id={}, rootSourceId={}", recordId, rootSourceId);
+        
+        record.setChannel("公众号合作-即转-稳定");
+        record.setCardId(rootSourceId);
+        
+        try {
+            // 查询cgi_reply_bucket_data获取ghId
+            CgiReplyBucketDataExample example = new CgiReplyBucketDataExample();
+            example.createCriteria().andRootSourceIdEqualTo(rootSourceId).andIsDeleteEqualTo(0);
+            example.setOrderByClause("id desc");
+            List<CgiReplyBucketData> bucketDataList = cgiReplyBucketDataMapper.selectByExample(example);
+            
+            if (!CollectionUtils.isEmpty(bucketDataList)) {
+                String ghId = bucketDataList.get(0).getGhId();
+                record.setAccountId(ghId);
+                
+                // 查询content_platform_gzh_account获取合作方
+                ContentPlatformGzhAccountExample accountExample = new ContentPlatformGzhAccountExample();
+                accountExample.createCriteria().andGhIdEqualTo(ghId).andStatusEqualTo(1);
+                List<ContentPlatformGzhAccount> accountList = gzhAccountMapper.selectByExample(accountExample);
+                
+                if (!CollectionUtils.isEmpty(accountList)) {
+                    record.setPartnerId(String.valueOf(accountList.get(0).getCreateAccountId()));
+                }
+            }
+        } catch (Exception e) {
+            log.error("获取公众号合作信息异常, id={}, rootSourceId={}", recordId, rootSourceId, e);
+        }
+        Integer status = ExternalChannelStatusEnum.PENDING.getVal();
+        if (StringUtils.isNotBlank(record.getPartnerId())) {
+            status = ExternalChannelStatusEnum.PROCESSED.getVal();
+        }
+        updateRecordProcessed(record, status);
+    }
+
+    /**
+     * 3. 群/企微合作-稳定
+     * 渠道判断:rootSourceId 以 dyyqw_ 或 touliu_tencentwbqw_ 开头
+     * 处理逻辑:查询 content_platform_qw_plan 获取合作方
+     */
+    private void processQwCooperate(ExternalChannel record) {
+        String rootSourceId = record.getRootSourceId();
+        Long recordId = record.getId();
+        log.info("处理群/企微合作, id={}, rootSourceId={}", recordId, rootSourceId);
+        
+        record.setChannel("群/企微合作-稳定");
+        record.setCardId(rootSourceId);
+
+        try {
+            // 查询content_platform_qw_plan获取合作方
+            ContentPlatformQwPlanExample example = new ContentPlatformQwPlanExample();
+            example.createCriteria().andRootSourceIdEqualTo(rootSourceId).andStatusEqualTo(1);
+            List<ContentPlatformQwPlan> planList = qwPlanMapper.selectByExample(example);
+            
+            if (!CollectionUtils.isEmpty(planList)) {
+                ContentPlatformQwPlan plan = planList.get(0);
+                record.setPartnerId(String.valueOf(plan.getCreateAccountId()));
+            }
+        } catch (Exception e) {
+            log.error("获取群/企微合作信息异常, id={}, rootSourceId={}", recordId, rootSourceId, e);
+        }
+
+        Integer status = ExternalChannelStatusEnum.PENDING.getVal();
+        if (StringUtils.isNotBlank(record.getPartnerId())) {
+            status = ExternalChannelStatusEnum.PROCESSED.getVal();
+        }
+        updateRecordProcessed(record, status);
+    }
+
+    /**
+     * 4. 公众号投流-稳定
+     * 渠道判断:rootSourceId 以 touliu_tencentgzh_、touliu_tencentGzhArticle_ 或 GzhTouLiu_Articles_gh 开头
+     * 处理逻辑:查询 cgi_reply_bucket_data 获取 ghId,通过 aigcApiService 获取 creativeId 和人群包信息
+     */
+    private void processGzhTouliu(ExternalChannel record) {
+        String rootSourceId = record.getRootSourceId();
+        Long recordId = record.getId();
+        log.info("处理公众号投流, id={}, rootSourceId={}", recordId, rootSourceId);
+        
+        record.setChannel("公众号投流-稳定");
+        record.setCardId(rootSourceId);
+
+        try {
+            // 查询cgi_reply_bucket_data获取ghId和pagePath
+            CgiReplyBucketDataExample example = new CgiReplyBucketDataExample();
+            example.createCriteria().andRootSourceIdEqualTo(rootSourceId).andIsDeleteEqualTo(0);
+            example.setOrderByClause("id desc");
+            List<CgiReplyBucketData> bucketDataList = cgiReplyBucketDataMapper.selectByExample(example);
+            
+            if (!CollectionUtils.isEmpty(bucketDataList)) {
+                CgiReplyBucketData bucketData = bucketDataList.get(0);
+                record.setAccountId(bucketData.getGhId());
+
+                // 投流即转 rootSourceId 查询 creativeId
+                String creativeId = aigcApiService.getCreativeIdByRootSourceIdAndGhId(rootSourceId, bucketData.getGhId());
+                if (StringUtils.isNotBlank(creativeId)) {
+                    record.setCreativeId(creativeId);
+
+                    // 获取人群包信息
+                    List<Long> packageIds = adApiService.getPackageIdByAdId(Long.valueOf(creativeId));
+                    if (packageIds != null && !packageIds.isEmpty()) {
+                        record.setPackageId(JSONObject.toJSONString(packageIds.get(0)));
+                    }
+                }
+            }
+        } catch (Exception e) {
+            log.error("获取公众号投流信息异常, id={}, rootSourceId={}", recordId, rootSourceId, e);
+        }
+
+        Integer status = ExternalChannelStatusEnum.PENDING.getVal();
+        if (StringUtils.isNotBlank(record.getCreativeId())) {
+            status = ExternalChannelStatusEnum.PROCESSED.getVal();
+        }
+        updateRecordProcessed(record, status);
+    }
+
+    /**
+     * 5. 服务号合作-Daily-自选
+     * 渠道判断:rootSourceId 以 fwhhzdyy_ 或 fwhdyy_ 开头
+     * 处理逻辑:查询 content_platform_gzh_plan_video 获取合作方和公众号信息
+     */
+    private void processFwhCooperateDaily(ExternalChannel record) {
+        String rootSourceId = record.getRootSourceId();
+        Long recordId = record.getId();
+        log.info("处理服务号合作-Daily, id={}, rootSourceId={}", recordId, rootSourceId);
+        
+        record.setChannel("服务号合作-Daily-自选");
+        
+        try {
+            // 查询content_platform_gzh_plan_video
+            ContentPlatformGzhPlanVideoExample videoExample = new ContentPlatformGzhPlanVideoExample();
+            videoExample.createCriteria().andRootSourceIdEqualTo(rootSourceId).andStatusEqualTo(1);
+            List<ContentPlatformGzhPlanVideo> videoList = gzhPlanVideoMapper.selectByExample(videoExample);
+            
+            if (!CollectionUtils.isEmpty(videoList)) {
+                ContentPlatformGzhPlanVideo video = videoList.get(0);
+                record.setPartnerId(String.valueOf(video.getCreateAccountId()));
+                
+                // 关联content_platform_gzh_plan获取公众号
+                ContentPlatformGzhPlan plan = gzhPlanMapper.selectByPrimaryKey(video.getPlanId());
+                if (plan != null) {
+                    ContentPlatformGzhAccount account = gzhAccountMapper.selectByPrimaryKey(plan.getAccountId());
+                    if (account != null) {
+                        record.setAccountId(account.getGhId());
+                    }
+                }
+                // 爬虫获取文章ID
+                String articleId = recommendApiService.getCooperateArticleIdByRootSourceId(rootSourceId);
+                if (StringUtils.isNotBlank(articleId)) {
+                    record.setArticleId(articleId);
+                }
+            }
+        } catch (Exception e) {
+            log.error("获取服务号合作信息异常, id={}, rootSourceId={}", recordId, rootSourceId, e);
+        }
+
+        Integer status = ExternalChannelStatusEnum.PENDING.getVal();
+        if (StringUtils.isNotBlank(record.getAccountId()) && StringUtils.isNotBlank(record.getArticleId())) {
+            status = ExternalChannelStatusEnum.PROCESSED.getVal();
+        }
+        updateRecordProcessed(record, status);
+    }
+
+    /**
+     * 6a. 服务号投流-Daily
+     * 渠道判断:rootSourceId 以 fwhtouliu_tencentgzh 开头且命中 recommend 接口
+     * 处理逻辑:通过 recommendApiService 获取文章信息
+     */
+    private void processFwhTouliuDaily(ExternalChannel record) {
+        String rootSourceId = record.getRootSourceId();
+        Long recordId = record.getId();
+        log.info("处理服务号投流-Daily, id={}, rootSourceId={}", recordId, rootSourceId);
+        record.setChannel("服务号投流-Daily");
+        record.setCardId(rootSourceId);
+        
+        try {
+            // 通过 recommendApiService 获取文章信息
+            JSONObject article = recommendApiService.getArticleByRootSourceId(rootSourceId);
+            if (article != null) {
+                String contentId = article.getString("wxSn");
+                if (StringUtils.isNotBlank(contentId)) {
+                    record.setArticleId(contentId);
+                    record.setAccountId(article.getString("ghId"));
+                }
+            }
+        } catch (Exception e) {
+            log.error("获取服务号投流-Daily信息异常, id={}, rootSourceId={}", recordId, rootSourceId, e);
+        }
+
+        Integer status = ExternalChannelStatusEnum.PENDING.getVal();
+        if (StringUtils.isNotBlank(record.getAccountId())) {
+            status = ExternalChannelStatusEnum.PROCESSED.getVal();
+        }
+        updateRecordProcessed(record, status);
+    }
+
+    /**
+     * 6b. 服务号投流-即转
+     * 渠道判断:rootSourceId 以 fwhtouliu_tencentgzh 开头且命中 cgiReply 接口
+     * 处理逻辑:查询 cgi_reply_bucket_data 获取 ghId,通过 aigcApiService 获取 creativeId
+     */
+    private void processFwhTouliuReply(ExternalChannel record) {
+        String rootSourceId = record.getRootSourceId();
+        Long recordId = record.getId();
+        log.info("处理服务号投流-即转, id={}, rootSourceId={}", recordId, rootSourceId);
+        record.setChannel("服务号投流-即转");
+        record.setCardId(rootSourceId);
+        
+        try {
+            // 查询cgi_reply_bucket_data获取ghId和pagePath
+            CgiReplyBucketDataExample example = new CgiReplyBucketDataExample();
+            example.createCriteria().andRootSourceIdEqualTo(rootSourceId).andIsDeleteEqualTo(0);
+            example.setOrderByClause("id desc");
+            List<CgiReplyBucketData> bucketDataList = cgiReplyBucketDataMapper.selectByExample(example);
+
+            if (!CollectionUtils.isEmpty(bucketDataList)) {
+                CgiReplyBucketData bucketData = bucketDataList.get(0);
+                record.setAccountId(bucketData.getGhId());
+
+                // 投流即转 rootSourceId 查询 creativeId
+                String creativeId = aigcApiService.getCreativeIdByRootSourceIdAndGhId(rootSourceId, bucketData.getGhId());
+                if (StringUtils.isNotBlank(creativeId)) {
+                    record.setCreativeId(creativeId);
+
+                    // 获取素材信息
+                    AdPutTencentCreativeAnalysis analysis = adApiService.getCreativeAnalysis(Long.valueOf(creativeId));
+                    if (analysis != null) {
+                        record.setArticleId(String.valueOf(analysis.getId()));
+                    }
+
+                    // 获取人群包信息
+                    List<Long> packageIds = adApiService.getPackageIdByAdId(Long.valueOf(creativeId));
+                    if (packageIds != null && !packageIds.isEmpty()) {
+                        record.setPackageId(JSONObject.toJSONString(packageIds.get(0)));
+                    }
+                }
+            }
+        } catch (Exception e) {
+            log.error("获取服务号投流-即转信息异常, id={}, rootSourceId={}", recordId, rootSourceId, e);
+        }
+
+        Integer status = ExternalChannelStatusEnum.PENDING.getVal();
+        if (StringUtils.isNotBlank(record.getCreativeId())) {
+            status = ExternalChannelStatusEnum.PROCESSED.getVal();
+        }
+        updateRecordProcessed(record, status);
+    }
+
+    /**
+     * 7. 公众号合作-Daily-自选
+     * 渠道判断:rootSourceId 以 longArticles_ 或 longArticles_outer 开头
+     * 处理逻辑:查询 content_platform_gzh_plan_video 获取合作方和公众号信息
+     */
+    private void processGzhCooperateDaily(ExternalChannel record) {
+        String rootSourceId = record.getRootSourceId();
+        Long recordId = record.getId();
+        log.info("处理公众号合作-Daily, id={}, rootSourceId={}", recordId, rootSourceId);
+        
+        record.setChannel("公众号合作-Daily-自选");
+        
+        try {
+            // 查询content_platform_gzh_plan_video
+            ContentPlatformGzhPlanVideoExample videoExample = new ContentPlatformGzhPlanVideoExample();
+            videoExample.createCriteria().andRootSourceIdEqualTo(rootSourceId).andStatusEqualTo(1);
+            List<ContentPlatformGzhPlanVideo> videoList = gzhPlanVideoMapper.selectByExample(videoExample);
+            
+            if (!CollectionUtils.isEmpty(videoList)) {
+                ContentPlatformGzhPlanVideo video = videoList.get(0);
+                record.setPartnerId(String.valueOf(video.getCreateAccountId()));
+                record.setArticleId(String.valueOf(video.getVideoId()));
+                
+                // 关联content_platform_gzh_plan获取公众号
+                ContentPlatformGzhPlan plan = gzhPlanMapper.selectByPrimaryKey(video.getPlanId());
+                if (plan != null) {
+                    ContentPlatformGzhAccount account = gzhAccountMapper.selectByPrimaryKey(plan.getAccountId());
+                    if (account != null) {
+                        record.setAccountId(account.getGhId());
+                    }
+                }
+                // 爬虫获取文章ID
+                String articleId = recommendApiService.getCooperateArticleIdByRootSourceId(rootSourceId);
+                if (StringUtils.isNotBlank(articleId)) {
+                    record.setArticleId(articleId);
+                }
+            }
+        } catch (Exception e) {
+            log.error("获取公众号合作-Daily信息异常, id={}, rootSourceId={}", recordId, rootSourceId, e);
+        }
+
+        Integer status = ExternalChannelStatusEnum.PENDING.getVal();
+        if (StringUtils.isNotBlank(record.getAccountId()) && StringUtils.isNotBlank(record.getArticleId())) {
+            status = ExternalChannelStatusEnum.PROCESSED.getVal();
+        }
+        updateRecordProcessed(record, status);
+    }
+
+    /**
+     * 8. 公众号买号
+     * 渠道判断:rootSourceId 以 longArticles_inner_ 开头且 AIGC 接口返回 "买号"
+     * 处理逻辑:通过 recommendApiService 获取文章和公众号信息
+     */
+    private void processGzhBuyAccount(ExternalChannel record) {
+        String rootSourceId = record.getRootSourceId();
+        Long recordId = record.getId();
+        log.info("处理公众号买号, id={}, rootSourceId={}", recordId, rootSourceId);
+        
+        record.setChannel("公众号买号");
+        record.setCardId(rootSourceId);
+
+        try {
+            // 解析发布内容ID
+            JSONObject article = recommendApiService.getArticleByRootSourceId(rootSourceId);
+            if (article != null) {
+                String contentId = article.getString("wxSn");
+                if (StringUtils.isNotBlank(contentId)) {
+                    record.setArticleId(contentId);
+                    record.setAccountId(article.getString("ghId"));
+                }
+            }
+        } catch (Exception e) {
+            log.error("获取公众号买号信息异常, id={}, rootSourceId={}", recordId, rootSourceId, e);
+        }
+
+        Integer status = ExternalChannelStatusEnum.PENDING.getVal();
+        if (StringUtils.isNotBlank(record.getAccountId())) {
+            status = ExternalChannelStatusEnum.PROCESSED.getVal();
+        }
+        updateRecordProcessed(record, status);
+    }
+
+    /**
+     * 9. 公众号代运营-Daily-系统
+     * 渠道判断:rootSourceId 以 longArticles_inner_ 开头且 AIGC 接口返回 "代运营"
+     * 处理逻辑:通过 recommendApiService 获取文章和公众号信息
+     */
+    private void processGzhOperationDaily(ExternalChannel record) {
+        String rootSourceId = record.getRootSourceId();
+        Long recordId = record.getId();
+        log.info("处理公众号代运营-Daily, id={}, rootSourceId={}", recordId, rootSourceId);
+        
+        record.setChannel("公众号代运营-Daily-系统");
+        record.setCardId(rootSourceId);
+
+        try {
+            // 解析发布内容ID
+            JSONObject article = recommendApiService.getArticleByRootSourceId(rootSourceId);
+            if (article != null) {
+                String contentId = article.getString("wxSn");
+                if (StringUtils.isNotBlank(contentId)) {
+                    record.setArticleId(contentId);
+                    record.setAccountId(article.getString("ghId"));
+                }
+            }
+        } catch (Exception e) {
+            log.error("获取公众号代运营-Daily信息异常, id={}, rootSourceId={}", recordId, rootSourceId, e);
+        }
+
+        Integer status = ExternalChannelStatusEnum.PENDING.getVal();
+        if (StringUtils.isNotBlank(record.getAccountId())) {
+            status = ExternalChannelStatusEnum.PROCESSED.getVal();
+        }
+        updateRecordProcessed(record, status);
+    }
+
+    /**
+     * 更新记录状态
+     *
+     * @param record 待更新记录
+     * @param status 目标状态
+     */
+    private void updateRecordProcessed(ExternalChannel record, Integer status) {
+        record.setStatus(status);
+        record.setUpdateTime(new Date());
+        externalChannelMapper.updateByPrimaryKeySelective(record);
+        log.info("记录处理完成, id={}, channel={}, status={}", record.getId(), record.getChannel(), status);
+    }
+
+}

+ 15 - 0
api-module/src/main/java/com/tzld/piaoquan/api/model/bo/AdPutTencentCreative.java

@@ -0,0 +1,15 @@
+package com.tzld.piaoquan.api.model.bo;
+
+import lombok.Data;
+
+/**
+ * 腾讯广告创意信息
+ */
+@Data
+public class AdPutTencentCreative {
+    private Long creativeId;
+    private String creativeName;
+    private Long adId;
+    private Long accountId;
+    private String creativeComponents;
+}

+ 14 - 0
api-module/src/main/java/com/tzld/piaoquan/api/model/bo/AdPutTencentCreativeAnalysis.java

@@ -0,0 +1,14 @@
+package com.tzld.piaoquan.api.model.bo;
+
+import lombok.Data;
+
+/**
+ * 腾讯广告创意素材分析信息
+ */
+@Data
+public class AdPutTencentCreativeAnalysis {
+    private Long id;
+    private Long creativeId;
+    private String materialType;
+    private String materialId;
+}

+ 158 - 0
api-module/src/main/java/com/tzld/piaoquan/api/model/po/contentplatform/ExternalChannel.java

@@ -0,0 +1,158 @@
+package com.tzld.piaoquan.api.model.po.contentplatform;
+
+import java.util.Date;
+
+public class ExternalChannel {
+    private Long id;
+
+    private String rootSourceId;
+
+    private String channel;
+
+    private String partnerId;
+
+    private String accountId;
+
+    private String creativeId;
+
+    private String articleId;
+
+    private String cardId;
+
+    private String packageId;
+
+    private Integer status;
+
+    private Integer isDelete;
+
+    private Date createTime;
+
+    private Date updateTime;
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public String getRootSourceId() {
+        return rootSourceId;
+    }
+
+    public void setRootSourceId(String rootSourceId) {
+        this.rootSourceId = rootSourceId;
+    }
+
+    public String getChannel() {
+        return channel;
+    }
+
+    public void setChannel(String channel) {
+        this.channel = channel;
+    }
+
+    public String getPartnerId() {
+        return partnerId;
+    }
+
+    public void setPartnerId(String partnerId) {
+        this.partnerId = partnerId;
+    }
+
+    public String getAccountId() {
+        return accountId;
+    }
+
+    public void setAccountId(String accountId) {
+        this.accountId = accountId;
+    }
+
+    public String getCreativeId() {
+        return creativeId;
+    }
+
+    public void setCreativeId(String creativeId) {
+        this.creativeId = creativeId;
+    }
+
+    public String getArticleId() {
+        return articleId;
+    }
+
+    public void setArticleId(String articleId) {
+        this.articleId = articleId;
+    }
+
+    public String getCardId() {
+        return cardId;
+    }
+
+    public void setCardId(String cardId) {
+        this.cardId = cardId;
+    }
+
+    public String getPackageId() {
+        return packageId;
+    }
+
+    public void setPackageId(String packageId) {
+        this.packageId = packageId;
+    }
+
+    public Integer getStatus() {
+        return status;
+    }
+
+    public void setStatus(Integer status) {
+        this.status = status;
+    }
+
+    public Integer getIsDelete() {
+        return isDelete;
+    }
+
+    public void setIsDelete(Integer isDelete) {
+        this.isDelete = isDelete;
+    }
+
+    public Date getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(Date createTime) {
+        this.createTime = createTime;
+    }
+
+    public Date getUpdateTime() {
+        return updateTime;
+    }
+
+    public void setUpdateTime(Date updateTime) {
+        this.updateTime = updateTime;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(getClass().getSimpleName());
+        sb.append(" [");
+        sb.append("Hash = ").append(hashCode());
+        sb.append(", id=").append(id);
+        sb.append(", rootSourceId=").append(rootSourceId);
+        sb.append(", channel=").append(channel);
+        sb.append(", partnerId=").append(partnerId);
+        sb.append(", accountId=").append(accountId);
+        sb.append(", creativeId=").append(creativeId);
+        sb.append(", articleId=").append(articleId);
+        sb.append(", cardId=").append(cardId);
+        sb.append(", packageId=").append(packageId);
+        sb.append(", status=").append(status);
+        sb.append(", isDelete=").append(isDelete);
+        sb.append(", createTime=").append(createTime);
+        sb.append(", updateTime=").append(updateTime);
+        sb.append("]");
+        return sb.toString();
+    }
+}

+ 1072 - 0
api-module/src/main/java/com/tzld/piaoquan/api/model/po/contentplatform/ExternalChannelExample.java

@@ -0,0 +1,1072 @@
+package com.tzld.piaoquan.api.model.po.contentplatform;
+
+import com.tzld.piaoquan.growth.common.utils.page.Page;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+public class ExternalChannelExample {
+    protected String orderByClause;
+
+    protected boolean distinct;
+
+    protected List<Criteria> oredCriteria;
+
+    protected Page page;
+
+    public ExternalChannelExample() {
+        oredCriteria = new ArrayList<Criteria>();
+    }
+
+    public void setOrderByClause(String orderByClause) {
+        this.orderByClause = orderByClause;
+    }
+
+    public String getOrderByClause() {
+        return orderByClause;
+    }
+
+    public void setDistinct(boolean distinct) {
+        this.distinct = distinct;
+    }
+
+    public boolean isDistinct() {
+        return distinct;
+    }
+
+    public List<Criteria> getOredCriteria() {
+        return oredCriteria;
+    }
+
+    public void or(Criteria criteria) {
+        oredCriteria.add(criteria);
+    }
+
+    public Criteria or() {
+        Criteria criteria = createCriteriaInternal();
+        oredCriteria.add(criteria);
+        return criteria;
+    }
+
+    public Criteria createCriteria() {
+        Criteria criteria = createCriteriaInternal();
+        if (oredCriteria.size() == 0) {
+            oredCriteria.add(criteria);
+        }
+        return criteria;
+    }
+
+    protected Criteria createCriteriaInternal() {
+        Criteria criteria = new Criteria();
+        return criteria;
+    }
+
+    public void clear() {
+        oredCriteria.clear();
+        orderByClause = null;
+        distinct = false;
+    }
+
+    public void setPage(Page page) {
+        this.page=page;
+    }
+
+    public Page getPage() {
+        return page;
+    }
+
+    protected abstract static class GeneratedCriteria {
+        protected List<Criterion> criteria;
+
+        protected GeneratedCriteria() {
+            super();
+            criteria = new ArrayList<Criterion>();
+        }
+
+        public boolean isValid() {
+            return criteria.size() > 0;
+        }
+
+        public List<Criterion> getAllCriteria() {
+            return criteria;
+        }
+
+        public List<Criterion> getCriteria() {
+            return criteria;
+        }
+
+        protected void addCriterion(String condition) {
+            if (condition == null) {
+                throw new RuntimeException("Value for condition cannot be null");
+            }
+            criteria.add(new Criterion(condition));
+        }
+
+        protected void addCriterion(String condition, Object value, String property) {
+            if (value == null) {
+                throw new RuntimeException("Value for " + property + " cannot be null");
+            }
+            criteria.add(new Criterion(condition, value));
+        }
+
+        protected void addCriterion(String condition, Object value1, Object value2, String property) {
+            if (value1 == null || value2 == null) {
+                throw new RuntimeException("Between values for " + property + " cannot be null");
+            }
+            criteria.add(new Criterion(condition, value1, value2));
+        }
+
+        public Criteria andIdIsNull() {
+            addCriterion("id is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andIdIsNotNull() {
+            addCriterion("id is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andIdEqualTo(Long value) {
+            addCriterion("id =", value, "id");
+            return (Criteria) this;
+        }
+
+        public Criteria andIdNotEqualTo(Long value) {
+            addCriterion("id <>", value, "id");
+            return (Criteria) this;
+        }
+
+        public Criteria andIdGreaterThan(Long value) {
+            addCriterion("id >", value, "id");
+            return (Criteria) this;
+        }
+
+        public Criteria andIdGreaterThanOrEqualTo(Long value) {
+            addCriterion("id >=", value, "id");
+            return (Criteria) this;
+        }
+
+        public Criteria andIdLessThan(Long value) {
+            addCriterion("id <", value, "id");
+            return (Criteria) this;
+        }
+
+        public Criteria andIdLessThanOrEqualTo(Long value) {
+            addCriterion("id <=", value, "id");
+            return (Criteria) this;
+        }
+
+        public Criteria andIdIn(List<Long> values) {
+            addCriterion("id in", values, "id");
+            return (Criteria) this;
+        }
+
+        public Criteria andIdNotIn(List<Long> values) {
+            addCriterion("id not in", values, "id");
+            return (Criteria) this;
+        }
+
+        public Criteria andIdBetween(Long value1, Long value2) {
+            addCriterion("id between", value1, value2, "id");
+            return (Criteria) this;
+        }
+
+        public Criteria andIdNotBetween(Long value1, Long value2) {
+            addCriterion("id not between", value1, value2, "id");
+            return (Criteria) this;
+        }
+
+        public Criteria andRootSourceIdIsNull() {
+            addCriterion("root_source_id is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andRootSourceIdIsNotNull() {
+            addCriterion("root_source_id is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andRootSourceIdEqualTo(String value) {
+            addCriterion("root_source_id =", value, "rootSourceId");
+            return (Criteria) this;
+        }
+
+        public Criteria andRootSourceIdNotEqualTo(String value) {
+            addCriterion("root_source_id <>", value, "rootSourceId");
+            return (Criteria) this;
+        }
+
+        public Criteria andRootSourceIdGreaterThan(String value) {
+            addCriterion("root_source_id >", value, "rootSourceId");
+            return (Criteria) this;
+        }
+
+        public Criteria andRootSourceIdGreaterThanOrEqualTo(String value) {
+            addCriterion("root_source_id >=", value, "rootSourceId");
+            return (Criteria) this;
+        }
+
+        public Criteria andRootSourceIdLessThan(String value) {
+            addCriterion("root_source_id <", value, "rootSourceId");
+            return (Criteria) this;
+        }
+
+        public Criteria andRootSourceIdLessThanOrEqualTo(String value) {
+            addCriterion("root_source_id <=", value, "rootSourceId");
+            return (Criteria) this;
+        }
+
+        public Criteria andRootSourceIdLike(String value) {
+            addCriterion("root_source_id like", value, "rootSourceId");
+            return (Criteria) this;
+        }
+
+        public Criteria andRootSourceIdNotLike(String value) {
+            addCriterion("root_source_id not like", value, "rootSourceId");
+            return (Criteria) this;
+        }
+
+        public Criteria andRootSourceIdIn(List<String> values) {
+            addCriterion("root_source_id in", values, "rootSourceId");
+            return (Criteria) this;
+        }
+
+        public Criteria andRootSourceIdNotIn(List<String> values) {
+            addCriterion("root_source_id not in", values, "rootSourceId");
+            return (Criteria) this;
+        }
+
+        public Criteria andRootSourceIdBetween(String value1, String value2) {
+            addCriterion("root_source_id between", value1, value2, "rootSourceId");
+            return (Criteria) this;
+        }
+
+        public Criteria andRootSourceIdNotBetween(String value1, String value2) {
+            addCriterion("root_source_id not between", value1, value2, "rootSourceId");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelIsNull() {
+            addCriterion("channel is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelIsNotNull() {
+            addCriterion("channel is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelEqualTo(String value) {
+            addCriterion("channel =", value, "channel");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelNotEqualTo(String value) {
+            addCriterion("channel <>", value, "channel");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelGreaterThan(String value) {
+            addCriterion("channel >", value, "channel");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelGreaterThanOrEqualTo(String value) {
+            addCriterion("channel >=", value, "channel");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelLessThan(String value) {
+            addCriterion("channel <", value, "channel");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelLessThanOrEqualTo(String value) {
+            addCriterion("channel <=", value, "channel");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelLike(String value) {
+            addCriterion("channel like", value, "channel");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelNotLike(String value) {
+            addCriterion("channel not like", value, "channel");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelIn(List<String> values) {
+            addCriterion("channel in", values, "channel");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelNotIn(List<String> values) {
+            addCriterion("channel not in", values, "channel");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelBetween(String value1, String value2) {
+            addCriterion("channel between", value1, value2, "channel");
+            return (Criteria) this;
+        }
+
+        public Criteria andChannelNotBetween(String value1, String value2) {
+            addCriterion("channel not between", value1, value2, "channel");
+            return (Criteria) this;
+        }
+
+        public Criteria andPartnerIdIsNull() {
+            addCriterion("partner_id is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andPartnerIdIsNotNull() {
+            addCriterion("partner_id is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andPartnerIdEqualTo(String value) {
+            addCriterion("partner_id =", value, "partnerId");
+            return (Criteria) this;
+        }
+
+        public Criteria andPartnerIdNotEqualTo(String value) {
+            addCriterion("partner_id <>", value, "partnerId");
+            return (Criteria) this;
+        }
+
+        public Criteria andPartnerIdGreaterThan(String value) {
+            addCriterion("partner_id >", value, "partnerId");
+            return (Criteria) this;
+        }
+
+        public Criteria andPartnerIdGreaterThanOrEqualTo(String value) {
+            addCriterion("partner_id >=", value, "partnerId");
+            return (Criteria) this;
+        }
+
+        public Criteria andPartnerIdLessThan(String value) {
+            addCriterion("partner_id <", value, "partnerId");
+            return (Criteria) this;
+        }
+
+        public Criteria andPartnerIdLessThanOrEqualTo(String value) {
+            addCriterion("partner_id <=", value, "partnerId");
+            return (Criteria) this;
+        }
+
+        public Criteria andPartnerIdLike(String value) {
+            addCriterion("partner_id like", value, "partnerId");
+            return (Criteria) this;
+        }
+
+        public Criteria andPartnerIdNotLike(String value) {
+            addCriterion("partner_id not like", value, "partnerId");
+            return (Criteria) this;
+        }
+
+        public Criteria andPartnerIdIn(List<String> values) {
+            addCriterion("partner_id in", values, "partnerId");
+            return (Criteria) this;
+        }
+
+        public Criteria andPartnerIdNotIn(List<String> values) {
+            addCriterion("partner_id not in", values, "partnerId");
+            return (Criteria) this;
+        }
+
+        public Criteria andPartnerIdBetween(String value1, String value2) {
+            addCriterion("partner_id between", value1, value2, "partnerId");
+            return (Criteria) this;
+        }
+
+        public Criteria andPartnerIdNotBetween(String value1, String value2) {
+            addCriterion("partner_id not between", value1, value2, "partnerId");
+            return (Criteria) this;
+        }
+
+        public Criteria andAccountIdIsNull() {
+            addCriterion("account_id is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andAccountIdIsNotNull() {
+            addCriterion("account_id is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andAccountIdEqualTo(String value) {
+            addCriterion("account_id =", value, "accountId");
+            return (Criteria) this;
+        }
+
+        public Criteria andAccountIdNotEqualTo(String value) {
+            addCriterion("account_id <>", value, "accountId");
+            return (Criteria) this;
+        }
+
+        public Criteria andAccountIdGreaterThan(String value) {
+            addCriterion("account_id >", value, "accountId");
+            return (Criteria) this;
+        }
+
+        public Criteria andAccountIdGreaterThanOrEqualTo(String value) {
+            addCriterion("account_id >=", value, "accountId");
+            return (Criteria) this;
+        }
+
+        public Criteria andAccountIdLessThan(String value) {
+            addCriterion("account_id <", value, "accountId");
+            return (Criteria) this;
+        }
+
+        public Criteria andAccountIdLessThanOrEqualTo(String value) {
+            addCriterion("account_id <=", value, "accountId");
+            return (Criteria) this;
+        }
+
+        public Criteria andAccountIdLike(String value) {
+            addCriterion("account_id like", value, "accountId");
+            return (Criteria) this;
+        }
+
+        public Criteria andAccountIdNotLike(String value) {
+            addCriterion("account_id not like", value, "accountId");
+            return (Criteria) this;
+        }
+
+        public Criteria andAccountIdIn(List<String> values) {
+            addCriterion("account_id in", values, "accountId");
+            return (Criteria) this;
+        }
+
+        public Criteria andAccountIdNotIn(List<String> values) {
+            addCriterion("account_id not in", values, "accountId");
+            return (Criteria) this;
+        }
+
+        public Criteria andAccountIdBetween(String value1, String value2) {
+            addCriterion("account_id between", value1, value2, "accountId");
+            return (Criteria) this;
+        }
+
+        public Criteria andAccountIdNotBetween(String value1, String value2) {
+            addCriterion("account_id not between", value1, value2, "accountId");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreativeIdIsNull() {
+            addCriterion("creative_id is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreativeIdIsNotNull() {
+            addCriterion("creative_id is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreativeIdEqualTo(String value) {
+            addCriterion("creative_id =", value, "creativeId");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreativeIdNotEqualTo(String value) {
+            addCriterion("creative_id <>", value, "creativeId");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreativeIdGreaterThan(String value) {
+            addCriterion("creative_id >", value, "creativeId");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreativeIdGreaterThanOrEqualTo(String value) {
+            addCriterion("creative_id >=", value, "creativeId");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreativeIdLessThan(String value) {
+            addCriterion("creative_id <", value, "creativeId");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreativeIdLessThanOrEqualTo(String value) {
+            addCriterion("creative_id <=", value, "creativeId");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreativeIdLike(String value) {
+            addCriterion("creative_id like", value, "creativeId");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreativeIdNotLike(String value) {
+            addCriterion("creative_id not like", value, "creativeId");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreativeIdIn(List<String> values) {
+            addCriterion("creative_id in", values, "creativeId");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreativeIdNotIn(List<String> values) {
+            addCriterion("creative_id not in", values, "creativeId");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreativeIdBetween(String value1, String value2) {
+            addCriterion("creative_id between", value1, value2, "creativeId");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreativeIdNotBetween(String value1, String value2) {
+            addCriterion("creative_id not between", value1, value2, "creativeId");
+            return (Criteria) this;
+        }
+
+        public Criteria andArticleIdIsNull() {
+            addCriterion("article_id is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andArticleIdIsNotNull() {
+            addCriterion("article_id is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andArticleIdEqualTo(String value) {
+            addCriterion("article_id =", value, "articleId");
+            return (Criteria) this;
+        }
+
+        public Criteria andArticleIdNotEqualTo(String value) {
+            addCriterion("article_id <>", value, "articleId");
+            return (Criteria) this;
+        }
+
+        public Criteria andArticleIdGreaterThan(String value) {
+            addCriterion("article_id >", value, "articleId");
+            return (Criteria) this;
+        }
+
+        public Criteria andArticleIdGreaterThanOrEqualTo(String value) {
+            addCriterion("article_id >=", value, "articleId");
+            return (Criteria) this;
+        }
+
+        public Criteria andArticleIdLessThan(String value) {
+            addCriterion("article_id <", value, "articleId");
+            return (Criteria) this;
+        }
+
+        public Criteria andArticleIdLessThanOrEqualTo(String value) {
+            addCriterion("article_id <=", value, "articleId");
+            return (Criteria) this;
+        }
+
+        public Criteria andArticleIdLike(String value) {
+            addCriterion("article_id like", value, "articleId");
+            return (Criteria) this;
+        }
+
+        public Criteria andArticleIdNotLike(String value) {
+            addCriterion("article_id not like", value, "articleId");
+            return (Criteria) this;
+        }
+
+        public Criteria andArticleIdIn(List<String> values) {
+            addCriterion("article_id in", values, "articleId");
+            return (Criteria) this;
+        }
+
+        public Criteria andArticleIdNotIn(List<String> values) {
+            addCriterion("article_id not in", values, "articleId");
+            return (Criteria) this;
+        }
+
+        public Criteria andArticleIdBetween(String value1, String value2) {
+            addCriterion("article_id between", value1, value2, "articleId");
+            return (Criteria) this;
+        }
+
+        public Criteria andArticleIdNotBetween(String value1, String value2) {
+            addCriterion("article_id not between", value1, value2, "articleId");
+            return (Criteria) this;
+        }
+
+        public Criteria andCardIdIsNull() {
+            addCriterion("card_id is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andCardIdIsNotNull() {
+            addCriterion("card_id is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andCardIdEqualTo(String value) {
+            addCriterion("card_id =", value, "cardId");
+            return (Criteria) this;
+        }
+
+        public Criteria andCardIdNotEqualTo(String value) {
+            addCriterion("card_id <>", value, "cardId");
+            return (Criteria) this;
+        }
+
+        public Criteria andCardIdGreaterThan(String value) {
+            addCriterion("card_id >", value, "cardId");
+            return (Criteria) this;
+        }
+
+        public Criteria andCardIdGreaterThanOrEqualTo(String value) {
+            addCriterion("card_id >=", value, "cardId");
+            return (Criteria) this;
+        }
+
+        public Criteria andCardIdLessThan(String value) {
+            addCriterion("card_id <", value, "cardId");
+            return (Criteria) this;
+        }
+
+        public Criteria andCardIdLessThanOrEqualTo(String value) {
+            addCriterion("card_id <=", value, "cardId");
+            return (Criteria) this;
+        }
+
+        public Criteria andCardIdLike(String value) {
+            addCriterion("card_id like", value, "cardId");
+            return (Criteria) this;
+        }
+
+        public Criteria andCardIdNotLike(String value) {
+            addCriterion("card_id not like", value, "cardId");
+            return (Criteria) this;
+        }
+
+        public Criteria andCardIdIn(List<String> values) {
+            addCriterion("card_id in", values, "cardId");
+            return (Criteria) this;
+        }
+
+        public Criteria andCardIdNotIn(List<String> values) {
+            addCriterion("card_id not in", values, "cardId");
+            return (Criteria) this;
+        }
+
+        public Criteria andCardIdBetween(String value1, String value2) {
+            addCriterion("card_id between", value1, value2, "cardId");
+            return (Criteria) this;
+        }
+
+        public Criteria andCardIdNotBetween(String value1, String value2) {
+            addCriterion("card_id not between", value1, value2, "cardId");
+            return (Criteria) this;
+        }
+
+        public Criteria andPackageIdIsNull() {
+            addCriterion("package_id is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andPackageIdIsNotNull() {
+            addCriterion("package_id is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andPackageIdEqualTo(String value) {
+            addCriterion("package_id =", value, "packageId");
+            return (Criteria) this;
+        }
+
+        public Criteria andPackageIdNotEqualTo(String value) {
+            addCriterion("package_id <>", value, "packageId");
+            return (Criteria) this;
+        }
+
+        public Criteria andPackageIdGreaterThan(String value) {
+            addCriterion("package_id >", value, "packageId");
+            return (Criteria) this;
+        }
+
+        public Criteria andPackageIdGreaterThanOrEqualTo(String value) {
+            addCriterion("package_id >=", value, "packageId");
+            return (Criteria) this;
+        }
+
+        public Criteria andPackageIdLessThan(String value) {
+            addCriterion("package_id <", value, "packageId");
+            return (Criteria) this;
+        }
+
+        public Criteria andPackageIdLessThanOrEqualTo(String value) {
+            addCriterion("package_id <=", value, "packageId");
+            return (Criteria) this;
+        }
+
+        public Criteria andPackageIdLike(String value) {
+            addCriterion("package_id like", value, "packageId");
+            return (Criteria) this;
+        }
+
+        public Criteria andPackageIdNotLike(String value) {
+            addCriterion("package_id not like", value, "packageId");
+            return (Criteria) this;
+        }
+
+        public Criteria andPackageIdIn(List<String> values) {
+            addCriterion("package_id in", values, "packageId");
+            return (Criteria) this;
+        }
+
+        public Criteria andPackageIdNotIn(List<String> values) {
+            addCriterion("package_id not in", values, "packageId");
+            return (Criteria) this;
+        }
+
+        public Criteria andPackageIdBetween(String value1, String value2) {
+            addCriterion("package_id between", value1, value2, "packageId");
+            return (Criteria) this;
+        }
+
+        public Criteria andPackageIdNotBetween(String value1, String value2) {
+            addCriterion("package_id not between", value1, value2, "packageId");
+            return (Criteria) this;
+        }
+
+        public Criteria andStatusIsNull() {
+            addCriterion("`status` is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andStatusIsNotNull() {
+            addCriterion("`status` is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andStatusEqualTo(Integer value) {
+            addCriterion("`status` =", value, "status");
+            return (Criteria) this;
+        }
+
+        public Criteria andStatusNotEqualTo(Integer value) {
+            addCriterion("`status` <>", value, "status");
+            return (Criteria) this;
+        }
+
+        public Criteria andStatusGreaterThan(Integer value) {
+            addCriterion("`status` >", value, "status");
+            return (Criteria) this;
+        }
+
+        public Criteria andStatusGreaterThanOrEqualTo(Integer value) {
+            addCriterion("`status` >=", value, "status");
+            return (Criteria) this;
+        }
+
+        public Criteria andStatusLessThan(Integer value) {
+            addCriterion("`status` <", value, "status");
+            return (Criteria) this;
+        }
+
+        public Criteria andStatusLessThanOrEqualTo(Integer value) {
+            addCriterion("`status` <=", value, "status");
+            return (Criteria) this;
+        }
+
+        public Criteria andStatusIn(List<Integer> values) {
+            addCriterion("`status` in", values, "status");
+            return (Criteria) this;
+        }
+
+        public Criteria andStatusNotIn(List<Integer> values) {
+            addCriterion("`status` not in", values, "status");
+            return (Criteria) this;
+        }
+
+        public Criteria andStatusBetween(Integer value1, Integer value2) {
+            addCriterion("`status` between", value1, value2, "status");
+            return (Criteria) this;
+        }
+
+        public Criteria andStatusNotBetween(Integer value1, Integer value2) {
+            addCriterion("`status` not between", value1, value2, "status");
+            return (Criteria) this;
+        }
+
+        public Criteria andIsDeleteIsNull() {
+            addCriterion("is_delete is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andIsDeleteIsNotNull() {
+            addCriterion("is_delete is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andIsDeleteEqualTo(Integer value) {
+            addCriterion("is_delete =", value, "isDelete");
+            return (Criteria) this;
+        }
+
+        public Criteria andIsDeleteNotEqualTo(Integer value) {
+            addCriterion("is_delete <>", value, "isDelete");
+            return (Criteria) this;
+        }
+
+        public Criteria andIsDeleteGreaterThan(Integer value) {
+            addCriterion("is_delete >", value, "isDelete");
+            return (Criteria) this;
+        }
+
+        public Criteria andIsDeleteGreaterThanOrEqualTo(Integer value) {
+            addCriterion("is_delete >=", value, "isDelete");
+            return (Criteria) this;
+        }
+
+        public Criteria andIsDeleteLessThan(Integer value) {
+            addCriterion("is_delete <", value, "isDelete");
+            return (Criteria) this;
+        }
+
+        public Criteria andIsDeleteLessThanOrEqualTo(Integer value) {
+            addCriterion("is_delete <=", value, "isDelete");
+            return (Criteria) this;
+        }
+
+        public Criteria andIsDeleteIn(List<Integer> values) {
+            addCriterion("is_delete in", values, "isDelete");
+            return (Criteria) this;
+        }
+
+        public Criteria andIsDeleteNotIn(List<Integer> values) {
+            addCriterion("is_delete not in", values, "isDelete");
+            return (Criteria) this;
+        }
+
+        public Criteria andIsDeleteBetween(Integer value1, Integer value2) {
+            addCriterion("is_delete between", value1, value2, "isDelete");
+            return (Criteria) this;
+        }
+
+        public Criteria andIsDeleteNotBetween(Integer value1, Integer value2) {
+            addCriterion("is_delete not between", value1, value2, "isDelete");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreateTimeIsNull() {
+            addCriterion("create_time is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreateTimeIsNotNull() {
+            addCriterion("create_time is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreateTimeEqualTo(Date value) {
+            addCriterion("create_time =", value, "createTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreateTimeNotEqualTo(Date value) {
+            addCriterion("create_time <>", value, "createTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreateTimeGreaterThan(Date value) {
+            addCriterion("create_time >", value, "createTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreateTimeGreaterThanOrEqualTo(Date value) {
+            addCriterion("create_time >=", value, "createTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreateTimeLessThan(Date value) {
+            addCriterion("create_time <", value, "createTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreateTimeLessThanOrEqualTo(Date value) {
+            addCriterion("create_time <=", value, "createTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreateTimeIn(List<Date> values) {
+            addCriterion("create_time in", values, "createTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreateTimeNotIn(List<Date> values) {
+            addCriterion("create_time not in", values, "createTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreateTimeBetween(Date value1, Date value2) {
+            addCriterion("create_time between", value1, value2, "createTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andCreateTimeNotBetween(Date value1, Date value2) {
+            addCriterion("create_time not between", value1, value2, "createTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andUpdateTimeIsNull() {
+            addCriterion("update_time is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andUpdateTimeIsNotNull() {
+            addCriterion("update_time is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andUpdateTimeEqualTo(Date value) {
+            addCriterion("update_time =", value, "updateTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andUpdateTimeNotEqualTo(Date value) {
+            addCriterion("update_time <>", value, "updateTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andUpdateTimeGreaterThan(Date value) {
+            addCriterion("update_time >", value, "updateTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andUpdateTimeGreaterThanOrEqualTo(Date value) {
+            addCriterion("update_time >=", value, "updateTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andUpdateTimeLessThan(Date value) {
+            addCriterion("update_time <", value, "updateTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andUpdateTimeLessThanOrEqualTo(Date value) {
+            addCriterion("update_time <=", value, "updateTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andUpdateTimeIn(List<Date> values) {
+            addCriterion("update_time in", values, "updateTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andUpdateTimeNotIn(List<Date> values) {
+            addCriterion("update_time not in", values, "updateTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andUpdateTimeBetween(Date value1, Date value2) {
+            addCriterion("update_time between", value1, value2, "updateTime");
+            return (Criteria) this;
+        }
+
+        public Criteria andUpdateTimeNotBetween(Date value1, Date value2) {
+            addCriterion("update_time not between", value1, value2, "updateTime");
+            return (Criteria) this;
+        }
+    }
+
+    public static class Criteria extends GeneratedCriteria {
+
+        protected Criteria() {
+            super();
+        }
+    }
+
+    public static class Criterion {
+        private String condition;
+
+        private Object value;
+
+        private Object secondValue;
+
+        private boolean noValue;
+
+        private boolean singleValue;
+
+        private boolean betweenValue;
+
+        private boolean listValue;
+
+        private String typeHandler;
+
+        public String getCondition() {
+            return condition;
+        }
+
+        public Object getValue() {
+            return value;
+        }
+
+        public Object getSecondValue() {
+            return secondValue;
+        }
+
+        public boolean isNoValue() {
+            return noValue;
+        }
+
+        public boolean isSingleValue() {
+            return singleValue;
+        }
+
+        public boolean isBetweenValue() {
+            return betweenValue;
+        }
+
+        public boolean isListValue() {
+            return listValue;
+        }
+
+        public String getTypeHandler() {
+            return typeHandler;
+        }
+
+        protected Criterion(String condition) {
+            super();
+            this.condition = condition;
+            this.typeHandler = null;
+            this.noValue = true;
+        }
+
+        protected Criterion(String condition, Object value, String typeHandler) {
+            super();
+            this.condition = condition;
+            this.value = value;
+            this.typeHandler = typeHandler;
+            if (value instanceof List<?>) {
+                this.listValue = true;
+            } else {
+                this.singleValue = true;
+            }
+        }
+
+        protected Criterion(String condition, Object value) {
+            this(condition, value, null);
+        }
+
+        protected Criterion(String condition, Object value, Object secondValue, String typeHandler) {
+            super();
+            this.condition = condition;
+            this.value = value;
+            this.secondValue = secondValue;
+            this.typeHandler = typeHandler;
+            this.betweenValue = true;
+        }
+
+        protected Criterion(String condition, Object value, Object secondValue) {
+            this(condition, value, secondValue, null);
+        }
+    }
+}

+ 26 - 0
api-module/src/main/java/com/tzld/piaoquan/api/model/vo/ExternalChannelVO.java

@@ -0,0 +1,26 @@
+package com.tzld.piaoquan.api.model.vo;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+@Data
+public class ExternalChannelVO {
+
+    @ApiModelProperty(value = "rootSourceId")
+    private String rootSourceId;
+    @ApiModelProperty(value = "渠道")
+    private String channel;
+    @ApiModelProperty(value = "合作方ID")
+    private String partnerId;
+    @ApiModelProperty(value = "公众号ID/群ID/服务号ID")
+    private String accountId;
+    @ApiModelProperty(value = "创意素材ID")
+    private String creativeId;
+    @ApiModelProperty(value = "文章ID")
+    private String articleId;
+    @ApiModelProperty(value = "卡片ID")
+    private String cardId;
+    @ApiModelProperty(value = "人群包")
+    private String packageId;
+
+}

+ 9 - 0
api-module/src/main/java/com/tzld/piaoquan/api/service/ExternalService.java

@@ -0,0 +1,9 @@
+package com.tzld.piaoquan.api.service;
+
+import com.tzld.piaoquan.api.model.vo.ExternalChannelVO;
+
+public interface ExternalService {
+
+    ExternalChannelVO getChannelByRootSourceId(String rootSourceId);
+
+}

+ 135 - 0
api-module/src/main/java/com/tzld/piaoquan/api/service/impl/ExternalServiceImpl.java

@@ -0,0 +1,135 @@
+package com.tzld.piaoquan.api.service.impl;
+
+import com.alibaba.fastjson.JSONObject;
+import com.tzld.piaoquan.api.common.enums.contentplatform.ExternalChannelStatusEnum;
+import com.tzld.piaoquan.api.dao.mapper.contentplatform.ExternalChannelMapper;
+import com.tzld.piaoquan.api.model.po.contentplatform.ExternalChannel;
+import com.tzld.piaoquan.api.model.po.contentplatform.ExternalChannelExample;
+import com.tzld.piaoquan.api.model.vo.ExternalChannelVO;
+import com.tzld.piaoquan.api.service.ExternalService;
+import com.tzld.piaoquan.growth.common.utils.RedisUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+
+import java.util.Date;
+import java.util.List;
+
+@Service
+@Slf4j
+public class ExternalServiceImpl implements ExternalService {
+
+    @Autowired
+    private ExternalChannelMapper externalChannelMapper;
+
+    @Autowired
+    private RedisUtils redisUtils;
+
+    // 缓存key前缀
+    private static final String CACHE_KEY_PREFIX = "external:channel:";
+    // 执行完成记录的缓存时间
+    private static final long PROCESSED_EXPIRE_TIME = 2L * 24 * 60 * 60;
+    // 待执行/执行中记录的缓存时间
+    private static final long PENDING_EXPIRE_TIME = 30L * 60;
+    // 失败记录的缓存时间
+    private static final long FAILED_EXPIRE_TIME = 6 * 60L * 60;
+    // 未知渠道记录的缓存时间
+    private static final long UNKNOWN_CHANNEL_EXPIRE_TIME = 6 * 60L * 60;
+    // 空值缓存标记
+    private static final String NULL_CACHE_VALUE = "NULL";
+    // 失败状态缓存标记
+    private static final String FAILED_CACHE_VALUE = "FAILED";
+    // 未知渠道状态缓存标记
+    private static final String UNKNOWN_CHANNEL_CACHE_VALUE = "UNKNOWN_CHANNEL";
+
+    @Override
+    public ExternalChannelVO getChannelByRootSourceId(String rootSourceId) {
+        // 1. 先查缓存
+        String cacheKey = CACHE_KEY_PREFIX + rootSourceId;
+        String cacheValue = redisUtils.getString(cacheKey);
+        if (cacheValue != null) {
+            // 缓存命中,判断特殊状态标记
+            if (NULL_CACHE_VALUE.equals(cacheValue)) {
+                // 空值缓存,表示记录不存在但已插入待处理
+                log.info("getChannelByRootSourceId 缓存命中空值, rootSourceId={}", rootSourceId);
+                return null;
+            }
+            if (FAILED_CACHE_VALUE.equals(cacheValue)) {
+                // 失败状态缓存
+                log.info("getChannelByRootSourceId 缓存命中失败状态, rootSourceId={}", rootSourceId);
+                return null;
+            }
+            if (UNKNOWN_CHANNEL_CACHE_VALUE.equals(cacheValue)) {
+                // 未知渠道状态缓存
+                log.info("getChannelByRootSourceId 缓存命中未知渠道状态, rootSourceId={}", rootSourceId);
+                return null;
+            }
+            try {
+                ExternalChannelVO vo = JSONObject.parseObject(cacheValue, ExternalChannelVO.class);
+                log.info("getChannelByRootSourceId 缓存命中, rootSourceId={}", rootSourceId);
+                return vo;
+            } catch (Exception e) {
+                log.error("缓存解析失败, rootSourceId={}", rootSourceId, e);
+            }
+        }
+
+        // 2. 查询数据库
+        ExternalChannelExample example = new ExternalChannelExample();
+        example.createCriteria()
+                .andRootSourceIdEqualTo(rootSourceId)
+                .andIsDeleteEqualTo(0);
+        List<ExternalChannel> list = externalChannelMapper.selectByExample(example);
+
+        // 3. 有结果则处理
+        if (!CollectionUtils.isEmpty(list)) {
+            ExternalChannel record = list.get(0);
+            Integer status = record.getStatus();
+
+            // FAILED 和 UNKNOWN_CHANNEL 不返回具体对象,写入对应特殊标记
+            if (ExternalChannelStatusEnum.FAILED.getVal().equals(status)) {
+                redisUtils.setValueWithExpire(cacheKey, FAILED_CACHE_VALUE, FAILED_EXPIRE_TIME);
+                log.info("getChannelByRootSourceId 写入失败状态缓存, rootSourceId={}, expireTime={}s",
+                        rootSourceId, FAILED_EXPIRE_TIME);
+                return null;
+            }
+            if (ExternalChannelStatusEnum.UNKNOWN_CHANNEL.getVal().equals(status)) {
+                redisUtils.setValueWithExpire(cacheKey, UNKNOWN_CHANNEL_CACHE_VALUE, UNKNOWN_CHANNEL_EXPIRE_TIME);
+                log.info("getChannelByRootSourceId 写入未知渠道状态缓存, rootSourceId={}, expireTime={}s",
+                        rootSourceId, UNKNOWN_CHANNEL_EXPIRE_TIME);
+                return null;
+            }
+
+            ExternalChannelVO vo = new ExternalChannelVO();
+            BeanUtils.copyProperties(record, vo);
+
+            // 根据状态设置不同的缓存时间
+            long expireTime = ExternalChannelStatusEnum.PROCESSED.getVal().equals(status)
+                    ? PROCESSED_EXPIRE_TIME : PENDING_EXPIRE_TIME;
+
+            // 写入缓存
+            redisUtils.setValueWithExpire(cacheKey, JSONObject.toJSONString(vo), expireTime);
+            log.info("getChannelByRootSourceId 写入缓存, rootSourceId={}, status={}, expireTime={}s",
+                    rootSourceId, status, expireTime);
+            return vo;
+        }
+
+        // 4. 没有结果则插入待处理记录
+        log.info("external_channel 未找到记录,插入待处理记录, rootSourceId={}", rootSourceId);
+        ExternalChannel pendingRecord = new ExternalChannel();
+        pendingRecord.setRootSourceId(rootSourceId);
+        pendingRecord.setStatus(ExternalChannelStatusEnum.PENDING.getVal());
+        pendingRecord.setIsDelete(0);
+        pendingRecord.setCreateTime(new Date());
+        pendingRecord.setUpdateTime(new Date());
+        externalChannelMapper.insertSelective(pendingRecord);
+
+        // 空值缓存,防止缓存穿透,缓存10分钟
+        redisUtils.setValueWithExpire(cacheKey, NULL_CACHE_VALUE, PENDING_EXPIRE_TIME);
+        log.info("getChannelByRootSourceId 写入空值缓存, rootSourceId={}, expireTime={}s",
+                rootSourceId, PENDING_EXPIRE_TIME);
+
+        return null;
+    }
+}

+ 341 - 0
api-module/src/main/resources/mapper/contentplatform/ExternalChannelMapper.xml

@@ -0,0 +1,341 @@
+<?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.tzld.piaoquan.api.dao.mapper.contentplatform.ExternalChannelMapper">
+  <resultMap id="BaseResultMap" type="com.tzld.piaoquan.api.model.po.contentplatform.ExternalChannel">
+    <id column="id" jdbcType="BIGINT" property="id" />
+    <result column="root_source_id" jdbcType="VARCHAR" property="rootSourceId" />
+    <result column="channel" jdbcType="VARCHAR" property="channel" />
+    <result column="partner_id" jdbcType="VARCHAR" property="partnerId" />
+    <result column="account_id" jdbcType="VARCHAR" property="accountId" />
+    <result column="creative_id" jdbcType="VARCHAR" property="creativeId" />
+    <result column="article_id" jdbcType="VARCHAR" property="articleId" />
+    <result column="card_id" jdbcType="VARCHAR" property="cardId" />
+    <result column="package_id" jdbcType="VARCHAR" property="packageId" />
+    <result column="status" jdbcType="INTEGER" property="status" />
+    <result column="is_delete" jdbcType="INTEGER" property="isDelete" />
+    <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
+    <result column="update_time" jdbcType="TIMESTAMP" property="updateTime" />
+  </resultMap>
+  <sql id="Example_Where_Clause">
+    <where>
+      <foreach collection="oredCriteria" item="criteria" separator="or">
+        <if test="criteria.valid">
+          <trim prefix="(" prefixOverrides="and" suffix=")">
+            <foreach collection="criteria.criteria" item="criterion">
+              <choose>
+                <when test="criterion.noValue">
+                  and ${criterion.condition}
+                </when>
+                <when test="criterion.singleValue">
+                  and ${criterion.condition} #{criterion.value}
+                </when>
+                <when test="criterion.betweenValue">
+                  and ${criterion.condition} #{criterion.value} and #{criterion.secondValue}
+                </when>
+                <when test="criterion.listValue">
+                  and ${criterion.condition}
+                  <foreach close=")" collection="criterion.value" item="listItem" open="(" separator=",">
+                    #{listItem}
+                  </foreach>
+                </when>
+              </choose>
+            </foreach>
+          </trim>
+        </if>
+      </foreach>
+    </where>
+  </sql>
+  <sql id="Update_By_Example_Where_Clause">
+    <where>
+      <foreach collection="example.oredCriteria" item="criteria" separator="or">
+        <if test="criteria.valid">
+          <trim prefix="(" prefixOverrides="and" suffix=")">
+            <foreach collection="criteria.criteria" item="criterion">
+              <choose>
+                <when test="criterion.noValue">
+                  and ${criterion.condition}
+                </when>
+                <when test="criterion.singleValue">
+                  and ${criterion.condition} #{criterion.value}
+                </when>
+                <when test="criterion.betweenValue">
+                  and ${criterion.condition} #{criterion.value} and #{criterion.secondValue}
+                </when>
+                <when test="criterion.listValue">
+                  and ${criterion.condition}
+                  <foreach close=")" collection="criterion.value" item="listItem" open="(" separator=",">
+                    #{listItem}
+                  </foreach>
+                </when>
+              </choose>
+            </foreach>
+          </trim>
+        </if>
+      </foreach>
+    </where>
+  </sql>
+  <sql id="Base_Column_List">
+    id, root_source_id, channel, partner_id, account_id, creative_id, article_id, card_id, 
+    package_id, `status`, is_delete, create_time, update_time
+  </sql>
+  <select id="selectByExample" parameterType="com.tzld.piaoquan.api.model.po.contentplatform.ExternalChannelExample" resultMap="BaseResultMap">
+    select
+    <if test="distinct">
+      distinct
+    </if>
+    <include refid="Base_Column_List" />
+    from external_channel
+    <if test="_parameter != null">
+      <include refid="Example_Where_Clause" />
+    </if>
+    <if test="orderByClause != null">
+      order by ${orderByClause}
+    </if>
+    <if test="page != null">
+      limit #{page.offset} , #{page.pageSize}
+    </if>
+  </select>
+  <select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap">
+    select 
+    <include refid="Base_Column_List" />
+    from external_channel
+    where id = #{id,jdbcType=BIGINT}
+  </select>
+  <delete id="deleteByPrimaryKey" parameterType="java.lang.Long">
+    delete from external_channel
+    where id = #{id,jdbcType=BIGINT}
+  </delete>
+  <delete id="deleteByExample" parameterType="com.tzld.piaoquan.api.model.po.contentplatform.ExternalChannelExample">
+    delete from external_channel
+    <if test="_parameter != null">
+      <include refid="Example_Where_Clause" />
+    </if>
+  </delete>
+  <insert id="insert" parameterType="com.tzld.piaoquan.api.model.po.contentplatform.ExternalChannel">
+    insert into external_channel (id, root_source_id, channel, 
+      partner_id, account_id, creative_id, 
+      article_id, card_id, package_id, 
+      `status`, is_delete, create_time, 
+      update_time)
+    values (#{id,jdbcType=BIGINT}, #{rootSourceId,jdbcType=VARCHAR}, #{channel,jdbcType=VARCHAR}, 
+      #{partnerId,jdbcType=VARCHAR}, #{accountId,jdbcType=VARCHAR}, #{creativeId,jdbcType=VARCHAR}, 
+      #{articleId,jdbcType=VARCHAR}, #{cardId,jdbcType=VARCHAR}, #{packageId,jdbcType=VARCHAR}, 
+      #{status,jdbcType=INTEGER}, #{isDelete,jdbcType=INTEGER}, #{createTime,jdbcType=TIMESTAMP}, 
+      #{updateTime,jdbcType=TIMESTAMP})
+  </insert>
+  <insert id="insertSelective" parameterType="com.tzld.piaoquan.api.model.po.contentplatform.ExternalChannel">
+    insert into external_channel
+    <trim prefix="(" suffix=")" suffixOverrides=",">
+      <if test="id != null">
+        id,
+      </if>
+      <if test="rootSourceId != null">
+        root_source_id,
+      </if>
+      <if test="channel != null">
+        channel,
+      </if>
+      <if test="partnerId != null">
+        partner_id,
+      </if>
+      <if test="accountId != null">
+        account_id,
+      </if>
+      <if test="creativeId != null">
+        creative_id,
+      </if>
+      <if test="articleId != null">
+        article_id,
+      </if>
+      <if test="cardId != null">
+        card_id,
+      </if>
+      <if test="packageId != null">
+        package_id,
+      </if>
+      <if test="status != null">
+        `status`,
+      </if>
+      <if test="isDelete != null">
+        is_delete,
+      </if>
+      <if test="createTime != null">
+        create_time,
+      </if>
+      <if test="updateTime != null">
+        update_time,
+      </if>
+    </trim>
+    <trim prefix="values (" suffix=")" suffixOverrides=",">
+      <if test="id != null">
+        #{id,jdbcType=BIGINT},
+      </if>
+      <if test="rootSourceId != null">
+        #{rootSourceId,jdbcType=VARCHAR},
+      </if>
+      <if test="channel != null">
+        #{channel,jdbcType=VARCHAR},
+      </if>
+      <if test="partnerId != null">
+        #{partnerId,jdbcType=VARCHAR},
+      </if>
+      <if test="accountId != null">
+        #{accountId,jdbcType=VARCHAR},
+      </if>
+      <if test="creativeId != null">
+        #{creativeId,jdbcType=VARCHAR},
+      </if>
+      <if test="articleId != null">
+        #{articleId,jdbcType=VARCHAR},
+      </if>
+      <if test="cardId != null">
+        #{cardId,jdbcType=VARCHAR},
+      </if>
+      <if test="packageId != null">
+        #{packageId,jdbcType=VARCHAR},
+      </if>
+      <if test="status != null">
+        #{status,jdbcType=INTEGER},
+      </if>
+      <if test="isDelete != null">
+        #{isDelete,jdbcType=INTEGER},
+      </if>
+      <if test="createTime != null">
+        #{createTime,jdbcType=TIMESTAMP},
+      </if>
+      <if test="updateTime != null">
+        #{updateTime,jdbcType=TIMESTAMP},
+      </if>
+    </trim>
+  </insert>
+  <select id="countByExample" parameterType="com.tzld.piaoquan.api.model.po.contentplatform.ExternalChannelExample" resultType="java.lang.Long">
+    select count(*) from external_channel
+    <if test="_parameter != null">
+      <include refid="Example_Where_Clause" />
+    </if>
+  </select>
+  <update id="updateByExampleSelective" parameterType="map">
+    update external_channel
+    <set>
+      <if test="record.id != null">
+        id = #{record.id,jdbcType=BIGINT},
+      </if>
+      <if test="record.rootSourceId != null">
+        root_source_id = #{record.rootSourceId,jdbcType=VARCHAR},
+      </if>
+      <if test="record.channel != null">
+        channel = #{record.channel,jdbcType=VARCHAR},
+      </if>
+      <if test="record.partnerId != null">
+        partner_id = #{record.partnerId,jdbcType=VARCHAR},
+      </if>
+      <if test="record.accountId != null">
+        account_id = #{record.accountId,jdbcType=VARCHAR},
+      </if>
+      <if test="record.creativeId != null">
+        creative_id = #{record.creativeId,jdbcType=VARCHAR},
+      </if>
+      <if test="record.articleId != null">
+        article_id = #{record.articleId,jdbcType=VARCHAR},
+      </if>
+      <if test="record.cardId != null">
+        card_id = #{record.cardId,jdbcType=VARCHAR},
+      </if>
+      <if test="record.packageId != null">
+        package_id = #{record.packageId,jdbcType=VARCHAR},
+      </if>
+      <if test="record.status != null">
+        `status` = #{record.status,jdbcType=INTEGER},
+      </if>
+      <if test="record.isDelete != null">
+        is_delete = #{record.isDelete,jdbcType=INTEGER},
+      </if>
+      <if test="record.createTime != null">
+        create_time = #{record.createTime,jdbcType=TIMESTAMP},
+      </if>
+      <if test="record.updateTime != null">
+        update_time = #{record.updateTime,jdbcType=TIMESTAMP},
+      </if>
+    </set>
+    <if test="_parameter != null">
+      <include refid="Update_By_Example_Where_Clause" />
+    </if>
+  </update>
+  <update id="updateByExample" parameterType="map">
+    update external_channel
+    set id = #{record.id,jdbcType=BIGINT},
+      root_source_id = #{record.rootSourceId,jdbcType=VARCHAR},
+      channel = #{record.channel,jdbcType=VARCHAR},
+      partner_id = #{record.partnerId,jdbcType=VARCHAR},
+      account_id = #{record.accountId,jdbcType=VARCHAR},
+      creative_id = #{record.creativeId,jdbcType=VARCHAR},
+      article_id = #{record.articleId,jdbcType=VARCHAR},
+      card_id = #{record.cardId,jdbcType=VARCHAR},
+      package_id = #{record.packageId,jdbcType=VARCHAR},
+      `status` = #{record.status,jdbcType=INTEGER},
+      is_delete = #{record.isDelete,jdbcType=INTEGER},
+      create_time = #{record.createTime,jdbcType=TIMESTAMP},
+      update_time = #{record.updateTime,jdbcType=TIMESTAMP}
+    <if test="_parameter != null">
+      <include refid="Update_By_Example_Where_Clause" />
+    </if>
+  </update>
+  <update id="updateByPrimaryKeySelective" parameterType="com.tzld.piaoquan.api.model.po.contentplatform.ExternalChannel">
+    update external_channel
+    <set>
+      <if test="rootSourceId != null">
+        root_source_id = #{rootSourceId,jdbcType=VARCHAR},
+      </if>
+      <if test="channel != null">
+        channel = #{channel,jdbcType=VARCHAR},
+      </if>
+      <if test="partnerId != null">
+        partner_id = #{partnerId,jdbcType=VARCHAR},
+      </if>
+      <if test="accountId != null">
+        account_id = #{accountId,jdbcType=VARCHAR},
+      </if>
+      <if test="creativeId != null">
+        creative_id = #{creativeId,jdbcType=VARCHAR},
+      </if>
+      <if test="articleId != null">
+        article_id = #{articleId,jdbcType=VARCHAR},
+      </if>
+      <if test="cardId != null">
+        card_id = #{cardId,jdbcType=VARCHAR},
+      </if>
+      <if test="packageId != null">
+        package_id = #{packageId,jdbcType=VARCHAR},
+      </if>
+      <if test="status != null">
+        `status` = #{status,jdbcType=INTEGER},
+      </if>
+      <if test="isDelete != null">
+        is_delete = #{isDelete,jdbcType=INTEGER},
+      </if>
+      <if test="createTime != null">
+        create_time = #{createTime,jdbcType=TIMESTAMP},
+      </if>
+      <if test="updateTime != null">
+        update_time = #{updateTime,jdbcType=TIMESTAMP},
+      </if>
+    </set>
+    where id = #{id,jdbcType=BIGINT}
+  </update>
+  <update id="updateByPrimaryKey" parameterType="com.tzld.piaoquan.api.model.po.contentplatform.ExternalChannel">
+    update external_channel
+    set root_source_id = #{rootSourceId,jdbcType=VARCHAR},
+      channel = #{channel,jdbcType=VARCHAR},
+      partner_id = #{partnerId,jdbcType=VARCHAR},
+      account_id = #{accountId,jdbcType=VARCHAR},
+      creative_id = #{creativeId,jdbcType=VARCHAR},
+      article_id = #{articleId,jdbcType=VARCHAR},
+      card_id = #{cardId,jdbcType=VARCHAR},
+      package_id = #{packageId,jdbcType=VARCHAR},
+      `status` = #{status,jdbcType=INTEGER},
+      is_delete = #{isDelete,jdbcType=INTEGER},
+      create_time = #{createTime,jdbcType=TIMESTAMP},
+      update_time = #{updateTime,jdbcType=TIMESTAMP}
+    where id = #{id,jdbcType=BIGINT}
+  </update>
+</mapper>

+ 48 - 0
api-module/src/main/resources/mapper/contentplatform/ext/ExternalChannelMapperExt.xml

@@ -0,0 +1,48 @@
+<?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.tzld.piaoquan.api.dao.mapper.contentplatform.ExternalChannelMapperExt">
+
+    <resultMap id="BaseResultMap" type="com.tzld.piaoquan.api.model.po.contentplatform.ExternalChannel">
+        <id column="id" jdbcType="BIGINT" property="id" />
+        <result column="root_source_id" jdbcType="VARCHAR" property="rootSourceId" />
+        <result column="channel" jdbcType="VARCHAR" property="channel" />
+        <result column="partner_id" jdbcType="VARCHAR" property="partnerId" />
+        <result column="account_id" jdbcType="VARCHAR" property="accountId" />
+        <result column="creative_id" jdbcType="VARCHAR" property="creativeId" />
+        <result column="article_id" jdbcType="VARCHAR" property="articleId" />
+        <result column="card_id" jdbcType="VARCHAR" property="cardId" />
+        <result column="package_id" jdbcType="VARCHAR" property="packageId" />
+        <result column="status" jdbcType="INTEGER" property="status" />
+        <result column="is_delete" jdbcType="INTEGER" property="isDelete" />
+        <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
+        <result column="update_time" jdbcType="TIMESTAMP" property="updateTime" />
+    </resultMap>
+
+    <sql id="Base_Column_List">
+        id, root_source_id, channel, partner_id, account_id, creative_id, article_id, card_id, 
+        package_id, `status`, is_delete, create_time, update_time
+    </sql>
+
+    <!-- 查询待处理的记录列表(status=0),仅查询创建时间在指定时间之后的记录 -->
+    <select id="selectPendingList" resultMap="BaseResultMap">
+        select
+        <include refid="Base_Column_List" />
+        from external_channel
+        where `status` = 0
+        and is_delete = 0
+        and create_time >= #{startTime}
+        order by create_time asc
+        limit #{limit}
+    </select>
+
+    <!-- 将创建时间早于指定时间的待处理记录标记为失败 -->
+    <update id="markFailedForTimeoutRecords">
+        update external_channel
+        set `status` = 2,
+            update_time = NOW()
+        where `status` = 0
+          and is_delete = 0
+          and create_time &lt; #{timeoutTime}
+    </update>
+
+</mapper>

+ 3 - 2
api-module/src/main/resources/mybatis-generator-config.xml

@@ -65,8 +65,9 @@
 <!--        <table tableName="we_com_corp_statistics_total" domainObjectName="CorpStatisticsTotal" alias=""/>-->
 <!--        <table tableName="we_com_staff_statistics_total" domainObjectName="StaffStatisticsTotal" alias=""/>-->
 <!--        <table tableName="we_com_staff_group_statistics_total" domainObjectName="StaffGroupStatisticsTotal" alias=""/>-->
-        <table tableName="video_growth_multi_cover" domainObjectName="VideoGrowthMultiCover" alias=""/>
-        <table tableName="video_growth_multi_title" domainObjectName="VideoGrowthMultiTitle" alias=""/>
+<!--        <table tableName="video_growth_multi_cover" domainObjectName="VideoGrowthMultiCover" alias=""/>-->
+<!--        <table tableName="video_growth_multi_title" domainObjectName="VideoGrowthMultiTitle" alias=""/>-->
+        <table tableName="external_channel" domainObjectName="ExternalChannel" alias=""/>
 
     </context>