|
@@ -25,8 +25,16 @@ import java.util.Map;
|
|
|
import java.util.concurrent.*;
|
|
import java.util.concurrent.*;
|
|
|
import java.util.stream.Collectors;
|
|
import java.util.stream.Collectors;
|
|
|
import java.util.ArrayList;
|
|
import java.util.ArrayList;
|
|
|
|
|
+import java.util.Collections;
|
|
|
|
|
+import java.util.HashMap;
|
|
|
|
|
+import java.util.Objects;
|
|
|
|
|
+import java.util.Set;
|
|
|
import com.moka.gdtauto.entity.AdPlan;
|
|
import com.moka.gdtauto.entity.AdPlan;
|
|
|
import com.moka.gdtauto.entity.AdPlanCreative;
|
|
import com.moka.gdtauto.entity.AdPlanCreative;
|
|
|
|
|
+import com.moka.gdtauto.entity.OssFile;
|
|
|
|
|
+import com.moka.gdtauto.entity.TencentAccountIdImage;
|
|
|
|
|
+import com.moka.gdtauto.entity.TencentAccountIdBrandImage;
|
|
|
|
|
+import com.moka.gdtauto.entity.TencentAccountIdVideoId;
|
|
|
import com.moka.gdtauto.service.AdPlanService;
|
|
import com.moka.gdtauto.service.AdPlanService;
|
|
|
import com.moka.gdtauto.service.TencentAccountIdBrandImageService;
|
|
import com.moka.gdtauto.service.TencentAccountIdBrandImageService;
|
|
|
import com.moka.gdtauto.service.TencentAccountIdImageService;
|
|
import com.moka.gdtauto.service.TencentAccountIdImageService;
|
|
@@ -511,7 +519,7 @@ public class AdPlanConfServiceImpl extends ServiceImpl<AdPlanConfMapper, AdPlanC
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
int planCount = plans.size();
|
|
int planCount = plans.size();
|
|
|
conf.setPlanCount(planCount);
|
|
conf.setPlanCount(planCount);
|
|
|
int creativeCount = plans.stream().mapToInt(p -> p.getAdCreatives() == null ? 0 : p.getAdCreatives().size()).sum();
|
|
int creativeCount = plans.stream().mapToInt(p -> p.getAdCreatives() == null ? 0 : p.getAdCreatives().size()).sum();
|
|
@@ -536,7 +544,7 @@ public class AdPlanConfServiceImpl extends ServiceImpl<AdPlanConfMapper, AdPlanC
|
|
|
|
|
|
|
|
// 第一步:准备阶段 - 填充创意的 imageId/videoId
|
|
// 第一步:准备阶段 - 填充创意的 imageId/videoId
|
|
|
try {
|
|
try {
|
|
|
- prepareCreativeAssets(tencentAds, plans);
|
|
|
|
|
|
|
+ prepareCreativeAssets2(conf, tencentAds, plans);
|
|
|
} catch (Exception e) {
|
|
} catch (Exception e) {
|
|
|
log.error("准备创意资产失败,confId={}", confId, e);
|
|
log.error("准备创意资产失败,confId={}", confId, e);
|
|
|
// 更新 AdPlanConf 状态为失败
|
|
// 更新 AdPlanConf 状态为失败
|
|
@@ -614,187 +622,258 @@ public class AdPlanConfServiceImpl extends ServiceImpl<AdPlanConfMapper, AdPlanC
|
|
|
log.info("异步对接腾讯接口完成,confId={}", confId);
|
|
log.info("异步对接腾讯接口完成,confId={}", confId);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- /**
|
|
|
|
|
- * 准备创意资产 - 填充创意的 imageId/videoId
|
|
|
|
|
- * 按 accountId 分组,先查询缓存/数据库,不存在则上传并保存
|
|
|
|
|
- */
|
|
|
|
|
- private void prepareCreativeAssets(TencentAds tencentAds, List<AdPlan> plans) throws Exception {
|
|
|
|
|
|
|
+ private void prepareCreativeAssets2(AdPlanConf conf, TencentAds tencentAds, List<AdPlan> plans) throws Exception {
|
|
|
log.info("准备创意资产开始,计划数量: {}", plans.size());
|
|
log.info("准备创意资产开始,计划数量: {}", plans.size());
|
|
|
|
|
+ String creativeFiles = conf.getCreativeFiles();
|
|
|
|
|
+ String brandImageFile = conf.getBrandImageFile();
|
|
|
|
|
+ List<OssFile> creativeOssFile = JSON.parseArray(creativeFiles, OssFile.class);
|
|
|
|
|
+ OssFile brandFile = JSON.parseObject(brandImageFile, OssFile.class);
|
|
|
|
|
+
|
|
|
|
|
+ // 第一步:收集所有 accountId
|
|
|
|
|
+ Set<Long> accountIds = plans.stream()
|
|
|
|
|
+ .map(AdPlan::getAccountId)
|
|
|
|
|
+ .collect(Collectors.toSet());
|
|
|
|
|
+ log.info("收集到 {} 个账户ID", accountIds.size());
|
|
|
|
|
+
|
|
|
|
|
+ // 收集创意文件的 fileMd5(区分图片和视频)
|
|
|
|
|
+ List<String> imageFileMd5List = creativeOssFile.stream()
|
|
|
|
|
+ .filter(f -> "image".equalsIgnoreCase(f.getFileType()))
|
|
|
|
|
+ .map(OssFile::getFileMd5)
|
|
|
|
|
+ .filter(Objects::nonNull)
|
|
|
|
|
+ .distinct()
|
|
|
|
|
+ .collect(Collectors.toList());
|
|
|
|
|
+
|
|
|
|
|
+ List<String> videoFileMd5List = creativeOssFile.stream()
|
|
|
|
|
+ .filter(f -> "video".equalsIgnoreCase(f.getFileType()))
|
|
|
|
|
+ .map(OssFile::getFileMd5)
|
|
|
|
|
+ .filter(Objects::nonNull)
|
|
|
|
|
+ .distinct()
|
|
|
|
|
+ .collect(Collectors.toList());
|
|
|
|
|
+
|
|
|
|
|
+ // 批量查询已上传的图片记录,构建双 key map: accountId -> fileMd5 -> imageId
|
|
|
|
|
+ Map<Long, Map<String, Long>> imageIdMap = new HashMap<>();
|
|
|
|
|
+ if (!imageFileMd5List.isEmpty()) {
|
|
|
|
|
+ List<TencentAccountIdImage> existingImages = tencentAccountIdImageService
|
|
|
|
|
+ .batchGetByAccountIdsAndMd5s(new ArrayList<>(accountIds), imageFileMd5List);
|
|
|
|
|
+ for (TencentAccountIdImage image : existingImages) {
|
|
|
|
|
+ imageIdMap.computeIfAbsent(image.getAccountId(), k -> new HashMap<>())
|
|
|
|
|
+ .put(image.getFileMd5(), image.getImageId());
|
|
|
|
|
+ }
|
|
|
|
|
+ log.info("从数据库查询到 {} 条已上传图片记录", existingImages.size());
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- // 按 accountId 分组
|
|
|
|
|
- Map<Long, List<AdPlan>> plansByAccountId = plans.stream()
|
|
|
|
|
- .collect(Collectors.groupingBy(AdPlan::getAccountId));
|
|
|
|
|
|
|
+ // 批量查询已上传的视频记录
|
|
|
|
|
+ Map<Long, Map<String, Long>> videoIdMap = new HashMap<>();
|
|
|
|
|
+ if (!videoFileMd5List.isEmpty()) {
|
|
|
|
|
+ List<TencentAccountIdVideoId> existingVideos = tencentAccountIdVideoIdService
|
|
|
|
|
+ .batchGetByAccountIdsAndMd5s(new ArrayList<>(accountIds), videoFileMd5List);
|
|
|
|
|
+ for (TencentAccountIdVideoId video : existingVideos) {
|
|
|
|
|
+ videoIdMap.computeIfAbsent(video.getAccountId(), k -> new HashMap<>())
|
|
|
|
|
+ .put(video.getFileMd5(), video.getVideoId());
|
|
|
|
|
+ }
|
|
|
|
|
+ log.info("从数据库查询到 {} 条已上传视频记录", existingVideos.size());
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- for (Map.Entry<Long, List<AdPlan>> entry : plansByAccountId.entrySet()) {
|
|
|
|
|
- Long accountId = entry.getKey();
|
|
|
|
|
- List<AdPlan> accountPlans = entry.getValue();
|
|
|
|
|
|
|
+ // 第二步:处理未上传的创意文件
|
|
|
|
|
+ for (OssFile ossFile : creativeOssFile) {
|
|
|
|
|
+ String fileMd5 = ossFile.getFileMd5();
|
|
|
|
|
+ String fileUrl = ossFile.getFileUrl();
|
|
|
|
|
+ String fileType = ossFile.getFileType();
|
|
|
|
|
+
|
|
|
|
|
+ if (fileMd5 == null || fileUrl == null || fileType == null) {
|
|
|
|
|
+ log.warn("跳过无效的创意文件: {}", ossFile.getFileName());
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- log.info("处理账户 {} 的创意资产,计划数量: {}", accountId, accountPlans.size());
|
|
|
|
|
|
|
+ if ("image".equalsIgnoreCase(fileType)) {
|
|
|
|
|
+ // 处理图片类型
|
|
|
|
|
+ List<Long> needUploadAccountIds = accountIds.stream()
|
|
|
|
|
+ .filter(accId -> !imageIdMap.getOrDefault(accId, Collections.emptyMap()).containsKey(fileMd5))
|
|
|
|
|
+ .toList();
|
|
|
|
|
+
|
|
|
|
|
+ if (!needUploadAccountIds.isEmpty()) {
|
|
|
|
|
+ log.info("图片 {} 需要上传到 {} 个账户", ossFile.getFileName(), needUploadAccountIds.size());
|
|
|
|
|
+ // 下载图片一次
|
|
|
|
|
+ byte[] fileBytes = downloadFileToBytes(fileUrl);
|
|
|
|
|
+ for (Long accountId : needUploadAccountIds) {
|
|
|
|
|
+ Long imageId = uploadImageFromBytes(tencentAds, accountId, fileBytes, fileMd5, "创意图片-" + ossFile.getFileName());
|
|
|
|
|
+ tencentAccountIdImageService.saveImageId(accountId, fileMd5, imageId, fileUrl);
|
|
|
|
|
+ imageIdMap.computeIfAbsent(accountId, k -> new HashMap<>()).put(fileMd5, imageId);
|
|
|
|
|
+ log.info("图片上传成功,accountId={}, fileMd5={}, imageId={}", accountId, fileMd5, imageId);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } else if ("video".equalsIgnoreCase(fileType)) {
|
|
|
|
|
+ // 处理视频类型
|
|
|
|
|
+ List<Long> needUploadAccountIds = accountIds.stream()
|
|
|
|
|
+ .filter(accId -> !videoIdMap.getOrDefault(accId, Collections.emptyMap()).containsKey(fileMd5))
|
|
|
|
|
+ .toList();
|
|
|
|
|
+
|
|
|
|
|
+ if (!needUploadAccountIds.isEmpty()) {
|
|
|
|
|
+ log.info("视频 {} 需要上传到 {} 个账户", ossFile.getFileName(), needUploadAccountIds.size());
|
|
|
|
|
+ for (Long accountId : needUploadAccountIds) {
|
|
|
|
|
+ VideoUploadResult videoResult = uploadVideoFromUrl(tencentAds, accountId, fileUrl, fileMd5, "创意视频-" + ossFile.getFileName());
|
|
|
|
|
+ tencentAccountIdVideoIdService.saveVideoId(accountId, fileMd5, videoResult.videoId(), fileUrl, videoResult.coverImageId());
|
|
|
|
|
+ videoIdMap.computeIfAbsent(accountId, k -> new HashMap<>()).put(fileMd5, videoResult.videoId());
|
|
|
|
|
+ log.info("视频上传成功,accountId={}, fileMd5={}, videoId={}", accountId, fileMd5, videoResult.videoId());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- // 收集该账户下所有创意
|
|
|
|
|
- List<AdPlanCreative> allCreatives = accountPlans.stream()
|
|
|
|
|
- .filter (plan -> plan.getAdCreatives() != null)
|
|
|
|
|
- .flatMap(plan -> plan.getAdCreatives().stream())
|
|
|
|
|
- .collect(Collectors.toList());
|
|
|
|
|
|
|
+ // 第三步:处理 brandFile(品牌形象图片)
|
|
|
|
|
+ Map<Long, Map<String, Long>> brandImageIdMap = new HashMap<>();
|
|
|
|
|
+ if (brandFile != null && brandFile.getFileMd5() != null && brandFile.getFileUrl() != null) {
|
|
|
|
|
+ String brandFileMd5 = brandFile.getFileMd5();
|
|
|
|
|
+ String brandFileUrl = brandFile.getFileUrl();
|
|
|
|
|
+
|
|
|
|
|
+ // 批量查询已上传的品牌形象记录
|
|
|
|
|
+ List<TencentAccountIdBrandImage> existingBrandImages = tencentAccountIdBrandImageService
|
|
|
|
|
+ .batchGetByAccountIdsAndMd5s(new ArrayList<>(accountIds), List.of(brandFileMd5));
|
|
|
|
|
+ for (TencentAccountIdBrandImage image : existingBrandImages) {
|
|
|
|
|
+ brandImageIdMap.computeIfAbsent(image.getAccountId(), k -> new HashMap<>())
|
|
|
|
|
+ .put(image.getFileMd5(), image.getImageId());
|
|
|
|
|
+ }
|
|
|
|
|
+ log.info("从数据库查询到 {} 条已上传品牌形象记录", existingBrandImages.size());
|
|
|
|
|
|
|
|
- // 使用虚拟线程并发处理创意资产,Semaphore限制最大并发数为100
|
|
|
|
|
- // TODO tencentAds不支持跨进程
|
|
|
|
|
- //Semaphore semaphore = new Semaphore(20);
|
|
|
|
|
- //CountDownLatch latch = new CountDownLatch(allCreatives.size());
|
|
|
|
|
|
|
+ // 处理未上传的品牌形象
|
|
|
|
|
+ List<Long> needUploadAccountIds = accountIds.stream()
|
|
|
|
|
+ .filter(accId -> !brandImageIdMap.getOrDefault(accId, Collections.emptyMap()).containsKey(brandFileMd5))
|
|
|
|
|
+ .toList();
|
|
|
|
|
|
|
|
- // try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
|
|
|
|
|
- // for (AdPlanCreative creative : allCreatives) {
|
|
|
|
|
- // executor.submit(() -> {
|
|
|
|
|
- // try {
|
|
|
|
|
- // semaphore.acquire();
|
|
|
|
|
- // prepareSingleCreativeAsset(tencentAds, accountId, creative);
|
|
|
|
|
- // } catch (Exception e) {
|
|
|
|
|
- // log.error("准备创意资产失败, accountId={}, creativeId={}, creativeName={}",
|
|
|
|
|
- // accountId, creative.getId(), creative.getCreativeName(), e);
|
|
|
|
|
- // adPlanCreativeService.lambdaUpdate()
|
|
|
|
|
- // .eq(AdPlanCreative::getId, creative.getId())
|
|
|
|
|
- // .set(AdPlanCreative::getFailReason, truncateError(e.getMessage()))
|
|
|
|
|
- // .set(AdPlanCreative::getStatus, AdPlanStatus.FAILED)
|
|
|
|
|
- // .update();
|
|
|
|
|
- // } finally {
|
|
|
|
|
- // semaphore.release();
|
|
|
|
|
- // latch.countDown();
|
|
|
|
|
- // }
|
|
|
|
|
- // });
|
|
|
|
|
- // }
|
|
|
|
|
|
|
+ if (!needUploadAccountIds.isEmpty()) {
|
|
|
|
|
+ log.info("品牌形象需要上传到 {} 个账户", needUploadAccountIds.size());
|
|
|
|
|
+ // 获取品牌名称(从 conf 或创意中获取)
|
|
|
|
|
+ String brandName = getBrandNameFromPlans(plans);
|
|
|
|
|
+ for (Long accountId : needUploadAccountIds) {
|
|
|
|
|
+ BrandUploadResult brandResult = uploadBrandImageFromUrl(tencentAds, accountId, brandFileUrl, brandFileMd5, brandName);
|
|
|
|
|
+ tencentAccountIdBrandImageService.saveBrandImageId(accountId, brandFileMd5, brandResult.imageId(), brandFileUrl, brandResult.width(), brandResult.height());
|
|
|
|
|
+ brandImageIdMap.computeIfAbsent(accountId, k -> new HashMap<>()).put(brandFileMd5, brandResult.imageId());
|
|
|
|
|
+ log.info("品牌形象上传成功,accountId={}, fileMd5={}, brandImageId={}", accountId, brandFileMd5, brandResult.imageId());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 第四步:给 plan.getAdCreatives() 下的创意填充 imageId/videoId/brandImageId
|
|
|
|
|
+ for (AdPlan plan : plans) {
|
|
|
|
|
+ Long accountId = plan.getAccountId();
|
|
|
|
|
+ List<AdPlanCreative> creatives = plan.getAdCreatives();
|
|
|
|
|
+ if (creatives == null) continue;
|
|
|
|
|
+
|
|
|
|
|
+ for (AdPlanCreative creative : creatives) {
|
|
|
|
|
+ String creativeFileMd5 = creative.getCreativeFileMd5();
|
|
|
|
|
+ String creativeFileType = creative.getCreativeFileType();
|
|
|
|
|
|
|
|
- // // 等待所有任务完成
|
|
|
|
|
- // latch.await();
|
|
|
|
|
- // } catch (InterruptedException e) {
|
|
|
|
|
- // Thread.currentThread().interrupt();
|
|
|
|
|
- // log.error("等待创意资产准备完成被中断, accountId={}", accountId, e);
|
|
|
|
|
- // }
|
|
|
|
|
- for (AdPlanCreative creative : allCreatives) {
|
|
|
|
|
- try {
|
|
|
|
|
- prepareSingleCreativeAsset(tencentAds, accountId, creative);
|
|
|
|
|
- } catch (Exception e) {
|
|
|
|
|
- log.error("准备创意资产失败, accountId={}, creativeId={}, creativeName={}",
|
|
|
|
|
- accountId, creative.getId(), creative.getCreativeName(), e);
|
|
|
|
|
- adPlanCreativeService.lambdaUpdate()
|
|
|
|
|
- .eq(AdPlanCreative::getId, creative.getId())
|
|
|
|
|
- .set(AdPlanCreative::getFailReason, truncateError(e.getMessage()))
|
|
|
|
|
- .set(AdPlanCreative::getStatus, AdPlanStatus.FAILED)
|
|
|
|
|
- .update();
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ // 填充创意文件的 imageId 或 videoId
|
|
|
|
|
+ if (creativeFileMd5 != null && creativeFileType != null) {
|
|
|
|
|
+ if ("image".equalsIgnoreCase(creativeFileType)) {
|
|
|
|
|
+ Long imageId = imageIdMap.getOrDefault(accountId, Collections.emptyMap()).get(creativeFileMd5);
|
|
|
|
|
+ if (imageId != null) {
|
|
|
|
|
+ creative.setImageId(imageId);
|
|
|
|
|
+ }
|
|
|
|
|
+ } else if ("video".equalsIgnoreCase(creativeFileType)) {
|
|
|
|
|
+ Long videoId = videoIdMap.getOrDefault(accountId, Collections.emptyMap()).get(creativeFileMd5);
|
|
|
|
|
+ if (videoId != null) {
|
|
|
|
|
+ creative.setVideoId(videoId);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 填充品牌形象的 brandImageId
|
|
|
|
|
+ if (brandFile != null && brandFile.getFileMd5() != null) {
|
|
|
|
|
+ Long brandImageId = brandImageIdMap.getOrDefault(accountId, Collections.emptyMap()).get(brandFile.getFileMd5());
|
|
|
|
|
+ if (brandImageId != null) {
|
|
|
|
|
+ creative.setBrandImageId(brandImageId);
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
log.info("准备创意资产完成");
|
|
log.info("准备创意资产完成");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 准备单个创意的资产
|
|
|
|
|
|
|
+ * 从 plans 中获取品牌名称
|
|
|
*/
|
|
*/
|
|
|
- private void prepareSingleCreativeAsset(TencentAds tencentAds, Long accountId, AdPlanCreative creative) throws Exception {
|
|
|
|
|
- String creativeFileType = creative.getCreativeFileType();
|
|
|
|
|
- String creativeFileUrl = creative.getCreativeFileUrl();
|
|
|
|
|
- String creativeFileMd5 = creative.getCreativeFileMd5();
|
|
|
|
|
- Long creativeImageId = creative.getImageId();
|
|
|
|
|
- Long creativeVideoId = creative.getVideoId();
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
- // 跳过无效数据
|
|
|
|
|
- if (creativeFileType == null || creativeFileType.isEmpty()
|
|
|
|
|
- || creativeFileUrl == null || creativeFileUrl.isEmpty()
|
|
|
|
|
- || creativeFileMd5 == null || creativeFileMd5.isEmpty()) {
|
|
|
|
|
- return;
|
|
|
|
|
|
|
+ private String getBrandNameFromPlans(List<AdPlan> plans) {
|
|
|
|
|
+ for (AdPlan plan : plans) {
|
|
|
|
|
+ if (plan.getAdCreatives() != null) {
|
|
|
|
|
+ for (AdPlanCreative creative : plan.getAdCreatives()) {
|
|
|
|
|
+ if (creative.getBrandName() != null && !creative.getBrandName().isEmpty()) {
|
|
|
|
|
+ return creative.getBrandName();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
+ return "默认品牌";
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- if ("image".equalsIgnoreCase(creativeFileType) && creativeImageId != null && creativeImageId > 0) {
|
|
|
|
|
- return;
|
|
|
|
|
- } else if ("video".equalsIgnoreCase(creativeFileType) && creativeVideoId != null && creativeVideoId > 0) {
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 下载文件并返回字节数组
|
|
|
|
|
+ */
|
|
|
|
|
+ private byte[] downloadFileToBytes(String fileUrl) throws Exception {
|
|
|
|
|
+ HttpURLConnection connection = null;
|
|
|
|
|
+ InputStream inputStream = null;
|
|
|
|
|
+ try {
|
|
|
|
|
+ URL url = new URL(fileUrl);
|
|
|
|
|
+ connection = (HttpURLConnection) url.openConnection();
|
|
|
|
|
+ connection.setConnectTimeout(10000);
|
|
|
|
|
+ connection.setReadTimeout(30000);
|
|
|
|
|
+ connection.setRequestMethod("GET");
|
|
|
|
|
+ connection.connect();
|
|
|
|
|
|
|
|
- if ("image".equalsIgnoreCase(creativeFileType)) {
|
|
|
|
|
- // 图片类型:查询或上传图片
|
|
|
|
|
- if (creative.getImageId() != null && creative.getImageId() > 0) {
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
- Long imageId = tencentAccountIdImageService.getImageIdByAccountIdAndMd5(accountId, creativeFileMd5);
|
|
|
|
|
- if (imageId != null) {
|
|
|
|
|
- log.info("从缓存/数据库获取图片ID,accountId={}, md5={}, imageId={}", accountId, creativeFileMd5, imageId);
|
|
|
|
|
- creative.setImageId(imageId);
|
|
|
|
|
- } else {
|
|
|
|
|
- // 上传图片
|
|
|
|
|
- log.info("上传创意图片,accountId={}, URL={}", accountId, creativeFileUrl);
|
|
|
|
|
- imageId = uploadImageFromUrl(tencentAds, accountId, creativeFileUrl, creativeFileMd5,
|
|
|
|
|
- "创意图片-" + creative.getCreativeName());
|
|
|
|
|
- creative.setImageId(imageId);
|
|
|
|
|
- // 保存到数据库
|
|
|
|
|
- tencentAccountIdImageService.saveImageId(accountId, creativeFileMd5, imageId, creativeFileUrl);
|
|
|
|
|
- log.info("图片上传并保存成功,accountId={}, md5={}, imageId={}", accountId, creativeFileMd5, imageId);
|
|
|
|
|
|
|
+ int responseCode = connection.getResponseCode();
|
|
|
|
|
+ if (responseCode != HttpURLConnection.HTTP_OK) {
|
|
|
|
|
+ throw new Exception("下载文件失败,HTTP状态码: " + responseCode);
|
|
|
}
|
|
}
|
|
|
- adPlanCreativeService.updateById(creative);
|
|
|
|
|
|
|
|
|
|
- } else if ("video".equalsIgnoreCase(creativeFileType)) {
|
|
|
|
|
- // 视频类型:查询或上传视频
|
|
|
|
|
- if (creative.getVideoId() != null && creative.getVideoId() > 0) {
|
|
|
|
|
- return;
|
|
|
|
|
|
|
+ inputStream = connection.getInputStream();
|
|
|
|
|
+ byte[] fileBytes = inputStream.readAllBytes();
|
|
|
|
|
+ log.info("文件下载成功,URL: {}, 大小: {} bytes", fileUrl, fileBytes.length);
|
|
|
|
|
+ return fileBytes;
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ if (inputStream != null) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ inputStream.close();
|
|
|
|
|
+ } catch (IOException e) {
|
|
|
|
|
+ log.warn("关闭输入流失败", e);
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
- Long videoId = tencentAccountIdVideoIdService.getVideoIdByAccountIdAndMd5(accountId, creativeFileMd5);
|
|
|
|
|
- if (videoId != null) {
|
|
|
|
|
- log.info("从缓存/数据库获取视频ID,accountId={}, md5={}, videoId={}", accountId, creativeFileMd5, videoId);
|
|
|
|
|
- creative.setVideoId(videoId);
|
|
|
|
|
- } else {
|
|
|
|
|
- // 上传视频
|
|
|
|
|
- log.info("上传创意视频,accountId={}, URL={}", accountId, creativeFileUrl);
|
|
|
|
|
- VideoUploadResult videoResult = uploadVideoFromUrl(tencentAds, accountId, creativeFileUrl, creativeFileMd5,
|
|
|
|
|
- "创意视频-" + creative.getCreativeName());
|
|
|
|
|
- creative.setVideoId(videoResult.videoId());
|
|
|
|
|
- // 保存到数据库
|
|
|
|
|
- tencentAccountIdVideoIdService.saveVideoId(accountId, creativeFileMd5, videoResult.videoId(),
|
|
|
|
|
- creativeFileUrl, videoResult.coverImageId());
|
|
|
|
|
- log.info("视频上传并保存成功,accountId={}, md5={}, videoId={}", accountId, creativeFileMd5, videoResult.videoId());
|
|
|
|
|
|
|
+ if (connection != null) {
|
|
|
|
|
+ connection.disconnect();
|
|
|
}
|
|
}
|
|
|
- adPlanCreativeService.updateById(creative);
|
|
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- // 处理品牌形象图片
|
|
|
|
|
- prepareBrandImageAsset(tencentAds, accountId, creative);
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 准备品牌形象图片资产
|
|
|
|
|
|
|
+ * 使用字节数组上传图片到腾讯广告
|
|
|
*/
|
|
*/
|
|
|
- private void prepareBrandImageAsset(TencentAds tencentAds, Long accountId, AdPlanCreative creative) throws Exception {
|
|
|
|
|
- String brandImageUrl = creative.getBrandImageUrl();
|
|
|
|
|
- String brandImageFileMd5 = creative.getBrandImageFileMd5();
|
|
|
|
|
- String brandName = creative.getBrandName();
|
|
|
|
|
-
|
|
|
|
|
- // 跳过无效数据
|
|
|
|
|
- if (brandImageUrl == null || brandImageUrl.isEmpty()
|
|
|
|
|
- || brandImageFileMd5 == null || brandImageFileMd5.isEmpty()
|
|
|
|
|
- || brandName == null || brandName.isEmpty()) {
|
|
|
|
|
- return;
|
|
|
|
|
|
|
+ private Long uploadImageFromBytes(TencentAds tencentAds, Long accountId, byte[] imageBytes, String fileMd5, String description)
|
|
|
|
|
+ throws Exception {
|
|
|
|
|
+ String base64Image = Base64.getEncoder().encodeToString(imageBytes);
|
|
|
|
|
+
|
|
|
|
|
+ ImagesAddResponseData response = tencentAds.images()
|
|
|
|
|
+ .imagesAdd(
|
|
|
|
|
+ "UPLOAD_TYPE_BYTES",
|
|
|
|
|
+ fileMd5,
|
|
|
|
|
+ accountId,
|
|
|
|
|
+ null,
|
|
|
|
|
+ null,
|
|
|
|
|
+ base64Image,
|
|
|
|
|
+ null,
|
|
|
|
|
+ description,
|
|
|
|
|
+ null,
|
|
|
|
|
+ null,
|
|
|
|
|
+ null
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ if (response == null || response.getImageId() == null) {
|
|
|
|
|
+ throw new Exception("图片上传返回结果为空");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 已经有 brandImageId 则跳过
|
|
|
|
|
- if (creative.getBrandImageId() != null && creative.getBrandImageId() > 0) {
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ log.info("图片上传成功,imageId: {}, 宽度: {}px, 高度: {}px",
|
|
|
|
|
+ response.getImageId(), response.getImageWidth(), response.getImageHeight());
|
|
|
|
|
|
|
|
- // 查询缓存/数据库
|
|
|
|
|
- Long brandImageId = tencentAccountIdBrandImageService.getBrandImageIdByAccountIdAndMd5(accountId, brandImageFileMd5);
|
|
|
|
|
- if (brandImageId != null) {
|
|
|
|
|
- log.info("从缓存/数据库获取品牌形象ID,accountId={}, md5={}, brandImageId={}", accountId, brandImageFileMd5, brandImageId);
|
|
|
|
|
- creative.setBrandImageId(brandImageId);
|
|
|
|
|
- } else {
|
|
|
|
|
- // 上传品牌形象
|
|
|
|
|
- log.info("上传品牌形象,accountId={}, URL={}, brandName={}", accountId, brandImageUrl, brandName);
|
|
|
|
|
- BrandUploadResult brandResult = uploadBrandImageFromUrl(tencentAds, accountId, brandImageUrl, brandImageFileMd5, brandName);
|
|
|
|
|
- creative.setBrandImageId(brandResult.imageId());
|
|
|
|
|
- // 保存到数据库
|
|
|
|
|
- tencentAccountIdBrandImageService.saveBrandImageId(accountId, brandImageFileMd5, brandResult.imageId(),
|
|
|
|
|
- brandImageUrl, brandResult.width(), brandResult.height());
|
|
|
|
|
- log.info("品牌形象上传并保存成功,accountId={}, md5={}, brandImageId={}", accountId, brandImageFileMd5, brandResult.imageId());
|
|
|
|
|
- }
|
|
|
|
|
- adPlanCreativeService.updateById(creative);
|
|
|
|
|
|
|
+ return Long.parseLong(response.getImageId());
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -842,6 +921,15 @@ public class AdPlanConfServiceImpl extends ServiceImpl<AdPlanConfMapper, AdPlanC
|
|
|
if (creative.getCreativeId() > 0 || creative.getStatus() == AdPlanStatus.SUCCESS)
|
|
if (creative.getCreativeId() > 0 || creative.getStatus() == AdPlanStatus.SUCCESS)
|
|
|
continue;
|
|
continue;
|
|
|
|
|
|
|
|
|
|
+ // 判定creative的imageId或者videoId没有值或者=0,直接判定为失败
|
|
|
|
|
+ if (creative.getImageId() == 0 || creative.getVideoId() == 0) {
|
|
|
|
|
+ creative.setStatus(AdPlanStatus.FAILED);
|
|
|
|
|
+ creative.setFailReason("创意图片或视频未上传成功");
|
|
|
|
|
+ adPlanCreativeService.updateById(creative);
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
// 更新状态为执行中
|
|
// 更新状态为执行中
|
|
|
creative.setStatus(AdPlanStatus.IN_PROGRESS);
|
|
creative.setStatus(AdPlanStatus.IN_PROGRESS);
|
|
|
creative.setAdgroupId(plan.getAdgroupId());
|
|
creative.setAdgroupId(plan.getAdgroupId());
|
|
@@ -899,7 +987,7 @@ public class AdPlanConfServiceImpl extends ServiceImpl<AdPlanConfMapper, AdPlanC
|
|
|
if (creative.getVideoId() > 0) {
|
|
if (creative.getVideoId() > 0) {
|
|
|
components.setVideo(buildVideoComponents(creative.getVideoId()));
|
|
components.setVideo(buildVideoComponents(creative.getVideoId()));
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
|
|
|
|
|
// 品牌形象 (固定品牌)
|
|
// 品牌形象 (固定品牌)
|
|
|
components.setBrand(buildBrandComponents(creative.getBrandImageId(), creative.getBrandName()));
|
|
components.setBrand(buildBrandComponents(creative.getBrandImageId(), creative.getBrandName()));
|