|
|
@@ -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);
|