feat: 支持视频及缩略图元数据存储
All checks were successful
test/timeline-server/pipeline/head This commit looks good

在文件服务和故事服务中增加了对视频、持续时间及缩略图相关字段的支持。

- 在 `ImageInfo` 和 `StoryItem` 实体类中添加 `duration`、`thumbnailInstanceId` 等字段
- 更新 MyBatis 映射文件以支持新字段的持久化
- 在 `FileService` 中新增 `generateVideoUrl` 接口用于获取视频预签名地址
- 调整 `saveFileMetadata` 接口返回生成的 `instanceId`
- 优化了部分代码的格式和缩进
This commit is contained in:
2026-02-12 14:43:57 +08:00
parent 0349bf3c70
commit f0d140c646
9 changed files with 1656 additions and 1080 deletions

View File

@@ -24,10 +24,12 @@ import java.util.Map;
public class FileController {
@Autowired
private FileService fileService;
@GetMapping("/hello")
public String hello(){
public String hello() {
return "file service hello";
}
@GetMapping("/create-default-bucket")
public String createDefaultBucket() throws Throwable {
fileService.createBucketIfNotExist();
@@ -46,21 +48,31 @@ public class FileController {
fileService.createUserBucket(userId);
return ResponseEntity.success("bucket created for current user");
}
@GetMapping("/get-upload-url/{fileName}")
public ResponseEntity<String> getUploadUrl(@PathVariable String fileName) throws Throwable {
String uploadUrl = fileService.generateUploadUrl(fileName);
return ResponseEntity.success(uploadUrl);
}
@GetMapping("/get-download-url/{fileName}")
public ResponseEntity<String> getDownloadUrl(@PathVariable String fileName) throws Throwable {
String downloadUrl = fileService.generateDownloadUrl(fileName);
return ResponseEntity.success(downloadUrl);
}
@GetMapping("/get-video-url/{instanceId}")
public ResponseEntity<String> getVideoUrl(@PathVariable String instanceId) throws Throwable {
String videoUrl = fileService.generateVideoUrl(instanceId);
return ResponseEntity.success(videoUrl);
}
@PostMapping("/uploaded")
public ResponseEntity<String> uploaded(@RequestBody ImageInfoVo imageInfoVo) throws Throwable {
fileService.saveFileMetadata(imageInfoVo);
return ResponseEntity.success("上传成功");
String instanceId = fileService.saveFileMetadata(imageInfoVo);
return ResponseEntity.success(instanceId);
}
@PostMapping("/upload-image")
public ResponseEntity<String> uploadCover(@RequestPart("image") MultipartFile image) throws Throwable {
String objectKey = fileService.uploadImage(image);
@@ -73,12 +85,14 @@ public class FileController {
response.setContentType("image/jpeg");
IOUtils.copy(inputStream, response.getOutputStream());
}
@RequestMapping(value = "/image-low-res/{instanceId}", method = RequestMethod.GET, produces = MediaType.IMAGE_JPEG_VALUE)
public void fetchImageLowRes(@PathVariable String instanceId, HttpServletResponse response) throws Throwable {
InputStream inputStream = fileService.fetchImageLowRes(instanceId);
response.setContentType("image/jpeg");
IOUtils.copy(inputStream, response.getOutputStream());
}
/**
* 上传图片后绑定到某个 StoryItem
*/
@@ -119,12 +133,15 @@ public class FileController {
fileService.removeImageFromStoryItem(imageInstanceId, storyItemId);
return ResponseEntity.success("图片已从故事项中移除");
}
@GetMapping("/image/list")
public ResponseEntity<Map<String, Object>> getImagesListByOwnerId(@SpringQueryMap ImageInfoVo imageInfoVo) throws Throwable {
public ResponseEntity<Map<String, Object>> getImagesListByOwnerId(@SpringQueryMap ImageInfoVo imageInfoVo)
throws Throwable {
imageInfoVo.setOwnerId(UserContextUtils.getCurrentUserId());
Map<String, Object> images = fileService.getImagesListByOwnerId(imageInfoVo);
return ResponseEntity.success(images);
}
@DeleteMapping("/image/{imageInstanceId}")
public ResponseEntity<String> deleteImage(@PathVariable String imageInstanceId) throws Throwable {
fileService.deleteImage(imageInstanceId);

View File

@@ -16,4 +16,6 @@ public class ImageInfo {
private String userId;
private Integer isDeleted;
private LocalDateTime updateTime;
private String thumbnailInstanceId;
private Long duration;
}

View File

@@ -13,20 +13,34 @@ import java.util.Map;
@Service
public interface FileService {
void createBucketIfNotExist() throws Throwable;
void createUserBucket(String userId) throws Throwable;
String generateUploadUrl(String fileName) throws Throwable;
String generateDownloadUrl(String fileName) throws Throwable;
void saveFileMetadata(ImageInfoVo imageInfoVo);
String saveFileMetadata(ImageInfoVo imageInfoVo);
List<ImageInfo> listAllImages();
void deleteImage(String objectKey) throws Throwable;
void associateImageWithStoryItem(String imageInstanceId, String storyItemId, String userId);
List<String> getStoryItemImages(String storyItemId);
void removeImageFromStoryItem(String imageInstanceId, String storyItemId);
ArrayList<String> getAllImageUrls(List<String> images) throws Throwable;
String uploadImage(MultipartFile cover) throws Throwable;
InputStream fetchImage(String coverKey) throws Throwable;
InputStream fetchImageLowRes(String instanceId) throws Throwable;
String generateVideoUrl(String instanceId) throws Throwable;
Map<String, Object> getImagesListByOwnerId(ImageInfoVo imageInfoVo);
}

View File

@@ -42,6 +42,7 @@ public class FileServiceImpl implements FileService {
private CommonRelationMapper commonRelationMapper;
@Autowired
private FileHashMapper fileHashMapper;
public FileServiceImpl(MinioClient minioClient, MinioConfig minioConfig) {
this.minioClient = minioClient;
this.minioConfig = minioConfig;
@@ -62,7 +63,8 @@ public class FileServiceImpl implements FileService {
@Override
public void createBucketIfNotExist() throws Throwable {
try {
boolean found = minioClient.bucketExists(BucketExistsArgs.builder().bucket(minioConfig.getBucketName()).build());
boolean found = minioClient
.bucketExists(BucketExistsArgs.builder().bucket(minioConfig.getBucketName()).build());
if (!found) {
log.info("bucket不存在创建bucket{}", minioConfig.getBucketName());
minioClient.makeBucket(MakeBucketArgs.builder().bucket(minioConfig.getBucketName()).build());
@@ -97,8 +99,7 @@ public class FileServiceImpl implements FileService {
GetPresignedObjectUrlArgs.builder()
.method(Method.PUT)
.bucket(bucket)
.object(fileName).build()
);
.object(fileName).build());
}
@Override
@@ -109,22 +110,42 @@ public class FileServiceImpl implements FileService {
GetPresignedObjectUrlArgs.builder()
.method(Method.GET)
.bucket(bucket)
.object(fileName).build()
);
.object(fileName).build());
}
@Override
public void saveFileMetadata(ImageInfoVo imageInfoVo) {
public String generateVideoUrl(String instanceId) throws Throwable {
ImageInfo imageInfo = imageInfoMapper.selectByInstanceId(instanceId);
if (imageInfo == null) {
throw new CustomException(404, "视频文件不存在");
}
String bucket = userBucket(imageInfo.getUserId());
// 生成预签名 URL有效期例如 1 小时
return minioClient.getPresignedObjectUrl(
GetPresignedObjectUrlArgs.builder()
.method(Method.GET)
.bucket(bucket)
.object(imageInfo.getObjectKey())
.expiry(3600) // 1小时
.build());
}
@Override
public String saveFileMetadata(ImageInfoVo imageInfoVo) {
try {
ImageInfo imageInfo = new ImageInfo();
imageInfo.setInstanceId(IdUtils.randomUuidUpper());
String instanceId = IdUtils.randomUuidUpper();
imageInfo.setInstanceId(instanceId);
imageInfo.setObjectKey(imageInfoVo.getObjectKey());
imageInfo.setImageName(imageInfoVo.getImageName());
imageInfo.setContentType(imageInfoVo.getContentType());
imageInfo.setSize(imageInfoVo.getSize());
imageInfo.setUploadTime(LocalDateTime.now());
imageInfo.setUserId(currentUserId());
imageInfo.setThumbnailInstanceId(imageInfoVo.getThumbnailInstanceId());
imageInfo.setDuration(imageInfoVo.getDuration());
imageInfoMapper.insert(imageInfo);
return instanceId;
} catch (Exception e) {
log.error("保存图片元数据失败", e);
throw new CustomException(500, "保存图片信息失败");
@@ -174,6 +195,7 @@ public class FileServiceImpl implements FileService {
throw new CustomException(500, "删除图片失败");
}
}
@Override
public void associateImageWithStoryItem(String imageInstanceId, String storyItemId, String userId) {
try {
@@ -200,7 +222,7 @@ public class FileServiceImpl implements FileService {
}
@Override
public ArrayList<String> getAllImageUrls(List<String > imageIds) throws Throwable {
public ArrayList<String> getAllImageUrls(List<String> imageIds) throws Throwable {
ArrayList<String> urls = new ArrayList<>();
for (String imageInstanceId : imageIds) {
@@ -214,8 +236,7 @@ public class FileServiceImpl implements FileService {
.method(Method.GET)
.bucket(bucket)
.object(info.getObjectKey())
.build()
);
.build());
urls.add(url);
}
return urls;
@@ -223,7 +244,8 @@ public class FileServiceImpl implements FileService {
@Override
public String uploadImage(MultipartFile image) throws Throwable {
String suffix = Objects.requireNonNull(image.getOriginalFilename()).substring(image.getOriginalFilename().lastIndexOf("."));
String suffix = Objects.requireNonNull(image.getOriginalFilename())
.substring(image.getOriginalFilename().lastIndexOf("."));
String hash = CommonUtils.calculateFileHash(image);
String objectKey = hash + suffix;
String lowResolutionObjectKey = CommonConstants.LOW_RESOLUTION_PREFIX + hash + suffix;
@@ -300,6 +322,7 @@ public class FileServiceImpl implements FileService {
.object(objectKey)
.build());
}
@Override
public InputStream fetchImageLowRes(String instanceId) throws Throwable {
String objectKey = imageInfoMapper.selectObjectKeyById(instanceId);
@@ -325,18 +348,21 @@ public class FileServiceImpl implements FileService {
.build());
}
}
@Override
public Map<String, Object> getImagesListByOwnerId(ImageInfoVo imageInfoVo) {
HashMap<String, String> map = new HashMap<>();
map.put("ownerId", imageInfoVo.getOwnerId());
@SuppressWarnings("unchecked")
Map<String, Object> resultMap = (Map<String, Object>) PageUtils.pageQuery(imageInfoVo.getCurrent(), imageInfoVo.getPageSize(), ImageInfoMapper.class, "selectListByOwnerId",
Map<String, Object> resultMap = (Map<String, Object>) PageUtils.pageQuery(imageInfoVo.getCurrent(),
imageInfoVo.getPageSize(), ImageInfoMapper.class, "selectListByOwnerId",
map, "list");
return resultMap;
}
/**
* 检查对象是否存在
*
* @param objectKey 对象键
* @return true表示存在false表示不存在
*/

View File

@@ -11,4 +11,6 @@ public class ImageInfoVo extends CommonVo {
private Long size;
private String instanceId;
private String ownerId;
private String thumbnailInstanceId;
private Long duration;
}