Node 中的装饰器 - 是否可以在构造函数中遍历 class 方法以覆盖/应用装饰器

Decorator in Node - is it possible to iterate through class methods inside the constructor in order to override / apply a decorator to them

首先我是 Node 的新手/Javascript 一个。我想要做的是使用装饰器函数将日志记录添加到我的存储库。因此,我试图从构造函数内部遍历每个函数,并用类似的东西覆盖它:“
Object.getOwnPropertyNames(Repository.prototype).forEach((func) => this.decorator(func));" 我的问题是“Object.getOwnPropertyNames”仅 returns 函数名称而不是实际函数。有没有办法将这个装饰器应用到每个函数?

"use strict"
const db = require("./Database/db_operations");
const logger = require("./utils/logger")
const {createTables} = require("./Database/db_operations");
const loggingTypes = require("./utils/logginTypes")

class Repository {

    async saveTermin(Termin) {

    }


    async saveToDo(toDo) {
        return await db.saveToDo(toDo);
    }

    async saveAppointment(Appointment) {
        return await db.saveAppointment(Appointment);
    }

    async updateAppointment(Appointment) {
        return await db.updateAppointment(Appointment);
    }

    async deleteAppointment(uuid) {
        return await db.deleteAppointment(uuid);
    }

    async saveAppointmentParticipants(appointment) {
        return await db.saveAppointmentParticipants(appointment);
    }

    async saveAppointmentFiles(appointment) {
        return await db.saveAppointmentFiles(appointment)
    }

    async getAppointmentFiles(appointment) {
        return await db.getAppointmentFiles(appointment)
    }

    async deleteToDo(todo) {
        return await db.deleteToDo(todo)
    }

}



// All functions will be mapped to there type to optimize logging. If a function is not mapped to its type,
// it will be automaticly assigned to the "unspecified type". Logging will still work, but depending on what
// arguments are given and what is returned, the output might not perfectly fit

const funcMapping = new Map();

// GET
funcMapping.set(Repository.prototype.getAppointmentFiles, loggingTypes.GET);
funcMapping.set(Repository.prototype.getAllDatas, loggingTypes.GET);
funcMapping.set(Repository.prototype.getAllToDos, loggingTypes.GET);


//SAVE
funcMapping.set(Repository.prototype.saveToDo, loggingTypes.SAVE);
funcMapping.set(Repository.prototype.saveAppointment, loggingTypes.SAVE);
funcMapping.set(Repository.prototype.saveAppointmentParticipants, loggingTypes.SAVE);

//DELETE
funcMapping.set(Repository.prototype.deleteAppointment, loggingTypes.DELETE);
funcMapping.set(Repository.prototype.deleteToDo, loggingTypes.DELETE);


Object.getOwnPropertyNames(Repository.prototype)
    .forEach(name => {
        const func = Repository.prototype[name];
        // checking loggingTypes - if no type is assigned function will be assigned to "UNASSIGNED".
        // console.log(funcMapping.has(func) +" "+ func.name)
        if (!funcMapping.has(func)) {
            funcMapping.set(func, loggingTypes.UNASSIGNED);
        }
        // function will only be wrapped if logging is enabled.
        if (funcMapping.get(func)[1]) {
            Repository.prototype[name] = async function (...args) {
                // calls the original methode

                const returnValue = await func.apply(this, args);

                const argumentsInArray = Array.prototype.slice.call(args);

                // Put any additional logic here and it will be applied -> magic
                // Logging
                db.writeLogging(logger(func, returnValue, funcMapping.get(func)[0]), args).then(() => {
                    console.log(`Function "${name}()" was successfully logged and saved to Database`)
                }).catch(e => {
                    console.log(`Function "${name}()" could not be logged and saved to Database. ${func}`)
                    console.log(e)
                })
                return returnValue;
            }
        }
    });

module.exports = new Repository();


    const appointment_model = require('../models/Appointment');
    const contact_model = require('../models/Contact');
    const toDo_model = require('../models/ToDo');
    const file_model = require('../models/File');
    const loggingTypes = require("./logginTypes")
    
    
    function log() {
        // returns a function that returns an object. When this function is then called the object is returned
        return function decorator(funcToLog, returnValue, funcType, ...args) {
            // console.log("arguments in logger" + args);
    
            // create prototype for object that later will be passed to database
            const descriptor = function (user, change, changedAt) {
                this.user = user; // some user id
                this.change = change; //
                this.changedAt = changedAt; // date when changes occoured
                this.appointmentId = getUuid(appointment_model);
                this.todoId = getUuid(toDo_model);
                this.contactId = getUuid(contact_model);
                this.fileId = getUuid(file_model);
            };
    
    
            // contains all logging Data about the function beeing called -> name of function, usedArguments and returnValue
            function getChanges(func, funcType, returnValue, args) {
                let changes = null;
                switch (funcType) {
                    case loggingTypes.GET[0]:
                        changes = {
                            funcName: func.name, //
                            funcType: funcType, //
                            dataSetToChange: {...args},
                            newData: returnValue
                        }
                        break;
                    case loggingTypes.SAVE[0]:
                        changes = {
                            funcName: func.name, //
                            funcType: funcType, //
                            dataSetToChange: {...args}, // ?
                            newData: returnValue // could call function here
                        }
                        break;
                    case loggingTypes.UPDATE[0]:
                        changes = {
                            funcName: func.name, //
                            funcType: funcType, //
                            dataSetToChange: {...args},
                            newData: returnValue
                        }
                        break;
                    case loggingTypes.DELETE[0]:
                        changes = {
                            funcName: func.name, //
                            funcType: funcType, //
                            dataSetToChange: {...args},
                            newData: returnValue
                        }
                        break;
                    case loggingTypes.UNASSIGNED[0]:
                        changes = {
                            funcName: func.name, //
                            funcType: funcType, //
                            dataSetToChange: {...args},
                            newData: returnValue
                        }
                }
                return changes;
            }
    
            function getUuid(model_type) {
                let uuid = null;
                console.log(args)
                for (let i = 0; i < args.length; i++) {
                     console.log(args[i])
                     if (args[i] instanceof model_type) {
                         uuid = parseInt(args[i].uuid);
                    }
                    return uuid;
                }
            }
    
    
            return new descriptor("someUserId", JSON.stringify(getChanges(funcToLog, funcType, returnValue, args)), new Date())
        }
    }
    
    module.exports = log();


您可以使用中间步骤轻松地将函数名称映射到它们的值:

Object.getOwnPropertyNames(Repository.prototype)
.map(name => Repository.prototype[name])
.forEach((func) => this.decorator(func));

无论如何,构造函数不是执行此操作的最佳位置,因为每次创建 class 的新实例时,您最终都会应用装饰器。

我宁愿将整个装饰器逻辑移动到 class 定义之后,然后再分配给 module.exports

Object.getOwnPropertyNames(Repository.prototype)
.forEach(name => {
    const func = Repository.prototype[name];
    Repository.prototype[name] = function (...args) {
        console.log("Decorator was called");
        const returnValue = func.apply(this, args);

        // Put additional logging logic here...

        return returnValue;
    }
});

更新

针对评论中提到的内容,这里是上述代码的更强大版本,其中包含您可能需要或不需要的额外预防措施:

  • 保留非函数
  • 保留非值属性
  • 保留构造函数
  • 保留不可配置的属性
  • 包含带有符号键的属性
Reflect.ownKeys(Repository.prototype).forEach(key => {
    const descriptor = Reflect.getOwnPropertyDescriptor(Repository.prototype, key);
    if (!descriptor.configurable) return;
    const { value } = descriptor;
    if (typeof value !== 'function') return;
    if (value === Repository) return;
    descriptor.value = function (...args) {
        console.log("Decorator was called");
        const returnValue = value.apply(this, args);

        // Additional logging logic here...

        return returnValue;
    };
    Object.defineProperty(Repository.prototype, key, descriptor);
});

我遗漏的另一件事是额外的逻辑,以确保 decorated 方法具有与原始函数相同的长度和名称属性以及相同的原型。当您在使用代码时发现其他要求时,您可能需要调整更多细节。