影响客服人员消息递送时间的一个主要因素是,您尝试联系的用户在客服人员发送消息时是否有数据连接。如果用户处于离线状态,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 对象后,您可以引入一个用于存储每个消息的 msisdn
、message id
、sent time
和 delivery
状态的函数。
/**
* 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 中,因此请在消息成功传送到用户设备后更新传送状态。
用户的设备通过 Cloud Pub/Sub 向 RBM 代理发送 DELIVERED
、READ
和 IS_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 作业下查看该任务。
查询 Datastore 以查找未送达的消息
在您在前面部分设置的 cron 作业的 webhook 中,您需要添加逻辑来查找所有已发送的消息,这些消息的 delivered
状态均为 false,并且 lastUpdated
时间大于适用于我们的用例的预定义超时时间。在此示例中,系统会让超过一小时的邮件失效。
为了支持这样的复合查询,数据存储区需要包含同时包含 delivered
和 lastUpdated
属性的复合索引。为此,您可以在项目中创建一个名为 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 来管理存储、查询和撤消过程,但任何用于跟踪已发送和已传递消息的存储机制都应该有效。
祝您好运,编程愉快!