pudongliang 4 месяцев назад
Родитель
Сommit
d201b5a9f7

+ 344 - 0
src/main/java/com/moka/gdtauto/service/TencentAssetService.java

@@ -0,0 +1,344 @@
+package com.moka.gdtauto.service;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.Base64;
+
+import org.bytedeco.librealsense2.rs2_firmware_log_message;
+import org.springframework.stereotype.Service;
+
+import com.moka.gdtauto.client.TencentAdsApiClientFactory;
+import com.moka.gdtauto.common.Constant;
+import com.moka.gdtauto.entity.OssFile;
+import com.tencent.ads.model.v3.BrandAddResponseData;
+import com.tencent.ads.model.v3.ImagesAddResponseData;
+import com.tencent.ads.model.v3.VideosAddResponseData;
+import com.tencent.ads.v3.TencentAds;
+
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+@Service
+@Slf4j
+@AllArgsConstructor
+public class TencentAssetService {
+    private TencentAdsApiClientFactory clientFactory;
+
+    public void uploadAsset(OssFile ossFile) throws Exception {
+        // 只支持创意上传
+        if (ossFile.getBusinessType() != 1) {
+            return;
+        }
+        log.info("开始上传文件到腾讯广告,ID: {}", ossFile.getId());
+        TencentAds tencentAds = clientFactory.getTencentAds("", Constant.MAIN_ORG_ACCOUNT_ID);
+        String fileUrl = ossFile.getFileUrl();
+        String fileMd5 = ossFile.getFileMd5();
+        // image/video
+        String fileType = ossFile.getFileType();
+        if (fileType.equals("image")) {
+            try {
+                byte[] fileBytes = downloadFileToBytes(fileUrl);
+                ImageUploadResult result = uploadImageFromBytes(tencentAds, fileBytes, fileMd5, fileType);
+                ossFile.setAssetId(result.imageId);
+                ossFile.setWidth(result.width);
+                ossFile.setHeight(result.height);
+                ossFile.setImageFileSize(result.imageFileSize);
+                ossFile.setImageType(result.imageType);
+                ossFile.setImageSignature(result.imageSignature);
+                ossFile.setOuterImageId(result.outerImageId);
+                ossFile.setPreviewUrl(result.previewUrl);
+                ossFile.setDescription(result.description);
+            } catch (Exception e) {
+                log.error("上传文件失败,URL: {}", fileUrl, e);
+            }
+        } else if (fileType.equals("video")) {
+            try {
+               VideoUploadResult result =  uploadVideoFromUrl(tencentAds, fileUrl, fileMd5, fileType);
+               ossFile.setAssetId(String.valueOf(result.videoId));
+               ossFile.setCoverImageId(result.coverImageId);
+            } catch (Exception e) {
+                log.error("上传文件失败,URL: {}", fileUrl, e);
+            }
+        }
+    }
+
+    public ImageUploadResult uploadImageFromBytes(TencentAds tencentAds, byte[] imageBytes, String fileMd5,
+            String description)
+            throws Exception {
+        String base64Image = Base64.getEncoder().encodeToString(imageBytes);
+        ImagesAddResponseData response = tencentAds.images()
+                .imagesAdd(
+                        "UPLOAD_TYPE_BYTES",
+                        fileMd5,
+                        null,
+                        Constant.MAIN_ORG_ACCOUNT_ID,
+                        null,
+                        base64Image,
+                        null,
+                        description,
+                        null,
+                        null,
+                        null);
+
+        if (response == null || response.getImageId() == null) {
+            throw new Exception("图片上传返回结果为空");
+        }
+
+        log.info("图片上传成功,imageId: {}, 宽度: {}px, 高度: {}px",
+                response.getImageId(), response.getImageWidth(), response.getImageHeight());
+
+        return new ImageUploadResult(
+                response.getImageId(),
+                response.getImageWidth(),
+                response.getImageHeight(),
+                response.getImageFileSize(),
+                response.getImageType().getValue(),
+                response.getImageSignature(),
+                response.getOuterImageId(),
+                response.getPreviewUrl(),
+                response.getDescription());
+    }
+
+    public record ImageUploadResult(
+            String imageId,
+            /**
+             * 图片宽度
+             */
+            Long width,
+            /**
+             * 图片高度
+             */
+            Long height,
+            /**
+             * 图片大小
+             */
+            Long imageFileSize,
+            /**
+             * 图片类型
+             */
+            String imageType,
+            /**
+             * 图片签名
+             */
+            String imageSignature,
+            /**
+             * 外部图片ID
+             */
+            String outerImageId,
+            /**
+             * 预览地址
+             */
+            String previewUrl,
+            /**
+             * 图片描述
+             */
+            String description) {
+    }
+
+    /**
+     * 视频上传结果记录
+     */
+    public record VideoUploadResult(Long videoId, Long coverImageId) {
+    }
+
+    /**
+     * 品牌形象上传结果记录
+     */
+    public record BrandUploadResult(Long imageId, Long width, Long height) {
+    }
+
+    /**
+     * 从URL下载品牌形象并上传到腾讯广告(返回完整信息)
+     * 品牌形象不支持业务单元ID上传
+     *
+     * @param tencentAds    腾讯广告SDK实例
+     * @param accountId     账户ID
+     * @param brandImageUrl 品牌形象图片URL
+     * @param fileMd5       文件MD5
+     * @param brandName     品牌名称
+     * @return 品牌形象上传结果
+     * @throws Exception 上传失败
+     */
+    public BrandUploadResult uploadBrandImageFromUrl(TencentAds tencentAds, Long accountId, String brandImageUrl, String fileMd5, String brandName)
+            throws Exception {
+        File tempFile = null;
+        try {
+            // 1. 从URL下载图片到临时文件
+            tempFile = downloadFileFromUrl(brandImageUrl);
+
+            // 2. 调用腾讯广告API创建品牌形象
+            BrandAddResponseData response = tencentAds.brand()
+                    .brandAdd(
+                            accountId, // accountId
+                            brandName, // name
+                            tempFile // brandImageFile
+                    );
+
+            if (response == null || response.getImageId() == null) {
+                throw new Exception("品牌形象上传返回结果为空");
+            }
+
+            log.info("品牌形象上传成功,imageId: {}, 品牌名称: {}, 宽度: {}px, 高度: {}px",
+                    response.getImageId(), response.getName(), response.getWidth(), response.getHeight());
+
+            return new BrandUploadResult(
+                    Long.parseLong(response.getImageId()),
+                    response.getWidth(),
+                    response.getHeight()
+            );
+
+        } finally {
+            // 3. 删除临时文件
+            if (tempFile != null && tempFile.exists()) {
+                tempFile.delete();
+            }
+        }
+    }
+
+    public VideoUploadResult uploadVideoFromUrl(TencentAds tencentAds,  String videoUrl, String fileMd5,  String description)
+            throws Exception {
+        File tempFile = null;
+        try {
+            // 1. 从URL下载视频到临时文件
+            tempFile = downloadFileFromUrl(videoUrl);
+
+            // 2. 检查文件大小(最大100MB)
+            long fileSizeInMB = tempFile.length() / (1024 * 1024);
+            if (fileSizeInMB > 100) {
+                throw new Exception("视频文件过大: " + fileSizeInMB + "MB,超过 100MB 限制");
+            }
+
+            // 3. 计算视频MD5签名
+            String signature = fileMd5;
+            log.info("视频MD5签名: {}, 文件大小: {}MB", signature, fileSizeInMB);
+
+            // 4. 调用腾讯广告API上传视频
+            VideosAddResponseData response = tencentAds.videos()
+                    .videosAdd(
+                            tempFile, // videoFile
+                            signature, // signature
+                            null, // accountId
+                            Constant.MAIN_ORG_ACCOUNT_ID, // organizationId
+                            description, // description
+                            null // adcreativeTemplateId
+                    );
+
+            if (response == null || response.getVideoId() == null) {
+                throw new Exception("视频上传返回结果为空");
+            }
+
+            log.info("视频上传成功,videoId: {}, 封面图片ID: {}",
+                    response.getVideoId(), response.getCoverImageId());
+            return new VideoUploadResult(response.getVideoId(), response.getCoverImageId());
+        } finally {
+            // 5. 删除临时文件
+            if (tempFile != null && tempFile.exists()) {
+                tempFile.delete();
+            }
+        }
+    }
+
+    private File downloadFileFromUrl(String fileUrl) throws Exception {
+        HttpURLConnection connection = null;
+        InputStream inputStream = null;
+        FileOutputStream outputStream = null;
+
+        try {
+            // 创建临时文件
+            String suffix = getFileSuffix(fileUrl);
+            File tempFile = File.createTempFile("gdt_upload_", suffix);
+
+            // 打开HTTP连接
+            URL url = new URL(fileUrl);
+            connection = (HttpURLConnection) url.openConnection();
+            connection.setConnectTimeout(10000);
+            connection.setReadTimeout(30000);
+            connection.setRequestMethod("GET");
+            connection.connect();
+
+            int responseCode = connection.getResponseCode();
+            if (responseCode != HttpURLConnection.HTTP_OK) {
+                throw new Exception("下载文件失败,HTTP状态码: " + responseCode);
+            }
+
+            // 读取文件内容
+            inputStream = connection.getInputStream();
+            outputStream = new FileOutputStream(tempFile);
+
+            byte[] buffer = new byte[8192];
+            int bytesRead;
+            while ((bytesRead = inputStream.read(buffer)) != -1) {
+                outputStream.write(buffer, 0, bytesRead);
+            }
+
+            log.info("文件下载成功,URL: {}, 大小: {} bytes", fileUrl, tempFile.length());
+            return tempFile;
+
+        } finally {
+            if (outputStream != null) {
+                try {
+                    outputStream.close();
+                } catch (Exception e) {
+                }
+            }
+            if (inputStream != null) {
+                try {
+                    inputStream.close();
+                } catch (Exception e) {
+                }
+            }
+            if (connection != null) {
+                connection.disconnect();
+            }
+        }
+    }
+
+    private String getFileSuffix(String fileUrl) {
+        int lastDotIndex = fileUrl.lastIndexOf('.');
+        int lastSlashIndex = fileUrl.lastIndexOf('/');
+
+        if (lastDotIndex > lastSlashIndex && lastDotIndex < fileUrl.length() - 1) {
+            return fileUrl.substring(lastDotIndex);
+        }
+
+        return ".mp4"; // 默认后缀
+    }
+
+    public 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();
+
+            int responseCode = connection.getResponseCode();
+            if (responseCode != HttpURLConnection.HTTP_OK) {
+                throw new Exception("下载文件失败,HTTP状态码: " + responseCode);
+            }
+
+            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);
+                }
+            }
+            if (connection != null) {
+                connection.disconnect();
+            }
+        }
+    }
+}

+ 15 - 446
src/main/java/com/moka/gdtauto/service/impl/AdPlanConfServiceImpl.java

@@ -10,6 +10,9 @@ import com.moka.gdtauto.mapper.AdPlanConfMapper;
 import com.moka.gdtauto.service.AdPlanConfService;
 import com.moka.gdtauto.service.AdPlanCreativeService;
 import com.moka.gdtauto.service.TencentAdsAdgroupService;
+import com.moka.gdtauto.service.TencentAssetService;
+import com.moka.gdtauto.service.TencentAssetService.BrandUploadResult;
+import com.moka.gdtauto.service.TencentAssetService.VideoUploadResult;
 import com.moka.gdtauto.service.TencentAccountService;
 import com.moka.gdtauto.client.TencentAdsApiClientFactory;
 
@@ -71,7 +74,6 @@ import com.moka.gdtauto.common.Constant;
 @Service
 public class AdPlanConfServiceImpl extends ServiceImpl<AdPlanConfMapper, AdPlanConf> implements AdPlanConfService {
 
-    private final AssetDramaSyncConfController assetDramaSyncConfController;
     @Autowired
     private AdPlanService adPlanService;
     @Autowired
@@ -81,17 +83,14 @@ public class AdPlanConfServiceImpl extends ServiceImpl<AdPlanConfMapper, AdPlanC
     @Autowired
     private TencentAdsApiClientFactory clientFactory;
     @Autowired
-    private TencentAccountService tencentAccountService;
-    @Autowired
     private TencentAccountIdImageService tencentAccountIdImageService;
     @Autowired
     private TencentAccountIdVideoIdService tencentAccountIdVideoIdService;
     @Autowired
     private TencentAccountIdBrandImageService tencentAccountIdBrandImageService;
 
-    AdPlanConfServiceImpl(AssetDramaSyncConfController assetDramaSyncConfController) {
-        this.assetDramaSyncConfController = assetDramaSyncConfController;
-    }
+    @Autowired
+    private TencentAssetService tencentAssetService;
 
     @Async
     @Override
@@ -543,7 +542,7 @@ public class AdPlanConfServiceImpl extends ServiceImpl<AdPlanConfMapper, AdPlanC
 
         // 第一步:准备阶段 - 填充创意的 imageId/videoId
         try {
-            prepareCreativeAssets2(conf, tencentAds, plans);
+            prepareCreativeAssets(conf, tencentAds, plans);
         } catch (Exception e) {
             log.error("准备创意资产失败,confId={}", confId, e);
             // 更新 AdPlanConf 状态为失败
@@ -621,7 +620,7 @@ public class AdPlanConfServiceImpl extends ServiceImpl<AdPlanConfMapper, AdPlanC
         log.info("异步对接腾讯接口完成,confId={}", confId);
     }
 
-    private void prepareCreativeAssets2(AdPlanConf conf, TencentAds tencentAds, List<AdPlan> plans) throws Exception {
+    private void prepareCreativeAssets(AdPlanConf conf, TencentAds tencentAds, List<AdPlan> plans) throws Exception {
         log.info("准备创意资产开始,计划数量: {}", plans.size());
         String creativeFiles = conf.getCreativeFiles();
         String brandImageFile = conf.getBrandImageFile();
@@ -693,12 +692,12 @@ public class AdPlanConfServiceImpl extends ServiceImpl<AdPlanConfMapper, AdPlanC
                 if (!needUploadAccountIds.isEmpty()) {
                     log.info("图片 {} 需要上传到 {} 个账户", ossFile.getFileName(), needUploadAccountIds.size());
                     // 下载图片一次
-                    byte[] fileBytes = downloadFileToBytes(fileUrl);
+                    byte[] fileBytes = tencentAssetService.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);
+                        TencentAssetService.ImageUploadResult result = tencentAssetService.uploadImageFromBytes(tencentAds, fileBytes, fileMd5, "创意图片-" + ossFile.getFileName());
+                        tencentAccountIdImageService.saveImageId(accountId, fileMd5, Long.valueOf(result.imageId()) , fileUrl);
+                        imageIdMap.computeIfAbsent(accountId, k -> new HashMap<>()).put(fileMd5, Long.valueOf(result.imageId()));
+                        log.info("图片上传成功,accountId={}, fileMd5={}, imageId={}", accountId, fileMd5, result.imageId());
                     }
                 }
             } else if ("video".equalsIgnoreCase(fileType)) {
@@ -710,7 +709,7 @@ public class AdPlanConfServiceImpl extends ServiceImpl<AdPlanConfMapper, AdPlanC
                 if (!needUploadAccountIds.isEmpty()) {
                     log.info("视频 {} 需要上传到 {} 个账户", ossFile.getFileName(), needUploadAccountIds.size());
                     for (Long accountId : needUploadAccountIds) {
-                        VideoUploadResult videoResult = uploadVideoFromUrl(tencentAds, accountId, fileUrl, fileMd5, "创意视频-" + ossFile.getFileName());
+                        TencentAssetService.VideoUploadResult videoResult = tencentAssetService.uploadVideoFromUrl(tencentAds, 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());
@@ -742,9 +741,9 @@ public class AdPlanConfServiceImpl extends ServiceImpl<AdPlanConfMapper, AdPlanC
             if (!needUploadAccountIds.isEmpty()) {
                 log.info("品牌形象需要上传到 {} 个账户", needUploadAccountIds.size());
                 // 获取品牌名称(从 conf 或创意中获取)
-                String brandName = getBrandNameFromPlans(plans);
+                String brandName = conf.getBrandName();
                 for (Long accountId : needUploadAccountIds) {
-                    BrandUploadResult brandResult = uploadBrandImageFromUrl(tencentAds, accountId, brandFileUrl, brandFileMd5, brandName);
+                    TencentAssetService.BrandUploadResult brandResult = tencentAssetService.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());
@@ -790,90 +789,6 @@ public class AdPlanConfServiceImpl extends ServiceImpl<AdPlanConfMapper, AdPlanC
         log.info("准备创意资产完成");
     }
 
-    /**
-     * 从 plans 中获取品牌名称
-     */
-    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 "默认品牌";
-    }
-
-    /**
-     * 下载文件并返回字节数组
-     */
-    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();
-
-            int responseCode = connection.getResponseCode();
-            if (responseCode != HttpURLConnection.HTTP_OK) {
-                throw new Exception("下载文件失败,HTTP状态码: " + responseCode);
-            }
-
-            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);
-                }
-            }
-            if (connection != null) {
-                connection.disconnect();
-            }
-        }
-    }
-
-    /**
-     * 使用字节数组上传图片到腾讯广告
-     */
-    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("图片上传返回结果为空");
-        }
-
-        log.info("图片上传成功,imageId: {}, 宽度: {}px, 高度: {}px",
-                response.getImageId(), response.getImageWidth(), response.getImageHeight());
-
-        return Long.parseLong(response.getImageId());
-    }
 
     /**
      * 构建广告组请求
@@ -1203,350 +1118,4 @@ public class AdPlanConfServiceImpl extends ServiceImpl<AdPlanConfMapper, AdPlanC
             return 1;
         }
     }
-
-    /**
-     * 从URL下载图片并上传到腾讯广告
-     *
-     * @param tencentAds  腾讯广告SDK实例
-     * @param accountId   账户ID
-     * @param imageUrl    图片URL
-     * @param description 图片描述
-     * @return 图片ID
-     * @throws Exception 上传失败
-     */
-    private Long uploadImageFromUrl(TencentAds tencentAds, Long accountId, String imageUrl, String fileMd5, String description)
-            throws Exception {
-        HttpURLConnection connection = null;
-        InputStream inputStream = null;
-        try {
-            // 1. 从URL直接读取图片字节流
-            URL url = new URL(imageUrl);
-            connection = (HttpURLConnection) url.openConnection();
-            connection.setConnectTimeout(10000);
-            connection.setReadTimeout(30000);
-            connection.setRequestMethod("GET");
-            connection.connect();
-
-            int responseCode = connection.getResponseCode();
-            if (responseCode != HttpURLConnection.HTTP_OK) {
-                throw new Exception("下载图片失败,HTTP状态码: " + responseCode);
-            }
-
-            // 2. 读取输入流并转Base64
-            inputStream = connection.getInputStream();
-            byte[] imageBytes = inputStream.readAllBytes();
-            String base64Image = Base64.getEncoder().encodeToString(imageBytes);
-
-            log.info("图片下载成功,URL: {}, 大小: {} bytes", imageUrl, imageBytes.length);
-
-            // 3. 计算MD5签名
-            String signature = fileMd5;
-
-            // 4. 调用腾讯广告API上传图片
-            ImagesAddResponseData response = tencentAds.images()
-                    .imagesAdd(
-                            "UPLOAD_TYPE_BYTES", // uploadType
-                            signature, // signature
-                            accountId, // accountId
-                            null, // organizationId
-                            null, // file
-                            base64Image, // bytes
-                            null, // imageUsage
-                            description, // description
-                            null, // resizeWidth
-                            null, // resizeHeight
-                            null // resizeFileSize
-                    );
-
-            if (response == null || response.getImageId() == null) {
-                throw new Exception("图片上传返回结果为空");
-            }
-
-            log.info("图片上传成功,imageId: {}, 宽度: {}px, 高度: {}px",
-                    response.getImageId(), response.getImageWidth(), response.getImageHeight());
-
-            return Long.parseLong(response.getImageId());
-
-        } finally {
-            if (inputStream != null) {
-                try {
-                    inputStream.close();
-                } catch (IOException e) {
-                    log.warn("关闭输入流失败", e);
-                }
-            }
-            if (connection != null) {
-                connection.disconnect();
-            }
-        }
-    }
-
-    /**
-     * 视频上传结果记录
-     */
-    private record VideoUploadResult(Long videoId, Long coverImageId) {}
-
-    /**
-     * 品牌形象上传结果记录
-     */
-    private record BrandUploadResult(Long imageId, Long width, Long height) {}
-
-    private VideoUploadResult uploadVideoFromUrl(TencentAds tencentAds, Long accountId, String videoUrl, String fileMd5, String description)
-            throws Exception {
-        File tempFile = null;
-        try {
-            // 1. 从URL下载视频到临时文件
-            tempFile = downloadFileFromUrl(videoUrl);
-
-            // 2. 检查文件大小(最大100MB)
-            long fileSizeInMB = tempFile.length() / (1024 * 1024);
-            if (fileSizeInMB > 100) {
-                throw new Exception("视频文件过大: " + fileSizeInMB + "MB,超过 100MB 限制");
-            }
-
-            // 3. 计算视频MD5签名
-            String signature = fileMd5;
-            log.info("视频MD5签名: {}, 文件大小: {}MB", signature, fileSizeInMB);
-
-            // 4. 调用腾讯广告API上传视频
-            VideosAddResponseData response = tencentAds.videos()
-                    .videosAdd(
-                            tempFile,           // videoFile
-                            signature,          // signature
-                            accountId,          // accountId
-                            null,               // organizationId
-                            description,        // description
-                            null                // adcreativeTemplateId
-                    );
-
-            if (response == null || response.getVideoId() == null) {
-                throw new Exception("视频上传返回结果为空");
-            }
-
-            log.info("视频上传成功,videoId: {}, 封面图片ID: {}",
-                    response.getVideoId(), response.getCoverImageId());
-
-            return new VideoUploadResult(response.getVideoId(), response.getCoverImageId());
-
-        } finally {
-            // 5. 删除临时文件
-            if (tempFile != null && tempFile.exists()) {
-                tempFile.delete();
-            }
-        }
-    }
-
-    /**
-     * 计算文件的MD5签名
-     *
-     * @param file 文件
-     * @return MD5签名(32位小写字符串)
-     * @throws Exception 计算失败
-     */
-    private String calculateFileMD5(File file) throws Exception {
-        MessageDigest md = MessageDigest.getInstance("MD5");
-        try (java.io.FileInputStream fis = new java.io.FileInputStream(file)) {
-            byte[] buffer = new byte[8192];
-            int bytesRead;
-            while ((bytesRead = fis.read(buffer)) != -1) {
-                md.update(buffer, 0, bytesRead);
-            }
-        }
-        byte[] digest = md.digest();
-        BigInteger bigInt = new BigInteger(1, digest);
-        String hashText = bigInt.toString(16);
-        // 补齐前导0
-        while (hashText.length() < 32) {
-            hashText = "0" + hashText;
-        }
-        return hashText;
-    }
-
-    /**
-     * 从URL下载品牌形象并上传到腾讯广告
-     *
-     * @param tencentAds    腾讯广告SDK实例
-     * @param accountId     账户ID
-     * @param brandImageUrl 品牌形象图片URL
-     * @param brandName     品牌名称
-     * @return 品牌图片ID
-     * @throws Exception 上传失败
-     */
-    private Long uploadBrandFromUrl(TencentAds tencentAds, Long accountId, String brandImageUrl, String brandName)
-            throws Exception {
-        File tempFile = null;
-        try {
-            // 1. 从URL下载图片到临时文件
-            tempFile = downloadFileFromUrl(brandImageUrl);
-
-            // 2. 调用腾讯广告API创建品牌形象
-            BrandAddResponseData response = tencentAds.brand()
-                    .brandAdd(
-                            accountId, // accountId
-                            brandName, // name
-                            tempFile // brandImageFile
-                    );
-
-            if (response == null || response.getImageId() == null) {
-                throw new Exception("品牌形象上传返回结果为空");
-            }
-
-            log.info("品牌形象上传成功,imageId: {}, 品牌名称: {}, 宽度: {}px, 高度: {}px",
-                    response.getImageId(), response.getName(), response.getWidth(), response.getHeight());
-
-            return Long.parseLong(response.getImageId());
-
-        } finally {
-            // 3. 删除临时文件
-            if (tempFile != null && tempFile.exists()) {
-                tempFile.delete();
-            }
-        }
-    }
-
-    /**
-     * 从URL下载品牌形象并上传到腾讯广告(返回完整信息)
-     *
-     * @param tencentAds    腾讯广告SDK实例
-     * @param accountId     账户ID
-     * @param brandImageUrl 品牌形象图片URL
-     * @param fileMd5       文件MD5
-     * @param brandName     品牌名称
-     * @return 品牌形象上传结果
-     * @throws Exception 上传失败
-     */
-    private BrandUploadResult uploadBrandImageFromUrl(TencentAds tencentAds, Long accountId, String brandImageUrl, String fileMd5, String brandName)
-            throws Exception {
-        File tempFile = null;
-        try {
-            // 1. 从URL下载图片到临时文件
-            tempFile = downloadFileFromUrl(brandImageUrl);
-
-            // 2. 调用腾讯广告API创建品牌形象
-            BrandAddResponseData response = tencentAds.brand()
-                    .brandAdd(
-                            accountId, // accountId
-                            brandName, // name
-                            tempFile // brandImageFile
-                    );
-
-            if (response == null || response.getImageId() == null) {
-                throw new Exception("品牌形象上传返回结果为空");
-            }
-
-            log.info("品牌形象上传成功,imageId: {}, 品牌名称: {}, 宽度: {}px, 高度: {}px",
-                    response.getImageId(), response.getName(), response.getWidth(), response.getHeight());
-
-            return new BrandUploadResult(
-                    Long.parseLong(response.getImageId()),
-                    response.getWidth(),
-                    response.getHeight()
-            );
-
-        } finally {
-            // 3. 删除临时文件
-            if (tempFile != null && tempFile.exists()) {
-                tempFile.delete();
-            }
-        }
-    }
-
-    /**
-     * 从URL下载文件到临时文件
-     *
-     * @param fileUrl 文件URL
-     * @return 临时文件
-     * @throws Exception 下载失败
-     */
-    private File downloadFileFromUrl(String fileUrl) throws Exception {
-        HttpURLConnection connection = null;
-        InputStream inputStream = null;
-        FileOutputStream outputStream = null;
-
-        try {
-            // 创建临时文件
-            String suffix = getFileSuffix(fileUrl);
-            File tempFile = File.createTempFile("gdt_upload_", suffix);
-
-            // 打开HTTP连接
-            URL url = new URL(fileUrl);
-            connection = (HttpURLConnection) url.openConnection();
-            connection.setConnectTimeout(10000);
-            connection.setReadTimeout(30000);
-            connection.setRequestMethod("GET");
-            connection.connect();
-
-            int responseCode = connection.getResponseCode();
-            if (responseCode != HttpURLConnection.HTTP_OK) {
-                throw new Exception("下载文件失败,HTTP状态码: " + responseCode);
-            }
-
-            // 读取文件内容
-            inputStream = connection.getInputStream();
-            outputStream = new FileOutputStream(tempFile);
-
-            byte[] buffer = new byte[8192];
-            int bytesRead;
-            while ((bytesRead = inputStream.read(buffer)) != -1) {
-                outputStream.write(buffer, 0, bytesRead);
-            }
-
-            log.info("文件下载成功,URL: {}, 大小: {} bytes", fileUrl, tempFile.length());
-            return tempFile;
-
-        } finally {
-            if (outputStream != null) {
-                try {
-                    outputStream.close();
-                } catch (Exception e) {
-                }
-            }
-            if (inputStream != null) {
-                try {
-                    inputStream.close();
-                } catch (Exception e) {
-                }
-            }
-            if (connection != null) {
-                connection.disconnect();
-            }
-        }
-    }
-
-    /**
-     * 获取文件后缀名
-     *
-     * @param fileUrl 文件URL
-     * @return 文件后缀名(包含点号,如:.jpg)
-     */
-    private String getFileSuffix(String fileUrl) {
-        int lastDotIndex = fileUrl.lastIndexOf('.');
-        int lastSlashIndex = fileUrl.lastIndexOf('/');
-
-        if (lastDotIndex > lastSlashIndex && lastDotIndex < fileUrl.length() - 1) {
-            return fileUrl.substring(lastDotIndex);
-        }
-
-        return ".jpg"; // 默认后缀
-    }
-
-    /**
-     * 计算字节数组的MD5签名
-     *
-     * @param bytes 字节数组
-     * @return MD5签名(32位小写字符串)
-     * @throws Exception 计算失败
-     */
-    private String calculateMD5(byte[] bytes) throws Exception {
-        MessageDigest md = MessageDigest.getInstance("MD5");
-        md.update(bytes);
-        byte[] digest = md.digest();
-        BigInteger bigInt = new BigInteger(1, digest);
-        String hashText = bigInt.toString(16);
-        // 补齐前导0
-        while (hashText.length() < 32) {
-            hashText = "0" + hashText;
-        }
-        return hashText;
-    }
 }

+ 3 - 210
src/main/java/com/moka/gdtauto/service/impl/OssFileServiceImpl.java

@@ -10,6 +10,7 @@ import com.moka.gdtauto.controller.OssFileController;
 import com.moka.gdtauto.entity.OssFile;
 import com.moka.gdtauto.mapper.OssFileMapper;
 import com.moka.gdtauto.service.OssFileService;
+import com.moka.gdtauto.service.TencentAssetService;
 import com.moka.gdtauto.util.OssUtil;
 import com.moka.gdtauto.util.TencentFileMd5Util;
 import com.tencent.ads.model.v3.ImagesAddResponseData;
@@ -43,7 +44,7 @@ import java.util.Base64;
 public class OssFileServiceImpl extends ServiceImpl<OssFileMapper, OssFile> implements OssFileService {
 
     private final OssUtil ossUtil;
-    private TencentAdsApiClientFactory clientFactory;
+    private TencentAssetService tencentAssetService;
 
     @Override
     @Transactional(rollbackFor = Exception.class)
@@ -86,7 +87,7 @@ public class OssFileServiceImpl extends ServiceImpl<OssFileMapper, OssFile> impl
             
             save(ossFile);
             if (businessType == 1) {
-                uploadFileToTencent(ossFile);   
+                tencentAssetService.uploadAsset(ossFile);
             }
             
             log.info("文件上传成功,ID: {}, URL: {}", ossFile.getId(), fileUrl);
@@ -97,214 +98,6 @@ public class OssFileServiceImpl extends ServiceImpl<OssFileMapper, OssFile> impl
         }
     }
 
-    private void uploadFileToTencent(OssFile ossFile) throws Exception {
-        log.info("开始上传文件到腾讯广告,ID: {}", ossFile.getId());
-        TencentAds tencentAds = clientFactory.getTencentAds("", Constant.MAIN_ORG_ACCOUNT_ID);
-        String fileUrl = ossFile.getFileUrl();
-            String fileMd5 = ossFile.getFileMd5();
-            //image/video
-            String fileType = ossFile.getFileType();
-            if (fileType.equals("image")) {
-                try  {
-                    byte[] fileBytes = downloadFileToBytes(fileUrl);
-                    uploadImageFromBytes(tencentAds, fileBytes, fileMd5, fileType, ossFile);
-                } catch (Exception e) {
-                    log.error("上传文件失败,URL: {}", fileUrl, e);
-                }
-            } else if(fileType.equals("video")) {
-                try  {
-                    uploadVideoFromUrl(tencentAds, Constant.MAIN_ORG_ACCOUNT_ID, fileUrl, fileMd5, fileType, ossFile);
-                } catch (Exception e) {
-                    log.error("上传文件失败,URL: {}", fileUrl, e);
-                }
-            }
-    }
-
-    private Long uploadImageFromBytes(TencentAds tencentAds, byte[] imageBytes, String fileMd5, String description, OssFile ossFile)
-            throws Exception {
-        String base64Image = Base64.getEncoder().encodeToString(imageBytes);
-
-        ImagesAddResponseData response = tencentAds.images()
-                .imagesAdd(
-                        "UPLOAD_TYPE_BYTES",
-                        fileMd5,
-                        null,
-                        Constant.MAIN_ORG_ACCOUNT_ID,
-                        null,
-                        base64Image,
-                        null,
-                        description,
-                        null,
-                        null,
-                        null
-                );
-
-        if (response == null || response.getImageId() == null) {
-            throw new Exception("图片上传返回结果为空");
-        }
-
-        log.info("图片上传成功,imageId: {}, 宽度: {}px, 高度: {}px",
-                response.getImageId(), response.getImageWidth(), response.getImageHeight());
-        ossFile.setAssetId(response.getImageId());
-        ossFile.setWidth(response.getImageWidth());
-        ossFile.setHeight(response.getImageHeight());
-        ossFile.setImageFileSize(response.getImageFileSize());
-        ossFile.setImageType(response.getImageType().getValue());
-        ossFile.setImageSignature(response.getImageSignature());
-        ossFile.setOuterImageId(response.getOuterImageId());
-        ossFile.setPreviewUrl(response.getPreviewUrl());
-        ossFile.setDescription(response.getDescription());
-        return Long.parseLong(response.getImageId());
-    }
-
-    private record VideoUploadResult(Long videoId, Long coverImageId) {}
-
-
-    private VideoUploadResult uploadVideoFromUrl(TencentAds tencentAds, Long accountId, String videoUrl, String fileMd5, String description, OssFile ossFile)
-            throws Exception {
-        File tempFile = null;
-        try {
-            // 1. 从URL下载视频到临时文件
-            tempFile = downloadFileFromUrl(videoUrl);
-
-            // 2. 检查文件大小(最大100MB)
-            long fileSizeInMB = tempFile.length() / (1024 * 1024);
-            if (fileSizeInMB > 100) {
-                throw new Exception("视频文件过大: " + fileSizeInMB + "MB,超过 100MB 限制");
-            }
-
-            // 3. 计算视频MD5签名
-            String signature = fileMd5;
-            log.info("视频MD5签名: {}, 文件大小: {}MB", signature, fileSizeInMB);
-
-            // 4. 调用腾讯广告API上传视频
-            VideosAddResponseData response = tencentAds.videos()
-                    .videosAdd(
-                            tempFile,           // videoFile
-                            signature,          // signature
-                            null,          // accountId
-                            Constant.MAIN_ORG_ACCOUNT_ID,               // organizationId
-                            description,        // description
-                            null                // adcreativeTemplateId
-                    );
-
-            if (response == null || response.getVideoId() == null) {
-                throw new Exception("视频上传返回结果为空");
-            }
-
-            log.info("视频上传成功,videoId: {}, 封面图片ID: {}",
-                    response.getVideoId(), response.getCoverImageId());
-            ossFile.setAssetId(String.valueOf(response.getVideoId()));
-            ossFile.setCoverImageId(response.getCoverImageId());
-            return new VideoUploadResult(response.getVideoId(), response.getCoverImageId());
-        } finally {
-            // 5. 删除临时文件
-            if (tempFile != null && tempFile.exists()) {
-                tempFile.delete();
-            }
-        }
-    }
-
-    private File downloadFileFromUrl(String fileUrl) throws Exception {
-        HttpURLConnection connection = null;
-        InputStream inputStream = null;
-        FileOutputStream outputStream = null;
-
-        try {
-            // 创建临时文件
-            String suffix = getFileSuffix(fileUrl);
-            File tempFile = File.createTempFile("gdt_upload_", suffix);
-
-            // 打开HTTP连接
-            URL url = new URL(fileUrl);
-            connection = (HttpURLConnection) url.openConnection();
-            connection.setConnectTimeout(10000);
-            connection.setReadTimeout(30000);
-            connection.setRequestMethod("GET");
-            connection.connect();
-
-            int responseCode = connection.getResponseCode();
-            if (responseCode != HttpURLConnection.HTTP_OK) {
-                throw new Exception("下载文件失败,HTTP状态码: " + responseCode);
-            }
-
-            // 读取文件内容
-            inputStream = connection.getInputStream();
-            outputStream = new FileOutputStream(tempFile);
-
-            byte[] buffer = new byte[8192];
-            int bytesRead;
-            while ((bytesRead = inputStream.read(buffer)) != -1) {
-                outputStream.write(buffer, 0, bytesRead);
-            }
-
-            log.info("文件下载成功,URL: {}, 大小: {} bytes", fileUrl, tempFile.length());
-            return tempFile;
-
-        } finally {
-            if (outputStream != null) {
-                try {
-                    outputStream.close();
-                } catch (Exception e) {
-                }
-            }
-            if (inputStream != null) {
-                try {
-                    inputStream.close();
-                } catch (Exception e) {
-                }
-            }
-            if (connection != null) {
-                connection.disconnect();
-            }
-        }
-    }
-
-    private String getFileSuffix(String fileUrl) {
-        int lastDotIndex = fileUrl.lastIndexOf('.');
-        int lastSlashIndex = fileUrl.lastIndexOf('/');
-
-        if (lastDotIndex > lastSlashIndex && lastDotIndex < fileUrl.length() - 1) {
-            return fileUrl.substring(lastDotIndex);
-        }
-
-        return ".mp4"; // 默认后缀
-    }
-
-    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();
-
-            int responseCode = connection.getResponseCode();
-            if (responseCode != HttpURLConnection.HTTP_OK) {
-                throw new Exception("下载文件失败,HTTP状态码: " + responseCode);
-            }
-
-            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);
-                }
-            }
-            if (connection != null) {
-                connection.disconnect();
-            }
-        }
-    }
-
     @Override
     @Transactional(rollbackFor = Exception.class)
     public boolean deleteFile(Long fileId) {