上傳前前端生成唯一 uploadId(UUID/雪花算法/時間戳+隨機數),隨文件一起傳給後端。後端用 uploadId 作為幂等性 Key,發現已處理則直接返回成功,不再重複存儲。
function generateUploadId() {
return Date.now().toString(36) + Math.random().toString(36).substr(2, 9);
}
async function uploadFile(file) {
const uploadId = generateUploadId();
const formData = new FormData();
formData.append('file', file);
formData.append('uploadId', uploadId);
// 可選:將 uploadId 存入 sessionStorage 防頁面刷新重複提交
sessionStorage.setItem('lastUploadId', uploadId);
const res = await fetch('/api/upload', {method: 'POST', body: formData});
return res.json();
}
@PostMapping("/upload")
public Result upload(@RequestParam MultipartFile file, @RequestParam String uploadId) {
// 1. 檢查幂等性 Key(Redis SETNX 或 數據庫唯一索引)
Boolean acquired = redisTemplate.opsForValue().setIfAbsent("upload:" + uploadId, "1", Duration.ofHours(24));
if (!acquired) {
return Result.success("文件已上傳,勿重複提交"); // 秒回,不再處理文件
}
// 2. 真正保存文件
String path = storageService.save(file);
// 3. 記錄文件元數據(可選)
fileMetaRepo.save(new FileMeta(uploadId, path, file.getOriginalFilename(), file.getSize()));
return Result.success(path);
}
分片上傳時,uploadId 標識整個文件,每片帶 chunkIndex。合併前檢查 uploadId 是否已合併完成。