express-validator误判合法输入,表单验证失败求助
使用express-validator 7.1.0验证multipart/form-data表单时请求体为空导致验证失败
问题背景
使用express-validator 7.1.0验证包含文件上传的EJS表单(编码为multipart/form-data),合法输入始终无法通过验证,日志显示req.body为空,但后续处理中间件能正常读取请求体。
相关代码
表单代码(EJS)
<%- include('partials/header') %> <h1>Feature-Request hinzufügen</h1> <body> <form class="addrequest" enctype="multipart/form-data" id="new_document_attachment" action="/auth/request/create" method="POST"> <div class="form-group mb-2"> <label for="title">Titel</label> <input type="text" class="form-control" id="title" name="title" maxlength="50" required> </div> <div class="form-group mb-2"> <label for="description">Beschreibung</label> <textarea class="form-control" id="description" name="description" rows="10" maxlength="<%=MAX_DESCRIPTION_CHARACTER_INPUT%>" required></textarea> </div> <div class="mb-2"> <label for="formFile" class="form-label">Dateianhang</label> <div class="file-loading"> <input id="input-folder-3" name="input-folder-3[]" type="file" accept=".jpeg,.jpg,.webp,.gif,.png.pdf" multiple> </div> </div> <div class="row"> <div class="form-group mb-2 col"> <label class="mb-2">Kategorie <span id="howManyCategoriesMustBeProvided" class="text-danger-emphasis">(mindestens eine)</span></label> <% requestOptions.categoriesFromDB.forEach(category => { %> <div class="form-check"> <input class="form-check-input" type="checkbox" value="<%= category.category %>" id="category<%= category.id %>" name="category[]"> <label class="form-check-label text-<%= category.cssClass %>" for="category<%= category.id %>"> <%= category.category %> </label> </div> <% }) %> </div> <% if (user && user.isAdmin) { %> <div class="col"> <label class="mb-2">Status</label> <% requestOptions.statusFromDB.forEach((status) => { %> <div class="form-group mb-2"> <input class="form-check-input" type="radio" value="<%= status.id %>" id="status<%= status.id %>" name="statusId" <%= status.id === ADMIN_DEFAULT_STATUS ? 'checked' : '' %>> <label class="form-check-label text-<%= status.cssClass %>" for="status<%= status.id %>"> <%= status.status %> </label> </div> <% }) %> </div> <% } %> <div class="col"> <label class="mb-2">Priorität</label> <% requestOptions.prioritiesFromDB.forEach((priority, index) => { %> <div class="form-group mb-2"> <input class="form-check-input" type="radio" value="<%= index +1 %>" id="<%= priority.priority %>" name="priority" <%= index === DEFAULT_PRIORITY ? 'checked' : '' %>> <label class="form-check-label text-<%= priority.cssClass %>" for="<%= priority.priority %>"> <%= priority.priority %> </label> </div> <% }) %> </div> </div> <span class="d-inline-block addrequest_submit_btn" tabindex="0" data-bs-toggle="popover" data-bs-trigger="hover focus" data-bs-placement="right" data-bs-content="Du musst mindestens eine Kategorie auswählen."> <button class="btn btn-primary" type="submit" disabled>Zur Überprüfung einreichen</button> </span> <p>Du kannst deine Anfrage nach dem Einreichen nicht mehr Bearbeiten.</p> </form> <%- include('partials/footer') %>
express-validator工具封装(utils.js)
const { body, validationResult } = require("express-validator"); const ensureValid = (req, res, next) => { const result = validationResult(req); if (!result.isEmpty()) { return res.status(400).json({ errors: result.array() }); } next(); }; module.exports = { body, ensureValid };
路由配置(app.js)
const authRoutes = require('./routes/authRoutes'); app.use('/auth', authRoutes);
验证链与路由处理(authRoutes.js)
const { body, ensureValid } = require('../utils'); const authController = require('../controllers/authController'); router.post('/request/create', body('title') .escape() .isString().withMessage('Der Titel muss ein String sein.') .isLength({ max: 40 }).withMessage('Der Titel darf maximal 40 Zeichen lang sein.'), body('description') .escape() .isString().withMessage('Die Beschreibung muss ein String sein.') .isLength({ min: 1, max: parseInt(process.env.MAX_DESCRIPTION_CHARACTER_INPUT, 10) || 1000 }) .withMessage(`Die Beschreibung muss zwischen 1 und ${process.env.MAX_DESCRIPTION_CHARACTER_INPUT || 1000} Zeichen lang sein.`), body('category') .escape() .isString().withMessage('Ungültige Kategorie.') .custom((value, { req }) => { const validCategories = [ process.env.categoryName1, process.env.categoryName2, process.env.categoryName3, process.env.categoryName4, process.env.categoryName5, process.env.categoryName6, process.env.categoryName7, process.env.categoryName8 ]; if (!validCategories.includes(value)) { throw new Error('Ungültige Kategorie.'); } return true; }), body('priority') .escape() .isInt({ min: 1, max: 1 }).withMessage('Priorität muss eine gültige Nummer sein.'), body('statusId') .escape() .isInt({ min: 1, max: 4 }).withMessage('Status muss eine gültige Nummer (1-4) sein.'), ensureValid, utils.uploadStoring.array('input-folder-3[]', 5), authController.postNewRequest );
测试输入
title: asdasd23 description: aer input-folder-3[]: (binary) category[]: Funktionswunsch statusId: 2 priority: 2
验证错误信息
{ "errors": [ { "type": "field", "value": "", "msg": "Die Beschreibung muss zwischen 1 und 1000 Zeichen lang sein.", "path": "description", "location": "body" }, { "type": "field", "value": "", "msg": "Ungültige Kategorie.", "path": "category", "location": "body" }, { "type": "field", "value": "", "msg": "Priorität muss eine gültige Nummer sein.", "path": "priority", "location": "body" }, { "type": "field", "value": "", "msg": "Status muss eine gültige Nummer (1-4) sein.", "path": "statusId", "location": "body" } ] }
排查现象
- 全局日志显示
POST /auth/request/create {},即req.body为空 - 移除express-validator后,日志仍显示
req.body为空,但authController.postNewRequest能正常读取请求体和文件
解决方案
1. 调整中间件顺序(核心问题)
multipart/form-data类型的请求需要通过multer解析后才能获取req.body,必须先执行multer解析中间件,再执行express-validator的验证链。
修改authRoutes.js中的路由顺序:
router.post('/request/create', // 先解析文件和表单数据 utils.uploadStoring.array('input-folder-3[]', 5), // 再执行验证链 body('title') .escape() .isString().withMessage('Der Titel muss ein String sein.') .isLength({ max: 40 }).withMessage('Der Titel darf maximal 40 Zeichen lang sein.'), // ... 其他验证规则 ensureValid, // 最后执行控制器 authController.postNewRequest );
2. 修复数组字段category[]的验证
表单中category[]是数组类型,原代码无法正确读取数组,需调整为:
body('category') .isArray({ min: 1 }).withMessage('Mindestens eine Kategorie muss ausgewählt werden.'), body('category.*') .escape() .isString().withMessage('Ungültige Kategorie.') .custom((value) => { const validCategories = [ process.env.categoryName1, process.env.categoryName2, process.env.categoryName3, process.env.categoryName4, process.env.categoryName5, process.env.categoryName6, process.env.categoryName7, process.env.categoryName8 ]; if (!validCategories.includes(value)) { throw new Error('Ungültige Kategorie.'); } return true; }),
3. 修复priority的验证规则
原代码限制priority只能为1,与测试输入冲突,需调整为表单实际的优先级范围(示例为1-3):
body('priority') .escape() .isInt({ min: 1, max: 3 }).withMessage('Priorität muss eine gültige Nummer sein.'),
4. 给statusId添加可选规则
statusId仅管理员可见,普通用户提交时无此字段,需添加optional():
body('statusId') .optional() .escape() .isInt({ min: 1, max: 4 }).withMessage('Status muss eine gültige Nummer (1-4) sein.'),
内容的提问来源于stack exchange,提问作者j-menter
相关产品推荐
相关产品推荐

