|
@@ -370,8 +370,15 @@ class AIVideoGenerationService
|
|
|
try {
|
|
try {
|
|
|
DB::beginTransaction();
|
|
DB::beginTransaction();
|
|
|
|
|
|
|
|
- // 在事务中更新任务状态
|
|
|
|
|
- $task->updateStatus(MpGenerateVideoTask::STATUS_SUCCESS, $statusInfo);
|
|
|
|
|
|
|
+ // 原子抢占:只有仍为 processing 的任务能更新为 success,避免并发重复处理
|
|
|
|
|
+ if (!$this->markVideoTaskSuccess($task, $statusInfo)) {
|
|
|
|
|
+ DB::rollBack();
|
|
|
|
|
+ dLog('generate')->info('视频任务已被其他进程处理,跳过重复更新', [
|
|
|
|
|
+ 'id' => $task->id,
|
|
|
|
|
+ 'result_url' => $statusInfo['result_url'] ?? null
|
|
|
|
|
+ ]);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
$now = date('Y-m-d H:i:s');
|
|
$now = date('Y-m-d H:i:s');
|
|
|
|
|
|
|
@@ -414,7 +421,7 @@ class AIVideoGenerationService
|
|
|
}
|
|
}
|
|
|
} else {
|
|
} else {
|
|
|
// 没有segment_id的情况下,直接更新任务状态
|
|
// 没有segment_id的情况下,直接更新任务状态
|
|
|
- $task->updateStatus(MpGenerateVideoTask::STATUS_SUCCESS, $statusInfo);
|
|
|
|
|
|
|
+ $this->markVideoTaskSuccess($task, $statusInfo);
|
|
|
}
|
|
}
|
|
|
} elseif ($statusInfo['status'] === 'failed') {
|
|
} elseif ($statusInfo['status'] === 'failed') {
|
|
|
logDB('generate', 'error', '即梦AI视频生成任务失败', ['id' => $task->id, 'error' => $statusInfo['error_message']]);
|
|
logDB('generate', 'error', '即梦AI视频生成任务失败', ['id' => $task->id, 'error' => $statusInfo['error_message']]);
|
|
@@ -1127,8 +1134,15 @@ class AIVideoGenerationService
|
|
|
try {
|
|
try {
|
|
|
DB::beginTransaction();
|
|
DB::beginTransaction();
|
|
|
|
|
|
|
|
- // 在事务中更新任务状态
|
|
|
|
|
- $task->updateStatus(MpGenerateVideoTask::STATUS_SUCCESS, $statusInfo);
|
|
|
|
|
|
|
+ // 原子抢占:只有仍为 processing 的任务能更新为 success,避免并发重复处理
|
|
|
|
|
+ if (!$this->markVideoTaskSuccess($task, $statusInfo)) {
|
|
|
|
|
+ DB::rollBack();
|
|
|
|
|
+ dLog('generate')->info('视频任务已被其他进程处理,跳过重复更新', [
|
|
|
|
|
+ 'id' => $task->id,
|
|
|
|
|
+ 'result_url' => $statusInfo['result_url'] ?? null
|
|
|
|
|
+ ]);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
$now = date('Y-m-d H:i:s');
|
|
$now = date('Y-m-d H:i:s');
|
|
|
|
|
|
|
@@ -1173,7 +1187,7 @@ class AIVideoGenerationService
|
|
|
}
|
|
}
|
|
|
} else {
|
|
} else {
|
|
|
// 没有segment_id的情况下,直接更新任务状态
|
|
// 没有segment_id的情况下,直接更新任务状态
|
|
|
- $task->updateStatus(MpGenerateVideoTask::STATUS_SUCCESS, $statusInfo);
|
|
|
|
|
|
|
+ $this->markVideoTaskSuccess($task, $statusInfo);
|
|
|
}
|
|
}
|
|
|
} elseif ($statusInfo['status'] === 'failed') {
|
|
} elseif ($statusInfo['status'] === 'failed') {
|
|
|
logDB('generate', 'error', 'Seedance视频生成任务失败', ['id' => $task->id, 'error' => $statusInfo['error_message']]);
|
|
logDB('generate', 'error', 'Seedance视频生成任务失败', ['id' => $task->id, 'error' => $statusInfo['error_message']]);
|
|
@@ -1556,14 +1570,31 @@ class AIVideoGenerationService
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 更新任务记录
|
|
|
|
|
- $task->update($updateData);
|
|
|
|
|
|
|
+ // 成功任务的状态更新放到下方事务中做原子抢占,其他状态直接更新
|
|
|
|
|
+ if (!($taskStatus === MpGenerateVideoTask::STATUS_SUCCESS && isset($updateData['result_url']))) {
|
|
|
|
|
+ $task->update($updateData);
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
// 如果任务成功,更新分镜表
|
|
// 如果任务成功,更新分镜表
|
|
|
if ($taskStatus === MpGenerateVideoTask::STATUS_SUCCESS && isset($updateData['result_url'])) {
|
|
if ($taskStatus === MpGenerateVideoTask::STATUS_SUCCESS && isset($updateData['result_url'])) {
|
|
|
try {
|
|
try {
|
|
|
DB::beginTransaction();
|
|
DB::beginTransaction();
|
|
|
|
|
|
|
|
|
|
+ // 原子抢占:只有仍为 processing 的任务能更新为 success,避免回调与轮询重复处理
|
|
|
|
|
+ if (!$this->markVideoTaskSuccess($task, $updateData)) {
|
|
|
|
|
+ DB::rollBack();
|
|
|
|
|
+ dLog('generate')->info('Seedance 任务已被其他进程处理,跳过重复更新', [
|
|
|
|
|
+ 'id' => $task->id,
|
|
|
|
|
+ 'result_url' => $updateData['result_url'] ?? null
|
|
|
|
|
+ ]);
|
|
|
|
|
+ return [
|
|
|
|
|
+ 'success' => true,
|
|
|
|
|
+ 'message' => '任务已被其他进程处理',
|
|
|
|
|
+ 'task_id' => $taskId,
|
|
|
|
|
+ 'status' => $taskStatus
|
|
|
|
|
+ ];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
$now = date('Y-m-d H:i:s');
|
|
$now = date('Y-m-d H:i:s');
|
|
|
|
|
|
|
|
// 获取分镜ID(兼容分镜模式和全能模式)
|
|
// 获取分镜ID(兼容分镜模式和全能模式)
|
|
@@ -1616,6 +1647,7 @@ class AIVideoGenerationService
|
|
|
'role' => 'user',
|
|
'role' => 'user',
|
|
|
'content' => '图片转视频',
|
|
'content' => '图片转视频',
|
|
|
'segment_id' => $segmentId,
|
|
'segment_id' => $segmentId,
|
|
|
|
|
+ 'video_task_id' => getProp($task, 'id'),
|
|
|
'video_url' => '',
|
|
'video_url' => '',
|
|
|
'created_at' => $now,
|
|
'created_at' => $now,
|
|
|
'updated_at' => $now
|
|
'updated_at' => $now
|
|
@@ -1627,6 +1659,7 @@ class AIVideoGenerationService
|
|
|
'role' => 'assistant',
|
|
'role' => 'assistant',
|
|
|
'content' => $tail_frame,
|
|
'content' => $tail_frame,
|
|
|
'segment_id' => $segmentId,
|
|
'segment_id' => $segmentId,
|
|
|
|
|
+ 'video_task_id' => getProp($task, 'id'),
|
|
|
'video_url' => $updateData['result_url'],
|
|
'video_url' => $updateData['result_url'],
|
|
|
'created_at' => $now,
|
|
'created_at' => $now,
|
|
|
'updated_at' => $now
|
|
'updated_at' => $now
|
|
@@ -2132,8 +2165,15 @@ class AIVideoGenerationService
|
|
|
try {
|
|
try {
|
|
|
DB::beginTransaction();
|
|
DB::beginTransaction();
|
|
|
|
|
|
|
|
- // 在事务中更新任务状态
|
|
|
|
|
- $task->updateStatus(MpGenerateVideoTask::STATUS_SUCCESS, $statusInfo);
|
|
|
|
|
|
|
+ // 原子抢占:只有仍为 processing 的任务能更新为 success,避免并发重复处理
|
|
|
|
|
+ if (!$this->markVideoTaskSuccess($task, $statusInfo)) {
|
|
|
|
|
+ DB::rollBack();
|
|
|
|
|
+ dLog('generate')->info('视频任务已被其他进程处理,跳过重复更新', [
|
|
|
|
|
+ 'id' => $task->id,
|
|
|
|
|
+ 'result_url' => $statusInfo['result_url'] ?? null
|
|
|
|
|
+ ]);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
$now = date('Y-m-d H:i:s');
|
|
$now = date('Y-m-d H:i:s');
|
|
|
|
|
|
|
@@ -2178,7 +2218,7 @@ class AIVideoGenerationService
|
|
|
}
|
|
}
|
|
|
} else {
|
|
} else {
|
|
|
// 没有segment_id的情况下,直接更新任务状态
|
|
// 没有segment_id的情况下,直接更新任务状态
|
|
|
- $task->updateStatus(MpGenerateVideoTask::STATUS_SUCCESS, $statusInfo);
|
|
|
|
|
|
|
+ $this->markVideoTaskSuccess($task, $statusInfo);
|
|
|
}
|
|
}
|
|
|
} elseif ($statusInfo['status'] === 'failed') {
|
|
} elseif ($statusInfo['status'] === 'failed') {
|
|
|
$task->updateStatus(MpGenerateVideoTask::STATUS_FAILED, [
|
|
$task->updateStatus(MpGenerateVideoTask::STATUS_FAILED, [
|
|
@@ -2813,7 +2853,25 @@ class AIVideoGenerationService
|
|
|
$assistantContent = $segment->act_show_content ? $segment->act_show_content : $segment->act_content;
|
|
$assistantContent = $segment->act_show_content ? $segment->act_show_content : $segment->act_content;
|
|
|
if (!$assistantContent) $assistantContent = '';
|
|
if (!$assistantContent) $assistantContent = '';
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // 同一视频任务只写一次对话记录(仅在有任务ID时按任务幂等,允许多个任务对应同一 act)
|
|
|
|
|
+ $videoTaskId = getProp($task, 'id');
|
|
|
|
|
+ if (!empty($videoTaskId)) {
|
|
|
|
|
+ $exists = DB::table('mp_anime_records')
|
|
|
|
|
+ ->where('video_task_id', $videoTaskId)
|
|
|
|
|
+ ->where('role', 'assistant')
|
|
|
|
|
+ ->exists();
|
|
|
|
|
+
|
|
|
|
|
+ if ($exists) {
|
|
|
|
|
+ dLog('generate')->info('该视频任务的对话记录已存在,跳过重复写入', [
|
|
|
|
|
+ 'video_task_id' => $videoTaskId,
|
|
|
|
|
+ 'segment_id_or_act_id' => $segmentIdOrActId,
|
|
|
|
|
+ 'video_url' => $videoUrl
|
|
|
|
|
+ ]);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
// 保存对话记录
|
|
// 保存对话记录
|
|
|
$records = [
|
|
$records = [
|
|
|
[
|
|
[
|
|
@@ -2822,6 +2880,7 @@ class AIVideoGenerationService
|
|
|
'sequence' => $segment->episode_number,
|
|
'sequence' => $segment->episode_number,
|
|
|
'role' => 'user',
|
|
'role' => 'user',
|
|
|
'content' => $userContent,
|
|
'content' => $userContent,
|
|
|
|
|
+ 'video_task_id' => $videoTaskId,
|
|
|
$recordField => $recordValue,
|
|
$recordField => $recordValue,
|
|
|
'video_url' => '',
|
|
'video_url' => '',
|
|
|
'reference_images' => json_encode([], 256),
|
|
'reference_images' => json_encode([], 256),
|
|
@@ -2834,6 +2893,7 @@ class AIVideoGenerationService
|
|
|
'sequence' => $segment->episode_number,
|
|
'sequence' => $segment->episode_number,
|
|
|
'role' => 'assistant',
|
|
'role' => 'assistant',
|
|
|
'content' => $assistantContent,
|
|
'content' => $assistantContent,
|
|
|
|
|
+ 'video_task_id' => $videoTaskId,
|
|
|
$recordField => $recordValue,
|
|
$recordField => $recordValue,
|
|
|
'video_url' => $videoUrl,
|
|
'video_url' => $videoUrl,
|
|
|
'reference_images' => $reference_images,
|
|
'reference_images' => $reference_images,
|
|
@@ -3265,8 +3325,15 @@ class AIVideoGenerationService
|
|
|
try {
|
|
try {
|
|
|
DB::beginTransaction();
|
|
DB::beginTransaction();
|
|
|
|
|
|
|
|
- // 在事务中更新任务状态
|
|
|
|
|
- $task->updateStatus(MpGenerateVideoTask::STATUS_SUCCESS, $statusInfo);
|
|
|
|
|
|
|
+ // 原子抢占:只有仍为 processing 的任务才能更新成功,避免多个进程重复处理同一任务
|
|
|
|
|
+ if (!$this->markVideoTaskSuccess($task, $statusInfo)) {
|
|
|
|
|
+ DB::rollBack();
|
|
|
|
|
+ dLog('generate')->info('视频任务已被其他进程处理,跳过重复更新', [
|
|
|
|
|
+ 'id' => $task->id,
|
|
|
|
|
+ 'result_url' => $statusInfo['result_url'] ?? null
|
|
|
|
|
+ ]);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
$now = date('Y-m-d H:i:s');
|
|
$now = date('Y-m-d H:i:s');
|
|
|
|
|
|
|
@@ -3338,7 +3405,7 @@ class AIVideoGenerationService
|
|
|
}
|
|
}
|
|
|
} else {
|
|
} else {
|
|
|
// 没有segment_id的情况下,直接更新任务状态
|
|
// 没有segment_id的情况下,直接更新任务状态
|
|
|
- $task->updateStatus(MpGenerateVideoTask::STATUS_SUCCESS, $statusInfo);
|
|
|
|
|
|
|
+ $this->markVideoTaskSuccess($task, $statusInfo);
|
|
|
}
|
|
}
|
|
|
} elseif ($statusInfo['status'] === 'failed') {
|
|
} elseif ($statusInfo['status'] === 'failed') {
|
|
|
logDB('generate', 'error', '统一API视频生成任务失败', ['id' => $task->id, 'error' => $statusInfo['error_message']]);
|
|
logDB('generate', 'error', '统一API视频生成任务失败', ['id' => $task->id, 'error' => $statusInfo['error_message']]);
|
|
@@ -3378,6 +3445,61 @@ class AIVideoGenerationService
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
|
|
+ * 将任务从 processing 原子更新为 success,返回是否更新成功
|
|
|
|
|
+ *
|
|
|
|
|
+ * 防止多个定时任务进程同时查询到同一 processing 任务后重复处理,
|
|
|
|
|
+ * 导致对话记录重复写入、任务表 result_url 被后写入的结果覆盖。
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param MpGenerateVideoTask $task
|
|
|
|
|
+ * @param array $statusInfo
|
|
|
|
|
+ * @return bool
|
|
|
|
|
+ */
|
|
|
|
|
+ private function markVideoTaskSuccess(MpGenerateVideoTask $task, array $statusInfo): bool
|
|
|
|
|
+ {
|
|
|
|
|
+ $updateData = [
|
|
|
|
|
+ 'status' => MpGenerateVideoTask::STATUS_SUCCESS,
|
|
|
|
|
+ 'updated_at' => now(),
|
|
|
|
|
+ ];
|
|
|
|
|
+
|
|
|
|
|
+ if (isset($statusInfo['result_json'])) {
|
|
|
|
|
+ $updateData['result_json'] = is_array($statusInfo['result_json'])
|
|
|
|
|
+ ? json_encode($statusInfo['result_json'], JSON_UNESCAPED_UNICODE)
|
|
|
|
|
+ : $statusInfo['result_json'];
|
|
|
|
|
+ }
|
|
|
|
|
+ if (isset($statusInfo['result_url'])) {
|
|
|
|
|
+ $updateData['result_url'] = $statusInfo['result_url'];
|
|
|
|
|
+ }
|
|
|
|
|
+ if (isset($statusInfo['compressed_url'])) {
|
|
|
|
|
+ $updateData['compressed_url'] = $statusInfo['compressed_url'];
|
|
|
|
|
+ }
|
|
|
|
|
+ if (isset($statusInfo['last_frame_url'])) {
|
|
|
|
|
+ $updateData['last_frame_url'] = $statusInfo['last_frame_url'];
|
|
|
|
|
+ }
|
|
|
|
|
+ if (isset($statusInfo['error_message'])) {
|
|
|
|
|
+ $updateData['error_message'] = $statusInfo['error_message'];
|
|
|
|
|
+ }
|
|
|
|
|
+ if (isset($statusInfo['extra_params'])) {
|
|
|
|
|
+ $updateData['extra_params'] = is_array($statusInfo['extra_params'])
|
|
|
|
|
+ ? json_encode($statusInfo['extra_params'], JSON_UNESCAPED_UNICODE)
|
|
|
|
|
+ : $statusInfo['extra_params'];
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!$task->completed_at) {
|
|
|
|
|
+ $updateData['completed_at'] = now();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $updated = DB::table('mp_generate_video_tasks')
|
|
|
|
|
+ ->where('id', $task->id)
|
|
|
|
|
+ ->where('status', MpGenerateVideoTask::STATUS_PROCESSING)
|
|
|
|
|
+ ->update($updateData);
|
|
|
|
|
+
|
|
|
|
|
+ if ($updated) {
|
|
|
|
|
+ $task->status = MpGenerateVideoTask::STATUS_SUCCESS;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return (bool)$updated;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
* 确保视频为标准分辨率(720p -> 1280*720 / 720*1280,1080p -> 1920*1080 / 1080*1920)
|
|
* 确保视频为标准分辨率(720p -> 1280*720 / 720*1280,1080p -> 1920*1080 / 1080*1920)
|
|
|
*
|
|
*
|
|
|
* 下载视频并探测实际分辨率,若属于目标档位但不是标准尺寸,则强制转码为标准分辨率
|
|
* 下载视频并探测实际分辨率,若属于目标档位但不是标准尺寸,则强制转码为标准分辨率
|
|
@@ -3915,7 +4037,7 @@ class AIVideoGenerationService
|
|
|
|
|
|
|
|
if (!$originalTaskId) {
|
|
if (!$originalTaskId) {
|
|
|
// 如果没有原始任务ID,只更新超分任务本身
|
|
// 如果没有原始任务ID,只更新超分任务本身
|
|
|
- $task->updateStatus(MpGenerateVideoTask::STATUS_SUCCESS, $statusInfo);
|
|
|
|
|
|
|
+ $this->markVideoTaskSuccess($task, $statusInfo);
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -3925,7 +4047,7 @@ class AIVideoGenerationService
|
|
|
'enhance_task_id' => $task->id,
|
|
'enhance_task_id' => $task->id,
|
|
|
'original_task_id' => $originalTaskId
|
|
'original_task_id' => $originalTaskId
|
|
|
]);
|
|
]);
|
|
|
- $task->updateStatus(MpGenerateVideoTask::STATUS_SUCCESS, $statusInfo);
|
|
|
|
|
|
|
+ $this->markVideoTaskSuccess($task, $statusInfo);
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -3935,11 +4057,17 @@ class AIVideoGenerationService
|
|
|
try {
|
|
try {
|
|
|
DB::beginTransaction();
|
|
DB::beginTransaction();
|
|
|
|
|
|
|
|
- // 更新超分任务状态
|
|
|
|
|
- $task->updateStatus(MpGenerateVideoTask::STATUS_SUCCESS, $statusInfo);
|
|
|
|
|
|
|
+ // 原子抢占:只有仍为 processing 的超分任务能更新为 success,避免并发重复处理
|
|
|
|
|
+ if (!$this->markVideoTaskSuccess($task, $statusInfo)) {
|
|
|
|
|
+ DB::rollBack();
|
|
|
|
|
+ dLog('video_enhance')->info('视频超分任务已被其他进程处理,跳过重复更新', [
|
|
|
|
|
+ 'enhance_task_id' => $task->id
|
|
|
|
|
+ ]);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- // 更新原始480p任务状态
|
|
|
|
|
- $originalTask->updateStatus(MpGenerateVideoTask::STATUS_SUCCESS, $statusInfo);
|
|
|
|
|
|
|
+ // 原始480p任务可能已被主流程处理为 success,这里仅尽力同步状态
|
|
|
|
|
+ $this->markVideoTaskSuccess($originalTask, $statusInfo);
|
|
|
|
|
|
|
|
$now = date('Y-m-d H:i:s');
|
|
$now = date('Y-m-d H:i:s');
|
|
|
|
|
|
|
@@ -4015,8 +4143,14 @@ class AIVideoGenerationService
|
|
|
try {
|
|
try {
|
|
|
DB::beginTransaction();
|
|
DB::beginTransaction();
|
|
|
|
|
|
|
|
- $task->updateStatus(MpGenerateVideoTask::STATUS_SUCCESS, $statusInfo);
|
|
|
|
|
- $originalTask->updateStatus(MpGenerateVideoTask::STATUS_SUCCESS, $statusInfo);
|
|
|
|
|
|
|
+ if (!$this->markVideoTaskSuccess($task, $statusInfo)) {
|
|
|
|
|
+ DB::rollBack();
|
|
|
|
|
+ dLog('video_enhance')->info('视频超分任务已被其他进程处理,跳过重复更新', [
|
|
|
|
|
+ 'enhance_task_id' => $task->id
|
|
|
|
|
+ ]);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ $this->markVideoTaskSuccess($originalTask, $statusInfo);
|
|
|
|
|
|
|
|
DB::commit();
|
|
DB::commit();
|
|
|
} catch (\Exception $e) {
|
|
} catch (\Exception $e) {
|