diff --git a/timeline-file-service/src/main/java/com/timeline/file/controller/FileController.java b/timeline-file-service/src/main/java/com/timeline/file/controller/FileController.java index 881b14c..b1d24bb 100644 --- a/timeline-file-service/src/main/java/com/timeline/file/controller/FileController.java +++ b/timeline-file-service/src/main/java/com/timeline/file/controller/FileController.java @@ -63,10 +63,53 @@ public class FileController { @GetMapping("/get-video-url/{instanceId}") public ResponseEntity getVideoUrl(@PathVariable String instanceId) throws Throwable { - String videoUrl = fileService.generateVideoUrl(instanceId); + // Return proxy URL instead of direct MinIO URL + // Assuming the frontend can access this service via /api/file + String videoUrl = "/api/file/video/" + instanceId; return ResponseEntity.success(videoUrl); } + @GetMapping("/video/{instanceId}") + public void fetchVideo(@PathVariable String instanceId, + @RequestHeader(value = "Range", required = false) String rangeHeader, + HttpServletResponse response) throws Throwable { + long fileSize = fileService.getVideoSize(instanceId); + long start = 0; + long end = fileSize - 1; + + if (rangeHeader != null && rangeHeader.startsWith("bytes=")) { + String[] ranges = rangeHeader.substring(6).split("-"); + try { + start = Long.parseLong(ranges[0]); + if (ranges.length > 1 && !ranges[1].isEmpty()) { + end = Long.parseLong(ranges[1]); + } + } catch (NumberFormatException e) { + // Ignore invalid range + } + } + + if (end >= fileSize) { + end = fileSize - 1; + } + + long length = end - start + 1; + + response.setContentType("video/mp4"); + response.setHeader("Accept-Ranges", "bytes"); + + if (rangeHeader != null) { + response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); + response.setHeader("Content-Range", "bytes " + start + "-" + end + "/" + fileSize); + response.setHeader("Content-Length", String.valueOf(length)); + } else { + response.setHeader("Content-Length", String.valueOf(fileSize)); + } + + InputStream inputStream = fileService.fetchVideo(instanceId, start, length); + IOUtils.copy(inputStream, response.getOutputStream()); + } + @PostMapping("/uploaded") public ResponseEntity uploaded(@RequestBody ImageInfoVo imageInfoVo) throws Throwable { String instanceId = fileService.saveFileMetadata(imageInfoVo); @@ -79,6 +122,12 @@ public class FileController { return ResponseEntity.success(objectKey); } + @PostMapping("/batch-info") + public ResponseEntity> getBatchFileInfo(@RequestBody List instanceIds) { + List list = fileService.getBatchFileInfo(instanceIds); + return ResponseEntity.success(list); + } + @RequestMapping(value = "/image/{instanceId}", method = RequestMethod.GET, produces = MediaType.IMAGE_JPEG_VALUE) public void fetchImage(@PathVariable String instanceId, HttpServletResponse response) throws Throwable { InputStream inputStream = fileService.fetchImage(instanceId); diff --git a/timeline-file-service/src/main/java/com/timeline/file/dao/ImageInfoMapper.java b/timeline-file-service/src/main/java/com/timeline/file/dao/ImageInfoMapper.java index 2b08722..921eae3 100644 --- a/timeline-file-service/src/main/java/com/timeline/file/dao/ImageInfoMapper.java +++ b/timeline-file-service/src/main/java/com/timeline/file/dao/ImageInfoMapper.java @@ -3,6 +3,7 @@ package com.timeline.file.dao; import com.timeline.file.entity.ImageInfo; import com.timeline.file.vo.ImageInfoVo; import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; import java.util.List; import java.util.Map; @@ -10,9 +11,16 @@ import java.util.Map; @Mapper public interface ImageInfoMapper { void insert(ImageInfo imageInfo); + void update(ImageInfo imageInfo); + + List selectListByInstanceIds(@Param("ids") List ids); + String selectObjectKeyById(String objectKey); + void delete(String objectKey); + List selectListByOwnerId(Map map); + ImageInfo selectByInstanceId(String instanceId); } diff --git a/timeline-file-service/src/main/java/com/timeline/file/service/FileService.java b/timeline-file-service/src/main/java/com/timeline/file/service/FileService.java index 77acb3e..98bfb70 100644 --- a/timeline-file-service/src/main/java/com/timeline/file/service/FileService.java +++ b/timeline-file-service/src/main/java/com/timeline/file/service/FileService.java @@ -36,11 +36,17 @@ public interface FileService { String uploadImage(MultipartFile cover) throws Throwable; - InputStream fetchImage(String coverKey) throws Throwable; + InputStream fetchImage(String instanceId) throws Throwable; InputStream fetchImageLowRes(String instanceId) throws Throwable; + InputStream fetchVideo(String instanceId, long offset, long length) throws Throwable; + + long getVideoSize(String instanceId); + String generateVideoUrl(String instanceId) throws Throwable; Map getImagesListByOwnerId(ImageInfoVo imageInfoVo); + + List getBatchFileInfo(List instanceIds); } diff --git a/timeline-file-service/src/main/java/com/timeline/file/service/impl/FileServiceImpl.java b/timeline-file-service/src/main/java/com/timeline/file/service/impl/FileServiceImpl.java index bfc60af..a6b7820 100644 --- a/timeline-file-service/src/main/java/com/timeline/file/service/impl/FileServiceImpl.java +++ b/timeline-file-service/src/main/java/com/timeline/file/service/impl/FileServiceImpl.java @@ -28,7 +28,13 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.time.LocalDateTime; -import java.util.*; +import java.util.stream.Collectors; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.TimeUnit; @Slf4j @Service @@ -386,6 +392,50 @@ public class FileServiceImpl implements FileService { } } + @Override + public InputStream fetchVideo(String instanceId, long offset, long length) throws Throwable { + ImageInfo imageInfo = imageInfoMapper.selectByInstanceId(instanceId); + if (imageInfo == null) { + throw new CustomException(ResponseEnum.NOT_FOUND_ERROR); + } + String bucket = userBucket(imageInfo.getUserId()); + return minioClient.getObject(GetObjectArgs.builder() + .bucket(bucket) + .object(imageInfo.getObjectKey()) + .offset(offset) + .length(length) + .build()); + } + + @Override + public long getVideoSize(String instanceId) { + ImageInfo imageInfo = imageInfoMapper.selectByInstanceId(instanceId); + if (imageInfo == null) { + throw new CustomException(ResponseEnum.NOT_FOUND_ERROR); + } + return imageInfo.getSize(); + } + + @Override + public List getBatchFileInfo(List instanceIds) { + if (instanceIds == null || instanceIds.isEmpty()) { + return new ArrayList<>(); + } + List imageInfos = imageInfoMapper.selectListByInstanceIds(instanceIds); + if (imageInfos == null) { + return new ArrayList<>(); + } + return imageInfos.stream().map(info -> { + ImageInfoVo vo = new ImageInfoVo(); + vo.setInstanceId(info.getInstanceId()); + vo.setImageName(info.getImageName()); + vo.setThumbnailInstanceId(info.getThumbnailInstanceId()); + vo.setContentType(info.getContentType()); + vo.setDuration(info.getDuration()); + return vo; + }).collect(Collectors.toList()); + } + @Override public Map getImagesListByOwnerId(ImageInfoVo imageInfoVo) { HashMap map = new HashMap<>(); diff --git a/timeline-file-service/src/main/resources/com/timeline/file/dao/ImageInfoMapper.xml b/timeline-file-service/src/main/resources/com/timeline/file/dao/ImageInfoMapper.xml index 8ca16cc..1c1764c 100644 --- a/timeline-file-service/src/main/resources/com/timeline/file/dao/ImageInfoMapper.xml +++ b/timeline-file-service/src/main/resources/com/timeline/file/dao/ImageInfoMapper.xml @@ -43,4 +43,11 @@ is_deleted = #{isDeleted} WHERE instance_id = #{instanceId} +