|
@@ -0,0 +1,308 @@
|
|
|
+package com.tzld.piaoquan.api.service.strategy.impl;
|
|
|
+
|
|
|
+import com.alibaba.fastjson.JSON;
|
|
|
+import com.alibaba.fastjson.JSONObject;
|
|
|
+import com.tzld.piaoquan.api.common.enums.ReplyStrategyServiceEnum;
|
|
|
+import com.tzld.piaoquan.api.component.TouLiuHttpClient;
|
|
|
+import com.tzld.piaoquan.api.dao.mapper.AlgGhAutoreplyVideoRankDataMapper;
|
|
|
+import com.tzld.piaoquan.api.dao.mapper.CgiReplyBucketDataMapper;
|
|
|
+import com.tzld.piaoquan.api.dao.mapper.GhDetailMapper;
|
|
|
+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.CgiReplyBucketData;
|
|
|
+import com.tzld.piaoquan.api.model.po.CgiReplyBucketDataExample;
|
|
|
+import com.tzld.piaoquan.api.service.strategy.ReplyStrategyService;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.apache.commons.lang3.StringUtils;
|
|
|
+import org.springframework.beans.BeanUtils;
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
+import org.springframework.beans.factory.annotation.Value;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+import org.springframework.util.CollectionUtils;
|
|
|
+
|
|
|
+import java.util.*;
|
|
|
+import java.util.stream.Collectors;
|
|
|
+
|
|
|
+@Slf4j
|
|
|
+@Service
|
|
|
+public class BuckStrategyV1 implements ReplyStrategyService {
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 实验分桶数量
|
|
|
+ */
|
|
|
+// private static final Integer bucketNum = 10;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 分桶实验策略,key为策略,arr为对应桶
|
|
|
+ * {"base":[0,1,2,3],"stg0909-base":[4,5],"stg0909-explore1":[6,7,8],"stg0909-explore2":[9]}
|
|
|
+ * {"stg0909-base":[5,6],"stg0909-explore1":[7],"stg0909-explore2":[8,9]}
|
|
|
+ */
|
|
|
+ private static final String bucketStrategyConfig = "{\"stg0909-base\":[5,6],\"stg0909-explore1\":[7],\"stg0909-explore2\":[8,9]}";
|
|
|
+
|
|
|
+ @Value("${bucketStrategyConfig:{}}")
|
|
|
+ private String bucketStrategyConfigV2;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 自动回复使用小程序Id
|
|
|
+ */
|
|
|
+ private static final String SMALL_APP_Id = "wxbdd2a2e93d9a6e25";
|
|
|
+
|
|
|
+ private static final String CDN_URL = "https://rescdn.piaoquantv.com/";
|
|
|
+
|
|
|
+ @Value("${small_page_url}")
|
|
|
+ private String GET_SMALL_PAGE_URL;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private AlgGhAutoreplyVideoRankDataMapper algGhAutoreplyVideoRankDataMapper;
|
|
|
+ @Autowired
|
|
|
+ private CgiReplyBucketDataMapper cgiReplyBucketDataMapper;
|
|
|
+ @Autowired
|
|
|
+ private TouLiuHttpClient touLiuHttpClient;
|
|
|
+ @Autowired
|
|
|
+ private GhDetailMapper ghDetailMapper;
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public ReplyBucketData getResult(BucketDataParam bucketDataParam) {
|
|
|
+ // 0 获取策略key
|
|
|
+ JSONObject bucketStrategyConfigJsonObject = getStrategyConfig(bucketDataParam.getGhId());
|
|
|
+ Set<String> keyedSet = bucketStrategyConfigJsonObject.keySet();
|
|
|
+ // 1 处理文章--算法引擎--排序文章数据
|
|
|
+// getWenzhangData();
|
|
|
+ // 2 处理小程序--读取离线数据表--获取策略排序小程序数据
|
|
|
+ List<CgiReplyBucketData> smallDataCgiReplyList = readStrategyOrderSmallData(keyedSet, bucketDataParam);
|
|
|
+ // 2.1 获取小程序落地页地址 http调用
|
|
|
+ smallDataCgiReplyList = setSmallPageUrl(smallDataCgiReplyList);
|
|
|
+ log.info(JSON.toJSONString(smallDataCgiReplyList));
|
|
|
+ // 3 入库读表
|
|
|
+ insertSmallData(smallDataCgiReplyList, keyedSet);
|
|
|
+ // 4 组装分桶数据
|
|
|
+ return getReplyBucketData(bucketStrategyConfigJsonObject, keyedSet, bucketDataParam.getGhId());
|
|
|
+ }
|
|
|
+
|
|
|
+ private JSONObject getStrategyConfig(String ghId) {
|
|
|
+ JSONObject allStrategyConfigs = JSON.parseObject(bucketStrategyConfigV2);
|
|
|
+ JSONObject currentGhIdStrategyConfig = null;
|
|
|
+ if (allStrategyConfigs.containsKey(ghId)) {
|
|
|
+ currentGhIdStrategyConfig = allStrategyConfigs.getJSONObject(ghId);
|
|
|
+ } else if (allStrategyConfigs.containsKey("default")) {
|
|
|
+ currentGhIdStrategyConfig = allStrategyConfigs.getJSONObject("default");
|
|
|
+ } else {
|
|
|
+ log.error("invalid strategy config: default key does not exist");
|
|
|
+ throw new RuntimeException("Default strategy config does not exist");
|
|
|
+ }
|
|
|
+ // check param
|
|
|
+ if (!currentGhIdStrategyConfig.containsKey("base")) {
|
|
|
+ throw new RuntimeException("Strategy config does not have manual base");
|
|
|
+ }
|
|
|
+ return currentGhIdStrategyConfig;
|
|
|
+ }
|
|
|
+
|
|
|
+ private ReplyBucketData getReplyBucketData(JSONObject bucketStrategyConfigJsonObject, Set<String> keyedSet, String ghId) {
|
|
|
+ // 策略小程序数据
|
|
|
+ ReplyBucketData replyBucketData = new ReplyBucketData();
|
|
|
+ List<GroupData> groupDataList = new ArrayList<>();
|
|
|
+ for (String key : keyedSet) {
|
|
|
+ if ("base".equals(key)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ CgiReplyBucketDataExample cgiReplyBucketDataExample = new CgiReplyBucketDataExample();
|
|
|
+ cgiReplyBucketDataExample.createCriteria().andIsDeleteEqualTo(0).andStrategyEqualTo(key).andGhIdEqualTo(ghId);
|
|
|
+ cgiReplyBucketDataExample.setOrderByClause("sort");
|
|
|
+ List<CgiReplyBucketData> cgiReplyBucketData = cgiReplyBucketDataMapper.selectByExample(cgiReplyBucketDataExample);
|
|
|
+ if (CollectionUtils.isEmpty(cgiReplyBucketData)) {
|
|
|
+ CgiReplyBucketDataExample cgiReplyBucketDataExampleNull = new CgiReplyBucketDataExample();
|
|
|
+ cgiReplyBucketDataExampleNull.createCriteria().andIsDeleteEqualTo(0).andStrategyEqualTo(key).andGhIdEqualTo("default");
|
|
|
+ cgiReplyBucketDataExampleNull.setOrderByClause("sort");
|
|
|
+ cgiReplyBucketData = cgiReplyBucketDataMapper.selectByExample(cgiReplyBucketDataExampleNull);
|
|
|
+ }
|
|
|
+ if (CollectionUtils.isEmpty(cgiReplyBucketData)) {
|
|
|
+ log.error("getReplyBucketData get data is null,key:" + key);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ List<Integer> groupList = bucketStrategyConfigJsonObject.getJSONArray(key).toJavaList(Integer.class);
|
|
|
+ for (Integer group : groupList) {
|
|
|
+ GroupData groupData = new GroupData();
|
|
|
+ groupData.setGroupIndex(group);
|
|
|
+ List<MsgData> msgDataList = new ArrayList<>();
|
|
|
+ for (CgiReplyBucketData cgiReplyBucketDatum : cgiReplyBucketData) {
|
|
|
+ MsgData msgData = new MsgData();
|
|
|
+ BeanUtils.copyProperties(cgiReplyBucketDatum, msgData);
|
|
|
+ if (cgiReplyBucketDatum.getMsgType().equals(1)) {
|
|
|
+ msgData.setMiniAppId(SMALL_APP_Id);
|
|
|
+ }
|
|
|
+ msgDataList.add(msgData);
|
|
|
+ }
|
|
|
+ groupData.setMsgDataList(msgDataList);
|
|
|
+ groupDataList.add(groupData);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 获取人工实验数据
|
|
|
+ List<GroupData> groupDataBaseList = touLiuHttpClient.sendPenGongBaseRequest(ghId);
|
|
|
+ int baseBucketNum = bucketStrategyConfigJsonObject.getJSONArray("base").size();
|
|
|
+ if (groupDataBaseList.size() > baseBucketNum) {
|
|
|
+ groupDataBaseList = groupDataBaseList.subList(0, baseBucketNum);
|
|
|
+ }
|
|
|
+ if (!CollectionUtils.isEmpty(groupDataBaseList)) {
|
|
|
+ GroupData groupData = groupDataBaseList.get(0);
|
|
|
+ List<MsgData> msgDataList = groupData.getMsgDataList();
|
|
|
+ List<MsgData> changwenBase = msgDataList.stream().filter(x -> x.getMsgType().equals(2)).collect(Collectors.toList());
|
|
|
+ if (CollectionUtils.isEmpty(changwenBase)) {
|
|
|
+ log.error("get base changwenBase is null,data:" + JSON.toJSONString(msgDataList));
|
|
|
+ } else {
|
|
|
+ // 策略拼接base数据
|
|
|
+ for (GroupData data : groupDataList) {
|
|
|
+ List<MsgData> msgDataList1 = data.getMsgDataList();
|
|
|
+ MsgData msgData = changwenBase.get(0);
|
|
|
+// msgData.setSort(3);
|
|
|
+ msgDataList1.add(msgData);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 补充人工数据
|
|
|
+ groupDataList.addAll(groupDataBaseList);
|
|
|
+ } else {
|
|
|
+ log.error("get base data is null,ghId:" + ghId);
|
|
|
+ }
|
|
|
+ // groupDataList排序
|
|
|
+ replyBucketData.setGroupList(groupDataList.stream().sorted(Comparator.comparingInt(GroupData::getGroupIndex)).collect(Collectors.toList()));
|
|
|
+ return replyBucketData;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ private void insertSmallData(List<CgiReplyBucketData> smallDataCgiReplyList, Set<String> keyedSet) {
|
|
|
+ if (CollectionUtils.isEmpty(smallDataCgiReplyList)) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ for (String key : keyedSet) {
|
|
|
+ if ("base".equals(key)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ List<CgiReplyBucketData> collect = smallDataCgiReplyList.stream().filter(x -> x.getStrategy().equals(key)).collect(Collectors.toList());
|
|
|
+ if (CollectionUtils.isEmpty(collect)) {
|
|
|
+ log.error("insertSmallData 算法排序数据异常,data:" + JSON.toJSONString(smallDataCgiReplyList));
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ // 清上个版本的策略数据
|
|
|
+ CgiReplyBucketDataExample cgiReplyBucketDataExample = new CgiReplyBucketDataExample();
|
|
|
+ cgiReplyBucketDataExample.createCriteria().andIsDeleteEqualTo(0).andMsgTypeEqualTo(1).andStrategyEqualTo(key);
|
|
|
+ List<CgiReplyBucketData> cgiReplyBucketData1 = cgiReplyBucketDataMapper.selectByExample(cgiReplyBucketDataExample);
|
|
|
+ for (CgiReplyBucketData cgiReplyBucketData : cgiReplyBucketData1) {
|
|
|
+ cgiReplyBucketData.setIsDelete(1);
|
|
|
+ cgiReplyBucketDataMapper.updateByPrimaryKeySelective(cgiReplyBucketData);
|
|
|
+ }
|
|
|
+ // 入库
|
|
|
+ for (CgiReplyBucketData cgiReplyBucketData : collect) {
|
|
|
+ cgiReplyBucketDataMapper.insertSelective(cgiReplyBucketData);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private List<CgiReplyBucketData> setSmallPageUrl(List<CgiReplyBucketData> smallDataCgiReplyList) {
|
|
|
+ if (CollectionUtils.isEmpty(smallDataCgiReplyList)) {
|
|
|
+ return smallDataCgiReplyList;
|
|
|
+ }
|
|
|
+ Set<String> keys = smallDataCgiReplyList.stream().map(x -> x.getGhId() + "&" + x.getMiniVideoId() + "&" + x.getSort()).collect(Collectors.toSet());
|
|
|
+ Map<String, SmallPageUrlDetail> keyPageUrl = new HashMap<>();
|
|
|
+ // gh-id + videoId + sort 复用同一page_url及落地页id
|
|
|
+ for (String key : keys) {
|
|
|
+ String[] keyArr = key.split("&");
|
|
|
+ String ghId = keyArr[0];
|
|
|
+ String videoId = keyArr[1];
|
|
|
+ String sort = keyArr[2];
|
|
|
+ // 查询库里是否存在,如果存在即复用
|
|
|
+ CgiReplyBucketDataExample cgiReplyBucketDataExample = new CgiReplyBucketDataExample();
|
|
|
+ cgiReplyBucketDataExample.createCriteria().andIsDeleteEqualTo(0).andMiniVideoIdEqualTo(Long.valueOf(videoId)).andGhIdEqualTo(ghId);
|
|
|
+ List<CgiReplyBucketData> cgiReplyBucketData = cgiReplyBucketDataMapper.selectByExample(cgiReplyBucketDataExample);
|
|
|
+ SmallPageUrlDetail smallPageUrlDetail = new SmallPageUrlDetail();
|
|
|
+ if (CollectionUtils.isEmpty(cgiReplyBucketData)) {
|
|
|
+ // 库里不存在,调用新生成
|
|
|
+ String putScene = "touliu";
|
|
|
+ String channel = "tencentgzh";
|
|
|
+ String response = touLiuHttpClient.sendAdFlowAddRequest(GET_SMALL_PAGE_URL, videoId, putScene, channel, "自动", "公众号", "自动回复小程序", "位置" + sort, ghId);
|
|
|
+ JSONObject jsonObject = JSON.parseObject(response);
|
|
|
+ if (jsonObject.getInteger("code").equals(0)) {
|
|
|
+ smallPageUrlDetail = jsonObject.getObject("data", SmallPageUrlDetail.class);
|
|
|
+ keyPageUrl.put(key, smallPageUrlDetail);
|
|
|
+ } else {
|
|
|
+ log.error("httpClientService get page url error,response:" + response);
|
|
|
+ throw new RuntimeException("httpClientService get page url error");
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 复用
|
|
|
+ CgiReplyBucketData cgiReplyBucketData1 = cgiReplyBucketData.get(0);
|
|
|
+ smallPageUrlDetail.setId(cgiReplyBucketData1.getPagePathUrlId());
|
|
|
+ smallPageUrlDetail.setUrl(cgiReplyBucketData1.getMiniPagePath());
|
|
|
+ }
|
|
|
+ keyPageUrl.put(key, smallPageUrlDetail);
|
|
|
+ }
|
|
|
+ // 处理数据
|
|
|
+ for (CgiReplyBucketData cgiReplyBucketData : smallDataCgiReplyList) {
|
|
|
+ String key = cgiReplyBucketData.getGhId() + "&" + cgiReplyBucketData.getMiniVideoId() + "&" + cgiReplyBucketData.getSort();
|
|
|
+ SmallPageUrlDetail smallPageUrlDetail = keyPageUrl.get(key);
|
|
|
+ if (Objects.isNull(smallPageUrlDetail)) {
|
|
|
+ log.error("setSmallPageUrl get map url is null" + JSON.toJSONString(keyPageUrl));
|
|
|
+ throw new RuntimeException("setSmallPageUrl get map url is null");
|
|
|
+ }
|
|
|
+ cgiReplyBucketData.setPagePathUrlId(smallPageUrlDetail.getId());
|
|
|
+ cgiReplyBucketData.setMiniPagePath(smallPageUrlDetail.getUrl());
|
|
|
+ }
|
|
|
+ return smallDataCgiReplyList;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ private List<CgiReplyBucketData> readStrategyOrderSmallData(Set<String> keyedSet, BucketDataParam bucketDataParam) {
|
|
|
+ List<CgiReplyBucketData> result = new ArrayList<>();
|
|
|
+ for (String key : keyedSet) {
|
|
|
+ if ("base".equals(key)) {
|
|
|
+ // base作为人工控制
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ // 获取最新dt的策略
|
|
|
+ String dtVersion = algGhAutoreplyVideoRankDataMapper.selectLatestDtVersionByStrategyKey(key);
|
|
|
+ // 判断当前的dtVersion是否已经处理过了
|
|
|
+ CgiReplyBucketDataExample cgiReplyBucketDataExample = new CgiReplyBucketDataExample();
|
|
|
+ cgiReplyBucketDataExample.createCriteria().andIsDeleteEqualTo(0).andStrategyDtEqualTo(dtVersion).andStrategyEqualTo(key);
|
|
|
+ long count = cgiReplyBucketDataMapper.countByExample(cgiReplyBucketDataExample);
|
|
|
+ if (count != 0) {
|
|
|
+ // 说明已处理过该dtVersion数据
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ // 获取最新dt数据
|
|
|
+ List<AlgGhAutoreplyVideoRankData> dtVersionStrategyData = getDtVersionStrategyData(key, dtVersion);
|
|
|
+ List<Long> videoIds = dtVersionStrategyData.stream().map(AlgGhAutoreplyVideoRankData::getVideoId).collect(Collectors.toList());
|
|
|
+ Map<Long, VideoDetail> videoDetailMap = touLiuHttpClient.getVideoDetailRequest(videoIds);
|
|
|
+ result.addAll(dtVersionStrategyData.stream().map(x -> {
|
|
|
+ CgiReplyBucketData cgiReplyBucketData = new CgiReplyBucketData();
|
|
|
+ cgiReplyBucketData.setStrategy(key);
|
|
|
+ cgiReplyBucketData.setSort(x.getSort());
|
|
|
+ cgiReplyBucketData.setStrategyDt(x.getDtVersion());
|
|
|
+ cgiReplyBucketData.setGhId(x.getGhId());
|
|
|
+ cgiReplyBucketData.setMsgType(1);
|
|
|
+ cgiReplyBucketData.setTitle(x.getTitle());
|
|
|
+ VideoDetail videoDetail = videoDetailMap.get(x.getVideoId());
|
|
|
+ if (videoDetail !=null && StringUtils.isNotEmpty(videoDetail.getCover())) {
|
|
|
+ cgiReplyBucketData.setCoverUrl(videoDetail.getCover());
|
|
|
+ } else {
|
|
|
+ cgiReplyBucketData.setCoverUrl(CDN_URL + x.getCoverUrl());
|
|
|
+ }
|
|
|
+ cgiReplyBucketData.setMiniAppId(SMALL_APP_Id);
|
|
|
+ cgiReplyBucketData.setMiniVideoId(x.getVideoId());
|
|
|
+ return cgiReplyBucketData;
|
|
|
+ }).collect(Collectors.toList()));
|
|
|
+ }
|
|
|
+ // 获取最新数据版本
|
|
|
+ return CollectionUtils.isEmpty(result) ? null : result;
|
|
|
+ }
|
|
|
+
|
|
|
+ private List<AlgGhAutoreplyVideoRankData> getDtVersionStrategyData(String key, String dtVersion) {
|
|
|
+ AlgGhAutoreplyVideoRankDataExample algGhAutoreplyVideoRankDataExample = new AlgGhAutoreplyVideoRankDataExample();
|
|
|
+ algGhAutoreplyVideoRankDataExample.createCriteria().andIsDeleteEqualTo(0).andDtVersionEqualTo(dtVersion).andStrategyKeyEqualTo(key);
|
|
|
+ return algGhAutoreplyVideoRankDataMapper.selectByExample(algGhAutoreplyVideoRankDataExample);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Boolean support(ReplyStrategyServiceEnum key) {
|
|
|
+ return ReplyStrategyServiceEnum.BUCKET_STRATEGY_V1.equals(key);
|
|
|
+ }
|
|
|
+}
|