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

Angular拦截器中如何先执行文件上传API再发起主请求?

问题描述

我的应用包含多个表单,表单生成的JSON对象结构多样,存在不同层级的嵌套对象与数组;这些表单支持用户上传文件,对象内会存储包含base64字符串、文件名等信息的数组。

目前的流程是:将文件转为base64字符串后,在发送包含文件的主POST请求前,先调用文件上传API,等待上传完成并获取响应后,替换请求体中的base64文件对象,再发送主请求。但当前遇到两个问题:

  • 文件上传请求与主请求并行执行,请求体未被正确修改
  • 文件上传请求会无原因地多次执行

相关代码如下:

export class FilesCheckerInterceptor implements HttpInterceptor {

  constructor(private filesService: FilesService) {}

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const data = request.body;
    if (data) {
      
      const uploaded: File[] = [];
      this.traverse(data, files => {
        files.forEach(file => {
          const base64 = file.data;
          const result = this.filesService.toFile(base64, file.name);
          uploaded.push(result);
        });
      });
      
      console.log(request);

      return this.filesService.uploadFile(uploaded).pipe(
        mergeMap(response => {
          this.traverse(data, files => {
            for (let i = 0; i < response.length; i++) {
              files[i] = response[i];
            }
          });
  
          return next.handle(request.clone({ body: data }));
        }),
      );      
    }
    else {      
      return next.handle(request);
    }
  }

  traverse(obj: any, action: (value: InternalFile[]) => void) {
    if (obj !== null && typeof obj === 'object') {
      Object.entries(obj).forEach(([key, value]) => {
        if (key === 'attachments' && Array.isArray(value)) {
          action(value);
        }
        else {
          this.traverse(value, action);
        }
      })
    }
  }
}
问题原因与修复方案

问题原因

  1. 文件替换逻辑错误:当前代码会遍历所有attachments数组并收集所有文件,但替换时会把每个attachments数组都覆盖成同一个上传响应结果,导致多组文件的场景下替换完全错误,同时如果上传响应长度与文件总数不匹配,还会出现越界问题。
  2. 多次触发上传:Angular HTTP拦截器返回的Observable如果被多次订阅(比如请求重试、组件重复订阅),会重复执行文件上传逻辑,导致多次上传。

修复代码

import { shareReplay } from 'rxjs/operators';

export class FilesCheckerInterceptor implements HttpInterceptor {

  constructor(private filesService: FilesService) {}

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const data = request.body;
    if (data) {
      // 收集所有需要替换的文件位置,以及对应的File对象
      const fileReplacements: { array: InternalFile[], index: number, file: File }[] = [];
      
      this.traverse(data, (files) => {
        files.forEach((file, index) => {
          const base64 = file.data;
          const result = this.filesService.toFile(base64, file.name);
          fileReplacements.push({ array: files, index, file: result });
        });
      });

      // 没有文件需要上传,直接走主请求
      if (fileReplacements.length === 0) {
        return next.handle(request);
      }

      // 提取待上传的File集合
      const uploadedFiles = fileReplacements.map(item => item.file);

      return this.filesService.uploadFile(uploadedFiles).pipe(
        mergeMap((responses) => {
          // 按顺序替换每个原始文件的位置
          fileReplacements.forEach((item, index) => {
            item.array[item.index] = responses[index];
          });
          // 深拷贝请求体,避免修改原始引用带来的副作用
          const modifiedBody = structuredClone(data);
          // 克隆请求并传入修改后的body
          const modifiedRequest = request.clone({ body: modifiedBody });
          return next.handle(modifiedRequest);
        }),
        // 保证Observable只被执行一次,防止多次上传
        shareReplay(1)
      );
    } else {
      return next.handle(request);
    }
  }

  traverse(obj: any, action: (value: InternalFile[]) => void) {
    if (obj !== null && typeof obj === 'object') {
      Object.entries(obj).forEach(([key, value]) => {
        if (key === 'attachments' && Array.isArray(value)) {
          action(value);
        } else {
          this.traverse(value, action);
        }
      });
    }
  }
}

关键修复点说明

  • 精准替换文件:通过fileReplacements数组记录每个待上传文件所在的数组和索引,上传完成后按顺序精准替换,解决多组attachments数组的替换错误问题。
  • 防止多次上传:添加shareReplay(1)操作符,确保拦截器返回的Observable仅被执行一次,避免重复订阅导致的多次上传。
  • 深拷贝请求体:使用structuredClone深拷贝请求体,避免修改原始引用带来的意外副作用。
  • 空文件判断:没有文件时直接跳过上传逻辑,减少无效请求。

内容的提问来源于stack exchange,提问作者user13749429

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 13:55:18