Node+Express+EJS+Mongoose项目中用户邮箱修改的表单异常排查
我开发了基于Node、Express、EJS和Mongoose的简易博客,在实现账户管理的修改邮箱功能时遇到奇怪现象:在account.ejs中,只有把表单选择器从const form = document.querySelector('form')改成const form2 = document.querySelector('form'),才能触发authController里的accountPost方法完成邮箱更新。相关代码如下,求排查原因。
相关代码
account.ejs
<html lang="en"> <%- include("./partials/head.ejs") %> <body> <%- include("./partials/nav.ejs") %> <div class="account content"> <div> <h2 class="management-header">Account Management for <%= user.email %></h2> </div> <div class="alter-email content"> <p>Change Email</p> <hr> <form class="alter-email-form" action="/account/<%= user._id %>" method="POST"> <label for="oldEmail">Old Email</label> <input type="text" id="oldEmail" name="oldEmail" required> <label for="newEmail">New Email</label> <input type="text" id="newEmail" name="newEmail" required> <button>Update Email</button> </form> </div> </div> <%- include("./partials/footer.ejs") %> <script> //THIS IS THE PROBLEM const form2 = document.querySelector('form'); form.addEventListener('submit', async (e) => { e.preventDefault(); //get values const oldEmail = form.oldEmail.value; const newEmail = form.newEmail.value; try { const res = await fetch('/account', { method: 'POST', body: JSON.stringify({ oldEmail, newEmail }), headers: { 'Content-Type': 'application/json' } }); const data = await res.json(); console.log(data); if(data.user) { location.assign('/blogs'); } } catch (err) { console.log(err); } }); </script> </body> </html>
authController.js 中的 accountPost 方法
const accountPost = async (req, res) => { const id = req.params.id; const {newEmail, oldEmail} = req.body; console.log(newEmail, oldEmail); let user = await User.findById(id); user.updateOne({ '_id': id, 'email': newEmail }) .then(result => { res.redirect('/'); }) } module.exports = { accountPost }
app.js
const express = require('express'); const morgan = require('morgan'); const mongoose = require('mongoose'); const blogRoutes = require('./routes/blogRoutes'); const authRoutes = require('./routes/authRoutes'); const cookieParser = require('cookie-parser'); const { checkUser } = require('./middleware/authMiddleware'); require('dotenv').config(); //express app const app = express(); //mongoDB connection string const dbURI = `mongodb+srv://${process.env.blog_username}:${process.env.blog_password}@nodecourse.h4qkmfb.mongodb.net/nodeCourse?retryWrites=true&w=majority`; mongoose.connect(dbURI) .then((result) => app.listen(3000)) .catch((err) => console.log(err)); //register view engine app.set('view engine', 'ejs'); app.get('/', (req, res) => { res.redirect('/blogs'); }); app.get('/about', (req, res) => { res.render('about', { title: 'About'}); }); app.use(authRoutes); //404 page app.use((req, res) => { res.status(404).render('404', {title: '404'}); })
问题根源分析
变量定义与引用不匹配
看account.ejs的脚本代码:你定义的变量是form2,但事件监听却绑定到了未定义的form变量上。当你用const form = document.querySelector('form')时,页面全局作用域中大概率已经存在同名的form变量(比如nav.ejs里有其他表单,或者之前的脚本定义过form),导致你的局部变量被覆盖,无法正确获取当前页面的修改邮箱表单。改成form2时,变量名无冲突,才能正确绑定事件。默认表单提交的干扰
你的form标签写了action="/account/<%= user._id %>" method="POST",当事件监听绑定失败时,浏览器会触发默认表单提交(直接发POST请求到该action地址),这时候你看到的“更新成功”其实是默认提交触发的,而非你的fetch逻辑。fetch地址与路由不匹配
你的fetch请求发往/account,但authController里的accountPost从req.params.id取用户ID,说明路由应为/account/:id,fetch地址写错了。Mongoose更新逻辑错误
user.updateOne({...})用法错误,并且缺少旧邮箱验证,存在安全隐患。
修复方案
修正脚本变量与事件绑定
用类选择器精准定位表单,避免全局变量冲突:const form = document.querySelector('.alter-email-form'); form.addEventListener('submit', async (e) => { e.preventDefault(); const oldEmail = form.oldEmail.value; const newEmail = form.newEmail.value; try { // 修正fetch地址,带上用户ID const res = await fetch('/account/<%= user._id %>', { method: 'POST', body: JSON.stringify({ oldEmail, newEmail }), headers: { 'Content-Type': 'application/json' } }); const data = await res.json(); console.log(data); if(data.success) { location.assign('/blogs'); } } catch (err) { console.log(err); } });修正后台更新逻辑
增加旧邮箱验证,用正确的Mongoose更新方式:const accountPost = async (req, res) => { const id = req.params.id; const { newEmail, oldEmail } = req.body; // 验证旧邮箱是否正确 const user = await User.findById(id); if (!user || user.email !== oldEmail) { return res.json({ success: false, message: '旧邮箱验证失败' }); } // 更新并保存邮箱 user.email = newEmail; await user.save(); res.json({ success: true, message: '邮箱更新成功' }); };添加JSON解析中间件
在app.js中添加解析JSON请求体的中间件,否则req.body会为空:// 在app.use(authRoutes)之前添加 app.use(express.json()); app.use(authRoutes);
内容的提问来源于stack exchange,提问作者Suede

