feat(media): 上传与改片自动入队转码并支持手动重转

This commit is contained in:
Zhou Weidong committed 2026-10-22 14:03:04 +08:00
1 parent 7e6075a2e9
commit 4219186ab0
6 files changed
+169

No files matched your search

@@ -11,6 +11,7 @@ import com.fruitshorts.media.dal.entity.MediaUploadTask;
import com.fruitshorts.media.dal.mapper.MediaAssetMapper;
import com.fruitshorts.media.dal.mapper.MediaUploadTaskMapper;
import com.fruitshorts.media.vo.MediaAssetRespVO;
import com.fruitshorts.media.vo.TranscodeTriggerRespVO;
import com.fruitshorts.media.vo.UploadCompleteReqVO;
import com.fruitshorts.media.vo.UploadInitReqVO;
import com.fruitshorts.media.vo.UploadInitRespVO;
@@ -199,6 +200,30 @@ public class MediaService {
return toResp(asset);
}
/** 后台手动触发/重转:锁住媒资行,保证同一媒体的重复请求串行判定任务状态。 */
@Transactional(rollbackFor = Exception.class)
public TranscodeTriggerRespVO triggerTranscode(Long id) {
MediaAsset asset = id == null ? null : mediaAssetMapper.selectOne(
Wrappers.<MediaAsset>lambdaQuery()
.eq(MediaAsset::getId, id)
.last("for update"));
if (asset == null) {
throw new ServiceException(ErrorCode.DATA_NOT_FOUND, "媒资不存在");
}
if (isImage(asset.getFileName())) {
throw new ServiceException(ErrorCode.BAD_REQUEST, "图片媒资无需转码");
}
String sourceUrl = asset.getSourceUrl();
if (sourceUrl == null || sourceUrl.isBlank()) {
sourceUrl = asset.getMp4Url();
}
TranscodeTriggerRespVO result = transcodeService.trigger(asset.getId(), sourceUrl);
asset.setStatus(STATUS_TRANSCODING);
asset.setTranscodeStatus(result.getStatus());
mediaAssetMapper.updateById(asset);
return result;
}
/** 断点续传用:回显传到第几片、还剩多少;会话归属同样校验。 */
public UploadProgressRespVO progress(String uploadId, Long userId) {
MediaUploadTask task = getTask(uploadId);
@@ -11,6 +11,7 @@ import com.fruitshorts.media.dal.entity.TranscodeTask;
import com.fruitshorts.media.dal.mapper.MediaRenditionMapper;
import com.fruitshorts.media.dal.mapper.MediaThumbnailMapper;
import com.fruitshorts.media.dal.mapper.TranscodeTaskMapper;
import com.fruitshorts.media.vo.TranscodeTriggerRespVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@@ -71,6 +72,28 @@ public class TranscodeService {
return task.getId();
}
/**
* 后台手动触发/重转。进行中任务直接复用;失败任务原地重置;成功任务才新建一轮。
*/
@Transactional(rollbackFor = Exception.class)
public TranscodeTriggerRespVO trigger(Long mediaAssetId, String sourceUrl) {
TranscodeTask latest = latestTask(mediaAssetId);
if (latest != null && isActive(latest.getStatus())) {
return response(latest.getId(), latest.getStatus());
}
if (latest != null && isRetryable(latest.getStatus())) {
resetFailedTask(latest, sourceUrl);
return response(latest.getId(), STATUS_QUEUED);
}
Long dramaId = latest == null ? 0L : latest.getDramaId();
Long episodeId = latest == null ? 0L : latest.getEpisodeId();
String retrySource = latest == null || latest.getSourceUrl() == null
|| latest.getSourceUrl().isBlank() ? sourceUrl : latest.getSourceUrl();
Long taskId = enqueue(mediaAssetId, dramaId, episodeId, retrySource);
return response(taskId, STATUS_QUEUED);
}
/**
* 分集保存视频地址后绑定任务;成功任务复用并同步回填已有的零绑定产物。
*/
@@ -246,6 +269,39 @@ public class TranscodeService {
.eq(TranscodeTask::getStatus, status));
}
private TranscodeTask latestTask(Long mediaAssetId) {
return transcodeTaskMapper.selectOne(Wrappers.<TranscodeTask>lambdaQuery()
.eq(TranscodeTask::getMediaAssetId, mediaAssetId)
.orderByDesc(TranscodeTask::getId)
.last("limit 1"));
}
private boolean isActive(String status) {
return STATUS_QUEUED.equals(status) || STATUS_RUNNING.equals(status);
}
private boolean isRetryable(String status) {
return STATUS_FAILED.equals(status) || "CANCELED".equals(status);
}
private void resetFailedTask(TranscodeTask task, String sourceUrl) {
boolean hasSource = sourceUrl != null && !sourceUrl.isBlank();
transcodeTaskMapper.update(null, Wrappers.<TranscodeTask>lambdaUpdate()
.eq(TranscodeTask::getId, task.getId())
.in(TranscodeTask::getStatus, List.of(STATUS_FAILED, "CANCELED"))
.set(TranscodeTask::getStatus, STATUS_QUEUED)
.set(TranscodeTask::getProgress, 0)
.set(TranscodeTask::getRetryCount, 0)
.set(TranscodeTask::getErrorMsg, "")
.set(TranscodeTask::getStartedAt, null)
.set(TranscodeTask::getFinishedAt, null)
.set(hasSource, TranscodeTask::getSourceUrl, sourceUrl));
}
private TranscodeTriggerRespVO response(Long taskId, String status) {
return new TranscodeTriggerRespVO(taskId, status);
}
private void updateTaskBinding(TranscodeTask task, Long dramaId,
Long episodeId, String sourceUrl) {
if (task == null) {
@@ -0,0 +1,17 @@
// Author: fruit.com.cn
package com.fruitshorts.media.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/** 后台手动触发转码后的任务状态。 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class TranscodeTriggerRespVO {
private Long taskId;
private String status;
}