feat: 支持外部端点映射与视频元数据扩展
Some checks failed
test/timeline-server/pipeline/head There was a failure building this commit
Some checks failed
test/timeline-server/pipeline/head There was a failure building this commit
1. 在 MinioConfig 中增加 externalEndpoint 配置,支持将生成的预签名 URL 内部地址替换为外部访问地址。 2. 更新数据库脚本及查询逻辑,增加视频时长、缩略图 ID 等字段支持,并在 查询列表时过滤掉作为缩略图存在的冗余记录。 3. 优化图片上传流程,增加压缩失败时的降级处理机制,防止非图片文件导致 上传中断。
This commit is contained in:
8
deploy/update_image_info.sql
Normal file
8
deploy/update_image_info.sql
Normal file
@@ -0,0 +1,8 @@
|
||||
ALTER TABLE `image_info`
|
||||
ADD COLUMN `thumbnail_instance_id` varchar(32) DEFAULT NULL COMMENT '视频缩略图ID',
|
||||
ADD COLUMN `duration` bigint DEFAULT NULL COMMENT '视频时长(秒)';ALTER TABLE `story_item`
|
||||
ADD COLUMN `video_url` VARCHAR(64) COMMENT '视频文件 Instance ID',
|
||||
ADD COLUMN `duration` BIGINT COMMENT '视频时长(秒)',
|
||||
ADD COLUMN `thumbnail_url` VARCHAR(64) COMMENT '视频封面 Instance ID';ALTALTER TABLE story_item ADD COLUMN share_id VARCHAR(64) DEFAULT NULL COMMENT '分享ID';ER TABLE `image_info`
|
||||
ADD COLUMN `thumbnail_instance_id` varchar(32) DEFAULT NULL COMMENT '视频缩略图ID',
|
||||
ADD COLUMN `duration` bigint DEFAULT NULL COMMENT '视频时长(秒)';
|
||||
@@ -11,6 +11,7 @@ import org.springframework.context.annotation.Configuration;
|
||||
@ConfigurationProperties(prefix = "minio")
|
||||
public class MinioConfig {
|
||||
private String endpoint;
|
||||
private String externalEndpoint;
|
||||
private String accessKey;
|
||||
private String secretKey;
|
||||
private String bucketName;
|
||||
|
||||
@@ -90,27 +90,44 @@ public class FileServiceImpl implements FileService {
|
||||
}
|
||||
}
|
||||
|
||||
private String replaceWithExternalEndpoint(String url) {
|
||||
String externalEndpoint = minioConfig.getExternalEndpoint();
|
||||
if (externalEndpoint != null && !externalEndpoint.isEmpty()) {
|
||||
String internalEndpoint = minioConfig.getEndpoint();
|
||||
// 简单替换:将内部 Endpoint 替换为外部 Endpoint
|
||||
// 注意:MinIO 生成的 URL 肯定以配置的 Endpoint 开头
|
||||
if (url.startsWith(internalEndpoint)) {
|
||||
return url.replaceFirst(java.util.regex.Pattern.quote(internalEndpoint), externalEndpoint);
|
||||
}
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String generateUploadUrl(String fileName) throws Throwable {
|
||||
String userId = currentUserId();
|
||||
String bucket = userBucket(userId);
|
||||
createUserBucket(userId);
|
||||
return minioClient.getPresignedObjectUrl(
|
||||
String url = minioClient.getPresignedObjectUrl(
|
||||
GetPresignedObjectUrlArgs.builder()
|
||||
.method(Method.PUT)
|
||||
.bucket(bucket)
|
||||
.object(fileName).build());
|
||||
.object(fileName)
|
||||
.expiry(30 * 60) // 30 minutes
|
||||
.build());
|
||||
return replaceWithExternalEndpoint(url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String generateDownloadUrl(String fileName) throws Throwable {
|
||||
String userId = currentUserId();
|
||||
String bucket = userBucket(userId);
|
||||
return minioClient.getPresignedObjectUrl(
|
||||
String url = minioClient.getPresignedObjectUrl(
|
||||
GetPresignedObjectUrlArgs.builder()
|
||||
.method(Method.GET)
|
||||
.bucket(bucket)
|
||||
.object(fileName).build());
|
||||
return replaceWithExternalEndpoint(url);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -121,13 +138,14 @@ public class FileServiceImpl implements FileService {
|
||||
}
|
||||
String bucket = userBucket(imageInfo.getUserId());
|
||||
// 生成预签名 URL,有效期例如 1 小时
|
||||
return minioClient.getPresignedObjectUrl(
|
||||
String url = minioClient.getPresignedObjectUrl(
|
||||
GetPresignedObjectUrlArgs.builder()
|
||||
.method(Method.GET)
|
||||
.bucket(bucket)
|
||||
.object(imageInfo.getObjectKey())
|
||||
.expiry(3600) // 1小时
|
||||
.build());
|
||||
return replaceWithExternalEndpoint(url);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -237,7 +255,7 @@ public class FileServiceImpl implements FileService {
|
||||
.bucket(bucket)
|
||||
.object(info.getObjectKey())
|
||||
.build());
|
||||
urls.add(url);
|
||||
urls.add(replaceWithExternalEndpoint(url));
|
||||
}
|
||||
return urls;
|
||||
}
|
||||
@@ -267,20 +285,39 @@ public class FileServiceImpl implements FileService {
|
||||
log.info("当前文件已存在,不进行minio文件上传");
|
||||
} else {
|
||||
// 1. 上传到 MinIO
|
||||
// 对原图进行压缩
|
||||
// 尝试对原图进行压缩
|
||||
ByteArrayOutputStream compressedOutputStream = new ByteArrayOutputStream();
|
||||
Thumbnails.of(image.getInputStream())
|
||||
.scale(1.0) // 保持原图尺寸
|
||||
.outputQuality(0.8) // 设置压缩质量
|
||||
.toOutputStream(compressedOutputStream);
|
||||
ByteArrayInputStream compressedInputStream = new ByteArrayInputStream(compressedOutputStream.toByteArray());
|
||||
boolean compressionSuccess = false;
|
||||
try {
|
||||
Thumbnails.of(image.getInputStream())
|
||||
.scale(1.0) // 保持原图尺寸
|
||||
.outputQuality(0.8) // 设置压缩质量
|
||||
.toOutputStream(compressedOutputStream);
|
||||
compressionSuccess = true;
|
||||
} catch (Exception e) {
|
||||
log.warn("图片压缩失败(可能是格式不支持或非图片文件),降级为直接上传原图: {}", image.getOriginalFilename(), e);
|
||||
}
|
||||
|
||||
minioClient.putObject(PutObjectArgs.builder()
|
||||
.bucket(bucket)
|
||||
.object(objectKey)
|
||||
.stream(compressedInputStream, compressedInputStream.available(), -1)
|
||||
.contentType(image.getContentType())
|
||||
.build());
|
||||
if (compressionSuccess) {
|
||||
ByteArrayInputStream compressedInputStream = new ByteArrayInputStream(
|
||||
compressedOutputStream.toByteArray());
|
||||
minioClient.putObject(PutObjectArgs.builder()
|
||||
.bucket(bucket)
|
||||
.object(objectKey)
|
||||
.stream(compressedInputStream, compressedInputStream.available(), -1)
|
||||
.contentType(image.getContentType())
|
||||
.build());
|
||||
} else {
|
||||
// 压缩失败,直接上传原图
|
||||
try (InputStream inputStream = image.getInputStream()) {
|
||||
minioClient.putObject(PutObjectArgs.builder()
|
||||
.bucket(bucket)
|
||||
.object(objectKey)
|
||||
.stream(inputStream, image.getSize(), -1)
|
||||
.contentType(image.getContentType())
|
||||
.build());
|
||||
}
|
||||
}
|
||||
// 生成并上传低分辨率版本
|
||||
try (InputStream inputStream = image.getInputStream()) {
|
||||
ByteArrayOutputStream lowResOutputStream = new ByteArrayOutputStream();
|
||||
@@ -296,10 +333,10 @@ public class FileServiceImpl implements FileService {
|
||||
.stream(lowResInputStream, lowResInputStream.available(), -1)
|
||||
.contentType(image.getContentType())
|
||||
.build());
|
||||
|
||||
log.info("低分辨率版本已生成并上传: {}", lowResolutionObjectKey);
|
||||
} catch (Exception e) {
|
||||
log.error("生成低分辨率版本失败", e);
|
||||
// 低分辨率生成失败不影响主流程
|
||||
}
|
||||
}
|
||||
fileHashMapper.insertFileHash(new FileHash(imageInfo.getInstanceId(), hash));
|
||||
|
||||
@@ -22,7 +22,14 @@
|
||||
</select>
|
||||
|
||||
<select id="selectListByOwnerId" resultType="com.timeline.file.entity.ImageInfo" parameterType="java.util.Map">
|
||||
SELECT * FROM image_info WHERE user_id = #{ownerId} AND is_deleted = 0
|
||||
SELECT * FROM image_info t1
|
||||
WHERE user_id = #{ownerId}
|
||||
AND is_deleted = 0
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM image_info t2
|
||||
WHERE t2.thumbnail_instance_id = t1.instance_id
|
||||
AND t2.is_deleted = 0
|
||||
)
|
||||
</select>
|
||||
|
||||
<select id="selectByInstanceId" resultType="com.timeline.file.entity.ImageInfo">
|
||||
|
||||
Reference in New Issue
Block a user