fix(media): 修正转码媒资 id 解析与存量分集补登记
This commit is contained in:
1 parent
a35b02dd18
commit
5ef5dd8eaf
11 files changed
+427
-31
No files matched your search
+25
@@ -0,0 +1,25 @@
|
||||
// Author: fruit.com.cn
|
||||
|
||||
package com.fruitshorts.media.service;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 媒资域读取分集视频来源的跨域端口,由 content 域实现。
|
||||
*/
|
||||
public interface EpisodeVideoSourcePort {
|
||||
|
||||
/** 按候选 id 查找分集;候选值可能是分集 id 或 video_asset_id。 */
|
||||
Optional<EpisodeVideoSource> resolve(Long candidateId);
|
||||
|
||||
/** 回填分集的 video_asset_id。 */
|
||||
void link(Long episodeId, Long mediaAssetId);
|
||||
|
||||
record EpisodeVideoSource(
|
||||
Long episodeId,
|
||||
Long dramaId,
|
||||
Long videoAssetId,
|
||||
String videoUrl,
|
||||
Integer durationSec) {
|
||||
}
|
||||
}
|
||||
+89
-4
@@ -21,6 +21,9 @@ import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -34,6 +37,8 @@ public class MediaService {
|
||||
private static final String STATUS_FAILED = "failed";
|
||||
private static final String TASK_UPLOADING = "uploading";
|
||||
private static final String TASK_COMPLETED = "completed";
|
||||
private static final String BIZ_TYPE_EPISODE = "EPISODE";
|
||||
private static final String STORAGE_TYPE_LOCAL = "LOCAL";
|
||||
private static final Set<String> IMAGE_EXTENSIONS =
|
||||
Set.of(".jpg", ".jpeg", ".png", ".webp", ".gif", ".bmp", ".avif");
|
||||
|
||||
@@ -42,6 +47,7 @@ public class MediaService {
|
||||
private final MediaAssetMapper mediaAssetMapper;
|
||||
private final MediaUploadTaskMapper mediaUploadTaskMapper;
|
||||
private final TranscodeService transcodeService;
|
||||
private final EpisodeVideoSourcePort episodeVideoSourcePort;
|
||||
|
||||
public UploadInitRespVO init(UploadInitReqVO req, Long userId) {
|
||||
if (req.getFileSize() > properties.getMaxFileSize()) {
|
||||
@@ -203,13 +209,92 @@ public class MediaService {
|
||||
/** 后台手动触发/重转:锁住媒资行,保证同一媒体的重复请求串行判定任务状态。 */
|
||||
@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"));
|
||||
MediaAsset asset = lockAsset(id);
|
||||
if (asset == null) {
|
||||
asset = resolveLegacyAsset(id);
|
||||
}
|
||||
if (asset == null) {
|
||||
throw new ServiceException(ErrorCode.DATA_NOT_FOUND, "媒资不存在");
|
||||
}
|
||||
return triggerAsset(asset);
|
||||
}
|
||||
|
||||
private MediaAsset lockAsset(Long id) {
|
||||
return id == null ? null : mediaAssetMapper.selectOne(
|
||||
Wrappers.<MediaAsset>lambdaQuery()
|
||||
.eq(MediaAsset::getId, id)
|
||||
.last("for update"));
|
||||
}
|
||||
|
||||
private MediaAsset resolveLegacyAsset(Long candidateId) {
|
||||
return episodeVideoSourcePort.resolve(candidateId)
|
||||
.map(this::resolveOrRegisterAsset)
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
private MediaAsset resolveOrRegisterAsset(EpisodeVideoSourcePort.EpisodeVideoSource source) {
|
||||
String videoUrl = source.videoUrl();
|
||||
if (videoUrl == null || videoUrl.isBlank()) {
|
||||
return null;
|
||||
}
|
||||
String objectKey = storageService.objectKeyOf(videoUrl);
|
||||
if (objectKey == null || objectKey.isBlank()) {
|
||||
return null;
|
||||
}
|
||||
MediaAsset asset = mediaAssetMapper.selectOne(Wrappers.<MediaAsset>lambdaQuery()
|
||||
.and(wrapper -> wrapper.eq(MediaAsset::getSourceUrl, videoUrl)
|
||||
.or().eq(MediaAsset::getObjectKey, objectKey))
|
||||
.last("limit 1"));
|
||||
if (asset == null) {
|
||||
asset = registerLegacyAsset(source, objectKey);
|
||||
}
|
||||
if (asset != null && source.episodeId() != null) {
|
||||
episodeVideoSourcePort.link(source.episodeId(), asset.getId());
|
||||
}
|
||||
return asset;
|
||||
}
|
||||
|
||||
private MediaAsset registerLegacyAsset(
|
||||
EpisodeVideoSourcePort.EpisodeVideoSource source, String objectKey) {
|
||||
Path file;
|
||||
try {
|
||||
file = storageService.fileFor(objectKey);
|
||||
} catch (ServiceException ex) {
|
||||
return null;
|
||||
}
|
||||
if (!Files.isRegularFile(file)) {
|
||||
return null;
|
||||
}
|
||||
MediaAsset asset = new MediaAsset();
|
||||
asset.setBizType(BIZ_TYPE_EPISODE);
|
||||
asset.setFileName(file.getFileName().toString());
|
||||
asset.setObjectKey(objectKey);
|
||||
asset.setSourceUrl(source.videoUrl());
|
||||
asset.setSizeBytes(sizeOf(file));
|
||||
asset.setMd5("");
|
||||
asset.setDurationSec(source.durationSec() == null ? 0 : source.durationSec());
|
||||
asset.setWidth(0);
|
||||
asset.setHeight(0);
|
||||
asset.setStatus(STATUS_UPLOADED);
|
||||
asset.setHlsUrl("");
|
||||
asset.setMp4Url(source.videoUrl());
|
||||
asset.setPosterUrl("");
|
||||
asset.setTranscodeStatus(TranscodeService.STATUS_QUEUED);
|
||||
asset.setStorageType(STORAGE_TYPE_LOCAL);
|
||||
asset.setCreateBy(0L);
|
||||
mediaAssetMapper.insert(asset);
|
||||
return asset;
|
||||
}
|
||||
|
||||
private long sizeOf(Path file) {
|
||||
try {
|
||||
return Files.size(file);
|
||||
} catch (IOException ex) {
|
||||
return 0L;
|
||||
}
|
||||
}
|
||||
|
||||
private TranscodeTriggerRespVO triggerAsset(MediaAsset asset) {
|
||||
if (isImage(asset.getFileName())) {
|
||||
throw new ServiceException(ErrorCode.BAD_REQUEST, "图片媒资无需转码");
|
||||
}
|
||||
|
||||
+6
-1
@@ -106,6 +106,10 @@ public class MediaTranscodeOutputService {
|
||||
transcodeService.findLatestTaskByMediaAssetId(mediaAssetId));
|
||||
}
|
||||
|
||||
public boolean mediaAssetExists(Long mediaAssetId) {
|
||||
return mediaAssetId != null && mediaAssetMapper.selectById(mediaAssetId) != null;
|
||||
}
|
||||
|
||||
public Long resolveMediaAssetId(String videoUrl) {
|
||||
if (!StringUtils.hasText(videoUrl)) {
|
||||
return null;
|
||||
@@ -115,7 +119,8 @@ public class MediaTranscodeOutputService {
|
||||
return null;
|
||||
}
|
||||
MediaAsset asset = mediaAssetMapper.selectOne(Wrappers.<MediaAsset>lambdaQuery()
|
||||
.eq(MediaAsset::getObjectKey, objectKey)
|
||||
.and(wrapper -> wrapper.eq(MediaAsset::getSourceUrl, videoUrl)
|
||||
.or().eq(MediaAsset::getObjectKey, objectKey))
|
||||
.last("limit 1"));
|
||||
return asset == null ? null : asset.getId();
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user