feat(kling): 优化时间轴对比组件样式和交互
Some checks failed
Build and Deploy / deploy (push) Has been cancelled

- 重构 TimelinePanel.vue 组件,使用 Tailwind CSS 替代 Less,简化样式代码
- 改进视觉设计:更新颜色方案、间距和图标,提升用户体验
- 移除音频结束位置标记,优化刻度尺和轨道显示逻辑
- 统一时长差异提示的样式和状态显示

feat(infra): 扩展文件预签名接口支持 Content-Type 参数

- 在 FileApi、FileClient、FileService 接口中新增带 Content-Type 参数的 presignGetUrl 方法
- 实现 S3FileClient 对 Content-Type 参数的支持,确保浏览器正确渲染媒体文件
- 在 TikUserFileServiceImpl 中为音视频文件生成预签名 URL 时自动推断 Content-Type
- 支持公开访问和私有访问两种模式下的 Content-Type 参数传递
This commit is contained in:
2026-04-09 01:04:07 +08:00
parent c607316f53
commit 63d3e7eecb
8 changed files with 118 additions and 290 deletions

View File

@@ -52,6 +52,18 @@ public interface FileApi {
String presignGetUrl(@NotEmpty(message = "URL 不能为空") String url,
Integer expirationSeconds);
/**
* 生成文件预签名地址(带 Content-Type用于读取
*
* @param url 完整的文件访问地址
* @param expirationSeconds 访问有效期,单位秒
* @param contentType 响应的 Content-Type为 null 时不设置
* @return 文件预签名地址
*/
String presignGetUrl(@NotEmpty(message = "URL 不能为空") String url,
Integer expirationSeconds,
String contentType);
/**
* 生成文件预签名地址(带 OSS 处理参数),用于读取
* 用于阿里云 OSS 视频截帧等图片处理场景

View File

@@ -31,7 +31,12 @@ public class FileApiImpl implements FileApi {
@Override
public String presignGetUrl(String url, Integer expirationSeconds) {
return fileService.presignGetUrl(url, expirationSeconds);
return presignGetUrl(url, expirationSeconds, null);
}
@Override
public String presignGetUrl(String url, Integer expirationSeconds, String contentType) {
return fileService.presignGetUrl(url, expirationSeconds, contentType);
}
@Override

View File

@@ -71,6 +71,18 @@ public interface FileClient {
* @return 文件预签名地址
*/
default String presignGetUrl(String url, Integer expirationSeconds) {
return presignGetUrl(url, expirationSeconds, null);
}
/**
* 生成文件预签名地址(带 Content-Type用于读取
*
* @param url 完整的文件访问地址
* @param expirationSeconds 访问有效期,单位秒
* @param contentType 响应的 Content-Type为 null 时不设置
* @return 文件预签名地址
*/
default String presignGetUrl(String url, Integer expirationSeconds, String contentType) {
throw new UnsupportedOperationException("不支持的操作");
}

View File

@@ -200,12 +200,19 @@ public class S3FileClient extends AbstractFileClient<S3FileClientConfig> {
}
@Override
public String presignGetUrl(String url, Integer expirationSeconds) {
return presignGetUrlWithProcess(url, expirationSeconds, null);
public String presignGetUrl(String url, Integer expirationSeconds, String contentType) {
return presignGetUrlWithProcess(url, expirationSeconds, null, contentType);
}
@Override
public String presignGetUrlWithProcess(String url, Integer expirationSeconds, String processParam) {
return presignGetUrlWithProcess(url, expirationSeconds, processParam, null);
}
/**
* 生成文件预签名地址(带 OSS 处理参数和 Content-Type用于读取
*/
private String presignGetUrlWithProcess(String url, Integer expirationSeconds, String processParam, String contentType) {
// 1. 将 url 转换为 path支持 CDN 域名和 OSS 原始域名)
String path = extractPathFromUrl(url);
String decodedPath = URLUtil.decode(path, StandardCharsets.UTF_8);
@@ -213,11 +220,17 @@ public class S3FileClient extends AbstractFileClient<S3FileClientConfig> {
// 2. 公开访问:无需签名,直接拼接参数
if (!BooleanUtil.isFalse(config.getEnablePublicAccess())) {
String encodedPath = UriUtils.encodePath(decodedPath, StandardCharsets.UTF_8);
String resultUrl = config.getDomain() + "/" + encodedPath;
StringBuilder resultUrl = new StringBuilder(config.getDomain()).append("/").append(encodedPath);
char separator = '?';
if (StrUtil.isNotBlank(processParam)) {
resultUrl = resultUrl + "?x-oss-process=" + processParam;
resultUrl.append(separator).append("x-oss-process=").append(processParam);
separator = '&';
}
return resultUrl;
if (StrUtil.isNotBlank(contentType)) {
resultUrl.append(separator).append("response-content-type=").append(
URLUtil.encode(contentType, StandardCharsets.UTF_8));
}
return resultUrl.toString();
}
// 3. 私有访问:生成预签名 URL需要将处理参数包含在签名中
@@ -229,10 +242,14 @@ public class S3FileClient extends AbstractFileClient<S3FileClientConfig> {
com.aliyun.oss.model.GeneratePresignedUrlRequest request =
new com.aliyun.oss.model.GeneratePresignedUrlRequest(config.getBucket(), decodedPath, HttpMethod.GET);
request.setExpiration(expirationDate);
// 关键:将 x-oss-process 参数包含在签名中
// 将 x-oss-process 参数包含在签名中
if (StrUtil.isNotBlank(processParam)) {
request.addQueryParameter("x-oss-process", processParam);
}
// 设置 response-content-type确保浏览器能正确渲染
if (StrUtil.isNotBlank(contentType)) {
request.addQueryParameter("response-content-type", contentType);
}
signedUrl = aliyunOssClient.generatePresignedUrl(request).toString();
} else {
// 非阿里云不支持 OSS 处理参数,直接返回普通预签名 URL

View File

@@ -54,6 +54,16 @@ public interface FileService {
*/
String presignGetUrl(String url, Integer expirationSeconds);
/**
* 生成文件预签名地址(带 Content-Type用于读取
*
* @param url 完整的文件访问地址
* @param expirationSeconds 访问有效期,单位秒
* @param contentType 响应的 Content-Type为 null 时不设置
* @return 文件预签名地址
*/
String presignGetUrl(String url, Integer expirationSeconds, String contentType);
/**
* 生成文件预签名地址(带 OSS 处理参数),用于读取
* 用于阿里云 OSS 视频截帧等图片处理场景

View File

@@ -142,8 +142,13 @@ public class FileServiceImpl implements FileService {
@Override
public String presignGetUrl(String url, Integer expirationSeconds) {
return presignGetUrl(url, expirationSeconds, null);
}
@Override
public String presignGetUrl(String url, Integer expirationSeconds, String contentType) {
FileClient fileClient = fileConfigService.getMasterFileClient();
return fileClient.presignGetUrl(url, expirationSeconds);
return fileClient.presignGetUrl(url, expirationSeconds, contentType);
}
@Override