Node.js Web Server路由改造:适配浏览器与移动端API复用
解决方案
针对你的需求,我推荐从响应格式判断和逻辑解耦两个方向入手改造,同时优化现有路由的耦合问题:
1. 通过请求特征区分响应类型
核心是让路由根据请求的"期望输出"来分支处理,不需要维护两套API。常见的实现方式有两种:
方式一:利用Accept请求头自动判断
浏览器发起页面请求时,通常会携带Accept: text/html;而移动应用的API请求一般会发送Accept: application/json。你可以在数据获取完成后,通过req.accepts()方法判断并返回对应格式:
router.get('/', libs.util.isLoggedIn, libs.util.checkPermission('/user-dashboard', 'view'), (req, res, next) => { controller.UserController.getDashboardDetail(req, res, (error, result) => { if (error) { next(error); return; } result.session = req.session; // 判断客户端期望的响应格式 const prefersJson = req.accepts('json'); const prefersHtml = req.accepts('html'); // 如果更偏好JSON,返回数据;否则渲染视图 if (prefersJson && (!prefersHtml || prefersJson < prefersHtml)) { res.json({ coursearray: result, username: req.session.displayname }); } else { res.render('consume/user-dashboard', { coursearray: result, username: req.session.displayname }); } }) });
方式二:用查询参数显式指定(适合调试/特殊场景)
允许请求通过?format=json强制返回JSON,这种方式更直观,也方便开发调试:
// 在控制器回调中添加判断 const isJsonRequest = req.query.format === 'json'; if (isJsonRequest) { res.json({ coursearray: result, username: req.session.displayname }); } else { res.render('consume/user-dashboard', { coursearray: result, username: req.session.displayname }); }
2. 抽离数据逻辑,解耦渲染与数据获取
当前路由的最大问题是数据处理和视图渲染耦合过紧——控制器的回调直接操作res.render,导致数据逻辑和响应逻辑绑定,完全无法复用。建议做以下改造:
第一步:改造控制器,只负责数据获取
让控制器专注于业务逻辑(比如数据库查询),只返回数据,不处理响应对象:
// UserController.js exports.getDashboardDetail = (req, callback) => { // 这里写原来的数据库查询、数据处理逻辑 const courseData = /* 查询结果 */; // 直接通过callback返回数据或错误,不操作res if (/* 发生错误 */) { callback(new Error('获取数据失败')); } else { callback(null, { coursearray: courseData, session: req.session }); } };
第二步:路由统一处理响应
在路由中拿到控制器返回的数据后,再根据请求类型决定返回JSON还是渲染视图:
router.get('/', libs.util.isLoggedIn, libs.util.checkPermission('/user-dashboard', 'view'), (req, res, next) => { controller.UserController.getDashboardDetail(req, (error, result) => { if (error) { next(error); return; } // 统一处理响应格式 const wantsJson = req.accepts('json'); if (wantsJson) { res.json({ ...result, username: req.session.displayname }); } else { res.render('consume/user-dashboard', { ...result, username: req.session.displayname }); } }) });
3. 当前路由结构的潜在问题总结
- 耦合度高:控制器直接操作
res对象,数据逻辑和响应渲染绑定,难以复用。 - 扩展性差:未来如果要支持新的响应格式(如XML)或修改渲染逻辑,需要逐个修改路由,成本很高。
- 错误处理不够健壮:虽然用了
next(error),但控制器内部如果有逻辑遗漏,可能导致错误无法正确传递到全局错误处理中间件。
额外优化:封装响应处理中间件
可以把响应格式判断的逻辑封装成一个全局中间件,避免在每个路由中重复写判断代码:
// 自定义中间件:统一处理响应格式 const respondWithFormat = (req, res, next) => { res.respond = (data, viewPath) => { const wantsJson = req.accepts('json'); if (wantsJson) { res.json(data); } else { res.render(viewPath, data); } }; next(); }; // 注册到路由或全局 app.use(respondWithFormat); // 在路由中使用 router.get('/', libs.util.isLoggedIn, libs.util.checkPermission('/user-dashboard', 'view'), (req, res, next) => { controller.UserController.getDashboardDetail(req, (error, result) => { if (error) { next(error); return; } // 一行代码搞定两种响应 res.respond( { coursearray: result, username: req.session.displayname }, 'consume/user-dashboard' ); }) });
内容的提问来源于stack exchange,提问作者Himanshu
相关产品推荐
相关产品推荐

