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

Flutter向Node.js+Multer传图时req.file为undefined问题求助

Flutter上传图片到Node.js+Multer服务报错排查

我在开发Flutter应用通过MultipartRequest向Node.js+Multer服务上传图片的功能时遇到问题:Postman调用接口正常,但Flutter端报错,服务器抛出异常。以下是相关代码及错误信息:


Flutter代码

print("TEST1");
var request =
    http.MultipartRequest("POST", Uri.parse(url));
print("TEST2");
request.fields['request'] =
    jsonEncode<String, int>{'todoId': todoId};
print("TEST3");
try {
  File file =
      await File.fromRawPath(result.files.single.bytes!);
  print("TEST4");
  http.MultipartFile file_forrequest = http.MultipartFile('filetoupload',
      file.readAsBytes().asStream(), file.lengthSync(),
      filename: file.path.split("/").last);
  request.files.add(file_forrequest);
} catch (e) {
  print("ERROR: " + e.toString());
}
print("TEST5");
request.headers.addAll<String, String>{
  "Authorization": "Bearer " + token.toString(),
  "Content-Type": "application/json"
};
print("TEST6");
var response = await request.send();
print("TEST7");
print(response.statusCode);
String res = await response.stream.bytesToString();
print(res);

Node.js接口代码

app.post(`${vorPath}/otherFileToToDo`, jsonParser, berechtigungmitgruppepruefen, uploadFileToDo("filetoupload"), async (req, res) => {
    console.log("FILES:");
    console.log(req.files);
    console.log("__");
    const request = JSON.parse(req.body.request);
    const todoId = request.todoId;
    const peopleId = req.user.peopleId;
    const filePath = req.file.path;
    try {
        const todos = await prisma.todos.findMany({
            where: {
                id: todoId,
                zustaendige: {
                    some: {
                        peopleId: peopleId
                    }
                }
            }
        });
        if (todos.length == 1) {
            const todo = todos[0];
            const otherFilesPaths = todo.otherFilesPaths;
            otherFilesPaths.push(filePath);
            await prisma.todos.update({
                where: {
                    id: todoId
                },
                data: {
                    otherFilesPaths: otherFilesPaths
                }
            })
            res.sendStatus(200);
        } else {
            const todos_2 = await prisma.todos.findMany({
                where: {
                    id: todoId
                }
            })
            if (todos_2.length == 1) {
                res.sendStatus(401);
            } else {
                res.sendStatus(404);
            }
        }
    } catch (error) {
        console.log("ERROR: " + error);
        res.sendStatus(500);
    }
    res.sendStatus(200);
})

uploadFileToDo函数代码

function uploadFileToDo(file) {
    const storage = multer.diskStorage({
        destination: (req, file, cb) => {
            cb(null, '/mnt/sknasserver/todofiles/');
        },
        filename: (req, file, cb) => {
            const withoutLastFour = file.originalname.slice(0, -4);
            const lastFour = file.originalname.slice(-4);
            const newFileName = withoutLastFour + "-" + password_generator.generate({
                length: 10,
                symbols: false,
                numbers: true
            }) + lastFour;
            cb(null, newFileName);
        },
    });
    const upload = multer({ storage: storage });
    const uploadsingle = upload.single(file);
    return (req, res, next) => {
        uploadsingle(req, res, (err) => {
            if (err) {
                console.error('Error during file upload:', err);
                return res.status(400).send('Error during file upload: ' + err.message);
            }
            console.log('File uploaded successfully:', req.file);
            req.file.path = "/todofiles/" + req.file.filename;
            next();
        });
    };
}

服务器控制台日志

file
App listening on port 5000
null
File uploaded successfully: undefined
/home/schemannkurier/api/sk_backend/api.js:108
            req.file.path = "/todofiles/" + req.file.filename;
                                                     ^

TypeError: Cannot read properties of undefined (reading 'filename')
    at /home/schemannkurier/api/sk_backend/api.js:108:54
    at done (/home/schemannkurier/api/sk_backend/node_modules/multer/lib/make-middleware.js:45:7)
    at indicateDone (/home/schemannkurier/api/sk_backend/node_modules/multer/lib/make-middleware.js:49:68)
    at Multipart.<anonymous> (/home/schemannkurier/api/sk_backend/node_modules/multer/lib/make-middleware.js:166:7)
    at Multipart.emit (node:events:514:28)
    at Multipart.emit (node:domain:488:12)
    at emitCloseNT (node:internal/streams/destroy:132:10)
    at process.processTicksAndRejections (node:internal/process/task_queues:81:21)

Node.js v20.10.0

Flutter调试日志

TEST1
TEST2
TEST3
TEST4
ERROR: Unsupported operation: _Namespace
TEST5
TEST6
TEST7
502
<html>
<head><title>502 Bad Gateway</title></head>
<body>
<center><h1>502 Bad Gateway</h1></center>
<hr><center>nginx/1.25.5</center>
</body>
</html>
6
<!-- a padding to disable MSIE and Chrome friendly error page -->

问题分析与修复步骤

1. Flutter端核心错误修复

  • Unsupported operation: _Namespace错误:File.fromRawPath用于解析原生文件路径的字节,不能直接用文件内容字节创建File对象。直接用字节数组生成MultipartFile即可,无需转成File。
  • 手动设置Content-Type: application/json:MultipartRequest会自动生成multipart/form-data请求头,手动覆盖会导致Multer无法解析表单数据,这是服务器req.file为undefined的根本原因,需删除该设置。

修复后的Flutter代码:

print("TEST1");
var request = http.MultipartRequest("POST", Uri.parse(url));
print("TEST2");
request.fields['request'] = jsonEncode({'todoId': todoId});
print("TEST3");
try {
  Uint8List fileBytes = result.files.single.bytes!;
  print("TEST4");
  http.MultipartFile file_forrequest = http.MultipartFile.fromBytes(
    'filetoupload',
    fileBytes,
    filename: result.files.single.name,
  );
  request.files.add(file_forrequest);
} catch (e) {
  print("ERROR: " + e.toString());
}
print("TEST5");
// 仅添加Authorization头,不手动设置Content-Type
request.headers.addAll({
  "Authorization": "Bearer " + token.toString(),
});
print("TEST6");
var response = await request.send();
print("TEST7");
print(response.statusCode);
String res = await response.stream.bytesToString();
print(res);

2. Node.js端增加防护逻辑

服务器端需先判断req.file是否存在,避免空指针崩溃:
修改uploadFileToDo函数:

return (req, res, next) => {
    uploadsingle(req, res, (err) => {
        if (err) {
            console.error('Error during file upload:', err);
            return res.status(400).send('Error during file upload: ' + err.message);
        }
        if (!req.file) {
            console.error('No file uploaded');
            return res.status(400).send('No file uploaded');
        }
        console.log('File uploaded successfully:', req.file);
        req.file.path = "/todofiles/" + req.file.filename;
        next();
    });
};

3. 额外注意事项

  • 确保Flutter端文件选择插件(如image_picker)返回的bytes是有效文件字节数组。
  • 服务器端/mnt/sknasserver/todofiles/目录需配置写入权限,避免Multer无法保存文件。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 06:08:08