You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 09:47:14