如何使用Angular/Laravel取消S3文件上传?现有实现逻辑是否正确?
文件上传取消逻辑的合理性确认及优化建议
我正在基于Angular和Laravel开发文件上传功能,需要实现取消正在进行中的上传操作。目前功能能正常运行,但不确定实现逻辑是否合理,想确认该逻辑是否存在漏洞,若不合理希望得到更优方案。
当前我的实现思路:通过Angular组件发起HTTP POST请求至后端API,后端利用Laravel的Storage facade及putFileAs()方法将文件上传至S3。作为Angular和Laravel新手,我理解当前是将文件流式传输至后端,再流式上传至S3,因此只需取消Angular组件中Observable的订阅即可终止S3上传。请问这个理解是否正确?逻辑存在漏洞吗?
处理上传的Angular函数
uploadFile(file: File){ this.progressBar = true; this.uploading = this.fileService.uploadFile(file) .pipe(finalize((res) => { console.log('Upload success!'); }) .subscribe(res => { this.progressBar = false; }); } cancelUpload(){ if(this.uploading){ this.uploading.unsubscribe(); } this.progressBar = false; }
处理上传的Laravel控制器函数
public function uploadFile(Request $request){ $data = $request->input(); $file = $request->file('file'); $res = Storage::disk('s3')->putFileAs('/somePath/', $file, $data['filename']); if($res){ return response()->json($res, 200); } else { return response()->json($res, 422); } }
逻辑合理性分析
你的理解不完全正确,当前实现存在明显漏洞:
- 取消Angular的Observable订阅确实会终止前端到后端的请求传输,但后端可能已经接收到部分甚至全部文件,且
putFileAs()是同步执行的方法——一旦开始向S3上传,即使前端断开请求,后端进程不会自动终止,会继续完成S3上传操作,直到成功或报错。 - 前端取消订阅后,后端的请求处理进程可能仍在后台运行,不仅造成资源浪费,还会出现「前端以为上传取消,但文件已成功传到S3」的不一致情况。
优化方案
前端优化:使用AbortController主动终止请求
替代单纯取消订阅,用标准的AbortController通知浏览器和后端终止请求,更可靠:
private abortController?: AbortController; uploadFile(file: File){ this.progressBar = true; this.abortController = new AbortController(); this.fileService.uploadFile(file, this.abortController.signal) .pipe(finalize(() => { console.log('上传完成/取消'); this.progressBar = false; })) .subscribe( res => console.log('上传成功!'), err => { if (err.name === 'AbortError') { console.log('上传已取消'); } } ); } cancelUpload(){ this.abortController?.abort(); this.abortController = undefined; this.progressBar = false; }
对应的文件服务需传递中止信号:
uploadFile(file: File, signal: AbortSignal): Observable<any> { const formData = new FormData(); formData.append('file', file); formData.append('filename', file.name); return this.http.post('/api/upload', formData, { signal }); }
后端优化:监听连接状态+分块上传终止S3任务
Laravel的putFileAs()无法中途终止,改用S3分块上传,同时监听客户端连接状态,一旦断开就取消S3上传任务:
public function uploadFile(Request $request){ $data = $request->input(); $file = $request->file('file'); $s3Bucket = config('filesystems.disks.s3.bucket'); $s3Key = 'somePath/' . $data['filename']; // 先检查客户端是否仍连接 if (!$request->connection()->isConnected()) { return response()->json('客户端已断开', 499); } // 初始化S3分块上传 $s3Client = Storage::disk('s3')->getDriver()->getAdapter()->getClient(); $uploader = $s3Client->createMultipartUpload([ 'Bucket' => $s3Bucket, 'Key' => $s3Key, ]); $stream = fopen($file->getPathname(), 'r'); $parts = []; $partNumber = 1; try { while (!feof($stream)) { // 每次上传分块前检查连接状态 if (!$request->connection()->isConnected()) { // 主动取消S3分块上传 $s3Client->abortMultipartUpload([ 'Bucket' => $s3Bucket, 'Key' => $s3Key, 'UploadId' => $uploader['UploadId'], ]); fclose($stream); return response()->json('上传已取消', 499); } // 上传5MB大小的分块 $part = $s3Client->uploadPart([ 'Bucket' => $s3Bucket, 'Key' => $s3Key, 'UploadId' => $uploader['UploadId'], 'PartNumber' => $partNumber, 'Body' => fread($stream, 5 * 1024 * 1024), ]); $parts[] = [ 'PartNumber' => $partNumber, 'ETag' => $part['ETag'], ]; $partNumber++; } fclose($stream); // 完成分块上传 $result = $s3Client->completeMultipartUpload([ 'Bucket' => $s3Bucket, 'Key' => $s3Key, 'UploadId' => $uploader['UploadId'], 'MultipartUpload' => ['Parts' => $parts], ]); return response()->json($result['Location'], 200); } catch (\Exception $e) { // 异常时取消分块上传 $s3Client->abortMultipartUpload([ 'Bucket' => $s3Bucket, 'Key' => $s3Key, 'UploadId' => $uploader['UploadId'], ]); fclose($stream); return response()->json('上传失败', 500); } }
补充说明
- 极端情况下可能出现「刚检查完连接状态,客户端就断开」的情况,此时已上传的分块会被S3自动清理(默认7天后),或通过
abortMultipartUpload立即清理。 - 分块上传不仅支持中途终止,还能提升大文件上传的稳定性,避免因网络波动导致整个上传失败。
内容的提问来源于stack exchange,提问作者bestfirstsearch
相关产品推荐
相关产品推荐

