|
|
@@ -4,11 +4,17 @@ import com.aliyun.oss.common.utils.BinaryUtil;
|
|
|
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.controller.OssFileController;
|
|
|
import com.moka.gdtauto.entity.OssFile;
|
|
|
import com.moka.gdtauto.mapper.OssFileMapper;
|
|
|
import com.moka.gdtauto.service.OssFileService;
|
|
|
import com.moka.gdtauto.util.OssUtil;
|
|
|
import com.moka.gdtauto.util.TencentFileMd5Util;
|
|
|
+import com.tencent.ads.model.v3.ImagesAddResponseData;
|
|
|
+import com.tencent.ads.model.v3.VideosAddResponseData;
|
|
|
+import com.tencent.ads.v3.TencentAds;
|
|
|
|
|
|
import lombok.RequiredArgsConstructor;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
@@ -16,7 +22,14 @@ import org.springframework.stereotype.Service;
|
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
import org.springframework.web.multipart.MultipartFile;
|
|
|
|
|
|
+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.time.LocalDateTime;
|
|
|
+import java.util.Base64;
|
|
|
|
|
|
/**
|
|
|
* OSS文件Service实现类
|
|
|
@@ -30,6 +43,7 @@ import java.time.LocalDateTime;
|
|
|
public class OssFileServiceImpl extends ServiceImpl<OssFileMapper, OssFile> implements OssFileService {
|
|
|
|
|
|
private final OssUtil ossUtil;
|
|
|
+ private TencentAdsApiClientFactory clientFactory;
|
|
|
|
|
|
@Override
|
|
|
@Transactional(rollbackFor = Exception.class)
|
|
|
@@ -71,6 +85,9 @@ public class OssFileServiceImpl extends ServiceImpl<OssFileMapper, OssFile> impl
|
|
|
ossFile.setUpdatedTime(LocalDateTime.now());
|
|
|
|
|
|
save(ossFile);
|
|
|
+ if (businessType == 1) {
|
|
|
+ uploadFileToTencent(ossFile);
|
|
|
+ }
|
|
|
|
|
|
log.info("文件上传成功,ID: {}, URL: {}", ossFile.getId(), fileUrl);
|
|
|
return ossFile;
|
|
|
@@ -80,6 +97,214 @@ 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) {
|