如何使用 HapiJS Joi 和 SequelizeJS 剥离未知数?

How to stripUnknown using HapiJS Joi and SequelizeJS?

所以我将数据库字段映射到响应字段,这样我就不会在 SequelizeJS 模型上使用 field 属性 向消费者公开数据层,例如:

module.exports = function (sequelize, DataTypes)
{
    return sequelize.define('Product',
    {
      id: 
      {
        type: DataTypes.INTEGER,
        primaryKey: true,
        field: 'sku_id'
      },
      name: 
      {
        type: DataTypes.STRING,
        field: 'sku_name'
      },
      code: 
      {
        type: DataTypes.STRING,
        field: 'sku_code'
      }
    },
    {
      timestamps: false,
      freezeTableName: true
    });
 };

路由控制器看起来像:

module.exports = server => 
{
  const Joi = require('joi');
  const db = require('rfr')('models/index');
  const _ = require('lodash');

  const schema = Joi.array()
    .items(Joi.object(
      {
        id: Joi.number().integer().positive().required().description('The id of the product'),
        name: Joi.string().required().description('The name of the product'),
        code: Joi.string().required().description('The code of the product')
      })
      .label('Product'))
    .label('ProductCollection');

  return server.route(
    {
      method: 'GET',
      path: '/products/group/{id}',
      config:
      {
        handler,
        validate:
        {
          params:
          {
            id: Joi.number().integer().positive().required().description('the product group id')
          }
        },
        response:
        {
          schema,
          modify: true,
          options: { stripUnknown: true }
        },
        tags: ['api', 'products'],
        description: 'Get a list of products by group'
      }
    });

  ////////////////////////

  function handler(request, reply)
  {
    var sql = db.sequelize;
    var options =
      {
        replacements:
        {
          id: request.params.id
        },
        model: db.Product,
        mapToModel: true,
        type: sql.QueryTypes.SELECT
      };

    sql.query('sp_GetProducts @RowId = :id', options)
      .then(results => 
      {
        let sorted = _.orderBy(results, 'code');
        return reply(sorted);
      })
      .catch(err => reply(require('boom').wrap(err)));
  }
};

挑战在于:

  1. 由于 SequelizeJS 模型上的所有私有属性,响应中没有 modify: truestripUnknown: true 会导致验证错误,例如; _changed、_options、_previousDataValues 等
  2. 要解决#1 问题并仍然保留 modify: truestripUnknown: true 值,我们可以将 unknown(true) 添加到 Joi 验证中......但是所有 public属性(不是上面 #1 中列出的私有属性)包含在响应中,因为验证允许它们并且我们不会剥离它们
  3. 如果我们从 Joi 验证中删除 unknown(true) 并添加 modify: truestripUnknown: true 属性(如上面的代码所示),则会抛出一个错误作为私有属性(并且public) 正在从 Sequelize 模型中剥离...因此模型出现故障,因为它期望这些存在

因此,与其手动将数据库对象映射到响应对象,我能看到的唯一方法是:

  1. 在验证上设置unknown(true)
  2. 实现某种全局处理程序(或每个路由处理程序),它将在验证完成并且响应即将发送后进行剥离

我不确定上面 #2 中的正确扩展点是什么,或者是否有更简洁的方法来实现所需的结果。

或使用 map-obj 之类的东西并让处理程序询问 Joi 模式以确定是否应将源对象(模型)key => value 复制到新的(响应)对象,我觉得与让 Sequelize 在构建模型时这样做相比,这只是更多的开销(cpu 周期并且对于大型对象数组来说是昂贵的)。

编辑

这是我为实现@Shivan 建议的映射而创建的 util 函数。它使用 object-mapper 包。

function mapFromModel(data, model)
{
  if (!data) { return []; }
  if (_.isArray(data) === false) { data = [data]; }
  if (!model.attributes && !model.sequelize) { throw new Error('Expecting `model` argument to be a sequelize model.'); }

  let transform = {};

  Object
    .keys(model.attributes)
    .forEach(key => 
    {
      let obj = model.attributes[key];
      transform[obj.field] = `${obj.fieldName}?`;
    });

  return data.map(value => objectMapper(value, {}, transform));
}

我在这里的用例是我使用模型来定义我希望对象看起来像的定义,然后这个函数进行映射,这也修改了属性 名称基于模型中每个数据字段的 字段 属性。通过这种方式,如果我需要为 updates/deletes 重新使用此对象,我还可以轻松地从对象此函数 returns 构建模型,我认为在不久的将来不需要。

您似乎在查询中使用 raw 属性 结果变平

你可以不用它试试吗