zhaotx 9 месяцев назад
Родитель
Сommit
c04f7c097e

+ 11 - 1
job-bytedance/src/main/java/cn/com/ctop/job/bytedance/controller/FeishuController.java

@@ -2,6 +2,7 @@ package cn.com.ctop.job.bytedance.controller;
 
 import cn.com.ctop.job.bytedance.data.service.*;
 import cn.com.ctop.job.bytedance.handler.CsjResoureJob;
+import cn.com.ctop.job.bytedance.handler.PromoteLinkRefreshJob;
 import cn.com.ctop.job.bytedance.utils.OssUtil;
 import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
@@ -50,13 +51,18 @@ public class FeishuController {
     private IMovieRankPushService movieRankPushService;
     @Autowired
     private IFeishuDocSyncService feishuDocSyncService;
+    @Autowired
+    private PromoteLinkRefreshJob promoteLinkRefreshJob;
+    @Autowired
+    private IBytedancePromoteRefreshService bytedancePromoteRefreshService;
 
     @Autowired
     IUserLoginRecordService userLoginRecordService;
     @PostMapping("/test")
     public void test(@RequestBody String str) throws Exception {
 //        bytedanceProductUpdateService.initNew();
-        bytedanceProductUpdateService.runMp();
+//        bytedanceProductUpdateService.runMp();
+//        bytedanceProductUpdateService.refreshAlbumStatus();
 //        reportsAdPushService.test();
 //        totalRankPushService.test();
 //        dateReportsPushService.preSend();
@@ -93,6 +99,10 @@ public class FeishuController {
 //        feishuDocSyncService.syncBytedanceDysDistributeMovies();
 //        bytedanceProductUpdateService.runDysDistribute();
 //        bytedanceProductUpdateService.clearAuditFailProduct();
+
+//        promoteLinkRefreshJob.runAdvertiserTaskStatusJob();
+//        promoteLinkRefreshJob.runHistoryAdvertiserTaskStatus();
+
     }
 
 

+ 58 - 84
job-bytedance/src/main/java/cn/com/ctop/job/bytedance/data/service/impl/BytedanceProductUpdateServiceImpl.java

@@ -9,6 +9,7 @@ import cn.com.ctop.job.bytedance.data.utils.DateUtils;
 import cn.com.ctop.job.bytedance.data.utils.HttpUtils;
 import cn.com.ctop.job.bytedance.utils.BytedanceProductDocProperties;
 import cn.com.ctop.job.bytedance.utils.FeishuRobotUtil;
+import cn.com.ctop.job.bytedance.utils.RateLimitUtil;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
@@ -23,6 +24,7 @@ import javax.annotation.Resource;
 import java.io.IOException;
 import java.util.*;
 import java.util.stream.Collectors;
+
 @Slf4j
 @Service
 public class BytedanceProductUpdateServiceImpl implements IBytedanceProductUpdateService {
@@ -32,7 +34,8 @@ public class BytedanceProductUpdateServiceImpl implements IBytedanceProductUpdat
     private RedisTemplate redis2Template_db5;
     @Autowired
     BytedanceProductDocProperties productDocProperties;
-
+    @Autowired
+    private RateLimitUtil rateLimitUtil;
 
     public static final String DOUYIN_MOVIE_SOURCE_APP="ttd652a0ef496310bd01";
 
@@ -82,8 +85,6 @@ public class BytedanceProductUpdateServiceImpl implements IBytedanceProductUpdat
         }
         log.info("付费商品库更新完成");
 
-
-
         log.info("免费商品库更新中...");
         List<AigcProductPlatform> iaaPlatformList = bytedanceProductUpdateMapper.getPlatformList("IAA", platformId); //商品库列表
         if (!iaaPlatformList.isEmpty()){
@@ -98,7 +99,6 @@ public class BytedanceProductUpdateServiceImpl implements IBytedanceProductUpdat
         }
         log.info("免费商品库更新完成");
 
-
         errors.removeIf(s -> s.contains("剧目不可用"));
         if (!Check.isNull(errors)){
             List<String> info = new ArrayList<>();
@@ -123,17 +123,17 @@ public class BytedanceProductUpdateServiceImpl implements IBytedanceProductUpdat
         for (BytedanceProductDocProperties.DysDistributorLinkTable table : productDocProperties.getDysMovies()){
             sourceCopywriteOwner.put(table.getMovieSource(), table.getCopyrightOwner());
         }
-        iaaPlatformList.forEach(p -> {
+        for (AigcProductPlatform p : iaaPlatformList){
             p.setCopyrightOwner(sourceCopywriteOwner.get(String.valueOf(p.getSource())));
-        });
+        }
 
         List<AigcProductAlbum> iaaVideos = bytedanceProductUpdateMapper.getDysDistributorVideo("IAA");
-        iaaVideos.forEach(v -> {
+        for (AigcProductAlbum v : iaaVideos){
             v.setPlayletId(getQueryParam(v.getAlbumLink(), "playlet_id"));
             if (StringUtils.isEmpty(v.getImg())){
                 v.setImg("https://oss.artlinkworld.cn/imgsource/20251110-144804.png");//兜底
             }
-        });
+        }
 
         runDysProductTask(iaaPlatformList, iaaVideos, new HashMap<>(), errors, BusinessType.IAA);
         System.out.println("端原生分销商品库更新完成");
@@ -170,7 +170,6 @@ public class BytedanceProductUpdateServiceImpl implements IBytedanceProductUpdat
                     continue;
                 }
                 if (!productTypeMap.containsKey(album.getTheme())){
-//                    errors.add("上传商品:没有找到对应的产品类型:" + album.getTheme());
                     System.out.println(">>>>没有找到对应的产品类型:" + album.getTheme());
                     System.out.println("\n\n\n");
                     continue;
@@ -178,7 +177,6 @@ public class BytedanceProductUpdateServiceImpl implements IBytedanceProductUpdat
                 String schemaLink = null;
                 if (platform.getIsBytedance()==1){
                     if (!albumIdToLinkMap.containsKey(album.getAlbumId())){
-//                        errors.add("上传商品:没有找到对应的推广链接,无法上传商品,剧目ID:" + album.getAlbumId() + ", 剧目名称:" + album.getName());
                         System.out.println(">>>>没有找到对应的推广链接,无法上传商品,剧目ID:" + album.getAlbumId() + ", 剧目名称:" + album.getName());
                         System.out.println("\n\n\n");
                         continue;
@@ -191,13 +189,11 @@ public class BytedanceProductUpdateServiceImpl implements IBytedanceProductUpdat
         }
     }
 
-
     private void runDysProductTask(List<AigcProductPlatform> platformList, List<AigcProductAlbum> albums, Map<String, JSONObject> productTypeMap, Set<String> errors, BusinessType businessType) throws IOException {
         for (AigcProductPlatform platform : platformList){
             // 拉取已上传剧目,对比,得到未上传剧目列表
             List<AigcProductAlbum> hasUploadAlbums = hasUploadProductList(platform);
             List<String> hasUploadPlayletIds = hasUploadAlbums.stream().map(AigcProductAlbum::getPlayletId).collect(Collectors.toList());
-//            System.out.println("已上传剧目数: "+hasUpload.size() + ", 商品库ID: "+platform.getPlatformId()+", 来源:"+platform.getSource());
             List<AigcProductAlbum> toUpload = albums.stream()
                     .filter(o -> !hasUploadPlayletIds.contains(o.getPlayletId()))
                     .filter(o -> Objects.equals(o.getPlatformType(), String.valueOf(platform.getPlatformType())))
@@ -206,9 +202,6 @@ public class BytedanceProductUpdateServiceImpl implements IBytedanceProductUpdat
                     .collect(Collectors.toList());
             System.out.println("待上传剧目数: "+toUpload.size() + ", 商品库ID: "+platform.getPlatformId()+", 来源:"+platform.getSource());
 
-//            if (1==1){
-//                return;
-//            }
             if (toUpload.isEmpty()){
                 continue;
             }
@@ -217,9 +210,6 @@ public class BytedanceProductUpdateServiceImpl implements IBytedanceProductUpdat
             for (AigcProductAlbum album : toUpload){
                 JSONObject defaultTheme = JSONObject.parseObject("{\"first_type_id\":\"2038\",\"first_type_name\":\"单剧目-短剧\",\"second_type_id\":\"203807\",\"second_type_name\":\"其他剧情\",\"third_type_name\":\"其他\",\"third_type_id\":\"20380701\"}");
                 uploadProduct(platform, album, null, errors, defaultTheme, businessType);
-//                try {
-//                    Thread.sleep(80);
-//                }catch (InterruptedException ignored){}
             }
         }
     }
@@ -249,7 +239,12 @@ public class BytedanceProductUpdateServiceImpl implements IBytedanceProductUpdat
         if (!filtering.isEmpty()){
             params.put("filtering", filtering);
         }
-        String res = HttpUtils.httpGet(url, params, headers);
+
+        String res = null;
+        if (rateLimitUtil.acquire(platform.getMiAppid(), url)){
+            res = HttpUtils.httpGet(url, params, headers);
+        }
+
         if (res == null || res.isEmpty()) {
             System.out.println("获取已上传商品列表失败, 广告账号ID:" + platform.getAdvertiserId() + ", 商品库ID:" + platform.getPlatformId());
             return new ArrayList<>();
@@ -289,7 +284,6 @@ public class BytedanceProductUpdateServiceImpl implements IBytedanceProductUpdat
         }
     }
 
-
     @Override
     public void authMovieResource() {
         //原片授权
@@ -337,7 +331,6 @@ public class BytedanceProductUpdateServiceImpl implements IBytedanceProductUpdat
         }
     }
 
-
     /**
      * 初始化新的字节小程序; 新增字节用的商品库时调用
      */
@@ -387,7 +380,12 @@ public class BytedanceProductUpdateServiceImpl implements IBytedanceProductUpdat
             app_page.put("start_param", "link_id=574391");
             requestBody.put("app_page", app_page);
 
-            String res = HttpUtils.httpPostRequest(createLinkUrl, requestBody, headers);
+            String res = null;
+            // Rate limit for /open_api/v3.0/tools/micro_app/create/
+            if (rateLimitUtil.acquire(obj.getMiAppid(), createLinkUrl)){
+                res = HttpUtils.httpPostRequest(createLinkUrl, requestBody, headers);
+            }
+
             if (res == null || res.isEmpty()){
                 System.out.println("创建字节小程序失败,advertiser_id:"+obj.getMiniAppAdvertiserId());
             }else {
@@ -406,14 +404,6 @@ public class BytedanceProductUpdateServiceImpl implements IBytedanceProductUpdat
         }
     }
 
-    /**
-     * 短剧更新
-     * @param platform 商品库信息
-     * @param album 剧信息
-     * @param schemaLink 小程序推广链接 schemaLink(sslocal://sxxxxx
-     * @param errorBlanket
-     * @param productType 商品短剧类型
-     */
     private void uploadProduct(AigcProductPlatform platform, AigcProductAlbum album,
                                   String schemaLink, Set<String> errorBlanket, JSONObject productType,
                               BusinessType businessType){
@@ -475,7 +465,6 @@ public class BytedanceProductUpdateServiceImpl implements IBytedanceProductUpdat
             profession.put("membership_types", "5");
             profession.put("starting_unlock_episode", String.valueOf(album.getStartingUnlockEpisode()));
         }
-//        String play_platform = album.getPlay_platform();
         List<String> ad_carrier = new ArrayList<>();
         if (platform.getIsBytedance() == 1){
             ad_carrier.add("字节小程序");
@@ -497,41 +486,23 @@ public class BytedanceProductUpdateServiceImpl implements IBytedanceProductUpdat
         product_info.put("profession", profession);
         request.put("product_info", product_info);
 
-
-//        System.out.println("请求地址:" + url);
-//        System.out.println("请求头:" + headers);
-//        System.out.println("请求参数:" + request.toJSONString());
-        //重试3次
-        for (int retry = 0; retry < 3; retry++) {
-            String res = HttpUtils.httpPostRequest(url, request, headers);
+        // 单次请求(已移除重试逻辑)
+        String res = HttpUtils.httpPostRequest(url, request, headers);
+        if (res == null || res.isEmpty()){
+            String payName = businessType==BusinessType.IAP?"付费":"免费";
+            errorBlanket.add("上传"+payName+"商品失败,msg: 请求失败或无响应, 关联剧目: " + album.getName());
+        } else {
             JSONObject btRes = JSONObject.parseObject(res);
             int code = btRes.getIntValue("code");
             if (code != 0){
                 String errorMessage = btRes.getString("message");
-                if (errorMessage.contains("系统请求频率超限") && retry < 2){
-                    try {
-                        Thread.sleep(100);
-                    }catch (InterruptedException ignored){}
-                    continue;
-                }else {
-                    String payName = businessType==BusinessType.IAP?"付费":"免费";
-                    errorBlanket.add("上传"+payName+"商品失败,code: [" + code + "], msg: [" + errorMessage +"], 关联剧目: " + album.getName());
-                    break;
-                }
-            }else {
-                break;
+                String payName = businessType==BusinessType.IAP?"付费":"免费";
+                errorBlanket.add("上传"+payName+"商品失败,code: [" + code + "], msg: [" + errorMessage +"], 关联剧目: " + album.getName());
             }
         }
 
     }
 
-    /**
-     *
-     * @param advertiserId 商品库主账户ID,也是创建字节小程序的ID
-     * @param accessToken 广告账号的access_token
-     * @param instanceId 字节小程序的实例ID
-     * @return map key: album_id, value: 对应的字节小程序推广链接,sslocal开头
-     */
     private Map<String, String> getBytedancePlayletPromoteLink(String advertiserId, String accessToken, String instanceId, List<AigcProductAlbum> requests, Set<String> errorBlanket){
         List<String> reqAlbumIds = requests.stream()
                 .map(obj -> obj.getAlbumId())
@@ -539,6 +510,13 @@ public class BytedanceProductUpdateServiceImpl implements IBytedanceProductUpdat
         Map<String, String> res = new HashMap<>(reqAlbumIds.size());
 
         List<AigcProductAlbum> unfound = new ArrayList<>(requests);
+    /*
+     *
+     * @param advertiserId 商品库主账户ID,也是创建字节小程序的ID
+     * @param accessToken 广告账号的access_token
+     * @param instanceId 字节小程序的实例ID
+     * @return map key: album_id, value: 对应的字节小程序推广链接,sslocal开头
+     */
         for (int page=1; page<=20; page++){
             JSONArray playletDetailInfo = getPlayletDetail(advertiserId, accessToken, instanceId, errorBlanket,page);
             if (playletDetailInfo == null || playletDetailInfo.isEmpty()){
@@ -553,7 +531,7 @@ public class BytedanceProductUpdateServiceImpl implements IBytedanceProductUpdat
                     }
                 }
             }
-             unfound = requests.stream()
+            unfound = requests.stream()
                     .filter(obj -> !res.containsKey(obj.getAlbumId()))
                     .collect(Collectors.toList());
             if (unfound.isEmpty()){
@@ -600,7 +578,12 @@ public class BytedanceProductUpdateServiceImpl implements IBytedanceProductUpdat
             requestBody.put("app_page", app_page_list);
             requestBody.put("tag_info", "1050107001");
 
-            String rr = HttpUtils.httpPostRequest(editLinkUrl, requestBody, headers);
+            String rr = null;
+            // Rate limit for /open_api/v3.0/tools/micro_app/update/
+            if (rateLimitUtil.acquire(advertiserId, editLinkUrl)){
+                rr = HttpUtils.httpPostRequest(editLinkUrl, requestBody, headers);
+            }
+
             if (rr == null || rr.isEmpty()){
                 errorBlanket.add("创建字节小程序推广链接失败, 广告账号ID:" + advertiserId + ", 小程序资产ID:" + instanceId);
                 System.out.println("创建字节小程序推广链接失败");
@@ -647,22 +630,11 @@ public class BytedanceProductUpdateServiceImpl implements IBytedanceProductUpdat
 
     }
 
-    /**
-     * 获取字节小程序中的推广链接
-     * @param advertiserId
-     * @param accessToken
-     * @param instanceId
-     * @param errorBlanket
-     * @param pageNum 从外部调用填1
-     * @return
-     */
     private JSONArray getPlayletDetail(String advertiserId, String accessToken, String instanceId, Set<String> errorBlanket, Integer pageNum) {
         if (pageNum >60){
             return new JSONArray();
         }
         String getPlayletDetailUrl = "https://api.oceanengine.com/open_api/v3.0/tools/asset_link/list/";
-        getPlayletDetailUrl = getPlayletDetailUrl.replace("${ADVERTISER_ID}", advertiserId);
-        getPlayletDetailUrl = getPlayletDetailUrl.replace("${INSTANCE_ID}", instanceId);
         Map<String, String> headers = new HashMap<>();
         headers.put("Access-Token", accessToken);
         Map<String, Object> params = new HashMap<>();
@@ -670,7 +642,13 @@ public class BytedanceProductUpdateServiceImpl implements IBytedanceProductUpdat
         params.put("page", String.valueOf(pageNum));
         params.put("page_size", "100");
         params.put("filtering", "{\"instance_id\":" + instanceId + "}");
-        String res = HttpUtils.httpGet(getPlayletDetailUrl, params, headers);
+
+        String res = null;
+        // Rate limit for /open_api/v3.0/tools/asset_link/list/
+        if (rateLimitUtil.acquire(advertiserId, getPlayletDetailUrl)){
+            res = HttpUtils.httpGet(getPlayletDetailUrl, params, headers);
+        }
+
         if (res == null || res.isEmpty()) {
             errorBlanket.add("获取字节小程序推广链接失败, 广告账号ID:" + advertiserId + ", 小程序资产ID:" + instanceId);
             System.out.println("获取字节小程序推广链接失败, 广告账号ID:" + advertiserId + ", 小程序资产ID:" + instanceId);
@@ -684,23 +662,15 @@ public class BytedanceProductUpdateServiceImpl implements IBytedanceProductUpdat
                 return new JSONArray();
             }else {
                 JSONArray list = jsonObject.getJSONObject("data").getJSONArray("list");
-                if (list.size() > 0){
-                    return list;
+                if (list == null || list.isEmpty()){
+                    return new JSONArray();
                 }
-//                JSONObject page_info = jsonObject.getJSONObject("data").getJSONObject("page_info");
-//                int total_page = page_info.getIntValue("total_page");
-//                if (total_page > pageNum){
-//                    //递归获取下一页
-//                    JSONArray nextPage = getPlayletDetail(advertiserId, accessToken, instanceId, errorBlanket, pageNum + 1);
-//                    list.addAll(nextPage);
-//                }
                 return list;
             }
         }
 
     }
 
-
     public void uploadAlbum() throws IOException {
         List<String> info = new ArrayList<>();
         info.add("仅微小剧目上传通知");
@@ -762,7 +732,13 @@ public class BytedanceProductUpdateServiceImpl implements IBytedanceProductUpdat
             Map<String, Object> params = new HashMap<>();
             params.put("album_id", al);
             params.put("app_id", app_id);
-            String res = HttpUtils.httpGet(url, params, headers);
+
+            String res = null;
+            // Rate limit for /open_api/v3.0/dpa/album_status/get/
+            if (rateLimitUtil.acquire(String.valueOf(app_id), url)){
+                res = HttpUtils.httpGet(url, params, headers);
+            }
+
             if (res == null || res.isEmpty()){
                 System.out.println("获取短剧状态失败,albumId: " + al);
             }else {
@@ -1040,7 +1016,6 @@ public class BytedanceProductUpdateServiceImpl implements IBytedanceProductUpdat
         headers.put("Content-Type", "application/json");
 
         System.out.println(requestBody.toJSONString());
-//        return null;
         String res = HttpUtils.httpPostRequest(url, requestBody, headers);
         System.out.println(">>> "+res);
         if (res == null || res.isEmpty()){
@@ -1066,7 +1041,6 @@ public class BytedanceProductUpdateServiceImpl implements IBytedanceProductUpdat
                 .getFirst(paramName);
     }
 
-
     private String getAppAccessToken(Long app_id, String app_secret){
         String url = "https://open.oceanengine.com/open_api/oauth2/app_access_token/";
         if (app_id == null || app_secret == null){

+ 2 - 1
job-bytedance/src/main/java/cn/com/ctop/job/bytedance/handler/PromoteLinkRefreshJob.java

@@ -98,7 +98,7 @@ public class PromoteLinkRefreshJob {
     public void runHistoryAdvertiserTaskStatus(){
         //恢复数据
         // 生成从 2025-04-01 开始的 2 天窗口,直到 2026-01-04(end 不超过 2026-01-04 23:59:59)
-        java.time.LocalDate endLimitDate = java.time.LocalDate.of(2026, 1, 4);
+        java.time.LocalDate endLimitDate = java.time.LocalDate.of(2026, 1, 1);
         java.time.LocalDateTime endLimit = endLimitDate.atTime(23, 59, 59);
 
         // 初始 start 日
@@ -119,6 +119,7 @@ public class PromoteLinkRefreshJob {
 
             // 使用 startTime / endTime 做你需要的操作(示例:打印或调用方法)
             System.out.println("startTime=" + startTime + ", endTime=" + endTime);
+            bytedancePromoteRefreshService.runAdvertiserTaskStatus(startTime, endTime);
             // 例: cpsReportService.runCPSSettlementDataRange(startTime, endTime);
 
             // 如果已经到达或超过限制,就退出

+ 6 - 0
job-bytedance/src/main/java/cn/com/ctop/job/bytedance/utils/RateLimitUtil.java

@@ -36,6 +36,12 @@ public class RateLimitUtil {
             .put("/open_api/2/advertiser/avatar/get/", 10)
             .put("/open_api/2/agent/advertiser_info/query/", 10)
             .put("/open_api/2/tools/site/get/", 10)
+            .put("/open_api/2/dpa/detail/get/", 10)
+            .put("/open_api/v3.0/tools/micro_app/create/", 10)
+            .put("/open_api/v3.0/tools/micro_app/update/", 10)
+            .put("/open_api/v3.0/dpa/album_status/get/", 10)
+            .put("/open_api/v3.0/dpa/album/create/", 10)
+            .put("/open_api/2/dpa/product/delete/", 50)
             .build();
 
     /**