Sails.js `req.query` 属性完全指南:查询字符串解析原理与实战用法
2026/9/20 22:32:42 网站建设 项目流程

Sails.jsreq.query属性完全指南:查询字符串解析原理与实战用法

【免费下载链接】sailsRealtime MVC Framework for Node.js项目地址: https://gitcode.com/gh_mirrors/sa/sails

req.query是 Sails.js 请求对象(Request)上的一个核心属性,它以字典(对象)形式保存经过解析的 URL 查询字符串(query-string),默认值为空对象{}。无论你是在控制器(Controller)、策略(Policy)还是自定义中间件中读取 GET 请求的过滤、分页或搜索参数,都会与它打交道。读完本文,你将掌握req.query的准确定义、解析时机与底层实现,并能结合req.param()req.allParams()正确地在实战中读取各类请求参数。

req.query是什么

按官方参考文档 req.query.md 的定义:

一个包含解析后的查询字符串的字典(dictionary),默认值为{}

也就是说,只要请求 URL 的?之后带有查询参数(如?q=mudslide&page=2),Sails 就会把它们解析为一个普通的 JavaScript 对象,并通过req.query暴露给应用代码。如果请求没有携带任何查询字符串,req.query依然是一个合法的对象({}),因此你可以放心地直接对它做属性访问或遍历,而不会碰到undefined报错。

基本用法

req.query;
  • 属性类型:Object(普通字典)
  • 默认值:{}
  • 适用场景:读取 URL 查询字符串中的参数

官方示例

如果请求是GET /search?q=mudslide

req.query.q // -> "mudslide"

req.query中的每个键值对都直接来自查询字符串:键为参数名,值为参数字符串。上例中req.query的实际内容等价于{ q: 'mudslide' }

req.query是怎么被填充的:源码级解析

理解req.query的关键在于搞清楚它是在什么时机、由哪段代码解析出来的。在当前仓库中,存在两条主要的请求路径,它们都负责维护req.query

1. 通用req构建器:lib/router/req.js

Sails 的"传输无关"请求对象由 lib/router/req.js 中的buildRequest工厂函数创建,它构成了 Sails 对 Connect/Express 中间件的通用支持基础,既被 socket 等 hooks 使用,也被测试与 Sails 核心复用。

在构建req时,该文件会尝试解析 URL 以取得查询字符串,并把结果作为query的默认值写入请求对象(lib/router/req.js#L120-L124):

req = defaultsDeep(req, { params: [], query: (_req && _req.query) || require('querystring').parse(parsedUrl.query) || {}, body: (_req && _req.body) || {}, // ... }, _req||{});

这段代码揭示了三点事实:

  • 如果上游已经提供了_req.query(例如来自 Express),则直接沿用;
  • 否则,使用 Node 内置模块querystringparse()方法解析parsedUrl.query(即 URL 中?之后的原始字符串);
  • 解析失败或没有查询字符串时,回退为空对象{},与官方文档"默认值为{}"的描述完全一致。

2. 虚拟请求路径:lib/router/index.js中的qsParser

对于通过sails.request()发起的虚拟请求(包括 socket.io 请求的底层处理),Sails 在 lib/router/index.js 中内置了一个"极其简单的查询字符串解析器"qsParser(lib/router/index.js#L506-L516):

function qsParser(req,res,next) { var queryStringPos = req.url.indexOf('?'); if (queryStringPos !== -1) { req.query = _.merge(req.query, QS.parse(req.url.substr(queryStringPos + 1))); } else { req.query = req.query || {}; } next(); }

它在路由处理之前作为基础中间件运行(lib/router/index.js#L207-L212):定位 URL 中第一个?,截取其后的查询字符串,交给 Node 内置querystring模块解析,再与已有的req.query合并。整个流程用sails.log.silly('Handling virtual request :: Running virtual querystring parser...')记录了调试日志,便于追踪。

3. HTTP 请求路径

对于标准的 HTTP 请求,Sails 建立在 Express 之上,req.query由 Express 层的查询解析中间件填充,随后 Sails 的请求对象同样会持有该属性。在 lib/app/request.js 的sails.request()实现中,也有一段对查询字符串的处理逻辑(lib/app/request.js#L85-L99),其要点是:当通过sails.request()GETHEADDELETE方法发起请求且传入了对象形式的body时,Sails 会把它序列化为查询字符串并拼接到 URL 上——这点我们会在后文"虚拟请求中的行为"展开。

req.queryreq.paramsreq.body的边界

在实际开发中,一个请求可能同时携带三种不同的参数,正确区分它们是避免 bug 的前提:

属性参数来源典型示例解析结果类型
req.params路由路径中的动态段GET /user/:id中的:id字符串(由路由匹配填充)
req.queryURL 中?之后的查询字符串GET /user?id=7中的id字符串
req.body请求体(表单、JSON 等)POST /user提交的字段依解析器而定

举个例子,对于请求GET /user/7?tab=posts并匹配路由get /user/:id

req.params.id // -> "7"(来自路径) req.query.tab // -> "posts"(来自查询字符串) req.query.id // -> undefined

注意req.query中的值始终是字符串(或由查询解析器产生的数组等结构),即使你传的是数字?page=2,读取到的也是字符串"2",需要时请自行用Number()转换。

统一读取参数的便捷方法:req.param()req.allParams()

虽然req.query是读取查询参数的直接途径,但 Sails 还提供了两个更高层的方法,让你不必在req.paramsreq.queryreq.body之间手动来回切换。

req.param(param, defaultValue)的查找顺序

在 lib/hooks/request/param.js 中,Sails 实现了req.param(),作为 Express 4 移除req.param()之后的门面(facade)方法,其查找顺序是:

  1. 路由参数req.params[param]
  2. 请求体req.body[param]
  3. 查询字符串req.query[param]
  4. 若以上都没有,返回defaultValue
// lib/hooks/request/param.js(核心逻辑) req.param = function(param, defaultValue) { if (typeof req.params[param] !== 'undefined') { return req.params[param]; } if (req.body && typeof req.body[param] !== 'undefined') { return req.body[param]; } return typeof req.query[param] !== 'undefined' ? req.query[param] : defaultValue; };

该 mixin 通过sails.on('router:route', ...)事件在每条路由匹配前注入(lib/hooks/request/index.js#L52-L67),注释明确说明"必须在每个路由上应用,而不是每个请求",因为req.params会随匹配到的路由而变化。这也解释了为什么req.query是开发中最常直接访问的属性——它不依赖路由匹配结果。

req.allParams():合并三种来源

lib/hooks/request/params.all.js 实现了req.allParams(),它把查询字符串、请求体和路由参数合并为一个对象:

var allParams = _.extend({}, req.query, req.body); // 再将 req.params 中已定义的路由参数并入

合并优先级为:req.body覆盖req.query的同名键,路由参数再覆盖前两者。该文件还保留了一段被删除的历史代码注释,记录了 Sails v1.0 的一个重要变更:req.params.all()已在 v1.0 中移除,请改用req.allParams()。这一变更据注释所述是出于性能考虑(Object.defineProperty()较慢)。

虚拟请求(sails.request())中的req.query行为

Sails 允许在服务器端代码中直接发起"虚拟请求"(virtual request),例如在测试中模拟 HTTP 调用。在 lib/app/request.js 的sails.request()实现中,有一个与req.query直接相关的设计(lib/app/request.js#L85-L99):

如果这是GETHEADDELETE请求,则将传入的"body"视为参数,序列化进查询字符串。

// 如果 body 是对象且方法是 GET/HEAD/DELETE: var stringifiedParams = QS.stringify(body); if (queryStringPos === -1) { url += '?' + stringifiedParams; } else { url = url.substring(0, queryStringPos) + '?' + stringifiedParams; }

这意味着当你用sails.request({ method: 'GET', url: '/search' }, { q: 'mudslide' })发起虚拟请求时,{ q: 'mudslide' }会被拼接到 URL 上成为?q=mudslide,最终同样通过req.query.q被控制器读到。这一机制让"以 GET 语义传参数"在虚拟请求环境中也能正常工作。

Blueprints 如何消费req.query

Sails 的 Blueprint API(自动 REST 路由)也是req.query的重度消费者。在 lib/hooks/blueprints/parse-blueprint-options.js 中,可以看到 Blueprint 会从req.query读取关联(association)相关的查询参数(lib/hooks/blueprints/parse-blueprint-options.js#L268-L270):

if (attrDef.collection && (!req.body || !req.body[attrName]) && (req.query && _.isString(req.query[attrName]))) { values[attrName] = JSON.parse(req.query[attrName]); }

也就是说,当某个属性在模型上被定义为collection(一对多/多对多关联)时,Blueprint 会优先从req.query中读取该属性名,并尝试将其作为 JSON 字符串解析——这是populate相关查询(如GET /pet?owner=1)在底层读取查询参数的方式之一。此外,关联 ID 列表也会从req.query[req.options.alias]读取(同文件 L410)。这提醒我们:查询字符串并非只能用于简单的键值过滤,Sails 的框架级功能也会按约定从req.query取值。

常见实战用法

结合req.query的特性,以下是最典型的控制器内用法。

搜索与过滤

// GET /search?q=mudslide module.exports = { search: async function (req, res) { const q = req.query.q || ''; const results = await Article.find({ title: { contains: q } }); return res.ok(results); } };

分页与排序

// GET /articles?page=2&limit=10&sort=createdAt module.exports = { list: async function (req, res) { const page = Number(req.query.page) || 1; const limit = Number(req.query.limit) || 10; const sort = req.query.sort || 'id'; const articles = await Article.find() .sort(sort) .skip((page - 1) * limit) .limit(limit); return res.ok(articles); } };

在策略(Policy)中读取权限参数

// 例如某策略判断是否携带了合法的来源标记 module.exports = function (req, res, next) { if (req.query.from && req.query.from === 'internal') { return next(); } return res.forbidden(); };

测试如何验证req.query

仓库的单元测试直接印证了本文描述的行为。在 test/unit/req.test.js 中:

  • 无查询字符串的请求(/):断言req.query是对象且为空(L41-L44),对应"默认值为{}";
  • 带查询字符串的请求(/hello?abc=123&foo=bar):断言req.query正确包含abc: '123'foo: 'bar'(L76-L80),同时验证req.param('abc')req.param('foo')也能取到相同值(L82-L85)。

这些测试用例可以作为你验证自定义解析行为或排查问题的参照模板。

注意事项与易踩的坑

  1. 值永远是字符串req.query.page得到的是"2"而非2,做算术运算前务必转换类型。
  2. 重复参数?tag=a&tag=b的解析结果取决于底层querystring.parse的行为(通常得到数组),跨环境(HTTP 中间件 vs 虚拟请求)时行为可能不同,建议避免依赖重复参数。
  3. 不要与req.params混淆:路径参数永远优先于查询参数被 Sails 的路由机制处理,二者命名空间独立。
  4. 敏感数据不要放查询字符串req.query内容会出现在日志、URL 历史与 Referrer 中,密码、令牌等请放入请求体或请求头。
  5. req.params.all()已废弃:如果你在旧代码或旧资料中看到req.params.all(),在 Sails v1.0 中请改为req.allParams()

相关参考

  • req.query 官方参考
  • req.allParams 参考
  • req.param 参考
  • req.body 参考
  • req.params 参考
  • 请求对象构建器源码
  • 虚拟请求查询解析器
  • req.param 门面实现
  • req.allParams 实现
  • req.query 单元测试

【免费下载链接】sailsRealtime MVC Framework for Node.js项目地址: https://gitcode.com/gh_mirrors/sa/sails

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询