|
|
@@ -3,15 +3,32 @@ package com.moka.gdtauto.service.impl;
|
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
|
|
+import com.moka.gdtauto.client.TencentAdsApiClientFactory;
|
|
|
+import com.moka.gdtauto.common.Constant;
|
|
|
+import com.moka.gdtauto.entity.AdPlan;
|
|
|
import com.moka.gdtauto.entity.AdPlanCreative;
|
|
|
import com.moka.gdtauto.mapper.AdPlanCreativeMapper;
|
|
|
import com.moka.gdtauto.service.AdPlanCreativeService;
|
|
|
+import com.moka.gdtauto.service.AdPlanService;
|
|
|
+import com.tencent.ads.model.v3.AdgroupsGetResponseData;
|
|
|
+import com.tencent.ads.model.v3.DynamicCreativesGetResponseData;
|
|
|
+import com.tencent.ads.model.v3.FilterOperator;
|
|
|
+import com.tencent.ads.model.v3.FilteringStruct;
|
|
|
+import com.tencent.ads.v3.TencentAds;
|
|
|
+
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
+
|
|
|
+import org.springframework.scheduling.annotation.Async;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
+import lombok.AllArgsConstructor;
|
|
|
+
|
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
|
|
|
import java.time.LocalDateTime;
|
|
|
+import java.util.ArrayList;
|
|
|
import java.util.List;
|
|
|
+import java.util.Map;
|
|
|
+import java.util.stream.Collectors;
|
|
|
|
|
|
/**
|
|
|
* 广告计划创意服务实现类
|
|
|
@@ -21,8 +38,13 @@ import java.util.List;
|
|
|
*/
|
|
|
@Slf4j
|
|
|
@Service
|
|
|
+@AllArgsConstructor
|
|
|
public class AdPlanCreativeServiceImpl extends ServiceImpl<AdPlanCreativeMapper, AdPlanCreative> implements AdPlanCreativeService {
|
|
|
|
|
|
+ private final AdPlanCreativeMapper adPlanCreativeMapper;
|
|
|
+ private final TencentAdsApiClientFactory clientFactory;
|
|
|
+ private final AdPlanService adPlanService;
|
|
|
+
|
|
|
@Override
|
|
|
@Transactional(rollbackFor = Exception.class)
|
|
|
public Long createAdPlanCreative(AdPlanCreative adPlanCreative) {
|
|
|
@@ -139,4 +161,124 @@ public class AdPlanCreativeServiceImpl extends ServiceImpl<AdPlanCreativeMapper,
|
|
|
.eq(AdPlanCreative::getDeleted, 0)
|
|
|
.orderByAsc(AdPlanCreative::getId));
|
|
|
}
|
|
|
+
|
|
|
+ @Async
|
|
|
+ @Override
|
|
|
+ public void syncAdPlanCreativeStatus(Long confId) {
|
|
|
+ final int PAGE_SIZE = 500;
|
|
|
+ final int BATCH_LIMIT = 100; // 腾讯广告 adgroup_id IN 最大 100
|
|
|
+
|
|
|
+ log.info("[定时任务] 开始同步广告创意 system_status");
|
|
|
+ int totalUpdated = 0;
|
|
|
+ try {
|
|
|
+ int pageNum = 1;
|
|
|
+ while (true) {
|
|
|
+ // 1. 分页查询 status=4 的广告创意,ID 降序
|
|
|
+ LambdaQueryWrapper<AdPlanCreative> queryWrapper = new LambdaQueryWrapper<>();
|
|
|
+ queryWrapper.eq(confId != null, AdPlanCreative::getAdPlanConfId, confId);
|
|
|
+ queryWrapper.eq(AdPlanCreative::getDeleted, 0);
|
|
|
+ queryWrapper.eq(AdPlanCreative::getStatus, 4);
|
|
|
+ queryWrapper.orderByDesc(AdPlanCreative::getId);
|
|
|
+ Page<AdPlanCreative> page = this.page(new Page<>(pageNum, PAGE_SIZE), queryWrapper);
|
|
|
+ List<AdPlanCreative> records = page.getRecords();
|
|
|
+ if (records == null || records.isEmpty()) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 按 accountId 分组
|
|
|
+ Map<Long, List<AdPlanCreative>> groupByAccount = records.stream()
|
|
|
+ .filter(p -> p.getAdgroupId() != null && p.getAccountId() != null)
|
|
|
+ .collect(Collectors.groupingBy(AdPlanCreative::getAccountId));
|
|
|
+
|
|
|
+ for (Map.Entry<Long, List<AdPlanCreative>> entry : groupByAccount.entrySet()) {
|
|
|
+ Long accountId = entry.getKey();
|
|
|
+ List<AdPlanCreative> plansOfAccount = entry.getValue();
|
|
|
+
|
|
|
+ // 3. 每组按 100 条分批调用 API
|
|
|
+ int batchCount = (int) Math.ceil((double) plansOfAccount.size() / BATCH_LIMIT);
|
|
|
+ for (int i = 0; i < batchCount; i++) {
|
|
|
+ int fromIdx = i * BATCH_LIMIT;
|
|
|
+ int toIdx = Math.min(fromIdx + BATCH_LIMIT, plansOfAccount.size());
|
|
|
+ List<AdPlanCreative> batch = plansOfAccount.subList(fromIdx, toIdx);
|
|
|
+
|
|
|
+ try {
|
|
|
+ int updated = syncBatch(accountId, batch);
|
|
|
+ totalUpdated += updated;
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("[定时任务] 同步 accountId={} 的广告状态失败,batch index={}", accountId, i, e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (pageNum >= page.getPages()) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ pageNum++;
|
|
|
+ }
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("[定时任务] 同步广告创意 system_status 发生异常", e);
|
|
|
+ }
|
|
|
+
|
|
|
+ log.info("[定时任务] 同步广告创意 system_status 完成,共更新 {} 条", totalUpdated);
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ private int syncBatch(Long accountId, List<AdPlanCreative> batch) throws Exception {
|
|
|
+ final int BATCH_LIMIT = 100; // 腾讯广告 adgroup_id IN 最大 100
|
|
|
+ final List<String> FIELDS = List.of("adgroup_id", "dynamic_creative_id", "creative_set_approval_status", "is_deleted");
|
|
|
+
|
|
|
+ // 构建 adgroup_id IN [...] 过滤条件
|
|
|
+ List<String> creativeIdValues = batch.stream()
|
|
|
+ .map(p -> String.valueOf(p.getCreativeId()))
|
|
|
+ .collect(Collectors.toList());
|
|
|
+
|
|
|
+ FilteringStruct filter = new FilteringStruct();
|
|
|
+ filter.setField("dynamic_creative_id");
|
|
|
+ filter.setOperator(FilterOperator.IN);
|
|
|
+ filter.setValues(creativeIdValues);
|
|
|
+
|
|
|
+ List<FilteringStruct> filtering = new ArrayList<>();
|
|
|
+ filtering.add(filter);
|
|
|
+
|
|
|
+ // 获取 TencentAds 实例(使用主 orgAccountId)
|
|
|
+ TencentAds tencentAds = clientFactory.getTencentAds(null, Constant.MAIN_ORG_ACCOUNT_ID);
|
|
|
+
|
|
|
+ DynamicCreativesGetResponseData response = tencentAds.dynamicCreatives().dynamicCreativesGet(
|
|
|
+ accountId,
|
|
|
+ filtering,
|
|
|
+ 1L,
|
|
|
+ (long) BATCH_LIMIT,
|
|
|
+ FIELDS,
|
|
|
+ null,
|
|
|
+ "PAGINATION_MODE_NORMAL",
|
|
|
+ null ,
|
|
|
+ null
|
|
|
+ );
|
|
|
+
|
|
|
+ if (response == null || response.getList() == null || response.getList().isEmpty()) {
|
|
|
+ log.warn("[定时任务] accountId={} 未返回广告状态数据", accountId);
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 构建待更新列表
|
|
|
+ List<AdPlanCreative> toUpdate = new ArrayList<>();
|
|
|
+ response.getList().stream().forEach(item -> {
|
|
|
+ AdPlanCreative update = new AdPlanCreative();
|
|
|
+ update.setCreativeId(item.getDynamicCreativeId());
|
|
|
+ update.setApprovalStatus(item.getCreativeSetApprovalStatus().getValue());
|
|
|
+ update.setSystemDelete(item.isIsDeleted() != null ? String.valueOf(item.isIsDeleted()) : null);
|
|
|
+ toUpdate.add(update);
|
|
|
+ });
|
|
|
+
|
|
|
+ if (toUpdate.isEmpty()) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ adPlanCreativeMapper.batchUpdateApprovalStatus(toUpdate);
|
|
|
+ log.info("[定时任务] accountId={} 批量更新 {} 条 approval_status", accountId, toUpdate.size());
|
|
|
+ return toUpdate.size();
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
}
|