Browse Source

修改单图片生成任务

glx
glx 2 weeks ago
parent
commit
90ef9bd7b2
  1. 21
      src/main/java/com/youlai/boot/mini/controller/AiGenerationController.java
  2. 78
      src/main/java/com/youlai/boot/mini/model/dto/PhotoToComicRequest.java
  3. 2
      src/main/java/com/youlai/boot/mini/model/entity/MiniAiGenerationTask.java
  4. 54
      src/main/java/com/youlai/boot/mini/model/form/AiSingleImageGenerateForm.java
  5. 25
      src/main/java/com/youlai/boot/mini/model/vo/AiTaskCallbackImage.java
  6. 38
      src/main/java/com/youlai/boot/mini/model/vo/AiTaskCallbackResult.java
  7. 31
      src/main/java/com/youlai/boot/mini/model/vo/AiTaskCallbackUsage.java
  8. 23
      src/main/java/com/youlai/boot/mini/model/vo/AiTaskCallbackVO.java
  9. 27
      src/main/java/com/youlai/boot/mini/service/AiGenerationService.java
  10. 267
      src/main/java/com/youlai/boot/mini/service/impl/AiGenerationServiceImpl.java
  11. 10
      src/main/resources/application-dev.yml

21
src/main/java/com/youlai/boot/mini/controller/AiGenerationController.java

@ -6,7 +6,7 @@ import com.youlai.boot.common.enums.ActionTypeEnum;
import com.youlai.boot.common.enums.LogModuleEnum;
import com.youlai.boot.common.result.Result;
import com.youlai.boot.framework.security.util.SecurityUtils;
import com.youlai.boot.mini.model.dto.PhotoToComicRequest;
import com.youlai.boot.mini.model.form.AiSingleImageGenerateForm;
import com.youlai.boot.mini.model.vo.AiTaskCallbackVO;
import com.youlai.boot.mini.service.AiGenerationService;
import io.swagger.v3.oas.annotations.Operation;
@ -45,20 +45,27 @@ public class AiGenerationController {
return Result.success(urlList);
}
@Operation(summary = "提交单图漫画生成任务")
@Operation(summary = "提交单图生成任务")
@PostMapping("/generate-single-image")
@Log(module = LogModuleEnum.AI_GENERATION_TASK, value = ActionTypeEnum.INSERT)
public Result<String> generateSingleImage(@Valid @RequestBody PhotoToComicRequest request) {
public Result<String> generateSingleImage(@Valid @RequestBody AiSingleImageGenerateForm form) {
Long userId = SecurityUtils.getUserId();
String taskUuid = aiGenerationService.createAndGenerateImage(request, userId);
String taskUuid = aiGenerationService.createAndGenerateImage(form, userId);
return Result.success(taskUuid);
}
@Operation(summary = "AI生成任务回调接口")
@PostMapping("/task/callback")
//TODO: 后续增加用户小程序订阅通知
@Operation(summary = "单图任务回调接口")
@PostMapping("/single-image/task/callback")
@Log(module = LogModuleEnum.AI_GENERATION_TASK, value = ActionTypeEnum.UPDATE)
public Result<Boolean> taskCallback(@Valid @RequestBody AiTaskCallbackVO vo) {
boolean success = aiGenerationService.handleTaskCallback(vo.getUuid(), vo.getStatus(), vo.getResultUrl());
boolean success = aiGenerationService.handleTaskCallback(vo);
return Result.success(success);
}
// TODO: 添加 4宫格图片生成任务 接口 ,外部服务接口为:http://127.0.0.1:8001//api/v1/four-panel-comic
}

78
src/main/java/com/youlai/boot/mini/model/dto/PhotoToComicRequest.java

@ -1,78 +0,0 @@
package com.youlai.boot.mini.model.dto;
import lombok.Data;
import java.util.List;
/**
* 照片漫画化请求DTO
*
* @author youlai
*/
@Data
public class PhotoToComicRequest {
/**
* 使用的模型ID
*/
private String model;
/**
* 参考图片URL数组
*/
private List<String> reference_image;
/**
* 画风: 1=治愈系水彩, 2=Q版卡通, 3=日式漫画, 4=吉卜力风, 5=3D毛绒, 6=搞笑夸张
*/
private Integer style = 1;
/**
* 比例: 1=1:1, 2=2:3, 3=3:4, 4=4:3, 5=5:4
*/
private Integer ratio = 1;
/**
* 图片尺寸: 1=2K, 2=4K, 3=8K
*/
private Integer img_size = 1;
/**
* 图片格式: 1=png, 2=jpeg
*/
private Integer img_type = 1;
/**
* 宠物物种cat, dog
*/
private String species;
/**
* 宠物品种British Shorthair, Corgi
*/
private String breed;
/**
* 宠物毛色blue-gray, orange
*/
private String color;
/**
* 眼睛颜色amber, blue
*/
private String eye_color;
/**
* 体型round chubby, slim and elegant
*/
private String body_type;
/**
* 特殊特征flat face, thick coat
*/
private String distinctive_features = "";
/**
* 场景描述用户自定义场景
*/
private String scene_description;
}

2
src/main/java/com/youlai/boot/mini/model/entity/MiniAiGenerationTask.java

@ -42,7 +42,7 @@ public class MiniAiGenerationTask implements Serializable {
@TableField("status")
@Schema(description = "任务状态:0=生成中 1=成功 2=失败 3=超时 4=已取消")
private Byte status;
private Integer status;
@TableField("result_resource_url")
@Schema(description = "生成结果资源URL")

54
src/main/java/com/youlai/boot/mini/model/form/AiSingleImageGenerateForm.java

@ -0,0 +1,54 @@
package com.youlai.boot.mini.model.form;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
/**
* AI单图漫画生成请求表单
*
* @author youlai
*/
@Data
@Schema(description = "AI单图漫画生成请求表单")
public class AiSingleImageGenerateForm {
@Schema(description = "参考图片URL列表,可选")
private List<String> imageUrl;
@Schema(description = "使用的模型ID", defaultValue = "doubao-seedream-5-0-260128")
private String model;
@Schema(description = "画风:1=治愈系水彩,2=Q版卡通,3=日式漫画,4=吉卜力风,5=3D毛绒,6=搞笑夸张", defaultValue = "1")
private Integer style = 1;
@Schema(description = "比例:1=1:1,2=2:3,3=3:4,4=4:3,5=5:4", defaultValue = "1")
private Integer ratio = 1;
@Schema(description = "图片尺寸:1=2K,2=4K,3=8K", defaultValue = "1")
private Integer imgSize = 1;
@Schema(description = "图片格式:1=png,2=jpeg", defaultValue = "1")
private Integer imgType = 1;
@Schema(description = "宠物物种:如cat、dog")
private String species;
@Schema(description = "宠物品种:如British Shorthair、Corgi")
private String breed;
@Schema(description = "宠物毛色:如blue-gray、orange")
private String color;
@Schema(description = "眼睛颜色:如amber、blue")
private String eyeColor;
@Schema(description = "体型:如round chubby、slim and elegant")
private String bodyType;
@Schema(description = "特殊特征:如flat face、thick coat", defaultValue = "")
private String distinctiveFeatures = "";
@Schema(description = "场景描述:用户自定义生成场景")
private String sceneDescription;
}

25
src/main/java/com/youlai/boot/mini/model/vo/AiTaskCallbackImage.java

@ -0,0 +1,25 @@
package com.youlai.boot.mini.model.vo;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* AI生成图片信息
*
* @author youlai
*/
@Data
@Schema(description = "AI生成图片信息")
public class AiTaskCallbackImage {
@Schema(description = "图片URL地址")
private String url;
@JsonProperty("b64_json")
@Schema(description = "Base64编码的图片JSON")
private String b64Json;
@Schema(description = "图片尺寸,格式如 1664x2496")
private String size;
}

38
src/main/java/com/youlai/boot/mini/model/vo/AiTaskCallbackResult.java

@ -0,0 +1,38 @@
package com.youlai.boot.mini.model.vo;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
/**
* AI任务生成结果
*
* @author youlai
*/
@Data
@Schema(description = "AI任务生成结果")
public class AiTaskCallbackResult {
@Schema(description = "使用的模型ID")
private String model;
@Schema(description = "生成的图片列表")
private List<AiTaskCallbackImage> data;
@Schema(description = "错误信息,无错误则为null")
private Object error;
@Schema(description = "Token使用统计")
private AiTaskCallbackUsage usage;
@JsonProperty("created_at")
@Schema(description = "创建时间戳")
private Long createdAt;
@Schema(description = "使用的工具")
private String tool;
@Schema(description = "创建时间戳")
private Long created;
}

31
src/main/java/com/youlai/boot/mini/model/vo/AiTaskCallbackUsage.java

@ -0,0 +1,31 @@
package com.youlai.boot.mini.model.vo;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* AI生成资源消耗统计
*
* @author youlai
*/
@Data
@Schema(description = "AI生成资源消耗统计")
public class AiTaskCallbackUsage {
@JsonProperty("generated_images")
@Schema(description = "生成的图片数量")
private Integer generatedImages;
@JsonProperty("output_tokens")
@Schema(description = "输出token数")
private Integer outputTokens;
@JsonProperty("total_tokens")
@Schema(description = "总token数")
private Integer totalTokens;
@JsonProperty("tool_usage")
@Schema(description = "工具使用信息")
private Object toolUsage;
}

23
src/main/java/com/youlai/boot/mini/model/vo/AiTaskCallbackVO.java

@ -1,25 +1,34 @@
package com.youlai.boot.mini.model.vo;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
/**
* AI任务回调VO
* AI生成任务回调请求VO
*
* @author youlai
*/
@Data
@Schema(description = "AI任务回调VO")
@Schema(description = "AI生成任务回调请求")
public class AiTaskCallbackVO {
@NotBlank(message = "任务UUID不能为空")
@Schema(description = "任务UUID", requiredMode = Schema.RequiredMode.REQUIRED)
@Schema(description = "任务唯一标识UUID", requiredMode = Schema.RequiredMode.REQUIRED)
private String uuid;
@Schema(description = "生成结果URL")
private String resultUrl;
@Schema(description = "任务状态:succeeded=成功,failed=失败")
private String status;
@Schema(description = "任务状态:0=生成中 1=成功 2=失败 3=超时 4=已取消")
private Byte status;
@Schema(description = "生成结果")
private AiTaskCallbackResult result;
@JsonProperty("created_at")
@Schema(description = "创建时间戳")
private Long createdAt;
@JsonProperty("updated_at")
@Schema(description = "更新时间戳")
private Long updatedAt;
}

27
src/main/java/com/youlai/boot/mini/service/AiGenerationService.java

@ -1,18 +1,37 @@
package com.youlai.boot.mini.service;
import com.youlai.boot.mini.model.dto.PhotoToComicRequest;
import com.youlai.boot.mini.model.form.AiSingleImageGenerateForm;
import com.youlai.boot.mini.model.entity.MiniAiGenerationTask;
import com.youlai.boot.mini.model.vo.AiTaskCallbackVO;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
public interface AiGenerationService {
/**
* 上传参考文件图片/视频
* @param images 上传的图片列表
* @param videos 上传的视频列表
* @param userId 用户ID
* @return 文件访问URL列表
*/
List<String> uploadReferenceFile(List<MultipartFile> images, List<MultipartFile> videos, Long userId);
String createAndGenerateImage(PhotoToComicRequest request, Long userId);
boolean handleTaskCallback(String taskUuid, Byte status, String externalResultUrl);
/**
* 创建AI生成任务并调用生成接口
* @param form 生成请求表单
* @param userId 用户ID
* @return 任务UUID
*/
String createAndGenerateImage(AiSingleImageGenerateForm form, Long userId);
/**
* 处理AI生成任务回调
* @param vo 回调请求参数
* @return 是否处理成功
*/
boolean handleTaskCallback(AiTaskCallbackVO vo);
MiniAiGenerationTask getTaskByUuid(String uuid);

267
src/main/java/com/youlai/boot/mini/service/impl/AiGenerationServiceImpl.java

@ -1,14 +1,18 @@
package com.youlai.boot.mini.service.impl;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONException;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.youlai.boot.common.exception.BusinessException;
import com.youlai.boot.common.exception.MsgException;
import com.youlai.boot.common.util.FileUtils;
import com.youlai.boot.common.util.JavaVCUtils;
import com.youlai.boot.common.util.RandomNumberUtils;
@ -17,25 +21,30 @@ import com.youlai.boot.file.service.FileService;
import com.youlai.boot.file.service.impl.AliyunFileService;
import com.youlai.boot.mini.mapper.MiniAiGenerationTaskMapper;
import com.youlai.boot.mini.mapper.MiniAiTaskMediaMapper;
import com.youlai.boot.mini.model.dto.GenerateResponse;
import com.youlai.boot.mini.model.dto.ImageData;
import com.youlai.boot.mini.model.dto.PhotoToComicRequest;
import com.youlai.boot.mini.model.entity.MiniAiGenerationTask;
import com.youlai.boot.mini.model.entity.MiniAiTaskMedia;
import com.youlai.boot.mini.model.form.AiSingleImageGenerateForm;
import com.youlai.boot.mini.model.form.MiniDeductPointForm;
import com.youlai.boot.mini.model.vo.AiTaskCallbackVO;
import com.youlai.boot.common.exception.BusinessException;
import com.youlai.boot.mini.service.AiGenerationService;
import com.youlai.boot.mini.service.MiniPointRecordService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FilenameUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import java.io.InputStream;
import java.net.URL;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.CompletableFuture;
@Slf4j
@ -47,11 +56,22 @@ public class AiGenerationServiceImpl implements AiGenerationService {
private final MiniAiTaskMediaMapper aiTaskMediaMapper;
private final FileService fileService;
private final AliyunFileService aliyunFileService;
private final MiniPointRecordService pointRecordService;
/**
* AI生成服务地址
*/
private static final String AI_GENERATE_URL = "http://127.0.0.1:8001/api/v1/photo-to-comic";
@Value("${ai.generate.server-url:http://127.0.0.1:8001/api/v1/photo-to-comic}")
private String aiGenerateServerUrl;
/**
* AI任务回调地址
*/
@Value("${ai.callback.url:http://127.0.0.1:30101/backend/api/v1/mini/ai/generation/task/callback}")
private String aiCallbackUrl;
@Value("${ai.default.model:doubao-seedream-5-0-260128}")
private String aiDefaultModel;
/**
* OSS存储目录配置
@ -59,6 +79,10 @@ public class AiGenerationServiceImpl implements AiGenerationService {
private static final String OSS_IMAGE_DIR = "ai/image/";
private static final String OSS_VIDEO_DIR = "ai/video/";
private static final String OSS_THUMBNAIL_DIR = "ai/thumbnail/";
/**
* AI单图生成积分规则编码
*/
private static final String AI_GENERATE_SINGLE_IMAGE_RULE = "AI_GENERATE_SINGLE_IMAGE";
@Override
public List<String> uploadReferenceFile(List<MultipartFile> images, List<MultipartFile> videos, Long userId) {
@ -180,97 +204,98 @@ public class AiGenerationServiceImpl implements AiGenerationService {
}
@Override
public String createAndGenerateImage(PhotoToComicRequest request, Long userId) {
// 创建生成任务
MiniAiGenerationTask task = new MiniAiGenerationTask();
@Transactional(rollbackFor = Exception.class) // 所有异常都触发事务回滚
public String createAndGenerateImage(AiSingleImageGenerateForm form, Long userId) {
// 生成任务UUID
String taskUuid = UUID.randomUUID().toString().replace("-", "");
Date now = new Date();
long timestamp = System.currentTimeMillis();
// 扣减积分(自动加入当前事务,失败会一起回滚)
MiniDeductPointForm deductForm = new MiniDeductPointForm();
deductForm.setRuleCode(AI_GENERATE_SINGLE_IMAGE_RULE);
deductForm.setBizId(taskUuid);
Integer deductPoint = pointRecordService.deductPoint(userId, deductForm.getRuleCode(), deductForm.getBizId());
// 创建生成任务(自动加入当前事务,失败会一起回滚)
MiniAiGenerationTask task = new MiniAiGenerationTask();
task.setUuid(taskUuid)
.setMiniUserId(userId)
.setType("img_single")
.setGenerateParams(JSONUtil.toJsonStr(request))
.setPointsConsumed(1) // 默认消耗1积分
.setStatus((byte) 0)
.setType("img_single") // 单图
.setGenerateParams(JSONUtil.toJsonStr(form))
.setPointsConsumed(Math.abs(deductPoint))
.setStatus(0) // 生成中
.setCreateBy(userId)
.setCreateTime(now)
.setCreateTimestamp(timestamp)
.setUpdateTime(now)
.setUpdateTime(now) // 补全update相关字段
.setUpdateTimestamp(timestamp)
.setDeleted(false);
aiGenerationTaskMapper.insert(task);
// 异步调用AI生成接口
CompletableFuture.runAsync(() -> {
try {
log.info("开始调用AI生成接口,任务UUID:{},请求参数:{}", taskUuid, JSONUtil.toJsonStr(request));
// 组装第三方AI接口需要的参数
Map<String, Object> aiRequest = new HashMap<>();
aiRequest.put("model", form.getModel() == null ? aiDefaultModel : form.getModel());
aiRequest.put("reference_image", form.getImageUrl());
aiRequest.put("style", form.getStyle());
aiRequest.put("ratio", form.getRatio());
aiRequest.put("img_size", form.getImgSize());
aiRequest.put("img_type", form.getImgType());
aiRequest.put("species", form.getSpecies());
aiRequest.put("breed", form.getBreed());
aiRequest.put("color", form.getColor());
aiRequest.put("eye_color", form.getEyeColor());
aiRequest.put("body_type", form.getBodyType());
aiRequest.put("distinctive_features", form.getDistinctiveFeatures());
aiRequest.put("scene_description", form.getSceneDescription());
// 传递任务UUID和回调地址
aiRequest.put("uuid", taskUuid);
aiRequest.put("callback_url", aiCallbackUrl);
// 发送HTTP请求
HttpResponse response = HttpRequest.post(AI_GENERATE_URL)
try {
log.info("提交AI生成任务,任务UUID:{},请求参数:{}", taskUuid, JSONUtil.toJsonStr(aiRequest));
// 同步调用AI接口,超时1秒
HttpResponse response = HttpRequest.post(aiGenerateServerUrl)
.header("Content-Type", "application/json")
.body(JSONUtil.toJsonStr(request))
.timeout(30000) // 超时30秒
.body(JSONUtil.toJsonStr(aiRequest))
.timeout(1000)
.execute();
// 先判断HTTP状态码
if (!response.isOk()) {
log.error("AI生成接口调用失败,状态码:{},响应内容:{}", response.getStatus(), response.body());
updateTaskStatus(taskUuid, (byte) 2, null);
return;
log.error("AI生成任务提交失败,HTTP状态码:{},响应内容:{}", response.getStatus(), response.body());
throw new MsgException("AI生成服务暂时不可用,请稍后重试");
}
// 解析响应
// 解析返回结果,校验业务状态码:只有code=0才代表提交成功
String responseBody = response.body();
GenerateResponse generateResponse = JSONUtil.toBean(responseBody, GenerateResponse.class);
if (generateResponse.getCode() != 0 || generateResponse.getData() == null || generateResponse.getData().getData().isEmpty()) {
log.error("AI生成接口返回错误,响应内容:{}", responseBody);
updateTaskStatus(taskUuid, (byte) 2, null);
return;
}
// 保存生成的图片
ImageData imageData = generateResponse.getData().getData().get(0);
String externalResultUrl = imageData.getUrl();
// 下载外部图片到我们的OSS
String ossUrl = downloadExternalUrlToOss(externalResultUrl);
// 保存媒体记录
MiniAiTaskMedia media = new MiniAiTaskMedia();
String mediaUuid = UUID.randomUUID().toString().replace("-", "");
media.setUuid(mediaUuid)
.setTaskId(task.getId())
.setFileSource("ai_generated")
.setMediaType("image")
.setSourceUrl(ossUrl)
.setCreateBy(userId)
.setCreateTime(new Date())
.setCreateTimestamp(System.currentTimeMillis())
.setUpdateTime(new Date())
.setUpdateTimestamp(System.currentTimeMillis())
.setDeleted(false);
aiTaskMediaMapper.insert(media);
// 更新任务状态为成功
updateTaskStatus(taskUuid, (byte) 1, ossUrl);
log.info("AI生成任务完成,任务UUID:{},结果URL:{}", taskUuid, ossUrl);
JSONObject resJson = JSONUtil.parseObj(responseBody);
Integer code = resJson.getInt("code");
if (!Integer.valueOf(0).equals(code)) {
String errMsg = resJson.getStr("msg", "服务调用失败");
log.error("AI生成任务提交失败,业务错误码:{},错误信息:{},request_id:{},完整响应:{}",
code, errMsg, resJson.getStr("request_id"), responseBody);
throw new MsgException("AI生成失败:" + errMsg);
}
} catch (JSONException e) {
log.error("AI生成任务返回结果解析失败,任务UUID:{},异常信息:{}", taskUuid, e.getMessage(), e);
throw new MsgException("AI生成服务暂时不可用,请稍后重试");
} catch (Exception e) {
log.error("AI生成任务异常,任务UUID:{},异常信息:{}", taskUuid, e.getMessage(), e);
updateTaskStatus(taskUuid, (byte) 2, null);
log.error("AI生成任务提交异常,任务UUID:{},异常信息:{}", taskUuid, e.getMessage(), e);
// 抛出异常触发事务回滚
throw new MsgException("AI生成服务暂时不可用,请稍后重试");
}
});
return taskUuid;
}
@Override
public boolean handleTaskCallback(String taskUuid, Byte status, String externalResultUrl) {
public boolean handleTaskCallback(AiTaskCallbackVO vo) {
log.info("处理AI生成任务回调,任务UUID:{}", vo.getUuid());
try {
String taskUuid = vo.getUuid();
// 查询任务是否存在
MiniAiGenerationTask task = getTaskByUuid(taskUuid);
if (task == null) {
@ -278,9 +303,21 @@ public class AiGenerationServiceImpl implements AiGenerationService {
return false;
}
// 转换任务状态
Integer status;
if ("succeeded".equals(vo.getStatus())) {
status = 1; // 成功
} else if ("failed".equals(vo.getStatus())) {
status = 2; // 失败
} else {
log.error("回调任务状态非法,UUID:{},status:{}", taskUuid, vo.getStatus());
return false;
}
// 如果生成成功,下载外部URL到OSS
String ossUrl = null;
if (status == 1 && externalResultUrl != null) {
if (status == 1 && vo.getResult() != null && !vo.getResult().getData().isEmpty()) {
String externalResultUrl = vo.getResult().getData().get(0).getUrl();
ossUrl = downloadExternalUrlToOss(externalResultUrl);
// 保存生成的媒体记录
@ -288,6 +325,7 @@ public class AiGenerationServiceImpl implements AiGenerationService {
String mediaUuid = UUID.randomUUID().toString().replace("-", "");
media.setUuid(mediaUuid)
.setMiniUserId(task.getMiniUserId())
.setTaskId(task.getId())
.setFileSource("ai_generated")
.setMediaType("image")
@ -306,7 +344,7 @@ public class AiGenerationServiceImpl implements AiGenerationService {
return updateTaskStatus(taskUuid, status, ossUrl);
} catch (Exception e) {
log.error("处理AI任务回调异常,UUID:{},异常信息:{}", taskUuid, e.getMessage(), e);
log.error("处理AI任务回调异常,UUID:{},异常信息:{}", vo.getUuid(), e.getMessage(), e);
return false;
}
}
@ -321,7 +359,7 @@ public class AiGenerationServiceImpl implements AiGenerationService {
/**
* 更新任务状态
*/
private boolean updateTaskStatus(String uuid, Byte status, String resultUrl) {
private boolean updateTaskStatus(String uuid, Integer status, String resultUrl) {
LambdaUpdateWrapper<MiniAiGenerationTask> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(MiniAiGenerationTask::getUuid, uuid)
.eq(MiniAiGenerationTask::getDeleted, false)
@ -337,61 +375,40 @@ public class AiGenerationServiceImpl implements AiGenerationService {
* 下载外部URL到OSS返回OSS访问地址
*/
private String downloadExternalUrlToOss(String externalUrl) {
try {
// 下载文件并获取输入流
byte[] fileBytes = HttpUtil.downloadBytes(externalUrl);
String fileName = IdUtil.fastSimpleUUID() + "." + FileUtil.extName(externalUrl);
// 创建临时的MultipartFile包装类
MultipartFile multipartFile = new MultipartFile() {
@Override
public String getName() {
return "file";
}
@Override
public String getOriginalFilename() {
return fileName;
}
@Override
public String getContentType() {
return "image/png";
}
@Override
public boolean isEmpty() {
return fileBytes == null || fileBytes.length == 0;
}
@Override
public long getSize() {
return fileBytes.length;
}
@Override
public byte[] getBytes() {
return fileBytes;
}
@Override
public java.io.InputStream getInputStream() {
return new java.io.ByteArrayInputStream(fileBytes);
}
@Override
public void transferTo(java.io.File dest) throws java.io.IOException, IllegalStateException {
FileUtil.writeBytes(fileBytes, dest);
}
};
// 上传到OSS
FileInfo fileInfo = fileService.uploadFile(multipartFile);
return fileInfo.getUrl();
try (
// 直接打开URL输入流,自动关闭
InputStream inputStream = new URL(externalUrl).openStream()
) {
long currentTimestamp = System.currentTimeMillis();
// 生成OSS存储路径:按日期分类 + 唯一ID + 原文件后缀
// String ext = FileUtil.extName(externalUrl);
String ext = null;
// 1. 如果URL包含查询参数 '?',则只取问号前面的部分
String path = externalUrl;
int queryIndex = externalUrl.indexOf('?');
if (queryIndex > 0) {
path = externalUrl.substring(0, queryIndex);
}
// 2. 从纯净的路径中提取文件后缀
int lastDotIndex = path.lastIndexOf('.');
if (lastDotIndex > 0 && lastDotIndex < path.length() - 1) {
ext = path.substring(lastDotIndex + 1);
}
if (ext == null){
log.error("无法从URL中提取文件后缀,URL:{}", externalUrl);
ext = "jpg";
}
String objectName = "ai/generate/"
+ currentTimestamp + RandomNumberUtils.createRandomLowerLetterAndNumber(8)
+ "."
+ ext;
return aliyunFileService.uploadFile(objectName, inputStream);
} catch (Exception e) {
log.error("下载外部URL到OSS失败,URL:{},异常信息:{}", externalUrl, e.getMessage(), e);
throw new BusinessException("下载生成结果失败");
throw new MsgException("资源转存失败");
}
}
}

10
src/main/resources/application-dev.yml

@ -91,6 +91,7 @@ security:
- /api/v1/mini/public/**
- /api/v1/mini/homePage/listByBounds
- /healthcheck
- /api/v1/mini/ai/generation/single-image/task/callback # AIGeneration 单图任务回调
# 非安全端点路径,完全绕过 Spring Security 的过滤器
unsecured-urls:
- ${springdoc.swagger-ui.path}
@ -225,3 +226,12 @@ wx:
miniapp:
appid: Your_AppId
secret: Your_AppSecret
ai:
generate:
server-url: http://192.168.31.91:8001/api/v1/photo-to-comic
callback:
url: http://192.168.31.197:30101/backend/api/v1/mini/ai/generation/task/callback
default:
model: doubao-seedream-5-0-260128

Loading…
Cancel
Save