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