内容目录
一、背景概述二、实现方案三、问题与解决
一、背景概述
在一些跨境业务场景,为了做好本地化推广以及售前、售中和售后支持,都需要在脸书(Facebook,简称FB)上打广告和开站点做支持,用户可以在广告业点击商品链接跳转到站点咨询以及搜索站点做咨询,对于客服人员的能力就比较分散,换句话说也就会影响到客服的工作效率。
客服业务旨在提供一站式的工作平台,提高客服工作效率,那么有没有可能客服在自己的工作台去处理FB上边的咨询诉求?
当然可以,脸书提供了Messenger(信鸽)能力,用来支持自定义网站与脸书交互的诉求。
诉求
- 将各渠道咨询集中、统一管理:减少客服跨平台登陆及处理咨询,提升客服处理客户咨询时效;减少多平台多渠道客服管理,降低重复性管理工作
- 将FB会话接入到在线客服,打通本地会话、客服和FB咨询三点一线能力
- 将FB会话转换成本地会话,并持有生命周期和问题分类属性
术语
- Messenger:脸书信鸽能力,用于和自定义网站交互
- Page:页面,对应于各个业务的站点,或者门店的概念
- Webhook:网络钩子,需要自定义网站提供接收Messenger事件变更的url
二、实现方案
1.添加Webhook回调
在Messenger开发平台配置Webhook回调,也就是Messenger会通过这个url将事件下发给我们,回到需要配置token。
2.添加Page和事件订阅
在Messenger Settings添加Page订阅,也即是我们需要接收哪些站点的事件。然后订阅我们关心的事件,一般Messages就够用。
3.申请API调用
有些场景我们需要查询用户的信息,以及通过API的方式给用户发送消息回复,需要申请API权限。
4.编码实现
网络钩子合法性验证:
代码语言:javascript复制@GetMapping(value = "/fb/webhook")
public ResponseEntity<String> webhookVerify(@RequestParam("hub.mode") String mode,
@RequestParam("hub.verify_token") String verifyToken,
@RequestParam("hub.challenge") String challenge) {
log.info("FacebookHookImpl.webhookVerify receive param;mode={},token={},challenge={}",mode,verifyToken,challenge);
if("subscribe".equalsIgnoreCase(mode) && this.fbToken.equals(verifyToken)) {
log.info("FacebookHookImpl.webhookVerify WEBHOOK_VERIFIED");
return ResponseEntity.ok(challenge);
} else {
return new ResponseEntity<>(HttpStatus.FORBIDDEN);
}
}
事件处理:
代码语言:javascript复制@PostMapping(value = "/fb/webhook")
public ResponseEntity<String> webhookMsgHandle(@RequestBody FacebookHookReq hookReq) {
String object = hookReq.getObject();
if("page".equals(object)) {
//永远不要抛异常给facebook的webhook,如果没有接到响应或者响应失败会20秒推一次数据,如果长时间无法返回200,那么高概率被取消订阅
try {
this.facebookBuzz.receiveFbMsgEvent(hookReq.getEntry());
} catch (Exception e) {
log.error("FacebookHookImpl.webhookMsgHandle msg handle occur error;hookReq={}",hookReq);
}
return ResponseEntity.ok("EVENT_RECEIVED");
} else {
log.warn("FacebookHookImpl.webhookMsgHandle current event not from page,not handle;req={}",hookReq);
return new ResponseEntity(HttpStatus.OK);
}
}
发送消息使用标准的https请求协议即可,可用HttpClient或者OkHttp替换如下代码。
代码语言:javascript复制curl -X POST -H "Content-Type: application/json" -d '{
"recipient":{
"id":"<PSID>"
},
"message":{
"text":"hello, world!"
}
}' "https://graph.facebook.com/v13.0/me/messages?access_token=<PAGE_ACCESS_TOKEN>"
https://developers.facebook.com/docs/messenger-platform/send-messages
三、问题与解决
我们简单的把FB的消息接收到自己的应用中不是目的,用户咨询是有诉求的,可能是商品咨询、也可能是售后问题,并且需要把用户的咨询添加生命周期属性,这些都涉及到客服的KPI考核以及影响客服的工作效率。
对于咨询归类问题,我们暂且可以在客服处理的时候手动做归类,重点说一下生命周期的问题以及解决方案。
通过上边的网络钩子,我们可以接收用户事件,并且创建自己的会话,那么就存在这样一个问题,如果用户短时间发了很多消息,我们如何处理?可能会存在并发和性能问题。
方案一
同步调用,接收到用户事件后,先检查有没有会话,没有的话创建会话,可能会出现重复创建会话问题,并且Messenger对网络钩子的RT有要求,在网络抖动时可能会导致事件重发问题。
方案二
依赖redis SortedSet,基于redis中心化节点,将接收到的Messenger消息事件添加到redis的SortedSet中,然后顺序消费并处理创建会话和发送消息。redis纯内存数据库,用它来处理消息成本有点高。
方案三
基于消息中间件顺序消息;可以基于消息中间件的顺序消息来实现,比如RocketMQ,把接收到的消息事件放入RocketMQ消息队列,然后消费端顺序消费消息处理创建会话和发送消息业务。提供消息顺序消费,并且能够起到在用户咨询高峰期削峰填谷的作用。
综合三种方案,最终才拿方案三,其成本就是需要引入消息中间件,但是试问哪个互联网公司没有中间件,我们只是借用了这个能力,不是在没有的情况下额外引入了一组消息套件。