在基于NodeJS的系统中支持多个用户的最佳方法?

时间:2018-10-30 11:25:10

标签: node.js javascript-objects

我正在Facebook Messenger中创建一个聊天机器人。我显然希望聊天机器人能够支持多个用户。用户首次访问聊天机器人时,应该创建一个用户帐户。创建用户帐户的过程如下:

  1. 当未注册的用户与聊天机器人进行交互时,它会从Facebook Graph API获取用户的名字,然后说“你好(名字)”。它说明用户需要创建用户个人资料。
  2. 聊天机器人询问用户他们想要被叫什么。选项可以是(名字)或“其他”
  3. 如果用户选择(名字),则聊天机器人会直接转到数字(5)。
  4. 如果用户选择“其他”,则聊天机器人会询问他们想要被呼叫的内容,然后键入。
  5. 聊天机器人要求用户选择性别(男性,女性,其他人,不想说)
  6. 聊天机器人要求用户选择年龄(从18-25、26-30等范围内选择)
  7. 一旦创建了个人资料,聊天机器人就会告诉用户,将来如果他们想与聊天机器人对话,他们只能说“嗨”或“你好”。
  8. 然后,聊天机器人会询问用户他们现在想做什么,并介绍主要功能。

这是当任何用户与聊天机器人进行交互时正常发生的事情:

  1. 用户与聊天机器人进行交互。
  2. Chatbot检查用户是否已加载到内存中
  3. 如果用户已加载到内存中,那么聊天机器人会与用户正常交互
  4. 如果未加载用户,则聊天机器人会尝试从数据库中获取用户
  5. 如果用户存在于数据库中,则将用户的个人资料加载到内存中,并且聊天机器人会像平常一样与用户交互
  6. 如果用户不存在于数据库中,则他们以前从未使用过聊天机器人,因此必须创建用户个人资料。

问题是,这种情况没有发生。当多个用户尝试同时设置其个人资料时,将会发生以下情况(假设有两个用户-“用户A”和“用户B”:

对于人A:

  1. 聊天机器人获得用户的名字,并说“你好(A人)”,并说明他们需要创建用户个人资料。

  2. 然后,聊天机器人会询问用户他们想要被叫什么。它提供的选项是(人B)或“其他”。

  3. 无论用户选择什么,他们都无法继续前进,因为聊天机器人不断重复第二阶段。

所以,我的代码:

用户数据存储在名为user.js的文件中的“数据”对象中:

data: {
    fbID: undefined,    // senderID (user's Facebook PSID)
    firstName: undefined,   // User's first name (only used during user profile creation)
    userID: undefined,  // User ID (i.e. first name or nickname)
    gender: undefined,  // Gender
    age: undefined, //  Age
}

User.js还包含用于重新加载用户的“重新加载”功能。稍后您将了解其工作原理:

reload: function(data) {
    self.data.fbID = data.fbid; /** @namespace data.fbid */
    self.data.userID = data.userid; /** @namespace data.userid */
    self.data.gender = data.gender; /** @namespace data.gender */
    self.data.age = data.age;   /** @namespace data.age */
    console.log("Profile reloaded for user '%s'.", self.data.fbID);
},

当用户向聊天机器人发送消息时,这会触发app.js内的webhook事件:

// Accept POST requests to webhook
app.post('/webhook/', function (req, res) {
    // Parse the request body from the POST
    let body = req.body;
    // Check the webhook event is from a Page subscription
    if (body.object === 'page') {
        body.entry.forEach(function(entry) {     /** @namespace body.entry */
            // Gets the body of the webhook event
            let webhookEvent = entry.messaging[0];      /** @param entry.messaging[] */
            // Get sender ID
            let senderID = webhookEvent.sender.id;
            // Set session ID
            session.set(senderID);
            // Handle webhook event
            handler.handleWebhookEvent(senderID, webhookEvent);
        });
        // Return a '200 OK' response to all events
        res.status(200).send('EVENT_RECEIVED');
    } else {
        // Return a '404 Not Found' if event is not from a page subscription
        res.sendStatus(404);
    }
});

webhook事件传递到handler.js,由handleWebhookEvent函数处理:

handleWebhookEvent: function(senderID, webhookEvent) {
    let greetingText, delay;
    let handleEvent = function() {
        /**
         * @param webhookEvent
         * @param webhookEvent.message
         * @param webhookEvent.optin
         * @param webhookEvent.delivery
         * @param webhookEvent.postback
         * @param webhookEvent.read
         * @param webhookEvent.account_linking
         * */
        if (webhookEvent.optin) {
            handleOptIn(webhookEvent);
        } else if (webhookEvent.message) {
            handleMessage(senderID, webhookEvent.message);
        } else if (webhookEvent.delivery) {
            handleDelivery(webhookEvent);
        } else if (webhookEvent.postback) {
            handlePostback(senderID, webhookEvent.postback);
        } else if (webhookEvent.read) {
            handleRead(webhookEvent);
        } else if (webhookEvent.account_linking) {
            handleAccountLink(senderID, webhookEvent.account_linking);
        } else {
            console.log("Webhook received unknown event: ", webhookEvent);
        }
    };
    if (common.isDefined(user.data.fbID) && user.data.fbID === senderID) { 
        // User is loaded - handle events as normal
        console.log("User '%s' is loaded.", senderID);
        //logging.logEvent(senderID, webhookEvent, function() {
            handleEvent();
        //});
    } else {    // User is not loaded
        getUser(senderID, function(result) {   // First try and load user
            if (common.isDefined(result)) { // Result returned
                user.reload(result);
                //logging.logEvent(senderID, webhookEvent, function() {
                    handleEvent();
                //});
            } else {    
                // User was not found in database.  We need to 
                create a new profile for the user
                common.getFBUser(senderID, function(name) {
                    console.log("User '%s' was not found in database.", senderID);
                    user.data.fbID = senderID;
                    user.data.firstName = name; // Stores user's first name 
                    in the user object
                    greetingText = strings.initialGreeting.replace("%USER", 
                    user.data.firstName);
                    delay = common.setDelay(greetingText);
                    messaging.sendTextMessage(senderID, greetingText);
                    setTimeout(function() {
                        // Pause before kicking off the 'uprofile' intent
                        dialogflow.sendRequest(session.getIDs(), senderID, 
                        strings.createUser);
                    }, delay);
                });
            }
        });
    }
}

我认为这里的问题是我认为通过使用user.js中的一个对象,聊天机器人将只创建该对象的多个实例,每个用户一个。显然我弄错了。创建多个实例并防止多个用户共享用户数据对象的最佳方法是什么?还是我应该完全放弃对象,而每次都只是从数据库中读取?我想避免这样做,因为这需要大量的读/写操作。

1 个答案:

答案 0 :(得分:0)

好的,所以我最终使用了Map()

我的user.js文件现在包含以下内容:

loadedUsers: new Map()

当我想创建一个新用户(数据库中不存在该用户)时,我使用以下对象:

newUser: {
    firstName: undefined,
    userID: undefined,
    gender: undefined,
    age: undefined,
},

当我想重新加载现有用户时,我使用以下对象:

user: {
    userID: undefined,
    gender: undefined,
    age: undefined,
},

我使用add()addNewUser()函数在地图上创建用户:

add: function(senderID, data) {
    self.loadedUsers.set(senderID, self.user);
    self.setProperty(senderID, 1, data.userid); /**@namespace data.userid */
    self.setProperty(senderID, 2, data.gender);
    self.setProperty(senderID, 3, data.age);
    console.log ("User '%s' added to map", senderID);
},

addNewUser: function(senderID, name) {
    self.loadedUsers.set(senderID, self.newUser);
    self.setProperty(senderID, 0, name);
    console.log("New User '%s' created", senderID);
},

如果要设置用户属性,请使用setter函数:

setProperty: function(senderID, property, value) {
    switch (property) {
        case 0: {
            // First Name
            self.loadedUsers.get(senderID).firstName = value;
            break;
        } case 1: {
            // UserID
            self.loadedUsers.get(senderID).userID = value;
            break;
        } case 2: {
            // Gender
            self.loadedUsers.get(senderID).gender = value;
            break;
        } case 3: {
            // Age
            self.loadedUsers.get(senderID).age = value
        }
    }
},

我使用getter函数获取属性:

getProperty: function(senderID, property) {
    switch (property) {
        case 0: {
            // First Name
            return self.loadedUsers.get(senderID).firstName;
        } case 1: {
            // UserID
            return self.loadedUsers.get(senderID).userID;
        } case 2: {
            // Gender
            return self.loadedUsers.get(senderID).gender;
        } case 3: {
            // Age
            return self.loadedUsers.get(senderID).age
        }
    }
},

最后,如果要查看是否已加载用户(在尝试从数据库读取之前),请使用以下方法:

isLoaded(senderID) {
    return self.loadedUsers.has(senderID);
},