tyr -on完善接口
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
package com.aida.lanecarford.config;
|
||||
|
||||
import jakarta.validation.constraints.Negative;
|
||||
import org.checkerframework.checker.units.qual.A;
|
||||
import org.springframework.boot.web.servlet.MultipartConfigFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.aida.lanecarford.service.impl;
|
||||
|
||||
import com.aida.lanecarford.common.constant.MinioFileConstants;
|
||||
import com.aida.lanecarford.config.MinioConfig;
|
||||
import com.aida.lanecarford.dto.CustomerPhotoDto;
|
||||
import com.aida.lanecarford.entity.CustomerPhoto;
|
||||
import com.aida.lanecarford.mapper.CustomerPhotoMapper;
|
||||
@@ -21,13 +22,15 @@ import org.springframework.stereotype.Service;
|
||||
@RequiredArgsConstructor
|
||||
public class CustomerPhotoServiceImpl extends ServiceImpl<CustomerPhotoMapper, CustomerPhoto> implements CustomerPhotoService {
|
||||
private final MinioUtil minioUtil;
|
||||
private final MinioConfig minioConfig;
|
||||
|
||||
@Override
|
||||
public CustomerPhoto upload(CustomerPhotoDto customerPhotoDto) {
|
||||
|
||||
String logicalUrl = minioUtil.uploadFile(
|
||||
customerPhotoDto.getFile(),
|
||||
MinioFileConstants.FileType.CUSTOMER_PHOTO
|
||||
MinioFileConstants.FileType.CUSTOMER_PHOTO,
|
||||
minioConfig.getBucketName()
|
||||
);
|
||||
|
||||
CustomerPhoto customerPhoto = CopyUtil.copyObject(customerPhotoDto, CustomerPhoto.class);
|
||||
|
||||
@@ -28,8 +28,7 @@ import java.util.Objects;
|
||||
@RequiredArgsConstructor
|
||||
public class CustomerServiceImpl extends ServiceImpl<CustomerMapper, Customer> implements CustomerService {
|
||||
|
||||
@Resource
|
||||
private VisitRecordService visitRecordService;
|
||||
private final VisitRecordService visitRecordService;
|
||||
|
||||
// 选择顾客登录并添加入店记录
|
||||
public CustomerCheckInVO customerCheckIn(String name, String email) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.aida.lanecarford.service.impl;
|
||||
|
||||
import com.aida.lanecarford.common.constant.MinioFileConstants;
|
||||
import com.aida.lanecarford.config.MinioConfig;
|
||||
import com.aida.lanecarford.service.ImageCompositionService;
|
||||
import com.aida.lanecarford.util.ImageCompositionUtil;
|
||||
import com.aida.lanecarford.util.MinioUtil;
|
||||
@@ -23,6 +24,7 @@ public class ImageCompositionServiceImpl implements ImageCompositionService {
|
||||
|
||||
private final ImageCompositionUtil imageCompositionUtil;
|
||||
private final MinioUtil minioUtil;
|
||||
private final MinioConfig minioConfig;
|
||||
|
||||
|
||||
|
||||
@@ -66,7 +68,8 @@ public class ImageCompositionServiceImpl implements ImageCompositionService {
|
||||
String logicalUrl = minioUtil.uploadBytes(
|
||||
composedImageBytes,
|
||||
MinioFileConstants.FileType.COMPOSED_IMAGE,
|
||||
"image/jpeg"
|
||||
"image/jpeg",
|
||||
minioConfig.getBucketName()
|
||||
);
|
||||
|
||||
log.info("图片合成并上传成功,逻辑URL: {}", logicalUrl);
|
||||
@@ -77,4 +80,4 @@ public class ImageCompositionServiceImpl implements ImageCompositionService {
|
||||
throw new RuntimeException("图片合成并上传失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,19 +2,14 @@ package com.aida.lanecarford.service.impl;
|
||||
|
||||
import cn.hutool.json.JSONObject;
|
||||
import com.aida.lanecarford.common.CommonConstant;
|
||||
import com.aida.lanecarford.config.MinioConfig;
|
||||
import com.aida.lanecarford.common.response.ResultEnum;
|
||||
import com.aida.lanecarford.common.constant.MinioFileConstants;
|
||||
import com.aida.lanecarford.entity.CustomerPhoto;
|
||||
import com.aida.lanecarford.entity.ModelPhoto;
|
||||
import com.aida.lanecarford.entity.Style;
|
||||
import com.aida.lanecarford.entity.TryOnEffect;
|
||||
import com.aida.lanecarford.entity.*;
|
||||
import com.aida.lanecarford.exception.BusinessException;
|
||||
import com.aida.lanecarford.mapper.CustomerMapper;
|
||||
import com.aida.lanecarford.mapper.TryOnEffectMapper;
|
||||
import com.aida.lanecarford.service.CustomerPhotoService;
|
||||
import com.aida.lanecarford.service.ImageCompositionService;
|
||||
import com.aida.lanecarford.service.ModelPhotoService;
|
||||
import com.aida.lanecarford.service.StyleService;
|
||||
import com.aida.lanecarford.service.TryOnEffectService;
|
||||
import com.aida.lanecarford.service.*;
|
||||
import com.aida.lanecarford.util.MinioUtil;
|
||||
import com.aida.lanecarford.vo.TryOnResultVo;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
@@ -25,6 +20,9 @@ import com.google.auth.oauth2.GoogleCredentials;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import okhttp3.*;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -40,23 +38,29 @@ import java.util.concurrent.TimeUnit;
|
||||
*
|
||||
* @author AI Assistant
|
||||
* @since 2024-01-01
|
||||
/**
|
||||
* 试穿效果服务实现类
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class TryOnEffectServiceImpl extends ServiceImpl<TryOnEffectMapper, TryOnEffect> implements TryOnEffectService {
|
||||
private static final Logger log = LoggerFactory.getLogger(TryOnEffectServiceImpl.class);
|
||||
private final StyleService styleService;
|
||||
private final ModelPhotoService modelPhotoService;
|
||||
private final CustomerPhotoService customerPhotoService;
|
||||
private final ImageCompositionService imageCompositionService;
|
||||
|
||||
private final CustomerMapper customerMapper;
|
||||
|
||||
private final MinioUtil minioUtil;
|
||||
private final MinioConfig minioConfig;
|
||||
|
||||
@Override
|
||||
public TryOnResultVo generateTryOnEffect(TryOnEffect tryOnEffectDto) {
|
||||
Integer isRegenerated = tryOnEffectDto.getIsRegenerated();
|
||||
String toAIlogicalUrl = null;
|
||||
String prompt = null;
|
||||
Style style = null;
|
||||
// 收集图片URL
|
||||
List<String> imageUrls = new ArrayList<>();
|
||||
if (isRegenerated == 1) {
|
||||
@@ -86,59 +90,44 @@ public class TryOnEffectServiceImpl extends ServiceImpl<TryOnEffectMapper, TryOn
|
||||
throw BusinessException.parameterRequired("styleId");
|
||||
}
|
||||
//根据id查到对应styleurl
|
||||
Style style = styleService.getById(styleId);
|
||||
style = styleService.getById(styleId);
|
||||
String styleImageUrl = style.getStyleImageUrl();
|
||||
if (styleImageUrl != null && !styleImageUrl.trim().isEmpty()) {
|
||||
imageUrls.add(styleImageUrl);
|
||||
}
|
||||
|
||||
Long modelPhotoId = tryOnEffectDto.getModelPhotoId();
|
||||
if (modelPhotoId != null) {
|
||||
//根据id查到对应modelurl
|
||||
ModelPhoto modelPhoto = modelPhotoService.getById(modelPhotoId);
|
||||
String modelPhotoUrl = modelPhoto.getPhotoUrl();
|
||||
if (modelPhotoUrl != null && !modelPhotoUrl.trim().isEmpty()) {
|
||||
imageUrls.add(modelPhotoUrl);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 合成图片
|
||||
if (!imageUrls.isEmpty() && imageUrls.size() > 1) {
|
||||
log.info("开始合成图片,图片数量: {}", imageUrls.size());
|
||||
try {
|
||||
// 将逻辑URL批量转换为预签名URL,以便图像合成服务可以访问
|
||||
List<String> presignedUrls = minioUtil.convertToPresignedUrls(imageUrls, CommonConstant.MINIO_IMAGE_EXPIRE_TIME);
|
||||
log.debug("批量转换逻辑URL为预签名URL,数量: {}", presignedUrls.size());
|
||||
|
||||
toAIlogicalUrl = imageCompositionService.composeAndUploadImages(presignedUrls);
|
||||
String aiRreultlogicalUrl = null;
|
||||
if (tryOnEffectDto.getIsRegenerated() == 0 && imageUrls.size() == 1) {
|
||||
Customer customer = customerMapper.selectById(tryOnEffectDto.getCustomerId());
|
||||
prompt = "A full-body, photorealistic professional studio shot of a **young " + customer.getGender() + "**, mid-20s, with **clear facial features and a confident expression**. The model is centered in the frame.They are **standing in a direct, static, full-view pose** on a **clean, light white backdrop** **The entire figure, from head to toe, should be in frame and occupy approximately 80% of the vertical space.**.\n" +
|
||||
"\n" +
|
||||
"**CRITICAL COMPOSITION INSTRUCTION:**\n" +
|
||||
"Generate a single, seamless image of the model with a complete and fully visible head,**wearing ALL distinct items** from the uploaded image. **ALL items MUST be visible and correctly worn in the final outfit.**" +
|
||||
"\n" +
|
||||
"**Placement Detail:** Outerwear must be worn on the outside, and if a bag is present, it must be visible.\n" +
|
||||
"**Quality:** Ultra-high resolution, The figure should fill the frame as much as possible,studio photography quality. Emphasize **realistic fabric textures and sharp print fidelity**.\n" +
|
||||
"**Negative Constraints (Exclude):** **NO text, NO borders, NO tables, NO multiple models, NO extra items.** **CRITICAL: NO cropping of the head or face.**";
|
||||
aiRreultlogicalUrl = AITryOnEffect(prompt, imageUrls);
|
||||
|
||||
log.info("图片合成成功,合成图片URL: {}", toAIlogicalUrl);
|
||||
} catch (Exception e) {
|
||||
log.error("图片合成失败: {}", e.getMessage(), e);
|
||||
throw new RuntimeException("image error " + e.getMessage(), e);
|
||||
}
|
||||
} else if (imageUrls.size() == 1) {
|
||||
toAIlogicalUrl = imageUrls.get(0);
|
||||
} else {
|
||||
log.warn("没有找到有效的图片URL进行合成");
|
||||
throw BusinessException.parameterRequired("image");
|
||||
}else if (tryOnEffectDto.getIsRegenerated() == 1 && imageUrls.size() == 1){
|
||||
//根据提示词修改图像
|
||||
aiRreultlogicalUrl = AITryOnEffect(prompt, imageUrls);
|
||||
}else if (tryOnEffectDto.getIsRegenerated() == 1 && imageUrls.size() > 1){
|
||||
//换脸
|
||||
aiRreultlogicalUrl = callFaceSwapAPI(imageUrls);
|
||||
}
|
||||
//调用模型生成试穿效果
|
||||
log.info("准备调用第三方AI服务,输入图片URL: {}", toAIlogicalUrl);
|
||||
prompt = "A woman is wearing the outfit (an outerwear, a top, pants, shoes, a handbag), white background, full body, professional portrait photography."+prompt;
|
||||
// String AIRreultlogicalUrl = AITryOnEffect(prompt, toAIlogicalUrl);
|
||||
String AIRreultlogicalUrl = "try_on_result/6ecb707a-f541-437a-aef5-9544b5b18164.png";
|
||||
|
||||
tryOnEffectDto.setResultImageUrl(AIRreultlogicalUrl);
|
||||
tryOnEffectDto.setResultImageUrl(aiRreultlogicalUrl);
|
||||
tryOnEffectDto.setGenerationStatus("completed");
|
||||
this.saveOrUpdate(tryOnEffectDto);
|
||||
|
||||
TryOnResultVo tryOnResultVo = new TryOnResultVo();
|
||||
tryOnResultVo.setTryOnId(tryOnEffectDto.getId());
|
||||
|
||||
tryOnResultVo.setTryOnUrl(minioUtil.convertToPresignedUrl(AIRreultlogicalUrl, CommonConstant.MINIO_IMAGE_EXPIRE_TIME));
|
||||
tryOnResultVo.setTryOnUrl(minioUtil.convertToPresignedUrl(aiRreultlogicalUrl, CommonConstant.MINIO_IMAGE_EXPIRE_TIME));
|
||||
|
||||
return tryOnResultVo;
|
||||
}
|
||||
@@ -164,7 +153,7 @@ public class TryOnEffectServiceImpl extends ServiceImpl<TryOnEffectMapper, TryOn
|
||||
styleLambdaQueryWrapper.eq(Style::getId, tryOnEffect.getStyleId()).select(Style::getStyleImageUrl);
|
||||
Style style = styleService.getOne(styleLambdaQueryWrapper);
|
||||
tryOnResultVo.setStyleUrl(minioUtil.convertToPresignedUrl(
|
||||
style.getStyleImageUrl(),
|
||||
style.getStyleImageUrl(),
|
||||
CommonConstant.MINIO_IMAGE_EXPIRE_TIME
|
||||
));
|
||||
}
|
||||
@@ -187,18 +176,18 @@ public class TryOnEffectServiceImpl extends ServiceImpl<TryOnEffectMapper, TryOn
|
||||
tryOnResultVo.setTryOnId(tryOnEffect.getId());
|
||||
// 使用新的API获取预签名URL,数据库存储的是逻辑URL
|
||||
tryOnResultVo.setTryOnUrl(minioUtil.convertToPresignedUrl(
|
||||
tryOnEffect.getResultImageUrl(),
|
||||
CommonConstant.MINIO_IMAGE_EXPIRE_TIME
|
||||
));
|
||||
tryOnEffect.getResultImageUrl(),
|
||||
CommonConstant.MINIO_IMAGE_EXPIRE_TIME
|
||||
));
|
||||
// 如果是原始效果,则获取对应的style图片
|
||||
if (tryOnEffect.getIsRegenerated() == 0) {
|
||||
LambdaQueryWrapper<Style> styleLambdaQueryWrapper = new LambdaQueryWrapper<>();
|
||||
styleLambdaQueryWrapper.eq(Style::getId, tryOnEffect.getStyleId()).select(Style::getStyleImageUrl);
|
||||
Style style = styleService.getOne(styleLambdaQueryWrapper);
|
||||
tryOnResultVo.setStyleUrl(minioUtil.convertToPresignedUrl(
|
||||
style.getStyleImageUrl(),
|
||||
CommonConstant.MINIO_IMAGE_EXPIRE_TIME
|
||||
));
|
||||
style.getStyleImageUrl(),
|
||||
CommonConstant.MINIO_IMAGE_EXPIRE_TIME
|
||||
));
|
||||
}
|
||||
|
||||
tryOnResultVo.setIsRegenerated(tryOnEffect.getIsRegenerated());
|
||||
@@ -243,8 +232,11 @@ public class TryOnEffectServiceImpl extends ServiceImpl<TryOnEffectMapper, TryOn
|
||||
}
|
||||
|
||||
|
||||
public String AITryOnEffect(String prompt, String url) {
|
||||
log.info("开始执行AITryOnEffect - prompt: {}, url: {}", prompt, url);
|
||||
public String AITryOnEffect(String prompt, List<String> urls) {
|
||||
System.setProperty("https.proxyHost", "127.0.0.1");
|
||||
System.setProperty("https.proxyPort", "10809");
|
||||
|
||||
// log.info("开始执行AITryOnEffect - prompt: {}, url: {}", prompt, url);
|
||||
|
||||
// 参数验证
|
||||
if (prompt == null || prompt.trim().isEmpty()) {
|
||||
@@ -252,16 +244,24 @@ public class TryOnEffectServiceImpl extends ServiceImpl<TryOnEffectMapper, TryOn
|
||||
throw new BusinessException("Prompt is required", "prompt不能为空", ResultEnum.PARAMETER_ERROR.getCode());
|
||||
}
|
||||
|
||||
if (url == null || url.trim().isEmpty()) {
|
||||
if (urls.isEmpty()) {
|
||||
log.error("参数验证失败 - url不能为空");
|
||||
throw new BusinessException("URL is required", "url不能为空", ResultEnum.PARAMETER_ERROR.getCode());
|
||||
}
|
||||
|
||||
ArrayList<String> base64Images = new ArrayList<>();
|
||||
for (String url : urls) {
|
||||
if (url == null || url.trim().isEmpty()) {
|
||||
log.error("参数验证失败 - url不能为空");
|
||||
throw new BusinessException("URL is required", "url不能为空", ResultEnum.PARAMETER_ERROR.getCode());
|
||||
}
|
||||
String base64Image = getImageAsBase64(url);
|
||||
base64Images.add(base64Image);
|
||||
}
|
||||
// 获取图片的base64编码
|
||||
String base64Image = getImageAsBase64(url);
|
||||
|
||||
// 调用谷歌API进行图生图
|
||||
String resultImageUrl = callGoogleImageGenerationAPI(prompt, base64Image);
|
||||
String resultImageUrl = callGoogleImageGenerationAPI(prompt, base64Images);
|
||||
|
||||
log.info("AITryOnEffect执行成功,结果URL: {}", resultImageUrl);
|
||||
return resultImageUrl;
|
||||
@@ -289,21 +289,21 @@ public class TryOnEffectServiceImpl extends ServiceImpl<TryOnEffectMapper, TryOn
|
||||
/**
|
||||
* 调用谷歌API进行图像生成
|
||||
*/
|
||||
private String callGoogleImageGenerationAPI(String prompt, String base64Image) {
|
||||
private String callGoogleImageGenerationAPI(String prompt, List<String> base64Images) {
|
||||
try {
|
||||
System.setProperty("https.proxyHost", "127.0.0.1");
|
||||
System.setProperty("https.proxyPort", "10809");
|
||||
// 谷歌API配置
|
||||
String projectId = "aida-461108";
|
||||
String location = "global";
|
||||
String model = "gemini-2.0-flash-exp"; // 使用适合的模型
|
||||
String model = "gemini-2.5-flash-image"; // 使用适合的模型
|
||||
String endpoint = String.format(
|
||||
"https://aiplatform.googleapis.com/v1/projects/%s/locations/%s/publishers/google/models/%s:generateContent",
|
||||
projectId, location, model
|
||||
);
|
||||
|
||||
// 构建请求体
|
||||
JSONObject requestBody = buildRequestBody(prompt, base64Image);
|
||||
JSONObject requestBody = buildRequestBody(prompt, base64Images);
|
||||
|
||||
// 获取访问令牌
|
||||
String accessToken = getGoogleAccessToken();
|
||||
@@ -322,15 +322,29 @@ public class TryOnEffectServiceImpl extends ServiceImpl<TryOnEffectMapper, TryOn
|
||||
/**
|
||||
* 构建谷歌API请求体
|
||||
*/
|
||||
private JSONObject buildRequestBody(String prompt, String base64Image) {
|
||||
private JSONObject buildRequestBody(String prompt, List<String> base64Images) {
|
||||
JSONObject requestBody = new JSONObject();
|
||||
|
||||
// 创建图片部分
|
||||
JSONObject imagePart = new JSONObject();
|
||||
JSONObject inlineData = new JSONObject();
|
||||
inlineData.set("mimeType", "image/png");
|
||||
inlineData.set("data", base64Image.replace("data:image/png;base64,", ""));
|
||||
imagePart.set("inlineData", inlineData);
|
||||
JSONObject imagePart2 = new JSONObject();
|
||||
if (base64Images.size() == 1) {
|
||||
JSONObject inlineData = new JSONObject();
|
||||
inlineData.set("mimeType", "image/png");
|
||||
inlineData.set("data", base64Images.get(0).replace("data:image/png;base64,", ""));
|
||||
imagePart.set("inlineData", inlineData);
|
||||
} else if (base64Images.size() == 2) {
|
||||
JSONObject inlineData = new JSONObject();
|
||||
inlineData.set("mimeType", "image/png");
|
||||
inlineData.set("data", base64Images.get(0));
|
||||
imagePart.set("inlineData", inlineData);
|
||||
|
||||
JSONObject inlineData2 = new JSONObject();
|
||||
inlineData2.set("mimeType", "image/jpeg");
|
||||
inlineData2.set("data", base64Images.get(1));
|
||||
imagePart2.set("inlineData", inlineData2);
|
||||
}
|
||||
|
||||
|
||||
// 创建文本部分
|
||||
JSONObject textPart = new JSONObject();
|
||||
@@ -339,7 +353,11 @@ public class TryOnEffectServiceImpl extends ServiceImpl<TryOnEffectMapper, TryOn
|
||||
// 创建内容对象
|
||||
JSONObject content = new JSONObject();
|
||||
content.set("role", "user");
|
||||
content.set("parts", Arrays.asList(imagePart, textPart));
|
||||
if (base64Images.size() == 1) {
|
||||
content.set("parts", Arrays.asList(imagePart, textPart));
|
||||
} else if (base64Images.size() == 2) {
|
||||
content.set("parts", Arrays.asList(imagePart, imagePart2, textPart));
|
||||
}
|
||||
|
||||
// 设置contents数组
|
||||
requestBody.set("contents", Arrays.asList(content));
|
||||
@@ -411,6 +429,7 @@ public class TryOnEffectServiceImpl extends ServiceImpl<TryOnEffectMapper, TryOn
|
||||
|
||||
try (Response response = client.newCall(request).execute()) {
|
||||
if (!response.isSuccessful()) {
|
||||
log.info("Google API响应 " + response.body());
|
||||
log.warn("Google API响应失败,状态码: {}", response.code());
|
||||
if (attempt < maxRetries) {
|
||||
Thread.sleep(retryDelay * attempt);
|
||||
@@ -486,7 +505,8 @@ public class TryOnEffectServiceImpl extends ServiceImpl<TryOnEffectMapper, TryOn
|
||||
String logicalUrl = minioUtil.uploadBytes(
|
||||
java.util.Base64.getDecoder().decode(base64Data),
|
||||
MinioFileConstants.FileType.TRY_ON_RESULT,
|
||||
"image/png"
|
||||
"image/png",
|
||||
minioConfig.getBucketName()
|
||||
);
|
||||
|
||||
log.info("生成的图片已上传到MinIO,逻辑URL: {}", logicalUrl);
|
||||
@@ -504,4 +524,198 @@ public class TryOnEffectServiceImpl extends ServiceImpl<TryOnEffectMapper, TryOn
|
||||
throw new BusinessException("Response processing failed", "响应处理失败", ResultEnum.ERROR.getCode());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用换脸API
|
||||
* @param imageUrls 图片URL列表,第一个为源图片,第二个为目标图片
|
||||
* @return 换脸后的图片URL
|
||||
*/
|
||||
private String callFaceSwapAPI(List<String> imageUrls) {
|
||||
|
||||
|
||||
try {
|
||||
log.info("开始调用换脸API - 源图片: {}, 目标图片: {}", imageUrls.get(0), imageUrls.get(1));
|
||||
|
||||
// 获取图片的预签名URL
|
||||
String inputFaceUrl = imageUrls.get(0);
|
||||
|
||||
// 构建输入图片列表(从第二张图片开始作为目标图片列表)
|
||||
JSONArray inputImageList = new JSONArray();
|
||||
for (int i = 1; i < imageUrls.size(); i++) {
|
||||
inputImageList.add(imageUrls.get(i));
|
||||
}
|
||||
|
||||
// 构建请求体
|
||||
JSONObject requestBody = new JSONObject();
|
||||
requestBody.put("input_image_list", inputImageList);
|
||||
requestBody.put("input_face", inputFaceUrl);
|
||||
requestBody.put("threshold", 0.2);
|
||||
|
||||
// 调用换脸API
|
||||
String response = sendFaceSwapRequest("http://18.167.251.121:10004/api/v1/reface", requestBody.toString());
|
||||
|
||||
// 处理响应
|
||||
return processFaceSwapResponse(response);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("换脸API调用失败: {}", e.getMessage(), e);
|
||||
throw new BusinessException("Face swap API call failed", "换脸API调用失败: " + e.getMessage(), ResultEnum.ERROR.getCode());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送换脸API请求
|
||||
*/
|
||||
private String sendFaceSwapRequest(String endpoint, String jsonBody) throws IOException {
|
||||
OkHttpClient client = new OkHttpClient.Builder()
|
||||
.connectTimeout(60, TimeUnit.SECONDS)
|
||||
.readTimeout(180, TimeUnit.SECONDS)
|
||||
.writeTimeout(180, TimeUnit.SECONDS)
|
||||
.callTimeout(300, TimeUnit.SECONDS)
|
||||
.retryOnConnectionFailure(true)
|
||||
.build();
|
||||
|
||||
Request request = new Request.Builder()
|
||||
.url(endpoint)
|
||||
.addHeader("Content-Type", "application/json")
|
||||
.addHeader("Accept", "application/json")
|
||||
.addHeader("User-Agent", "LaneCarford-Client/1.0")
|
||||
.post(RequestBody.create(MediaType.parse("application/json"), jsonBody))
|
||||
.build();
|
||||
|
||||
int maxRetries = 3;
|
||||
int retryDelay = 3000; // 3秒
|
||||
|
||||
for (int attempt = 1; attempt <= maxRetries; attempt++) {
|
||||
try {
|
||||
log.info("发起换脸API请求 - 尝试次数: {}/{}, URL: {}", attempt, maxRetries, request.url());
|
||||
|
||||
try (Response response = client.newCall(request).execute()) {
|
||||
if (!response.isSuccessful()) {
|
||||
log.warn("换脸API响应失败,状态码: {}, 响应: {}", response.code(), response.body() != null ? response.body().string() : "null");
|
||||
if (attempt < maxRetries) {
|
||||
Thread.sleep(retryDelay * attempt);
|
||||
continue;
|
||||
} else {
|
||||
throw new IOException("HTTP error code: " + response.code());
|
||||
}
|
||||
}
|
||||
|
||||
String result = response.body().string();
|
||||
log.info("换脸API调用成功");
|
||||
return result;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.warn("换脸API网络连接问题 - 尝试: {}/{}, 错误: {}", attempt, maxRetries, e.getMessage());
|
||||
if (attempt < maxRetries) {
|
||||
try {
|
||||
Thread.sleep(retryDelay * attempt);
|
||||
} catch (InterruptedException ie) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new RuntimeException("重试被中断", ie);
|
||||
}
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new RuntimeException("请求被中断", e);
|
||||
}
|
||||
}
|
||||
|
||||
throw new IOException("所有重试都失败了");
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理换脸API响应
|
||||
*/
|
||||
private String processFaceSwapResponse(String response) {
|
||||
try {
|
||||
log.info("开始处理换脸API响应: {}", response);
|
||||
|
||||
JSONObject jsonResponse = new JSONObject(response);
|
||||
|
||||
// 检查响应状态
|
||||
if (jsonResponse.containsKey("success") && jsonResponse.getBool("success")) {
|
||||
// 成功响应,获取结果图片URL
|
||||
if (jsonResponse.containsKey("result_url")) {
|
||||
String resultUrl = jsonResponse.getStr("result_url");
|
||||
log.info("换脸成功,结果URL: {}", resultUrl);
|
||||
|
||||
// 下载图片并上传到MinIO
|
||||
return downloadAndUploadToMinio(resultUrl);
|
||||
} else if (jsonResponse.containsKey("data") && jsonResponse.getJSONObject("data").containsKey("result_url")) {
|
||||
String resultUrl = jsonResponse.getJSONObject("data").getStr("result_url");
|
||||
log.info("换脸成功,结果URL: {}", resultUrl);
|
||||
|
||||
// 下载图片并上传到MinIO
|
||||
return downloadAndUploadToMinio(resultUrl);
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否有错误信息
|
||||
if (jsonResponse.containsKey("error")) {
|
||||
String error = jsonResponse.getStr("error");
|
||||
log.error("换脸API返回错误: {}", error);
|
||||
throw new BusinessException("Face swap failed", "换脸失败: " + error, ResultEnum.ERROR.getCode());
|
||||
}
|
||||
|
||||
log.error("换脸API响应格式不正确: {}", response);
|
||||
throw new BusinessException("Invalid response format", "响应格式不正确", ResultEnum.ERROR.getCode());
|
||||
|
||||
} catch (BusinessException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
log.error("处理换脸API响应失败: {}", e.getMessage(), e);
|
||||
throw new BusinessException("Response processing failed", "响应处理失败", ResultEnum.ERROR.getCode());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载图片并上传到MinIO
|
||||
*/
|
||||
private String downloadAndUploadToMinio(String imageUrl) {
|
||||
try {
|
||||
log.info("开始下载换脸结果图片: {}", imageUrl);
|
||||
|
||||
// 使用现有的下载方法
|
||||
byte[] imageData = downloadImageFromUrl(imageUrl);
|
||||
|
||||
// 生成对象名称
|
||||
String objectName = MinioFileConstants.TRY_ON_RESULT_DIR + "/" + "faceswap_" + System.currentTimeMillis() + ".jpg";
|
||||
|
||||
// 上传到MinIO
|
||||
String logicalUrl = minioUtil.uploadBytes(imageData, objectName, "image/jpeg", minioConfig.getBucketName());
|
||||
|
||||
log.info("换脸结果图片上传成功,逻辑URL: {}", logicalUrl);
|
||||
return logicalUrl;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("下载并上传换脸结果图片失败: {}", e.getMessage(), e);
|
||||
throw new BusinessException("Failed to download and upload result image", "下载并上传结果图片失败", ResultEnum.ERROR.getCode());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从URL下载图片数据
|
||||
*/
|
||||
private byte[] downloadImageFromUrl(String imageUrl) throws IOException {
|
||||
OkHttpClient client = new OkHttpClient.Builder()
|
||||
.connectTimeout(30, TimeUnit.SECONDS)
|
||||
.readTimeout(60, TimeUnit.SECONDS)
|
||||
.build();
|
||||
|
||||
Request request = new Request.Builder()
|
||||
.url(imageUrl)
|
||||
.addHeader("User-Agent", "LaneCarford-Client/1.0")
|
||||
.build();
|
||||
|
||||
try (Response response = client.newCall(request).execute()) {
|
||||
if (!response.isSuccessful()) {
|
||||
throw new IOException("Failed to download image: HTTP " + response.code());
|
||||
}
|
||||
|
||||
return response.body().bytes();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -88,21 +88,24 @@ public class MinioUtil {
|
||||
|
||||
// ==================== 文件上传操作 ====================
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 上传MultipartFile文件
|
||||
* 上传文件到指定桶
|
||||
*
|
||||
* @param file 文件
|
||||
* @param fileType 文件类型
|
||||
* @return 逻辑URL(不含桶名)
|
||||
* @param file 文件
|
||||
* @param fileType 文件类型
|
||||
* @param bucketName 桶名
|
||||
* @return 文件的逻辑路径(包含桶名)
|
||||
*/
|
||||
public String uploadFile(MultipartFile file, MinioFileConstants.FileType fileType) {
|
||||
public String uploadFile(MultipartFile file, MinioFileConstants.FileType fileType, String bucketName) {
|
||||
if (file == null || file.isEmpty()) {
|
||||
throw new MinioException("文件不能为空");
|
||||
}
|
||||
|
||||
try {
|
||||
// 确保默认桶存在
|
||||
ensureDefaultBucketExists();
|
||||
// 确保桶存在
|
||||
createBucket(bucketName);
|
||||
|
||||
// 生成对象名称
|
||||
String objectName = MinioFileConstants.generateObjectNameByType(fileType);
|
||||
@@ -110,50 +113,53 @@ public class MinioUtil {
|
||||
// 上传文件
|
||||
minioClient.putObject(
|
||||
PutObjectArgs.builder()
|
||||
.bucket(minioConfig.getBucketName())
|
||||
.bucket(bucketName)
|
||||
.object(objectName)
|
||||
.stream(file.getInputStream(), file.getSize(), -1)
|
||||
.contentType(file.getContentType())
|
||||
.build()
|
||||
);
|
||||
|
||||
log.info("文件上传成功: {}", objectName);
|
||||
return objectName; // 返回逻辑URL
|
||||
log.info("文件上传成功: {}/{}", bucketName, objectName);
|
||||
return bucketName + "/" + objectName; // 返回包含桶名的逻辑路径
|
||||
} catch (Exception e) {
|
||||
log.error("文件上传失败: {}", e.getMessage(), e);
|
||||
throw new MinioException("文件上传失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 上传字节数组
|
||||
* 上传字节数组到指定桶
|
||||
*
|
||||
* @param bytes 字节数组
|
||||
* @param objectName 对象名称(逻辑路径)
|
||||
* @param objectName 对象名称
|
||||
* @param contentType 内容类型
|
||||
* @return 逻辑URL(不含桶名)
|
||||
* @param bucketName 桶名
|
||||
* @return 文件的逻辑路径(包含桶名)
|
||||
*/
|
||||
public String uploadBytes(byte[] bytes, String objectName, String contentType) {
|
||||
public String uploadBytes(byte[] bytes, String objectName, String contentType, String bucketName) {
|
||||
if (bytes == null || bytes.length == 0) {
|
||||
throw new MinioException("文件内容不能为空");
|
||||
}
|
||||
|
||||
try {
|
||||
// 确保默认桶存在
|
||||
ensureDefaultBucketExists();
|
||||
// 确保桶存在
|
||||
createBucket(bucketName);
|
||||
|
||||
// 上传文件
|
||||
minioClient.putObject(
|
||||
PutObjectArgs.builder()
|
||||
.bucket(minioConfig.getBucketName())
|
||||
.bucket(bucketName)
|
||||
.object(objectName)
|
||||
.stream(new ByteArrayInputStream(bytes), bytes.length, -1)
|
||||
.contentType(contentType)
|
||||
.build()
|
||||
);
|
||||
|
||||
log.info("字节数组上传成功: {}", objectName);
|
||||
return objectName; // 返回逻辑URL
|
||||
log.info("字节数组上传成功: {}/{}", bucketName, objectName);
|
||||
return bucketName + "/" + objectName; // 返回包含桶名的逻辑路径
|
||||
} catch (Exception e) {
|
||||
log.error("字节数组上传失败: {}", e.getMessage(), e);
|
||||
throw new MinioException("字节数组上传失败", e);
|
||||
@@ -166,11 +172,12 @@ public class MinioUtil {
|
||||
* @param bytes 字节数组
|
||||
* @param fileType 文件类型
|
||||
* @param contentType 内容类型
|
||||
* @return 逻辑URL(不含桶名)
|
||||
* @param bucketName 桶名
|
||||
* @return 逻辑URL(包含桶名)
|
||||
*/
|
||||
public String uploadBytes(byte[] bytes, MinioFileConstants.FileType fileType, String contentType) {
|
||||
public String uploadBytes(byte[] bytes, MinioFileConstants.FileType fileType, String contentType, String bucketName) {
|
||||
String objectName = MinioFileConstants.generateObjectNameByType(fileType);
|
||||
return uploadBytes(bytes, objectName, contentType);
|
||||
return uploadBytes(bytes, objectName, contentType, bucketName);
|
||||
}
|
||||
|
||||
// ==================== 文件下载操作 ====================
|
||||
@@ -178,19 +185,34 @@ public class MinioUtil {
|
||||
/**
|
||||
* 下载文件
|
||||
*
|
||||
* @param objectName 对象名称(逻辑路径)
|
||||
* @param logicalPath 逻辑路径(格式:bucketName/folder/filename)
|
||||
* @return 文件输入流
|
||||
*/
|
||||
public InputStream downloadFile(String objectName) {
|
||||
public InputStream downloadFile(String logicalPath) {
|
||||
// 解析桶名和对象名:bucketName/folder/filename
|
||||
int index = logicalPath.indexOf("/");
|
||||
if (index <= 0) {
|
||||
throw new MinioException("逻辑路径格式错误,应包含桶名: " + logicalPath);
|
||||
}
|
||||
|
||||
String bucketName = logicalPath.substring(0, index);
|
||||
String objectName = logicalPath.substring(index + 1);
|
||||
|
||||
// 验证桶名是否存在
|
||||
try {
|
||||
// 检查文件是否存在
|
||||
if (!doesObjectExist(objectName)) {
|
||||
throw new IOException("文件不存在: " + objectName);
|
||||
boolean bucketExists = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
|
||||
if (!bucketExists) {
|
||||
throw new MinioException("桶不存在: " + bucketName);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("验证桶存在性失败: {}", e.getMessage(), e);
|
||||
throw new MinioException("验证桶存在性失败", e);
|
||||
}
|
||||
|
||||
try {
|
||||
return minioClient.getObject(
|
||||
GetObjectArgs.builder()
|
||||
.bucket(minioConfig.getBucketName())
|
||||
.bucket(bucketName)
|
||||
.object(objectName)
|
||||
.build()
|
||||
);
|
||||
@@ -205,17 +227,37 @@ public class MinioUtil {
|
||||
/**
|
||||
* 删除文件
|
||||
*
|
||||
* @param objectName 对象名称(逻辑路径)
|
||||
* @param logicalPath 逻辑路径(格式:bucketName/folder/filename)
|
||||
*/
|
||||
public void deleteFile(String objectName) {
|
||||
public void deleteFile(String logicalPath) {
|
||||
// 解析桶名和对象名:bucketName/folder/filename
|
||||
int index = logicalPath.indexOf("/");
|
||||
if (index <= 0) {
|
||||
throw new MinioException("逻辑路径格式错误,应包含桶名: " + logicalPath);
|
||||
}
|
||||
|
||||
String bucketName = logicalPath.substring(0, index);
|
||||
String objectName = logicalPath.substring(index + 1);
|
||||
|
||||
// 验证桶名是否存在
|
||||
try {
|
||||
boolean bucketExists = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
|
||||
if (!bucketExists) {
|
||||
throw new MinioException("桶不存在: " + bucketName);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("验证桶存在性失败: {}", e.getMessage(), e);
|
||||
throw new MinioException("验证桶存在性失败", e);
|
||||
}
|
||||
|
||||
try {
|
||||
minioClient.removeObject(
|
||||
RemoveObjectArgs.builder()
|
||||
.bucket(minioConfig.getBucketName())
|
||||
.bucket(bucketName)
|
||||
.object(objectName)
|
||||
.build()
|
||||
);
|
||||
log.info("文件删除成功: {}", objectName);
|
||||
log.info("文件删除成功: {}/{}", bucketName, objectName);
|
||||
} catch (Exception e) {
|
||||
log.error("文件删除失败: {}", e.getMessage(), e);
|
||||
throw new MinioException("文件删除失败", e);
|
||||
@@ -224,25 +266,28 @@ public class MinioUtil {
|
||||
|
||||
// ==================== 文件查询操作 ====================
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 获取文件列表
|
||||
* 获取指定桶的文件列表
|
||||
*
|
||||
* @param prefix 文件前缀
|
||||
* @return 文件列表
|
||||
* @param prefix 文件前缀
|
||||
* @param bucketName 桶名
|
||||
* @return 文件列表(包含桶名)
|
||||
*/
|
||||
public List<String> listFiles(String prefix) {
|
||||
public List<String> listFiles(String prefix, String bucketName) {
|
||||
List<String> files = new ArrayList<>();
|
||||
try {
|
||||
Iterable<Result<Item>> results = minioClient.listObjects(
|
||||
ListObjectsArgs.builder()
|
||||
.bucket(minioConfig.getBucketName())
|
||||
.bucket(bucketName)
|
||||
.prefix(prefix)
|
||||
.build()
|
||||
);
|
||||
|
||||
for (Result<Item> result : results) {
|
||||
Item item = result.get();
|
||||
files.add(item.objectName());
|
||||
files.add(bucketName + "/" + item.objectName()); // 返回包含桶名的路径
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("获取文件列表失败: {}", e.getMessage(), e);
|
||||
@@ -254,14 +299,34 @@ public class MinioUtil {
|
||||
/**
|
||||
* 检查文件是否存在
|
||||
*
|
||||
* @param objectName 对象名称(逻辑路径)
|
||||
* @param logicalPath 逻辑路径(格式:bucketName/folder/filename)
|
||||
* @return 是否存在
|
||||
*/
|
||||
public boolean doesObjectExist(String objectName) {
|
||||
public boolean doesObjectExist(String logicalPath) {
|
||||
// 解析桶名和对象名:bucketName/folder/filename
|
||||
int index = logicalPath.indexOf("/");
|
||||
if (index <= 0) {
|
||||
throw new MinioException("逻辑路径格式错误,应包含桶名: " + logicalPath);
|
||||
}
|
||||
|
||||
String bucketName = logicalPath.substring(0, index);
|
||||
String objectName = logicalPath.substring(index + 1);
|
||||
|
||||
// 验证桶名是否存在
|
||||
try {
|
||||
boolean bucketExists = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
|
||||
if (!bucketExists) {
|
||||
return false; // 桶不存在,文件肯定不存在
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("验证桶存在性失败: {}", e.getMessage(), e);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
minioClient.statObject(
|
||||
StatObjectArgs.builder()
|
||||
.bucket(minioConfig.getBucketName())
|
||||
.bucket(bucketName)
|
||||
.object(objectName)
|
||||
.build()
|
||||
);
|
||||
@@ -278,13 +343,11 @@ public class MinioUtil {
|
||||
*
|
||||
* @param objectName 对象名称(逻辑路径)
|
||||
* @param expires 过期时间(秒)
|
||||
* @param bucketName 桶名
|
||||
* @return 预签名URL
|
||||
*/
|
||||
public String getPresignedUrl(String objectName, int expires, String bucketName) {
|
||||
try {
|
||||
if (bucketName == null){
|
||||
bucketName = minioConfig.getBucketName();
|
||||
}
|
||||
return minioClient.getPresignedObjectUrl(
|
||||
GetPresignedObjectUrlArgs.builder()
|
||||
.method(Method.GET)
|
||||
@@ -319,15 +382,16 @@ public class MinioUtil {
|
||||
/**
|
||||
* 批量获取预签名URL
|
||||
*
|
||||
* @param objectNames 对象名称列表(逻辑路径)
|
||||
* @param expires 过期时间(秒)
|
||||
* @param logicalPaths 逻辑路径列表(格式:bucketName/folder/filename)
|
||||
* @param expires 过期时间(秒)
|
||||
* @return 预签名URL列表
|
||||
*/
|
||||
public List<String> getPresignedUrls(List<String> objectNames, int expires) {
|
||||
public List<String> getPresignedUrls(List<String> logicalPaths, int expires) {
|
||||
List<String> presignedUrls = new ArrayList<>();
|
||||
for (String objectName : objectNames) {
|
||||
if (objectName != null && !objectName.trim().isEmpty()) {
|
||||
presignedUrls.add(getPresignedUrl(objectName, expires, null));
|
||||
for (String logicalPath : logicalPaths) {
|
||||
if (logicalPath != null && !logicalPath.trim().isEmpty()) {
|
||||
// 所有路径都应该包含桶名
|
||||
presignedUrls.add(getPresignedUrl(logicalPath, expires));
|
||||
}
|
||||
}
|
||||
return presignedUrls;
|
||||
@@ -352,13 +416,14 @@ public class MinioUtil {
|
||||
/**
|
||||
* 将MinIO逻辑URL转换为预签名URL
|
||||
*
|
||||
* @param logicalUrl 逻辑URL
|
||||
* @param logicalUrl 逻辑URL(格式:bucketName/folder/filename)
|
||||
* @param expires 过期时间(秒)
|
||||
* @return 预签名URL,如果不是逻辑URL则返回原URL
|
||||
*/
|
||||
public String convertToPresignedUrl(String logicalUrl, int expires) {
|
||||
if (isMinioLogicalUrl(logicalUrl)) {
|
||||
return getPresignedUrl(logicalUrl, expires, null);
|
||||
// 所有逻辑URL都应该包含桶名
|
||||
return getPresignedUrl(logicalUrl, expires);
|
||||
}
|
||||
return logicalUrl;
|
||||
}
|
||||
@@ -383,17 +448,17 @@ public class MinioUtil {
|
||||
/**
|
||||
* 获取压缩后的图片Base64编码
|
||||
*
|
||||
* @param objectName 对象名称(逻辑路径)
|
||||
* @param logicalPath 逻辑路径(可以包含桶名或不包含)
|
||||
* @param targetWidth 目标宽度
|
||||
* @param targetHeight 目标高度
|
||||
* @return 压缩后的图片Base64编码
|
||||
*/
|
||||
public String getCompressedImageAsBase64(String objectName, int targetWidth, int targetHeight) throws IOException {
|
||||
try (InputStream stream = downloadFile(objectName)) {
|
||||
public String getCompressedImageAsBase64(String logicalPath, int targetWidth, int targetHeight) throws IOException {
|
||||
try (InputStream stream = downloadFile(logicalPath)) {
|
||||
// 读取原始图片
|
||||
BufferedImage originalImage = ImageIO.read(stream);
|
||||
if (originalImage == null) {
|
||||
throw new IOException("无法读取图片: " + objectName);
|
||||
throw new IOException("无法读取图片: " + logicalPath);
|
||||
}
|
||||
|
||||
// 计算压缩比例,保持宽高比
|
||||
@@ -425,7 +490,7 @@ public class MinioUtil {
|
||||
ImageIO.write(compressedImage, "JPEG", baos);
|
||||
byte[] imageBytes = baos.toByteArray();
|
||||
|
||||
log.info("图片压缩完成: {} -> {}x{} (原始: {}x{})", objectName, newWidth, newHeight, originalWidth, originalHeight);
|
||||
log.info("图片压缩完成: {} -> {}x{} (原始: {}x{})", logicalPath, newWidth, newHeight, originalWidth, originalHeight);
|
||||
return Base64.getEncoder().encodeToString(imageBytes);
|
||||
|
||||
} catch (Exception e) {
|
||||
@@ -436,26 +501,8 @@ public class MinioUtil {
|
||||
|
||||
// ==================== 私有辅助方法 ====================
|
||||
|
||||
/**
|
||||
* 确保默认桶存在
|
||||
*/
|
||||
private void ensureDefaultBucketExists() {
|
||||
createBucket(minioConfig.getBucketName());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated 使用 uploadBytes(byte[], String, String) 替代
|
||||
*/
|
||||
@Deprecated
|
||||
public String uploadBytes(byte[] bytes, String fileName, String contentType, String bucketName) {
|
||||
log.warn("使用了已废弃的方法,建议使用新的API");
|
||||
if (!bucketName.equals(minioConfig.getBucketName())) {
|
||||
log.warn("指定的桶名 {} 与配置的桶名 {} 不一致,将使用配置的桶名", bucketName, minioConfig.getBucketName());
|
||||
}
|
||||
return uploadBytes(bytes, fileName, contentType);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated 使用 getPresignedUrl(String, int) 替代
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user