Просмотр исходного кода

Merge branch 'master' into test

wangyunpeng 3 дней назад
Родитель
Сommit
4b1ba740b1

+ 5 - 0
api-module/src/main/java/com/tzld/piaoquan/api/job/contentplatform/ContentPlatformDemandVideoJob.java

@@ -253,6 +253,11 @@ public class ContentPlatformDemandVideoJob {
                     demandVideo.setRov(videoItem.getDouble("rov"));
                     demandVideo.setMatchText(videoItem.getString("text") != null ? videoItem.getString("text") : "");
                     demandVideo.setExperimentId(videoItem.getString("experimentId"));
+                    demandVideo.setMatchExposurePv(videoItem.getLong("matchExposurePv"));
+                    demandVideo.setMatchHeadSingleReturnRate(videoItem.getDouble("matchHeadSingleReturnRate"));
+                    demandVideo.setMatchHeadDistributionSingleReturnRate(videoItem.getDouble("matchHeadDistributionSingleReturnRate"));
+                    demandVideo.setMatchGeneralizedElement(videoItem.getString("matchGeneralizedElement"));
+                    demandVideo.setMatchGeneralizedPointType(videoItem.getString("matchGeneralizedPointType"));
                     demandVideo.setStatus(1);
                     demandVideo.setCreateTimestamp(now);
                     if (Objects.isNull(demandVideo.getRov()) || demandVideo.getRov() == 0.0) {

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

@@ -45,6 +45,10 @@ public class ContentPlatformDemandVideo {
 
     private String standardElement;
 
+    private String matchGeneralizedElement;
+
+    private String matchGeneralizedPointType;
+
     private String elementDimension;
 
     private String categoryName;
@@ -97,6 +101,12 @@ public class ContentPlatformDemandVideo {
 
     private String category;
 
+    private Long matchExposurePv;
+
+    private Double matchHeadSingleReturnRate;
+
+    private Double matchHeadDistributionSingleReturnRate;
+
     public Long getId() {
         return id;
     }
@@ -273,6 +283,22 @@ public class ContentPlatformDemandVideo {
         this.standardElement = standardElement;
     }
 
+    public String getMatchGeneralizedElement() {
+        return matchGeneralizedElement;
+    }
+
+    public void setMatchGeneralizedElement(String matchGeneralizedElement) {
+        this.matchGeneralizedElement = matchGeneralizedElement;
+    }
+
+    public String getMatchGeneralizedPointType() {
+        return matchGeneralizedPointType;
+    }
+
+    public void setMatchGeneralizedPointType(String matchGeneralizedPointType) {
+        this.matchGeneralizedPointType = matchGeneralizedPointType;
+    }
+
     public String getElementDimension() {
         return elementDimension;
     }
@@ -481,6 +507,30 @@ public class ContentPlatformDemandVideo {
         this.category = category;
     }
 
+    public Long getMatchExposurePv() {
+        return matchExposurePv;
+    }
+
+    public void setMatchExposurePv(Long matchExposurePv) {
+        this.matchExposurePv = matchExposurePv;
+    }
+
+    public Double getMatchHeadSingleReturnRate() {
+        return matchHeadSingleReturnRate;
+    }
+
+    public void setMatchHeadSingleReturnRate(Double matchHeadSingleReturnRate) {
+        this.matchHeadSingleReturnRate = matchHeadSingleReturnRate;
+    }
+
+    public Double getMatchHeadDistributionSingleReturnRate() {
+        return matchHeadDistributionSingleReturnRate;
+    }
+
+    public void setMatchHeadDistributionSingleReturnRate(Double matchHeadDistributionSingleReturnRate) {
+        this.matchHeadDistributionSingleReturnRate = matchHeadDistributionSingleReturnRate;
+    }
+
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder();
@@ -509,6 +559,8 @@ public class ContentPlatformDemandVideo {
         sb.append(", demandContentTopic=").append(demandContentTopic);
         sb.append(", pointType=").append(pointType);
         sb.append(", standardElement=").append(standardElement);
+        sb.append(", matchGeneralizedElement=").append(matchGeneralizedElement);
+        sb.append(", matchGeneralizedPointType=").append(matchGeneralizedPointType);
         sb.append(", elementDimension=").append(elementDimension);
         sb.append(", categoryName=").append(categoryName);
         sb.append(", crowdCount=").append(crowdCount);
@@ -535,6 +587,9 @@ public class ContentPlatformDemandVideo {
         sb.append(", createTimestamp=").append(createTimestamp);
         sb.append(", updateTimestamp=").append(updateTimestamp);
         sb.append(", category=").append(category);
+        sb.append(", matchExposurePv=").append(matchExposurePv);
+        sb.append(", matchHeadSingleReturnRate=").append(matchHeadSingleReturnRate);
+        sb.append(", matchHeadDistributionSingleReturnRate=").append(matchHeadDistributionSingleReturnRate);
         sb.append("]");
         return sb.toString();
     }

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

@@ -1645,6 +1645,146 @@ public class ContentPlatformDemandVideoExample {
             return (Criteria) this;
         }
 
+        public Criteria andMatchGeneralizedElementIsNull() {
+            addCriterion("match_generalized_element is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchGeneralizedElementIsNotNull() {
+            addCriterion("match_generalized_element is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchGeneralizedElementEqualTo(String value) {
+            addCriterion("match_generalized_element =", value, "matchGeneralizedElement");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchGeneralizedElementNotEqualTo(String value) {
+            addCriterion("match_generalized_element <>", value, "matchGeneralizedElement");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchGeneralizedElementGreaterThan(String value) {
+            addCriterion("match_generalized_element >", value, "matchGeneralizedElement");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchGeneralizedElementGreaterThanOrEqualTo(String value) {
+            addCriterion("match_generalized_element >=", value, "matchGeneralizedElement");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchGeneralizedElementLessThan(String value) {
+            addCriterion("match_generalized_element <", value, "matchGeneralizedElement");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchGeneralizedElementLessThanOrEqualTo(String value) {
+            addCriterion("match_generalized_element <=", value, "matchGeneralizedElement");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchGeneralizedElementLike(String value) {
+            addCriterion("match_generalized_element like", value, "matchGeneralizedElement");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchGeneralizedElementNotLike(String value) {
+            addCriterion("match_generalized_element not like", value, "matchGeneralizedElement");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchGeneralizedElementIn(List<String> values) {
+            addCriterion("match_generalized_element in", values, "matchGeneralizedElement");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchGeneralizedElementNotIn(List<String> values) {
+            addCriterion("match_generalized_element not in", values, "matchGeneralizedElement");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchGeneralizedElementBetween(String value1, String value2) {
+            addCriterion("match_generalized_element between", value1, value2, "matchGeneralizedElement");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchGeneralizedElementNotBetween(String value1, String value2) {
+            addCriterion("match_generalized_element not between", value1, value2, "matchGeneralizedElement");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchGeneralizedPointTypeIsNull() {
+            addCriterion("match_generalized_point_type is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchGeneralizedPointTypeIsNotNull() {
+            addCriterion("match_generalized_point_type is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchGeneralizedPointTypeEqualTo(String value) {
+            addCriterion("match_generalized_point_type =", value, "matchGeneralizedPointType");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchGeneralizedPointTypeNotEqualTo(String value) {
+            addCriterion("match_generalized_point_type <>", value, "matchGeneralizedPointType");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchGeneralizedPointTypeGreaterThan(String value) {
+            addCriterion("match_generalized_point_type >", value, "matchGeneralizedPointType");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchGeneralizedPointTypeGreaterThanOrEqualTo(String value) {
+            addCriterion("match_generalized_point_type >=", value, "matchGeneralizedPointType");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchGeneralizedPointTypeLessThan(String value) {
+            addCriterion("match_generalized_point_type <", value, "matchGeneralizedPointType");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchGeneralizedPointTypeLessThanOrEqualTo(String value) {
+            addCriterion("match_generalized_point_type <=", value, "matchGeneralizedPointType");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchGeneralizedPointTypeLike(String value) {
+            addCriterion("match_generalized_point_type like", value, "matchGeneralizedPointType");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchGeneralizedPointTypeNotLike(String value) {
+            addCriterion("match_generalized_point_type not like", value, "matchGeneralizedPointType");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchGeneralizedPointTypeIn(List<String> values) {
+            addCriterion("match_generalized_point_type in", values, "matchGeneralizedPointType");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchGeneralizedPointTypeNotIn(List<String> values) {
+            addCriterion("match_generalized_point_type not in", values, "matchGeneralizedPointType");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchGeneralizedPointTypeBetween(String value1, String value2) {
+            addCriterion("match_generalized_point_type between", value1, value2, "matchGeneralizedPointType");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchGeneralizedPointTypeNotBetween(String value1, String value2) {
+            addCriterion("match_generalized_point_type not between", value1, value2, "matchGeneralizedPointType");
+            return (Criteria) this;
+        }
+
         public Criteria andElementDimensionIsNull() {
             addCriterion("element_dimension is null");
             return (Criteria) this;
@@ -3334,6 +3474,186 @@ public class ContentPlatformDemandVideoExample {
             addCriterion("category not between", value1, value2, "category");
             return (Criteria) this;
         }
+
+        public Criteria andMatchExposurePvIsNull() {
+            addCriterion("match_exposure_pv is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchExposurePvIsNotNull() {
+            addCriterion("match_exposure_pv is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchExposurePvEqualTo(Long value) {
+            addCriterion("match_exposure_pv =", value, "matchExposurePv");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchExposurePvNotEqualTo(Long value) {
+            addCriterion("match_exposure_pv <>", value, "matchExposurePv");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchExposurePvGreaterThan(Long value) {
+            addCriterion("match_exposure_pv >", value, "matchExposurePv");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchExposurePvGreaterThanOrEqualTo(Long value) {
+            addCriterion("match_exposure_pv >=", value, "matchExposurePv");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchExposurePvLessThan(Long value) {
+            addCriterion("match_exposure_pv <", value, "matchExposurePv");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchExposurePvLessThanOrEqualTo(Long value) {
+            addCriterion("match_exposure_pv <=", value, "matchExposurePv");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchExposurePvIn(List<Long> values) {
+            addCriterion("match_exposure_pv in", values, "matchExposurePv");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchExposurePvNotIn(List<Long> values) {
+            addCriterion("match_exposure_pv not in", values, "matchExposurePv");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchExposurePvBetween(Long value1, Long value2) {
+            addCriterion("match_exposure_pv between", value1, value2, "matchExposurePv");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchExposurePvNotBetween(Long value1, Long value2) {
+            addCriterion("match_exposure_pv not between", value1, value2, "matchExposurePv");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchHeadSingleReturnRateIsNull() {
+            addCriterion("match_head_single_return_rate is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchHeadSingleReturnRateIsNotNull() {
+            addCriterion("match_head_single_return_rate is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchHeadSingleReturnRateEqualTo(Double value) {
+            addCriterion("match_head_single_return_rate =", value, "matchHeadSingleReturnRate");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchHeadSingleReturnRateNotEqualTo(Double value) {
+            addCriterion("match_head_single_return_rate <>", value, "matchHeadSingleReturnRate");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchHeadSingleReturnRateGreaterThan(Double value) {
+            addCriterion("match_head_single_return_rate >", value, "matchHeadSingleReturnRate");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchHeadSingleReturnRateGreaterThanOrEqualTo(Double value) {
+            addCriterion("match_head_single_return_rate >=", value, "matchHeadSingleReturnRate");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchHeadSingleReturnRateLessThan(Double value) {
+            addCriterion("match_head_single_return_rate <", value, "matchHeadSingleReturnRate");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchHeadSingleReturnRateLessThanOrEqualTo(Double value) {
+            addCriterion("match_head_single_return_rate <=", value, "matchHeadSingleReturnRate");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchHeadSingleReturnRateIn(List<Double> values) {
+            addCriterion("match_head_single_return_rate in", values, "matchHeadSingleReturnRate");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchHeadSingleReturnRateNotIn(List<Double> values) {
+            addCriterion("match_head_single_return_rate not in", values, "matchHeadSingleReturnRate");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchHeadSingleReturnRateBetween(Double value1, Double value2) {
+            addCriterion("match_head_single_return_rate between", value1, value2, "matchHeadSingleReturnRate");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchHeadSingleReturnRateNotBetween(Double value1, Double value2) {
+            addCriterion("match_head_single_return_rate not between", value1, value2, "matchHeadSingleReturnRate");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchHeadDistributionSingleReturnRateIsNull() {
+            addCriterion("match_head_distribution_single_return_rate is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchHeadDistributionSingleReturnRateIsNotNull() {
+            addCriterion("match_head_distribution_single_return_rate is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchHeadDistributionSingleReturnRateEqualTo(Double value) {
+            addCriterion("match_head_distribution_single_return_rate =", value, "matchHeadDistributionSingleReturnRate");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchHeadDistributionSingleReturnRateNotEqualTo(Double value) {
+            addCriterion("match_head_distribution_single_return_rate <>", value, "matchHeadDistributionSingleReturnRate");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchHeadDistributionSingleReturnRateGreaterThan(Double value) {
+            addCriterion("match_head_distribution_single_return_rate >", value, "matchHeadDistributionSingleReturnRate");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchHeadDistributionSingleReturnRateGreaterThanOrEqualTo(Double value) {
+            addCriterion("match_head_distribution_single_return_rate >=", value, "matchHeadDistributionSingleReturnRate");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchHeadDistributionSingleReturnRateLessThan(Double value) {
+            addCriterion("match_head_distribution_single_return_rate <", value, "matchHeadDistributionSingleReturnRate");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchHeadDistributionSingleReturnRateLessThanOrEqualTo(Double value) {
+            addCriterion("match_head_distribution_single_return_rate <=", value, "matchHeadDistributionSingleReturnRate");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchHeadDistributionSingleReturnRateIn(List<Double> values) {
+            addCriterion("match_head_distribution_single_return_rate in", values, "matchHeadDistributionSingleReturnRate");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchHeadDistributionSingleReturnRateNotIn(List<Double> values) {
+            addCriterion("match_head_distribution_single_return_rate not in", values, "matchHeadDistributionSingleReturnRate");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchHeadDistributionSingleReturnRateBetween(Double value1, Double value2) {
+            addCriterion("match_head_distribution_single_return_rate between", value1, value2, "matchHeadDistributionSingleReturnRate");
+            return (Criteria) this;
+        }
+
+        public Criteria andMatchHeadDistributionSingleReturnRateNotBetween(Double value1, Double value2) {
+            addCriterion("match_head_distribution_single_return_rate not between", value1, value2, "matchHeadDistributionSingleReturnRate");
+            return (Criteria) this;
+        }
     }
 
     public static class Criteria extends GeneratedCriteria {

+ 61 - 58
api-module/src/main/java/com/tzld/piaoquan/api/service/contentplatform/impl/ContentPlatformPlanServiceImpl.java

@@ -135,6 +135,14 @@ public class ContentPlatformPlanServiceImpl implements ContentPlatformPlanServic
     @Value("${video.min.score:0.0}")
     private Double videoMinScore;
 
+    /**
+     * 曝光下发过滤阈值(全渠道、全向量召回池统一生效):match_exposure_pv 有值时要求 > 该阈值才下发。
+     * NULL(无曝光数据,如「场景已看视频」池 100% 无此字段)一律放行,不受影响。
+     * <=0 表示关闭该过滤(全放行)。Apollo 可调,改阈值不发版。
+     */
+    @Value("${demand.min.exposure.pv:2000}")
+    private Long demandMinExposurePv;
+
     @Value("${video.title.search.max.count:500}")
     private int videoTitleSearchMaxCount;
 
@@ -382,13 +390,10 @@ public class ContentPlatformPlanServiceImpl implements ContentPlatformPlanServic
                                   ContentPlatformGzhAccount account, ContentPlatformAccount loginAccount) {
         List<ContentPlatformGzhPlanVideo> existsVideo = getGzhVideoByPlanId(planId);
         List<Long> existsVideoIds = existsVideo.stream().map(ContentPlatformGzhPlanVideo::getVideoId).collect(Collectors.toList());
-        Map<Long, ContentPlatformGzhPlanVideo> existsVideoMap = existsVideo.stream()
-                .collect(Collectors.toMap(ContentPlatformGzhPlanVideo::getVideoId, item -> item));
-        for (ContentPlatformGzhPlanVideo item : existsVideo) {
-            if (!videoIds.contains(item.getVideoId())) {
-                gzhPlanVideoMapper.deleteByPrimaryKey(item.getId());
-            }
-        }
+        // 删除该计划下所有已有视频,后续统一重新插入
+        ContentPlatformGzhPlanVideoExample deleteExample = new ContentPlatformGzhPlanVideoExample();
+        deleteExample.createCriteria().andPlanIdEqualTo(planId);
+        gzhPlanVideoMapper.deleteByExample(deleteExample);
         for (GzhPlanVideoContentItemParam vo : param.getVideoList()) {
             if (!StringUtils.hasText(vo.getCustomTitle())) {
                 vo.setCustomTitle(null);
@@ -396,59 +401,40 @@ public class ContentPlatformPlanServiceImpl implements ContentPlatformPlanServic
             if (!StringUtils.hasText(vo.getCustomCover())) {
                 vo.setCustomCover(null);
             }
-            if (existsVideoIds.contains(vo.getVideoId())) {
-                ContentPlatformGzhPlanVideo item = existsVideoMap.get(vo.getVideoId());
-                item.setTitle(vo.getTitle());
-                setCustomTitle(item, vo);
-                item.setCover(vo.getCover());
-                setCustomCover(item, vo);
-                item.setCustomCoverType(vo.getCustomCoverType());
-                // 上报多标题封面
-                item.setPageUrl(setMultiTitleCoverPagePath(item));
-                item.setExperimentId(vo.getExperimentId());
-                if (StringUtils.hasText(item.getPageUrl())) {
-                    String rootSourceId = MessageUtil.getRootSourceId(item.getPageUrl());
-                    if (StringUtils.hasText(rootSourceId)) {
-                        item.setRootSourceId(rootSourceId);
-                    }
-                }
-                gzhPlanVideoMapper.updateByPrimaryKey(item);
-            } else {
-                ContentPlatformGzhPlanVideo item = new ContentPlatformGzhPlanVideo();
-                item.setPlanId(planId);
-                item.setVideoId(vo.getVideoId());
-                item.setTitle(vo.getTitle());
-                setCustomTitle(item, vo);
-                item.setCover(vo.getCover());
-                setCustomCover(item, vo);
-                item.setCustomCoverType(vo.getCustomCoverType());
-                item.setVideo(vo.getVideo());
-                if (param.getType() == ContentPlatformGzhPlanTypeEnum.FWH_PUSH.getVal()) {
-                    String response = touLiuHttpClient.sendAdFlowAddRequest(GET_SMALL_PAGE_URL, String.valueOf(vo.getVideoId()), "fwhdyy",
-                            loginAccount.getChannel(), "自动", "公众号", "文章插小程序", "文字和小程序", account.getGhId(), vo.getExperimentId());
-                    JSONObject json = JSONObject.parseObject(response);
-                    SmallPageUrlDetail smallPageUrlDetail = json.getJSONObject("data").toJavaObject(SmallPageUrlDetail.class);
-                    item.setPageUrl(smallPageUrlDetail.getUrl());
-                } else if (param.getType() == ContentPlatformGzhPlanTypeEnum.GZH_PUSH.getVal()) {
-                    String response = touLiuHttpClient.sendAdFlowAddRequest(GET_SMALL_PAGE_URL, String.valueOf(vo.getVideoId()), "longArticles_outer",
-                            loginAccount.getChannel(), "自动", "公众号", "文章插小程序", "小程序", account.getGhId(), vo.getExperimentId());
-                    JSONObject json = JSONObject.parseObject(response);
-                    SmallPageUrlDetail smallPageUrlDetail = json.getJSONObject("data").toJavaObject(SmallPageUrlDetail.class);
-                    item.setPageUrl(smallPageUrlDetail.getUrl());
-                }
-                // 上报多标题封面
-                item.setPageUrl(setMultiTitleCoverPagePath(item));
-                item.setExperimentId(vo.getExperimentId());
-                if (StringUtils.hasText(item.getPageUrl())) {
-                    String rootSourceId = MessageUtil.getRootSourceId(item.getPageUrl());
-                    if (StringUtils.hasText(rootSourceId)) {
-                        item.setRootSourceId(rootSourceId);
-                    }
+            ContentPlatformGzhPlanVideo item = new ContentPlatformGzhPlanVideo();
+            item.setPlanId(planId);
+            item.setVideoId(vo.getVideoId());
+            item.setTitle(vo.getTitle());
+            setCustomTitle(item, vo);
+            item.setCover(vo.getCover());
+            setCustomCover(item, vo);
+            item.setCustomCoverType(vo.getCustomCoverType());
+            item.setVideo(vo.getVideo());
+            if (param.getType() == ContentPlatformGzhPlanTypeEnum.FWH_PUSH.getVal()) {
+                String response = touLiuHttpClient.sendAdFlowAddRequest(GET_SMALL_PAGE_URL, String.valueOf(vo.getVideoId()), "fwhdyy",
+                        loginAccount.getChannel(), "自动", "公众号", "文章插小程序", "文字和小程序", account.getGhId(), vo.getExperimentId());
+                JSONObject json = JSONObject.parseObject(response);
+                SmallPageUrlDetail smallPageUrlDetail = json.getJSONObject("data").toJavaObject(SmallPageUrlDetail.class);
+                item.setPageUrl(smallPageUrlDetail.getUrl());
+            } else if (param.getType() == ContentPlatformGzhPlanTypeEnum.GZH_PUSH.getVal()) {
+                String response = touLiuHttpClient.sendAdFlowAddRequest(GET_SMALL_PAGE_URL, String.valueOf(vo.getVideoId()), "longArticles_outer",
+                        loginAccount.getChannel(), "自动", "公众号", "文章插小程序", "小程序", account.getGhId(), vo.getExperimentId());
+                JSONObject json = JSONObject.parseObject(response);
+                SmallPageUrlDetail smallPageUrlDetail = json.getJSONObject("data").toJavaObject(SmallPageUrlDetail.class);
+                item.setPageUrl(smallPageUrlDetail.getUrl());
+            }
+            // 上报多标题封面
+            item.setPageUrl(setMultiTitleCoverPagePath(item));
+            item.setExperimentId(vo.getExperimentId());
+            if (StringUtils.hasText(item.getPageUrl())) {
+                String rootSourceId = MessageUtil.getRootSourceId(item.getPageUrl());
+                if (StringUtils.hasText(rootSourceId)) {
+                    item.setRootSourceId(rootSourceId);
                 }
-                item.setCreateAccountId(loginAccount.getId());
-                item.setCreateTimestamp(System.currentTimeMillis());
-                gzhPlanVideoMapper.insert(item);
             }
+            item.setCreateAccountId(loginAccount.getId());
+            item.setCreateTimestamp(System.currentTimeMillis());
+            gzhPlanVideoMapper.insert(item);
         }
         if (CollectionUtils.isNotEmpty(param.getVideoList())) {
             saveChangeVideoLog(planId, existsVideoIds, param.getVideoList());
@@ -725,6 +711,19 @@ public class ContentPlatformPlanServiceImpl implements ContentPlatformPlanServic
                 : Arrays.asList("早中晚好", "节日祝福");
     }
 
+    /**
+     * 曝光下发过滤的纯逻辑(可单测,全渠道统一口径):
+     *   - minExposurePv 为 null 或 <=0:关闭过滤,全放行;
+     *   - match_exposure_pv 为 NULL(无曝光数据,如「场景已看视频」池):放行,不受影响;
+     *   - 有曝光数据:要求 > minExposurePv 才下发。
+     */
+    static boolean passesExposureFilter(Long matchExposurePv, Long minExposurePv) {
+        if (minExposurePv == null || minExposurePv <= 0) {
+            return true;
+        }
+        return matchExposurePv == null || matchExposurePv > minExposurePv;
+    }
+
     @Override
     public Page<VideoContentItemVO> getVideoContentList(VideoContentListParam param) {
         ContentPlatformAccount user = LoginUserContext.getUser();
@@ -1053,6 +1052,8 @@ public class ContentPlatformPlanServiceImpl implements ContentPlatformPlanServic
         for (ContentPlatformDemandVideo r : bestPerVideo.values()) {
             if (r.getRov() == null || r.getRov() <= 0) continue;
             if (r.getSceneSumRov() == null || r.getSceneSumRov() < PRIOR_SCENE_MIN_SUM_ROV) continue;
+            // 曝光下发过滤:场景已看视频池 match_exposure_pv 100% 为 NULL,此处恒放行;保留以统一全池口径。
+            if (!passesExposureFilter(r.getMatchExposurePv(), demandMinExposurePv)) continue;
             filtered.add(r);
         }
         // 3. 按 sceneSumRov DESC 排序,次级 total_rov DESC
@@ -1117,6 +1118,7 @@ public class ContentPlatformPlanServiceImpl implements ContentPlatformPlanServic
 
         rows = rows.stream()
                 .filter(r -> r.getRov() != null && r.getRov() >= DEMAND_MIN_ROV)
+                .filter(r -> passesExposureFilter(r.getMatchExposurePv(), demandMinExposurePv))
                 .collect(Collectors.toList());
 
         Function<ContentPlatformDemandVideo, String> keyFn = r ->
@@ -1197,6 +1199,7 @@ public class ContentPlatformPlanServiceImpl implements ContentPlatformPlanServic
         // 近 7 日 rov 下限,与 prior 池一致(DEMAND_MIN_ROV,统一到 0.03)
         rows = rows.stream()
                 .filter(r -> r.getRov() != null && r.getRov() >= DEMAND_MIN_ROV)
+                .filter(r -> passesExposureFilter(r.getMatchExposurePv(), demandMinExposurePv))
                 .collect(Collectors.toList());
 
         Function<ContentPlatformDemandVideo, String> keyFn = r ->

+ 30 - 8
api-module/src/main/java/com/tzld/piaoquan/api/service/strategy/impl/BuckStrategyV1.java

@@ -10,9 +10,11 @@ import com.tzld.piaoquan.api.model.bo.*;
 import com.tzld.piaoquan.api.model.po.AlgGhAutoreplyVideoRankData;
 import com.tzld.piaoquan.api.model.po.AlgGhAutoreplyVideoRankDataExample;
 import com.tzld.piaoquan.api.model.po.GhDetailExt;
+import com.tzld.piaoquan.api.model.po.contentplatform.ContentPlatformGzhPlanVideo;
 import com.tzld.piaoquan.api.model.vo.VideoCharacteristicVO;
 import com.tzld.piaoquan.api.service.GhDetailService;
 import com.tzld.piaoquan.api.service.VideoMultiService;
+import com.tzld.piaoquan.api.service.contentplatform.ContentPlatformPlanService;
 import com.tzld.piaoquan.api.service.strategy.ReplyStrategyService;
 import com.tzld.piaoquan.growth.common.common.enums.GhTypeEnum;
 import com.tzld.piaoquan.growth.common.common.enums.StrategyStatusEnum;
@@ -85,6 +87,8 @@ public class BuckStrategyV1 implements ReplyStrategyService {
     private VideoMultiService videoMultiService;
     @Autowired
     private GhDetailService ghDetailService;
+    @Autowired
+    private ContentPlatformPlanService contentPlatformPlanService;
 
     @Autowired
     private RedisUtils redisUtils;
@@ -105,9 +109,10 @@ public class BuckStrategyV1 implements ReplyStrategyService {
             // 1 处理文章--算法引擎--排序文章数据
 //            getWenzhangData();
             // 2 处理小程序--读取离线数据表--获取策略排序小程序数据
-            List<CgiReplyBucketData> smallDataCgiReplyList = readStrategyOrderSmallData(keyedSet, bucketDataParam);
+            Map<Long, String> videoTestIdMap = new HashMap<>();
+            List<CgiReplyBucketData> smallDataCgiReplyList = readStrategyOrderSmallData(keyedSet, bucketDataParam, videoTestIdMap);
             // 2.1 获取小程序落地页地址 http调用
-            smallDataCgiReplyList = setSmallPageUrl(smallDataCgiReplyList);
+            smallDataCgiReplyList = setSmallPageUrl(smallDataCgiReplyList, videoTestIdMap);
             log.info(JSON.toJSONString(smallDataCgiReplyList));
             // 3 入库读表
             insertSmallData(smallDataCgiReplyList, keyedSet, bucketDataParam);
@@ -351,7 +356,8 @@ public class BuckStrategyV1 implements ReplyStrategyService {
         }
     }
 
-    private List<CgiReplyBucketData> setSmallPageUrl(List<CgiReplyBucketData> smallDataCgiReplyList) {
+    private List<CgiReplyBucketData> setSmallPageUrl(List<CgiReplyBucketData> smallDataCgiReplyList,
+                                                     Map<Long, String> videoTestIdMap) {
         if (CollectionUtils.isEmpty(smallDataCgiReplyList)) {
             return smallDataCgiReplyList;
         }
@@ -393,7 +399,7 @@ public class BuckStrategyV1 implements ReplyStrategyService {
             if (CollectionUtils.isEmpty(cgiReplyBucketData)) {
                 // 库里不存在,调用新生成
                 String response = touLiuHttpClient.sendAdFlowAddRequest(GET_SMALL_PAGE_URL, videoId, putScene, channel,
-                        "自动", "公众号", "自动回复小程序", "位置" + sort, ghId, null);
+                        "自动", "公众号", "自动回复小程序", "位置" + sort, ghId, videoTestIdMap.get(Long.valueOf(videoId)));
                 JSONObject jsonObject = JSON.parseObject(response);
                 if (jsonObject.getInteger("code").equals(0)) {
                     smallPageUrlDetail = jsonObject.getObject("data", SmallPageUrlDetail.class);
@@ -426,11 +432,12 @@ public class BuckStrategyV1 implements ReplyStrategyService {
     }
 
 
-    private List<CgiReplyBucketData> readStrategyOrderSmallData(Set<String> keyedSet, BucketDataParam bucketDataParam) {
+    private List<CgiReplyBucketData> readStrategyOrderSmallData(Set<String> keyedSet, BucketDataParam bucketDataParam,
+                                                                Map<Long, String> videoTestIdMap) {
         List<CgiReplyBucketData> result = new ArrayList<>();
         if (Objects.equals(StrategyStatusEnum.DEFAULT.status, bucketDataParam.getStrategyStatus())) {
             for (String key : keyedSet) {
-                List<CgiReplyBucketData> defaultData = getDefaultData(bucketDataParam, key);
+                List<CgiReplyBucketData> defaultData = getDefaultData(bucketDataParam, key, videoTestIdMap);
                 if (!CollectionUtils.isEmpty(defaultData)) {
                     result.addAll(defaultData);
                 }
@@ -448,7 +455,7 @@ public class BuckStrategyV1 implements ReplyStrategyService {
         } else if (Objects.equals(StrategyStatusEnum.MIXED_STRATEGY.status, bucketDataParam.getStrategyStatus())) {
             for (String key : keyedSet) {
                 if (key.equals(MANUAL)) {
-                    List<CgiReplyBucketData> defaultData = getDefaultData(bucketDataParam, key);
+                    List<CgiReplyBucketData> defaultData = getDefaultData(bucketDataParam, key, videoTestIdMap);
                     if (!CollectionUtils.isEmpty(defaultData)) {
                         result.addAll(defaultData);
                     }
@@ -467,7 +474,8 @@ public class BuckStrategyV1 implements ReplyStrategyService {
     }
 
 
-    private List<CgiReplyBucketData> getDefaultData(BucketDataParam bucketDataParam, String key) {
+    private List<CgiReplyBucketData> getDefaultData(BucketDataParam bucketDataParam, String key,
+                                                    Map<Long, String> videoTestIdMap) {
         List<CgiReplyBucketData> result = new ArrayList<>();
         if (CollectionUtils.isEmpty(bucketDataParam.getVideos()) && CollectionUtils.isEmpty(bucketDataParam.getMiniPageDatas())) {
             return result;
@@ -500,6 +508,9 @@ public class BuckStrategyV1 implements ReplyStrategyService {
             String batchId1 = UUID.randomUUID().toString();
             int totalSize1 = bucketDataParam.getMiniPageDatas().size();
             Map<Long, VideoDetail> videoDetailMap = touLiuHttpClient.getVideoDetailRequest(bucketDataParam.getVideos());
+            List<ContentPlatformGzhPlanVideo> gzhPlanVideoList = contentPlatformPlanService.getGzhPlanVideoListByCooperateAccountId(bucketDataParam.getGhId());
+            Map<Long, ContentPlatformGzhPlanVideo> gzhPlanVideoMap = gzhPlanVideoList.stream()
+                    .collect(Collectors.toMap(ContentPlatformGzhPlanVideo::getVideoId, x -> x, (a, b) -> b));
             for (int i = 0; i < bucketDataParam.getMiniPageDatas().size(); i++) {
                 int sort = i + 1;
                 Long videoId = bucketDataParam.getVideos().get(i);
@@ -525,6 +536,10 @@ public class BuckStrategyV1 implements ReplyStrategyService {
                                 "?x-oss-process=image/resize,m_fill,w_600,h_480,limit_0/format,jpg/watermark,image_eXNoL3BpYy93YXRlcm1hcmtlci9pY29uX3BsYXlfd2hpdGUucG5nP3gtb3NzLXByb2Nlc3M9aW1hZ2UvcmVzaXplLHdfMTQ0,g_center");
                     }
                 }
+                ContentPlatformGzhPlanVideo gzhPlanVideo = gzhPlanVideoMap.get(videoId);
+                if (Objects.nonNull(gzhPlanVideo) && Objects.nonNull(videoDetail)) {
+                    videoTestIdMap.put(gzhPlanVideo.getVideoId(), gzhPlanVideo.getExperimentId());
+                }
                 cgiReplyBucketData.setTitle(miniPageData.getTitle());
                 cgiReplyBucketData.setMiniPagePath(miniPageData.getPage());
                 cgiReplyBucketData.setMiniAppId(SMALL_APP_Id);
@@ -553,6 +568,9 @@ public class BuckStrategyV1 implements ReplyStrategyService {
             String batchId2 = UUID.randomUUID().toString();
             int totalSize2 = bucketDataParam.getVideos().size();
             Map<Long, VideoDetail> videoDetailMap = touLiuHttpClient.getVideoDetailRequest(bucketDataParam.getVideos());
+            List<ContentPlatformGzhPlanVideo> gzhPlanVideoList = contentPlatformPlanService.getGzhPlanVideoListByCooperateAccountId(bucketDataParam.getGhId());
+            Map<Long, ContentPlatformGzhPlanVideo> gzhPlanVideoMap = gzhPlanVideoList.stream()
+                    .collect(Collectors.toMap(ContentPlatformGzhPlanVideo::getVideoId, x -> x, (a, b) -> b));
             for (int i = 0; i < bucketDataParam.getVideos().size(); i++) {
                 int sort = i + 1;
                 Long videoId = bucketDataParam.getVideos().get(i);
@@ -564,6 +582,10 @@ public class BuckStrategyV1 implements ReplyStrategyService {
                 cgiReplyBucketData.setGhId(bucketDataParam.getGhId());
                 cgiReplyBucketData.setMsgType(1);
                 VideoDetail videoDetail = videoDetailMap.get(videoId);
+                ContentPlatformGzhPlanVideo gzhPlanVideo = gzhPlanVideoMap.get(videoId);
+                if (Objects.nonNull(gzhPlanVideo) && Objects.nonNull(videoDetail)) {
+                    videoTestIdMap.put(gzhPlanVideo.getVideoId(), gzhPlanVideo.getExperimentId());
+                }
                 if (videoDetail != null && StringUtils.isNotEmpty(videoDetail.getCover())) {
                     cgiReplyBucketData.setCoverUrl(videoDetail.getCover());
                 }

+ 93 - 13
api-module/src/main/resources/mapper/contentplatform/ContentPlatformDemandVideoMapper.xml

@@ -24,6 +24,8 @@
     <result column="demand_content_topic" jdbcType="VARCHAR" property="demandContentTopic" />
     <result column="point_type" jdbcType="VARCHAR" property="pointType" />
     <result column="standard_element" jdbcType="VARCHAR" property="standardElement" />
+    <result column="match_generalized_element" jdbcType="VARCHAR" property="matchGeneralizedElement" />
+    <result column="match_generalized_point_type" jdbcType="VARCHAR" property="matchGeneralizedPointType" />
     <result column="element_dimension" jdbcType="VARCHAR" property="elementDimension" />
     <result column="category_name" jdbcType="VARCHAR" property="categoryName" />
     <result column="crowd_count" jdbcType="INTEGER" property="crowdCount" />
@@ -50,6 +52,9 @@
     <result column="create_timestamp" jdbcType="BIGINT" property="createTimestamp" />
     <result column="update_timestamp" jdbcType="BIGINT" property="updateTimestamp" />
     <result column="category" jdbcType="VARCHAR" property="category" />
+    <result column="match_exposure_pv" jdbcType="BIGINT" property="matchExposurePv" />
+    <result column="match_head_single_return_rate" jdbcType="DOUBLE" property="matchHeadSingleReturnRate" />
+    <result column="match_head_distribution_single_return_rate" jdbcType="DOUBLE" property="matchHeadDistributionSingleReturnRate" />
   </resultMap>
   <sql id="Example_Where_Clause">
     <where>
@@ -113,11 +118,12 @@
     id, dt, online_action, channel_name, crowd_segment, channel_level3, demand_id, crowd_package, 
     conversion_target, partner, account, scene_value, demand_strategy, drive_dimension_time, 
     dimension, demand_filter_sort_strategy, demand_type, demand_content_id, demand_content_title, 
-    demand_content_topic, point_type, standard_element, element_dimension, category_name, 
-    crowd_count, video_count, visit_uv, uv_ratio, total_rov, match_experiment_id, match_method, 
-    match_video_filter, match_sort, video_id, config_code, score, sim, rov, match_text, 
-    title, cover, video, experiment_id, scene_sum_rov, `status`, create_timestamp, update_timestamp, 
-    category
+    demand_content_topic, point_type, standard_element, match_generalized_element, match_generalized_point_type, 
+    element_dimension, category_name, crowd_count, video_count, visit_uv, uv_ratio, total_rov, 
+    match_experiment_id, match_method, match_video_filter, match_sort, video_id, config_code, 
+    score, sim, rov, match_text, title, cover, video, experiment_id, scene_sum_rov, `status`, 
+    create_timestamp, update_timestamp, category, match_exposure_pv, match_head_single_return_rate, 
+    match_head_distribution_single_return_rate
   </sql>
   <select id="selectByExample" parameterType="com.tzld.piaoquan.api.model.po.contentplatform.ContentPlatformDemandVideoExample" resultMap="BaseResultMap">
     select
@@ -160,7 +166,8 @@
       demand_strategy, drive_dimension_time, dimension, 
       demand_filter_sort_strategy, demand_type, demand_content_id, 
       demand_content_title, demand_content_topic, 
-      point_type, standard_element, element_dimension, 
+      point_type, standard_element, match_generalized_element, 
+      match_generalized_point_type, element_dimension, 
       category_name, crowd_count, video_count, 
       visit_uv, uv_ratio, total_rov, 
       match_experiment_id, match_method, match_video_filter, 
@@ -168,8 +175,9 @@
       score, sim, rov, match_text, 
       title, cover, video, 
       experiment_id, scene_sum_rov, `status`, 
-      create_timestamp, update_timestamp, category
-      )
+      create_timestamp, update_timestamp, category, 
+      match_exposure_pv, match_head_single_return_rate, 
+      match_head_distribution_single_return_rate)
     values (#{id,jdbcType=BIGINT}, #{dt,jdbcType=VARCHAR}, #{onlineAction,jdbcType=VARCHAR}, 
       #{channelName,jdbcType=VARCHAR}, #{crowdSegment,jdbcType=VARCHAR}, #{channelLevel3,jdbcType=VARCHAR}, 
       #{demandId,jdbcType=VARCHAR}, #{crowdPackage,jdbcType=VARCHAR}, #{conversionTarget,jdbcType=VARCHAR}, 
@@ -177,7 +185,8 @@
       #{demandStrategy,jdbcType=VARCHAR}, #{driveDimensionTime,jdbcType=VARCHAR}, #{dimension,jdbcType=VARCHAR}, 
       #{demandFilterSortStrategy,jdbcType=VARCHAR}, #{demandType,jdbcType=VARCHAR}, #{demandContentId,jdbcType=VARCHAR}, 
       #{demandContentTitle,jdbcType=VARCHAR}, #{demandContentTopic,jdbcType=VARCHAR}, 
-      #{pointType,jdbcType=VARCHAR}, #{standardElement,jdbcType=VARCHAR}, #{elementDimension,jdbcType=VARCHAR}, 
+      #{pointType,jdbcType=VARCHAR}, #{standardElement,jdbcType=VARCHAR}, #{matchGeneralizedElement,jdbcType=VARCHAR}, 
+      #{matchGeneralizedPointType,jdbcType=VARCHAR}, #{elementDimension,jdbcType=VARCHAR}, 
       #{categoryName,jdbcType=VARCHAR}, #{crowdCount,jdbcType=INTEGER}, #{videoCount,jdbcType=INTEGER}, 
       #{visitUv,jdbcType=BIGINT}, #{uvRatio,jdbcType=DOUBLE}, #{totalRov,jdbcType=DOUBLE}, 
       #{matchExperimentId,jdbcType=VARCHAR}, #{matchMethod,jdbcType=VARCHAR}, #{matchVideoFilter,jdbcType=VARCHAR}, 
@@ -185,8 +194,9 @@
       #{score,jdbcType=DOUBLE}, #{sim,jdbcType=DOUBLE}, #{rov,jdbcType=DOUBLE}, #{matchText,jdbcType=VARCHAR}, 
       #{title,jdbcType=VARCHAR}, #{cover,jdbcType=VARCHAR}, #{video,jdbcType=VARCHAR}, 
       #{experimentId,jdbcType=VARCHAR}, #{sceneSumRov,jdbcType=DOUBLE}, #{status,jdbcType=INTEGER}, 
-      #{createTimestamp,jdbcType=BIGINT}, #{updateTimestamp,jdbcType=BIGINT}, #{category,jdbcType=VARCHAR}
-      )
+      #{createTimestamp,jdbcType=BIGINT}, #{updateTimestamp,jdbcType=BIGINT}, #{category,jdbcType=VARCHAR}, 
+      #{matchExposurePv,jdbcType=BIGINT}, #{matchHeadSingleReturnRate,jdbcType=DOUBLE}, 
+      #{matchHeadDistributionSingleReturnRate,jdbcType=DOUBLE})
   </insert>
   <insert id="insertSelective" parameterType="com.tzld.piaoquan.api.model.po.contentplatform.ContentPlatformDemandVideo">
     insert into content_platform_demand_video
@@ -257,6 +267,12 @@
       <if test="standardElement != null">
         standard_element,
       </if>
+      <if test="matchGeneralizedElement != null">
+        match_generalized_element,
+      </if>
+      <if test="matchGeneralizedPointType != null">
+        match_generalized_point_type,
+      </if>
       <if test="elementDimension != null">
         element_dimension,
       </if>
@@ -335,6 +351,15 @@
       <if test="category != null">
         category,
       </if>
+      <if test="matchExposurePv != null">
+        match_exposure_pv,
+      </if>
+      <if test="matchHeadSingleReturnRate != null">
+        match_head_single_return_rate,
+      </if>
+      <if test="matchHeadDistributionSingleReturnRate != null">
+        match_head_distribution_single_return_rate,
+      </if>
     </trim>
     <trim prefix="values (" suffix=")" suffixOverrides=",">
       <if test="id != null">
@@ -403,6 +428,12 @@
       <if test="standardElement != null">
         #{standardElement,jdbcType=VARCHAR},
       </if>
+      <if test="matchGeneralizedElement != null">
+        #{matchGeneralizedElement,jdbcType=VARCHAR},
+      </if>
+      <if test="matchGeneralizedPointType != null">
+        #{matchGeneralizedPointType,jdbcType=VARCHAR},
+      </if>
       <if test="elementDimension != null">
         #{elementDimension,jdbcType=VARCHAR},
       </if>
@@ -481,6 +512,15 @@
       <if test="category != null">
         #{category,jdbcType=VARCHAR},
       </if>
+      <if test="matchExposurePv != null">
+        #{matchExposurePv,jdbcType=BIGINT},
+      </if>
+      <if test="matchHeadSingleReturnRate != null">
+        #{matchHeadSingleReturnRate,jdbcType=DOUBLE},
+      </if>
+      <if test="matchHeadDistributionSingleReturnRate != null">
+        #{matchHeadDistributionSingleReturnRate,jdbcType=DOUBLE},
+      </if>
     </trim>
   </insert>
   <select id="countByExample" parameterType="com.tzld.piaoquan.api.model.po.contentplatform.ContentPlatformDemandVideoExample" resultType="java.lang.Long">
@@ -558,6 +598,12 @@
       <if test="record.standardElement != null">
         standard_element = #{record.standardElement,jdbcType=VARCHAR},
       </if>
+      <if test="record.matchGeneralizedElement != null">
+        match_generalized_element = #{record.matchGeneralizedElement,jdbcType=VARCHAR},
+      </if>
+      <if test="record.matchGeneralizedPointType != null">
+        match_generalized_point_type = #{record.matchGeneralizedPointType,jdbcType=VARCHAR},
+      </if>
       <if test="record.elementDimension != null">
         element_dimension = #{record.elementDimension,jdbcType=VARCHAR},
       </if>
@@ -636,6 +682,15 @@
       <if test="record.category != null">
         category = #{record.category,jdbcType=VARCHAR},
       </if>
+      <if test="record.matchExposurePv != null">
+        match_exposure_pv = #{record.matchExposurePv,jdbcType=BIGINT},
+      </if>
+      <if test="record.matchHeadSingleReturnRate != null">
+        match_head_single_return_rate = #{record.matchHeadSingleReturnRate,jdbcType=DOUBLE},
+      </if>
+      <if test="record.matchHeadDistributionSingleReturnRate != null">
+        match_head_distribution_single_return_rate = #{record.matchHeadDistributionSingleReturnRate,jdbcType=DOUBLE},
+      </if>
     </set>
     <if test="_parameter != null">
       <include refid="Update_By_Example_Where_Clause" />
@@ -665,6 +720,8 @@
       demand_content_topic = #{record.demandContentTopic,jdbcType=VARCHAR},
       point_type = #{record.pointType,jdbcType=VARCHAR},
       standard_element = #{record.standardElement,jdbcType=VARCHAR},
+      match_generalized_element = #{record.matchGeneralizedElement,jdbcType=VARCHAR},
+      match_generalized_point_type = #{record.matchGeneralizedPointType,jdbcType=VARCHAR},
       element_dimension = #{record.elementDimension,jdbcType=VARCHAR},
       category_name = #{record.categoryName,jdbcType=VARCHAR},
       crowd_count = #{record.crowdCount,jdbcType=INTEGER},
@@ -690,7 +747,10 @@
       `status` = #{record.status,jdbcType=INTEGER},
       create_timestamp = #{record.createTimestamp,jdbcType=BIGINT},
       update_timestamp = #{record.updateTimestamp,jdbcType=BIGINT},
-      category = #{record.category,jdbcType=VARCHAR}
+      category = #{record.category,jdbcType=VARCHAR},
+      match_exposure_pv = #{record.matchExposurePv,jdbcType=BIGINT},
+      match_head_single_return_rate = #{record.matchHeadSingleReturnRate,jdbcType=DOUBLE},
+      match_head_distribution_single_return_rate = #{record.matchHeadDistributionSingleReturnRate,jdbcType=DOUBLE}
     <if test="_parameter != null">
       <include refid="Update_By_Example_Where_Clause" />
     </if>
@@ -761,6 +821,12 @@
       <if test="standardElement != null">
         standard_element = #{standardElement,jdbcType=VARCHAR},
       </if>
+      <if test="matchGeneralizedElement != null">
+        match_generalized_element = #{matchGeneralizedElement,jdbcType=VARCHAR},
+      </if>
+      <if test="matchGeneralizedPointType != null">
+        match_generalized_point_type = #{matchGeneralizedPointType,jdbcType=VARCHAR},
+      </if>
       <if test="elementDimension != null">
         element_dimension = #{elementDimension,jdbcType=VARCHAR},
       </if>
@@ -839,6 +905,15 @@
       <if test="category != null">
         category = #{category,jdbcType=VARCHAR},
       </if>
+      <if test="matchExposurePv != null">
+        match_exposure_pv = #{matchExposurePv,jdbcType=BIGINT},
+      </if>
+      <if test="matchHeadSingleReturnRate != null">
+        match_head_single_return_rate = #{matchHeadSingleReturnRate,jdbcType=DOUBLE},
+      </if>
+      <if test="matchHeadDistributionSingleReturnRate != null">
+        match_head_distribution_single_return_rate = #{matchHeadDistributionSingleReturnRate,jdbcType=DOUBLE},
+      </if>
     </set>
     where id = #{id,jdbcType=BIGINT}
   </update>
@@ -865,6 +940,8 @@
       demand_content_topic = #{demandContentTopic,jdbcType=VARCHAR},
       point_type = #{pointType,jdbcType=VARCHAR},
       standard_element = #{standardElement,jdbcType=VARCHAR},
+      match_generalized_element = #{matchGeneralizedElement,jdbcType=VARCHAR},
+      match_generalized_point_type = #{matchGeneralizedPointType,jdbcType=VARCHAR},
       element_dimension = #{elementDimension,jdbcType=VARCHAR},
       category_name = #{categoryName,jdbcType=VARCHAR},
       crowd_count = #{crowdCount,jdbcType=INTEGER},
@@ -890,7 +967,10 @@
       `status` = #{status,jdbcType=INTEGER},
       create_timestamp = #{createTimestamp,jdbcType=BIGINT},
       update_timestamp = #{updateTimestamp,jdbcType=BIGINT},
-      category = #{category,jdbcType=VARCHAR}
+      category = #{category,jdbcType=VARCHAR},
+      match_exposure_pv = #{matchExposurePv,jdbcType=BIGINT},
+      match_head_single_return_rate = #{matchHeadSingleReturnRate,jdbcType=DOUBLE},
+      match_head_distribution_single_return_rate = #{matchHeadDistributionSingleReturnRate,jdbcType=DOUBLE}
     where id = #{id,jdbcType=BIGINT}
   </update>
 </mapper>

+ 29 - 18
api-module/src/main/resources/mapper/contentplatform/ext/ContentPlatformDemandVideoMapperExt.xml

@@ -5,23 +5,28 @@
     <insert id="batchInsert" parameterType="java.util.List">
         INSERT INTO content_platform_demand_video
         (dt, channel_name, crowd_segment, dimension, point_type, standard_element, element_dimension,
-         category_name, category, demand_id, crowd_package, conversion_target, partner, account, scene_value,
-         demand_strategy, drive_dimension_time, demand_filter_sort_strategy, demand_type,
-         demand_content_id, demand_content_title, demand_content_topic,
-         crowd_count, video_count, visit_uv, uv_ratio, total_rov, online_action, match_experiment_id,
-         match_method, match_video_filter, match_sort,
-         video_id, config_code, score, sim, rov,
-         match_text, title, cover, video, experiment_id, scene_sum_rov, channel_level3, status, create_timestamp, update_timestamp)
+        category_name, category, demand_id, crowd_package, conversion_target, partner, account, scene_value,
+        demand_strategy, drive_dimension_time, demand_filter_sort_strategy, demand_type,
+        demand_content_id, demand_content_title, demand_content_topic, crowd_count, video_count, visit_uv, uv_ratio,
+        total_rov, online_action, match_experiment_id, match_method, match_video_filter, match_sort,
+        video_id, config_code, score, sim, rov, match_text, title, cover, video, experiment_id, scene_sum_rov,
+        channel_level3, status, create_timestamp, update_timestamp,
+        match_exposure_pv, match_head_single_return_rate, match_head_distribution_single_return_rate,
+        match_generalized_element, match_generalized_point_type)
         VALUES
         <foreach collection="list" item="item" separator=",">
-            (#{item.dt}, #{item.channelName}, #{item.crowdSegment}, #{item.dimension}, #{item.pointType}, #{item.standardElement}, #{item.elementDimension},
-             #{item.categoryName}, #{item.category}, #{item.demandId}, #{item.crowdPackage}, #{item.conversionTarget}, #{item.partner}, #{item.account}, #{item.sceneValue},
-             #{item.demandStrategy}, #{item.driveDimensionTime}, #{item.demandFilterSortStrategy}, #{item.demandType},
-             #{item.demandContentId}, #{item.demandContentTitle}, #{item.demandContentTopic},
-             #{item.crowdCount}, #{item.videoCount}, #{item.visitUv}, #{item.uvRatio}, #{item.totalRov}, #{item.onlineAction}, #{item.matchExperimentId},
-             #{item.matchMethod}, #{item.matchVideoFilter}, #{item.matchSort},
-             #{item.videoId}, #{item.configCode}, #{item.score}, #{item.sim}, #{item.rov},
-             #{item.matchText}, #{item.title}, #{item.cover}, #{item.video}, #{item.experimentId}, #{item.sceneSumRov}, #{item.channelLevel3}, #{item.status}, #{item.createTimestamp}, #{item.updateTimestamp})
+            (#{item.dt}, #{item.channelName}, #{item.crowdSegment}, #{item.dimension}, #{item.pointType},
+            #{item.standardElement}, #{item.elementDimension}, #{item.categoryName}, #{item.category}, #{item.demandId},
+            #{item.crowdPackage}, #{item.conversionTarget}, #{item.partner}, #{item.account}, #{item.sceneValue},
+            #{item.demandStrategy}, #{item.driveDimensionTime}, #{item.demandFilterSortStrategy}, #{item.demandType},
+            #{item.demandContentId}, #{item.demandContentTitle}, #{item.demandContentTopic},
+            #{item.crowdCount}, #{item.videoCount}, #{item.visitUv}, #{item.uvRatio}, #{item.totalRov},
+            #{item.onlineAction}, #{item.matchExperimentId}, #{item.matchMethod}, #{item.matchVideoFilter}, #{item.matchSort},
+            #{item.videoId}, #{item.configCode}, #{item.score}, #{item.sim}, #{item.rov},
+            #{item.matchText}, #{item.title}, #{item.cover}, #{item.video}, #{item.experimentId}, #{item.sceneSumRov},
+            #{item.channelLevel3}, #{item.status}, #{item.createTimestamp}, #{item.updateTimestamp},
+            #{item.matchExposurePv}, #{item.matchHeadSingleReturnRate}, #{item.matchHeadDistributionSingleReturnRate},
+            #{item.matchGeneralizedElement}, #{item.matchGeneralizedPointType})
         </foreach>
     </insert>
 
@@ -45,7 +50,9 @@
                crowd_count, video_count, visit_uv, uv_ratio, total_rov, online_action, match_experiment_id,
                match_method, match_video_filter, match_sort,
                video_id, config_code, score, sim, rov,
-               match_text, title, cover, video, experiment_id, scene_sum_rov, channel_level3, status, create_timestamp, update_timestamp
+               match_text, title, cover, video, experiment_id, scene_sum_rov, channel_level3, status, create_timestamp, update_timestamp,
+               match_exposure_pv, match_head_single_return_rate, match_head_distribution_single_return_rate,
+               match_generalized_element, match_generalized_point_type
         FROM content_platform_demand_video
         WHERE dt = #{dt} AND status = 1
         <if test="channelName != null and channelName != ''">
@@ -107,7 +114,9 @@
                crowd_count, video_count, visit_uv, uv_ratio, total_rov, online_action, match_experiment_id,
                match_method, match_video_filter, match_sort,
                video_id, config_code, score, sim, rov,
-               match_text, title, cover, video, experiment_id, scene_sum_rov, status, create_timestamp, update_timestamp
+               match_text, title, cover, video, experiment_id, scene_sum_rov, status, create_timestamp, update_timestamp,
+               match_exposure_pv, match_head_single_return_rate, match_head_distribution_single_return_rate,
+               match_generalized_element, match_generalized_point_type
         FROM content_platform_demand_video
         WHERE dt = #{dt} AND status = 1
         <if test="excludeCategories != null and excludeCategories.size() > 0">
@@ -172,7 +181,9 @@
                crowd_count, video_count, visit_uv, uv_ratio, total_rov, online_action, match_experiment_id,
                match_method, match_video_filter, match_sort,
                video_id, config_code, score, sim, rov,
-               match_text, title, cover, video, experiment_id, scene_sum_rov, status, create_timestamp, update_timestamp
+               match_text, title, cover, video, experiment_id, scene_sum_rov, status, create_timestamp, update_timestamp,
+               match_exposure_pv, match_head_single_return_rate, match_head_distribution_single_return_rate,
+               match_generalized_element, match_generalized_point_type
         FROM content_platform_demand_video
         WHERE dt = #{dt} AND status = 1
           AND channel_name = #{channelName}

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

@@ -77,9 +77,9 @@
 <!--        <table tableName="content_platform_illegal_msg" domainObjectName="ContentPlatformIllegalMsg" alias=""/>-->
 <!--        <table tableName="content_platform_pq_account_rel" domainObjectName="ContentPlatformPqAccountRel" alias=""/>-->
 <!--        <table tableName="content_platform_upload_video" domainObjectName="ContentPlatformUploadVideo" alias=""/>-->
-<!--        <table tableName="content_platform_demand_video" domainObjectName="ContentPlatformDemandVideo" alias=""/>-->
-        <table tableName="content_platform_xcx_plan" domainObjectName="ContentPlatformXcxPlan" alias=""/>
-        <table tableName="content_platform_xcx_plan_video" domainObjectName="ContentPlatformXcxPlanVideo" alias=""/>
+        <table tableName="content_platform_demand_video" domainObjectName="ContentPlatformDemandVideo" alias=""/>
+<!--        <table tableName="content_platform_xcx_plan" domainObjectName="ContentPlatformXcxPlan" alias=""/>-->
+<!--        <table tableName="content_platform_xcx_plan_video" domainObjectName="ContentPlatformXcxPlanVideo" alias=""/>-->
     </context>
 
 </generatorConfiguration>

+ 44 - 0
api-module/src/test/java/com/tzld/piaoquan/api/service/contentplatform/impl/ContentPlatformPlanServiceImplExposureFilterTest.java

@@ -0,0 +1,44 @@
+package com.tzld.piaoquan.api.service.contentplatform.impl;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * 曝光下发过滤纯逻辑单测(不依赖 Spring/DB)。
+ * 语义见 {@link ContentPlatformPlanServiceImpl#passesExposureFilter(Long, Long)}。
+ */
+public class ContentPlatformPlanServiceImplExposureFilterTest {
+
+    /** 阈值为 null:过滤关闭,任何值(含 NULL)都放行。 */
+    @Test
+    public void thresholdNull_passesAll() {
+        assertTrue(ContentPlatformPlanServiceImpl.passesExposureFilter(null, null));
+        assertTrue(ContentPlatformPlanServiceImpl.passesExposureFilter(0L, null));
+        assertTrue(ContentPlatformPlanServiceImpl.passesExposureFilter(999999L, null));
+    }
+
+    /** 阈值 <=0:过滤关闭,全放行。 */
+    @Test
+    public void thresholdNonPositive_passesAll() {
+        assertTrue(ContentPlatformPlanServiceImpl.passesExposureFilter(null, 0L));
+        assertTrue(ContentPlatformPlanServiceImpl.passesExposureFilter(1L, 0L));
+        assertTrue(ContentPlatformPlanServiceImpl.passesExposureFilter(null, -1L));
+    }
+
+    /** 无曝光数据(NULL,如「场景已看视频」池)在阈值开启时仍放行——核心豁免语义。 */
+    @Test
+    public void nullExposure_alwaysPasses_whenThresholdOn() {
+        assertTrue(ContentPlatformPlanServiceImpl.passesExposureFilter(null, 2000L));
+    }
+
+    /** 有曝光数据:严格大于阈值才放行,边界值(==阈值)被砍。 */
+    @Test
+    public void hasExposure_strictlyGreaterThanThreshold() {
+        assertFalse(ContentPlatformPlanServiceImpl.passesExposureFilter(1000L, 2000L));
+        assertFalse(ContentPlatformPlanServiceImpl.passesExposureFilter(2000L, 2000L)); // 边界:==2000 砍掉
+        assertTrue(ContentPlatformPlanServiceImpl.passesExposureFilter(2001L, 2000L));
+        assertTrue(ContentPlatformPlanServiceImpl.passesExposureFilter(50000L, 2000L));
+    }
+}