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

为Sails.js API添加自定义搜索参数:半径查询路由丢失基础功能

解决Sails.js API半径查询保留默认路由功能的方案

我来帮你搞定这个问题——你现在的核心痛点是直接用原生SQL查询绕过了Waterline(Sails的ORM)的核心能力,导致丢失了默认索引路由自带的过滤、分页、排序、关联填充等功能。下面是几个既能实现半径地理查询,又能完整保留Sails默认API特性的可行方案:

方案一:自定义模型方法,结合Waterline查询

在你的Cheer模型里新增一个自定义方法,把地理查询逻辑和Waterline的标准查询能力结合起来,这样就能复用所有默认路由的功能:

// api/models/Cheer.js
module.exports = {
  attributes: {
    lat: { type: 'number', required: true },
    lng: { type: 'number', required: true },
    // 你的其他模型属性...
  },

  // 自定义方法:支持半径查询 + 所有Waterline标准查询参数
  findNearby: async function(lat, lng, radiusInMeters, queryOpts = {}) {
    // 初始化基础Waterline查询
    let baseQuery = Cheer.find(queryOpts);

    // 添加参数化的地理查询条件(避免SQL注入)
    baseQuery.where({
      sql: "earth_box(ll_to_earth($1, $2), $3) @> ll_to_earth(lat, lng)",
      values: [lat, lng, radiusInMeters]
    });

    // 执行查询,自动继承分页、排序、关联填充等功能
    return await baseQuery;
  }
};

然后在控制器里调用这个方法,同时提取并传递所有默认查询参数:

// api/controllers/CheerController.js
const _ = require('lodash'); // 用lodash过滤参数,也可以自己手写逻辑

module.exports = {
  nearby: async function(req, res) {
    try {
      // 提取地理查询参数
      const { lat, lng, rad: radiusInMeters } = req.query;
      // 过滤掉地理参数,剩下的交给Waterline处理
      const standardQueryOpts = _.omit(req.query, ['lat', 'lng', 'rad']);

      const results = await Cheer.findNearby(lat, lng, radiusInMeters, standardQueryOpts);
      return res.ok(results);
    } catch (err) {
      return res.serverError(err);
    }
  }
};

现在你的/cheer/nearby路由就能支持所有默认功能了,比如:
GET /cheer/nearby?lat=40.7128&lng=-74.0060&rad=5000&limit=10&sort=createdAt DESC&populate=author

方案二:原生SQL筛选ID + Waterline查询详情

如果需要更复杂的原生地理逻辑,可以先通过原生查询拿到符合条件的记录ID,再用Waterline查询这些ID的详情,同时应用所有标准查询参数:

// api/controllers/CheerController.js
const _ = require('lodash');

module.exports = {
  nearby: async function(req, res) {
    try {
      const { lat, lng, rad: radiusInMeters } = req.query;
      const standardQueryOpts = _.omit(req.query, ['lat', 'lng', 'rad']);

      // 第一步:用原生SQL获取符合半径条件的记录ID
      const rawResult = await sails.sendNativeQuery(
        "SELECT id FROM cheer WHERE earth_box(ll_to_earth($1, $2), $3) @> ll_to_earth(lat, lng);",
        [lat, lng, radiusInMeters]
      );
      const validIds = rawResult.rows.map(row => row.id);

      if (validIds.length === 0) {
        return res.ok([]);
      }

      // 第二步:用Waterline查询详情,自动支持分页、排序、关联等
      const results = await Cheer.find({
        ...standardQueryOpts,
        id: validIds
      });

      return res.ok(results);
    } catch (err) {
      return res.serverError(err);
    }
  }
};

这个方法的优势是把地理筛选和数据查询解耦,原生SQL只负责最核心的地理判断,剩下的所有复杂逻辑都交给Waterline处理。

方案三:生命周期钩子扩展默认路由(高级用法)

如果想让默认的/cheer索引路由直接支持半径查询,可以在模型的beforeFind钩子中添加逻辑,自动识别并处理地理参数:

// api/models/Cheer.js
module.exports = {
  attributes: {
    lat: { type: 'number', required: true },
    lng: { type: 'number', required: true },
    // 你的其他模型属性...
  },

  beforeFind: function(options) {
    const { lat, lng, radius } = options.where || {};
    // 如果请求中包含地理参数,添加半径查询条件
    if (lat && lng && radius) {
      options.where = {
        ...options.where,
        // 添加参数化的地理查询
        sql: "earth_box(ll_to_earth($1, $2), $3) @> ll_to_earth(lat, lng)",
        values: [lat, lng, radius]
      };
      // 移除临时的地理参数,避免Waterline尝试查询不存在的字段
      delete options.where.lat;
      delete options.where.lng;
      delete options.where.radius;
    }
  }
};

现在你直接用默认路由就能实现半径查询,同时保留所有原生功能:
GET /cheer?lat=40.7128&lng=-74.0060&radius=5000&limit=20&sort=createdAt DESC

关键注意事项

  1. 确保PostgreSQL扩展已启用:
    CREATE EXTENSION IF NOT EXISTS cube;
    CREATE EXTENSION IF NOT EXISTS earthdistance;
    
  2. 添加性能索引:为了提升地理查询速度,建议创建GIN索引:
    CREATE INDEX cheer_earth_idx ON cheer USING GIN (ll_to_earth(lat, lng));
    
  3. 始终使用参数化查询:避免直接拼接SQL,防止SQL注入风险。

内容的提问来源于stack exchange,提问作者WalksAway

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.25 03:55:12