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

Nginx+Node.js环境下如何修复文件上传502跨域错误?

生产环境文件上传接口502跨域错误排查与修复

问题描述

本地服务器所有接口(含文件上传功能)均可正常访问,但生产环境中唯独文件上传接口失效,返回502跨域错误。前端基于Angular HttpClient实现文件上传,后端使用Node.js+Express框架,已配置CORS允许所有来源请求。

前端核心代码

async uploadSingle(event) {
  this.file = event.target.files[0];
  const compressedFile = new File(
    [(await this.resizeImage({ maxSize: 1080, file: this.file })) as Blob],
    this.file.name
  );
  const res = (await this.http.uploadFile<HttpResponse<IUploadResponse>>(
    this.uploadUrl + '?noteId=' + this.selectedNote?.id,
    compressedFile,
    (prog: number) => {
      this.progressBar = prog;
    }
  )) as HttpResponse<IUploadResponse>;
  if (res.body?.error) {
    this.notification.errorNotification(res.body.error);
  } else {
    this.value = res.body.path;
    this.valueChange.emit(this.value);
    this.notification.successNotification(this.str('fileUploadSuccess'));
  }
  this.progressBar = undefined;
}

async uploadFile<T>(
  url: string,
  file: File,
  progress?: (prog: number) => void
) {
  try {
    if (file) {
      const form = new FormData();
      form.append('file', file, file.name);

      const res = await this.http
        .post<T>(this.getUrl(url), form, {
          headers: {
            Authorization: 'Bearer ' + this.auth?.loginToken,
          },
          reportProgress: true,
          observe: 'events',
        })
        .pipe(
          tap((event: any) => {
            if (event.type === HttpEventType.UploadProgress) {
              const prog = Math.round((event.loaded / event.total) * 100);
              if (progress) {
                progress(prog);
              }
            }
          }),
          catchError(this.catchError.bind(this))
        )
        .toPromise();
      return res;
    }
  } catch (err) {
    this.notification.errorNotification(err);
  }
}

后端核心配置

import express from "express";
require("custom-env").env(process.env.NODE_ENV ?? "production");

console.log(`NODE_ENV = ${process.env.NODE_ENV}`);

const { app } = require("./dist/app/server/main");
export const baseServer = express();
const PORT = process.env.PORT;
const bodyParser = require('body-parser');            

baseServer.use(express.json());
baseServer.use(bodyParser.json({limit:'50mb'})); 
baseServer.use(bodyParser.urlencoded({extended:true, limit:'50mb'})); 

export const fs = require("fs");
export const parser = require("xml2js");

// CORS配置
const cors = require("cors");
baseServer.use(cors());

文件上传路由配置

var multer = require("multer");
var ftpStorage = require("multer-ftp");
const upload = multer();

const storage = new ftpStorage({
  ftp: {
    host: process.env.FTP_URL,
    secure: true, // 启用FTPS/FTP with TLS
    user: process.env.FTP_USERNAME,
    password: process.env.FTP_PASSWORD,
  },
  destination: function (req: Request, file: any, options: any, callback: any) {
    callback(
      null,
      process.env.FTP_UPLOAD_PATH +
        generateUUIDString() +
        generateUUIDString() +
        "-" +
        Date.now() +
        "." +
        file.originalname.split("/").pop()
    );
  },
});

const uploadToFTP = multer({ storage: storage }).single("file");

baseServer.post(
  "/api/upload-note-image",
  registeredUser,
  function (req: Request, res: Response, next: NextFunction) {
    const noteId = req.query.noteId as string;
    const userId = res.locals.user.id;
    uploadToFTP(req, res, async function (err: any) {
      if (err) {
        console.log(err);
        res.json({ error: err });
      } else {
        const url =
          (process.env.IMG_URL ?? "") +
          (res.req as any).file.path.split("/").pop();

        await db.noteImages.create({
          data: {
            url,
            noteId,
            userId,
            ftpUrl: url.split("/").pop() ? process.env.FTP_UPLOAD_PATH! + url.split("/").pop() : "",
          },
        });

        res.json({
          success: true,
          path: url,
        });
      }
    });
  }
);

错误截图

错误截图1
错误截图2


原因分析

  1. 502并非真正跨域,是网关错误的次生表现:浏览器提示跨域,本质是服务器返回502时未携带CORS响应头,导致浏览器误判。核心问题是生产环境服务或上游依赖(FTP)故障,无法正常返回响应。
  2. FTP服务连通性问题:生产环境中FTP的主机地址、端口、凭证可能配置错误,或防火墙/安全组限制了服务器对FTP的访问,导致multer-ftp上传超时或失败,触发服务无响应。
  3. 请求体大小限制:后端虽设置了50MB的body-parser限制,但生产环境的反向代理(如Nginx)可能配置了更小的client_max_body_size,截断大文件请求导致服务异常。
  4. 异步代码异常未捕获:路由回调中使用async/await但未包裹在try/catch中,数据库操作或FTP上传的未捕获异常会导致服务崩溃,返回502。

修复方案

1. 排查FTP服务连通性

  • 在生产服务器上手动测试FTP连接:
    ftp -v [FTP_URL]
    
  • 验证FTPS端口(默认990)及被动模式端口范围是否在防火墙/安全组中开放,确认FTP_URL、FTP_USERNAME、FTP_PASSWORD等环境变量在生产环境配置正确。

2. 修复异步异常处理

将路由回调改为带try/catch的异步函数,统一捕获并处理异常:

baseServer.post(
  "/api/upload-note-image",
  registeredUser,
  function (req: Request, res: Response, next: NextFunction) {
    const noteId = req.query.noteId as string;
    const userId = res.locals.user.id;
    uploadToFTP(req, res, async function (err: any) {
      try {
        if (err) {
          console.error("FTP上传错误:", err);
          return res.status(500).json({ error: "文件上传失败,请稍后重试" });
        }
        const url =
          (process.env.IMG_URL ?? "") +
          (res.req as any).file.path.split("/").pop();

        await db.noteImages.create({
          data: {
            url,
            noteId,
            userId,
            ftpUrl: url.split("/").pop() ? process.env.FTP_UPLOAD_PATH! + url.split("/").pop() : "",
          },
        });

        res.json({
          success: true,
          path: url,
        });
      } catch (dbErr) {
        console.error("数据库存储错误:", dbErr);
        res.status(500).json({ error: "文件信息存储失败" });
      }
    });
  }
);

3. 调整反向代理请求大小限制

若使用Nginx作为反向代理,修改配置文件增加请求体大小限制:

server {
  ...
  client_max_body_size 50M; # 与后端配置保持一致
  ...
}

重启Nginx生效:

sudo systemctl restart nginx

4. 确保错误响应携带CORS头

修改CORS配置,强制所有响应(包括错误响应)携带CORS头:

baseServer.use(cors({
  origin: true,
  credentials: true,
}));

// 兜底中间件,确保错误响应也带CORS头
baseServer.use((err, req, res, next) => {
  res.header('Access-Control-Allow-Origin', '*');
  res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
  res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  next(err);
});

5. 增强生产环境日志

添加详细日志便于排查问题:

// 在FTP上传失败时打印详细错误
if (err) {
  console.error("FTP上传详细错误:", JSON.stringify(err, null, 2));
  res.status(500).json({ error: "文件上传失败,请稍后重试" });
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 06:15:17