Ver Fonte

feat: 处理接口限流问题

pudongliang há 4 meses atrás
pai
commit
3aa195c5c3

+ 25 - 1
src/main/java/com/moka/gdtauto/service/AdPlanCreativeDailyReportService.java

@@ -11,6 +11,7 @@ import com.moka.gdtauto.mapper.AdPlanConfMapper;
 import com.moka.gdtauto.mapper.AdPlanCreativeMapper;
 import com.moka.gdtauto.mapper.AdPlanMapper;
 import com.tencent.ads.model.v3.DailyReportsGetResponseData;
+import com.tencent.ads.exception.TencentAdsResponseException;
 import com.tencent.ads.model.v3.DailyReportsFilteringStruct;
 import com.tencent.ads.model.v3.FilterOperator;
 import com.tencent.ads.model.v3.ReportDateRange;
@@ -31,6 +32,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
 /**
@@ -236,7 +238,9 @@ public class AdPlanCreativeDailyReportService {
         filtering.add(filter);
 
         // 调用 API
-        DailyReportsGetResponseData response = tencentAds.dailyReports().dailyReportsGet(
+        DailyReportsGetResponseData response = null;
+        try {
+           response = tencentAds.dailyReports().dailyReportsGet(
                 "REPORT_LEVEL_DYNAMIC_CREATIVE",
                 dateRange,
                 groupBy,
@@ -249,6 +253,26 @@ public class AdPlanCreativeDailyReportService {
                 (long) ADGROUP_BATCH_LIMIT,
                 null   // modularDetailMode
         );
+        } catch (TencentAdsResponseException e) {
+            // 11016 Application has reached the request limit per second.
+            // 应用程序已达到每秒的请求限制,请稍后再试
+            // 11017 Application has reached the request limit per minute.
+            // 应用请求已达分钟频次上限,请登陆开发者后台查看当前接口对应分配的频次,并在要求范围内请求接口。
+            if (e.getCode() == 11016) {
+                log.warn("请求达到每秒限制,请稍后再试");
+                TimeUnit.SECONDS.sleep(1);
+            } else if (e.getCode() == 11017) {
+                log.warn("请求达到每分钟限制,请稍后再试");
+                // 计算出还剩多少时间
+                LocalDateTime now = LocalDateTime.now();
+                // the second-of-minute, from 0 to 59
+                int second = now.getSecond();
+                int sleepSeconds = (59 - second);
+                log.warn("请求达到每分钟限制,sleep:{} s", sleepSeconds);
+                TimeUnit.SECONDS.sleep(sleepSeconds);
+            }
+            syncBatch(tencentAds, accountId, batch, dateRange, groupBy, fields, timeLine, isReqTime);
+        }
 
         if (response == null) {
             log.warn("[syncBatch] accountId={} 未返回日报数据", accountId);

+ 56 - 30
src/main/java/com/moka/gdtauto/service/AdPlanDailyReportService.java

@@ -9,6 +9,7 @@ import com.moka.gdtauto.entity.AdPlanConf;
 import com.moka.gdtauto.mapper.AdPlanConfMapper;
 import com.moka.gdtauto.mapper.AdPlanMapper;
 import com.tencent.ads.model.v3.DailyReportsGetResponseData;
+import com.tencent.ads.exception.TencentAdsResponseException;
 import com.tencent.ads.model.v3.DailyReportsFilteringStruct;
 import com.tencent.ads.model.v3.FilterOperator;
 import com.tencent.ads.model.v3.ReportDateRange;
@@ -29,6 +30,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
 /**
@@ -50,13 +52,11 @@ public class AdPlanDailyReportService {
     /** REQUEST_TIME 口径同步字段 */
     private static final List<String> REQ_FIELDS = Arrays.asList(
             "adgroup_id", "date", "cost", "income_val_1", "income_roi_1",
-            "view_count", "valid_click_count", "ctr", "conversions_cost" ,"scan_follow_user_cost"
-    );
+            "view_count", "valid_click_count", "ctr", "conversions_cost", "scan_follow_user_cost");
 
     /** REPORTING_TIME 口径同步字段 */
     private static final List<String> REP_FIELDS = Arrays.asList(
-            "adgroup_id", "date", "income_val_1", "income_roi_1"
-    );
+            "adgroup_id", "date", "income_val_1", "income_roi_1");
 
     private static final int PAGE_SIZE = 100;
     private static final int ADGROUP_BATCH_LIMIT = 100;
@@ -94,12 +94,13 @@ public class AdPlanDailyReportService {
     /**
      * 执行同步逻辑
      *
-     * @param startTime   开始时间
-     * @param endTime     结束时间
-     * @param timeLine    时间口径
-     * @param isReqTime   是否为 REQUEST_TIME 口径(决定更新哪些字段)
+     * @param startTime 开始时间
+     * @param endTime   结束时间
+     * @param timeLine  时间口径
+     * @param isReqTime 是否为 REQUEST_TIME 口径(决定更新哪些字段)
      */
-    private void doSyncDailyReport(Long confId, LocalDateTime startTime, LocalDateTime endTime, String timeLine, boolean isReqTime) {
+    private void doSyncDailyReport(Long confId, LocalDateTime startTime, LocalDateTime endTime, String timeLine,
+            boolean isReqTime) {
         // 1. 分页查询 create_time 在范围内的 adPlanConf
         int confPageNum = 1;
         int totalUpdated = 0;
@@ -146,7 +147,8 @@ public class AdPlanDailyReportService {
     /**
      * 处理单个 AdPlanConf
      */
-    private int processConf(AdPlanConf conf, LocalDateTime startTime, LocalDateTime endTime, String timeLine, boolean isReqTime) throws Exception {
+    private int processConf(AdPlanConf conf, LocalDateTime startTime, LocalDateTime endTime, String timeLine,
+            boolean isReqTime) throws Exception {
         String jobNumber = conf.getJobNumber();
         TencentAds tencentAds = clientFactory.getTencentAds(jobNumber, Constant.MAIN_ORG_ACCOUNT_ID);
 
@@ -196,7 +198,8 @@ public class AdPlanDailyReportService {
                     List<AdPlan> batch = plansOfAccount.subList(fromIdx, toIdx);
 
                     try {
-                        int updated = syncBatch(tencentAds, accountId, batch, dateRange, groupBy, fields, timeLine, isReqTime);
+                        int updated = syncBatch(tencentAds, accountId, batch, dateRange, groupBy, fields, timeLine,
+                                isReqTime);
                         totalUpdated += updated;
                     } catch (Exception e) {
                         log.error("[processConf] 同步失败 accountId={} confId={} batch={}", accountId, conf.getId(), i, e);
@@ -217,8 +220,8 @@ public class AdPlanDailyReportService {
      * 批量同步单批 adPlan
      */
     private int syncBatch(TencentAds tencentAds, Long accountId, List<AdPlan> batch,
-                          ReportDateRange dateRange, List<String> groupBy, List<String> fields,
-                          String timeLine, boolean isReqTime) throws Exception {
+            ReportDateRange dateRange, List<String> groupBy, List<String> fields,
+            String timeLine, boolean isReqTime) throws Exception {
 
         // 构建 filtering: adgroup_id IN [...]
         List<String> adgroupIdValues = batch.stream()
@@ -234,19 +237,41 @@ public class AdPlanDailyReportService {
         filtering.add(filter);
 
         // 调用 API
-        DailyReportsGetResponseData response = tencentAds.dailyReports().dailyReportsGet(
-                "REPORT_LEVEL_ADGROUP",
-                dateRange,
-                groupBy,
-                fields,
-                accountId,
-                filtering,
-                null,  // orderBy
-                timeLine,
-                1L,
-                (long) ADGROUP_BATCH_LIMIT,
-                null   // modularDetailMode
-        );
+        DailyReportsGetResponseData response = null;
+        try {
+            response = tencentAds.dailyReports().dailyReportsGet(
+                    "REPORT_LEVEL_ADGROUP",
+                    dateRange,
+                    groupBy,
+                    fields,
+                    accountId,
+                    filtering,
+                    null, // orderBy
+                    timeLine,
+                    1L,
+                    (long) ADGROUP_BATCH_LIMIT,
+                    null // modularDetailMode
+            );
+        } catch (TencentAdsResponseException e) {
+            // 11016 Application has reached the request limit per second.
+            // 应用程序已达到每秒的请求限制,请稍后再试
+            // 11017 Application has reached the request limit per minute.
+            // 应用请求已达分钟频次上限,请登陆开发者后台查看当前接口对应分配的频次,并在要求范围内请求接口。
+            if (e.getCode() == 11016) {
+                log.warn("请求达到每秒限制,请稍后再试");
+                TimeUnit.SECONDS.sleep(1);
+            } else if (e.getCode() == 11017) {
+                log.warn("请求达到每分钟限制,请稍后再试");
+                // 计算出还剩多少时间
+                LocalDateTime now = LocalDateTime.now();
+                // the second-of-minute, from 0 to 59
+                int second = now.getSecond();
+                int sleepSeconds = (59 - second);
+                log.warn("请求达到每分钟限制,sleep:{} s", sleepSeconds);
+                TimeUnit.SECONDS.sleep(sleepSeconds);
+            }
+            syncBatch(tencentAds, accountId, batch, dateRange, groupBy, fields, timeLine, isReqTime);
+        }
 
         if (response == null) {
             log.warn("[syncBatch] accountId={} 未返回日报数据", accountId);
@@ -264,7 +289,8 @@ public class AdPlanDailyReportService {
                 update.setAdgroupId(rs.getAdgroupId());
 
                 if (isReqTime) {
-                    // REQUEST_TIME 口径:更新 cost, view_count, valid_click_count, ctr, conversions_cost, req_income_val_1, req_income_roi_1
+                    // REQUEST_TIME 口径:更新 cost, view_count, valid_click_count, ctr,
+                    // conversions_cost, req_income_val_1, req_income_roi_1
                     update.setCost(rs.getCost());
                     update.setViewCount(rs.getViewCount());
                     update.setValidClickCount(rs.getValidClickCount());
@@ -280,17 +306,17 @@ public class AdPlanDailyReportService {
                 }
                 update.setUpdateTime(LocalDateTime.now());
                 toUpdate.add(update);
-            }   
+            }
         }
 
-        //无报表数据的,填充0值
+        // 无报表数据的,填充0值
         Map<Long, AdPlan> updateMap = toUpdate.stream().collect(Collectors.toMap(AdPlan::getAdgroupId, p -> p));
         batch.forEach(p -> {
             if (!updateMap.containsKey(p.getAdgroupId())) {
                 AdPlan update = new AdPlan();
                 update.setAdgroupId(p.getAdgroupId());
 
-                if (isReqTime) { 
+                if (isReqTime) {
                     update.setCost(0L);
                     update.setViewCount(0L);
                     update.setValidClickCount(0L);

+ 26 - 3
src/main/java/com/moka/gdtauto/service/DailyAdgroupReportService.java

@@ -2,6 +2,7 @@ package com.moka.gdtauto.service;
 
 import java.math.BigDecimal;
 import java.time.LocalDate;
+import java.time.LocalDateTime;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -27,6 +28,7 @@ import com.moka.gdtauto.entity.RepDailyAdgroupReport;
 import com.moka.gdtauto.entity.ReqDailyAdgroupReport;
 import com.moka.gdtauto.mapper.RepDailyAdgroupReportMapper;
 import com.moka.gdtauto.mapper.ReqDailyAdgroupReportMapper;
+import com.tencent.ads.exception.TencentAdsResponseException;
 import com.tencent.ads.model.v3.DailyReportsFilteringStruct;
 import com.tencent.ads.model.v3.DailyReportsGetResponseData;
 import com.tencent.ads.model.v3.FilterOperator;
@@ -160,9 +162,10 @@ public class DailyAdgroupReportService {
         List<AdPlanConf> confList = adPlanConfService.list(
                 new LambdaQueryWrapper<AdPlanConf>()
                         .eq(confId != null, AdPlanConf::getId, confId)
-                        .eq(AdPlanConf::getAdType, 2)
+                        .ge(confId == null, AdPlanConf::getId, 57L)
                         .in(AdPlanConf::getStatus, List.of(4, 5))
-                        .eq(AdPlanConf::getDeleted, 0));
+                        .eq(AdPlanConf::getDeleted, 0)
+                        .orderByDesc(AdPlanConf::getId));
 
         log.info("查询到 {} 个广告计划配置", confList.size());
         if (confList.isEmpty()) {
@@ -227,7 +230,9 @@ public class DailyAdgroupReportService {
         int totalSaved = 0;
 
         while (true) {
-            DailyReportsGetResponseData response = tencentAds.dailyReports().dailyReportsGet(
+            DailyReportsGetResponseData response = null;
+            try {
+                response = tencentAds.dailyReports().dailyReportsGet(
                     "REPORT_LEVEL_ADGROUP",
                     dateRange,
                     groupBy,
@@ -239,6 +244,24 @@ public class DailyAdgroupReportService {
                     page,
                     pageSize,
                     null);
+            } catch (TencentAdsResponseException e) {
+                //11016	Application has reached the request limit per second.	应用程序已达到每秒的请求限制,请稍后再试
+                // 11017	Application has reached the request limit per minute.	应用请求已达分钟频次上限,请登陆开发者后台查看当前接口对应分配的频次,并在要求范围内请求接口。
+                if (e.getCode() == 11016) {
+                    log.warn("请求达到每秒限制,请稍后再试");
+                    TimeUnit.SECONDS.sleep(1);
+                } else if (e.getCode() == 11017) {
+                    log.warn("请求达到每分钟限制,请稍后再试");
+                    // 计算出还剩多少时间
+                    LocalDateTime now = LocalDateTime.now();
+                    //the second-of-minute, from 0 to 59
+                    int second = now.getSecond();
+                    int sleepSeconds = (59 - second);
+                    log.warn("请求达到每分钟限制,sleep:{} s", sleepSeconds);
+                    TimeUnit.SECONDS.sleep(sleepSeconds);
+                }
+                continue;
+            }
 
             if (response == null || response.getList() == null || response.getList().isEmpty()) {
                 break;

+ 26 - 4
src/main/java/com/moka/gdtauto/service/DailyCreativeReportService.java

@@ -2,6 +2,7 @@ package com.moka.gdtauto.service;
 
 import java.math.BigDecimal;
 import java.time.LocalDate;
+import java.time.LocalDateTime;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -27,6 +28,7 @@ import com.moka.gdtauto.mapper.AccountGroupAccountRelationshipMapper;
 import com.moka.gdtauto.mapper.AccountGroupMapper;
 import com.moka.gdtauto.mapper.RepDailyCreativeReportMapper;
 import com.moka.gdtauto.mapper.ReqDailyCreativeReportMapper;
+import com.tencent.ads.exception.TencentAdsResponseException;
 import com.tencent.ads.model.v3.DailyReportsFilteringStruct;
 import com.tencent.ads.model.v3.DailyReportsGetResponseData;
 import com.tencent.ads.model.v3.FilterOperator;
@@ -160,7 +162,9 @@ public class DailyCreativeReportService {
             struct.setValues(creativeIds.stream().map(String::valueOf).toList());
             filtering.add(struct);
 
-            DailyReportsGetResponseData response = tencentAds.dailyReports().dailyReportsGet(
+            DailyReportsGetResponseData response = null;
+            try {
+                response = tencentAds.dailyReports().dailyReportsGet(
                     "REPORT_LEVEL_DYNAMIC_CREATIVE",
                     dateRange,
                     groupBy,
@@ -172,7 +176,24 @@ public class DailyCreativeReportService {
                     1L,
                     100L,
                     null
-            );
+                );
+            } catch (TencentAdsResponseException e) {
+                //11016	Application has reached the request limit per second.	应用程序已达到每秒的请求限制,请稍后再试
+                // 11017	Application has reached the request limit per minute.	应用请求已达分钟频次上限,请登陆开发者后台查看当前接口对应分配的频次,并在要求范围内请求接口。
+                if (e.getCode() == 11016) {
+                    log.warn("请求达到每秒限制,sleep:1 s");
+                    TimeUnit.SECONDS.sleep(1);
+                } else if (e.getCode() == 11017) {
+                    // 计算出还剩多少时间
+                    LocalDateTime now = LocalDateTime.now();
+                    //the second-of-minute, from 0 to 59
+                    int second = now.getSecond();
+                    int sleepSeconds = (59 - second);
+                    log.warn("请求达到每分钟限制,sleep:{} s", sleepSeconds);
+                    TimeUnit.SECONDS.sleep(sleepSeconds);
+                }
+                continue;
+            }
 
             if (response == null || response.getList() == null || response.getList().isEmpty()) {
                 pageNum++;
@@ -228,9 +249,10 @@ public class DailyCreativeReportService {
         List<AdPlanConf> confList = adPlanConfService.list(
             new LambdaQueryWrapper<AdPlanConf>()
                 .eq(confId != null, AdPlanConf::getId, confId)
-                .eq(AdPlanConf::getAdType, 2)
+                .ge(confId == null, AdPlanConf::getId, 57L)
                 .in(AdPlanConf::getStatus, List.of(4,5))
-                .eq(AdPlanConf::getDeleted, 0));
+                .eq(AdPlanConf::getDeleted, 0)
+                .orderByDesc(AdPlanConf::getId));
         log.info("查询到 {} 个广告计划配置", confList.size());
 
         if (confList.isEmpty()) {

+ 23 - 2
src/main/java/com/moka/gdtauto/service/TencentAssetDramaService.java

@@ -17,8 +17,10 @@ import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.util.CollectionUtils;
 
+import java.time.LocalDateTime;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.TimeUnit;
 
 /**
  * 腾讯广告短剧资产服务
@@ -99,8 +101,27 @@ public class TencentAssetDramaService extends ServiceImpl<TencentAssetDramaMappe
         Long pageSize = 100L;
         
         while (totalCount < maxRecords) {
-            MarketingTargetAssetsGetResponseData response = getMarketingAssets(
-                Constant.MAIN_ORG_ACCOUNT_ID, jobNumber, accountId, null, page, pageSize);
+            MarketingTargetAssetsGetResponseData response = null;
+            try {
+                response = getMarketingAssets(orgAccountId, jobNumber, accountId, null, page, pageSize);
+            }  catch (TencentAdsResponseException e) {
+                //11016	Application has reached the request limit per second.	应用程序已达到每秒的请求限制,请稍后再试
+                // 11017	Application has reached the request limit per minute.	应用请求已达分钟频次上限,请登陆开发者后台查看当前接口对应分配的频次,并在要求范围内请求接口。
+                if (e.getCode() == 11016) {
+                    log.warn("请求达到每秒限制,请稍后再试");
+                    TimeUnit.SECONDS.sleep(1);
+                } else if (e.getCode() == 11017) {
+                    log.warn("请求达到每分钟限制,请稍后再试");
+                    // 计算出还剩多少时间
+                    LocalDateTime now = LocalDateTime.now();
+                    //the second-of-minute, from 0 to 59
+                    int second = now.getSecond();
+                    int sleepSeconds = (59 - second);
+                    log.warn("请求达到每分钟限制,sleep:{} s", sleepSeconds);
+                    TimeUnit.SECONDS.sleep(sleepSeconds);
+                }
+                continue;
+            }
             
             if (response == null || CollectionUtils.isEmpty(response.getList())) {
                 log.info("没有更多数据,结束同步");

+ 24 - 2
src/main/java/com/moka/gdtauto/service/TencentAssetFictionService.java

@@ -17,8 +17,10 @@ import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.util.CollectionUtils;
 
+import java.time.LocalDateTime;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.TimeUnit;
 
 /**
  * 腾讯广告小说资产服务
@@ -98,8 +100,28 @@ public class TencentAssetFictionService extends ServiceImpl<TencentAssetFictionM
         Long pageSize = 100L;
 
         while (totalCount < maxRecords) {
-            MarketingTargetAssetsGetResponseData response = getMarketingAssets(
-                Constant.MAIN_ORG_ACCOUNT_ID, jobNumber, accountId, null, page, pageSize);
+            MarketingTargetAssetsGetResponseData response = null;
+            try {
+                response = getMarketingAssets(
+                    Constant.MAIN_ORG_ACCOUNT_ID, jobNumber, accountId, null, page, pageSize);
+            }   catch (TencentAdsResponseException e) {
+                //11016	Application has reached the request limit per second.	应用程序已达到每秒的请求限制,请稍后再试
+                // 11017	Application has reached the request limit per minute.	应用请求已达分钟频次上限,请登陆开发者后台查看当前接口对应分配的频次,并在要求范围内请求接口。
+                if (e.getCode() == 11016) {
+                    log.warn("请求达到每秒限制,请稍后再试");
+                    TimeUnit.SECONDS.sleep(1);
+                } else if (e.getCode() == 11017) {
+                    log.warn("请求达到每分钟限制,请稍后再试");
+                    // 计算出还剩多少时间
+                    LocalDateTime now = LocalDateTime.now();
+                    //the second-of-minute, from 0 to 59
+                    int second = now.getSecond();
+                    int sleepSeconds = (59 - second);
+                    log.warn("请求达到每分钟限制,sleep:{} s", sleepSeconds);
+                    TimeUnit.SECONDS.sleep(sleepSeconds);
+                }
+                continue;
+            }
 
             if (response == null || CollectionUtils.isEmpty(response.getList())) {
                 log.info("没有更多数据,结束同步");

+ 3 - 3
src/test/java/com/moka/gdtauto/service/AdPlanDailyReportSyncTest.java

@@ -55,7 +55,7 @@ public class AdPlanDailyReportSyncTest {
     @Test
     public void testSyncTenDaysDailyCreativeReport() {
         DateTimeFormatter FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
-        for (int i = 0; i < 10; i++) {
+        for (int i = 0; i < 3; i++) {
             String day = LocalDate.now().plusDays(-i).format(FMT);
             dailyCreativeReportService.syncAllAccounts(null, day, day, TimeLine.REQUEST_TIME.getValue());
             dailyCreativeReportService.syncAllAccounts(null, day, day, TimeLine.REPORTING_TIME.getValue());
@@ -75,8 +75,8 @@ public class AdPlanDailyReportSyncTest {
         DateTimeFormatter FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
         for (int i = 0; i < 10; i++) {
             String day = LocalDate.now().plusDays(-i).format(FMT);
-            dailyAdgroupReportService.syncAllAccounts(62L, day, day, TimeLine.REQUEST_TIME.getValue());
-            dailyAdgroupReportService.syncAllAccounts(62L, day, day, TimeLine.REPORTING_TIME.getValue());
+            dailyAdgroupReportService.syncAllAccounts(null, day, day, TimeLine.REQUEST_TIME.getValue());
+            dailyAdgroupReportService.syncAllAccounts(null, day, day, TimeLine.REPORTING_TIME.getValue());
         }
     }
 }

+ 7 - 0
src/test/java/com/moka/gdtauto/util/SampleTest.java

@@ -87,4 +87,11 @@ public class SampleTest {
         Long dailyBudgetLimit = dailyBudgetRateTop.multiply(BigDecimal.valueOf(dailyBudget)).longValue();
         System.out.println(dailyBudgetLimit);
     }
+
+    @Test
+    public void getSecondOfMinute() {
+        LocalDateTime now = LocalDateTime.now();
+        int second = now.getSecond();
+        System.out.println(second);
+    }
 }