React前端无法上传文件至Express后端,日志解析器故障排查
问题分析与解决方案
你的问题核心在于文件上传的前后端处理逻辑错误,以及对FormData对象的理解偏差,下面分前端和后端给出具体修复方案:
前端修复
- FormData打印问题:直接
console.log(formData)无法看到内部内容,因为FormData是不可枚举的特殊对象,需要用formData.get('logFile')或遍历entries查看:
// 替换原console.log(formData) console.log('上传的文件:', formData.get('logFile')); // 或者遍历所有项 for (let [key, value] of formData.entries()) { console.log(key, value); }
- Axios请求头错误:手动设置
"Content-Type": "multipart/form-data"会丢失自动生成的boundary分隔符,导致后端无法解析。axios会自动识别FormData并添加正确的头,所以删除该配置。
修改后的完整前端代码:
import React, { useState } from "react"; import axios from "axios"; import "./App.css"; const App = () => { const [logFile, setLogFile] = useState<File | null>(null); const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { setLogFile(e.target.files![0]); }; const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => { e.preventDefault(); if (!logFile) return; // 增加文件非空校验 try { const formData = new FormData(); formData.append("logFile", logFile); // 正确打印FormData内容 console.log('上传文件:', formData.get('logFile')); for (let [key, value] of formData.entries()) { console.log(key, value); } const response = await axios.post( "http://localhost:3001/logs", formData ); console.log('解析结果:', response.data); } catch (error) { console.error('上传失败:', error); } }; return ( <> <div className="App"> <h1>日志文件上传</h1> <form onSubmit={handleSubmit}> <div> <input type="file" onChange={handleChange} accept=".log" /> </div> <button type="submit" disabled={!logFile}>提交</button> </form> </div> </> ); }; export default App;
后端修复
- 添加文件上传中间件:Express默认不处理
multipart/form-data,需要安装multer来处理文件上传:
npm install multer
- 配置multer并修改接口逻辑:
- 使用multer配置内存存储(也可配置磁盘存储)
- 读取上传的文件内容,按行解析日志
- 过滤error和warn级别的日志,同时处理解析异常
修改后的完整后端代码:
const express = require('express'); const app = express(); const port = 3001; const winston = require('winston'); const cors = require("cors"); const multer = require('multer'); const fs = require('fs').promises; // 用异步文件读取 app.use(cors()) // 配置multer,使用内存存储 const storage = multer.memoryStorage(); const upload = multer({ storage: storage }); const logger = winston.createLogger({ transports: [ new winston.transports.File({ level: 'error', filename: 'error.log', handleExceptions: true, json: true, maxsize: 5242880, maxFiles: 5, colorize: false }), new winston.transports.File({ level: 'warn', filename: 'warning.log', handleExceptions: true, json: true, maxsize: 5242880, maxFiles: 5, colorize: false }) ], exitOnError: false }); // 移除express.json(),因为接口处理的是文件上传而非JSON请求 app.get("/", (req, res) => { res.json({ "message": "日志解析服务运行中" }) }) // 使用upload.single('logFile')处理单文件上传,对应前端formData的key app.post('/logs', upload.single('logFile'), async (req, res) => { try { if (!req.file) { return res.status(400).json({ error: '未上传文件' }); } // 将二进制文件内容转成字符串,按行拆分日志 const logContent = req.file.buffer.toString('utf8'); const logLines = logContent.split('\n').filter(line => line.trim() !== ''); const errorLogs = []; const warnLogs = []; // 解析每行JSON格式日志,兼容解析失败的情况 for (const line of logLines) { try { const log = JSON.parse(line); if (log.level === 'error') { logger.error(log); errorLogs.push(log); } else if (log.level === 'warn') { logger.warn(log); warnLogs.push(log); } } catch (parseErr) { console.error('解析日志行失败:', parseErr, line); } } res.json({ errorLogs, warnLogs, totalLines: logLines.length }); } catch (error) { console.error('处理日志失败:', error); res.status(500).json({ error: '服务器内部错误' }); } }); app.listen(port, () => { console.log(`服务运行在 http://localhost:${port}`); });
关键说明
- 前端:必须删除手动设置的
multipart/form-data头,axios会自动处理boundary;增加文件非空校验,避免提交空表单。 - 后端:通过multer获取上传的文件,读取buffer转成文本;解析日志时要处理可能的JSON解析错误,避免整个接口崩溃;不再需要
express.json()中间件,因为接口处理的是文件上传请求。
内容的提问来源于stack exchange,提问作者S2000
相关产品推荐
相关产品推荐

