====== 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": "修改时间" }