You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 15:55:02