diff --git a/src/main/java/com/ai/da/common/RabbitMQ/GenerateConsumer.java b/src/main/java/com/ai/da/common/RabbitMQ/GenerateConsumer.java index e9ca9d36..40b192c8 100644 --- a/src/main/java/com/ai/da/common/RabbitMQ/GenerateConsumer.java +++ b/src/main/java/com/ai/da/common/RabbitMQ/GenerateConsumer.java @@ -293,7 +293,7 @@ public class GenerateConsumer { } else { // 修改redis中的数据状态为exception String key = generateResultKey + ":" + generateResult.get("tasks_id"); - generateService.updatePoseTransferStatus(generateResult.get("tasks_id"), "Fail"); + generateService.updatePoseTransferStatus(generateResult.get("tasks_id"), "Fail", null); redisUtil.addToString(key, new Gson().toJson(new PoseTransformationVO(null, generateResult.get("tasks_id"),null, null, null, (byte)0, "Fail")), CommonConstant.GENERATE_RESULT_EXPIRE_TIME); // 将异常信息存到exception中 HashMap exceptionInfo = new HashMap<>(); @@ -529,7 +529,7 @@ public class GenerateConsumer { } else { // 修改redis中的数据状态为exception String key = generateResultKey + ":" + generateResult.getString("task_id"); - generateService.updatePoseTransferStatus(generateResult.getString("task_id"), "Fail"); + generateService.updatePoseTransferStatus(generateResult.getString("task_id"), "Fail", null); redisUtil.addToString(key, new Gson().toJson(new PoseTransformationVO(null, generateResult.getString("task_id"),null, null, null, (byte)0, "Fail")), CommonConstant.GENERATE_RESULT_EXPIRE_TIME); // 将异常信息存到exception中 HashMap exceptionInfo = new HashMap<>(); diff --git a/src/main/java/com/ai/da/common/task/GenerateTask.java b/src/main/java/com/ai/da/common/task/GenerateTask.java new file mode 100644 index 00000000..34c97964 --- /dev/null +++ b/src/main/java/com/ai/da/common/task/GenerateTask.java @@ -0,0 +1,134 @@ +package com.ai.da.common.task; + +import com.ai.da.common.config.exception.BusinessException; +import com.ai.da.common.utils.DateUtil; +import com.ai.da.mapper.primary.PoseTransformationMapper; +import com.ai.da.mapper.primary.ToProductImageResultMapper; +import com.ai.da.mapper.primary.entity.*; +import com.ai.da.service.APIGenerateService; +import com.ai.da.service.CreditsService; +import com.ai.da.service.GenerateService; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import io.netty.util.internal.StringUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Objects; + +import static com.ai.da.common.enums.CreditsEventsEnum.TO_PRODUCT_IMAGE; + +@Slf4j +@Component +public class GenerateTask { + @Resource + private APIGenerateService apiGenerateService; + @Resource + private CreditsService creditsService; + @Resource + private GenerateService generateService; + @Resource + private ToProductImageResultMapper toProductImageResultMapper; + @Resource + private PoseTransformationMapper poseTransformationMapper; + + + /* + * 对于使用了第三方api的允许异步获得结果的生成功能,可能在第三方接口的结果Ready时没有及时存储结果,导致第三方链接失效 + * 万相 24h失效, + * flux 10mins失效 (使用了flux接口的功能 ToProductImage || Relight, Pattern这里不做补偿) + * 故这里通过定时任务做补偿 + * flux五分钟查询一次,万相1小时查询一次 + */ + @Scheduled(cron = "0 */4 * * * ?") + public void fluxCompensationMechanism(){ + // 1、查所有 任务还没成功、还没失败,正在等待或者执行中的任务id有哪些 + // (由于获取结果的polling_url在redis中只存一天,大部分结果超过一天之后就无法再找到任务,小部分可以通过公共路径查到结果) + List apiGenerates = apiGenerateService.getPendingTaskByStatus("flux"); + if (apiGenerates != null && !apiGenerates.isEmpty()){ + for (APIGenerate apiGenerate : apiGenerates){ + String taskId = apiGenerate.getTaskId(); + // 1. 根据taskId查toProductImageResult, 判断当前任务状态与超时状态 + ToProductImageResult toProductImageResult = toProductImageResultMapper.selectOne(new QueryWrapper().eq("task_id", taskId)); + if (Objects.nonNull(toProductImageResult) && "Pending".equals(toProductImageResult.getStatus())){ + // 判断当前任务的超时状态 + if (!DateUtil.isMoreThanOneDayApart(toProductImageResult.getCreateTime())){ + // 1. 未超时,获取当前任务结果 + String fileName = toProductImageResult.getResultType().equals(TO_PRODUCT_IMAGE.getName()) ? "product_image" : "relight_image"; + String objectName = apiGenerate.getAccountId() + "/" + fileName +"/" + taskId + ".png"; + String fluxResult = generateService.getFluxResult(taskId, objectName); + + // 2. 成功,获取结果,下载图片,上传至minio,更新toProductImageResult表 + if (StringUtil.isNullOrEmpty(fluxResult) || fluxResult.equals("Fail")){ + toProductImageResult.setStatus("Fail"); + toProductImageResultMapper.updateById(toProductImageResult); + + apiGenerate.setStatus("Fail"); + apiGenerate.setUpdateTime(LocalDateTime.now()); + apiGenerateService.updateById(apiGenerate); + } else if (!fluxResult.equals("Pending")){ + if (StringUtil.isNullOrEmpty(toProductImageResult.getUrl())){ + toProductImageResult.setStatus("Success"); + toProductImageResult.setUrl(fluxResult); + toProductImageResultMapper.updateById(toProductImageResult); + + apiGenerate.setStatus("Success"); + apiGenerate.setUpdateTime(LocalDateTime.now()); + apiGenerateService.updateById(apiGenerate); + } + // 扣积分 + Boolean flag = creditsService.taskCreditsDeduction(apiGenerate.getAccountId(), taskId); + if (flag) creditsService.updateChangedCredits(String.valueOf(apiGenerate.getAccountId()), taskId); + } + } else { + // 超时,设置状态为失败 + toProductImageResult.setStatus("Fail"); + toProductImageResultMapper.updateById(toProductImageResult); + + apiGenerate.setStatus("Fail"); + apiGenerate.setUpdateTime(LocalDateTime.now()); + apiGenerateService.updateById(apiGenerate); + } + } + } + } + + } + + // 万相 -> pose transformation 补偿 一小时执行一次 + @Scheduled(fixedDelay = 60 * 60 * 1000) + public void wxCompensationMechanism(){ + List apiGenerates = apiGenerateService.getPendingTaskByStatus("wx"); + if (apiGenerates != null && !apiGenerates.isEmpty()){ + for (APIGenerate apiGenerate : apiGenerates){ + String taskId = apiGenerate.getTaskId(); + PoseTransformation poseTransformation = poseTransformationMapper.selectOne(new QueryWrapper().eq("unique_id", taskId)); + if (Objects.nonNull(poseTransformation) && "Pending".equals(poseTransformation.getTaskStatus())){ + // 判断当前任务的超时状态 + if (!DateUtil.isMoreThanOneDayApart(poseTransformation.getCreateTime())){ + try { + // 方法中已经完成了pose_transformation和api_generate表的更新,不用额外做处理 + generateService.getAnimateResult(taskId); + } catch (BusinessException e){ + log.warn("万相 animation 生成失败,原因:{}", e.getMessage()); + } + } + } else { + poseTransformation.setTaskStatus("Fail"); + poseTransformation.setUpdateTime(LocalDateTime.now()); + poseTransformationMapper.updateById(poseTransformation); + + apiGenerate.setStatus("Fail"); + apiGenerate.setUpdateTime(LocalDateTime.now()); + apiGenerateService.updateById(apiGenerate); + } + } + } + } + + + +} diff --git a/src/main/java/com/ai/da/common/utils/DateUtil.java b/src/main/java/com/ai/da/common/utils/DateUtil.java index 2ae61ab5..ff3137d5 100644 --- a/src/main/java/com/ai/da/common/utils/DateUtil.java +++ b/src/main/java/com/ai/da/common/utils/DateUtil.java @@ -5,10 +5,7 @@ import lombok.extern.slf4j.Slf4j; import java.text.ParseException; import java.text.SimpleDateFormat; -import java.time.Instant; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.ZoneId; +import java.time.*; import java.time.format.DateTimeFormatter; import java.util.Date; import java.util.Locale; @@ -101,4 +98,10 @@ public class DateUtil { return localDate.format(DateTimeFormatter.ofPattern(CommonConstant.TIME_FORMAT_MMM_dd_yyyy_EEEE, Locale.US)); } + public static boolean isMoreThanOneDayApart(LocalDateTime givenDateTime) { + LocalDateTime now = LocalDateTime.now(); + Duration duration = Duration.between(givenDateTime, now); + return duration.toHours() >= 24; + } + } diff --git a/src/main/java/com/ai/da/common/utils/RedisUtil.java b/src/main/java/com/ai/da/common/utils/RedisUtil.java index 6b8294ea..526d601b 100644 --- a/src/main/java/com/ai/da/common/utils/RedisUtil.java +++ b/src/main/java/com/ai/da/common/utils/RedisUtil.java @@ -9,6 +9,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ValueOperations; import org.springframework.data.redis.core.ZSetOperations; import org.springframework.data.redis.core.script.DefaultRedisScript; import org.springframework.stereotype.Component; @@ -594,4 +595,19 @@ public class RedisUtil { return count; } + public boolean allowRequest(String apiKey) { + String key = "rate_limit:" + apiKey; + ValueOperations ops = redisTemplate.opsForValue(); + + // 使用Redis的INCR命令 + Long count = ops.increment(key, 1); + + if (count == 1) { + // 第一次调用,设置过期时间 + redisTemplate.expire(key, 1, TimeUnit.MINUTES); + } + + return count <= 3; + } + } diff --git a/src/main/java/com/ai/da/controller/GenerateController.java b/src/main/java/com/ai/da/controller/GenerateController.java index 759402a2..135322bf 100644 --- a/src/main/java/com/ai/da/controller/GenerateController.java +++ b/src/main/java/com/ai/da/controller/GenerateController.java @@ -1,233 +1,240 @@ -package com.ai.da.controller; - -import com.ai.da.common.enums.CreditsEventsEnum; -import com.ai.da.common.response.Response; -import com.ai.da.mapper.primary.entity.CollectionSort; -import com.ai.da.model.dto.*; -import com.ai.da.model.vo.*; -import com.ai.da.service.GenerateService; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; -import lombok.extern.slf4j.Slf4j; -import org.springframework.web.bind.annotation.*; - -import javax.annotation.Resource; -import javax.validation.Valid; -import javax.validation.constraints.NotNull; -import java.util.List; -import java.util.Map; - -/** - * @author XP - */ -@Api(tags = "Generate模块") -@Slf4j -@RestController -@RequestMapping("/api/generate") -public class GenerateController { - - @Resource - private GenerateService generateService; - - /*@ApiOperation("自动识别sketch的caption 暂时未上") - @PostMapping("/caption") - public Response generateCaption(@RequestParam Long sketchElementId) { - return Response.success(generateService.generateCaption(sketchElementId)); - }*/ - - /*@ApiOperation("通过文字、图片生成图片") - @PostMapping("/sketchAndPrint") - public void generateThroughImageText(@Valid @RequestBody GenerateThroughImageTextDTO generateThroughImageTextDTO) { -// return Response.success(generateService.generateThroughImageText(generateThroughImageTextDTO)); - generateService.generateThroughImageText(generateThroughImageTextDTO); - }*/ - - @ApiOperation("喜欢生成的图片") - @PostMapping("/like") - public Response like(@Valid @RequestBody GenerateLikeDTO generateLikeDTO) { - return Response.success(generateService.generateLike(generateLikeDTO)); - } - - @ApiOperation(value = "取消喜欢") - @GetMapping("/dislike") - public Response dislike(@ApiParam("generateDetailId") @RequestParam Long generateDetailId, - @ApiParam("timeZone") @RequestParam String timeZone) { - return Response.success(generateService.generateDislike(generateDetailId, timeZone)); - } - - @ApiOperation(value = "发起生成请求,异步获取结果") - @PostMapping("/prepare") - public Response prepareForGenerate(@Valid @RequestBody GenerateThroughImageTextDTO generateThroughImageTextDTO) { - return Response.success(generateService.prepareForGenerate(generateThroughImageTextDTO)); - } - - @ApiOperation(value = "取消继续生成") - @GetMapping("/stopWaiting") - public Response stopWaiting(@RequestParam("userId") Long userId, - @RequestParam("uniqueId") List uniqueId, - @RequestParam("timeZone") String timeZone, - @RequestParam("type") String type) { - generateService.cancelGenerate(userId, uniqueId, timeZone, type); - return Response.success("stop waiting successfully"); - } - - /*@ApiOperation(value = "获取生成结果") - @GetMapping("/result") - public Response getGenerateResult(@RequestParam("uniqueId") String uniqueId) { - GenerateCollectionVO generateResult = generateService.getGenerateResult(uniqueId); - return Response.success(generateResult); - }*/ - - @ApiOperation(value = "获取生成结果") - @PostMapping("/result") - public Response> getGenerateResults(@Valid @RequestBody List taskIdList) { - List generateResult = generateService.getGenerateResultList(taskIdList); - return Response.success(generateResult); - } - - @ApiOperation(value = "imageToSketch") - @PostMapping("/imageToSketch") - public Response imageToSketch(@Valid @RequestBody ImageToSketchDTO imageToSketchDTO) { - return Response.success(generateService.imageToSketchAsync(imageToSketchDTO, null, null)); -// return Response.success(generateService.imageToSketch(imageToSketchDTO, null, null)); - } - - // modifySketch - @ApiOperation(value = "modifySketch") - @PostMapping("/modifySketch") - public Response modifySketch(@Valid @RequestBody GenerateModifyDTO generateModifyDTO) { - return Response.success(generateService.modifySketch(generateModifyDTO)); - } - - @ApiOperation(value = "请求进行姿势变换") - @PostMapping("/poseTransform") - public Response poseTransform(@Valid @RequestBody PoseTransformDTO poseTransformDTO) { - return Response.success(generateService.poseTransform(poseTransformDTO)); - } - - @ApiOperation(value = "获取姿势变换生成结果") - @PostMapping("/poseTransformResult") - public Response> getPoseTransformationResults(@Valid @RequestBody List taskIdList) { - List generateResult = generateService.getPoseTransformationResult(taskIdList, null, null); - return Response.success(generateResult); - } - - @ApiOperation("喜欢或取消喜欢姿势变换生成的图片") - @PostMapping("/likeOrDislike") - public Response likeOrDislike(@ApiParam("id") @RequestParam Long transformedId, @ApiParam("like || dislike") @RequestParam String likeOrDislike, @RequestParam("projectId") Long projectId, @RequestParam(value = "collectionSortParentId", required = false) Long collectionSortParentId) { - return Response.success(generateService.disOrLikePose(transformedId, likeOrDislike, projectId, collectionSortParentId)); - } - - @ApiOperation(value = "修改模特比例") - @PostMapping("/modifyProportion") - public Response modifyModelProportion(@Valid @RequestBody ModifyModelProportionDTO proportionDTO){ - String path = generateService.modifyModelProportion(proportionDTO); - return Response.success(path); - } - - @ApiOperation(value = "拼贴图生成线稿") - @PostMapping("/genSketchRecon") - public Response sketchReconstructionGenerate(@Valid @RequestBody SketchReconstructionDTO sketchReconstructionDTO){ - GenerateResultVO generateResultVO = generateService.sketchReconstructionGenerate(sketchReconstructionDTO); - return Response.success(generateResultVO); - } - - @ApiOperation(value = "获取拼贴图最后一次生成结果") - @GetMapping("/getReconLastResult") - public Response getSketchReconstruction(@RequestParam("projectId") Long projectId){ - SketchReconstructionVO sketchReconstruction = generateService.getSketchReconstruction(projectId); - return Response.success(sketchReconstruction); - } - - @ApiOperation(value = "获取pose transfer的所有pose") - @GetMapping("/getAllPose") - public Response>> getAllPose(){ - return Response.success(generateService.getAllPose()); - } - - @ApiOperation(value = "删除pose transfer的结果") - @GetMapping("/deleteResult") - public Response deleteToProductRelightResult(@RequestParam("projectId") @NotNull Long projectId, - @RequestParam("id") @NotNull Long id){ - try{ - generateService.deleteGeneratedPose(projectId, id); - return Response.success(); - }catch (Exception e){ - return Response.fail(e.getMessage()); - } - } - - /*@ApiOperation(value = "万象 t2i 创建异步任务") - @GetMapping("/createAsyncTask") - public Response createAsyncTask(@RequestParam("prompt") String prompt){ - return Response.success(generateService.createAsyncTask(87L, prompt, "")); - } - - @ApiOperation(value = "万象 t2i 获取异步任务结果") - @GetMapping("/waitAsyncTask") - public Response waitAsyncTask(@RequestParam("taskId") String taskId){ - return Response.success(generateService.getAsyncTaskResult(taskId)); - } - - @ApiOperation(value = "万象 图生动图") - @GetMapping("/animateAnyone") - public Response animateAnyone(@Valid @RequestBody PoseTransformDTO poseTransformDTO){ - return Response.success(generateService.animateAnyone(poseTransformDTO, null)); - } - - @ApiOperation(value = "万象 获取动图模板id") - @GetMapping("/getVideoTemplateId") - public Response getVideoTemplateId(@RequestParam("videoPath") String videoPath){ - return Response.success(generateService.getVideoTemplateId(videoPath)); - } - - @ApiOperation(value = "万象 获取动图结果") - @GetMapping("/getAnimateResult") - public Response getAnimateResult(@RequestParam("taskId") String taskId){ - return Response.success(generateService.getAnimateResult(taskId)); - } - - @ApiOperation(value = "freepik toProductImage") - @GetMapping("/reimagineFreePik") - public Response reimagineFreePik(@RequestParam("path") String path, - @RequestParam("prompt") String prompt, - @RequestParam("style") String style) throws IOException { - return Response.success(generateService.reimagineFreePik(path, prompt, style)); - } - - @ApiOperation(value = "获取图片描述") - @GetMapping("/getImageDescription") - public Response getImageDescription(@RequestParam("path") String path) { - return Response.success(generateService.getImageDescription(path)); - }*/ - -// @ApiOperation(value = "试用flux") -// @GetMapping("/flux") - public Response flux(@RequestParam(value = "path", required = false) String path, - @RequestParam("type") int type, - @RequestParam(value = "prompt", required = false) String prompt){ - CreditsEventsEnum typeEnum = CreditsEventsEnum.RELIGHT; - switch (type){ - case 1: - typeEnum = CreditsEventsEnum.TO_PRODUCT_IMAGE; - break; - case 2: - typeEnum = CreditsEventsEnum.IMAGE_TO_SKETCH; - break; - case 3: - typeEnum = CreditsEventsEnum.PATTERN; - break; - } - return Response.success(generateService.flux(typeEnum, prompt, path, false)); - } - -// @ApiOperation(value = "获取flux结果") -// @GetMapping("/fluxResult") - public Response fluxResult(@RequestParam("taskId") String taskId){ - return Response.success(generateService.getFluxResult(taskId, "87/" + taskId + ".png")); - } - - - -} +package com.ai.da.controller; + +import com.ai.da.common.enums.CreditsEventsEnum; +import com.ai.da.common.response.Response; +import com.ai.da.mapper.primary.entity.CollectionSort; +import com.ai.da.model.dto.*; +import com.ai.da.model.vo.*; +import com.ai.da.service.GenerateService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.validation.Valid; +import javax.validation.constraints.NotNull; +import java.util.List; +import java.util.Map; + +/** + * @author XP + */ +@Api(tags = "Generate模块") +@Slf4j +@RestController +@RequestMapping("/api/generate") +public class GenerateController { + + @Resource + private GenerateService generateService; + + /*@ApiOperation("自动识别sketch的caption 暂时未上") + @PostMapping("/caption") + public Response generateCaption(@RequestParam Long sketchElementId) { + return Response.success(generateService.generateCaption(sketchElementId)); + }*/ + + /*@ApiOperation("通过文字、图片生成图片") + @PostMapping("/sketchAndPrint") + public void generateThroughImageText(@Valid @RequestBody GenerateThroughImageTextDTO generateThroughImageTextDTO) { +// return Response.success(generateService.generateThroughImageText(generateThroughImageTextDTO)); + generateService.generateThroughImageText(generateThroughImageTextDTO); + }*/ + + @ApiOperation("喜欢生成的图片") + @PostMapping("/like") + public Response like(@Valid @RequestBody GenerateLikeDTO generateLikeDTO) { + return Response.success(generateService.generateLike(generateLikeDTO)); + } + + @ApiOperation(value = "取消喜欢") + @GetMapping("/dislike") + public Response dislike(@ApiParam("generateDetailId") @RequestParam Long generateDetailId, + @ApiParam("timeZone") @RequestParam String timeZone) { + return Response.success(generateService.generateDislike(generateDetailId, timeZone)); + } + + @ApiOperation(value = "发起生成请求,异步获取结果") + @PostMapping("/prepare") + public ResponseEntity> prepareForGenerate(@Valid @RequestBody GenerateThroughImageTextDTO generateThroughImageTextDTO) { + PrepareForGenerateVO prepareForGenerateVO = generateService.prepareForGenerate(generateThroughImageTextDTO); + if (prepareForGenerateVO.getStatus().equals(200)){ + prepareForGenerateVO.setStatus(0); + return ResponseEntity.ok(Response.success(prepareForGenerateVO)); + } else { + return ResponseEntity.status(prepareForGenerateVO.getStatus()).body(Response.fail("The rate limit has been exceeded. Please wait 1 minute before retrying.")); + } + } + + @ApiOperation(value = "取消继续生成") + @GetMapping("/stopWaiting") + public Response stopWaiting(@RequestParam("userId") Long userId, + @RequestParam("uniqueId") List uniqueId, + @RequestParam("timeZone") String timeZone, + @RequestParam("type") String type) { + generateService.cancelGenerate(userId, uniqueId, timeZone, type); + return Response.success("stop waiting successfully"); + } + + /*@ApiOperation(value = "获取生成结果") + @GetMapping("/result") + public Response getGenerateResult(@RequestParam("uniqueId") String uniqueId) { + GenerateCollectionVO generateResult = generateService.getGenerateResult(uniqueId); + return Response.success(generateResult); + }*/ + + @ApiOperation(value = "获取生成结果") + @PostMapping("/result") + public Response> getGenerateResults(@Valid @RequestBody List taskIdList) { + List generateResult = generateService.getGenerateResultList(taskIdList); + return Response.success(generateResult); + } + + @ApiOperation(value = "imageToSketch") + @PostMapping("/imageToSketch") + public Response imageToSketch(@Valid @RequestBody ImageToSketchDTO imageToSketchDTO) { + return Response.success(generateService.imageToSketchAsync(imageToSketchDTO, null, null)); +// return Response.success(generateService.imageToSketch(imageToSketchDTO, null, null)); + } + + // modifySketch + @ApiOperation(value = "modifySketch") + @PostMapping("/modifySketch") + public Response modifySketch(@Valid @RequestBody GenerateModifyDTO generateModifyDTO) { + return Response.success(generateService.modifySketch(generateModifyDTO)); + } + + @ApiOperation(value = "请求进行姿势变换") + @PostMapping("/poseTransform") + public Response poseTransform(@Valid @RequestBody PoseTransformDTO poseTransformDTO) { + return Response.success(generateService.poseTransform(poseTransformDTO)); + } + + @ApiOperation(value = "获取姿势变换生成结果") + @PostMapping("/poseTransformResult") + public Response> getPoseTransformationResults(@Valid @RequestBody List taskIdList) { + List generateResult = generateService.getPoseTransformationResult(taskIdList, null, null); + return Response.success(generateResult); + } + + @ApiOperation("喜欢或取消喜欢姿势变换生成的图片") + @PostMapping("/likeOrDislike") + public Response likeOrDislike(@ApiParam("id") @RequestParam Long transformedId, @ApiParam("like || dislike") @RequestParam String likeOrDislike, @RequestParam("projectId") Long projectId, @RequestParam(value = "collectionSortParentId", required = false) Long collectionSortParentId) { + return Response.success(generateService.disOrLikePose(transformedId, likeOrDislike, projectId, collectionSortParentId)); + } + + @ApiOperation(value = "修改模特比例") + @PostMapping("/modifyProportion") + public Response modifyModelProportion(@Valid @RequestBody ModifyModelProportionDTO proportionDTO){ + String path = generateService.modifyModelProportion(proportionDTO); + return Response.success(path); + } + + @ApiOperation(value = "拼贴图生成线稿") + @PostMapping("/genSketchRecon") + public Response sketchReconstructionGenerate(@Valid @RequestBody SketchReconstructionDTO sketchReconstructionDTO){ + GenerateResultVO generateResultVO = generateService.sketchReconstructionGenerate(sketchReconstructionDTO); + return Response.success(generateResultVO); + } + + @ApiOperation(value = "获取拼贴图最后一次生成结果") + @GetMapping("/getReconLastResult") + public Response getSketchReconstruction(@RequestParam("projectId") Long projectId){ + SketchReconstructionVO sketchReconstruction = generateService.getSketchReconstruction(projectId); + return Response.success(sketchReconstruction); + } + + @ApiOperation(value = "获取pose transfer的所有pose") + @GetMapping("/getAllPose") + public Response>> getAllPose(){ + return Response.success(generateService.getAllPose()); + } + + @ApiOperation(value = "删除pose transfer的结果") + @GetMapping("/deleteResult") + public Response deleteToProductRelightResult(@RequestParam("projectId") @NotNull Long projectId, + @RequestParam("id") @NotNull Long id){ + try{ + generateService.deleteGeneratedPose(projectId, id); + return Response.success(); + }catch (Exception e){ + return Response.fail(e.getMessage()); + } + } + + /*@ApiOperation(value = "万象 t2i 创建异步任务") + @GetMapping("/createAsyncTask") + public Response createAsyncTask(@RequestParam("prompt") String prompt){ + return Response.success(generateService.createAsyncTask(87L, prompt, "")); + } + + @ApiOperation(value = "万象 t2i 获取异步任务结果") + @GetMapping("/waitAsyncTask") + public Response waitAsyncTask(@RequestParam("taskId") String taskId){ + return Response.success(generateService.getAsyncTaskResult(taskId)); + } + + @ApiOperation(value = "万象 图生动图") + @GetMapping("/animateAnyone") + public Response animateAnyone(@Valid @RequestBody PoseTransformDTO poseTransformDTO){ + return Response.success(generateService.animateAnyone(poseTransformDTO, null)); + } + + @ApiOperation(value = "万象 获取动图模板id") + @GetMapping("/getVideoTemplateId") + public Response getVideoTemplateId(@RequestParam("videoPath") String videoPath){ + return Response.success(generateService.getVideoTemplateId(videoPath)); + } + + @ApiOperation(value = "万象 获取动图结果") + @GetMapping("/getAnimateResult") + public Response getAnimateResult(@RequestParam("taskId") String taskId){ + return Response.success(generateService.getAnimateResult(taskId)); + } + + @ApiOperation(value = "freepik toProductImage") + @GetMapping("/reimagineFreePik") + public Response reimagineFreePik(@RequestParam("path") String path, + @RequestParam("prompt") String prompt, + @RequestParam("style") String style) throws IOException { + return Response.success(generateService.reimagineFreePik(path, prompt, style)); + } + + @ApiOperation(value = "获取图片描述") + @GetMapping("/getImageDescription") + public Response getImageDescription(@RequestParam("path") String path) { + return Response.success(generateService.getImageDescription(path)); + }*/ + +// @ApiOperation(value = "试用flux") +// @GetMapping("/flux") + public Response flux(@RequestParam(value = "path", required = false) String path, + @RequestParam("type") int type, + @RequestParam(value = "prompt", required = false) String prompt){ + CreditsEventsEnum typeEnum = CreditsEventsEnum.RELIGHT; + switch (type){ + case 1: + typeEnum = CreditsEventsEnum.TO_PRODUCT_IMAGE; + break; + case 2: + typeEnum = CreditsEventsEnum.IMAGE_TO_SKETCH; + break; + case 3: + typeEnum = CreditsEventsEnum.PATTERN; + break; + } + return Response.success(generateService.flux(typeEnum, prompt, path, false)); + } + +// @ApiOperation(value = "获取flux结果") +// @GetMapping("/fluxResult") + public Response fluxResult(@RequestParam("taskId") String taskId){ + return Response.success(generateService.getFluxResult(taskId, "87/" + taskId + ".png")); + } + + + +} diff --git a/src/main/java/com/ai/da/model/vo/PrepareForGenerateVO.java b/src/main/java/com/ai/da/model/vo/PrepareForGenerateVO.java index 78c7260c..733141d8 100644 --- a/src/main/java/com/ai/da/model/vo/PrepareForGenerateVO.java +++ b/src/main/java/com/ai/da/model/vo/PrepareForGenerateVO.java @@ -1,27 +1,27 @@ -package com.ai.da.model.vo; - -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; -import lombok.Data; - -import java.util.List; - -@Data -@ApiModel("prepare for generate响应vo") -public class PrepareForGenerateVO { - - @ApiModelProperty("uniqueId") - private List uniqueId; - - @ApiModelProperty("剩余使用次数") - private Integer leftUsageCount; - - public PrepareForGenerateVO(List uniqueId, Integer leftUsageCount) { - this.uniqueId = uniqueId; - this.leftUsageCount = leftUsageCount; - } - - public PrepareForGenerateVO(Integer leftUsageCount) { - this.leftUsageCount = leftUsageCount; - } -} +package com.ai.da.model.vo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +@Data +@ApiModel("prepare for generate响应vo") +public class PrepareForGenerateVO { + + @ApiModelProperty("uniqueId") + private List uniqueId; + + @ApiModelProperty("剩余使用次数") + private Integer status; + + public PrepareForGenerateVO(List uniqueId, Integer status) { + this.uniqueId = uniqueId; + this.status = status; + } + + public PrepareForGenerateVO(Integer status) { + this.status = status; + } +} diff --git a/src/main/java/com/ai/da/service/GenerateService.java b/src/main/java/com/ai/da/service/GenerateService.java index bdeb6452..587f4592 100644 --- a/src/main/java/com/ai/da/service/GenerateService.java +++ b/src/main/java/com/ai/da/service/GenerateService.java @@ -1,99 +1,99 @@ -package com.ai.da.service; - -import com.ai.da.common.enums.CreditsEventsEnum; -import com.ai.da.mapper.primary.entity.CollectionSort; -import com.ai.da.mapper.primary.entity.Generate; -import com.ai.da.mapper.primary.entity.GenerateDetail; -import com.ai.da.model.dto.*; -import com.ai.da.model.vo.*; -import com.baomidou.mybatisplus.extension.service.IService; - -import java.io.IOException; -import java.util.List; -import java.util.Map; - -public interface GenerateService extends IService { - - GenerateCaptionVO generateCaption(Long sketchElementId); - - void generateThroughImageText(GenerateThroughImageTextDTO generateThroughImageTextDTO); - - void processGenerateResult(String taskId, String url, String category); - - void processToProductImageResult(String taskId, String url, String category); - - void updateToProductTaskStatus(String taskId, String status); - - GenerateLikeVO generateLike(GenerateLikeDTO generateLikeDTO); - - Boolean generateDislike(Long generateDetailId, String timeZone); - - void updateLikeStatusBatch(List generateDetailIdList, Byte hasLike, Long libraryId, String timeZone); - - List selectBatchByLibraryId(List libraryId); - -// GenerateCollectionVO getGenerateResult(String uniqueId); - - List getGenerateResultList(List taskIdList); - - PrepareForGenerateVO prepareForGenerate(GenerateThroughImageTextDTO generateThroughImageTextDTO); - - Long getRankPosition(String uniqueId); - - void cancelGenerate(Long userId, List uniqueId, String timeZone, String type); - - void processRelightResult(String taskId, String url, String category); - - List> getCountByUserAndTime(String startTime, String endTime, List accountIdList); - - GenerateResultVO imageToSketch(ImageToSketchDTO imageToSketchDTO, String collagePictureUrl, Long projectId); - - String imageToSketchAsync(ImageToSketchDTO imageToSketchDTO, String collagePictureUrl, Long projectId); - - GenerateResultVO modifySketch(GenerateModifyDTO generateModifyDTO); - - ToProductImageResultVO poseTransform(PoseTransformDTO poseTransformDTO); - - void processPoseTransformResult(String taskId, String gifUrl, String videoUrl, String imageUrl); - - List getPoseTransformationResult(List taskIdList, Long projectId, Boolean like); - - void updatePoseTransferStatus(String taskId, String status); - - - CollectionSort disOrLikePose(Long transformedId, String likeOrDislike, Long projectId, Long sortLikeParentId); - - String modifyModelProportion(ModifyModelProportionDTO proportionDTO); - - GenerateResultVO sketchReconstructionGenerate(SketchReconstructionDTO sketchReconstructionDTO); - - SketchReconstructionVO getSketchReconstruction(Long projectId); - - List> getAllPose(); - - void processPoseTransformResultBatch(String taskId, String gifUrl, String videoUrl, String imageUrl, String progress); - - void processPoseTransformResultBatch(String taskId, String progress); - - void deleteGeneratedPose(Long projectId, Long id); - - String createAsyncTask(GenerateThroughImageTextDTO generateThroughImageTextDTO); - - GenerateResultVO getAsyncTaskResult(String taskId); - - String animateAnyone(PoseTransformDTO poseTransformDTO, Long accountId); - - String getVideoTemplateId(String videoPath); - - PoseTransformationVO getAnimateResult(String taskId); - - String reimagineFreePik(String path, String prompt, String style) throws IOException; - - String getImageDescription(String imagePath); - - String flux(CreditsEventsEnum func, String prompt, String imagePath, boolean childStyle); - - String getFluxResult(String taskId, String objectName); - - byte[] downloadVideoOrImage(String url); -} +package com.ai.da.service; + +import com.ai.da.common.enums.CreditsEventsEnum; +import com.ai.da.mapper.primary.entity.CollectionSort; +import com.ai.da.mapper.primary.entity.Generate; +import com.ai.da.mapper.primary.entity.GenerateDetail; +import com.ai.da.mapper.primary.entity.PoseTransformation; +import com.ai.da.model.dto.*; +import com.ai.da.model.vo.*; +import com.baomidou.mybatisplus.extension.service.IService; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +public interface GenerateService extends IService { + + GenerateCaptionVO generateCaption(Long sketchElementId); + + void generateThroughImageText(GenerateThroughImageTextDTO generateThroughImageTextDTO); + + void processGenerateResult(String taskId, String url, String category); + + void processToProductImageResult(String taskId, String url, String category); + + void updateToProductTaskStatus(String taskId, String status); + + GenerateLikeVO generateLike(GenerateLikeDTO generateLikeDTO); + + Boolean generateDislike(Long generateDetailId, String timeZone); + + void updateLikeStatusBatch(List generateDetailIdList, Byte hasLike, Long libraryId, String timeZone); + + List selectBatchByLibraryId(List libraryId); + +// GenerateCollectionVO getGenerateResult(String uniqueId); + + List getGenerateResultList(List taskIdList); + + PrepareForGenerateVO prepareForGenerate(GenerateThroughImageTextDTO generateThroughImageTextDTO); + + Long getRankPosition(String uniqueId); + + void cancelGenerate(Long userId, List uniqueId, String timeZone, String type); + + void processRelightResult(String taskId, String url, String category); + + List> getCountByUserAndTime(String startTime, String endTime, List accountIdList); + + GenerateResultVO imageToSketch(ImageToSketchDTO imageToSketchDTO, String collagePictureUrl, Long projectId); + + String imageToSketchAsync(ImageToSketchDTO imageToSketchDTO, String collagePictureUrl, Long projectId); + + GenerateResultVO modifySketch(GenerateModifyDTO generateModifyDTO); + + ToProductImageResultVO poseTransform(PoseTransformDTO poseTransformDTO); + + void processPoseTransformResult(String taskId, String gifUrl, String videoUrl, String imageUrl); + + List getPoseTransformationResult(List taskIdList, Long projectId, Boolean like); + + void updatePoseTransferStatus(String taskId, String status, PoseTransformation poseTransformation); + + CollectionSort disOrLikePose(Long transformedId, String likeOrDislike, Long projectId, Long sortLikeParentId); + + String modifyModelProportion(ModifyModelProportionDTO proportionDTO); + + GenerateResultVO sketchReconstructionGenerate(SketchReconstructionDTO sketchReconstructionDTO); + + SketchReconstructionVO getSketchReconstruction(Long projectId); + + List> getAllPose(); + + void processPoseTransformResultBatch(String taskId, String gifUrl, String videoUrl, String imageUrl, String progress); + + void processPoseTransformResultBatch(String taskId, String progress); + + void deleteGeneratedPose(Long projectId, Long id); + + String createAsyncTask(GenerateThroughImageTextDTO generateThroughImageTextDTO); + + GenerateResultVO getAsyncTaskResult(String taskId); + + String animateAnyone(PoseTransformDTO poseTransformDTO, Long accountId); + + String getVideoTemplateId(String videoPath); + + PoseTransformationVO getAnimateResult(String taskId); + + String reimagineFreePik(String path, String prompt, String style) throws IOException; + + String getImageDescription(String imagePath); + + String flux(CreditsEventsEnum func, String prompt, String imagePath, boolean childStyle); + + String getFluxResult(String taskId, String objectName); + + byte[] downloadVideoOrImage(String url); +} diff --git a/src/main/java/com/ai/da/service/impl/APIGenerateServiceImpl.java b/src/main/java/com/ai/da/service/impl/APIGenerateServiceImpl.java index c4f7a4d9..d5561d65 100644 --- a/src/main/java/com/ai/da/service/impl/APIGenerateServiceImpl.java +++ b/src/main/java/com/ai/da/service/impl/APIGenerateServiceImpl.java @@ -53,7 +53,8 @@ public class APIGenerateServiceImpl extends ServiceImpl implements GenerateService { - - @Resource - private CollectionElementMapper collectionElementMapper; - @Resource - private GenerateDetailMapper generateDetailMapper; - @Resource - private LibraryService libraryService; - @Resource - private PythonService pythonService; - @Resource - private CollectionElementService collectionElementService; - @Resource - private CreditsService creditsService; - @Resource - private MinioUtil minioUtil; - @Resource - private RabbitMQService rabbitMQService; - @Resource - private RedisUtil redisUtil; - @Resource - private GenerateCancelMapper generateCancelMapper; - @Resource - private SketchReconstructionMapper sketchReconstructionMapper; - @Resource - private SendRequestUtil sendRequestUtil; - @Resource - private ProjectService projectService; - @Resource - private CollectionSortService collectionSortService; - @Resource - private CloudTaskService cloudTaskService; - @Resource - private APIGenerateMapper apiGenerateMapper; - @Resource - private CollectionSortMapper collectionSortMapper; - @Resource - private SysFileService sysFileService; - @Resource - private LibraryModelPointService libraryModelPointService; - @Resource - private PoseTransformationMapper poseTransformationMapper; - @Resource - private CloudTaskMapper cloudTaskMapper; - @Resource - private ToProductImageResultMapper toProductImageResultMapper; - @Resource - private AccountService accountService; - @Resource - private APIGenerateService apiGenerateService; - @Resource - private UserLikeGroupService userLikeGroupService; - - @Value("${redis.key.orderForGenerate}") - private String consumptionOrderKey; - - @Value("${redis.key.generateCancelSet}") - private String cancelSetKey; - - @Value("${redis.key.generateExceptionMap}") - private String exceptionMapKey; - - @Value("${redis.key.generateResult}") - private String generateResultKey; - - @Value("${minio.bucketName.slogan}") - private String sloganBucket; - - @Value("${minio.bucketName.users}") - private String userBucket; - - @Value("${redis.key.relightResultKey}") - private String relightResultKey; - - @Value("${redis.key.toProductImageResultKey}") - private String toProductImageResultKey; - - @Value("${access.python.generate_sr_port}") - private String generateServicePort; - - @Value("${ALIYUN_API_KEY}") - private String ALIYUN_API_KEY; - - @Value("${FREEPIK_API_KEY}") - private String FREEPIK_API_KEY; - - - // 创建 Random 对象 - Random random = new Random(); - - @Override - public GenerateCaptionVO generateCaption(Long sketchElementId) { - CollectionElement collectionElement = collectionElementMapper.selectById(sketchElementId); - if (Objects.isNull(collectionElement)) { - throw new BusinessException("the.image.does.not.exist.please.reselect"); - } - String url = collectionElement.getUrl(); -// String caption = pythonService.generateSketchCaption(url); - GenerateCaptionVO recognized_caption = new GenerateCaptionVO("recognized caption"); - - return recognized_caption; - } - - @Override - @Transactional(rollbackFor = Exception.class) - public void generateThroughImageText(GenerateThroughImageTextDTO generateThroughImageTextDTO) { - // 1、获取用户信息 - Long accountId = generateThroughImageTextDTO.getUserId(); - String generateType = generateThroughImageTextDTO.getGenerateType(); - - // 2、判断必须入参是否为非空(在prepare阶段已校验) - Generate generate = new Generate(); - generate.setAccountId(accountId); - generate.setUniqueId(generateThroughImageTextDTO.getUniqueId()); - generate.setLevel1Type(generateThroughImageTextDTO.getLevel1Type()); - generate.setLevel2Type(generateThroughImageTextDTO.getLevel2Type()); - generate.setSeed(StringUtil.isNullOrEmpty(generateThroughImageTextDTO.getSeed()) ? "" : generateThroughImageTextDTO.getSeed()); - // 当level1type是sketchboard时,存数据库需要加上当前性别 - generate.setGenerateType(generate.getLevel1Type().equals(SKETCH_BOARD.getRealName()) ? - generateType + " (" + generateThroughImageTextDTO.getGender() + ")" : - generateType); - generate.setModelName(StringUtil.isNullOrEmpty(generateThroughImageTextDTO.getModelName()) ? ModelNameEnum.MODEL_0.getCode() : generateThroughImageTextDTO.getModelName()); - generate.setCreateDate(DateUtil.getByTimeZone(generateThroughImageTextDTO.getTimeZone())); - generate.setElementSource(StringUtil.isNullOrEmpty(generateThroughImageTextDTO.getDesignType()) ? null : generateThroughImageTextDTO.getDesignType()); - - String text = generateThroughImageTextDTO.getText(); - generate.setText(text); - Long elementId = generateThroughImageTextDTO.getCollectionElementId(); -// validateGeneraType(generate, text, elementId); - if (!StringUtil.isNullOrEmpty(text)) { - text = modifyPrompt(text, generate, generateThroughImageTextDTO.getLevel1Type(), generateThroughImageTextDTO.getAgeGroup()); - } - - // todo 这一步现在还是有必要的吗? - // 2.1 sketch或print在t_collection_element表/t_library表中的信息是否需要更新 如 level2Type - CollectionElement collectionElement = collectionElementService.editLevel2Type(elementId, generateThroughImageTextDTO.getLevel2Type(), generateThroughImageTextDTO.getDesignType()); - - // 3、向模型发起请求 - String mode = GenerateModeEnum.TEXT.getValue().equals(generateType) ? - GenerateModeEnum.TEXT.getType() : - GenerateModeEnum.TEXT_IMAGE.getType(); - String category = generateThroughImageTextDTO.getLevel1Type().equals(SKETCH_BOARD.getRealName()) ? "sketch" : - generateThroughImageTextDTO.getLevel1Type().equals(PRINT_BOARD.getRealName()) ? "print" : "moodboard"; - String path = CommonConstant.GENERATE_PATH; - String port = generateServicePort; - String jsonString = ""; - HashMap params = new HashMap<>(); - String version = null; - if (!StringUtil.isNullOrEmpty(generateThroughImageTextDTO.getModelName()) && generateThroughImageTextDTO.getModelName().equals("high")) { - version = "high"; - params.put("version", "high"); - } else if (!StringUtil.isNullOrEmpty(generateThroughImageTextDTO.getModelName()) && generateThroughImageTextDTO.getModelName().equals("fast")) { - version = "fast"; - params.put("version", "fast"); - } - // 3.1 确定不同类型的印花分别调哪个接口 - if (generateThroughImageTextDTO.getLevel1Type().equals(PRINT_BOARD.getRealName())) { - switch (generateThroughImageTextDTO.getLevel2Type()) { - case "Logo": - path = CommonConstant.GENERATE_SINGLE_LOGO; - params.put("tasks_id", generateThroughImageTextDTO.getUniqueId()); - params.put("prompt", text); - params.put("seed", generateThroughImageTextDTO.getSeed()); - jsonString = JSON.toJSONString(params, SerializerFeature.WriteMapNullValue); - break; - case "Slogan": - path = CommonConstant.GENERATE_SLOGAN; - port = CommonConstant.PYTHON_PORT_9997; - params.put("num_point", "16"); - params.put("tasks_id", generateThroughImageTextDTO.getUniqueId()); - params.put("prompt", text); - params.put("image_url", collectionElement.getUrl()); - jsonString = JSON.toJSONString(params, SerializerFeature.WriteMapNullValue); - break; - case "Pattern": - GenerateToPythonDTO generateToPythonDTO = new GenerateToPythonDTO(generateThroughImageTextDTO.getUniqueId(), text, Objects.isNull(collectionElement) ? "" : collectionElement.getUrl(), - mode, category, generateThroughImageTextDTO.getGender(), version); - jsonString = JSON.toJSONString(generateToPythonDTO, SerializerFeature.WriteMapNullValue); - } - } else { - GenerateToPythonDTO generateToPythonDTO = new GenerateToPythonDTO(generateThroughImageTextDTO.getUniqueId(), text, Objects.isNull(collectionElement) ? "" : collectionElement.getUrl(), - mode, category, generateThroughImageTextDTO.getGender(), version); - jsonString = JSON.toJSONString(generateToPythonDTO, SerializerFeature.WriteMapNullValue); - } - - - Boolean requestResult = pythonService.generateSketchOrPrint(jsonString, port, path); - - // 4、将请求信息落库,将本次generate的请求信息添加到t_generate表中 - save(generate); - - // 5、将本次请求存入redis - String key = generateResultKey + ":" + generateThroughImageTextDTO.getUniqueId(); - String status; - if (requestResult) { - status = "Executing"; - } else { - status = "Fail"; - } - GenerateResultVO generateResultVO = new GenerateResultVO(generateThroughImageTextDTO.getUniqueId(), null, null, status); - redisUtil.addToString(key, new Gson().toJson(generateResultVO), CommonConstant.GENERATE_RESULT_EXPIRE_TIME); - - } - - @Override - @Transactional(rollbackFor = Exception.class) - public void processGenerateResult(String taskId, String url, String category) { - // 1、处理模型返回的数据 - GenerateDetail generateDetail = new GenerateDetail(); - GenerateCollectionItemVO generateCollectionItemVO = new GenerateCollectionItemVO(); - Generate generate; - try { - generate = selectByUniqueId(taskId); - } catch (MybatisPlusException e) { - log.error(e.getMessage()); - if (e.getMessage().equals("One record is expected, but the query result is multiple records")) { - generate = selectListByUniqueId(taskId).get(0); - } else { - throw new BusinessException("There are some problems with database query, please try again."); - } - } -// Generate generate = selectByUniqueId(taskId); - String md5 = MD5Utils.encryptFile(minioUtil.getPreSignedUrl(url, 24 * 60), Boolean.FALSE); - // 通过MD5值和level1Type,判断不同level1Type下相同的图片是否被like过 - List> libraryIdList = generateDetailMapper.getLibraryIdThroughMD5(md5, generate.getLevel1Type()); - if (!libraryIdList.isEmpty()) { - generateDetail.setIsLike((byte) 1); - generateDetail.setLibraryId(libraryIdList.get(0).get("library_id")); - generateCollectionItemVO.setIsLiked(Boolean.TRUE); - } - generateDetail.setUrl(url); - generateDetail.setGenerateId(generate.getId()); - generateDetail.setCreateDate(LocalDateTime.now()); - generateDetail.setMd5(md5); - // 将相应的url保存到数据库 - generateDetailMapper.insert(generateDetail); - - String uuid = taskId.substring(0, taskId.substring(0, taskId.lastIndexOf("-")).lastIndexOf("-")); - String key = generateResultKey + ":" + taskId; - String imageName = url.substring(url.lastIndexOf("/") + 1); - String status = imageName.equals("white_image.jpg") ? "Invalid" : "Success"; - if (StringUtil.isNullOrEmpty(category)){ - Generate generateRecord = selectByUniqueId(taskId); - category = generateRecord.getLevel2Type(); - } - GenerateResultVO generateResultVO = new GenerateResultVO(taskId, generateDetail.getId(), url, status, category); - // 更新redis - redisUtil.addToString(key, new Gson().toJson(generateResultVO), CommonConstant.GENERATE_RESULT_EXPIRE_TIME); - - // 执行积分扣除 - // ** 注:如果生成的图片都是空白 则不扣积分 - if (!status.equals("Invalid")) { - String accountId = taskId.substring(taskId.lastIndexOf("-") + 1); - Boolean flag = creditsService.taskCreditsDeduction(Long.parseLong(accountId), uuid); - if (flag) creditsService.updateChangedCredits(accountId, uuid); - } - } - - @Override - @Transactional(rollbackFor = Exception.class) - public void processToProductImageResult(String taskId, String url, String category) { - QueryWrapper qw = new QueryWrapper<>(); - qw.lambda().eq(ToProductImageResult::getTaskId, taskId); - List toProductImageResults = toProductImageResultMapper.selectList(qw); - if (CollectionUtils.isEmpty(toProductImageResults)) { - return; -// throw new BusinessException(""); - } - ToProductImageResult toProductImageResult = toProductImageResults.get(0); - toProductImageResult.setUrl(url); - toProductImageResult.setStatus("Success"); -// toProductImageResult.setResultType("ToProductImage"); - toProductImageResultMapper.updateById(toProductImageResult); - - String key = toProductImageResultKey + ":" + taskId; - String imageName = url.substring(url.lastIndexOf("/") + 1); - String status = imageName.equals("white_image.jpg") ? "Invalid" : "Success"; - GenerateResultVO generateResultVO = new GenerateResultVO(taskId, toProductImageResult.getId(), url, status, category); - redisUtil.addToString(key, new Gson().toJson(generateResultVO), CommonConstant.GENERATE_RESULT_EXPIRE_TIME); - - Long accountId = Long.parseLong(taskId.substring(taskId.lastIndexOf("-") + 1)); - if (!status.equals("Invalid")) { - // 4、扣除积分 - Boolean b = creditsService.taskCreditsDeduction(accountId, taskId); - // 3、记录积分变更 - if (b) creditsService.insertToCreditsDetail(accountId, - TO_PRODUCT_IMAGE.getName(), - TO_PRODUCT_IMAGE.getValue(), - "negative", null); - } - } - - public void updateToProductTaskStatus(String taskId, String status){ - QueryWrapper qw = new QueryWrapper<>(); - qw.lambda().eq(ToProductImageResult::getTaskId, taskId); - List toProductImageResults = toProductImageResultMapper.selectList(qw); - if (!CollectionUtils.isEmpty(toProductImageResults)) { - ToProductImageResult toProductImageResult = toProductImageResults.get(0); - if (StringUtil.isNullOrEmpty(toProductImageResult.getStatus()) || !toProductImageResult.getStatus().equals(status)){ - toProductImageResult.setStatus(status); - toProductImageResultMapper.updateById(toProductImageResult); - } - } - } - - private void validateGeneraType(Generate generate, String text, Long elementId) { - String generateType = ""; - if (StringUtil.isNullOrEmpty(text.trim()) && Objects.isNull(elementId)) { - throw new BusinessException("please.input.the.caption.or.choose.an.image"); - } else if (!StringUtil.isNullOrEmpty(text.trim()) && !Objects.isNull(elementId)) { - generateType = "text-image"; - generate.setText(text); - generate.setElementId(elementId); - } else if (!StringUtil.isNullOrEmpty(text.trim())) { - generateType = "text"; - generate.setText(text); - } else if (!Objects.isNull(elementId)) { - generateType = "image"; - generate.setElementId(elementId); - } - generate.setGenerateType(generateType); - - } - - private String modifyPrompt(String userInput, Generate generate, String level1Type, String ageGroup) { - String text = ""; - String prefix = ""; - if (userInput.startsWith("Painting Style") - || userInput.startsWith("Illustration Style") - || userInput.startsWith("Real Style")) { - prefix = userInput.substring(0, userInput.indexOf(",")) + ", "; - userInput = userInput.substring(userInput.indexOf(",") + 1); - } - String translated = prefix + pythonService.promptTranslate(userInput); - switch (level1Type) { - case "Moodboard": - text = translated + ",high quality"; -// generate.setText(text); - break; - case "Printboard": - case "Pattern": - text = translated; - /*if (prefix.contains("Painting Style")) { - text = "Picasso,increased color saturation,increased glossiness," + translated + ", fabric print, high quality"; - } else if (prefix.contains("Illustration Style")) { - text = "Flat coating,romantic,soft,pencil strokes,accentuating and widening the depth of pencil strokes,paper patterns,block colors,crayons,reducing image contrast,and hand drawn painting marks," + translated + ", fabric print, high quality"; - } else if (prefix.contains("Real Style")) { - text = "Hyper realism,3d,deepened projection,increased permutation value,increased concavity and convexity value," + translated + ", fabric print, high quality"; - } else { - text = translated; - }*/ -// text = userInput + ", fabric print, high quality"; -// generate.setText(text); - break; - case "Sketchboard": -// text = "clear lines, simple outlines monochrome white vector image of " + translated + ", no background, sketch flat, front view display, best quality, ultra-high resolution 8k"; - text = "a single item of sketch of " + translated + ", 4k, white background"; - if (!StringUtil.isNullOrEmpty(ageGroup) && ageGroup.equals("Child")) { - text = text + ", Children's clothing"; - } -// generate.setText(text); - default: - text = translated; - } - return text; - } - - @Override - @Transactional(rollbackFor = Exception.class) - public GenerateLikeVO generateLike(GenerateLikeDTO generateLikeDTO) { - // 1、判断参数是否正确 - // 1.1 必须参数是否非空 - if (SKETCH_BOARD.getRealName().equals(generateLikeDTO.getLevel1Type())) { - if (StringUtil.isNullOrEmpty(generateLikeDTO.getLevel2Type())) { - throw new BusinessException("level2Type.cannot.be.empty"); - } - if (StringUtil.isNullOrEmpty(generateLikeDTO.getGender())) { - throw new BusinessException("gender.cannot.be.empty"); - } - } - if (PRINT_BOARD.getRealName().equals(generateLikeDTO.getLevel1Type())) { - if (StringUtil.isNullOrEmpty(generateLikeDTO.getLevel2Type())) { - throw new BusinessException("level2Type.cannot.be.empty"); - } - } - // 1.2 判断参数是否真实有效 - Long generateDetailId = generateLikeDTO.getGenerateDetailId(); - GenerateDetail generateDetail = generateDetailMapper.selectById(generateDetailId); - if (Objects.isNull(generateDetail)) { - throw new BusinessException("generateItem.does.not.exist"); - } - Generate generate = getById(generateDetail.getGenerateId()); - if (!generateLikeDTO.getLevel1Type().equals(generate.getLevel1Type())) { - throw new BusinessException("level1Type.does.not.match"); - } - - // 2、将like的图片信息存入library - // 2.1、不能重复喜欢 - // 2.1.1 判断该图片是否被喜欢过 - Library libraryDetail = libraryService.getById(generateDetail.getLibraryId()); - if ((Objects.nonNull(generateDetail.getLibraryId()) && !generateDetail.getLibraryId().equals(0L)) - || Objects.nonNull(libraryDetail)) { - throw new BusinessException("duplicate.likes.are.not.allowed"); - } - - // todo 2.1.2、判断library中是否有相同MD5的图片 - - // 2.2、添加到library - AuthPrincipalVo userInfo = UserContext.getUserHolder(); - Long accountId = userInfo.getId(); - Library library = setLibrary(accountId, generateLikeDTO, generateDetail.getUrl()); - libraryService.save(library); - - // 3、更新generateDetail表的isLike列和libraryId列 - updateLikeStatus(generateLikeDTO.getGenerateDetailId(), (byte) 1, library.getId(), generateLikeDTO.getTimeZone()); - - return new GenerateLikeVO(library.getId()); - } - - @Override - @Transactional(rollbackFor = Exception.class) - public Boolean generateDislike(Long generateDetailId, String timeZone) { - // 1、确定generateDetail中是否有这条记录 - GenerateDetail generateDetail = generateDetailMapper.selectById(generateDetailId); - if (Objects.isNull(generateDetail)) { - throw new BusinessException("generateItem.does.not.exist"); - } - - // 2、修改generateDetail表中的isLike、libraryId字段 - updateLikeStatus(generateDetailId, (byte) 0, 0L, timeZone); - - // 3、确定library中是否添加该条记录 - Library libraryDetail = libraryService.getById(generateDetail.getLibraryId()); - if (Objects.isNull(libraryDetail)) { - return Boolean.TRUE; - } - - // 4、删除library相关记录 - libraryService.removeById(libraryDetail.getId()); - - // 5、返回成功 - return Boolean.TRUE; - } - - public Library setLibrary(Long accountId, GenerateLikeDTO generateLikeDTO, String imageUrl) { - Library library = new Library(); - library.setAccountId(accountId); - library.setLevel1Type(generateLikeDTO.getLevel1Type()); - library.setLevel2Type(StringUtil.isNullOrEmpty(generateLikeDTO.getLevel2Type()) ? null : generateLikeDTO.getLevel2Type()); - library.setLevel3Type(StringUtil.isNullOrEmpty(generateLikeDTO.getGender()) ? null : generateLikeDTO.getGender()); - library.setName(DateUtil.getTimeStamp(generateLikeDTO.getTimeZone()) + "_N_G"); - library.setUrl(imageUrl); - try { - library.setMd5(MD5Utils.encryptFile(minioUtil.download(imageUrl))); - } catch (MinioException e) { - throw new RuntimeException(e); - } catch (IOException e) { - throw new RuntimeException(e); - } - library.setCreateDate(DateUtil.getByTimeZone(generateLikeDTO.getTimeZone())); - return library; - } - - public void updateLikeStatus(Long generateDetailId, Byte hasLike, Long libraryId, String timeZone) { - QueryWrapper queryWrapper = new QueryWrapper<>(); - queryWrapper.eq("id", generateDetailId); - - GenerateDetail generateDetail = new GenerateDetail(); - generateDetail.setIsLike(hasLike); - generateDetail.setLibraryId(libraryId); - generateDetail.setUpdateDate(DateUtil.getByTimeZone(timeZone)); - generateDetailMapper.update(generateDetail, queryWrapper); - } - - // 避免循环注入,已移到libraryService中 - public void updateLikeStatusBatch(List generateDetailIdList, Byte hasLike, Long libraryId, String timeZone) { - QueryWrapper queryWrapper = new QueryWrapper<>(); - queryWrapper.in("id", generateDetailIdList); - - GenerateDetail generateDetail = new GenerateDetail(); - generateDetail.setIsLike(hasLike); - generateDetail.setLibraryId(libraryId); - generateDetail.setUpdateDate(DateUtil.getByTimeZone(timeZone)); - - generateDetailMapper.update(generateDetail, queryWrapper); - } - - // 避免循环注入,已移到libraryService中 - public List selectBatchByLibraryId(List libraryId) { - QueryWrapper qw = new QueryWrapper<>(); - qw.in("library_id", libraryId); - - return generateDetailMapper.selectList(qw); - } - - @Override - public PrepareForGenerateVO prepareForGenerate(GenerateThroughImageTextDTO generateDTO) { - // 1、参数检查,判断必须参数是否为空 - validateRequiredParams(generateDTO); - - // 2、处理特殊模型情况(wx/flux) - if (isWxModel(generateDTO)) { - return handleWxModelGeneration(generateDTO); - } - if (isFluxPatternModel(generateDTO)) { - return handleFluxPatternGeneration(generateDTO); - } - - // 3、处理标准生成流程 - return handleStandardGeneration(generateDTO); - } - -// ============== 以下是辅助方法 ============== - - /** - * 参数校验 - */ - private void validateRequiredParams(GenerateThroughImageTextDTO generateDTO) { - if (Objects.isNull(generateDTO.getUserId())) { - throw new BusinessException("userId cannot be empty"); - } - - // Printboard必须要有level2Type - if (generateDTO.getLevel1Type().equals(PRINT_BOARD.getRealName()) - && StringUtil.isNullOrEmpty(generateDTO.getLevel2Type())) { - throw new BusinessException("level2Type.cannot.be.empty"); - } - } - - /** - * 判断是否为wx模型 - */ - private boolean isWxModel(GenerateThroughImageTextDTO generateDTO) { - return !StringUtil.isNullOrEmpty(generateDTO.getModelName()) - && "wx".equals(generateDTO.getModelName()); - } - - /** - * 处理wx模型生成 - */ - private PrepareForGenerateVO handleWxModelGeneration(GenerateThroughImageTextDTO generateDTO) { - String taskId = createAsyncTask(generateDTO); - processCreditDeduction(generateDTO.getUserId(), taskId, CreditsEventsEnum.WX_TEXT2IMG); - return new PrepareForGenerateVO(Collections.singletonList(taskId), 2); - } - - /** - * 判断是否为flux模型且类型为Pattern - */ - private boolean isFluxPatternModel(GenerateThroughImageTextDTO generateDTO) { - return !StringUtil.isNullOrEmpty(generateDTO.getModelName()) - && "flux".equals(generateDTO.getModelName()) - && "Pattern".equals(generateDTO.getLevel2Type()); - } - - /** - * 处理flux pattern生成 - */ - private PrepareForGenerateVO handleFluxPatternGeneration(GenerateThroughImageTextDTO generateDTO) { - // 获取图片路径 - String imagePath = getImagePathForFlux(generateDTO); - - // 创建生成任务 - String taskId = flux(PATTERN, generateDTO.getText(), imagePath, false); - - // 保存生成记录 - saveGenerateRecord(generateDTO, taskId, imagePath); - - // 处理积分扣除 - processCreditDeduction(generateDTO.getUserId(), taskId, CreditsEventsEnum.FLUX_IMG2IMG); - - return new PrepareForGenerateVO(Collections.singletonList(taskId), 2); - } - - /** - * 获取flux模型需要的图片路径 - */ - private String getImagePathForFlux(GenerateThroughImageTextDTO generateDTO) { - if (Objects.isNull(generateDTO.getCollectionElementId()) - || StringUtil.isNullOrEmpty(generateDTO.getDesignType())) { - return null; - } - - switch (generateDTO.getDesignType()) { - case "collection": - CollectionElement element = collectionElementMapper.selectById(generateDTO.getCollectionElementId()); - return element != null ? element.getUrl() : null; - case "library": - Library library = libraryService.getById(generateDTO.getCollectionElementId()); - return library != null ? library.getUrl() : null; - default: - return null; - } - } - - /** - * 保存生成记录 - */ - private void saveGenerateRecord(GenerateThroughImageTextDTO generateDTO, String taskId, String imagePath) { - Generate generate = CopyUtil.copyObject(generateDTO, Generate.class); - generate.setAccountId(generateDTO.getUserId()); - generate.setUniqueId(taskId); - generate.setElementSource(generateDTO.getDesignType()); - generate.setElementId(generateDTO.getCollectionElementId()); - - // 确定生成类型 - String generateType = determineGenerateType(generateDTO); - generate.setGenerateType(generateType); - generate.setModelName("flux"); - generate.setCreateDate(new Date()); - - save(generate); - } - - /** - * 确定生成类型 - */ - private String determineGenerateType(GenerateThroughImageTextDTO generateDTO) { - if (Objects.nonNull(generateDTO.getCollectionElementId())) { - return StringUtil.isNullOrEmpty(generateDTO.getText()) ? "image" : "text-image"; - } - return "text"; - } - - /** - * 处理标准生成流程 - */ - private PrepareForGenerateVO handleStandardGeneration(GenerateThroughImageTextDTO generateDTO) { - // 确定积分事件和生成次数 - GenerationConfig config = determineGenerationConfig(generateDTO); - - // 校验积分是否足够 - validateCredits(config.creditsEvent); - - // 创建生成任务 - List taskIds = createGenerationTasks(generateDTO, config.times); - - // 处理积分扣除(使用第一个任务的UUID前缀) - processCreditDeduction(generateDTO.getUserId(), taskIds.get(0).split("-")[0], config.creditsEvent); - - return new PrepareForGenerateVO(taskIds, 2); - } - - /** - * 确定生成配置(积分事件和生成次数) - */ - private GenerationConfig determineGenerationConfig(GenerateThroughImageTextDTO generateDTO) { - CreditsEventsEnum creditsEvent = CreditsEventsEnum.OTHER; - int times = 4; - - // 根据不同类型确定配置 - // high -> 生成图片质量高,但生成速度慢,每次生成只返回一张图片 - // fast -> 生成图片质量低,但生成速度快,每次生成返回四张图片 - switch (generateDTO.getLevel1Type()) { - case "Printboard": - GenerationConfig generationConfig = handlePrintboardConfig(generateDTO); - creditsEvent = generationConfig.creditsEvent; - times = generationConfig.times; - break; - case "Moodboard": - creditsEvent = CreditsEventsEnum.MOOD_BOARD; - if (isHighModel(generateDTO)) { - creditsEvent = CreditsEventsEnum.LOCAL_TEXT2IMG_HIGH; - times = 1; - } - break; - case "Sketchboard": - creditsEvent = CreditsEventsEnum.SKETCH_BOARD; - if (isHighModel(generateDTO)) { - creditsEvent = CreditsEventsEnum.LOCAL_TEXT2IMG_HIGH; - times = 1; - } - break; - } - - return new GenerationConfig(creditsEvent, times); - } - - /** - * 处理Printboard的特殊配置 - */ - private GenerationConfig handlePrintboardConfig(GenerateThroughImageTextDTO generateDTO) { - String level2Type = generateDTO.getLevel2Type(); - CreditsEventsEnum creditsEvent = CreditsEventsEnum.OTHER; - int times = 4; - - if (!CollectionLevel2TypeEnum.printType().contains(level2Type)) { - throw new BusinessException("unknown.parameter.level2Type"); - } - - switch (level2Type) { - case "Pattern": - // Pattern参数校验 - Generate generate = new Generate(); - validateGeneraType(generate, generateDTO.getText(), generateDTO.getCollectionElementId()); - generateDTO.setGenerateType(generate.getGenerateType()); - - creditsEvent = CreditsEventsEnum.PATTERN; - if (isHighModel(generateDTO)) { - creditsEvent = CreditsEventsEnum.LOCAL_TEXT2IMG_HIGH; - times = 1; - } - break; - - case "Slogan": - validateSloganParams(generateDTO); - processSloganImage(generateDTO); - creditsEvent = CreditsEventsEnum.SLOGAN; - times = 1; - break; - - case "Logo": - validateLogoParams(generateDTO); - generateDTO.setSeed(String.valueOf(random.nextInt(501))); - creditsEvent = CreditsEventsEnum.LOGO; - times = 1; - break; - } - - return new GenerationConfig(creditsEvent, times); - } - - /** - * 校验Slogan参数 - */ - private void validateSloganParams(GenerateThroughImageTextDTO generateDTO) { - if (StringUtil.isNullOrEmpty(generateDTO.getSloganBase64())) { - log.error("Printboard-Slogan模式下,slogan image为空"); - throw new BusinessException("slogan.image.cannot.be.empty"); - } - if (StringUtil.isNullOrEmpty(generateDTO.getText())) { - log.error("Printboard-Slogan模式下,slogan text为空"); - throw new BusinessException("slogan.style.cannot.be.empty"); - } - } - - /** - * 处理Slogan图片上传 - */ - private void processSloganImage(GenerateThroughImageTextDTO generateDTO) { - // 上传图片到服务器 - String path = minioUtil.base64UploadToPath(generateDTO.getSloganBase64(), sloganBucket, null); - String name = path.substring(path.lastIndexOf("/") + 1, path.lastIndexOf(".")); - - // 保存到数据库 - CollectionElement element = new CollectionElement(); - element.setAccountId(generateDTO.getUserId()); - element.setCollectionId(0L); - element.setLevel1Type(PRINT_BOARD.getRealName()); - element.setLevel2Type(CollectionLevel2TypeEnum.SLOGAN.getRealName()); - element.setName(name); - element.setUrl(path); - element.setHasPin((byte) 0); - element.setMd5(MD5Utils.encryptFile(minioUtil.getPreSignedUrl(path, 24 * 60), Boolean.FALSE)); - element.setCreateDate(DateUtil.getByTimeZone(generateDTO.getTimeZone())); - collectionElementService.save(element); - - // 更新DTO - generateDTO.setCollectionElementId(element.getId()); - generateDTO.setSloganBase64(null); - generateDTO.setDesignType("collection"); - } - - /** - * 校验Logo参数 - */ - private void validateLogoParams(GenerateThroughImageTextDTO generateDTO) { - if (StringUtil.isNullOrEmpty(generateDTO.getText().trim())) { - throw new BusinessException("please.input.the.prompt"); - } - } - - /** - * 判断是否为high模型 - */ - private boolean isHighModel(GenerateThroughImageTextDTO generateDTO) { - return !StringUtil.isNullOrEmpty(generateDTO.getModelName()) - && "high".equals(generateDTO.getModelName()); - } - - /** - * 校验积分是否足够 - */ - private void validateCredits(CreditsEventsEnum creditsEvent) { - if (!creditsService.creditsPreDeduction(creditsEvent, 1)) { - throw new BusinessException("remaining.credits.insufficient", ResultEnum.WARNING.getCode()); - } - } - - /** - * 创建生成任务 - */ - private List createGenerationTasks(GenerateThroughImageTextDTO generateDTO, int times) { - String uuid = UUID.randomUUID().toString(); - List taskIds = new ArrayList<>(); - - // 特殊处理:某些情况下需要清空modelName - if ("Printboard".equals(generateDTO.getLevel1Type()) - && !"Pattern".equals(generateDTO.getLevel2Type())) { - // Logo 和 Slogan 没有模型可选 - generateDTO.setModelName(null); - } - - for (int i = 1; i <= times; i++) { - String taskId = uuid + "-" + i + "-" + generateDTO.getUserId(); - taskIds.add(taskId); - generateDTO.setUniqueId(taskId); - - // 序列化为JSON - String jsonString = JSON.toJSONString(generateDTO); - - // 加入Redis队列 - addToRedisQueue(taskId, jsonString); - } - - return taskIds; - } - - /** - * 添加到Redis队列 - */ - private void addToRedisQueue(String taskId, String jsonString) { - // 加入排队队列 -// Double maxScore = redisUtil.getMaxScore(consumptionOrderKey); -// redisUtil.addToZSet(consumptionOrderKey, taskId, maxScore); - - // 加入结果映射 - String key = generateResultKey + ":" + taskId; - GenerateResultVO resultVO = new GenerateResultVO(taskId, null, null, "Waiting"); - redisUtil.addToString(key, new Gson().toJson(resultVO), CommonConstant.GENERATE_RESULT_EXPIRE_TIME); - - // 发布到MQ - rabbitMQService.publishMessageToGenerate(jsonString); - } - - /** - * 处理积分扣除 - */ - private void processCreditDeduction(Long userId, String taskId, CreditsEventsEnum creditsEvent) { - // 添加到Redis - creditsService.addRecordToCreditsDeduction(userId, taskId, creditsEvent); - // 预插入到数据库 - creditsService.preInsert(userId, creditsEvent.getName(), taskId, Boolean.TRUE, null); - } - -// ============== 配置类 ============== - - /** - * 生成任务配置类 - * 包含积分事件类型和生成次数 - */ - private static class GenerationConfig { - final CreditsEventsEnum creditsEvent; - final int times; - - GenerationConfig(CreditsEventsEnum creditsEvent, int times) { - this.creditsEvent = creditsEvent; - this.times = times; - } - } - - @Override - public Long getRankPosition(String uniqueId) { - // rank 从0开始 - return redisUtil.getRank(consumptionOrderKey, uniqueId); - } - - @Override - public List getGenerateResultList(List taskIdList) { - List results = new ArrayList<>(); - Set collect = new HashSet<>(); - boolean flag = true; - String type = null; - for (String taskId : taskIdList) { - String key = generateResultKey + ":" + taskId; - GenerateResultVO generateResultVO = new Gson().fromJson(redisUtil.getFromString(key), GenerateResultVO.class); - - if (flag) { - type = resolveModelType(taskId, null); - flag = false; - } - // 暂定万象每次生成1个 - if (type.equals("wx")) { - return Collections.singletonList(getAsyncTaskResult(taskId)); - } else if (type.equals("freepik")) { - results.add(generateResultVO); - continue; - } else if (type.equals("flux")) { - results.add(getFluxResultAndSave(taskId)); - continue; - } - - if (generateResultVO != null && !StringUtil.isNullOrEmpty(generateResultVO.getUrl())) { - String url = generateResultVO.getUrl(); - if (url.substring(url.lastIndexOf("/") + 1).equals("white_image.jpg")) { - generateResultVO.setStatus("Invalid"); - } else { - generateResultVO.setUrl(minioUtil.getPreSignedUrl(url, CommonConstant.MINIO_IMAGE_EXPIRE_TIME)); - } - } else if (generateResultVO == null) { - generateResultVO = new GenerateResultVO(); - } - - if (!StringUtil.isNullOrEmpty(generateResultVO.getStatus())) { - collect.add(generateResultVO.getStatus()); - } - results.add(generateResultVO); - } - - if (taskIdList.size() == 4 && collect.size() == 1 && collect.contains("Fail")) { - log.info("当前4个生成结果均为失败"); - throw new BusinessException("generate.result.below.standard"); - } - return results; - } - - - public Generate selectByUniqueId(String uniqueId) { - QueryWrapper qw = new QueryWrapper<>(); - qw.eq("unique_id", uniqueId); - - return getOne(qw); - } - - public List selectListByUniqueId(String uniqueId) { - QueryWrapper qw = new QueryWrapper<>(); - qw.eq("unique_id", uniqueId).orderByDesc("id"); - - return baseMapper.selectList(qw); - } - - @Override - @Transactional(rollbackFor = Exception.class) - public void cancelGenerate(Long userId, List uniqueIdList, String timeZone, String type) { - // todo 取消待优化 - uniqueIdList.forEach(uniqueId -> { - // 1、将需要取消的唯一id加入redis,以便及时取消生成 - redisUtil.addToSet(cancelSetKey, uniqueId); - - /*// 1、确认当前消息是否还在排队中 - Boolean exists = redisUtil.isElementExistsInZSet(consumptionOrderKey, uniqueId); - Boolean flag = Boolean.FALSE; - if (exists) flag = redisUtil.getRank(consumptionOrderKey, uniqueId) > 1L ? Boolean.TRUE : Boolean.FALSE; - // 不管flag的默认值是true还是false,只要exists为false,&& 将短路 - if (exists && flag) { - // 1.1、将需要取消的唯一id加入redis,以便及时取消生成 - redisUtil.addToSet(cancelSetKey, uniqueId); - // 1.2 将需要取消的id从redis的ConsumptionOrder中删除 - redisUtil.removeFromZSet(consumptionOrderKey, uniqueId); - } else { - // 2、判断该消息是否异常 - boolean hasKey = redisUtil.isElementExistsInMap(exceptionMapKey, uniqueId); - // 3、判断该消息是否已经消费结束 - Boolean existsInResult = redisUtil.isElementExistsInMap(resultMapKey, uniqueId); - if (!hasKey && !existsInResult) { - // 设置取等待状态为false - AsyncCallerUtil.waitingStatus.put(uniqueId, false); - // 3、直接发送取消请求到python端 - pythonService.cancelGenerateTask(uniqueId); - } - }*/ - String path; - if (type.equals("Logo")) { - path = CommonConstant.GENERATE_LOGO_SINGLE_CANCEL; - } else if (type.equals("PoseTransformation")) { - path = CommonConstant.POSE_TRANSFORMATION_CANCEL; - } else { - path = CommonConstant.GENERATE_CANCEL; - } - - String key = generateResultKey + ":" + uniqueId; - GenerateResultVO generateResultVO = new Gson().fromJson(redisUtil.getFromString(key), GenerateResultVO.class); - if (Objects.isNull(generateResultVO)) { - log.warn("任务不存在,无法取消"); - return; - } - // 判断当前task的状态是不是Fail - if (!generateResultVO.getStatus().equals("Fail")) { - // 2、不是,直接发送取消请求到python端 - pythonService.cancelGenerateTask(uniqueId, path); - // 3、更改result中当前taskId的状态 - redisUtil.addToString(key, new Gson().toJson(new GenerateResultVO(uniqueId, null, null, "Cancelled")), CommonConstant.GENERATE_RESULT_EXPIRE_TIME); - } - - // 3、考虑加一张表,专门用于记录哪些用户在什么时间进行了取消操作,包括已经异常的请求 - GenerateCancel generateCancel = new GenerateCancel(userId, uniqueId, DateUtil.getByTimeZone(timeZone)); - generateCancelMapper.insert(generateCancel); - }); - - - } - - @Override - public void processRelightResult(String taskId, String url, String category) { - QueryWrapper qw = new QueryWrapper<>(); - qw.lambda().eq(ToProductImageResult::getTaskId, taskId); - List toProductImageResults = toProductImageResultMapper.selectList(qw); - if (CollectionUtils.isEmpty(toProductImageResults)) { - return; -// throw new BusinessException(""); - } - ToProductImageResult toProductImageResult = toProductImageResults.get(0); - if (toProductImageResult.getBrightenValue() != null && toProductImageResult.getBrightenValue() != 1.0) { - pythonService.bright(url, toProductImageResult.getBrightenValue()); - } - toProductImageResult.setUrl(url); - toProductImageResult.setStatus("Success"); -// toProductImageResult.setResultType("Relight"); - toProductImageResultMapper.updateById(toProductImageResult); - - String key = relightResultKey + ":" + taskId; - String imageName = url.substring(url.lastIndexOf("/") + 1); - String status = imageName.equals("white_image.jpg") ? "Invalid" : "Success"; - GenerateResultVO generateResultVO = new GenerateResultVO(taskId, toProductImageResult.getId(), url, status, category); - redisUtil.addToString(key, new Gson().toJson(generateResultVO), CommonConstant.GENERATE_RESULT_EXPIRE_TIME); - - Long accountId = Long.parseLong(taskId.substring(taskId.lastIndexOf("-") + 1)); - if (!status.equals("Invalid")) { - // 4、扣除积分 - Boolean b = creditsService.taskCreditsDeduction(accountId, taskId); - // 3、记录积分变更 - if (b) creditsService.insertToCreditsDetail(accountId, - CreditsEventsEnum.RELIGHT.getName(), - CreditsEventsEnum.RELIGHT.getValue(), - "negative", null); - } - } - - // 判断试用用户试用generate机会是否使用完毕 每个board 3次机会 - private int getTrialsCount(Long userId, String level1Type) { - List getGenerateList = getGenerateByAccountId(userId, level1Type); - int trialsCount; - if (getGenerateList.isEmpty()) { - trialsCount = 0; - } else if (getGenerateList.size() == 1 || (getGenerateList.size() >= 4 && getGenerateList.size() / 4 == 1)) { - trialsCount = 1; - } else if (getGenerateList.size() == 2 || (getGenerateList.size() >= 4 && getGenerateList.size() / 4 == 2)) { - trialsCount = 2; - } else { - trialsCount = 2; - } - return trialsCount; - } - - public List getGenerateByAccountId(Long accountId, String level1Type) { - QueryWrapper qw = new QueryWrapper<>(); - qw.eq("account_id", accountId); - qw.eq("level1_type", level1Type); - - return baseMapper.selectList(qw); - } - - public List> getCountByUserAndTime(String startTime, String endTime, List accountIdList) { - List> byTypeAndTime = baseMapper.getByTypeAndTime(startTime, endTime, accountIdList); - return byTypeAndTime; - } - - @Override - @Transactional(rollbackFor = Exception.class) - public GenerateResultVO imageToSketch(ImageToSketchDTO imageToSketchDTO, String collagePictureUrl, Long projectId) { - - Long accountId = UserContext.getUserHolder().getId(); - log.info("imageToSketch parameter : {}", imageToSketchDTO); - - // 检查积分是否够本次扣除 - CreditsEventsEnum event = CreditsEventsEnum.IMAGE_TO_SKETCH; - Boolean b = creditsService.checkCredits(accountId, event, 1); - if (!b) { - throw new BusinessException("remaining.credits.insufficient", ResultEnum.PROMPT.getCode()); - } - - String style = imageToSketchDTO.getStyle(); - String styleCode = style.equals(SketchStyle.THICK.getValue()) ? "1" : - style.equals(SketchStyle.MEDIUM.getValue()) ? "2" : - style.equals(SketchStyle.THIN.getValue()) ? "3" : "Custom"; - - // 线稿提取 - String sketchPath = requestSketchExtract(imageToSketchDTO, collagePictureUrl, accountId, styleCode); - // 存数据库 - Generate generate = saveExtractSketchRequest(imageToSketchDTO, collagePictureUrl, projectId, - accountId, styleCode, "local", "0"); - GenerateResultVO generateResultVO = saveExtractSketchResult(generate, sketchPath, imageToSketchDTO.getGender()); - // 积分扣除 - doCreditsSubtract(accountId, event); - - return generateResultVO; - } - - private String requestSketchExtract(ImageToSketchDTO imageToSketchDTO, String collagePictureUrl, - Long accountId, String styleCode) { - String imagePath; - if (StringUtil.isNullOrEmpty(collagePictureUrl)) { - CollectionElement collectionElement = collectionElementService.getById(imageToSketchDTO.getElementId()); - imagePath = collectionElement.getUrl(); - } else { - imagePath = collagePictureUrl; - } - - log.info(minioUtil.getPreSignedUrl(imagePath, CommonConstant.MINIO_IMAGE_EXPIRE_TIME)); - String imageName = UUID.randomUUID().toString(); - String objectName = accountId + "/imageToSketch/" + imageName; - - String styleImage; - if (!Objects.isNull(imageToSketchDTO.getStyleImageId())) { - CollectionElement styleElement = collectionElementService.getById(imageToSketchDTO.getElementId()); - styleImage = styleElement.getUrl(); - } else { - styleImage = ""; - } - String sketchPath = pythonService.imageToSketch(imagePath, userBucket, objectName, styleCode, styleImage); - log.info("初步图片提取结果:{}", sketchPath); - return sketchPath; - } - - private Generate saveExtractSketchRequest(ImageToSketchDTO imageToSketchDTO, String collagePictureUrl, - Long projectId, Long accountId, String styleCode, - String modelName, String taskId) { - // 存DB - Generate generate = new Generate(); - generate.setAccountId(accountId); - generate.setUniqueId(taskId); - generate.setLevel1Type(SKETCH_BOARD.getRealName()); - generate.setLevel2Type("ImageToSketch"); - generate.setElementSource("collection"); - generate.setElementId(imageToSketchDTO.getElementId()); - generate.setGenerateType("image(" + imageToSketchDTO.getGender() + ")"); - generate.setModelName(modelName); - generate.setSketchStyle(styleCode); - generate.setStyleImageElementId(imageToSketchDTO.getStyleImageId()); - generate.setProjectId(projectId); - generate.setInputImageUrl(collagePictureUrl); - generate.setCreateDate(new Date()); - baseMapper.insert(generate); - return generate; - } - - public GenerateResultVO saveExtractSketchResult(Generate generate, String sketchPath, String gender) { - // 将生成结果存入DB - GenerateDetail generateDetail = new GenerateDetail(); - generateDetail.setGenerateId(generate.getId()); - generateDetail.setUrl(sketchPath); - generateDetail.setIsLike((byte) 0); - generateDetail.setMd5(MD5Utils.encryptFile(minioUtil.getPreSignedUrl(sketchPath, 24 * 60), Boolean.FALSE)); - generateDetail.setCreateDate(LocalDateTime.now()); - generateDetailMapper.insert(generateDetail); - - String clothCategory = pythonService.getClothCategory(sketchPath, gender); - - return new GenerateResultVO(generate.getUniqueId(), generateDetail.getId(), - minioUtil.getPreSignedUrl(sketchPath, CommonConstant.MINIO_IMAGE_EXPIRE_TIME), "Success", clothCategory); - } - - public void doCreditsSubtract(Long accountId, CreditsEventsEnum event) { - BigDecimal existingCredits = accountService.getById(accountId).getCredits(); - BigDecimal subtract = existingCredits.subtract(new BigDecimal(event.getValue())); - accountService.updateCreditsAndEndTime(accountId, subtract.toString(), null); - creditsService.preInsert(accountId, event.getName(), null, Boolean.FALSE, event.getValue()); - } - - // 注入线程池(可在配置类中定义) - @Resource - private Executor asyncTaskExecutor; - - public String imageToSketchAsync(ImageToSketchDTO imageToSketchDTO, String collagePictureUrl, Long projectId) { - Long accountId = UserContext.getUserHolder().getId(); - log.info("imageToSketch parameter : {}", imageToSketchDTO); - - // 检查积分是否够本次扣除 -// CreditsEventsEnum event = CreditsEventsEnum.IMAGE_TO_SKETCH; - CreditsEventsEnum event = CreditsEventsEnum.IMAGE_TO_SKETCH_FLUX; - Boolean b = creditsService.checkCredits(accountId, event, 1); - if (!b) { - throw new BusinessException("remaining.credits.insufficient", ResultEnum.PROMPT.getCode()); - } - - // 生成唯一任务ID - String taskId; - if (!StringUtil.isNullOrEmpty(imageToSketchDTO.getModelName()) - && imageToSketchDTO.getModelName().equals("flux")) { - String imagePath; - // todo 拼贴图的线稿提取是否能用flux - if (StringUtil.isNullOrEmpty(collagePictureUrl)) { - CollectionElement collectionElement = collectionElementService.getById(imageToSketchDTO.getElementId()); - imagePath = collectionElement.getUrl(); - } else { - imagePath = collagePictureUrl; - } - taskId = flux(event, null, imagePath, false); - // 存数据库 - saveExtractSketchRequest(imageToSketchDTO, collagePictureUrl, projectId, - accountId, imageToSketchDTO.getStyle(), "flux", taskId); - - // 6、添加预扣除积分到redis - creditsService.addRecordToCreditsDeduction(accountId, taskId, event); - // 6.1 添加积分扣除记录到db - creditsService.preInsert(accountId, event.getName(), taskId, Boolean.TRUE, null); - - return taskId; - } - - taskId = UUID.randomUUID().toString(); - // 异步执行耗时操作(由于prompt提取耗时较长,页面暂时只提供flux生成,废弃以下部分) - CompletableFuture.runAsync(() -> { - try { - processImageToSketch(taskId, imageToSketchDTO, collagePictureUrl, projectId, accountId, event); - } catch (Exception e) { - log.error("异步处理图片转sketch失败, taskId: {}", taskId, e); - // 更新redis - redisUtil.addToString(generateResultKey + ":" + taskId, new Gson().toJson(new GenerateResultVO(taskId, "Fail")), CommonConstant.GENERATE_RESULT_EXPIRE_TIME); - } - }, asyncTaskExecutor); - return taskId; - } - - private void processImageToSketch(String taskId, ImageToSketchDTO imageToSketchDTO, - String collagePictureUrl, Long projectId, - Long accountId, CreditsEventsEnum event) throws IOException { - // 设置任务状态为处理中 - redisUtil.addToString(generateResultKey + ":" + taskId, new Gson().toJson(new GenerateResultVO(taskId, "Executing")), CommonConstant.GENERATE_RESULT_EXPIRE_TIME); - String style = imageToSketchDTO.getStyle(); - String styleCode = style.equals(SketchStyle.THICK.getValue()) ? "1" : - style.equals(SketchStyle.MEDIUM.getValue()) ? "2" : - style.equals(SketchStyle.THIN.getValue()) ? "3" : "Custom"; - // 请求记录存数据库 - Generate generate = saveExtractSketchRequest(imageToSketchDTO, collagePictureUrl, projectId, - accountId, styleCode, "freepik", taskId); - // 1、初步提取结果 - String sketchPath = requestSketchExtract(imageToSketchDTO, collagePictureUrl, accountId, styleCode); - // 2、获取输入图的描述 - String imageDescription = getImageDescription(sketchPath); - // 3、请求freepik reimage - String dataStr = reimagineFreePik(sketchPath, imageDescription, "vivid"); - if (StringUtil.isNullOrEmpty(dataStr)) { - throw new BusinessException("extract sketch failed"); - } - - JSONObject data = JSONUtil.parseObj(dataStr); - String upgradeImageUrl = data.getBeanList("generated", String.class).get(0); - String freepikTaskId = data.getStr("task_id"); - - // 4、下载图片 - byte[] bytes = downloadVideoOrImage(upgradeImageUrl); -// byte[] bytes = downloadWithProxy(upgradeImageUrl); - // 5、上传图片到minio保存 - String objectName = accountId + "/imageToSketch/" + freepikTaskId + ".png"; - minioUtil.uploadToMinio(bytes, userBucket, objectName, "image/png"); - // 6、保存结果到db - GenerateResultVO generateResultVO = saveExtractSketchResult(generate, userBucket + "/" + objectName, imageToSketchDTO.getGender()); - // 7、积分扣除 - doCreditsSubtract(accountId, event); - // 8、将结果存入Redis - redisUtil.addToString(generateResultKey + ":" + taskId, new Gson().toJson(generateResultVO), CommonConstant.GENERATE_RESULT_EXPIRE_TIME); - } - - // 对提取出来的sketch做调整 - // 输入 base64,以及 性别 分类,将图片添加到library - @Override - @Transactional(rollbackFor = Exception.class) - public GenerateResultVO modifySketch(GenerateModifyDTO generateModifyDTO) { - log.info("修改生成或library中的sketch或print"); - - // 提取常用参数 - Long accountId = UserContext.getUserHolder().getId(); - String base64 = generateModifyDTO.getBase64(); - String gender = generateModifyDTO.getGender(); - String category = generateModifyDTO.getCategory(); - Long originalId = generateModifyDTO.getOriginalId(); - String originalIdSource = generateModifyDTO.getOriginalIdSource(); - boolean isOverride = generateModifyDTO.getIsOverride(); - boolean isSketch = generateModifyDTO.getType().equals(SKETCH_BOARD.getRealName()); - - // 获取原始路径和可能的generateId - PathInfo pathInfo = getOriginalPathAndGenerateId(originalIdSource, originalId); - if (Objects.isNull(pathInfo)){ - throw new BusinessException("unknown sourceIdType", ResultEnum.PROMPT.getCode()); - } - - // 确定存储路径 - String storagePath = isOverride - ? pathInfo.originalPath.replaceFirst("^[^/]+/", "").replaceFirst("\\.[^.]+$", "") - : isSketch - ? accountId + "/sketchboard/" + gender + "/" + category + "/" + UUID.randomUUID() - : accountId + "/printboard/" + UUID.randomUUID(); - - // 上传到MinIO - String minioPath = minioUtil.base64UploadToPath(base64, userBucket, storagePath); - log.info("修改后的图片:{}", minioPath); - - // 保存到数据库并返回结果 - return originalIdSource.equals("Library") - ? handleLibrarySave(accountId, originalId, minioPath, category, gender, isOverride, generateModifyDTO.getType()) - : originalIdSource.equals("Generate") - ? handleGenerateSave(originalId, pathInfo.generateId, minioPath, category, isOverride) - : handleUploadSave(accountId, originalId, minioPath, category, isOverride, generateModifyDTO.getType()); - } - - private static class PathInfo { - String originalPath; - Long generateId; - - PathInfo(String originalPath, Long generateId) { - this.originalPath = originalPath; - this.generateId = generateId; - } - } - - private PathInfo getOriginalPathAndGenerateId(String originalIdSource, Long originalId) { - switch (originalIdSource) { - case "Library": - return new PathInfo(libraryService.getById(originalId).getUrl(), null); - case "Generate": - GenerateDetail detail = generateDetailMapper.selectById(originalId); - return new PathInfo(detail.getUrl(), detail.getGenerateId()); - case "Collection": - CollectionElement collectionElement = collectionElementMapper.selectById(originalId); - return new PathInfo(collectionElement.getUrl(), null); - default: - return null; - } - } - - private GenerateResultVO handleLibrarySave(Long accountId, Long libraryId, String minioPath, - String category, String gender, boolean isOverride, String level1Type) { - Library library; - String md5 = MD5Utils.encryptFile(minioUtil.getPreSignedUrl(minioPath, CommonConstant.MINIO_IMAGE_EXPIRE_TIME), false); - if (isOverride) { - library = new Library(); - library.setId(libraryId); - library.setUrl(minioPath); - library.setUpdateDate(new Date()); - libraryService.updateById(library); - } else { - library = new Library(accountId, level1Type, category, gender, minioPath, md5, new Date()); - libraryService.save(library); - libraryId = library.getId(); - } - return buildResultVO(libraryId, minioPath, category); - } - - private GenerateResultVO handleGenerateSave(Long originalId, Long generateId, String minioPath, - String category, boolean isOverride) { - GenerateDetail generateDetail = new GenerateDetail(); - String md5 = MD5Utils.encryptFile(minioUtil.getPreSignedUrl(minioPath, CommonConstant.MINIO_IMAGE_EXPIRE_TIME, true), Boolean.FALSE); - if (isOverride) { - generateDetail.setId(originalId); - generateDetail.setUrl(minioPath); - generateDetail.setUpdateDate(new Date()); - generateDetailMapper.updateById(generateDetail); - } else { - generateDetail.setGenerateId(generateId); - generateDetail.setUrl(minioPath); - generateDetail.setIsLike((byte)0); - generateDetail.setMd5(md5); - generateDetail.setCreateDate(LocalDateTime.now()); - generateDetailMapper.insert(generateDetail); - originalId = generateDetail.getId(); - } - return buildResultVO(originalId, minioPath, category); - } - - private GenerateResultVO handleUploadSave(Long accountId, Long originalId, String minioPath, - String category, boolean isOverride, String level1Type){ - CollectionElement collectionElement; - String md5 = MD5Utils.encryptFile(minioUtil.getPreSignedUrl(minioPath, CommonConstant.MINIO_IMAGE_EXPIRE_TIME), false); - if (isOverride){ - collectionElement = new CollectionElement(); - collectionElement.setId(originalId); - collectionElement.setUrl(minioPath); - collectionElement.setMd5(md5); - collectionElement.setUpdateDate(new Date()); - collectionElementMapper.updateById(collectionElement); - }else { - CollectionElement originalElement = collectionElementMapper.selectById(originalId); - String name = minioPath.substring(minioPath.lastIndexOf("/") + 1, minioPath.lastIndexOf(".")); - collectionElement = new CollectionElement(accountId, level1Type, category, name, minioPath, (byte)0, md5, new Date(), originalElement.getProjectId()); - collectionElementMapper.insert(collectionElement); - } - return buildResultVO(collectionElement.getId(), minioPath, category); - } - - private GenerateResultVO buildResultVO(Long id, String minioPath, String category) { - String url = minioUtil.getPreSignedUrl(minioPath, CommonConstant.MINIO_IMAGE_EXPIRE_TIME, true); - return new GenerateResultVO(id, url, "Success", category); - } - - public ToProductImageResultVO poseTransform(PoseTransformDTO poseTransformDTO) { - Long accountId = UserContext.getUserHolder().getId(); - Long projectId = poseTransformDTO.getProjectId(); - String productImage = poseTransformDTO.getProductImage(); - Integer poseId = poseTransformDTO.getPoseId(); - boolean wxTask = StringUtil.isNullOrEmpty(poseTransformDTO.getModelName()) && poseTransformDTO.getModelName().equals("wx"); - - // 1、判断用户当前积分是否够本次生成消耗 - CreditsEventsEnum creditsEventsEnum = wxTask ? CreditsEventsEnum.WX_ANIMATION : CreditsEventsEnum.LOCAL_ANIMATION; - Boolean preDeduction = creditsService.creditsPreDeduction(creditsEventsEnum, 1); - if (!preDeduction) { - throw new BusinessException("remaining.credits.insufficient", ResultEnum.WARNING.getCode()); - } - - // 3、生成唯一id 使用uuid,由于uuid重复的几率很小,故取消对uuid重复性的校验 - String taskId; - Boolean isRequestSuccess = false; - PoseTransformation poseTransformation = new PoseTransformation(); - if (!StringUtil.isNullOrEmpty(poseTransformDTO.getModelName()) && poseTransformDTO.getModelName().equals("wx")) { - taskId = animateAnyone(poseTransformDTO, accountId); - if (!StringUtil.isNullOrEmpty(taskId)){ - isRequestSuccess = true; - apiGenerateService.addAPIGenerateRecordAsync(accountId, taskId, Module.poseTransfer.getValue(), "wx", "Pending"); - } - poseTransformation.setModelName("wx"); - } else { - String uuid = UUID.randomUUID().toString(); - taskId = uuid + "-" + accountId; - isRequestSuccess = pythonService.poseTransformation(productImage, poseId, taskId); - } - - poseTransformation.setProjectId(projectId); - poseTransformation.setAccountId(accountId); - poseTransformation.setUniqueId(taskId); - poseTransformation.setProductImage(productImage); - poseTransformation.setPoseId(poseId); - poseTransformation.setIsLiked((byte) 0); - String taskStatus = isRequestSuccess ? "Executing" : "Fail"; - poseTransformation.setTaskStatus(taskStatus); - poseTransformation.setCreateTime(LocalDateTime.now()); - poseTransformationMapper.insert(poseTransformation); - // 当需要默认like - ToProductImageResultVO toProductImageResultVO = new ToProductImageResultVO(); - toProductImageResultVO.setParentId(poseTransformDTO.getParentId()); - toProductImageResultVO.setResultType(Module.poseTransfer.getValue()); - toProductImageResultVO.setTaskId(taskId); - toProductImageResultVO.setStatus(taskStatus); - toProductImageResultVO.setSourceUrl(minioUtil.getPreSignedUrl(productImage, CommonConstant.MINIO_IMAGE_EXPIRE_TIME)); - toProductImageResultVO.setPoseId(poseId); - toProductImageResultVO.setModelName(poseTransformDTO.getModelName()); - - if (Objects.nonNull(poseTransformDTO.getIsDefaultLike()) && poseTransformDTO.getIsDefaultLike()) { - // 满足条件下添加到like - poseTransformation.setIsLiked((byte) 1); - poseTransformation.setUpdateTime(LocalDateTime.now()); - poseTransformationMapper.updateById(poseTransformation); - CollectionSort collectionSort = addPoseTransferLike(poseTransformDTO, poseTransformation.getId()); - Integer reSort = collectionSortService.rearrangeChildSort(poseTransformation.getId(), CollectionType.POSE_TRANSFORM.getValue(), - poseTransformDTO.getParentId(), poseTransformDTO.getUserLikeSortId()); - toProductImageResultVO.setSort(Objects.isNull(reSort) ? Objects.isNull(collectionSort) ? null : collectionSort.getSort() : reSort); - toProductImageResultVO.setParentId(poseTransformDTO.getParentId()); - toProductImageResultVO.setUserLikeSortId(Objects.isNull(collectionSort) ? null : collectionSort.getId()); - } else if (Objects.nonNull(poseTransformDTO.getIsDefaultLike()) && Objects.nonNull(poseTransformDTO.getParentId())) { - toProductImageResultVO.setParentId(poseTransformDTO.getParentId()); - } - - - if (isRequestSuccess) { - // 6、添加预扣除积分到redis - creditsService.addRecordToCreditsDeduction(accountId, taskId, creditsEventsEnum); - // 6.1 添加积分扣除记录到db - creditsService.preInsert(accountId, creditsEventsEnum.getName(), taskId, Boolean.TRUE, null); - // 更新项目更新时间 - projectService.modifyProjectUpdateTime(projectId); - return toProductImageResultVO; - } - throw new BusinessException("pose transformation error", ResultEnum.ERROR.getCode()); - } - - private CollectionSort addPoseTransferLike(PoseTransformDTO poseTransformDTO, Long poseTransformationId) { - if (Objects.nonNull(poseTransformDTO.getParentId()) - && !poseTransformDTO.getParentId().equals(0L)) { - return disOrLikePose(poseTransformationId, "like", - poseTransformDTO.getProjectId(), poseTransformDTO.getParentId()); - } - return null; - } - - public void processPoseTransformResult(String taskId, String gifUrl, String videoUrl, String imageUrl) { - // 1、存储模型返回的数据 - PoseTransformation poseTransformation; - QueryWrapper qw = new QueryWrapper<>(); - qw.eq("unique_id", taskId); - List poseTransformations = poseTransformationMapper.selectList(qw); - if (poseTransformations != null && poseTransformations.size() > 1) { - log.warn("通过taskId {} 查询到的PoseTransformation的结果不止一条", taskId); - } else if (poseTransformations == null || poseTransformations.isEmpty()) { - return; - } - poseTransformation = poseTransformations.get(0); - poseTransformation.setGifUrl(gifUrl); - poseTransformation.setVideoUrl(videoUrl); - poseTransformation.setFirstFrameUrl(imageUrl); - poseTransformation.setTaskStatus("Success"); - poseTransformation.setUpdateTime(LocalDateTime.now()); - poseTransformationMapper.updateById(poseTransformation); - - String key = generateResultKey + ":" + taskId; - PoseTransformationVO poseTransformationVO = new PoseTransformationVO( - poseTransformation.getId(), taskId, gifUrl, videoUrl, imageUrl, (byte) 0, "Success"); - poseTransformationVO.setPoseId(poseTransformation.getPoseId()); - poseTransformationVO.setModelName(poseTransformation.getModelName()); - - // 2、更新redis - redisUtil.addToString(key, new Gson().toJson(poseTransformationVO), CommonConstant.GENERATE_RESULT_EXPIRE_TIME); - - // 3、执行积分扣除 - String accountId = taskId.substring(taskId.lastIndexOf("-") + 1); -// String uuid = taskId.substring(0, taskId.lastIndexOf("-")); - Boolean flag = creditsService.taskCreditsDeduction(Long.parseLong(accountId), taskId); - if (flag) creditsService.updateChangedCredits(accountId, taskId); - } - - public List getPoseTransformationResult(List taskIdList, Long projectId, Boolean like) { - List resultList = new ArrayList<>(); - - // 处理按taskId列表查询的情况 - if (taskIdList != null && !taskIdList.isEmpty()) { - for (String taskId : taskIdList) { - PoseTransformationVO vo = buildPoseTransformationVO(taskId, null); - if (vo != null) { - resultList.add(vo); - } - } - } - // 处理按projectId和like查询的情况 - else if (projectId != null) { - QueryWrapper queryWrapper = new QueryWrapper() - .eq("project_id", projectId) - .ne("task_status", "Fail"); - - if (like != null) { - queryWrapper.eq("is_liked", like ? 1 : 0); - } - - List poseTransformations = poseTransformationMapper.selectList(queryWrapper); - - if (!CollectionUtils.isEmpty(poseTransformations)) { - for (PoseTransformation item : poseTransformations) { - PoseTransformationVO vo = buildPoseTransformationVO(item.getUniqueId(), item); - if (vo != null && !isInvalidStatus(vo.getStatus())) { - resultList.add(vo); - } - } - } - } - - return resultList; - } - - private PoseTransformationVO buildPoseTransformationVO(String taskId, PoseTransformation dbItem) { - String type = resolveModelType(taskId, CreditsEventsEnum.POSE_TRANSFORMATION.getValue()); - String key = generateResultKey + ":" + taskId; - String resultJson = redisUtil.getFromString(key); - - PoseTransformationVO vo; - - // 1. 优先从Redis获取数据 - if (!StringUtil.isNullOrEmpty(resultJson)) { - vo = new Gson().fromJson(resultJson, PoseTransformationVO.class); - - // 设置数据库中的额外字段 - if (dbItem != null) { - vo.setId(dbItem.getId()); - vo.setIsLiked(dbItem.getIsLiked()); - } - - // 处理成功状态的数据 - if ("Success".equals(vo.getStatus()) && !"wx".equals(type)) { - processAllUrls(vo, dbItem); - } - - vo.setResultType(CollectionType.POSE_TRANSFORM.getValue()); - } - // 2. 处理wx类型的情况 - else if ("wx".equals(type)) { - vo = getAnimateResult(taskId); - } - // 3. 处理既没有Redis数据也不是wx类型的情况 - else { - // 如果有数据库记录 - if (dbItem != null) { - vo = CopyUtil.copyObject(dbItem, PoseTransformationVO.class); - vo.setTaskId(taskId); - - // 设置产品图片URL - if (dbItem.getProductImage() != null) { - vo.setProductImage(minioUtil.getPreSignedUrl( - dbItem.getProductImage(), CommonConstant.MINIO_IMAGE_EXPIRE_TIME)); - } - - // 如果视频URL为空,直接返回 - if (StringUtil.isNullOrEmpty(dbItem.getVideoUrl())) { - return vo; - } - - processAllUrls(vo, dbItem); - } - // 如果没有数据库记录 - else { - vo = new PoseTransformationVO(taskId, "Executing"); - } - } - - // 处理父ID逻辑 - processParentId(vo, dbItem != null ? dbItem : - poseTransformationMapper.selectOne(new QueryWrapper().eq("unique_id", taskId))); - - return vo; - } - - private void processAllUrls(PoseTransformationVO vo, PoseTransformation dbItem) { - // 处理各种URL - processUrl(vo.getGifUrl(), url -> - vo.setGifUrl(minioUtil.getPreSignedUrl(url, CommonConstant.MINIO_IMAGE_EXPIRE_TIME))); - processUrl(vo.getVideoUrl(), url -> - vo.setVideoUrl(minioUtil.getPreSignedUrl(url, CommonConstant.MINIO_IMAGE_EXPIRE_TIME))); - processUrl(vo.getFirstFrameUrl(), url -> - vo.setFirstFrameUrl(minioUtil.getPreSignedUrl(url, CommonConstant.MINIO_IMAGE_EXPIRE_TIME))); - } - - private void processParentId(PoseTransformationVO vo, PoseTransformation poseTransformation) { - if (poseTransformation != null) { - ToProductImageResult productResult = getProductResultByPath(poseTransformation.getProductImage()); - if (productResult != null) { - Long parentId = collectionSortService.getParentIdByElementIdAndElementType( - productResult.getId(), CollectionType.TO_PRODUCT_IMAGE.getValue()); - if (Objects.isNull(parentId)){ - parentId = userLikeGroupService.getUnlikedResultParentId(null, poseTransformation.getProductImage()); - } - vo.setParentId(parentId); - vo.setId(poseTransformation.getId()); - vo.setModelName(poseTransformation.getModelName()); - vo.setRelationType(Module.poseTransfer.getValue()); - vo.setProductImage(minioUtil.getPreSignedUrl(productResult.getUrl(), CommonConstant.MINIO_IMAGE_EXPIRE_TIME)); - } - } - } - - private boolean isInvalidStatus(String status) { - return "Invalid".equals(status) || "Fail".equals(status); - } - - public void updatePoseTransferStatus(String taskId, String status){ - PoseTransformation poseTransformation = poseTransformationMapper.selectOne(new QueryWrapper().eq("unique_id", taskId)); - - if (StringUtil.isNullOrEmpty(poseTransformation.getTaskStatus()) || !status.equals(poseTransformation.getTaskStatus())){ - poseTransformation.setTaskStatus(status); - poseTransformation.setUpdateTime(LocalDateTime.now()); - poseTransformationMapper.updateById(poseTransformation); - } - } - - private ToProductImageResult getProductResultByPath(String minioPath) { - QueryWrapper qw = new QueryWrapper<>(); - qw.lambda().eq(ToProductImageResult::getUrl, minioPath); - return toProductImageResultMapper.selectOne(qw); - } - - /*public List getPoseTransformationResultList(Long projectId, boolean like) { - List poseTransformations = poseTransformationMapper.selectList(new QueryWrapper().eq("project_id", projectId) - .eq("is_liked", like ? 1 : 0).ne("task_status", "Fail")); - List vos = new ArrayList<>(); -// if (poseTransformations != null && poseTransformations.size() > 1){ - if (!CollectionUtils.isEmpty(poseTransformations)) { - for (PoseTransformation item : poseTransformations) { - String taskId = item.getUniqueId(); - String key = generateResultKey + ":" + taskId; - String resultJson = redisUtil.getFromString(key); - - PoseTransformationVO poseTransformationVO; - - if (!StringUtil.isNullOrEmpty(resultJson)) { - // 从Redis获取并转换数据 - poseTransformationVO = new Gson().fromJson(resultJson, PoseTransformationVO.class); - poseTransformationVO.setId(item.getId()); - poseTransformationVO.setIsLiked(item.getIsLiked()); - - // 处理成功状态的数据 - if ("Success".equals(poseTransformationVO.getStatus())) { - poseTransformationVO.setProductImage( - minioUtil.getPreSignedUrl(item.getProductImage(), CommonConstant.MINIO_IMAGE_EXPIRE_TIME)); - - // 处理各种URL - processUrl(poseTransformationVO.getGifUrl(), url -> - poseTransformationVO.setGifUrl(minioUtil.getPreSignedUrl(url, CommonConstant.MINIO_IMAGE_EXPIRE_TIME))); - processUrl(poseTransformationVO.getVideoUrl(), url -> - poseTransformationVO.setVideoUrl(minioUtil.getPreSignedUrl(url, CommonConstant.MINIO_IMAGE_EXPIRE_TIME))); - processUrl(poseTransformationVO.getFirstFrameUrl(), url -> - poseTransformationVO.setFirstFrameUrl(minioUtil.getPreSignedUrl(url, CommonConstant.MINIO_IMAGE_EXPIRE_TIME))); - } - - // 添加有效数据到结果列表 - if (!"Invalid".equals(poseTransformationVO.getStatus()) && !"Fail".equals(poseTransformationVO.getStatus())) { - vos.add(poseTransformationVO); - } - } else { - // 处理Redis中没有缓存的情况 - poseTransformationVO = CopyUtil.copyObject(item, PoseTransformationVO.class); - - poseTransformationVO.setTaskId(taskId); - poseTransformationVO.setProductImage( - minioUtil.getPreSignedUrl(item.getProductImage(), CommonConstant.MINIO_IMAGE_EXPIRE_TIME)); - - // todo 面对没有生成结束的情况,返回taskId - if (StringUtil.isNullOrEmpty(item.getVideoUrl())) { - vos.add(poseTransformationVO); - continue; - } - // 处理各种URL - processUrl(poseTransformationVO.getGifUrl(), url -> - poseTransformationVO.setGifUrl(minioUtil.getPreSignedUrl(url, CommonConstant.MINIO_IMAGE_EXPIRE_TIME))); - processUrl(poseTransformationVO.getVideoUrl(), url -> - poseTransformationVO.setVideoUrl(minioUtil.getPreSignedUrl(url, CommonConstant.MINIO_IMAGE_EXPIRE_TIME))); - processUrl(poseTransformationVO.getFirstFrameUrl(), url -> - poseTransformationVO.setFirstFrameUrl(minioUtil.getPreSignedUrl(url, CommonConstant.MINIO_IMAGE_EXPIRE_TIME))); - - vos.add(poseTransformationVO); - } - } - } - return vos; - }*/ - - // 辅助方法:处理URL - private void processUrl(String url, Consumer processor) { - if (!StringUtil.isNullOrEmpty(url) && !"None".equals(url)) { - processor.accept(url); - } - } - - public CollectionSort disOrLikePose(Long transformedId, String likeOrDislike, Long projectId, Long collectionSortParentId) { - PoseTransformation poseTransformation = poseTransformationMapper.selectById(transformedId); - CollectionSort collectionSort = null; - if (Objects.nonNull(poseTransformation)) { - if (likeOrDislike.equals("like")) { - poseTransformation.setIsLiked((byte) 1); - if (null != collectionSortParentId) { - collectionSort = collectionSortService.addCollectionSort(poseTransformation.getId(), CollectionType.POSE_TRANSFORM.getValue(), projectId, collectionSortParentId); - } - } else if (likeOrDislike.equals("dislike")) { - poseTransformation.setIsLiked((byte) 0); - if (null != collectionSortParentId) { - collectionSortService.deleteCollectionSort(poseTransformation.getId(), CollectionType.POSE_TRANSFORM.getValue(), projectId, collectionSortParentId); - } - } - poseTransformation.setUpdateTime(LocalDateTime.now()); - poseTransformationMapper.updateById(poseTransformation); - } - if (Objects.nonNull(collectionSort)) { - projectService.modifyProjectUpdateTime(projectId); - } - return collectionSort; - } - - public String modifyModelProportion(ModifyModelProportionDTO proportionDTO) { - log.info("modifyModelProportion params: {}", proportionDTO); - String name; - String gender; - Library model = null; - Long accountId = UserContext.getUserHolder().getId(); - String uuid = UUID.randomUUID().toString(); - // 所有修改的图片都另存为,不覆盖原图 - if (proportionDTO.getType().equals("Library")) { - model = libraryService.getById(proportionDTO.getId()); - String url = model.getUrl(); - name = url.substring(url.indexOf("/") + 1, url.lastIndexOf("/")) + "/" + uuid; -// gender = model.getLevel2Type(); - } else { - SysFileVO sysModel = sysFileService.getById(proportionDTO.getId()); - gender = sysModel.getLevel2Type(); - name = accountId + "/models/" + gender.toLowerCase() + "/" + uuid; - } - // 只需要将结果存入library - String modifiedModel = pythonService.modifyModelProportion(proportionDTO.getModelPath(), proportionDTO.getStretch(), name, proportionDTO.getTop(), proportionDTO.getBottom()); -// List imagesWidthAndHeight = minioUtil.getImagesWidthAndHeight(modifiedModel); - - /*// 存储修改后的模特到个人library - model = new Library(); - model.setAccountId(accountId); - model.setLevel1Type(LibraryLevel1TypeEnum.MODELS.getRealName()); - model.setLevel2Type(gender); - model.setName(uuid); - model.setUrl(modifiedModel); - model.setMd5(MD5Utils.encryptFile(minioUtil.getPreSignedUrl(modifiedModel, 24 * 60),false)); - model.setWidth(imagesWidthAndHeight.get(0)); - model.setHigh(imagesWidthAndHeight.get(1)); - model.setCreateDate(new Date()); - libraryService.save(model);*/ - -/* // 新建模特点位信息 - LibraryModelPoint libraryModelPoint = new LibraryModelPoint(); - libraryModelPoint.setModelType("Library"); - libraryModelPoint.setRelationId(model.getId()); - libraryModelPoint.setShoulderLeft(Arrays.toString(proportionDTO.getShoulderLeft())); - libraryModelPoint.setShoulderRight(Arrays.toString(proportionDTO.getShoulderRight())); - libraryModelPoint.setWaistbandLeft(Arrays.toString(proportionDTO.getWaistbandLeft())); - libraryModelPoint.setWaistbandRight(Arrays.toString(proportionDTO.getWaistbandRight())); - libraryModelPoint.setHandLeft(Arrays.toString(proportionDTO.getHandLeft())); - libraryModelPoint.setHandRight(Arrays.toString(proportionDTO.getHandRight())); - libraryModelPoint.setCreateDate(new Date()); - libraryModelPointService.save(libraryModelPoint);*/ - return minioUtil.getPreSignedUrl(modifiedModel, CommonConstant.MINIO_IMAGE_EXPIRE_TIME); - } - - /** - * String collagePicture(Base64) - * List elements - * File file - * - * @return - */ - @Transactional(rollbackFor = Exception.class) - public GenerateResultVO sketchReconstructionGenerate(SketchReconstructionDTO sketchReconstructionDTO) { -// log.info("sketchReconstructionGenerate params: {}", sketchReconstructionDTO); - - Long accountId = UserContext.getUserHolder().getId(); - // 1、线稿生成 - String collagePictureBase64 = sketchReconstructionDTO.getCollagePicture(); - String path = accountId + "/CollagePicture/" + UUID.randomUUID(); - String minioPath = minioUtil.base64UploadToPath(collagePictureBase64, userBucket, path); - - Long projectId = sketchReconstructionDTO.getProjectId(); - - GenerateResultVO generateResultVO = imageToSketch(new ImageToSketchDTO(null, "2", sketchReconstructionDTO.getGender()), minioPath, projectId); - QueryWrapper qw = new QueryWrapper<>(); - qw.eq("project_id", projectId); - SketchReconstruction sketchReconstruction = sketchReconstructionMapper.selectOne(qw); - - String url = generateResultVO.getUrl(); - // 找到路径的起始位置(从"://"之后查找第一个"/") - int pathStartIndex = url.indexOf("/", url.indexOf("://") + 3); - // 找到查询参数的起始位置("?" 的位置) - int queryStartIndex = url.indexOf("?"); - // 截取目标部分 - String targetPath = url.substring(pathStartIndex + 1, queryStartIndex); - - try { - if (Objects.isNull(sketchReconstruction)) { - sketchReconstruction = new SketchReconstruction(); - sketchReconstruction.setProjectId(projectId); - sketchReconstruction.setCollageImgSketchUrl(targetPath); - sketchReconstruction.setGenerateDetailId(generateResultVO.getId()); - sketchReconstruction.setGender(sketchReconstructionDTO.getGender()); - sketchReconstruction.setCreateTime(LocalDateTime.now()); - sketchReconstructionMapper.insert(sketchReconstruction); - } else { - sketchReconstruction.setCollageImgSketchUrl(targetPath); - sketchReconstruction.setGenerateDetailId(generateResultVO.getId()); - sketchReconstructionMapper.updateById(sketchReconstruction); - } - } catch (DuplicateKeyException e) { - // 如果发生唯一键冲突,说明其他请求已经创建了记录 - // 重新查询并更新 - log.info("sketch拼贴,唯一键(project_id)冲突,改为更新"); - sketchReconstruction = sketchReconstructionMapper.selectOne(qw); - sketchReconstruction.setCollageImgSketchUrl(targetPath); - sketchReconstruction.setGenerateDetailId(generateResultVO.getId()); - sketchReconstructionMapper.updateById(sketchReconstruction); - } - - return generateResultVO; - } - - public SketchReconstructionVO getSketchReconstruction(Long projectId) { - QueryWrapper qw = new QueryWrapper<>(); - qw.eq("project_id", projectId); - SketchReconstruction sketchReconstruction = sketchReconstructionMapper.selectOne(qw); - - SketchReconstructionVO vo = new SketchReconstructionVO(); - if (Objects.nonNull(sketchReconstruction) && Objects.nonNull(sketchReconstruction.getGenerateDetailId())) { - GenerateDetail generateDetail = generateDetailMapper.selectById(sketchReconstruction.getGenerateDetailId()); - vo.setCollageSketchUrl(minioUtil.getPreSignedUrl(generateDetail.getUrl(), CommonConstant.MINIO_IMAGE_EXPIRE_TIME)); - vo.setLiked(generateDetail.getIsLike().equals((byte) 1)); - String clothCategory = pythonService.getClothCategory(generateDetail.getUrl(), sketchReconstruction.getGender()); - String messageFromResource = BusinessException.getMessageFromResource(clothCategory.toUpperCase()); - vo.setCategory(clothCategory); - vo.setCategoryValue(messageFromResource); - } - List collectionElements = collectionElementService.getByProjectId(projectId); - if (!collectionElements.isEmpty()){ - collectionElements.forEach(item -> { - item.setUrl(StringUtil.isNullOrEmpty(item.getUrl()) ? null : minioUtil.getPreSignedUrl(item.getUrl(), CommonConstant.MINIO_IMAGE_EXPIRE_TIME)); - }); - vo.setUploadImages(collectionElements); - }else { - vo.setUploadImages(new ArrayList<>()); - } - return vo; - } - - public List> getAllPose() { - List> propertyList = PoseEnum.getPropertyList(); - propertyList.forEach(item -> { - item.put("gif", minioUtil.getPreSignedUrl(item.get("gif"), CommonConstant.MINIO_IMAGE_EXPIRE_TIME)); - item.put("firstFrame", minioUtil.getPreSignedUrl(item.get("firstFrame"), CommonConstant.MINIO_IMAGE_EXPIRE_TIME)); - }); - return propertyList; - } - - @Override - @Transactional - public void processPoseTransformResultBatch(String taskId, String gifUrl, String videoUrl, String imageUrl, String progress) { - // 1、存储模型返回的数据 - PoseTransformation poseTransformation; - QueryWrapper qw = new QueryWrapper<>(); - qw.eq("unique_id", taskId); - List poseTransformations = poseTransformationMapper.selectList(qw); - if (poseTransformations != null && poseTransformations.size() > 1) { - log.warn("通过taskId {} 查询到的PoseTransformation的结果不止一条", taskId); - } else if (poseTransformations == null || poseTransformations.isEmpty()) { - return; - } - poseTransformation = poseTransformations.get(0); - poseTransformation.setGifUrl(gifUrl); - poseTransformation.setVideoUrl(videoUrl); - poseTransformation.setFirstFrameUrl(imageUrl); - poseTransformation.setTaskStatus("Success"); - poseTransformation.setUpdateTime(LocalDateTime.now()); - poseTransformationMapper.updateById(poseTransformation); - - String taskIdBatch = poseTransformation.getTaskIdBatch(); - QueryWrapper cloudTaskQueryWrapper = new QueryWrapper<>(); - cloudTaskQueryWrapper.lambda().eq(CloudTask::getTaskId, taskIdBatch); - CloudTask cloudTask = cloudTaskMapper.selectOne(cloudTaskQueryWrapper); - if (Objects.nonNull(cloudTask)){ - cloudTask.setUpdateTime(LocalDateTime.now()); - cloudTaskMapper.updateById(cloudTask); - } -// if (Objects.nonNull(cloudTask)) { -// if (cloudTask.getCompletedNum() == null) { -// cloudTask.setCompletedNum(1); -// }else { -// cloudTask.setCompletedNum(cloudTask.getCompletedNum() + 1); -// } -// cloudTaskMapper.updateById(cloudTask); -// } - cloudTaskMapper.increaseCompletedNum(taskIdBatch); - - String key = generateResultKey + ":" + taskId; - PoseTransformationVO poseTransformationVO = new PoseTransformationVO( - poseTransformation.getId(), taskId, gifUrl, videoUrl, imageUrl, (byte) 0, "Success"); - - // 2、更新redis - redisUtil.addToString(key, new Gson().toJson(poseTransformationVO), CommonConstant.GENERATE_RESULT_EXPIRE_TIME); - - /*// 3、执行积分扣除 - String accountId = taskId.substring(taskId.lastIndexOf("-") + 1); - String uuid = taskId.substring(0, taskId.lastIndexOf("-")); - Boolean flag = creditsService.taskCreditsDeduction(Long.parseLong(accountId), taskIdBatch); - if (flag) creditsService.updateChangedCredits(accountId, taskIdBatch);*/ - } - - @Transactional - public void processPoseTransformResultBatch(String progress, String taskId) { - // 1、存储模型返回的数据 - PoseTransformation poseTransformation; - QueryWrapper qw = new QueryWrapper<>(); - qw.eq("unique_id", taskId); - List poseTransformations = poseTransformationMapper.selectList(qw); - log.info("poseTransformations : {}", poseTransformations); - if (poseTransformations != null && poseTransformations.size() > 1) { - log.warn("通过taskId {} 查询到的PoseTransformation的结果不止一条", taskId); - } else if (poseTransformations == null || poseTransformations.isEmpty()) { - return; - } - poseTransformation = poseTransformations.get(0); - String taskIdBatch = poseTransformation.getTaskIdBatch(); - log.info("progress:{}", progress); - log.info("taskIdBatch:{}", taskIdBatch); - if (progress.equals("OK")) { - if (!StringUtils.isEmpty(taskIdBatch)) { - cloudTaskService.completeTask(taskIdBatch); - } - }else if (progress.startsWith("0/")) { - if (!StringUtils.isEmpty(taskIdBatch)) { - cloudTaskService.startTask(taskIdBatch); - } - } - } - - - - @Transactional(rollbackFor = Exception.class) - public void deleteGeneratedPose(Long projectId, Long id) { - // 1. 权限校验 - Long accountId = UserContext.getUserHolder().getId(); - Project project = projectService.getById(projectId); - if (!project.getAccountId().equals(accountId)) { - throw new IllegalArgumentException("项目不属于当前账号"); - } - - // 2. 软删除主表数据 - int update = poseTransformationMapper.update(null, - new UpdateWrapper() - .eq("id", id) - .set("is_deleted", 1) - .set("update_time", LocalDateTime.now())); - - log.info("删除PoseTransfer 结果, id为{}, 影响行数={}", id, update); - - if (update == 0) return; - - // 3. 查询可能删除的多个排序项(存在脏数据可能,所有会查出多个) - List deletedItems = collectionSortMapper.selectList( - new QueryWrapper() - .eq("project_id", projectId) - .eq("relation_id", id) - .eq("relation_type", "PoseTransfer") - .orderByAsc("sort") - ); - - if (deletedItems.isEmpty()) return; - - // 4. 删除这些排序记录 - int deletedCount = collectionSortMapper.delete( - new QueryWrapper() - .eq("project_id", projectId) - .eq("relation_id", id) - .eq("relation_type", "PoseTransfer") - ); - - // 5. 重新调整剩余记录的排序(两种方案可选) - // 方案一:精确调整(每条被删除记录单独处理) - for (CollectionSort deletedItem : deletedItems) { - collectionSortMapper.update( - null, - new UpdateWrapper() - .eq("project_id", projectId) - .gt("sort", deletedItem.getSort()) - .setSql("sort = sort - 1") - ); - } - - log.info("删除PoseTransfer排序记录:id={}, 删除{}条,已重新排序后续记录", id, deletedCount); - } - - - - /** - * 万象专业版 - * 1、MoodBoard t2i - * 2、PrintBoard t2i - * 3、SketchBoard t2i - * 4、pose transfer 图生舞蹈视频-舞动人像AnimateAnyone - */ - - /** - * 创建异步任务 - * - * @return taskId - */ - public String createAsyncTask(GenerateThroughImageTextDTO generateDTO) { -// String prompt = "一间有着精致窗户的花店,漂亮的木质门,摆放着花朵"; - String level1Type = generateDTO.getLevel1Type(); - String level2Type = generateDTO.getLevel2Type(); - String prompt = generateDTO.getText(); - Long userId = generateDTO.getUserId(); - String gender = generateDTO.getGender(); - - // 添加预设prompt,使生成结果更加具有指向性(区分不同的board) - switch (level1Type) { - case "Moodboard": - break; - case "Printboard": - prompt = "pattern image, " + prompt; - break; - case "Sketchboard": - prompt = "a single item of sketch of " + prompt + ", clean white background, simple lines"; - break; - default: - log.warn("未知类型 type:{}", level1Type); - } - HashMap promptExtend = new HashMap<>(); - promptExtend.put("prompt_extend", false); - ImageSynthesisParam param = - ImageSynthesisParam.builder() - .apiKey(ALIYUN_API_KEY) - .model("wanx2.1-t2i-plus") - .prompt(prompt) - .n(1) - .size("1024*1024") - .parameters(promptExtend) - .build(); - - log.info(param.toString()); - - ImageSynthesis imageSynthesis = new ImageSynthesis(); - ImageSynthesisResult result = null; - try { - result = imageSynthesis.asyncCall(param); - } catch (Exception e) { - throw new RuntimeException(e.getMessage()); - } - String taskId = result.getOutput().getTaskId(); - log.info("wx text2image 请求生成:{}, taskId:{}", JsonUtils.toJson(result), taskId); - - Generate generate = new Generate(userId, taskId, level1Type, level2Type, prompt, "text(" + gender + ")", "wx", new Date()); - save(generate); - return taskId; - } - - /** - * 获取异步任务结果 - * - * @param taskId 任务id - */ - public GenerateResultVO getAsyncTaskResult(String taskId) { - ImageSynthesis imageSynthesis = new ImageSynthesis(); - ImageSynthesisResult result = null; - try { - //如果已经在环境变量中设置了 DASHSCOPE_API_KEY,wait()方法可将apiKey设置为null - result = imageSynthesis.fetch(taskId, ALIYUN_API_KEY); - log.info(JsonUtils.toJson(result)); - //PENDING:任务排队中; RUNNING:任务处理中; SUCCEEDED:任务执行成功; FAILED:任务执行失败; CANCELED:任务取消成功; UNKNOWN:任务不存在或状态未知 - String taskStatus = result.getOutput().getTaskStatus(); - - if (taskStatus.equals("SUCCEEDED")) { - List generates = selectListByUniqueId(taskId); - String url = result.getOutput().getResults().get(0).get("url"); - - String path = null; - if (!generates.isEmpty()) { - Generate generate = generates.get(0); - Long accountId = generate.getAccountId(); - - // 1、下载图片 -// InputStream inputStream = downloadImageFromAliyun(url); - byte[] bytes = downloadVideoOrImage(url); - // 2、上传图片到minio保存 - String objectName = accountId + "/" + generate.getLevel1Type().toLowerCase() + "/" + taskId + ".png"; - minioUtil.uploadToMinio(bytes, userBucket, objectName, "image/png"); - path = userBucket + "/" + objectName; - // 3、生成结果保存到db - GenerateDetail generateDetail = new GenerateDetail(); - generateDetail.setGenerateId(generate.getId()); - generateDetail.setUrl(path); - generateDetail.setMd5(MD5Utils.encryptFile(minioUtil.getPreSignedUrl(path, 24 * 60), false)); - generateDetail.setCreateDate(LocalDateTime.now()); - generateDetailMapper.insert(generateDetail); - // 4、扣积分 - Boolean flag = creditsService.taskCreditsDeduction(accountId, taskId); - if (flag) creditsService.updateChangedCredits(String.valueOf(generate.getAccountId()), taskId); - - GenerateResultVO generateResultVO = new GenerateResultVO(taskId, generateDetail.getId(), minioUtil.getPreSignedUrl(path, 24 * 60), "Success"); - if (generate.getLevel1Type().equals(SKETCH_BOARD.getRealName())) { - String gender = extractGender(generate.getGenerateType()); - if (!StringUtil.isNullOrEmpty(gender)) { - String clothCategory = pythonService.getClothCategory(path, gender); - generateResultVO.setCategory(clothCategory); - } else { - log.warn("未提取到性别"); - } - }else if (generate.getLevel1Type().equals(PRINT_BOARD.getRealName())){ - Generate generateRecord = selectByUniqueId(taskId); - generateResultVO.setCategory(generateRecord.getLevel2Type()); - } - return generateResultVO; - } else { - throw new BusinessException("Unknown generate task"); - } - } else if (taskStatus.equals("PENDING") || taskStatus.equals("RUNNING")) { - log.info("万象 异步接口返回生成状态为:{}", taskStatus); - return new GenerateResultVO(taskId, null, null, "Executing"); - } else { - log.warn("万象 异步接口返回生成状态为:{}", taskStatus); - return new GenerateResultVO(taskId, null, null, "Fail"); - } - } catch (ApiException | NoApiKeyException e) { - throw new RuntimeException(e.getMessage()); - } catch (Exception e) { - log.error("从aliyun下载图片失败, {}", e.getMessage()); - throw new BusinessException("Generation result retrieval failed"); - } - } - - private static final String IMAGE_DETECT = "https://dashscope.aliyuncs.com/api/v1/services/aigc/image2video/aa-detect"; - private static final String TEMPLATE_ID_GEN = "https://dashscope.aliyuncs.com/api/v1/services/aigc/image2video/aa-template-generation/"; - private static final String GET_ASYNC_RESULT = "https://dashscope.aliyuncs.com/api/v1/tasks/"; - private static final String ANIMATE = "https://dashscope.aliyuncs.com/api/v1/services/aigc/image2video/video-synthesis/"; - - public String animateAnyone(PoseTransformDTO poseTransformDTO, Long accountId) { - String inputImage = poseTransformDTO.getProductImage(); - String inputImageUrl = minioUtil.getPreSignedUrl(inputImage, CommonConstant.MINIO_IMAGE_EXPIRE_TIME); - // 1、输入图片检测 - checkImage(inputImageUrl); - - // 2、动作模板生成 - String videoTemplateId = PoseEnum.getById(poseTransformDTO.getPoseId()).getTemplateId(); - if (StringUtil.isNullOrEmpty(videoTemplateId)) { - throw new BusinessException("unknown pose"); - } - // 3、生成动图 - JSONObject requestBody1 = new JSONObject(); - requestBody1.set("model", "animate-anyone-gen2"); - - JSONObject input1 = new JSONObject(); - input1.set("image_url", inputImageUrl); // 替换为实际图片URL - input1.set("template_id", videoTemplateId); // 替换为实际图片URL - JSONObject parameters1 = new JSONObject(); - parameters1.set("use_ref_img_bg", false); - parameters1.set("video_ratio", "9:16"); - - requestBody1.set("input", input1); - requestBody1.set("parameters", parameters1); - - log.info("万象 pose transfer 请求入参:{}", requestBody1); - String resp = sendRequestUtil.sendAliYunPostAsync(ANIMATE, requestBody1.toString()); -// String resp = "{\"request_id\":\"656c4339-59e5-9b34-a010-b5aa625a4008\",\"output\":{\"task_id\":\"05c0fe3e-8d93-4754-babe-28a1efc62151\",\"task_status\":\"PENDING\"}}"; - log.info("wx pose transform 请求生成,获取taskId:{}", resp); - JSONObject jsonResponse = JSONUtil.parseObj(resp); - JSONObject output = jsonResponse.getJSONObject("output"); - String status = output.getStr("task_status"); - if (status.equals(FAILED.getName()) || status.equals(UNKNOWN_W.getName())) { - return null; - } - return output.getStr("task_id"); - } - - public void checkImage(String inputImageUrl) { - JSONObject requestBody = new JSONObject(); - requestBody.set("model", "animate-anyone-detect-gen2"); - - JSONObject input = new JSONObject(); - input.set("image_url", inputImageUrl); // 替换为实际图片URL - JSONObject parameters = new JSONObject(); - - requestBody.set("input", input); - requestBody.set("parameters", parameters); - - String response = sendRequestUtil.sendAliYunPost(IMAGE_DETECT, requestBody.toString()); - - System.out.println("API响应: " + response); - JSONObject jsonResponse = JSONUtil.parseObj(response); - // 获取check_pass值 - JSONObject output = jsonResponse.getJSONObject("output"); - Boolean checkPass = output.getBool("check_pass"); - - if (!checkPass) { - String reason = output.getStr("reason"); - log.info("原因: {}", reason); - throw new BusinessException("输入的图片不满足要求"); - } - } - - // 轮询配置 - private static final int MAX_RETRIES = 30; // 最大重试次数 - private static final int POLL_INTERVAL = 20000; // 轮询间隔(毫秒) - - public String getVideoTemplateId(String videoPath) { - boolean contains = PoseEnum.getVideoList().contains(videoPath); - - String templateId; - if (!contains) { - String videoUrl = minioUtil.getPreSignedUrl(videoPath, CommonConstant.MINIO_IMAGE_EXPIRE_TIME); - JSONObject requestBody = new JSONObject(); - requestBody.set("model", "animate-anyone-template-gen2"); - JSONObject input = new JSONObject(); - input.set("video_url", videoUrl); // 替换为实际图片URL - JSONObject parameters = new JSONObject(); - requestBody.set("input", input); - requestBody.set("parameters", parameters); - - log.info("获取pose的模板id 请求数据:{}", requestBody); - String resp = sendRequestUtil.sendAliYunPostAsync(TEMPLATE_ID_GEN, requestBody.toString()); - if (StringUtil.isNullOrEmpty(resp)) { - throw new BusinessException("请求获取video template id失败"); - } - JSONObject jsonResponse = JSONUtil.parseObj(resp); - log.info("getVideoTemplateId response:{}", jsonResponse); - JSONObject output = jsonResponse.getJSONObject("output"); - String taskId = output.getStr("task_id"); - - // 暂时用while循环轮询 - templateId = pollTemplateIdResult(taskId); - if (StringUtil.isNullOrEmpty(templateId)) { - throw new BusinessException("获取动作模板失败"); - } -// templateId = "AACT.8090e67b.-E3pujumEfCbDTI_rjSH-A.LwIlGT3j"; - } else { - templateId = PoseEnum.getByVideoPath(videoPath).getTemplateId(); - } - - return templateId; - } - - public String pollTemplateIdResult(String taskId) { - int attempt = 0; - boolean isCompleted = false; - String templateId = null; - - while (attempt < MAX_RETRIES && !isCompleted) { - attempt++; - System.out.printf("尝试第 %d 次查询...%n", attempt); - - try { - // 发送GET请求查询任务状态 - HttpResponse httpResponse = HttpRequest.get(GET_ASYNC_RESULT + taskId) - .header(Header.AUTHORIZATION, "Bearer " + ALIYUN_API_KEY) - .timeout(10000) - .execute(); - - if (httpResponse.getStatus() == 200) { - JSONObject response = JSONUtil.parseObj(httpResponse.body()); - JSONObject output = JSONUtil.parseObj(response.getStr("output")); - String taskStatus = output.getStr("task_status", "UNKNOWN"); - WangXiangTaskStatusEnum statusEnum = WangXiangTaskStatusEnum.fromName(taskStatus); - System.out.println("当前任务状态: " + taskStatus); - - switch (statusEnum) { - case SUCCEEDED: - templateId = handleSuccessResponse(response); - isCompleted = true; - break; - case FAILED: - case UNKNOWN_W: - handleFailedResponse(response); - isCompleted = true; - break; - case RUNNING: - case PENDING_W: - // 任务仍在运行,继续等待 - break; - default: - System.out.println("未知状态: " + taskStatus); - } - } else { - System.out.println("请求失败,状态码: " + httpResponse.getStatus()); - } - - // 如果不是最终状态,等待一段时间再重试 - if (!isCompleted && attempt < MAX_RETRIES) { - TimeUnit.MILLISECONDS.sleep(POLL_INTERVAL); - } - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - System.out.println("轮询被中断"); - break; - } catch (Exception e) { - System.out.println("请求发生异常: " + e.getMessage()); - // 发生异常时可以选择重试或退出 - break; - } - } - if (!isCompleted) { - System.out.println("达到最大重试次数仍未获取最终结果"); - } - return templateId; - } - - private static String handleSuccessResponse(JSONObject response) { - log.info("任务执行成功!"); - // 提取成功结果 - JSONObject output = response.getJSONObject("output"); - if (output != null) { - log.info("任务输出: {}", output.toStringPretty()); - return output.getStr("template_id"); - } else { - return null; - } - } - - private static void handleFailedResponse(JSONObject response) { - log.info("任务执行失败!"); - // 提取失败原因 - String errorMsg = response.getStr("error_message", "未知错误"); - log.info("失败原因: {}", errorMsg); - } - - - public PoseTransformationVO getAnimateResult(String taskId) { - String fullUrl = GET_ASYNC_RESULT + taskId; - String respBody = sendRequestUtil.sendAliYunGet(fullUrl); - log.info("获取wx pose transform 的结果: {}", respBody); - - String outputStr = JSONUtil.parseObj(respBody).getStr("output"); - JSONObject output = JSONUtil.parseObj(outputStr); - String videoUrl = output.getStr("video_url"); - String status = output.getStr("task_status"); - WangXiangTaskStatusEnum statusEnum = WangXiangTaskStatusEnum.fromName(status); - - apiGenerateService.updateAPIGenerateStatusAsync(taskId, status); - - PoseTransformationVO poseTransformationVO = new PoseTransformationVO(); - switch (statusEnum) { - case SUCCEEDED: - List poseTransformations = poseTransformationMapper.selectList(new QueryWrapper().eq("unique_id", taskId).orderByDesc("id")); - if (!poseTransformations.isEmpty()) { - PoseTransformation poseTransformation = poseTransformations.get(0); - poseTransformationVO = CopyUtil.copyObject(poseTransformation, PoseTransformationVO.class); - // 生成视频的gif和第一帧图片 - poseTransformationVO.setStatus("Success"); - processVideo(videoUrl, poseTransformation); - poseTransformationVO.setId(poseTransformation.getId()); - if (!StringUtil.isNullOrEmpty(poseTransformation.getGifUrl())) { - poseTransformationVO.setGifUrl(minioUtil.getPreSignedUrl(poseTransformation.getGifUrl(), CommonConstant.MINIO_IMAGE_EXPIRE_TIME)); - } - if (!StringUtil.isNullOrEmpty(poseTransformation.getVideoUrl())) { - poseTransformationVO.setVideoUrl(minioUtil.getPreSignedUrl(poseTransformation.getVideoUrl(), CommonConstant.MINIO_IMAGE_EXPIRE_TIME)); - } - if (!StringUtil.isNullOrEmpty(poseTransformation.getFirstFrameUrl())) { - poseTransformationVO.setFirstFrameUrl(minioUtil.getPreSignedUrl(poseTransformation.getFirstFrameUrl(), CommonConstant.MINIO_IMAGE_EXPIRE_TIME)); - } - // 执行积分扣除 - Long accountId = poseTransformation.getAccountId(); - Boolean flag = creditsService.taskCreditsDeduction(accountId, taskId); - if (flag) creditsService.updateChangedCredits(String.valueOf(accountId), taskId); - - // 保存数据到redis - String key = generateResultKey + ":" + taskId; - redisUtil.addToString(key, new Gson().toJson(poseTransformationVO), CommonConstant.GENERATE_RESULT_EXPIRE_TIME); - } - break; - case FAILED: - throw new BusinessException(output.getStr("message"), ResultEnum.PROMPT.getCode()); - case UNKNOWN_W: - poseTransformationVO.setStatus("Fail"); - break; - case RUNNING: - case PENDING_W: - // 任务仍在运行,继续等待 - poseTransformationVO.setStatus("Executing"); - break; - default: - log.info("未知状态: {}", status); - poseTransformationVO.setStatus("Fail"); - } - poseTransformationVO.setTaskId(taskId); - - return poseTransformationVO; - } - - public void processVideo(String aliyunVideoUrl, PoseTransformation poseTransformation) /*throws Exception*/ { - // 1. 从阿里云下载视频到内存 - byte[] videoBytes = downloadVideoOrImage(aliyunVideoUrl); - Long accountId = poseTransformation.getAccountId(); - String taskId = poseTransformation.getUniqueId(); - - // 2. 提取第一帧和生成GIF - ByteArrayOutputStream firstFrameOutput = new ByteArrayOutputStream(); - ByteArrayOutputStream gifOutput = new ByteArrayOutputStream(); - - try (FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(new ByteArrayInputStream(videoBytes))) { - grabber.start(); - // 提取第一帧 - BufferedImage firstFrame = new Java2DFrameConverter().convert(grabber.grabImage()); - ImageIO.write(firstFrame, "jpg", firstFrameOutput); - - // 生成GIF(取前12秒,50帧) - generateGif(grabber, gifOutput, 12, 60); - } catch (Exception e) { - throw new RuntimeException(e); - } - - // 3. 上传所有文件到MinIO - String videoPrefix = accountId + "/pose_transform_video/" + taskId + ".mp4"; - String imgPrefix = accountId + "/pose_transform_first_img/" + taskId + ".jpg"; - String gifPrefix = accountId + "/pose_transform_gif/" + taskId + ".gif"; - - minioUtil.uploadToMinio(videoBytes, userBucket, videoPrefix, "video/mp4"); - minioUtil.uploadToMinio(firstFrameOutput.toByteArray(), userBucket, imgPrefix, "image/jpeg"); - minioUtil.uploadToMinio(gifOutput.toByteArray(), userBucket, gifPrefix, "image/gif"); - // 存储数据到数据库 - poseTransformation.setGifUrl(userBucket + "/" + gifPrefix); - poseTransformation.setVideoUrl(userBucket + "/" + videoPrefix); - poseTransformation.setFirstFrameUrl(userBucket + "/" + imgPrefix); - poseTransformation.setTaskStatus("Success"); - poseTransformation.setUpdateTime(LocalDateTime.now()); - poseTransformationMapper.updateById(poseTransformation); - } - - public byte[] downloadVideoOrImage(String url) { - try (CloseableHttpClient client = HttpClients.createDefault(); - InputStream in = client.execute(new HttpGet(url)).getEntity().getContent()) { - return IOUtils.toByteArray(in); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - // 增强版下载方法 todo 最好不要报错 - private byte[] downloadVideoOrImageWithValidation(String url) throws IOException { - CloseableHttpClient client = HttpClients.createDefault(); - HttpGet request = new HttpGet(url); - - try (CloseableHttpResponse response = client.execute(request)) { - // 状态码检查 - if (response.getStatusLine().getStatusCode() != 200) { - throw new IOException("Invalid status: " + response.getStatusLine()); - } - - // 内容类型检查 - org.apache.http.Header contentTypeHeader = response.getFirstHeader("Content-Type"); - if (contentTypeHeader == null || !contentTypeHeader.getValue().startsWith("image/")) { - throw new IOException("Invalid content type: " + - (contentTypeHeader != null ? contentTypeHeader.getValue() : "null")); - } - - // 内容长度检查 - org.apache.http.Header contentLengthHeader = response.getFirstHeader("Content-Length"); - if (contentLengthHeader != null) { - long length = Long.parseLong(contentLengthHeader.getValue()); - if (length <= 0) { - throw new IOException("Empty content"); - } - } - - return IOUtils.toByteArray(response.getEntity().getContent()); - } - } - - public byte[] downloadWithProxy(String url) throws IOException { - // 获取系统代理设置(适用于大多数VPN) -// String proxyHost = System.getProperty("http.proxyHost"); -// String proxyPort = System.getProperty("http.proxyPort"); - String proxyHost = "localhost"; - String proxyPort = "7890"; - - CloseableHttpClient client; - if (proxyHost != null && proxyPort != null) { - // 配置代理 - HttpHost proxy = new HttpHost(proxyHost, Integer.parseInt(proxyPort)); - RequestConfig config = RequestConfig.custom().setProxy(proxy).build(); - client = HttpClients.custom().setDefaultRequestConfig(config).build(); - } else { - client = HttpClients.createDefault(); - } - - try { - return client.execute(new HttpGet(url), response -> { - if (response.getStatusLine().getStatusCode() == 200) { - return IOUtils.toByteArray(response.getEntity().getContent()); - } else { - throw new IOException("HTTP Error: " + response.getStatusLine()); - } - }); - } finally { - client.close(); - } - } - - public static void generateGif(FFmpegFrameGrabber grabber, OutputStream output, - int durationSec, int frameCount) throws Exception { - Java2DFrameConverter converter = new Java2DFrameConverter(); - AnimatedGifEncoder gifEncoder = new AnimatedGifEncoder(); - - // 配置GIF参数 - gifEncoder.start(output); - gifEncoder.setDelay(100); // 每帧延迟(毫秒) - gifEncoder.setRepeat(0); // 0=无限循环 - - int totalFrames = (int) (grabber.getFrameRate() * durationSec); - int step = Math.max(1, totalFrames / frameCount); - - // 逐帧处理 - for (int i = 0; i < totalFrames; i += step) { - grabber.setVideoFrameNumber(i); - BufferedImage frame = converter.convert(grabber.grabImage()); - if (frame != null) { - gifEncoder.addFrame(frame); - } - } - gifEncoder.finish(); - } - - /** - * Freepik - * To Product Image - */ - public String reimagineFreePik(String path, String prompt, String style) throws IOException { - String imageAsBase64 = minioUtil.getImageAsBase64(path); - log.info(minioUtil.getPreSignedUrl(path, CommonConstant.MINIO_IMAGE_EXPIRE_TIME)); - JSONObject requestBody = new JSONObject(); - requestBody.set("image", imageAsBase64); - requestBody.set("prompt", prompt); - requestBody.set("imagination", style); - - String resp = sendRequestUtil.sendFreepikPost(requestBody.toString()); - if (!StringUtil.isNullOrEmpty(resp)) { - JSONObject jsonResp = JSONUtil.parseObj(resp); - JSONObject data = JSONUtil.parseObj(jsonResp.get("data")); - String status = data.getStr("status"); - if (status.equals("COMPLETED")) { -// List generated = data.getBeanList("generated", String.class); - log.info("freepik 调用结果:{}", jsonResp); - return jsonResp.getStr("data"); - } - } - return null; - } - - /** - * imagePath 图片的minio地址 - * ollama - * prompt 助手 - */ - public String getImageDescription(String imagePath) { -/* // 1. 读取图片并编码为 Base64 - String imageAsBase64 = null; - try { - imageAsBase64 = minioUtil.getImageAsBase64(imagePath); - } catch (IOException e) { - throw new RuntimeException(e); - } - - // 2. 构建 JSON 请求体 - JSONObject message = new JSONObject(); - message.set("role", "user"); - message.set("content", "Please describe the clothing in the image and provide a line art description of the outfit. The description should allow for the reconstruction of the corresponding line art based on the details given."); - message.set("images", JSONUtil.createArray().set(imageAsBase64)); - - JSONObject requestBody = new JSONObject(); - requestBody.set("model", "llama3.2-vision"); - requestBody.set("messages", JSONUtil.createArray().set(message)); - requestBody.set("stream", false);*/ - -// log.info("request body:{}", requestBody); - JSONObject requestBody = new JSONObject(); - requestBody.set("img", imagePath); -// String description = sendRequestUtil.sendPost("http://localhost:8000/api/img2prompt", requestBody.toString()); - String description = sendRequestUtil.sendPost("http://18.167.251.121:9994/api/img2prompt", requestBody.toString()); - if (StringUtil.isNullOrEmpty(description)) { - throw new BusinessException("从ollama获取图片描述失败"); - } - /*Object msg = JSONUtil.parseObj(resp).get("message"); - String description = JSONUtil.parseObj(msg).getStr("content");*/ - log.info("image :{} \n, description: {}", - minioUtil.getPreSignedUrl(imagePath, CommonConstant.MINIO_IMAGE_EXPIRE_TIME), description); - return description; - } - - private String resolveModelType(String taskId, String func) { - // 判断当前task来自哪个模型 - if (!StringUtil.isNullOrEmpty(func) - && func.equals(CreditsEventsEnum.POSE_TRANSFORMATION.getValue())) { - List poseTransformations = poseTransformationMapper.selectList( - new QueryWrapper().eq("unique_id", taskId)); - if (!poseTransformations.isEmpty() - && !StringUtil.isNullOrEmpty(poseTransformations.get(0).getModelName()) - && poseTransformations.get(0).getModelName().equals("wx")) { - return "wx"; - } else { - return "local"; - } - } - - Generate generate = selectByUniqueId(taskId); - if (Objects.nonNull(generate) && - !StringUtil.isNullOrEmpty(generate.getModelName()) && - (generate.getModelName().equals("wx") - || generate.getModelName().equals("freepik") - || generate.getModelName().equals("flux"))) { - return generate.getModelName(); - } else { - return "local"; - } - } - - public static String extractGender(String text) { - // 匹配末尾的 (Male) 或 (Female),忽略大小写 - Pattern pattern = Pattern.compile("\\(([Mm]ale|[Ff]emale)\\)$"); - Matcher matcher = pattern.matcher(text); - - if (matcher.find()) { - return matcher.group(1); // 返回括号内的内容 - } - return null; // 未匹配到性别 - } - - /** - * 接入flux模型,用于imageToSketch(sketch extract) || relighting || to product image - * - * @param func 功能枚举名 - * @param prompt 用户输入 - * @param imagePath 图片minio路径 - * @return 返回taskId,用于异步获取结果 - */ - public String flux(CreditsEventsEnum func, String prompt, String imagePath, boolean childStyle) { - String fluxRequestUrl = "https://api.bfl.ai/v1/flux-kontext-pro"; - if (StringUtil.isNullOrEmpty(prompt)) { - switch (func) { - case RELIGHT_FLUX: - prompt = "a model standing on the beautiful beach, ultra high quality, 8k"; - break; - case IMAGE_TO_SKETCH_FLUX: - prompt = "generate the sketch of the image, simple line, ultra high quality"; - break; - case TO_PRODUCT_IMAGE_FLUX: - prompt = "change the image to real style, ultra high quality, 8k"; - if (childStyle) prompt = prompt + ", Children's face"; - break; - } - } else { - prompt = modifyPrompt(prompt, null, func.getName(), null); - switch (func) { - case PATTERN: - prompt = "pattern image, " + prompt; - break; - case SKETCH_BOARD: - prompt = "a single item of sketch of " + prompt + ", clean white background, simple lines"; - break; - } - } - JSONObject requestBody = new JSONObject(); - - requestBody.set("prompt", prompt); - requestBody.set("seed", 42); - requestBody.set("aspect_ratio", "9:16"); - requestBody.set("output_format", "png"); - - log.info("flux 请求入参:{}", requestBody); - if (prompt.isEmpty())throw new BusinessException("test"); - if (!StringUtil.isNullOrEmpty(imagePath)) { - try { - String imageAsBase64 = null; - if (func.equals(TO_PRODUCT_IMAGE_FLUX)) { - imageAsBase64 = addWhiteBackground(imagePath); - } - if (StringUtil.isNullOrEmpty(imageAsBase64)) { - imageAsBase64 = minioUtil.getImageAsBase64(imagePath); - } - requestBody.set("input_image", imageAsBase64); - } catch (IOException e) { - log.error("获取图片的base64格式失败,{}", String.valueOf(e)); - throw new BusinessException("Failed to obtain the image in base64 format."); - } - } - - String resp = sendRequestUtil.sendFluxPost(fluxRequestUrl, requestBody.toString()); - JSONObject respObj = JSONUtil.parseObj(resp); - log.info("flux 发起生成请求返回结果: {}", respObj); - String taskId = respObj.getStr("id"); - if (StringUtil.isNullOrEmpty(taskId)) { - requestBody.set("input_image", imagePath); - log.error("flux生成任务创建失败,func :{}, requestBody:{}", func.getName(), requestBody); - throw new BusinessException("Failed to generate task. Please retry later."); - } - - String pollingUrl = respObj.getStr("polling_url"); - String key = RedisUtil.FLUX_POLLING_URL + taskId; - redisUtil.addToString(key, pollingUrl, CommonConstant.GENERATE_RESULT_EXPIRE_TIME); - // 添加到api_generate表中,以便之后对结果查询做补偿 - apiGenerateService.addAPIGenerateRecordAsync(UserContext.getUserHolder().getId(), taskId, func.getName(), "flux", "Pending"); - - return taskId; - } - - @Override - public String getFluxResult(String taskId, String objectName) { - // 获取轮询URL - String pollingUrl = redisUtil.getFromString(RedisUtil.FLUX_POLLING_URL + taskId); - - // 准备请求参数 - String fluxResultRequestUrl = StringUtil.isNullOrEmpty(pollingUrl) - ? "https://api.bfl.ai/v1/get_result" - : pollingUrl; - - HashMap params = new HashMap<>(); - if (StringUtil.isNullOrEmpty(pollingUrl)) { - params.put("id", taskId); - } - - // 发送请求并解析响应 - String resp = sendRequestUtil.sendGet(fluxResultRequestUrl, params); - log.info("获取flux生成的结果为:{}", resp); - - JSONObject respObj = JSONUtil.parseObj(resp); - String status = respObj.getStr("status"); - FluxTaskStatusEnum statusEnum = FluxTaskStatusEnum.fromName(status); - // 异步更新状态 - apiGenerateService.updateAPIGenerateStatusAsync(taskId, status); - - // 处理不同状态 - switch (statusEnum) { - case TASK_NOT_FOUND: - // 审核没过 - case REQUEST_MODERATED: - // 审核没过 - case CONTENT_MODERATED: - // 出错 - case ERROR: - return "Fail"; - case PENDING_F: - return "Pending"; - case SUCCESS: - // 已完成 获取结果 - return handleReadyStatus(respObj, objectName); - default: - return null; - } - } - - private String handleReadyStatus(JSONObject respObj, String objectName) { - // 1. 首先检查MinIO中是否已存在该图片 - if (minioUtil.doesObjectExist(userBucket, objectName)) { - return userBucket + "/" + objectName; - } - - // 2. 解析响应获取结果URL和生成时间 - JSONObject resultObj = JSONUtil.parseObj(respObj.getStr("result")); - String fluxResult = resultObj.getStr("sample"); - double endTime = resultObj.getDouble("end_time"); // 获取任务结束时间戳 - - // 3. 检查图片链接是否已过期(超过10分钟) - long currentTime = System.currentTimeMillis() / 1000; // 当前Unix时间戳(秒) - long generateTime = (long) endTime; // 生成结束时间戳 - // 图片10分钟过期,保险起见,保留一分钟 - long tenMinutesInSeconds = 9 * 60; - - if (currentTime - generateTime > tenMinutesInSeconds) { - log.warn("Flux result image has expired, generateTime: {}, currentTime: {}", - generateTime, currentTime); - return null; - } - - // 4. 图片未过期,下载并上传到MinIO - try { - byte[] bytes = downloadVideoOrImage(fluxResult); - minioUtil.uploadToMinio(bytes, userBucket, objectName, "image/png"); - return userBucket + "/" + objectName; - } catch (Exception e) { - log.error("Failed to download or upload Flux result image", e); - return null; - } - } - - private GenerateResultVO getFluxResultAndSave(String taskId) { - Generate generate = selectByUniqueId(taskId); - if (Objects.nonNull(generate)) { - GenerateDetail generateDetail = generateDetailMapper.selectOne(new QueryWrapper().eq("generate_id", generate.getId())); - Long accountId = generate.getAccountId(); - String objectName = accountId + "/imageToSketch/" + taskId + ".png"; - String fluxResult = getFluxResult(taskId, objectName); - if (Objects.isNull(generateDetail)) { - if (StringUtil.isNullOrEmpty(fluxResult)) { - return new GenerateResultVO(taskId, "Fail"); - } else if (fluxResult.equals("Fail") || fluxResult.equals("Pending")) { - String status = fluxResult.equals("Fail") ? "Fail" : "Executing"; - return new GenerateResultVO(taskId, status); - } - generateDetail = new GenerateDetail(generate.getId(), fluxResult, - MD5Utils.encryptFile( - minioUtil.getPreSignedUrl(fluxResult, CommonConstant.MINIO_IMAGE_EXPIRE_TIME), false), - LocalDateTime.now()); - generateDetailMapper.insert(generateDetail); - // 扣积分 - Boolean flag = creditsService.taskCreditsDeduction(accountId, taskId); - if (flag) creditsService.updateChangedCredits(String.valueOf(accountId), taskId); - } else if (StringUtil.isNullOrEmpty(generateDetail.getUrl())) { - // 结果已经存入db,一般走不到这条线 - generateDetail.setGenerateId(generate.getId()); - generateDetail.setUrl(fluxResult); - generateDetail.setMd5(MD5Utils.encryptFile( - minioUtil.getPreSignedUrl(fluxResult, CommonConstant.MINIO_IMAGE_EXPIRE_TIME), false)); - generateDetail.setUpdateDate(new Date()); - generateDetailMapper.updateById(generateDetail); - } - String url = generateDetail.getUrl(); - String category ; - if (generate.getLevel1Type().equals(SKETCH_BOARD.getRealName())){ - category = pythonService.getClothCategory(url, extractGender(generate.getGenerateType())); - } else { - category = generate.getLevel2Type(); - } - - return new GenerateResultVO(taskId, generateDetail.getId(), - minioUtil.getPreSignedUrl(url, CommonConstant.MINIO_IMAGE_EXPIRE_TIME), "Success", category); - } else { - throw new BusinessException("unknown generate"); - } - } - - private String addWhiteBackground(String minioPath) { - // 1、先通过后缀判断输入图片类型有没有透明通道 - String extension = minioPath.substring(minioPath.lastIndexOf(".") + 1); - - // 2、如果有,为其添加白色背景 - if (extension.equals("png")) { - return minioUtil.changeToWhiteBackground(minioPath); - } else { - log.info("图片 {} 没有透明通道, 不用添加白底", minioPath); - return null; - } - } - - -} +package com.ai.da.service.impl; + +import cn.hutool.core.img.gif.AnimatedGifEncoder; +import cn.hutool.http.Header; +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpResponse; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import com.ai.da.common.config.exception.BusinessException; +import com.ai.da.common.constant.CommonConstant; +import com.ai.da.common.context.UserContext; +import com.ai.da.common.enums.*; +import com.ai.da.common.response.ResultEnum; +import com.ai.da.common.utils.*; +import com.ai.da.mapper.primary.*; +import com.ai.da.mapper.primary.entity.*; +import com.ai.da.model.dto.*; +import com.ai.da.model.enums.CollectionType; +import com.ai.da.model.enums.Module; +import com.ai.da.model.enums.SketchStyle; +import com.ai.da.model.vo.*; +import com.ai.da.python.PythonService; +import com.ai.da.service.*; +import com.alibaba.dashscope.aigc.imagesynthesis.ImageSynthesis; +import com.alibaba.dashscope.aigc.imagesynthesis.ImageSynthesisParam; +import com.alibaba.dashscope.aigc.imagesynthesis.ImageSynthesisResult; +import com.alibaba.dashscope.exception.ApiException; +import com.alibaba.dashscope.exception.NoApiKeyException; +import com.alibaba.dashscope.utils.JsonUtils; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.serializer.SerializerFeature; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; +import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException; +import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.google.gson.Gson; +import io.minio.errors.MinioException; +import io.netty.util.internal.StringUtil; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.io.IOUtils; +import org.apache.http.HttpHost; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.bytedeco.javacv.FFmpegFrameGrabber; +import org.bytedeco.javacv.Java2DFrameConverter; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.dao.DuplicateKeyException; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StringUtils; + +import javax.annotation.Resource; +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.*; +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static com.ai.da.common.enums.CollectionLevel1TypeEnum.*; +import static com.ai.da.common.enums.CreditsEventsEnum.PATTERN; +import static com.ai.da.common.enums.CreditsEventsEnum.TO_PRODUCT_IMAGE; +import static com.ai.da.common.enums.CreditsEventsEnum.TO_PRODUCT_IMAGE_FLUX; +import static com.ai.da.common.enums.WangXiangTaskStatusEnum.FAILED; +import static com.ai.da.common.enums.WangXiangTaskStatusEnum.UNKNOWN_W; + +@Slf4j +@Service +public class GenerateServiceImpl extends ServiceImpl implements GenerateService { + + @Resource + private CollectionElementMapper collectionElementMapper; + @Resource + private GenerateDetailMapper generateDetailMapper; + @Resource + private LibraryService libraryService; + @Resource + private PythonService pythonService; + @Resource + private CollectionElementService collectionElementService; + @Resource + private CreditsService creditsService; + @Resource + private MinioUtil minioUtil; + @Resource + private RabbitMQService rabbitMQService; + @Resource + private RedisUtil redisUtil; + @Resource + private GenerateCancelMapper generateCancelMapper; + @Resource + private SketchReconstructionMapper sketchReconstructionMapper; + @Resource + private SendRequestUtil sendRequestUtil; + @Resource + private ProjectService projectService; + @Resource + private CollectionSortService collectionSortService; + @Resource + private CloudTaskService cloudTaskService; + @Resource + private APIGenerateMapper apiGenerateMapper; + @Resource + private CollectionSortMapper collectionSortMapper; + @Resource + private SysFileService sysFileService; + @Resource + private LibraryModelPointService libraryModelPointService; + @Resource + private PoseTransformationMapper poseTransformationMapper; + @Resource + private CloudTaskMapper cloudTaskMapper; + @Resource + private ToProductImageResultMapper toProductImageResultMapper; + @Resource + private AccountService accountService; + @Resource + private APIGenerateService apiGenerateService; + @Resource + private UserLikeGroupService userLikeGroupService; + + @Value("${redis.key.orderForGenerate}") + private String consumptionOrderKey; + + @Value("${redis.key.generateCancelSet}") + private String cancelSetKey; + + @Value("${redis.key.generateExceptionMap}") + private String exceptionMapKey; + + @Value("${redis.key.generateResult}") + private String generateResultKey; + + @Value("${minio.bucketName.slogan}") + private String sloganBucket; + + @Value("${minio.bucketName.users}") + private String userBucket; + + @Value("${redis.key.relightResultKey}") + private String relightResultKey; + + @Value("${redis.key.toProductImageResultKey}") + private String toProductImageResultKey; + + @Value("${access.python.generate_sr_port}") + private String generateServicePort; + + @Value("${ALIYUN_API_KEY}") + private String ALIYUN_API_KEY; + + @Value("${FREEPIK_API_KEY}") + private String FREEPIK_API_KEY; + + + // 创建 Random 对象 + Random random = new Random(); + + @Override + public GenerateCaptionVO generateCaption(Long sketchElementId) { + CollectionElement collectionElement = collectionElementMapper.selectById(sketchElementId); + if (Objects.isNull(collectionElement)) { + throw new BusinessException("the.image.does.not.exist.please.reselect"); + } + String url = collectionElement.getUrl(); +// String caption = pythonService.generateSketchCaption(url); + GenerateCaptionVO recognized_caption = new GenerateCaptionVO("recognized caption"); + + return recognized_caption; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void generateThroughImageText(GenerateThroughImageTextDTO generateThroughImageTextDTO) { + // 1、获取用户信息 + Long accountId = generateThroughImageTextDTO.getUserId(); + String generateType = generateThroughImageTextDTO.getGenerateType(); + + // 2、判断必须入参是否为非空(在prepare阶段已校验) + Generate generate = new Generate(); + generate.setAccountId(accountId); + generate.setUniqueId(generateThroughImageTextDTO.getUniqueId()); + generate.setLevel1Type(generateThroughImageTextDTO.getLevel1Type()); + generate.setLevel2Type(generateThroughImageTextDTO.getLevel2Type()); + generate.setSeed(StringUtil.isNullOrEmpty(generateThroughImageTextDTO.getSeed()) ? "" : generateThroughImageTextDTO.getSeed()); + // 当level1type是sketchboard时,存数据库需要加上当前性别 + generate.setGenerateType(generate.getLevel1Type().equals(SKETCH_BOARD.getRealName()) ? + generateType + " (" + generateThroughImageTextDTO.getGender() + ")" : + generateType); + generate.setModelName(StringUtil.isNullOrEmpty(generateThroughImageTextDTO.getModelName()) ? ModelNameEnum.MODEL_0.getCode() : generateThroughImageTextDTO.getModelName()); + generate.setCreateDate(DateUtil.getByTimeZone(generateThroughImageTextDTO.getTimeZone())); + generate.setElementSource(StringUtil.isNullOrEmpty(generateThroughImageTextDTO.getDesignType()) ? null : generateThroughImageTextDTO.getDesignType()); + + String text = generateThroughImageTextDTO.getText(); + generate.setText(text); + Long elementId = generateThroughImageTextDTO.getCollectionElementId(); +// validateGeneraType(generate, text, elementId); + if (!StringUtil.isNullOrEmpty(text)) { + text = modifyPrompt(text, generate, generateThroughImageTextDTO.getLevel1Type(), generateThroughImageTextDTO.getAgeGroup()); + } + + // todo 这一步现在还是有必要的吗? + // 2.1 sketch或print在t_collection_element表/t_library表中的信息是否需要更新 如 level2Type + CollectionElement collectionElement = collectionElementService.editLevel2Type(elementId, generateThroughImageTextDTO.getLevel2Type(), generateThroughImageTextDTO.getDesignType()); + + // 3、向模型发起请求 + String mode = GenerateModeEnum.TEXT.getValue().equals(generateType) ? + GenerateModeEnum.TEXT.getType() : + GenerateModeEnum.TEXT_IMAGE.getType(); + String category = generateThroughImageTextDTO.getLevel1Type().equals(SKETCH_BOARD.getRealName()) ? "sketch" : + generateThroughImageTextDTO.getLevel1Type().equals(PRINT_BOARD.getRealName()) ? "print" : "moodboard"; + String path = CommonConstant.GENERATE_PATH; + String port = generateServicePort; + String jsonString = ""; + HashMap params = new HashMap<>(); + String version = null; + if (!StringUtil.isNullOrEmpty(generateThroughImageTextDTO.getModelName()) && generateThroughImageTextDTO.getModelName().equals("high")) { + version = "high"; + params.put("version", "high"); + } else if (!StringUtil.isNullOrEmpty(generateThroughImageTextDTO.getModelName()) && generateThroughImageTextDTO.getModelName().equals("fast")) { + version = "fast"; + params.put("version", "fast"); + } + // 3.1 确定不同类型的印花分别调哪个接口 + if (generateThroughImageTextDTO.getLevel1Type().equals(PRINT_BOARD.getRealName())) { + switch (generateThroughImageTextDTO.getLevel2Type()) { + case "Logo": + path = CommonConstant.GENERATE_SINGLE_LOGO; + params.put("tasks_id", generateThroughImageTextDTO.getUniqueId()); + params.put("prompt", text); + params.put("seed", generateThroughImageTextDTO.getSeed()); + jsonString = JSON.toJSONString(params, SerializerFeature.WriteMapNullValue); + break; + case "Slogan": + path = CommonConstant.GENERATE_SLOGAN; + port = CommonConstant.PYTHON_PORT_9997; + params.put("num_point", "16"); + params.put("tasks_id", generateThroughImageTextDTO.getUniqueId()); + params.put("prompt", text); + params.put("image_url", collectionElement.getUrl()); + jsonString = JSON.toJSONString(params, SerializerFeature.WriteMapNullValue); + break; + case "Pattern": + GenerateToPythonDTO generateToPythonDTO = new GenerateToPythonDTO(generateThroughImageTextDTO.getUniqueId(), text, Objects.isNull(collectionElement) ? "" : collectionElement.getUrl(), + mode, category, generateThroughImageTextDTO.getGender(), version); + jsonString = JSON.toJSONString(generateToPythonDTO, SerializerFeature.WriteMapNullValue); + } + } else { + GenerateToPythonDTO generateToPythonDTO = new GenerateToPythonDTO(generateThroughImageTextDTO.getUniqueId(), text, Objects.isNull(collectionElement) ? "" : collectionElement.getUrl(), + mode, category, generateThroughImageTextDTO.getGender(), version); + jsonString = JSON.toJSONString(generateToPythonDTO, SerializerFeature.WriteMapNullValue); + } + + + Boolean requestResult = pythonService.generateSketchOrPrint(jsonString, port, path); + + // 4、将请求信息落库,将本次generate的请求信息添加到t_generate表中 + save(generate); + + // 5、将本次请求存入redis + String key = generateResultKey + ":" + generateThroughImageTextDTO.getUniqueId(); + String status; + if (requestResult) { + status = "Executing"; + } else { + status = "Fail"; + } + GenerateResultVO generateResultVO = new GenerateResultVO(generateThroughImageTextDTO.getUniqueId(), null, null, status); + redisUtil.addToString(key, new Gson().toJson(generateResultVO), CommonConstant.GENERATE_RESULT_EXPIRE_TIME); + + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void processGenerateResult(String taskId, String url, String category) { + // 1、处理模型返回的数据 + GenerateDetail generateDetail = new GenerateDetail(); + GenerateCollectionItemVO generateCollectionItemVO = new GenerateCollectionItemVO(); + Generate generate; + try { + generate = selectByUniqueId(taskId); + } catch (MybatisPlusException e) { + log.error(e.getMessage()); + if (e.getMessage().equals("One record is expected, but the query result is multiple records")) { + generate = selectListByUniqueId(taskId).get(0); + } else { + throw new BusinessException("There are some problems with database query, please try again."); + } + } +// Generate generate = selectByUniqueId(taskId); + String md5 = MD5Utils.encryptFile(minioUtil.getPreSignedUrl(url, 24 * 60), Boolean.FALSE); + // 通过MD5值和level1Type,判断不同level1Type下相同的图片是否被like过 + List> libraryIdList = generateDetailMapper.getLibraryIdThroughMD5(md5, generate.getLevel1Type()); + if (!libraryIdList.isEmpty()) { + generateDetail.setIsLike((byte) 1); + generateDetail.setLibraryId(libraryIdList.get(0).get("library_id")); + generateCollectionItemVO.setIsLiked(Boolean.TRUE); + } + generateDetail.setUrl(url); + generateDetail.setGenerateId(generate.getId()); + generateDetail.setCreateDate(LocalDateTime.now()); + generateDetail.setMd5(md5); + // 将相应的url保存到数据库 + generateDetailMapper.insert(generateDetail); + + String uuid = taskId.substring(0, taskId.substring(0, taskId.lastIndexOf("-")).lastIndexOf("-")); + String key = generateResultKey + ":" + taskId; + String imageName = url.substring(url.lastIndexOf("/") + 1); + String status = imageName.equals("white_image.jpg") ? "Invalid" : "Success"; + if (StringUtil.isNullOrEmpty(category)){ + Generate generateRecord = selectByUniqueId(taskId); + category = generateRecord.getLevel2Type(); + } + GenerateResultVO generateResultVO = new GenerateResultVO(taskId, generateDetail.getId(), url, status, category); + // 更新redis + redisUtil.addToString(key, new Gson().toJson(generateResultVO), CommonConstant.GENERATE_RESULT_EXPIRE_TIME); + + // 执行积分扣除 + // ** 注:如果生成的图片都是空白 则不扣积分 + if (!status.equals("Invalid")) { + String accountId = taskId.substring(taskId.lastIndexOf("-") + 1); + Boolean flag = creditsService.taskCreditsDeduction(Long.parseLong(accountId), uuid); + if (flag) creditsService.updateChangedCredits(accountId, uuid); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void processToProductImageResult(String taskId, String url, String category) { + QueryWrapper qw = new QueryWrapper<>(); + qw.lambda().eq(ToProductImageResult::getTaskId, taskId); + List toProductImageResults = toProductImageResultMapper.selectList(qw); + if (CollectionUtils.isEmpty(toProductImageResults)) { + return; +// throw new BusinessException(""); + } + ToProductImageResult toProductImageResult = toProductImageResults.get(0); + toProductImageResult.setUrl(url); + toProductImageResult.setStatus("Success"); +// toProductImageResult.setResultType("ToProductImage"); + toProductImageResultMapper.updateById(toProductImageResult); + + String key = toProductImageResultKey + ":" + taskId; + String imageName = url.substring(url.lastIndexOf("/") + 1); + String status = imageName.equals("white_image.jpg") ? "Invalid" : "Success"; + GenerateResultVO generateResultVO = new GenerateResultVO(taskId, toProductImageResult.getId(), url, status, category); + redisUtil.addToString(key, new Gson().toJson(generateResultVO), CommonConstant.GENERATE_RESULT_EXPIRE_TIME); + + Long accountId = Long.parseLong(taskId.substring(taskId.lastIndexOf("-") + 1)); + if (!status.equals("Invalid")) { + // 4、扣除积分 + Boolean b = creditsService.taskCreditsDeduction(accountId, taskId); + // 3、记录积分变更 + if (b) creditsService.insertToCreditsDetail(accountId, + TO_PRODUCT_IMAGE.getName(), + TO_PRODUCT_IMAGE.getValue(), + "negative", null); + } + } + + public void updateToProductTaskStatus(String taskId, String status){ + QueryWrapper qw = new QueryWrapper<>(); + qw.lambda().eq(ToProductImageResult::getTaskId, taskId); + List toProductImageResults = toProductImageResultMapper.selectList(qw); + if (!CollectionUtils.isEmpty(toProductImageResults)) { + ToProductImageResult toProductImageResult = toProductImageResults.get(0); + if (StringUtil.isNullOrEmpty(toProductImageResult.getStatus()) || !toProductImageResult.getStatus().equals(status)){ + toProductImageResult.setStatus(status); + toProductImageResultMapper.updateById(toProductImageResult); + } + } + } + + private void validateGeneraType(Generate generate, String text, Long elementId) { + String generateType = ""; + if (StringUtil.isNullOrEmpty(text.trim()) && Objects.isNull(elementId)) { + throw new BusinessException("please.input.the.caption.or.choose.an.image"); + } else if (!StringUtil.isNullOrEmpty(text.trim()) && !Objects.isNull(elementId)) { + generateType = "text-image"; + generate.setText(text); + generate.setElementId(elementId); + } else if (!StringUtil.isNullOrEmpty(text.trim())) { + generateType = "text"; + generate.setText(text); + } else if (!Objects.isNull(elementId)) { + generateType = "image"; + generate.setElementId(elementId); + } + generate.setGenerateType(generateType); + + } + + private String modifyPrompt(String userInput, Generate generate, String level1Type, String ageGroup) { + String text = ""; + String prefix = ""; + if (userInput.startsWith("Painting Style") + || userInput.startsWith("Illustration Style") + || userInput.startsWith("Real Style")) { + prefix = userInput.substring(0, userInput.indexOf(",")) + ", "; + userInput = userInput.substring(userInput.indexOf(",") + 1); + } + String translated = prefix + pythonService.promptTranslate(userInput); + switch (level1Type) { + case "Moodboard": + text = translated + ",high quality"; +// generate.setText(text); + break; + case "Printboard": + case "Pattern": + text = translated; + /*if (prefix.contains("Painting Style")) { + text = "Picasso,increased color saturation,increased glossiness," + translated + ", fabric print, high quality"; + } else if (prefix.contains("Illustration Style")) { + text = "Flat coating,romantic,soft,pencil strokes,accentuating and widening the depth of pencil strokes,paper patterns,block colors,crayons,reducing image contrast,and hand drawn painting marks," + translated + ", fabric print, high quality"; + } else if (prefix.contains("Real Style")) { + text = "Hyper realism,3d,deepened projection,increased permutation value,increased concavity and convexity value," + translated + ", fabric print, high quality"; + } else { + text = translated; + }*/ +// text = userInput + ", fabric print, high quality"; +// generate.setText(text); + break; + case "Sketchboard": +// text = "clear lines, simple outlines monochrome white vector image of " + translated + ", no background, sketch flat, front view display, best quality, ultra-high resolution 8k"; + text = "a single item of sketch of " + translated + ", 4k, white background"; + if (!StringUtil.isNullOrEmpty(ageGroup) && ageGroup.equals("Child")) { + text = text + ", Children's clothing"; + } +// generate.setText(text); + default: + text = translated; + } + return text; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public GenerateLikeVO generateLike(GenerateLikeDTO generateLikeDTO) { + // 1、判断参数是否正确 + // 1.1 必须参数是否非空 + if (SKETCH_BOARD.getRealName().equals(generateLikeDTO.getLevel1Type())) { + if (StringUtil.isNullOrEmpty(generateLikeDTO.getLevel2Type())) { + throw new BusinessException("level2Type.cannot.be.empty"); + } + if (StringUtil.isNullOrEmpty(generateLikeDTO.getGender())) { + throw new BusinessException("gender.cannot.be.empty"); + } + } + if (PRINT_BOARD.getRealName().equals(generateLikeDTO.getLevel1Type())) { + if (StringUtil.isNullOrEmpty(generateLikeDTO.getLevel2Type())) { + throw new BusinessException("level2Type.cannot.be.empty"); + } + } + // 1.2 判断参数是否真实有效 + Long generateDetailId = generateLikeDTO.getGenerateDetailId(); + GenerateDetail generateDetail = generateDetailMapper.selectById(generateDetailId); + if (Objects.isNull(generateDetail)) { + throw new BusinessException("generateItem.does.not.exist"); + } + Generate generate = getById(generateDetail.getGenerateId()); + if (!generateLikeDTO.getLevel1Type().equals(generate.getLevel1Type())) { + throw new BusinessException("level1Type.does.not.match"); + } + + // 2、将like的图片信息存入library + // 2.1、不能重复喜欢 + // 2.1.1 判断该图片是否被喜欢过 + Library libraryDetail = libraryService.getById(generateDetail.getLibraryId()); + if ((Objects.nonNull(generateDetail.getLibraryId()) && !generateDetail.getLibraryId().equals(0L)) + || Objects.nonNull(libraryDetail)) { + throw new BusinessException("duplicate.likes.are.not.allowed"); + } + + // todo 2.1.2、判断library中是否有相同MD5的图片 + + // 2.2、添加到library + AuthPrincipalVo userInfo = UserContext.getUserHolder(); + Long accountId = userInfo.getId(); + Library library = setLibrary(accountId, generateLikeDTO, generateDetail.getUrl()); + libraryService.save(library); + + // 3、更新generateDetail表的isLike列和libraryId列 + updateLikeStatus(generateLikeDTO.getGenerateDetailId(), (byte) 1, library.getId(), generateLikeDTO.getTimeZone()); + + return new GenerateLikeVO(library.getId()); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean generateDislike(Long generateDetailId, String timeZone) { + // 1、确定generateDetail中是否有这条记录 + GenerateDetail generateDetail = generateDetailMapper.selectById(generateDetailId); + if (Objects.isNull(generateDetail)) { + throw new BusinessException("generateItem.does.not.exist"); + } + + // 2、修改generateDetail表中的isLike、libraryId字段 + updateLikeStatus(generateDetailId, (byte) 0, 0L, timeZone); + + // 3、确定library中是否添加该条记录 + Library libraryDetail = libraryService.getById(generateDetail.getLibraryId()); + if (Objects.isNull(libraryDetail)) { + return Boolean.TRUE; + } + + // 4、删除library相关记录 + libraryService.removeById(libraryDetail.getId()); + + // 5、返回成功 + return Boolean.TRUE; + } + + public Library setLibrary(Long accountId, GenerateLikeDTO generateLikeDTO, String imageUrl) { + Library library = new Library(); + library.setAccountId(accountId); + library.setLevel1Type(generateLikeDTO.getLevel1Type()); + library.setLevel2Type(StringUtil.isNullOrEmpty(generateLikeDTO.getLevel2Type()) ? null : generateLikeDTO.getLevel2Type()); + library.setLevel3Type(StringUtil.isNullOrEmpty(generateLikeDTO.getGender()) ? null : generateLikeDTO.getGender()); + library.setName(DateUtil.getTimeStamp(generateLikeDTO.getTimeZone()) + "_N_G"); + library.setUrl(imageUrl); + try { + library.setMd5(MD5Utils.encryptFile(minioUtil.download(imageUrl))); + } catch (MinioException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } + library.setCreateDate(DateUtil.getByTimeZone(generateLikeDTO.getTimeZone())); + return library; + } + + public void updateLikeStatus(Long generateDetailId, Byte hasLike, Long libraryId, String timeZone) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("id", generateDetailId); + + GenerateDetail generateDetail = new GenerateDetail(); + generateDetail.setIsLike(hasLike); + generateDetail.setLibraryId(libraryId); + generateDetail.setUpdateDate(DateUtil.getByTimeZone(timeZone)); + generateDetailMapper.update(generateDetail, queryWrapper); + } + + // 避免循环注入,已移到libraryService中 + public void updateLikeStatusBatch(List generateDetailIdList, Byte hasLike, Long libraryId, String timeZone) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.in("id", generateDetailIdList); + + GenerateDetail generateDetail = new GenerateDetail(); + generateDetail.setIsLike(hasLike); + generateDetail.setLibraryId(libraryId); + generateDetail.setUpdateDate(DateUtil.getByTimeZone(timeZone)); + + generateDetailMapper.update(generateDetail, queryWrapper); + } + + // 避免循环注入,已移到libraryService中 + public List selectBatchByLibraryId(List libraryId) { + QueryWrapper qw = new QueryWrapper<>(); + qw.in("library_id", libraryId); + + return generateDetailMapper.selectList(qw); + } + + @Override + public PrepareForGenerateVO prepareForGenerate(GenerateThroughImageTextDTO generateDTO) { + // 1、参数检查,判断必须参数是否为空 + validateRequiredParams(generateDTO); + + // 2、处理特殊模型情况(wx/flux) + if (isWxModel(generateDTO)) { + return handleWxModelGeneration(generateDTO); + } + if (isFluxPatternModel(generateDTO)) { + return handleFluxPatternGeneration(generateDTO); + } + + // 3、处理标准生成流程 + return handleStandardGeneration(generateDTO); + } + +// ============== 以下是辅助方法 ============== + + /** + * 参数校验 + */ + private void validateRequiredParams(GenerateThroughImageTextDTO generateDTO) { + if (Objects.isNull(generateDTO.getUserId())) { + throw new BusinessException("userId cannot be empty"); + } + + // Printboard必须要有level2Type + if (generateDTO.getLevel1Type().equals(PRINT_BOARD.getRealName()) + && StringUtil.isNullOrEmpty(generateDTO.getLevel2Type())) { + throw new BusinessException("level2Type.cannot.be.empty"); + } + } + + /** + * 判断是否为wx模型 + */ + private boolean isWxModel(GenerateThroughImageTextDTO generateDTO) { + return !StringUtil.isNullOrEmpty(generateDTO.getModelName()) + && "wx".equals(generateDTO.getModelName()); + } + + /** + * 处理wx模型生成 + */ + private PrepareForGenerateVO handleWxModelGeneration(GenerateThroughImageTextDTO generateDTO) { + String taskId = createAsyncTask(generateDTO); + processCreditDeduction(generateDTO.getUserId(), taskId, CreditsEventsEnum.WX_TEXT2IMG); + return new PrepareForGenerateVO(Collections.singletonList(taskId), 200); + } + + /** + * 判断是否为flux模型且类型为Pattern + */ + private boolean isFluxPatternModel(GenerateThroughImageTextDTO generateDTO) { + return !StringUtil.isNullOrEmpty(generateDTO.getModelName()) + && "flux".equals(generateDTO.getModelName()) + && "Pattern".equals(generateDTO.getLevel2Type()); + } + + /** + * 处理flux pattern生成 + */ + private PrepareForGenerateVO handleFluxPatternGeneration(GenerateThroughImageTextDTO generateDTO) { + // 获取图片路径 + String imagePath = getImagePathForFlux(generateDTO); + + // 创建生成任务 + String taskId = flux(PATTERN, generateDTO.getText(), imagePath, false); + + // 保存生成记录 + saveGenerateRecord(generateDTO, taskId, imagePath); + + // 处理积分扣除 + processCreditDeduction(generateDTO.getUserId(), taskId, CreditsEventsEnum.FLUX_IMG2IMG); + + return new PrepareForGenerateVO(Collections.singletonList(taskId), 200); + } + + /** + * 获取flux模型需要的图片路径 + */ + private String getImagePathForFlux(GenerateThroughImageTextDTO generateDTO) { + if (Objects.isNull(generateDTO.getCollectionElementId()) + || StringUtil.isNullOrEmpty(generateDTO.getDesignType())) { + return null; + } + + switch (generateDTO.getDesignType()) { + case "collection": + CollectionElement element = collectionElementMapper.selectById(generateDTO.getCollectionElementId()); + return element != null ? element.getUrl() : null; + case "library": + Library library = libraryService.getById(generateDTO.getCollectionElementId()); + return library != null ? library.getUrl() : null; + default: + return null; + } + } + + /** + * 保存生成记录 + */ + private void saveGenerateRecord(GenerateThroughImageTextDTO generateDTO, String taskId, String imagePath) { + Generate generate = CopyUtil.copyObject(generateDTO, Generate.class); + generate.setAccountId(generateDTO.getUserId()); + generate.setUniqueId(taskId); + generate.setElementSource(generateDTO.getDesignType()); + generate.setElementId(generateDTO.getCollectionElementId()); + + // 确定生成类型 + String generateType = determineGenerateType(generateDTO); + generate.setGenerateType(generateType); + generate.setModelName("flux"); + generate.setCreateDate(new Date()); + + save(generate); + } + + /** + * 确定生成类型 + */ + private String determineGenerateType(GenerateThroughImageTextDTO generateDTO) { + if (Objects.nonNull(generateDTO.getCollectionElementId())) { + return StringUtil.isNullOrEmpty(generateDTO.getText()) ? "image" : "text-image"; + } + return "text"; + } + + /** + * 处理标准生成流程 + */ + private PrepareForGenerateVO handleStandardGeneration(GenerateThroughImageTextDTO generateDTO) { + // 确定积分事件和生成次数 + GenerationConfig config = determineGenerationConfig(generateDTO); + + // 对Slogan功能限流 + if (config.creditsEvent.equals(CreditsEventsEnum.SLOGAN)){ + boolean b = redisUtil.allowRequest("Slogan"); + if (!b){ + return new PrepareForGenerateVO(429); + } + } + + // 校验积分是否足够 + validateCredits(config.creditsEvent); + + // 创建生成任务 + List taskIds = createGenerationTasks(generateDTO, config.times); + + // 处理积分扣除(使用第一个任务的UUID前缀) + processCreditDeduction(generateDTO.getUserId(), taskIds.get(0).split("-")[0], config.creditsEvent); + + return new PrepareForGenerateVO(taskIds, 200); + } + + /** + * 确定生成配置(积分事件和生成次数) + */ + private GenerationConfig determineGenerationConfig(GenerateThroughImageTextDTO generateDTO) { + CreditsEventsEnum creditsEvent = CreditsEventsEnum.OTHER; + int times = 4; + + // 根据不同类型确定配置 + // high -> 生成图片质量高,但生成速度慢,每次生成只返回一张图片 + // fast -> 生成图片质量低,但生成速度快,每次生成返回四张图片 + switch (generateDTO.getLevel1Type()) { + case "Printboard": + GenerationConfig generationConfig = handlePrintboardConfig(generateDTO); + creditsEvent = generationConfig.creditsEvent; + times = generationConfig.times; + break; + case "Moodboard": + creditsEvent = CreditsEventsEnum.MOOD_BOARD; + if (isHighModel(generateDTO)) { + creditsEvent = CreditsEventsEnum.LOCAL_TEXT2IMG_HIGH; + times = 1; + } + break; + case "Sketchboard": + creditsEvent = CreditsEventsEnum.SKETCH_BOARD; + if (isHighModel(generateDTO)) { + creditsEvent = CreditsEventsEnum.LOCAL_TEXT2IMG_HIGH; + times = 1; + } + break; + } + + return new GenerationConfig(creditsEvent, times); + } + + /** + * 处理Printboard的特殊配置 + */ + private GenerationConfig handlePrintboardConfig(GenerateThroughImageTextDTO generateDTO) { + String level2Type = generateDTO.getLevel2Type(); + CreditsEventsEnum creditsEvent = CreditsEventsEnum.OTHER; + int times = 4; + + if (!CollectionLevel2TypeEnum.printType().contains(level2Type)) { + throw new BusinessException("unknown.parameter.level2Type"); + } + + switch (level2Type) { + case "Pattern": + // Pattern参数校验 + Generate generate = new Generate(); + validateGeneraType(generate, generateDTO.getText(), generateDTO.getCollectionElementId()); + generateDTO.setGenerateType(generate.getGenerateType()); + + creditsEvent = CreditsEventsEnum.PATTERN; + if (isHighModel(generateDTO)) { + creditsEvent = CreditsEventsEnum.LOCAL_TEXT2IMG_HIGH; + times = 1; + } + break; + + case "Slogan": + validateSloganParams(generateDTO); + processSloganImage(generateDTO); + creditsEvent = CreditsEventsEnum.SLOGAN; + times = 1; + break; + + case "Logo": + validateLogoParams(generateDTO); + generateDTO.setSeed(String.valueOf(random.nextInt(501))); + creditsEvent = CreditsEventsEnum.LOGO; + times = 1; + break; + } + + return new GenerationConfig(creditsEvent, times); + } + + /** + * 校验Slogan参数 + */ + private void validateSloganParams(GenerateThroughImageTextDTO generateDTO) { + if (StringUtil.isNullOrEmpty(generateDTO.getSloganBase64())) { + log.error("Printboard-Slogan模式下,slogan image为空"); + throw new BusinessException("slogan.image.cannot.be.empty"); + } + if (StringUtil.isNullOrEmpty(generateDTO.getText())) { + log.error("Printboard-Slogan模式下,slogan text为空"); + throw new BusinessException("slogan.style.cannot.be.empty"); + } + } + + /** + * 处理Slogan图片上传 + */ + private void processSloganImage(GenerateThroughImageTextDTO generateDTO) { + // 上传图片到服务器 + String path = minioUtil.base64UploadToPath(generateDTO.getSloganBase64(), sloganBucket, null); + String name = path.substring(path.lastIndexOf("/") + 1, path.lastIndexOf(".")); + + // 保存到数据库 + CollectionElement element = new CollectionElement(); + element.setAccountId(generateDTO.getUserId()); + element.setCollectionId(0L); + element.setLevel1Type(PRINT_BOARD.getRealName()); + element.setLevel2Type(CollectionLevel2TypeEnum.SLOGAN.getRealName()); + element.setName(name); + element.setUrl(path); + element.setHasPin((byte) 0); + element.setMd5(MD5Utils.encryptFile(minioUtil.getPreSignedUrl(path, 24 * 60), Boolean.FALSE)); + element.setCreateDate(DateUtil.getByTimeZone(generateDTO.getTimeZone())); + collectionElementService.save(element); + + // 更新DTO + generateDTO.setCollectionElementId(element.getId()); + generateDTO.setSloganBase64(null); + generateDTO.setDesignType("collection"); + } + + /** + * 校验Logo参数 + */ + private void validateLogoParams(GenerateThroughImageTextDTO generateDTO) { + if (StringUtil.isNullOrEmpty(generateDTO.getText().trim())) { + throw new BusinessException("please.input.the.prompt"); + } + } + + /** + * 判断是否为high模型 + */ + private boolean isHighModel(GenerateThroughImageTextDTO generateDTO) { + return !StringUtil.isNullOrEmpty(generateDTO.getModelName()) + && "high".equals(generateDTO.getModelName()); + } + + /** + * 校验积分是否足够 + */ + private void validateCredits(CreditsEventsEnum creditsEvent) { + if (!creditsService.creditsPreDeduction(creditsEvent, 1)) { + throw new BusinessException("remaining.credits.insufficient", ResultEnum.WARNING.getCode()); + } + } + + /** + * 创建生成任务 + */ + private List createGenerationTasks(GenerateThroughImageTextDTO generateDTO, int times) { + String uuid = UUID.randomUUID().toString(); + List taskIds = new ArrayList<>(); + + // 特殊处理:某些情况下需要清空modelName + if ("Printboard".equals(generateDTO.getLevel1Type()) + && !"Pattern".equals(generateDTO.getLevel2Type())) { + // Logo 和 Slogan 没有模型可选 + generateDTO.setModelName(null); + } + + for (int i = 1; i <= times; i++) { + String taskId = uuid + "-" + i + "-" + generateDTO.getUserId(); + taskIds.add(taskId); + generateDTO.setUniqueId(taskId); + + // 序列化为JSON + String jsonString = JSON.toJSONString(generateDTO); + + // 加入Redis队列 + addToRedisQueue(taskId, jsonString); + } + + return taskIds; + } + + /** + * 添加到Redis队列 + */ + private void addToRedisQueue(String taskId, String jsonString) { + // 加入排队队列 +// Double maxScore = redisUtil.getMaxScore(consumptionOrderKey); +// redisUtil.addToZSet(consumptionOrderKey, taskId, maxScore); + + // 加入结果映射 + String key = generateResultKey + ":" + taskId; + GenerateResultVO resultVO = new GenerateResultVO(taskId, null, null, "Waiting"); + redisUtil.addToString(key, new Gson().toJson(resultVO), CommonConstant.GENERATE_RESULT_EXPIRE_TIME); + + // 发布到MQ + rabbitMQService.publishMessageToGenerate(jsonString); + } + + /** + * 处理积分扣除 + */ + private void processCreditDeduction(Long userId, String taskId, CreditsEventsEnum creditsEvent) { + // 添加到Redis + creditsService.addRecordToCreditsDeduction(userId, taskId, creditsEvent); + // 预插入到数据库 + creditsService.preInsert(userId, creditsEvent.getName(), taskId, Boolean.TRUE, null); + } + +// ============== 配置类 ============== + + /** + * 生成任务配置类 + * 包含积分事件类型和生成次数 + */ + private static class GenerationConfig { + final CreditsEventsEnum creditsEvent; + final int times; + + GenerationConfig(CreditsEventsEnum creditsEvent, int times) { + this.creditsEvent = creditsEvent; + this.times = times; + } + } + + @Override + public Long getRankPosition(String uniqueId) { + // rank 从0开始 + return redisUtil.getRank(consumptionOrderKey, uniqueId); + } + + @Override + public List getGenerateResultList(List taskIdList) { + List results = new ArrayList<>(); + Set collect = new HashSet<>(); + boolean flag = true; + String type = null; + for (String taskId : taskIdList) { + String key = generateResultKey + ":" + taskId; + GenerateResultVO generateResultVO = new Gson().fromJson(redisUtil.getFromString(key), GenerateResultVO.class); + + if (flag) { + type = resolveModelType(taskId, null); + flag = false; + } + // 暂定万象每次生成1个 + if (type.equals("wx")) { + return Collections.singletonList(getAsyncTaskResult(taskId)); + } else if (type.equals("freepik")) { + results.add(generateResultVO); + continue; + } else if (type.equals("flux")) { + results.add(getFluxResultAndSave(taskId)); + continue; + } + + if (generateResultVO != null && !StringUtil.isNullOrEmpty(generateResultVO.getUrl())) { + String url = generateResultVO.getUrl(); + if (url.substring(url.lastIndexOf("/") + 1).equals("white_image.jpg")) { + generateResultVO.setStatus("Invalid"); + } else { + generateResultVO.setUrl(minioUtil.getPreSignedUrl(url, CommonConstant.MINIO_IMAGE_EXPIRE_TIME)); + } + } else if (generateResultVO == null) { + generateResultVO = new GenerateResultVO(); + } + + if (!StringUtil.isNullOrEmpty(generateResultVO.getStatus())) { + collect.add(generateResultVO.getStatus()); + } + results.add(generateResultVO); + } + + if (taskIdList.size() == 4 && collect.size() == 1 && collect.contains("Fail")) { + log.info("当前4个生成结果均为失败"); + throw new BusinessException("generate.result.below.standard"); + } + return results; + } + + + public Generate selectByUniqueId(String uniqueId) { + QueryWrapper qw = new QueryWrapper<>(); + qw.eq("unique_id", uniqueId); + + return getOne(qw); + } + + public List selectListByUniqueId(String uniqueId) { + QueryWrapper qw = new QueryWrapper<>(); + qw.eq("unique_id", uniqueId).orderByDesc("id"); + + return baseMapper.selectList(qw); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void cancelGenerate(Long userId, List uniqueIdList, String timeZone, String type) { + // todo 取消待优化 + uniqueIdList.forEach(uniqueId -> { + // 1、将需要取消的唯一id加入redis,以便及时取消生成 + redisUtil.addToSet(cancelSetKey, uniqueId); + + /*// 1、确认当前消息是否还在排队中 + Boolean exists = redisUtil.isElementExistsInZSet(consumptionOrderKey, uniqueId); + Boolean flag = Boolean.FALSE; + if (exists) flag = redisUtil.getRank(consumptionOrderKey, uniqueId) > 1L ? Boolean.TRUE : Boolean.FALSE; + // 不管flag的默认值是true还是false,只要exists为false,&& 将短路 + if (exists && flag) { + // 1.1、将需要取消的唯一id加入redis,以便及时取消生成 + redisUtil.addToSet(cancelSetKey, uniqueId); + // 1.2 将需要取消的id从redis的ConsumptionOrder中删除 + redisUtil.removeFromZSet(consumptionOrderKey, uniqueId); + } else { + // 2、判断该消息是否异常 + boolean hasKey = redisUtil.isElementExistsInMap(exceptionMapKey, uniqueId); + // 3、判断该消息是否已经消费结束 + Boolean existsInResult = redisUtil.isElementExistsInMap(resultMapKey, uniqueId); + if (!hasKey && !existsInResult) { + // 设置取等待状态为false + AsyncCallerUtil.waitingStatus.put(uniqueId, false); + // 3、直接发送取消请求到python端 + pythonService.cancelGenerateTask(uniqueId); + } + }*/ + String path; + if (type.equals("Logo")) { + path = CommonConstant.GENERATE_LOGO_SINGLE_CANCEL; + } else if (type.equals("PoseTransformation")) { + path = CommonConstant.POSE_TRANSFORMATION_CANCEL; + } else { + path = CommonConstant.GENERATE_CANCEL; + } + + String key = generateResultKey + ":" + uniqueId; + GenerateResultVO generateResultVO = new Gson().fromJson(redisUtil.getFromString(key), GenerateResultVO.class); + if (Objects.isNull(generateResultVO)) { + log.warn("任务不存在,无法取消"); + return; + } + // 判断当前task的状态是不是Fail + if (!generateResultVO.getStatus().equals("Fail")) { + // 2、不是,直接发送取消请求到python端 + pythonService.cancelGenerateTask(uniqueId, path); + // 3、更改result中当前taskId的状态 + redisUtil.addToString(key, new Gson().toJson(new GenerateResultVO(uniqueId, null, null, "Cancelled")), CommonConstant.GENERATE_RESULT_EXPIRE_TIME); + } + + // 3、考虑加一张表,专门用于记录哪些用户在什么时间进行了取消操作,包括已经异常的请求 + GenerateCancel generateCancel = new GenerateCancel(userId, uniqueId, DateUtil.getByTimeZone(timeZone)); + generateCancelMapper.insert(generateCancel); + }); + + + } + + @Override + public void processRelightResult(String taskId, String url, String category) { + QueryWrapper qw = new QueryWrapper<>(); + qw.lambda().eq(ToProductImageResult::getTaskId, taskId); + List toProductImageResults = toProductImageResultMapper.selectList(qw); + if (CollectionUtils.isEmpty(toProductImageResults)) { + return; +// throw new BusinessException(""); + } + ToProductImageResult toProductImageResult = toProductImageResults.get(0); + if (toProductImageResult.getBrightenValue() != null && toProductImageResult.getBrightenValue() != 1.0) { + pythonService.bright(url, toProductImageResult.getBrightenValue()); + } + toProductImageResult.setUrl(url); + toProductImageResult.setStatus("Success"); +// toProductImageResult.setResultType("Relight"); + toProductImageResultMapper.updateById(toProductImageResult); + + String key = relightResultKey + ":" + taskId; + String imageName = url.substring(url.lastIndexOf("/") + 1); + String status = imageName.equals("white_image.jpg") ? "Invalid" : "Success"; + GenerateResultVO generateResultVO = new GenerateResultVO(taskId, toProductImageResult.getId(), url, status, category); + redisUtil.addToString(key, new Gson().toJson(generateResultVO), CommonConstant.GENERATE_RESULT_EXPIRE_TIME); + + Long accountId = Long.parseLong(taskId.substring(taskId.lastIndexOf("-") + 1)); + if (!status.equals("Invalid")) { + // 4、扣除积分 + Boolean b = creditsService.taskCreditsDeduction(accountId, taskId); + // 3、记录积分变更 + if (b) creditsService.insertToCreditsDetail(accountId, + CreditsEventsEnum.RELIGHT.getName(), + CreditsEventsEnum.RELIGHT.getValue(), + "negative", null); + } + } + + // 判断试用用户试用generate机会是否使用完毕 每个board 3次机会 + private int getTrialsCount(Long userId, String level1Type) { + List getGenerateList = getGenerateByAccountId(userId, level1Type); + int trialsCount; + if (getGenerateList.isEmpty()) { + trialsCount = 0; + } else if (getGenerateList.size() == 1 || (getGenerateList.size() >= 4 && getGenerateList.size() / 4 == 1)) { + trialsCount = 1; + } else if (getGenerateList.size() == 2 || (getGenerateList.size() >= 4 && getGenerateList.size() / 4 == 2)) { + trialsCount = 2; + } else { + trialsCount = 2; + } + return trialsCount; + } + + public List getGenerateByAccountId(Long accountId, String level1Type) { + QueryWrapper qw = new QueryWrapper<>(); + qw.eq("account_id", accountId); + qw.eq("level1_type", level1Type); + + return baseMapper.selectList(qw); + } + + public List> getCountByUserAndTime(String startTime, String endTime, List accountIdList) { + List> byTypeAndTime = baseMapper.getByTypeAndTime(startTime, endTime, accountIdList); + return byTypeAndTime; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public GenerateResultVO imageToSketch(ImageToSketchDTO imageToSketchDTO, String collagePictureUrl, Long projectId) { + + Long accountId = UserContext.getUserHolder().getId(); + log.info("imageToSketch parameter : {}", imageToSketchDTO); + + // 检查积分是否够本次扣除 + CreditsEventsEnum event = CreditsEventsEnum.IMAGE_TO_SKETCH; + Boolean b = creditsService.checkCredits(accountId, event, 1); + if (!b) { + throw new BusinessException("remaining.credits.insufficient", ResultEnum.PROMPT.getCode()); + } + + String style = imageToSketchDTO.getStyle(); + String styleCode = style.equals(SketchStyle.THICK.getValue()) ? "1" : + style.equals(SketchStyle.MEDIUM.getValue()) ? "2" : + style.equals(SketchStyle.THIN.getValue()) ? "3" : "Custom"; + + // 线稿提取 + String sketchPath = requestSketchExtract(imageToSketchDTO, collagePictureUrl, accountId, styleCode); + // 存数据库 + Generate generate = saveExtractSketchRequest(imageToSketchDTO, collagePictureUrl, projectId, + accountId, styleCode, "local", "0"); + GenerateResultVO generateResultVO = saveExtractSketchResult(generate, sketchPath, imageToSketchDTO.getGender()); + // 积分扣除 + doCreditsSubtract(accountId, event); + + return generateResultVO; + } + + private String requestSketchExtract(ImageToSketchDTO imageToSketchDTO, String collagePictureUrl, + Long accountId, String styleCode) { + String imagePath; + if (StringUtil.isNullOrEmpty(collagePictureUrl)) { + CollectionElement collectionElement = collectionElementService.getById(imageToSketchDTO.getElementId()); + imagePath = collectionElement.getUrl(); + } else { + imagePath = collagePictureUrl; + } + + log.info(minioUtil.getPreSignedUrl(imagePath, CommonConstant.MINIO_IMAGE_EXPIRE_TIME)); + String imageName = UUID.randomUUID().toString(); + String objectName = accountId + "/imageToSketch/" + imageName; + + String styleImage; + if (!Objects.isNull(imageToSketchDTO.getStyleImageId())) { + CollectionElement styleElement = collectionElementService.getById(imageToSketchDTO.getElementId()); + styleImage = styleElement.getUrl(); + } else { + styleImage = ""; + } + String sketchPath = pythonService.imageToSketch(imagePath, userBucket, objectName, styleCode, styleImage); + log.info("初步图片提取结果:{}", sketchPath); + return sketchPath; + } + + private Generate saveExtractSketchRequest(ImageToSketchDTO imageToSketchDTO, String collagePictureUrl, + Long projectId, Long accountId, String styleCode, + String modelName, String taskId) { + // 存DB + Generate generate = new Generate(); + generate.setAccountId(accountId); + generate.setUniqueId(taskId); + generate.setLevel1Type(SKETCH_BOARD.getRealName()); + generate.setLevel2Type("ImageToSketch"); + generate.setElementSource("collection"); + generate.setElementId(imageToSketchDTO.getElementId()); + generate.setGenerateType("image(" + imageToSketchDTO.getGender() + ")"); + generate.setModelName(modelName); + generate.setSketchStyle(styleCode); + generate.setStyleImageElementId(imageToSketchDTO.getStyleImageId()); + generate.setProjectId(projectId); + generate.setInputImageUrl(collagePictureUrl); + generate.setCreateDate(new Date()); + baseMapper.insert(generate); + return generate; + } + + public GenerateResultVO saveExtractSketchResult(Generate generate, String sketchPath, String gender) { + // 将生成结果存入DB + GenerateDetail generateDetail = new GenerateDetail(); + generateDetail.setGenerateId(generate.getId()); + generateDetail.setUrl(sketchPath); + generateDetail.setIsLike((byte) 0); + generateDetail.setMd5(MD5Utils.encryptFile(minioUtil.getPreSignedUrl(sketchPath, 24 * 60), Boolean.FALSE)); + generateDetail.setCreateDate(LocalDateTime.now()); + generateDetailMapper.insert(generateDetail); + + String clothCategory = pythonService.getClothCategory(sketchPath, gender); + + return new GenerateResultVO(generate.getUniqueId(), generateDetail.getId(), + minioUtil.getPreSignedUrl(sketchPath, CommonConstant.MINIO_IMAGE_EXPIRE_TIME), "Success", clothCategory); + } + + public void doCreditsSubtract(Long accountId, CreditsEventsEnum event) { + BigDecimal existingCredits = accountService.getById(accountId).getCredits(); + BigDecimal subtract = existingCredits.subtract(new BigDecimal(event.getValue())); + accountService.updateCreditsAndEndTime(accountId, subtract.toString(), null); + creditsService.preInsert(accountId, event.getName(), null, Boolean.FALSE, event.getValue()); + } + + // 注入线程池(可在配置类中定义) + @Resource + private Executor asyncTaskExecutor; + + public String imageToSketchAsync(ImageToSketchDTO imageToSketchDTO, String collagePictureUrl, Long projectId) { + Long accountId = UserContext.getUserHolder().getId(); + log.info("imageToSketch parameter : {}", imageToSketchDTO); + + // 检查积分是否够本次扣除 +// CreditsEventsEnum event = CreditsEventsEnum.IMAGE_TO_SKETCH; + CreditsEventsEnum event = CreditsEventsEnum.IMAGE_TO_SKETCH_FLUX; + Boolean b = creditsService.checkCredits(accountId, event, 1); + if (!b) { + throw new BusinessException("remaining.credits.insufficient", ResultEnum.PROMPT.getCode()); + } + + // 生成唯一任务ID + String taskId; + if (!StringUtil.isNullOrEmpty(imageToSketchDTO.getModelName()) + && imageToSketchDTO.getModelName().equals("flux")) { + String imagePath; + // todo 拼贴图的线稿提取是否能用flux + if (StringUtil.isNullOrEmpty(collagePictureUrl)) { + CollectionElement collectionElement = collectionElementService.getById(imageToSketchDTO.getElementId()); + imagePath = collectionElement.getUrl(); + } else { + imagePath = collagePictureUrl; + } + taskId = flux(event, null, imagePath, false); + // 存数据库 + saveExtractSketchRequest(imageToSketchDTO, collagePictureUrl, projectId, + accountId, imageToSketchDTO.getStyle(), "flux", taskId); + + // 6、添加预扣除积分到redis + creditsService.addRecordToCreditsDeduction(accountId, taskId, event); + // 6.1 添加积分扣除记录到db + creditsService.preInsert(accountId, event.getName(), taskId, Boolean.TRUE, null); + + return taskId; + } + + taskId = UUID.randomUUID().toString(); + // 异步执行耗时操作(由于prompt提取耗时较长,页面暂时只提供flux生成,废弃以下部分) + CompletableFuture.runAsync(() -> { + try { + processImageToSketch(taskId, imageToSketchDTO, collagePictureUrl, projectId, accountId, event); + } catch (Exception e) { + log.error("异步处理图片转sketch失败, taskId: {}", taskId, e); + // 更新redis + redisUtil.addToString(generateResultKey + ":" + taskId, new Gson().toJson(new GenerateResultVO(taskId, "Fail")), CommonConstant.GENERATE_RESULT_EXPIRE_TIME); + } + }, asyncTaskExecutor); + return taskId; + } + + private void processImageToSketch(String taskId, ImageToSketchDTO imageToSketchDTO, + String collagePictureUrl, Long projectId, + Long accountId, CreditsEventsEnum event) throws IOException { + // 设置任务状态为处理中 + redisUtil.addToString(generateResultKey + ":" + taskId, new Gson().toJson(new GenerateResultVO(taskId, "Executing")), CommonConstant.GENERATE_RESULT_EXPIRE_TIME); + String style = imageToSketchDTO.getStyle(); + String styleCode = style.equals(SketchStyle.THICK.getValue()) ? "1" : + style.equals(SketchStyle.MEDIUM.getValue()) ? "2" : + style.equals(SketchStyle.THIN.getValue()) ? "3" : "Custom"; + // 请求记录存数据库 + Generate generate = saveExtractSketchRequest(imageToSketchDTO, collagePictureUrl, projectId, + accountId, styleCode, "freepik", taskId); + // 1、初步提取结果 + String sketchPath = requestSketchExtract(imageToSketchDTO, collagePictureUrl, accountId, styleCode); + // 2、获取输入图的描述 + String imageDescription = getImageDescription(sketchPath); + // 3、请求freepik reimage + String dataStr = reimagineFreePik(sketchPath, imageDescription, "vivid"); + if (StringUtil.isNullOrEmpty(dataStr)) { + throw new BusinessException("extract sketch failed"); + } + + JSONObject data = JSONUtil.parseObj(dataStr); + String upgradeImageUrl = data.getBeanList("generated", String.class).get(0); + String freepikTaskId = data.getStr("task_id"); + + // 4、下载图片 + byte[] bytes = downloadVideoOrImage(upgradeImageUrl); +// byte[] bytes = downloadWithProxy(upgradeImageUrl); + // 5、上传图片到minio保存 + String objectName = accountId + "/imageToSketch/" + freepikTaskId + ".png"; + minioUtil.uploadToMinio(bytes, userBucket, objectName, "image/png"); + // 6、保存结果到db + GenerateResultVO generateResultVO = saveExtractSketchResult(generate, userBucket + "/" + objectName, imageToSketchDTO.getGender()); + // 7、积分扣除 + doCreditsSubtract(accountId, event); + // 8、将结果存入Redis + redisUtil.addToString(generateResultKey + ":" + taskId, new Gson().toJson(generateResultVO), CommonConstant.GENERATE_RESULT_EXPIRE_TIME); + } + + // 对提取出来的sketch做调整 + // 输入 base64,以及 性别 分类,将图片添加到library + @Override + @Transactional(rollbackFor = Exception.class) + public GenerateResultVO modifySketch(GenerateModifyDTO generateModifyDTO) { + log.info("修改生成或library中的sketch或print"); + + // 提取常用参数 + Long accountId = UserContext.getUserHolder().getId(); + String base64 = generateModifyDTO.getBase64(); + String gender = generateModifyDTO.getGender(); + String category = generateModifyDTO.getCategory(); + Long originalId = generateModifyDTO.getOriginalId(); + String originalIdSource = generateModifyDTO.getOriginalIdSource(); + boolean isOverride = generateModifyDTO.getIsOverride(); + boolean isSketch = generateModifyDTO.getType().equals(SKETCH_BOARD.getRealName()); + + // 获取原始路径和可能的generateId + PathInfo pathInfo = getOriginalPathAndGenerateId(originalIdSource, originalId); + if (Objects.isNull(pathInfo)){ + throw new BusinessException("unknown sourceIdType", ResultEnum.PROMPT.getCode()); + } + + // 确定存储路径 + String storagePath = isOverride + ? pathInfo.originalPath.replaceFirst("^[^/]+/", "").replaceFirst("\\.[^.]+$", "") + : isSketch + ? accountId + "/sketchboard/" + gender + "/" + category + "/" + UUID.randomUUID() + : accountId + "/printboard/" + UUID.randomUUID(); + + // 上传到MinIO + String minioPath = minioUtil.base64UploadToPath(base64, userBucket, storagePath); + log.info("修改后的图片:{}", minioPath); + + // 保存到数据库并返回结果 + return originalIdSource.equals("Library") + ? handleLibrarySave(accountId, originalId, minioPath, category, gender, isOverride, generateModifyDTO.getType()) + : originalIdSource.equals("Generate") + ? handleGenerateSave(originalId, pathInfo.generateId, minioPath, category, isOverride) + : handleUploadSave(accountId, originalId, minioPath, category, isOverride, generateModifyDTO.getType()); + } + + private static class PathInfo { + String originalPath; + Long generateId; + + PathInfo(String originalPath, Long generateId) { + this.originalPath = originalPath; + this.generateId = generateId; + } + } + + private PathInfo getOriginalPathAndGenerateId(String originalIdSource, Long originalId) { + switch (originalIdSource) { + case "Library": + return new PathInfo(libraryService.getById(originalId).getUrl(), null); + case "Generate": + GenerateDetail detail = generateDetailMapper.selectById(originalId); + return new PathInfo(detail.getUrl(), detail.getGenerateId()); + case "Collection": + CollectionElement collectionElement = collectionElementMapper.selectById(originalId); + return new PathInfo(collectionElement.getUrl(), null); + default: + return null; + } + } + + private GenerateResultVO handleLibrarySave(Long accountId, Long libraryId, String minioPath, + String category, String gender, boolean isOverride, String level1Type) { + Library library; + String md5 = MD5Utils.encryptFile(minioUtil.getPreSignedUrl(minioPath, CommonConstant.MINIO_IMAGE_EXPIRE_TIME), false); + if (isOverride) { + library = new Library(); + library.setId(libraryId); + library.setUrl(minioPath); + library.setUpdateDate(new Date()); + libraryService.updateById(library); + } else { + library = new Library(accountId, level1Type, category, gender, minioPath, md5, new Date()); + libraryService.save(library); + libraryId = library.getId(); + } + return buildResultVO(libraryId, minioPath, category); + } + + private GenerateResultVO handleGenerateSave(Long originalId, Long generateId, String minioPath, + String category, boolean isOverride) { + GenerateDetail generateDetail = new GenerateDetail(); + String md5 = MD5Utils.encryptFile(minioUtil.getPreSignedUrl(minioPath, CommonConstant.MINIO_IMAGE_EXPIRE_TIME, true), Boolean.FALSE); + if (isOverride) { + generateDetail.setId(originalId); + generateDetail.setUrl(minioPath); + generateDetail.setUpdateDate(new Date()); + generateDetailMapper.updateById(generateDetail); + } else { + generateDetail.setGenerateId(generateId); + generateDetail.setUrl(minioPath); + generateDetail.setIsLike((byte)0); + generateDetail.setMd5(md5); + generateDetail.setCreateDate(LocalDateTime.now()); + generateDetailMapper.insert(generateDetail); + originalId = generateDetail.getId(); + } + return buildResultVO(originalId, minioPath, category); + } + + private GenerateResultVO handleUploadSave(Long accountId, Long originalId, String minioPath, + String category, boolean isOverride, String level1Type){ + CollectionElement collectionElement; + String md5 = MD5Utils.encryptFile(minioUtil.getPreSignedUrl(minioPath, CommonConstant.MINIO_IMAGE_EXPIRE_TIME), false); + if (isOverride){ + collectionElement = new CollectionElement(); + collectionElement.setId(originalId); + collectionElement.setUrl(minioPath); + collectionElement.setMd5(md5); + collectionElement.setUpdateDate(new Date()); + collectionElementMapper.updateById(collectionElement); + }else { + CollectionElement originalElement = collectionElementMapper.selectById(originalId); + String name = minioPath.substring(minioPath.lastIndexOf("/") + 1, minioPath.lastIndexOf(".")); + collectionElement = new CollectionElement(accountId, level1Type, category, name, minioPath, (byte)0, md5, new Date(), originalElement.getProjectId()); + collectionElementMapper.insert(collectionElement); + } + return buildResultVO(collectionElement.getId(), minioPath, category); + } + + private GenerateResultVO buildResultVO(Long id, String minioPath, String category) { + String url = minioUtil.getPreSignedUrl(minioPath, CommonConstant.MINIO_IMAGE_EXPIRE_TIME, true); + return new GenerateResultVO(id, url, "Success", category); + } + + public ToProductImageResultVO poseTransform(PoseTransformDTO poseTransformDTO) { + Long accountId = UserContext.getUserHolder().getId(); + Long projectId = poseTransformDTO.getProjectId(); + String productImage = poseTransformDTO.getProductImage(); + Integer poseId = poseTransformDTO.getPoseId(); + boolean wxTask = StringUtil.isNullOrEmpty(poseTransformDTO.getModelName()) && poseTransformDTO.getModelName().equals("wx"); + + // 1、判断用户当前积分是否够本次生成消耗 + CreditsEventsEnum creditsEventsEnum = wxTask ? CreditsEventsEnum.WX_ANIMATION : CreditsEventsEnum.LOCAL_ANIMATION; + Boolean preDeduction = creditsService.creditsPreDeduction(creditsEventsEnum, 1); + if (!preDeduction) { + throw new BusinessException("remaining.credits.insufficient", ResultEnum.WARNING.getCode()); + } + + // 3、生成唯一id 使用uuid,由于uuid重复的几率很小,故取消对uuid重复性的校验 + String taskId; + Boolean isRequestSuccess = false; + PoseTransformation poseTransformation = new PoseTransformation(); + if (!StringUtil.isNullOrEmpty(poseTransformDTO.getModelName()) && poseTransformDTO.getModelName().equals("wx")) { + taskId = animateAnyone(poseTransformDTO, accountId); + if (!StringUtil.isNullOrEmpty(taskId)){ + isRequestSuccess = true; + apiGenerateService.addAPIGenerateRecordAsync(accountId, taskId, Module.poseTransfer.getValue(), "wx", "Pending"); + } + poseTransformation.setModelName("wx"); + } else { + String uuid = UUID.randomUUID().toString(); + taskId = uuid + "-" + accountId; + isRequestSuccess = pythonService.poseTransformation(productImage, poseId, taskId); + } + + poseTransformation.setProjectId(projectId); + poseTransformation.setAccountId(accountId); + poseTransformation.setUniqueId(taskId); + poseTransformation.setProductImage(productImage); + poseTransformation.setPoseId(poseId); + poseTransformation.setIsLiked((byte) 0); + String taskStatus = isRequestSuccess ? "Executing" : "Fail"; + poseTransformation.setTaskStatus(taskStatus); + poseTransformation.setCreateTime(LocalDateTime.now()); + poseTransformationMapper.insert(poseTransformation); + // 当需要默认like + ToProductImageResultVO toProductImageResultVO = new ToProductImageResultVO(); + toProductImageResultVO.setParentId(poseTransformDTO.getParentId()); + toProductImageResultVO.setResultType(Module.poseTransfer.getValue()); + toProductImageResultVO.setTaskId(taskId); + toProductImageResultVO.setStatus(taskStatus); + toProductImageResultVO.setSourceUrl(minioUtil.getPreSignedUrl(productImage, CommonConstant.MINIO_IMAGE_EXPIRE_TIME)); + toProductImageResultVO.setPoseId(poseId); + toProductImageResultVO.setModelName(poseTransformDTO.getModelName()); + + if (Objects.nonNull(poseTransformDTO.getIsDefaultLike()) && poseTransformDTO.getIsDefaultLike()) { + // 满足条件下添加到like + poseTransformation.setIsLiked((byte) 1); + poseTransformation.setUpdateTime(LocalDateTime.now()); + poseTransformationMapper.updateById(poseTransformation); + CollectionSort collectionSort = addPoseTransferLike(poseTransformDTO, poseTransformation.getId()); + Integer reSort = collectionSortService.rearrangeChildSort(poseTransformation.getId(), CollectionType.POSE_TRANSFORM.getValue(), + poseTransformDTO.getParentId(), poseTransformDTO.getUserLikeSortId()); + toProductImageResultVO.setSort(Objects.isNull(reSort) ? Objects.isNull(collectionSort) ? null : collectionSort.getSort() : reSort); + toProductImageResultVO.setParentId(poseTransformDTO.getParentId()); + toProductImageResultVO.setUserLikeSortId(Objects.isNull(collectionSort) ? null : collectionSort.getId()); + } else if (Objects.nonNull(poseTransformDTO.getIsDefaultLike()) && Objects.nonNull(poseTransformDTO.getParentId())) { + toProductImageResultVO.setParentId(poseTransformDTO.getParentId()); + } + + + if (isRequestSuccess) { + // 6、添加预扣除积分到redis + creditsService.addRecordToCreditsDeduction(accountId, taskId, creditsEventsEnum); + // 6.1 添加积分扣除记录到db + creditsService.preInsert(accountId, creditsEventsEnum.getName(), taskId, Boolean.TRUE, null); + // 更新项目更新时间 + projectService.modifyProjectUpdateTime(projectId); + return toProductImageResultVO; + } + throw new BusinessException("pose transformation error", ResultEnum.ERROR.getCode()); + } + + private CollectionSort addPoseTransferLike(PoseTransformDTO poseTransformDTO, Long poseTransformationId) { + if (Objects.nonNull(poseTransformDTO.getParentId()) + && !poseTransformDTO.getParentId().equals(0L)) { + return disOrLikePose(poseTransformationId, "like", + poseTransformDTO.getProjectId(), poseTransformDTO.getParentId()); + } + return null; + } + + public void processPoseTransformResult(String taskId, String gifUrl, String videoUrl, String imageUrl) { + // 1、存储模型返回的数据 + PoseTransformation poseTransformation; + QueryWrapper qw = new QueryWrapper<>(); + qw.eq("unique_id", taskId); + List poseTransformations = poseTransformationMapper.selectList(qw); + if (poseTransformations != null && poseTransformations.size() > 1) { + log.warn("通过taskId {} 查询到的PoseTransformation的结果不止一条", taskId); + } else if (poseTransformations == null || poseTransformations.isEmpty()) { + return; + } + poseTransformation = poseTransformations.get(0); + poseTransformation.setGifUrl(gifUrl); + poseTransformation.setVideoUrl(videoUrl); + poseTransformation.setFirstFrameUrl(imageUrl); + poseTransformation.setTaskStatus("Success"); + poseTransformation.setUpdateTime(LocalDateTime.now()); + poseTransformationMapper.updateById(poseTransformation); + + String key = generateResultKey + ":" + taskId; + PoseTransformationVO poseTransformationVO = new PoseTransformationVO( + poseTransformation.getId(), taskId, gifUrl, videoUrl, imageUrl, (byte) 0, "Success"); + poseTransformationVO.setPoseId(poseTransformation.getPoseId()); + poseTransformationVO.setModelName(poseTransformation.getModelName()); + + // 2、更新redis + redisUtil.addToString(key, new Gson().toJson(poseTransformationVO), CommonConstant.GENERATE_RESULT_EXPIRE_TIME); + + // 3、执行积分扣除 + String accountId = taskId.substring(taskId.lastIndexOf("-") + 1); +// String uuid = taskId.substring(0, taskId.lastIndexOf("-")); + Boolean flag = creditsService.taskCreditsDeduction(Long.parseLong(accountId), taskId); + if (flag) creditsService.updateChangedCredits(accountId, taskId); + } + + public List getPoseTransformationResult(List taskIdList, Long projectId, Boolean like) { + List resultList = new ArrayList<>(); + + // 处理按taskId列表查询的情况 + if (taskIdList != null && !taskIdList.isEmpty()) { + for (String taskId : taskIdList) { + PoseTransformationVO vo = buildPoseTransformationVO(taskId, null); + if (vo != null) { + resultList.add(vo); + } + } + } + // 处理按projectId和like查询的情况 + else if (projectId != null) { + QueryWrapper queryWrapper = new QueryWrapper() + .eq("project_id", projectId) + .ne("task_status", "Fail"); + + if (like != null) { + queryWrapper.eq("is_liked", like ? 1 : 0); + } + + List poseTransformations = poseTransformationMapper.selectList(queryWrapper); + + if (!CollectionUtils.isEmpty(poseTransformations)) { + for (PoseTransformation item : poseTransformations) { + PoseTransformationVO vo = buildPoseTransformationVO(item.getUniqueId(), item); + if (vo != null && !isInvalidStatus(vo.getStatus())) { + resultList.add(vo); + } + } + } + } + + return resultList; + } + + private PoseTransformationVO buildPoseTransformationVO(String taskId, PoseTransformation dbItem) { + String type = resolveModelType(taskId, CreditsEventsEnum.POSE_TRANSFORMATION.getValue()); + String key = generateResultKey + ":" + taskId; + String resultJson = redisUtil.getFromString(key); + + PoseTransformationVO vo; + + // 1. 优先从Redis获取数据 + if (!StringUtil.isNullOrEmpty(resultJson)) { + vo = new Gson().fromJson(resultJson, PoseTransformationVO.class); + + // 设置数据库中的额外字段 + if (dbItem != null) { + vo.setId(dbItem.getId()); + vo.setIsLiked(dbItem.getIsLiked()); + } + + // 处理成功状态的数据 + if ("Success".equals(vo.getStatus()) && !"wx".equals(type)) { + processAllUrls(vo, dbItem); + } + + vo.setResultType(CollectionType.POSE_TRANSFORM.getValue()); + } + // 2. 处理wx类型的情况 + else if ("wx".equals(type)) { + vo = getAnimateResult(taskId); + } + // 3. 处理既没有Redis数据也不是wx类型的情况 + else { + // 如果有数据库记录 + if (dbItem != null) { + vo = CopyUtil.copyObject(dbItem, PoseTransformationVO.class); + vo.setTaskId(taskId); + + // 设置产品图片URL + if (dbItem.getProductImage() != null) { + vo.setProductImage(minioUtil.getPreSignedUrl( + dbItem.getProductImage(), CommonConstant.MINIO_IMAGE_EXPIRE_TIME)); + } + + // 如果视频URL为空,直接返回 + if (StringUtil.isNullOrEmpty(dbItem.getVideoUrl())) { + return vo; + } + + processAllUrls(vo, dbItem); + } + // 如果没有数据库记录 + else { + vo = new PoseTransformationVO(taskId, "Executing"); + } + } + + // 处理父ID逻辑 + processParentId(vo, dbItem != null ? dbItem : + poseTransformationMapper.selectOne(new QueryWrapper().eq("unique_id", taskId))); + + return vo; + } + + private void processAllUrls(PoseTransformationVO vo, PoseTransformation dbItem) { + // 处理各种URL + processUrl(vo.getGifUrl(), url -> + vo.setGifUrl(minioUtil.getPreSignedUrl(url, CommonConstant.MINIO_IMAGE_EXPIRE_TIME))); + processUrl(vo.getVideoUrl(), url -> + vo.setVideoUrl(minioUtil.getPreSignedUrl(url, CommonConstant.MINIO_IMAGE_EXPIRE_TIME))); + processUrl(vo.getFirstFrameUrl(), url -> + vo.setFirstFrameUrl(minioUtil.getPreSignedUrl(url, CommonConstant.MINIO_IMAGE_EXPIRE_TIME))); + } + + private void processParentId(PoseTransformationVO vo, PoseTransformation poseTransformation) { + if (poseTransformation != null) { + ToProductImageResult productResult = getProductResultByPath(poseTransformation.getProductImage()); + if (productResult != null) { + Long parentId = collectionSortService.getParentIdByElementIdAndElementType( + productResult.getId(), CollectionType.TO_PRODUCT_IMAGE.getValue()); + if (Objects.isNull(parentId)){ + parentId = userLikeGroupService.getUnlikedResultParentId(null, poseTransformation.getProductImage()); + } + vo.setParentId(parentId); + vo.setId(poseTransformation.getId()); + vo.setModelName(poseTransformation.getModelName()); + vo.setRelationType(Module.poseTransfer.getValue()); + vo.setProductImage(minioUtil.getPreSignedUrl(productResult.getUrl(), CommonConstant.MINIO_IMAGE_EXPIRE_TIME)); + } + } + } + + private boolean isInvalidStatus(String status) { + return "Invalid".equals(status) || "Fail".equals(status); + } + + public void updatePoseTransferStatus(String taskId, String status, PoseTransformation poseTransformation){ + if (Objects.isNull(poseTransformation)){ + poseTransformation = poseTransformationMapper.selectOne(new QueryWrapper().eq("unique_id", taskId)); + } + + if (StringUtil.isNullOrEmpty(poseTransformation.getTaskStatus()) || !status.equals(poseTransformation.getTaskStatus())){ + poseTransformation.setTaskStatus(status); + poseTransformation.setUpdateTime(LocalDateTime.now()); + poseTransformationMapper.updateById(poseTransformation); + } + } + + private ToProductImageResult getProductResultByPath(String minioPath) { + QueryWrapper qw = new QueryWrapper<>(); + qw.lambda().eq(ToProductImageResult::getUrl, minioPath); + return toProductImageResultMapper.selectOne(qw); + } + + /*public List getPoseTransformationResultList(Long projectId, boolean like) { + List poseTransformations = poseTransformationMapper.selectList(new QueryWrapper().eq("project_id", projectId) + .eq("is_liked", like ? 1 : 0).ne("task_status", "Fail")); + List vos = new ArrayList<>(); +// if (poseTransformations != null && poseTransformations.size() > 1){ + if (!CollectionUtils.isEmpty(poseTransformations)) { + for (PoseTransformation item : poseTransformations) { + String taskId = item.getUniqueId(); + String key = generateResultKey + ":" + taskId; + String resultJson = redisUtil.getFromString(key); + + PoseTransformationVO poseTransformationVO; + + if (!StringUtil.isNullOrEmpty(resultJson)) { + // 从Redis获取并转换数据 + poseTransformationVO = new Gson().fromJson(resultJson, PoseTransformationVO.class); + poseTransformationVO.setId(item.getId()); + poseTransformationVO.setIsLiked(item.getIsLiked()); + + // 处理成功状态的数据 + if ("Success".equals(poseTransformationVO.getStatus())) { + poseTransformationVO.setProductImage( + minioUtil.getPreSignedUrl(item.getProductImage(), CommonConstant.MINIO_IMAGE_EXPIRE_TIME)); + + // 处理各种URL + processUrl(poseTransformationVO.getGifUrl(), url -> + poseTransformationVO.setGifUrl(minioUtil.getPreSignedUrl(url, CommonConstant.MINIO_IMAGE_EXPIRE_TIME))); + processUrl(poseTransformationVO.getVideoUrl(), url -> + poseTransformationVO.setVideoUrl(minioUtil.getPreSignedUrl(url, CommonConstant.MINIO_IMAGE_EXPIRE_TIME))); + processUrl(poseTransformationVO.getFirstFrameUrl(), url -> + poseTransformationVO.setFirstFrameUrl(minioUtil.getPreSignedUrl(url, CommonConstant.MINIO_IMAGE_EXPIRE_TIME))); + } + + // 添加有效数据到结果列表 + if (!"Invalid".equals(poseTransformationVO.getStatus()) && !"Fail".equals(poseTransformationVO.getStatus())) { + vos.add(poseTransformationVO); + } + } else { + // 处理Redis中没有缓存的情况 + poseTransformationVO = CopyUtil.copyObject(item, PoseTransformationVO.class); + + poseTransformationVO.setTaskId(taskId); + poseTransformationVO.setProductImage( + minioUtil.getPreSignedUrl(item.getProductImage(), CommonConstant.MINIO_IMAGE_EXPIRE_TIME)); + + // todo 面对没有生成结束的情况,返回taskId + if (StringUtil.isNullOrEmpty(item.getVideoUrl())) { + vos.add(poseTransformationVO); + continue; + } + // 处理各种URL + processUrl(poseTransformationVO.getGifUrl(), url -> + poseTransformationVO.setGifUrl(minioUtil.getPreSignedUrl(url, CommonConstant.MINIO_IMAGE_EXPIRE_TIME))); + processUrl(poseTransformationVO.getVideoUrl(), url -> + poseTransformationVO.setVideoUrl(minioUtil.getPreSignedUrl(url, CommonConstant.MINIO_IMAGE_EXPIRE_TIME))); + processUrl(poseTransformationVO.getFirstFrameUrl(), url -> + poseTransformationVO.setFirstFrameUrl(minioUtil.getPreSignedUrl(url, CommonConstant.MINIO_IMAGE_EXPIRE_TIME))); + + vos.add(poseTransformationVO); + } + } + } + return vos; + }*/ + + // 辅助方法:处理URL + private void processUrl(String url, Consumer processor) { + if (!StringUtil.isNullOrEmpty(url) && !"None".equals(url)) { + processor.accept(url); + } + } + + public CollectionSort disOrLikePose(Long transformedId, String likeOrDislike, Long projectId, Long collectionSortParentId) { + PoseTransformation poseTransformation = poseTransformationMapper.selectById(transformedId); + CollectionSort collectionSort = null; + if (Objects.nonNull(poseTransformation)) { + if (likeOrDislike.equals("like")) { + poseTransformation.setIsLiked((byte) 1); + if (null != collectionSortParentId) { + collectionSort = collectionSortService.addCollectionSort(poseTransformation.getId(), CollectionType.POSE_TRANSFORM.getValue(), projectId, collectionSortParentId); + } + } else if (likeOrDislike.equals("dislike")) { + poseTransformation.setIsLiked((byte) 0); + if (null != collectionSortParentId) { + collectionSortService.deleteCollectionSort(poseTransformation.getId(), CollectionType.POSE_TRANSFORM.getValue(), projectId, collectionSortParentId); + } + } + poseTransformation.setUpdateTime(LocalDateTime.now()); + poseTransformationMapper.updateById(poseTransformation); + } + if (Objects.nonNull(collectionSort)) { + projectService.modifyProjectUpdateTime(projectId); + } + return collectionSort; + } + + public String modifyModelProportion(ModifyModelProportionDTO proportionDTO) { + log.info("modifyModelProportion params: {}", proportionDTO); + String name; + String gender; + Library model = null; + Long accountId = UserContext.getUserHolder().getId(); + String uuid = UUID.randomUUID().toString(); + // 所有修改的图片都另存为,不覆盖原图 + if (proportionDTO.getType().equals("Library")) { + model = libraryService.getById(proportionDTO.getId()); + String url = model.getUrl(); + name = url.substring(url.indexOf("/") + 1, url.lastIndexOf("/")) + "/" + uuid; +// gender = model.getLevel2Type(); + } else { + SysFileVO sysModel = sysFileService.getById(proportionDTO.getId()); + gender = sysModel.getLevel2Type(); + name = accountId + "/models/" + gender.toLowerCase() + "/" + uuid; + } + // 只需要将结果存入library + String modifiedModel = pythonService.modifyModelProportion(proportionDTO.getModelPath(), proportionDTO.getStretch(), name, proportionDTO.getTop(), proportionDTO.getBottom()); +// List imagesWidthAndHeight = minioUtil.getImagesWidthAndHeight(modifiedModel); + + /*// 存储修改后的模特到个人library + model = new Library(); + model.setAccountId(accountId); + model.setLevel1Type(LibraryLevel1TypeEnum.MODELS.getRealName()); + model.setLevel2Type(gender); + model.setName(uuid); + model.setUrl(modifiedModel); + model.setMd5(MD5Utils.encryptFile(minioUtil.getPreSignedUrl(modifiedModel, 24 * 60),false)); + model.setWidth(imagesWidthAndHeight.get(0)); + model.setHigh(imagesWidthAndHeight.get(1)); + model.setCreateDate(new Date()); + libraryService.save(model);*/ + +/* // 新建模特点位信息 + LibraryModelPoint libraryModelPoint = new LibraryModelPoint(); + libraryModelPoint.setModelType("Library"); + libraryModelPoint.setRelationId(model.getId()); + libraryModelPoint.setShoulderLeft(Arrays.toString(proportionDTO.getShoulderLeft())); + libraryModelPoint.setShoulderRight(Arrays.toString(proportionDTO.getShoulderRight())); + libraryModelPoint.setWaistbandLeft(Arrays.toString(proportionDTO.getWaistbandLeft())); + libraryModelPoint.setWaistbandRight(Arrays.toString(proportionDTO.getWaistbandRight())); + libraryModelPoint.setHandLeft(Arrays.toString(proportionDTO.getHandLeft())); + libraryModelPoint.setHandRight(Arrays.toString(proportionDTO.getHandRight())); + libraryModelPoint.setCreateDate(new Date()); + libraryModelPointService.save(libraryModelPoint);*/ + return minioUtil.getPreSignedUrl(modifiedModel, CommonConstant.MINIO_IMAGE_EXPIRE_TIME); + } + + /** + * String collagePicture(Base64) + * List elements + * File file + * + * @return + */ + @Transactional(rollbackFor = Exception.class) + public GenerateResultVO sketchReconstructionGenerate(SketchReconstructionDTO sketchReconstructionDTO) { +// log.info("sketchReconstructionGenerate params: {}", sketchReconstructionDTO); + + Long accountId = UserContext.getUserHolder().getId(); + // 1、线稿生成 + String collagePictureBase64 = sketchReconstructionDTO.getCollagePicture(); + String path = accountId + "/CollagePicture/" + UUID.randomUUID(); + String minioPath = minioUtil.base64UploadToPath(collagePictureBase64, userBucket, path); + + Long projectId = sketchReconstructionDTO.getProjectId(); + + GenerateResultVO generateResultVO = imageToSketch(new ImageToSketchDTO(null, "2", sketchReconstructionDTO.getGender()), minioPath, projectId); + QueryWrapper qw = new QueryWrapper<>(); + qw.eq("project_id", projectId); + SketchReconstruction sketchReconstruction = sketchReconstructionMapper.selectOne(qw); + + String url = generateResultVO.getUrl(); + // 找到路径的起始位置(从"://"之后查找第一个"/") + int pathStartIndex = url.indexOf("/", url.indexOf("://") + 3); + // 找到查询参数的起始位置("?" 的位置) + int queryStartIndex = url.indexOf("?"); + // 截取目标部分 + String targetPath = url.substring(pathStartIndex + 1, queryStartIndex); + + try { + if (Objects.isNull(sketchReconstruction)) { + sketchReconstruction = new SketchReconstruction(); + sketchReconstruction.setProjectId(projectId); + sketchReconstruction.setCollageImgSketchUrl(targetPath); + sketchReconstruction.setGenerateDetailId(generateResultVO.getId()); + sketchReconstruction.setGender(sketchReconstructionDTO.getGender()); + sketchReconstruction.setCreateTime(LocalDateTime.now()); + sketchReconstructionMapper.insert(sketchReconstruction); + } else { + sketchReconstruction.setCollageImgSketchUrl(targetPath); + sketchReconstruction.setGenerateDetailId(generateResultVO.getId()); + sketchReconstructionMapper.updateById(sketchReconstruction); + } + } catch (DuplicateKeyException e) { + // 如果发生唯一键冲突,说明其他请求已经创建了记录 + // 重新查询并更新 + log.info("sketch拼贴,唯一键(project_id)冲突,改为更新"); + sketchReconstruction = sketchReconstructionMapper.selectOne(qw); + sketchReconstruction.setCollageImgSketchUrl(targetPath); + sketchReconstruction.setGenerateDetailId(generateResultVO.getId()); + sketchReconstructionMapper.updateById(sketchReconstruction); + } + + return generateResultVO; + } + + public SketchReconstructionVO getSketchReconstruction(Long projectId) { + QueryWrapper qw = new QueryWrapper<>(); + qw.eq("project_id", projectId); + SketchReconstruction sketchReconstruction = sketchReconstructionMapper.selectOne(qw); + + SketchReconstructionVO vo = new SketchReconstructionVO(); + if (Objects.nonNull(sketchReconstruction) && Objects.nonNull(sketchReconstruction.getGenerateDetailId())) { + GenerateDetail generateDetail = generateDetailMapper.selectById(sketchReconstruction.getGenerateDetailId()); + vo.setCollageSketchUrl(minioUtil.getPreSignedUrl(generateDetail.getUrl(), CommonConstant.MINIO_IMAGE_EXPIRE_TIME)); + vo.setLiked(generateDetail.getIsLike().equals((byte) 1)); + String clothCategory = pythonService.getClothCategory(generateDetail.getUrl(), sketchReconstruction.getGender()); + String messageFromResource = BusinessException.getMessageFromResource(clothCategory.toUpperCase()); + vo.setCategory(clothCategory); + vo.setCategoryValue(messageFromResource); + } + List collectionElements = collectionElementService.getByProjectId(projectId); + if (!collectionElements.isEmpty()){ + collectionElements.forEach(item -> { + item.setUrl(StringUtil.isNullOrEmpty(item.getUrl()) ? null : minioUtil.getPreSignedUrl(item.getUrl(), CommonConstant.MINIO_IMAGE_EXPIRE_TIME)); + }); + vo.setUploadImages(collectionElements); + }else { + vo.setUploadImages(new ArrayList<>()); + } + return vo; + } + + public List> getAllPose() { + List> propertyList = PoseEnum.getPropertyList(); + propertyList.forEach(item -> { + item.put("gif", minioUtil.getPreSignedUrl(item.get("gif"), CommonConstant.MINIO_IMAGE_EXPIRE_TIME)); + item.put("firstFrame", minioUtil.getPreSignedUrl(item.get("firstFrame"), CommonConstant.MINIO_IMAGE_EXPIRE_TIME)); + }); + return propertyList; + } + + @Override + @Transactional + public void processPoseTransformResultBatch(String taskId, String gifUrl, String videoUrl, String imageUrl, String progress) { + // 1、存储模型返回的数据 + PoseTransformation poseTransformation; + QueryWrapper qw = new QueryWrapper<>(); + qw.eq("unique_id", taskId); + List poseTransformations = poseTransformationMapper.selectList(qw); + if (poseTransformations != null && poseTransformations.size() > 1) { + log.warn("通过taskId {} 查询到的PoseTransformation的结果不止一条", taskId); + } else if (poseTransformations == null || poseTransformations.isEmpty()) { + return; + } + poseTransformation = poseTransformations.get(0); + poseTransformation.setGifUrl(gifUrl); + poseTransformation.setVideoUrl(videoUrl); + poseTransformation.setFirstFrameUrl(imageUrl); + poseTransformation.setTaskStatus("Success"); + poseTransformation.setUpdateTime(LocalDateTime.now()); + poseTransformationMapper.updateById(poseTransformation); + + String taskIdBatch = poseTransformation.getTaskIdBatch(); + QueryWrapper cloudTaskQueryWrapper = new QueryWrapper<>(); + cloudTaskQueryWrapper.lambda().eq(CloudTask::getTaskId, taskIdBatch); + CloudTask cloudTask = cloudTaskMapper.selectOne(cloudTaskQueryWrapper); + if (Objects.nonNull(cloudTask)){ + cloudTask.setUpdateTime(LocalDateTime.now()); + cloudTaskMapper.updateById(cloudTask); + } +// if (Objects.nonNull(cloudTask)) { +// if (cloudTask.getCompletedNum() == null) { +// cloudTask.setCompletedNum(1); +// }else { +// cloudTask.setCompletedNum(cloudTask.getCompletedNum() + 1); +// } +// cloudTaskMapper.updateById(cloudTask); +// } + cloudTaskMapper.increaseCompletedNum(taskIdBatch); + + String key = generateResultKey + ":" + taskId; + PoseTransformationVO poseTransformationVO = new PoseTransformationVO( + poseTransformation.getId(), taskId, gifUrl, videoUrl, imageUrl, (byte) 0, "Success"); + + // 2、更新redis + redisUtil.addToString(key, new Gson().toJson(poseTransformationVO), CommonConstant.GENERATE_RESULT_EXPIRE_TIME); + + /*// 3、执行积分扣除 + String accountId = taskId.substring(taskId.lastIndexOf("-") + 1); + String uuid = taskId.substring(0, taskId.lastIndexOf("-")); + Boolean flag = creditsService.taskCreditsDeduction(Long.parseLong(accountId), taskIdBatch); + if (flag) creditsService.updateChangedCredits(accountId, taskIdBatch);*/ + } + + @Transactional + public void processPoseTransformResultBatch(String progress, String taskId) { + // 1、存储模型返回的数据 + PoseTransformation poseTransformation; + QueryWrapper qw = new QueryWrapper<>(); + qw.eq("unique_id", taskId); + List poseTransformations = poseTransformationMapper.selectList(qw); + log.info("poseTransformations : {}", poseTransformations); + if (poseTransformations != null && poseTransformations.size() > 1) { + log.warn("通过taskId {} 查询到的PoseTransformation的结果不止一条", taskId); + } else if (poseTransformations == null || poseTransformations.isEmpty()) { + return; + } + poseTransformation = poseTransformations.get(0); + String taskIdBatch = poseTransformation.getTaskIdBatch(); + log.info("progress:{}", progress); + log.info("taskIdBatch:{}", taskIdBatch); + if (progress.equals("OK")) { + if (!StringUtils.isEmpty(taskIdBatch)) { + cloudTaskService.completeTask(taskIdBatch); + } + }else if (progress.startsWith("0/")) { + if (!StringUtils.isEmpty(taskIdBatch)) { + cloudTaskService.startTask(taskIdBatch); + } + } + } + + + + @Transactional(rollbackFor = Exception.class) + public void deleteGeneratedPose(Long projectId, Long id) { + // 1. 权限校验 + Long accountId = UserContext.getUserHolder().getId(); + Project project = projectService.getById(projectId); + if (!project.getAccountId().equals(accountId)) { + throw new IllegalArgumentException("项目不属于当前账号"); + } + + // 2. 软删除主表数据 + int update = poseTransformationMapper.update(null, + new UpdateWrapper() + .eq("id", id) + .set("is_deleted", 1) + .set("update_time", LocalDateTime.now())); + + log.info("删除PoseTransfer 结果, id为{}, 影响行数={}", id, update); + + if (update == 0) return; + + // 3. 查询可能删除的多个排序项(存在脏数据可能,所有会查出多个) + List deletedItems = collectionSortMapper.selectList( + new QueryWrapper() + .eq("project_id", projectId) + .eq("relation_id", id) + .eq("relation_type", "PoseTransfer") + .orderByAsc("sort") + ); + + if (deletedItems.isEmpty()) return; + + // 4. 删除这些排序记录 + int deletedCount = collectionSortMapper.delete( + new QueryWrapper() + .eq("project_id", projectId) + .eq("relation_id", id) + .eq("relation_type", "PoseTransfer") + ); + + // 5. 重新调整剩余记录的排序(两种方案可选) + // 方案一:精确调整(每条被删除记录单独处理) + for (CollectionSort deletedItem : deletedItems) { + collectionSortMapper.update( + null, + new UpdateWrapper() + .eq("project_id", projectId) + .gt("sort", deletedItem.getSort()) + .setSql("sort = sort - 1") + ); + } + + log.info("删除PoseTransfer排序记录:id={}, 删除{}条,已重新排序后续记录", id, deletedCount); + } + + + + /** + * 万象专业版 + * 1、MoodBoard t2i + * 2、PrintBoard t2i + * 3、SketchBoard t2i + * 4、pose transfer 图生舞蹈视频-舞动人像AnimateAnyone + */ + + /** + * 创建异步任务 + * + * @return taskId + */ + public String createAsyncTask(GenerateThroughImageTextDTO generateDTO) { +// String prompt = "一间有着精致窗户的花店,漂亮的木质门,摆放着花朵"; + String level1Type = generateDTO.getLevel1Type(); + String level2Type = generateDTO.getLevel2Type(); + String prompt = generateDTO.getText(); + Long userId = generateDTO.getUserId(); + String gender = generateDTO.getGender(); + + // 添加预设prompt,使生成结果更加具有指向性(区分不同的board) + switch (level1Type) { + case "Moodboard": + break; + case "Printboard": + prompt = "pattern image, " + prompt; + break; + case "Sketchboard": + prompt = "a single item of sketch of " + prompt + ", clean white background, simple lines"; + break; + default: + log.warn("未知类型 type:{}", level1Type); + } + HashMap promptExtend = new HashMap<>(); + promptExtend.put("prompt_extend", false); + ImageSynthesisParam param = + ImageSynthesisParam.builder() + .apiKey(ALIYUN_API_KEY) + .model("wanx2.1-t2i-plus") + .prompt(prompt) + .n(1) + .size("1024*1024") + .parameters(promptExtend) + .build(); + + log.info(param.toString()); + + ImageSynthesis imageSynthesis = new ImageSynthesis(); + ImageSynthesisResult result = null; + try { + result = imageSynthesis.asyncCall(param); + } catch (Exception e) { + throw new RuntimeException(e.getMessage()); + } + String taskId = result.getOutput().getTaskId(); + log.info("wx text2image 请求生成:{}, taskId:{}", JsonUtils.toJson(result), taskId); + + Generate generate = new Generate(userId, taskId, level1Type, level2Type, prompt, "text(" + gender + ")", "wx", new Date()); + save(generate); + return taskId; + } + + /** + * 获取异步任务结果 + * + * @param taskId 任务id + */ + public GenerateResultVO getAsyncTaskResult(String taskId) { + ImageSynthesis imageSynthesis = new ImageSynthesis(); + ImageSynthesisResult result = null; + try { + //如果已经在环境变量中设置了 DASHSCOPE_API_KEY,wait()方法可将apiKey设置为null + result = imageSynthesis.fetch(taskId, ALIYUN_API_KEY); + log.info(JsonUtils.toJson(result)); + //PENDING:任务排队中; RUNNING:任务处理中; SUCCEEDED:任务执行成功; FAILED:任务执行失败; CANCELED:任务取消成功; UNKNOWN:任务不存在或状态未知 + String taskStatus = result.getOutput().getTaskStatus(); + + if (taskStatus.equals("SUCCEEDED")) { + List generates = selectListByUniqueId(taskId); + String url = result.getOutput().getResults().get(0).get("url"); + + String path = null; + if (!generates.isEmpty()) { + Generate generate = generates.get(0); + Long accountId = generate.getAccountId(); + + // 1、下载图片 +// InputStream inputStream = downloadImageFromAliyun(url); + byte[] bytes = downloadVideoOrImage(url); + // 2、上传图片到minio保存 + String objectName = accountId + "/" + generate.getLevel1Type().toLowerCase() + "/" + taskId + ".png"; + minioUtil.uploadToMinio(bytes, userBucket, objectName, "image/png"); + path = userBucket + "/" + objectName; + // 3、生成结果保存到db + GenerateDetail generateDetail = new GenerateDetail(); + generateDetail.setGenerateId(generate.getId()); + generateDetail.setUrl(path); + generateDetail.setMd5(MD5Utils.encryptFile(minioUtil.getPreSignedUrl(path, 24 * 60), false)); + generateDetail.setCreateDate(LocalDateTime.now()); + generateDetailMapper.insert(generateDetail); + // 4、扣积分 + Boolean flag = creditsService.taskCreditsDeduction(accountId, taskId); + if (flag) creditsService.updateChangedCredits(String.valueOf(generate.getAccountId()), taskId); + + GenerateResultVO generateResultVO = new GenerateResultVO(taskId, generateDetail.getId(), minioUtil.getPreSignedUrl(path, 24 * 60), "Success"); + if (generate.getLevel1Type().equals(SKETCH_BOARD.getRealName())) { + String gender = extractGender(generate.getGenerateType()); + if (!StringUtil.isNullOrEmpty(gender)) { + String clothCategory = pythonService.getClothCategory(path, gender); + generateResultVO.setCategory(clothCategory); + } else { + log.warn("未提取到性别"); + } + }else if (generate.getLevel1Type().equals(PRINT_BOARD.getRealName())){ + Generate generateRecord = selectByUniqueId(taskId); + generateResultVO.setCategory(generateRecord.getLevel2Type()); + } + return generateResultVO; + } else { + throw new BusinessException("Unknown generate task"); + } + } else if (taskStatus.equals("PENDING") || taskStatus.equals("RUNNING")) { + log.info("万象 异步接口返回生成状态为:{}", taskStatus); + return new GenerateResultVO(taskId, null, null, "Executing"); + } else { + log.warn("万象 异步接口返回生成状态为:{}", taskStatus); + return new GenerateResultVO(taskId, null, null, "Fail"); + } + } catch (ApiException | NoApiKeyException e) { + throw new RuntimeException(e.getMessage()); + } catch (Exception e) { + log.error("从aliyun下载图片失败, {}", e.getMessage()); + throw new BusinessException("Generation result retrieval failed"); + } + } + + private static final String IMAGE_DETECT = "https://dashscope.aliyuncs.com/api/v1/services/aigc/image2video/aa-detect"; + private static final String TEMPLATE_ID_GEN = "https://dashscope.aliyuncs.com/api/v1/services/aigc/image2video/aa-template-generation/"; + private static final String GET_ASYNC_RESULT = "https://dashscope.aliyuncs.com/api/v1/tasks/"; + private static final String ANIMATE = "https://dashscope.aliyuncs.com/api/v1/services/aigc/image2video/video-synthesis/"; + + public String animateAnyone(PoseTransformDTO poseTransformDTO, Long accountId) { + AccessLimitUtils.validate("animation", 5); + String inputImage = poseTransformDTO.getProductImage(); + String inputImageUrl = minioUtil.getPreSignedUrl(inputImage, CommonConstant.MINIO_IMAGE_EXPIRE_TIME); + // 1、输入图片检测 + checkImage(inputImageUrl); + + // 2、动作模板生成 + String videoTemplateId = PoseEnum.getById(poseTransformDTO.getPoseId()).getTemplateId(); + if (StringUtil.isNullOrEmpty(videoTemplateId)) { + throw new BusinessException("unknown pose"); + } + // 3、生成动图 + JSONObject requestBody1 = new JSONObject(); + requestBody1.set("model", "animate-anyone-gen2"); + + JSONObject input1 = new JSONObject(); + input1.set("image_url", inputImageUrl); // 替换为实际图片URL + input1.set("template_id", videoTemplateId); // 替换为实际图片URL + JSONObject parameters1 = new JSONObject(); + parameters1.set("use_ref_img_bg", false); + parameters1.set("video_ratio", "9:16"); + + requestBody1.set("input", input1); + requestBody1.set("parameters", parameters1); + + log.info("万象 pose transfer 请求入参:{}", requestBody1); + String resp = sendRequestUtil.sendAliYunPostAsync(ANIMATE, requestBody1.toString()); +// String resp = "{\"request_id\":\"656c4339-59e5-9b34-a010-b5aa625a4008\",\"output\":{\"task_id\":\"05c0fe3e-8d93-4754-babe-28a1efc62151\",\"task_status\":\"PENDING\"}}"; + log.info("wx pose transform 请求生成,获取taskId:{}", resp); + JSONObject jsonResponse = JSONUtil.parseObj(resp); + JSONObject output = jsonResponse.getJSONObject("output"); + String status = output.getStr("task_status"); + if (status.equals(FAILED.getName()) || status.equals(UNKNOWN_W.getName())) { + return null; + } + return output.getStr("task_id"); + } + + public void checkImage(String inputImageUrl) { + JSONObject requestBody = new JSONObject(); + requestBody.set("model", "animate-anyone-detect-gen2"); + + JSONObject input = new JSONObject(); + input.set("image_url", inputImageUrl); // 替换为实际图片URL + JSONObject parameters = new JSONObject(); + + requestBody.set("input", input); + requestBody.set("parameters", parameters); + + String response = sendRequestUtil.sendAliYunPost(IMAGE_DETECT, requestBody.toString()); + + System.out.println("API响应: " + response); + JSONObject jsonResponse = JSONUtil.parseObj(response); + // 获取check_pass值 + JSONObject output = jsonResponse.getJSONObject("output"); + Boolean checkPass = output.getBool("check_pass"); + + if (!checkPass) { + String reason = output.getStr("reason"); + log.info("原因: {}", reason); + throw new BusinessException("输入的图片不满足要求"); + } + } + + // 轮询配置 + private static final int MAX_RETRIES = 30; // 最大重试次数 + private static final int POLL_INTERVAL = 20000; // 轮询间隔(毫秒) + + public String getVideoTemplateId(String videoPath) { + boolean contains = PoseEnum.getVideoList().contains(videoPath); + + String templateId; + if (!contains) { + String videoUrl = minioUtil.getPreSignedUrl(videoPath, CommonConstant.MINIO_IMAGE_EXPIRE_TIME); + JSONObject requestBody = new JSONObject(); + requestBody.set("model", "animate-anyone-template-gen2"); + JSONObject input = new JSONObject(); + input.set("video_url", videoUrl); // 替换为实际图片URL + JSONObject parameters = new JSONObject(); + requestBody.set("input", input); + requestBody.set("parameters", parameters); + + log.info("获取pose的模板id 请求数据:{}", requestBody); + String resp = sendRequestUtil.sendAliYunPostAsync(TEMPLATE_ID_GEN, requestBody.toString()); + if (StringUtil.isNullOrEmpty(resp)) { + throw new BusinessException("请求获取video template id失败"); + } + JSONObject jsonResponse = JSONUtil.parseObj(resp); + log.info("getVideoTemplateId response:{}", jsonResponse); + JSONObject output = jsonResponse.getJSONObject("output"); + String taskId = output.getStr("task_id"); + + // 暂时用while循环轮询 + templateId = pollTemplateIdResult(taskId); + if (StringUtil.isNullOrEmpty(templateId)) { + throw new BusinessException("获取动作模板失败"); + } +// templateId = "AACT.8090e67b.-E3pujumEfCbDTI_rjSH-A.LwIlGT3j"; + } else { + templateId = PoseEnum.getByVideoPath(videoPath).getTemplateId(); + } + + return templateId; + } + + public String pollTemplateIdResult(String taskId) { + int attempt = 0; + boolean isCompleted = false; + String templateId = null; + + while (attempt < MAX_RETRIES && !isCompleted) { + attempt++; + System.out.printf("尝试第 %d 次查询...%n", attempt); + + try { + // 发送GET请求查询任务状态 + HttpResponse httpResponse = HttpRequest.get(GET_ASYNC_RESULT + taskId) + .header(Header.AUTHORIZATION, "Bearer " + ALIYUN_API_KEY) + .timeout(10000) + .execute(); + + if (httpResponse.getStatus() == 200) { + JSONObject response = JSONUtil.parseObj(httpResponse.body()); + JSONObject output = JSONUtil.parseObj(response.getStr("output")); + String taskStatus = output.getStr("task_status", "UNKNOWN"); + WangXiangTaskStatusEnum statusEnum = WangXiangTaskStatusEnum.fromName(taskStatus); + System.out.println("当前任务状态: " + taskStatus); + + switch (statusEnum) { + case SUCCEEDED: + templateId = handleSuccessResponse(response); + isCompleted = true; + break; + case FAILED: + case UNKNOWN_W: + handleFailedResponse(response); + isCompleted = true; + break; + case RUNNING: + case PENDING_W: + // 任务仍在运行,继续等待 + break; + default: + System.out.println("未知状态: " + taskStatus); + } + } else { + System.out.println("请求失败,状态码: " + httpResponse.getStatus()); + } + + // 如果不是最终状态,等待一段时间再重试 + if (!isCompleted && attempt < MAX_RETRIES) { + TimeUnit.MILLISECONDS.sleep(POLL_INTERVAL); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + System.out.println("轮询被中断"); + break; + } catch (Exception e) { + System.out.println("请求发生异常: " + e.getMessage()); + // 发生异常时可以选择重试或退出 + break; + } + } + if (!isCompleted) { + System.out.println("达到最大重试次数仍未获取最终结果"); + } + return templateId; + } + + private static String handleSuccessResponse(JSONObject response) { + log.info("任务执行成功!"); + // 提取成功结果 + JSONObject output = response.getJSONObject("output"); + if (output != null) { + log.info("任务输出: {}", output.toStringPretty()); + return output.getStr("template_id"); + } else { + return null; + } + } + + private static void handleFailedResponse(JSONObject response) { + log.info("任务执行失败!"); + // 提取失败原因 + String errorMsg = response.getStr("error_message", "未知错误"); + log.info("失败原因: {}", errorMsg); + } + + + public PoseTransformationVO getAnimateResult(String taskId) { + String fullUrl = GET_ASYNC_RESULT + taskId; + // 从接口获取当前任务的结果 + String respBody = sendRequestUtil.sendAliYunGet(fullUrl); + log.info("获取wx pose transform 的结果: {}", respBody); + + String outputStr = JSONUtil.parseObj(respBody).getStr("output"); + JSONObject output = JSONUtil.parseObj(outputStr); + String videoUrl = output.getStr("video_url"); + String status = output.getStr("task_status"); + + // 更新api_generate表 + apiGenerateService.updateAPIGenerateStatusAsync(taskId, status); + + List poseTransformations = poseTransformationMapper.selectList(new QueryWrapper().eq("unique_id", taskId).orderByDesc("id")); + PoseTransformation poseTransformation; + if (!poseTransformations.isEmpty()) { + poseTransformation = poseTransformations.get(0); + } else { + throw new BusinessException("unknown motion task"); + } + PoseTransformationVO poseTransformationVO = new PoseTransformationVO(); + WangXiangTaskStatusEnum statusEnum = WangXiangTaskStatusEnum.fromName(status); + switch (statusEnum) { + case SUCCEEDED: + AccessLimitUtils.validateOut("animation"); + poseTransformationVO = CopyUtil.copyObject(poseTransformation, PoseTransformationVO.class); + poseTransformationVO.setStatus("Success"); + + // 生成视频的gif和第一帧图片并上传图片、视频、gif等数据,更新pose transformation表 + processVideo(videoUrl, poseTransformation); + poseTransformationVO.setId(poseTransformation.getId()); + if (!StringUtil.isNullOrEmpty(poseTransformation.getGifUrl())) { + poseTransformationVO.setGifUrl(minioUtil.getPreSignedUrl(poseTransformation.getGifUrl(), CommonConstant.MINIO_IMAGE_EXPIRE_TIME)); + } + if (!StringUtil.isNullOrEmpty(poseTransformation.getVideoUrl())) { + poseTransformationVO.setVideoUrl(minioUtil.getPreSignedUrl(poseTransformation.getVideoUrl(), CommonConstant.MINIO_IMAGE_EXPIRE_TIME)); + } + if (!StringUtil.isNullOrEmpty(poseTransformation.getFirstFrameUrl())) { + poseTransformationVO.setFirstFrameUrl(minioUtil.getPreSignedUrl(poseTransformation.getFirstFrameUrl(), CommonConstant.MINIO_IMAGE_EXPIRE_TIME)); + } + // 执行积分扣除 + Long accountId = poseTransformation.getAccountId(); + Boolean flag = creditsService.taskCreditsDeduction(accountId, taskId); + if (flag) creditsService.updateChangedCredits(String.valueOf(accountId), taskId); + + // 保存数据到redis + String key = generateResultKey + ":" + taskId; + redisUtil.addToString(key, new Gson().toJson(poseTransformationVO), CommonConstant.GENERATE_RESULT_EXPIRE_TIME); + break; + case FAILED: + AccessLimitUtils.validateOut("animation"); + updatePoseTransferStatus(taskId, "Fail", poseTransformation); + throw new BusinessException(output.getStr("message"), ResultEnum.PROMPT.getCode()); + case UNKNOWN_W: + AccessLimitUtils.validateOut("animation"); + poseTransformationVO.setStatus("Fail"); + updatePoseTransferStatus(taskId, "Fail", poseTransformation); + break; + case RUNNING: + case PENDING_W: + // 任务仍在运行,继续等待 + poseTransformationVO.setStatus("Executing"); + break; + default: + AccessLimitUtils.validateOut("animation"); + log.info("未知状态: {}", status); + poseTransformationVO.setStatus("Fail"); + updatePoseTransferStatus(taskId, "Fail", poseTransformation); + } + poseTransformationVO.setTaskId(taskId); + + return poseTransformationVO; + } + + public void processVideo(String aliyunVideoUrl, PoseTransformation poseTransformation) /*throws Exception*/ { + // 1. 从阿里云下载视频到内存 + byte[] videoBytes = downloadVideoOrImage(aliyunVideoUrl); + Long accountId = poseTransformation.getAccountId(); + String taskId = poseTransformation.getUniqueId(); + + // 2. 提取第一帧和生成GIF + ByteArrayOutputStream firstFrameOutput = new ByteArrayOutputStream(); + ByteArrayOutputStream gifOutput = new ByteArrayOutputStream(); + + try (FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(new ByteArrayInputStream(videoBytes))) { + grabber.start(); + // 提取第一帧 + BufferedImage firstFrame = new Java2DFrameConverter().convert(grabber.grabImage()); + ImageIO.write(firstFrame, "jpg", firstFrameOutput); + + // 生成GIF(取前12秒,50帧) + generateGif(grabber, gifOutput, 12, 60); + } catch (Exception e) { + throw new RuntimeException(e); + } + + // 3. 上传所有文件到MinIO + String videoPrefix = accountId + "/pose_transform_video/" + taskId + ".mp4"; + String imgPrefix = accountId + "/pose_transform_first_img/" + taskId + ".jpg"; + String gifPrefix = accountId + "/pose_transform_gif/" + taskId + ".gif"; + + minioUtil.uploadToMinio(videoBytes, userBucket, videoPrefix, "video/mp4"); + minioUtil.uploadToMinio(firstFrameOutput.toByteArray(), userBucket, imgPrefix, "image/jpeg"); + minioUtil.uploadToMinio(gifOutput.toByteArray(), userBucket, gifPrefix, "image/gif"); + // 存储数据到数据库 + poseTransformation.setGifUrl(userBucket + "/" + gifPrefix); + poseTransformation.setVideoUrl(userBucket + "/" + videoPrefix); + poseTransformation.setFirstFrameUrl(userBucket + "/" + imgPrefix); + poseTransformation.setTaskStatus("Success"); + poseTransformation.setUpdateTime(LocalDateTime.now()); + poseTransformationMapper.updateById(poseTransformation); + } + + public byte[] downloadVideoOrImage(String url) { + try (CloseableHttpClient client = HttpClients.createDefault(); + InputStream in = client.execute(new HttpGet(url)).getEntity().getContent()) { + return IOUtils.toByteArray(in); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + // 增强版下载方法 todo 最好不要报错 + private byte[] downloadVideoOrImageWithValidation(String url) throws IOException { + CloseableHttpClient client = HttpClients.createDefault(); + HttpGet request = new HttpGet(url); + + try (CloseableHttpResponse response = client.execute(request)) { + // 状态码检查 + if (response.getStatusLine().getStatusCode() != 200) { + throw new IOException("Invalid status: " + response.getStatusLine()); + } + + // 内容类型检查 + org.apache.http.Header contentTypeHeader = response.getFirstHeader("Content-Type"); + if (contentTypeHeader == null || !contentTypeHeader.getValue().startsWith("image/")) { + throw new IOException("Invalid content type: " + + (contentTypeHeader != null ? contentTypeHeader.getValue() : "null")); + } + + // 内容长度检查 + org.apache.http.Header contentLengthHeader = response.getFirstHeader("Content-Length"); + if (contentLengthHeader != null) { + long length = Long.parseLong(contentLengthHeader.getValue()); + if (length <= 0) { + throw new IOException("Empty content"); + } + } + + return IOUtils.toByteArray(response.getEntity().getContent()); + } + } + + public byte[] downloadWithProxy(String url) throws IOException { + // 获取系统代理设置(适用于大多数VPN) +// String proxyHost = System.getProperty("http.proxyHost"); +// String proxyPort = System.getProperty("http.proxyPort"); + String proxyHost = "localhost"; + String proxyPort = "7890"; + + CloseableHttpClient client; + if (proxyHost != null && proxyPort != null) { + // 配置代理 + HttpHost proxy = new HttpHost(proxyHost, Integer.parseInt(proxyPort)); + RequestConfig config = RequestConfig.custom().setProxy(proxy).build(); + client = HttpClients.custom().setDefaultRequestConfig(config).build(); + } else { + client = HttpClients.createDefault(); + } + + try { + return client.execute(new HttpGet(url), response -> { + if (response.getStatusLine().getStatusCode() == 200) { + return IOUtils.toByteArray(response.getEntity().getContent()); + } else { + throw new IOException("HTTP Error: " + response.getStatusLine()); + } + }); + } finally { + client.close(); + } + } + + public static void generateGif(FFmpegFrameGrabber grabber, OutputStream output, + int durationSec, int frameCount) throws Exception { + Java2DFrameConverter converter = new Java2DFrameConverter(); + AnimatedGifEncoder gifEncoder = new AnimatedGifEncoder(); + + // 配置GIF参数 + gifEncoder.start(output); + gifEncoder.setDelay(100); // 每帧延迟(毫秒) + gifEncoder.setRepeat(0); // 0=无限循环 + + int totalFrames = (int) (grabber.getFrameRate() * durationSec); + int step = Math.max(1, totalFrames / frameCount); + + // 逐帧处理 + for (int i = 0; i < totalFrames; i += step) { + grabber.setVideoFrameNumber(i); + BufferedImage frame = converter.convert(grabber.grabImage()); + if (frame != null) { + gifEncoder.addFrame(frame); + } + } + gifEncoder.finish(); + } + + /** + * Freepik + * To Product Image + */ + public String reimagineFreePik(String path, String prompt, String style) throws IOException { + String imageAsBase64 = minioUtil.getImageAsBase64(path); + log.info(minioUtil.getPreSignedUrl(path, CommonConstant.MINIO_IMAGE_EXPIRE_TIME)); + JSONObject requestBody = new JSONObject(); + requestBody.set("image", imageAsBase64); + requestBody.set("prompt", prompt); + requestBody.set("imagination", style); + + String resp = sendRequestUtil.sendFreepikPost(requestBody.toString()); + if (!StringUtil.isNullOrEmpty(resp)) { + JSONObject jsonResp = JSONUtil.parseObj(resp); + JSONObject data = JSONUtil.parseObj(jsonResp.get("data")); + String status = data.getStr("status"); + if (status.equals("COMPLETED")) { +// List generated = data.getBeanList("generated", String.class); + log.info("freepik 调用结果:{}", jsonResp); + return jsonResp.getStr("data"); + } + } + return null; + } + + /** + * imagePath 图片的minio地址 + * ollama + * prompt 助手 + */ + public String getImageDescription(String imagePath) { +/* // 1. 读取图片并编码为 Base64 + String imageAsBase64 = null; + try { + imageAsBase64 = minioUtil.getImageAsBase64(imagePath); + } catch (IOException e) { + throw new RuntimeException(e); + } + + // 2. 构建 JSON 请求体 + JSONObject message = new JSONObject(); + message.set("role", "user"); + message.set("content", "Please describe the clothing in the image and provide a line art description of the outfit. The description should allow for the reconstruction of the corresponding line art based on the details given."); + message.set("images", JSONUtil.createArray().set(imageAsBase64)); + + JSONObject requestBody = new JSONObject(); + requestBody.set("model", "llama3.2-vision"); + requestBody.set("messages", JSONUtil.createArray().set(message)); + requestBody.set("stream", false);*/ + +// log.info("request body:{}", requestBody); + JSONObject requestBody = new JSONObject(); + requestBody.set("img", imagePath); +// String description = sendRequestUtil.sendPost("http://localhost:8000/api/img2prompt", requestBody.toString()); + String description = sendRequestUtil.sendPost("http://18.167.251.121:9994/api/img2prompt", requestBody.toString()); + if (StringUtil.isNullOrEmpty(description)) { + throw new BusinessException("从ollama获取图片描述失败"); + } + /*Object msg = JSONUtil.parseObj(resp).get("message"); + String description = JSONUtil.parseObj(msg).getStr("content");*/ + log.info("image :{} \n, description: {}", + minioUtil.getPreSignedUrl(imagePath, CommonConstant.MINIO_IMAGE_EXPIRE_TIME), description); + return description; + } + + private String resolveModelType(String taskId, String func) { + // 判断当前task来自哪个模型 + if (!StringUtil.isNullOrEmpty(func) + && func.equals(CreditsEventsEnum.POSE_TRANSFORMATION.getValue())) { + List poseTransformations = poseTransformationMapper.selectList( + new QueryWrapper().eq("unique_id", taskId)); + if (!poseTransformations.isEmpty() + && !StringUtil.isNullOrEmpty(poseTransformations.get(0).getModelName()) + && poseTransformations.get(0).getModelName().equals("wx")) { + return "wx"; + } else { + return "local"; + } + } + + Generate generate = selectByUniqueId(taskId); + if (Objects.nonNull(generate) && + !StringUtil.isNullOrEmpty(generate.getModelName()) && + (generate.getModelName().equals("wx") + || generate.getModelName().equals("freepik") + || generate.getModelName().equals("flux"))) { + return generate.getModelName(); + } else { + return "local"; + } + } + + public static String extractGender(String text) { + // 匹配末尾的 (Male) 或 (Female),忽略大小写 + Pattern pattern = Pattern.compile("\\(([Mm]ale|[Ff]emale)\\)$"); + Matcher matcher = pattern.matcher(text); + + if (matcher.find()) { + return matcher.group(1); // 返回括号内的内容 + } + return null; // 未匹配到性别 + } + + /** + * 接入flux模型,用于imageToSketch(sketch extract) || relighting || to product image + * + * @param func 功能枚举名 + * @param prompt 用户输入 + * @param imagePath 图片minio路径 + * @return 返回taskId,用于异步获取结果 + */ + public String flux(CreditsEventsEnum func, String prompt, String imagePath, boolean childStyle) { + String fluxRequestUrl = "https://api.bfl.ai/v1/flux-kontext-pro"; + if (StringUtil.isNullOrEmpty(prompt)) { + switch (func) { + case RELIGHT_FLUX: + prompt = "a model standing on the beautiful beach, ultra high quality, 8k"; + break; + case IMAGE_TO_SKETCH_FLUX: + prompt = "generate the sketch of the image, simple line, ultra high quality"; + break; + case TO_PRODUCT_IMAGE_FLUX: + prompt = "change the image to real style, ultra high quality, 8k"; + if (childStyle) prompt = prompt + ", Children's face"; + break; + } + } else { + prompt = modifyPrompt(prompt, null, func.getName(), null); + switch (func) { + case PATTERN: + prompt = "pattern image, " + prompt; + break; + case SKETCH_BOARD: + prompt = "a single item of sketch of " + prompt + ", clean white background, simple lines"; + break; + } + } + JSONObject requestBody = new JSONObject(); + + requestBody.set("prompt", prompt); + requestBody.set("seed", 42); + requestBody.set("aspect_ratio", "9:16"); + requestBody.set("output_format", "png"); + + log.info("flux 请求入参:{}", requestBody); + if (prompt.isEmpty())throw new BusinessException("test"); + if (!StringUtil.isNullOrEmpty(imagePath)) { + try { + String imageAsBase64 = null; + if (func.equals(TO_PRODUCT_IMAGE_FLUX)) { + imageAsBase64 = addWhiteBackground(imagePath); + } + if (StringUtil.isNullOrEmpty(imageAsBase64)) { + imageAsBase64 = minioUtil.getImageAsBase64(imagePath); + } + requestBody.set("input_image", imageAsBase64); + } catch (IOException e) { + log.error("获取图片的base64格式失败,{}", String.valueOf(e)); + throw new BusinessException("Failed to obtain the image in base64 format."); + } + } + + String resp = sendRequestUtil.sendFluxPost(fluxRequestUrl, requestBody.toString()); + JSONObject respObj = JSONUtil.parseObj(resp); + log.info("flux 发起生成请求返回结果: {}", respObj); + String taskId = respObj.getStr("id"); + if (StringUtil.isNullOrEmpty(taskId)) { + requestBody.set("input_image", imagePath); + log.error("flux生成任务创建失败,func :{}, requestBody:{}", func.getName(), requestBody); + throw new BusinessException("Failed to generate task. Please retry later."); + } + + String pollingUrl = respObj.getStr("polling_url"); + String key = RedisUtil.FLUX_POLLING_URL + taskId; + redisUtil.addToString(key, pollingUrl, CommonConstant.GENERATE_RESULT_EXPIRE_TIME); + // 添加到api_generate表中,以便之后对结果查询做补偿 + apiGenerateService.addAPIGenerateRecordAsync(UserContext.getUserHolder().getId(), taskId, func.getName(), "flux", "Pending"); + + return taskId; + } + + @Override + public String getFluxResult(String taskId, String objectName) { + // 获取轮询URL + String pollingUrl = redisUtil.getFromString(RedisUtil.FLUX_POLLING_URL + taskId); + + // 准备请求参数 + String fluxResultRequestUrl = StringUtil.isNullOrEmpty(pollingUrl) + ? "https://api.bfl.ai/v1/get_result" + : pollingUrl; + + HashMap params = new HashMap<>(); + if (StringUtil.isNullOrEmpty(pollingUrl)) { + params.put("id", taskId); + } + + // 发送请求并解析响应 + String resp = sendRequestUtil.sendGet(fluxResultRequestUrl, params); + log.info("获取flux生成的结果为:{}", resp); + + JSONObject respObj = JSONUtil.parseObj(resp); + String status = respObj.getStr("status"); + FluxTaskStatusEnum statusEnum = FluxTaskStatusEnum.fromName(status); + // 异步更新状态 + apiGenerateService.updateAPIGenerateStatusAsync(taskId, status); + + // 处理不同状态 + switch (statusEnum) { + case TASK_NOT_FOUND: + // 审核没过 + case REQUEST_MODERATED: + // 审核没过 + case CONTENT_MODERATED: + // 出错 + case ERROR: + return "Fail"; + case PENDING_F: + return "Pending"; + case SUCCESS: + // 已完成 获取结果 + return handleReadyStatus(respObj, objectName); + default: + return null; + } + } + + private String handleReadyStatus(JSONObject respObj, String objectName) { + // 1. 首先检查MinIO中是否已存在该图片 + if (minioUtil.doesObjectExist(userBucket, objectName)) { + return userBucket + "/" + objectName; + } + + // 2. 解析响应获取结果URL和生成时间 + JSONObject resultObj = JSONUtil.parseObj(respObj.getStr("result")); + String fluxResult = resultObj.getStr("sample"); + double endTime = resultObj.getDouble("end_time"); // 获取任务结束时间戳 + + // 3. 检查图片链接是否已过期(超过10分钟) + long currentTime = System.currentTimeMillis() / 1000; // 当前Unix时间戳(秒) + long generateTime = (long) endTime; // 生成结束时间戳 + // 图片10分钟过期,保险起见,保留一分钟 + long tenMinutesInSeconds = 9 * 60; + + if (currentTime - generateTime > tenMinutesInSeconds) { + log.warn("Flux result image has expired, generateTime: {}, currentTime: {}", + generateTime, currentTime); + return null; + } + + // 4. 图片未过期,下载并上传到MinIO + try { + byte[] bytes = downloadVideoOrImage(fluxResult); + minioUtil.uploadToMinio(bytes, userBucket, objectName, "image/png"); + return userBucket + "/" + objectName; + } catch (Exception e) { + log.error("Failed to download or upload Flux result image", e); + return null; + } + } + + private GenerateResultVO getFluxResultAndSave(String taskId) { + Generate generate = selectByUniqueId(taskId); + if (Objects.nonNull(generate)) { + GenerateDetail generateDetail = generateDetailMapper.selectOne(new QueryWrapper().eq("generate_id", generate.getId())); + Long accountId = generate.getAccountId(); + String objectName = accountId + "/imageToSketch/" + taskId + ".png"; + String fluxResult = getFluxResult(taskId, objectName); + if (Objects.isNull(generateDetail)) { + if (StringUtil.isNullOrEmpty(fluxResult)) { + return new GenerateResultVO(taskId, "Fail"); + } else if (fluxResult.equals("Fail") || fluxResult.equals("Pending")) { + String status = fluxResult.equals("Fail") ? "Fail" : "Executing"; + return new GenerateResultVO(taskId, status); + } + generateDetail = new GenerateDetail(generate.getId(), fluxResult, + MD5Utils.encryptFile( + minioUtil.getPreSignedUrl(fluxResult, CommonConstant.MINIO_IMAGE_EXPIRE_TIME), false), + LocalDateTime.now()); + generateDetailMapper.insert(generateDetail); + // 扣积分 + Boolean flag = creditsService.taskCreditsDeduction(accountId, taskId); + if (flag) creditsService.updateChangedCredits(String.valueOf(accountId), taskId); + } else if (StringUtil.isNullOrEmpty(generateDetail.getUrl())) { + // 结果已经存入db,一般走不到这条线 + generateDetail.setGenerateId(generate.getId()); + generateDetail.setUrl(fluxResult); + generateDetail.setMd5(MD5Utils.encryptFile( + minioUtil.getPreSignedUrl(fluxResult, CommonConstant.MINIO_IMAGE_EXPIRE_TIME), false)); + generateDetail.setUpdateDate(new Date()); + generateDetailMapper.updateById(generateDetail); + } + String url = generateDetail.getUrl(); + String category ; + if (generate.getLevel1Type().equals(SKETCH_BOARD.getRealName())){ + category = pythonService.getClothCategory(url, extractGender(generate.getGenerateType())); + } else { + category = generate.getLevel2Type(); + } + + return new GenerateResultVO(taskId, generateDetail.getId(), + minioUtil.getPreSignedUrl(url, CommonConstant.MINIO_IMAGE_EXPIRE_TIME), "Success", category); + } else { + throw new BusinessException("unknown generate"); + } + } + + private String addWhiteBackground(String minioPath) { + // 1、先通过后缀判断输入图片类型有没有透明通道 + String extension = minioPath.substring(minioPath.lastIndexOf(".") + 1); + + // 2、如果有,为其添加白色背景 + if (extension.equals("png")) { + return minioUtil.changeToWhiteBackground(minioPath); + } else { + log.info("图片 {} 没有透明通道, 不用添加白底", minioPath); + return null; + } + } + + +}