node.js - 使用关系/关联查询时未执行 Sequelize afterFind 挂钩
问题描述
我有两个模型User
和Email
. Email
有一个来自 的外键User
。
数据库中电子邮件的值在保存到数据库之前被加密。并在检索电子邮件时对其进行解密。因此,电子邮件在数据库中永远不会是纯文本,但在 API 中使用时它们可以是纯文本。我正在使用钩子来实现该功能。
我指定的钩子如下:
hooks: {
/**
* The query will have plain text email,
* The database has encrypted email.
* Thus, encrypt the query email (if any) BEFORE the query is fired
**/
beforeFind: query => {
if (query && query.where && query.where.email) {
const email = query.where.email;
const AESHash = AES.encrypt(email, KEY, { iv: IV });
const encrypted = AESHash.toString();
query.where.email = encrypted;
console.log(`[hook beforeFind] email "${email}" was changed to "${encrypted}"`);
} else {
console.log(`[hook beforeFind] skipped "${query ? JSON.stringify(query) : query}"`);
}
},
/**
* Once the result is retrieved, the emails (if any) would be encrypted.
* But, the API expects plain text emails.
* Thus, decrypt them BEFORE the query response is returned.
*/
afterFind: query => {
if (query && (query.dataValues || query.email)) {
const email = query.dataValues || query.email;
const decrypt = AES.decrypt(email, KEY, { iv: IV });
const decrypted = decrypt.toString(enc.Utf8);
if (query.dataValues) {
query.dataValues.email = decrypted;
} else {
query.email = decrypted;
}
console.log(`[hook afterFind] email "${email}" was changed to "${decrypted}"`);
} else {
console.log(`[hook afterFind] skipped "${query ? JSON.stringify(query) : query}"`);
}
},
/**
* The API provides plain text email when creating an instance.
* But emails in database have to be encrypted.
* Thus, we need to encrypt the email BEFORE it gets saved in database
*/
beforeCreate: model => {
const email = model.dataValues.email;
if (email.includes("@")) {
const AESHash = AES.encrypt(email, KEY, { iv: IV });
const encrypted = AESHash.toString();
model.dataValues.email = encrypted;
console.log(`[hook beforeCreate] email "${email}" was changed to "${encrypted}"`);
} else {
console.log(`[hook beforeCreate] skipped "${email}"`);
}
},
/**
* Once they are created, the create() response will have the encrypted email
* As API uses plain text email, we will need to decrypt them.
* Thus, Decrypt the email BEFORE the create() response is returned.
*/
afterCreate: model => {
const email = model.dataValues.email;
if (!email.includes("@")) {
const decrypt = AES.decrypt(email, KEY, { iv: IV });
const decrypted = decrypt.toString(enc.Utf8);
model.dataValues.email = decrypted;
console.log(`[hook afterCreate] email "${email}" was changed to "${decrypted}"`);
} else {
console.log(`[hook afterCreate] skipped "${email}"`);
}
}
}
当我需要创建/查询Email
模型时,它们可以完美地工作。例如:
async function findEmail() {
console.log("[function findEmail] Executing");
const existingEmail = await Email.findOne({
raw: true
});
console.log("[function findEmail] Result:", existingEmail);
}
并输出:
[function findEmail] Executing
[hook beforeFind] skipped "{"raw":true,"limit":1,"plain":true,"rejectOnEmpty":false,"hooks":true}"
[hook afterFind] email "ZxJlbVDJ9MNdCTreKUHPDW6SiNCTslSPCZygnfxE9n0=" was changed to "someone@example.com"
[function findEmail] Result: { id: 1, email: 'someone@example.com', user_id: 1 }
但是User
,当我查询模型并包含模型时,它们不起作用Email
。例如:
async function findUser() {
console.log("[function findUser] Executing");
const existingUser = await User.findOne({
include: [{ model: Email }],
raw: true
});
console.log("[function findUser] Result:", existingUser);
}
输出是:
[function findUser] Executing
[hook afterFind] skipped "null"
[hook beforeCreate] email "someone@example.com" was changed to "QuLr/hi7QaJ4vKmxneW0jqwyqQdwhQDQbp+qW1vGpPE="
[hook afterCreate] email "QuLr/hi7QaJ4vKmxneW0jqwyqQdwhQDQbp+qW1vGpPE=" was changed to "someone@example.com"
[function findUser] Result: { id: 1,
name: 'John Doe',
'Email.id': 1,
'Email.email': 'QuLr/hi7QaJ4vKmxneW0jqwyqQdwhQDQbp+qW1vGpPE=',
'Email.user_id': 1 }
我的问题是: 为什么在查询其他模型时包含指定挂钩的模型时没有执行挂钩?
这是我正在使用的完整代码:- 在代码和框上
const Sequelize = require("sequelize");
const cryptoJS = require("crypto-js");
const crypto = require("crypto");
const AES = cryptoJS.AES;
const enc = cryptoJS.enc;
const KEY = enc.Utf8.parse(crypto.randomBytes(64).toString("base64"));
const IV = enc.Utf8.parse(crypto.randomBytes(64).toString("base64"));
const DataTypes = Sequelize.DataTypes;
const connectionOptions = {
dialect: "sqlite",
operatorsAliases: false,
storage: "./database.sqlite",
logging: null,
define: {
timestamps: false,
underscored: true
}
};
const sequelize = new Sequelize(connectionOptions);
const User = sequelize.define("User", {
name: {
type: DataTypes.STRING
}
});
const Email = sequelize.define(
"Email",
{
email: {
type: DataTypes.STRING
}
},
{
hooks: {
/**
* The query will have plain text email,
* The database has encrypted email.
* Thus, encrypt the query email (if any) BEFORE the query is fired
**/
beforeFind: query => {
if (query && query.where && query.where.email) {
const email = query.where.email;
const AESHash = AES.encrypt(email, KEY, { iv: IV });
const encrypted = AESHash.toString();
query.where.email = encrypted;
console.log(`[hook beforeFind] email "${email}" was changed to "${encrypted}"`);
} else {
console.log(`[hook beforeFind] skipped "${query ? JSON.stringify(query) : query}"`);
}
},
/**
* Once the result is retrieved, the emails (if any) would be encrypted.
* But, the API expects plain text emails.
* Thus, decrypt them BEFORE the query response is returned.
*/
afterFind: query => {
if (query && (query.dataValues || query.email)) {
const email = query.dataValues || query.email;
const decrypt = AES.decrypt(email, KEY, { iv: IV });
const decrypted = decrypt.toString(enc.Utf8);
if (query.dataValues) {
query.dataValues.email = decrypted;
} else {
query.email = decrypted;
}
console.log(`[hook afterFind] email "${email}" was changed to "${decrypted}"`);
} else {
console.log(`[hook afterFind] skipped "${query ? JSON.stringify(query) : query}"`);
}
},
/**
* The API provides plain text email when creating an instance.
* But emails in database have to be encrypted.
* Thus, we need to encrypt the email BEFORE it gets saved in database
*/
beforeCreate: model => {
const email = model.dataValues.email;
if (email.includes("@")) {
const AESHash = AES.encrypt(email, KEY, { iv: IV });
const encrypted = AESHash.toString();
model.dataValues.email = encrypted;
console.log(`[hook beforeCreate] email "${email}" was changed to "${encrypted}"`);
} else {
console.log(`[hook beforeCreate] skipped "${email}"`);
}
},
/**
* Once they are created, the create() response will have the encrypted email
* As API uses plain text email, we will need to decrypt them.
* Thus, Decrypt the email BEFORE the create() response is returned.
*/
afterCreate: model => {
const email = model.dataValues.email;
if (!email.includes("@")) {
const decrypt = AES.decrypt(email, KEY, { iv: IV });
const decrypted = decrypt.toString(enc.Utf8);
model.dataValues.email = decrypted;
console.log(`[hook afterCreate] email "${email}" was changed to "${decrypted}"`);
} else {
console.log(`[hook afterCreate] skipped "${email}"`);
}
}
}
}
);
Email.belongsTo(User, { allowNull: true });
User.hasOne(Email, { allowNull: true });
sequelize
.authenticate()
.then(() => sequelize.sync({ force: true }))
.then(() => main())
.catch(err => {
console.log(err);
});
async function create() {
const aUser = await User.build({ name: "John Doe" });
const anEmail = await Email.build({ email: "someone@example.com" });
aUser.setEmail(anEmail);
await aUser.save();
}
async function findUser() {
console.log("[function findUser] Executing");
const existingUser = await User.findOne({
include: [{ model: Email }],
raw: true
});
console.log("[function findUser] Result:", existingUser);
}
async function findEmail() {
console.log("[function findEmail] Executing");
const existingEmail = await Email.findOne({
raw: true
});
console.log("[function findEmail] Result:", existingEmail);
}
async function main() {
await create();
console.log();
await findUser();
console.log();
await findEmail();
}
解决方案
这是 sequelize 中的一个已知问题,似乎没有任何修复它的计划。请参阅此处的github 问题。
另一种方法是在模型属性上使用 getter 和 setter。然而,钩子的问题在于它们不支持异步,因此没有承诺或回调。
推荐阅读
- jupyter-notebook - 是否可以将我笔记本电脑上运行的 jupyter 与 google colab 连接起来
- azure - 是否可以通过 HTTP 请求标头确认机器人的身份?
- arrays - 检查 sub 是否返回 undef
- python - 计算大型数据集中规则的支持度
- google-chrome-devtools - 无法为 devtools 启动 http 服务器。停止 devtools / HTTP/1.1 404 Not Found
- python - 重构`yield None`
- python-3.x - 用python中的两个向量构建一个比较矩阵
- python - 获取具有最大值的密钥。这段代码的时间复杂度是多少?
- css - Apply ng-class based on length of Input
- django - 在 view.py 中注册后重定向到上一页:Django 2