优化
This commit is contained in:
@@ -81,8 +81,17 @@ public class TikUserFileGroupServiceImpl implements TikUserFileGroupService {
|
||||
}
|
||||
}
|
||||
|
||||
log.info("[addFilesToGroups][用户({})添加文件到分组成功,文件数量({}),分组数量({})]",
|
||||
userId, fileIds.size(), groupIds.size());
|
||||
// 统一更新 tik_user_file 表的 groupId 字段(使用第一个分组作为主分组)
|
||||
// 重要:这里必须更新 groupId,否则查询时 groupId 仍为空
|
||||
Long primaryGroupId = groupIds.get(0); // 使用第一个分组ID作为主分组
|
||||
userFileMapper.update(
|
||||
new TikUserFileDO().setGroupId(primaryGroupId),
|
||||
new LambdaQueryWrapperX<TikUserFileDO>()
|
||||
.in(TikUserFileDO::getId, fileIds)
|
||||
);
|
||||
|
||||
log.info("[addFilesToGroups][用户({})添加文件到分组成功,文件数量({}),分组数量({}),主分组ID({})]",
|
||||
userId, fileIds.size(), groupIds.size(), primaryGroupId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -104,6 +113,24 @@ public class TikUserFileGroupServiceImpl implements TikUserFileGroupService {
|
||||
.in(TikUserFileGroupDO::getGroupId, groupIds)
|
||||
);
|
||||
|
||||
// 清理 tik_user_file 表的 groupId 字段(当文件不再属于任何分组时)
|
||||
for (Long fileId : fileIds) {
|
||||
// 检查文件是否还有其他分组关联
|
||||
List<TikUserFileGroupDO> remainingRelations = userFileGroupMapper.selectList(
|
||||
new LambdaQueryWrapperX<TikUserFileGroupDO>()
|
||||
.eq(TikUserFileGroupDO::getFileId, fileId)
|
||||
);
|
||||
|
||||
// 如果没有剩余关联关系,清理 groupId 字段
|
||||
if (CollUtil.isEmpty(remainingRelations)) {
|
||||
userFileMapper.update(
|
||||
new TikUserFileDO().setGroupId(null),
|
||||
new LambdaQueryWrapperX<TikUserFileDO>()
|
||||
.eq(TikUserFileDO::getId, fileId)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
log.info("[removeFilesFromGroups][用户({})从分组移除文件成功,文件数量({}),分组数量({})]",
|
||||
userId, fileIds.size(), groupIds.size());
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
package cn.iocoder.yudao.module.tik.media;
|
||||
|
||||
import cn.iocoder.yudao.module.tik.mix.config.IceProperties;
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.aliyun.ice20201109.Client;
|
||||
import com.aliyun.ice20201109.models.*;
|
||||
import com.aliyun.teaopenapi.models.Config;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@@ -28,78 +30,35 @@ import java.util.*;
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class BatchProduceAlignment {
|
||||
|
||||
static final String regionId = "cn-hangzhou";
|
||||
static final String bucket = "muye-ai-chat";
|
||||
private final IceProperties properties;
|
||||
private Client iceClient;
|
||||
|
||||
private final String accessKeyId = "LTAI5tPV9Ag3csf41GZjaLTA";
|
||||
private final String accessKeySecret = "kDqlGeJTKw6tJtFYiaY8vQTFuVIQDs";
|
||||
|
||||
|
||||
/*public static void main(String[] args) {
|
||||
try {
|
||||
BatchProduceAlignment batchProduce = new BatchProduceAlignment();
|
||||
batchProduce.initClient();
|
||||
// 文字素材
|
||||
String text = "人们懂得用五味杂陈形容人生,因为懂得味道是每个人心中固守的情怀。在这个时代,每一个人都经历了太多的苦痛和喜悦,人们总会将苦涩藏在心里,而把幸福变成食物,呈现在四季的餐桌之上";
|
||||
// 视频素材
|
||||
String[] videoArray = new String[]{
|
||||
"https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/test_media/food/f1.mp4",
|
||||
"https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/test_media/food/f2.mp4",
|
||||
"https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/test_media/food/f3.mp4",
|
||||
"https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/test_media/food/f4.mp4",
|
||||
"https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/test_media/food/f5.mp4",
|
||||
"https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/test_media/food/f6.mp4",
|
||||
"https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/test_media/food/f7.mp4",
|
||||
"https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/test_media/food/f8.mp4",
|
||||
"https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/test_media/food/f9.mp4",
|
||||
"https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/test_media/food/f10.mp4",
|
||||
"https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/test_media/food/f11.mp4",
|
||||
"https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/test_media/food/f12.mp4",
|
||||
"https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/test_media/food/f13.mp4",
|
||||
"https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/test_media/food/f14.mp4",
|
||||
"https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/test_media/food/f15.mp4",
|
||||
"https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/test_media/food/f16.mp4",
|
||||
"https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/test_media/food/f17.mp4",
|
||||
"https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/test_media/food/f18.mp4"
|
||||
};
|
||||
// 背景音乐素材
|
||||
String[] bgMusicArray = new String[]{
|
||||
"https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/test_media/music/m1.wav",
|
||||
"https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/test_media/music/m2.wav",
|
||||
"https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/test_media/music/m3.wav"
|
||||
};
|
||||
// 视频标题
|
||||
String title = "舌尖上的美食";
|
||||
// 生成的成片数
|
||||
int produceCount = 3;
|
||||
List<String> mediaList = batchProduce.batchProduceAlignment(title,text,videoArray,bgMusicArray,produceCount);
|
||||
mediaList.forEach(System.out::println);
|
||||
} catch (Exception e) {
|
||||
System.out.println("Produce failed. Exception: " + e.toString());
|
||||
}
|
||||
}*/
|
||||
|
||||
|
||||
public void initClient() throws Exception {
|
||||
log.info("初始化阿里云 ICE 客户端...");
|
||||
// 阿里云账号AccessKey拥有所有API的访问权限,建议您使用RAM用户进行API访问或日常运维。
|
||||
// 本示例以将AccessKey ID和 AccessKey Secret保存在环境变量为例说明。配置方法请参见:https://help.aliyun.com/zh/sdk/developer-reference/v2-manage-access-credentials?spm=a2c4g.11186623.0.0.423350fbOTFdOB#2a38e5c14b4em
|
||||
com.aliyun.credentials.Client credentialClient = new com.aliyun.credentials.Client();
|
||||
Config config = new Config();
|
||||
config.setCredential(credentialClient);
|
||||
// 如需硬编码AccessKey ID和AccessKey Secret,代码如下,但强烈建议不要把AccessKey ID和AccessKey Secret保存到工程代码里,否则可能导致AccessKey泄露,威胁您账号下所有资源的安全。
|
||||
config.accessKeyId = accessKeyId;
|
||||
config.accessKeySecret = accessKeySecret;
|
||||
config.endpoint = "ice." + regionId + ".aliyuncs.com";
|
||||
config.regionId = regionId;
|
||||
iceClient = new Client(config);
|
||||
log.info("ICE 客户端初始化成功");
|
||||
if (iceClient == null) {
|
||||
synchronized (this) {
|
||||
if (iceClient == null) {
|
||||
if (!properties.isEnabled()) {
|
||||
log.error("ICE 配置未启用或未配置 AccessKey");
|
||||
throw new IllegalStateException("未配置 ICE AccessKey");
|
||||
}
|
||||
log.info("初始化阿里云 ICE 客户端... regionId={}, bucket={}",
|
||||
properties.getRegionId(), properties.getBucket());
|
||||
Config config = new Config();
|
||||
config.accessKeyId = properties.getAccessKeyId();
|
||||
config.accessKeySecret = properties.getAccessKeySecret();
|
||||
config.endpoint = "ice." + properties.getRegionId() + ".aliyuncs.com";
|
||||
config.regionId = properties.getRegionId();
|
||||
iceClient = new Client(config);
|
||||
log.info("ICE 客户端初始化成功");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<String> batchProduceAlignment(String title,String text,String[] videoArray,String[] bgMusicArray,int produceCount) throws Exception {
|
||||
public List<String> batchProduceAlignment(String title, String[] videoArray, int produceCount) throws Exception {
|
||||
// 初始化 ICE 客户端
|
||||
if (iceClient == null) {
|
||||
initClient();
|
||||
@@ -107,48 +66,72 @@ public class BatchProduceAlignment {
|
||||
|
||||
// 批量提交任务,返回 "jobId:url" 格式
|
||||
List<String> jobIdWithUrls = new ArrayList<>();
|
||||
for (int i = 0; i < produceCount; i++) {
|
||||
String jobIdWithUrl = produceSingleVideo(title, text, videoArray, bgMusicArray);
|
||||
|
||||
if (produceCount <= 1) {
|
||||
// 生成1个视频,包含所有片段
|
||||
String jobIdWithUrl = produceSingleVideo(title, videoArray);
|
||||
jobIdWithUrls.add(jobIdWithUrl);
|
||||
} else {
|
||||
// 生成多个视频:将视频数组分成多份,每份生成一个视频
|
||||
int videoCount = videoArray.length;
|
||||
|
||||
// 计算每份的起始和结束索引
|
||||
int videosPerGroup = Math.max(1, videoCount / produceCount);
|
||||
int remainder = videoCount % produceCount;
|
||||
|
||||
int start = 0;
|
||||
for (int i = 0; i < produceCount; i++) {
|
||||
// 计算当前组的视频数量(尽可能平均分配)
|
||||
int groupSize = videosPerGroup + (i < remainder ? 1 : 0);
|
||||
|
||||
// 提取当前组的视频片段
|
||||
String[] groupVideos = Arrays.copyOfRange(videoArray, start, start + groupSize);
|
||||
|
||||
// 生成单个视频
|
||||
String jobIdWithUrl = produceSingleVideo(title, groupVideos);
|
||||
jobIdWithUrls.add(jobIdWithUrl);
|
||||
|
||||
start += groupSize;
|
||||
}
|
||||
}
|
||||
|
||||
// 改为异步模式,不在这里等待
|
||||
return jobIdWithUrls;
|
||||
}
|
||||
|
||||
public String produceSingleVideo(String title, String text, String[] videoArray, String[] bgMusicArray) throws Exception {
|
||||
public String produceSingleVideo(String title, String[] videoArray) throws Exception {
|
||||
// 初始化 ICE 客户端
|
||||
if (iceClient == null) {
|
||||
initClient();
|
||||
}
|
||||
|
||||
text = text.replace(",", "。");
|
||||
text = text.replace("\n", "。");
|
||||
String[] sentenceArray = text.split("。");
|
||||
|
||||
// 纯画面模式:仅拼接视频片段,添加空的音频轨道
|
||||
JSONArray videoClipArray = new JSONArray();
|
||||
JSONArray audioClipArray = new JSONArray();
|
||||
|
||||
List<String> videoList = Arrays.asList(videoArray);
|
||||
Collections.shuffle(videoList);
|
||||
for (int i = 0; i < sentenceArray.length; i++) {
|
||||
String sentence = sentenceArray[i];
|
||||
// 按顺序拼接视频片段(不随机打乱)
|
||||
for (int i = 0; i < videoArray.length; i++) {
|
||||
String clipId = "clip" + i;
|
||||
String videoUrl = videoList.get(i);
|
||||
String videoClip = "{\"MediaURL\":\""+videoUrl+"\",\"ReferenceClipId\":\""+clipId+"\",\"Effects\":[{\"Type\":\"Background\",\"SubType\":\"Blur\",\"Radius\":0.1}]}";
|
||||
String videoUrl = videoArray[i];
|
||||
|
||||
// 验证视频URL必须是阿里云OSS地址
|
||||
if (!videoUrl.contains(".aliyuncs.com")) {
|
||||
log.error("[ICE][视频URL不是阿里云OSS地址][视频{}: {}]", i + 1, videoUrl);
|
||||
throw new IllegalArgumentException("视频URL必须是阿里云OSS地址,当前URL: " + videoUrl);
|
||||
}
|
||||
|
||||
log.debug("[ICE][添加视频片段][{}: {}]", i + 1, videoUrl);
|
||||
// 每个视频片段添加静音效果(Volume: 0)
|
||||
String videoClip = "{\"MediaURL\":\""+videoUrl+"\",\"ReferenceClipId\":\""+clipId+"\",\"Volume\":0,\"Effects\":[{\"Type\":\"Background\",\"SubType\":\"Blur\",\"Radius\":0.1}]}";
|
||||
videoClipArray.add(JSONObject.parseObject(videoClip));
|
||||
String audioClip = "{\"Type\":\"AI_TTS\",\"Content\":\"" + sentence + "\",\"Voice\":\"zhichu\",\"ClipId\":\""+clipId+"\",\"Effects\":[{\"Type\":\"AI_ASR\",\"Font\":\"Alibaba PuHuiTi\",\"Alignment\":\"TopCenter\",\"Y\":0.75,\"FontSize\":55,\"FontColor\":\"#ffffff\",\"AdaptMode\":\"AutoWrap\",\"TextWidth\":0.8,\"Outline\":2,\"OutlineColour\":\"#000000\"}]}";
|
||||
audioClipArray.add(JSONObject.parseObject(audioClip));
|
||||
}
|
||||
|
||||
String subtitleTrack = "{\"SubtitleTrackClips\":[{\"Type\":\"Text\",\"Font\":\"HappyZcool-2016\",\"Content\":\""+title+"\",\"FontSize\":80,\"FontColor\":\"#ffffff\",\"Y\":0.15,\"Alignment\":\"TopCenter\",\"EffectColorStyle\":\"CS0004-000005\",\"FontFace\":{\"Bold\":true,\"Italic\":false,\"Underline\":false}}]}";
|
||||
|
||||
int bgMusicIndex = (int)(Math.random() * bgMusicArray.length);
|
||||
String bgMusicUrl = bgMusicArray[bgMusicIndex];
|
||||
String timeline = "{\"VideoTracks\":[{\"VideoTrackClips\":"+videoClipArray.toJSONString()+"}],\"AudioTracks\":[{\"AudioTrackClips\":"+audioClipArray.toJSONString()+"},{\"AudioTrackClips\":[{\"MediaURL\":\""+bgMusicUrl+"\"}]}],\"SubtitleTracks\":[" + subtitleTrack + "]}";
|
||||
// 添加空的音频轨道(避免ICE报错)
|
||||
JSONArray emptyAudioClipArray = new JSONArray();
|
||||
String timeline = "{\"VideoTracks\":[{\"VideoTrackClips\":"+videoClipArray.toJSONString()+"}],\"AudioTracks\":[{\"AudioTrackClips\":"+emptyAudioClipArray.toJSONString()+"}]}";
|
||||
|
||||
//
|
||||
String targetFileName = UUID.randomUUID().toString().replace("-", "");
|
||||
String outputMediaUrl = "http://" + bucket + ".oss-" + regionId + ".aliyuncs.com/ice_output/" + targetFileName + ".mp4";
|
||||
String outputMediaUrl = "http://" + properties.getBucket() + ".oss-" + properties.getRegionId() + ".aliyuncs.com/ice_output/" + targetFileName + ".mp4";
|
||||
int width = 720;
|
||||
int height = 1280;
|
||||
String outputMediaConfig = "{\"MediaURL\":\"" + outputMediaUrl + "\",\"Width\":"+width+",\"Height\":"+height+"}";
|
||||
@@ -156,9 +139,13 @@ public class BatchProduceAlignment {
|
||||
SubmitMediaProducingJobRequest request = new SubmitMediaProducingJobRequest();
|
||||
request.setTimeline(timeline);
|
||||
request.setOutputMediaConfig(outputMediaConfig);
|
||||
|
||||
log.info("[ICE][提交任务][视频数量={}, timeline={}]", videoArray.length, timeline);
|
||||
SubmitMediaProducingJobResponse response = iceClient.submitMediaProducingJob(request);
|
||||
log.info("start job. jobid: " + response.getBody().getJobId() + ", outputMediaUrl: " + outputMediaUrl);
|
||||
return response.getBody().getJobId() + " : " + outputMediaUrl;
|
||||
|
||||
String jobId = response.getBody().getJobId();
|
||||
log.info("[ICE][任务提交成功][jobId={}, outputMediaUrl={}]", jobId, outputMediaUrl);
|
||||
return jobId + " : " + outputMediaUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,28 +19,24 @@ import java.util.List;
|
||||
@Slf4j
|
||||
public class BatchProduceAlignmentController {
|
||||
|
||||
private final BatchProduceAlignment batchProduceAlignment;
|
||||
|
||||
@PostMapping("/batchProduceAlignment")
|
||||
@Operation(summary = "视频混剪", description = "视频混剪")
|
||||
@Operation(summary = "视频混剪(纯画面模式)", description = "仅拼接视频片段,无配音、无背景音乐")
|
||||
public Object batchProduceAlignment(
|
||||
//视频标题
|
||||
@RequestParam String title,
|
||||
//文字素材
|
||||
@RequestParam String text,
|
||||
// 视频素材
|
||||
@RequestParam String[] videoArray,
|
||||
// 背景音乐素材
|
||||
@RequestParam String[] bgMusicArray,
|
||||
// 生成的成片数
|
||||
int produceCount) {
|
||||
try {
|
||||
BatchProduceAlignment batchProduce = new BatchProduceAlignment();
|
||||
batchProduce.initClient();
|
||||
List<String> jobIds = batchProduce.batchProduceAlignment(title,text,videoArray,bgMusicArray,produceCount);
|
||||
// 纯画面模式:仅传入视频数组,无需text和bgMusicArray
|
||||
List<String> jobIds = batchProduceAlignment.batchProduceAlignment(title, videoArray, produceCount);
|
||||
return CommonResult.success(jobIds);
|
||||
} catch (Exception e) {
|
||||
return CommonResult.error(new ErrorCode(500, "Produce failed. Exception: " + e));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -24,8 +24,15 @@ public class MixTaskConstants {
|
||||
|
||||
/**
|
||||
* 定时任务配置
|
||||
* 改为每2分钟检查一次,降低API调用频率
|
||||
*/
|
||||
public static final String CRON_CHECK_STATUS = "*/30 * * * * ?";
|
||||
public static final String CRON_CHECK_STATUS = "0 */2 * * * ?";
|
||||
|
||||
/**
|
||||
* 任务状态检查优化配置
|
||||
*/
|
||||
public static final int CHECK_HOURS_LIMIT = 6; // 只检查最近6小时内的任务
|
||||
public static final int CHECK_BATCH_SIZE = 50; // 每次最多检查50个任务
|
||||
|
||||
private MixTaskConstants() {
|
||||
// 防止实例化
|
||||
|
||||
@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.tik.mix.service;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.module.tik.mix.client.IceClient;
|
||||
import cn.iocoder.yudao.module.tik.mix.constants.MixTaskConstants;
|
||||
import cn.iocoder.yudao.module.tik.mix.dal.dataobject.MixTaskDO;
|
||||
import cn.iocoder.yudao.module.tik.mix.dal.mysql.MixTaskMapper;
|
||||
@@ -13,6 +14,8 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
@@ -28,6 +31,7 @@ public class MixTaskServiceImpl implements MixTaskService {
|
||||
|
||||
private final MixTaskMapper mixTaskMapper;
|
||||
private final BatchProduceAlignment batchProduceAlignment;
|
||||
private final IceClient iceClient;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@@ -149,7 +153,11 @@ public class MixTaskServiceImpl implements MixTaskService {
|
||||
// 3. 重新提交到ICE
|
||||
CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
MixTaskSaveReqVO saveReqVO = BeanUtils.toBean(existTask, MixTaskSaveReqVO.class);
|
||||
// 手动构建请求对象(纯画面模式:无需text和bgMusicUrls)
|
||||
MixTaskSaveReqVO saveReqVO = new MixTaskSaveReqVO();
|
||||
saveReqVO.setTitle(existTask.getTitle());
|
||||
saveReqVO.setVideoUrls(existTask.getVideoUrlList());
|
||||
saveReqVO.setProduceCount(existTask.getProduceCount());
|
||||
submitToICE(id, saveReqVO);
|
||||
} catch (Exception e) {
|
||||
log.error("重新提交任务失败,任务ID: {}", id, e);
|
||||
@@ -195,18 +203,29 @@ public class MixTaskServiceImpl implements MixTaskService {
|
||||
public void checkTaskStatusBatch() {
|
||||
log.debug("开始批量检查任务状态");
|
||||
|
||||
// 查询所有运行中的任务
|
||||
// 性能优化点:
|
||||
// 1. 时间范围限制:只检查最近6小时内的任务(避免检查历史任务)
|
||||
// 2. 数量限制:每次最多检查50个任务(避免单次查询过多)
|
||||
// 3. 索引优化:需要为 create_time 字段添加索引(见 V20251129 迁移文件)
|
||||
// 4. 频率优化:定时任务频率从30秒改为2分钟
|
||||
LocalDateTime startTime = LocalDateTime.now().minusHours(MixTaskConstants.CHECK_HOURS_LIMIT);
|
||||
|
||||
// 查询运行中的任务(限制时间和数量)
|
||||
List<MixTaskDO> runningTasks = mixTaskMapper.selectList(
|
||||
new cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX<MixTaskDO>()
|
||||
.eq(MixTaskDO::getStatus, MixTaskConstants.STATUS_RUNNING)
|
||||
.ge(MixTaskDO::getCreateTime, startTime)
|
||||
.orderByDesc(MixTaskDO::getCreateTime)
|
||||
.last("LIMIT " + MixTaskConstants.CHECK_BATCH_SIZE) // 限制数量
|
||||
);
|
||||
|
||||
if (runningTasks.isEmpty()) {
|
||||
log.debug("没有运行中的任务,跳过检查");
|
||||
log.debug("没有最近{}小时内运行中的任务,跳过检查", MixTaskConstants.CHECK_HOURS_LIMIT);
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("发现 {} 个运行中的任务,开始检查状态", runningTasks.size());
|
||||
log.info("发现 {} 个最近{}小时内的运行中任务,开始检查状态(最多检查{}个)",
|
||||
runningTasks.size(), MixTaskConstants.CHECK_HOURS_LIMIT, MixTaskConstants.CHECK_BATCH_SIZE);
|
||||
|
||||
// 逐个检查任务状态
|
||||
for (MixTaskDO task : runningTasks) {
|
||||
@@ -219,6 +238,7 @@ public class MixTaskServiceImpl implements MixTaskService {
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("检查任务状态失败,任务ID: {}", task.getId(), e);
|
||||
// 单个任务失败不影响其他任务
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,16 +250,52 @@ public class MixTaskServiceImpl implements MixTaskService {
|
||||
log.debug("同步任务状态,任务ID: {}, jobId: {}", taskId, jobId);
|
||||
|
||||
try {
|
||||
// TODO: 调用阿里云 ICE API 查询任务状态
|
||||
// 这里需要集成具体的 ICE SDK 或 HTTP API
|
||||
// 检查任务是否超时(超过12小时则标记为失败)
|
||||
MixTaskDO task = mixTaskMapper.selectById(taskId);
|
||||
if (task != null) {
|
||||
LocalDateTime createTime = task.getCreateTime();
|
||||
if (createTime != null) {
|
||||
long hoursPassed = ChronoUnit.HOURS.between(createTime, LocalDateTime.now());
|
||||
if (hoursPassed > 12) {
|
||||
log.warn("[ICE][任务超时,自动标记为失败][taskId={}, 已运行{}小时]", taskId, hoursPassed);
|
||||
updateTaskError(taskId, "任务执行超时(超过12小时)");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 模拟状态检查逻辑
|
||||
// String status = iceClient.getJobStatus(jobId);
|
||||
// String progress = iceClient.getJobProgress(jobId);
|
||||
// String outputUrl = iceClient.getJobOutput(jobId);
|
||||
// 调用阿里云 ICE API 查询任务状态
|
||||
String status = iceClient.getMediaProducingJobStatus(jobId);
|
||||
log.debug("[ICE][查询到任务状态][taskId={}, jobId={}, status={}]", taskId, jobId, status);
|
||||
|
||||
// 根据返回的状态更新任务
|
||||
// updateTaskStatus(taskId, status, progress, outputUrl);
|
||||
// 根据ICE状态更新任务
|
||||
if ("Success".equalsIgnoreCase(status) || "success".equalsIgnoreCase(status)) {
|
||||
// 任务成功完成,更新为100%
|
||||
log.info("[ICE][任务执行成功][taskId={}, jobId={}]", taskId, jobId);
|
||||
updateTaskStatus(taskId, MixTaskConstants.STATUS_SUCCESS, MixTaskConstants.PROGRESS_COMPLETED);
|
||||
} else if ("Failed".equalsIgnoreCase(status) || "failed".equalsIgnoreCase(status) || "Failure".equalsIgnoreCase(status)) {
|
||||
// 任务失败 - 获取详细错误信息
|
||||
String errorMsg = "ICE任务执行失败";
|
||||
try {
|
||||
// 尝试获取更详细的失败信息(如果ICE API支持)
|
||||
errorMsg = "ICE任务执行失败,状态: " + status;
|
||||
} catch (Exception ex) {
|
||||
log.warn("[ICE][获取详细失败信息失败][taskId={}]", taskId, ex);
|
||||
}
|
||||
log.error("[ICE][任务执行失败][taskId={}, jobId={}, status={}]", taskId, jobId, status);
|
||||
updateTaskError(taskId, errorMsg);
|
||||
} else if ("Running".equalsIgnoreCase(status) || "running".equalsIgnoreCase(status) || "Processing".equalsIgnoreCase(status)) {
|
||||
// 任务仍在运行,更新进度为70%
|
||||
updateTaskStatus(taskId, MixTaskConstants.STATUS_RUNNING, 70);
|
||||
log.debug("[ICE][任务执行中][taskId={}, jobId={}, progress=70%]", taskId, jobId);
|
||||
} else if ("Pending".equalsIgnoreCase(status) || "pending".equalsIgnoreCase(status)) {
|
||||
// 任务等待中,更新进度为60%
|
||||
updateTaskStatus(taskId, MixTaskConstants.STATUS_RUNNING, 60);
|
||||
log.debug("[ICE][任务等待中][taskId={}, jobId={}, progress=60%]", taskId, jobId);
|
||||
} else {
|
||||
// 未知状态,记录日志但不更新
|
||||
log.warn("[ICE][未知任务状态][taskId={}, jobId={}, status={}]", taskId, jobId, status);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("同步任务状态失败,任务ID: {}, jobId: {}", taskId, jobId, e);
|
||||
@@ -276,14 +332,11 @@ public class MixTaskServiceImpl implements MixTaskService {
|
||||
|
||||
// 2. 转换为ICE需要的参数格式
|
||||
String[] videoArray = createReqVO.getVideoUrls().toArray(new String[0]);
|
||||
String[] bgMusicArray = createReqVO.getBgMusicUrls().toArray(new String[0]);
|
||||
|
||||
// 3. 调用ICE批量生成接口
|
||||
// 3. 调用ICE批量生成接口(纯画面模式:无需text和bgMusic)
|
||||
List<String> jobIdWithUrls = batchProduceAlignment.batchProduceAlignment(
|
||||
createReqVO.getTitle(),
|
||||
createReqVO.getText(),
|
||||
videoArray,
|
||||
bgMusicArray,
|
||||
createReqVO.getProduceCount()
|
||||
);
|
||||
|
||||
|
||||
@@ -26,9 +26,9 @@ public class MixTaskUtils {
|
||||
MixTaskDO task = new MixTaskDO();
|
||||
task.setUserId(userId);
|
||||
task.setTitle(reqVO.getTitle());
|
||||
task.setText(reqVO.getText());
|
||||
task.setText(null); // 纯画面模式,不需要文案
|
||||
task.setVideoUrlList(reqVO.getVideoUrls());
|
||||
task.setBgMusicUrlList(reqVO.getBgMusicUrls());
|
||||
task.setBgMusicUrlList(null); // 纯画面模式,不需要背景音乐
|
||||
task.setProduceCount(reqVO.getProduceCount());
|
||||
task.setStatus(MixTaskConstants.STATUS_PENDING);
|
||||
task.setProgress(0);
|
||||
|
||||
@@ -16,19 +16,11 @@ public class MixTaskSaveReqVO {
|
||||
@NotBlank(message = "视频标题不能为空")
|
||||
private String title;
|
||||
|
||||
@Schema(description = "文案内容", required = true, example = "人们懂得用五味杂陈形容人生...")
|
||||
@NotBlank(message = "文案内容不能为空")
|
||||
private String text;
|
||||
|
||||
@Schema(description = "视频素材URL列表", required = true)
|
||||
@NotEmpty(message = "视频素材不能为空")
|
||||
private List<String> videoUrls;
|
||||
|
||||
@Schema(description = "背景音乐URL列表", required = true)
|
||||
@NotEmpty(message = "背景音乐不能为空")
|
||||
private List<String> bgMusicUrls;
|
||||
|
||||
@Schema(description = "生成数量", required = true, example = "3")
|
||||
@Schema(description = "生成数量", required = true, example = "1")
|
||||
@NotNull(message = "生成数量不能为空")
|
||||
private Integer produceCount;
|
||||
private Integer produceCount = 1; // 默认生成1个
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user