如何使用 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)));
}
};
挑战在于:
- 由于 SequelizeJS 模型上的所有私有属性,响应中没有
modify: true
和 stripUnknown: true
会导致验证错误,例如; _changed、_options、_previousDataValues 等
- 要解决#1 问题并仍然保留
modify: true
和 stripUnknown: true
值,我们可以将 unknown(true)
添加到 Joi 验证中......但是所有 public属性(不是上面 #1 中列出的私有属性)包含在响应中,因为验证允许它们并且我们不会剥离它们
- 如果我们从 Joi 验证中删除
unknown(true)
并添加 modify: true
和 stripUnknown: true
属性(如上面的代码所示),则会抛出一个错误作为私有属性(并且public) 正在从 Sequelize 模型中剥离...因此模型出现故障,因为它期望这些存在
因此,与其手动将数据库对象映射到响应对象,我能看到的唯一方法是:
- 在验证上设置
unknown(true)
- 实现某种全局处理程序(或每个路由处理程序),它将在验证完成并且响应即将发送后进行剥离
我不确定上面 #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
属性
结果变平
你可以不用它试试吗
所以我将数据库字段映射到响应字段,这样我就不会在 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)));
}
};
挑战在于:
- 由于 SequelizeJS 模型上的所有私有属性,响应中没有
modify: true
和stripUnknown: true
会导致验证错误,例如; _changed、_options、_previousDataValues 等 - 要解决#1 问题并仍然保留
modify: true
和stripUnknown: true
值,我们可以将unknown(true)
添加到 Joi 验证中......但是所有 public属性(不是上面 #1 中列出的私有属性)包含在响应中,因为验证允许它们并且我们不会剥离它们 - 如果我们从 Joi 验证中删除
unknown(true)
并添加modify: true
和stripUnknown: true
属性(如上面的代码所示),则会抛出一个错误作为私有属性(并且public) 正在从 Sequelize 模型中剥离...因此模型出现故障,因为它期望这些存在
因此,与其手动将数据库对象映射到响应对象,我能看到的唯一方法是:
- 在验证上设置
unknown(true)
- 实现某种全局处理程序(或每个路由处理程序),它将在验证完成并且响应即将发送后进行剥离
我不确定上面 #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
属性
结果变平
你可以不用它试试吗