# 消息推送

# 主动消息推送

在 4.1 版本中,为了增强企业的主动营销能力,加入给 iOS 和 Android 用户主动推送消息的服务端接口。服务端推送说明见 消息推送

推送消息到达访客端,体现出来的,其实就是一条带扩展字段的普通消息,用户进入客服界面后,也会在消息流中正常展示出来。如果 App 也需要处理这条消息,可以添加对这一事件的监听。接口使用示例如下:


 private void addPushMessageListener() {
        if (qiyuPushMessageListener == null) {
            qiyuPushMessageListener = new OnPushMessageListener() {
                @Override
                public void onReceive(IMMessage message, PushMessageExtension extension) {
                    qiyuPushMessage = message;
                    pushMessageExtension = extension;
                    showPushMessageIfResumed();
                }
            };
            Unicorn.addPushMessageListener(qiyuPushMessageListener);
        }
    }

# 省电策略(V5.7.7 以上版本已删除)

七鱼 SDK 作为用户端的 IM 工具,需要能够及时收到客服回复的消息。同时,就算用户没有主动咨询过客服,客服也可以主动发起会话,进行主动营销。因此,在 2.0 版本之前,SDK一旦初始化之后,会一直保持长连接,定时与服务器交换心跳。

但是,毕竟用户咨询以及客服主动营销都是不太常用的操作,为此保持一个长连接,性价比不高。同时,我们了解到,很多客户的 APP 本身就自带了推送模块,或者集成了第三方的推送 SDK,由此就存在了两条长连接,浪费了用户的电量和流量。因此,在2.0版本中,我们针对 SDK 的长连接策略做了更改,以优化在用户没有发起咨询时的电量消耗。如果开发者需要开启省电特性,需要在 YSFOptions 中设置 savePowerConfig。从版本3.1.0开始,该选项默认开启,并使用默认间隔。

在咨询客服时,SDK 会保持长连接以便及时收到推送。当一次会话结束后,用户极有可能还会继续发起咨询会话,同时客服也很有可能会主动发起会话,因此,一次会话结束后,长连接可以在继续保持一段时间(SavePowerConfig.activeDelay),以便及时收到消息,同时也可以减少重新连接造成的消耗。如果在这段时间内,再也没有开始过会话,则在计时结束后即可转入省电模式。根据用户配置,省电模式分为两种状态:

  1. 推送模式:用户需要在七鱼的管理后台配置推送服务器,且 SavePowerConfig.customPush 设置为 true。此时,如果有新消息,会自动转到后台配置的推送通道上。由于推送不受 SDK 控制,因此收到推送后 SDK 不会自动建立长连接,需要用户进入咨询界面后,才会建立长连接。开启自定义推送后,还需要设置推送的设备 ID:SavePowerConfig.deviceIdentifier。在七鱼推送的消息结构体中,会包含该字段。
  2. 轮询模式:SDK 每隔 SavePowerConfig.checkInterval 秒去服务器检测一次有没有新消息。如果有新消息,会自动建立长连接,并收取新消息,弹出通知。

# 配置推送服务器地址

客服发送消息给用户,而用户此时已经转入推送模式后,消息将被推送给开发者的服务器端,然后再由开发者推送到 APP 端。

要配置推送服务器,请使用管理员帐号登录七鱼管理后台,在「设置」 -> 「APP设置」 -> 「添加/编辑APP」中设置。

推送服务器配置

开发者服务器收到推送请求后,应对立即返回一个 空字符串 告诉七鱼服务器。七鱼服务器发出 POST 请求后,如果在 10 秒内收不到响应,或者收到的响应非空,会断掉连接,并且重新发起请求,总共重试 2 次。 如果连续 10 次都推送失败,该推送服务器 url 将被暂停推送 1 个小时。

# 推送消息数据结构

当有消息需要推送时,七鱼服务器会向开发者设置的服务器地址发送推送消息,方法类型为 POST,数据格式为 JSON 。

POST 请求url中会包含以下参数:

参数 参数说明
nonce 随机数字符串
time 当前 UTC 时间戳,从 1970 年 1 月 1 日 0 点 0 分 0 秒开始到现在的毫秒数
checksum SHA1(AppSecret + nonce + time), 三个参数拼接的字符串,进行SHA1哈希计算,转化成16进制字符(String,小写)

其中,checksum 可用于校验该请求是否由七鱼服务器发出,出于安全性考虑,还应根据 time 参数校验 checksum 的有效期,超过一定时间(比如 5 分钟)的请求也应判定为非法。请确认发起请求的服务器是与标准时间同步的,比如有NTP服务。

重要提示: 本文档中提供的所有接口均面向开发者服务器端调用,用于计算CheckSum的AppSecret是添加App时生成的secret key,开发者应妥善保管,可在应用的服务器端存储和使用,但不应存储或传递到客户端,也不应在网页等前端代码中嵌入。

计算 checksum 的 java 示例代码如下:

public class QiyuPushCheckSum {
   private static final char[] HEX_DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};

   public static String encode(String appSecret, String nonce, String time) {
       String content = appSecret + nonce + time;
       try {
           MessageDigest messageDigest = MessageDigest.getInstance("sha1");
           messageDigest.update(content.getBytes());
           return getFormattedText(messageDigest.digest());
       } catch (Exception e) {
           throw new RuntimeException(e);
       }
   }

   private static String getFormattedText(byte[] bytes) {
       int len = bytes.length;
       StringBuilder buf = new StringBuilder(len * 2);
       for (int j = 0; j < len; j++) {
           buf.append(HEX_DIGITS[(bytes[j] >> 4) & 0x0f]);
           buf.append(HEX_DIGITS[bytes[j] & 0x0f]);
       }
       return buf.toString();
   }
}

POST 请求的内容为 JSON 格式,编码格式为 UTF-8,其各个字段说明如下:

字段 字段说明
content 推送的消息内容
time 消息的发出时间
messageId 消息的唯一 ID,当消息有重发时,开发者的推送服务器可依据此去重。
staffName 说话的客服昵称
deviceIdentifier 消息发送对象用户的 deviceIdentifier
package 消息接收对象 APP 的 package name

请求实体示例如下:

{
	"content":"hello world",
	"time":1592903619000,
	"messageId":"44568613556234662#566ds3651s13e89",
	"staffName":"客服小宁",
	"deviceIdentifier":"13f568551d966564s45e4587",
	"package":"com.kaixinggongfang.zaome"
}

# 新消息提醒

当用户不处在聊天界面时,收到客服的消息,App 应当在通知栏或者聊天入口给出提醒。

通知栏提醒可以显示最近一条消息的内容,并提供给用户快速进入 App 的入口。要打开通知栏提醒功能,只需给 YSFOptionsstatusBarNotificationConfig 域赋予非 null 值即可。同时,通过定制该域的各配置项,还能实现提醒开关,免打扰等功能。

要实现点击通知栏提醒直接跳转到会话窗口的功能,需要设置 StatusBarNotificationConfignotificationEntrance (不可配置 SDK 内部 Activity ,例如 ServiceMessageActivity),并在对应的 Activity 里添加处理。还需要设置StatusBarNotificationConfignotificationExtraType 属性为 NotificationExtraTypeEnum.MESSAGE。如果没有设置 notificationEntrance,则是在 AndroidManifest 中设置的入口 Activity 中处理。示例代码如下:

Intent intent = getIntent();
if (intent.hasExtra(NimIntent.EXTRA_NOTIFY_CONTENT)) {
    // 打开客服窗口
    Unicorn.openServiceActivity(context, title, source);
    // 最好将intent清掉,以免从堆栈恢复时又打开客服窗口
    setIntent(new Intent());
}

在聊天入口的地方,App 可以给出是否有未读消息,以及未读数的提示。App 可以通过添加以下监听来跟踪未读数变化,更新界面,反馈给用户:

// 添加未读数变化监听,add 为 true 是添加,为 false 是撤销监听。
// 退出界面时,必须撤销,以免造成资源泄露
private UnreadCountChangeListener listener = new UnreadCountChangeListener() { // 声明一个成员变量
    @Override
    public void onUnreadCountChange(int count) {
        // 在此更新界面, count 为当前未读数,
        // 也可以用 Unicorn.getUnreadCount() 获取总的未读数
    }
}

private void addUnreadCountChangeListener(boolean add) {
    Unicorn.addUnreadCountChangeListener(listener, add);
}

默认情况下,只有访客在聊天界面时,才不会有通知栏提醒,其他界面以及 App 在后台时,都会有消息提醒。如果当 App 在前台时,不需要通知栏提醒新消息,可以调用Unicorn.toggleNotification(false)关闭消息提醒,然后在 App 退到后台时,调用Unicorn.toggleNotification(true)重新打开。

用户收到新消息后(通过未读数变化监听接口可获知此事件),如果开发者需要显示新收到的消息,可通过下面接口获取最近一条消息:

/**
 * 获取和客服的最后一条聊天消息内容。
 * 可用于未读消息变化时,展示最后一条未读消息,或者展示客服的最后一条消息。
 * @return 最后一条消息
 * 在 <= 5.7.7 版本为 UnicornMessage
 */
IMMessage message = Unicorn.queryLastMessage();

在 V3.2 版本中增加了该接口的平台商家企业版本,需要传入商家ID,用于平台商家企业调用。

/**
 * 获取和客服的最后一条聊天消息内容。
 * 可用于未读消息变化时,展示最后一条未读消息,或者展示客服的最后一条消息。
 *
 * @param shopId 商家ID
 * @return 最后一条消息
 * 在 > V5.7.7 版本中 POPManager.queryLastMessage(String shopId) 获取结果类型为 UnicornMessage
 */
UnicornMessage message = POPManager.queryLastMessage(String shopId);