feat(material): remove video cover extraction and simplify upload API

- Remove extractVideoCoverOptional function and related video cover processing
- Update MaterialService.uploadFile method signature to remove coverBase64 parameter
- Simplify uploadAndIdentifyVideo function by removing cover generation logic
- Remove loading indicator from VideoSelector component during video preview
- Add presignGetUrlWithProcess method to FileClient interface for processed file URLs
- Add logging support to S3FileClient implementation
This commit is contained in:
2026-03-04 22:37:31 +08:00
parent 07579e27e9
commit 27d1c53b49
23 changed files with 576 additions and 361 deletions

View File

@@ -43,13 +43,11 @@ public class AppTikUserFileController {
@RequestParam("file") MultipartFile file,
@Parameter(description = "文件分类video/generate/audio/mix/voice", required = true)
@RequestParam("fileCategory") String fileCategory,
@Parameter(description = "视频封面 base64可选data URI 格式)")
@RequestParam(value = "coverBase64", required = false) String coverBase64,
@Parameter(description = "视频时长(秒)")
@RequestParam(value = "duration", required = false) Integer duration,
@Parameter(description = "分组编号(可选)")
@RequestParam(value = "groupId", required = false) Long groupId) {
return success(userFileService.uploadFile(file, fileCategory, coverBase64, duration, groupId));
return success(userFileService.uploadFile(file, fileCategory, duration, groupId));
}
@GetMapping("/page")

View File

@@ -59,14 +59,6 @@ public class TikUserFileDO extends TenantBaseDO {
* 文件存储路径
*/
private String filePath;
/**
* 封面图URL视频文件的封面图
*/
private String coverUrl;
/**
* 封面图Base64视频文件的封面图原始base64数据可选
*/
private String coverBase64;
/**
* 缩略图URL图片文件的缩略图
*/

View File

@@ -19,12 +19,11 @@ public interface TikUserFileService {
*
* @param file 文件
* @param fileCategory 文件分类video/generate/audio/mix/voice
* @param coverBase64 视频封面 base64可选data URI 格式)
* @param duration 视频时长(秒,可选)
* @param groupId 分组编号(可选)
* @return 文件编号
*/
Long uploadFile(MultipartFile file, String fileCategory, String coverBase64, Integer duration, Long groupId);
Long uploadFile(MultipartFile file, String fileCategory, Integer duration, Long groupId);
/**
* 分页查询文件列表
@@ -75,6 +74,17 @@ public interface TikUserFileService {
*/
String getCachedPresignUrl(String url, Integer expirationSeconds);
/**
* 获取缓存的预签名URL带 OSS 处理参数,带 Redis 缓存)
* 用于阿里云 OSS 视频截帧等图片处理场景
*
* @param url 文件URL
* @param expirationSeconds 过期时间(秒)
* @param processParam OSS 处理参数,如 "video/snapshot,t_1000,f_jpg,w_300"
* @return 预签名URL
*/
String getCachedPresignUrlWithProcess(String url, Integer expirationSeconds, String processParam);
/**
* 获取预签名URL直传模式
*

View File

@@ -74,7 +74,7 @@ public class TikUserFileServiceImpl implements TikUserFileService {
private FileConfigService fileConfigService;
@Override
public Long uploadFile(MultipartFile file, String fileCategory, String coverBase64, Integer duration, Long groupId) {
public Long uploadFile(MultipartFile file, String fileCategory, Integer duration, Long groupId) {
Long userId = SecurityFrameworkUtils.getLoginUserId();
// 校验文件分类和配额
@@ -88,7 +88,7 @@ public class TikUserFileServiceImpl implements TikUserFileService {
// 保存数据库
return saveFileRecord(userId, file, fileCategory, context.fileUrl, context.filePath,
coverBase64, baseDirectory, context.infraFileId, duration, groupId);
context.infraFileId, duration, groupId);
}
/**
@@ -179,44 +179,19 @@ public class TikUserFileServiceImpl implements TikUserFileService {
}
}
/**
* 处理视频封面上传
*/
private String handleCoverUpload(String coverBase64, String fileName, String fileType, String baseDirectory) {
if (StrUtil.isBlank(coverBase64) || !StrUtil.containsIgnoreCase(fileType, "video")) {
return null;
}
try {
String base64Data = coverBase64.contains(",")
? coverBase64.substring(coverBase64.indexOf(",") + 1)
: coverBase64;
byte[] coverBytes = java.util.Base64.getDecoder().decode(base64Data);
String coverFileName = fileName.replaceFirst("\\.[^.]+$", "_cover.jpg");
String uploadedUrl = fileApi.createFile(coverBytes, coverFileName, baseDirectory, "image/jpeg");
if (StrUtil.isNotBlank(uploadedUrl) && !uploadedUrl.contains("data:image")) {
return HttpUtils.removeUrlQuery(uploadedUrl);
}
} catch (Exception e) {
log.warn("[handleCoverUpload][视频封面处理失败: {}]", e.getMessage());
}
return null;
}
/**
* 保存文件记录到数据库
*/
@Transactional(rollbackFor = Exception.class)
public Long saveFileRecord(Long userId, MultipartFile file, String fileCategory,
String fileUrl, String filePath, String coverBase64,
String baseDirectory, Long infraFileId, Integer duration, Long groupId) {
String fileUrl, String filePath,
Long infraFileId, Integer duration, Long groupId) {
if (infraFileId == null) {
log.error("[saveFileRecord][infra_file.id 为空,用户({})URL({})]", userId, fileUrl);
throw exception(FILE_NOT_EXISTS, "文件记录保存失败无法获取文件ID");
}
String fileName = file.getOriginalFilename();
String fileType = file.getContentType();
String coverUrl = handleCoverUpload(coverBase64, fileName, fileType, baseDirectory);
String displayName = FileUtil.mainName(fileName);
TikUserFileDO userFile = new TikUserFileDO()
@@ -224,13 +199,11 @@ public class TikUserFileServiceImpl implements TikUserFileService {
.setFileId(infraFileId)
.setFileName(fileName)
.setDisplayName(displayName)
.setFileType(fileType)
.setFileType(file.getContentType())
.setFileCategory(fileCategory)
.setFileSize(file.getSize())
.setFileUrl(fileUrl)
.setFilePath(filePath)
.setCoverUrl(coverUrl)
.setCoverBase64(StrUtil.isNotBlank(coverBase64) ? coverBase64 : null)
.setDuration(duration)
.setGroupId(groupId);
@@ -278,11 +251,19 @@ public class TikUserFileServiceImpl implements TikUserFileService {
vo.setIsVideo(isVideo);
vo.setIsImage(isImage);
// 视频文件不生成OSS预签名URL前端使用coverBase64缓存
// 视频文件:使用 OSS 截帧
if (isVideo) {
vo.setCoverUrl(null);
vo.setThumbnailUrl(null);
vo.setPreviewUrl(null);
// 使用 OSS 视频截帧作为封面(预签名时包含截帧参数,避免 403
// t_1000: 截取1秒处避免开头黑屏/非关键帧)
// f_jpg: 输出JPG格式
// w_300: 宽度300px
if (StrUtil.isNotBlank(file.getFileUrl())) {
String snapshotUrl = getCachedPresignUrlWithProcess(
file.getFileUrl(),
PRESIGN_URL_EXPIRATION_SECONDS,
"video/snapshot,t_1000,f_jpg,w_300");
vo.setPreviewUrl(snapshotUrl);
}
return vo;
}
@@ -290,7 +271,6 @@ public class TikUserFileServiceImpl implements TikUserFileService {
String thumbnailUrl = StrUtil.isNotBlank(file.getThumbnailUrl())
? getCachedPresignUrl(file.getThumbnailUrl(), PRESIGN_URL_EXPIRATION_SECONDS)
: null;
vo.setCoverUrl(null);
vo.setThumbnailUrl(thumbnailUrl);
// 图片预览URL优先缩略图否则原图
@@ -381,15 +361,12 @@ public class TikUserFileServiceImpl implements TikUserFileService {
* 确定预览URL
*/
private String determinePreviewUrl(TikUserFileDO file, String type) {
// 视频文件:不返回任何OSS URL前端使用coverBase64缓存
// 视频文件:使用 OSS 截帧,通过 getFilePage 返回 previewUrl
if (StrUtil.containsIgnoreCase(file.getFileType(), "video")) {
return null;
}
// 明确指定类型
if ("cover".equals(type) && StrUtil.isNotBlank(file.getCoverUrl())) {
return file.getCoverUrl();
}
if ("thumbnail".equals(type) && StrUtil.isNotBlank(file.getThumbnailUrl())) {
return file.getThumbnailUrl();
}
@@ -402,7 +379,7 @@ public class TikUserFileServiceImpl implements TikUserFileService {
}
@Override
@Cacheable(value = "tik:file:presign",
@Cacheable(value = "tik:file:presign#23h",
key = "#url + ':' + (#expirationSeconds != null ? #expirationSeconds : 86400)")
public String getCachedPresignUrl(String url, Integer expirationSeconds) {
if (StrUtil.isBlank(url)) {
@@ -411,6 +388,18 @@ public class TikUserFileServiceImpl implements TikUserFileService {
return fileApi.presignGetUrl(url, expirationSeconds != null ? expirationSeconds : 86400);
}
@Override
@Cacheable(value = "tik:file:presign:process#23h",
key = "#url + ':' + (#expirationSeconds != null ? #expirationSeconds : 86400) + ':' + #processParam")
public String getCachedPresignUrlWithProcess(String url, Integer expirationSeconds, String processParam) {
if (StrUtil.isBlank(url)) {
return null;
}
return fileApi.presignGetUrlWithProcess(url,
expirationSeconds != null ? expirationSeconds : 86400,
processParam);
}
@Override
public Object getPresignedUrl(String fileName, String fileCategory, Long groupId, Long fileSize, String contentType) {
Long userId = SecurityFrameworkUtils.getLoginUserId();
@@ -490,7 +479,6 @@ public class TikUserFileServiceImpl implements TikUserFileService {
Long fileSize = params.get("fileSize") != null ? ((Number) params.get("fileSize")).longValue() : 0L;
String fileType = (String) params.get("fileType");
Long groupId = params.get("groupId") != null ? ((Number) params.get("groupId")).longValue() : null;
String coverBase64 = (String) params.get("coverBase64");
Integer duration = params.get("duration") != null ? ((Number) params.get("duration")).intValue() : null;
validateUploadRequest(fileCategory, fileSize);
@@ -509,8 +497,6 @@ public class TikUserFileServiceImpl implements TikUserFileService {
fileMapper.insert(infraFile);
Long infraFileId = infraFile.getId();
String baseDirectory = ossInitService.getOssDirectoryByCategory(userId, fileCategory);
String coverUrl = handleCoverUpload(coverBase64, fileName, fileType, baseDirectory);
String displayName = FileUtil.mainName(fileName);
TikUserFileDO userFile = new TikUserFileDO()
@@ -523,8 +509,6 @@ public class TikUserFileServiceImpl implements TikUserFileService {
.setFileSize(fileSize)
.setFileUrl(fileUrl)
.setFilePath(fileKey)
.setCoverUrl(coverUrl)
.setCoverBase64(coverBase64)
.setDuration(duration)
.setGroupId(groupId);
userFileMapper.insert(userFile);

View File

@@ -47,12 +47,6 @@ public class AppTikUserFileRespVO {
@Schema(description = "是否为图片文件", example = "false")
private Boolean isImage;
@Schema(description = "封面图URL视频文件的封面图")
private String coverUrl;
@Schema(description = "封面图Base64视频文件的封面图原始base64数据可选")
private String coverBase64;
@Schema(description = "缩略图URL图片文件的缩略图")
private String thumbnailUrl;