为了在上传意外中断后能够恢复进度,需要将上传进度持久化存储。以下介绍两种常用的方案:
方案一:使用数据库存储上传进度
-
数据库设计:
- 创建一个
upload_progress
表,包含以下字段:id
: 主键,自增 IDidentifier
: 全局唯一标识符,例如 UUID,用于标识一个上传任务file_name
: 文件名total_chunks
: 总分片数uploaded_chunks
: 已上传的分片索引列表,可以使用 JSON 字符串存储
- 创建一个
-
代码实现:
- 创建实体类:
import javax.persistence.*; import java.util.List; @Entity @Table(name = "upload_progress") public class UploadProgress { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(unique = true) private String identifier; private String fileName; private int totalChunks; @ElementCollection @CollectionTable(name = "uploaded_chunks", joinColumns = @JoinColumn(name = "progress_id")) @Column(name = "chunk_index") private List<Integer> uploadedChunks; // 构造函数、Getter 和 Setter 方法 }
- 注入
UploadProgressRepository
:
@Autowired private UploadProgressRepository uploadProgressRepository;
- 修改
saveChunkToFile
方法:
private void saveChunkToFile(MultipartFile file, int chunkIndex, int totalChunks, String fileName, String identifier) throws IOException { // ... 保存分片文件 ... // 更新上传进度到数据库 UploadProgress progress = uploadProgressRepository.findByIdentifier(identifier) .orElseGet(() -> new UploadProgress(identifier, fileName, totalChunks, new ArrayList<>())); progress.getUploadedChunks().add(chunkIndex); uploadProgressRepository.save(progress); // 检查所有分片是否已上传完成 if (progress.getUploadedChunks().size() == totalChunks) { // ... 合并分片 ... // 清除上传进度 uploadProgressRepository.delete(progress); } }
- 修改
/upload/progress
接口:
@GetMapping("/upload/progress") public ResponseEntity<UploadResponse> getUploadProgress( @RequestParam("identifier") String identifier ) { UploadProgress progress = uploadProgressRepository.findByIdentifier(identifier).orElse(null); if (progress != null) { return ResponseEntity.ok(new UploadResponse(progress.getUploadedChunks())); } else { return ResponseEntity.ok(new UploadResponse(Collections.emptyList())); } }
方案二:使用 Redis 存储上传进度
-
Redis 数据结构:
- 使用 Hash 结构存储每个文件的上传进度,key 为 identifier,field 为分片索引,value 为 true 或 false,表示分片是否已上传。
-
代码实现:
- 注入
RedisTemplate
:
@Autowired private RedisTemplate<String, String> redisTemplate;
- 修改
saveChunkToFile
方法:
private void saveChunkToFile(MultipartFile file, int chunkIndex, int totalChunks, String fileName, String identifier) throws IOException { // ... 保存分片文件 ... // 更新上传进度到 Redis String chunkKey = identifier + ":" + chunkIndex; redisTemplate.opsForValue().set(chunkKey, "true"); // 检查所有分片是否已上传完成 if (redisTemplate.opsForHash().size(identifier) == totalChunks) { // ... 合并分片 ... // 清除上传进度 redisTemplate.delete(identifier); } }
- 修改
/upload/progress
接口:
@GetMapping("/upload/progress") public ResponseEntity<UploadResponse> getUploadProgress( @RequestParam("identifier") String identifier ) { Set<String> uploadedChunks = redisTemplate.keys(identifier + ":*"); Set<Integer> uploadedChunkIndices = uploadedChunks.stream() .map(s -> Integer.parseInt(s.substring((identifier + ":").length()))) .collect(Collectors.toSet()); return ResponseEntity.ok(new UploadResponse(uploadedChunkIndices)); }
- 注入
重新上传获取进度:
- 用户重新选择同一个文件上传时,需要生成相同的
identifier
。 - 在前端上传前,调用
/upload/progress
接口,传入identifier
获取已上传的分片信息。 - 根据返回的已上传分片信息,跳过已上传的分片,继续上传剩余分片。
选择方案的建议:
- 如果上传文件较多,且需要持久化存储上传进度,建议使用数据库方案。
- 如果追求更高的性能,且上传进度信息不需要永久保存,可以考虑使用 Redis 方案。
无论选择哪种方案,都需要确保 identifier
的唯一性,以便准确地标识一个上传任务,实现断点续传功能。