这是本文档旧的修订版!


APP渠道集成

您已拥有一款手机APP,只需在环信客服云创建“APP关联”,并集成环信提供的SDK,即可轻松实现APP接入。

APP SDK集成

环信客服云为您提供了 Android SDK和iOS SDK,两个SDK均基于环信即时通讯云(IM) SDK 3.x,只需5分钟即可集成客服云通用功能。

“商城”demo源码:

如果您的APP已集成IM SDK 2.x,您也可以打开“商城”demo源码的地址,跳转到“旧版商城demo源码”,参考“旧版商城demo源码”集成客服云的基本功能。

注:集成过程中有任何技术问题,请联系环信技术支持。

集成留言功能

使用下文中的REST API实现留言的界面。

集成前的准备

集成留言功能前,需获取以下数据:

  • 当前租户的tenantId。前往“管理员模式 > 设置 > 企业信息”页面查看,并记录下来;
  • 留言项目的Project ID。前往“管理员模式 > 留言”页面查看,并记录下来;
  • 对接客服云的AppKey和IM服务号。前往“管理员模式 > 渠道管理 > 手机APP”页面,点击关联的“编辑”按钮查看,并记录下来;
  • 访客的visitorId(环信ID);
  • 该访客对应环信IM用户的token。

注:当前只支持通过环信IM这个渠道的访客端集成。

获取IM用户的token

使用以下REST API获取IM用户的token。Path前需加上域名,示例:https://a1.easemob.com/easemob-demo/chatdemoui/token

  • Path: /{org_name}/{app_name}/token
  • HTTP Method: POST
  • URL Params: 无
  • Request Headers: {“Content-Type”:“application/json”}
  • Request Body:
    {"grant_type": "password", "username": "{IM用户的用户名}", "password": "{IM用户的密码}"}

创建留言

使用以下REST API创建留言。创建语音留言时,请将附件类型设置为audio。Path需填写tenantId所在的域名,示例:https://kefu.easemob.com/tenants/2112/projects/2/tickets

  • Path: /tenants/:tenantId/projects/:projectId/tickets
  • HTTP Method: POST
  • URL Params: {“easemob-appkey”:“${orgName%23appName}”,“easemob-username”:“${visitorId}”,“easemob-target-username”:“${IM服务号}”}
  • Request Headers: {“Content-Type”:“application/json”,“Authorization”:“Easemob IM ${token}”}
  • Request Body:
{
    "subject": "留言的主题",   // 可选, 如果没有的话, 那么默认是content的前20个字
    "content": "留言的主要内容",
    "status_id": "留言的默认处理状态",   //  可选, 如果没有则使用project定义的默认的status, 如果没有定义默认的status则留空
    "priority_id": "优先级",   // 可选, 如果没有则使用project定义的默认的priority, 如果没有定义默认的priority则留空
    "category_id": "类别",    // 可选, 如果没有则使用project定义的默认的category, 如果没有定义默认的category则留空
    "origin_type": "渠道类型", // 可选, 参数的值为app, webim, weixin, weibo, 如果没有则默认为app
    "creator": {    // 留言的创建者
        "name": "创建这个留言的访客的名称",
        "avatar": "创建这个留言的访客的头像",   // 可选
        "email": "电子邮件地址",
        "phone": "电话号码",
        "qq": "qq号码",
        "company": "公司",
        "description": "具体的描述信息"
    },
    "attachments":[{    // 留言的附件
        "name": "该附件的名称",
        "url": "该附件的url",    // 附件需上传到您自己的服务器
        "type": "附件的类型, 当前支持image, file和audio"
    }]
}

获取全部留言

默认情况下,会返回此项目中创建者为此访客的最新的10个留言。Path需填写tenantId所在的域名,示例:https://kefu.easemob.com/tenants/2112/projects/2/tickets

  • Path: /tenants/:tenantId/projects/:projectId/tickets
  • HTTP Method: GET
  • URL Params: {“easemob-appkey”:“${orgName%23appName}”,“easemob-username”:“${visitorId}”,“easemob-target-username”:“${IM服务号}”}
  • Request Headers: {“Content-Type”:“application/json”,“Authorization”:“Easemob IM ${token}”}
  • Request Body: 无

URL Params还可以添加以下查询参数:

  • statusId: 按状态id过滤(可选,默认返回所有状态的留言)
  • categoryId: 按分类id过滤(可选,默认返回所有分类的留言,-1表示过滤未分类的留言)
  • assignee: 按处理者的id过滤。分配给谁的id或者none(区分大小写)来表示获取所有未分配的留言(可选,默认返回所有的处理人的留言)
  • startTime, endTime: timestamp类型的参数,用来按时间段来查询留言,取创建时间(可选,默认返回所有的创建时间的留言)
  • creator 按创建者的id过滤

返回值(忽略了分页部分的数据结构):

"entities": [{
    "id": "此ticket的id, long类型",
    "status": {
        "id": "此status的id",
        "name": "此status的name"
    },
    "subject": "留言的主题,可选,如果没有的话,那么默认是content的前20个字",
    "content": "留言的主要内容",
    "origin_type": "渠道类型",
    "creator": {
        "id": "此ticket的创建者的id",
        "name": "此ticket的创建者的name",
        "agentNumber":"创建这个ticket的人如果是座席,座席的工号,如果是访客,没有这个字段" ,
        "avatar": "此ticket的创建者的头像",
        "email": "电子邮件地址",
        "phone": "电话号码",
        "qq": "qq号码",
        "company": "公司",
        "description": "具体的描述信息"
    },
    "assignee": {
        "id": "此ticket的处理者的id",
        "name": "此ticket的处理者的name",
        "agentNumber":"处理这个ticket的人的工号",
        "avatar": "此ticket的处理者的头像",
        "email": "电子邮件地址",
        "phone": "电话号码",
        "qq": "qq号码",
        "company": "公司",
        "description": "具体的描述信息"
    }
}]

注:

  • 出于安全考虑,访客端查询的时候,只会返回创建者为此访客的留言,也就是访客只能查看自己创建的留言,而不能查看别人创建的。
  • 并且这个api只会返回ticket的基本信息,并不包括所有的comments,是为了供列表展示用。

分页

所有的查询一组数据的API,例如获取全部的留言(tickets),都是支持分页的,并且有默认的大小。

  • page:第几页,从0开始,默认为第0页
  • size:每一页的大小,默认为10,最大不能超过100
  • sort:排序相关的信息,以property,property(,ASC|DESC)的方式组织,例如sort=firstname&sort=lastname,desc表示在按firstname正序排列基础上按lastname倒序排列,默认为创建时间(created_at属性)

例如,获取全部留言(tickets)的API,可以写成:

GET /tenants/:tenantId/projects/:projectId/tickets?page=3&size=29&sort=createdAt,desc&sort=priorityId,asc

这样,会返回第三页的数据,包括最多29个tickets,并且会先按照创建时间倒序排列然后在按照优先级升序排列。

返回结果中包括了分页的相关信息,例如包括如下的属性:

  • first: 是否是第一页,true 或者false
  • last: 是否是最后一页,true或者false
  • totalPages: 总共有多少页
  • size: 每页大小
  • number: 当前页为第几页,从0开始
  • numberOfElements: 当前页一共有多少数据
  • entities: 数据,包括了所有这个页面中的数据

获取留言详情

根据留言ID获取一个留言的详情。Path需填写tenantId所在的域名,示例:https://kefu.easemob.com/tenants/2112/projects/2/tickets/10001

  • Path: /tenants/:tenantId/projects/:projectId/tickets/:ticketId
  • HTTP Method: GET
  • URL Params: {“easemob-appkey”:“${orgName%23appName}”,“easemob-username”:“${visitorId}”,“easemob-target-username”:“${IM服务号}”}
  • Request Headers: {“Content-Type”:“application/json”,“Authorization”:“Easemob IM ${token}”}
  • Request Body: 无

返回值:

{
    "id": "long类型的id",
    "origin_type": "渠道类型",
    "status": {    // 当前状态
        "id": "这个status对应的id",
        "name": "这个status对应的名称",
        "description": "这个status对应的描述",
        "icon_url": "这个status对应的图标的url"
    },
    "attachments":[{  // 附件
        "id": "附件的id",
        "name": "该附件的名称",
        "url": "该附件的url",
        "type": "附件的类型, 当前支持image, file和audio"
    }],
    "subject": "留言的主题,可选,如果没有的话,那么默认是content的前20个字",
    "content": "留言的主要内容",
    "creator": {  // 创建者
        "id": "创建这个评论的人的id",
        "username": "创建这个ticket的人的环信ID",
        "name": "创建这个ticket的人的name",
        "avatar": "创建这个ticket的人的头像",
        "type": "创建这个ticket的人的类型, 例如是坐席还是访客",
        "agentNumber":"创建这个ticket的人如果是座席,座席的工号,如果是访客,没有这个字段" ,
        "email": "电子邮件地址",
        "phone": "电话号码",
        "qq": "qq号码",
        "company": "公司",
        "description": "具体的描述信息"
    },
    "assignee": {  // 留言被分配给了谁
        "id": "这个留言被分配给了谁",
        "username": "处理这个留言的人的环信ID",
        "name": "处理这个留言的人的name",
        "avatar": "处理这个留言的人的头像",
        "type": "处理这个留言的人的类型,例如是坐席还是访客",
        "agentNumber":"处理这个ticket的人的工号",
        "email": "电子邮件地址",
        "phone": "电话号码",
        "qq": "qq号码",
        "company": "公司",
        "description": "具体的描述信息"
    },
    "created_at": "创建时间",
    "updated_at": "修改时间"
}

注:出于安全考虑,访客端查询的时候,只会返回创建者为此访客的留言,也就是访客只能查看自己创建的留言,而不能查看别人创建的。

对留言进行评论

给一个留言添加评论。添加语音评论时,请将附件类型设置为audio。Path需填写tenantId所在的域名,示例:https://kefu.easemob.com/tenants/2112/projects/2/tickets/10001/comments

  • Path: /tenants/:tenantId/projects/:projectId/tickets/:ticketId/comments
  • HTTP Method: POST
  • URL Params: {“easemob-appkey”:“${orgName%23appName}”,“easemob-username”:“${visitorId}”,“easemob-target-username”:“${IM服务号}”}
  • Request Headers: {“Content-Type”:“application/json”,“Authorization”:“Easemob IM ${token}”}
  • Request Body:
{
    "subject": "评论的主题, 可选",
    "content": "评论的内容",
    "reply": {
        "id": "回复的哪条评论的id, 可选"
    },
    "creator": {
        "id": "创建这个评论的人的id,可选",
        "username": "创建这个comment的人的环信ID",
        "name": "创建这个comment的人的name",
        "avatar": "创建这个comment的人的头像",
        "type": "创建这个comment的人的类型, 例如是坐席还是访客",
        "agentNumber":"创建这个comment的人如果是座席,座席的工号,如果是访客,没有这个字段" ,
        "email": "电子邮件地址",
        "phone": "电话号码",
        "qq": "qq号码",
        "company": "公司",
        "description": "具体的描述信息"
    },
    "attachments":[{
        "name": "该附件的名称",
        "url": "该附件的url",   // 附件需上传到您自己的服务器
        "type": "附件的类型, 当前支持image, file和audio"
    }], 
    "status_id": "status 的id" //设置了这个属性的话, 可以在添加评论的时候同时设置这个ticket的状态, 只有agent能够调用
}

获取留言评论

根据留言ID获取一个留言的评论。Path需填写tenantId所在的域名,示例:https://kefu.easemob.com/tenants/2112/projects/2/tickets/10001/comments

  • Path: /tenants/:tenantId/projects/:projectId/tickets/:ticketId/comments
  • HTTP Method: GET
  • URL Params: {“easemob-appkey”:“${orgName%23appName}”,“easemob-username”:“${visitorId}”,“easemob-target-username”:“${IM服务号}”}
  • Request Headers: {“Content-Type”:“application/json”,“Authorization”:“Easemob IM ${token}”}
  • Request Body: 无

返回值(忽略了分页部分的数据结构):

"entities":[  
  {  
    "id":"long类型的id",
    "subject":"评论的主题, 可选",
    "content":"评论的内容",
    "reply":{  
      "id":"回复的哪条评论的id, 可选"
    },
    "creator":{  
      "id":"创建这个评论的人的id",
      "username":"创建这个comment的人的环信ID",
      "name":"创建这个comment的人的name",
      "avatar":"创建这个comment的人的头像",
      "type":"创建这个comment的人的类型, 例如是坐席还是访客",
      "agentNumber":"创建这个comment的人如果是座席,座席的工号,如果是访客,没有这个字段",
      "email":"电子邮件地址",
      "phone":"电话号码",
      "qq":"qq号码",
      "company":"公司",
      "description":"具体的描述信息"
    },
    "attachments":[  
      {  
        "name":"该附件的名称",
        "url":"该附件的url",
        "type":"附件的类型, 当前支持image, file和audio"
      }
    ],
    "created_at":"创建时间",
    "updated_at":"修改时间"
  }
]

分页

获取留言评论(comments)支持分页,并且有默认的大小。

  • page:第几页,从0开始,默认为第0页
  • size:每一页的大小,默认为10,最大不能超过100
  • sort:排序相关的信息,以property,property(,ASC|DESC)的方式组织,例如sort=firstname&sort=lastname,desc表示在按firstname正序排列基础上按lastname倒序排列,默认为创建时间(created_at属性)

例如,获取留言评论的API,可以写成:

GET /tenants/:tenantId/projects/:projectId/tickets/:ticketId/comments?page=3&size=29&sort=createdAt,desc&sort=priorityId,asc

这样,会返回第三页的数据,包括最多29个comments,并且会先按照创建时间倒序排列然后在按照优先级升序排列。

返回结果中包括了分页的相关信息,例如包括如下的属性:

  • first: 是否是第一页,true 或者false
  • last: 是否是最后一页,true或者false
  • totalPages: 总共有多少页
  • size: 每页大小
  • number: 当前页为第几页,从0开始
  • numberOfElements: 当前页一共有多少数据
  • entities: 数据,包括了所有这个页面中的数据

APP端接收留言通知

一个人创建了留言之后,如果有别人回复了或者变更了这个留言的状态(例如把一个留言标记成已解决),留言系统能够发出通知给创建者,在手机APP端,可以在收到通知消息之后,调用上面相应的API来获取最新的变动,然后在留言的界面做展示,展示之后应该及时清除掉通知消息。

当前,留言系统只支持通过环信即时通讯云的消息扩展进行通知。

留言通知

目前支持以下留言通知。

留言状态改变

坐席改变一个留言的状态的时候,会给留言的创建者(访客的手机上)推送如下信息:

消息格式:

{
  "target" : [ "stliu0002" ],
  "msg" : {
    "type" : "txt",
    "msg" : "坐席[agent111]把留言[this is a ticket con]的状态从[未处理]变成了[已解决]"
  },
  "ext" : {
    "weichat" : {
      "notification" : true,
      "event" : {
        "eventName" : "TicketStatusChangedEvent",
        "ticket" : {
          "id" : 2000,
          "subject" : "this is a ticket con",
          "content" : "this is a ticket content",
          "version" : 0,
          "created_at" : "2016-01-10T16:43:40.914Z",
          "updated_at" : "2016-01-10T16:43:40.948Z"
        },
        "statusBefore" : {
          "id" : 1000,
          "name" : "未处理",
        },
        "statusAfter" : {
          "id" : 1001,
          "name" : "已解决",
        }
      }
    }
  }
}

新的留言评论

坐席回复一个留言的时候,会给留言的创建者(访客的手机)上推送如下的信息:

消息格式:

{
  "target" : [ "stliu0002" ],
  "msg" : {
    "type" : "txt",
    "msg" : "坐席[agent111]回复了留言[this is a ticket con], 内容是[sss]"
  },
  "ext" : {
    "weichat" : {
      "notification" : true,
      "event" : {
        "eventName" : "CommentCreatedEvent",
        "ticket" : {
          "id" : 2000,
          "subject" : "this is a ticket con",
          "content" : "this is a ticket content",
          "version" : 0,
          "created_at" : "2016-01-10T17:09:31.365Z",
          "updated_at" : "2016-01-10T17:09:31.366Z"
        },
        "comment" : {
          "id" : 3000,
          "creator" : {
            "id" : "dec9da4a-d692-43d9-9bed-9687adf8353a",
            "name" : "agent111",
          },
          "version" : 0,
          "created_at" : "2016-01-10T17:09:31.390Z",
          "updated_at" : "2016-01-10T17:09:31.390Z"
        }
      }
    }
  },
  "target_type" : "users"
}

接收通知消息

和正常接收消息一样,需要在主界面和聊天界面添加监听。详细用法可以参考商城Demo中实现。

附:留言和评论的数据结构

留言(Ticket)

留言是指一个具体的留言,其包括:

  • 创建者
  • 执行者(具体这个留言被分配给了谁)
  • 当前状态(例如是未分配,还是处理进行中,还是已解决)
  • 优先级
  • 类别
  • 如果被解决了的话,那么还包括解决方案
  • 主题
  • 具体问题的描述
  • 附件(多个)

留言的数据结构:

{
    "id": "long类型的id",
    "subject": "ticket的主题, 可选, 如果没有的话, 那么默认是content的前10个字",
    "content": "ticket的主要内容",
    "origin_type": "渠道类型",
    "status": {
        "id": "这个status对应的id",
        "name": "这个status对应的名称",
        "description": "这个status对应的描述",
        "icon_url": "这个status对应的图标的url"
    },
    "priority": {
        "id": "这个priority对应的id",
        "name": "这个priority对应的名称",
        "description": "这个priority对应的描述",
        "icon_url": "这个priority对应的图标的url"
    },
    "category": {
        "id": "这个category对应的id",
        "name": "这个category对应的名称",
        "description": "这个category对应的描述",
        "icon_url": "这个category对应的图标的url"
    },
    "creator": {
        "id": "创建这个评论的人的id",
        "username": "创建这个ticket的人的环信ID",
        "name": "创建这个ticket的人的name",
        "avatar": "创建这个ticket的人的头像",
        "type": "创建这个ticket的人的类型, 例如是坐席还是访客",
        "agentNumber":"创建这个ticket的人如果是座席,座席的工号,如果是访客,没有这个字段" ,
        "email": "电子邮件地址",
        "phone": "电话号码",
        "qq": "qq号码",
        "company": "公司",
        "description": "具体的描述信息"
    },
    "assignee": {
        "id": "这个ticket被分配给了谁",
        "username": "处理这个ticket的人的环信ID",
        "agentNumber":"处理这个ticket的人的工号",
        "name": "处理这个ticket的人的name",
        "phone":"处理这个ticket的人的手机号",
        "avatar": "处理这个ticket的人的头像",
        "type": "处理这个ticket的人的类型, 例如是坐席还是访客"
    },
    "attachments":[{
        "name": "该附件的名称",
        "url": "该附件的url",
        "type": "附件的类型, 当前支持image, file和audio"
    }],
    "created_at": "创建时间",
    "updated_at": "修改时间"
}

评论(Comment)

评论是指对一个留言的讨论,因为在解决一个留言的过程中,可能需要和留言的创建者进行多次的沟通交流,每一个消息都是一个comment。

评论包括:

  • 创建者
  • 具体内容
  • 附件(多个)
  • 是否对访客可见(因为可能涉及到多个坐席的协作)
  • 回复给特定的comment

评论的数据结构:

{
    "id": "long类型的id",
    "subject": "评论的主题, 可选",
    "content": "评论的内容",
    "reply": {
        "id": "回复的哪条评论的id, 可选"
    },
    "creator": {
        "name": "创建这个comment的人的name",
        "avatar": "创建这个comment的人的头像",
        "agentNumber":"创建这个comment的人如果是座席,座席的工号,如果是访客,没有这个字段" ,
        "email": "电子邮件地址",
        "phone": "电话号码",
        "qq": "qq号码",
        "company": "公司",
        "description": "具体的描述信息"
    },
    "attachments":[{
        "name": "该附件的名称",
        "url": "该附件的url",
        "type": "附件的类型, 当前支持image, file和audio"
    }],
    "created_at": "创建时间",
    "updated_at": "修改时间"
}