|
@@ -0,0 +1,672 @@
|
|
|
|
+package org.jeecg.modules.ads.service.impl;
|
|
|
|
+
|
|
|
|
+import cn.com.ctop.common.module.entity.CtopOauthToken;
|
|
|
|
+import cn.com.ctop.common.module.entity.MaterialCutFrame;
|
|
|
|
+import cn.com.ctop.common.module.entity.MaterialImageInfo;
|
|
|
|
+import cn.com.ctop.common.module.service.ICtopOauthTokenService;
|
|
|
|
+import cn.com.ctop.common.module.service.IMaterialCutFrameService;
|
|
|
|
+import cn.com.ctop.common.module.service.IMaterialImageInfoService;
|
|
|
|
+import cn.com.ctop.kuaishou.modules.batch.entity.KuaiShouImageGet;
|
|
|
|
+import cn.com.ctop.kuaishou.modules.batch.entity.KuaiShouVideoGet;
|
|
|
|
+import cn.com.ctop.kuaishou.modules.batch.entity.KuaishouStrategy;
|
|
|
|
+import cn.com.ctop.kuaishou.modules.batch.service.IKuaiShouImageGetService;
|
|
|
|
+import cn.com.ctop.kuaishou.modules.batch.service.IKuaiShouMaterialUploadService;
|
|
|
|
+import cn.com.ctop.kuaishou.modules.batch.service.IKuaiShouVideoGetService;
|
|
|
|
+import cn.com.ctop.kuaishou.modules.batch.service.IKuaishouInterfaceService;
|
|
|
|
+import com.alibaba.fastjson.JSONArray;
|
|
|
|
+import com.alibaba.fastjson.JSONObject;
|
|
|
|
+import org.jeecg.common.util.DateUtils;
|
|
|
|
+import org.jeecg.modules.ads.entity.*;
|
|
|
|
+import org.jeecg.modules.ads.service.*;
|
|
|
|
+import org.slf4j.Logger;
|
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
|
+
|
|
|
|
+import java.math.BigDecimal;
|
|
|
|
+import java.util.*;
|
|
|
|
+import java.util.stream.Collectors;
|
|
|
|
+
|
|
|
|
+@Service
|
|
|
|
+public class AiStrategyServiceImpl implements IAiStrategyService {
|
|
|
|
+
|
|
|
|
+ private static final Logger log = LoggerFactory.getLogger(AiStrategyServiceImpl.class);
|
|
|
|
+
|
|
|
|
+ @Autowired
|
|
|
|
+ private IKuaiShouVideoGetService videoGetService;
|
|
|
|
+ @Autowired
|
|
|
|
+ private IAiKuaishouStrategyTargetBaseService targetBaseService;
|
|
|
|
+ @Autowired
|
|
|
|
+ private IAiKuaishouStrategyTemplateService templateService;
|
|
|
|
+ @Autowired
|
|
|
|
+ private IAiKuaishouStrategyMiddleService middleService;
|
|
|
|
+ @Autowired
|
|
|
|
+ private IAiKuaishouStrategyTargetUnionService targetUnionService;
|
|
|
|
+ @Autowired
|
|
|
|
+ private IKuaishouInterfaceService kuaishouInterfaceService;
|
|
|
|
+ @Autowired
|
|
|
|
+ private ICtopOauthTokenService tokenService;
|
|
|
|
+ @Autowired
|
|
|
|
+ private IAiKuaishouStrategyMapCreativeService mapCreativeService;
|
|
|
|
+ @Autowired
|
|
|
|
+ private IMaterialCutFrameService materialCutFrameService;
|
|
|
|
+ @Autowired
|
|
|
|
+ private IMaterialImageInfoService materialImageInfoService;
|
|
|
|
+ @Autowired
|
|
|
|
+ private IKuaiShouImageGetService imageGetService;
|
|
|
|
+ @Autowired
|
|
|
|
+ private IKuaiShouMaterialUploadService materialUploadService;
|
|
|
|
+ /**
|
|
|
|
+ *
|
|
|
|
+ * @param strategy
|
|
|
|
+ */
|
|
|
|
+ @Override
|
|
|
|
+ public void createCreativeByStrategy(KuaishouStrategy strategy){
|
|
|
|
+ //获取账户id
|
|
|
|
+ Long accountId = strategy.getAccountId();
|
|
|
|
+ CtopOauthToken token = tokenService.getOauthTokenByAccountId(accountId.toString());
|
|
|
|
+ if(null == token){
|
|
|
|
+ log.info("token获取失败=>accountId:{}",strategy.getAccountId());
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ //1:获取相应账户下的最新的5条素材信息
|
|
|
|
+ List<KuaiShouVideoGet> videoGets = videoGetService.getNewVideoByAccountId(accountId,1);
|
|
|
|
+ if(null == videoGets){
|
|
|
|
+ log.info("素材获取失败=>accountId:{}",accountId);
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ //策略类型
|
|
|
|
+ String scenes = strategy.getScenes();
|
|
|
|
+ //根据策略类型获取策略模板信息
|
|
|
|
+ AiKuaishouStrategyTemplate strategyTemplate = templateService.getOneByParams(scenes, 1);
|
|
|
|
+ if(null == strategyTemplate){
|
|
|
|
+ log.info("策略模板信息获取失败=>testDirection:{}",scenes);
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ String materialFixType = strategyTemplate.getMaterialFixType();
|
|
|
|
+ if(scenes.equals("1")){
|
|
|
|
+ //素材+出价
|
|
|
|
+ List<Long>getCapbids = splitCapBidByDomain(strategy.getCpaBidMin(),strategy.getCpaBidMax(),strategy.getStepSize());
|
|
|
|
+ if(null == getCapbids||getCapbids.isEmpty()){
|
|
|
|
+ log.info("出价范围异常=>start:{};end:{}",strategy.getCpaBidMin().toString(),strategy.getCpaBidMax().toString());
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ //7、一个计划,拼装创建计划的入参json
|
|
|
|
+ JSONObject campaignParams = new JSONObject();
|
|
|
|
+ campaignParams.put("advertiser_id",token.getAccountId());
|
|
|
|
+ Date campaignDate = new Date();
|
|
|
|
+ String timestamp = DateUtils.formatDate(campaignDate,"yyyyMMddHHmmss");
|
|
|
|
+ campaignParams.put("campaign_name",strategy.getStrategyName()+ timestamp);
|
|
|
|
+ campaignParams.put("type",Integer.parseInt(strategy.getMarketingGoal()));
|
|
|
|
+ campaignParams.put("day_budget",0L);
|
|
|
|
+ Map<String, Object> campaignCreateResult = kuaishouInterfaceService.campaignCreate(token.getAccessToken(), token.getAccountId(), campaignParams);
|
|
|
|
+ Integer code = (Integer) campaignCreateResult.get("code");
|
|
|
|
+ String message = (String) campaignCreateResult.get("message");
|
|
|
|
+ if(code != 0 ){
|
|
|
|
+ log.info("计划创建失败=>message:{}",message);
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ Long campaignId = (Long) campaignCreateResult.get("campaignId");
|
|
|
|
+ int unitCnt = 1;
|
|
|
|
+ // 8、根据定向和素材,两层循环 创建组和创意
|
|
|
|
+ for (Long item:getCapbids) {
|
|
|
|
+ //判定素材搭配方式
|
|
|
|
+ if(null!=materialFixType&&materialFixType.trim().equals("single")){
|
|
|
|
+ // 选择素材,创建创意
|
|
|
|
+ for(KuaiShouVideoGet videoItem: videoGets){
|
|
|
|
+ //拼装广告组参数
|
|
|
|
+ JSONObject unitParams = createUnitParams(token,campaignId,strategy,item,timestamp,unitCnt);
|
|
|
|
+ Map<String, Object> unitCreateResult = kuaishouInterfaceService.adUnitCreate(token.getAccessToken(), token.getAccountId(), unitParams,1);
|
|
|
|
+ Integer unitCode = (Integer) unitCreateResult.get("code");
|
|
|
|
+ String unitMessage = (String) unitCreateResult.get("message");
|
|
|
|
+ if(unitCode != 0 ){
|
|
|
|
+ log.info("组创建失败=>accountId:{};message:{}",accountId,unitMessage);
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
+ Long unitId = (Long) unitCreateResult.get("unitId");
|
|
|
|
+ JSONObject creativeParams = creativeParams(token,unitId,videoItem,unitCnt,strategy);
|
|
|
|
+ JSONArray sceneIdJsonArray = JSONArray.parseArray(strategy.getSceneId());
|
|
|
|
+ // 如果广告位只包含 上下滑大屏广告(6),则一个素材搭配1张封面,生成1个创意
|
|
|
|
+ if (sceneIdJsonArray.contains(6) && sceneIdJsonArray.size() == 1){
|
|
|
|
+ //直接创建创意
|
|
|
|
+ createCreative(token,creativeParams,campaignId,unitId,strategy,null,1,videoItem);
|
|
|
|
+ } else {
|
|
|
|
+ // 素材自带的封面 ctop_material_image_info 内部素材的自带封面,素造的没有
|
|
|
|
+ // 素材的智能抽帧封面 ctop_material_cut_frame 其中素造的为8张封面,内部的为15张封面
|
|
|
|
+ List<MaterialCutFrame> cutFrameList = materialCutFrameService.getListByVideoSignature(videoItem.getSignature());
|
|
|
|
+ List<MaterialImageInfo> orgFrameList = materialImageInfoService.getListByVideoSignature(videoItem.getSignature());
|
|
|
|
+ // 其他情况下,一个素材搭配15张封面,生成15个创意
|
|
|
|
+ if ((null == cutFrameList||cutFrameList.isEmpty())&&(null == orgFrameList|| orgFrameList.isEmpty())){
|
|
|
|
+ log.info("未获取到视频封面=>videoSignature:{}",videoItem.getSignature());
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
+ createCreativeByImage(cutFrameList,orgFrameList,token,creativeParams,campaignId,unitId,strategy,videoItem,15,null);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }else{
|
|
|
|
+ //混搭素材逻辑
|
|
|
|
+ JSONObject unitParams = createUnitParams(token,campaignId,strategy,item,timestamp,unitCnt);
|
|
|
|
+ Map<String, Object> unitCreateResult = kuaishouInterfaceService.adUnitCreate(token.getAccessToken(), token.getAccountId(), unitParams,1);
|
|
|
|
+ Integer unitCode = (Integer) unitCreateResult.get("code");
|
|
|
|
+ String unitMessage = (String) unitCreateResult.get("message");
|
|
|
|
+ if(unitCode != 0 ){
|
|
|
|
+ log.info("组创建失败=>accountId:{};message:{}",accountId,unitMessage);
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
+ Long unitId = (Long) unitCreateResult.get("unitId");
|
|
|
|
+ // 选择素材,创建创意
|
|
|
|
+ for(KuaiShouVideoGet videoItem: videoGets){
|
|
|
|
+ //拼装广告组参数
|
|
|
|
+ JSONObject creativeParams = creativeParams(token,unitId,videoItem,unitCnt,strategy);
|
|
|
|
+ JSONArray sceneIdJsonArray = JSONArray.parseArray(strategy.getSceneId());
|
|
|
|
+ // 如果广告位只包含 上下滑大屏广告(6),则一个素材搭配1张封面,生成1个创意
|
|
|
|
+ if (sceneIdJsonArray.contains(6) && sceneIdJsonArray.size() == 1){
|
|
|
|
+ //直接创建创意
|
|
|
|
+ createCreative(token,creativeParams,campaignId,unitId,strategy,null,1,videoItem);
|
|
|
|
+ } else {
|
|
|
|
+ // 素材自带的封面 ctop_material_image_info 内部素材的自带封面,素造的没有
|
|
|
|
+ // 素材的智能抽帧封面 ctop_material_cut_frame 其中素造的为8张封面,内部的为15张封面
|
|
|
|
+ List<MaterialCutFrame> cutFrameList = materialCutFrameService.getListByVideoSignature(videoItem.getSignature());
|
|
|
|
+ List<MaterialImageInfo> orgFrameList = materialImageInfoService.getListByVideoSignature(videoItem.getSignature());
|
|
|
|
+ // 其他情况下,一个素材搭配15张封面,生成15个创意
|
|
|
|
+ if ((null == cutFrameList||cutFrameList.isEmpty())&&(null == orgFrameList|| orgFrameList.isEmpty())){
|
|
|
|
+ log.info("未获取到视频封面=>videoSignature:{}",videoItem.getSignature());
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
+ createCreativeByImage(cutFrameList,orgFrameList,token,creativeParams,campaignId,unitId,strategy,videoItem,1,null);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }else if(scenes.equals(2)){
|
|
|
|
+ //素材+定向
|
|
|
|
+ //2:根据测试方向获取相应的定向拆分数据类型
|
|
|
|
+ List<AiKuaishouStrategyTargetBase>targetBases = targetBaseService.listByType(strategyTemplate.getTestDirection());
|
|
|
|
+ if(null == targetBases){
|
|
|
|
+ log.info("定向拆分信息获取失败=>testDirection:{}",strategyTemplate.getTestDirection());
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ //3:获取相应字段相应的用户定义域
|
|
|
|
+ String definitionDomain = getStragyDefDomain(strategyTemplate,strategy);
|
|
|
|
+ //4:计算定向拆分逻辑
|
|
|
|
+ List<AiKuaishouStrategyTargetBase>getBases = splitBasesByDomain(definitionDomain,targetBases);
|
|
|
|
+ if(null == getBases||getBases.isEmpty()){
|
|
|
|
+ log.info("定向拆分失败=>definitionDomain:{}",definitionDomain);
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ //5、策略--策略模板 关联表,写入数据库
|
|
|
|
+ AiKuaishouStrategyMiddle strategyMiddle = new AiKuaishouStrategyMiddle(strategyTemplate.getId(),strategy.getId());
|
|
|
|
+ middleService.save(strategyMiddle);
|
|
|
|
+ //7、一个计划,拼装创建计划的入参json
|
|
|
|
+ JSONObject campaignParams = new JSONObject();
|
|
|
|
+ campaignParams.put("advertiser_id",token.getAccountId());
|
|
|
|
+ Date campaignDate = new Date();
|
|
|
|
+ String timestamp = DateUtils.formatDate(campaignDate,"yyyyMMddHHmmss");
|
|
|
|
+ campaignParams.put("campaign_name",strategy.getStrategyName()+ timestamp);
|
|
|
|
+ campaignParams.put("type",Integer.parseInt(strategy.getMarketingGoal()));
|
|
|
|
+ campaignParams.put("day_budget",0L);
|
|
|
|
+ Map<String, Object> campaignCreateResult = kuaishouInterfaceService.campaignCreate(token.getAccessToken(), token.getAccountId(), campaignParams);
|
|
|
|
+ Integer code = (Integer) campaignCreateResult.get("code");
|
|
|
|
+ String message = (String) campaignCreateResult.get("message");
|
|
|
|
+ if(code != 0 ){
|
|
|
|
+ log.info("计划创建失败=>message:{}",message);
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ Long campaignId = (Long) campaignCreateResult.get("campaignId");
|
|
|
|
+ int unitCnt = 0;
|
|
|
|
+ // 8、根据定向和素材,两层循环 创建组和创意
|
|
|
|
+ for (AiKuaishouStrategyTargetBase item:getBases) {
|
|
|
|
+ unitCnt++;
|
|
|
|
+ if(null!=materialFixType&&materialFixType.trim().equals("single")){
|
|
|
|
+ // 策略定向拆分数据,写入数据库
|
|
|
|
+ AiKuaishouStrategyTargetUnion targetUnion = new AiKuaishouStrategyTargetUnion(strategyMiddle.getId(),item.getId(),item.getTargetContent());
|
|
|
|
+ targetUnionService.save(targetUnion);
|
|
|
|
+ // 选择素材,创建创意
|
|
|
|
+ for(KuaiShouVideoGet videoItem: videoGets){
|
|
|
|
+ //拼装广告组参数
|
|
|
|
+ JSONObject unitParams = createUnitParams(token,campaignId,strategy,strategyTemplate, item,timestamp,unitCnt);
|
|
|
|
+ Map<String, Object> unitCreateResult = kuaishouInterfaceService.adUnitCreate(token.getAccessToken(), token.getAccountId(), unitParams,1);
|
|
|
|
+ Integer unitCode = (Integer) unitCreateResult.get("code");
|
|
|
|
+ String unitMessage = (String) unitCreateResult.get("message");
|
|
|
|
+ if(unitCode != 0 ){
|
|
|
|
+ log.info("组创建失败=>accountId:{};message:{}",accountId,unitMessage);
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
+ Long unitId = (Long) unitCreateResult.get("unitId");
|
|
|
|
+ JSONObject creativeParams = creativeParams(token,unitId,videoItem,unitCnt,strategy);
|
|
|
|
+ JSONArray sceneIdJsonArray = JSONArray.parseArray(strategy.getSceneId());
|
|
|
|
+ // 如果广告位只包含 上下滑大屏广告(6),则一个素材搭配1张封面,生成1个创意
|
|
|
|
+ if (sceneIdJsonArray.contains(6) && sceneIdJsonArray.size() == 1){
|
|
|
|
+ //直接创建创意
|
|
|
|
+ createCreative(token,creativeParams,campaignId,unitId,strategy,targetUnion,1,videoItem);
|
|
|
|
+ } else {
|
|
|
|
+ // 素材自带的封面 ctop_material_image_info 内部素材的自带封面,素造的没有
|
|
|
|
+ // 素材的智能抽帧封面 ctop_material_cut_frame 其中素造的为8张封面,内部的为15张封面
|
|
|
|
+ List<MaterialCutFrame> cutFrameList = materialCutFrameService.getListByVideoSignature(videoItem.getSignature());
|
|
|
|
+ List<MaterialImageInfo> orgFrameList = materialImageInfoService.getListByVideoSignature(videoItem.getSignature());
|
|
|
|
+ // 其他情况下,一个素材搭配15张封面,生成15个创意
|
|
|
|
+ if ((null == cutFrameList||cutFrameList.isEmpty())&&(null == orgFrameList|| orgFrameList.isEmpty())){
|
|
|
|
+ log.info("未获取到视频封面=>videoSignature:{}",videoItem.getSignature());
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
+ createCreativeByImage(cutFrameList,orgFrameList,token,creativeParams,campaignId,unitId,strategy,videoItem,15,targetUnion);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }else{
|
|
|
|
+ // 策略定向拆分数据,写入数据库
|
|
|
|
+ AiKuaishouStrategyTargetUnion targetUnion = new AiKuaishouStrategyTargetUnion(strategyMiddle.getId(),item.getId(),item.getTargetContent());
|
|
|
|
+ targetUnionService.save(targetUnion);
|
|
|
|
+ // 选择素材,创建创意
|
|
|
|
+ for(KuaiShouVideoGet videoItem: videoGets){
|
|
|
|
+ //拼装广告组参数
|
|
|
|
+ JSONObject unitParams = createUnitParams(token,campaignId,strategy,strategyTemplate, item,timestamp,unitCnt);
|
|
|
|
+ Map<String, Object> unitCreateResult = kuaishouInterfaceService.adUnitCreate(token.getAccessToken(), token.getAccountId(), unitParams,1);
|
|
|
|
+ Integer unitCode = (Integer) unitCreateResult.get("code");
|
|
|
|
+ String unitMessage = (String) unitCreateResult.get("message");
|
|
|
|
+ if(unitCode != 0 ){
|
|
|
|
+ log.info("组创建失败=>accountId:{};message:{}",accountId,unitMessage);
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
+ Long unitId = (Long) unitCreateResult.get("unitId");
|
|
|
|
+ JSONObject creativeParams = creativeParams(token,unitId,videoItem,unitCnt,strategy);
|
|
|
|
+ JSONArray sceneIdJsonArray = JSONArray.parseArray(strategy.getSceneId());
|
|
|
|
+ // 如果广告位只包含 上下滑大屏广告(6),则一个素材搭配1张封面,生成1个创意
|
|
|
|
+ if (sceneIdJsonArray.contains(6) && sceneIdJsonArray.size() == 1){
|
|
|
|
+ //直接创建创意
|
|
|
|
+ createCreative(token,creativeParams,campaignId,unitId,strategy,targetUnion,1,videoItem);
|
|
|
|
+ } else {
|
|
|
|
+ // 素材自带的封面 ctop_material_image_info 内部素材的自带封面,素造的没有
|
|
|
|
+ // 素材的智能抽帧封面 ctop_material_cut_frame 其中素造的为8张封面,内部的为15张封面
|
|
|
|
+ List<MaterialCutFrame> cutFrameList = materialCutFrameService.getListByVideoSignature(videoItem.getSignature());
|
|
|
|
+ List<MaterialImageInfo> orgFrameList = materialImageInfoService.getListByVideoSignature(videoItem.getSignature());
|
|
|
|
+ // 其他情况下,一个素材搭配15张封面,生成15个创意
|
|
|
|
+ if ((null == cutFrameList||cutFrameList.isEmpty())&&(null == orgFrameList|| orgFrameList.isEmpty())){
|
|
|
|
+ log.info("未获取到视频封面=>videoSignature:{}",videoItem.getSignature());
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
+ createCreativeByImage(cutFrameList,orgFrameList,token,creativeParams,campaignId,unitId,strategy,videoItem,1,targetUnion);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private void createCreativeByImage(List<MaterialCutFrame> cutFrameList,List<MaterialImageInfo> orgFrameList,CtopOauthToken token,JSONObject creativeParams,Long campaignId,Long unitId,KuaishouStrategy strategy,KuaiShouVideoGet videoItem,int imageNumber,AiKuaishouStrategyTargetUnion targetUnion){
|
|
|
|
+ int creativeCnt = 1;
|
|
|
|
+ if (null!=orgFrameList&&!orgFrameList.isEmpty()) {
|
|
|
|
+ for (int i = 0; i < orgFrameList.size(); i++) {
|
|
|
|
+ String imageCode = orgFrameList.get(i).getCode();
|
|
|
|
+ String imageUrl = orgFrameList.get(i).getUrl();
|
|
|
|
+ String imageToken = getImageToken(imageUrl,imageCode,token.getAccountId(),token.getAccessToken());
|
|
|
|
+ creativeParams.put("image_token",imageToken);
|
|
|
|
+ createCreative(token,creativeParams,campaignId,unitId,strategy,null,creativeCnt,videoItem);
|
|
|
|
+ creativeCnt++;
|
|
|
|
+ if (creativeCnt > imageNumber) {
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if ((null!=cutFrameList&&!cutFrameList.isEmpty()) && (creativeCnt<=15)){
|
|
|
|
+ for (int i = 0; i < cutFrameList.size(); i++) {
|
|
|
|
+ String imageCode = cutFrameList.get(i).getSignature();
|
|
|
|
+ String imageUrl = cutFrameList.get(i).getUrl();
|
|
|
|
+ String imageToken = getImageToken(imageUrl,imageCode,token.getAccountId(),token.getAccessToken());
|
|
|
|
+ creativeParams.put("image_token",imageToken);
|
|
|
|
+ createCreative(token,creativeParams,campaignId,unitId,strategy,null,creativeCnt,videoItem);
|
|
|
|
+ creativeCnt ++;
|
|
|
|
+ if (creativeCnt > imageNumber) {
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private JSONObject createUnitParams(CtopOauthToken token, Long campaignId, KuaishouStrategy strategy, Long item, String timestamp, int unitCnt) {
|
|
|
|
+ // 拼装创建组的入参 json
|
|
|
|
+ JSONObject unitParams = new JSONObject();
|
|
|
|
+ unitParams.put("advertiser_id",token.getAccountId());
|
|
|
|
+ unitParams.put("campaign_id",campaignId);
|
|
|
|
+ String uuid = UUID.randomUUID().toString();
|
|
|
|
+ unitParams.put("unit_name", strategy.getStrategyName() + "cpaBid-" + item.toString()+"-" + uuid + unitCnt);
|
|
|
|
+ unitParams.put("bid_type",10);
|
|
|
|
+ unitParams.put("ocpx_action_type",180);
|
|
|
|
+
|
|
|
|
+ if(null!=strategy.getUseAppMarket()&&!"".equals(strategy.getUseAppMarket())){
|
|
|
|
+ unitParams.put("use_app_market",Integer.parseInt(strategy.getUseAppMarket()));
|
|
|
|
+ }
|
|
|
|
+ if(null!=strategy.getAppStore()&&!"".equals(strategy.getAppStore())){
|
|
|
|
+ unitParams.put("app_store",JSONArray.parseArray(strategy.getAppStore()));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 在本策略中,都是 OCPM 的出价方式
|
|
|
|
+ unitParams.put("cpa_bid",item);
|
|
|
|
+ unitParams.put("ocpx_action_type",strategy.getOcpxActionType());
|
|
|
|
+ unitParams.put("deep_conversion_type",strategy.getDeepConversionType());
|
|
|
|
+ unitParams.put("deep_conversion_bid",strategy.getDeepConversionBid());
|
|
|
|
+ unitParams.put("scene_id",JSONArray.parseArray(strategy.getSceneId()));
|
|
|
|
+ unitParams.put("unit_type",4);
|
|
|
|
+
|
|
|
|
+ // 格式为yyyy-MM-dd,需大于等于当前时间
|
|
|
|
+ unitParams.put("begin_time",DateUtils.formatDate(new Date()));
|
|
|
|
+ if(null!=strategy.getConvertId()){
|
|
|
|
+ unitParams.put("convert_id",strategy.getConvertId());
|
|
|
|
+ }
|
|
|
|
+ // 当计划类型是3/4/5时必填
|
|
|
|
+ if(null!=strategy.getUrl()&&!"".equals(strategy.getUrl())){
|
|
|
|
+ unitParams.put("url",strategy.getUrl());
|
|
|
|
+ }
|
|
|
|
+ JSONArray appIds = JSONArray.parseArray(strategy.getAppIds());
|
|
|
|
+ //TODO 先只做单appId的逻辑
|
|
|
|
+ if(null!=appIds&&!appIds.isEmpty()){
|
|
|
|
+ unitParams.put("app_id",appIds.getLong(0));
|
|
|
|
+ }
|
|
|
|
+ unitParams.put("show_mode",2);
|
|
|
|
+ unitParams.put("speed",1);
|
|
|
|
+ // 设置定向内容
|
|
|
|
+ JSONObject targetJsonObject = new JSONObject();
|
|
|
|
+ targetJsonObject.put("region",JSONArray.parseArray(strategy.getRegion()));
|
|
|
|
+ targetJsonObject.put("ages_range",JSONArray.parseArray(strategy.getAgesRange()));
|
|
|
|
+ targetJsonObject.put("gender",strategy.getGender());
|
|
|
|
+ targetJsonObject.put("platform_os",strategy.getPlatformOs());
|
|
|
|
+ JSONArray population = JSONArray.parseArray(strategy.getPopulation());
|
|
|
|
+ if(null!=population&&!population.isEmpty()){
|
|
|
|
+ targetJsonObject.put("population",population);
|
|
|
|
+ }
|
|
|
|
+ JSONArray excludePopulation = JSONArray.parseArray(strategy.getExcludePopulation());
|
|
|
|
+ if(null!=excludePopulation&&!excludePopulation.isEmpty()){
|
|
|
|
+ targetJsonObject.put("paid_audience",excludePopulation);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ unitParams.put("target",targetJsonObject);
|
|
|
|
+ return unitParams;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private static List<Long> splitCapBidByDomain(Long cpaBidMin, Long cpaBidMax, Long stepSize) {
|
|
|
|
+ List<Long>result = new ArrayList<>();
|
|
|
|
+ if(cpaBidMin>cpaBidMax){
|
|
|
|
+ return result;
|
|
|
|
+ }
|
|
|
|
+ if(cpaBidMin.equals(cpaBidMax)){
|
|
|
|
+ result.add(cpaBidMin);
|
|
|
|
+ return result;
|
|
|
|
+ }
|
|
|
|
+ if(cpaBidMin<cpaBidMax){
|
|
|
|
+ result.add(cpaBidMin);
|
|
|
|
+ result.add(cpaBidMax);
|
|
|
|
+ while (cpaBidMin<cpaBidMax){
|
|
|
|
+ cpaBidMin+=stepSize;
|
|
|
|
+ if(cpaBidMin>= cpaBidMax){
|
|
|
|
+ return result;
|
|
|
|
+ }
|
|
|
|
+ result.add(cpaBidMin);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return result;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private JSONObject creativeParams(CtopOauthToken token,Long unitId,KuaiShouVideoGet videoItem,int unitCnt,KuaishouStrategy strategy){
|
|
|
|
+ JSONObject creativeParams = new JSONObject();
|
|
|
|
+ creativeParams.put("advertiser_id",token.getAccountId());
|
|
|
|
+ creativeParams.put("unit_id",unitId);
|
|
|
|
+ creativeParams.put("photo_id",videoItem.getPhotoId());
|
|
|
|
+ String uuid = UUID.randomUUID().toString();
|
|
|
|
+ creativeParams.put("creative_name","智能创意-"+uuid+unitCnt+"-"+1);
|
|
|
|
+ creativeParams.put("creative_material_type",videoItem.getMaterialType());
|
|
|
|
+ creativeParams.put("action_bar_text","立即下载");
|
|
|
|
+ creativeParams.put("description","太美了!来电显示设为视频铃声,来电话就开始播放~");
|
|
|
|
+ // 安卓下载中间页ID
|
|
|
|
+ JSONArray siteIdArray = JSONArray.parseArray(strategy.getSiteId());
|
|
|
|
+ if(null == siteIdArray||!siteIdArray.isEmpty()){
|
|
|
|
+ JSONObject siteObject = siteIdArray.getJSONObject(0);
|
|
|
|
+ Long appId = siteObject.getLong("appId");
|
|
|
|
+ Long siteId = siteObject.getLong("siteId");
|
|
|
|
+ if(null!=siteId){
|
|
|
|
+ creativeParams.put("site_id",siteId);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ if(null != strategy.getClickTrackUrl()&&!"".equals(strategy.getClickTrackUrl())){
|
|
|
|
+ creativeParams.put("click_track_url",strategy.getClickTrackUrl());
|
|
|
|
+ }
|
|
|
|
+ if(null != strategy.getImpressionUrl()&&!"".equals(strategy.getImpressionUrl())){
|
|
|
|
+ creativeParams.put("impression_url",strategy.getImpressionUrl());
|
|
|
|
+ }
|
|
|
|
+ if(null != strategy.getActionbarClickUrl()&&!"".equals(strategy.getActionbarClickUrl())){
|
|
|
|
+ creativeParams.put("actionbar_click_url",strategy.getActionbarClickUrl());
|
|
|
|
+ }
|
|
|
|
+ if(null!=strategy.getCreativeCategory()){
|
|
|
|
+ creativeParams.put("creative_category",strategy.getCreativeCategory());
|
|
|
|
+ creativeParams.put("creative_tag",JSONArray.parseArray(strategy.getCreativeTag()));
|
|
|
|
+ }
|
|
|
|
+ return creativeParams;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private String getStragyDefDomain(AiKuaishouStrategyTemplate strategyTemplate,KuaishouStrategy strategy){
|
|
|
|
+ String definitionDomain = null;
|
|
|
|
+ if (strategyTemplate.getTestDirection().equals("age")){
|
|
|
|
+ definitionDomain = strategy.getAgesRange();
|
|
|
|
+ }
|
|
|
|
+ if (strategyTemplate.getTestDirection().equals("gender")){
|
|
|
|
+ definitionDomain = strategy.getGender().toString();
|
|
|
|
+ }
|
|
|
|
+ if (strategyTemplate.getTestDirection().equals("region")){
|
|
|
|
+ definitionDomain = strategy.getRegion();
|
|
|
|
+ }
|
|
|
|
+ if (strategyTemplate.getTestDirection().equals("operation_system")){
|
|
|
|
+ definitionDomain = strategy.getPlatformOs().toString();
|
|
|
|
+ }
|
|
|
|
+ return definitionDomain;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private JSONObject createUnitParams(CtopOauthToken token,Long campaignId,KuaishouStrategy strategy,AiKuaishouStrategyTemplate strategyTemplate,
|
|
|
|
+ AiKuaishouStrategyTargetBase item,String timestamp,int unitCnt){
|
|
|
|
+ // 拼装创建组的入参 json
|
|
|
|
+ JSONObject unitParams = new JSONObject();
|
|
|
|
+ unitParams.put("advertiser_id",token.getAccountId());
|
|
|
|
+ unitParams.put("campaign_id",campaignId);
|
|
|
|
+ String uuid = UUID.randomUUID().toString();
|
|
|
|
+ unitParams.put("unit_name", strategy.getStrategyName() + item.getTargetType() + item.getTargetContent() + uuid + unitCnt);
|
|
|
|
+ unitParams.put("bid_type",10);
|
|
|
|
+ unitParams.put("ocpx_action_type",180);
|
|
|
|
+
|
|
|
|
+ if(null!=strategy.getUseAppMarket()&&!"".equals(strategy.getUseAppMarket())){
|
|
|
|
+ unitParams.put("use_app_market",Integer.parseInt(strategy.getUseAppMarket()));
|
|
|
|
+ }
|
|
|
|
+ if(null!=strategy.getAppStore()&&!"".equals(strategy.getAppStore())){
|
|
|
|
+ unitParams.put("app_store",JSONArray.parseArray(strategy.getAppStore()));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 在本策略中,都是 OCPM 的出价方式
|
|
|
|
+ unitParams.put("cpa_bid",strategy.getCpaBid());
|
|
|
|
+ unitParams.put("ocpx_action_type",strategy.getOcpxActionType());
|
|
|
|
+ unitParams.put("deep_conversion_type",strategy.getDeepConversionType());
|
|
|
|
+ unitParams.put("deep_conversion_bid",strategy.getDeepConversionBid());
|
|
|
|
+ unitParams.put("scene_id",JSONArray.parseArray(strategy.getSceneId()));
|
|
|
|
+ unitParams.put("unit_type",4);
|
|
|
|
+
|
|
|
|
+ // 格式为yyyy-MM-dd,需大于等于当前时间
|
|
|
|
+ unitParams.put("begin_time",DateUtils.formatDate(new Date()));
|
|
|
|
+ if(null!=strategy.getConvertId()){
|
|
|
|
+ unitParams.put("convert_id",strategy.getConvertId());
|
|
|
|
+ }
|
|
|
|
+ // 当计划类型是3/4/5时必填
|
|
|
|
+ if(null!=strategy.getUrl()&&!"".equals(strategy.getUrl())){
|
|
|
|
+ unitParams.put("url",strategy.getUrl());
|
|
|
|
+ }
|
|
|
|
+ JSONArray appIds = JSONArray.parseArray(strategy.getAppIds());
|
|
|
|
+ //TODO 先只做单appId的逻辑
|
|
|
|
+ if(null!=appIds&&!appIds.isEmpty()){
|
|
|
|
+ unitParams.put("app_id",appIds.getLong(0));
|
|
|
|
+ }
|
|
|
|
+ unitParams.put("show_mode",2);
|
|
|
|
+ unitParams.put("speed",1);
|
|
|
|
+ // 设置定向内容
|
|
|
|
+ JSONObject targetJsonObject = new JSONObject();
|
|
|
|
+ //TODO 34
|
|
|
|
+ if (strategyTemplate.getTestDirection().equals("region")){
|
|
|
|
+ targetJsonObject.put("region",JSONArray.parseArray(item.getTargetContent()));
|
|
|
|
+ } else {
|
|
|
|
+ targetJsonObject.put("region",JSONArray.parseArray(strategy.getRegion()));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ //TODO 5
|
|
|
|
+ if (strategyTemplate.getTestDirection().equals("age")){
|
|
|
|
+ targetJsonObject.put("ages_range",JSONArray.parseArray(item.getTargetContent()));
|
|
|
|
+ } else{
|
|
|
|
+ targetJsonObject.put("ages_range",JSONArray.parseArray(strategy.getAgesRange()));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ //TODO 3
|
|
|
|
+ if (strategyTemplate.getTestDirection().equals("gender")){
|
|
|
|
+ targetJsonObject.put("gender",item.getTargetContent());
|
|
|
|
+ } else {
|
|
|
|
+ targetJsonObject.put("gender",strategy.getGender());
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ //TODO
|
|
|
|
+ if (strategyTemplate.getTestDirection().equals("operation_system")){
|
|
|
|
+ targetJsonObject.put("platform_os",item.getTargetContent());
|
|
|
|
+ } else {
|
|
|
|
+ targetJsonObject.put("platform_os",strategy.getPlatformOs());
|
|
|
|
+ }
|
|
|
|
+ JSONArray population = JSONArray.parseArray(strategy.getPopulation());
|
|
|
|
+ if(null!=population&&!population.isEmpty()){
|
|
|
|
+ targetJsonObject.put("population",population);
|
|
|
|
+ }
|
|
|
|
+ JSONArray excludePopulation = JSONArray.parseArray(strategy.getExcludePopulation());
|
|
|
|
+ if(null!=excludePopulation&&!excludePopulation.isEmpty()){
|
|
|
|
+ targetJsonObject.put("paid_audience",excludePopulation);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ unitParams.put("target",targetJsonObject);
|
|
|
|
+ return unitParams;
|
|
|
|
+ }
|
|
|
|
+ private void createCreative(CtopOauthToken token,JSONObject creativeParams,Long campaignId,Long unitId,
|
|
|
|
+ KuaishouStrategy strategy,AiKuaishouStrategyTargetUnion targetUnion,int creativeCnt,KuaiShouVideoGet videoGet){
|
|
|
|
+ creativeParams.put("creative_name",creativeParams.getString("creative_name")+creativeCnt);
|
|
|
|
+ Map<String, Object> creativeCreateResult = kuaishouInterfaceService.creativeCreate
|
|
|
|
+ (token.getAccessToken(), token.getAccountId(), creativeParams,1);
|
|
|
|
+ Integer creativeCode = (Integer) creativeCreateResult.get("code");
|
|
|
|
+ String creativeMessage = (String) creativeCreateResult.get("message");
|
|
|
|
+ if(creativeCode != 0 ){
|
|
|
|
+ log.info("创意创建失败=>message:{}",creativeMessage);
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ Long creativeId = (Long) creativeCreateResult.get("creativeId");
|
|
|
|
+ AiKuaishouStrategyMapCreative mapCreative = new AiKuaishouStrategyMapCreative();
|
|
|
|
+ mapCreative.setAccountId(token.getAccountId());
|
|
|
|
+ mapCreative.setCampaignId(campaignId);
|
|
|
|
+ mapCreative.setUnitId(unitId);
|
|
|
|
+ //TODO 添加
|
|
|
|
+ mapCreative.setVideoSignature(videoGet.getSignature());
|
|
|
|
+ mapCreative.setCreativeId(creativeId);
|
|
|
|
+ mapCreative.setStrategyId(strategy.getId());
|
|
|
|
+ if(null!=targetUnion){
|
|
|
|
+ mapCreative.setStrategyTargetUnionId(targetUnion.getId());
|
|
|
|
+ }
|
|
|
|
+ mapCreativeService.save(mapCreative);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private String getImageToken(String imageUrl, String imageCode, Long accountId,String token){
|
|
|
|
+ // 根据imageUrl 和 imageCode,获取 imageToken (创建创意用)
|
|
|
|
+ KuaiShouImageGet imageGet = imageGetService.getOneByParams(null,imageCode);
|
|
|
|
+ if ((null == imageGet) || ("".equals(imageGet.getImageToken()))){
|
|
|
|
+ // 如果取不到image_token
|
|
|
|
+ // 则调用上传url的方法 kuauiShouImageUpload(url, signature, accountId, oauthToken.getAccessToken());
|
|
|
|
+ return materialUploadService.kuauiShouImageUpload(imageUrl,imageCode,accountId,token);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return imageGet.getImageToken();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private List<AiKuaishouStrategyTargetBase> splitBasesByDomain(String definitionDomain,
|
|
|
|
+ List<AiKuaishouStrategyTargetBase> targetBases) {
|
|
|
|
+ if(null==definitionDomain){
|
|
|
|
+ return targetBases;
|
|
|
|
+ }
|
|
|
|
+ if(null == targetBases||targetBases.isEmpty()){
|
|
|
|
+ return null;
|
|
|
|
+ }
|
|
|
|
+ String type = targetBases.get(0).getType();
|
|
|
|
+ List<AiKuaishouStrategyTargetBase> resultBase = null;
|
|
|
|
+ //数字
|
|
|
|
+ if("number".equals(type)){
|
|
|
|
+ resultBase = targetBases.stream().filter(target -> definitionDomain.equals(target.getTargetContent())).
|
|
|
|
+ collect(Collectors.toList());
|
|
|
|
+ }
|
|
|
|
+ //数组
|
|
|
|
+ if("array".equals(type)){
|
|
|
|
+ resultBase = targetBases.stream().filter(target -> checkContainsEveryOne(definitionDomain,target.getTargetContent())).map(target->{
|
|
|
|
+ target.setTargetContent(getContainsElement(definitionDomain,target.getTargetContent()));
|
|
|
|
+ return target;
|
|
|
|
+ }).collect(Collectors.toList());
|
|
|
|
+ }
|
|
|
|
+ return resultBase;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 后期需要优化
|
|
|
|
+ * 判定是否存在交集
|
|
|
|
+ * @param definitionDomain
|
|
|
|
+ * @param targetContent
|
|
|
|
+ * @return
|
|
|
|
+ */
|
|
|
|
+ private boolean checkContainsEveryOne(String definitionDomain, String targetContent) {
|
|
|
|
+ JSONArray definitionDomainArray = JSONArray.parseArray(definitionDomain);
|
|
|
|
+ JSONArray targetContentArray = JSONArray.parseArray(targetContent);
|
|
|
|
+ if(definitionDomainArray.size()>=targetContentArray.size()){
|
|
|
|
+ return checkContains(targetContentArray,definitionDomainArray);
|
|
|
|
+ }
|
|
|
|
+ return checkContains(definitionDomainArray,targetContentArray);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ *
|
|
|
|
+ * @param shortArray
|
|
|
|
+ * @param longArray
|
|
|
|
+ * @return
|
|
|
|
+ */
|
|
|
|
+ private boolean checkContains(JSONArray shortArray, JSONArray longArray) {
|
|
|
|
+ for (int i = 0; i < shortArray.size(); i++) {
|
|
|
|
+ Long s = shortArray.getLong(i);
|
|
|
|
+ for(int j=0;j<longArray.size();j++){
|
|
|
|
+ Long l = longArray.getLong(j);
|
|
|
|
+ if(s.equals(l)){
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 获取交集元素
|
|
|
|
+ * @param definitionDomain
|
|
|
|
+ * @param targetContent
|
|
|
|
+ * @return
|
|
|
|
+ */
|
|
|
|
+ private String getContainsElement(String definitionDomain, String targetContent){
|
|
|
|
+ JSONArray definitionDomainArray = JSONArray.parseArray(definitionDomain);
|
|
|
|
+ JSONArray targetContentArray = JSONArray.parseArray(targetContent);
|
|
|
|
+ return getContainsArray(definitionDomainArray,targetContentArray);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private String getContainsArray(JSONArray definitionDomainArray, JSONArray targetContentArray) {
|
|
|
|
+ log.info("definitionDomainArray:"+definitionDomainArray.toJSONString());
|
|
|
|
+ log.info("targetContentArray:"+targetContentArray.toJSONString());
|
|
|
|
+ JSONArray resultArray = new JSONArray();
|
|
|
|
+ for (int i = 0; i < definitionDomainArray.size(); i++) {
|
|
|
|
+ Long s = definitionDomainArray.getLong(i);
|
|
|
|
+ for(int j=0;j<targetContentArray.size();j++){
|
|
|
|
+ Long l = targetContentArray.getLong(j);
|
|
|
|
+ if(l.equals(s)){
|
|
|
|
+ resultArray.add(l);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ log.info("result:"+resultArray.toJSONString());
|
|
|
|
+ return resultArray.toJSONString();
|
|
|
|
+ }
|
|
|
|
+}
|