Browse Source

增加手动刷新任务状态接口

glx
glx 17 hours ago
parent
commit
b4ca8a6e12
  1. 1
      .gitignore
  2. 18
      src/main/java/com/youlai/boot/mini/controller/AiGenerationController.java
  3. 3
      src/main/java/com/youlai/boot/mini/model/vo/AiGenerationTaskVO.java
  4. 2
      src/main/java/com/youlai/boot/mini/service/AiGenerationService.java
  5. 149
      src/main/java/com/youlai/boot/mini/service/impl/AiGenerationServiceImpl.java
  6. 19
      src/main/resources/application-dev.yml

1
.gitignore

@ -18,3 +18,4 @@ docker/minio/config
docker/xxljob/logs
application-youlai.yml
.claude
CLAUDE.md

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

@ -25,6 +25,7 @@ import org.springframework.security.access.prepost.PreAuthorize;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.youlai.boot.mini.model.query.AiTaskMediaQuery;
import com.youlai.boot.mini.model.query.DiscoveryPublicWorkQuery;
import com.youlai.boot.mini.model.entity.MiniAiGenerationTask;
import com.youlai.boot.mini.model.vo.AiGenerationTaskVO;
import com.youlai.boot.mini.model.vo.DiscoveryPublicWorkVO;
import cn.hutool.core.util.StrUtil;
@ -179,4 +180,21 @@ public class AiGenerationController {
return Result.success(result);
}
@Operation(summary = "手动同步AI任务状态")
@PostMapping("/task/{taskUuid}/sync-status")
@RepeatSubmit(expire = 60)
@Log(module = LogModuleEnum.AI_GENERATION_TASK, value = ActionTypeEnum.UPDATE)
public Result<Void> syncTaskStatus(@PathVariable String taskUuid) {
Long userId = SecurityUtils.getUserId();
MiniAiGenerationTask task = aiGenerationService.getTaskByUuid(taskUuid);
if (task == null) {
return Result.failed("任务不存在");
}
if (!task.getMiniUserId().equals(userId)) {
return Result.failed("无权操作该任务");
}
aiGenerationService.syncTaskStatus(taskUuid);
return Result.success();
}
}

3
src/main/java/com/youlai/boot/mini/model/vo/AiGenerationTaskVO.java

@ -31,6 +31,9 @@ public class AiGenerationTaskVO {
@Schema(description = "消耗积分")
private Integer pointsConsumed;
@Schema(description = "可见范围:public(公开) / private(仅自己)")
private String visibility;
@Schema(description = "创建时间")
private Date createTime;

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

@ -51,4 +51,6 @@ public interface AiGenerationService {
void updateTaskVisibility(Long userId, AiTaskVisibilityForm form);
void syncTaskStatus(String taskUuid);
}

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

@ -123,6 +123,19 @@ public class AiGenerationServiceImpl implements AiGenerationService {
@Value("${ai.task.timeout-minutes:300}")
private int taskTimeoutMinutes;
//AI任务状态查询接口
@Value("${ai.status.single-image-url}")
private String aiSingleImageStatusUrl;
@Value("${ai.status.four-panel-url}")
private String aiFourPanelStatusUrl;
@Value("${ai.status.video-url}")
private String aiVideoStatusUrl;
@Value("${ai.status.sync-min-interval-minutes:30}")
private int syncMinIntervalMinutes;
//AI单图生成积分规则编码
private static final String AI_GENERATE_SINGLE_IMAGE_RULE = "AI_GENERATE_SINGLE_IMAGE";
//AI四宫格生成积分规则编码
@ -285,6 +298,9 @@ public class AiGenerationServiceImpl implements AiGenerationService {
aiGenerationTaskMapper.insert(task);
// 立即关联用户上传的参考文件到当前任务并软删除
softDeleteUserUploads(userId, task.getId());
// 组装第三方AI接口需要的参数
Map<String, Object> aiRequest = new HashMap<>();
aiRequest.put("model", form.getModel() == null ? aiDefaultImageModel : form.getModel());
@ -373,6 +389,9 @@ public class AiGenerationServiceImpl implements AiGenerationService {
aiGenerationTaskMapper.insert(task);
// 立即关联用户上传的参考文件到当前任务并软删除
softDeleteUserUploads(userId, task.getId());
// 组装第三方AI接口需要的参数
Map<String, Object> aiRequest = new HashMap<>();
aiRequest.put("model", form.getModel() == null ? aiDefaultImageModel : form.getModel());
@ -461,6 +480,9 @@ public class AiGenerationServiceImpl implements AiGenerationService {
aiGenerationTaskMapper.insert(task);
// 立即关联用户上传的参考文件到当前任务并软删除
softDeleteUserUploads(userId, task.getId());
// 组装第三方接口参数
Map<String, Object> aiRequest = new HashMap<>();
aiRequest.put("model", form.getModel() == null ? aiDefaultVideoModel : form.getModel());
@ -564,18 +586,6 @@ public class AiGenerationServiceImpl implements AiGenerationService {
.setCreateTime(new Date());
aiTaskMediaMapper.insert(media);
// 更新用户上传的参考文件:关联当前任务ID并软删除
LambdaUpdateWrapper<MiniAiTaskMedia> updateMediaWrapper = new LambdaUpdateWrapper<>();
updateMediaWrapper.eq(MiniAiTaskMedia::getMiniUserId, task.getMiniUserId())
.isNull(MiniAiTaskMedia::getTaskId)
.eq(MiniAiTaskMedia::getFileSource, "user_upload")
.eq(MiniAiTaskMedia::getDeleted, false)
.set(MiniAiTaskMedia::getTaskId, task.getId())
.set(MiniAiTaskMedia::getDeleted, true)
.set(MiniAiTaskMedia::getUpdateTime, new Date())
.set(MiniAiTaskMedia::getUpdateTimestamp, System.currentTimeMillis());
aiTaskMediaMapper.update(null, updateMediaWrapper);
// 同步发送订阅消息通知
sendAiGenerateSuccessNotify(task.getMiniUserId(), subscribeTemplate, task.getId());
}
@ -654,7 +664,7 @@ public class AiGenerationServiceImpl implements AiGenerationService {
media.setUuid(mediaUuid)
.setTaskId(task.getId())
.setMiniUserId(task.getMiniUserId())
.setFileSource("ai_generate")
.setFileSource("ai_generated")
.setMediaType("image")
.setSourceUrl(ossUrl)
.setWidth(width)
@ -665,18 +675,6 @@ public class AiGenerationServiceImpl implements AiGenerationService {
aiTaskMediaMapper.insert(media);
}
// 更新用户上传的参考文件:关联当前任务ID并软删除
LambdaUpdateWrapper<MiniAiTaskMedia> updateMediaWrapper = new LambdaUpdateWrapper<>();
updateMediaWrapper.eq(MiniAiTaskMedia::getMiniUserId, task.getMiniUserId())
.isNull(MiniAiTaskMedia::getTaskId)
.eq(MiniAiTaskMedia::getFileSource, "user_upload")
.eq(MiniAiTaskMedia::getDeleted, false)
.set(MiniAiTaskMedia::getTaskId, task.getId())
.set(MiniAiTaskMedia::getDeleted, true)
.set(MiniAiTaskMedia::getUpdateTime, new Date())
.set(MiniAiTaskMedia::getUpdateTimestamp, System.currentTimeMillis());
aiTaskMediaMapper.update(null, updateMediaWrapper);
// 同步发送订阅消息通知
sendAiGenerateSuccessNotify(task.getMiniUserId(), subscribeTemplate, task.getId());
}
@ -770,18 +768,6 @@ public class AiGenerationServiceImpl implements AiGenerationService {
aiTaskMediaMapper.insert(media);
}
// 更新用户上传的参考文件:关联当前任务ID并软删除
LambdaUpdateWrapper<MiniAiTaskMedia> updateMediaWrapper = new LambdaUpdateWrapper<>();
updateMediaWrapper.eq(MiniAiTaskMedia::getMiniUserId, task.getMiniUserId())
.isNull(MiniAiTaskMedia::getTaskId)
.eq(MiniAiTaskMedia::getFileSource, "user_upload")
.eq(MiniAiTaskMedia::getDeleted, false)
.set(MiniAiTaskMedia::getTaskId, task.getId())
.set(MiniAiTaskMedia::getDeleted, true)
.set(MiniAiTaskMedia::getUpdateTime, new Date())
.set(MiniAiTaskMedia::getUpdateTimestamp, System.currentTimeMillis());
aiTaskMediaMapper.update(null, updateMediaWrapper);
// 同步发送订阅消息通知
sendAiGenerateSuccessNotify(task.getMiniUserId(), subscribeTemplate, task.getId());
}
@ -805,6 +791,77 @@ public class AiGenerationServiceImpl implements AiGenerationService {
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public void syncTaskStatus(String taskUuid) {
MiniAiGenerationTask task = getTaskByUuid(taskUuid);
if (task == null) {
throw new BusinessException("任务不存在");
}
// 终态不允许手动同步
if (task.getStatus() != null && (task.getStatus() == 1 || task.getStatus() == 2)) {
throw new BusinessException("任务已完成或已失败,无需同步");
}
// 创建后需等待配置的时间才能手动同步
long elapsedMinutes = (System.currentTimeMillis() - task.getCreateTimestamp()) / 60000;
if (elapsedMinutes < syncMinIntervalMinutes) {
throw new BusinessException("任务创建不足" + syncMinIntervalMinutes + "分钟,请" + (syncMinIntervalMinutes - elapsedMinutes) + "分钟后再试");
}
String statusUrl = buildAiStatusUrl(task);
log.info("手动同步任务状态,taskUuid:{},type:{},statusUrl:{}", taskUuid, task.getType(), statusUrl);
String responseBody;
try {
HttpResponse response = HttpRequest.get(statusUrl).timeout(10000).execute();
if (!response.isOk()) {
log.error("AI状态查询返回非200,taskUuid:{},httpStatus:{}", taskUuid, response.getStatus());
throw new BusinessException("查询AI任务状态失败,HTTP状态码: " + response.getStatus());
}
responseBody = response.body();
} catch (BusinessException e) {
throw e;
} catch (Exception e) {
log.error("调用AI状态查询接口失败,taskUuid:{}", taskUuid, e);
throw new BusinessException("查询AI任务状态失败: " + e.getMessage());
}
log.info("AI状态查询响应,taskUuid:{},body:{}", taskUuid, responseBody);
processAiStatusResponse(task.getType(), responseBody);
}
private String buildAiStatusUrl(MiniAiGenerationTask task) {
switch (task.getType()) {
case "img_single":
return aiSingleImageStatusUrl + task.getUuid();
case "img_grid_4":
return aiFourPanelStatusUrl + task.getUuid();
case "video":
if (StrUtil.isBlank(task.getVideoTaskUuid())) {
throw new BusinessException("视频任务尚未获取到第三方任务ID");
}
return aiVideoStatusUrl + task.getVideoTaskUuid();
default:
throw new BusinessException("不支持的任务类型: " + task.getType());
}
}
private void processAiStatusResponse(String type, String responseBody) {
switch (type) {
case "img_single":
handleTaskCallback(JSONUtil.toBean(responseBody, AiSingleImageCallbackForm.class));
break;
case "img_grid_4":
handleFourPanelCallback(JSONUtil.toBean(responseBody, AiFourPanelCallbackForm.class));
break;
case "video":
handleVideoTaskCallback(JSONUtil.toBean(responseBody, AiVideoCallbackVO.class));
break;
}
}
@Override
public MiniAiGenerationTask getTaskByUuid(String uuid) {
return aiGenerationTaskMapper.selectOne(new LambdaQueryWrapper<MiniAiGenerationTask>()
@ -1093,6 +1150,7 @@ public class AiGenerationServiceImpl implements AiGenerationService {
AiGenerationTaskVO vo = new AiGenerationTaskVO();
// vo.setId(task.getId());
vo.setUuid(task.getUuid());
vo.setVisibility(task.getVisibility());
vo.setType(task.getType());
vo.setStatus(task.getStatus());
vo.setPointsConsumed(task.getPointsConsumed());
@ -1181,6 +1239,23 @@ public class AiGenerationServiceImpl implements AiGenerationService {
}
}
/**
* 将用户当前未关联任务的上传文件关联到指定任务并软删除
* 在创建任务时调用避免任务生成中用户仍能看到旧上传文件
*/
private void softDeleteUserUploads(Long userId, Long taskId) {
LambdaUpdateWrapper<MiniAiTaskMedia> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(MiniAiTaskMedia::getMiniUserId, userId)
.isNull(MiniAiTaskMedia::getTaskId)
.eq(MiniAiTaskMedia::getFileSource, "user_upload")
.eq(MiniAiTaskMedia::getDeleted, false)
.set(MiniAiTaskMedia::getTaskId, taskId)
.set(MiniAiTaskMedia::getDeleted, true)
.set(MiniAiTaskMedia::getUpdateTime, new Date())
.set(MiniAiTaskMedia::getUpdateTimestamp, System.currentTimeMillis());
aiTaskMediaMapper.update(null, updateWrapper);
}
/**
* 发送AI作品完成订阅消息通知
* @param userId 接收用户ID

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

@ -233,20 +233,27 @@ wx:
# AIGeneration 配置
ai:
generate:
single-image-server-url: http://192.168.31.93:8001/api/v1/photo-to-comic
four-panel-server-url: http://192.168.31.93:8001/api/v1/four-panel-comic
video-server-url: http://192.168.31.93:8001/api/v1/video/submit
single-image-server-url: http://127.0.0.1:8001/api/v1/photo-to-comic
four-panel-server-url: http://127.0.0.1:8001/api/v1/four-panel-comic
video-server-url: http://127.0.0.1:8001/api/v1/video/submit
callback:
single-image-callback-url: http://192.168.31.197:30101/backend/api/v1/mini/ai/generation/single-image/task/callback
four-panel-callback-url: http://192.168.31.197:30101/backend/api/v1/mini/ai/generation/four-panel/task/callback
single-image-callback-url: https://0401-153-34-180-144.ngrok-free.app/backend/api/v1/mini/ai/generation/single-image/task/callback
four-panel-callback-url: https://0401-153-34-180-144.ngrok-free.app/backend/api/v1/mini/ai/generation/four-panel/task/callback
#需要内网穿透工具 由火山方舟 api 回调,ngrok http 30101 替换为内网穿透工具地址
video-url: http://101.34.78.57:30101/backend/api/v1/mini/ai/generation/video/task/callback
video-url: https://0401-153-34-180-144.ngrok-free.app/backend/api/v1/mini/ai/generation/video/task/callback
default:
image-model: doubao-seedream-5-0-260128
video-model: doubao-seedance-2-0-260128
task:
# AI任务超时时间 单位分钟,默认300分钟
timeout-minutes: 300
# AI任务状态查询接口
status:
single-image-url: http://127.0.0.1:8001/api/v1/photo-to-comic/status/
four-panel-url: http://127.0.0.1:8001/api/v1/four-panel-comic/status/
video-url: http://127.0.0.1:8001/api/v1/video/status/
# 手动同步最小间隔(分钟),任务创建后至少等待此时间才能手动同步
sync-min-interval-minutes: 30
# 订阅模板配置
subscribe:

Loading…
Cancel
Save