Node.js+EJS项目中/admin/blogs/edit/:id路由无法获取请求体
问题描述
我使用Node.js、EJS模板、MongoDB和Express开发Web应用,已创建包含用户及其他数据库操作的多组路由。/register路由的POST请求可正常获取请求体数据,但/admin/blogs/edit/:id路由无法获取任何请求体内容,浏览器与控制台均无报错信息,仅返回空对象。相关代码及控制台输出如下:
app.js代码
if (process.env.NODE_ENV !== 'production') { require('dotenv').config() } const express = require('express'); const mongoose = require('mongoose'); const ejsLayouts = require('express-ejs-layouts'); const cors = require('cors'); const passport = require('passport'); const flash = require('express-flash'); const session = require('express-session'); const methodOverride = require('method-override'); const webRoutes = require('./routes/webRoutes'); const userRoutes = require('./routes/userRoutes'); const initializePassport = require('./passport-config'); const app = express(); app.use(cors()); app.use(express.json()); app.use(express.urlencoded({ extended: true })); app.use(express.static(__dirname + '/public')); app.set('view engine', 'ejs'); app.use(ejsLayouts); app.use(function(req, res, next) { res.locals.currentPage = req.path; next(); }); app.use("/", webRoutes); app.use("/admin", userRoutes); mongoose.connect('mongodb://127.0.0.1:27017/myDB'); const PORT = process.env.PORT || 3000; app.listen(PORT, () => { console.log(`Server is running on port ${PORT}`); });
userRoutes.js代码
if (process.env.NODE_ENV !== 'production') { require('dotenv').config() } let express = require('express'); const app = express(); const router = express.Router(); const bcrypt = require('bcrypt'); const passport = require('passport'); const flash = require('express-flash'); const session = require('express-session'); const methodOverride = require('method-override'); const fs = require('fs'); const path = require('path'); const { Blog, Contact, JobOpening, User } = require('../models/dbModels'); const initializePassport = require('../passport-config') initializePassport( passport, email => users.find(user => user.email === email), id => users.find(user => user.id === id) ) router.use(flash()) router.use(session({ secret: process.env.SECRET_KEY, resave: false, saveUninitialized: false })) router.use(passport.initialize()) router.use(passport.session()) router.use(methodOverride('_method')) // Function to get JavaScript filenames function getAdminJSFiles() { const adminJsDir = path.join(__dirname, '..', 'public', 'javascript', 'admin'); const files = fs.readdirSync(adminJsDir); const jsFiles = files.filter(file => file.endsWith('.js')); return jsFiles; } // Function to get JavaScript filenames function getAdminCSSFiles() { const adminCSSDir = path.join(__dirname, '..', 'public', 'styles', 'admin'); const files = fs.readdirSync(adminCSSDir); const cssFiles = files.filter(file => file.endsWith('.css')); return cssFiles; } const current_user = true; // Middleware to set layout for all admin routes router.use((req, res, next) => { if (req.originalUrl.startsWith('/admin/login')) { // Exclude layout for login routes res.locals.layout = 'layout_login'; return next(); } res.locals.layout = 'layout_admin'; // Specify the admin layout res.locals.jsFiles = getAdminJSFiles(); // Pass the jsFiles variable res.locals.cssFiles = getAdminCSSFiles(); // Pass the jsFiles variable res.locals.user = { name: "dummyUser", email: 'dummyUser@gmail'} next(); }); router.use((req, res, next) => { if (req.isAuthenticated()) { if (req.originalUrl === '/admin/login') { return res.redirect('/admin'); } return next(); } if (req.originalUrl === '/admin/login') { return next(); } res.redirect('/admin/login'); }); router.get('/', (req, res) => { res.render('./admin/admin_home', { current_user: req.body.email, email: req.body.name, params: req.params }); }); router.get('/register', (req, res) => { res.render('./admin/admin_register'); }); router.post('/register', async (req, res) => { try { const hashedPassword = await bcrypt.hash(req.body.password, 10); const newUser = new User({ email: req.body.email, password: hashedPassword, role: req.body.role }); await newUser.save(); res.redirect('/admin/login'); } catch (error) { console.error(error); res.redirect('/admin/register'); } }); router.get('/login', (req, res) => { res.render('./admin/admin_login'); }); router.post('/login', passport.authenticate('local', { successRedirect: '/admin', failureRedirect: '/admin/login', failureFlash: true })); router.get('/logout', (req, res, next) => { req.logout((err) => { if (err) { return next(err); } res.redirect('/admin'); }); }); router.get('/blogs', async (req, res) => { try { const blogs = await Blog.find(); res.render('./admin/blogs/blog_home', { blogs }); } catch (err) { console.error(err); res.status(500).send('Server Error'); } }); router.get('/blogs/add', async (req, res) => { try { res.render('./admin/blogs/blog_add', { action: `/admin/blog/add`, blog: {} }); } catch (err) { console.error(err); res.status(500).send('Server Error'); } }); router.get('/blogs/edit/:id', async (req, res) => { try { const blog = await Blog.findById(req.params.id); res.render('./admin/blogs/blog_add', { action: `/admin/blogs/edit/${blog._id}`, blog }); } catch (err) { console.error(err); res.status(500).send('Server Error'); } }); router.post('/blogs/edit/:id', async (req, res) => { try { const userID = req.params.id; console.log(userID) const { title } = req.body; console.log('Request Body:', req.body); // Log request body console.log('Update Query:', { title }); // Log update query res.redirect('/admin/blogs'); } catch (err) { console.error(err); res.status(500).send('Server Error'); } }); module.exports = router;
blog_add.ejs代码
<div class="content-wrapper"> <section class="content-header"> <h1>New</h1> <div class="breadcrumb"> <a class="btn btn-sm btn-info" href="/admin/blogs">Back</a> </div> </section> <section class="content"> <%- include ('../../partials/admin_form')%> </section> </div>
admin_form.ejs代码
<% // include partials/layouts/include_ckeditor.ejs %> <div class="box box-default"> <div class="box-header with-border"> <h3 class="box-title">Form</h3> <div class="box-tools pull-right"> <button class="btn btn-box-tool" data-widget="collapse"> <i class="fa fa-minus"></i> </button> </div> </div> <h1><%= blog ? 'Edit blog' : 'Add blog' %></h1> <form action="<%= action %>" method="post" enctype="multipart/form-data"> <div class="box-body"> <div class="row"> <div class="col-md-6"> <div class="form-group"> <label>Title</label> <input type="text" name="title" class="form-control" placeholder="Enter Title" value="<%= blog.title || '' %>"> </div> <div class="form-group"> <label>SEO Name</label> <input type="text" name="seo_name" class="form-control" placeholder="Enter SEO Name" value=""> </div> <div class="form-group"> <label>Blog Type</label> <select name="blog_type" class="form-control"> <option value="BLOG">BLOG</option> <option value="NEWS">NEWS</option> </select> </div> <div class="form-group"> <label>Author Name</label> <input type="text" name="author_name" class="form-control" placeholder="Enter Author Name" value=""> </div> <div class="form-group"> <label>Image</label> <input type="file" name="image" class="form-control"> <p style="color:red;">(530px X 429 px)</p> </div> <div class="form-group"> <label>Banner Image</label> <input type="file" name="banner_image" class="form-control"> <p style="color:red;">(1015px X 452 px)</p> </div> <div class="form-group"> <label>Publish Date</label> <input type="text" name="publish_date" class="form-control" placeholder="Enter Publish Date" id="adrt-datepicker" autocomplete="off" value=""> </div> <div class="form-group"> <label>Status</label> <input type="checkbox" name="status"> </div> <div class="form-group"> <label>Trending Blog</label> <input type="checkbox" name="trending"> </div> </div> <div class="col-md-6"> <div class="form-group"> <label>Long Description</label> <textarea name="long_description" class="form-control"></textarea> </div> </div> </div> </div> <div class="box-footer"> <button type="submit" class="btn btn-primary">Submit</button> </div> </form> </div> <script> $(document).ready(function(){ $("#adrt-datepicker").datepicker({ dateFormat: 'dd/mm/yy', maxDate: 0 }); }); </script>
控制台输出
id: 65eb8dce672a051114e1a090 Request Body: {} Update Query: { title: undefined }
问题原因及解决方法
表单设置了enctype="multipart/form-data"(用于文件上传),但Express默认的express.urlencoded中间件无法解析这种格式的请求体,这就是req.body为空的核心原因。而/register路由的表单没有文件上传字段,所以能被正常解析。
解决步骤
- 安装multer中间件:multer是Express生态中专门处理
multipart/form-data格式请求的工具:
npm install multer
- 在userRoutes.js中配置multer:
const multer = require('multer'); // 用内存存储文件,也可以配置磁盘存储路径,按需选择 const storage = multer.memoryStorage(); const upload = multer({ storage: storage });
- 修改编辑博客的POST路由:添加multer中间件来解析请求体:
router.post('/blogs/edit/:id', upload.any(), async (req, res) => { try { const blogID = req.params.id; console.log(blogID) const { title } = req.body; console.log('Request Body:', req.body); console.log('Update Query:', { title }); // 执行数据库更新操作 await Blog.findByIdAndUpdate(blogID, req.body); res.redirect('/admin/blogs'); } catch (err) { console.error(err); res.status(500).send('Server Error'); } });
- 添加博客的路由同理:如果添加博客的表单也有文件上传,同样需要添加
upload.any()中间件:
router.post('/blog/add', upload.any(), async (req, res) => { // 处理博客添加逻辑 });
额外说明
- 若只想处理特定文件字段,可替换
upload.any()为upload.fields([{name: 'image'}, {name: 'banner_image'}]),精准指定要解析的文件字段。 - 如果不需要上传文件,也可以直接删除表单的
enctype="multipart/form-data"属性,这样express.urlencoded就能正常解析请求体,但会失去文件上传功能。
内容的提问来源于stack exchange,提问作者Afzal Hamdulay
相关产品推荐
相关产品推荐

