如何实现POST表单无跳转提交并在页面内展示错误信息
表单无跳转提交:解决验证错误与JSON解析问题
问题描述
要实现表单提交时不跳转页面,直接在当前页面的div中显示成功/错误信息。原Express服务器能正常处理表单但会跳转页面,编写前端脚本后出现以下问题:
- 明明填了subject和body,却一直提示「Subject is required」「Message body is required」
- 偶尔出现JSON解析错误
问题根源
前端用JSON格式提交数据,但服务器只配置了express.urlencoded()中间件,无法解析JSON请求体,导致验证时拿不到字段值;同时服务器部分响应是纯文本,前端按JSON解析就会报错。
修复方案
前端:改用URL-encoded格式提交
把FormData转成URL-encoded字符串提交,既保留所有表单字段(包括蜜罐字段),又匹配服务器原有的解析能力:
<form id="contactForm" action="/send-email" method="POST" style="z-index: 200;"> <div> <input autocomplete="off" type="text" placeholder="Subject" name="subject" required /> </div> <div> <textarea autocomplete="off" placeholder="Your message" name="body" required></textarea> </div> <div class="oh-no-hun" > <input tabindex="-1" autocomplete="off" type="text" name="body2" /> </div> <div> <button type="submit">Send a message</button> </div> </form> <div id="message" class="message"></div> <script> document.getElementById('contactForm').addEventListener('submit', async function(event) { event.preventDefault(); // 阻止默认跳转 const form = event.target; const formData = new FormData(form); const messageDiv = document.getElementById('message'); // 把FormData转成URL-encoded字符串 const urlEncodedData = new URLSearchParams(formData).toString(); try { const response = await fetch(form.action, { method: form.method, headers: { 'Content-Type': 'application/x-www-form-urlencoded', // 指定格式 }, body: urlEncodedData, }); // 处理服务器响应 const contentType = response.headers.get('content-type'); let responseJson = {}; if (contentType && contentType.includes('application/json')) { try { responseJson = await response.json(); } catch (jsonError) { console.error('解析JSON失败:', jsonError); messageDiv.innerHTML = `<p class="error">解析服务器响应失败.</p>`; return; } } else { const responseText = await response.text(); console.error('响应格式异常:', responseText); messageDiv.innerHTML = `<p class="error">响应格式异常: ${responseText}</p>`; return; } if (response.ok) { // 显示成功信息并重置表单 messageDiv.innerHTML = `<p class="success">${responseJson.message || '邮件发送成功!'}</p>`; form.reset(); } else { // 显示错误信息 const errors = responseJson.errors || [{ msg: '未知错误.' }]; messageDiv.innerHTML = `<p class="error">${errors.map(err => err.msg).join(', ')}</p>`; } } catch (error) { messageDiv.innerHTML = `<p class="error">发生意外错误: ${error.message}</p>`; } }); </script>
后端:兼容两种数据格式+统一JSON响应
添加JSON解析中间件,把所有响应改成JSON格式,同时优化验证和蜜罐检查逻辑:
const express = require('express'); const nodemailer = require('nodemailer'); const path = require('path'); const { body, validationResult } = require('express-validator'); require('dotenv').config(); const rateLimit = require('express-rate-limit'); const app = express(); const port = process.env.PORT; // 复用验证函数 const validate = validations => { return async (req, res, next) => { for (const validation of validations) { const result = await validation.run(req); if (!result.isEmpty()) { return res.status(400).json({ errors: result.array() }); } } next(); }; }; // 信任代理,获取真实IP用于限流 app.set('trust proxy', true); // 静态文件服务 app.use(express.static(path.join(__dirname, 'public'))); // 同时支持解析URL-encoded和JSON格式 app.use(express.urlencoded({ extended: true })); app.use(express.json()); // 限流配置 const formSubmitLimiter = rateLimit({ windowMs: 24 * 60 * 60 * 1000, // 24小时 max: 10, // 每个IP限制10次提交 standardHeaders: true, legacyHeaders: false, message: '提交次数过多,请稍后再试.', }); // 表单提交路由 app.post('/send-email', // 蜜罐检查:只有当body2有内容时才拦截 (req, res, next) => { if (req.body.body2 && req.body.body2.length > 0) { return res.status(400).json({ message: '表单提交失败.' }); } next(); }, formSubmitLimiter, // 验证中间件 validate([ body('subject').isLength({ min: 1 }).withMessage('Subject is required'), body('body').isLength({ min: 1 }).withMessage('Message body is required') ]), async (req, res) => { const errors = validationResult(req); if (!errors.isEmpty()) { return res.status(400).json({ errors: errors.array() }); } const { subject, body } = req.body; // 邮件配置 let transporter = nodemailer.createTransport({ service: process.env.SMTP_SERVICE, auth: { user: process.env.EMAIL_USER, pass: process.env.EMAIL_PASS } }); try { await transporter.sendMail({ from: `"SITENAMEREDACTED" <${process.env.EMAIL_USER}>`, to: process.env.RECIPIENT_EMAIL, subject: subject, text: body, }); console.log('邮件发送成功'); res.status(200).json({ message: 'Email sent successfully!' }); } catch (error) { console.error('邮件发送失败:', error); res.status(500).json({ message: 'Error sending email' }); } } ); app.listen(port, () => { console.log(`服务器运行在 http://localhost:${port}`); });
内容的提问来源于stack exchange,提问作者Laterthanlate
相关产品推荐
相关产品推荐

