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, }); } }); } );
错误截图


原因分析
- 502并非真正跨域,是网关错误的次生表现:浏览器提示跨域,本质是服务器返回502时未携带CORS响应头,导致浏览器误判。核心问题是生产环境服务或上游依赖(FTP)故障,无法正常返回响应。
- FTP服务连通性问题:生产环境中FTP的主机地址、端口、凭证可能配置错误,或防火墙/安全组限制了服务器对FTP的访问,导致
multer-ftp上传超时或失败,触发服务无响应。 - 请求体大小限制:后端虽设置了50MB的
body-parser限制,但生产环境的反向代理(如Nginx)可能配置了更小的client_max_body_size,截断大文件请求导致服务异常。 - 异步代码异常未捕获:路由回调中使用
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
相关产品推荐
相关产品推荐

