====== APP渠道集成 ======
您已拥有一款手机APP,只需在环信客户互动云创建“APP关联”,并集成环信提供的SDK,即可轻松实现APP接入。
===== APP SDK集成 =====
环信客户互动云为您提供了 Android SDK和iOS SDK,两个SDK均基于环信即时通讯云(IM) SDK 3.x,只需5分钟即可集成客户互动云通用功能。
* Android SDK:请参考[[cs:300visitoraccess:androidsdk|CEC Android SDK 集成]]
* iOS SDK:请参考[[cs:300visitoraccess:iossdk|CEC iOS SDK 集成]]
“商城”demo源码:
* 下载地址:[[https://github.com/easemob/kefu-android-demo|Android“商城”demo源码]]
* 下载地址:[[https://github.com/easemob/helpdeskdemo-ios|iOS“商城”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": "修改时间"
}