Лучший способ поддержки нескольких пользователей в системе на базе NodeJS? - PullRequest
0 голосов
/ 30 октября 2018

Я создаю чат-бота в мессенджере Facebook. Я хочу, чтобы чатбот мог поддерживать нескольких пользователей. Когда пользователь впервые получает доступ к чат-боту, он должен создать учетную запись пользователя. Процесс создания учетной записи пользователя выглядит следующим образом:

  1. Когда незарегистрированный пользователь взаимодействует с чат-ботом, он получает имя пользователя из API Graph Facebook, а затем говорит «Привет (имя)». Это объясняет, что пользователю необходимо создать профиль пользователя.
  2. Чатбот спрашивает пользователя, как он хочет, чтобы его вызывали. Возможны варианты (имя) или «Что-то еще»
  3. Если пользователь выбирает (имя), чат-бэт сразу переходит на номер (5).
  4. Если пользователь выбирает «Что-то еще», чатбот спрашивает, как он хочет позвонить, и вводит его.
  5. Чатбот просит пользователя выбрать пол (мужской, женский, другой, предпочитают не говорить)
  6. Чатбот просит пользователя выбрать его возраст (из диапазона значений, например, 18-25, 26-30 и т. Д.)
  7. Как только профиль создан, чатбот сообщает пользователю, что в будущем, если он захочет поговорить с чатботом, он может просто сказать «привет» или «привет».
  8. Затем чатбот спрашивает пользователя, что он хочет сделать сейчас, и представляет основные функции.

Вот что обычно происходит, когда любой пользователь взаимодействует с чатботом:

  1. Пользователь взаимодействует с чатботом.
  2. Чатбот проверяет, загружен ли пользователь в память
  3. Если пользователь загружен в память, чатбот взаимодействует с ним как обычно
  4. Если пользователь не загружен, чатбот пытается получить пользователя из базы данных
  5. Если пользователь существует в базе данных, профиль пользователя загружается в память и чат-бот взаимодействует с пользователем в обычном режиме
  6. Если пользователь не существует в базе данных, он никогда ранее не использовал чат-бота, поэтому ему необходимо создать профиль пользователя.

Проблема в том, что этого не происходит. Когда несколько пользователей пытаются настроить свой профиль одновременно, происходит следующее (предположим, что два пользователя - «пользователь A» и «пользователь B»):

Для лица А:

  1. Чат-робот получает имя пользователя, говорит «Привет (человек А)» и объясняет, что ему необходимо создать профиль пользователя.

  2. Затем чатбот спрашивает пользователя, как он хочет быть вызванным. Возможные варианты: (человек B) или «Нечто другое».

  3. Независимо от того, что выберет пользователь, он не сможет двигаться дальше, потому что чат-робот продолжает повторять этап 2.

Итак, мой код:

Пользовательские данные хранятся в объекте «data» в файле с именем 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);
},

Когда пользователь отправляет сообщение в чат-бота, это вызывает событие webhook, в пределах app.js:

// 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 голосов
/ 29 ноября 2018

ОК, поэтому я использовал 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);
},

И если я хочу установить пользовательское свойство, я использую функцию установки:

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
        }
    }
},

И я использую функцию получения, чтобы получить свойство:

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);
},
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...