如何处理 RBM 广告系列中的离线用户

影响客服人员消息递送时间的一个主要因素是,您尝试联系的用户在客服人员发送消息时是否有数据连接。如果用户处于离线状态,RBM 平台会存储消息,并最多尝试 30 天将其传送。如果消息在此之前无法传送,系统会将其从系统中移除。

在很多情况下,当客服人员尝试与用户联系时,用户可能无法连接。他们可能关闭了流量以节省移动套餐费用,可能是在没有 Wi-Fi 连接的飞机上,也可能是在隧道内的地铁上。根据消息需要送达的紧急程度,代理需要通过撤消未送达的 RBM 消息并通过其他渠道重新路由这些消息,以妥善处理离线用户。

以下部分详细介绍了如何使用 Google Cloud Datastore 跟踪您发送和传送的消息、如何使用 cron 撤消未传送的消息,以及如何通过短信重新路由这些消息。

跟踪发送的邮件

RBM 代理发送的每条消息都必须包含唯一的消息 ID。 如需跟踪客服人员发送的消息,您需要保存每条消息的消息 ID、电话号码和时间戳。

您可以使用各种技术来存储此类信息,包括 Google Cloud Datastore。Cloud Datastore 是一个可扩缩性极强的 NoSQL 数据库,可自动处理分片和复制操作。它是存储非关系型数据(例如代理发送的消息)的绝佳解决方案。Cloud Datastore 需要有一个处于活跃状态的 Google App Engine 实例,因此您可以使用 App Engine 托管 RBM 代理并配置 cron 作业。

Cloud Datastore 的客户端库支持多种语言。在此示例中,您可以使用 Node.js,并将 RBM 代理代码基于 RBM 开发者网站上提供的第一个代理 Node.js 示例。首先,运行以下命令为我的 Node.js 项目安装 Cloud Datastore:

npm install --save @google-cloud/datastore

在代理源代码中,添加对 Cloud Datastore 客户端库的全局引用。

// Imports the Google Cloud client library
const Datastore = require('@google-cloud/datastore');

// Creates a client
const datastore = new Datastore({
    projectId: PROJECT_ID,
});

创建 Datastore 对象后,您可以引入一个用于存储每个消息的 msisdnmessage idsent timedelivery 状态的函数。

/**
 *   Records an entry in the Cloud Datastore to keep track of the
 *   messageIds sent to users and the delivery state.
 *
 *   @property {string} msisdn The user's phone number in E.164 format.
 *   @property {string} messageId The unique message identifier.
 *   @property {boolean} delivered True if message has been delivered.
 */
function saveMessage(msisdn, messageId, delivered) {
    const messageKey = datastore.key(['Message', messageId]);

    const dataForMessage = {
        key: messageKey,
        data: {
            id: messageId,
            msisdn: msisdn,
            lastUpdated: new Date().getTime(),
            delivered: delivered
        },
    };

    // Record that the message was sent.
    datastore
        .save(dataForMessage)
        .then(function() {
            console.log('saved message successfully');
        })
        .catch((err) => {
            console.error('ERROR:', err);
        });
}

有了此函数后,每当代理向用户发送消息时,您都需要调用此方法。当 RBM Node.js 客户端库发送 RBM 消息时,该库会在回调方法中提供响应对象,其中包含已发送给用户的消息的 messageId

以下示例展示了如何在成功与 RBM API 通信后向用户发送纯文本消息并记录消息信息。

let params = {
    messageText: 'Hello, World!',
    msisdn:'+12223334444',
};

// Send "Hello, World!" to the user.
rbmApiHelper.sendMessage(params,
function(response) {
    // Extract the message ID from the response
    let messageId = response.config.params.messageId;

    // Store the sent state in the Datastore
    saveMessage(phoneNumber, messageId, false);
});

运行代码后,您可以在 Google Cloud 控制台Datastore 实体视图中检查 Datastore。

Datastore

更新邮件的递送状态

现在,您的代理会将已发送的消息请求存储在 Datastore 中,因此请在消息成功传送到用户设备后更新传送状态。

用户的设备通过 Cloud Pub/Sub 向 RBM 代理发送 DELIVEREDREADIS_TYPING 事件。在 Pub/Sub 的处理程序中,检查已传送事件,并将已传送标志的 Datastore 设置更新为 true。

/**
 *   Uses the event received by the Pub/Sub subscription to send a
 *   response to the client's device.
 *   @param {object} userEvent The JSON object of a message
 *   received by the subscription.
 */
function handleMessage(userEvent) {
    if (userEvent.senderPhoneNumber != undefined) {
        let msisdn = userEvent.senderPhoneNumber;
        let messageId = userEvent.messageId;
        let eventType = userEvent.eventType;

        if(eventType === 'DELIVERED') {
            saveMessage(msisdn, messageId, true);
        }

        // TODO: Process message and create RBM response
    }
}

代理会将外发邮件保存到 Datastore,并在收到传送通知时更新数据。在下一部分中,了解如何在 Google App Engine 上设置一个每 10 分钟运行一次的 cron 作业,以监控未送达的消息。

Google App Engine 上的 Cron

您可以使用 Cron 作业按不同的时间间隔安排任务。在 Google 的 App Engine 中,Cron 作业是通过说明、网址和间隔时间进行配置的。

在 Node.js 应用中,您可以在 cron.yaml 文件中配置这些环境变量,您可以使用 Google Cloud SDK 将这些文件部署到 App Engine。如需了解其他语言配置设置,请参阅使用 cron.yaml 安排作业

由于 Cron 任务需要网址,因此您需要向 Express 应用路由器添加网址端点,以便 Cron 调用。此网络钩子负责查询数据存储区中的旧消息,将其从 RBM 平台中删除,并通过短信将其发送给用户。

router.get('/expireMessages', function(req, res, next) {
    // TOOD: Query the Datastore for undelivered messages,
    // remove them from the RBM platform, and send them over SMS

    res.status(200).send();
});

以下是每 10 分钟执行此端点的 cron.yaml 文件配置。

cron:
-   description: "Processing expired RBM messages"
  url: /expireMessages
  schedule: every 10 mins

如需将 Cron 任务部署到 App Engine,请运行以下命令:

gcloud app deploy cron.yaml

部署后,App Engine 会自动配置 Cron 任务,您可以在 App Engine > Cron 作业下查看该任务。

Cron 作业

查询 Datastore 以查找未送达的消息

在您在前面部分设置的 cron 作业的 webhook 中,您需要添加逻辑来查找所有已发送的消息,这些消息的 delivered 状态均为 false,并且 lastUpdated 时间大于适用于我们的用例的预定义超时时间。在此示例中,系统会让超过一小时的邮件失效。

为了支持这样的复合查询,数据存储区需要包含同时包含 deliveredlastUpdated 属性的复合索引。为此,您可以在项目中创建一个名为 index.yaml 的文件,并在其中添加以下信息:

indexes:
-   kind: Message
  properties:
  -   name: delivered
    direction: asc
  -   name: lastUpdated
    direction: desc

与部署之前定义的 Cron 作业类似,使用 Google Cloud SDK 通过以下命令部署您定义的复合索引:

gcloud datastore create-indexes index.yaml

部署后,App Engine 会自动配置索引,并且索引会显示在 Datastore > Indexes 下。

数据存储区索引

定义索引后,您可以返回为 Cron 作业创建的 webhook,并完成消息过期逻辑:

router.get('/expireMessages', function(req, res, next) {
    // Milliseconds in an hour
    const TIMEOUT = 3600000;

    // Threshold is current time minus one hour
    const OLD_MESSAGE_THRESHOLD = new Date().getTime() - TIMEOUT;

    // Create a query to find old undelivered messages
    const query = datastore
        .createQuery('Message')
        .filter('delivered', '=', false)
        .filter('lastUpdated', '<', OLD_MESSAGE_THRESHOLD);

    // Execute the query
    datastore.runQuery(query).then((results) => {
        for(var i = 0; i < results[0].length; i++) {
            let msisdn = results[0][i].msisdn;
            let messageId = results[0][i].id;

            // Stop the message from being sent
            rbmApiHelper.revokeMessage(msisdn, messageId);

            // Remove the message from the Datastore
            datastore.delete(results[0][i][datastore.KEY]);

            // TODO: Send the user the message as SMS
        }
    });

    res.status(200).send();
});

RBM 本身不支持短信回退,因此您需要实现相应逻辑,以便通过短信发送未递送的消息。

总结

如需处理离线用户,您可以为未送达的 RBM 消息构建撤消逻辑。消息过期前所用的时间取决于您传输的信息时效性。对于时效性较高的信息,建议的超时时间少于 2 小时。

本示例使用 Cloud Datastore 和 Google App Engine 来管理存储、查询和撤消过程,但任何用于跟踪已发送和已传递消息的存储机制都应该有效。

祝您好运,编程愉快!