Merge remote-tracking branch 'origin/dev-ltx' into dev/3.1_release_merge
# Conflicts: # src/main/java/com/ai/da/service/impl/UserLikeGroupServiceImpl.java
This commit is contained in:
8
aida.iml
8
aida.iml
@@ -1,8 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<module version="4">
|
|
||||||
<component name="FacetManager">
|
|
||||||
<facet type="Spring" name="Spring">
|
|
||||||
<configuration />
|
|
||||||
</facet>
|
|
||||||
</component>
|
|
||||||
</module>
|
|
||||||
@@ -98,4 +98,6 @@ public interface GenerateService extends IService<Generate> {
|
|||||||
byte[] downloadVideoOrImage(String url);
|
byte[] downloadVideoOrImage(String url);
|
||||||
|
|
||||||
String createGoogleAsyncTask(GenerateThroughImageTextDTO generateDTO, String useModel, String prompt);
|
String createGoogleAsyncTask(GenerateThroughImageTextDTO generateDTO, String useModel, String prompt);
|
||||||
|
|
||||||
|
String toProductAsyncTask(String imagePath, String useModel, String prompt);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -244,7 +244,9 @@ public class CollectionElementServiceImpl extends ServiceImpl<CollectionElementM
|
|||||||
collectionElementMapper.deleteBatchIds(ids);
|
collectionElementMapper.deleteBatchIds(ids);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 该方法已不再使用 */
|
/**
|
||||||
|
* 该方法已不再使用
|
||||||
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
@Override
|
@Override
|
||||||
public GenerateCollectionItemVO generatePrint(CollectionGeneratePrintDTO generatePrintDTO) {
|
public GenerateCollectionItemVO generatePrint(CollectionGeneratePrintDTO generatePrintDTO) {
|
||||||
@@ -555,11 +557,17 @@ public class CollectionElementServiceImpl extends ServiceImpl<CollectionElementM
|
|||||||
while (sketchIterator.hasNext()) {
|
while (sketchIterator.hasNext()) {
|
||||||
CollectionSketchDTO sketchBoard = sketchIterator.next();
|
CollectionSketchDTO sketchBoard = sketchIterator.next();
|
||||||
String level2Type = sketchBoard.getLevel2Type();
|
String level2Type = sketchBoard.getLevel2Type();
|
||||||
|
String level3Type = "";
|
||||||
|
if ("Collection".equals(sketchBoard.getDesignType())) {
|
||||||
|
level3Type = collectionElementMapper.selectOne(new LambdaQueryWrapper<CollectionElement>().eq(CollectionElement::getId, sketchBoard.getSketchBoardId()).select(CollectionElement::getLevel3Type)).getLevel3Type();
|
||||||
|
} else if ("Library".equals(sketchBoard.getDesignType())){
|
||||||
|
level3Type = libraryService.getById(sketchBoard.getSketchBoardId()).getLevel3Type();
|
||||||
|
}
|
||||||
//判断性别和当前project性别是否一致,不一致则移除
|
//判断性别和当前project性别是否一致,不一致则移除
|
||||||
String level3Type = collectionElementMapper.selectOne(new LambdaQueryWrapper<CollectionElement>().eq(CollectionElement::getId, sketchBoard.getSketchBoardId()).select(CollectionElement::getLevel3Type)).getLevel3Type();
|
|
||||||
if (!level3Type.equalsIgnoreCase(designDTO.getModelSex())) {
|
if (!level3Type.equalsIgnoreCase(designDTO.getModelSex())) {
|
||||||
sketchIterator.remove();
|
sketchIterator.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
//再次判断草图板数量,如果为null,则跳出当前if
|
//再次判断草图板数量,如果为null,则跳出当前if
|
||||||
if (CollectionUtil.isNotEmpty(designDTO.getSketchBoards())) {
|
if (CollectionUtil.isNotEmpty(designDTO.getSketchBoards())) {
|
||||||
|
|||||||
@@ -619,6 +619,9 @@ public class GenerateServiceImpl extends ServiceImpl<GenerateMapper, Generate> i
|
|||||||
}
|
}
|
||||||
|
|
||||||
String modelName = generateDTO.getModelName();
|
String modelName = generateDTO.getModelName();
|
||||||
|
if (StringUtil.isNullOrEmpty(modelName)){
|
||||||
|
return handleStandardGeneration(generateDTO);
|
||||||
|
}
|
||||||
|
|
||||||
HashMap<String, String> modelAndPromptMap = chooseModelAndPrompt(generateDTO, modelName);
|
HashMap<String, String> modelAndPromptMap = chooseModelAndPrompt(generateDTO, modelName);
|
||||||
String useModel = modelAndPromptMap.get(ModelConstants.USE_MODEL);
|
String useModel = modelAndPromptMap.get(ModelConstants.USE_MODEL);
|
||||||
@@ -671,6 +674,160 @@ public class GenerateServiceImpl extends ServiceImpl<GenerateMapper, Generate> i
|
|||||||
// processCreditDeduction(generateDTO.getUserId(), taskId, CreditsEventsEnum.WX_TEXT2IMG);
|
// processCreditDeduction(generateDTO.getUserId(), taskId, CreditsEventsEnum.WX_TEXT2IMG);
|
||||||
return new PrepareForGenerateVO(Collections.singletonList(taskId), 200);
|
return new PrepareForGenerateVO(Collections.singletonList(taskId), 200);
|
||||||
}
|
}
|
||||||
|
public String toProductAsyncTask(String imagePath, String useModel, String prompt) {
|
||||||
|
if (StringUtil.isNullOrEmpty(imagePath)||StringUtil.isNullOrEmpty(useModel)||StringUtil.isNullOrEmpty(prompt)){
|
||||||
|
throw new BusinessException("Parameter Exception");
|
||||||
|
}
|
||||||
|
AuthPrincipalVo userHolder = UserContext.getUserHolder();
|
||||||
|
Long userId = userHolder.getId();
|
||||||
|
|
||||||
|
String uuid = UUID.randomUUID().toString();
|
||||||
|
// 生成唯一的任务ID
|
||||||
|
String taskId = uuid + "-" + userId;
|
||||||
|
String finalImagePath = null;
|
||||||
|
try {
|
||||||
|
finalImagePath = addWhiteBackground(imagePath);
|
||||||
|
//去掉"data:image/png;base64,"
|
||||||
|
finalImagePath = finalImagePath.replace("data:image/png;base64,", "");
|
||||||
|
// 如果白色背景处理失败或不需要,直接获取原图的base64编码
|
||||||
|
if (StringUtil.isNullOrEmpty(finalImagePath)) {
|
||||||
|
finalImagePath = minioUtil.getImageAsBase64(imagePath);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("Error getting image as base64 taskId: {} ", taskId, e);
|
||||||
|
throw new BusinessException("Parameter Exception");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化Redis中的任务状态为"Executing"
|
||||||
|
String key = generateResultKey + ":" + taskId;
|
||||||
|
// 异步处理token获取和API调用
|
||||||
|
String projectId = "aida-461108";
|
||||||
|
String location = "global";
|
||||||
|
String endpoint = String.format(
|
||||||
|
"https://aiplatform.googleapis.com/v1/projects/%s/locations/%s/publishers/google/models/%s:generateContent",
|
||||||
|
projectId, location, ModelConstants.NANO_BANANA
|
||||||
|
);
|
||||||
|
|
||||||
|
JSONObject requestBody = new JSONObject();
|
||||||
|
// 使用 gemini-2.5-flash-image-preview 模型时的请求体格式
|
||||||
|
// 创建图片部分
|
||||||
|
JSONObject imagePart = new JSONObject();
|
||||||
|
JSONObject inlineData = new JSONObject();
|
||||||
|
inlineData.set("mimeType", "image/png");
|
||||||
|
inlineData.set("data", finalImagePath);
|
||||||
|
imagePart.set("inlineData", inlineData);
|
||||||
|
|
||||||
|
// 创建文本部分
|
||||||
|
JSONObject textPart = new JSONObject();
|
||||||
|
textPart.set("text", prompt);
|
||||||
|
|
||||||
|
// 创建内容对象
|
||||||
|
JSONObject content = new JSONObject();
|
||||||
|
content.set("role", "user");
|
||||||
|
content.set("parts", Arrays.asList(imagePart, textPart));
|
||||||
|
|
||||||
|
// 设置 contents 数组
|
||||||
|
requestBody.set("contents", Arrays.asList(content));
|
||||||
|
|
||||||
|
// 设置 generationConfig
|
||||||
|
JSONObject generationConfig = new JSONObject();
|
||||||
|
// generationConfig.set("temperature", 1);
|
||||||
|
generationConfig.set("maxOutputTokens", 8192);
|
||||||
|
generationConfig.set("responseModalities", Arrays.asList("TEXT", "IMAGE"));
|
||||||
|
// generationConfig.set("topP", 0.95);
|
||||||
|
JSONObject imageConfig = new JSONObject();
|
||||||
|
imageConfig.set("aspectRatio", "9:16");
|
||||||
|
generationConfig.set("imageConfig", imageConfig);
|
||||||
|
requestBody.set("generationConfig", generationConfig);
|
||||||
|
String jsonBody = requestBody.toString();
|
||||||
|
log.info("Google 请求入参:{}", jsonBody);
|
||||||
|
|
||||||
|
GenerateResultVO resultVO = new GenerateResultVO(taskId, null, null, "Pending");
|
||||||
|
redisUtil.addToString(key, new Gson().toJson(resultVO), CommonConstant.GENERATE_RESULT_EXPIRE_TIME);
|
||||||
|
CompletableFuture.runAsync(() -> {
|
||||||
|
try {
|
||||||
|
// 异步获取token
|
||||||
|
String tokenValue = null;
|
||||||
|
try (InputStream inputStream = GenerateServiceImpl.class.getClassLoader()
|
||||||
|
.getResourceAsStream("aida-461108-b4afaabebb84.json")) {
|
||||||
|
|
||||||
|
GoogleCredentials credentials = GoogleCredentials
|
||||||
|
.fromStream(inputStream)
|
||||||
|
.createScoped(Collections.singletonList("https://www.googleapis.com/auth/cloud-platform"));
|
||||||
|
|
||||||
|
credentials.refreshIfExpired();
|
||||||
|
tokenValue = credentials.getAccessToken().getTokenValue();
|
||||||
|
}
|
||||||
|
if (tokenValue == null) {
|
||||||
|
throw new RuntimeException("google token error");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 异步发送API请求
|
||||||
|
OkHttpClient client = new OkHttpClient.Builder()
|
||||||
|
.connectTimeout(30, TimeUnit.SECONDS) // 连接超时时间
|
||||||
|
.readTimeout(60, TimeUnit.SECONDS) // 读取超时时间
|
||||||
|
.writeTimeout(60, TimeUnit.SECONDS) // 写入超时时间
|
||||||
|
.build();
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.url(endpoint)
|
||||||
|
.addHeader("Authorization", "Bearer " + tokenValue)
|
||||||
|
.addHeader("Content-Type", "application/json")
|
||||||
|
.post(RequestBody.create(MediaType.parse("application/json"), jsonBody))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
try (Response response = client.newCall(request).execute()) {
|
||||||
|
String result = response.body().string();
|
||||||
|
|
||||||
|
log.info("Google 响应结果:{}", result);
|
||||||
|
com.alibaba.fastjson.JSONObject jsonResponse = JSON.parseObject(result);
|
||||||
|
|
||||||
|
String base64Data = null;
|
||||||
|
|
||||||
|
//根据模型类别按照api取出结果
|
||||||
|
if (ModelConstants.NANO_BANANA.equals(useModel)) {
|
||||||
|
JSONArray candidates = jsonResponse.getJSONArray("candidates");
|
||||||
|
|
||||||
|
if (candidates != null && !candidates.isEmpty()) {
|
||||||
|
com.alibaba.fastjson.JSONObject candidate = candidates.getJSONObject(0);
|
||||||
|
com.alibaba.fastjson.JSONObject contentResult = candidate.getJSONObject("content");
|
||||||
|
JSONArray parts = contentResult.getJSONArray("parts");
|
||||||
|
|
||||||
|
// 遍历parts数组找到包含inlineData的对象
|
||||||
|
for (int i = 0; i < parts.size(); i++) {
|
||||||
|
com.alibaba.fastjson.JSONObject part = parts.getJSONObject(i);
|
||||||
|
if (part.containsKey("inlineData")) {
|
||||||
|
com.alibaba.fastjson.JSONObject inlineDataResult= part.getJSONObject("inlineData");
|
||||||
|
base64Data = inlineDataResult.getString("data");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (base64Data != null && !base64Data.isEmpty()) {
|
||||||
|
String resultPath = userId + "/product_image" + "/" + uuid;
|
||||||
|
String minioPath = minioUtil.base64UploadToPath("data:image/png;base64," + base64Data, userBucket, resultPath);
|
||||||
|
// 生成成功,更新Redis状态和URL
|
||||||
|
GenerateResultVO successResultVO = new GenerateResultVO(taskId, null, minioPath, "Success");
|
||||||
|
redisUtil.addToString(key, new Gson().toJson(successResultVO), CommonConstant.GENERATE_RESULT_EXPIRE_TIME);
|
||||||
|
} else {
|
||||||
|
// 没有找到图像数据或数据为空,标记为失败
|
||||||
|
log.warn("Google generation response does not contain valid image data for taskId: {}", taskId);
|
||||||
|
GenerateResultVO failResultVO = new GenerateResultVO(taskId, null, null, "Fail");
|
||||||
|
redisUtil.addToString(key, new Gson().toJson(failResultVO), CommonConstant.GENERATE_RESULT_EXPIRE_TIME);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Google generation failed for taskId: {}", taskId, e);
|
||||||
|
// 生成失败,更新Redis状态
|
||||||
|
GenerateResultVO failResultVO = new GenerateResultVO(taskId, null, null, "Fail");
|
||||||
|
redisUtil.addToString(key, new Gson().toJson(failResultVO), CommonConstant.GENERATE_RESULT_EXPIRE_TIME);
|
||||||
|
}
|
||||||
|
}, asyncTaskExecutor);
|
||||||
|
return taskId;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public String createGoogleAsyncTask(GenerateThroughImageTextDTO generateDTO, String useModel, String prompt) {
|
public String createGoogleAsyncTask(GenerateThroughImageTextDTO generateDTO, String useModel, String prompt) {
|
||||||
// 从 resources 加载 JSON 文件
|
// 从 resources 加载 JSON 文件
|
||||||
@@ -978,7 +1135,7 @@ public class GenerateServiceImpl extends ServiceImpl<GenerateMapper, Generate> i
|
|||||||
&& StringUtil.isNullOrEmpty(generateDTO.getLevel2Type())) {
|
&& StringUtil.isNullOrEmpty(generateDTO.getLevel2Type())) {
|
||||||
throw new BusinessException("level2Type.cannot.be.empty");
|
throw new BusinessException("level2Type.cannot.be.empty");
|
||||||
}
|
}
|
||||||
if (generateDTO.getLevel1Type().equals(PRINT_BOARD.getRealName())) {
|
if (generateDTO.getLevel1Type().equals(PRINT_BOARD.getRealName())&&generateDTO.getLevel2Type().equals(CreditsEventsEnum.PATTERN.getName())) {
|
||||||
int firstCommaIndex = generateDTO.getText().indexOf(",");
|
int firstCommaIndex = generateDTO.getText().indexOf(",");
|
||||||
String style = generateDTO.getText().substring(0, firstCommaIndex).trim();
|
String style = generateDTO.getText().substring(0, firstCommaIndex).trim();
|
||||||
//如果style不等于painting style,illustration style,real style中的一种
|
//如果style不等于painting style,illustration style,real style中的一种
|
||||||
@@ -1060,7 +1217,7 @@ public class GenerateServiceImpl extends ServiceImpl<GenerateMapper, Generate> i
|
|||||||
throw new RuntimeException(e.getMessage());
|
throw new RuntimeException(e.getMessage());
|
||||||
}
|
}
|
||||||
String taskId = result.getOutput().getTaskId();
|
String taskId = result.getOutput().getTaskId();
|
||||||
log.info("wx text2image 请求生成:{}, taskId:{}", JsonUtils.toJson(result), taskId);
|
log.info("qwen text2image 请求生成:{}, taskId:{}", JsonUtils.toJson(result), taskId);
|
||||||
|
|
||||||
Generate generate = new Generate(userId, taskId, level1Type, level2Type, prompt, "text(" + gender + ")", "qwen-image", new Date());
|
Generate generate = new Generate(userId, taskId, level1Type, level2Type, prompt, "text(" + gender + ")", "qwen-image", new Date());
|
||||||
save(generate);
|
save(generate);
|
||||||
@@ -3486,59 +3643,85 @@ public class GenerateServiceImpl extends ServiceImpl<GenerateMapper, Generate> i
|
|||||||
/**
|
/**
|
||||||
* 接入flux模型,用于imageToSketch(sketch extract) || relighting || to product image
|
* 接入flux模型,用于imageToSketch(sketch extract) || relighting || to product image
|
||||||
*
|
*
|
||||||
* @param func 功能枚举名
|
* @param func 功能枚举名,指定使用flux模型的具体功能类型
|
||||||
* @param prompt 用户输入
|
* @param prompt 用户输入的提示词,如果为空则使用默认提示词
|
||||||
* @param imagePath 图片minio路径
|
* @param imagePath 图片minio路径,作为输入图像的base64编码源
|
||||||
|
* @param childStyle 是否为儿童风格,影响提示词的构建
|
||||||
* @return 返回taskId,用于异步获取结果
|
* @return 返回taskId,用于异步获取结果
|
||||||
*/
|
*/
|
||||||
public String flux(CreditsEventsEnum func, String prompt, String imagePath, boolean childStyle) {
|
public String flux(CreditsEventsEnum func, String prompt, String imagePath, boolean childStyle) {
|
||||||
|
// Flux API的请求地址
|
||||||
String fluxRequestUrl = "https://api.bfl.ai/v1/flux-kontext-pro";
|
String fluxRequestUrl = "https://api.bfl.ai/v1/flux-kontext-pro";
|
||||||
|
|
||||||
|
// 如果用户没有提供提示词,根据功能类型设置默认提示词
|
||||||
if (StringUtil.isNullOrEmpty(prompt)) {
|
if (StringUtil.isNullOrEmpty(prompt)) {
|
||||||
switch (func) {
|
switch (func) {
|
||||||
case RELIGHT_FLUX:
|
case RELIGHT_FLUX:
|
||||||
|
// 重新打光功能的默认提示词
|
||||||
prompt = "a model standing on the beautiful beach, ultra high quality, 8k";
|
prompt = "a model standing on the beautiful beach, ultra high quality, 8k";
|
||||||
break;
|
break;
|
||||||
case IMAGE_TO_SKETCH_FLUX:
|
case IMAGE_TO_SKETCH_FLUX:
|
||||||
|
// 图片转线稿功能的默认提示词
|
||||||
prompt = "generate the sketch of the image, simple line, ultra high quality";
|
prompt = "generate the sketch of the image, simple line, ultra high quality";
|
||||||
break;
|
break;
|
||||||
case TO_PRODUCT_IMAGE_ADVANCED:
|
case TO_PRODUCT_IMAGE_ADVANCED:
|
||||||
|
// 转产品图功能的默认提示词
|
||||||
prompt = "change the image to real style, ultra high quality, 8k";
|
prompt = "change the image to real style, ultra high quality, 8k";
|
||||||
|
// 如果是儿童风格,添加儿童面部特征描述
|
||||||
if (childStyle) prompt = prompt + ", Children's face";
|
if (childStyle) prompt = prompt + ", Children's face";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// 如果用户提供了提示词,先进行提示词优化处理
|
||||||
prompt = modifyPrompt(prompt, null, func.getName(), null);
|
prompt = modifyPrompt(prompt, null, func.getName(), null);
|
||||||
|
// 根据不同功能类型,为提示词添加特定前缀
|
||||||
switch (func) {
|
switch (func) {
|
||||||
case PATTERN:
|
case PATTERN:
|
||||||
|
// 图案生成功能,添加图案前缀
|
||||||
prompt = "pattern image, " + prompt;
|
prompt = "pattern image, " + prompt;
|
||||||
break;
|
break;
|
||||||
case SKETCH_BOARD:
|
case SKETCH_BOARD:
|
||||||
|
// 线稿板功能,添加线稿描述
|
||||||
prompt = "a single item of sketch of " + prompt + ", clean white background, simple lines";
|
prompt = "a single item of sketch of " + prompt + ", clean white background, simple lines";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 构建Flux API请求体
|
||||||
JSONObject requestBody = new JSONObject();
|
JSONObject requestBody = new JSONObject();
|
||||||
|
|
||||||
|
// 设置生成提示词
|
||||||
requestBody.set("prompt", prompt);
|
requestBody.set("prompt", prompt);
|
||||||
|
// 设置随机种子,确保结果的可重现性
|
||||||
requestBody.set("seed", 42);
|
requestBody.set("seed", 42);
|
||||||
|
// 根据功能类型设置图片宽高比
|
||||||
if (func.equals(PATTERN)) {
|
if (func.equals(PATTERN)) {
|
||||||
|
// 图案生成使用正方形比例
|
||||||
requestBody.set("aspect_ratio", "1:1");
|
requestBody.set("aspect_ratio", "1:1");
|
||||||
} else {
|
} else {
|
||||||
|
// 其他功能使用竖屏比例
|
||||||
requestBody.set("aspect_ratio", "9:16");
|
requestBody.set("aspect_ratio", "9:16");
|
||||||
}
|
}
|
||||||
|
// 设置输出格式为PNG
|
||||||
requestBody.set("output_format", "png");
|
requestBody.set("output_format", "png");
|
||||||
|
|
||||||
log.info("flux 请求入参:{}", requestBody);
|
log.info("flux 请求入参:{}", requestBody);
|
||||||
|
// 提示词不能为空的校验
|
||||||
if (prompt.isEmpty()) throw new BusinessException("test");
|
if (prompt.isEmpty()) throw new BusinessException("test");
|
||||||
|
|
||||||
|
// 如果提供了输入图片路径,需要将图片转换为base64格式
|
||||||
if (!StringUtil.isNullOrEmpty(imagePath)) {
|
if (!StringUtil.isNullOrEmpty(imagePath)) {
|
||||||
try {
|
try {
|
||||||
String imageAsBase64 = null;
|
String imageAsBase64 = null;
|
||||||
|
// 对于转产品图功能,先添加白色背景处理
|
||||||
if (func.equals(TO_PRODUCT_IMAGE_ADVANCED)) {
|
if (func.equals(TO_PRODUCT_IMAGE_ADVANCED)) {
|
||||||
imageAsBase64 = addWhiteBackground(imagePath);
|
imageAsBase64 = addWhiteBackground(imagePath);
|
||||||
}
|
}
|
||||||
|
// 如果白色背景处理失败或不需要,直接获取原图的base64编码
|
||||||
if (StringUtil.isNullOrEmpty(imageAsBase64)) {
|
if (StringUtil.isNullOrEmpty(imageAsBase64)) {
|
||||||
imageAsBase64 = minioUtil.getImageAsBase64(imagePath);
|
imageAsBase64 = minioUtil.getImageAsBase64(imagePath);
|
||||||
}
|
}
|
||||||
|
// 将base64编码的图片添加到请求体中
|
||||||
requestBody.set("input_image", imageAsBase64);
|
requestBody.set("input_image", imageAsBase64);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
log.error("获取图片的base64格式失败,{}", String.valueOf(e));
|
log.error("获取图片的base64格式失败,{}", String.valueOf(e));
|
||||||
@@ -3546,22 +3729,30 @@ public class GenerateServiceImpl extends ServiceImpl<GenerateMapper, Generate> i
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 发送POST请求到Flux API
|
||||||
String resp = sendRequestUtil.sendFluxPost(fluxRequestUrl, requestBody.toString());
|
String resp = sendRequestUtil.sendFluxPost(fluxRequestUrl, requestBody.toString());
|
||||||
JSONObject respObj = JSONUtil.parseObj(resp);
|
JSONObject respObj = JSONUtil.parseObj(resp);
|
||||||
log.info("flux 发起生成请求返回结果: {}", respObj);
|
log.info("flux 发起生成请求返回结果: {}", respObj);
|
||||||
|
|
||||||
|
// 从响应中提取任务ID
|
||||||
String taskId = respObj.getStr("id");
|
String taskId = respObj.getStr("id");
|
||||||
if (StringUtil.isNullOrEmpty(taskId)) {
|
if (StringUtil.isNullOrEmpty(taskId)) {
|
||||||
|
// 任务创建失败,记录错误信息并抛出异常
|
||||||
requestBody.set("input_image", imagePath);
|
requestBody.set("input_image", imagePath);
|
||||||
log.error("flux生成任务创建失败,func :{}, requestBody:{}", func.getName(), requestBody);
|
log.error("flux生成任务创建失败,func :{}, requestBody:{}", func.getName(), requestBody);
|
||||||
throw new BusinessException("Failed to generate task. Please retry later.");
|
throw new BusinessException("Failed to generate task. Please retry later.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取轮询URL,用于后续查询任务状态
|
||||||
String pollingUrl = respObj.getStr("polling_url");
|
String pollingUrl = respObj.getStr("polling_url");
|
||||||
String key = RedisUtil.FLUX_POLLING_URL + taskId;
|
String key = RedisUtil.FLUX_POLLING_URL + taskId;
|
||||||
|
// 将轮询URL存储到Redis中,设置过期时间
|
||||||
redisUtil.addToString(key, pollingUrl, CommonConstant.GENERATE_RESULT_EXPIRE_TIME);
|
redisUtil.addToString(key, pollingUrl, CommonConstant.GENERATE_RESULT_EXPIRE_TIME);
|
||||||
// 添加到api_generate表中,以便之后对结果查询做补偿
|
|
||||||
|
// 添加到api_generate表中,以便之后对结果查询做补偿机制
|
||||||
apiGenerateService.addAPIGenerateRecordAsync(UserContext.getUserHolder().getId(), taskId, func.getName(), "flux", "Pending");
|
apiGenerateService.addAPIGenerateRecordAsync(UserContext.getUserHolder().getId(), taskId, func.getName(), "flux", "Pending");
|
||||||
|
|
||||||
|
// 返回任务ID,用于异步查询结果
|
||||||
return taskId;
|
return taskId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -157,6 +157,8 @@ public class UserLikeGroupServiceImpl extends ServiceImpl<UserLikeGroupMapper, U
|
|||||||
private ExportFileMapper exportFileMapper;
|
private ExportFileMapper exportFileMapper;
|
||||||
@Resource
|
@Resource
|
||||||
private DesignItemDetailCanvasMapper designItemDetailCanvasMapper;
|
private DesignItemDetailCanvasMapper designItemDetailCanvasMapper;
|
||||||
|
@Value("${redis.key.generateResult}")
|
||||||
|
private String generateResultKey;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void deleteUserGroup(Long userGroupId) {
|
public void deleteUserGroup(Long userGroupId) {
|
||||||
@@ -402,22 +404,38 @@ public class UserLikeGroupServiceImpl extends ServiceImpl<UserLikeGroupMapper, U
|
|||||||
return exportFile.getId();
|
return exportFile.getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 产品图像生成方法
|
||||||
|
* 根据输入的设计元素和参数,生成对应的产品图像
|
||||||
|
*
|
||||||
|
* @param toProductImageDTO 产品图像生成请求参数
|
||||||
|
* @return 生成的产品图像结果列表
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public List<ToProductImageResultVO> toProduct(ToProductImageDTO toProductImageDTO) {
|
public List<ToProductImageResultVO> toProduct(ToProductImageDTO toProductImageDTO) {
|
||||||
// 判断用户当前积分是否够本次生成消耗
|
// 判断是否使用(高级模型)
|
||||||
boolean nanoBananaTask = !StringUtil.isNullOrEmpty(toProductImageDTO.getModelName())
|
boolean advanced = !StringUtil.isNullOrEmpty(toProductImageDTO.getModelName())
|
||||||
&& toProductImageDTO.getModelName().equals(ModelConstants.ADVANCED)||toProductImageDTO.getModelName().equals(ModelConstants.HIGH);
|
&& toProductImageDTO.getModelName().equals(ModelConstants.ADVANCED);
|
||||||
CreditsEventsEnum creditsEventsEnum = nanoBananaTask ? CreditsEventsEnum.TO_PRODUCT_IMAGE_ADVANCED : CreditsEventsEnum.TO_PRODUCT_IMAGE;
|
|
||||||
|
// 根据模型类型选择对应的积分消耗事件
|
||||||
|
CreditsEventsEnum creditsEventsEnum = advanced ? CreditsEventsEnum.TO_PRODUCT_IMAGE_ADVANCED : CreditsEventsEnum.TO_PRODUCT_IMAGE;
|
||||||
|
|
||||||
|
// 预扣除积分,检查用户积分是否足够本次生成消耗
|
||||||
Boolean preDeduction = creditsService.creditsPreDeduction(creditsEventsEnum, toProductImageDTO.getToProductImageVOList().size());
|
Boolean preDeduction = creditsService.creditsPreDeduction(creditsEventsEnum, toProductImageDTO.getToProductImageVOList().size());
|
||||||
if (!preDeduction) {
|
if (!preDeduction) {
|
||||||
throw new BusinessException("remaining.credits.insufficient", ResultEnum.WARNING.getCode());
|
throw new BusinessException("remaining.credits.insufficient", ResultEnum.WARNING.getCode());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取当前登录用户信息
|
||||||
AuthPrincipalVo userHolder = UserContext.getUserHolder();
|
AuthPrincipalVo userHolder = UserContext.getUserHolder();
|
||||||
|
|
||||||
|
// 获取项目ID并查找对应的用户喜欢组
|
||||||
Long projectId = toProductImageDTO.getProjectId();
|
Long projectId = toProductImageDTO.getProjectId();
|
||||||
UserLikeGroup userLikeGroup = getByProjectId(projectId);
|
UserLikeGroup userLikeGroup = getByProjectId(projectId);
|
||||||
Long userLikeGroupId = null;
|
Long userLikeGroupId = null;
|
||||||
|
|
||||||
|
// 创建产品图像记录对象,用于记录本次生成任务的基本信息
|
||||||
ToProductImageRecord toProductImageRecord = new ToProductImageRecord();
|
ToProductImageRecord toProductImageRecord = new ToProductImageRecord();
|
||||||
toProductImageRecord.setProjectId(projectId);
|
toProductImageRecord.setProjectId(projectId);
|
||||||
if (Objects.nonNull(userLikeGroup)) {
|
if (Objects.nonNull(userLikeGroup)) {
|
||||||
@@ -425,219 +443,237 @@ public class UserLikeGroupServiceImpl extends ServiceImpl<UserLikeGroupMapper, U
|
|||||||
toProductImageRecord.setUserLikeGroupId(userLikeGroupId);
|
toProductImageRecord.setUserLikeGroupId(userLikeGroupId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 翻译
|
// 处理用户输入的提示词
|
||||||
String prompt = toProductImageDTO.getPrompt();
|
String prompt = toProductImageDTO.getPrompt();
|
||||||
|
String advancedPrompt;
|
||||||
|
//nanobanana模型支持中文,未保证语义,使用这个模型的不翻译
|
||||||
|
if (StringUtil.isNullOrEmpty(prompt)) {
|
||||||
|
advancedPrompt = "Transform this image into a realistic, studio-quality photograph."
|
||||||
|
+ "Pay attention to the size of the garment, the print, and the fabric texture. the white background. 8K, HDR, DOF, soft lighting" +
|
||||||
|
", high detail, high quality";
|
||||||
|
} else {
|
||||||
|
advancedPrompt = prompt + "Pay attention to the size of the garment, the print, and the fabric texture. the white background. 8K, HDR, DOF, soft lighting, high detail, high quality";
|
||||||
|
}
|
||||||
|
// 初始化基础提示词,确保生成高质量的真实图像
|
||||||
StringBuilder sb = new StringBuilder("The best quality, masterpiece, real image.");
|
StringBuilder sb = new StringBuilder("The best quality, masterpiece, real image.");
|
||||||
if (!StringUtil.isNullOrEmpty(prompt)) {
|
if (!StringUtil.isNullOrEmpty(prompt)) {
|
||||||
|
// 调用Python服务进行提示词翻译(中文转英文等)
|
||||||
prompt = pythonService.promptTranslate(prompt);
|
prompt = pythonService.promptTranslate(prompt);
|
||||||
|
|
||||||
|
// 检查翻译后的提示词长度,限制在180个单词以内
|
||||||
String[] words = prompt.split("\\s+");
|
String[] words = prompt.split("\\s+");
|
||||||
if (words.length > 180) {
|
if (words.length > 180) {
|
||||||
throw new BusinessException("Please keep your input text under 200 words. Thanks!");
|
throw new BusinessException("Please keep your input text under 200 words. Thanks!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 设置记录创建时间并保存原始提示词
|
||||||
toProductImageRecord.setCreateTime(LocalDateTime.now());
|
toProductImageRecord.setCreateTime(LocalDateTime.now());
|
||||||
if (!StringUtils.isEmpty(toProductImageDTO.getPrompt())) {
|
if (!StringUtils.isEmpty(toProductImageDTO.getPrompt())) {
|
||||||
toProductImageRecord.setPrompt(toProductImageDTO.getPrompt());
|
toProductImageRecord.setPrompt(toProductImageDTO.getPrompt());
|
||||||
}
|
}
|
||||||
|
// 将产品图像记录插入数据库
|
||||||
toProductImageRecordMapper.insert(toProductImageRecord);
|
toProductImageRecordMapper.insert(toProductImageRecord);
|
||||||
|
|
||||||
|
// 初始化结果列表
|
||||||
List<ToProductImageResultVO> result = new ArrayList<>();
|
List<ToProductImageResultVO> result = new ArrayList<>();
|
||||||
|
// 判断是否为儿童年龄组,用于后续生成不同的提示词
|
||||||
boolean childFlag = !StringUtil.isNullOrEmpty(toProductImageDTO.getAgeGroup())
|
boolean childFlag = !StringUtil.isNullOrEmpty(toProductImageDTO.getAgeGroup())
|
||||||
&& toProductImageDTO.getAgeGroup().equals("Child");
|
&& toProductImageDTO.getAgeGroup().equals("Child");
|
||||||
|
// 循环计数器,用于生成唯一的任务ID
|
||||||
int i = 0;
|
int i = 0;
|
||||||
|
|
||||||
// else {
|
// else {
|
||||||
// s = "best quality, masterpiece. detailed, high-res, simple background, studio photography, extremely detailed, updo, detailed face, face, close-up, HDR, UHD, 8K realistic, Highly detailed, simple background, Studio lighting";
|
// s = "best quality, masterpiece. detailed, high-res, simple background, studio photography, extremely detailed, updo, detailed face, face, close-up, HDR, UHD, 8K realistic, Highly detailed, simple background, Studio lighting";
|
||||||
// }
|
// }
|
||||||
|
// 遍历所有需要生成的产品图像元素
|
||||||
for (ToProductImageVO toProductImageVO : toProductImageDTO.getToProductImageVOList()) {
|
for (ToProductImageVO toProductImageVO : toProductImageDTO.getToProductImageVOList()) {
|
||||||
String taskId;
|
String taskId;
|
||||||
|
// 处理设计服装类型的元素
|
||||||
if (toProductImageVO.getElementType().equals("DesignOutfit")) {
|
if (toProductImageVO.getElementType().equals("DesignOutfit")) {
|
||||||
|
// 生成唯一的任务ID,包含UUID、序号和用户ID
|
||||||
taskId = UUID.randomUUID() + "-" + i + "-" + userHolder.getId();
|
taskId = UUID.randomUUID() + "-" + i + "-" + userHolder.getId();
|
||||||
|
// 根据元素ID查询设计Python服装信息
|
||||||
TDesignPythonOutfit tDesignPythonOutfit = designPythonOutfitMapper.selectById(toProductImageVO.getElementId());
|
TDesignPythonOutfit tDesignPythonOutfit = designPythonOutfitMapper.selectById(toProductImageVO.getElementId());
|
||||||
|
|
||||||
|
// 获取设计项目ID并查询相关的设计项目详情
|
||||||
Long designItemId = tDesignPythonOutfit.getDesignItemId();
|
Long designItemId = tDesignPythonOutfit.getDesignItemId();
|
||||||
QueryWrapper<DesignItemDetail> designItemDetailQueryWrapper = new QueryWrapper<>();
|
QueryWrapper<DesignItemDetail> designItemDetailQueryWrapper = new QueryWrapper<>();
|
||||||
designItemDetailQueryWrapper.lambda().eq(DesignItemDetail::getDesignItemId, designItemId);
|
designItemDetailQueryWrapper.lambda().eq(DesignItemDetail::getDesignItemId, designItemId);
|
||||||
designItemDetailQueryWrapper.lambda().ne(DesignItemDetail::getType, "Body");
|
designItemDetailQueryWrapper.lambda().ne(DesignItemDetail::getType, "Body"); // 排除身体部分
|
||||||
List<DesignItemDetail> designItemDetails = designItemDetailService.list(designItemDetailQueryWrapper);
|
List<DesignItemDetail> designItemDetails = designItemDetailService.list(designItemDetailQueryWrapper);
|
||||||
|
// 将设计项目的类型拼接成字符串(如:Tops,Bottoms)
|
||||||
String collect = designItemDetails.stream().map(DesignItemDetail::getType).collect(Collectors.joining(","));
|
String collect = designItemDetails.stream().map(DesignItemDetail::getType).collect(Collectors.joining(","));
|
||||||
|
|
||||||
|
// 获取设计ID并查询设计信息
|
||||||
Long designId = tDesignPythonOutfit.getDesignId();
|
Long designId = tDesignPythonOutfit.getDesignId();
|
||||||
Design design = designMapper.selectById(designId);
|
Design design = designMapper.selectById(designId);
|
||||||
|
// 默认产品类型为整体
|
||||||
String productType = "overall";
|
String productType = "overall";
|
||||||
|
// 根据设计的单品/整体属性决定产品类型和提示词构建方式
|
||||||
if (design.getSingleOverall().equals("single")) {
|
if (design.getSingleOverall().equals("single")) {
|
||||||
|
// 单品设计:直接使用服装类型
|
||||||
productType = "single";
|
productType = "single";
|
||||||
sb.append(collect);
|
sb.append(collect);
|
||||||
} else {
|
} else {
|
||||||
|
// 整体设计:根据服装类型和年龄组添加人物描述
|
||||||
if (collect.contains("Tops") && childFlag) {
|
if (collect.contains("Tops") && childFlag) {
|
||||||
sb.append("a handsome boy,");
|
sb.append("a handsome boy,"); // 儿童上装 - 男孩
|
||||||
} else if (collect.contains("Tops") && !childFlag) {
|
} else if (collect.contains("Tops") && !childFlag) {
|
||||||
sb.append("a handsome man,");
|
sb.append("a handsome man,"); // 成人上装 - 男性
|
||||||
} else if (childFlag) {
|
} else if (childFlag) {
|
||||||
sb.append("a beautiful girl,");
|
sb.append("a beautiful girl,"); // 儿童其他 - 女孩
|
||||||
} else {
|
} else {
|
||||||
sb.append("a beautiful woman,");
|
sb.append("a beautiful woman,"); // 成人其他 - 女性
|
||||||
}
|
}
|
||||||
// sb.append("wearing ").append(collect);
|
// sb.append("wearing ").append(collect);
|
||||||
}
|
}
|
||||||
|
// 根据是否有自定义提示词来完善最终的提示词
|
||||||
if (StringUtils.isEmpty(prompt)) {
|
if (StringUtils.isEmpty(prompt)) {
|
||||||
|
// 无自定义提示词:使用默认的高质量服装细节描述
|
||||||
sb.append(",high quality clothing details,8K realistic,HDR");
|
sb.append(",high quality clothing details,8K realistic,HDR");
|
||||||
} else {
|
} else {
|
||||||
|
// 有自定义提示词:结合用户提示词和高质量描述
|
||||||
sb.append(",high quality clothing details,").append(prompt).append(",8K realistic,HDR");
|
sb.append(",high quality clothing details,").append(prompt).append(",8K realistic,HDR");
|
||||||
}
|
}
|
||||||
|
// 创建产品图像结果对象
|
||||||
ToProductImageResultVO toProductImageResult = new ToProductImageResultVO();
|
ToProductImageResultVO toProductImageResult = new ToProductImageResultVO();
|
||||||
if (nanoBananaTask){
|
// 根据是否使用flux模型选择不同的生成方式
|
||||||
|
if (advanced) {
|
||||||
|
// 使用flux高级模型
|
||||||
if (childFlag) {
|
if (childFlag) {
|
||||||
sb.append(", Children's face");
|
sb.append(", Children's face"); // 儿童面部特征
|
||||||
}
|
}
|
||||||
// 使用Google nanobanana模型
|
taskId = generateService.toProductAsyncTask(tDesignPythonOutfit.getDesignUrl(), ModelConstants.NANO_BANANA, advancedPrompt);
|
||||||
GenerateThroughImageTextDTO generateDTO = new GenerateThroughImageTextDTO();
|
toProductImageResult.setModelName(toProductImageDTO.getModelName());
|
||||||
generateDTO.setUserId(userHolder.getId());
|
|
||||||
generateDTO.setLevel1Type("Product");
|
|
||||||
generateDTO.setLevel2Type("ToProduct");
|
|
||||||
generateDTO.setGender("unisex");
|
|
||||||
generateDTO.setCollectionElementId(tDesignPythonOutfit.getId());
|
|
||||||
generateDTO.setDesignType("DesignOutfit");
|
|
||||||
try {
|
|
||||||
//使用nano banana用这个提示词
|
|
||||||
String nanoPrompt = "Change the input image to the real image. Full length portrait, match the clothing size, clothing style, " +
|
|
||||||
"patterns, and fabric texture from the reference image, replicate garment design details precisely, " +
|
|
||||||
"maintain original color scheme, high fidelity to reference attire, realistic lighting, detailed fabric rendering.";
|
|
||||||
taskId = generateService.createGoogleAsyncTask(generateDTO, ModelConstants.NANO_BANANA, nanoPrompt);
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("Google nanobanana generation failed", e);
|
|
||||||
throw new BusinessException("Generation failed, please try again");
|
|
||||||
}
|
|
||||||
toProductImageResult.setModelName("nanobanana");
|
|
||||||
toProductImageResult.setResultType(CollectionType.TO_PRODUCT_IMAGE.getValue());
|
toProductImageResult.setResultType(CollectionType.TO_PRODUCT_IMAGE.getValue());
|
||||||
} else {
|
} else {
|
||||||
// 走模型
|
// 使用本地Python模型
|
||||||
if (childFlag) {
|
if (childFlag) {
|
||||||
sb.append(", (Children's face:1.3)");
|
sb.append(", (Children's face:1.3)"); // 儿童面部特征(权重1.3)
|
||||||
}
|
}
|
||||||
|
// 调用Python服务生成产品图像
|
||||||
pythonService.toProductImage(tDesignPythonOutfit.getDesignUrl(), taskId, sb.toString(), toProductImageDTO.getImageStrength(), productType);
|
pythonService.toProductImage(tDesignPythonOutfit.getDesignUrl(), taskId, sb.toString(), toProductImageDTO.getImageStrength(), productType);
|
||||||
// toProductImageResult.setModelName("local");
|
// toProductImageResult.setModelName("local"); // 注释掉的本地模型标记
|
||||||
}
|
}
|
||||||
|
|
||||||
toProductImageResult.setElementId(tDesignPythonOutfit.getId());
|
// 设置产品图像结果的基本信息
|
||||||
toProductImageResult.setElementType("DesignOutfit");
|
toProductImageResult.setElementId(tDesignPythonOutfit.getId()); // 关联的设计服装ID
|
||||||
toProductImageResult.setCreateTime(LocalDateTime.now());
|
toProductImageResult.setElementType("DesignOutfit"); // 元素类型:设计服装
|
||||||
toProductImageResult.setToProductImageRecordId(toProductImageRecord.getId());
|
toProductImageResult.setCreateTime(LocalDateTime.now()); // 创建时间
|
||||||
// toProductImageResult.setUrl(productImageUrl);
|
toProductImageResult.setToProductImageRecordId(toProductImageRecord.getId()); // 关联的记录ID
|
||||||
toProductImageResult.setIsLike(0);
|
// toProductImageResult.setUrl(productImageUrl); // 生成的图像URL(暂时注释)
|
||||||
toProductImageResult.setTaskId(taskId);
|
toProductImageResult.setIsLike(0); // 默认未点赞
|
||||||
toProductImageResult.setProjectId(projectId);
|
toProductImageResult.setTaskId(taskId); // 任务ID
|
||||||
|
toProductImageResult.setProjectId(projectId); // 项目ID
|
||||||
if (userLikeGroupId != null) {
|
if (userLikeGroupId != null) {
|
||||||
toProductImageResult.setUserLikeGroupId(userLikeGroupId);
|
toProductImageResult.setUserLikeGroupId(userLikeGroupId); // 用户喜欢组ID
|
||||||
}
|
}
|
||||||
toProductImageResult.setImageStrength(toProductImageDTO.getImageStrength());
|
toProductImageResult.setImageStrength(toProductImageDTO.getImageStrength()); // 图像强度
|
||||||
toProductImageResult.setResultType(CollectionType.TO_PRODUCT_IMAGE.getValue());
|
toProductImageResult.setResultType(CollectionType.TO_PRODUCT_IMAGE.getValue()); // 结果类型
|
||||||
toProductImageResult.setStatus("Pending");
|
toProductImageResult.setStatus("Pending"); // 状态:待处理
|
||||||
|
// 将结果插入数据库
|
||||||
toProductImageResultMapper.insert(toProductImageResult);
|
toProductImageResultMapper.insert(toProductImageResult);
|
||||||
// toProductImageResult.setUrl(minioUtil.getPresignedUrl(toProductImageResult.getUrl(), 24 * 60));
|
// toProductImageResult.setUrl(minioUtil.getPresignedUrl(toProductImageResult.getUrl(), 24 * 60));
|
||||||
// 先判断是否需要默认like
|
// 处理默认点赞逻辑
|
||||||
if (Objects.nonNull(toProductImageDTO.getIsDefaultLike()) && toProductImageDTO.getIsDefaultLike()) {
|
if (Objects.nonNull(toProductImageDTO.getIsDefaultLike()) && toProductImageDTO.getIsDefaultLike()) {
|
||||||
// 满足条件情况下默认添加到like
|
// 如果设置了默认点赞,则自动添加到用户喜欢列表
|
||||||
CollectionSort collectionSort = addToProductLike(toProductImageVO.getParentId(), toProductImageResult.getId(), toProductImageDTO.getProjectId());
|
CollectionSort collectionSort = addToProductLike(toProductImageVO.getParentId(), toProductImageResult.getId(), toProductImageDTO.getProjectId());
|
||||||
// 重新排序
|
// 重新排序:根据用户指定的排序位置调整
|
||||||
Integer reSort = collectionSortService.rearrangeChildSort(toProductImageResult.getId(), CollectionType.TO_PRODUCT_IMAGE.getValue(),
|
Integer reSort = collectionSortService.rearrangeChildSort(toProductImageResult.getId(), CollectionType.TO_PRODUCT_IMAGE.getValue(),
|
||||||
toProductImageVO.getParentId(), toProductImageVO.getUserLikeSortId());
|
toProductImageVO.getParentId(), toProductImageVO.getUserLikeSortId());
|
||||||
// 将生成结果的排序返回
|
// 设置排序信息到结果对象中
|
||||||
toProductImageResult.setSort(Objects.isNull(reSort) ? Objects.isNull(collectionSort) ? null : collectionSort.getSort() : reSort);
|
toProductImageResult.setSort(Objects.isNull(reSort) ? Objects.isNull(collectionSort) ? null : collectionSort.getSort() : reSort);
|
||||||
toProductImageResult.setParentId(toProductImageVO.getParentId());
|
toProductImageResult.setParentId(toProductImageVO.getParentId()); // 父级ID
|
||||||
toProductImageResult.setUserLikeSortId(Objects.isNull(collectionSort) ? null : collectionSort.getId());
|
toProductImageResult.setUserLikeSortId(Objects.isNull(collectionSort) ? null : collectionSort.getId()); // 排序记录ID
|
||||||
} else if (Objects.nonNull(toProductImageDTO.getIsDefaultLike()) && Objects.nonNull(toProductImageVO.getParentId())) {
|
} else if (Objects.nonNull(toProductImageDTO.getIsDefaultLike()) && Objects.nonNull(toProductImageVO.getParentId())) {
|
||||||
|
// 不默认点赞但有父级ID的情况
|
||||||
toProductImageResult.setParentId(toProductImageVO.getParentId());
|
toProductImageResult.setParentId(toProductImageVO.getParentId());
|
||||||
// 默认不添加到like,但是需要有parentId,所以这里添加到collectionSort表中
|
// 注释:原本计划添加到collectionSort表中,但当前未实现
|
||||||
// designService.addCollectionSort(toProductImageResult.getId(), CollectionType.TO_PRODUCT_IMAGE.getValue(), toProductImageDTO.getProjectId(), toProductImageVO.getParentId());
|
// designService.addCollectionSort(toProductImageResult.getId(), CollectionType.TO_PRODUCT_IMAGE.getValue(), toProductImageDTO.getProjectId(), toProductImageVO.getParentId());
|
||||||
}
|
}
|
||||||
|
// 将处理完的结果添加到返回列表中
|
||||||
result.add(toProductImageResult);
|
result.add(toProductImageResult);
|
||||||
} else {
|
} else {
|
||||||
|
// 处理ProductElement类型的元素
|
||||||
|
// 根据是否有自定义提示词来构建最终提示词
|
||||||
if (StringUtils.isEmpty(prompt)) {
|
if (StringUtils.isEmpty(prompt)) {
|
||||||
sb.append(",high quality clothing details,8K realistic,HDR");
|
sb.append(",high quality clothing details,8K realistic,HDR");
|
||||||
} else {
|
} else {
|
||||||
sb.append(",high quality clothing details,").append(prompt).append(",8K realistic,HDR");
|
sb.append(",high quality clothing details,").append(prompt).append(",8K realistic,HDR");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 生成唯一的任务ID
|
||||||
taskId = UUID.randomUUID() + "-" + i + "-" + userHolder.getId();
|
taskId = UUID.randomUUID() + "-" + i + "-" + userHolder.getId();
|
||||||
|
// 查询产品元素信息
|
||||||
ToProductElement toProductElement = toProductElementMapper.selectById(toProductImageVO.getElementId());
|
ToProductElement toProductElement = toProductElementMapper.selectById(toProductImageVO.getElementId());
|
||||||
|
// 创建产品图像结果对象
|
||||||
ToProductImageResultVO toProductImageResult = new ToProductImageResultVO();
|
ToProductImageResultVO toProductImageResult = new ToProductImageResultVO();
|
||||||
|
|
||||||
if (nanoBananaTask){
|
// 根据是否使用高级模型选择不同的图像生成方式
|
||||||
// 使用Google nanobanana模型
|
if (advanced) {
|
||||||
GenerateThroughImageTextDTO generateDTO = new GenerateThroughImageTextDTO();
|
// 使用模型生成图像
|
||||||
generateDTO.setUserId(userHolder.getId());
|
taskId = generateService.toProductAsyncTask(toProductElement.getUrl(), ModelConstants.NANO_BANANA, advancedPrompt);
|
||||||
generateDTO.setLevel1Type("Product");
|
toProductImageResult.setModelName(toProductImageDTO.getModelName()); // 设置模型名称
|
||||||
generateDTO.setLevel2Type("ToProduct");
|
toProductImageResult.setResultType(CollectionType.TO_PRODUCT_IMAGE.getValue()); // 设置结果类型
|
||||||
generateDTO.setGender("unisex");
|
|
||||||
generateDTO.setCollectionElementId(toProductElement.getId());
|
|
||||||
generateDTO.setDesignType("ProductElement");
|
|
||||||
try {
|
|
||||||
//使用nano banana用这个提示词
|
|
||||||
String nanoPrompt = "Change the input image to the real image. Full length portrait, match the clothing size, clothing style, " +
|
|
||||||
"patterns, and fabric texture from the reference image, replicate garment design details precisely, " +
|
|
||||||
"maintain original color scheme, high fidelity to reference attire, realistic lighting, detailed fabric rendering.";
|
|
||||||
taskId = generateService.createGoogleAsyncTask(generateDTO, ModelConstants.NANO_BANANA, nanoPrompt);
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("Google nanobanana generation failed", e);
|
|
||||||
throw new BusinessException("Generation failed, please try again");
|
|
||||||
}
|
|
||||||
toProductImageResult.setModelName("nanobanana");
|
|
||||||
toProductImageResult.setResultType(CollectionType.TO_PRODUCT_IMAGE.getValue());
|
|
||||||
} else {
|
} else {
|
||||||
// 走模型
|
// 使用本地Python模型生成图像
|
||||||
if (childFlag) {
|
if (childFlag) {
|
||||||
|
// 如果是儿童年龄组,添加儿童面部特征提示
|
||||||
sb.append(", (Children's face:1.3)");
|
sb.append(", (Children's face:1.3)");
|
||||||
}
|
}
|
||||||
// 走模型
|
// 调用Python服务生成产品图像
|
||||||
pythonService.toProductImage(toProductElement.getUrl(), taskId, sb.toString(), toProductImageDTO.getImageStrength(), "overall");
|
pythonService.toProductImage(toProductElement.getUrl(), taskId, sb.toString(), toProductImageDTO.getImageStrength(), "overall");
|
||||||
// toProductImageResult.setModelName("local");
|
// toProductImageResult.setModelName("local"); // 本地模型名称(已注释)
|
||||||
}
|
}
|
||||||
toProductImageResult.setElementId(toProductElement.getId());
|
// 设置产品图像结果的基本信息
|
||||||
toProductImageResult.setElementType("ProductElement");
|
toProductImageResult.setElementId(toProductElement.getId()); // 关联的产品元素ID
|
||||||
toProductImageResult.setCreateTime(LocalDateTime.now());
|
toProductImageResult.setElementType("ProductElement"); // 元素类型:产品元素
|
||||||
toProductImageResult.setToProductImageRecordId(toProductImageRecord.getId());
|
toProductImageResult.setCreateTime(LocalDateTime.now()); // 创建时间
|
||||||
// toProductImageResult.setUrl(productImageUrl);
|
toProductImageResult.setToProductImageRecordId(toProductImageRecord.getId()); // 关联的记录ID
|
||||||
toProductImageResult.setIsLike(0);
|
// toProductImageResult.setUrl(productImageUrl); // 生成的图像URL(暂时注释)
|
||||||
toProductImageResult.setTaskId(taskId);
|
toProductImageResult.setIsLike(0); // 默认未点赞
|
||||||
toProductImageResult.setProjectId(projectId);
|
toProductImageResult.setTaskId(taskId); // 任务ID
|
||||||
|
toProductImageResult.setProjectId(projectId); // 项目ID
|
||||||
if (userLikeGroupId != null) {
|
if (userLikeGroupId != null) {
|
||||||
toProductImageResult.setUserLikeGroupId(userLikeGroupId);
|
toProductImageResult.setUserLikeGroupId(userLikeGroupId); // 用户喜欢组ID
|
||||||
}
|
}
|
||||||
toProductImageResult.setImageStrength(toProductImageDTO.getImageStrength());
|
toProductImageResult.setImageStrength(toProductImageDTO.getImageStrength()); // 图像强度
|
||||||
toProductImageResult.setResultType(CollectionType.TO_PRODUCT_IMAGE.getValue());
|
toProductImageResult.setResultType(CollectionType.TO_PRODUCT_IMAGE.getValue()); // 结果类型
|
||||||
toProductImageResult.setStatus("Pending");
|
toProductImageResult.setStatus("Pending"); // 状态:待处理
|
||||||
|
// 将结果插入数据库
|
||||||
toProductImageResultMapper.insert(toProductImageResult);
|
toProductImageResultMapper.insert(toProductImageResult);
|
||||||
// toProductImageResult.setUrl(minioUtil.getPresignedUrl(toProductImageResult.getUrl(), 24 * 60));
|
// toProductImageResult.setUrl(minioUtil.getPresignedUrl(toProductImageResult.getUrl(), 24 * 60));
|
||||||
// 先判断是否需要默认like
|
// 处理默认点赞逻辑
|
||||||
if (Objects.nonNull(toProductImageDTO.getIsDefaultLike()) && toProductImageDTO.getIsDefaultLike()) {
|
if (Objects.nonNull(toProductImageDTO.getIsDefaultLike()) && toProductImageDTO.getIsDefaultLike()) {
|
||||||
// 满足条件情况下默认添加到like
|
// 如果设置了默认点赞,则自动添加到用户喜欢列表
|
||||||
CollectionSort collectionSort = addToProductLike(toProductImageVO.getParentId(), toProductImageResult.getId(), toProductImageDTO.getProjectId());
|
CollectionSort collectionSort = addToProductLike(toProductImageVO.getParentId(), toProductImageResult.getId(), toProductImageDTO.getProjectId());
|
||||||
// 重新排序
|
// 重新排序:根据用户指定的排序位置调整
|
||||||
Integer reSort = collectionSortService.rearrangeChildSort(toProductImageResult.getId(), CollectionType.TO_PRODUCT_IMAGE.getValue(),
|
Integer reSort = collectionSortService.rearrangeChildSort(toProductImageResult.getId(), CollectionType.TO_PRODUCT_IMAGE.getValue(),
|
||||||
toProductImageVO.getParentId(), toProductImageVO.getUserLikeSortId());
|
toProductImageVO.getParentId(), toProductImageVO.getUserLikeSortId());
|
||||||
// 将生成结果的排序返回
|
// 设置排序信息到结果对象中
|
||||||
toProductImageResult.setSort(Objects.isNull(reSort) ? Objects.isNull(collectionSort) ? null : collectionSort.getSort() : reSort);
|
toProductImageResult.setSort(Objects.isNull(reSort) ? Objects.isNull(collectionSort) ? null : collectionSort.getSort() : reSort);
|
||||||
toProductImageResult.setParentId(toProductImageVO.getParentId());
|
toProductImageResult.setParentId(toProductImageVO.getParentId()); // 父级ID
|
||||||
toProductImageResult.setUserLikeSortId(Objects.isNull(collectionSort) ? null : collectionSort.getId());
|
toProductImageResult.setUserLikeSortId(Objects.isNull(collectionSort) ? null : collectionSort.getId()); // 排序记录ID
|
||||||
} else if (Objects.nonNull(toProductImageDTO.getIsDefaultLike()) && Objects.nonNull(toProductImageVO.getParentId())) {
|
} else if (Objects.nonNull(toProductImageDTO.getIsDefaultLike()) && Objects.nonNull(toProductImageVO.getParentId())) {
|
||||||
|
// 不默认点赞但有父级ID的情况
|
||||||
toProductImageResult.setParentId(toProductImageVO.getParentId());
|
toProductImageResult.setParentId(toProductImageVO.getParentId());
|
||||||
// 默认不添加到like,但是需要有parentId,所以这里添加到collectionSort表中
|
// 注释:原本计划添加到collectionSort表中,但当前未实现
|
||||||
// designService.addCollectionSort(toProductImageResult.getId(), CollectionType.TO_PRODUCT_IMAGE.getValue(), toProductImageDTO.getProjectId(), toProductImageVO.getParentId());
|
// designService.addCollectionSort(toProductImageResult.getId(), CollectionType.TO_PRODUCT_IMAGE.getValue(), toProductImageDTO.getProjectId(), toProductImageVO.getParentId());
|
||||||
}
|
}
|
||||||
|
// 将处理完的结果添加到返回列表中
|
||||||
result.add(toProductImageResult);
|
result.add(toProductImageResult);
|
||||||
}
|
}
|
||||||
i ++;
|
i++; // 循环计数器递增
|
||||||
sb = new StringBuilder("The best quality, masterpiece, real image.");
|
sb = new StringBuilder("The best quality, masterpiece, real image."); // 重置提示词构建器
|
||||||
// 添加需要扣除的积分到预扣除区
|
// 添加需要扣除的积分到预扣除区
|
||||||
creditsService.addRecordToCreditsDeduction(userHolder.getId(), taskId, creditsEventsEnum);
|
creditsService.addRecordToCreditsDeduction(userHolder.getId(), taskId, creditsEventsEnum);
|
||||||
// 添加积分扣除记录到db
|
// 添加积分扣除记录到数据库
|
||||||
creditsService.preInsert(userHolder.getId(), creditsEventsEnum.getName(), taskId, Boolean.TRUE, null);
|
creditsService.preInsert(userHolder.getId(), creditsEventsEnum.getName(), taskId, Boolean.TRUE, null);
|
||||||
}
|
}
|
||||||
// 更新项目更新时间
|
// 更新项目更新时间,记录最后一次操作时间
|
||||||
projectService.modifyProjectUpdateTime(toProductImageDTO.getProjectId());
|
projectService.modifyProjectUpdateTime(toProductImageDTO.getProjectId());
|
||||||
|
// 返回生成的产品图像结果列表
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -757,71 +793,108 @@ public class UserLikeGroupServiceImpl extends ServiceImpl<UserLikeGroupMapper, U
|
|||||||
projectService.modifyProjectUpdateTime(productImageLikeDTO.getProjectId());
|
projectService.modifyProjectUpdateTime(productImageLikeDTO.getProjectId());
|
||||||
return collectionSort;
|
return collectionSort;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private RedisUtil redisUtil;
|
private RedisUtil redisUtil;
|
||||||
@Value("${redis.key.toProductImageResultKey}")
|
@Value("${redis.key.toProductImageResultKey}")
|
||||||
private String toProductImageResultKey;
|
private String toProductImageResultKey;
|
||||||
@Value("${redis.key.relightResultKey}")
|
@Value("${redis.key.relightResultKey}")
|
||||||
private String relightResultKey;
|
private String relightResultKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取产品图像生成结果列表
|
||||||
|
* 根据任务ID列表查询对应的产品图像生成结果,支持flux模型和传统Python模型两种处理方式
|
||||||
|
*
|
||||||
|
* @param taskIdList 任务ID列表,用于查询对应的产品图像生成结果
|
||||||
|
* @return 返回MagicToolResultVO列表,包含图像生成结果的详细信息(URL、状态、元素信息等)
|
||||||
|
* @throws BusinessException 当源图像不存在或任务不存在时抛出业务异常
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public List<MagicToolResultVO> getToProductImageResultList(List<String> taskIdList) {
|
public List<MagicToolResultVO> getToProductImageResultList(List<String> taskIdList) {
|
||||||
|
// 初始化结果列表和状态收集集合
|
||||||
List<MagicToolResultVO> results = new ArrayList<>();
|
List<MagicToolResultVO> results = new ArrayList<>();
|
||||||
Set<String> collect = new HashSet<>();
|
Set<String> collect = new HashSet<>();
|
||||||
|
|
||||||
|
// 遍历每个任务ID,处理对应的产品图像生成结果
|
||||||
taskIdList.forEach(taskId -> {
|
taskIdList.forEach(taskId -> {
|
||||||
// 查记录
|
// 根据任务ID查询产品图像生成结果记录
|
||||||
QueryWrapper<ToProductImageResult> qw = new QueryWrapper<>();
|
QueryWrapper<ToProductImageResult> qw = new QueryWrapper<>();
|
||||||
qw.lambda().eq(ToProductImageResult::getTaskId, taskId);
|
qw.lambda().eq(ToProductImageResult::getTaskId, taskId);
|
||||||
ToProductImageResult toProductImageResult = toProductImageResultMapper.selectOne(qw);
|
ToProductImageResult toProductImageResult = toProductImageResultMapper.selectOne(qw);
|
||||||
if (Objects.isNull(toProductImageResult)) {
|
if (Objects.isNull(toProductImageResult)) {
|
||||||
throw new BusinessException("source.image.does.not.exist");
|
throw new BusinessException("source.image.does.not.exist");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 查询产品图像记录,获取提示词等信息
|
||||||
ToProductImageRecord toProductImageRecord = toProductImageRecordMapper.selectById(toProductImageResult.getToProductImageRecordId());
|
ToProductImageRecord toProductImageRecord = toProductImageRecordMapper.selectById(toProductImageResult.getToProductImageRecordId());
|
||||||
if (Objects.isNull(toProductImageRecord)) {
|
if (Objects.isNull(toProductImageRecord)) {
|
||||||
throw new BusinessException("This task does not exist.");
|
throw new BusinessException("This task does not exist.");
|
||||||
}
|
}
|
||||||
// 判断当任务从哪个模型获取结果
|
|
||||||
if (!StringUtil.isNullOrEmpty(toProductImageResult.getModelName()) && toProductImageResult.getModelName().equals("flux")){
|
if (!StringUtil.isNullOrEmpty(toProductImageResult.getModelName()) && toProductImageResult.getModelName().equals(ModelConstants.ADVANCED)) {
|
||||||
|
// 获取项目信息,用于构建文件路径和积分扣除
|
||||||
Project project = projectService.getById(toProductImageResult.getProjectId());
|
Project project = projectService.getById(toProductImageResult.getProjectId());
|
||||||
if (Objects.isNull(project)) {
|
if (Objects.isNull(project)) {
|
||||||
throw new BusinessException("unknown project");
|
throw new BusinessException("unknown project");
|
||||||
}
|
}
|
||||||
String fluxResult;
|
|
||||||
|
// 获取flux模型生成结果
|
||||||
|
String redisResult;
|
||||||
if (toProductImageResult.getStatus().equals("Success") && !StringUtil.isNullOrEmpty(toProductImageResult.getUrl())) {
|
if (toProductImageResult.getStatus().equals("Success") && !StringUtil.isNullOrEmpty(toProductImageResult.getUrl())) {
|
||||||
fluxResult = toProductImageResult.getUrl();
|
// 如果已经有成功的结果URL,直接使用
|
||||||
|
redisResult = toProductImageResult.getUrl();
|
||||||
} else {
|
} else {
|
||||||
String objectName = project.getAccountId() + "/product_image/" + taskId + ".png";
|
String key = generateResultKey + ":" + taskId;
|
||||||
fluxResult = generateService.getFluxResult(taskId, objectName);
|
redisResult = redisUtil.getFromString(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (StringUtil.isNullOrEmpty(fluxResult)){
|
GenerateResultVO cachedResult = new Gson().fromJson(redisResult, GenerateResultVO.class);
|
||||||
|
|
||||||
|
|
||||||
|
// 根据flux结果状态进行不同处理
|
||||||
|
if (StringUtil.isNullOrEmpty(redisResult)) {
|
||||||
|
// 如果结果为空,标记为失败状态
|
||||||
toProductImageResult.setStatus("Fail");
|
toProductImageResult.setStatus("Fail");
|
||||||
toProductImageResultMapper.updateById(toProductImageResult);
|
toProductImageResultMapper.updateById(toProductImageResult);
|
||||||
|
collectionSortMapper.delete(new QueryWrapper<CollectionSort>().lambda().eq(CollectionSort::getRelationId, toProductImageResult.getId()));
|
||||||
results.add(new MagicToolResultVO(taskId, "Fail"));
|
results.add(new MagicToolResultVO(taskId, "Fail"));
|
||||||
} else if (fluxResult.equals("Fail") || fluxResult.equals("Pending")) {
|
} else if ("Pending".equals(cachedResult.getStatus())||"Fail".equals(cachedResult.getStatus())) {
|
||||||
toProductImageResult.setStatus(fluxResult);
|
// 如果结果为失败或待处理状态,更新状态并返回对应结果
|
||||||
|
toProductImageResult.setStatus(cachedResult.getStatus());
|
||||||
toProductImageResultMapper.updateById(toProductImageResult);
|
toProductImageResultMapper.updateById(toProductImageResult);
|
||||||
results.add(new MagicToolResultVO(taskId, fluxResult));
|
if (cachedResult.getStatus().equals("Fail")){
|
||||||
|
collectionSortMapper.delete(new QueryWrapper<CollectionSort>().lambda().eq(CollectionSort::getRelationId, toProductImageResult.getId()));
|
||||||
|
}
|
||||||
|
results.add(new MagicToolResultVO(taskId, cachedResult.getStatus()));
|
||||||
} else {
|
} else {
|
||||||
results.add(processFluxResult(fluxResult, toProductImageResult, taskId, toProductImageRecord.getPrompt()));
|
// 如果结果成功,处理flux结果并构建返回对象
|
||||||
// 扣积分
|
results.add(processNanoBananaResult(cachedResult.getUrl(), toProductImageResult, taskId, toProductImageRecord.getPrompt()));
|
||||||
|
// 执行积分扣除操作
|
||||||
Boolean flag = creditsService.taskCreditsDeduction(project.getAccountId(), taskId);
|
Boolean flag = creditsService.taskCreditsDeduction(project.getAccountId(), taskId);
|
||||||
if (flag) creditsService.updateChangedCredits(String.valueOf(project.getAccountId()), taskId);
|
if (flag) creditsService.updateChangedCredits(String.valueOf(project.getAccountId()), taskId);
|
||||||
}
|
}
|
||||||
// 将积分暂扣区的积分移除
|
|
||||||
|
// 如果任务失败,将积分预扣除记录移除
|
||||||
if (toProductImageResult.getStatus().equals("Fail")) {
|
if (toProductImageResult.getStatus().equals("Fail")) {
|
||||||
creditsService.deleteCreditsDeduction(project.getAccountId(), taskId);
|
creditsService.deleteCreditsDeduction(project.getAccountId(), taskId);
|
||||||
}
|
}
|
||||||
// 在这个stream中不再继续往后执行
|
|
||||||
|
// flux模型处理完成,跳过后续的Redis缓存处理逻辑
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 非flux模型的处理逻辑:从Redis缓存获取结果
|
||||||
String key = toProductImageResultKey + ":" + taskId;
|
String key = toProductImageResultKey + ":" + taskId;
|
||||||
MagicToolResultVO magicToolResultVO = new Gson().fromJson(redisUtil.getFromString(key), MagicToolResultVO.class);
|
MagicToolResultVO magicToolResultVO = new Gson().fromJson(redisUtil.getFromString(key), MagicToolResultVO.class);
|
||||||
|
|
||||||
|
// 如果从Redis获取到结果且URL不为空,则构建返回对象
|
||||||
if (!Objects.isNull(magicToolResultVO) && !StringUtil.isNullOrEmpty(magicToolResultVO.getUrl())) {
|
if (!Objects.isNull(magicToolResultVO) && !StringUtil.isNullOrEmpty(magicToolResultVO.getUrl())) {
|
||||||
String url = magicToolResultVO.getUrl();
|
String url = magicToolResultVO.getUrl();
|
||||||
|
// 检查是否为无效图片(白色图片表示生成失败)
|
||||||
if (url.substring(url.lastIndexOf("/") + 1).equals("white_image.jpg")) {
|
if (url.substring(url.lastIndexOf("/") + 1).equals("white_image.jpg")) {
|
||||||
magicToolResultVO.setStatus("Invalid");
|
magicToolResultVO.setStatus("Invalid");
|
||||||
} else {
|
} else {
|
||||||
|
// 设置预签名URL和相关属性
|
||||||
magicToolResultVO.setUrl(minioUtil.getPreSignedUrl(url, CommonConstant.MINIO_IMAGE_EXPIRE_TIME));
|
magicToolResultVO.setUrl(minioUtil.getPreSignedUrl(url, CommonConstant.MINIO_IMAGE_EXPIRE_TIME));
|
||||||
magicToolResultVO.setResultType(toProductImageResult.getResultType());
|
magicToolResultVO.setResultType(toProductImageResult.getResultType());
|
||||||
magicToolResultVO.setElementId(toProductImageResult.getElementId());
|
magicToolResultVO.setElementId(toProductImageResult.getElementId());
|
||||||
@@ -830,16 +903,22 @@ public class UserLikeGroupServiceImpl extends ServiceImpl<UserLikeGroupMapper, U
|
|||||||
magicToolResultVO.setBrightenValue(toProductImageResult.getBrightenValue());
|
magicToolResultVO.setBrightenValue(toProductImageResult.getBrightenValue());
|
||||||
magicToolResultVO.setImageStrength(toProductImageResult.getImageStrength());
|
magicToolResultVO.setImageStrength(toProductImageResult.getImageStrength());
|
||||||
magicToolResultVO.setPrompt(toProductImageRecord.getPrompt());
|
magicToolResultVO.setPrompt(toProductImageRecord.getPrompt());
|
||||||
|
|
||||||
|
// 根据元素类型设置源图片URL和父ID
|
||||||
if (toProductImageResult.getElementType().equals("ProductElement")) {
|
if (toProductImageResult.getElementType().equals("ProductElement")) {
|
||||||
|
// 处理ProductElement类型:获取产品元素信息
|
||||||
ToProductElement toProductElement = toProductElementMapper.selectById(toProductImageResult.getElementId());
|
ToProductElement toProductElement = toProductElementMapper.selectById(toProductImageResult.getElementId());
|
||||||
magicToolResultVO.setSourceUrl(minioUtil.getPreSignedUrl(toProductElement.getUrl(), 24 * 60));
|
magicToolResultVO.setSourceUrl(minioUtil.getPreSignedUrl(toProductElement.getUrl(), 24 * 60));
|
||||||
|
|
||||||
|
// 获取ProductElement的父ID
|
||||||
Long parentId = collectionSortService.getParentIdByElementIdAndElementType(toProductImageResult.getElementId(), toProductImageResult.getElementType());
|
Long parentId = collectionSortService.getParentIdByElementIdAndElementType(toProductImageResult.getElementId(), toProductImageResult.getElementType());
|
||||||
magicToolResultVO.setParentId(parentId);
|
magicToolResultVO.setParentId(parentId);
|
||||||
} else {
|
} else {
|
||||||
|
// 处理DesignOutfit类型:获取设计服装信息
|
||||||
TDesignPythonOutfit tDesignPythonOutfit = designPythonOutfitMapper.selectById(toProductImageResult.getElementId());
|
TDesignPythonOutfit tDesignPythonOutfit = designPythonOutfitMapper.selectById(toProductImageResult.getElementId());
|
||||||
magicToolResultVO.setSourceUrl(minioUtil.getPreSignedUrl(tDesignPythonOutfit.getDesignUrl(), 24 * 60));
|
magicToolResultVO.setSourceUrl(minioUtil.getPreSignedUrl(tDesignPythonOutfit.getDesignUrl(), 24 * 60));
|
||||||
|
|
||||||
|
// 通过DesignOutfit查找对应的UserLike记录,获取父ID
|
||||||
QueryWrapper<UserLike> userLikeQueryWrapper = new QueryWrapper<>();
|
QueryWrapper<UserLike> userLikeQueryWrapper = new QueryWrapper<>();
|
||||||
userLikeQueryWrapper.lambda().eq(UserLike::getDesignOutfitId, tDesignPythonOutfit.getId());
|
userLikeQueryWrapper.lambda().eq(UserLike::getDesignOutfitId, tDesignPythonOutfit.getId());
|
||||||
List<UserLike> userLikeList = userLikeMapper.selectList(userLikeQueryWrapper);
|
List<UserLike> userLikeList = userLikeMapper.selectList(userLikeQueryWrapper);
|
||||||
@@ -850,43 +929,62 @@ public class UserLikeGroupServiceImpl extends ServiceImpl<UserLikeGroupMapper, U
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 将构建好的结果对象添加到返回列表
|
||||||
|
results.add(magicToolResultVO);
|
||||||
} else if (Objects.isNull(magicToolResultVO)) {
|
} else if (Objects.isNull(magicToolResultVO)) {
|
||||||
|
// 如果Redis中没有结果对象,创建执行中状态的结果对象
|
||||||
magicToolResultVO = new MagicToolResultVO(taskId, "Executing");
|
magicToolResultVO = new MagicToolResultVO(taskId, "Executing");
|
||||||
|
results.add(magicToolResultVO);
|
||||||
|
} else {
|
||||||
|
// 如果Redis中有结果对象但URL为空,直接添加到返回列表
|
||||||
|
results.add(magicToolResultVO);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 收集任务状态用于统计
|
||||||
if (!StringUtil.isNullOrEmpty(magicToolResultVO.getStatus())) collect.add(magicToolResultVO.getStatus());
|
if (!StringUtil.isNullOrEmpty(magicToolResultVO.getStatus())) collect.add(magicToolResultVO.getStatus());
|
||||||
results.add(magicToolResultVO);
|
results.add(magicToolResultVO);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 返回所有任务的处理结果列表
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
private MagicToolResultVO processFluxResult(String fluxImgMinioPath, ToProductImageResult toProductImageResult, String taskId, String prompt){
|
private MagicToolResultVO processNanoBananaResult(String resultImgMinioPath, ToProductImageResult toProductImageResult, String taskId, String prompt) {
|
||||||
|
// 如果任务状态不是成功且URL为空,则更新任务状态和URL
|
||||||
if (!toProductImageResult.getStatus().equals("Success")
|
if (!toProductImageResult.getStatus().equals("Success")
|
||||||
&& StringUtil.isNullOrEmpty(toProductImageResult.getUrl())) {
|
&& StringUtil.isNullOrEmpty(toProductImageResult.getUrl())) {
|
||||||
toProductImageResult.setStatus("Success");
|
toProductImageResult.setStatus("Success");
|
||||||
toProductImageResult.setUrl(fluxImgMinioPath);
|
toProductImageResult.setUrl(resultImgMinioPath);
|
||||||
toProductImageResultMapper.updateById(toProductImageResult);
|
toProductImageResultMapper.updateById(toProductImageResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 创建返回结果对象并设置基本信息
|
||||||
MagicToolResultVO magicToolResultVO = CopyUtil.copyObject(toProductImageResult, MagicToolResultVO.class);
|
MagicToolResultVO magicToolResultVO = CopyUtil.copyObject(toProductImageResult, MagicToolResultVO.class);
|
||||||
magicToolResultVO.setTaskId(taskId);
|
magicToolResultVO.setTaskId(taskId);
|
||||||
magicToolResultVO.setId(toProductImageResult.getId());
|
magicToolResultVO.setId(toProductImageResult.getId());
|
||||||
magicToolResultVO.setUrl(minioUtil.getPreSignedUrl(fluxImgMinioPath, CommonConstant.MINIO_IMAGE_EXPIRE_TIME));
|
// 设置Flux生成图片的预签名URL(使用系统配置的过期时间)
|
||||||
|
magicToolResultVO.setUrl(minioUtil.getPreSignedUrl(resultImgMinioPath, CommonConstant.MINIO_IMAGE_EXPIRE_TIME));
|
||||||
magicToolResultVO.setStatus("Success");
|
magicToolResultVO.setStatus("Success");
|
||||||
magicToolResultVO.setResultType(toProductImageResult.getResultType());
|
magicToolResultVO.setResultType(toProductImageResult.getResultType());
|
||||||
magicToolResultVO.setElementId(toProductImageResult.getElementId());
|
magicToolResultVO.setElementId(toProductImageResult.getElementId());
|
||||||
magicToolResultVO.setElementType(toProductImageResult.getElementType());
|
magicToolResultVO.setElementType(toProductImageResult.getElementType());
|
||||||
magicToolResultVO.setPrompt(prompt);
|
magicToolResultVO.setPrompt(prompt);
|
||||||
|
|
||||||
|
// 根据元素类型设置源图片URL和父ID
|
||||||
if (toProductImageResult.getElementType().equals("ProductElement")) {
|
if (toProductImageResult.getElementType().equals("ProductElement")) {
|
||||||
|
// 处理产品元素类型:获取产品元素信息并设置源图片URL
|
||||||
ToProductElement toProductElement = toProductElementMapper.selectById(toProductImageResult.getElementId());
|
ToProductElement toProductElement = toProductElementMapper.selectById(toProductImageResult.getElementId());
|
||||||
magicToolResultVO.setSourceUrl(minioUtil.getPreSignedUrl(toProductElement.getUrl(), 24 * 60));
|
magicToolResultVO.setSourceUrl(minioUtil.getPreSignedUrl(toProductElement.getUrl(), 24 * 60));
|
||||||
|
|
||||||
|
// 通过CollectionSort服务获取父ID
|
||||||
Long parentId = collectionSortService.getParentIdByElementIdAndElementType(toProductImageResult.getElementId(), toProductImageResult.getElementType());
|
Long parentId = collectionSortService.getParentIdByElementIdAndElementType(toProductImageResult.getElementId(), toProductImageResult.getElementType());
|
||||||
magicToolResultVO.setParentId(parentId);
|
magicToolResultVO.setParentId(parentId);
|
||||||
} else if (toProductImageResult.getElementType().equals("DesignOutfit")) {
|
} else if (toProductImageResult.getElementType().equals("DesignOutfit")) {
|
||||||
|
// 处理设计服装类型:获取设计服装信息并设置源图片URL
|
||||||
TDesignPythonOutfit tDesignPythonOutfit = designPythonOutfitMapper.selectById(toProductImageResult.getElementId());
|
TDesignPythonOutfit tDesignPythonOutfit = designPythonOutfitMapper.selectById(toProductImageResult.getElementId());
|
||||||
magicToolResultVO.setSourceUrl(minioUtil.getPreSignedUrl(tDesignPythonOutfit.getDesignUrl(), 24 * 60));
|
magicToolResultVO.setSourceUrl(minioUtil.getPreSignedUrl(tDesignPythonOutfit.getDesignUrl(), 24 * 60));
|
||||||
|
|
||||||
|
// 通过设计服装ID查找对应的用户喜欢记录,获取父ID
|
||||||
QueryWrapper<UserLike> userLikeQueryWrapper = new QueryWrapper<>();
|
QueryWrapper<UserLike> userLikeQueryWrapper = new QueryWrapper<>();
|
||||||
userLikeQueryWrapper.lambda().eq(UserLike::getDesignOutfitId, tDesignPythonOutfit.getId());
|
userLikeQueryWrapper.lambda().eq(UserLike::getDesignOutfitId, tDesignPythonOutfit.getId());
|
||||||
List<UserLike> userLikeList = userLikeMapper.selectList(userLikeQueryWrapper);
|
List<UserLike> userLikeList = userLikeMapper.selectList(userLikeQueryWrapper);
|
||||||
@@ -896,9 +994,79 @@ public class UserLikeGroupServiceImpl extends ServiceImpl<UserLikeGroupMapper, U
|
|||||||
magicToolResultVO.setParentId(parentId);
|
magicToolResultVO.setParentId(parentId);
|
||||||
}
|
}
|
||||||
} else if (toProductImageResult.getElementType().equals("ToProductImage")) {
|
} else if (toProductImageResult.getElementType().equals("ToProductImage")) {
|
||||||
|
// 处理转产品图像类型:获取转产品图像信息并设置源图片URL
|
||||||
ToProductImageResult toProductImage = toProductImageResultMapper.selectById(toProductImageResult.getElementId());
|
ToProductImageResult toProductImage = toProductImageResultMapper.selectById(toProductImageResult.getElementId());
|
||||||
magicToolResultVO.setSourceUrl(minioUtil.getPreSignedUrl(toProductImage.getUrl(), 24 * 60));
|
magicToolResultVO.setSourceUrl(minioUtil.getPreSignedUrl(toProductImage.getUrl(), 24 * 60));
|
||||||
|
|
||||||
|
// 直接通过元素ID和类型获取父ID
|
||||||
|
Long parentId = collectionSortService.getParentIdByElementIdAndElementType(toProductImageResult.getElementId(), toProductImageResult.getElementType());
|
||||||
|
magicToolResultVO.setParentId(parentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Long parentId = collectionSortService.getParentIdByElementIdAndElementType(toProductImageResult.getId(), toProductImageResult.getResultType());
|
||||||
|
// magicToolResultVO.setParentId(parentId);
|
||||||
|
return magicToolResultVO;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理Flux模型生成的结果,构建返回给前端的结果对象
|
||||||
|
*
|
||||||
|
* @param fluxImgMinioPath Flux生成的图片在Minio中的路径
|
||||||
|
* @param toProductImageResult 产品图像生成结果记录
|
||||||
|
* @param taskId 任务ID
|
||||||
|
* @param prompt 生成提示词
|
||||||
|
* @return MagicToolResultVO 构建好的魔法工具结果对象
|
||||||
|
*/
|
||||||
|
private MagicToolResultVO processFluxResult(String fluxImgMinioPath, ToProductImageResult toProductImageResult, String taskId, String prompt) {
|
||||||
|
// 如果任务状态不是成功且URL为空,则更新任务状态和URL
|
||||||
|
if (!toProductImageResult.getStatus().equals("Success")
|
||||||
|
&& StringUtil.isNullOrEmpty(toProductImageResult.getUrl())) {
|
||||||
|
toProductImageResult.setStatus("Success");
|
||||||
|
toProductImageResult.setUrl(fluxImgMinioPath);
|
||||||
|
toProductImageResultMapper.updateById(toProductImageResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建返回结果对象并设置基本信息
|
||||||
|
MagicToolResultVO magicToolResultVO = CopyUtil.copyObject(toProductImageResult, MagicToolResultVO.class);
|
||||||
|
magicToolResultVO.setTaskId(taskId);
|
||||||
|
magicToolResultVO.setId(toProductImageResult.getId());
|
||||||
|
// 设置Flux生成图片的预签名URL(使用系统配置的过期时间)
|
||||||
|
magicToolResultVO.setUrl(minioUtil.getPreSignedUrl(fluxImgMinioPath, CommonConstant.MINIO_IMAGE_EXPIRE_TIME));
|
||||||
|
magicToolResultVO.setStatus("Success");
|
||||||
|
magicToolResultVO.setResultType(toProductImageResult.getResultType());
|
||||||
|
magicToolResultVO.setElementId(toProductImageResult.getElementId());
|
||||||
|
magicToolResultVO.setElementType(toProductImageResult.getElementType());
|
||||||
|
magicToolResultVO.setPrompt(prompt);
|
||||||
|
|
||||||
|
// 根据元素类型设置源图片URL和父ID
|
||||||
|
if (toProductImageResult.getElementType().equals("ProductElement")) {
|
||||||
|
// 处理产品元素类型:获取产品元素信息并设置源图片URL
|
||||||
|
ToProductElement toProductElement = toProductElementMapper.selectById(toProductImageResult.getElementId());
|
||||||
|
magicToolResultVO.setSourceUrl(minioUtil.getPreSignedUrl(toProductElement.getUrl(), 24 * 60));
|
||||||
|
|
||||||
|
// 通过CollectionSort服务获取父ID
|
||||||
|
Long parentId = collectionSortService.getParentIdByElementIdAndElementType(toProductImageResult.getElementId(), toProductImageResult.getElementType());
|
||||||
|
magicToolResultVO.setParentId(parentId);
|
||||||
|
} else if (toProductImageResult.getElementType().equals("DesignOutfit")) {
|
||||||
|
// 处理设计服装类型:获取设计服装信息并设置源图片URL
|
||||||
|
TDesignPythonOutfit tDesignPythonOutfit = designPythonOutfitMapper.selectById(toProductImageResult.getElementId());
|
||||||
|
magicToolResultVO.setSourceUrl(minioUtil.getPreSignedUrl(tDesignPythonOutfit.getDesignUrl(), 24 * 60));
|
||||||
|
|
||||||
|
// 通过设计服装ID查找对应的用户喜欢记录,获取父ID
|
||||||
|
QueryWrapper<UserLike> userLikeQueryWrapper = new QueryWrapper<>();
|
||||||
|
userLikeQueryWrapper.lambda().eq(UserLike::getDesignOutfitId, tDesignPythonOutfit.getId());
|
||||||
|
List<UserLike> userLikeList = userLikeMapper.selectList(userLikeQueryWrapper);
|
||||||
|
if (!CollectionUtils.isEmpty(userLikeList)) {
|
||||||
|
UserLike userLike = userLikeList.get(0);
|
||||||
|
Long parentId = collectionSortService.getParentIdByElementIdAndElementType(userLike.getId(), toProductImageResult.getElementType());
|
||||||
|
magicToolResultVO.setParentId(parentId);
|
||||||
|
}
|
||||||
|
} else if (toProductImageResult.getElementType().equals("ToProductImage")) {
|
||||||
|
// 处理转产品图像类型:获取转产品图像信息并设置源图片URL
|
||||||
|
ToProductImageResult toProductImage = toProductImageResultMapper.selectById(toProductImageResult.getElementId());
|
||||||
|
magicToolResultVO.setSourceUrl(minioUtil.getPreSignedUrl(toProductImage.getUrl(), 24 * 60));
|
||||||
|
|
||||||
|
// 直接通过元素ID和类型获取父ID
|
||||||
Long parentId = collectionSortService.getParentIdByElementIdAndElementType(toProductImageResult.getElementId(), toProductImageResult.getElementType());
|
Long parentId = collectionSortService.getParentIdByElementIdAndElementType(toProductImageResult.getElementId(), toProductImageResult.getElementType());
|
||||||
magicToolResultVO.setParentId(parentId);
|
magicToolResultVO.setParentId(parentId);
|
||||||
}
|
}
|
||||||
@@ -932,7 +1100,8 @@ public class UserLikeGroupServiceImpl extends ServiceImpl<UserLikeGroupMapper, U
|
|||||||
String type = jsonObjectLevel1.getString("type");
|
String type = jsonObjectLevel1.getString("type");
|
||||||
if (type.equals("image")) {
|
if (type.equals("image")) {
|
||||||
String minioUrl = jsonObjectLevel1.getString("minioUrl");
|
String minioUrl = jsonObjectLevel1.getString("minioUrl");
|
||||||
if (!StringUtil.isNullOrEmpty(minioUrl)) jsonObjectLevel1.put("src", minioUtil.getPreSignedUrl(minioUrl, 24 * 60));
|
if (!StringUtil.isNullOrEmpty(minioUrl))
|
||||||
|
jsonObjectLevel1.put("src", minioUtil.getPreSignedUrl(minioUrl, 24 * 60));
|
||||||
}
|
}
|
||||||
if (type.equals("group")) {
|
if (type.equals("group")) {
|
||||||
JSONArray objectJSONArrayLevel2 = jsonObjectLevel1.getJSONArray("objects");
|
JSONArray objectJSONArrayLevel2 = jsonObjectLevel1.getJSONArray("objects");
|
||||||
@@ -969,7 +1138,8 @@ public class UserLikeGroupServiceImpl extends ServiceImpl<UserLikeGroupMapper, U
|
|||||||
String type = jsonObjectLevel1.getString("type");
|
String type = jsonObjectLevel1.getString("type");
|
||||||
if (type.equals("image")) {
|
if (type.equals("image")) {
|
||||||
String minioUrl = jsonObjectLevel1.getString("minioUrl");
|
String minioUrl = jsonObjectLevel1.getString("minioUrl");
|
||||||
if (!StringUtil.isNullOrEmpty(minioUrl)) jsonObjectLevel1.put("src", minioUtil.getPreSignedUrl(minioUrl, 24 * 60));
|
if (!StringUtil.isNullOrEmpty(minioUrl))
|
||||||
|
jsonObjectLevel1.put("src", minioUtil.getPreSignedUrl(minioUrl, 24 * 60));
|
||||||
}
|
}
|
||||||
if (type.equals("group")) {
|
if (type.equals("group")) {
|
||||||
JSONArray objectJSONArrayLevel2 = jsonObjectLevel1.getJSONArray("objects");
|
JSONArray objectJSONArrayLevel2 = jsonObjectLevel1.getJSONArray("objects");
|
||||||
@@ -1572,8 +1742,6 @@ public class UserLikeGroupServiceImpl extends ServiceImpl<UserLikeGroupMapper, U
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private String getStyleByUrl(String replace) {
|
private String getStyleByUrl(String replace) {
|
||||||
String[] split = replace.split("/");
|
String[] split = replace.split("/");
|
||||||
String tableName = getTableName(split);
|
String tableName = getTableName(split);
|
||||||
|
|||||||
@@ -727,12 +727,8 @@ public class WorkspaceServiceImpl extends ServiceImpl<WorkspaceMapper, Workspace
|
|||||||
// deleteMannequinByProjectId(projectId);
|
// deleteMannequinByProjectId(projectId);
|
||||||
if (!workspaceNew.getSex().equals(workspace.getSex())) {
|
if (!workspaceNew.getSex().equals(workspace.getSex())) {
|
||||||
// deleteSketchByProjectId(projectId);
|
// deleteSketchByProjectId(projectId);
|
||||||
|
|
||||||
LambdaQueryWrapper<CollectionElement> queryWrapper = new LambdaQueryWrapper<CollectionElement>().eq(CollectionElement::getProjectId, projectId)
|
|
||||||
.eq(CollectionElement::getLevel1Type, CollectionLevel1TypeEnum.MODEL.getRealName())
|
|
||||||
.eq(CollectionElement::getLevel3Type, workspaceNew.getSex());
|
|
||||||
if (collectionElementMapper.selectCount(queryWrapper) == 0){
|
|
||||||
SysFile sysFile = sysFileService.getOneBySex(projectDTO.getStyleId(), projectDTO.getWorkspace().getSex(), projectDTO.getWorkspace().getAgeGroup());
|
SysFile sysFile = sysFileService.getOneBySex(projectDTO.getStyleId(), projectDTO.getWorkspace().getSex(), projectDTO.getWorkspace().getAgeGroup());
|
||||||
|
if (!checkIfModelExistsInProject(sysFile.getMd5(), projectId)) {
|
||||||
CollectionElement collectionElement = new CollectionElement();
|
CollectionElement collectionElement = new CollectionElement();
|
||||||
collectionElement.setAccountId(userInfo.getId());
|
collectionElement.setAccountId(userInfo.getId());
|
||||||
collectionElement.setProjectId(projectId);
|
collectionElement.setProjectId(projectId);
|
||||||
@@ -898,6 +894,17 @@ public class WorkspaceServiceImpl extends ServiceImpl<WorkspaceMapper, Workspace
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 由于在数据库中表设计问题,目前只能通过路径来判断项目中是否存在某一个模特
|
||||||
|
public Boolean checkIfModelExistsInProject(String md5, Long projectId) {
|
||||||
|
QueryWrapper<CollectionElement> qw = new QueryWrapper<>();
|
||||||
|
qw.lambda().eq(CollectionElement::getProjectId, projectId)
|
||||||
|
.eq(CollectionElement::getLevel1Type, CollectionLevel1TypeEnum.MODEL.getRealName())
|
||||||
|
.eq(CollectionElement::getMd5, md5);
|
||||||
|
|
||||||
|
List<CollectionElement> collectionElements = collectionElementMapper.selectList(qw);
|
||||||
|
return collectionElements != null && !collectionElements.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
public static List<File> getPNGFiles(String directoryPath) {
|
public static List<File> getPNGFiles(String directoryPath) {
|
||||||
List<File> pngFiles = new ArrayList<>();
|
List<File> pngFiles = new ArrayList<>();
|
||||||
File directory = new File(directoryPath);
|
File directory = new File(directoryPath);
|
||||||
|
|||||||
Reference in New Issue
Block a user