使用关系/关联查询时未执行 Sequelize afterFind 挂钩

Ana*_*via 5 node.js sequelize.js

我有两个型号UserEmailEmail有一个来自 的外键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}"`);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

当我需要创建/查询模型时它们完美地工作Email。例如:

async function findEmail() {
    console.log("[function findEmail] Executing");
    const existingEmail = await Email.findOne({
        raw: true
    });
    console.log("[function findEmail] Result:", existingEmail);
}
Run Code Online (Sandbox Code Playgroud)

并输出:

[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 }
Run Code Online (Sandbox Code Playgroud)

但是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);
}
Run Code Online (Sandbox Code Playgroud)

输出是:

[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 }
Run Code Online (Sandbox Code Playgroud)



我的问题是: 当查询其他模型时包含指定钩子的模型时,为什么钩子没有被执行?

这是我正在使用的完整代码:--在codesandbox上

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();
}
Run Code Online (Sandbox Code Playgroud)

gri*_*urd 2

这是sequelize 中的一个已知问题,似乎没有任何计划来修复它。请参阅此处的github 问题。

另一种方法是对模型属性使用 getter 和 setter。然而,钩子的问题是它们不支持异步,因此没有承诺或回调。

这是有关如何使用 getter/setter 的指南