iOS

iOS

6
评论

【开源OA项目】基于环信IM开发完整的企业通讯解决方案-Dolores Dolores OA 开源项目

KevinGong 发表了文章 • 6640 次浏览 • 2017-06-26 10:53 • 来自相关话题

  

  前阵子钉钉在微信楼下刷了一波#创业很苦,坚持很酷#的广告,浓浓的“丧”文化风格文案受到了各界褒贬不一的评价,也引起了大家对OA办公系统的关注。
   对企业而言,初选OA办公系统是为了满足需求,解决当下问题,由于OA办公系统的在公司运作流程中扮演的重要性,安全与隐私等问题急需未雨绸缪,“可定制”、“可私有化部署”的OA办公系统成为了更多企业的首选。公司想自己开发一套IM系统应该从哪里开始呢? 企业通讯录怎么保持同步呢? 企业通讯录的权限管理应该怎么做?
   三个关于OA办公系统的究极问题,从开源的OA办公项目-Dolores(朵拉)诞生迎刃而解了。Dolores项目遵循Apache Licence 2.0 开源协议,可以直接拿来用,也可以修改代码来满足需要并作为开源或商业产品发布/销售。




关于Dolores?
Dolores是一套完整的企业通信解决方案,一个完整的企业沟通工具(以下简称企业IM),支持以下几个功能:IM消息服务、组织架构管理、工作流集成。
Dolores项目源码地址:https://github.com/DoloresTeam​ 
技术讨论群:641256202(QQ群)

整个解决方案都包括了什么?
企业通讯录的管理:部门/员工的增删改查通讯录全量更新:全量/增量更新 企业通讯录权限管理:基于RBAC权限管理模型企业即时通讯IM:企业通信对IM这块的可靠性要求高,选择了目前比较成熟的IM云服务厂商-环信
 
 
组织架构

企业通讯录可以说是企业沟通中最重的业务之一,能够提供员工各种服务的认证,获取员工的联系方式等。
 
组织架构-Server

服务端主要包括以下功能:
支持管理人员(例如HR)对部门和员工进行增删改查支持部门和员工自定义排序,自定义元信息存储权限管理员工通讯录视图 (员工根据自己的权限生成通讯录)通讯录增量更新 (鉴于移动端特殊的网络环境和设备,通讯录应该支持差量更新)集成 IM 用户系统

在这里我们主要讨论以下两个问题:
 
权限管理

  随着企业逐渐的发展,团队壮大为了更有效的沟通,以及保护公司内部的一些商业信息不被泄漏,我们应该为通讯录添加权限管理。

基于Role-based access control(RBAC)的权限管理模型

为了介绍此权限管理模型,我们先解释一下基本概念
角色:通常是指企业中某一个工作岗位,这个岗位具有特定的权利和职责。被赋予此角色的员工,将获得这种权利与职责权限:被赋予访问实体的权利。在本项目中是指访问部门和访问某一个或者某一类员工的权利用户-角色分配(User-Role Assignment URA):为某个用户指定一个或者多个角色,此员工将获得这些角色所具有权利的集合角色-权限分配(Role-Permission Assignment RPA):将权限分配给角色,一个角色可以包含多个权利。在本项目中是指多个访问部门和访问员工的权限

在用户和权限之间引入角色中介,将用户与权限的直接关系弱化为间接关系。|ˉˉˉ| |ˉˉ ˉ| |ˉˉˉˉ ˉˉ|
| User |---URA---> | Role |<---RPA---| Permission |
|______| |_______| |_____________|
    以角色为中介,首先创建访问每个部门和员工的访问权限,然后创建不同的角色,根据这些角色的职责不同分配不同的权限,建立角色-权限的关系以后,不同的角色将会有不同的权限。根据员工不同的岗位,将对应的角色分配给他们,建立用户-角色关系,这就是RBAC的主要思想。

一个员工可以用户多个角色,一个角色可以用于多个访问权限。RBAC 极大的简化了员工的授权管理。

   由于企业的部门和员工数量很多,在创建权限时管理员不可能去设置每一个权限可以访问的每一个部门和每一个员工。所以本项目将功能和指责类似的部门和员工看作是同一类型,在创建部门和员工的时候为每一个部门和员工分配固有属性type,管理员在设置权限规则的时候只需要指定可访问的部门类型和员工即可。

增量更新

   鉴于移动终端计算资源有限,如网络,存储,电量等,所以通讯录的更新技术应该保证尽量少的资源。另外由于通讯录的特殊性,通讯录的变化需要能实时通知到受影响的在线员工。

基于版本号与变更日志的增量更新模型

   客户端第一次登陆系统以后,我们根据当前登录角色生成对应的通讯录视图,并以当前时间戳作为版本号,返回给客户端。客户端后续通过此版本号增量更新通讯录。

版本号

   版本号有两种:一是客户端当前通讯录版本 c-version, 二是服务端通讯录每一次变化时的版本号s-version

变更日志

   在管理员修改权限规则,或者修改某个岗位的访问规则时会影响大面积员工的通讯录视图,此时如果用增量更新会导致服务器流量异常,因此在这2中情况会清空原来的变更日志并且要求客户端进行一次全量更新。

   如果管理员新增了员工,服务端会根据被修改的员工或者部门type, 反推出所有受影响的员工,然后生成一条变更日志, 例如:{
"content" : [
{
"cn" : "Lucy.Liu",
"id" : "b4vlfg91scgi1dcju8v0",
"title" : "市场运营负责人",
"email" : [
"lucy.liu@dolores.store"
],
"priority" : "101111",
"name" : "刘小飞",
"telephoneNumber" : "18888888888"
}
],
"createTimestamp" : "20170614063303Z",
"category" : "member",
"action" : "add"
}
客户端在请求增量更新的时候,通过当前登陆ID与版本号,可查找出所有与自己相关的变更日志,然后在客户端数据库中应用这些变更,即可完成同步。

组织架构-Client

   由于现在员工办公设备的多样性,客户端要根据自己公司的情况,覆盖的足够完整,常见的平台有 iOS Android windowsmac linux , 对于后三个平台可以用 Web APP 来覆盖,iOS&Android 用原生的app来提升用户体验。

客户端App主要包括以下功能:
会话列表优秀的聊天界面,历史记录组织机构全量/增量更新员工个人资料展示

客户端数据库设计

IM数据库设计
 
当前版本使用环信SDK
 
组织架构数据库设计

表设计

客户端组织架构较服务端简单,不关联用户Role,客户端本地存储Staff(员工)和Department(部门)信息:
一个部门可以包含相关子部门和部门员工。该部门员工和部门在视图上处于同级关系。员工隶属于部门,同一员工可以存在于多个部门。员工角色用title来表示。

用户在登录客户端成功后,会根据该用户信息创建用户对应的数据库文件,用户表(User)保存用户相关信息,关联该用户staff信息。

客户端组织架构同服务端逻辑。

工作流集成

(TODO)
 
如何使用Dolores

本项目现在已经完成了第一个测试版本,本小节将指导您如何安装使用。

后端数据库

鉴于通讯录对数据库操作的特点多度少写,以及部门之间的树状关系,我们选择LDAP协议来存取数据。

我们有独立的repo来帮助您完成数据库的安装与初始化。请移步这里

组织架构管理

Dolores 初始版本使用Golang实现,大家既可以下载各个平台的可执行包,也可以安装Go语言的开发环境自己编译。

我们有独立的repo来帮助您,运行后端服务。请移步这里

客户端

我们现在有提供一个iOS版的Demo。请移步这里

Done

如果您顺利的完成以上三步,访问 http://localhost:3280 (端口号根据自己的配置,可能会有差异),使用 username: admin, password: dolores 登陆后端管理页面,添加权限规则,添加角色,添加员工、部门,然后使用iOS客户端登陆,就可以愉快的开始聊天啦~
 
负载均衡

(TODO)

多机容灾

(TODO)

LICENSE Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
更多信息请前往github项目主页

 
这里我对每个repo做一个简单的介绍


Dolores: 项目简介, 整个项目的架构, 数据库设计等等 你想了解的一切都可以在这里看到
dolores-ios: iOS版demo,可以聊天查看组织架构
dolores-android: 哈哈 还没有,当然我们欢迎各路安卓大牛贡献安卓版demo
organization: 组织架构的创建管理、更新、审计等等核心的东西都在这里啦
dolores-server: 为客户端提供restfull api 与环信服务器集成
dolores-admin: 后台管理网站,用于管理部门员工。一个基于React的webapp还很基础,欢迎各位大牛pr.
dolores-ldap-init: 后台数据库的初始化工具,详情可以查看readme
easemob-resty:对环信rest api的封装,让调用环信api更简单
dolores-avatar:生成类似钉钉那样的默认头像


最后再说一点整个服务端是用go来写的,作者也是golang的初学者,如果代码哪里写的有问题或者架构有问题欢迎大家指正THE CALM BEFORE THE STORM.
暴风雨前的宁静
ONE MORE THING 最后附上Dolores项目LOGO
当时作者正在二刷 《西部世界》这部剧,所以选择了女主的名字dolores作为整个项目的名字,而这个logo则寓意剧中的host。 查看全部
  

  前阵子钉钉在微信楼下刷了一波#创业很苦,坚持很酷#的广告,浓浓的“丧”文化风格文案受到了各界褒贬不一的评价,也引起了大家对OA办公系统的关注。
   对企业而言,初选OA办公系统是为了满足需求,解决当下问题,由于OA办公系统的在公司运作流程中扮演的重要性,安全与隐私等问题急需未雨绸缪,“可定制”、“可私有化部署”的OA办公系统成为了更多企业的首选。
公司想自己开发一套IM系统应该从哪里开始呢? 企业通讯录怎么保持同步呢? 企业通讯录的权限管理应该怎么做?

   三个关于OA办公系统的究极问题,从开源的OA办公项目-Dolores(朵拉)诞生迎刃而解了。Dolores项目遵循Apache Licence 2.0 开源协议,可以直接拿来用,也可以修改代码来满足需要并作为开源或商业产品发布/销售。
OA广告图.jpg

关于Dolores?

Dolores是一套完整的企业通信解决方案,一个完整的企业沟通工具(以下简称企业IM),支持以下几个功能:IM消息服务、组织架构管理、工作流集成。


Dolores项目源码地址:https://github.com/DoloresTeam​ 
技术讨论群:641256202(QQ群)

整个解决方案都包括了什么?
  • 企业通讯录的管理:部门/员工的增删改查
  • 通讯录全量更新:全量/增量更新 
  • 企业通讯录权限管理:基于RBAC权限管理模型
  • 企业即时通讯IM:企业通信对IM这块的可靠性要求高,选择了目前比较成熟的IM云服务厂商-环信

 
 
组织架构

企业通讯录可以说是企业沟通中最重的业务之一,能够提供员工各种服务的认证,获取员工的联系方式等。
 
组织架构-Server

服务端主要包括以下功能:
  1. 支持管理人员(例如HR)对部门和员工进行增删改查
  2. 支持部门和员工自定义排序,自定义元信息存储
  3. 权限管理
  4. 员工通讯录视图 (员工根据自己的权限生成通讯录)
  5. 通讯录增量更新 (鉴于移动端特殊的网络环境和设备,通讯录应该支持差量更新)
  6. 集成 IM 用户系统


在这里我们主要讨论以下两个问题:
 
权限管理

  随着企业逐渐的发展,团队壮大为了更有效的沟通,以及保护公司内部的一些商业信息不被泄漏,我们应该为通讯录添加权限管理。

基于Role-based access control(RBAC)的权限管理模型

为了介绍此权限管理模型,我们先解释一下基本概念
  • 角色:通常是指企业中某一个工作岗位,这个岗位具有特定的权利和职责。被赋予此角色的员工,将获得这种权利与职责
  • 权限:被赋予访问实体的权利。在本项目中是指访问部门和访问某一个或者某一类员工的权利
  • 用户-角色分配(User-Role Assignment URA):为某个用户指定一个或者多个角色,此员工将获得这些角色所具有权利的集合
  • 角色-权限分配(Role-Permission Assignment RPA):将权限分配给角色,一个角色可以包含多个权利。在本项目中是指多个访问部门和访问员工的权限


在用户和权限之间引入角色中介,将用户与权限的直接关系弱化为间接关系。
|ˉˉˉ|           |ˉˉ ˉ|          |ˉˉˉˉ ˉˉ|  
| User |---URA---> | Role |<---RPA---| Permission |
|______| |_______| |_____________|

    以角色为中介,首先创建访问每个部门和员工的访问权限,然后创建不同的角色,根据这些角色的职责不同分配不同的权限,建立角色-权限的关系以后,不同的角色将会有不同的权限。根据员工不同的岗位,将对应的角色分配给他们,建立用户-角色关系,这就是RBAC的主要思想。

一个员工可以用户多个角色,一个角色可以用于多个访问权限。RBAC 极大的简化了员工的授权管理。

   由于企业的部门和员工数量很多,在创建权限时管理员不可能去设置每一个权限可以访问的每一个部门和每一个员工。所以本项目将功能和指责类似的部门和员工看作是同一类型,在创建部门和员工的时候为每一个部门和员工分配固有属性type,管理员在设置权限规则的时候只需要指定可访问的部门类型和员工即可。

增量更新

   鉴于移动终端计算资源有限,如网络,存储,电量等,所以通讯录的更新技术应该保证尽量少的资源。另外由于通讯录的特殊性,通讯录的变化需要能实时通知到受影响的在线员工。

基于版本号与变更日志的增量更新模型

   客户端第一次登陆系统以后,我们根据当前登录角色生成对应的通讯录视图,并以当前时间戳作为版本号,返回给客户端。客户端后续通过此版本号增量更新通讯录。

版本号

   版本号有两种:一是客户端当前通讯录版本 c-version, 二是服务端通讯录每一次变化时的版本号s-version

变更日志

   在管理员修改权限规则,或者修改某个岗位的访问规则时会影响大面积员工的通讯录视图,此时如果用增量更新会导致服务器流量异常,因此在这2中情况会清空原来的变更日志并且要求客户端进行一次全量更新。

   如果管理员新增了员工,服务端会根据被修改的员工或者部门type, 反推出所有受影响的员工,然后生成一条变更日志, 例如:
{
"content" : [
{
"cn" : "Lucy.Liu",
"id" : "b4vlfg91scgi1dcju8v0",
"title" : "市场运营负责人",
"email" : [
"lucy.liu@dolores.store"
],
"priority" : "101111",
"name" : "刘小飞",
"telephoneNumber" : "18888888888"
}
],
"createTimestamp" : "20170614063303Z",
"category" : "member",
"action" : "add"
}

客户端在请求增量更新的时候,通过当前登陆ID与版本号,可查找出所有与自己相关的变更日志,然后在客户端数据库中应用这些变更,即可完成同步。

组织架构-Client

   由于现在员工办公设备的多样性,客户端要根据自己公司的情况,覆盖的足够完整,常见的平台有 iOS Android windowsmac linux , 对于后三个平台可以用 Web APP 来覆盖,iOS&Android 用原生的app来提升用户体验。

客户端App主要包括以下功能:
  1. 会话列表
  2. 优秀的聊天界面,历史记录
  3. 组织机构全量/增量更新
  4. 员工个人资料展示


客户端数据库设计

IM数据库设计
 
当前版本使用环信SDK
 
组织架构数据库设计

表设计

客户端组织架构较服务端简单,不关联用户Role,客户端本地存储Staff(员工)和Department(部门)信息:
  • 一个部门可以包含相关子部门和部门员工。该部门员工和部门在视图上处于同级关系。
  • 员工隶属于部门,同一员工可以存在于多个部门。
  • 员工角色用title来表示。


用户在登录客户端成功后,会根据该用户信息创建用户对应的数据库文件,用户表(User)保存用户相关信息,关联该用户staff信息。

客户端组织架构同服务端逻辑。

工作流集成

(TODO)
 
如何使用Dolores

本项目现在已经完成了第一个测试版本,本小节将指导您如何安装使用。

后端数据库

鉴于通讯录对数据库操作的特点多度少写,以及部门之间的树状关系,我们选择LDAP协议来存取数据。

我们有独立的repo来帮助您完成数据库的安装与初始化。请移步这里

组织架构管理

Dolores 初始版本使用Golang实现,大家既可以下载各个平台的可执行包,也可以安装Go语言的开发环境自己编译。

我们有独立的repo来帮助您,运行后端服务。请移步这里

客户端

我们现在有提供一个iOS版的Demo。请移步这里

Done

如果您顺利的完成以上三步,访问 http://localhost:3280 (端口号根据自己的配置,可能会有差异),使用 username: admin, password: dolores 登陆后端管理页面,添加权限规则,添加角色,添加员工、部门,然后使用iOS客户端登陆,就可以愉快的开始聊天啦~
 
负载均衡

(TODO)

多机容灾

(TODO)

LICENSE
 Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/

更多信息请前往github项目主页

 
这里我对每个repo做一个简单的介绍


Dolores: 项目简介, 整个项目的架构, 数据库设计等等 你想了解的一切都可以在这里看到
dolores-ios: iOS版demo,可以聊天查看组织架构
dolores-android: 哈哈 还没有,当然我们欢迎各路安卓大牛贡献安卓版demo
organization: 组织架构的创建管理、更新、审计等等核心的东西都在这里啦
dolores-server: 为客户端提供restfull api 与环信服务器集成
dolores-admin: 后台管理网站,用于管理部门员工。一个基于React的webapp还很基础,欢迎各位大牛pr.
dolores-ldap-init: 后台数据库的初始化工具,详情可以查看readme
easemob-resty:对环信rest api的封装,让调用环信api更简单
dolores-avatar:生成类似钉钉那样的默认头像


最后再说一点整个服务端是用go来写的,作者也是golang的初学者,如果代码哪里写的有问题或者架构有问题欢迎大家指正
THE CALM BEFORE THE STORM.
暴风雨前的宁静

ONE MORE THING 最后附上Dolores项目LOGO
当时作者正在二刷 《西部世界》这部剧,所以选择了女主的名字dolores作为整个项目的名字,而这个logo则寓意剧中的host。
687474703a2f2f6f7131696e636b76692e626b742e636c6f7564646e2e636f6d2f646f6c6f726573313032342e706e67.png
4
评论

【新手快速入门】集成环信常见问题+解决方案汇总 常见问题

dujiepeng 发表了文章 • 4070 次浏览 • 2017-05-22 15:51 • 来自相关话题

   这里整理了集成环信的常见问题和一些功能的实现思路,希望能帮助到大家。感谢热心的开发者贡献,大家在观看过程中有不明白的地方欢迎直接跟帖咨询。
 
ios篇
APNs证书创建和上传到环信后台头像昵称的简述和处理方案音视频离线推送Demo实现环信服务器聊天记录保存多久?离线收不到好友请求IOS中环信聊天窗口如何实现文件发送和预览的功能ios集成常见问题环信推送的一些常见问题实现名片|红包|话题聊天室等自定义cell
 
Android篇
环信3.0SDK集成小米推送教程EaseUI库中V4、v7包冲突解决方案Android EaseUI里的百度地图替换为高德地图android扩展消息(名片集成)关于会话列表的置顶聊天java.lang.UnsatisfiedLinkError: 的问题android 端 app 后台被杀死收不到消息的解决方案
昵称头像篇
android中如何显示开发者服务器上的昵称和头像 Android中显示头像(接上一篇文章看)环信(Android)设置头像和昵称的方法(最简单暴力的基于环信demo的集成)IOS中如何显示开发者服务器上的昵称和头像【环信公开课第12期视频回放】-所有关于环信IM昵称头像的问题听这课就够了
 
直播篇
一言不合你就搞个直播APP
 
客服集成
IM-SDK和客服SDK并存开发指南—Android篇IM-SDK和客服SDK并存开发指南—iOS篇
 
开源项目
Android简版demoios简版demo凡信2.0:超仿微信的开源项目 凡信3.0:携直播和红包而来高仿微信:Github 3,515 Star方圆十里:环信编程大赛冠军项目泛聊:定一个小目标写一个QQSlack聊天机器人:一天时间做一个聊天机器人TV视频通话:在电视上视频通话视频通话:Android手机视频通话酷信:ios高仿微信公众号助手:与订阅用户聊天沟通
 
持续更新ing...小伙伴们还有什么想知道欢迎跟帖提出。
  查看全部
   这里整理了集成环信的常见问题和一些功能的实现思路,希望能帮助到大家。感谢热心的开发者贡献,大家在观看过程中有不明白的地方欢迎直接跟帖咨询。
 
ios篇

 
Android篇

昵称头像篇

 
直播篇
  1. 一言不合你就搞个直播APP

 
客服集成
  1. IM-SDK和客服SDK并存开发指南—Android篇
  2. IM-SDK和客服SDK并存开发指南—iOS篇

 
开源项目

 
持续更新ing...小伙伴们还有什么想知道欢迎跟帖提出。
 
7
评论

【视频教程+源码】基于环信IM做一个仿微信APP-更新ing 郭永峰 高仿微信 仿微信 环信 XMPP

郭永峰 发表了文章 • 7836 次浏览 • 2017-05-16 15:29 • 来自相关话题

我只是一个普通人,做人要谦虚。
我不是大神,我也不是很厉害的。
天外有天,人外有人。
老师引入门,修行靠个人。
希望能帮助大家,谢谢。
    大家好,我是郭永峰(峰哥) | 一个普通大学计算机系毕业的大学生,曾就职于澳门遊澳集团有限公司,负责大型银联支付业务系统、跨国际短信业务系统(基于电信的SGIP)以及集团内部通讯系统 (负责android和openfire后台二次开发)的主要开发任务,担任项目负责人。13年就职于广州拓谷科技有限公司负责“酷蛙”车联网产品研发及汽车销售产品研发。14来年到17年2月份,就职于国内知名教育机构,负责教学研发及授课的工作。
 
本人现状况:
  
   在家录制教学视频(无收入),搞工作室,组建团队成立公司,如果大家觉得分享内容很喜欢,可以给我打点赏支持本人的工作室,二维码在文末,就不影响阅读了。

 
郭永峰IT教育工作室于2017年4月12日成立!
 
成立原因:

希望把近10年来从事IT互联网的知识分享给大家,包括Linux,WindowServer,Java,PHP,Android,iOS,H5等等等。
 

进入正题,本套课程基于环信IM教大家如何做一个类似微信的APP,只用于技术交流,请勿用于任何商业用途。
4月12号成立工作室,现在18号,过了一个星期一个星期录了5天的环信教程视频,我将放在网盘免费分享环信的教程视频主要是针对有开发经验者教程主要是使用环信来模仿微信来做一个即时通讯的案例课程主要是先讲socket基础 -> 环信 ->自定义协议希望这些教程视频能帮助大家,对即时通讯、socket和自定义协议有个较深入的了解同时能希望大家在面试时,在即时通讯这块不在陌生
  持续更新

第一阶段:即时通讯的了解和微信APP开发前的准备!

【视频教程+源码】基于环信IM做一个仿微信APP-01.即时通讯简介(了解)

【视频教程+源码】基于环信IM做一个仿微信APP-02.XMPP简介(了解)

【视频教程+源码】基于环信IM做一个仿微信APP-03.XMPP实现即时通信的准备工作(了解)

【视频教程+源码】基于环信IM做一个仿微信APP-04.环信简介(了解) 

【视频教程+源码】基于环信IM做一个仿微信APP-05.集成环信的前提准备(掌握)
 
【视频教程+源码】基于环信IM做一个仿微信APP-06.环信SDK的版本的区别(掌握)

【视频教程+源码】基于环信IM做一个仿微信APP-07.微信-项目创建及代码目录结构规范(MVC)

【视频教程+源码】基于环信IM做一个仿微信APP-08.微信-集成环信SDK

【视频教程+源码】基于环信IM做一个仿微信APP-09.微信-登录界面排版

【视频教程+源码】基于环信IM做一个仿微信APP-10.微信-主界面搭建

【视频教程+源码】基于环信IM做一个仿微信APP-11.微信-注册功能

【视频教程+源码】基于环信IM做一个仿微信APP- 12.微信-登录功能

【视频教程+源码】基于环信IM做一个仿微信APP- 13.微信-自动登录

【视频教程+源码】基于环信IM做一个仿微信APP- 14.微信-主动退出
 
【视频教程+源码】基于环信IM做一个仿微信APP-15.微信-在其它设备登录
 
整个项目源码,git地址https://github.com/mayaole/fWeiXin

 微信打赏






支付宝打赏






谢谢大家的支持,个人微信号清扫描下面张图






 
郭永峰IT交流QQ群请加:596441895 查看全部
我只是一个普通人,做人要谦虚。
我不是大神,我也不是很厉害的。
天外有天,人外有人。
老师引入门,修行靠个人。
希望能帮助大家,谢谢。

    大家好,我是郭永峰(峰哥) | 一个普通大学计算机系毕业的大学生,曾就职于澳门遊澳集团有限公司,负责大型银联支付业务系统、跨国际短信业务系统(基于电信的SGIP)以及集团内部通讯系统 (负责android和openfire后台二次开发)的主要开发任务,担任项目负责人。13年就职于广州拓谷科技有限公司负责“酷蛙”车联网产品研发及汽车销售产品研发。14来年到17年2月份,就职于国内知名教育机构,负责教学研发及授课的工作。
 
本人现状况:
  
   在家录制教学视频(无收入),搞工作室,组建团队成立公司,如果大家觉得分享内容很喜欢,可以给我打点赏支持本人的工作室,二维码在文末,就不影响阅读了。

 
郭永峰IT教育工作室于2017年4月12日成立!
 
成立原因:

希望把近10年来从事IT互联网的知识分享给大家,包括Linux,WindowServer,Java,PHP,Android,iOS,H5等等等。
 

进入正题,本套课程基于环信IM教大家如何做一个类似微信的APP,只用于技术交流,请勿用于任何商业用途。
  1. 4月12号成立工作室,现在18号,过了一个星期
  2. 一个星期录了5天的环信教程视频,我将放在网盘免费分享
  3. 环信的教程视频主要是针对有开发经验者
  4. 教程主要是使用环信来模仿微信来做一个即时通讯的案例
  5. 课程主要是先讲socket基础 -> 环信 ->自定义协议
  6. 希望这些教程视频能帮助大家,对即时通讯、socket和自定义协议有个较深入的了解
  7. 同时能希望大家在面试时,在即时通讯这块不在陌生

  持续更新

第一阶段:即时通讯的了解和微信APP开发前的准备!

【视频教程+源码】基于环信IM做一个仿微信APP-01.即时通讯简介(了解)

【视频教程+源码】基于环信IM做一个仿微信APP-02.XMPP简介(了解)

【视频教程+源码】基于环信IM做一个仿微信APP-03.XMPP实现即时通信的准备工作(了解)

【视频教程+源码】基于环信IM做一个仿微信APP-04.环信简介(了解) 

【视频教程+源码】基于环信IM做一个仿微信APP-05.集成环信的前提准备(掌握)
 
【视频教程+源码】基于环信IM做一个仿微信APP-06.环信SDK的版本的区别(掌握)

【视频教程+源码】基于环信IM做一个仿微信APP-07.微信-项目创建及代码目录结构规范(MVC)

【视频教程+源码】基于环信IM做一个仿微信APP-08.微信-集成环信SDK

【视频教程+源码】基于环信IM做一个仿微信APP-09.微信-登录界面排版

【视频教程+源码】基于环信IM做一个仿微信APP-10.微信-主界面搭建

【视频教程+源码】基于环信IM做一个仿微信APP-11.微信-注册功能

【视频教程+源码】基于环信IM做一个仿微信APP- 12.微信-登录功能

【视频教程+源码】基于环信IM做一个仿微信APP- 13.微信-自动登录

【视频教程+源码】基于环信IM做一个仿微信APP- 14.微信-主动退出
 
【视频教程+源码】基于环信IM做一个仿微信APP-15.微信-在其它设备登录
 
整个项目源码,git地址https://github.com/mayaole/fWeiXin

 微信打赏

微信.png


支付宝打赏

支付宝.png


谢谢大家的支持,个人微信号清扫描下面张图

个人.png


 
郭永峰IT交流QQ群请加:596441895
1
回复

ios编译通过 运行直接crash iOS

wangyuzhang 回复了问题 • 2 人关注 • 47 次浏览 • 2017-07-20 19:18 • 来自相关话题

6
最佳

会话列表接受消息时没反应 要下拉刷新才出来 怎么解决 消息接受 iOS 环信_iOS

ljQwe 回复了问题 • 5 人关注 • 1972 次浏览 • 2017-07-12 16:16 • 来自相关话题

1
回复

请教环信3.2.1版本升级到3.3.2版本指南。 iOS 环信_iOS

木云落 回复了问题 • 2 人关注 • 88 次浏览 • 2017-07-06 14:23 • 来自相关话题

25
最佳

求一份 iOS 3.x 群聊demo 环信_iOS 群聊demo 群聊 iOS

撑住丶为心中狂野的目标 回复了问题 • 25 人关注 • 2143 次浏览 • 2017-07-04 22:00 • 来自相关话题

2
回复

ios 导入easeui 报错 iOS iOS 环信

KevinGong 回复了问题 • 3 人关注 • 113 次浏览 • 2017-06-22 19:04 • 来自相关话题

2
回复
1
回复

iOS pod导入环信SDK iOS pod SDK

lifei9241 回复了问题 • 2 人关注 • 151 次浏览 • 2017-06-20 16:08 • 来自相关话题

2
评论

环信进阶篇-实现名片|红包|话题聊天室等自定义cell cell自定义 cell iOS环信 iOS iOS 新手

不死小强 发表了文章 • 237 次浏览 • 2017-06-20 09:50 • 来自相关话题

    伴随着即时通讯成为了越来越多APP的刚需,匿名社交、阅后即焚、红包等新玩法层出不穷,基本的聊天方式越来越难以满足变态的需求。环信提供的自定义扩展属性功能非常的强大,能够帮助我们在cell中的各种需求做定制处理。这是分享一个之前做过的方法及实现,大家可以借鉴处理的过程及思路,如有不妥之处,请大家及时留言告知,谢谢。 
今天就给大家介绍下怎么对cell中的各种需求的定制处理


 类型一:在现有会话cell上修改UI效果

类似于上面给出的截图,我们有时候需要对环信官方给出的cell进行些许的调整。例如:项目中加入了不同于普通群聊或者聊天室的功能需求
点击话题聊天,大家加入聊天室,这里发出的各种就是不同于普通聊天,普通的聊天只需展示文字、地址、图片等等,但是这里的需求是得加上时间、私聊按钮,没砍需求之前是还有点赞和取消赞的按钮。
我们在普通聊天的基础上新建几个cell,文字、语音、图片、地图等等,不能和原有的普通cell混合起来,因为需求有普通聊天。

直接把普通聊天cell中的代码拷贝过来,再在此基础上进行cell的UI自定义处理,就拿文字聊天时的处理情况为例:

1、拷贝复制原有普通聊天cell内的代码

2、把需要的新增的UI控件初始化

3、适配各类控件

4、传值及赋值

5、新增按钮点击和本身cell的点击效果处理(别和cell上的点击效果混到一起)

6、耐心调整cell上UI效果

以上基本就是简单的自定义cell步骤了,有基础的小伙伴看下步骤应该就有思路了

类型二:类似于红包和名片Cell的UI效果


通常在我们项目中,并不只有文字、图片等等这些简单的聊天内容,有时候我们需要把自己的信息作为一张名片发给对方、发个红包给好朋友、发一个项目中的一个模块介绍给对方等等功能要求。
我们就拿雷哥的这张假名片为例:/*!
@method
@brief 新增一个新的功能按钮
@discussion
@param image 按钮图片
@param highLightedImage 高亮图片
@param title 按钮标题
@result
*/
- (void)insertItemWithImage:(UIImage*)image
highlightedImage:(UIImage*)highLightedImage
title:(NSString*)title;
/*!
@method
@brief 修改功能按钮图片
@discussion
@param image 按钮图片
@param highLightedImage 高亮图片
@param title 按钮标题
@param index 按钮索引
@result
*/
- (void)updateItemWithImage:(UIImage*)image
highlightedImage:(UIImage*)highLightedImage
title:(NSString*)title
atIndex:(NSInteger)index;
/*!
@method
@brief 根据索引删除功能按钮
@discussion
@param index 按钮索引
@result
*/
- (void)removeItematIndex:(NSInteger)index;*  消息体类型
typedef enum{
EMMessageBodyTypeText  = 1,    /*! \~chinese 文本类型 \~english Text */
EMMessageBodyTypeImage,        /*! \~chinese 图片类型 \~english Image */
EMMessageBodyTypeVideo,        /*! \~chinese 视频类型 \~english Video */
EMMessageBodyTypeLocation,      /*! \~chinese 位置类型 \~english Location */
EMMessageBodyTypeVoice,        /*! \~chinese 语音类型 \~english Voice */
EMMessageBodyTypeFile,          /*! \~chinese 文件类型 \~english File */
EMMessageBodyTypeCmd,          /*! \~chinese 命令类型 \~english Command */
}EMMessageBodyType;
如果环信把这个开放出来,或许我们就更加简单了我们只需自己修改成自己对应的类型即可。但是这个目前就想想,所以我们可以在以上类型中找一个出来,在它的基础上做些文章,变成我们想要的类型。

红包和名片最像什么。。。。对,不就和图片差不多嘛,不过小伙伴也不要以为只能拿图片来做文章,其他的我们都可以拿来用,这里就拿文字类型来作为例子(原理都一样)。




名片类型
这里我们只简要介绍怎么根据会话类型来显示名片,具体传值等怎么做,有基础的小伙伴应该都懂,不懂的小伙伴见文章底部。
 
我们需要在发送名片时,在拓展消息里面存一个名片的字段,这个字段可以被用来判断是名片、红包等等。名片、红包等等中内容,同样也存在拓展属性中(这里不做过多介绍)我们在展示自己的消息和接收到对方的消息时,在文字类型的基础上再进一步判断是什么类型,加载对应类型的视图,如果是红包就加载红包的view,如果是名片就展示名片view......





加载不同类型的cell
好了,以上就是我们所要介绍的两种不同类型cell的处理办法。


以下是补充自定义cell时遇到的各种情况及处理:
1、cell上语音、图片等原始点击和新增按钮点击冲突处理:

注释掉原有的点击方法,把原有的点击方法放到具体的控件上去,避免cell上多个控件点击的冲突

重点:记得把气泡上的点击权限打开_backgroundImageView.userInteractionEnabled = YES;





解决点击冲突
2、cell上语音气泡长度的改变,避免过段影响布局

我们只需把原有语音上的语音长度Label距语音图片控件调大一点距离就能自动把语音类气泡拉长。(其他类型一样处理原理)




语音气泡拉长
3、因新增控件导致在原有cell上高度的变化处理
/*! @method @brief 根据消息的内容,获取当前cell的高度 @discussion @param model 消息对象model @result 返回cell高度 */
+ (CGFloat)cellHeightWithModel:(id)model在原cell高度处理的情况下,根据各种类型的判断进行cell高度的自适应。




cell高度处理
4、文字类型气泡长度的处理

我暂时的处理方法:判断输入的文字长度,加入文字长度小于10,我会在后面自动补全5个空格,被动撑长气泡的长度。

假如小伙伴们有更好的建议也可以留言,谢谢! 查看全部
    伴随着即时通讯成为了越来越多APP的刚需,匿名社交、阅后即焚、红包等新玩法层出不穷,基本的聊天方式越来越难以满足变态的需求。环信提供的自定义扩展属性功能非常的强大,能够帮助我们在cell中的各种需求做定制处理。这是分享一个之前做过的方法及实现,大家可以借鉴处理的过程及思路,如有不妥之处,请大家及时留言告知,谢谢。 
今天就给大家介绍下怎么对cell中的各种需求的定制处理


 类型一:在现有会话cell上修改UI效果

类似于上面给出的截图,我们有时候需要对环信官方给出的cell进行些许的调整。例如:项目中加入了不同于普通群聊或者聊天室的功能需求
点击话题聊天,大家加入聊天室,这里发出的各种就是不同于普通聊天,普通的聊天只需展示文字、地址、图片等等,但是这里的需求是得加上时间、私聊按钮,没砍需求之前是还有点赞和取消赞的按钮。
我们在普通聊天的基础上新建几个cell,文字、语音、图片、地图等等,不能和原有的普通cell混合起来,因为需求有普通聊天。

直接把普通聊天cell中的代码拷贝过来,再在此基础上进行cell的UI自定义处理,就拿文字聊天时的处理情况为例:


1、拷贝复制原有普通聊天cell内的代码

2、把需要的新增的UI控件初始化

3、适配各类控件

4、传值及赋值

5、新增按钮点击和本身cell的点击效果处理(别和cell上的点击效果混到一起)

6、耐心调整cell上UI效果

以上基本就是简单的自定义cell步骤了,有基础的小伙伴看下步骤应该就有思路了


类型二:类似于红包和名片Cell的UI效果


通常在我们项目中,并不只有文字、图片等等这些简单的聊天内容,有时候我们需要把自己的信息作为一张名片发给对方、发个红包给好朋友、发一个项目中的一个模块介绍给对方等等功能要求。
我们就拿雷哥的这张假名片为例:
/*!
@method
@brief 新增一个新的功能按钮
@discussion
@param image 按钮图片
@param highLightedImage 高亮图片
@param title 按钮标题
@result
*/
- (void)insertItemWithImage:(UIImage*)image
highlightedImage:(UIImage*)highLightedImage
title:(NSString*)title;
/*!
@method
@brief 修改功能按钮图片
@discussion
@param image 按钮图片
@param highLightedImage 高亮图片
@param title 按钮标题
@param index 按钮索引
@result
*/
- (void)updateItemWithImage:(UIImage*)image
highlightedImage:(UIImage*)highLightedImage
title:(NSString*)title
atIndex:(NSInteger)index;
/*!
@method
@brief 根据索引删除功能按钮
@discussion
@param index 按钮索引
@result
*/
- (void)removeItematIndex:(NSInteger)index;
*  消息体类型
typedef enum{
EMMessageBodyTypeText  = 1,    /*! \~chinese 文本类型 \~english Text */
EMMessageBodyTypeImage,        /*! \~chinese 图片类型 \~english Image */
EMMessageBodyTypeVideo,        /*! \~chinese 视频类型 \~english Video */
EMMessageBodyTypeLocation,      /*! \~chinese 位置类型 \~english Location */
EMMessageBodyTypeVoice,        /*! \~chinese 语音类型 \~english Voice */
EMMessageBodyTypeFile,          /*! \~chinese 文件类型 \~english File */
EMMessageBodyTypeCmd,          /*! \~chinese 命令类型 \~english Command */
}EMMessageBodyType;
如果环信把这个开放出来,或许我们就更加简单了我们只需自己修改成自己对应的类型即可。但是这个目前就想想,所以我们可以在以上类型中找一个出来,在它的基础上做些文章,变成我们想要的类型。

红包和名片最像什么。。。。对,不就和图片差不多嘛,不过小伙伴也不要以为只能拿图片来做文章,其他的我们都可以拿来用,这里就拿文字类型来作为例子(原理都一样)。
01.jpg

名片类型
这里我们只简要介绍怎么根据会话类型来显示名片,具体传值等怎么做,有基础的小伙伴应该都懂,不懂的小伙伴见文章底部。
 
  1. 我们需要在发送名片时,在拓展消息里面存一个名片的字段,这个字段可以被用来判断是名片、红包等等。
  2. 名片、红包等等中内容,同样也存在拓展属性中(这里不做过多介绍)
  3. 我们在展示自己的消息和接收到对方的消息时,在文字类型的基础上再进一步判断是什么类型,加载对应类型的视图,如果是红包就加载红包的view,如果是名片就展示名片view......


02.jpg

加载不同类型的cell
好了,以上就是我们所要介绍的两种不同类型cell的处理办法。


以下是补充自定义cell时遇到的各种情况及处理:
1、cell上语音、图片等原始点击和新增按钮点击冲突处理:

注释掉原有的点击方法,把原有的点击方法放到具体的控件上去,避免cell上多个控件点击的冲突

重点:记得把气泡上的点击权限打开
_backgroundImageView.userInteractionEnabled = YES;


03.jpg

解决点击冲突
2、cell上语音气泡长度的改变,避免过段影响布局

我们只需把原有语音上的语音长度Label距语音图片控件调大一点距离就能自动把语音类气泡拉长。(其他类型一样处理原理)
04.jpg

语音气泡拉长
3、因新增控件导致在原有cell上高度的变化处理
/*! @method @brief 根据消息的内容,获取当前cell的高度 @discussion @param model        消息对象model @result 返回cell高度 */
+ (CGFloat)cellHeightWithModel:(id)model
在原cell高度处理的情况下,根据各种类型的判断进行cell高度的自适应。
05.jpg

cell高度处理
4、文字类型气泡长度的处理

我暂时的处理方法:判断输入的文字长度,加入文字长度小于10,我会在后面自动补全5个空格,被动撑长气泡的长度。

假如小伙伴们有更好的建议也可以留言,谢谢!
1
回复

iOS 自己给自己发送消息 会重复 iOS iOS 环信

木云落 回复了问题 • 2 人关注 • 130 次浏览 • 2017-06-19 11:00 • 来自相关话题

5
回复

环信cocoapods集成EaseUI 继承时报错 iOS swift EaseUI cocoapod

爱我所爱 回复了问题 • 3 人关注 • 242 次浏览 • 2017-06-15 11:18 • 来自相关话题

0
评论

环信推送的一些常见问题 iOS

木云落 发表了文章 • 519 次浏览 • 2017-06-07 13:15 • 来自相关话题

原文地址 :  http://blog.csdn.net/jyt199011302/article/details/72829520

参考资料
APNS证书创建和上传到环信后台 : http://www.imgeek.org/article/825308748 
APNS离线推送文档 : http://docs.easemob.com/im/300iosclientintegration/75apns
离线推送的集成代码这里就不一一介绍了, 上面文档中写的 很明白了, 接下来说下比较容易误会的几点
一. 离线推送
如果app集成时添加- (void)applicationDidEnterBackground:(UIApplication *)application {
[[EMClient sharedClient] applicationDidEnterBackground:application];
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
[[EMClient sharedClient] applicationWillEnterForeground:application];
}
App后台静默后,能够保持长连接3分钟左右。超过3分钟,长连接会断开,当前登录的账号,在服务端被认为离线。消息会存入离线消息空间,之后接收的消息会在再次登录后,连接上服务器,然后通过长连接把消息取走,投递给此用户。如果app配置了推送证书,上传了推送证书并且集成了推送功能,服务器会给接收方发一个APNs推送,则会对离线消息进行APNs推送提示消息内容,通知接收方有一条新消息。
如果想自定义推送的alert,可以在发消息的时候,在消息扩展中添加相应的字段。文档见:APNS内容解析

离线推送 : 当app被杀死或者进入后台三分钟之后
消息回调 : app在前台及app进入后台三分钟之内

注意 : 环信支持推送消息,只是目前还不能根据标签推送给特定用户组,也暂不支持推送模板。CMD消息没有推送,好友请求也没有推送

收不到离线推送时可以从下面几个方面找下原因

1.测试apns推送的时候,接受消息方的app是杀掉状态吗,或者进入后台三分钟以后
2.看看你环信后台上传的证书名称与工程中初始化SDK那里填的证书名 是不是相同的
3.配置证书时候填的id与你工程中的bundle id 是否相同
4.devicetoken有没有传给环信SDK。即查看管理后台中,对应 IM 账户下是否有您刚刚写的证书名。(如果没有,请检查您是否得到了 deviceToken)
5.确认Xcode环境是否配置正确 ,Build Settings---signing,看Debug对应的是不是开发的,Release对应的是不是生产的
6.在确认xcode运行环境是否正确 (Product-->Scheme-->Edit Scheme, 开发证书选Debug,生产证书选Release)
7.证书制作上传过程是否有问题,配置证书的时候是否设置了密码,正确的步骤可以参考:http://www.imgeek.org/article/825308748。另外可以用推送工具进行验证。
8.如果以上都没有问题,可以尝试重新制作上传一下推送证书。
对照这些检查一下,基本上就是这些原因
如果上面几点都符合的话,看下重新登录之后是否可以收到之前收不到推送消息
可以的,话提供一下AppKey,证书名 (查下证书是否被封)以及收不到的推送消息的消息id及发送方和接收方log
log导出请看这篇文章: http://www.imgeek.org/article/825308785 然后转成txt格式上传到工单上,同时注明上述配置都正确 , 环信这边来查下消息推送记录

注意 :后台没有证书名 是指用户列表后面没有显示证书名。这个证书名是SDK初始化的时候传的字符串,用户登录之后会进行绑定。
如果用户没有绑定证书名的话,肯定收不到推送的。这个证书名是用户登录之后绑定的,要确认下初始化SDK的时候有没有传。 options.apnsCertName = apnsCertName;

==============常见问题==============

Q : iOS apns离线推送证书apns的离线推送可以和友盟(极光)推送共用一个证书吗?
A : 环信的推送只要和后台上传的证书对应就可以实现,其他的不关心。
首先苹果推送证书的生成都是统一的方式,这个不区分是极光(友盟)推送证书还是个推证书等等。使用的推送证书只要按照正确的苹果推送证书生成流程创建,都可以使用。
环信添加推送证书可以看http://www.imgeek.org/article/825308748 不是要求必须重新生成推送证书 
Q : 好友申请通知的离线推送?
A : 我们的好友体系,添加好友的申请不支持离线推送。
如果你们是使用App本身的好友体系,可以在app的添加好友业务上向被添加的好友发送文本消息,在EMMessage的ext中设置自定义字段,来区分此条文本消息是否用于好友申请提示,由此来判断处理UI的显示。

Q : iOS的杀死进程远程推送和服务端有关么
A : 如果客户端把远程通知给关了肯定就收不到通知,我们服务器会检测客户端是否有deviceToken,有的话才会把消息发送到deviceToken对应的设备上

Q : 每个项目创建了一个开发的推送证书一个生产的推送证书。这俩证书什么时候要做切换?
A : 在App上传AppStore前需要修改App内初始化SDK设置的推送证书名,EMOptions的apnsCertName。
注意,这里的值需要和在Console管理后天上传时设置的证书名一致。

Q : 绑定devicetoken的时候是否需要先登录到环信?
A : 绑定是需要登录过之后才进行的,
 - (EMError *)bindDeviceToken:(NSData *)aDeviceToken; 是把deviceToken传给SDK。调用登录,SDK会进行绑定。也可以调用 - (void)registerForRemoteNotificationsWithDeviceToken:(NSData *)aDeviceToken
completion:(void (^)(EMError *aError))aCompletionBlock;自己绑定
需要判断是否已经登录,如果已经有登录的账号,再登录会返回 已登录的错误。

Q : 离线推送在客户端怎么设置显示详情?
A : EMPushOptions *pushOptions = [[EMClient sharedClient] pushOptions];
pushOptions.displayStyle = EMPushDisplayStyleMessageSummary;
可以设置离线推送消息显示具体内容还是只显示-您收到一条消息
要设置在登录成功之后,然后要用服务器拉取一遍APNS 属性EMError *error = nil;
EMPushOptions *options = [[EMClient sharedClient] getPushOptionsFromServerWithError:&error];
然后在修改displayStyle

Q : 两个APP通信,如果只希望其中一类APP能收到推送,而另一端的APP不希望收到推送,是不是不希望收到推送的APP不配置证书就好了?
A : 两个App的推送证书都是在同一appkey下单独配置的,如果不希望收到推送,可以对此App不配置推送证书,同时在App代码中注释掉注册远程通知的相关代码。
bundle id对应的证书也可以取消push的功能,针对App不使用任何远程推送服务,包括其他第三方的,如果App还需要其他第三方的推送服务,请忽略这句话。

Q : 不配置推送证书的APP是不是只有刷新的情况下才会显示新的消息?不刷新的情况下APP是看不到新的消息?
A : 不配置推送功能的App,只有在用户登录成功后,才能通过长连接的接收消息回掉中拿到消息体。

Q : 在开发环境下收到了离线推送消息,但是在生成环境下没有收到?
A : 看一下SDK初始化时,是否设置的apnsCerName与生产环境证书上传时填写的证书名一致,还有是否为adhoc打包成ipa文件安装测试的。

Q :发送消息1,2,3,4,5 对方收到推送 2 1 5 顺序不对而且丢失, app角标也不对
A : 1.首先,苹果不保证所有远程推送的到达率。这个可以看苹果官方文档。
Because the delivery of remote notifications is not guaranteed, never include sensitive data or data that can be retrieved by other means in your payload.
2.我们只保证,把离线消息执行远程推送,发给苹果服务器。苹果服务器是否能够百分之百把所有推送送达到指定移动端,这个根据苹果的策略,当 APNs 向你发送了多条推送,你的设备在 APNs 那里下线了,这时 APNs 到你的手机的链路上有多条任务堆积,APNs 的处理方式是,只保留最后一条消息推送给你,然后告知你推送数。那么其他消息会被APNs丢弃。
3.我们保证的是离线消息,当用户重新登录时,可以都接收到。
4. 如果需要找后台查询离线消息(前提接收方已绑定deviceToken)是否成功,需要提供离线消息的messageId,接收方环信id
5.我们的推送角标,是接收方的在服务端的离线消息数。
https://developer.apple.com/li ... 8-SW1
这是官方文档





Q11 :ios是怎么判断离线了 然后发推送的啊 有时候把应用杀掉后 半天收不到推送
A : rest可以查用户的状态,推送前提是此用户有devicetoken已经绑定成功
如果账号所有配置都没问题,杀掉后,其他人发的消息,过几秒就能看到推送
Q12 : Q11不管用会是什么原因呢
A : 配置,还有账号在我们这绑的deviceToken

Q : 多个app共用一个appkey 推送证书怎么配置呢
A : 后台可以上传多套推送证书。

Q :程序关闭后推送了一个消息,点击后怎样获取到环信传过来的数据
A : 需要用户点击横幅后,重新启动App,这时从- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions中,获取字典launchOptions,UIApplicationLaunchOptionsRemoteNotificationKey这个key下的数据,就是aps的字典数据

二 . 消息回调
app的长连接存在的时候,环信服务器检测您为在线状态,是不会给app推送消息的。app端在线的情况下,消息会通过长连接直接收取(didreceivemessage),收到消息,SDK会通过回调通知给上层。app通过收消息的回调拿到消息对象,然后解析并展示UI。
目前我们不支持App切后台后,可以一直执行。
我们SDK在切后台后,实现[[EMClient sharedClient] applicationDidEnterBackground:application];,会保持,直到被系统释放iOS目前其他方式应该都无法去实现一直保持App的活跃状态了。

==============常见问题==============
Q : 本地推送声音设置在哪 ?一条消息推送两声
A : 推送声音设置的要自己实现,具体可参考demo里的ChatDemoHelper类和MainViewController类里的
- (void)showNotificationWithMessage:(EMMessage *)message方法,该方法中有发送本地推送做的一系列操作,本地通知怎么做的,本地通知触发几次,一条消息推送几声,一下接收到多条消息响几声,都需要用户自己实现.

Q : 视频通话,推送怎么实现?
iOS 3.2.3之后,如果在实时通话接收方不在线时,发送提醒。
A : 1.在发起实时音视频通话前,需要设置EMCallOptions对象属性isSendPushIfOffline为YES;
2.遵守协议EMCallBuilderDelegate,实现其中的- (void)callRemoteOffline:(NSString *)aRemoteName 委托方法。
3.在第2步的方法中向 aRemoteName用户发送单聊消息。
如果被叫方已注册远程通知且绑定deviceToken,会收到对应消息的APNs推送,点击横幅来唤醒App。
上面是接收方离线的情况。如果接收方长连接还未断开,只是App切到后台,需要在回调- (void)callDidReceive:(EMCallSession *)aSession中判断当前App是否在后台,如果是弹出本地通知。

Q : EMCallOptions *options = [[EMClient sharedClient].callManager getCallOptions]; //当对方不在线时,是否给对方发送离线消息和推送,并等待对方回应 options.isSendPushIfOffline = YES; [[EMClient sharedClient].callManager setCallOptions:options];isSendPushIfOffline设置为YES后,A用户呼叫B用户,B用户处于离线状态,但B用户没有收到推送。
A : 1. 先确接收方杀掉App后,文本消息是否能收到APNs推送。
2.在1点确认App杀掉可以收到推送前提下,确认实时音视频发送方代码执行顺序如下:(1) EMCallOptions *callOptions = [[EMClient sharedClient].callManager getCallOptions];
callOptions.isSendPushIfOffline = YES;
callOptions.offlineMessageText = @"提示文本";//可选
[[EMClient sharedClient].callManager setCallOptions:callOptions];
(2) callManager调用- (void)startVideoCall:(NSString *)aUsername
completion:(void (^)(EMCallSession *aCallSession, EMError *aError))aCompletionBlock;
或者- (void)startVoiceCall:(NSString *)aUsername
completion:(void (^)(EMCallSession *aCallSession, EMError *aError))aCompletionBlock;

Q : 推送的提示音可不可以自定义啊。
A: 推送的提示音目前不支持自定义,本地通知的你们以自己去设置。
 
Q : app压后台,立刻收到聊天推送来的信息,点击通知栏信息,捕获不到唤起程序事件
A : App切后台后,长连接为断开前,当前弹出的横幅是本地通知,那么此时唤醒时间是本地通知的回调- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification;
iOS10后- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)())completionHandler;

Q : 环信在离线状态下能收消息,但是程序运行状态按home按键进入后台的时候无法接受消息,怎么处理
A : App切入后台一段时间内,长连接还未断开,这时候接收消息都是通过SDK的接收消息回调(EMChatManagerDelegate)来收消息,不会执行APNs推送。
如果是需要弹出横幅提醒,需要在接收消息的回调方法中,判断[[UIApplication sharedApplication] applicationState]为UIApplicationStateBackground,然后实现本地通知。可以参考demo中的处理(ChatDemoHelper的- (void)didReceiveMessages:(NSArray *)aMessages)
[attach]7500[/attach][size=13]


[/size] 查看全部
原文地址 :  http://blog.csdn.net/jyt199011302/article/details/72829520

参考资料
APNS证书创建和上传到环信后台 : http://www.imgeek.org/article/825308748 
APNS离线推送文档 : http://docs.easemob.com/im/300iosclientintegration/75apns
离线推送的集成代码这里就不一一介绍了, 上面文档中写的 很明白了, 接下来说下比较容易误会的几点
一. 离线推送
如果app集成时添加
- (void)applicationDidEnterBackground:(UIApplication *)application {
[[EMClient sharedClient] applicationDidEnterBackground:application];
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
[[EMClient sharedClient] applicationWillEnterForeground:application];
}

App后台静默后,能够保持长连接3分钟左右。超过3分钟,长连接会断开,当前登录的账号,在服务端被认为离线。消息会存入离线消息空间,之后接收的消息会在再次登录后,连接上服务器,然后通过长连接把消息取走,投递给此用户。如果app配置了推送证书,上传了推送证书并且集成了推送功能,服务器会给接收方发一个APNs推送,则会对离线消息进行APNs推送提示消息内容,通知接收方有一条新消息。
如果想自定义推送的alert,可以在发消息的时候,在消息扩展中添加相应的字段。文档见:APNS内容解析

离线推送 : 当app被杀死或者进入后台三分钟之后
消息回调 : app在前台及app进入后台三分钟之内

注意 : 环信支持推送消息,只是目前还不能根据标签推送给特定用户组,也暂不支持推送模板。CMD消息没有推送,好友请求也没有推送

收不到离线推送时可以从下面几个方面找下原因

1.测试apns推送的时候,接受消息方的app是杀掉状态吗,或者进入后台三分钟以后
2.看看你环信后台上传的证书名称与工程中初始化SDK那里填的证书名 是不是相同的
3.配置证书时候填的id与你工程中的bundle id 是否相同
4.devicetoken有没有传给环信SDK。即查看管理后台中,对应 IM 账户下是否有您刚刚写的证书名。(如果没有,请检查您是否得到了 deviceToken)
5.确认Xcode环境是否配置正确 ,Build Settings---signing,看Debug对应的是不是开发的,Release对应的是不是生产的
6.在确认xcode运行环境是否正确 (Product-->Scheme-->Edit Scheme, 开发证书选Debug,生产证书选Release)
7.证书制作上传过程是否有问题,配置证书的时候是否设置了密码,正确的步骤可以参考:http://www.imgeek.org/article/825308748。另外可以用推送工具进行验证。
8.如果以上都没有问题,可以尝试重新制作上传一下推送证书。
对照这些检查一下,基本上就是这些原因
如果上面几点都符合的话,看下重新登录之后是否可以收到之前收不到推送消息
可以的,话提供一下AppKey,证书名 (查下证书是否被封)以及收不到的推送消息的消息id及发送方和接收方log
log导出请看这篇文章: http://www.imgeek.org/article/825308785 然后转成txt格式上传到工单上,同时注明上述配置都正确 , 环信这边来查下消息推送记录

注意 :后台没有证书名 是指用户列表后面没有显示证书名。这个证书名是SDK初始化的时候传的字符串,用户登录之后会进行绑定。
如果用户没有绑定证书名的话,肯定收不到推送的。这个证书名是用户登录之后绑定的,要确认下初始化SDK的时候有没有传。 options.apnsCertName = apnsCertName;

==============常见问题==============

Q : iOS apns离线推送证书apns的离线推送可以和友盟(极光)推送共用一个证书吗?
A : 环信的推送只要和后台上传的证书对应就可以实现,其他的不关心。
首先苹果推送证书的生成都是统一的方式,这个不区分是极光(友盟)推送证书还是个推证书等等。使用的推送证书只要按照正确的苹果推送证书生成流程创建,都可以使用。
环信添加推送证书可以看http://www.imgeek.org/article/825308748 不是要求必须重新生成推送证书 
Q : 好友申请通知的离线推送?
A : 我们的好友体系,添加好友的申请不支持离线推送。
如果你们是使用App本身的好友体系,可以在app的添加好友业务上向被添加的好友发送文本消息,在EMMessage的ext中设置自定义字段,来区分此条文本消息是否用于好友申请提示,由此来判断处理UI的显示。

Q : iOS的杀死进程远程推送和服务端有关么
A : 如果客户端把远程通知给关了肯定就收不到通知,我们服务器会检测客户端是否有deviceToken,有的话才会把消息发送到deviceToken对应的设备上

Q : 每个项目创建了一个开发的推送证书一个生产的推送证书。这俩证书什么时候要做切换?
A : 在App上传AppStore前需要修改App内初始化SDK设置的推送证书名,EMOptions的apnsCertName。
注意,这里的值需要和在Console管理后天上传时设置的证书名一致。

Q : 绑定devicetoken的时候是否需要先登录到环信?
A : 绑定是需要登录过之后才进行的,
 - (EMError *)bindDeviceToken:(NSData *)aDeviceToken; 是把deviceToken传给SDK。调用登录,SDK会进行绑定。也可以调用 - (void)registerForRemoteNotificationsWithDeviceToken:(NSData *)aDeviceToken
completion:(void (^)(EMError *aError))aCompletionBlock;自己绑定
需要判断是否已经登录,如果已经有登录的账号,再登录会返回 已登录的错误。

Q : 离线推送在客户端怎么设置显示详情?
A :
 EMPushOptions *pushOptions = [[EMClient sharedClient] pushOptions];
pushOptions.displayStyle = EMPushDisplayStyleMessageSummary;

可以设置离线推送消息显示具体内容还是只显示-您收到一条消息
要设置在登录成功之后,然后要用服务器拉取一遍APNS 属性
EMError *error = nil;
EMPushOptions *options = [[EMClient sharedClient] getPushOptionsFromServerWithError:&error];

然后在修改displayStyle

Q : 两个APP通信,如果只希望其中一类APP能收到推送,而另一端的APP不希望收到推送,是不是不希望收到推送的APP不配置证书就好了?
A : 两个App的推送证书都是在同一appkey下单独配置的,如果不希望收到推送,可以对此App不配置推送证书,同时在App代码中注释掉注册远程通知的相关代码。
bundle id对应的证书也可以取消push的功能,针对App不使用任何远程推送服务,包括其他第三方的,如果App还需要其他第三方的推送服务,请忽略这句话。

Q : 不配置推送证书的APP是不是只有刷新的情况下才会显示新的消息?不刷新的情况下APP是看不到新的消息?
A : 不配置推送功能的App,只有在用户登录成功后,才能通过长连接的接收消息回掉中拿到消息体。

Q : 在开发环境下收到了离线推送消息,但是在生成环境下没有收到?
A : 看一下SDK初始化时,是否设置的apnsCerName与生产环境证书上传时填写的证书名一致,还有是否为adhoc打包成ipa文件安装测试的。

Q :发送消息1,2,3,4,5 对方收到推送 2 1 5 顺序不对而且丢失, app角标也不对
A : 1.首先,苹果不保证所有远程推送的到达率。这个可以看苹果官方文档。
Because the delivery of remote notifications is not guaranteed, never include sensitive data or data that can be retrieved by other means in your payload.
2.我们只保证,把离线消息执行远程推送,发给苹果服务器。苹果服务器是否能够百分之百把所有推送送达到指定移动端,这个根据苹果的策略,当 APNs 向你发送了多条推送,你的设备在 APNs 那里下线了,这时 APNs 到你的手机的链路上有多条任务堆积,APNs 的处理方式是,只保留最后一条消息推送给你,然后告知你推送数。那么其他消息会被APNs丢弃。
3.我们保证的是离线消息,当用户重新登录时,可以都接收到。
4. 如果需要找后台查询离线消息(前提接收方已绑定deviceToken)是否成功,需要提供离线消息的messageId,接收方环信id
5.我们的推送角标,是接收方的在服务端的离线消息数。
https://developer.apple.com/li ... 8-SW1
这是官方文档
20170605122458543-1.jpeg


Q11 :ios是怎么判断离线了 然后发推送的啊 有时候把应用杀掉后 半天收不到推送
A : rest可以查用户的状态,推送前提是此用户有devicetoken已经绑定成功
如果账号所有配置都没问题,杀掉后,其他人发的消息,过几秒就能看到推送
Q12 : Q11不管用会是什么原因呢
A : 配置,还有账号在我们这绑的deviceToken

Q : 多个app共用一个appkey 推送证书怎么配置呢
A : 后台可以上传多套推送证书。

Q :程序关闭后推送了一个消息,点击后怎样获取到环信传过来的数据
A : 需要用户点击横幅后,重新启动App,这时从- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions中,获取字典launchOptions,UIApplicationLaunchOptionsRemoteNotificationKey这个key下的数据,就是aps的字典数据

二 . 消息回调
app的长连接存在的时候,环信服务器检测您为在线状态,是不会给app推送消息的。app端在线的情况下,消息会通过长连接直接收取(didreceivemessage),收到消息,SDK会通过回调通知给上层。app通过收消息的回调拿到消息对象,然后解析并展示UI。
目前我们不支持App切后台后,可以一直执行。
我们SDK在切后台后,实现[[EMClient sharedClient] applicationDidEnterBackground:application];,会保持,直到被系统释放iOS目前其他方式应该都无法去实现一直保持App的活跃状态了。

==============常见问题==============
Q : 本地推送声音设置在哪 ?一条消息推送两声
A : 推送声音设置的要自己实现,具体可参考demo里的ChatDemoHelper类和MainViewController类里的
- (void)showNotificationWithMessage:(EMMessage *)message方法,该方法中有发送本地推送做的一系列操作,本地通知怎么做的,本地通知触发几次,一条消息推送几声,一下接收到多条消息响几声,都需要用户自己实现.

Q : 视频通话,推送怎么实现?
iOS 3.2.3之后,如果在实时通话接收方不在线时,发送提醒。
A : 1.在发起实时音视频通话前,需要设置EMCallOptions对象属性isSendPushIfOffline为YES;
2.遵守协议EMCallBuilderDelegate,实现其中的- (void)callRemoteOffline:(NSString *)aRemoteName 委托方法。
3.在第2步的方法中向 aRemoteName用户发送单聊消息。
如果被叫方已注册远程通知且绑定deviceToken,会收到对应消息的APNs推送,点击横幅来唤醒App。
上面是接收方离线的情况。如果接收方长连接还未断开,只是App切到后台,需要在回调- (void)callDidReceive:(EMCallSession *)aSession中判断当前App是否在后台,如果是弹出本地通知。

Q : EMCallOptions *options = [[EMClient sharedClient].callManager getCallOptions]; //当对方不在线时,是否给对方发送离线消息和推送,并等待对方回应 options.isSendPushIfOffline = YES; [[EMClient sharedClient].callManager setCallOptions:options];isSendPushIfOffline设置为YES后,A用户呼叫B用户,B用户处于离线状态,但B用户没有收到推送。
A : 1. 先确接收方杀掉App后,文本消息是否能收到APNs推送。
2.在1点确认App杀掉可以收到推送前提下,确认实时音视频发送方代码执行顺序如下:
(1) EMCallOptions *callOptions = [[EMClient sharedClient].callManager getCallOptions];
callOptions.isSendPushIfOffline = YES;
callOptions.offlineMessageText = @"提示文本";//可选
[[EMClient sharedClient].callManager setCallOptions:callOptions];

(2) callManager调用
- (void)startVideoCall:(NSString *)aUsername
completion:(void (^)(EMCallSession *aCallSession, EMError *aError))aCompletionBlock;

或者
- (void)startVoiceCall:(NSString *)aUsername
completion:(void (^)(EMCallSession *aCallSession, EMError *aError))aCompletionBlock;

Q : 推送的提示音可不可以自定义啊。
A: 推送的提示音目前不支持自定义,本地通知的你们以自己去设置。
 
Q : app压后台,立刻收到聊天推送来的信息,点击通知栏信息,捕获不到唤起程序事件
A : App切后台后,长连接为断开前,当前弹出的横幅是本地通知,那么此时唤醒时间是本地通知的回调
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification;

iOS10后
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)())completionHandler;

Q : 环信在离线状态下能收消息,但是程序运行状态按home按键进入后台的时候无法接受消息,怎么处理
A : App切入后台一段时间内,长连接还未断开,这时候接收消息都是通过SDK的接收消息回调(EMChatManagerDelegate)来收消息,不会执行APNs推送。
如果是需要弹出横幅提醒,需要在接收消息的回调方法中,判断[[UIApplication sharedApplication] applicationState]为UIApplicationStateBackground,然后实现本地通知。可以参考demo中的处理
(ChatDemoHelper的- (void)didReceiveMessages:(NSArray *)aMessages)
[attach]7500[/attach][size=13]


[/size]
1
回复

cocopod集成EaseUI出现300多个警告怎么回事 求解 iOS

木云落 回复了问题 • 2 人关注 • 104 次浏览 • 2017-06-06 17:00 • 来自相关话题

0
评论

IM-SDK和客服SDK并存开发指南—iOS篇 环信移动客服 iOS

beyond 发表了文章 • 692 次浏览 • 2017-06-06 10:47 • 来自相关话题

一、SDK 介绍
 
环信客服访客端SDK 基于 IM SDK 3.x , 如果同时使用客服访客端SDK和IM  SDK,只需要在初始化、登录、登出操作时使用客服访客端 SDK 提供的相应 API ,IM 的其他API 均不受影响。UI 部分集成需要分别导入HelpDeskUI 和 EaseUI(也可以自定义UI)。
 下面详细介绍IM 和 客服共存的开发步骤。
二、注意事项
 
开发过程中 初始化、登录和登出,务必使用客服访客端SDK 的API。IM SDK 和客服SDK 都包括了模拟器的CPU 架构,在上传到app store时需要剔除模拟器的CPU 架构,保留armv7、arm64。
 
三、资源准备
 
到环信官网下载客服访客端的开源的商城Demo源码 + SDK,下载链接:http://www.easemob.com/download/cs  选择“iOS客服访客端SDK”下载(如下图)。


到环信官网下载IM的开源的Demo源码 + SDK ,下载链接:http://www.easemob.com/download/im 选择iOS SDK(如下图)。  



四、资源简介
两个SDK 均包括基础版和实时音视频版,根据需求的不同需要导入不同的SDK组合。基础版命名在名称后边添加Lite标识。从官网下载的客服访客端SDK包括以下目录: helpdeskdemo-ios、HelpDeskFramework、HelpDeskUI和HyphenateFramework,分别表示:
- helpdeskdemo-ios 为包含实时音视频的Demo,可直接运行。- HelpDeskFramework 为客服访客端SDK,HelpDesk.framework包含实时音视频、HelpDeskLite.framework不包含实时音视频。- HelpDeskUI 为环信用户提供的单聊UI,可在集成的时候视情况使用。- HyphenateFramework 为客服SDK依赖库(IM SDK),Hyphenate.framework和HelpDesk.framework一起导入,HyphenateLite.framework和HelpDeskLite.framework一起导入。

下载的IM SDK+Demo,包含五个文件夹,并且和客服SDK 中都有 IM 的framework文件,为了保持版本的匹配,我们只使用其中的 EaseUI 而不使用IM SDK中 的framework文件。

五、集成步骤
 
参考客服访客端SDK文档集成客服的访客端SDK,文档地址:http://docs.easemob.com/cs/300visitoraccess/iossdk。将IM SDK 中的EaseUI 导入到工程中,需要在导入访客端客服SDK的地方导入#import "EaseUI.h"   [重要提示:IM 的初始化、登录、登出操作需要使用客服的相关API]在pch文件中引入
#ifdef __OBJC__
//包含实时音视频功能
#import <HelpDesk/HelpDesk.h>
//若不包含实时音视频,则替换为
#import <HelpDeskLite/HelpDeskLite.h>

#import "HelpDeskUI.h"
#import "EaseUI.h"
#endif
   4. 然后将EaseUI或者HelpDeskUI中的 FixFopen.c文件删除(重复冲突)。
   5. 由于HelpDeskUI 和 EaseUI 中使用了 第三方库,如果工程中出现三方重复的问题,可将HelpDeskUI 和 EaseUI  中的重复文件删除,如果部分接口已经升级或弃用可自行升级、调整。
 
六、工程设置
 
在General -> Embedded Binaries 中导入HelpDesk.framework和Hyphenate.framework(这是包含实时音视频的,如果不包含实时音视频则导入HelpDeskLite.framework和HyphenateLite.framework)。在Build Settings -> Linking -> Other Linker Flags 中增加 -ObjC项,注意区分大小写。客服 访客端SDK暂不支持bitcode,所以需要将Build Settings -> Build Options -> Enable Bitcode 设为NO。为了减少不必要的警告,可将Build Settings -> Documentation Comments 设为 NO。

提供的兼容Demo介绍:
 
Demo是在客服的商城Demo上修改,在左上角添加了一个聊天室的按钮,点击按钮会根据appkey随机创建一个账号并登录,登录成功后会进入聊天室列表界面,点击某个聊天室可以在聊天室中聊天。Demo中客服部分功能还是和原商城Demo功能一致。Demo中为了演示因此采用随机注册账号的方式,对于用户场景中,可以先注册好这些账号和自己的账号绑定,这样每次咨询客服就都是同一个人了,也可以显示这个访客曾经的聊天记录。
 
Demo源码地址:http://kefu-prod-apk.oss-cn-ha ... o.zip
  查看全部
一、SDK 介绍
 
  1. 环信客服访客端SDK 基于 IM SDK 3.x , 如果同时使用客服访客端SDK和IM  SDK,只需要在初始化、登录、登出操作时使用客服访客端 SDK 提供的相应 API ,IM 的其他API 均不受影响。
  2. UI 部分集成需要分别导入HelpDeskUI 和 EaseUI(也可以自定义UI)。

 下面详细介绍IM 和 客服共存的开发步骤。
二、注意事项
 
  1. 开发过程中 初始化、登录和登出,务必使用客服访客端SDK 的API。
  2. IM SDK 和客服SDK 都包括了模拟器的CPU 架构,在上传到app store时需要剔除模拟器的CPU 架构,保留armv7、arm64。

 
三、资源准备
 
  1. 到环信官网下载客服访客端的开源的商城Demo源码 + SDK,下载链接:http://www.easemob.com/download/cs  选择“iOS客服访客端SDK”下载(如下图)。
    01.jpg
  2. 到环信官网下载IM的开源的Demo源码 + SDK ,下载链接:http://www.easemob.com/download/im 选择iOS SDK(如下图)。  
    02.png

四、资源简介
  1. 两个SDK 均包括基础版和实时音视频版,根据需求的不同需要导入不同的SDK组合。基础版命名在名称后边添加Lite标识。
  2. 从官网下载的客服访客端SDK包括以下目录: helpdeskdemo-ios、HelpDeskFramework、HelpDeskUI和HyphenateFramework,分别表示:

  • - helpdeskdemo-ios 为包含实时音视频的Demo,可直接运行。
  • - HelpDeskFramework 为客服访客端SDK,HelpDesk.framework包含实时音视频、HelpDeskLite.framework不包含实时音视频。
  • - HelpDeskUI 为环信用户提供的单聊UI,可在集成的时候视情况使用。
  • - HyphenateFramework 为客服SDK依赖库(IM SDK),Hyphenate.framework和HelpDesk.framework一起导入,HyphenateLite.framework和HelpDeskLite.framework一起导入。


下载的IM SDK+Demo,包含五个文件夹,并且和客服SDK 中都有 IM 的framework文件,为了保持版本的匹配,我们只使用其中的 EaseUI 而不使用IM SDK中 的framework文件。

五、集成步骤
 
  1. 参考客服访客端SDK文档集成客服的访客端SDK,文档地址:http://docs.easemob.com/cs/300visitoraccess/iossdk
  2. 将IM SDK 中的EaseUI 导入到工程中,需要在导入访客端客服SDK的地方导入#import "EaseUI.h"   [重要提示:IM 的初始化、登录、登出操作需要使用客服的相关API]
  3. 在pch文件中引入

 #ifdef __OBJC__
//包含实时音视频功能
#import <HelpDesk/HelpDesk.h>
//若不包含实时音视频,则替换为
#import <HelpDeskLite/HelpDeskLite.h>

#import "HelpDeskUI.h"
#import "EaseUI.h"
#endif

   4. 然后将EaseUI或者HelpDeskUI中的 FixFopen.c文件删除(重复冲突)。
   5. 由于HelpDeskUI 和 EaseUI 中使用了 第三方库,如果工程中出现三方重复的问题,可将HelpDeskUI 和 EaseUI  中的重复文件删除,如果部分接口已经升级或弃用可自行升级、调整。
 
六、工程设置
 
  1. 在General -> Embedded Binaries 中导入HelpDesk.framework和Hyphenate.framework(这是包含实时音视频的,如果不包含实时音视频则导入HelpDeskLite.framework和HyphenateLite.framework)。
  2. 在Build Settings -> Linking -> Other Linker Flags 中增加 -ObjC项,注意区分大小写。
  3. 客服 访客端SDK暂不支持bitcode,所以需要将Build Settings -> Build Options -> Enable Bitcode 设为NO。
  4. 为了减少不必要的警告,可将Build Settings -> Documentation Comments 设为 NO。


提供的兼容Demo介绍:
 
  1. Demo是在客服的商城Demo上修改,在左上角添加了一个聊天室的按钮,点击按钮会根据appkey随机创建一个账号并登录,登录成功后会进入聊天室列表界面,点击某个聊天室可以在聊天室中聊天。
  2. Demo中客服部分功能还是和原商城Demo功能一致。
  3. Demo中为了演示因此采用随机注册账号的方式,对于用户场景中,可以先注册好这些账号和自己的账号绑定,这样每次咨询客服就都是同一个人了,也可以显示这个访客曾经的聊天记录。

 
Demo源码地址:http://kefu-prod-apk.oss-cn-ha ... o.zip
 
1
回复

在pct中导入EMSDKFull.h时报错 iOS 环信

木云落 回复了问题 • 2 人关注 • 139 次浏览 • 2017-05-26 11:06 • 来自相关话题

2
回复

iOS 环信2.0 发送消息重复。 iOS

刘小刘 回复了问题 • 3 人关注 • 947 次浏览 • 2017-05-22 20:01 • 来自相关话题

1
回复

为什么环信的demo里边群主可以屏蔽群消息? iOS

木云落 回复了问题 • 2 人关注 • 102 次浏览 • 2017-05-22 17:14 • 来自相关话题

2
评论

集成环信遇到的相关问题整理 iOS

木云落 发表了文章 • 1398 次浏览 • 2017-05-22 16:36 • 来自相关话题

最近在整理这段时间被别人问到引入环信可能会出现的问题,记得的也不太多,想到一个就在这里记录一个吧,如果有遇到过本文中没有列出来的,可以问我,我会一一解答的
原文地址: http://blog.csdn.net/jyt199011 ... 83995

1. pod引入的Hyphenate里面的.h文件中和手动下载的sdk相比会缺少Hyphenate.h 。
A :  主要是pod 问题 本地仓库太旧了, 终端行pod repo update, 之后在pod search 'Hyphenate' 如果可以找到3.3.0版本, 就可以下载了 podfile 里面 platform 要指定8.0

2. iOS SDK 从低版本 升到3.3.0 后运行报错 (集成动态库版本报错)
dyld: Library not loaded: @rpath/Hyphenate.framework/Hyphenate
  Referenced from: /Users/white/Library/Developer/CoreSimulator/Devices/BE0DDC26-96AE-4396-A6C5-48DC6938042B/data/Containers/Bundle/Application/4F9F570A-44B5-4F81-AD19-F7AA38D26E40/SYSchoolProject.app/SYSchoolProject
  Reason: image not found




A : 在Build setting -> General这里加上。 还有这里也加上 改不能成optional,
注意 : 改成optional之后会导致初始化为null






3.在AppDelegate中执行[EaseMob sharedInstance]崩溃
A : other link flags添加“-ObjC”选项(注意:O和C大写)


4. pod导入EaseUI 时报错 
A : 先进入Podfile文件中,添加pod 'EaseUI', :git => 'https://github.com/easemob/easeui-ios-hyphenate-cocoapods.git' ,保存退出之后执行pod update即可 ,如果还是失败,可以升级一下pod版本





5.‘Hyphenate/EMSDK.h’ file no found
A : 换下引用#import <HyphenateLite/HyphenateLite.h>
     或者#import <Hyphenate/Hyphenate.h>
     如果此方法不行, 可以试试选中你的项目中的Pods -> EaseUI->Build Phases->Link Binary With Libraries ,点➕->Add Other ,找到工程里面,Pods里面的Hyphenate文件夹下面的Hyphenate.framework 点击open,重新编译就好了






6. 




A :  可以参考问题2的基础上, 再看下相对路径那里


7.集成动态库上传AppStore出现问题, 打包上线时报错
ERROR ITMS-90087: "Unsupported Architectures. The executable for xiantaiApp.app/Frameworks/Hyphenate.framework contains unsupported architectures '[x86_64, i386]'."
A :  遇到这个问题的小伙伴一定是没有认真看咱们环信的官方文档,
由于 iOS 编译的特殊性,为了方便开发者使用,我们将 i386 x86_64 armv7 arm64 几个平台都合并到了一起,所以使用动态库上传appstore时需要将i386 x86_64两个平台删除后,才能正常提交审核

在SDK当前路径下执行以下命令删除i386 x86_64两个平台
实时音视频版本Hyphenate.frameworklipo Hyphenate.framework/Hyphenate -thin armv7 -output Hyphenate_armv7 lipo Hyphenate.framework/Hyphenate -thin arm64 -output Hyphenate_arm64 lipo -create Hyphenate_armv7 Hyphenate_arm64 -output Hyphenate mv Hyphenate Hyphenate.framework/
 
不包含实时音视频版本HyphenateLite.frameworklipo HyphenateLite.framework/HyphenateLite -thin armv7 -output HyphenateLite_armv7 lipo HyphenateLite.framework/HyphenateLite -thin arm64 -output HyphenateLite_arm64 lipo -create HyphenateLite_armv7 HyphenateLite_arm64 -output HyphenateLite mv HyphenateLite HyphenateLite.framework/
拿实时音视频版本版本为例 : 执行完以上命令如图所示




运行完毕后得到的Hyphenate.framework就是最后的结果,拖进工程,编译打包上架。




注意 : 
1. 最后得到的包必须真机编译运行,并且工程要设置编译二进制文件General->Embedded Bunaries.
2. 删除i386、x86_64平台后,SDK会无法支持模拟器编译,只需要在上传AppStore时在进行删除,上传后,替换为删除前的SDK,建议先分别把i386、x86_64、arm64、armv7各平台的包拆分到本地,上传App Store时合并arm64、armv7平台,并移入Hyphenate.framework内。上传后,重新把各平台包合并移入动态库


打包时还有可能报这个错误
ERROR ITMS-90535: "Unexpected CFBundleExecutable Key. The bundle at 'Payload/xiantaiApp.app/EaseUIResource.bundle' does not contain a bundle executable. If this bundle intentionally does not contain an executable, consider removing the CFBundleExecutable key from its Info.plist and using a CFBundlePackageType of BNDL. If this bundle is part of a third-party framework, consider contacting the developer of the framework for an update to address this issue."
A :  ​从EaseUIResource.bundle中找到info.plist删掉CFBundleExecutable,或者整个info.plist删掉



8.ios apns推送是什么原因导致这个错误
注册deviceToken失败:application:didFailToRegisterForRemoteNotificationsWithError: Error Domain=NSCocoaErrorDomain Code=3000 "未找到应用程序的“aps-environment”的授权字符串" UserInfo={NSLocalizedDescription=未找到应用程序的“aps-environment”的授权字符串}
A: 工程配置没有打开推送功能。

9.运行demo报这个错误




A: 没有存储空间了。
 
 
10. SDK3.3.1 以上版本手动导入EaseUI报错
A : 由于demo是用pod集成的,所以直接引入demo中的EaseUI会缺少相关文件,可以直接拖入附件中的EaseUI
如果引入之后报如下图的错误








其实碰到上面这个问题还是很好解决的,这个是因为用到了UIKit里的类,但是只导入了Foundation框架,这个错误在其他类里也会出现,我们可以手动修改Founfation为UIKit,但是我不建议这么做,第一这个做法的工程量比较大, 在其他类里面也要导入,二,不利于移植,当以后环信更新的时候我们还是需要做同样的操作,这里我的做法的创建一个pch文件,在pch文件里面导入UIKit。解决办法:建一个PCH文件在里面添加如下代码:





以上应该会正常了,但是如果集成的是不包含实时音视频的SDK, 您导入的EaseUI不是Lite版的,  那么此时还会报跟第六点一样的错误 , 需要导入EaseUILite 版本,所以会找不到Hyphenate/Hyphenate.h,如果是手动集成,建议在xcode设置一下Build Settings> GCC_PREPROCESSOR_DEFINITIONS >ENABLE_LITE=1,这样easeui就去找HyphenateLite/HyphenateLite.h
也可以通过pod集成,文档上针对easeui集成Full版本和Lite版本sdk特殊的说明http://docs.easemob.com/im/300 ... guide
 
或者不想导入Lite版的 , 只想引入EaseUI 
这时需要把 #import <Hyphenate/Hyphenate.h>注释掉,然后把报错地方的Hyphenate换成HyphenateLite就可以了
 
 
11. 




A : 可以删除或者重命名Podfile.lock文件,重新执行pod install命令 查看全部
最近在整理这段时间被别人问到引入环信可能会出现的问题,记得的也不太多,想到一个就在这里记录一个吧,如果有遇到过本文中没有列出来的,可以问我,我会一一解答的
原文地址: http://blog.csdn.net/jyt199011 ... 83995

1. pod引入的Hyphenate里面的.h文件中和手动下载的sdk相比会缺少Hyphenate.h 。
A :  主要是pod 问题 本地仓库太旧了, 终端行pod repo update, 之后在pod search 'Hyphenate' 如果可以找到3.3.0版本, 就可以下载了 podfile 里面 platform 要指定8.0

2. iOS SDK 从低版本 升到3.3.0 后运行报错 (集成动态库版本报错)
dyld: Library not loaded: @rpath/Hyphenate.framework/Hyphenate
  Referenced from: /Users/white/Library/Developer/CoreSimulator/Devices/BE0DDC26-96AE-4396-A6C5-48DC6938042B/data/Containers/Bundle/Application/4F9F570A-44B5-4F81-AD19-F7AA38D26E40/SYSchoolProject.app/SYSchoolProject
  Reason: image not found
20170330110241533.jpeg

A : 在Build setting -> General这里加上。 还有这里也加上 改不能成optional,
注意 : 改成optional之后会导致初始化为null
20170330104308569.jpeg



3.在AppDelegate中执行[EaseMob sharedInstance]崩溃
A : other link flags添加“-ObjC”选项(注意:O和C大写)


4. pod导入EaseUI 时报错 
A : 先进入Podfile文件中,添加pod 'EaseUI', :git => 'https://github.com/easemob/easeui-ios-hyphenate-cocoapods.git' ,保存退出之后执行pod update即可 ,如果还是失败,可以升级一下pod版本
屏幕快照_2017-05-22_上午10.27_.07_.png


5.‘Hyphenate/EMSDK.h’ file no found
A : 换下引用#import <HyphenateLite/HyphenateLite.h>
     或者#import <Hyphenate/Hyphenate.h>
     如果此方法不行, 可以试试选中你的项目中的Pods -> EaseUI->Build Phases->Link Binary With Libraries ,点➕->Add Other ,找到工程里面,Pods里面的Hyphenate文件夹下面的Hyphenate.framework 点击open,重新编译就好了
20170331200729906.jpeg



6. 
20170331110834145.jpeg

A :  可以参考问题2的基础上, 再看下相对路径那里


7.集成动态库上传AppStore出现问题, 打包上线时报错
ERROR ITMS-90087: "Unsupported Architectures. The executable for xiantaiApp.app/Frameworks/Hyphenate.framework contains unsupported architectures '[x86_64, i386]'."
A :  遇到这个问题的小伙伴一定是没有认真看咱们环信的官方文档,
由于 iOS 编译的特殊性,为了方便开发者使用,我们将 i386 x86_64 armv7 arm64 几个平台都合并到了一起,所以使用动态库上传appstore时需要将i386 x86_64两个平台删除后,才能正常提交审核

在SDK当前路径下执行以下命令删除i386 x86_64两个平台
实时音视频版本Hyphenate.frameworklipo Hyphenate.framework/Hyphenate -thin armv7 -output Hyphenate_armv7 lipo Hyphenate.framework/Hyphenate -thin arm64 -output Hyphenate_arm64 lipo -create Hyphenate_armv7 Hyphenate_arm64 -output Hyphenate mv Hyphenate Hyphenate.framework/
 
不包含实时音视频版本HyphenateLite.frameworklipo HyphenateLite.framework/HyphenateLite -thin armv7 -output HyphenateLite_armv7 lipo HyphenateLite.framework/HyphenateLite -thin arm64 -output HyphenateLite_arm64 lipo -create HyphenateLite_armv7 HyphenateLite_arm64 -output HyphenateLite mv HyphenateLite HyphenateLite.framework/
拿实时音视频版本版本为例 : 执行完以上命令如图所示
20170401112052481.png

运行完毕后得到的Hyphenate.framework就是最后的结果,拖进工程,编译打包上架。
20170401112216045.png

注意 : 
1. 最后得到的包必须真机编译运行,并且工程要设置编译二进制文件General->Embedded Bunaries.
2. 删除i386、x86_64平台后,SDK会无法支持模拟器编译,只需要在上传AppStore时在进行删除,上传后,替换为删除前的SDK,建议先分别把i386、x86_64、arm64、armv7各平台的包拆分到本地,上传App Store时合并arm64、armv7平台,并移入Hyphenate.framework内。上传后,重新把各平台包合并移入动态库


打包时还有可能报这个错误
ERROR ITMS-90535: "Unexpected CFBundleExecutable Key. The bundle at 'Payload/xiantaiApp.app/EaseUIResource.bundle' does not contain a bundle executable. If this bundle intentionally does not contain an executable, consider removing the CFBundleExecutable key from its Info.plist and using a CFBundlePackageType of BNDL. If this bundle is part of a third-party framework, consider contacting the developer of the framework for an update to address this issue."
A :  ​从EaseUIResource.bundle中找到info.plist删掉CFBundleExecutable,或者整个info.plist删掉



8.ios apns推送是什么原因导致这个错误
注册deviceToken失败:application:didFailToRegisterForRemoteNotificationsWithError: Error Domain=NSCocoaErrorDomain Code=3000 "未找到应用程序的“aps-environment”的授权字符串" UserInfo={NSLocalizedDescription=未找到应用程序的“aps-environment”的授权字符串}
A: 工程配置没有打开推送功能。

9.运行demo报这个错误
20170519110027739.png

A: 没有存储空间了。
 
 
10. SDK3.3.1 以上版本手动导入EaseUI报错
A : 由于demo是用pod集成的,所以直接引入demo中的EaseUI会缺少相关文件,可以直接拖入附件中的EaseUI
如果引入之后报如下图的错误
10.1_.png

10.2_.png

其实碰到上面这个问题还是很好解决的,这个是因为用到了UIKit里的类,但是只导入了Foundation框架,这个错误在其他类里也会出现,我们可以手动修改Founfation为UIKit,但是我不建议这么做,第一这个做法的工程量比较大, 在其他类里面也要导入,二,不利于移植,当以后环信更新的时候我们还是需要做同样的操作,这里我的做法的创建一个pch文件,在pch文件里面导入UIKit。解决办法:建一个PCH文件在里面添加如下代码:
10.3_.png


以上应该会正常了,但是如果集成的是不包含实时音视频的SDK, 您导入的EaseUI不是Lite版的,  那么此时还会报跟第六点一样的错误 , 需要导入EaseUILite 版本,所以会找不到Hyphenate/Hyphenate.h,如果是手动集成,建议在xcode设置一下Build Settings> GCC_PREPROCESSOR_DEFINITIONS >ENABLE_LITE=1,这样easeui就去找HyphenateLite/HyphenateLite.h
也可以通过pod集成,文档上针对easeui集成Full版本和Lite版本sdk特殊的说明http://docs.easemob.com/im/300 ... guide
 
或者不想导入Lite版的 , 只想引入EaseUI 
这时需要把 #import <Hyphenate/Hyphenate.h>注释掉,然后把报错地方的Hyphenate换成HyphenateLite就可以了
 
 
11. 
1.png

A : 可以删除或者重命名Podfile.lock文件,重新执行pod install命令
8
回复

各位大神,有单聊集成音视频的demo吗?iOS3.0的? 音视频 iOS 环信_iOS

SN 回复了问题 • 8 人关注 • 1063 次浏览 • 2017-05-12 14:29 • 来自相关话题

0
评论

环信聊天游客身份和正常用户身份的切换 环信聊天游客身份与正常用户身份的切换 iOS

奋斗的蜗牛 发表了文章 • 191 次浏览 • 2017-05-10 20:39 • 来自相关话题

 
   最近搞环信聊天,需求是游客身份也可以进行聊天,当用户注册了我们的APP后也需要把游客身份切换过来进行聊天,首先我们的环信注册,登录全都放前段处理了,下面就按照我们的需求逻辑来如何切换游客。
 
   1.APP用户的注册,也就注册环信,APP的登录返回的有用户ID,这个时候并没有让他登录环信,只是保存了返回的ID,下面就是用ID来判断该用户是否注册过环信的依据
 
大致说明一下,代码中用到一个类来保证uuid不会改变的状态,为防止app卸载后uuid的改变,我们把他存储到钥匙串里面来保存

下面用图来表示
我们先来看下整个身份切换实现的逻辑图





下面就上代码了,第一步从图中第一步来说判断userID是否存在

这个地方是在点击聊天按钮开始判断的
 -(void)releaseInfo:(UIButton*)sender{
NSString*Hxusername=[userdic objectForKey:@"useid"];//获取保存的userID
NSString*phonestr=  [[NSUserDefaults standardUserDefaults]objectForKey:@"phonenum"];
NSString*chatid=[[phonestr md5String]substringFromIndex:16];//这个是获取客服的欢信ID
//单例里面处理用户是否登录,以及游客随机分配uuid来注册环信IM号
DataManager*datamage= [DataManager shareDataManager];
//判断用户ID是否存在,也就证明是否注册过环信
if (Hxusername.length>0) {
if ([datamage loginKefuSDK])//判断用户是否登录
{//单聊
ChattingViewController *chatController = [[ChattingViewController alloc] initWithConversationChatter:chatid conversationType:EMConversationTypeChat];[self.navigationController pushViewController:chatController animated:YES];
}
}else{
//游客身份的判断
if ([datamage customelogin]) {
//单聊
ChattingViewController *chatController = [[ChattingViewController alloc] initWithConversationChatter:chatid conversationType:EMConversationTypeChat];[self.navigationController pushViewController:chatController animated:YES];
}
}
}上面这是按钮方法里面的数据下面来说,DataManager*datamage= [DataManager shareDataManager];这个单利的方法DataManager.h
@interface DataManager : NSObject
-(BOOL)customelogin;//判断游客之前是否有登录
-(void)requestchattphone;//获取美容院客服聊天的对象电话
@endDataManager.m


@implementation DataManager+(instancetype)shareDataManager{
static DataManager *manager;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
manager = [[DataManager alloc] init];
});
return manager;
}//userID存在的时候 登录IM
- (BOOL)loginKefuSDK {
NSDictionary*userdic=[[NSUserDefaults standardUserDefaults]objectForKey:@"userMessage"];//接受用户是否登录
NSString*loguser=[NSString stringWithFormat:@"%@",[userdic objectForKey:@"useid"] ];
EMClient *client = [EMClient sharedClient];
//用户已经登录
if (client.isLoggedIn) {
if ([loguser isEqualToString:client.currentUsername])//当前登录用户的ID和即将要登录人的ID是否一样
{
return YES;
}else
{
EMError *error = [[EMClient sharedClient] logout:YES];
if (!error) {
NSLog(@"退出成功");
}
}
}//这里APP用户登录环信的密码统统是123456
EMError *error = [[EMClient sharedClient] loginWithUsername:loguser password:@"123456"];
if (!error) { //IM登录成功
return YES;
} else { //登录失败
NSLog(@"登录失败 error code :%d,error description:%@",error.code,error.errorDescription);
return NO;
}
return NO;
}//游客身份的登录方法
-(BOOL)customelogin
{
EMClient *client = [EMClient sharedClient];
//用户已经登录
if (client.isLoggedIn) {
return YES;
}//该用户没有注册,来用改设备UUID来给用户注册环信,并登录环信
if (![self registerIMuser]) {
return NO;
}
EMError *error = [[EMClient sharedClient] loginWithUsername:self.Hxusername password:@"123456"];
if (!error) { //IM登录成功
return YES;
} else { //登录失败
NSLog(@"登录失败 error code :%d,error description:%@",error.code,error.errorDescription);
return NO;
}
return NO;
}- (BOOL)registerIMuser { //举个栗子。注册建议在服务端创建环信id与自己app的账号一一对应,\
而不要放到APP中,可以在登录自己APP时从返回的结果中获取环信账号再登录环信服务器
EMError *error = nil;
NSString *newUser = [self getrandomUsername];
self.Hxusername = newUser;
error = [[EMClient sharedClient] registerWithUsername:newUser password:@"123456"];
if (error &&  error.code != EMErrorUserAlreadyExist) {
NSLog(@"注册失败;error code:%d,error description :%@",error.code,error.errorDescription);
return NO;
}return YES;
}
//创建一个随机的用户名,这里是设备UUID来代替的​- (NSString *)getrandomUsername {
//第一种方法:
/*NSString *username = nil;
UIDevice *device = [UIDevice currentDevice];//创建设备对象
NSString *deviceUID = [[NSString alloc] initWithString:[[device identifierForVendor] UUIDString]];
if ([deviceUID length] == 0) {
CFUUIDRef uuid = CFUUIDCreate(NULL);
if (uuid)
{
deviceUID = (__bridge_transfer NSString *)CFUUIDCreateString(NULL, uuid);
CFRelease(uuid);
}
username = [deviceUID stringByReplacingOccurrencesOfString:@"-" withString:@""];
username = [username stringByAppendingString:[NSString stringWithFormat:@"%u",arc4random()0000]];
return username;*/
//第二种方法
//加上build ID是为了保证设备的唯一性,如果这里的buildID换了,设备的uuid也会变,这里的解决办法也就是放倒了钥匙串里面,不会因卸载程序,程序升级设备的标识会改变
NSString *SERVICE_NAME = NAVI_TEST_BUNDLE_ID;//最好用程序的bundle id
NSString * str =  [SFHFKeychainUtils getPasswordForUsername:@"UUID" andServiceName:SERVICE_NAME error:nil];  // 从keychain获取数据
if ([str length]<=0)
{
str  = [[[UIDevice currentDevice] identifierForVendor] UUIDString];  // 保存UUID作为手机唯一标识符[SFHFKeychainUtils storeUsername:@"UUID"   andPassword:str    forServiceName:SERVICE_NAME updateExisting:1  error:nil];  // 往keychain添加数据
}
str = [str stringByReplacingOccurrencesOfString:@"-" withString:@""];
return str;
}在这里用到了一个类来处理的UUID不变(APP卸载后不会改变)
SFHFKeychainUtils.h
#import@interface SFHFKeychainUtils : NSObject
+ (NSString *) getPasswordForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error;
+ (BOOL) storeUsername: (NSString *) username andPassword: (NSString *) password forServiceName: (NSString *) serviceName updateExisting: (BOOL) updateExisting error: (NSError **) error;
+ (BOOL) deleteItemForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error;
@endSFHFKeychainUtils.m​#import "SFHFKeychainUtils.h"
static NSString *SFHFKeychainUtilsErrorDomain = @"SFHFKeychainUtilsErrorDomain";
#if __IPHONE_OS_VERSION_MIN_REQUIRED < 30000 && TARGET_IPHONE_SIMULATOR
@interface SFHFKeychainUtils (PrivateMethods)
+ (SecKeychainItemRef) getKeychainItemReferenceForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error;
@end
#endif
@implementation SFHFKeychainUtils
#if __IPHONE_OS_VERSION_MIN_REQUIRED < 30000 && TARGET_IPHONE_SIMULATOR
+ (NSString *) getPasswordForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error { if (!username || !serviceName) { *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil]; return nil; }      SecKeychainItemRef item = [SFHFKeychainUtils getKeychainItemReferenceForUsername: username andServiceName: serviceName error: error];      if (*error || !item) { return nil; }
// from Advanced Mac OS X Programming, ch. 16
UInt32 length;
char *password;
SecKeychainAttribute attributes[8];
SecKeychainAttributeList list;
attributes[0].tag = kSecAccountItemAttr;
attributes[1].tag = kSecDescriptionItemAttr;
attributes[2].tag = kSecLabelItemAttr;
attributes[3].tag = kSecModDateItemAttr;
list.count = 4;
list.attr = attributes;
OSStatus status = SecKeychainItemCopyContent(item, NULL, &list, &length, (void **)&password);
if (status != noErr)
{
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
return nil;
}
NSString *passwordString = nil;
if (password != NULL)
{ char passwordBuffer[1024];
if (length > 1023) {length = 1023;
}
strncpy(passwordBuffer, password, length);
passwordBuffer[length] = '\0';
passwordString = [NSString stringWithCString:passwordBuffer];
}SecKeychainItemFreeContent(&list, password);CFRelease(item);return passwordString;
}
+ (void) storeUsername: (NSString *) username andPassword: (NSString *) password forServiceName: (NSString *) serviceName updateExisting: (BOOL) updateExisting error: (NSError **) error {
if (!username || !password || !serviceName) {
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil];
return;
}
OSStatus status = noErr;
SecKeychainItemRef item = [SFHFKeychainUtils getKeychainItemReferenceForUsername: username andServiceName: serviceName error: error];
if (*error && [*error code] != noErr) {
return;
}
*error = nil;
if (item) {
status = SecKeychainItemModifyAttributesAndData(item,
NULL,
strlen([password UTF8String]),
[password UTF8String]);
CFRelease(item);
}
else {
status = SecKeychainAddGenericPassword(NULL,
strlen([serviceName UTF8String]),
[serviceName UTF8String],
strlen([username UTF8String]),
[username UTF8String],
strlen([password UTF8String]),
[password UTF8String],
NULL);
}
if (status != noErr) {
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
}
}
+ (void) deleteItemForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error {
if (!username || !serviceName) {
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: 2000 userInfo: nil];
return;
}
*error = nil;
SecKeychainItemRef item = [SFHFKeychainUtils getKeychainItemReferenceForUsername: username andServiceName: serviceName error: error];
if (*error && [*error code] != noErr) {
return;
}
OSStatus status;
if (item) {
status = SecKeychainItemDelete(item);
CFRelease(item);
}
if (status != noErr) {
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
}
}
+ (SecKeychainItemRef) getKeychainItemReferenceForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error {
if (!username || !serviceName) {
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil];
return nil;
}
*error = nil;
SecKeychainItemRef item;
OSStatus status = SecKeychainFindGenericPassword(NULL,
strlen([serviceName UTF8String]),
[serviceName UTF8String],
strlen([username UTF8String]),
[username UTF8String],
NULL,
NULL,
&item);
if (status != noErr) {
if (status != errSecItemNotFound) {
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
}
return nil;
}
return item;
}
#else
+ (NSString *) getPasswordForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error {
if (!username || !serviceName) {
if (error != nil) {
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil];
}
return nil;
}
if (error != nil) {
*error = nil;
}
// Set up a query dictionary with the base query attributes: item type (generic), username, and service
NSArray *keys = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClass, kSecAttrAccount, kSecAttrService, nil];
NSArray *objects = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClassGenericPassword, username, serviceName, nil];
NSMutableDictionary *query = [[NSMutableDictionary alloc] initWithObjects: objects forKeys: keys];
// First do a query for attributes, in case we already have a Keychain item with no password data set.
// One likely way such an incorrect item could have come about is due to the previous (incorrect)
// version of this code (which set the password as a generic attribute instead of password data).
NSMutableDictionary *attributeQuery = [query mutableCopy];
[attributeQuery setObject: (id) kCFBooleanTrue forKey:(__bridge_transfer id) kSecReturnAttributes];
CFTypeRef attrResult = NULL;
OSStatus status = SecItemCopyMatching((__bridge_retained CFDictionaryRef) attributeQuery, &attrResult);
//NSDictionary *attributeResult = (__bridge_transfer NSDictionary *)attrResult;
if (status != noErr) {
// No existing item found--simply return nil for the password
if (error != nil && status != errSecItemNotFound) {
//Only return an error if a real exception happened--not simply for "not found."
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
}
return nil;
}
// We have an existing item, now query for the password data associated with it.
NSMutableDictionary *passwordQuery = [query mutableCopy];
[passwordQuery setObject: (id) kCFBooleanTrue forKey: (__bridge_transfer id) kSecReturnData];
CFTypeRef resData = NULL;
status = SecItemCopyMatching((__bridge_retained CFDictionaryRef) passwordQuery, (CFTypeRef *) &resData);
NSData *resultData = (__bridge_transfer NSData *)resData;
if (status != noErr) {
if (status == errSecItemNotFound) {
// We found attributes for the item previously, but no password now, so return a special error.
// Users of this API will probably want to detect this error and prompt the user to
// re-enter their credentials.  When you attempt to store the re-entered credentials
// using storeUsername:andPassword:forServiceName:updateExisting:error
// the old, incorrect entry will be deleted and a new one with a properly encrypted
// password will be added.
if (error != nil) {
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -1999 userInfo: nil];
}
}
else {
// Something else went wrong. Simply return the normal Keychain API error code.
if (error != nil) {
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
}
}
return nil;
}
NSString *password = nil;
if (resultData) {
password = [[NSString alloc] initWithData: resultData encoding: NSUTF8StringEncoding];
}
else {
// There is an existing item, but we weren't able to get password data for it for some reason,
// Possibly as a result of an item being incorrectly entered by the previous code.
// Set the -1999 error so the code above us can prompt the user again.
if (error != nil) {
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -1999 userInfo: nil];
}
}
return password;
}
+ (BOOL) storeUsername: (NSString *) username andPassword: (NSString *) password forServiceName: (NSString *) serviceName updateExisting: (BOOL) updateExisting error: (NSError **) error
{
if (!username || !password || !serviceName)
{
if (error != nil)
{
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil];
}
return NO;
}
// See if we already have a password entered for these credentials.
NSError *getError = nil;
NSString *existingPassword = [SFHFKeychainUtils getPasswordForUsername: username andServiceName: serviceName error:&getError];
if ([getError code] == -1999)
{
// There is an existing entry without a password properly stored (possibly as a result of the previous incorrect version of this code.
// Delete the existing item before moving on entering a correct one.
getError = nil;
[self deleteItemForUsername: username andServiceName: serviceName error: &getError];
if ([getError code] != noErr)
{
if (error != nil)
{
*error = getError;
}
return NO;
}
}
else if ([getError code] != noErr)
{
if (error != nil)
{
*error = getError;
}
return NO;
}
if (error != nil)
{
*error = nil;
}
OSStatus status = noErr;
if (existingPassword)
{
// We have an existing, properly entered item with a password.
// Update the existing item.
if (![existingPassword isEqualToString:password] && updateExisting)
{
//Only update if we're allowed to update existing.  If not, simply do nothing.
NSArray *keys = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClass,
kSecAttrService,
kSecAttrLabel,
kSecAttrAccount,
nil];
NSArray *objects = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClassGenericPassword,
serviceName,
serviceName,
username,
nil];
NSDictionary *query = [[NSDictionary alloc] initWithObjects: objects forKeys: keys];
status = SecItemUpdate((__bridge_retained CFDictionaryRef) query, (__bridge_retained CFDictionaryRef) [NSDictionary dictionaryWithObject: [password dataUsingEncoding: NSUTF8StringEncoding] forKey: (__bridge_transfer NSString *) kSecValueData]);
}
}
else
{
// No existing entry (or an existing, improperly entered, and therefore now
// deleted, entry).  Create a new entry.
NSArray *keys = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClass,
kSecAttrService,
kSecAttrLabel,
kSecAttrAccount,
kSecValueData,
nil];
NSArray *objects = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClassGenericPassword,
serviceName,
serviceName,
username,
[password dataUsingEncoding: NSUTF8StringEncoding],
nil];
NSDictionary *query = [[NSDictionary alloc] initWithObjects: objects forKeys: keys];
status = SecItemAdd((__bridge_retained CFDictionaryRef) query, NULL);
}
if (error != nil && status != noErr)
{
// Something went wrong with adding the new item. Return the Keychain error code.
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
return NO;
}
return YES;
}
+ (BOOL) deleteItemForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error
{
if (!username || !serviceName)
{
if (error != nil)
{
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil];
}
return NO;
}
if (error != nil)
{
*error = nil;
}
NSArray *keys = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClass, kSecAttrAccount, kSecAttrService, kSecReturnAttributes, nil];
NSArray *objects = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClassGenericPassword, username, serviceName, kCFBooleanTrue, nil];
NSDictionary *query = [[NSDictionary alloc] initWithObjects: objects forKeys: keys];
OSStatus status = SecItemDelete((__bridge_retained CFDictionaryRef) query);
if (error != nil && status != noErr)
{
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
return NO;
}
return YES;
}
#endif
@end 查看全部
 
   最近搞环信聊天,需求是游客身份也可以进行聊天,当用户注册了我们的APP后也需要把游客身份切换过来进行聊天,首先我们的环信注册,登录全都放前段处理了,下面就按照我们的需求逻辑来如何切换游客。
 
   1.APP用户的注册,也就注册环信,APP的登录返回的有用户ID,这个时候并没有让他登录环信,只是保存了返回的ID,下面就是用ID来判断该用户是否注册过环信的依据
 
大致说明一下,代码中用到一个类来保证uuid不会改变的状态,为防止app卸载后uuid的改变,我们把他存储到钥匙串里面来保存

下面用图来表示
我们先来看下整个身份切换实现的逻辑图
4861502-fa2f7d87d00c78d7.jpg


下面就上代码了,第一步从图中第一步来说判断userID是否存在

这个地方是在点击聊天按钮开始判断的
 
-(void)releaseInfo:(UIButton*)sender{
NSString*Hxusername=[userdic objectForKey:@"useid"];//获取保存的userID
NSString*phonestr=  [[NSUserDefaults standardUserDefaults]objectForKey:@"phonenum"];
NSString*chatid=[[phonestr md5String]substringFromIndex:16];//这个是获取客服的欢信ID
//单例里面处理用户是否登录,以及游客随机分配uuid来注册环信IM号
DataManager*datamage= [DataManager shareDataManager];
//判断用户ID是否存在,也就证明是否注册过环信
if (Hxusername.length>0) {
if ([datamage loginKefuSDK])//判断用户是否登录
{//单聊
ChattingViewController *chatController = [[ChattingViewController alloc] initWithConversationChatter:chatid conversationType:EMConversationTypeChat];[self.navigationController pushViewController:chatController animated:YES];
}
}else{
//游客身份的判断
if ([datamage customelogin]) {
//单聊
ChattingViewController *chatController = [[ChattingViewController alloc] initWithConversationChatter:chatid conversationType:EMConversationTypeChat];[self.navigationController pushViewController:chatController animated:YES];
}
}
}上面这是按钮方法里面的数据下面来说,DataManager*datamage= [DataManager shareDataManager];这个单利的方法
DataManager.h
@interface DataManager : NSObject
-(BOOL)customelogin;//判断游客之前是否有登录
-(void)requestchattphone;//获取美容院客服聊天的对象电话
@end
DataManager.m


@implementation DataManager
+(instancetype)shareDataManager{
static DataManager *manager;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
manager = [[DataManager alloc] init];
});
return manager;
}
//userID存在的时候 登录IM
- (BOOL)loginKefuSDK {
NSDictionary*userdic=[[NSUserDefaults standardUserDefaults]objectForKey:@"userMessage"];//接受用户是否登录
NSString*loguser=[NSString stringWithFormat:@"%@",[userdic objectForKey:@"useid"] ];
EMClient *client = [EMClient sharedClient];
//用户已经登录
if (client.isLoggedIn) {
if ([loguser isEqualToString:client.currentUsername])//当前登录用户的ID和即将要登录人的ID是否一样
{
return YES;
}else
{
EMError *error = [[EMClient sharedClient] logout:YES];
if (!error) {
NSLog(@"退出成功");
}
}
}//这里APP用户登录环信的密码统统是123456
EMError *error = [[EMClient sharedClient] loginWithUsername:loguser password:@"123456"];
if (!error) { //IM登录成功
return YES;
} else { //登录失败
NSLog(@"登录失败 error code :%d,error description:%@",error.code,error.errorDescription);
return NO;
}
return NO;
}
//游客身份的登录方法
-(BOOL)customelogin
{
EMClient *client = [EMClient sharedClient];
//用户已经登录
if (client.isLoggedIn) {
return YES;
}//该用户没有注册,来用改设备UUID来给用户注册环信,并登录环信
if (![self registerIMuser]) {
return NO;
}
EMError *error = [[EMClient sharedClient] loginWithUsername:self.Hxusername password:@"123456"];
if (!error) { //IM登录成功
return YES;
} else { //登录失败
NSLog(@"登录失败 error code :%d,error description:%@",error.code,error.errorDescription);
return NO;
}
return NO;
}
- (BOOL)registerIMuser { //举个栗子。注册建议在服务端创建环信id与自己app的账号一一对应,\
而不要放到APP中,可以在登录自己APP时从返回的结果中获取环信账号再登录环信服务器
EMError *error = nil;
NSString *newUser = [self getrandomUsername];
self.Hxusername = newUser;
error = [[EMClient sharedClient] registerWithUsername:newUser password:@"123456"];
if (error &&  error.code != EMErrorUserAlreadyExist) {
NSLog(@"注册失败;error code:%d,error description :%@",error.code,error.errorDescription);
return NO;
}return YES;
}
//创建一个随机的用户名,这里是设备UUID来代替的​
- (NSString *)getrandomUsername {
//第一种方法:
/*NSString *username = nil;
UIDevice *device = [UIDevice currentDevice];//创建设备对象
NSString *deviceUID = [[NSString alloc] initWithString:[[device identifierForVendor] UUIDString]];
if ([deviceUID length] == 0) {
CFUUIDRef uuid = CFUUIDCreate(NULL);
if (uuid)
{
deviceUID = (__bridge_transfer NSString *)CFUUIDCreateString(NULL, uuid);
CFRelease(uuid);
}
username = [deviceUID stringByReplacingOccurrencesOfString:@"-" withString:@""];
username = [username stringByAppendingString:[NSString stringWithFormat:@"%u",arc4random()0000]];
return username;*/
//第二种方法
//加上build ID是为了保证设备的唯一性,如果这里的buildID换了,设备的uuid也会变,这里的解决办法也就是放倒了钥匙串里面,不会因卸载程序,程序升级设备的标识会改变
NSString *SERVICE_NAME = NAVI_TEST_BUNDLE_ID;//最好用程序的bundle id
NSString * str =  [SFHFKeychainUtils getPasswordForUsername:@"UUID" andServiceName:SERVICE_NAME error:nil];  // 从keychain获取数据
if ([str length]<=0)
{
str  = [[[UIDevice currentDevice] identifierForVendor] UUIDString];  // 保存UUID作为手机唯一标识符[SFHFKeychainUtils storeUsername:@"UUID"   andPassword:str    forServiceName:SERVICE_NAME updateExisting:1  error:nil];  // 往keychain添加数据
}
str = [str stringByReplacingOccurrencesOfString:@"-" withString:@""];
return str;
}在这里用到了一个类来处理的UUID不变(APP卸载后不会改变)
SFHFKeychainUtils.h
#import@interface SFHFKeychainUtils : NSObject
+ (NSString *) getPasswordForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error;
+ (BOOL) storeUsername: (NSString *) username andPassword: (NSString *) password forServiceName: (NSString *) serviceName updateExisting: (BOOL) updateExisting error: (NSError **) error;
+ (BOOL) deleteItemForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error;
@end
SFHFKeychainUtils.m​
#import "SFHFKeychainUtils.h"
static NSString *SFHFKeychainUtilsErrorDomain = @"SFHFKeychainUtilsErrorDomain";
#if __IPHONE_OS_VERSION_MIN_REQUIRED < 30000 && TARGET_IPHONE_SIMULATOR
@interface SFHFKeychainUtils (PrivateMethods)
+ (SecKeychainItemRef) getKeychainItemReferenceForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error;
@end
#endif
@implementation SFHFKeychainUtils
#if __IPHONE_OS_VERSION_MIN_REQUIRED < 30000 && TARGET_IPHONE_SIMULATOR
+ (NSString *) getPasswordForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error { if (!username || !serviceName) { *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil]; return nil; }      SecKeychainItemRef item = [SFHFKeychainUtils getKeychainItemReferenceForUsername: username andServiceName: serviceName error: error];      if (*error || !item) { return nil; }
// from Advanced Mac OS X Programming, ch. 16
UInt32 length;
char *password;
SecKeychainAttribute attributes[8];
SecKeychainAttributeList list;
attributes[0].tag = kSecAccountItemAttr;
attributes[1].tag = kSecDescriptionItemAttr;
attributes[2].tag = kSecLabelItemAttr;
attributes[3].tag = kSecModDateItemAttr;
list.count = 4;
list.attr = attributes;
OSStatus status = SecKeychainItemCopyContent(item, NULL, &list, &length, (void **)&password);
if (status != noErr)
{
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
return nil;
}
NSString *passwordString = nil;
if (password != NULL)
{ char passwordBuffer[1024];
if (length > 1023) {length = 1023;
}
strncpy(passwordBuffer, password, length);
passwordBuffer[length] = '\0';
passwordString = [NSString stringWithCString:passwordBuffer];
}SecKeychainItemFreeContent(&list, password);CFRelease(item);return passwordString;
}
+ (void) storeUsername: (NSString *) username andPassword: (NSString *) password forServiceName: (NSString *) serviceName updateExisting: (BOOL) updateExisting error: (NSError **) error {
if (!username || !password || !serviceName) {
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil];
return;
}
OSStatus status = noErr;
SecKeychainItemRef item = [SFHFKeychainUtils getKeychainItemReferenceForUsername: username andServiceName: serviceName error: error];
if (*error && [*error code] != noErr) {
return;
}
*error = nil;
if (item) {
status = SecKeychainItemModifyAttributesAndData(item,
NULL,
strlen([password UTF8String]),
[password UTF8String]);
CFRelease(item);
}
else {
status = SecKeychainAddGenericPassword(NULL,
strlen([serviceName UTF8String]),
[serviceName UTF8String],
strlen([username UTF8String]),
[username UTF8String],
strlen([password UTF8String]),
[password UTF8String],
NULL);
}
if (status != noErr) {
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
}
}
+ (void) deleteItemForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error {
if (!username || !serviceName) {
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: 2000 userInfo: nil];
return;
}
*error = nil;
SecKeychainItemRef item = [SFHFKeychainUtils getKeychainItemReferenceForUsername: username andServiceName: serviceName error: error];
if (*error && [*error code] != noErr) {
return;
}
OSStatus status;
if (item) {
status = SecKeychainItemDelete(item);
CFRelease(item);
}
if (status != noErr) {
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
}
}
+ (SecKeychainItemRef) getKeychainItemReferenceForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error {
if (!username || !serviceName) {
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil];
return nil;
}
*error = nil;
SecKeychainItemRef item;
OSStatus status = SecKeychainFindGenericPassword(NULL,
strlen([serviceName UTF8String]),
[serviceName UTF8String],
strlen([username UTF8String]),
[username UTF8String],
NULL,
NULL,
&item);
if (status != noErr) {
if (status != errSecItemNotFound) {
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
}
return nil;
}
return item;
}
#else
+ (NSString *) getPasswordForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error {
if (!username || !serviceName) {
if (error != nil) {
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil];
}
return nil;
}
if (error != nil) {
*error = nil;
}
// Set up a query dictionary with the base query attributes: item type (generic), username, and service
NSArray *keys = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClass, kSecAttrAccount, kSecAttrService, nil];
NSArray *objects = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClassGenericPassword, username, serviceName, nil];
NSMutableDictionary *query = [[NSMutableDictionary alloc] initWithObjects: objects forKeys: keys];
// First do a query for attributes, in case we already have a Keychain item with no password data set.
// One likely way such an incorrect item could have come about is due to the previous (incorrect)
// version of this code (which set the password as a generic attribute instead of password data).
NSMutableDictionary *attributeQuery = [query mutableCopy];
[attributeQuery setObject: (id) kCFBooleanTrue forKey:(__bridge_transfer id) kSecReturnAttributes];
CFTypeRef attrResult = NULL;
OSStatus status = SecItemCopyMatching((__bridge_retained CFDictionaryRef) attributeQuery, &attrResult);
//NSDictionary *attributeResult = (__bridge_transfer NSDictionary *)attrResult;
if (status != noErr) {
// No existing item found--simply return nil for the password
if (error != nil && status != errSecItemNotFound) {
//Only return an error if a real exception happened--not simply for "not found."
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
}
return nil;
}
// We have an existing item, now query for the password data associated with it.
NSMutableDictionary *passwordQuery = [query mutableCopy];
[passwordQuery setObject: (id) kCFBooleanTrue forKey: (__bridge_transfer id) kSecReturnData];
CFTypeRef resData = NULL;
status = SecItemCopyMatching((__bridge_retained CFDictionaryRef) passwordQuery, (CFTypeRef *) &resData);
NSData *resultData = (__bridge_transfer NSData *)resData;
if (status != noErr) {
if (status == errSecItemNotFound) {
// We found attributes for the item previously, but no password now, so return a special error.
// Users of this API will probably want to detect this error and prompt the user to
// re-enter their credentials.  When you attempt to store the re-entered credentials
// using storeUsername:andPassword:forServiceName:updateExisting:error
// the old, incorrect entry will be deleted and a new one with a properly encrypted
// password will be added.
if (error != nil) {
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -1999 userInfo: nil];
}
}
else {
// Something else went wrong. Simply return the normal Keychain API error code.
if (error != nil) {
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
}
}
return nil;
}
NSString *password = nil;
if (resultData) {
password = [[NSString alloc] initWithData: resultData encoding: NSUTF8StringEncoding];
}
else {
// There is an existing item, but we weren't able to get password data for it for some reason,
// Possibly as a result of an item being incorrectly entered by the previous code.
// Set the -1999 error so the code above us can prompt the user again.
if (error != nil) {
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -1999 userInfo: nil];
}
}
return password;
}
+ (BOOL) storeUsername: (NSString *) username andPassword: (NSString *) password forServiceName: (NSString *) serviceName updateExisting: (BOOL) updateExisting error: (NSError **) error
{
if (!username || !password || !serviceName)
{
if (error != nil)
{
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil];
}
return NO;
}
// See if we already have a password entered for these credentials.
NSError *getError = nil;
NSString *existingPassword = [SFHFKeychainUtils getPasswordForUsername: username andServiceName: serviceName error:&getError];
if ([getError code] == -1999)
{
// There is an existing entry without a password properly stored (possibly as a result of the previous incorrect version of this code.
// Delete the existing item before moving on entering a correct one.
getError = nil;
[self deleteItemForUsername: username andServiceName: serviceName error: &getError];
if ([getError code] != noErr)
{
if (error != nil)
{
*error = getError;
}
return NO;
}
}
else if ([getError code] != noErr)
{
if (error != nil)
{
*error = getError;
}
return NO;
}
if (error != nil)
{
*error = nil;
}
OSStatus status = noErr;
if (existingPassword)
{
// We have an existing, properly entered item with a password.
// Update the existing item.
if (![existingPassword isEqualToString:password] && updateExisting)
{
//Only update if we're allowed to update existing.  If not, simply do nothing.
NSArray *keys = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClass,
kSecAttrService,
kSecAttrLabel,
kSecAttrAccount,
nil];
NSArray *objects = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClassGenericPassword,
serviceName,
serviceName,
username,
nil];
NSDictionary *query = [[NSDictionary alloc] initWithObjects: objects forKeys: keys];
status = SecItemUpdate((__bridge_retained CFDictionaryRef) query, (__bridge_retained CFDictionaryRef) [NSDictionary dictionaryWithObject: [password dataUsingEncoding: NSUTF8StringEncoding] forKey: (__bridge_transfer NSString *) kSecValueData]);
}
}
else
{
// No existing entry (or an existing, improperly entered, and therefore now
// deleted, entry).  Create a new entry.
NSArray *keys = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClass,
kSecAttrService,
kSecAttrLabel,
kSecAttrAccount,
kSecValueData,
nil];
NSArray *objects = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClassGenericPassword,
serviceName,
serviceName,
username,
[password dataUsingEncoding: NSUTF8StringEncoding],
nil];
NSDictionary *query = [[NSDictionary alloc] initWithObjects: objects forKeys: keys];
status = SecItemAdd((__bridge_retained CFDictionaryRef) query, NULL);
}
if (error != nil && status != noErr)
{
// Something went wrong with adding the new item. Return the Keychain error code.
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
return NO;
}
return YES;
}
+ (BOOL) deleteItemForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error
{
if (!username || !serviceName)
{
if (error != nil)
{
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil];
}
return NO;
}
if (error != nil)
{
*error = nil;
}
NSArray *keys = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClass, kSecAttrAccount, kSecAttrService, kSecReturnAttributes, nil];
NSArray *objects = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClassGenericPassword, username, serviceName, kCFBooleanTrue, nil];
NSDictionary *query = [[NSDictionary alloc] initWithObjects: objects forKeys: keys];
OSStatus status = SecItemDelete((__bridge_retained CFDictionaryRef) query);
if (error != nil && status != noErr)
{
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
return NO;
}
return YES;
}
#endif
@end
2
最佳

EMConversation中的ext保存后读取不出来 ext iOS

asd777 回复了问题 • 2 人关注 • 208 次浏览 • 2017-04-20 15:33 • 来自相关话题

1
回复

跟着视频一起写的demo 环信 iOS

xxl 回复了问题 • 2 人关注 • 279 次浏览 • 2017-04-12 15:59 • 来自相关话题

3
回复

手动集成EaseUI,Tabbar变黑色 iOS

lizilong 回复了问题 • 3 人关注 • 220 次浏览 • 2017-04-12 15:37 • 来自相关话题

1
回复

环信视频通话,A用户和B用户怎么才能一连接就用扬声器视频对话呢? iOS 环信 音视频

lizilong 回复了问题 • 2 人关注 • 823 次浏览 • 2017-04-11 15:01 • 来自相关话题

1
回复

有没有人做过环信图片连续滑动?能稍微解释一下基本步骤吗? 环信_iOS iOS

donghai 回复了问题 • 2 人关注 • 197 次浏览 • 2017-04-10 17:27 • 来自相关话题

1
最佳

ios 环信最新版本 打包上线时候报错 审核bug 集成上线 iOS

donghai 回复了问题 • 2 人关注 • 510 次浏览 • 2017-03-31 18:10 • 来自相关话题

条新动态, 点击查看
zl

zl 回答了问题 • 2015-12-03 17:00 • 19 个回复 不感兴趣

iOS EaseMessageCell.h:48:44: Property has a previous declaration

赞同来自:

emoji的  表情。添加下相关库、
emoji的  表情。添加下相关库、
获取会话的最后一条消息,conversation.latestmessage.ext 然后获取出来之后去显示吧
 
获取会话的最后一条消息,conversation.latestmessage.ext 然后获取出来之后去显示吧
 
参考demo中chatviewcontroller这个方法:
//通过会话管理者获取已收发消息
    [self tableViewDidTriggerHeaderRefresh];
参考demo中chatviewcontroller这个方法:
//通过会话管理者获取已收发消息
    [self tableViewDidTriggerHeaderRefresh];
根控制器中这个回调方法没实现吧:/*!
 @method
 @brief 实时通话状态发生变化时的回调
 @param callSession 实时通话的实例
 @param reason   变化原因
 @param error    错误信息
 */
- ... 显示全部 »
根控制器中这个回调方法没实现吧:/*!
 @method
 @brief 实时通话状态发生变化时的回调
 @param callSession 实时通话的实例
 @param reason   变化原因
 @param error    错误信息
 */
- (void)callSessionStatusChanged:(EMCallSession *)callSession
                    changeReason:(EMCallStatusChangedReason)reason
                           error:(EMError *)error;
参考demo中怎么设置的,还需要注册代理:
[[EaseMob sharedInstance].callManager removeDelegate:self];
    // 注册为Call的Delegate
    [[EaseMob sharedInstance].callManager addDelegate:self delegateQueue:nil];
发送方看下didsendmessage回调方法中,message.ext有没有设置的值。
发送方看下didsendmessage回调方法中,message.ext有没有设置的值。
zl

zl 回答了问题 • 2016-02-22 17:46 • 4 个回复 不感兴趣

好友申请和入群申请

赞同来自:

是啊 。这个是回调,代理传值。sdk内部接收到好友请求,然后返回给你,就是通过这个方法接收。
是啊 。这个是回调,代理传值。sdk内部接收到好友请求,然后返回给你,就是通过这个方法接收。
sdk.a包括实时语音通话和实时视频,里面有lite.a里的功能。 lite.a有基本聊天功能。 这两个留一个就行了。不用实时语音通话和视频,就用lite.a就可以了
sdk.a包括实时语音通话和实时视频,里面有lite.a里的功能。 lite.a有基本聊天功能。 这两个留一个就行了。不用实时语音通话和视频,就用lite.a就可以了
zl

zl 回答了问题 • 2016-03-23 11:53 • 3 个回复 不感兴趣

与支付宝冲突

赞同来自:

other linker 设置下 -force_load + 支付宝的库的路径,并放在最前面。这里不需要再设置环信sdk。
other linker 设置下 -force_load + 支付宝的库的路径,并放在最前面。这里不需要再设置环信sdk。
环信沈冲

环信沈冲 回答了问题 • 2016-04-07 18:52 • 1 个回复 不感兴趣

iOS集成环信Demo 3.0在不同机型上运行问题

赞同来自:

在3.0demo中找到FixFopen.c导入项目中,并在pch文件中的首尾加上__OBJC__和endif
在3.0demo中找到FixFopen.c导入项目中,并在pch文件中的首尾加上__OBJC__和endif
donghai

donghai 回答了问题 • 2016-04-12 19:37 • 25 个回复 不感兴趣

求一份 iOS 3.x 群聊demo

赞同来自:

QQ邮箱给我,我给你发一份。
QQ邮箱给我,我给你发一份。
貌似应该是三方框架有重复, 解决了
貌似应该是三方框架有重复, 解决了
- (NSMutableArray *)loadDataSource

{

    NSMutableArray *ret = nil;

    NSArray *conversations = [[EaseMob sharedInstance].chat... 显示全部 »
- (NSMutableArray *)loadDataSource

{

    NSMutableArray *ret = nil;

    NSArray *conversations = [[EaseMob sharedInstance].chatManager conversations];




    NSArray* sorte = [conversations sortedArrayUsingComparator:

           ^(EMConversation *obj1, EMConversation* obj2){

               EMMessage *message1 = [obj1 latestMessage];

               EMMessage *message2 = [obj2 latestMessage];

               if(message1.timestamp > message2.timestamp) {

                   return(NSComparisonResult)NSOrderedAscending;

               }else {

                   return(NSComparisonResult)NSOrderedDescending;

               }

           }];

    

    ret = [[NSMutableArray alloc] initWithArray:sorte];

    return ret;

}
 
你在每次刷新的时候调这个方法就可以吧。  Demo有
lizilong

lizilong 回答了问题 • 2016-05-16 14:26 • 2 个回复 不感兴趣

iOS集成parse的报错,大神请进

赞同来自:

应该是缺少这两个依赖
Accounts.framework
Social.framework
 
应该是缺少这两个依赖
Accounts.framework
Social.framework
 
zhangyb

zhangyb 回答了问题 • 2016-06-20 19:37 • 1 个回复 不感兴趣

ios 8, ipV6, 环信demo,无法登录,无法用了

赞同来自:

iOS9.2以下,不支持ipv6
iOS9.2以下,不支持ipv6
feynmanren

feynmanren 回答了问题 • 2016-06-29 15:16 • 1 个回复 不感兴趣

环信3.0,登录admin帐户失败!

赞同来自:

自己回答吧, 需要自己手动创建admin账户。
自己回答吧, 需要自己手动创建admin账户。
mazhihua

mazhihua 回答了问题 • 2016-08-09 19:28 • 2 个回复 不感兴趣

iOS 角标问题 app 活跃状态 和 被杀死 情况下

赞同来自:

您好,这个角标是服务器设置的,改不了的,因为服务器推送的离线消息是从0 开始计算的,所以从1 开始重新累加了
您好,这个角标是服务器设置的,改不了的,因为服务器推送的离线消息是从0 开始计算的,所以从1 开始重新累加了
[[EaseSDKHelper shareHelper] hyphenateApplication:application
                    didFinishLaunchingWithOptions:launchOptions
    ... 显示全部 »
[[EaseSDKHelper shareHelper] hyphenateApplication:application
                    didFinishLaunchingWithOptions:launchOptions
                                           appkey:appkey
                                     apnsCertName:apnsCertName
                                      otherConfig:@{kSDKConfigEnableConsoleLogger:[NSNumber numberWithBool:YES]}];
Swift

Swift 回答了问题 • 2016-11-03 16:46 • 2 个回复 不感兴趣

ios如何添加Emoji哇

赞同来自:

已解决EaseMessageViewController在的viewdidload方法的
第一行添加:[self setupEmotion];
最后一行添加: EaseEmotionManager *manager= [[EaseEmotionManager ... 显示全部 »
已解决EaseMessageViewController在的viewdidload方法的
第一行添加:[self setupEmotion];
最后一行添加: EaseEmotionManager *manager= [[EaseEmotionManager alloc] initWithType:EMEmotionDefault emotionRow:3 emotionCol:7 emotions:[EaseEmoji allEmoji]]; [self.faceView setEmotionManagers:@[manager]];
然后神奇的出现了emoji
环信沈冲

环信沈冲 回答了问题 • 2016-12-26 14:32 • 2 个回复 不感兴趣

环信服务端集成,如何集成?

赞同来自:

客户端直接集成SDK即可,服务端可以根据自己需求调用相应的rest接口集成
客户端直接集成SDK即可,服务端可以根据自己需求调用相应的rest接口集成
beyond

beyond 回答了问题 • 2017-01-10 17:32 • 1 个回复 不感兴趣

iOS动态库sdk上架问题?

赞同来自:

你好,环信在V3.2.3 2016-12-29版本支持了动态SDK
可以参考环信动态库sdk上架问题解决方案 http://www.imgeek.org/article/825308639
你好,环信在V3.2.3 2016-12-29版本支持了动态SDK
可以参考环信动态库sdk上架问题解决方案 http://www.imgeek.org/article/825308639
donghai

donghai 回答了问题 • 2017-01-21 11:49 • 6 个回复 不感兴趣

关于ios登陆的问题

赞同来自:

环信demo在初始化SDK方法那里换你自己的appkey,别在宏定义那里换,报用户不存在就是你自己的appkey填的不对,或者登录环信的账号不对
环信demo在初始化SDK方法那里换你自己的appkey,别在宏定义那里换,报用户不存在就是你自己的appkey填的不对,或者登录环信的账号不对
环信沈冲

环信沈冲 回答了问题 • 2017-02-15 12:14 • 1 个回复 不感兴趣

iOS 是从哪一版开始不支持 iOS 7的?

赞同来自:

3.2.3动态库版本不支持iOS7
3.2.3动态库版本不支持iOS7
 插入消息方法换一下 [[EMClient sharedClient].chatManager importMessages:@[message1] completion:^(EMError *aError) {

                    }]... 显示全部 »
 插入消息方法换一下 [[EMClient sharedClient].chatManager importMessages:@[message1] completion:^(EMError *aError) {

                    }];换成 [conversation insertmessage:]
donghai

donghai 回答了问题 • 2017-03-31 18:10 • 1 个回复 不感兴趣

ios 环信最新版本 打包上线时候报错

赞同来自:

环信动态库分离上架:http://www.jianshu.com/p/f058b25163b8  看下这里
环信动态库分离上架:http://www.jianshu.com/p/f058b25163b8  看下这里
zl

zl 回答了问题 • 2017-04-20 15:21 • 2 个回复 不感兴趣

EMConversation中的ext保存后读取不出来

赞同来自:

具体操作代码贴一下。
具体操作代码贴一下。
6
评论

【开源OA项目】基于环信IM开发完整的企业通讯解决方案-Dolores Dolores OA 开源项目

KevinGong 发表了文章 • 6640 次浏览 • 2017-06-26 10:53 • 来自相关话题

  

  前阵子钉钉在微信楼下刷了一波#创业很苦,坚持很酷#的广告,浓浓的“丧”文化风格文案受到了各界褒贬不一的评价,也引起了大家对OA办公系统的关注。
   对企业而言,初选OA办公系统是为了满足需求,解决当下问题,由于OA办公系统的在公司运作流程中扮演的重要性,安全与隐私等问题急需未雨绸缪,“可定制”、“可私有化部署”的OA办公系统成为了更多企业的首选。公司想自己开发一套IM系统应该从哪里开始呢? 企业通讯录怎么保持同步呢? 企业通讯录的权限管理应该怎么做?
   三个关于OA办公系统的究极问题,从开源的OA办公项目-Dolores(朵拉)诞生迎刃而解了。Dolores项目遵循Apache Licence 2.0 开源协议,可以直接拿来用,也可以修改代码来满足需要并作为开源或商业产品发布/销售。




关于Dolores?
Dolores是一套完整的企业通信解决方案,一个完整的企业沟通工具(以下简称企业IM),支持以下几个功能:IM消息服务、组织架构管理、工作流集成。
Dolores项目源码地址:https://github.com/DoloresTeam​ 
技术讨论群:641256202(QQ群)

整个解决方案都包括了什么?
企业通讯录的管理:部门/员工的增删改查通讯录全量更新:全量/增量更新 企业通讯录权限管理:基于RBAC权限管理模型企业即时通讯IM:企业通信对IM这块的可靠性要求高,选择了目前比较成熟的IM云服务厂商-环信
 
 
组织架构

企业通讯录可以说是企业沟通中最重的业务之一,能够提供员工各种服务的认证,获取员工的联系方式等。
 
组织架构-Server

服务端主要包括以下功能:
支持管理人员(例如HR)对部门和员工进行增删改查支持部门和员工自定义排序,自定义元信息存储权限管理员工通讯录视图 (员工根据自己的权限生成通讯录)通讯录增量更新 (鉴于移动端特殊的网络环境和设备,通讯录应该支持差量更新)集成 IM 用户系统

在这里我们主要讨论以下两个问题:
 
权限管理

  随着企业逐渐的发展,团队壮大为了更有效的沟通,以及保护公司内部的一些商业信息不被泄漏,我们应该为通讯录添加权限管理。

基于Role-based access control(RBAC)的权限管理模型

为了介绍此权限管理模型,我们先解释一下基本概念
角色:通常是指企业中某一个工作岗位,这个岗位具有特定的权利和职责。被赋予此角色的员工,将获得这种权利与职责权限:被赋予访问实体的权利。在本项目中是指访问部门和访问某一个或者某一类员工的权利用户-角色分配(User-Role Assignment URA):为某个用户指定一个或者多个角色,此员工将获得这些角色所具有权利的集合角色-权限分配(Role-Permission Assignment RPA):将权限分配给角色,一个角色可以包含多个权利。在本项目中是指多个访问部门和访问员工的权限

在用户和权限之间引入角色中介,将用户与权限的直接关系弱化为间接关系。|ˉˉˉ| |ˉˉ ˉ| |ˉˉˉˉ ˉˉ|
| User |---URA---> | Role |<---RPA---| Permission |
|______| |_______| |_____________|
    以角色为中介,首先创建访问每个部门和员工的访问权限,然后创建不同的角色,根据这些角色的职责不同分配不同的权限,建立角色-权限的关系以后,不同的角色将会有不同的权限。根据员工不同的岗位,将对应的角色分配给他们,建立用户-角色关系,这就是RBAC的主要思想。

一个员工可以用户多个角色,一个角色可以用于多个访问权限。RBAC 极大的简化了员工的授权管理。

   由于企业的部门和员工数量很多,在创建权限时管理员不可能去设置每一个权限可以访问的每一个部门和每一个员工。所以本项目将功能和指责类似的部门和员工看作是同一类型,在创建部门和员工的时候为每一个部门和员工分配固有属性type,管理员在设置权限规则的时候只需要指定可访问的部门类型和员工即可。

增量更新

   鉴于移动终端计算资源有限,如网络,存储,电量等,所以通讯录的更新技术应该保证尽量少的资源。另外由于通讯录的特殊性,通讯录的变化需要能实时通知到受影响的在线员工。

基于版本号与变更日志的增量更新模型

   客户端第一次登陆系统以后,我们根据当前登录角色生成对应的通讯录视图,并以当前时间戳作为版本号,返回给客户端。客户端后续通过此版本号增量更新通讯录。

版本号

   版本号有两种:一是客户端当前通讯录版本 c-version, 二是服务端通讯录每一次变化时的版本号s-version

变更日志

   在管理员修改权限规则,或者修改某个岗位的访问规则时会影响大面积员工的通讯录视图,此时如果用增量更新会导致服务器流量异常,因此在这2中情况会清空原来的变更日志并且要求客户端进行一次全量更新。

   如果管理员新增了员工,服务端会根据被修改的员工或者部门type, 反推出所有受影响的员工,然后生成一条变更日志, 例如:{
"content" : [
{
"cn" : "Lucy.Liu",
"id" : "b4vlfg91scgi1dcju8v0",
"title" : "市场运营负责人",
"email" : [
"lucy.liu@dolores.store"
],
"priority" : "101111",
"name" : "刘小飞",
"telephoneNumber" : "18888888888"
}
],
"createTimestamp" : "20170614063303Z",
"category" : "member",
"action" : "add"
}
客户端在请求增量更新的时候,通过当前登陆ID与版本号,可查找出所有与自己相关的变更日志,然后在客户端数据库中应用这些变更,即可完成同步。

组织架构-Client

   由于现在员工办公设备的多样性,客户端要根据自己公司的情况,覆盖的足够完整,常见的平台有 iOS Android windowsmac linux , 对于后三个平台可以用 Web APP 来覆盖,iOS&Android 用原生的app来提升用户体验。

客户端App主要包括以下功能:
会话列表优秀的聊天界面,历史记录组织机构全量/增量更新员工个人资料展示

客户端数据库设计

IM数据库设计
 
当前版本使用环信SDK
 
组织架构数据库设计

表设计

客户端组织架构较服务端简单,不关联用户Role,客户端本地存储Staff(员工)和Department(部门)信息:
一个部门可以包含相关子部门和部门员工。该部门员工和部门在视图上处于同级关系。员工隶属于部门,同一员工可以存在于多个部门。员工角色用title来表示。

用户在登录客户端成功后,会根据该用户信息创建用户对应的数据库文件,用户表(User)保存用户相关信息,关联该用户staff信息。

客户端组织架构同服务端逻辑。

工作流集成

(TODO)
 
如何使用Dolores

本项目现在已经完成了第一个测试版本,本小节将指导您如何安装使用。

后端数据库

鉴于通讯录对数据库操作的特点多度少写,以及部门之间的树状关系,我们选择LDAP协议来存取数据。

我们有独立的repo来帮助您完成数据库的安装与初始化。请移步这里

组织架构管理

Dolores 初始版本使用Golang实现,大家既可以下载各个平台的可执行包,也可以安装Go语言的开发环境自己编译。

我们有独立的repo来帮助您,运行后端服务。请移步这里

客户端

我们现在有提供一个iOS版的Demo。请移步这里

Done

如果您顺利的完成以上三步,访问 http://localhost:3280 (端口号根据自己的配置,可能会有差异),使用 username: admin, password: dolores 登陆后端管理页面,添加权限规则,添加角色,添加员工、部门,然后使用iOS客户端登陆,就可以愉快的开始聊天啦~
 
负载均衡

(TODO)

多机容灾

(TODO)

LICENSE Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
更多信息请前往github项目主页

 
这里我对每个repo做一个简单的介绍


Dolores: 项目简介, 整个项目的架构, 数据库设计等等 你想了解的一切都可以在这里看到
dolores-ios: iOS版demo,可以聊天查看组织架构
dolores-android: 哈哈 还没有,当然我们欢迎各路安卓大牛贡献安卓版demo
organization: 组织架构的创建管理、更新、审计等等核心的东西都在这里啦
dolores-server: 为客户端提供restfull api 与环信服务器集成
dolores-admin: 后台管理网站,用于管理部门员工。一个基于React的webapp还很基础,欢迎各位大牛pr.
dolores-ldap-init: 后台数据库的初始化工具,详情可以查看readme
easemob-resty:对环信rest api的封装,让调用环信api更简单
dolores-avatar:生成类似钉钉那样的默认头像


最后再说一点整个服务端是用go来写的,作者也是golang的初学者,如果代码哪里写的有问题或者架构有问题欢迎大家指正THE CALM BEFORE THE STORM.
暴风雨前的宁静
ONE MORE THING 最后附上Dolores项目LOGO
当时作者正在二刷 《西部世界》这部剧,所以选择了女主的名字dolores作为整个项目的名字,而这个logo则寓意剧中的host。 查看全部
  

  前阵子钉钉在微信楼下刷了一波#创业很苦,坚持很酷#的广告,浓浓的“丧”文化风格文案受到了各界褒贬不一的评价,也引起了大家对OA办公系统的关注。
   对企业而言,初选OA办公系统是为了满足需求,解决当下问题,由于OA办公系统的在公司运作流程中扮演的重要性,安全与隐私等问题急需未雨绸缪,“可定制”、“可私有化部署”的OA办公系统成为了更多企业的首选。
公司想自己开发一套IM系统应该从哪里开始呢? 企业通讯录怎么保持同步呢? 企业通讯录的权限管理应该怎么做?

   三个关于OA办公系统的究极问题,从开源的OA办公项目-Dolores(朵拉)诞生迎刃而解了。Dolores项目遵循Apache Licence 2.0 开源协议,可以直接拿来用,也可以修改代码来满足需要并作为开源或商业产品发布/销售。
OA广告图.jpg

关于Dolores?

Dolores是一套完整的企业通信解决方案,一个完整的企业沟通工具(以下简称企业IM),支持以下几个功能:IM消息服务、组织架构管理、工作流集成。


Dolores项目源码地址:https://github.com/DoloresTeam​ 
技术讨论群:641256202(QQ群)

整个解决方案都包括了什么?
  • 企业通讯录的管理:部门/员工的增删改查
  • 通讯录全量更新:全量/增量更新 
  • 企业通讯录权限管理:基于RBAC权限管理模型
  • 企业即时通讯IM:企业通信对IM这块的可靠性要求高,选择了目前比较成熟的IM云服务厂商-环信

 
 
组织架构

企业通讯录可以说是企业沟通中最重的业务之一,能够提供员工各种服务的认证,获取员工的联系方式等。
 
组织架构-Server

服务端主要包括以下功能:
  1. 支持管理人员(例如HR)对部门和员工进行增删改查
  2. 支持部门和员工自定义排序,自定义元信息存储
  3. 权限管理
  4. 员工通讯录视图 (员工根据自己的权限生成通讯录)
  5. 通讯录增量更新 (鉴于移动端特殊的网络环境和设备,通讯录应该支持差量更新)
  6. 集成 IM 用户系统


在这里我们主要讨论以下两个问题:
 
权限管理

  随着企业逐渐的发展,团队壮大为了更有效的沟通,以及保护公司内部的一些商业信息不被泄漏,我们应该为通讯录添加权限管理。

基于Role-based access control(RBAC)的权限管理模型

为了介绍此权限管理模型,我们先解释一下基本概念
  • 角色:通常是指企业中某一个工作岗位,这个岗位具有特定的权利和职责。被赋予此角色的员工,将获得这种权利与职责
  • 权限:被赋予访问实体的权利。在本项目中是指访问部门和访问某一个或者某一类员工的权利
  • 用户-角色分配(User-Role Assignment URA):为某个用户指定一个或者多个角色,此员工将获得这些角色所具有权利的集合
  • 角色-权限分配(Role-Permission Assignment RPA):将权限分配给角色,一个角色可以包含多个权利。在本项目中是指多个访问部门和访问员工的权限


在用户和权限之间引入角色中介,将用户与权限的直接关系弱化为间接关系。
|ˉˉˉ|           |ˉˉ ˉ|          |ˉˉˉˉ ˉˉ|  
| User |---URA---> | Role |<---RPA---| Permission |
|______| |_______| |_____________|

    以角色为中介,首先创建访问每个部门和员工的访问权限,然后创建不同的角色,根据这些角色的职责不同分配不同的权限,建立角色-权限的关系以后,不同的角色将会有不同的权限。根据员工不同的岗位,将对应的角色分配给他们,建立用户-角色关系,这就是RBAC的主要思想。

一个员工可以用户多个角色,一个角色可以用于多个访问权限。RBAC 极大的简化了员工的授权管理。

   由于企业的部门和员工数量很多,在创建权限时管理员不可能去设置每一个权限可以访问的每一个部门和每一个员工。所以本项目将功能和指责类似的部门和员工看作是同一类型,在创建部门和员工的时候为每一个部门和员工分配固有属性type,管理员在设置权限规则的时候只需要指定可访问的部门类型和员工即可。

增量更新

   鉴于移动终端计算资源有限,如网络,存储,电量等,所以通讯录的更新技术应该保证尽量少的资源。另外由于通讯录的特殊性,通讯录的变化需要能实时通知到受影响的在线员工。

基于版本号与变更日志的增量更新模型

   客户端第一次登陆系统以后,我们根据当前登录角色生成对应的通讯录视图,并以当前时间戳作为版本号,返回给客户端。客户端后续通过此版本号增量更新通讯录。

版本号

   版本号有两种:一是客户端当前通讯录版本 c-version, 二是服务端通讯录每一次变化时的版本号s-version

变更日志

   在管理员修改权限规则,或者修改某个岗位的访问规则时会影响大面积员工的通讯录视图,此时如果用增量更新会导致服务器流量异常,因此在这2中情况会清空原来的变更日志并且要求客户端进行一次全量更新。

   如果管理员新增了员工,服务端会根据被修改的员工或者部门type, 反推出所有受影响的员工,然后生成一条变更日志, 例如:
{
"content" : [
{
"cn" : "Lucy.Liu",
"id" : "b4vlfg91scgi1dcju8v0",
"title" : "市场运营负责人",
"email" : [
"lucy.liu@dolores.store"
],
"priority" : "101111",
"name" : "刘小飞",
"telephoneNumber" : "18888888888"
}
],
"createTimestamp" : "20170614063303Z",
"category" : "member",
"action" : "add"
}

客户端在请求增量更新的时候,通过当前登陆ID与版本号,可查找出所有与自己相关的变更日志,然后在客户端数据库中应用这些变更,即可完成同步。

组织架构-Client

   由于现在员工办公设备的多样性,客户端要根据自己公司的情况,覆盖的足够完整,常见的平台有 iOS Android windowsmac linux , 对于后三个平台可以用 Web APP 来覆盖,iOS&Android 用原生的app来提升用户体验。

客户端App主要包括以下功能:
  1. 会话列表
  2. 优秀的聊天界面,历史记录
  3. 组织机构全量/增量更新
  4. 员工个人资料展示


客户端数据库设计

IM数据库设计
 
当前版本使用环信SDK
 
组织架构数据库设计

表设计

客户端组织架构较服务端简单,不关联用户Role,客户端本地存储Staff(员工)和Department(部门)信息:
  • 一个部门可以包含相关子部门和部门员工。该部门员工和部门在视图上处于同级关系。
  • 员工隶属于部门,同一员工可以存在于多个部门。
  • 员工角色用title来表示。


用户在登录客户端成功后,会根据该用户信息创建用户对应的数据库文件,用户表(User)保存用户相关信息,关联该用户staff信息。

客户端组织架构同服务端逻辑。

工作流集成

(TODO)
 
如何使用Dolores

本项目现在已经完成了第一个测试版本,本小节将指导您如何安装使用。

后端数据库

鉴于通讯录对数据库操作的特点多度少写,以及部门之间的树状关系,我们选择LDAP协议来存取数据。

我们有独立的repo来帮助您完成数据库的安装与初始化。请移步这里

组织架构管理

Dolores 初始版本使用Golang实现,大家既可以下载各个平台的可执行包,也可以安装Go语言的开发环境自己编译。

我们有独立的repo来帮助您,运行后端服务。请移步这里

客户端

我们现在有提供一个iOS版的Demo。请移步这里

Done

如果您顺利的完成以上三步,访问 http://localhost:3280 (端口号根据自己的配置,可能会有差异),使用 username: admin, password: dolores 登陆后端管理页面,添加权限规则,添加角色,添加员工、部门,然后使用iOS客户端登陆,就可以愉快的开始聊天啦~
 
负载均衡

(TODO)

多机容灾

(TODO)

LICENSE
 Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/

更多信息请前往github项目主页

 
这里我对每个repo做一个简单的介绍


Dolores: 项目简介, 整个项目的架构, 数据库设计等等 你想了解的一切都可以在这里看到
dolores-ios: iOS版demo,可以聊天查看组织架构
dolores-android: 哈哈 还没有,当然我们欢迎各路安卓大牛贡献安卓版demo
organization: 组织架构的创建管理、更新、审计等等核心的东西都在这里啦
dolores-server: 为客户端提供restfull api 与环信服务器集成
dolores-admin: 后台管理网站,用于管理部门员工。一个基于React的webapp还很基础,欢迎各位大牛pr.
dolores-ldap-init: 后台数据库的初始化工具,详情可以查看readme
easemob-resty:对环信rest api的封装,让调用环信api更简单
dolores-avatar:生成类似钉钉那样的默认头像


最后再说一点整个服务端是用go来写的,作者也是golang的初学者,如果代码哪里写的有问题或者架构有问题欢迎大家指正
THE CALM BEFORE THE STORM.
暴风雨前的宁静

ONE MORE THING 最后附上Dolores项目LOGO
当时作者正在二刷 《西部世界》这部剧,所以选择了女主的名字dolores作为整个项目的名字,而这个logo则寓意剧中的host。
687474703a2f2f6f7131696e636b76692e626b742e636c6f7564646e2e636f6d2f646f6c6f726573313032342e706e67.png
4
评论

【新手快速入门】集成环信常见问题+解决方案汇总 常见问题

dujiepeng 发表了文章 • 4070 次浏览 • 2017-05-22 15:51 • 来自相关话题

   这里整理了集成环信的常见问题和一些功能的实现思路,希望能帮助到大家。感谢热心的开发者贡献,大家在观看过程中有不明白的地方欢迎直接跟帖咨询。
 
ios篇
APNs证书创建和上传到环信后台头像昵称的简述和处理方案音视频离线推送Demo实现环信服务器聊天记录保存多久?离线收不到好友请求IOS中环信聊天窗口如何实现文件发送和预览的功能ios集成常见问题环信推送的一些常见问题实现名片|红包|话题聊天室等自定义cell
 
Android篇
环信3.0SDK集成小米推送教程EaseUI库中V4、v7包冲突解决方案Android EaseUI里的百度地图替换为高德地图android扩展消息(名片集成)关于会话列表的置顶聊天java.lang.UnsatisfiedLinkError: 的问题android 端 app 后台被杀死收不到消息的解决方案
昵称头像篇
android中如何显示开发者服务器上的昵称和头像 Android中显示头像(接上一篇文章看)环信(Android)设置头像和昵称的方法(最简单暴力的基于环信demo的集成)IOS中如何显示开发者服务器上的昵称和头像【环信公开课第12期视频回放】-所有关于环信IM昵称头像的问题听这课就够了
 
直播篇
一言不合你就搞个直播APP
 
客服集成
IM-SDK和客服SDK并存开发指南—Android篇IM-SDK和客服SDK并存开发指南—iOS篇
 
开源项目
Android简版demoios简版demo凡信2.0:超仿微信的开源项目 凡信3.0:携直播和红包而来高仿微信:Github 3,515 Star方圆十里:环信编程大赛冠军项目泛聊:定一个小目标写一个QQSlack聊天机器人:一天时间做一个聊天机器人TV视频通话:在电视上视频通话视频通话:Android手机视频通话酷信:ios高仿微信公众号助手:与订阅用户聊天沟通
 
持续更新ing...小伙伴们还有什么想知道欢迎跟帖提出。
  查看全部
   这里整理了集成环信的常见问题和一些功能的实现思路,希望能帮助到大家。感谢热心的开发者贡献,大家在观看过程中有不明白的地方欢迎直接跟帖咨询。
 
ios篇

 
Android篇

昵称头像篇

 
直播篇
  1. 一言不合你就搞个直播APP

 
客服集成
  1. IM-SDK和客服SDK并存开发指南—Android篇
  2. IM-SDK和客服SDK并存开发指南—iOS篇

 
开源项目

 
持续更新ing...小伙伴们还有什么想知道欢迎跟帖提出。
 
7
评论

【视频教程+源码】基于环信IM做一个仿微信APP-更新ing 郭永峰 高仿微信 仿微信 环信 XMPP

郭永峰 发表了文章 • 7836 次浏览 • 2017-05-16 15:29 • 来自相关话题

我只是一个普通人,做人要谦虚。
我不是大神,我也不是很厉害的。
天外有天,人外有人。
老师引入门,修行靠个人。
希望能帮助大家,谢谢。
    大家好,我是郭永峰(峰哥) | 一个普通大学计算机系毕业的大学生,曾就职于澳门遊澳集团有限公司,负责大型银联支付业务系统、跨国际短信业务系统(基于电信的SGIP)以及集团内部通讯系统 (负责android和openfire后台二次开发)的主要开发任务,担任项目负责人。13年就职于广州拓谷科技有限公司负责“酷蛙”车联网产品研发及汽车销售产品研发。14来年到17年2月份,就职于国内知名教育机构,负责教学研发及授课的工作。
 
本人现状况:
  
   在家录制教学视频(无收入),搞工作室,组建团队成立公司,如果大家觉得分享内容很喜欢,可以给我打点赏支持本人的工作室,二维码在文末,就不影响阅读了。

 
郭永峰IT教育工作室于2017年4月12日成立!
 
成立原因:

希望把近10年来从事IT互联网的知识分享给大家,包括Linux,WindowServer,Java,PHP,Android,iOS,H5等等等。
 

进入正题,本套课程基于环信IM教大家如何做一个类似微信的APP,只用于技术交流,请勿用于任何商业用途。
4月12号成立工作室,现在18号,过了一个星期一个星期录了5天的环信教程视频,我将放在网盘免费分享环信的教程视频主要是针对有开发经验者教程主要是使用环信来模仿微信来做一个即时通讯的案例课程主要是先讲socket基础 -> 环信 ->自定义协议希望这些教程视频能帮助大家,对即时通讯、socket和自定义协议有个较深入的了解同时能希望大家在面试时,在即时通讯这块不在陌生
  持续更新

第一阶段:即时通讯的了解和微信APP开发前的准备!

【视频教程+源码】基于环信IM做一个仿微信APP-01.即时通讯简介(了解)

【视频教程+源码】基于环信IM做一个仿微信APP-02.XMPP简介(了解)

【视频教程+源码】基于环信IM做一个仿微信APP-03.XMPP实现即时通信的准备工作(了解)

【视频教程+源码】基于环信IM做一个仿微信APP-04.环信简介(了解) 

【视频教程+源码】基于环信IM做一个仿微信APP-05.集成环信的前提准备(掌握)
 
【视频教程+源码】基于环信IM做一个仿微信APP-06.环信SDK的版本的区别(掌握)

【视频教程+源码】基于环信IM做一个仿微信APP-07.微信-项目创建及代码目录结构规范(MVC)

【视频教程+源码】基于环信IM做一个仿微信APP-08.微信-集成环信SDK

【视频教程+源码】基于环信IM做一个仿微信APP-09.微信-登录界面排版

【视频教程+源码】基于环信IM做一个仿微信APP-10.微信-主界面搭建

【视频教程+源码】基于环信IM做一个仿微信APP-11.微信-注册功能

【视频教程+源码】基于环信IM做一个仿微信APP- 12.微信-登录功能

【视频教程+源码】基于环信IM做一个仿微信APP- 13.微信-自动登录

【视频教程+源码】基于环信IM做一个仿微信APP- 14.微信-主动退出
 
【视频教程+源码】基于环信IM做一个仿微信APP-15.微信-在其它设备登录
 
整个项目源码,git地址https://github.com/mayaole/fWeiXin

 微信打赏






支付宝打赏






谢谢大家的支持,个人微信号清扫描下面张图






 
郭永峰IT交流QQ群请加:596441895 查看全部
我只是一个普通人,做人要谦虚。
我不是大神,我也不是很厉害的。
天外有天,人外有人。
老师引入门,修行靠个人。
希望能帮助大家,谢谢。

    大家好,我是郭永峰(峰哥) | 一个普通大学计算机系毕业的大学生,曾就职于澳门遊澳集团有限公司,负责大型银联支付业务系统、跨国际短信业务系统(基于电信的SGIP)以及集团内部通讯系统 (负责android和openfire后台二次开发)的主要开发任务,担任项目负责人。13年就职于广州拓谷科技有限公司负责“酷蛙”车联网产品研发及汽车销售产品研发。14来年到17年2月份,就职于国内知名教育机构,负责教学研发及授课的工作。
 
本人现状况:
  
   在家录制教学视频(无收入),搞工作室,组建团队成立公司,如果大家觉得分享内容很喜欢,可以给我打点赏支持本人的工作室,二维码在文末,就不影响阅读了。

 
郭永峰IT教育工作室于2017年4月12日成立!
 
成立原因:

希望把近10年来从事IT互联网的知识分享给大家,包括Linux,WindowServer,Java,PHP,Android,iOS,H5等等等。
 

进入正题,本套课程基于环信IM教大家如何做一个类似微信的APP,只用于技术交流,请勿用于任何商业用途。
  1. 4月12号成立工作室,现在18号,过了一个星期
  2. 一个星期录了5天的环信教程视频,我将放在网盘免费分享
  3. 环信的教程视频主要是针对有开发经验者
  4. 教程主要是使用环信来模仿微信来做一个即时通讯的案例
  5. 课程主要是先讲socket基础 -> 环信 ->自定义协议
  6. 希望这些教程视频能帮助大家,对即时通讯、socket和自定义协议有个较深入的了解
  7. 同时能希望大家在面试时,在即时通讯这块不在陌生

  持续更新

第一阶段:即时通讯的了解和微信APP开发前的准备!

【视频教程+源码】基于环信IM做一个仿微信APP-01.即时通讯简介(了解)

【视频教程+源码】基于环信IM做一个仿微信APP-02.XMPP简介(了解)

【视频教程+源码】基于环信IM做一个仿微信APP-03.XMPP实现即时通信的准备工作(了解)

【视频教程+源码】基于环信IM做一个仿微信APP-04.环信简介(了解) 

【视频教程+源码】基于环信IM做一个仿微信APP-05.集成环信的前提准备(掌握)
 
【视频教程+源码】基于环信IM做一个仿微信APP-06.环信SDK的版本的区别(掌握)

【视频教程+源码】基于环信IM做一个仿微信APP-07.微信-项目创建及代码目录结构规范(MVC)

【视频教程+源码】基于环信IM做一个仿微信APP-08.微信-集成环信SDK

【视频教程+源码】基于环信IM做一个仿微信APP-09.微信-登录界面排版

【视频教程+源码】基于环信IM做一个仿微信APP-10.微信-主界面搭建

【视频教程+源码】基于环信IM做一个仿微信APP-11.微信-注册功能

【视频教程+源码】基于环信IM做一个仿微信APP- 12.微信-登录功能

【视频教程+源码】基于环信IM做一个仿微信APP- 13.微信-自动登录

【视频教程+源码】基于环信IM做一个仿微信APP- 14.微信-主动退出
 
【视频教程+源码】基于环信IM做一个仿微信APP-15.微信-在其它设备登录
 
整个项目源码,git地址https://github.com/mayaole/fWeiXin

 微信打赏

微信.png


支付宝打赏

支付宝.png


谢谢大家的支持,个人微信号清扫描下面张图

个人.png


 
郭永峰IT交流QQ群请加:596441895
2
评论

ios详细的证书创建和上传 iOS ios证书

o孟力o 发表了文章 • 7578 次浏览 • 2015-12-30 11:58 • 来自相关话题

为新手制定一个详细的创建推送证书流程
第一步:生成.certSigningRequest证书
打开钥匙串,在钥匙串导航栏找到,“钥匙串访问”这一项,在钥匙串访问找到“从证书颁发机构请求证书”这一项,如图




点击后进入到如图界面




用户电子邮件地址:写上你的邮件就行
常用名称:随便写
然后选择储存到磁盘,CA电子邮件地址不用写,点继续就生成了.certSigningRequest文件了,记得这个文件的储存位置,一会我们生成推送证书还要用到
第二步:创建推送证书
打开开发者中心,进入生成证书界面(应该找的到吧),进入之后,如果你要生成测试环境的推送证书,选择Certificates-Development,如果你要生成正式环境证书就选择Certificates-Priduction,这里我们以测试为例,点击右上角加号,如图




进入下个界面,如果你要生成测试环境推送证书,就选择红色部分,你要生成正式环境证书,就选择绿色部分,如图




点Continue,进入下个界面,让选择app id,提醒一下,选择app id要和你工程的Bundle Identifier对应,新手应该找的到Bundle Identifier吧,找不到看下面的图,我截了张图




选择完app id ,一直点击Continue,进入如图界面




让选择文件,还记得我们刚开始用钥匙串生成的.certSigningRequest在哪放着吧,就选择它就对了,选择完之后,接着往下面走,把证书下载下来之后,双击就放到钥匙串里面了,这时候推送证书就生成完了
第三步:生成p12文件
我们最终放到环信后台的证书是p12证书,打开钥匙串,找到我们刚才生成的证书,右键找到导出选项,如图




点击导出“Apple Development.....”之后进入下个界面,如图:




红色圈着的部分,储存为:里面要写上证书的名字,名字随便起,不过要记住这个名字,一会在环信后台上传证书还要用到这个名字,名字填完之后,点击储存,储存的位置要记住,一会还要用这个文件,这个文件就是我们要上传到环信的后台的p12文件,(储存的时候会让你输入密码,这个密码你要记住,一会上传到环信后台证书的时候要用到这个密码),这时候p12文件就制作完成了。
第四步:把p12文件上传到环信后台
打开我们的环信后台,找到 推送证书-ios,下面有上传证书的地方,如图




证书名称:就是我们刚才生成p12的名称,我的叫zhuma_test
证书:就是我们刚才生成的p12证书,找到传上去
证书密码:生成p12证书的时候输入的密码(不是瞎输的吧,能记得吗,记不得重新导p12文件吧)
证书类型:我生成的是开发环境的证书,就勾选开发环境
填完之后就可以上传了,上传成功,就ok了 查看全部


为新手制定一个详细的创建推送证书流程
第一步:生成.certSigningRequest证书
打开钥匙串,在钥匙串导航栏找到,“钥匙串访问”这一项,在钥匙串访问找到“从证书颁发机构请求证书”这一项,如图
p1.png

点击后进入到如图界面
p2.png

用户电子邮件地址:写上你的邮件就行
常用名称:随便写
然后选择储存到磁盘,CA电子邮件地址不用写,点继续就生成了.certSigningRequest文件了,记得这个文件的储存位置,一会我们生成推送证书还要用到
第二步:创建推送证书
打开开发者中心,进入生成证书界面(应该找的到吧),进入之后,如果你要生成测试环境的推送证书,选择Certificates-Development,如果你要生成正式环境证书就选择Certificates-Priduction,这里我们以测试为例,点击右上角加号,如图
p3.png

进入下个界面,如果你要生成测试环境推送证书,就选择红色部分,你要生成正式环境证书,就选择绿色部分,如图
p4.png

点Continue,进入下个界面,让选择app id,提醒一下,选择app id要和你工程的Bundle Identifier对应,新手应该找的到Bundle Identifier吧,找不到看下面的图,我截了张图
p10.png

选择完app id ,一直点击Continue,进入如图界面
p5.png

让选择文件,还记得我们刚开始用钥匙串生成的.certSigningRequest在哪放着吧,就选择它就对了,选择完之后,接着往下面走,把证书下载下来之后,双击就放到钥匙串里面了,这时候推送证书就生成完了
第三步:生成p12文件
我们最终放到环信后台的证书是p12证书,打开钥匙串,找到我们刚才生成的证书,右键找到导出选项,如图
p6.png

点击导出“Apple Development.....”之后进入下个界面,如图:
p7.png

红色圈着的部分,储存为:里面要写上证书的名字,名字随便起,不过要记住这个名字,一会在环信后台上传证书还要用到这个名字,名字填完之后,点击储存,储存的位置要记住,一会还要用这个文件,这个文件就是我们要上传到环信的后台的p12文件,(储存的时候会让你输入密码,这个密码你要记住,一会上传到环信后台证书的时候要用到这个密码),这时候p12文件就制作完成了。
第四步:把p12文件上传到环信后台
打开我们的环信后台,找到 推送证书-ios,下面有上传证书的地方,如图
p8.png

证书名称:就是我们刚才生成p12的名称,我的叫zhuma_test
证书:就是我们刚才生成的p12证书,找到传上去
证书密码:生成p12证书的时候输入的密码(不是瞎输的吧,能记得吗,记不得重新导p12文件吧)
证书类型:我生成的是开发环境的证书,就勾选开发环境
填完之后就可以上传了,上传成功,就ok了
6
评论

【开源OA项目】基于环信IM开发完整的企业通讯解决方案-Dolores Dolores OA 开源项目

KevinGong 发表了文章 • 6640 次浏览 • 2017-06-26 10:53 • 来自相关话题

  

  前阵子钉钉在微信楼下刷了一波#创业很苦,坚持很酷#的广告,浓浓的“丧”文化风格文案受到了各界褒贬不一的评价,也引起了大家对OA办公系统的关注。
   对企业而言,初选OA办公系统是为了满足需求,解决当下问题,由于OA办公系统的在公司运作流程中扮演的重要性,安全与隐私等问题急需未雨绸缪,“可定制”、“可私有化部署”的OA办公系统成为了更多企业的首选。公司想自己开发一套IM系统应该从哪里开始呢? 企业通讯录怎么保持同步呢? 企业通讯录的权限管理应该怎么做?
   三个关于OA办公系统的究极问题,从开源的OA办公项目-Dolores(朵拉)诞生迎刃而解了。Dolores项目遵循Apache Licence 2.0 开源协议,可以直接拿来用,也可以修改代码来满足需要并作为开源或商业产品发布/销售。




关于Dolores?
Dolores是一套完整的企业通信解决方案,一个完整的企业沟通工具(以下简称企业IM),支持以下几个功能:IM消息服务、组织架构管理、工作流集成。
Dolores项目源码地址:https://github.com/DoloresTeam​ 
技术讨论群:641256202(QQ群)

整个解决方案都包括了什么?
企业通讯录的管理:部门/员工的增删改查通讯录全量更新:全量/增量更新 企业通讯录权限管理:基于RBAC权限管理模型企业即时通讯IM:企业通信对IM这块的可靠性要求高,选择了目前比较成熟的IM云服务厂商-环信
 
 
组织架构

企业通讯录可以说是企业沟通中最重的业务之一,能够提供员工各种服务的认证,获取员工的联系方式等。
 
组织架构-Server

服务端主要包括以下功能:
支持管理人员(例如HR)对部门和员工进行增删改查支持部门和员工自定义排序,自定义元信息存储权限管理员工通讯录视图 (员工根据自己的权限生成通讯录)通讯录增量更新 (鉴于移动端特殊的网络环境和设备,通讯录应该支持差量更新)集成 IM 用户系统

在这里我们主要讨论以下两个问题:
 
权限管理

  随着企业逐渐的发展,团队壮大为了更有效的沟通,以及保护公司内部的一些商业信息不被泄漏,我们应该为通讯录添加权限管理。

基于Role-based access control(RBAC)的权限管理模型

为了介绍此权限管理模型,我们先解释一下基本概念
角色:通常是指企业中某一个工作岗位,这个岗位具有特定的权利和职责。被赋予此角色的员工,将获得这种权利与职责权限:被赋予访问实体的权利。在本项目中是指访问部门和访问某一个或者某一类员工的权利用户-角色分配(User-Role Assignment URA):为某个用户指定一个或者多个角色,此员工将获得这些角色所具有权利的集合角色-权限分配(Role-Permission Assignment RPA):将权限分配给角色,一个角色可以包含多个权利。在本项目中是指多个访问部门和访问员工的权限

在用户和权限之间引入角色中介,将用户与权限的直接关系弱化为间接关系。|ˉˉˉ| |ˉˉ ˉ| |ˉˉˉˉ ˉˉ|
| User |---URA---> | Role |<---RPA---| Permission |
|______| |_______| |_____________|
    以角色为中介,首先创建访问每个部门和员工的访问权限,然后创建不同的角色,根据这些角色的职责不同分配不同的权限,建立角色-权限的关系以后,不同的角色将会有不同的权限。根据员工不同的岗位,将对应的角色分配给他们,建立用户-角色关系,这就是RBAC的主要思想。

一个员工可以用户多个角色,一个角色可以用于多个访问权限。RBAC 极大的简化了员工的授权管理。

   由于企业的部门和员工数量很多,在创建权限时管理员不可能去设置每一个权限可以访问的每一个部门和每一个员工。所以本项目将功能和指责类似的部门和员工看作是同一类型,在创建部门和员工的时候为每一个部门和员工分配固有属性type,管理员在设置权限规则的时候只需要指定可访问的部门类型和员工即可。

增量更新

   鉴于移动终端计算资源有限,如网络,存储,电量等,所以通讯录的更新技术应该保证尽量少的资源。另外由于通讯录的特殊性,通讯录的变化需要能实时通知到受影响的在线员工。

基于版本号与变更日志的增量更新模型

   客户端第一次登陆系统以后,我们根据当前登录角色生成对应的通讯录视图,并以当前时间戳作为版本号,返回给客户端。客户端后续通过此版本号增量更新通讯录。

版本号

   版本号有两种:一是客户端当前通讯录版本 c-version, 二是服务端通讯录每一次变化时的版本号s-version

变更日志

   在管理员修改权限规则,或者修改某个岗位的访问规则时会影响大面积员工的通讯录视图,此时如果用增量更新会导致服务器流量异常,因此在这2中情况会清空原来的变更日志并且要求客户端进行一次全量更新。

   如果管理员新增了员工,服务端会根据被修改的员工或者部门type, 反推出所有受影响的员工,然后生成一条变更日志, 例如:{
"content" : [
{
"cn" : "Lucy.Liu",
"id" : "b4vlfg91scgi1dcju8v0",
"title" : "市场运营负责人",
"email" : [
"lucy.liu@dolores.store"
],
"priority" : "101111",
"name" : "刘小飞",
"telephoneNumber" : "18888888888"
}
],
"createTimestamp" : "20170614063303Z",
"category" : "member",
"action" : "add"
}
客户端在请求增量更新的时候,通过当前登陆ID与版本号,可查找出所有与自己相关的变更日志,然后在客户端数据库中应用这些变更,即可完成同步。

组织架构-Client

   由于现在员工办公设备的多样性,客户端要根据自己公司的情况,覆盖的足够完整,常见的平台有 iOS Android windowsmac linux , 对于后三个平台可以用 Web APP 来覆盖,iOS&Android 用原生的app来提升用户体验。

客户端App主要包括以下功能:
会话列表优秀的聊天界面,历史记录组织机构全量/增量更新员工个人资料展示

客户端数据库设计

IM数据库设计
 
当前版本使用环信SDK
 
组织架构数据库设计

表设计

客户端组织架构较服务端简单,不关联用户Role,客户端本地存储Staff(员工)和Department(部门)信息:
一个部门可以包含相关子部门和部门员工。该部门员工和部门在视图上处于同级关系。员工隶属于部门,同一员工可以存在于多个部门。员工角色用title来表示。

用户在登录客户端成功后,会根据该用户信息创建用户对应的数据库文件,用户表(User)保存用户相关信息,关联该用户staff信息。

客户端组织架构同服务端逻辑。

工作流集成

(TODO)
 
如何使用Dolores

本项目现在已经完成了第一个测试版本,本小节将指导您如何安装使用。

后端数据库

鉴于通讯录对数据库操作的特点多度少写,以及部门之间的树状关系,我们选择LDAP协议来存取数据。

我们有独立的repo来帮助您完成数据库的安装与初始化。请移步这里

组织架构管理

Dolores 初始版本使用Golang实现,大家既可以下载各个平台的可执行包,也可以安装Go语言的开发环境自己编译。

我们有独立的repo来帮助您,运行后端服务。请移步这里

客户端

我们现在有提供一个iOS版的Demo。请移步这里

Done

如果您顺利的完成以上三步,访问 http://localhost:3280 (端口号根据自己的配置,可能会有差异),使用 username: admin, password: dolores 登陆后端管理页面,添加权限规则,添加角色,添加员工、部门,然后使用iOS客户端登陆,就可以愉快的开始聊天啦~
 
负载均衡

(TODO)

多机容灾

(TODO)

LICENSE Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
更多信息请前往github项目主页

 
这里我对每个repo做一个简单的介绍


Dolores: 项目简介, 整个项目的架构, 数据库设计等等 你想了解的一切都可以在这里看到
dolores-ios: iOS版demo,可以聊天查看组织架构
dolores-android: 哈哈 还没有,当然我们欢迎各路安卓大牛贡献安卓版demo
organization: 组织架构的创建管理、更新、审计等等核心的东西都在这里啦
dolores-server: 为客户端提供restfull api 与环信服务器集成
dolores-admin: 后台管理网站,用于管理部门员工。一个基于React的webapp还很基础,欢迎各位大牛pr.
dolores-ldap-init: 后台数据库的初始化工具,详情可以查看readme
easemob-resty:对环信rest api的封装,让调用环信api更简单
dolores-avatar:生成类似钉钉那样的默认头像


最后再说一点整个服务端是用go来写的,作者也是golang的初学者,如果代码哪里写的有问题或者架构有问题欢迎大家指正THE CALM BEFORE THE STORM.
暴风雨前的宁静
ONE MORE THING 最后附上Dolores项目LOGO
当时作者正在二刷 《西部世界》这部剧,所以选择了女主的名字dolores作为整个项目的名字,而这个logo则寓意剧中的host。 查看全部
  

  前阵子钉钉在微信楼下刷了一波#创业很苦,坚持很酷#的广告,浓浓的“丧”文化风格文案受到了各界褒贬不一的评价,也引起了大家对OA办公系统的关注。
   对企业而言,初选OA办公系统是为了满足需求,解决当下问题,由于OA办公系统的在公司运作流程中扮演的重要性,安全与隐私等问题急需未雨绸缪,“可定制”、“可私有化部署”的OA办公系统成为了更多企业的首选。
公司想自己开发一套IM系统应该从哪里开始呢? 企业通讯录怎么保持同步呢? 企业通讯录的权限管理应该怎么做?

   三个关于OA办公系统的究极问题,从开源的OA办公项目-Dolores(朵拉)诞生迎刃而解了。Dolores项目遵循Apache Licence 2.0 开源协议,可以直接拿来用,也可以修改代码来满足需要并作为开源或商业产品发布/销售。
OA广告图.jpg

关于Dolores?

Dolores是一套完整的企业通信解决方案,一个完整的企业沟通工具(以下简称企业IM),支持以下几个功能:IM消息服务、组织架构管理、工作流集成。


Dolores项目源码地址:https://github.com/DoloresTeam​ 
技术讨论群:641256202(QQ群)

整个解决方案都包括了什么?
  • 企业通讯录的管理:部门/员工的增删改查
  • 通讯录全量更新:全量/增量更新 
  • 企业通讯录权限管理:基于RBAC权限管理模型
  • 企业即时通讯IM:企业通信对IM这块的可靠性要求高,选择了目前比较成熟的IM云服务厂商-环信

 
 
组织架构

企业通讯录可以说是企业沟通中最重的业务之一,能够提供员工各种服务的认证,获取员工的联系方式等。
 
组织架构-Server

服务端主要包括以下功能:
  1. 支持管理人员(例如HR)对部门和员工进行增删改查
  2. 支持部门和员工自定义排序,自定义元信息存储
  3. 权限管理
  4. 员工通讯录视图 (员工根据自己的权限生成通讯录)
  5. 通讯录增量更新 (鉴于移动端特殊的网络环境和设备,通讯录应该支持差量更新)
  6. 集成 IM 用户系统


在这里我们主要讨论以下两个问题:
 
权限管理

  随着企业逐渐的发展,团队壮大为了更有效的沟通,以及保护公司内部的一些商业信息不被泄漏,我们应该为通讯录添加权限管理。

基于Role-based access control(RBAC)的权限管理模型

为了介绍此权限管理模型,我们先解释一下基本概念
  • 角色:通常是指企业中某一个工作岗位,这个岗位具有特定的权利和职责。被赋予此角色的员工,将获得这种权利与职责
  • 权限:被赋予访问实体的权利。在本项目中是指访问部门和访问某一个或者某一类员工的权利
  • 用户-角色分配(User-Role Assignment URA):为某个用户指定一个或者多个角色,此员工将获得这些角色所具有权利的集合
  • 角色-权限分配(Role-Permission Assignment RPA):将权限分配给角色,一个角色可以包含多个权利。在本项目中是指多个访问部门和访问员工的权限


在用户和权限之间引入角色中介,将用户与权限的直接关系弱化为间接关系。
|ˉˉˉ|           |ˉˉ ˉ|          |ˉˉˉˉ ˉˉ|  
| User |---URA---> | Role |<---RPA---| Permission |
|______| |_______| |_____________|

    以角色为中介,首先创建访问每个部门和员工的访问权限,然后创建不同的角色,根据这些角色的职责不同分配不同的权限,建立角色-权限的关系以后,不同的角色将会有不同的权限。根据员工不同的岗位,将对应的角色分配给他们,建立用户-角色关系,这就是RBAC的主要思想。

一个员工可以用户多个角色,一个角色可以用于多个访问权限。RBAC 极大的简化了员工的授权管理。

   由于企业的部门和员工数量很多,在创建权限时管理员不可能去设置每一个权限可以访问的每一个部门和每一个员工。所以本项目将功能和指责类似的部门和员工看作是同一类型,在创建部门和员工的时候为每一个部门和员工分配固有属性type,管理员在设置权限规则的时候只需要指定可访问的部门类型和员工即可。

增量更新

   鉴于移动终端计算资源有限,如网络,存储,电量等,所以通讯录的更新技术应该保证尽量少的资源。另外由于通讯录的特殊性,通讯录的变化需要能实时通知到受影响的在线员工。

基于版本号与变更日志的增量更新模型

   客户端第一次登陆系统以后,我们根据当前登录角色生成对应的通讯录视图,并以当前时间戳作为版本号,返回给客户端。客户端后续通过此版本号增量更新通讯录。

版本号

   版本号有两种:一是客户端当前通讯录版本 c-version, 二是服务端通讯录每一次变化时的版本号s-version

变更日志

   在管理员修改权限规则,或者修改某个岗位的访问规则时会影响大面积员工的通讯录视图,此时如果用增量更新会导致服务器流量异常,因此在这2中情况会清空原来的变更日志并且要求客户端进行一次全量更新。

   如果管理员新增了员工,服务端会根据被修改的员工或者部门type, 反推出所有受影响的员工,然后生成一条变更日志, 例如:
{
"content" : [
{
"cn" : "Lucy.Liu",
"id" : "b4vlfg91scgi1dcju8v0",
"title" : "市场运营负责人",
"email" : [
"lucy.liu@dolores.store"
],
"priority" : "101111",
"name" : "刘小飞",
"telephoneNumber" : "18888888888"
}
],
"createTimestamp" : "20170614063303Z",
"category" : "member",
"action" : "add"
}

客户端在请求增量更新的时候,通过当前登陆ID与版本号,可查找出所有与自己相关的变更日志,然后在客户端数据库中应用这些变更,即可完成同步。

组织架构-Client

   由于现在员工办公设备的多样性,客户端要根据自己公司的情况,覆盖的足够完整,常见的平台有 iOS Android windowsmac linux , 对于后三个平台可以用 Web APP 来覆盖,iOS&Android 用原生的app来提升用户体验。

客户端App主要包括以下功能:
  1. 会话列表
  2. 优秀的聊天界面,历史记录
  3. 组织机构全量/增量更新
  4. 员工个人资料展示


客户端数据库设计

IM数据库设计
 
当前版本使用环信SDK
 
组织架构数据库设计

表设计

客户端组织架构较服务端简单,不关联用户Role,客户端本地存储Staff(员工)和Department(部门)信息:
  • 一个部门可以包含相关子部门和部门员工。该部门员工和部门在视图上处于同级关系。
  • 员工隶属于部门,同一员工可以存在于多个部门。
  • 员工角色用title来表示。


用户在登录客户端成功后,会根据该用户信息创建用户对应的数据库文件,用户表(User)保存用户相关信息,关联该用户staff信息。

客户端组织架构同服务端逻辑。

工作流集成

(TODO)
 
如何使用Dolores

本项目现在已经完成了第一个测试版本,本小节将指导您如何安装使用。

后端数据库

鉴于通讯录对数据库操作的特点多度少写,以及部门之间的树状关系,我们选择LDAP协议来存取数据。

我们有独立的repo来帮助您完成数据库的安装与初始化。请移步这里

组织架构管理

Dolores 初始版本使用Golang实现,大家既可以下载各个平台的可执行包,也可以安装Go语言的开发环境自己编译。

我们有独立的repo来帮助您,运行后端服务。请移步这里

客户端

我们现在有提供一个iOS版的Demo。请移步这里

Done

如果您顺利的完成以上三步,访问 http://localhost:3280 (端口号根据自己的配置,可能会有差异),使用 username: admin, password: dolores 登陆后端管理页面,添加权限规则,添加角色,添加员工、部门,然后使用iOS客户端登陆,就可以愉快的开始聊天啦~
 
负载均衡

(TODO)

多机容灾

(TODO)

LICENSE
 Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/

更多信息请前往github项目主页

 
这里我对每个repo做一个简单的介绍


Dolores: 项目简介, 整个项目的架构, 数据库设计等等 你想了解的一切都可以在这里看到
dolores-ios: iOS版demo,可以聊天查看组织架构
dolores-android: 哈哈 还没有,当然我们欢迎各路安卓大牛贡献安卓版demo
organization: 组织架构的创建管理、更新、审计等等核心的东西都在这里啦
dolores-server: 为客户端提供restfull api 与环信服务器集成
dolores-admin: 后台管理网站,用于管理部门员工。一个基于React的webapp还很基础,欢迎各位大牛pr.
dolores-ldap-init: 后台数据库的初始化工具,详情可以查看readme
easemob-resty:对环信rest api的封装,让调用环信api更简单
dolores-avatar:生成类似钉钉那样的默认头像


最后再说一点整个服务端是用go来写的,作者也是golang的初学者,如果代码哪里写的有问题或者架构有问题欢迎大家指正
THE CALM BEFORE THE STORM.
暴风雨前的宁静

ONE MORE THING 最后附上Dolores项目LOGO
当时作者正在二刷 《西部世界》这部剧,所以选择了女主的名字dolores作为整个项目的名字,而这个logo则寓意剧中的host。
687474703a2f2f6f7131696e636b76692e626b742e636c6f7564646e2e636f6d2f646f6c6f726573313032342e706e67.png
4
评论

【新手快速入门】集成环信常见问题+解决方案汇总 常见问题

dujiepeng 发表了文章 • 4070 次浏览 • 2017-05-22 15:51 • 来自相关话题

   这里整理了集成环信的常见问题和一些功能的实现思路,希望能帮助到大家。感谢热心的开发者贡献,大家在观看过程中有不明白的地方欢迎直接跟帖咨询。
 
ios篇
APNs证书创建和上传到环信后台头像昵称的简述和处理方案音视频离线推送Demo实现环信服务器聊天记录保存多久?离线收不到好友请求IOS中环信聊天窗口如何实现文件发送和预览的功能ios集成常见问题环信推送的一些常见问题实现名片|红包|话题聊天室等自定义cell
 
Android篇
环信3.0SDK集成小米推送教程EaseUI库中V4、v7包冲突解决方案Android EaseUI里的百度地图替换为高德地图android扩展消息(名片集成)关于会话列表的置顶聊天java.lang.UnsatisfiedLinkError: 的问题android 端 app 后台被杀死收不到消息的解决方案
昵称头像篇
android中如何显示开发者服务器上的昵称和头像 Android中显示头像(接上一篇文章看)环信(Android)设置头像和昵称的方法(最简单暴力的基于环信demo的集成)IOS中如何显示开发者服务器上的昵称和头像【环信公开课第12期视频回放】-所有关于环信IM昵称头像的问题听这课就够了
 
直播篇
一言不合你就搞个直播APP
 
客服集成
IM-SDK和客服SDK并存开发指南—Android篇IM-SDK和客服SDK并存开发指南—iOS篇
 
开源项目
Android简版demoios简版demo凡信2.0:超仿微信的开源项目 凡信3.0:携直播和红包而来高仿微信:Github 3,515 Star方圆十里:环信编程大赛冠军项目泛聊:定一个小目标写一个QQSlack聊天机器人:一天时间做一个聊天机器人TV视频通话:在电视上视频通话视频通话:Android手机视频通话酷信:ios高仿微信公众号助手:与订阅用户聊天沟通
 
持续更新ing...小伙伴们还有什么想知道欢迎跟帖提出。
  查看全部
   这里整理了集成环信的常见问题和一些功能的实现思路,希望能帮助到大家。感谢热心的开发者贡献,大家在观看过程中有不明白的地方欢迎直接跟帖咨询。
 
ios篇

 
Android篇

昵称头像篇

 
直播篇
  1. 一言不合你就搞个直播APP

 
客服集成
  1. IM-SDK和客服SDK并存开发指南—Android篇
  2. IM-SDK和客服SDK并存开发指南—iOS篇

 
开源项目

 
持续更新ing...小伙伴们还有什么想知道欢迎跟帖提出。
 
7
评论

【视频教程+源码】基于环信IM做一个仿微信APP-更新ing 郭永峰 高仿微信 仿微信 环信 XMPP

郭永峰 发表了文章 • 7836 次浏览 • 2017-05-16 15:29 • 来自相关话题

我只是一个普通人,做人要谦虚。
我不是大神,我也不是很厉害的。
天外有天,人外有人。
老师引入门,修行靠个人。
希望能帮助大家,谢谢。
    大家好,我是郭永峰(峰哥) | 一个普通大学计算机系毕业的大学生,曾就职于澳门遊澳集团有限公司,负责大型银联支付业务系统、跨国际短信业务系统(基于电信的SGIP)以及集团内部通讯系统 (负责android和openfire后台二次开发)的主要开发任务,担任项目负责人。13年就职于广州拓谷科技有限公司负责“酷蛙”车联网产品研发及汽车销售产品研发。14来年到17年2月份,就职于国内知名教育机构,负责教学研发及授课的工作。
 
本人现状况:
  
   在家录制教学视频(无收入),搞工作室,组建团队成立公司,如果大家觉得分享内容很喜欢,可以给我打点赏支持本人的工作室,二维码在文末,就不影响阅读了。

 
郭永峰IT教育工作室于2017年4月12日成立!
 
成立原因:

希望把近10年来从事IT互联网的知识分享给大家,包括Linux,WindowServer,Java,PHP,Android,iOS,H5等等等。
 

进入正题,本套课程基于环信IM教大家如何做一个类似微信的APP,只用于技术交流,请勿用于任何商业用途。
4月12号成立工作室,现在18号,过了一个星期一个星期录了5天的环信教程视频,我将放在网盘免费分享环信的教程视频主要是针对有开发经验者教程主要是使用环信来模仿微信来做一个即时通讯的案例课程主要是先讲socket基础 -> 环信 ->自定义协议希望这些教程视频能帮助大家,对即时通讯、socket和自定义协议有个较深入的了解同时能希望大家在面试时,在即时通讯这块不在陌生
  持续更新

第一阶段:即时通讯的了解和微信APP开发前的准备!

【视频教程+源码】基于环信IM做一个仿微信APP-01.即时通讯简介(了解)

【视频教程+源码】基于环信IM做一个仿微信APP-02.XMPP简介(了解)

【视频教程+源码】基于环信IM做一个仿微信APP-03.XMPP实现即时通信的准备工作(了解)

【视频教程+源码】基于环信IM做一个仿微信APP-04.环信简介(了解) 

【视频教程+源码】基于环信IM做一个仿微信APP-05.集成环信的前提准备(掌握)
 
【视频教程+源码】基于环信IM做一个仿微信APP-06.环信SDK的版本的区别(掌握)

【视频教程+源码】基于环信IM做一个仿微信APP-07.微信-项目创建及代码目录结构规范(MVC)

【视频教程+源码】基于环信IM做一个仿微信APP-08.微信-集成环信SDK

【视频教程+源码】基于环信IM做一个仿微信APP-09.微信-登录界面排版

【视频教程+源码】基于环信IM做一个仿微信APP-10.微信-主界面搭建

【视频教程+源码】基于环信IM做一个仿微信APP-11.微信-注册功能

【视频教程+源码】基于环信IM做一个仿微信APP- 12.微信-登录功能

【视频教程+源码】基于环信IM做一个仿微信APP- 13.微信-自动登录

【视频教程+源码】基于环信IM做一个仿微信APP- 14.微信-主动退出
 
【视频教程+源码】基于环信IM做一个仿微信APP-15.微信-在其它设备登录
 
整个项目源码,git地址https://github.com/mayaole/fWeiXin

 微信打赏






支付宝打赏






谢谢大家的支持,个人微信号清扫描下面张图






 
郭永峰IT交流QQ群请加:596441895 查看全部
我只是一个普通人,做人要谦虚。
我不是大神,我也不是很厉害的。
天外有天,人外有人。
老师引入门,修行靠个人。
希望能帮助大家,谢谢。

    大家好,我是郭永峰(峰哥) | 一个普通大学计算机系毕业的大学生,曾就职于澳门遊澳集团有限公司,负责大型银联支付业务系统、跨国际短信业务系统(基于电信的SGIP)以及集团内部通讯系统 (负责android和openfire后台二次开发)的主要开发任务,担任项目负责人。13年就职于广州拓谷科技有限公司负责“酷蛙”车联网产品研发及汽车销售产品研发。14来年到17年2月份,就职于国内知名教育机构,负责教学研发及授课的工作。
 
本人现状况:
  
   在家录制教学视频(无收入),搞工作室,组建团队成立公司,如果大家觉得分享内容很喜欢,可以给我打点赏支持本人的工作室,二维码在文末,就不影响阅读了。

 
郭永峰IT教育工作室于2017年4月12日成立!
 
成立原因:

希望把近10年来从事IT互联网的知识分享给大家,包括Linux,WindowServer,Java,PHP,Android,iOS,H5等等等。
 

进入正题,本套课程基于环信IM教大家如何做一个类似微信的APP,只用于技术交流,请勿用于任何商业用途。
  1. 4月12号成立工作室,现在18号,过了一个星期
  2. 一个星期录了5天的环信教程视频,我将放在网盘免费分享
  3. 环信的教程视频主要是针对有开发经验者
  4. 教程主要是使用环信来模仿微信来做一个即时通讯的案例
  5. 课程主要是先讲socket基础 -> 环信 ->自定义协议
  6. 希望这些教程视频能帮助大家,对即时通讯、socket和自定义协议有个较深入的了解
  7. 同时能希望大家在面试时,在即时通讯这块不在陌生

  持续更新

第一阶段:即时通讯的了解和微信APP开发前的准备!

【视频教程+源码】基于环信IM做一个仿微信APP-01.即时通讯简介(了解)

【视频教程+源码】基于环信IM做一个仿微信APP-02.XMPP简介(了解)

【视频教程+源码】基于环信IM做一个仿微信APP-03.XMPP实现即时通信的准备工作(了解)

【视频教程+源码】基于环信IM做一个仿微信APP-04.环信简介(了解) 

【视频教程+源码】基于环信IM做一个仿微信APP-05.集成环信的前提准备(掌握)
 
【视频教程+源码】基于环信IM做一个仿微信APP-06.环信SDK的版本的区别(掌握)

【视频教程+源码】基于环信IM做一个仿微信APP-07.微信-项目创建及代码目录结构规范(MVC)

【视频教程+源码】基于环信IM做一个仿微信APP-08.微信-集成环信SDK

【视频教程+源码】基于环信IM做一个仿微信APP-09.微信-登录界面排版

【视频教程+源码】基于环信IM做一个仿微信APP-10.微信-主界面搭建

【视频教程+源码】基于环信IM做一个仿微信APP-11.微信-注册功能

【视频教程+源码】基于环信IM做一个仿微信APP- 12.微信-登录功能

【视频教程+源码】基于环信IM做一个仿微信APP- 13.微信-自动登录

【视频教程+源码】基于环信IM做一个仿微信APP- 14.微信-主动退出
 
【视频教程+源码】基于环信IM做一个仿微信APP-15.微信-在其它设备登录
 
整个项目源码,git地址https://github.com/mayaole/fWeiXin

 微信打赏

微信.png


支付宝打赏

支付宝.png


谢谢大家的支持,个人微信号清扫描下面张图

个人.png


 
郭永峰IT交流QQ群请加:596441895
1
回复

ios编译通过 运行直接crash iOS

回复

wangyuzhang 回复了问题 • 2 人关注 • 47 次浏览 • 2017-07-20 19:18 • 来自相关话题

6
最佳

会话列表接受消息时没反应 要下拉刷新才出来 怎么解决 消息接受 iOS 环信_iOS

回复

ljQwe 回复了问题 • 5 人关注 • 1972 次浏览 • 2017-07-12 16:16 • 来自相关话题

1
回复

请教环信3.2.1版本升级到3.3.2版本指南。 iOS 环信_iOS

回复

木云落 回复了问题 • 2 人关注 • 88 次浏览 • 2017-07-06 14:23 • 来自相关话题

25
最佳

求一份 iOS 3.x 群聊demo 环信_iOS 群聊demo 群聊 iOS

回复

撑住丶为心中狂野的目标 回复了问题 • 25 人关注 • 2143 次浏览 • 2017-07-04 22:00 • 来自相关话题

2
回复

ios 导入easeui 报错 iOS iOS 环信

回复

KevinGong 回复了问题 • 3 人关注 • 113 次浏览 • 2017-06-22 19:04 • 来自相关话题

1
回复

iOS pod导入环信SDK iOS pod SDK

回复

lifei9241 回复了问题 • 2 人关注 • 151 次浏览 • 2017-06-20 16:08 • 来自相关话题

1
回复

iOS 自己给自己发送消息 会重复 iOS iOS 环信

回复

木云落 回复了问题 • 2 人关注 • 130 次浏览 • 2017-06-19 11:00 • 来自相关话题

5
回复

环信cocoapods集成EaseUI 继承时报错 iOS swift EaseUI cocoapod

回复

爱我所爱 回复了问题 • 3 人关注 • 242 次浏览 • 2017-06-15 11:18 • 来自相关话题

1
回复

cocopod集成EaseUI出现300多个警告怎么回事 求解 iOS

回复

木云落 回复了问题 • 2 人关注 • 104 次浏览 • 2017-06-06 17:00 • 来自相关话题

1
回复

在pct中导入EMSDKFull.h时报错 iOS 环信

回复

木云落 回复了问题 • 2 人关注 • 139 次浏览 • 2017-05-26 11:06 • 来自相关话题

2
回复

iOS 环信2.0 发送消息重复。 iOS

回复

刘小刘 回复了问题 • 3 人关注 • 947 次浏览 • 2017-05-22 20:01 • 来自相关话题

1
回复

为什么环信的demo里边群主可以屏蔽群消息? iOS

回复

木云落 回复了问题 • 2 人关注 • 102 次浏览 • 2017-05-22 17:14 • 来自相关话题

8
回复

各位大神,有单聊集成音视频的demo吗?iOS3.0的? 音视频 iOS 环信_iOS

回复

SN 回复了问题 • 8 人关注 • 1063 次浏览 • 2017-05-12 14:29 • 来自相关话题

2
最佳

EMConversation中的ext保存后读取不出来 ext iOS

回复

asd777 回复了问题 • 2 人关注 • 208 次浏览 • 2017-04-20 15:33 • 来自相关话题

1
回复

跟着视频一起写的demo 环信 iOS

回复

xxl 回复了问题 • 2 人关注 • 279 次浏览 • 2017-04-12 15:59 • 来自相关话题

3
回复

手动集成EaseUI,Tabbar变黑色 iOS

回复

lizilong 回复了问题 • 3 人关注 • 220 次浏览 • 2017-04-12 15:37 • 来自相关话题

1
回复

环信视频通话,A用户和B用户怎么才能一连接就用扬声器视频对话呢? iOS 环信 音视频

回复

lizilong 回复了问题 • 2 人关注 • 823 次浏览 • 2017-04-11 15:01 • 来自相关话题

1
回复

有没有人做过环信图片连续滑动?能稍微解释一下基本步骤吗? 环信_iOS iOS

回复

donghai 回复了问题 • 2 人关注 • 197 次浏览 • 2017-04-10 17:27 • 来自相关话题

1
最佳

ios 环信最新版本 打包上线时候报错 审核bug 集成上线 iOS

回复

donghai 回复了问题 • 2 人关注 • 510 次浏览 • 2017-03-31 18:10 • 来自相关话题

4
回复

怎么修改EaseChatBarMoreView,自定义高度,自定义图标 iOS

回复

zjjzmw1 回复了问题 • 3 人关注 • 1232 次浏览 • 2017-03-22 17:09 • 来自相关话题

5
回复

ios 环信创建会话失败 iOS 环信

回复

zl 回复了问题 • 3 人关注 • 316 次浏览 • 2017-03-20 09:54 • 来自相关话题

1
回复

看视频如果出现问题可以看这里 dyld: Library not loaded: @rpath/Hyphenate.framework/Hyphenate iOS 环信

回复

donghai 回复了问题 • 2 人关注 • 1382 次浏览 • 2017-03-15 12:33 • 来自相关话题

2
最佳

环信服务端集成,如何集成? iOS Android webim

回复

执念 回复了问题 • 3 人关注 • 1683 次浏览 • 2017-03-13 11:36 • 来自相关话题

2
最佳

我在环信登录成功的回调中插入了一条消息,但是数据库中并没有这一条消息请问是为什么? iOS

回复

zl 回复了问题 • 2 人关注 • 268 次浏览 • 2017-03-03 19:31 • 来自相关话题

6
评论

【开源OA项目】基于环信IM开发完整的企业通讯解决方案-Dolores Dolores OA 开源项目

KevinGong 发表了文章 • 6640 次浏览 • 2017-06-26 10:53 • 来自相关话题

  

  前阵子钉钉在微信楼下刷了一波#创业很苦,坚持很酷#的广告,浓浓的“丧”文化风格文案受到了各界褒贬不一的评价,也引起了大家对OA办公系统的关注。
   对企业而言,初选OA办公系统是为了满足需求,解决当下问题,由于OA办公系统的在公司运作流程中扮演的重要性,安全与隐私等问题急需未雨绸缪,“可定制”、“可私有化部署”的OA办公系统成为了更多企业的首选。公司想自己开发一套IM系统应该从哪里开始呢? 企业通讯录怎么保持同步呢? 企业通讯录的权限管理应该怎么做?
   三个关于OA办公系统的究极问题,从开源的OA办公项目-Dolores(朵拉)诞生迎刃而解了。Dolores项目遵循Apache Licence 2.0 开源协议,可以直接拿来用,也可以修改代码来满足需要并作为开源或商业产品发布/销售。




关于Dolores?
Dolores是一套完整的企业通信解决方案,一个完整的企业沟通工具(以下简称企业IM),支持以下几个功能:IM消息服务、组织架构管理、工作流集成。
Dolores项目源码地址:https://github.com/DoloresTeam​ 
技术讨论群:641256202(QQ群)

整个解决方案都包括了什么?
企业通讯录的管理:部门/员工的增删改查通讯录全量更新:全量/增量更新 企业通讯录权限管理:基于RBAC权限管理模型企业即时通讯IM:企业通信对IM这块的可靠性要求高,选择了目前比较成熟的IM云服务厂商-环信
 
 
组织架构

企业通讯录可以说是企业沟通中最重的业务之一,能够提供员工各种服务的认证,获取员工的联系方式等。
 
组织架构-Server

服务端主要包括以下功能:
支持管理人员(例如HR)对部门和员工进行增删改查支持部门和员工自定义排序,自定义元信息存储权限管理员工通讯录视图 (员工根据自己的权限生成通讯录)通讯录增量更新 (鉴于移动端特殊的网络环境和设备,通讯录应该支持差量更新)集成 IM 用户系统

在这里我们主要讨论以下两个问题:
 
权限管理

  随着企业逐渐的发展,团队壮大为了更有效的沟通,以及保护公司内部的一些商业信息不被泄漏,我们应该为通讯录添加权限管理。

基于Role-based access control(RBAC)的权限管理模型

为了介绍此权限管理模型,我们先解释一下基本概念
角色:通常是指企业中某一个工作岗位,这个岗位具有特定的权利和职责。被赋予此角色的员工,将获得这种权利与职责权限:被赋予访问实体的权利。在本项目中是指访问部门和访问某一个或者某一类员工的权利用户-角色分配(User-Role Assignment URA):为某个用户指定一个或者多个角色,此员工将获得这些角色所具有权利的集合角色-权限分配(Role-Permission Assignment RPA):将权限分配给角色,一个角色可以包含多个权利。在本项目中是指多个访问部门和访问员工的权限

在用户和权限之间引入角色中介,将用户与权限的直接关系弱化为间接关系。|ˉˉˉ| |ˉˉ ˉ| |ˉˉˉˉ ˉˉ|
| User |---URA---> | Role |<---RPA---| Permission |
|______| |_______| |_____________|
    以角色为中介,首先创建访问每个部门和员工的访问权限,然后创建不同的角色,根据这些角色的职责不同分配不同的权限,建立角色-权限的关系以后,不同的角色将会有不同的权限。根据员工不同的岗位,将对应的角色分配给他们,建立用户-角色关系,这就是RBAC的主要思想。

一个员工可以用户多个角色,一个角色可以用于多个访问权限。RBAC 极大的简化了员工的授权管理。

   由于企业的部门和员工数量很多,在创建权限时管理员不可能去设置每一个权限可以访问的每一个部门和每一个员工。所以本项目将功能和指责类似的部门和员工看作是同一类型,在创建部门和员工的时候为每一个部门和员工分配固有属性type,管理员在设置权限规则的时候只需要指定可访问的部门类型和员工即可。

增量更新

   鉴于移动终端计算资源有限,如网络,存储,电量等,所以通讯录的更新技术应该保证尽量少的资源。另外由于通讯录的特殊性,通讯录的变化需要能实时通知到受影响的在线员工。

基于版本号与变更日志的增量更新模型

   客户端第一次登陆系统以后,我们根据当前登录角色生成对应的通讯录视图,并以当前时间戳作为版本号,返回给客户端。客户端后续通过此版本号增量更新通讯录。

版本号

   版本号有两种:一是客户端当前通讯录版本 c-version, 二是服务端通讯录每一次变化时的版本号s-version

变更日志

   在管理员修改权限规则,或者修改某个岗位的访问规则时会影响大面积员工的通讯录视图,此时如果用增量更新会导致服务器流量异常,因此在这2中情况会清空原来的变更日志并且要求客户端进行一次全量更新。

   如果管理员新增了员工,服务端会根据被修改的员工或者部门type, 反推出所有受影响的员工,然后生成一条变更日志, 例如:{
"content" : [
{
"cn" : "Lucy.Liu",
"id" : "b4vlfg91scgi1dcju8v0",
"title" : "市场运营负责人",
"email" : [
"lucy.liu@dolores.store"
],
"priority" : "101111",
"name" : "刘小飞",
"telephoneNumber" : "18888888888"
}
],
"createTimestamp" : "20170614063303Z",
"category" : "member",
"action" : "add"
}
客户端在请求增量更新的时候,通过当前登陆ID与版本号,可查找出所有与自己相关的变更日志,然后在客户端数据库中应用这些变更,即可完成同步。

组织架构-Client

   由于现在员工办公设备的多样性,客户端要根据自己公司的情况,覆盖的足够完整,常见的平台有 iOS Android windowsmac linux , 对于后三个平台可以用 Web APP 来覆盖,iOS&Android 用原生的app来提升用户体验。

客户端App主要包括以下功能:
会话列表优秀的聊天界面,历史记录组织机构全量/增量更新员工个人资料展示

客户端数据库设计

IM数据库设计
 
当前版本使用环信SDK
 
组织架构数据库设计

表设计

客户端组织架构较服务端简单,不关联用户Role,客户端本地存储Staff(员工)和Department(部门)信息:
一个部门可以包含相关子部门和部门员工。该部门员工和部门在视图上处于同级关系。员工隶属于部门,同一员工可以存在于多个部门。员工角色用title来表示。

用户在登录客户端成功后,会根据该用户信息创建用户对应的数据库文件,用户表(User)保存用户相关信息,关联该用户staff信息。

客户端组织架构同服务端逻辑。

工作流集成

(TODO)
 
如何使用Dolores

本项目现在已经完成了第一个测试版本,本小节将指导您如何安装使用。

后端数据库

鉴于通讯录对数据库操作的特点多度少写,以及部门之间的树状关系,我们选择LDAP协议来存取数据。

我们有独立的repo来帮助您完成数据库的安装与初始化。请移步这里

组织架构管理

Dolores 初始版本使用Golang实现,大家既可以下载各个平台的可执行包,也可以安装Go语言的开发环境自己编译。

我们有独立的repo来帮助您,运行后端服务。请移步这里

客户端

我们现在有提供一个iOS版的Demo。请移步这里

Done

如果您顺利的完成以上三步,访问 http://localhost:3280 (端口号根据自己的配置,可能会有差异),使用 username: admin, password: dolores 登陆后端管理页面,添加权限规则,添加角色,添加员工、部门,然后使用iOS客户端登陆,就可以愉快的开始聊天啦~
 
负载均衡

(TODO)

多机容灾

(TODO)

LICENSE Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
更多信息请前往github项目主页

 
这里我对每个repo做一个简单的介绍


Dolores: 项目简介, 整个项目的架构, 数据库设计等等 你想了解的一切都可以在这里看到
dolores-ios: iOS版demo,可以聊天查看组织架构
dolores-android: 哈哈 还没有,当然我们欢迎各路安卓大牛贡献安卓版demo
organization: 组织架构的创建管理、更新、审计等等核心的东西都在这里啦
dolores-server: 为客户端提供restfull api 与环信服务器集成
dolores-admin: 后台管理网站,用于管理部门员工。一个基于React的webapp还很基础,欢迎各位大牛pr.
dolores-ldap-init: 后台数据库的初始化工具,详情可以查看readme
easemob-resty:对环信rest api的封装,让调用环信api更简单
dolores-avatar:生成类似钉钉那样的默认头像


最后再说一点整个服务端是用go来写的,作者也是golang的初学者,如果代码哪里写的有问题或者架构有问题欢迎大家指正THE CALM BEFORE THE STORM.
暴风雨前的宁静
ONE MORE THING 最后附上Dolores项目LOGO
当时作者正在二刷 《西部世界》这部剧,所以选择了女主的名字dolores作为整个项目的名字,而这个logo则寓意剧中的host。 查看全部
  

  前阵子钉钉在微信楼下刷了一波#创业很苦,坚持很酷#的广告,浓浓的“丧”文化风格文案受到了各界褒贬不一的评价,也引起了大家对OA办公系统的关注。
   对企业而言,初选OA办公系统是为了满足需求,解决当下问题,由于OA办公系统的在公司运作流程中扮演的重要性,安全与隐私等问题急需未雨绸缪,“可定制”、“可私有化部署”的OA办公系统成为了更多企业的首选。
公司想自己开发一套IM系统应该从哪里开始呢? 企业通讯录怎么保持同步呢? 企业通讯录的权限管理应该怎么做?

   三个关于OA办公系统的究极问题,从开源的OA办公项目-Dolores(朵拉)诞生迎刃而解了。Dolores项目遵循Apache Licence 2.0 开源协议,可以直接拿来用,也可以修改代码来满足需要并作为开源或商业产品发布/销售。
OA广告图.jpg

关于Dolores?

Dolores是一套完整的企业通信解决方案,一个完整的企业沟通工具(以下简称企业IM),支持以下几个功能:IM消息服务、组织架构管理、工作流集成。


Dolores项目源码地址:https://github.com/DoloresTeam​ 
技术讨论群:641256202(QQ群)

整个解决方案都包括了什么?
  • 企业通讯录的管理:部门/员工的增删改查
  • 通讯录全量更新:全量/增量更新 
  • 企业通讯录权限管理:基于RBAC权限管理模型
  • 企业即时通讯IM:企业通信对IM这块的可靠性要求高,选择了目前比较成熟的IM云服务厂商-环信

 
 
组织架构

企业通讯录可以说是企业沟通中最重的业务之一,能够提供员工各种服务的认证,获取员工的联系方式等。
 
组织架构-Server

服务端主要包括以下功能:
  1. 支持管理人员(例如HR)对部门和员工进行增删改查
  2. 支持部门和员工自定义排序,自定义元信息存储
  3. 权限管理
  4. 员工通讯录视图 (员工根据自己的权限生成通讯录)
  5. 通讯录增量更新 (鉴于移动端特殊的网络环境和设备,通讯录应该支持差量更新)
  6. 集成 IM 用户系统


在这里我们主要讨论以下两个问题:
 
权限管理

  随着企业逐渐的发展,团队壮大为了更有效的沟通,以及保护公司内部的一些商业信息不被泄漏,我们应该为通讯录添加权限管理。

基于Role-based access control(RBAC)的权限管理模型

为了介绍此权限管理模型,我们先解释一下基本概念
  • 角色:通常是指企业中某一个工作岗位,这个岗位具有特定的权利和职责。被赋予此角色的员工,将获得这种权利与职责
  • 权限:被赋予访问实体的权利。在本项目中是指访问部门和访问某一个或者某一类员工的权利
  • 用户-角色分配(User-Role Assignment URA):为某个用户指定一个或者多个角色,此员工将获得这些角色所具有权利的集合
  • 角色-权限分配(Role-Permission Assignment RPA):将权限分配给角色,一个角色可以包含多个权利。在本项目中是指多个访问部门和访问员工的权限


在用户和权限之间引入角色中介,将用户与权限的直接关系弱化为间接关系。
|ˉˉˉ|           |ˉˉ ˉ|          |ˉˉˉˉ ˉˉ|  
| User |---URA---> | Role |<---RPA---| Permission |
|______| |_______| |_____________|

    以角色为中介,首先创建访问每个部门和员工的访问权限,然后创建不同的角色,根据这些角色的职责不同分配不同的权限,建立角色-权限的关系以后,不同的角色将会有不同的权限。根据员工不同的岗位,将对应的角色分配给他们,建立用户-角色关系,这就是RBAC的主要思想。

一个员工可以用户多个角色,一个角色可以用于多个访问权限。RBAC 极大的简化了员工的授权管理。

   由于企业的部门和员工数量很多,在创建权限时管理员不可能去设置每一个权限可以访问的每一个部门和每一个员工。所以本项目将功能和指责类似的部门和员工看作是同一类型,在创建部门和员工的时候为每一个部门和员工分配固有属性type,管理员在设置权限规则的时候只需要指定可访问的部门类型和员工即可。

增量更新

   鉴于移动终端计算资源有限,如网络,存储,电量等,所以通讯录的更新技术应该保证尽量少的资源。另外由于通讯录的特殊性,通讯录的变化需要能实时通知到受影响的在线员工。

基于版本号与变更日志的增量更新模型

   客户端第一次登陆系统以后,我们根据当前登录角色生成对应的通讯录视图,并以当前时间戳作为版本号,返回给客户端。客户端后续通过此版本号增量更新通讯录。

版本号

   版本号有两种:一是客户端当前通讯录版本 c-version, 二是服务端通讯录每一次变化时的版本号s-version

变更日志

   在管理员修改权限规则,或者修改某个岗位的访问规则时会影响大面积员工的通讯录视图,此时如果用增量更新会导致服务器流量异常,因此在这2中情况会清空原来的变更日志并且要求客户端进行一次全量更新。

   如果管理员新增了员工,服务端会根据被修改的员工或者部门type, 反推出所有受影响的员工,然后生成一条变更日志, 例如:
{
"content" : [
{
"cn" : "Lucy.Liu",
"id" : "b4vlfg91scgi1dcju8v0",
"title" : "市场运营负责人",
"email" : [
"lucy.liu@dolores.store"
],
"priority" : "101111",
"name" : "刘小飞",
"telephoneNumber" : "18888888888"
}
],
"createTimestamp" : "20170614063303Z",
"category" : "member",
"action" : "add"
}

客户端在请求增量更新的时候,通过当前登陆ID与版本号,可查找出所有与自己相关的变更日志,然后在客户端数据库中应用这些变更,即可完成同步。

组织架构-Client

   由于现在员工办公设备的多样性,客户端要根据自己公司的情况,覆盖的足够完整,常见的平台有 iOS Android windowsmac linux , 对于后三个平台可以用 Web APP 来覆盖,iOS&Android 用原生的app来提升用户体验。

客户端App主要包括以下功能:
  1. 会话列表
  2. 优秀的聊天界面,历史记录
  3. 组织机构全量/增量更新
  4. 员工个人资料展示


客户端数据库设计

IM数据库设计
 
当前版本使用环信SDK
 
组织架构数据库设计

表设计

客户端组织架构较服务端简单,不关联用户Role,客户端本地存储Staff(员工)和Department(部门)信息:
  • 一个部门可以包含相关子部门和部门员工。该部门员工和部门在视图上处于同级关系。
  • 员工隶属于部门,同一员工可以存在于多个部门。
  • 员工角色用title来表示。


用户在登录客户端成功后,会根据该用户信息创建用户对应的数据库文件,用户表(User)保存用户相关信息,关联该用户staff信息。

客户端组织架构同服务端逻辑。

工作流集成

(TODO)
 
如何使用Dolores

本项目现在已经完成了第一个测试版本,本小节将指导您如何安装使用。

后端数据库

鉴于通讯录对数据库操作的特点多度少写,以及部门之间的树状关系,我们选择LDAP协议来存取数据。

我们有独立的repo来帮助您完成数据库的安装与初始化。请移步这里

组织架构管理

Dolores 初始版本使用Golang实现,大家既可以下载各个平台的可执行包,也可以安装Go语言的开发环境自己编译。

我们有独立的repo来帮助您,运行后端服务。请移步这里

客户端

我们现在有提供一个iOS版的Demo。请移步这里

Done

如果您顺利的完成以上三步,访问 http://localhost:3280 (端口号根据自己的配置,可能会有差异),使用 username: admin, password: dolores 登陆后端管理页面,添加权限规则,添加角色,添加员工、部门,然后使用iOS客户端登陆,就可以愉快的开始聊天啦~
 
负载均衡

(TODO)

多机容灾

(TODO)

LICENSE
 Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/

更多信息请前往github项目主页

 
这里我对每个repo做一个简单的介绍


Dolores: 项目简介, 整个项目的架构, 数据库设计等等 你想了解的一切都可以在这里看到
dolores-ios: iOS版demo,可以聊天查看组织架构
dolores-android: 哈哈 还没有,当然我们欢迎各路安卓大牛贡献安卓版demo
organization: 组织架构的创建管理、更新、审计等等核心的东西都在这里啦
dolores-server: 为客户端提供restfull api 与环信服务器集成
dolores-admin: 后台管理网站,用于管理部门员工。一个基于React的webapp还很基础,欢迎各位大牛pr.
dolores-ldap-init: 后台数据库的初始化工具,详情可以查看readme
easemob-resty:对环信rest api的封装,让调用环信api更简单
dolores-avatar:生成类似钉钉那样的默认头像


最后再说一点整个服务端是用go来写的,作者也是golang的初学者,如果代码哪里写的有问题或者架构有问题欢迎大家指正
THE CALM BEFORE THE STORM.
暴风雨前的宁静

ONE MORE THING 最后附上Dolores项目LOGO
当时作者正在二刷 《西部世界》这部剧,所以选择了女主的名字dolores作为整个项目的名字,而这个logo则寓意剧中的host。
687474703a2f2f6f7131696e636b76692e626b742e636c6f7564646e2e636f6d2f646f6c6f726573313032342e706e67.png
4
评论

【新手快速入门】集成环信常见问题+解决方案汇总 常见问题

dujiepeng 发表了文章 • 4070 次浏览 • 2017-05-22 15:51 • 来自相关话题

   这里整理了集成环信的常见问题和一些功能的实现思路,希望能帮助到大家。感谢热心的开发者贡献,大家在观看过程中有不明白的地方欢迎直接跟帖咨询。
 
ios篇
APNs证书创建和上传到环信后台头像昵称的简述和处理方案音视频离线推送Demo实现环信服务器聊天记录保存多久?离线收不到好友请求IOS中环信聊天窗口如何实现文件发送和预览的功能ios集成常见问题环信推送的一些常见问题实现名片|红包|话题聊天室等自定义cell
 
Android篇
环信3.0SDK集成小米推送教程EaseUI库中V4、v7包冲突解决方案Android EaseUI里的百度地图替换为高德地图android扩展消息(名片集成)关于会话列表的置顶聊天java.lang.UnsatisfiedLinkError: 的问题android 端 app 后台被杀死收不到消息的解决方案
昵称头像篇
android中如何显示开发者服务器上的昵称和头像 Android中显示头像(接上一篇文章看)环信(Android)设置头像和昵称的方法(最简单暴力的基于环信demo的集成)IOS中如何显示开发者服务器上的昵称和头像【环信公开课第12期视频回放】-所有关于环信IM昵称头像的问题听这课就够了
 
直播篇
一言不合你就搞个直播APP
 
客服集成
IM-SDK和客服SDK并存开发指南—Android篇IM-SDK和客服SDK并存开发指南—iOS篇
 
开源项目
Android简版demoios简版demo凡信2.0:超仿微信的开源项目 凡信3.0:携直播和红包而来高仿微信:Github 3,515 Star方圆十里:环信编程大赛冠军项目泛聊:定一个小目标写一个QQSlack聊天机器人:一天时间做一个聊天机器人TV视频通话:在电视上视频通话视频通话:Android手机视频通话酷信:ios高仿微信公众号助手:与订阅用户聊天沟通
 
持续更新ing...小伙伴们还有什么想知道欢迎跟帖提出。
  查看全部
   这里整理了集成环信的常见问题和一些功能的实现思路,希望能帮助到大家。感谢热心的开发者贡献,大家在观看过程中有不明白的地方欢迎直接跟帖咨询。
 
ios篇

 
Android篇

昵称头像篇

 
直播篇
  1. 一言不合你就搞个直播APP

 
客服集成
  1. IM-SDK和客服SDK并存开发指南—Android篇
  2. IM-SDK和客服SDK并存开发指南—iOS篇

 
开源项目

 
持续更新ing...小伙伴们还有什么想知道欢迎跟帖提出。
 
7
评论

【视频教程+源码】基于环信IM做一个仿微信APP-更新ing 郭永峰 高仿微信 仿微信 环信 XMPP

郭永峰 发表了文章 • 7836 次浏览 • 2017-05-16 15:29 • 来自相关话题

我只是一个普通人,做人要谦虚。
我不是大神,我也不是很厉害的。
天外有天,人外有人。
老师引入门,修行靠个人。
希望能帮助大家,谢谢。
    大家好,我是郭永峰(峰哥) | 一个普通大学计算机系毕业的大学生,曾就职于澳门遊澳集团有限公司,负责大型银联支付业务系统、跨国际短信业务系统(基于电信的SGIP)以及集团内部通讯系统 (负责android和openfire后台二次开发)的主要开发任务,担任项目负责人。13年就职于广州拓谷科技有限公司负责“酷蛙”车联网产品研发及汽车销售产品研发。14来年到17年2月份,就职于国内知名教育机构,负责教学研发及授课的工作。
 
本人现状况:
  
   在家录制教学视频(无收入),搞工作室,组建团队成立公司,如果大家觉得分享内容很喜欢,可以给我打点赏支持本人的工作室,二维码在文末,就不影响阅读了。

 
郭永峰IT教育工作室于2017年4月12日成立!
 
成立原因:

希望把近10年来从事IT互联网的知识分享给大家,包括Linux,WindowServer,Java,PHP,Android,iOS,H5等等等。
 

进入正题,本套课程基于环信IM教大家如何做一个类似微信的APP,只用于技术交流,请勿用于任何商业用途。
4月12号成立工作室,现在18号,过了一个星期一个星期录了5天的环信教程视频,我将放在网盘免费分享环信的教程视频主要是针对有开发经验者教程主要是使用环信来模仿微信来做一个即时通讯的案例课程主要是先讲socket基础 -> 环信 ->自定义协议希望这些教程视频能帮助大家,对即时通讯、socket和自定义协议有个较深入的了解同时能希望大家在面试时,在即时通讯这块不在陌生
  持续更新

第一阶段:即时通讯的了解和微信APP开发前的准备!

【视频教程+源码】基于环信IM做一个仿微信APP-01.即时通讯简介(了解)

【视频教程+源码】基于环信IM做一个仿微信APP-02.XMPP简介(了解)

【视频教程+源码】基于环信IM做一个仿微信APP-03.XMPP实现即时通信的准备工作(了解)

【视频教程+源码】基于环信IM做一个仿微信APP-04.环信简介(了解) 

【视频教程+源码】基于环信IM做一个仿微信APP-05.集成环信的前提准备(掌握)
 
【视频教程+源码】基于环信IM做一个仿微信APP-06.环信SDK的版本的区别(掌握)

【视频教程+源码】基于环信IM做一个仿微信APP-07.微信-项目创建及代码目录结构规范(MVC)

【视频教程+源码】基于环信IM做一个仿微信APP-08.微信-集成环信SDK

【视频教程+源码】基于环信IM做一个仿微信APP-09.微信-登录界面排版

【视频教程+源码】基于环信IM做一个仿微信APP-10.微信-主界面搭建

【视频教程+源码】基于环信IM做一个仿微信APP-11.微信-注册功能

【视频教程+源码】基于环信IM做一个仿微信APP- 12.微信-登录功能

【视频教程+源码】基于环信IM做一个仿微信APP- 13.微信-自动登录

【视频教程+源码】基于环信IM做一个仿微信APP- 14.微信-主动退出
 
【视频教程+源码】基于环信IM做一个仿微信APP-15.微信-在其它设备登录
 
整个项目源码,git地址https://github.com/mayaole/fWeiXin

 微信打赏






支付宝打赏






谢谢大家的支持,个人微信号清扫描下面张图






 
郭永峰IT交流QQ群请加:596441895 查看全部
我只是一个普通人,做人要谦虚。
我不是大神,我也不是很厉害的。
天外有天,人外有人。
老师引入门,修行靠个人。
希望能帮助大家,谢谢。

    大家好,我是郭永峰(峰哥) | 一个普通大学计算机系毕业的大学生,曾就职于澳门遊澳集团有限公司,负责大型银联支付业务系统、跨国际短信业务系统(基于电信的SGIP)以及集团内部通讯系统 (负责android和openfire后台二次开发)的主要开发任务,担任项目负责人。13年就职于广州拓谷科技有限公司负责“酷蛙”车联网产品研发及汽车销售产品研发。14来年到17年2月份,就职于国内知名教育机构,负责教学研发及授课的工作。
 
本人现状况:
  
   在家录制教学视频(无收入),搞工作室,组建团队成立公司,如果大家觉得分享内容很喜欢,可以给我打点赏支持本人的工作室,二维码在文末,就不影响阅读了。

 
郭永峰IT教育工作室于2017年4月12日成立!
 
成立原因:

希望把近10年来从事IT互联网的知识分享给大家,包括Linux,WindowServer,Java,PHP,Android,iOS,H5等等等。
 

进入正题,本套课程基于环信IM教大家如何做一个类似微信的APP,只用于技术交流,请勿用于任何商业用途。
  1. 4月12号成立工作室,现在18号,过了一个星期
  2. 一个星期录了5天的环信教程视频,我将放在网盘免费分享
  3. 环信的教程视频主要是针对有开发经验者
  4. 教程主要是使用环信来模仿微信来做一个即时通讯的案例
  5. 课程主要是先讲socket基础 -> 环信 ->自定义协议
  6. 希望这些教程视频能帮助大家,对即时通讯、socket和自定义协议有个较深入的了解
  7. 同时能希望大家在面试时,在即时通讯这块不在陌生

  持续更新

第一阶段:即时通讯的了解和微信APP开发前的准备!

【视频教程+源码】基于环信IM做一个仿微信APP-01.即时通讯简介(了解)

【视频教程+源码】基于环信IM做一个仿微信APP-02.XMPP简介(了解)

【视频教程+源码】基于环信IM做一个仿微信APP-03.XMPP实现即时通信的准备工作(了解)

【视频教程+源码】基于环信IM做一个仿微信APP-04.环信简介(了解) 

【视频教程+源码】基于环信IM做一个仿微信APP-05.集成环信的前提准备(掌握)
 
【视频教程+源码】基于环信IM做一个仿微信APP-06.环信SDK的版本的区别(掌握)

【视频教程+源码】基于环信IM做一个仿微信APP-07.微信-项目创建及代码目录结构规范(MVC)

【视频教程+源码】基于环信IM做一个仿微信APP-08.微信-集成环信SDK

【视频教程+源码】基于环信IM做一个仿微信APP-09.微信-登录界面排版

【视频教程+源码】基于环信IM做一个仿微信APP-10.微信-主界面搭建

【视频教程+源码】基于环信IM做一个仿微信APP-11.微信-注册功能

【视频教程+源码】基于环信IM做一个仿微信APP- 12.微信-登录功能

【视频教程+源码】基于环信IM做一个仿微信APP- 13.微信-自动登录

【视频教程+源码】基于环信IM做一个仿微信APP- 14.微信-主动退出
 
【视频教程+源码】基于环信IM做一个仿微信APP-15.微信-在其它设备登录
 
整个项目源码,git地址https://github.com/mayaole/fWeiXin

 微信打赏

微信.png


支付宝打赏

支付宝.png


谢谢大家的支持,个人微信号清扫描下面张图

个人.png


 
郭永峰IT交流QQ群请加:596441895
2
评论

环信进阶篇-实现名片|红包|话题聊天室等自定义cell cell自定义 cell iOS环信 iOS iOS 新手

不死小强 发表了文章 • 237 次浏览 • 2017-06-20 09:50 • 来自相关话题

    伴随着即时通讯成为了越来越多APP的刚需,匿名社交、阅后即焚、红包等新玩法层出不穷,基本的聊天方式越来越难以满足变态的需求。环信提供的自定义扩展属性功能非常的强大,能够帮助我们在cell中的各种需求做定制处理。这是分享一个之前做过的方法及实现,大家可以借鉴处理的过程及思路,如有不妥之处,请大家及时留言告知,谢谢。 
今天就给大家介绍下怎么对cell中的各种需求的定制处理


 类型一:在现有会话cell上修改UI效果

类似于上面给出的截图,我们有时候需要对环信官方给出的cell进行些许的调整。例如:项目中加入了不同于普通群聊或者聊天室的功能需求
点击话题聊天,大家加入聊天室,这里发出的各种就是不同于普通聊天,普通的聊天只需展示文字、地址、图片等等,但是这里的需求是得加上时间、私聊按钮,没砍需求之前是还有点赞和取消赞的按钮。
我们在普通聊天的基础上新建几个cell,文字、语音、图片、地图等等,不能和原有的普通cell混合起来,因为需求有普通聊天。

直接把普通聊天cell中的代码拷贝过来,再在此基础上进行cell的UI自定义处理,就拿文字聊天时的处理情况为例:

1、拷贝复制原有普通聊天cell内的代码

2、把需要的新增的UI控件初始化

3、适配各类控件

4、传值及赋值

5、新增按钮点击和本身cell的点击效果处理(别和cell上的点击效果混到一起)

6、耐心调整cell上UI效果

以上基本就是简单的自定义cell步骤了,有基础的小伙伴看下步骤应该就有思路了

类型二:类似于红包和名片Cell的UI效果


通常在我们项目中,并不只有文字、图片等等这些简单的聊天内容,有时候我们需要把自己的信息作为一张名片发给对方、发个红包给好朋友、发一个项目中的一个模块介绍给对方等等功能要求。
我们就拿雷哥的这张假名片为例:/*!
@method
@brief 新增一个新的功能按钮
@discussion
@param image 按钮图片
@param highLightedImage 高亮图片
@param title 按钮标题
@result
*/
- (void)insertItemWithImage:(UIImage*)image
highlightedImage:(UIImage*)highLightedImage
title:(NSString*)title;
/*!
@method
@brief 修改功能按钮图片
@discussion
@param image 按钮图片
@param highLightedImage 高亮图片
@param title 按钮标题
@param index 按钮索引
@result
*/
- (void)updateItemWithImage:(UIImage*)image
highlightedImage:(UIImage*)highLightedImage
title:(NSString*)title
atIndex:(NSInteger)index;
/*!
@method
@brief 根据索引删除功能按钮
@discussion
@param index 按钮索引
@result
*/
- (void)removeItematIndex:(NSInteger)index;*  消息体类型
typedef enum{
EMMessageBodyTypeText  = 1,    /*! \~chinese 文本类型 \~english Text */
EMMessageBodyTypeImage,        /*! \~chinese 图片类型 \~english Image */
EMMessageBodyTypeVideo,        /*! \~chinese 视频类型 \~english Video */
EMMessageBodyTypeLocation,      /*! \~chinese 位置类型 \~english Location */
EMMessageBodyTypeVoice,        /*! \~chinese 语音类型 \~english Voice */
EMMessageBodyTypeFile,          /*! \~chinese 文件类型 \~english File */
EMMessageBodyTypeCmd,          /*! \~chinese 命令类型 \~english Command */
}EMMessageBodyType;
如果环信把这个开放出来,或许我们就更加简单了我们只需自己修改成自己对应的类型即可。但是这个目前就想想,所以我们可以在以上类型中找一个出来,在它的基础上做些文章,变成我们想要的类型。

红包和名片最像什么。。。。对,不就和图片差不多嘛,不过小伙伴也不要以为只能拿图片来做文章,其他的我们都可以拿来用,这里就拿文字类型来作为例子(原理都一样)。




名片类型
这里我们只简要介绍怎么根据会话类型来显示名片,具体传值等怎么做,有基础的小伙伴应该都懂,不懂的小伙伴见文章底部。
 
我们需要在发送名片时,在拓展消息里面存一个名片的字段,这个字段可以被用来判断是名片、红包等等。名片、红包等等中内容,同样也存在拓展属性中(这里不做过多介绍)我们在展示自己的消息和接收到对方的消息时,在文字类型的基础上再进一步判断是什么类型,加载对应类型的视图,如果是红包就加载红包的view,如果是名片就展示名片view......





加载不同类型的cell
好了,以上就是我们所要介绍的两种不同类型cell的处理办法。


以下是补充自定义cell时遇到的各种情况及处理:
1、cell上语音、图片等原始点击和新增按钮点击冲突处理:

注释掉原有的点击方法,把原有的点击方法放到具体的控件上去,避免cell上多个控件点击的冲突

重点:记得把气泡上的点击权限打开_backgroundImageView.userInteractionEnabled = YES;





解决点击冲突
2、cell上语音气泡长度的改变,避免过段影响布局

我们只需把原有语音上的语音长度Label距语音图片控件调大一点距离就能自动把语音类气泡拉长。(其他类型一样处理原理)




语音气泡拉长
3、因新增控件导致在原有cell上高度的变化处理
/*! @method @brief 根据消息的内容,获取当前cell的高度 @discussion @param model 消息对象model @result 返回cell高度 */
+ (CGFloat)cellHeightWithModel:(id)model在原cell高度处理的情况下,根据各种类型的判断进行cell高度的自适应。




cell高度处理
4、文字类型气泡长度的处理

我暂时的处理方法:判断输入的文字长度,加入文字长度小于10,我会在后面自动补全5个空格,被动撑长气泡的长度。

假如小伙伴们有更好的建议也可以留言,谢谢! 查看全部
    伴随着即时通讯成为了越来越多APP的刚需,匿名社交、阅后即焚、红包等新玩法层出不穷,基本的聊天方式越来越难以满足变态的需求。环信提供的自定义扩展属性功能非常的强大,能够帮助我们在cell中的各种需求做定制处理。这是分享一个之前做过的方法及实现,大家可以借鉴处理的过程及思路,如有不妥之处,请大家及时留言告知,谢谢。 
今天就给大家介绍下怎么对cell中的各种需求的定制处理


 类型一:在现有会话cell上修改UI效果

类似于上面给出的截图,我们有时候需要对环信官方给出的cell进行些许的调整。例如:项目中加入了不同于普通群聊或者聊天室的功能需求
点击话题聊天,大家加入聊天室,这里发出的各种就是不同于普通聊天,普通的聊天只需展示文字、地址、图片等等,但是这里的需求是得加上时间、私聊按钮,没砍需求之前是还有点赞和取消赞的按钮。
我们在普通聊天的基础上新建几个cell,文字、语音、图片、地图等等,不能和原有的普通cell混合起来,因为需求有普通聊天。

直接把普通聊天cell中的代码拷贝过来,再在此基础上进行cell的UI自定义处理,就拿文字聊天时的处理情况为例:


1、拷贝复制原有普通聊天cell内的代码

2、把需要的新增的UI控件初始化

3、适配各类控件

4、传值及赋值

5、新增按钮点击和本身cell的点击效果处理(别和cell上的点击效果混到一起)

6、耐心调整cell上UI效果

以上基本就是简单的自定义cell步骤了,有基础的小伙伴看下步骤应该就有思路了


类型二:类似于红包和名片Cell的UI效果


通常在我们项目中,并不只有文字、图片等等这些简单的聊天内容,有时候我们需要把自己的信息作为一张名片发给对方、发个红包给好朋友、发一个项目中的一个模块介绍给对方等等功能要求。
我们就拿雷哥的这张假名片为例:
/*!
@method
@brief 新增一个新的功能按钮
@discussion
@param image 按钮图片
@param highLightedImage 高亮图片
@param title 按钮标题
@result
*/
- (void)insertItemWithImage:(UIImage*)image
highlightedImage:(UIImage*)highLightedImage
title:(NSString*)title;
/*!
@method
@brief 修改功能按钮图片
@discussion
@param image 按钮图片
@param highLightedImage 高亮图片
@param title 按钮标题
@param index 按钮索引
@result
*/
- (void)updateItemWithImage:(UIImage*)image
highlightedImage:(UIImage*)highLightedImage
title:(NSString*)title
atIndex:(NSInteger)index;
/*!
@method
@brief 根据索引删除功能按钮
@discussion
@param index 按钮索引
@result
*/
- (void)removeItematIndex:(NSInteger)index;
*  消息体类型
typedef enum{
EMMessageBodyTypeText  = 1,    /*! \~chinese 文本类型 \~english Text */
EMMessageBodyTypeImage,        /*! \~chinese 图片类型 \~english Image */
EMMessageBodyTypeVideo,        /*! \~chinese 视频类型 \~english Video */
EMMessageBodyTypeLocation,      /*! \~chinese 位置类型 \~english Location */
EMMessageBodyTypeVoice,        /*! \~chinese 语音类型 \~english Voice */
EMMessageBodyTypeFile,          /*! \~chinese 文件类型 \~english File */
EMMessageBodyTypeCmd,          /*! \~chinese 命令类型 \~english Command */
}EMMessageBodyType;
如果环信把这个开放出来,或许我们就更加简单了我们只需自己修改成自己对应的类型即可。但是这个目前就想想,所以我们可以在以上类型中找一个出来,在它的基础上做些文章,变成我们想要的类型。

红包和名片最像什么。。。。对,不就和图片差不多嘛,不过小伙伴也不要以为只能拿图片来做文章,其他的我们都可以拿来用,这里就拿文字类型来作为例子(原理都一样)。
01.jpg

名片类型
这里我们只简要介绍怎么根据会话类型来显示名片,具体传值等怎么做,有基础的小伙伴应该都懂,不懂的小伙伴见文章底部。
 
  1. 我们需要在发送名片时,在拓展消息里面存一个名片的字段,这个字段可以被用来判断是名片、红包等等。
  2. 名片、红包等等中内容,同样也存在拓展属性中(这里不做过多介绍)
  3. 我们在展示自己的消息和接收到对方的消息时,在文字类型的基础上再进一步判断是什么类型,加载对应类型的视图,如果是红包就加载红包的view,如果是名片就展示名片view......


02.jpg

加载不同类型的cell
好了,以上就是我们所要介绍的两种不同类型cell的处理办法。


以下是补充自定义cell时遇到的各种情况及处理:
1、cell上语音、图片等原始点击和新增按钮点击冲突处理:

注释掉原有的点击方法,把原有的点击方法放到具体的控件上去,避免cell上多个控件点击的冲突

重点:记得把气泡上的点击权限打开
_backgroundImageView.userInteractionEnabled = YES;


03.jpg

解决点击冲突
2、cell上语音气泡长度的改变,避免过段影响布局

我们只需把原有语音上的语音长度Label距语音图片控件调大一点距离就能自动把语音类气泡拉长。(其他类型一样处理原理)
04.jpg

语音气泡拉长
3、因新增控件导致在原有cell上高度的变化处理
/*! @method @brief 根据消息的内容,获取当前cell的高度 @discussion @param model        消息对象model @result 返回cell高度 */
+ (CGFloat)cellHeightWithModel:(id)model
在原cell高度处理的情况下,根据各种类型的判断进行cell高度的自适应。
05.jpg

cell高度处理
4、文字类型气泡长度的处理

我暂时的处理方法:判断输入的文字长度,加入文字长度小于10,我会在后面自动补全5个空格,被动撑长气泡的长度。

假如小伙伴们有更好的建议也可以留言,谢谢!
0
评论

环信推送的一些常见问题 iOS

木云落 发表了文章 • 519 次浏览 • 2017-06-07 13:15 • 来自相关话题

原文地址 :  http://blog.csdn.net/jyt199011302/article/details/72829520

参考资料
APNS证书创建和上传到环信后台 : http://www.imgeek.org/article/825308748 
APNS离线推送文档 : http://docs.easemob.com/im/300iosclientintegration/75apns
离线推送的集成代码这里就不一一介绍了, 上面文档中写的 很明白了, 接下来说下比较容易误会的几点
一. 离线推送
如果app集成时添加- (void)applicationDidEnterBackground:(UIApplication *)application {
[[EMClient sharedClient] applicationDidEnterBackground:application];
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
[[EMClient sharedClient] applicationWillEnterForeground:application];
}
App后台静默后,能够保持长连接3分钟左右。超过3分钟,长连接会断开,当前登录的账号,在服务端被认为离线。消息会存入离线消息空间,之后接收的消息会在再次登录后,连接上服务器,然后通过长连接把消息取走,投递给此用户。如果app配置了推送证书,上传了推送证书并且集成了推送功能,服务器会给接收方发一个APNs推送,则会对离线消息进行APNs推送提示消息内容,通知接收方有一条新消息。
如果想自定义推送的alert,可以在发消息的时候,在消息扩展中添加相应的字段。文档见:APNS内容解析

离线推送 : 当app被杀死或者进入后台三分钟之后
消息回调 : app在前台及app进入后台三分钟之内

注意 : 环信支持推送消息,只是目前还不能根据标签推送给特定用户组,也暂不支持推送模板。CMD消息没有推送,好友请求也没有推送

收不到离线推送时可以从下面几个方面找下原因

1.测试apns推送的时候,接受消息方的app是杀掉状态吗,或者进入后台三分钟以后
2.看看你环信后台上传的证书名称与工程中初始化SDK那里填的证书名 是不是相同的
3.配置证书时候填的id与你工程中的bundle id 是否相同
4.devicetoken有没有传给环信SDK。即查看管理后台中,对应 IM 账户下是否有您刚刚写的证书名。(如果没有,请检查您是否得到了 deviceToken)
5.确认Xcode环境是否配置正确 ,Build Settings---signing,看Debug对应的是不是开发的,Release对应的是不是生产的
6.在确认xcode运行环境是否正确 (Product-->Scheme-->Edit Scheme, 开发证书选Debug,生产证书选Release)
7.证书制作上传过程是否有问题,配置证书的时候是否设置了密码,正确的步骤可以参考:http://www.imgeek.org/article/825308748。另外可以用推送工具进行验证。
8.如果以上都没有问题,可以尝试重新制作上传一下推送证书。
对照这些检查一下,基本上就是这些原因
如果上面几点都符合的话,看下重新登录之后是否可以收到之前收不到推送消息
可以的,话提供一下AppKey,证书名 (查下证书是否被封)以及收不到的推送消息的消息id及发送方和接收方log
log导出请看这篇文章: http://www.imgeek.org/article/825308785 然后转成txt格式上传到工单上,同时注明上述配置都正确 , 环信这边来查下消息推送记录

注意 :后台没有证书名 是指用户列表后面没有显示证书名。这个证书名是SDK初始化的时候传的字符串,用户登录之后会进行绑定。
如果用户没有绑定证书名的话,肯定收不到推送的。这个证书名是用户登录之后绑定的,要确认下初始化SDK的时候有没有传。 options.apnsCertName = apnsCertName;

==============常见问题==============

Q : iOS apns离线推送证书apns的离线推送可以和友盟(极光)推送共用一个证书吗?
A : 环信的推送只要和后台上传的证书对应就可以实现,其他的不关心。
首先苹果推送证书的生成都是统一的方式,这个不区分是极光(友盟)推送证书还是个推证书等等。使用的推送证书只要按照正确的苹果推送证书生成流程创建,都可以使用。
环信添加推送证书可以看http://www.imgeek.org/article/825308748 不是要求必须重新生成推送证书 
Q : 好友申请通知的离线推送?
A : 我们的好友体系,添加好友的申请不支持离线推送。
如果你们是使用App本身的好友体系,可以在app的添加好友业务上向被添加的好友发送文本消息,在EMMessage的ext中设置自定义字段,来区分此条文本消息是否用于好友申请提示,由此来判断处理UI的显示。

Q : iOS的杀死进程远程推送和服务端有关么
A : 如果客户端把远程通知给关了肯定就收不到通知,我们服务器会检测客户端是否有deviceToken,有的话才会把消息发送到deviceToken对应的设备上

Q : 每个项目创建了一个开发的推送证书一个生产的推送证书。这俩证书什么时候要做切换?
A : 在App上传AppStore前需要修改App内初始化SDK设置的推送证书名,EMOptions的apnsCertName。
注意,这里的值需要和在Console管理后天上传时设置的证书名一致。

Q : 绑定devicetoken的时候是否需要先登录到环信?
A : 绑定是需要登录过之后才进行的,
 - (EMError *)bindDeviceToken:(NSData *)aDeviceToken; 是把deviceToken传给SDK。调用登录,SDK会进行绑定。也可以调用 - (void)registerForRemoteNotificationsWithDeviceToken:(NSData *)aDeviceToken
completion:(void (^)(EMError *aError))aCompletionBlock;自己绑定
需要判断是否已经登录,如果已经有登录的账号,再登录会返回 已登录的错误。

Q : 离线推送在客户端怎么设置显示详情?
A : EMPushOptions *pushOptions = [[EMClient sharedClient] pushOptions];
pushOptions.displayStyle = EMPushDisplayStyleMessageSummary;
可以设置离线推送消息显示具体内容还是只显示-您收到一条消息
要设置在登录成功之后,然后要用服务器拉取一遍APNS 属性EMError *error = nil;
EMPushOptions *options = [[EMClient sharedClient] getPushOptionsFromServerWithError:&error];
然后在修改displayStyle

Q : 两个APP通信,如果只希望其中一类APP能收到推送,而另一端的APP不希望收到推送,是不是不希望收到推送的APP不配置证书就好了?
A : 两个App的推送证书都是在同一appkey下单独配置的,如果不希望收到推送,可以对此App不配置推送证书,同时在App代码中注释掉注册远程通知的相关代码。
bundle id对应的证书也可以取消push的功能,针对App不使用任何远程推送服务,包括其他第三方的,如果App还需要其他第三方的推送服务,请忽略这句话。

Q : 不配置推送证书的APP是不是只有刷新的情况下才会显示新的消息?不刷新的情况下APP是看不到新的消息?
A : 不配置推送功能的App,只有在用户登录成功后,才能通过长连接的接收消息回掉中拿到消息体。

Q : 在开发环境下收到了离线推送消息,但是在生成环境下没有收到?
A : 看一下SDK初始化时,是否设置的apnsCerName与生产环境证书上传时填写的证书名一致,还有是否为adhoc打包成ipa文件安装测试的。

Q :发送消息1,2,3,4,5 对方收到推送 2 1 5 顺序不对而且丢失, app角标也不对
A : 1.首先,苹果不保证所有远程推送的到达率。这个可以看苹果官方文档。
Because the delivery of remote notifications is not guaranteed, never include sensitive data or data that can be retrieved by other means in your payload.
2.我们只保证,把离线消息执行远程推送,发给苹果服务器。苹果服务器是否能够百分之百把所有推送送达到指定移动端,这个根据苹果的策略,当 APNs 向你发送了多条推送,你的设备在 APNs 那里下线了,这时 APNs 到你的手机的链路上有多条任务堆积,APNs 的处理方式是,只保留最后一条消息推送给你,然后告知你推送数。那么其他消息会被APNs丢弃。
3.我们保证的是离线消息,当用户重新登录时,可以都接收到。
4. 如果需要找后台查询离线消息(前提接收方已绑定deviceToken)是否成功,需要提供离线消息的messageId,接收方环信id
5.我们的推送角标,是接收方的在服务端的离线消息数。
https://developer.apple.com/li ... 8-SW1
这是官方文档





Q11 :ios是怎么判断离线了 然后发推送的啊 有时候把应用杀掉后 半天收不到推送
A : rest可以查用户的状态,推送前提是此用户有devicetoken已经绑定成功
如果账号所有配置都没问题,杀掉后,其他人发的消息,过几秒就能看到推送
Q12 : Q11不管用会是什么原因呢
A : 配置,还有账号在我们这绑的deviceToken

Q : 多个app共用一个appkey 推送证书怎么配置呢
A : 后台可以上传多套推送证书。

Q :程序关闭后推送了一个消息,点击后怎样获取到环信传过来的数据
A : 需要用户点击横幅后,重新启动App,这时从- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions中,获取字典launchOptions,UIApplicationLaunchOptionsRemoteNotificationKey这个key下的数据,就是aps的字典数据

二 . 消息回调
app的长连接存在的时候,环信服务器检测您为在线状态,是不会给app推送消息的。app端在线的情况下,消息会通过长连接直接收取(didreceivemessage),收到消息,SDK会通过回调通知给上层。app通过收消息的回调拿到消息对象,然后解析并展示UI。
目前我们不支持App切后台后,可以一直执行。
我们SDK在切后台后,实现[[EMClient sharedClient] applicationDidEnterBackground:application];,会保持,直到被系统释放iOS目前其他方式应该都无法去实现一直保持App的活跃状态了。

==============常见问题==============
Q : 本地推送声音设置在哪 ?一条消息推送两声
A : 推送声音设置的要自己实现,具体可参考demo里的ChatDemoHelper类和MainViewController类里的
- (void)showNotificationWithMessage:(EMMessage *)message方法,该方法中有发送本地推送做的一系列操作,本地通知怎么做的,本地通知触发几次,一条消息推送几声,一下接收到多条消息响几声,都需要用户自己实现.

Q : 视频通话,推送怎么实现?
iOS 3.2.3之后,如果在实时通话接收方不在线时,发送提醒。
A : 1.在发起实时音视频通话前,需要设置EMCallOptions对象属性isSendPushIfOffline为YES;
2.遵守协议EMCallBuilderDelegate,实现其中的- (void)callRemoteOffline:(NSString *)aRemoteName 委托方法。
3.在第2步的方法中向 aRemoteName用户发送单聊消息。
如果被叫方已注册远程通知且绑定deviceToken,会收到对应消息的APNs推送,点击横幅来唤醒App。
上面是接收方离线的情况。如果接收方长连接还未断开,只是App切到后台,需要在回调- (void)callDidReceive:(EMCallSession *)aSession中判断当前App是否在后台,如果是弹出本地通知。

Q : EMCallOptions *options = [[EMClient sharedClient].callManager getCallOptions]; //当对方不在线时,是否给对方发送离线消息和推送,并等待对方回应 options.isSendPushIfOffline = YES; [[EMClient sharedClient].callManager setCallOptions:options];isSendPushIfOffline设置为YES后,A用户呼叫B用户,B用户处于离线状态,但B用户没有收到推送。
A : 1. 先确接收方杀掉App后,文本消息是否能收到APNs推送。
2.在1点确认App杀掉可以收到推送前提下,确认实时音视频发送方代码执行顺序如下:(1) EMCallOptions *callOptions = [[EMClient sharedClient].callManager getCallOptions];
callOptions.isSendPushIfOffline = YES;
callOptions.offlineMessageText = @"提示文本";//可选
[[EMClient sharedClient].callManager setCallOptions:callOptions];
(2) callManager调用- (void)startVideoCall:(NSString *)aUsername
completion:(void (^)(EMCallSession *aCallSession, EMError *aError))aCompletionBlock;
或者- (void)startVoiceCall:(NSString *)aUsername
completion:(void (^)(EMCallSession *aCallSession, EMError *aError))aCompletionBlock;

Q : 推送的提示音可不可以自定义啊。
A: 推送的提示音目前不支持自定义,本地通知的你们以自己去设置。
 
Q : app压后台,立刻收到聊天推送来的信息,点击通知栏信息,捕获不到唤起程序事件
A : App切后台后,长连接为断开前,当前弹出的横幅是本地通知,那么此时唤醒时间是本地通知的回调- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification;
iOS10后- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)())completionHandler;

Q : 环信在离线状态下能收消息,但是程序运行状态按home按键进入后台的时候无法接受消息,怎么处理
A : App切入后台一段时间内,长连接还未断开,这时候接收消息都是通过SDK的接收消息回调(EMChatManagerDelegate)来收消息,不会执行APNs推送。
如果是需要弹出横幅提醒,需要在接收消息的回调方法中,判断[[UIApplication sharedApplication] applicationState]为UIApplicationStateBackground,然后实现本地通知。可以参考demo中的处理(ChatDemoHelper的- (void)didReceiveMessages:(NSArray *)aMessages)
[attach]7500[/attach][size=13]


[/size] 查看全部
原文地址 :  http://blog.csdn.net/jyt199011302/article/details/72829520

参考资料
APNS证书创建和上传到环信后台 : http://www.imgeek.org/article/825308748 
APNS离线推送文档 : http://docs.easemob.com/im/300iosclientintegration/75apns
离线推送的集成代码这里就不一一介绍了, 上面文档中写的 很明白了, 接下来说下比较容易误会的几点
一. 离线推送
如果app集成时添加
- (void)applicationDidEnterBackground:(UIApplication *)application {
[[EMClient sharedClient] applicationDidEnterBackground:application];
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
[[EMClient sharedClient] applicationWillEnterForeground:application];
}

App后台静默后,能够保持长连接3分钟左右。超过3分钟,长连接会断开,当前登录的账号,在服务端被认为离线。消息会存入离线消息空间,之后接收的消息会在再次登录后,连接上服务器,然后通过长连接把消息取走,投递给此用户。如果app配置了推送证书,上传了推送证书并且集成了推送功能,服务器会给接收方发一个APNs推送,则会对离线消息进行APNs推送提示消息内容,通知接收方有一条新消息。
如果想自定义推送的alert,可以在发消息的时候,在消息扩展中添加相应的字段。文档见:APNS内容解析

离线推送 : 当app被杀死或者进入后台三分钟之后
消息回调 : app在前台及app进入后台三分钟之内

注意 : 环信支持推送消息,只是目前还不能根据标签推送给特定用户组,也暂不支持推送模板。CMD消息没有推送,好友请求也没有推送

收不到离线推送时可以从下面几个方面找下原因

1.测试apns推送的时候,接受消息方的app是杀掉状态吗,或者进入后台三分钟以后
2.看看你环信后台上传的证书名称与工程中初始化SDK那里填的证书名 是不是相同的
3.配置证书时候填的id与你工程中的bundle id 是否相同
4.devicetoken有没有传给环信SDK。即查看管理后台中,对应 IM 账户下是否有您刚刚写的证书名。(如果没有,请检查您是否得到了 deviceToken)
5.确认Xcode环境是否配置正确 ,Build Settings---signing,看Debug对应的是不是开发的,Release对应的是不是生产的
6.在确认xcode运行环境是否正确 (Product-->Scheme-->Edit Scheme, 开发证书选Debug,生产证书选Release)
7.证书制作上传过程是否有问题,配置证书的时候是否设置了密码,正确的步骤可以参考:http://www.imgeek.org/article/825308748。另外可以用推送工具进行验证。
8.如果以上都没有问题,可以尝试重新制作上传一下推送证书。
对照这些检查一下,基本上就是这些原因
如果上面几点都符合的话,看下重新登录之后是否可以收到之前收不到推送消息
可以的,话提供一下AppKey,证书名 (查下证书是否被封)以及收不到的推送消息的消息id及发送方和接收方log
log导出请看这篇文章: http://www.imgeek.org/article/825308785 然后转成txt格式上传到工单上,同时注明上述配置都正确 , 环信这边来查下消息推送记录

注意 :后台没有证书名 是指用户列表后面没有显示证书名。这个证书名是SDK初始化的时候传的字符串,用户登录之后会进行绑定。
如果用户没有绑定证书名的话,肯定收不到推送的。这个证书名是用户登录之后绑定的,要确认下初始化SDK的时候有没有传。 options.apnsCertName = apnsCertName;

==============常见问题==============

Q : iOS apns离线推送证书apns的离线推送可以和友盟(极光)推送共用一个证书吗?
A : 环信的推送只要和后台上传的证书对应就可以实现,其他的不关心。
首先苹果推送证书的生成都是统一的方式,这个不区分是极光(友盟)推送证书还是个推证书等等。使用的推送证书只要按照正确的苹果推送证书生成流程创建,都可以使用。
环信添加推送证书可以看http://www.imgeek.org/article/825308748 不是要求必须重新生成推送证书 
Q : 好友申请通知的离线推送?
A : 我们的好友体系,添加好友的申请不支持离线推送。
如果你们是使用App本身的好友体系,可以在app的添加好友业务上向被添加的好友发送文本消息,在EMMessage的ext中设置自定义字段,来区分此条文本消息是否用于好友申请提示,由此来判断处理UI的显示。

Q : iOS的杀死进程远程推送和服务端有关么
A : 如果客户端把远程通知给关了肯定就收不到通知,我们服务器会检测客户端是否有deviceToken,有的话才会把消息发送到deviceToken对应的设备上

Q : 每个项目创建了一个开发的推送证书一个生产的推送证书。这俩证书什么时候要做切换?
A : 在App上传AppStore前需要修改App内初始化SDK设置的推送证书名,EMOptions的apnsCertName。
注意,这里的值需要和在Console管理后天上传时设置的证书名一致。

Q : 绑定devicetoken的时候是否需要先登录到环信?
A : 绑定是需要登录过之后才进行的,
 - (EMError *)bindDeviceToken:(NSData *)aDeviceToken; 是把deviceToken传给SDK。调用登录,SDK会进行绑定。也可以调用 - (void)registerForRemoteNotificationsWithDeviceToken:(NSData *)aDeviceToken
completion:(void (^)(EMError *aError))aCompletionBlock;自己绑定
需要判断是否已经登录,如果已经有登录的账号,再登录会返回 已登录的错误。

Q : 离线推送在客户端怎么设置显示详情?
A :
 EMPushOptions *pushOptions = [[EMClient sharedClient] pushOptions];
pushOptions.displayStyle = EMPushDisplayStyleMessageSummary;

可以设置离线推送消息显示具体内容还是只显示-您收到一条消息
要设置在登录成功之后,然后要用服务器拉取一遍APNS 属性
EMError *error = nil;
EMPushOptions *options = [[EMClient sharedClient] getPushOptionsFromServerWithError:&error];

然后在修改displayStyle

Q : 两个APP通信,如果只希望其中一类APP能收到推送,而另一端的APP不希望收到推送,是不是不希望收到推送的APP不配置证书就好了?
A : 两个App的推送证书都是在同一appkey下单独配置的,如果不希望收到推送,可以对此App不配置推送证书,同时在App代码中注释掉注册远程通知的相关代码。
bundle id对应的证书也可以取消push的功能,针对App不使用任何远程推送服务,包括其他第三方的,如果App还需要其他第三方的推送服务,请忽略这句话。

Q : 不配置推送证书的APP是不是只有刷新的情况下才会显示新的消息?不刷新的情况下APP是看不到新的消息?
A : 不配置推送功能的App,只有在用户登录成功后,才能通过长连接的接收消息回掉中拿到消息体。

Q : 在开发环境下收到了离线推送消息,但是在生成环境下没有收到?
A : 看一下SDK初始化时,是否设置的apnsCerName与生产环境证书上传时填写的证书名一致,还有是否为adhoc打包成ipa文件安装测试的。

Q :发送消息1,2,3,4,5 对方收到推送 2 1 5 顺序不对而且丢失, app角标也不对
A : 1.首先,苹果不保证所有远程推送的到达率。这个可以看苹果官方文档。
Because the delivery of remote notifications is not guaranteed, never include sensitive data or data that can be retrieved by other means in your payload.
2.我们只保证,把离线消息执行远程推送,发给苹果服务器。苹果服务器是否能够百分之百把所有推送送达到指定移动端,这个根据苹果的策略,当 APNs 向你发送了多条推送,你的设备在 APNs 那里下线了,这时 APNs 到你的手机的链路上有多条任务堆积,APNs 的处理方式是,只保留最后一条消息推送给你,然后告知你推送数。那么其他消息会被APNs丢弃。
3.我们保证的是离线消息,当用户重新登录时,可以都接收到。
4. 如果需要找后台查询离线消息(前提接收方已绑定deviceToken)是否成功,需要提供离线消息的messageId,接收方环信id
5.我们的推送角标,是接收方的在服务端的离线消息数。
https://developer.apple.com/li ... 8-SW1
这是官方文档
20170605122458543-1.jpeg


Q11 :ios是怎么判断离线了 然后发推送的啊 有时候把应用杀掉后 半天收不到推送
A : rest可以查用户的状态,推送前提是此用户有devicetoken已经绑定成功
如果账号所有配置都没问题,杀掉后,其他人发的消息,过几秒就能看到推送
Q12 : Q11不管用会是什么原因呢
A : 配置,还有账号在我们这绑的deviceToken

Q : 多个app共用一个appkey 推送证书怎么配置呢
A : 后台可以上传多套推送证书。

Q :程序关闭后推送了一个消息,点击后怎样获取到环信传过来的数据
A : 需要用户点击横幅后,重新启动App,这时从- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions中,获取字典launchOptions,UIApplicationLaunchOptionsRemoteNotificationKey这个key下的数据,就是aps的字典数据

二 . 消息回调
app的长连接存在的时候,环信服务器检测您为在线状态,是不会给app推送消息的。app端在线的情况下,消息会通过长连接直接收取(didreceivemessage),收到消息,SDK会通过回调通知给上层。app通过收消息的回调拿到消息对象,然后解析并展示UI。
目前我们不支持App切后台后,可以一直执行。
我们SDK在切后台后,实现[[EMClient sharedClient] applicationDidEnterBackground:application];,会保持,直到被系统释放iOS目前其他方式应该都无法去实现一直保持App的活跃状态了。

==============常见问题==============
Q : 本地推送声音设置在哪 ?一条消息推送两声
A : 推送声音设置的要自己实现,具体可参考demo里的ChatDemoHelper类和MainViewController类里的
- (void)showNotificationWithMessage:(EMMessage *)message方法,该方法中有发送本地推送做的一系列操作,本地通知怎么做的,本地通知触发几次,一条消息推送几声,一下接收到多条消息响几声,都需要用户自己实现.

Q : 视频通话,推送怎么实现?
iOS 3.2.3之后,如果在实时通话接收方不在线时,发送提醒。
A : 1.在发起实时音视频通话前,需要设置EMCallOptions对象属性isSendPushIfOffline为YES;
2.遵守协议EMCallBuilderDelegate,实现其中的- (void)callRemoteOffline:(NSString *)aRemoteName 委托方法。
3.在第2步的方法中向 aRemoteName用户发送单聊消息。
如果被叫方已注册远程通知且绑定deviceToken,会收到对应消息的APNs推送,点击横幅来唤醒App。
上面是接收方离线的情况。如果接收方长连接还未断开,只是App切到后台,需要在回调- (void)callDidReceive:(EMCallSession *)aSession中判断当前App是否在后台,如果是弹出本地通知。

Q : EMCallOptions *options = [[EMClient sharedClient].callManager getCallOptions]; //当对方不在线时,是否给对方发送离线消息和推送,并等待对方回应 options.isSendPushIfOffline = YES; [[EMClient sharedClient].callManager setCallOptions:options];isSendPushIfOffline设置为YES后,A用户呼叫B用户,B用户处于离线状态,但B用户没有收到推送。
A : 1. 先确接收方杀掉App后,文本消息是否能收到APNs推送。
2.在1点确认App杀掉可以收到推送前提下,确认实时音视频发送方代码执行顺序如下:
(1) EMCallOptions *callOptions = [[EMClient sharedClient].callManager getCallOptions];
callOptions.isSendPushIfOffline = YES;
callOptions.offlineMessageText = @"提示文本";//可选
[[EMClient sharedClient].callManager setCallOptions:callOptions];

(2) callManager调用
- (void)startVideoCall:(NSString *)aUsername
completion:(void (^)(EMCallSession *aCallSession, EMError *aError))aCompletionBlock;

或者
- (void)startVoiceCall:(NSString *)aUsername
completion:(void (^)(EMCallSession *aCallSession, EMError *aError))aCompletionBlock;

Q : 推送的提示音可不可以自定义啊。
A: 推送的提示音目前不支持自定义,本地通知的你们以自己去设置。
 
Q : app压后台,立刻收到聊天推送来的信息,点击通知栏信息,捕获不到唤起程序事件
A : App切后台后,长连接为断开前,当前弹出的横幅是本地通知,那么此时唤醒时间是本地通知的回调
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification;

iOS10后
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)())completionHandler;

Q : 环信在离线状态下能收消息,但是程序运行状态按home按键进入后台的时候无法接受消息,怎么处理
A : App切入后台一段时间内,长连接还未断开,这时候接收消息都是通过SDK的接收消息回调(EMChatManagerDelegate)来收消息,不会执行APNs推送。
如果是需要弹出横幅提醒,需要在接收消息的回调方法中,判断[[UIApplication sharedApplication] applicationState]为UIApplicationStateBackground,然后实现本地通知。可以参考demo中的处理
(ChatDemoHelper的- (void)didReceiveMessages:(NSArray *)aMessages)
[attach]7500[/attach][size=13]


[/size]
0
评论

IM-SDK和客服SDK并存开发指南—iOS篇 环信移动客服 iOS

beyond 发表了文章 • 692 次浏览 • 2017-06-06 10:47 • 来自相关话题

一、SDK 介绍
 
环信客服访客端SDK 基于 IM SDK 3.x , 如果同时使用客服访客端SDK和IM  SDK,只需要在初始化、登录、登出操作时使用客服访客端 SDK 提供的相应 API ,IM 的其他API 均不受影响。UI 部分集成需要分别导入HelpDeskUI 和 EaseUI(也可以自定义UI)。
 下面详细介绍IM 和 客服共存的开发步骤。
二、注意事项
 
开发过程中 初始化、登录和登出,务必使用客服访客端SDK 的API。IM SDK 和客服SDK 都包括了模拟器的CPU 架构,在上传到app store时需要剔除模拟器的CPU 架构,保留armv7、arm64。
 
三、资源准备
 
到环信官网下载客服访客端的开源的商城Demo源码 + SDK,下载链接:http://www.easemob.com/download/cs  选择“iOS客服访客端SDK”下载(如下图)。


到环信官网下载IM的开源的Demo源码 + SDK ,下载链接:http://www.easemob.com/download/im 选择iOS SDK(如下图)。  



四、资源简介
两个SDK 均包括基础版和实时音视频版,根据需求的不同需要导入不同的SDK组合。基础版命名在名称后边添加Lite标识。从官网下载的客服访客端SDK包括以下目录: helpdeskdemo-ios、HelpDeskFramework、HelpDeskUI和HyphenateFramework,分别表示:
- helpdeskdemo-ios 为包含实时音视频的Demo,可直接运行。- HelpDeskFramework 为客服访客端SDK,HelpDesk.framework包含实时音视频、HelpDeskLite.framework不包含实时音视频。- HelpDeskUI 为环信用户提供的单聊UI,可在集成的时候视情况使用。- HyphenateFramework 为客服SDK依赖库(IM SDK),Hyphenate.framework和HelpDesk.framework一起导入,HyphenateLite.framework和HelpDeskLite.framework一起导入。

下载的IM SDK+Demo,包含五个文件夹,并且和客服SDK 中都有 IM 的framework文件,为了保持版本的匹配,我们只使用其中的 EaseUI 而不使用IM SDK中 的framework文件。

五、集成步骤
 
参考客服访客端SDK文档集成客服的访客端SDK,文档地址:http://docs.easemob.com/cs/300visitoraccess/iossdk。将IM SDK 中的EaseUI 导入到工程中,需要在导入访客端客服SDK的地方导入#import "EaseUI.h"   [重要提示:IM 的初始化、登录、登出操作需要使用客服的相关API]在pch文件中引入
#ifdef __OBJC__
//包含实时音视频功能
#import <HelpDesk/HelpDesk.h>
//若不包含实时音视频,则替换为
#import <HelpDeskLite/HelpDeskLite.h>

#import "HelpDeskUI.h"
#import "EaseUI.h"
#endif
   4. 然后将EaseUI或者HelpDeskUI中的 FixFopen.c文件删除(重复冲突)。
   5. 由于HelpDeskUI 和 EaseUI 中使用了 第三方库,如果工程中出现三方重复的问题,可将HelpDeskUI 和 EaseUI  中的重复文件删除,如果部分接口已经升级或弃用可自行升级、调整。
 
六、工程设置
 
在General -> Embedded Binaries 中导入HelpDesk.framework和Hyphenate.framework(这是包含实时音视频的,如果不包含实时音视频则导入HelpDeskLite.framework和HyphenateLite.framework)。在Build Settings -> Linking -> Other Linker Flags 中增加 -ObjC项,注意区分大小写。客服 访客端SDK暂不支持bitcode,所以需要将Build Settings -> Build Options -> Enable Bitcode 设为NO。为了减少不必要的警告,可将Build Settings -> Documentation Comments 设为 NO。

提供的兼容Demo介绍:
 
Demo是在客服的商城Demo上修改,在左上角添加了一个聊天室的按钮,点击按钮会根据appkey随机创建一个账号并登录,登录成功后会进入聊天室列表界面,点击某个聊天室可以在聊天室中聊天。Demo中客服部分功能还是和原商城Demo功能一致。Demo中为了演示因此采用随机注册账号的方式,对于用户场景中,可以先注册好这些账号和自己的账号绑定,这样每次咨询客服就都是同一个人了,也可以显示这个访客曾经的聊天记录。
 
Demo源码地址:http://kefu-prod-apk.oss-cn-ha ... o.zip
  查看全部
一、SDK 介绍
 
  1. 环信客服访客端SDK 基于 IM SDK 3.x , 如果同时使用客服访客端SDK和IM  SDK,只需要在初始化、登录、登出操作时使用客服访客端 SDK 提供的相应 API ,IM 的其他API 均不受影响。
  2. UI 部分集成需要分别导入HelpDeskUI 和 EaseUI(也可以自定义UI)。

 下面详细介绍IM 和 客服共存的开发步骤。
二、注意事项
 
  1. 开发过程中 初始化、登录和登出,务必使用客服访客端SDK 的API。
  2. IM SDK 和客服SDK 都包括了模拟器的CPU 架构,在上传到app store时需要剔除模拟器的CPU 架构,保留armv7、arm64。

 
三、资源准备
 
  1. 到环信官网下载客服访客端的开源的商城Demo源码 + SDK,下载链接:http://www.easemob.com/download/cs  选择“iOS客服访客端SDK”下载(如下图)。
    01.jpg
  2. 到环信官网下载IM的开源的Demo源码 + SDK ,下载链接:http://www.easemob.com/download/im 选择iOS SDK(如下图)。  
    02.png

四、资源简介
  1. 两个SDK 均包括基础版和实时音视频版,根据需求的不同需要导入不同的SDK组合。基础版命名在名称后边添加Lite标识。
  2. 从官网下载的客服访客端SDK包括以下目录: helpdeskdemo-ios、HelpDeskFramework、HelpDeskUI和HyphenateFramework,分别表示:

  • - helpdeskdemo-ios 为包含实时音视频的Demo,可直接运行。
  • - HelpDeskFramework 为客服访客端SDK,HelpDesk.framework包含实时音视频、HelpDeskLite.framework不包含实时音视频。
  • - HelpDeskUI 为环信用户提供的单聊UI,可在集成的时候视情况使用。
  • - HyphenateFramework 为客服SDK依赖库(IM SDK),Hyphenate.framework和HelpDesk.framework一起导入,HyphenateLite.framework和HelpDeskLite.framework一起导入。


下载的IM SDK+Demo,包含五个文件夹,并且和客服SDK 中都有 IM 的framework文件,为了保持版本的匹配,我们只使用其中的 EaseUI 而不使用IM SDK中 的framework文件。

五、集成步骤
 
  1. 参考客服访客端SDK文档集成客服的访客端SDK,文档地址:http://docs.easemob.com/cs/300visitoraccess/iossdk
  2. 将IM SDK 中的EaseUI 导入到工程中,需要在导入访客端客服SDK的地方导入#import "EaseUI.h"   [重要提示:IM 的初始化、登录、登出操作需要使用客服的相关API]
  3. 在pch文件中引入

 #ifdef __OBJC__
//包含实时音视频功能
#import <HelpDesk/HelpDesk.h>
//若不包含实时音视频,则替换为
#import <HelpDeskLite/HelpDeskLite.h>

#import "HelpDeskUI.h"
#import "EaseUI.h"
#endif

   4. 然后将EaseUI或者HelpDeskUI中的 FixFopen.c文件删除(重复冲突)。
   5. 由于HelpDeskUI 和 EaseUI 中使用了 第三方库,如果工程中出现三方重复的问题,可将HelpDeskUI 和 EaseUI  中的重复文件删除,如果部分接口已经升级或弃用可自行升级、调整。
 
六、工程设置
 
  1. 在General -> Embedded Binaries 中导入HelpDesk.framework和Hyphenate.framework(这是包含实时音视频的,如果不包含实时音视频则导入HelpDeskLite.framework和HyphenateLite.framework)。
  2. 在Build Settings -> Linking -> Other Linker Flags 中增加 -ObjC项,注意区分大小写。
  3. 客服 访客端SDK暂不支持bitcode,所以需要将Build Settings -> Build Options -> Enable Bitcode 设为NO。
  4. 为了减少不必要的警告,可将Build Settings -> Documentation Comments 设为 NO。


提供的兼容Demo介绍:
 
  1. Demo是在客服的商城Demo上修改,在左上角添加了一个聊天室的按钮,点击按钮会根据appkey随机创建一个账号并登录,登录成功后会进入聊天室列表界面,点击某个聊天室可以在聊天室中聊天。
  2. Demo中客服部分功能还是和原商城Demo功能一致。
  3. Demo中为了演示因此采用随机注册账号的方式,对于用户场景中,可以先注册好这些账号和自己的账号绑定,这样每次咨询客服就都是同一个人了,也可以显示这个访客曾经的聊天记录。

 
Demo源码地址:http://kefu-prod-apk.oss-cn-ha ... o.zip
 
2
评论

集成环信遇到的相关问题整理 iOS

木云落 发表了文章 • 1398 次浏览 • 2017-05-22 16:36 • 来自相关话题

最近在整理这段时间被别人问到引入环信可能会出现的问题,记得的也不太多,想到一个就在这里记录一个吧,如果有遇到过本文中没有列出来的,可以问我,我会一一解答的
原文地址: http://blog.csdn.net/jyt199011 ... 83995

1. pod引入的Hyphenate里面的.h文件中和手动下载的sdk相比会缺少Hyphenate.h 。
A :  主要是pod 问题 本地仓库太旧了, 终端行pod repo update, 之后在pod search 'Hyphenate' 如果可以找到3.3.0版本, 就可以下载了 podfile 里面 platform 要指定8.0

2. iOS SDK 从低版本 升到3.3.0 后运行报错 (集成动态库版本报错)
dyld: Library not loaded: @rpath/Hyphenate.framework/Hyphenate
  Referenced from: /Users/white/Library/Developer/CoreSimulator/Devices/BE0DDC26-96AE-4396-A6C5-48DC6938042B/data/Containers/Bundle/Application/4F9F570A-44B5-4F81-AD19-F7AA38D26E40/SYSchoolProject.app/SYSchoolProject
  Reason: image not found




A : 在Build setting -> General这里加上。 还有这里也加上 改不能成optional,
注意 : 改成optional之后会导致初始化为null






3.在AppDelegate中执行[EaseMob sharedInstance]崩溃
A : other link flags添加“-ObjC”选项(注意:O和C大写)


4. pod导入EaseUI 时报错 
A : 先进入Podfile文件中,添加pod 'EaseUI', :git => 'https://github.com/easemob/easeui-ios-hyphenate-cocoapods.git' ,保存退出之后执行pod update即可 ,如果还是失败,可以升级一下pod版本





5.‘Hyphenate/EMSDK.h’ file no found
A : 换下引用#import <HyphenateLite/HyphenateLite.h>
     或者#import <Hyphenate/Hyphenate.h>
     如果此方法不行, 可以试试选中你的项目中的Pods -> EaseUI->Build Phases->Link Binary With Libraries ,点➕->Add Other ,找到工程里面,Pods里面的Hyphenate文件夹下面的Hyphenate.framework 点击open,重新编译就好了






6. 




A :  可以参考问题2的基础上, 再看下相对路径那里


7.集成动态库上传AppStore出现问题, 打包上线时报错
ERROR ITMS-90087: "Unsupported Architectures. The executable for xiantaiApp.app/Frameworks/Hyphenate.framework contains unsupported architectures '[x86_64, i386]'."
A :  遇到这个问题的小伙伴一定是没有认真看咱们环信的官方文档,
由于 iOS 编译的特殊性,为了方便开发者使用,我们将 i386 x86_64 armv7 arm64 几个平台都合并到了一起,所以使用动态库上传appstore时需要将i386 x86_64两个平台删除后,才能正常提交审核

在SDK当前路径下执行以下命令删除i386 x86_64两个平台
实时音视频版本Hyphenate.frameworklipo Hyphenate.framework/Hyphenate -thin armv7 -output Hyphenate_armv7 lipo Hyphenate.framework/Hyphenate -thin arm64 -output Hyphenate_arm64 lipo -create Hyphenate_armv7 Hyphenate_arm64 -output Hyphenate mv Hyphenate Hyphenate.framework/
 
不包含实时音视频版本HyphenateLite.frameworklipo HyphenateLite.framework/HyphenateLite -thin armv7 -output HyphenateLite_armv7 lipo HyphenateLite.framework/HyphenateLite -thin arm64 -output HyphenateLite_arm64 lipo -create HyphenateLite_armv7 HyphenateLite_arm64 -output HyphenateLite mv HyphenateLite HyphenateLite.framework/
拿实时音视频版本版本为例 : 执行完以上命令如图所示




运行完毕后得到的Hyphenate.framework就是最后的结果,拖进工程,编译打包上架。




注意 : 
1. 最后得到的包必须真机编译运行,并且工程要设置编译二进制文件General->Embedded Bunaries.
2. 删除i386、x86_64平台后,SDK会无法支持模拟器编译,只需要在上传AppStore时在进行删除,上传后,替换为删除前的SDK,建议先分别把i386、x86_64、arm64、armv7各平台的包拆分到本地,上传App Store时合并arm64、armv7平台,并移入Hyphenate.framework内。上传后,重新把各平台包合并移入动态库


打包时还有可能报这个错误
ERROR ITMS-90535: "Unexpected CFBundleExecutable Key. The bundle at 'Payload/xiantaiApp.app/EaseUIResource.bundle' does not contain a bundle executable. If this bundle intentionally does not contain an executable, consider removing the CFBundleExecutable key from its Info.plist and using a CFBundlePackageType of BNDL. If this bundle is part of a third-party framework, consider contacting the developer of the framework for an update to address this issue."
A :  ​从EaseUIResource.bundle中找到info.plist删掉CFBundleExecutable,或者整个info.plist删掉



8.ios apns推送是什么原因导致这个错误
注册deviceToken失败:application:didFailToRegisterForRemoteNotificationsWithError: Error Domain=NSCocoaErrorDomain Code=3000 "未找到应用程序的“aps-environment”的授权字符串" UserInfo={NSLocalizedDescription=未找到应用程序的“aps-environment”的授权字符串}
A: 工程配置没有打开推送功能。

9.运行demo报这个错误




A: 没有存储空间了。
 
 
10. SDK3.3.1 以上版本手动导入EaseUI报错
A : 由于demo是用pod集成的,所以直接引入demo中的EaseUI会缺少相关文件,可以直接拖入附件中的EaseUI
如果引入之后报如下图的错误








其实碰到上面这个问题还是很好解决的,这个是因为用到了UIKit里的类,但是只导入了Foundation框架,这个错误在其他类里也会出现,我们可以手动修改Founfation为UIKit,但是我不建议这么做,第一这个做法的工程量比较大, 在其他类里面也要导入,二,不利于移植,当以后环信更新的时候我们还是需要做同样的操作,这里我的做法的创建一个pch文件,在pch文件里面导入UIKit。解决办法:建一个PCH文件在里面添加如下代码:





以上应该会正常了,但是如果集成的是不包含实时音视频的SDK, 您导入的EaseUI不是Lite版的,  那么此时还会报跟第六点一样的错误 , 需要导入EaseUILite 版本,所以会找不到Hyphenate/Hyphenate.h,如果是手动集成,建议在xcode设置一下Build Settings> GCC_PREPROCESSOR_DEFINITIONS >ENABLE_LITE=1,这样easeui就去找HyphenateLite/HyphenateLite.h
也可以通过pod集成,文档上针对easeui集成Full版本和Lite版本sdk特殊的说明http://docs.easemob.com/im/300 ... guide
 
或者不想导入Lite版的 , 只想引入EaseUI 
这时需要把 #import <Hyphenate/Hyphenate.h>注释掉,然后把报错地方的Hyphenate换成HyphenateLite就可以了
 
 
11. 




A : 可以删除或者重命名Podfile.lock文件,重新执行pod install命令 查看全部
最近在整理这段时间被别人问到引入环信可能会出现的问题,记得的也不太多,想到一个就在这里记录一个吧,如果有遇到过本文中没有列出来的,可以问我,我会一一解答的
原文地址: http://blog.csdn.net/jyt199011 ... 83995

1. pod引入的Hyphenate里面的.h文件中和手动下载的sdk相比会缺少Hyphenate.h 。
A :  主要是pod 问题 本地仓库太旧了, 终端行pod repo update, 之后在pod search 'Hyphenate' 如果可以找到3.3.0版本, 就可以下载了 podfile 里面 platform 要指定8.0

2. iOS SDK 从低版本 升到3.3.0 后运行报错 (集成动态库版本报错)
dyld: Library not loaded: @rpath/Hyphenate.framework/Hyphenate
  Referenced from: /Users/white/Library/Developer/CoreSimulator/Devices/BE0DDC26-96AE-4396-A6C5-48DC6938042B/data/Containers/Bundle/Application/4F9F570A-44B5-4F81-AD19-F7AA38D26E40/SYSchoolProject.app/SYSchoolProject
  Reason: image not found
20170330110241533.jpeg

A : 在Build setting -> General这里加上。 还有这里也加上 改不能成optional,
注意 : 改成optional之后会导致初始化为null
20170330104308569.jpeg



3.在AppDelegate中执行[EaseMob sharedInstance]崩溃
A : other link flags添加“-ObjC”选项(注意:O和C大写)


4. pod导入EaseUI 时报错 
A : 先进入Podfile文件中,添加pod 'EaseUI', :git => 'https://github.com/easemob/easeui-ios-hyphenate-cocoapods.git' ,保存退出之后执行pod update即可 ,如果还是失败,可以升级一下pod版本
屏幕快照_2017-05-22_上午10.27_.07_.png


5.‘Hyphenate/EMSDK.h’ file no found
A : 换下引用#import <HyphenateLite/HyphenateLite.h>
     或者#import <Hyphenate/Hyphenate.h>
     如果此方法不行, 可以试试选中你的项目中的Pods -> EaseUI->Build Phases->Link Binary With Libraries ,点➕->Add Other ,找到工程里面,Pods里面的Hyphenate文件夹下面的Hyphenate.framework 点击open,重新编译就好了
20170331200729906.jpeg



6. 
20170331110834145.jpeg

A :  可以参考问题2的基础上, 再看下相对路径那里


7.集成动态库上传AppStore出现问题, 打包上线时报错
ERROR ITMS-90087: "Unsupported Architectures. The executable for xiantaiApp.app/Frameworks/Hyphenate.framework contains unsupported architectures '[x86_64, i386]'."
A :  遇到这个问题的小伙伴一定是没有认真看咱们环信的官方文档,
由于 iOS 编译的特殊性,为了方便开发者使用,我们将 i386 x86_64 armv7 arm64 几个平台都合并到了一起,所以使用动态库上传appstore时需要将i386 x86_64两个平台删除后,才能正常提交审核

在SDK当前路径下执行以下命令删除i386 x86_64两个平台
实时音视频版本Hyphenate.frameworklipo Hyphenate.framework/Hyphenate -thin armv7 -output Hyphenate_armv7 lipo Hyphenate.framework/Hyphenate -thin arm64 -output Hyphenate_arm64 lipo -create Hyphenate_armv7 Hyphenate_arm64 -output Hyphenate mv Hyphenate Hyphenate.framework/
 
不包含实时音视频版本HyphenateLite.frameworklipo HyphenateLite.framework/HyphenateLite -thin armv7 -output HyphenateLite_armv7 lipo HyphenateLite.framework/HyphenateLite -thin arm64 -output HyphenateLite_arm64 lipo -create HyphenateLite_armv7 HyphenateLite_arm64 -output HyphenateLite mv HyphenateLite HyphenateLite.framework/
拿实时音视频版本版本为例 : 执行完以上命令如图所示
20170401112052481.png

运行完毕后得到的Hyphenate.framework就是最后的结果,拖进工程,编译打包上架。
20170401112216045.png

注意 : 
1. 最后得到的包必须真机编译运行,并且工程要设置编译二进制文件General->Embedded Bunaries.
2. 删除i386、x86_64平台后,SDK会无法支持模拟器编译,只需要在上传AppStore时在进行删除,上传后,替换为删除前的SDK,建议先分别把i386、x86_64、arm64、armv7各平台的包拆分到本地,上传App Store时合并arm64、armv7平台,并移入Hyphenate.framework内。上传后,重新把各平台包合并移入动态库


打包时还有可能报这个错误
ERROR ITMS-90535: "Unexpected CFBundleExecutable Key. The bundle at 'Payload/xiantaiApp.app/EaseUIResource.bundle' does not contain a bundle executable. If this bundle intentionally does not contain an executable, consider removing the CFBundleExecutable key from its Info.plist and using a CFBundlePackageType of BNDL. If this bundle is part of a third-party framework, consider contacting the developer of the framework for an update to address this issue."
A :  ​从EaseUIResource.bundle中找到info.plist删掉CFBundleExecutable,或者整个info.plist删掉



8.ios apns推送是什么原因导致这个错误
注册deviceToken失败:application:didFailToRegisterForRemoteNotificationsWithError: Error Domain=NSCocoaErrorDomain Code=3000 "未找到应用程序的“aps-environment”的授权字符串" UserInfo={NSLocalizedDescription=未找到应用程序的“aps-environment”的授权字符串}
A: 工程配置没有打开推送功能。

9.运行demo报这个错误
20170519110027739.png

A: 没有存储空间了。
 
 
10. SDK3.3.1 以上版本手动导入EaseUI报错
A : 由于demo是用pod集成的,所以直接引入demo中的EaseUI会缺少相关文件,可以直接拖入附件中的EaseUI
如果引入之后报如下图的错误
10.1_.png

10.2_.png

其实碰到上面这个问题还是很好解决的,这个是因为用到了UIKit里的类,但是只导入了Foundation框架,这个错误在其他类里也会出现,我们可以手动修改Founfation为UIKit,但是我不建议这么做,第一这个做法的工程量比较大, 在其他类里面也要导入,二,不利于移植,当以后环信更新的时候我们还是需要做同样的操作,这里我的做法的创建一个pch文件,在pch文件里面导入UIKit。解决办法:建一个PCH文件在里面添加如下代码:
10.3_.png


以上应该会正常了,但是如果集成的是不包含实时音视频的SDK, 您导入的EaseUI不是Lite版的,  那么此时还会报跟第六点一样的错误 , 需要导入EaseUILite 版本,所以会找不到Hyphenate/Hyphenate.h,如果是手动集成,建议在xcode设置一下Build Settings> GCC_PREPROCESSOR_DEFINITIONS >ENABLE_LITE=1,这样easeui就去找HyphenateLite/HyphenateLite.h
也可以通过pod集成,文档上针对easeui集成Full版本和Lite版本sdk特殊的说明http://docs.easemob.com/im/300 ... guide
 
或者不想导入Lite版的 , 只想引入EaseUI 
这时需要把 #import <Hyphenate/Hyphenate.h>注释掉,然后把报错地方的Hyphenate换成HyphenateLite就可以了
 
 
11. 
1.png

A : 可以删除或者重命名Podfile.lock文件,重新执行pod install命令
0
评论

环信聊天游客身份和正常用户身份的切换 环信聊天游客身份与正常用户身份的切换 iOS

奋斗的蜗牛 发表了文章 • 191 次浏览 • 2017-05-10 20:39 • 来自相关话题

 
   最近搞环信聊天,需求是游客身份也可以进行聊天,当用户注册了我们的APP后也需要把游客身份切换过来进行聊天,首先我们的环信注册,登录全都放前段处理了,下面就按照我们的需求逻辑来如何切换游客。
 
   1.APP用户的注册,也就注册环信,APP的登录返回的有用户ID,这个时候并没有让他登录环信,只是保存了返回的ID,下面就是用ID来判断该用户是否注册过环信的依据
 
大致说明一下,代码中用到一个类来保证uuid不会改变的状态,为防止app卸载后uuid的改变,我们把他存储到钥匙串里面来保存

下面用图来表示
我们先来看下整个身份切换实现的逻辑图





下面就上代码了,第一步从图中第一步来说判断userID是否存在

这个地方是在点击聊天按钮开始判断的
 -(void)releaseInfo:(UIButton*)sender{
NSString*Hxusername=[userdic objectForKey:@"useid"];//获取保存的userID
NSString*phonestr=  [[NSUserDefaults standardUserDefaults]objectForKey:@"phonenum"];
NSString*chatid=[[phonestr md5String]substringFromIndex:16];//这个是获取客服的欢信ID
//单例里面处理用户是否登录,以及游客随机分配uuid来注册环信IM号
DataManager*datamage= [DataManager shareDataManager];
//判断用户ID是否存在,也就证明是否注册过环信
if (Hxusername.length>0) {
if ([datamage loginKefuSDK])//判断用户是否登录
{//单聊
ChattingViewController *chatController = [[ChattingViewController alloc] initWithConversationChatter:chatid conversationType:EMConversationTypeChat];[self.navigationController pushViewController:chatController animated:YES];
}
}else{
//游客身份的判断
if ([datamage customelogin]) {
//单聊
ChattingViewController *chatController = [[ChattingViewController alloc] initWithConversationChatter:chatid conversationType:EMConversationTypeChat];[self.navigationController pushViewController:chatController animated:YES];
}
}
}上面这是按钮方法里面的数据下面来说,DataManager*datamage= [DataManager shareDataManager];这个单利的方法DataManager.h
@interface DataManager : NSObject
-(BOOL)customelogin;//判断游客之前是否有登录
-(void)requestchattphone;//获取美容院客服聊天的对象电话
@endDataManager.m


@implementation DataManager+(instancetype)shareDataManager{
static DataManager *manager;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
manager = [[DataManager alloc] init];
});
return manager;
}//userID存在的时候 登录IM
- (BOOL)loginKefuSDK {
NSDictionary*userdic=[[NSUserDefaults standardUserDefaults]objectForKey:@"userMessage"];//接受用户是否登录
NSString*loguser=[NSString stringWithFormat:@"%@",[userdic objectForKey:@"useid"] ];
EMClient *client = [EMClient sharedClient];
//用户已经登录
if (client.isLoggedIn) {
if ([loguser isEqualToString:client.currentUsername])//当前登录用户的ID和即将要登录人的ID是否一样
{
return YES;
}else
{
EMError *error = [[EMClient sharedClient] logout:YES];
if (!error) {
NSLog(@"退出成功");
}
}
}//这里APP用户登录环信的密码统统是123456
EMError *error = [[EMClient sharedClient] loginWithUsername:loguser password:@"123456"];
if (!error) { //IM登录成功
return YES;
} else { //登录失败
NSLog(@"登录失败 error code :%d,error description:%@",error.code,error.errorDescription);
return NO;
}
return NO;
}//游客身份的登录方法
-(BOOL)customelogin
{
EMClient *client = [EMClient sharedClient];
//用户已经登录
if (client.isLoggedIn) {
return YES;
}//该用户没有注册,来用改设备UUID来给用户注册环信,并登录环信
if (![self registerIMuser]) {
return NO;
}
EMError *error = [[EMClient sharedClient] loginWithUsername:self.Hxusername password:@"123456"];
if (!error) { //IM登录成功
return YES;
} else { //登录失败
NSLog(@"登录失败 error code :%d,error description:%@",error.code,error.errorDescription);
return NO;
}
return NO;
}- (BOOL)registerIMuser { //举个栗子。注册建议在服务端创建环信id与自己app的账号一一对应,\
而不要放到APP中,可以在登录自己APP时从返回的结果中获取环信账号再登录环信服务器
EMError *error = nil;
NSString *newUser = [self getrandomUsername];
self.Hxusername = newUser;
error = [[EMClient sharedClient] registerWithUsername:newUser password:@"123456"];
if (error &&  error.code != EMErrorUserAlreadyExist) {
NSLog(@"注册失败;error code:%d,error description :%@",error.code,error.errorDescription);
return NO;
}return YES;
}
//创建一个随机的用户名,这里是设备UUID来代替的​- (NSString *)getrandomUsername {
//第一种方法:
/*NSString *username = nil;
UIDevice *device = [UIDevice currentDevice];//创建设备对象
NSString *deviceUID = [[NSString alloc] initWithString:[[device identifierForVendor] UUIDString]];
if ([deviceUID length] == 0) {
CFUUIDRef uuid = CFUUIDCreate(NULL);
if (uuid)
{
deviceUID = (__bridge_transfer NSString *)CFUUIDCreateString(NULL, uuid);
CFRelease(uuid);
}
username = [deviceUID stringByReplacingOccurrencesOfString:@"-" withString:@""];
username = [username stringByAppendingString:[NSString stringWithFormat:@"%u",arc4random()0000]];
return username;*/
//第二种方法
//加上build ID是为了保证设备的唯一性,如果这里的buildID换了,设备的uuid也会变,这里的解决办法也就是放倒了钥匙串里面,不会因卸载程序,程序升级设备的标识会改变
NSString *SERVICE_NAME = NAVI_TEST_BUNDLE_ID;//最好用程序的bundle id
NSString * str =  [SFHFKeychainUtils getPasswordForUsername:@"UUID" andServiceName:SERVICE_NAME error:nil];  // 从keychain获取数据
if ([str length]<=0)
{
str  = [[[UIDevice currentDevice] identifierForVendor] UUIDString];  // 保存UUID作为手机唯一标识符[SFHFKeychainUtils storeUsername:@"UUID"   andPassword:str    forServiceName:SERVICE_NAME updateExisting:1  error:nil];  // 往keychain添加数据
}
str = [str stringByReplacingOccurrencesOfString:@"-" withString:@""];
return str;
}在这里用到了一个类来处理的UUID不变(APP卸载后不会改变)
SFHFKeychainUtils.h
#import@interface SFHFKeychainUtils : NSObject
+ (NSString *) getPasswordForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error;
+ (BOOL) storeUsername: (NSString *) username andPassword: (NSString *) password forServiceName: (NSString *) serviceName updateExisting: (BOOL) updateExisting error: (NSError **) error;
+ (BOOL) deleteItemForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error;
@endSFHFKeychainUtils.m​#import "SFHFKeychainUtils.h"
static NSString *SFHFKeychainUtilsErrorDomain = @"SFHFKeychainUtilsErrorDomain";
#if __IPHONE_OS_VERSION_MIN_REQUIRED < 30000 && TARGET_IPHONE_SIMULATOR
@interface SFHFKeychainUtils (PrivateMethods)
+ (SecKeychainItemRef) getKeychainItemReferenceForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error;
@end
#endif
@implementation SFHFKeychainUtils
#if __IPHONE_OS_VERSION_MIN_REQUIRED < 30000 && TARGET_IPHONE_SIMULATOR
+ (NSString *) getPasswordForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error { if (!username || !serviceName) { *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil]; return nil; }      SecKeychainItemRef item = [SFHFKeychainUtils getKeychainItemReferenceForUsername: username andServiceName: serviceName error: error];      if (*error || !item) { return nil; }
// from Advanced Mac OS X Programming, ch. 16
UInt32 length;
char *password;
SecKeychainAttribute attributes[8];
SecKeychainAttributeList list;
attributes[0].tag = kSecAccountItemAttr;
attributes[1].tag = kSecDescriptionItemAttr;
attributes[2].tag = kSecLabelItemAttr;
attributes[3].tag = kSecModDateItemAttr;
list.count = 4;
list.attr = attributes;
OSStatus status = SecKeychainItemCopyContent(item, NULL, &list, &length, (void **)&password);
if (status != noErr)
{
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
return nil;
}
NSString *passwordString = nil;
if (password != NULL)
{ char passwordBuffer[1024];
if (length > 1023) {length = 1023;
}
strncpy(passwordBuffer, password, length);
passwordBuffer[length] = '\0';
passwordString = [NSString stringWithCString:passwordBuffer];
}SecKeychainItemFreeContent(&list, password);CFRelease(item);return passwordString;
}
+ (void) storeUsername: (NSString *) username andPassword: (NSString *) password forServiceName: (NSString *) serviceName updateExisting: (BOOL) updateExisting error: (NSError **) error {
if (!username || !password || !serviceName) {
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil];
return;
}
OSStatus status = noErr;
SecKeychainItemRef item = [SFHFKeychainUtils getKeychainItemReferenceForUsername: username andServiceName: serviceName error: error];
if (*error && [*error code] != noErr) {
return;
}
*error = nil;
if (item) {
status = SecKeychainItemModifyAttributesAndData(item,
NULL,
strlen([password UTF8String]),
[password UTF8String]);
CFRelease(item);
}
else {
status = SecKeychainAddGenericPassword(NULL,
strlen([serviceName UTF8String]),
[serviceName UTF8String],
strlen([username UTF8String]),
[username UTF8String],
strlen([password UTF8String]),
[password UTF8String],
NULL);
}
if (status != noErr) {
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
}
}
+ (void) deleteItemForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error {
if (!username || !serviceName) {
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: 2000 userInfo: nil];
return;
}
*error = nil;
SecKeychainItemRef item = [SFHFKeychainUtils getKeychainItemReferenceForUsername: username andServiceName: serviceName error: error];
if (*error && [*error code] != noErr) {
return;
}
OSStatus status;
if (item) {
status = SecKeychainItemDelete(item);
CFRelease(item);
}
if (status != noErr) {
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
}
}
+ (SecKeychainItemRef) getKeychainItemReferenceForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error {
if (!username || !serviceName) {
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil];
return nil;
}
*error = nil;
SecKeychainItemRef item;
OSStatus status = SecKeychainFindGenericPassword(NULL,
strlen([serviceName UTF8String]),
[serviceName UTF8String],
strlen([username UTF8String]),
[username UTF8String],
NULL,
NULL,
&item);
if (status != noErr) {
if (status != errSecItemNotFound) {
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
}
return nil;
}
return item;
}
#else
+ (NSString *) getPasswordForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error {
if (!username || !serviceName) {
if (error != nil) {
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil];
}
return nil;
}
if (error != nil) {
*error = nil;
}
// Set up a query dictionary with the base query attributes: item type (generic), username, and service
NSArray *keys = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClass, kSecAttrAccount, kSecAttrService, nil];
NSArray *objects = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClassGenericPassword, username, serviceName, nil];
NSMutableDictionary *query = [[NSMutableDictionary alloc] initWithObjects: objects forKeys: keys];
// First do a query for attributes, in case we already have a Keychain item with no password data set.
// One likely way such an incorrect item could have come about is due to the previous (incorrect)
// version of this code (which set the password as a generic attribute instead of password data).
NSMutableDictionary *attributeQuery = [query mutableCopy];
[attributeQuery setObject: (id) kCFBooleanTrue forKey:(__bridge_transfer id) kSecReturnAttributes];
CFTypeRef attrResult = NULL;
OSStatus status = SecItemCopyMatching((__bridge_retained CFDictionaryRef) attributeQuery, &attrResult);
//NSDictionary *attributeResult = (__bridge_transfer NSDictionary *)attrResult;
if (status != noErr) {
// No existing item found--simply return nil for the password
if (error != nil && status != errSecItemNotFound) {
//Only return an error if a real exception happened--not simply for "not found."
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
}
return nil;
}
// We have an existing item, now query for the password data associated with it.
NSMutableDictionary *passwordQuery = [query mutableCopy];
[passwordQuery setObject: (id) kCFBooleanTrue forKey: (__bridge_transfer id) kSecReturnData];
CFTypeRef resData = NULL;
status = SecItemCopyMatching((__bridge_retained CFDictionaryRef) passwordQuery, (CFTypeRef *) &resData);
NSData *resultData = (__bridge_transfer NSData *)resData;
if (status != noErr) {
if (status == errSecItemNotFound) {
// We found attributes for the item previously, but no password now, so return a special error.
// Users of this API will probably want to detect this error and prompt the user to
// re-enter their credentials.  When you attempt to store the re-entered credentials
// using storeUsername:andPassword:forServiceName:updateExisting:error
// the old, incorrect entry will be deleted and a new one with a properly encrypted
// password will be added.
if (error != nil) {
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -1999 userInfo: nil];
}
}
else {
// Something else went wrong. Simply return the normal Keychain API error code.
if (error != nil) {
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
}
}
return nil;
}
NSString *password = nil;
if (resultData) {
password = [[NSString alloc] initWithData: resultData encoding: NSUTF8StringEncoding];
}
else {
// There is an existing item, but we weren't able to get password data for it for some reason,
// Possibly as a result of an item being incorrectly entered by the previous code.
// Set the -1999 error so the code above us can prompt the user again.
if (error != nil) {
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -1999 userInfo: nil];
}
}
return password;
}
+ (BOOL) storeUsername: (NSString *) username andPassword: (NSString *) password forServiceName: (NSString *) serviceName updateExisting: (BOOL) updateExisting error: (NSError **) error
{
if (!username || !password || !serviceName)
{
if (error != nil)
{
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil];
}
return NO;
}
// See if we already have a password entered for these credentials.
NSError *getError = nil;
NSString *existingPassword = [SFHFKeychainUtils getPasswordForUsername: username andServiceName: serviceName error:&getError];
if ([getError code] == -1999)
{
// There is an existing entry without a password properly stored (possibly as a result of the previous incorrect version of this code.
// Delete the existing item before moving on entering a correct one.
getError = nil;
[self deleteItemForUsername: username andServiceName: serviceName error: &getError];
if ([getError code] != noErr)
{
if (error != nil)
{
*error = getError;
}
return NO;
}
}
else if ([getError code] != noErr)
{
if (error != nil)
{
*error = getError;
}
return NO;
}
if (error != nil)
{
*error = nil;
}
OSStatus status = noErr;
if (existingPassword)
{
// We have an existing, properly entered item with a password.
// Update the existing item.
if (![existingPassword isEqualToString:password] && updateExisting)
{
//Only update if we're allowed to update existing.  If not, simply do nothing.
NSArray *keys = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClass,
kSecAttrService,
kSecAttrLabel,
kSecAttrAccount,
nil];
NSArray *objects = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClassGenericPassword,
serviceName,
serviceName,
username,
nil];
NSDictionary *query = [[NSDictionary alloc] initWithObjects: objects forKeys: keys];
status = SecItemUpdate((__bridge_retained CFDictionaryRef) query, (__bridge_retained CFDictionaryRef) [NSDictionary dictionaryWithObject: [password dataUsingEncoding: NSUTF8StringEncoding] forKey: (__bridge_transfer NSString *) kSecValueData]);
}
}
else
{
// No existing entry (or an existing, improperly entered, and therefore now
// deleted, entry).  Create a new entry.
NSArray *keys = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClass,
kSecAttrService,
kSecAttrLabel,
kSecAttrAccount,
kSecValueData,
nil];
NSArray *objects = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClassGenericPassword,
serviceName,
serviceName,
username,
[password dataUsingEncoding: NSUTF8StringEncoding],
nil];
NSDictionary *query = [[NSDictionary alloc] initWithObjects: objects forKeys: keys];
status = SecItemAdd((__bridge_retained CFDictionaryRef) query, NULL);
}
if (error != nil && status != noErr)
{
// Something went wrong with adding the new item. Return the Keychain error code.
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
return NO;
}
return YES;
}
+ (BOOL) deleteItemForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error
{
if (!username || !serviceName)
{
if (error != nil)
{
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil];
}
return NO;
}
if (error != nil)
{
*error = nil;
}
NSArray *keys = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClass, kSecAttrAccount, kSecAttrService, kSecReturnAttributes, nil];
NSArray *objects = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClassGenericPassword, username, serviceName, kCFBooleanTrue, nil];
NSDictionary *query = [[NSDictionary alloc] initWithObjects: objects forKeys: keys];
OSStatus status = SecItemDelete((__bridge_retained CFDictionaryRef) query);
if (error != nil && status != noErr)
{
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
return NO;
}
return YES;
}
#endif
@end 查看全部
 
   最近搞环信聊天,需求是游客身份也可以进行聊天,当用户注册了我们的APP后也需要把游客身份切换过来进行聊天,首先我们的环信注册,登录全都放前段处理了,下面就按照我们的需求逻辑来如何切换游客。
 
   1.APP用户的注册,也就注册环信,APP的登录返回的有用户ID,这个时候并没有让他登录环信,只是保存了返回的ID,下面就是用ID来判断该用户是否注册过环信的依据
 
大致说明一下,代码中用到一个类来保证uuid不会改变的状态,为防止app卸载后uuid的改变,我们把他存储到钥匙串里面来保存

下面用图来表示
我们先来看下整个身份切换实现的逻辑图
4861502-fa2f7d87d00c78d7.jpg


下面就上代码了,第一步从图中第一步来说判断userID是否存在

这个地方是在点击聊天按钮开始判断的
 
-(void)releaseInfo:(UIButton*)sender{
NSString*Hxusername=[userdic objectForKey:@"useid"];//获取保存的userID
NSString*phonestr=  [[NSUserDefaults standardUserDefaults]objectForKey:@"phonenum"];
NSString*chatid=[[phonestr md5String]substringFromIndex:16];//这个是获取客服的欢信ID
//单例里面处理用户是否登录,以及游客随机分配uuid来注册环信IM号
DataManager*datamage= [DataManager shareDataManager];
//判断用户ID是否存在,也就证明是否注册过环信
if (Hxusername.length>0) {
if ([datamage loginKefuSDK])//判断用户是否登录
{//单聊
ChattingViewController *chatController = [[ChattingViewController alloc] initWithConversationChatter:chatid conversationType:EMConversationTypeChat];[self.navigationController pushViewController:chatController animated:YES];
}
}else{
//游客身份的判断
if ([datamage customelogin]) {
//单聊
ChattingViewController *chatController = [[ChattingViewController alloc] initWithConversationChatter:chatid conversationType:EMConversationTypeChat];[self.navigationController pushViewController:chatController animated:YES];
}
}
}上面这是按钮方法里面的数据下面来说,DataManager*datamage= [DataManager shareDataManager];这个单利的方法
DataManager.h
@interface DataManager : NSObject
-(BOOL)customelogin;//判断游客之前是否有登录
-(void)requestchattphone;//获取美容院客服聊天的对象电话
@end
DataManager.m


@implementation DataManager
+(instancetype)shareDataManager{
static DataManager *manager;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
manager = [[DataManager alloc] init];
});
return manager;
}
//userID存在的时候 登录IM
- (BOOL)loginKefuSDK {
NSDictionary*userdic=[[NSUserDefaults standardUserDefaults]objectForKey:@"userMessage"];//接受用户是否登录
NSString*loguser=[NSString stringWithFormat:@"%@",[userdic objectForKey:@"useid"] ];
EMClient *client = [EMClient sharedClient];
//用户已经登录
if (client.isLoggedIn) {
if ([loguser isEqualToString:client.currentUsername])//当前登录用户的ID和即将要登录人的ID是否一样
{
return YES;
}else
{
EMError *error = [[EMClient sharedClient] logout:YES];
if (!error) {
NSLog(@"退出成功");
}
}
}//这里APP用户登录环信的密码统统是123456
EMError *error = [[EMClient sharedClient] loginWithUsername:loguser password:@"123456"];
if (!error) { //IM登录成功
return YES;
} else { //登录失败
NSLog(@"登录失败 error code :%d,error description:%@",error.code,error.errorDescription);
return NO;
}
return NO;
}
//游客身份的登录方法
-(BOOL)customelogin
{
EMClient *client = [EMClient sharedClient];
//用户已经登录
if (client.isLoggedIn) {
return YES;
}//该用户没有注册,来用改设备UUID来给用户注册环信,并登录环信
if (![self registerIMuser]) {
return NO;
}
EMError *error = [[EMClient sharedClient] loginWithUsername:self.Hxusername password:@"123456"];
if (!error) { //IM登录成功
return YES;
} else { //登录失败
NSLog(@"登录失败 error code :%d,error description:%@",error.code,error.errorDescription);
return NO;
}
return NO;
}
- (BOOL)registerIMuser { //举个栗子。注册建议在服务端创建环信id与自己app的账号一一对应,\
而不要放到APP中,可以在登录自己APP时从返回的结果中获取环信账号再登录环信服务器
EMError *error = nil;
NSString *newUser = [self getrandomUsername];
self.Hxusername = newUser;
error = [[EMClient sharedClient] registerWithUsername:newUser password:@"123456"];
if (error &&  error.code != EMErrorUserAlreadyExist) {
NSLog(@"注册失败;error code:%d,error description :%@",error.code,error.errorDescription);
return NO;
}return YES;
}
//创建一个随机的用户名,这里是设备UUID来代替的​
- (NSString *)getrandomUsername {
//第一种方法:
/*NSString *username = nil;
UIDevice *device = [UIDevice currentDevice];//创建设备对象
NSString *deviceUID = [[NSString alloc] initWithString:[[device identifierForVendor] UUIDString]];
if ([deviceUID length] == 0) {
CFUUIDRef uuid = CFUUIDCreate(NULL);
if (uuid)
{
deviceUID = (__bridge_transfer NSString *)CFUUIDCreateString(NULL, uuid);
CFRelease(uuid);
}
username = [deviceUID stringByReplacingOccurrencesOfString:@"-" withString:@""];
username = [username stringByAppendingString:[NSString stringWithFormat:@"%u",arc4random()0000]];
return username;*/
//第二种方法
//加上build ID是为了保证设备的唯一性,如果这里的buildID换了,设备的uuid也会变,这里的解决办法也就是放倒了钥匙串里面,不会因卸载程序,程序升级设备的标识会改变
NSString *SERVICE_NAME = NAVI_TEST_BUNDLE_ID;//最好用程序的bundle id
NSString * str =  [SFHFKeychainUtils getPasswordForUsername:@"UUID" andServiceName:SERVICE_NAME error:nil];  // 从keychain获取数据
if ([str length]<=0)
{
str  = [[[UIDevice currentDevice] identifierForVendor] UUIDString];  // 保存UUID作为手机唯一标识符[SFHFKeychainUtils storeUsername:@"UUID"   andPassword:str    forServiceName:SERVICE_NAME updateExisting:1  error:nil];  // 往keychain添加数据
}
str = [str stringByReplacingOccurrencesOfString:@"-" withString:@""];
return str;
}在这里用到了一个类来处理的UUID不变(APP卸载后不会改变)
SFHFKeychainUtils.h
#import@interface SFHFKeychainUtils : NSObject
+ (NSString *) getPasswordForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error;
+ (BOOL) storeUsername: (NSString *) username andPassword: (NSString *) password forServiceName: (NSString *) serviceName updateExisting: (BOOL) updateExisting error: (NSError **) error;
+ (BOOL) deleteItemForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error;
@end
SFHFKeychainUtils.m​
#import "SFHFKeychainUtils.h"
static NSString *SFHFKeychainUtilsErrorDomain = @"SFHFKeychainUtilsErrorDomain";
#if __IPHONE_OS_VERSION_MIN_REQUIRED < 30000 && TARGET_IPHONE_SIMULATOR
@interface SFHFKeychainUtils (PrivateMethods)
+ (SecKeychainItemRef) getKeychainItemReferenceForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error;
@end
#endif
@implementation SFHFKeychainUtils
#if __IPHONE_OS_VERSION_MIN_REQUIRED < 30000 && TARGET_IPHONE_SIMULATOR
+ (NSString *) getPasswordForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error { if (!username || !serviceName) { *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil]; return nil; }      SecKeychainItemRef item = [SFHFKeychainUtils getKeychainItemReferenceForUsername: username andServiceName: serviceName error: error];      if (*error || !item) { return nil; }
// from Advanced Mac OS X Programming, ch. 16
UInt32 length;
char *password;
SecKeychainAttribute attributes[8];
SecKeychainAttributeList list;
attributes[0].tag = kSecAccountItemAttr;
attributes[1].tag = kSecDescriptionItemAttr;
attributes[2].tag = kSecLabelItemAttr;
attributes[3].tag = kSecModDateItemAttr;
list.count = 4;
list.attr = attributes;
OSStatus status = SecKeychainItemCopyContent(item, NULL, &list, &length, (void **)&password);
if (status != noErr)
{
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
return nil;
}
NSString *passwordString = nil;
if (password != NULL)
{ char passwordBuffer[1024];
if (length > 1023) {length = 1023;
}
strncpy(passwordBuffer, password, length);
passwordBuffer[length] = '\0';
passwordString = [NSString stringWithCString:passwordBuffer];
}SecKeychainItemFreeContent(&list, password);CFRelease(item);return passwordString;
}
+ (void) storeUsername: (NSString *) username andPassword: (NSString *) password forServiceName: (NSString *) serviceName updateExisting: (BOOL) updateExisting error: (NSError **) error {
if (!username || !password || !serviceName) {
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil];
return;
}
OSStatus status = noErr;
SecKeychainItemRef item = [SFHFKeychainUtils getKeychainItemReferenceForUsername: username andServiceName: serviceName error: error];
if (*error && [*error code] != noErr) {
return;
}
*error = nil;
if (item) {
status = SecKeychainItemModifyAttributesAndData(item,
NULL,
strlen([password UTF8String]),
[password UTF8String]);
CFRelease(item);
}
else {
status = SecKeychainAddGenericPassword(NULL,
strlen([serviceName UTF8String]),
[serviceName UTF8String],
strlen([username UTF8String]),
[username UTF8String],
strlen([password UTF8String]),
[password UTF8String],
NULL);
}
if (status != noErr) {
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
}
}
+ (void) deleteItemForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error {
if (!username || !serviceName) {
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: 2000 userInfo: nil];
return;
}
*error = nil;
SecKeychainItemRef item = [SFHFKeychainUtils getKeychainItemReferenceForUsername: username andServiceName: serviceName error: error];
if (*error && [*error code] != noErr) {
return;
}
OSStatus status;
if (item) {
status = SecKeychainItemDelete(item);
CFRelease(item);
}
if (status != noErr) {
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
}
}
+ (SecKeychainItemRef) getKeychainItemReferenceForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error {
if (!username || !serviceName) {
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil];
return nil;
}
*error = nil;
SecKeychainItemRef item;
OSStatus status = SecKeychainFindGenericPassword(NULL,
strlen([serviceName UTF8String]),
[serviceName UTF8String],
strlen([username UTF8String]),
[username UTF8String],
NULL,
NULL,
&item);
if (status != noErr) {
if (status != errSecItemNotFound) {
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
}
return nil;
}
return item;
}
#else
+ (NSString *) getPasswordForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error {
if (!username || !serviceName) {
if (error != nil) {
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil];
}
return nil;
}
if (error != nil) {
*error = nil;
}
// Set up a query dictionary with the base query attributes: item type (generic), username, and service
NSArray *keys = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClass, kSecAttrAccount, kSecAttrService, nil];
NSArray *objects = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClassGenericPassword, username, serviceName, nil];
NSMutableDictionary *query = [[NSMutableDictionary alloc] initWithObjects: objects forKeys: keys];
// First do a query for attributes, in case we already have a Keychain item with no password data set.
// One likely way such an incorrect item could have come about is due to the previous (incorrect)
// version of this code (which set the password as a generic attribute instead of password data).
NSMutableDictionary *attributeQuery = [query mutableCopy];
[attributeQuery setObject: (id) kCFBooleanTrue forKey:(__bridge_transfer id) kSecReturnAttributes];
CFTypeRef attrResult = NULL;
OSStatus status = SecItemCopyMatching((__bridge_retained CFDictionaryRef) attributeQuery, &attrResult);
//NSDictionary *attributeResult = (__bridge_transfer NSDictionary *)attrResult;
if (status != noErr) {
// No existing item found--simply return nil for the password
if (error != nil && status != errSecItemNotFound) {
//Only return an error if a real exception happened--not simply for "not found."
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
}
return nil;
}
// We have an existing item, now query for the password data associated with it.
NSMutableDictionary *passwordQuery = [query mutableCopy];
[passwordQuery setObject: (id) kCFBooleanTrue forKey: (__bridge_transfer id) kSecReturnData];
CFTypeRef resData = NULL;
status = SecItemCopyMatching((__bridge_retained CFDictionaryRef) passwordQuery, (CFTypeRef *) &resData);
NSData *resultData = (__bridge_transfer NSData *)resData;
if (status != noErr) {
if (status == errSecItemNotFound) {
// We found attributes for the item previously, but no password now, so return a special error.
// Users of this API will probably want to detect this error and prompt the user to
// re-enter their credentials.  When you attempt to store the re-entered credentials
// using storeUsername:andPassword:forServiceName:updateExisting:error
// the old, incorrect entry will be deleted and a new one with a properly encrypted
// password will be added.
if (error != nil) {
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -1999 userInfo: nil];
}
}
else {
// Something else went wrong. Simply return the normal Keychain API error code.
if (error != nil) {
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
}
}
return nil;
}
NSString *password = nil;
if (resultData) {
password = [[NSString alloc] initWithData: resultData encoding: NSUTF8StringEncoding];
}
else {
// There is an existing item, but we weren't able to get password data for it for some reason,
// Possibly as a result of an item being incorrectly entered by the previous code.
// Set the -1999 error so the code above us can prompt the user again.
if (error != nil) {
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -1999 userInfo: nil];
}
}
return password;
}
+ (BOOL) storeUsername: (NSString *) username andPassword: (NSString *) password forServiceName: (NSString *) serviceName updateExisting: (BOOL) updateExisting error: (NSError **) error
{
if (!username || !password || !serviceName)
{
if (error != nil)
{
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil];
}
return NO;
}
// See if we already have a password entered for these credentials.
NSError *getError = nil;
NSString *existingPassword = [SFHFKeychainUtils getPasswordForUsername: username andServiceName: serviceName error:&getError];
if ([getError code] == -1999)
{
// There is an existing entry without a password properly stored (possibly as a result of the previous incorrect version of this code.
// Delete the existing item before moving on entering a correct one.
getError = nil;
[self deleteItemForUsername: username andServiceName: serviceName error: &getError];
if ([getError code] != noErr)
{
if (error != nil)
{
*error = getError;
}
return NO;
}
}
else if ([getError code] != noErr)
{
if (error != nil)
{
*error = getError;
}
return NO;
}
if (error != nil)
{
*error = nil;
}
OSStatus status = noErr;
if (existingPassword)
{
// We have an existing, properly entered item with a password.
// Update the existing item.
if (![existingPassword isEqualToString:password] && updateExisting)
{
//Only update if we're allowed to update existing.  If not, simply do nothing.
NSArray *keys = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClass,
kSecAttrService,
kSecAttrLabel,
kSecAttrAccount,
nil];
NSArray *objects = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClassGenericPassword,
serviceName,
serviceName,
username,
nil];
NSDictionary *query = [[NSDictionary alloc] initWithObjects: objects forKeys: keys];
status = SecItemUpdate((__bridge_retained CFDictionaryRef) query, (__bridge_retained CFDictionaryRef) [NSDictionary dictionaryWithObject: [password dataUsingEncoding: NSUTF8StringEncoding] forKey: (__bridge_transfer NSString *) kSecValueData]);
}
}
else
{
// No existing entry (or an existing, improperly entered, and therefore now
// deleted, entry).  Create a new entry.
NSArray *keys = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClass,
kSecAttrService,
kSecAttrLabel,
kSecAttrAccount,
kSecValueData,
nil];
NSArray *objects = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClassGenericPassword,
serviceName,
serviceName,
username,
[password dataUsingEncoding: NSUTF8StringEncoding],
nil];
NSDictionary *query = [[NSDictionary alloc] initWithObjects: objects forKeys: keys];
status = SecItemAdd((__bridge_retained CFDictionaryRef) query, NULL);
}
if (error != nil && status != noErr)
{
// Something went wrong with adding the new item. Return the Keychain error code.
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
return NO;
}
return YES;
}
+ (BOOL) deleteItemForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error
{
if (!username || !serviceName)
{
if (error != nil)
{
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil];
}
return NO;
}
if (error != nil)
{
*error = nil;
}
NSArray *keys = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClass, kSecAttrAccount, kSecAttrService, kSecReturnAttributes, nil];
NSArray *objects = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClassGenericPassword, username, serviceName, kCFBooleanTrue, nil];
NSDictionary *query = [[NSDictionary alloc] initWithObjects: objects forKeys: keys];
OSStatus status = SecItemDelete((__bridge_retained CFDictionaryRef) query);
if (error != nil && status != noErr)
{
*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
return NO;
}
return YES;
}
#endif
@end
0
评论

炸窝了,苹果禁止使用热更新 iOS 热更新

新闻资讯 发表了文章 • 323 次浏览 • 2017-03-09 11:41 • 来自相关话题

今天一早,不少iOS开发群都炸窝了,原因是部分iOS开发者收到了苹果的警告邮件:




有开发者质疑可能是项目中使用了JSPatch、weex以及ReactNative等热更新技术。对于修复bug提交审核的开发者来说,热更新技术可以帮开发者避免长时间的审核等待以及多次被拒造成的成本开销。但也给黑客留了后门,也就违反了苹果的安全和隐私政策。

不过这次苹果只是对使用热更新的应用进行了警告,并没有开发者反应产品因此问题被下架。

对此,开发者表示:

舞小月:苹果注重的就是流畅性和用户体验,混编做的东西肯定没有native的流畅,这就违背了苹果本来的意愿,被禁也是正常的,而且苹果自己的蛋糕为何要分给竞争对手?以前没混编的时候你该怎么做不还是做了,现在没有,不代表以后没有,就像之前没有混编,后来有了混编。新的框架苹果自然也会去完善,苹果既然做了这个决定,他肯定会优化自己的东西。

Gilbertat:苹果爸爸会不会在自己的生态中搞死js啊

luohui8891:我们也是昨天收到的,目前没有什么对策。我们的APP只是用JSPatch做热修复,并不修改应用的功能行为等(但我觉得Apple并不care这个)。

lsllsllsl:没用RN没用JSPatch,同样收到警告。

luohui8891:@tcathy 根据邮件里说是你下次提交前请去掉这样远程下载代码运行的机制。所以应该就是下个版本如果不删除就reject

Loooren:早上收到邮件,itunesconnect站内信,电话通知....用到了weex

xiaofuyesnew:昨天晚上微软发布了Visual Studio 2017,自带基于React Native的iOS开发功能。鉴于微软这两年来开源的力度,发布这一功能似乎是在抢占开发者的市场,基于vs2017,在非苹果上开发ios应用更容易了。所以,苹果在这个节骨眼发出这个警告邮件,就有点威胁现有开发者的意思。暗地里想跟微软互怼。

对于那些已经在学习RN、weex、JSPatch的同学来说,这是个悲惨的故事









从苹果的角度看,禁止应用使用热更新技术更多是为了保护用户隐私、数据安全以及其全力打造的生态圈。对于用户来说,出于安全起见,应谨慎授予应用权限;对于开发者来说,为了审核以及长远的用户体验考虑,不要轻易触碰苹果拉的那条红线。




以上内容来源于CocoaChina,GitHub 查看全部
今天一早,不少iOS开发群都炸窝了,原因是部分iOS开发者收到了苹果的警告邮件:
001.png

有开发者质疑可能是项目中使用了JSPatch、weex以及ReactNative等热更新技术。对于修复bug提交审核的开发者来说,热更新技术可以帮开发者避免长时间的审核等待以及多次被拒造成的成本开销。但也给黑客留了后门,也就违反了苹果的安全和隐私政策。

不过这次苹果只是对使用热更新的应用进行了警告,并没有开发者反应产品因此问题被下架。

对此,开发者表示:

舞小月:苹果注重的就是流畅性和用户体验,混编做的东西肯定没有native的流畅,这就违背了苹果本来的意愿,被禁也是正常的,而且苹果自己的蛋糕为何要分给竞争对手?以前没混编的时候你该怎么做不还是做了,现在没有,不代表以后没有,就像之前没有混编,后来有了混编。新的框架苹果自然也会去完善,苹果既然做了这个决定,他肯定会优化自己的东西。

Gilbertat:苹果爸爸会不会在自己的生态中搞死js啊

luohui8891:我们也是昨天收到的,目前没有什么对策。我们的APP只是用JSPatch做热修复,并不修改应用的功能行为等(但我觉得Apple并不care这个)。

lsllsllsl:没用RN没用JSPatch,同样收到警告。

luohui8891:@tcathy 根据邮件里说是你下次提交前请去掉这样远程下载代码运行的机制。所以应该就是下个版本如果不删除就reject

Loooren:早上收到邮件,itunesconnect站内信,电话通知....用到了weex

xiaofuyesnew:昨天晚上微软发布了Visual Studio 2017,自带基于React Native的iOS开发功能。鉴于微软这两年来开源的力度,发布这一功能似乎是在抢占开发者的市场,基于vs2017,在非苹果上开发ios应用更容易了。所以,苹果在这个节骨眼发出这个警告邮件,就有点威胁现有开发者的意思。暗地里想跟微软互怼。

对于那些已经在学习RN、weex、JSPatch的同学来说,这是个悲惨的故事
002.png


003.png

从苹果的角度看,禁止应用使用热更新技术更多是为了保护用户隐私、数据安全以及其全力打造的生态圈。对于用户来说,出于安全起见,应谨慎授予应用权限;对于开发者来说,为了审核以及长远的用户体验考虑,不要轻易触碰苹果拉的那条红线。
004.png

以上内容来源于CocoaChinaGitHub
3
评论

Android ios V3.3.0 SDK 已发布,增加群组、聊天室管理员权限 iOS Android 产品更新

产品更新 发表了文章 • 501 次浏览 • 2017-03-08 16:37 • 来自相关话题

 Android​ V3.3.0 2017-03-07
 
新功能:
群组和聊天室改造:增加管理员权限,新增禁言,增减管理员的功能,支持使用分批的方式获取成员,禁言,管理员列表,支持完善的聊天室功能。新增加API请查看链接3.3.0 api修改优化dns劫持时的处理增加EMConversation.latestMessageFromOthers,表示收到对方的最后一条消息增加EMClient.compressLogs,压缩log,Demo中增加通过邮件发送log的示例libs.without.audio继续支持armeabi,解决armeabi-v5te的支持问题

bug 修订:
修复2.x升级3.x消息未读数为0的bugDemo在视频通话时,主叫方铃声没有播放的问题Demo在视频通话时,主叫方在建立连接成功后,文字提示不正确Demo在聊天窗口界面,清空消息后,收到新的消息,返回会话列表,未读消息数显示不正确修复在Oppo和Vivo手机上出现的JobService报错。EMGroupManager.createGroup成员列表数超过512产生的overflow错误修复部分手机在网络切换时发消息慢的bug
 
ios V3.3.0 2017-03-07
 
新功能:
新增:群组改造,增加一系列新接口,具体查看iOS iOS 3.3.0 api修改新增:获取SDK日志路径接口,将日志文件压缩成.gz文件,返回gz文件路径,[EMClient getLogFilesPath:]更新:使用视频通话录制功能时,必须在开始通话之前调用[EMVideoRecorderPlugin initGlobalConfig]

优化:
优化DNS劫持时的处理切换网络时,减小消息重发的等待时间

修复:
音视频通话丢包率(以前返回的是丢包数)IOS动态库用H264编码在iPhone6s上崩溃实时音视频新旧版互通崩溃
 
版本历史:Android SDK更新日志  ios SDK更新日志
下载地址:SDK下载 查看全部

7658.jpg_wh860_.jpg

 Android​ V3.3.0 2017-03-07
 
新功能:
  1. 群组和聊天室改造:增加管理员权限,新增禁言,增减管理员的功能,支持使用分批的方式获取成员,禁言,管理员列表,支持完善的聊天室功能。新增加API请查看链接3.3.0 api修改
  2. 优化dns劫持时的处理
  3. 增加EMConversation.latestMessageFromOthers,表示收到对方的最后一条消息
  4. 增加EMClient.compressLogs,压缩log,Demo中增加通过邮件发送log的示例
  5. libs.without.audio继续支持armeabi,解决armeabi-v5te的支持问题


bug 修订:
  1. 修复2.x升级3.x消息未读数为0的bug
  2. Demo在视频通话时,主叫方铃声没有播放的问题
  3. Demo在视频通话时,主叫方在建立连接成功后,文字提示不正确
  4. Demo在聊天窗口界面,清空消息后,收到新的消息,返回会话列表,未读消息数显示不正确
  5. 修复在Oppo和Vivo手机上出现的JobService报错。
  6. EMGroupManager.createGroup成员列表数超过512产生的overflow错误
  7. 修复部分手机在网络切换时发消息慢的bug

 
ios V3.3.0 2017-03-07
 
新功能:
  1. 新增:群组改造,增加一系列新接口,具体查看iOS iOS 3.3.0 api修改
  2. 新增:获取SDK日志路径接口,将日志文件压缩成.gz文件,返回gz文件路径,[EMClient getLogFilesPath:]
  3. 更新:使用视频通话录制功能时,必须在开始通话之前调用[EMVideoRecorderPlugin initGlobalConfig]


优化:
  1. 优化DNS劫持时的处理
  2. 切换网络时,减小消息重发的等待时间


修复:
  1. 音视频通话丢包率(以前返回的是丢包数)
  2. IOS动态库用H264编码在iPhone6s上崩溃
  3. 实时音视频新旧版互通崩溃

 
版本历史:Android SDK更新日志  ios SDK更新日志
下载地址:SDK下载
1
评论

淘宝购物车界面背后的逻辑及实现源码,欢迎Star! iOS

不死小强 发表了文章 • 743 次浏览 • 2017-02-22 17:16 • 来自相关话题

ViewController: 购物车界面
整个界面就是TableView + 底部结账栏View组成





以店铺为section:商店下的商品为row和店铺名称组成一个 section

定制段头的View 把section的全选按钮、点击商品、编辑的三个按钮的方法用代理的方法。

-(UIView*)tableView:(UITableView*)tableView viewForHeaderInSection:(NSInteger)section;





建议使用Masonry进行cell适配

cell的创建就是和我们平常的一样,把要展示的样式代码编写或者xib都可以。再把数据源填充到我们所创建好的cell中和段头上。
创建好一个View添加在TableView的下方。View上写上全选及总金额等UI。每次我们选定的物品的增减都要调用该View赋值的方法,刷新金额等字段显示。





Cell:物品栏

创建两种cell,一个是正常的物品显示cell,另一个cell是编辑后的cell。

正常的cell:只说下label中划线的实现
//中划线


NSDictionary *attribtDic = @{NSStrikethroughStyleAttributeName: [NSNumber numberWithInteger:NSUnderlineStyleSingle]};

NSMutableAttributedString *attribtStr = [[NSMutableAttributedString alloc]initWithString:info[@"GoodsOldPrice"] attributes:attribtDic];

// 赋值

_Goods_OldPrice.attributedText = attribtStr;

编辑后的cell:

主要由三部分组成。商品数量 + 商品种类 + 删除




商品数量用的是第三方PPNumberButton,点击时改变model中该商品的实际数量;点击商品种类进行重新选择(方法未实现,原理一样);点击删除进行cell的删除及数据源的删除。

注释:这里的删除 及 修改 都是要对数据源进行修改在刷新的

Model:数据源的处理及购物车内各类按钮的判断

demo中的数据源我没有放到model中去处理,其实原理都一样,我把判断各类按钮的判断字段加到数据源中去了,如果用model模型的话,自己加上相对应的字段,并设置初始值。当进行model数据源的修改时,直接进行修改。





这是一种model处理方式,还有一种就是用JsonModel来处理,一层层的写下来,原理一样。 
购物车逻辑及实现总结

逻辑整理:当我们把有购买意向的物品加到购物车后,我们在购物车中调用接口获取购物车中的物品信息。数据源格式大概是(感觉不怎么对,但是能理解就行)

[

{@“店铺信息”:[@{物品信息},@{物品信息},@{物品信息}]},  -------》组一

{@"店铺信息":[@{物品信息}]},                                              -------》组二

{@”店铺信息“:[@{物品信息},@{物品信息}]}                        -------》组三

]

把数据源用model装起来,把数据填充到tableview中去。

1.单个商品的选择、单个店铺内所有商品的选择、结账栏下的全选                      

如果做得很简单的话,可以直接用系统的单选和全选方法。

最重要的两句 !!!!

TableDemo.editing=YES;      编辑状态

TableDemo.allowsMultipleSelectionDuringEditing=YES;   编辑的时候多选


cell.tintColor= [UIColorredColor];   选中后的颜色


选中和取消选中


-(void)tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath    选中


-(void)tableView:(UITableView*)tableView didDeselectRowAtIndexPath:(NSIndexPath*)indexPath    取消选中





如果不用系统的话,则利用model来进行单选和全选的操作,选中和取消选中对model中的判断字段进行修改,刷新当前cell或者section。

//一个section刷新


NSIndexSet *indexSet=[[NSIndexSet alloc]initWithIndex:section];


[tableview reloadSections:indexSet


withRowAnimation:UITableViewRowAnimationAutomatic];


//一个cell刷新


NSIndexPath *indexPath=[NSIndexPath indexPathForRow:row inSection:section];


[tableView reloadRowsAtIndexPaths:[NSArray arrayWithObjects:indexPath,nil] withRowAnimation:UITableViewRowAnimationNone];


这里需要注意的是,每次单选和全选的时候,需要对底部结账栏进行数据的刷新,并且单选完整组后,需要对section上的按钮进行选中状态变化,当选中section时,同样需要对section下的row进行选中状态,如果全部商品选中,还得需要改变结账栏的状态。(这里其实不难,无非就是对model中各类字段进行改变,再刷新,同时判断选中数量的多少来进行按钮的状态变换)

2.删除单个商品

删除的话就相对容易了,直接对数据源删除对应的row,当只剩一个后,删除该数据后,记得删除该组section不然报错。删除后及时刷新底部结账栏金额显示。

3.编辑section

点击编辑按钮,修改model,展示编辑cell。编辑按钮上放了数量计数器、商品的信息、删除。

计数器:用到的是好友的一个库PPNumberButton 喜欢的大家可以去玩玩。点击后用代理方法把数量的变化跟新model。

商品信息:点击对商品的信息进行重新选择,同样修改数据源。

删除:代理出来进行model的修改。

因为这里用的是假数据,所以进行的都是对数据源的修改,正常情况下,原理都一样,可以在次基础上,如果接口成功,就对本地数据进行修改,最后提交的信息会和后台匹配一次的,如果有问题,可以自己修改一下。

有小伙伴提出demo中没有下拉刷新,其实下拉刷新不影响该demo。不过加上效果更好。

谢谢“爱在巴黎梦醒时”该小伙伴。
demo的bug注释:

因为demo中判断section的全选和编辑的按钮都是放在每个section的第一个row中的,所以删除section的第一个row后,会有全选和编辑的固定bug出来。特此声明,该bug不影响主体逻辑,如次bug影响小伙伴对逻辑思路的学习,那我后面再重新组织数据源。
demo纯代码编写的,只隔离了部分模块,因为我也是拿来练练手,所以如果有需要,后续我会把购物车模块化。如果内容有不妥和臃肿的地方,大家可以提出来,我及时学习并修改。如果大家有意见的可以@我1804094055qq.com。

项目源码Git地址https://github.com/zl645420646/-ZLShoppingCart
欢迎Star! 查看全部
淘宝.gif


ViewController: 购物车界面
整个界面就是TableView + 底部结账栏View组成

3899794-1ec96767b3e7afd5.png

以店铺为section:商店下的商品为row和店铺名称组成一个 section

定制段头的View 把section的全选按钮、点击商品、编辑的三个按钮的方法用代理的方法。

-(UIView*)tableView:(UITableView*)tableView viewForHeaderInSection:(NSInteger)section;



3899794-6ef9165898df88f7.png

建议使用Masonry进行cell适配

cell的创建就是和我们平常的一样,把要展示的样式代码编写或者xib都可以。再把数据源填充到我们所创建好的cell中和段头上。
创建好一个View添加在TableView的下方。View上写上全选及总金额等UI。每次我们选定的物品的增减都要调用该View赋值的方法,刷新金额等字段显示。

3899794-700992bcfec1fcb0.png

Cell:物品栏

创建两种cell,一个是正常的物品显示cell,另一个cell是编辑后的cell。

正常的cell:只说下label中划线的实现

//中划线


NSDictionary *attribtDic = @{NSStrikethroughStyleAttributeName: [NSNumber numberWithInteger:NSUnderlineStyleSingle]};

NSMutableAttributedString *attribtStr = [[NSMutableAttributedString alloc]initWithString:info[@"GoodsOldPrice"] attributes:attribtDic];

// 赋值

_Goods_OldPrice.attributedText = attribtStr;



编辑后的cell:

主要由三部分组成。商品数量 + 商品种类 + 删除
3899794-6ce64a551798a1eb.png

商品数量用的是第三方PPNumberButton,点击时改变model中该商品的实际数量;点击商品种类进行重新选择(方法未实现,原理一样);点击删除进行cell的删除及数据源的删除。

注释:这里的删除 及 修改 都是要对数据源进行修改在刷新的

Model:数据源的处理及购物车内各类按钮的判断

demo中的数据源我没有放到model中去处理,其实原理都一样,我把判断各类按钮的判断字段加到数据源中去了,如果用model模型的话,自己加上相对应的字段,并设置初始值。当进行model数据源的修改时,直接进行修改。

3899794-272f9ea50f05377f.png

这是一种model处理方式,还有一种就是用JsonModel来处理,一层层的写下来,原理一样。 

购物车逻辑及实现总结

逻辑整理:当我们把有购买意向的物品加到购物车后,我们在购物车中调用接口获取购物车中的物品信息。数据源格式大概是(感觉不怎么对,但是能理解就行)

[

{@“店铺信息”:[@{物品信息},@{物品信息},@{物品信息}]},  -------》组一

{@"店铺信息":[@{物品信息}]},                                              -------》组二

{@”店铺信息“:[@{物品信息},@{物品信息}]}                        -------》组三

]

把数据源用model装起来,把数据填充到tableview中去。

1.单个商品的选择、单个店铺内所有商品的选择、结账栏下的全选                      

如果做得很简单的话,可以直接用系统的单选和全选方法。

最重要的两句 !!!!

TableDemo.editing=YES;      编辑状态

TableDemo.allowsMultipleSelectionDuringEditing=YES;   编辑的时候多选


cell.tintColor= [UIColorredColor];   选中后的颜色


选中和取消选中


-(void)tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath    选中


-(void)tableView:(UITableView*)tableView didDeselectRowAtIndexPath:(NSIndexPath*)indexPath    取消选中



3899794-b9d2effc299da195.png

如果不用系统的话,则利用model来进行单选和全选的操作,选中和取消选中对model中的判断字段进行修改,刷新当前cell或者section。

//一个section刷新


NSIndexSet *indexSet=[[NSIndexSet alloc]initWithIndex:section];


[tableview reloadSections:indexSet


withRowAnimation:UITableViewRowAnimationAutomatic];


//一个cell刷新


NSIndexPath *indexPath=[NSIndexPath indexPathForRow:row inSection:section];


[tableView reloadRowsAtIndexPaths:[NSArray arrayWithObjects:indexPath,nil] withRowAnimation:UITableViewRowAnimationNone];


这里需要注意的是,每次单选和全选的时候,需要对底部结账栏进行数据的刷新,并且单选完整组后,需要对section上的按钮进行选中状态变化,当选中section时,同样需要对section下的row进行选中状态,如果全部商品选中,还得需要改变结账栏的状态。(这里其实不难,无非就是对model中各类字段进行改变,再刷新,同时判断选中数量的多少来进行按钮的状态变换)

2.删除单个商品

删除的话就相对容易了,直接对数据源删除对应的row,当只剩一个后,删除该数据后,记得删除该组section不然报错。删除后及时刷新底部结账栏金额显示。

3.编辑section

点击编辑按钮,修改model,展示编辑cell。编辑按钮上放了数量计数器、商品的信息、删除。

计数器:用到的是好友的一个库PPNumberButton 喜欢的大家可以去玩玩。点击后用代理方法把数量的变化跟新model。

商品信息:点击对商品的信息进行重新选择,同样修改数据源。

删除:代理出来进行model的修改。

因为这里用的是假数据,所以进行的都是对数据源的修改,正常情况下,原理都一样,可以在次基础上,如果接口成功,就对本地数据进行修改,最后提交的信息会和后台匹配一次的,如果有问题,可以自己修改一下。

有小伙伴提出demo中没有下拉刷新,其实下拉刷新不影响该demo。不过加上效果更好。

谢谢“爱在巴黎梦醒时”该小伙伴。


demo的bug注释:

因为demo中判断section的全选和编辑的按钮都是放在每个section的第一个row中的,所以删除section的第一个row后,会有全选和编辑的固定bug出来。特此声明,该bug不影响主体逻辑,如次bug影响小伙伴对逻辑思路的学习,那我后面再重新组织数据源。


demo纯代码编写的,只隔离了部分模块,因为我也是拿来练练手,所以如果有需要,后续我会把购物车模块化。如果内容有不妥和臃肿的地方,大家可以提出来,我及时学习并修改。如果大家有意见的可以@我1804094055qq.com。

项目源码Git地址https://github.com/zl645420646/-ZLShoppingCart
欢迎Star!
0
评论

ios V2.3.1 已发布,增加获取日志压缩文件路径接口 产品快递 iOS

产品更新 发表了文章 • 537 次浏览 • 2017-02-20 11:48 • 来自相关话题

ios版本:V2.3.1 2016-02-17





新功能/改进:
修改HttpsOnly参数默认值,默认设置为NO(由于苹果强制ATS政策延缓, 所以SDK默认关闭httpsOnly)
[[EaseMob sharedInstance].chatManager setIsUseHttpsOnly:YES];//设置httpsonly,YES开启,NO关闭
增加获取日志压缩文件路径接口(具体上传日志方式可由开发者决定, Demo是通过邮件的形式上报日志)优化群组过多时重连卡顿问题修复离线已读回执有时丢失问题修复SDK收到特殊消息闪退问题
 
 版本历史:ios 2.x更新日志
下载地址:SDK下载 查看全部
ios版本:V2.3.1 2016-02-17
2351.jpg_wh860_.jpg


新功能/改进:
  1. 修改HttpsOnly参数默认值,默认设置为NO(由于苹果强制ATS政策延缓, 所以SDK默认关闭httpsOnly)

[[EaseMob sharedInstance].chatManager setIsUseHttpsOnly:YES];//设置httpsonly,YES开启,NO关闭

  1. 增加获取日志压缩文件路径接口(具体上传日志方式可由开发者决定, Demo是通过邮件的形式上报日志)
  2. 优化群组过多时重连卡顿问题
  3. 修复离线已读回执有时丢失问题
  4. 修复SDK收到特殊消息闪退问题

 
 版本历史:ios 2.x更新日志
下载地址:SDK下载
6
评论

基于环信写的通讯项目,实现QQ小表情教程 自定义表情 iOS

zl 发表了文章 • 512 次浏览 • 2017-02-09 15:27 • 来自相关话题

先上效果图:










 
大家直接看下面demo就可以,搜索#pragma mark smallpngface就是所有修改的地方。
图片资源可以自己替换自己的,大家可以照猫画虎,我只是抛砖引玉。     

https://pan.baidu.com/s/1bA7eMy
  查看全部
先上效果图:

6069EDD5-304A-41BD-AE22-443F1604185A.png


568D243B-599A-43F3-A08D-E2B7C0CAC6E5.png

 
大家直接看下面demo就可以,搜索#pragma mark smallpngface就是所有修改的地方。
图片资源可以自己替换自己的,大家可以照猫画虎,我只是抛砖引玉。     

https://pan.baidu.com/s/1bA7eMy
 
2
评论

环信头像和昵称显示的详细、详细、详细教程! 头像 昵称 扩展 头像和昵称的显示 iOS 昵称 头像

xuke007008 发表了文章 • 1710 次浏览 • 2017-02-07 18:17 • 来自相关话题

写在前边:本文由江南大神的环信集成demo衍生而来!

附上大神的集成链接: http://www.imgeek.org/article/825307886 
 
通过官方的文档我们知道有两种显示头像和昵称的方式(http://docs.easemob.com/im/490integrationcases/10nickname  官方文档)

这里主要讲方式二!(通过扩展消息传递显示)
 
这里主要有三个类需要改,分别是:
EaseMessageViewController  
EaseBaseMessageCell 
chatUIhelper 

首先我们需要在发送消息的时候添加扩展字段,在EaseMessageViewController.m里。可以看到有以下方法:
#pragma mark - send message

- (void)_refreshAfterSentMessage:(EMMessage*)aMessage
{
if ([self.messsagesSource count] && [EMClient sharedClient].options.sortMessageByServerTime) {
NSString *msgId = aMessage.messageId;
EMMessage *last = self.messsagesSource.lastObject;
if ([last isKindOfClass:[EMMessage class]]) {

__block NSUInteger index = NSNotFound;
index = NSNotFound;
[self.messsagesSource enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(EMMessage *obj, NSUInteger idx, BOOL *stop) {
if ([obj isKindOfClass:[EMMessage class]] && [obj.messageId isEqualToString:msgId]) {
index = idx;
*stop = YES;
}
}];
if (index != NSNotFound) {
[self.messsagesSource removeObjectAtIndex:index];
[self.messsagesSource addObject:aMessage];

//格式化消息
self.messageTimeIntervalTag = -1;
NSArray *formattedMessages = [self formatMessages:self.messsagesSource];
[self.dataArray removeAllObjects];
[self.dataArray addObjectsFromArray:formattedMessages];
[self.tableView reloadData];
[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:[self.dataArray count] - 1 inSection:0] atScrollPosition:UITableViewScrollPositionBottom animated:YES];
return;
}
}
}
[self.tableView reloadData];
}

- (void)_sendMessage:(EMMessage *)message
{
if (self.conversation.type == EMConversationTypeGroupChat){
message.chatType = EMChatTypeGroupChat;
}
else if (self.conversation.type == EMConversationTypeChatRoom){
message.chatType = EMChatTypeChatRoom;
}

[self addMessageToDataSource:message
progress:nil];

__weak typeof(self) weakself = self;
[[EMClient sharedClient].chatManager sendMessage:message progress:nil completion:^(EMMessage *aMessage, EMError *aError) {
if (!aError) {
[weakself _refreshAfterSentMessage:aMessage];
}
else {
[weakself.tableView reloadData];
}
}];
}

- (void)sendTextMessage:(NSString *)text
{
NSDictionary *ext = nil;
if (self.conversation.type == EMConversationTypeGroupChat) {
NSArray *targets = [self _searchAtTargets:text];
if ([targets count]) {
__block BOOL atAll = NO;
[targets enumerateObjectsUsingBlock:^(NSString *target, NSUInteger idx, BOOL *stop) {
if ([target compare:kGroupMessageAtAll options:NSCaseInsensitiveSearch] == NSOrderedSame) {
atAll = YES;
*stop = YES;
}
}];
if (atAll) {
ext = @{kGroupMessageAtList: kGroupMessageAtAll};
}
else {
ext = @{kGroupMessageAtList: targets};
}
}
}
[self sendTextMessage:text withExt:ext];
}

- (void)sendTextMessage:(NSString *)text withExt:(NSDictionary*)ext
{
EMMessage *message = [EaseSDKHelper sendTextMessage:text
to:self.conversation.conversationId
messageType:[self _messageTypeFromConversationType]
messageExt:ext];
[self _sendMessage:message];
}

- (void)sendLocationMessageLatitude:(double)latitude
longitude:(double)longitude
andAddress:(NSString *)address
{
EMMessage *message = [EaseSDKHelper sendLocationMessageWithLatitude:latitude
longitude:longitude
address:address
to:self.conversation.conversationId
messageType:[self _messageTypeFromConversationType]
messageExt:nil];
[self _sendMessage:message];
}

- (void)sendImageMessageWithData:(NSData *)imageData
{
id progress = nil;
if (_dataSource && [_dataSource respondsToSelector:@selector(messageViewController:progressDelegateForMessageBodyType:)]) {
progress = [_dataSource messageViewController:self progressDelegateForMessageBodyType:EMMessageBodyTypeImage];
}
else{
progress = self;
}

EMMessage *message = [EaseSDKHelper sendImageMessageWithImageData:imageData
to:self.conversation.conversationId
messageType:[self _messageTypeFromConversationType]
messageExt:nil];
[self _sendMessage:message];
}

- (void)sendImageMessage:(UIImage *)image
{
id progress = nil;
if (_dataSource && [_dataSource respondsToSelector:@selector(messageViewController:progressDelegateForMessageBodyType:)]) {
progress = [_dataSource messageViewController:self progressDelegateForMessageBodyType:EMMessageBodyTypeImage];
}
else{
progress = self;
}

EMMessage *message = [EaseSDKHelper sendImageMessageWithImage:image
to:self.conversation.conversationId
messageType:[self _messageTypeFromConversationType]
messageExt:nil];
[self _sendMessage:message];
}

- (void)sendVoiceMessageWithLocalPath:(NSString *)localPath
duration:(NSInteger)duration
{
id progress = nil;
if (_dataSource && [_dataSource respondsToSelector:@selector(messageViewController:progressDelegateForMessageBodyType:)]) {
progress = [_dataSource messageViewController:self progressDelegateForMessageBodyType:EMMessageBodyTypeVoice];
}
else{
progress = self;
}

EMMessage *message = [EaseSDKHelper sendVoiceMessageWithLocalPath:localPath
duration:duration
to:self.conversation.conversationId
messageType:[self _messageTypeFromConversationType]
messageExt:nil];
[self _sendMessage:message];
}

- (void)sendVideoMessageWithURL:(NSURL *)url
{
id progress = nil;
if (_dataSource && [_dataSource respondsToSelector:@selector(messageViewController:progressDelegateForMessageBodyType:)]) {
progress = [_dataSource messageViewController:self progressDelegateForMessageBodyType:EMMessageBodyTypeVideo];
}
else{
progress = self;
}

EMMessage *message = [EaseSDKHelper sendVideoMessageWithURL:url
to:self.conversation.conversationId
messageType:[self _messageTypeFromConversationType]
messageExt:nil];
[self _sendMessage:message];
}
有发送各种消息的,我们要每个里边都加扩展字段么?那恐怕要累死咯!  仔细看会发现发送消息的方法最后都会走一个方法:
- (void)_sendMessage:(EMMessage *)message
{
if (self.conversation.type == EMConversationTypeGroupChat){
message.chatType = EMChatTypeGroupChat;
}
else if (self.conversation.type == EMConversationTypeChatRoom){
message.chatType = EMChatTypeChatRoom;
}

[self addMessageToDataSource:message
progress:nil];

__weak typeof(self) weakself = self;
[[EMClient sharedClient].chatManager sendMessage:message progress:nil completion:^(EMMessage *aMessage, EMError *aError) {
if (!aError) {
[weakself _refreshAfterSentMessage:aMessage];
}
else {
[weakself.tableView reloadData];
}
}];
}
好的,就是这里了,添加扩展字段,包含用户的头像地址,昵称和环信ID。 找到保存用户信息的类UserCacheInfo,找到相应的字段,在这个方法里添加如下代码:
NSMutableDictionary *Muext = [NSMutableDictionary dictionaryWithDictionary:message.ext];
UserCacheInfo *info = [UserCacheManager currUser];
[Muext setObject:kCurrEaseUserId forKey:kChatUserId];
[Muext setObject:info.NickName forKey:kChatUserNick];
[Muext setObject:info.AvatarUrl forKey:kChatUserPic];
message.ext = Muext;

 
这样第一步就完成了!


接下来我们要在接收消息的方法里保存传过来的扩展消息里的头像、昵称和环信ID,这就用到chatUIhelper.m这个类,这个方法里:
- (void)didReceiveMessages:(NSArray *)aMessages
{
BOOL isRefreshCons = YES;
for(EMMessage *message in aMessages){
[UserCacheManager saveInfo:message.ext];// 通过消息的扩展属性传递昵称和头像时,需要调用这句代码缓存
BOOL needShowNotification = (message.chatType != EMChatTypeChat) ? [self _needShowNotification:message.conversationId] : YES;

#ifdef REDPACKET_AVALABLE
/**
* 屏蔽红包被抢消息的提示
*/
NSDictionary *dict = message.ext;
needShowNotification = (dict && [dict valueForKey:RedpacketKeyRedpacketTakenMessageSign]) ? NO : needShowNotification;
#endif

UIApplicationState state = [[UIApplication sharedApplication] applicationState];
if (needShowNotification) {
#if !TARGET_IPHONE_SIMULATOR
switch (state) {
case UIApplicationStateActive:
[self playSoundAndVibration];
break;
case UIApplicationStateInactive:
[self playSoundAndVibration];
break;
case UIApplicationStateBackground:
[self showNotificationWithMessage:message];
break;
default:
break;
}
#endif
}

if (_chatVC == nil) {
_chatVC = [self _getCurrentChatView];
}
BOOL isChatting = NO;
if (_chatVC) {
isChatting = [message.conversationId isEqualToString:_chatVC.conversation.conversationId];
}
if (_chatVC == nil || !isChatting || state == UIApplicationStateBackground) {
[self _handleReceivedAtMessage:message];

if (self.conversationListVC) {
[_conversationListVC refresh];
}

if (self.mainVC) {
NOTIFY_POST(kSetupUnreadMessageCount);
}
return;
}

if (isChatting) {
isRefreshCons = NO;
}
}

if (isRefreshCons) {
if (self.conversationListVC) {
[_conversationListVC refresh];
}

if (self.mainVC) {
NOTIFY_POST(kSetupUnreadMessageCount);
}
}
}
关键就是这句话:
[UserCacheManager saveInfo:message.ext];// 通过消息的扩展属性传递昵称和头像时,需要调用这句代码缓存!!!
 
到这里头像和昵称的问题就基本解决了!
 
 
重要的总是留在最后!!!  不看后悔哦!!!
 
上两步完成后你会惊奇的发现头像和昵称正常显示了,然而当你换个头像测试的时候,你会发现很不美妙,头像没有更换,这是什么问题呢?   这就要用到开始讲到的第一个类EaseBaseMessageCell.m,我们仔细看代码会发现它是怎么赋值的,如下:
#pragma mark - setter

- (void)setModel:(id<IMessageModel>)model
{
[super setModel:model];

if (model.avatarURLPath) {
[self.avatarView sd_setImageWithURL:[NSURL URLWithString:model.avatarURLPath] placeholderImage:model.avatarImage];
} else {
self.avatarView.image = model.avatarImage;
}
_nameLabel.text = model.nickname;

if (self.model.isSender) {
_hasRead.hidden = YES;
switch (self.model.messageStatus) {
case EMMessageStatusDelivering:
{
_statusButton.hidden = YES;
[_activity setHidden:NO];
[_activity startAnimating];
}
break;
case EMMessageStatusSuccessed:
{
_statusButton.hidden = YES;
[_activity stopAnimating];
if (self.model.isMessageRead) {
_hasRead.hidden = NO;
}
}
break;
case EMMessageStatusPending:
case EMMessageStatusFailed:
{
[_activity stopAnimating];
[_activity setHidden:YES];
_statusButton.hidden = NO;
}
break;
default:
break;
}
}
}
看到这里就明白了是头像缓存了,直接用的是缓存里的头像,我们需要更新的话直接设置一下缓存策略就可以了,代码修改如下:
把 [self.avatarView sd_setImageWithURL:[NSURL URLWithString:model.avatarURLPath] placeholderImage:model.avatarImage];
改成 [self.avatarView sd_setImageWithURL:[NSURL URLWithString:model.avatarURLPath] placeholderImage:model.avatarImage options:EMSDWebImageRefreshCached];
然后运行一下你会发现世界如此美好,大功告成!  
对各位小伙伴you有没有帮助呢? 

如有任何问题,请咨询【环信IM互帮互助群】,群号:340452063 (进群记得改名片哦!江南大神也在群里!)
本人群里的名片:上海-iOS-小码农  。 查看全部
写在前边:本文由江南大神的环信集成demo衍生而来!

附上大神的集成链接: http://www.imgeek.org/article/825307886 
 
通过官方的文档我们知道有两种显示头像和昵称的方式(http://docs.easemob.com/im/490integrationcases/10nickname  官方文档)

这里主要讲方式二!(通过扩展消息传递显示)
 
这里主要有三个类需要改,分别是:
EaseMessageViewController  
EaseBaseMessageCell 
chatUIhelper 

首先我们需要在发送消息的时候添加扩展字段,在EaseMessageViewController.m里。可以看到有以下方法:
#pragma mark - send message

- (void)_refreshAfterSentMessage:(EMMessage*)aMessage
{
if ([self.messsagesSource count] && [EMClient sharedClient].options.sortMessageByServerTime) {
NSString *msgId = aMessage.messageId;
EMMessage *last = self.messsagesSource.lastObject;
if ([last isKindOfClass:[EMMessage class]]) {

__block NSUInteger index = NSNotFound;
index = NSNotFound;
[self.messsagesSource enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(EMMessage *obj, NSUInteger idx, BOOL *stop) {
if ([obj isKindOfClass:[EMMessage class]] && [obj.messageId isEqualToString:msgId]) {
index = idx;
*stop = YES;
}
}];
if (index != NSNotFound) {
[self.messsagesSource removeObjectAtIndex:index];
[self.messsagesSource addObject:aMessage];

//格式化消息
self.messageTimeIntervalTag = -1;
NSArray *formattedMessages = [self formatMessages:self.messsagesSource];
[self.dataArray removeAllObjects];
[self.dataArray addObjectsFromArray:formattedMessages];
[self.tableView reloadData];
[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:[self.dataArray count] - 1 inSection:0] atScrollPosition:UITableViewScrollPositionBottom animated:YES];
return;
}
}
}
[self.tableView reloadData];
}

- (void)_sendMessage:(EMMessage *)message
{
if (self.conversation.type == EMConversationTypeGroupChat){
message.chatType = EMChatTypeGroupChat;
}
else if (self.conversation.type == EMConversationTypeChatRoom){
message.chatType = EMChatTypeChatRoom;
}

[self addMessageToDataSource:message
progress:nil];

__weak typeof(self) weakself = self;
[[EMClient sharedClient].chatManager sendMessage:message progress:nil completion:^(EMMessage *aMessage, EMError *aError) {
if (!aError) {
[weakself _refreshAfterSentMessage:aMessage];
}
else {
[weakself.tableView reloadData];
}
}];
}

- (void)sendTextMessage:(NSString *)text
{
NSDictionary *ext = nil;
if (self.conversation.type == EMConversationTypeGroupChat) {
NSArray *targets = [self _searchAtTargets:text];
if ([targets count]) {
__block BOOL atAll = NO;
[targets enumerateObjectsUsingBlock:^(NSString *target, NSUInteger idx, BOOL *stop) {
if ([target compare:kGroupMessageAtAll options:NSCaseInsensitiveSearch] == NSOrderedSame) {
atAll = YES;
*stop = YES;
}
}];
if (atAll) {
ext = @{kGroupMessageAtList: kGroupMessageAtAll};
}
else {
ext = @{kGroupMessageAtList: targets};
}
}
}
[self sendTextMessage:text withExt:ext];
}

- (void)sendTextMessage:(NSString *)text withExt:(NSDictionary*)ext
{
EMMessage *message = [EaseSDKHelper sendTextMessage:text
to:self.conversation.conversationId
messageType:[self _messageTypeFromConversationType]
messageExt:ext];
[self _sendMessage:message];
}

- (void)sendLocationMessageLatitude:(double)latitude
longitude:(double)longitude
andAddress:(NSString *)address
{
EMMessage *message = [EaseSDKHelper sendLocationMessageWithLatitude:latitude
longitude:longitude
address:address
to:self.conversation.conversationId
messageType:[self _messageTypeFromConversationType]
messageExt:nil];
[self _sendMessage:message];
}

- (void)sendImageMessageWithData:(NSData *)imageData
{
id progress = nil;
if (_dataSource && [_dataSource respondsToSelector:@selector(messageViewController:progressDelegateForMessageBodyType:)]) {
progress = [_dataSource messageViewController:self progressDelegateForMessageBodyType:EMMessageBodyTypeImage];
}
else{
progress = self;
}

EMMessage *message = [EaseSDKHelper sendImageMessageWithImageData:imageData
to:self.conversation.conversationId
messageType:[self _messageTypeFromConversationType]
messageExt:nil];
[self _sendMessage:message];
}

- (void)sendImageMessage:(UIImage *)image
{
id progress = nil;
if (_dataSource && [_dataSource respondsToSelector:@selector(messageViewController:progressDelegateForMessageBodyType:)]) {
progress = [_dataSource messageViewController:self progressDelegateForMessageBodyType:EMMessageBodyTypeImage];
}
else{
progress = self;
}

EMMessage *message = [EaseSDKHelper sendImageMessageWithImage:image
to:self.conversation.conversationId
messageType:[self _messageTypeFromConversationType]
messageExt:nil];
[self _sendMessage:message];
}

- (void)sendVoiceMessageWithLocalPath:(NSString *)localPath
duration:(NSInteger)duration
{
id progress = nil;
if (_dataSource && [_dataSource respondsToSelector:@selector(messageViewController:progressDelegateForMessageBodyType:)]) {
progress = [_dataSource messageViewController:self progressDelegateForMessageBodyType:EMMessageBodyTypeVoice];
}
else{
progress = self;
}

EMMessage *message = [EaseSDKHelper sendVoiceMessageWithLocalPath:localPath
duration:duration
to:self.conversation.conversationId
messageType:[self _messageTypeFromConversationType]
messageExt:nil];
[self _sendMessage:message];
}

- (void)sendVideoMessageWithURL:(NSURL *)url
{
id progress = nil;
if (_dataSource && [_dataSource respondsToSelector:@selector(messageViewController:progressDelegateForMessageBodyType:)]) {
progress = [_dataSource messageViewController:self progressDelegateForMessageBodyType:EMMessageBodyTypeVideo];
}
else{
progress = self;
}

EMMessage *message = [EaseSDKHelper sendVideoMessageWithURL:url
to:self.conversation.conversationId
messageType:[self _messageTypeFromConversationType]
messageExt:nil];
[self _sendMessage:message];
}

有发送各种消息的,我们要每个里边都加扩展字段么?那恐怕要累死咯!  仔细看会发现发送消息的方法最后都会走一个方法:
- (void)_sendMessage:(EMMessage *)message
{
if (self.conversation.type == EMConversationTypeGroupChat){
message.chatType = EMChatTypeGroupChat;
}
else if (self.conversation.type == EMConversationTypeChatRoom){
message.chatType = EMChatTypeChatRoom;
}

[self addMessageToDataSource:message
progress:nil];

__weak typeof(self) weakself = self;
[[EMClient sharedClient].chatManager sendMessage:message progress:nil completion:^(EMMessage *aMessage, EMError *aError) {
if (!aError) {
[weakself _refreshAfterSentMessage:aMessage];
}
else {
[weakself.tableView reloadData];
}
}];
}

好的,就是这里了,添加扩展字段,包含用户的头像地址,昵称和环信ID。 找到保存用户信息的类UserCacheInfo,找到相应的字段,在这个方法里添加如下代码:
NSMutableDictionary *Muext = [NSMutableDictionary dictionaryWithDictionary:message.ext];
UserCacheInfo *info = [UserCacheManager currUser];
[Muext setObject:kCurrEaseUserId forKey:kChatUserId];
[Muext setObject:info.NickName forKey:kChatUserNick];
[Muext setObject:info.AvatarUrl forKey:kChatUserPic];
message.ext = Muext;

 
这样第一步就完成了!


接下来我们要在接收消息的方法里保存传过来的扩展消息里的头像、昵称和环信ID,这就用到chatUIhelper.m这个类,这个方法里:
- (void)didReceiveMessages:(NSArray *)aMessages
{
BOOL isRefreshCons = YES;
for(EMMessage *message in aMessages){
[UserCacheManager saveInfo:message.ext];// 通过消息的扩展属性传递昵称和头像时,需要调用这句代码缓存
BOOL needShowNotification = (message.chatType != EMChatTypeChat) ? [self _needShowNotification:message.conversationId] : YES;

#ifdef REDPACKET_AVALABLE
/**
* 屏蔽红包被抢消息的提示
*/
NSDictionary *dict = message.ext;
needShowNotification = (dict && [dict valueForKey:RedpacketKeyRedpacketTakenMessageSign]) ? NO : needShowNotification;
#endif

UIApplicationState state = [[UIApplication sharedApplication] applicationState];
if (needShowNotification) {
#if !TARGET_IPHONE_SIMULATOR
switch (state) {
case UIApplicationStateActive:
[self playSoundAndVibration];
break;
case UIApplicationStateInactive:
[self playSoundAndVibration];
break;
case UIApplicationStateBackground:
[self showNotificationWithMessage:message];
break;
default:
break;
}
#endif
}

if (_chatVC == nil) {
_chatVC = [self _getCurrentChatView];
}
BOOL isChatting = NO;
if (_chatVC) {
isChatting = [message.conversationId isEqualToString:_chatVC.conversation.conversationId];
}
if (_chatVC == nil || !isChatting || state == UIApplicationStateBackground) {
[self _handleReceivedAtMessage:message];

if (self.conversationListVC) {
[_conversationListVC refresh];
}

if (self.mainVC) {
NOTIFY_POST(kSetupUnreadMessageCount);
}
return;
}

if (isChatting) {
isRefreshCons = NO;
}
}

if (isRefreshCons) {
if (self.conversationListVC) {
[_conversationListVC refresh];
}

if (self.mainVC) {
NOTIFY_POST(kSetupUnreadMessageCount);
}
}
}

关键就是这句话:
[UserCacheManager saveInfo:message.ext];// 通过消息的扩展属性传递昵称和头像时,需要调用这句代码缓存!!!
 
到这里头像和昵称的问题就基本解决了!
 
 
  • 重要的总是留在最后!!!  不看后悔哦!!!

 
上两步完成后你会惊奇的发现头像和昵称正常显示了,然而当你换个头像测试的时候,你会发现很不美妙,头像没有更换,这是什么问题呢?   这就要用到开始讲到的第一个类EaseBaseMessageCell.m,我们仔细看代码会发现它是怎么赋值的,如下:
#pragma mark - setter

- (void)setModel:(id<IMessageModel>)model
{
[super setModel:model];

if (model.avatarURLPath) {
[self.avatarView sd_setImageWithURL:[NSURL URLWithString:model.avatarURLPath] placeholderImage:model.avatarImage];
} else {
self.avatarView.image = model.avatarImage;
}
_nameLabel.text = model.nickname;

if (self.model.isSender) {
_hasRead.hidden = YES;
switch (self.model.messageStatus) {
case EMMessageStatusDelivering:
{
_statusButton.hidden = YES;
[_activity setHidden:NO];
[_activity startAnimating];
}
break;
case EMMessageStatusSuccessed:
{
_statusButton.hidden = YES;
[_activity stopAnimating];
if (self.model.isMessageRead) {
_hasRead.hidden = NO;
}
}
break;
case EMMessageStatusPending:
case EMMessageStatusFailed:
{
[_activity stopAnimating];
[_activity setHidden:YES];
_statusButton.hidden = NO;
}
break;
default:
break;
}
}
}

看到这里就明白了是头像缓存了,直接用的是缓存里的头像,我们需要更新的话直接设置一下缓存策略就可以了,代码修改如下:
[self.avatarView sd_setImageWithURL:[NSURL URLWithString:model.avatarURLPath] placeholderImage:model.avatarImage];
改成 [self.avatarView sd_setImageWithURL:[NSURL URLWithString:model.avatarURLPath] placeholderImage:model.avatarImage options:EMSDWebImageRefreshCached];

然后运行一下你会发现世界如此美好,大功告成!  
对各位小伙伴you有没有帮助呢? 

如有任何问题,请咨询【环信IM互帮互助群】,群号:340452063 (进群记得改名片哦!江南大神也在群里!)
本人群里的名片:上海-iOS-小码农  。
1
评论

环信及时通信iOS SDK的一些看法和建议 环信 ios iOS

xiaohao01 发表了文章 • 447 次浏览 • 2017-01-16 11:46 • 来自相关话题

先说下环境:我使用的是HyphenateLite,3.2.3版本
 1,关于bitcode
终于看到3.2.3版本有说:sdk支持bitcode拉,赶紧下载组装。编译时提示:
libopencore-amrnb.a(wrapper.o)' does not contain bitcode. You must rebuild it with bitcode enabled (Xcode setting ENABLE_BITCODE), obtain an updated library from the vendor, or disable bitcode for this target. for architecture arm64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
看来官方给出的sdk支持bitcode还真是仅限于“sdk”了,EaseUI里的libopencore-amrnb不是环信出品的,就不管了??可这对开发者来说有什么用呢?我们需要sdk提供的全部组件都支持bicode才行呢!!!
另外:跟iOS SDK客服沟通了下,似乎没有搞明白开启bitcode时什么意思。
强调一遍:必须在TARGET中设置bitcode为YES,然后真机运行,真机运行!!!!
麻烦官方再仔细检查一遍给我们开发者一个真正能用的bitcode版本!!!
 
2,关于文档
这次3.2.3说使用了动态framework,嗯,对官方的这种与时俱进赞一个。但你们能仔细看看给的文档吗:
注: 由于 iOS 编译的特殊性,为了方便开发者使用,我们将 i386 x86_64 armv7 arm64 几个平台都合并到了一起,所以使用动态库上传appstore时需要将i386 x86_64两个平台删除后,才能正常提交
然后呢?如何操作?能再简洁一点吗?
熟悉开发的肯定都知道用lipo操作,但是刚入门的是还需要查询的呀。能直接说下怎么操作岂不是更好!
 
3,一些建议
1,做SDK不像平常开发个app,自己的一亩三分地想怎么搞就怎么搞,sdk是拿出来给大家用的,对象都是开发者,需要开发sdk的同学本身就非常精通。一个建议,对于暴漏的.h文件最好都引用:
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>//如何有UI
 
2,并且文档什么的要尽量细化。看到官方推出了不少视频的开发文档,这个虽然很好,降低了门槛,可是sdk总是在升级的在变动的,视频的内容往往发出来不久就落后于实际的代码了。建议多些文字文档,重要步骤尽量详细,文字能快速传达信息,没人有希望集成个sdk都要花费很多时间。
 
以上是我个人的浅见,不是纯粹是发牢骚,大家都是开发者,都希望作品精益求精,希望共同进步!
 
匆促中难免有错字,望理解!谢谢!
  查看全部
先说下环境:我使用的是HyphenateLite,3.2.3版本
 1,关于bitcode
终于看到3.2.3版本有说:sdk支持bitcode拉,赶紧下载组装。编译时提示:
libopencore-amrnb.a(wrapper.o)' does not contain bitcode. You must rebuild it with bitcode enabled (Xcode setting ENABLE_BITCODE), obtain an updated library from the vendor, or disable bitcode for this target. for architecture arm64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
看来官方给出的sdk支持bitcode还真是仅限于“sdk”了,EaseUI里的libopencore-amrnb不是环信出品的,就不管了??可这对开发者来说有什么用呢?我们需要sdk提供的全部组件都支持bicode才行呢!!!
另外:跟iOS SDK客服沟通了下,似乎没有搞明白开启bitcode时什么意思。
强调一遍:必须在TARGET中设置bitcode为YES,然后真机运行,真机运行!!!!
麻烦官方再仔细检查一遍给我们开发者一个真正能用的bitcode版本!!!
 
2,关于文档
这次3.2.3说使用了动态framework,嗯,对官方的这种与时俱进赞一个。但你们能仔细看看给的文档吗:

注: 由于 iOS 编译的特殊性,为了方便开发者使用,我们将 i386 x86_64 armv7 arm64 几个平台都合并到了一起,所以使用动态库上传appstore时需要将i386 x86_64两个平台删除后,才能正常提交


然后呢?如何操作?能再简洁一点吗?
熟悉开发的肯定都知道用lipo操作,但是刚入门的是还需要查询的呀。能直接说下怎么操作岂不是更好!
 
3,一些建议
1,做SDK不像平常开发个app,自己的一亩三分地想怎么搞就怎么搞,sdk是拿出来给大家用的,对象都是开发者,需要开发sdk的同学本身就非常精通。一个建议,对于暴漏的.h文件最好都引用:
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>//如何有UI
 
2,并且文档什么的要尽量细化。看到官方推出了不少视频的开发文档,这个虽然很好,降低了门槛,可是sdk总是在升级的在变动的,视频的内容往往发出来不久就落后于实际的代码了。建议多些文字文档,重要步骤尽量详细,文字能快速传达信息,没人有希望集成个sdk都要花费很多时间。
 
以上是我个人的浅见,不是纯粹是发牢骚,大家都是开发者,都希望作品精益求精,希望共同进步!
 
匆促中难免有错字,望理解!谢谢!
 
0
评论

环信动态库sdk上架问题解决方案 iOS 动态库

beyond 发表了文章 • 1015 次浏览 • 2017-01-10 17:28 • 来自相关话题

环信发布了动态库sdk:




但是也会有一些问题,这里讲下关于这个上架的问题。

1.先把Hyphenate.framework放到桌面上;

2.终端位置cd到桌面;

3.运行:lipo Hyphenate.framework/Hyphenate -thin armv7 -output Hyphenate_armv7  

4.运行后没有输出提示,直接运行下一个命令:lipo Hyphenate.framework/Hyphenate -thin arm64 -output Hyphenate_arm64                                                                                                                                                                

5.运行后一样没有输出提示,直接运行下一个命令:lipo -create Hyphenate_armv7 Hyphenate_arm64 -output Hyphenate                                                                                                                                                                            

6.运行后一样没有输出提示,直接运行最后一个命令:mv Hyphenate Hyphenate.framework/




得到的Hyphenate.framework就是最后的结果,拖进工程,编译打包上架。
 
作者:环信ios工程师张磊 查看全部
环信发布了动态库sdk:
2639170-be3d6bfe875d544c.png

但是也会有一些问题,这里讲下关于这个上架的问题。

1.先把Hyphenate.framework放到桌面上;

2.终端位置cd到桌面;

3.运行:lipo Hyphenate.framework/Hyphenate -thin armv7 -output Hyphenate_armv7  

4.运行后没有输出提示,直接运行下一个命令:lipo Hyphenate.framework/Hyphenate -thin arm64 -output Hyphenate_arm64                                                                                                                                                                

5.运行后一样没有输出提示,直接运行下一个命令:lipo -create Hyphenate_armv7 Hyphenate_arm64 -output Hyphenate                                                                                                                                                                            

6.运行后一样没有输出提示,直接运行最后一个命令:mv Hyphenate Hyphenate.framework/
2639170-6aa66fdee8102250.png

得到的Hyphenate.framework就是最后的结果,拖进工程,编译打包上架。
 
作者:环信ios工程师张磊
0
评论

Android ios V3.2.3 SDK 已发布,SDK十余项更新,更加简洁易用,新增广告红包 产品快递 Android iOS

产品更新 发表了文章 • 588 次浏览 • 2016-12-30 11:51 • 来自相关话题

Android​ V3.2.3 2016-12-29
新功能/优化:
sdk提供aar及gradle方式集成,具体方法查看gradle方式导入aar增加离线推送设置的相关接口,具体方法可查看EMPushManager API文档为了使sdk更简洁易用,修改以及过时了一些api,具体修改查看3.2.3api修改,另外过时的api后续3-5个版本会进行删除优化loadAllConversationsFromDB()方法,从联表查询改为从两个表分别查询,解决在个别乐视手机上执行很慢的问题优化登录模块,减少登录失败的概率鉴于市面上的手机基本都是armeabi-v7a及以上的架构,从这版本开始不再提供普通的armeabi架构的so,减少打包时app的体积

红包相关:
新增:
小额随机红包增加广告红包(需要使用请单独联系商务)商户后台增加广告红包配置、统计功能商户后台增加修改密码功能

优化:
绑卡后的用户验证四要素改为验证二要素发红包等页面增加点击空白区域收回键盘的功能群成员列表索引增加常用姓氏以及汉字的支持

修复bug:
红包详情页领取人列表展示不全华为P8手机密码框无法获取焦点部分银行卡号输入正确,提示银行卡号不正确红包祝福语有换行符显示不正确修复Emoji表情显示乱码修复商户自主配置红包最低限额错误修复零钱明细显示顺序错误问题
 
iOS​ V3.2.3 2016-12-29
新功能/优化:
新增:实时1v1音视频,设置了对方不在线发送离线推送的前提下,当对方不在线时返回回调,以便于用户自定义离线消息推送更新:SDK支持bitcode更新:SDK使用动态库为了使SDK更简洁易用,过时的API会在后续3~5个版本进行删除

红包相关:
新增:
小额随机红包商户后台增加修改密码功能

优化:
绑卡后的用户验证四要素改为验证二要素iOS和Android两端UI展示一致性支付流程的优化SDK注册流程去掉XIB集成过程的参数检查风险策略

修复:
SDKToken注册失败的问题发红包缺少参数的问题修复Emoji表情显示乱码修复支付密码可能误报出错修复商户自主配置红包最低限额错误修复零钱明细显示顺序错误问题修改抢红包流程为依赖后端数据修复支行信息返回为空时的文案
 
 版本历史:Android SDK更新日志  ios SDK更新日志
下载地址:SDK下载 查看全部
7750.jpg_wh860_.jpg

Android​ V3.2.3 2016-12-29
新功能/优化:
  • sdk提供aar及gradle方式集成,具体方法查看gradle方式导入aar
  • 增加离线推送设置的相关接口,具体方法可查看EMPushManager API文档
  • 为了使sdk更简洁易用,修改以及过时了一些api,具体修改查看3.2.3api修改,另外过时的api后续3-5个版本会进行删除
  • 优化loadAllConversationsFromDB()方法,从联表查询改为从两个表分别查询,解决在个别乐视手机上执行很慢的问题
  • 优化登录模块,减少登录失败的概率
  • 鉴于市面上的手机基本都是armeabi-v7a及以上的架构,从这版本开始不再提供普通的armeabi架构的so,减少打包时app的体积


红包相关:
新增:

  • 小额随机红包
  • 增加广告红包(需要使用请单独联系商务)
  • 商户后台增加广告红包配置、统计功能
  • 商户后台增加修改密码功能


优化:
  • 绑卡后的用户验证四要素改为验证二要素
  • 发红包等页面增加点击空白区域收回键盘的功能
  • 群成员列表索引增加常用姓氏以及汉字的支持


修复bug:
  • 红包详情页领取人列表展示不全
  • 华为P8手机密码框无法获取焦点
  • 部分银行卡号输入正确,提示银行卡号不正确
  • 红包祝福语有换行符显示不正确
  • 修复Emoji表情显示乱码
  • 修复商户自主配置红包最低限额错误
  • 修复零钱明细显示顺序错误问题

 
iOS​ V3.2.3 2016-12-29
新功能/优化:
  • 新增:实时1v1音视频,设置了对方不在线发送离线推送的前提下,当对方不在线时返回回调,以便于用户自定义离线消息推送
  • 更新:SDK支持bitcode
  • 更新:SDK使用动态库
  • 为了使SDK更简洁易用,过时的API会在后续3~5个版本进行删除


红包相关:
新增:

  • 小额随机红包
  • 商户后台增加修改密码功能


优化:
  • 绑卡后的用户验证四要素改为验证二要素
  • iOS和Android两端UI展示一致性
  • 支付流程的优化
  • SDK注册流程
  • 去掉XIB
  • 集成过程的参数检查
  • 风险策略


修复:
  • SDKToken注册失败的问题
  • 发红包缺少参数的问题
  • 修复Emoji表情显示乱码
  • 修复支付密码可能误报出错
  • 修复商户自主配置红包最低限额错误
  • 修复零钱明细显示顺序错误问题
  • 修改抢红包流程为依赖后端数据
  • 修复支行信息返回为空时的文案

 
 版本历史:Android SDK更新日志  ios SDK更新日志
下载地址:SDK下载
0
评论

ios V2.3.0 SDK 已发布,增加HttpsOnly参数,允许用户配置 iOS 产品快递

产品更新 发表了文章 • 268 次浏览 • 2016-12-28 14:46 • 来自相关话题

ios V2.3.0 2016-12-28




新功能/改进:
修复2.2.9升级覆盖2.1.5至2.2.3版本,可能无法登录的bug

增加HttpsOnly参数,允许用户配置,默认设置为YES

SDK支持bitcode
版本历史:更新日志
下载地址:SDK下载 查看全部
ios V2.3.0 2016-12-28
1494.jpg_wh860_.jpg

新功能/改进:

修复2.2.9升级覆盖2.1.5至2.2.3版本,可能无法登录的bug

增加HttpsOnly参数,允许用户配置,默认设置为YES

SDK支持bitcode


版本历史:更新日志
下载地址:SDK下载
0
评论

IOS 2.2.9设置https only遇到无法登录的解决方案 ios_2.X iOS 环信集成指南 https

beyond 发表了文章 • 614 次浏览 • 2016-12-28 14:34 • 来自相关话题

分享一个自己遇到的在环信IOS2.X中设置https登录不上的解决方案。
 
SDK的老版本(版本范围为SDK2.1.5-2.2.3)中存在默认不使用https的设置。部分使用了范围内SDK版本的用户在升级到最新的SDK2.2.9时,设置https only的选项后会出现用户无法正常登录的问题。

这个问题可以升级到SDK2.2.9及以上版本,在SDK初始化时添加otherConfig:@{kSDKConfigUseHttps:@YES}的设置,具体的代码实现如下:[[EaseMob sharedInstance] registerSDKWithAppKey:@"easemob-demo#chatdemoui" apnsCertName:@"chatdemoui" otherConfig:@{kSDKConfigUseHttps:@YES}];
上记代码仅未示例,具体在使用时需要将appkey等信息替换为自己的对应信息就可以了。
 
集成过程中遇到问题欢迎在IMGeek社区发帖咨询。 查看全部
分享一个自己遇到的在环信IOS2.X中设置https登录不上的解决方案。
 
SDK的老版本(版本范围为SDK2.1.5-2.2.3)中存在默认不使用https的设置。部分使用了范围内SDK版本的用户在升级到最新的SDK2.2.9时,设置https only的选项后会出现用户无法正常登录的问题。

这个问题可以升级到SDK2.2.9及以上版本,在SDK初始化时添加otherConfig:@{kSDKConfigUseHttps:@YES}的设置,具体的代码实现如下:
[[EaseMob sharedInstance] registerSDKWithAppKey:@"easemob-demo#chatdemoui" apnsCertName:@"chatdemoui" otherConfig:@{kSDKConfigUseHttps:@YES}];

上记代码仅未示例,具体在使用时需要将appkey等信息替换为自己的对应信息就可以了。
 
集成过程中遇到问题欢迎在IMGeek社区发帖咨询。
1
评论

ios V3.2.2 SDK 已发布,增加是否删除会话选项 ios_3.x iOS 产品快递

产品更新 发表了文章 • 519 次浏览 • 2016-12-08 19:10 • 来自相关话题

ios ​V3.2.2 2016-12-08





 
新功能/优化:
 

SDK满足apple ATS的要求

删除好友逻辑的修改(增加是否删除会话选项)

修复呼叫时对方不在线,不能正确显示通话结束原因的问题

版本历史:更新日志 
下载地址:SDK下载 查看全部
ios ​V3.2.2 2016-12-08

2597.jpg_wh860_.jpg

 
新功能/优化:
 


SDK满足apple ATS的要求

删除好友逻辑的修改(增加是否删除会话选项)

修复呼叫时对方不在线,不能正确显示通话结束原因的问题


版本历史:更新日志 
下载地址:SDK下载
0
评论

ios V2.2.9 SDK 已发布,SDK满足apple ATS的要求 ios_2.X iOS 产品快递

产品更新 发表了文章 • 588 次浏览 • 2016-12-08 18:54 • 来自相关话题

ios 版本 V3.2.2 2016-12-08





 
新功能/优化:

SDK满足apple ATS的要求

删除好友逻辑的修改(增加是否删除会话选项)

修复呼叫时对方不在线,不能正确显示通话结束原因的问题

版本历史:更新日志 
下载地址:SDK下载 查看全部
ios 版本 V3.2.2 2016-12-08
7282.jpg_wh860_.jpg


 
新功能/优化:


SDK满足apple ATS的要求

删除好友逻辑的修改(增加是否删除会话选项)

修复呼叫时对方不在线,不能正确显示通话结束原因的问题


版本历史:更新日志 
下载地址:SDK下载
0
评论

ios V3.2.1 SDK 已发布,聊天室列表支持分页获取 iOS 产品快递

产品更新 发表了文章 • 425 次浏览 • 2016-12-07 11:33 • 来自相关话题

ios V3.2.1 2016-11-12
 





新功能/优化:
 
聊天室列表支持分页获取

EMOption中usingHttps默认为YES

bug fix:
修复Lite版本SDK编译warning的问题
 
版本历史:更新日志  
下载地址:SDK下载 查看全部
ios V3.2.1 2016-11-12
 
3099.jpg_wh860_.jpg


新功能/优化:
 

聊天室列表支持分页获取

EMOption中usingHttps默认为YES



bug fix:

修复Lite版本SDK编译warning的问题


 
版本历史:更新日志  
下载地址:SDK下载
0
评论

快速找到当前页面的viewcontroller,有助于看别人代码 iOS

奋斗的蜗牛 发表了文章 • 296 次浏览 • 2016-10-28 17:52 • 来自相关话题

下面我将上传一个微信demo试试看了下效果,只需要往工程里面添加创建的视图控制器,就可以快速找到当前页面的viewcontroler




 
下面我将上传一个微信demo试试看了下效果,只需要往工程里面添加创建的视图控制器,就可以快速找到当前页面的viewcontroler
屏幕快照_2016-10-28_下午5.47_.49_.png

 
1
评论

【环信集成笔记】入门篇-分享一些ios集成小技巧 环信集成笔记 iOS 环信_iOS

beyond 发表了文章 • 2349 次浏览 • 2016-09-20 11:47 • 来自相关话题

这段时间由于家里的事和工作上的项目太忙好久没写东西了,,,今天先说说集成环信的经验吧,也许会很乱,我会慢慢修改的。新手一枚第一次集成环信,咱们慢慢看算是我自己的一个整理。
 
一、准备工作:

1.环信官网http://www.easemob.com 也可以百度环信还是很好找的。

2.苹果账号,因为集成即时聊天要推送证书所以必须有账号,证书制作我就不在这里说了,可以上网查。

3.在环信创建APP上传推送证书,这样可以了过程很简单的(appkey,推送证书名是有用的可以先记录)。

二、开始集成

1.这里总的说一下

第一点环信提供两套SDK,一套带有实时语音(打电话)版本HyphenateFullSDK,一套没有的HyphenateSDK,正常不是专门聊天的APP都是不用实时语音功能的。

第二点环信给了一套做好的UI叫EaseUI,可以用能减少很多时间,特别是聊天页面自己写比较麻烦,特别是EASYUI中的Model写的都不错。

第三点集成方式可以手动集成SDK,可以用pod集成,手动集成就不说了,下面给出pod命令
pod 'HyphenateSDK', :git => 'https://github.com/easemob/hyphenate-cocoapods.git'
pod 'HyphenateFullSDK', :git => 'https://github.com/easemob/hyphenate-full-cocoapods.git'
pod 'EaseUI' //这个环信官网上没有是我在环信的git上找到的。
这里有一点注意的地方,如果你用pod集成,注意pod更新是会吧你改动的代码刷新掉,可以不直接用EaseUI里的东西,可以写子类,或者刷新掉了,可以用git或者svn,还原修改,这样就能回来了。
这样我们就把SDK集成到我们的项目了。

第四点什么改APP端做什么该服务端做,服务端做的其实很少,只有两个,第一注册环信,这部分一般式绑定在我们注册当前APP用户的要查询服务端数据库,所以服务端做,第二,好友关系,环信用户之间聊天是不需要好友关系的,所以决定了好友关系这部分可以直接用我们的服务端维护。这两点以外,其他的都是我们APP端做,,,

2.开始代码部分(这部分代码前提集成了HyphenateSDK和EaseUI)

首先在AppDelegate中注册环信,直接可用EaseUI中的注册方法

一,注册环信/*!

* APP启动时注册环信,并登陆当前用户(如果有用户的话)

*

* @param application application description

* @param launchOptions launchOptions description

*/

-(void)startHuanXinEasyUIUseapplication:(UIApplication *)application Options:(NSDictionary *)launchOptions{

//AppKey:注册的AppKey,详细见下面注释。

//apnsCertName:推送证书名(不需要加后缀),详细见下面注释。

NSString *apnsCertName = nil;

#if DEBUG

apnsCertName = @"开发环境测试证书";

#else

apnsCertName = @"发布环境证书";

#endif

//环信appkey存放在UserDefaults

NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];

NSString *appkey = [ud stringForKey:@"identifier_appkey"];

if (!appkey) {

appkey = @"appkey";

[ud setObject:appkey forKey:@"identifier_appkey"];

}

//利用EasyUI启动环信(这里有一个坑,下面这个注册方法,点进去看会吧用户是否同意被加进群组改成NO,这里会导致群组加不上,记得看看,改回来)

[[EaseSDKHelper shareHelper] easemobApplication:application

didFinishLaunchingWithOptions:launchOptions

appkey:appkey

apnsCertName:apnsCertName

otherConfig:@{kSDKConfigEnableConsoleLogger:[NSNumber numberWithBool:YES]}];

//判断当前是否有用户,有用户登陆环信

if (DEF_PERSISTENT_GET_OBJECT(@"userId") != nil && DEF_PERSISTENT_GET_OBJECT(@"userPwd") != nil) {

NSString *accound = [NSString stringWithFormat:@"%@",DEF_PERSISTENT_GET_OBJECT(@"userId")];

NSString *pwd = [NSString stringWithFormat:@"%@",DEF_PERSISTENT_GET_OBJECT(@"userPwd")];

EMError *error = [[EMClient sharedClient] loginWithUsername:accound password:pwd];

[[EMClient sharedClient] addDelegate:self];

if (!error) {

NSLog(@"登录成功");
}
}
}二、环信好友

聊天就得有好友,刚开始总述中说过好友是咱们服务器来维护的所以不用管,走一个接口就好了,因为我们集成EasyUI所以我们从接口中请求的数据最好都变成EasyUI中用户的Model(EaseUserModel),这样在用环信聊天的是不会那么麻烦,也可以自己写一个Model这样麻烦但是不乱,#import <Foundation/Foundation.h>

#import "IUserModel.h"

@interface EaseUserModel : NSObject<IUserModel>

@property (strong, nonatomic, readonly) NSString *buddy;//用户名
@property (strong, nonatomic) NSString *nickname;//名字
@property (strong, nonatomic) NSString *avatarURLPath;//头像地址
@property (strong, nonatomic) UIImage *avatarImage;//头像图片

- (instancetype)initWithBuddy:(NSString *)buddy;

@end中用户名是最有用的是直接用于创建聊天的,名字头像用处不大,因为这些存储都是在本地的,地方一改名字头像你这面可能没反应所以不用这个头像名字,都是使用消息中的扩展字段下面说。

三、环信聊天

聊天界面我推荐直接用EasyUI的,自己写各种消息cell实在麻烦,使用这个直接就是可以聊天的创建方法如下EaseMessageViewController *viewController = [[EaseMessageViewController alloc] initWithConversationChatter:@"聊天对象的id,或者群组的id" conversationType:聊天的类型(单聊,群聊,聊天室)];四、环信聊天消息重点*

在发消息过程中是不能传递用户名和头像的,所以我们要给消息添加扩展字段ext,(扩展字段可以其实就是发送消息是附带传递一个json数据),我们可以吧自己的头像和名字在每次发消息的时候发过去,这样聊天时,当前本人可以知道自己的名字和头像,聊天对方会在消息中发过来名字头像,这样两个人都有名字头像了,下面贴下我的扩展字段- (void)_sendMessage:(EMMessage *)message //发消息的总方法
{
message.ext = @{@"nikeName":_myNikeName,@"headImagePath":_myHeadImagePath}; //添加消息扩展,我的名字,我的头像

if (self.conversation.type == EMConversationTypeGroupChat){
message.chatType = EMChatTypeGroupChat;
}
else if (self.conversation.type == EMConversationTypeChatRoom){
message.chatType = EMChatTypeChatRoom;
}

[self addMessageToDataSource:message
progress:nil];

__weak typeof(self) weakself = self;
[[EMClient sharedClient].chatManager asyncSendMessage:message progress:nil completion:^(EMMessage *aMessage, EMError *aError) {
[weakself.tableView reloadData];
}];
}五、消息列表

消息列表的获取用环信的获取方法- (void)tableViewDidTriggerHeaderRefresh
{
NSArray *conversations = [[EMClient sharedClient].chatManager getAllConversations];//本地获取消息
NSArray* sorted = [conversations sortedArrayUsingComparator:
^(EMConversation *obj1, EMConversation* obj2){
EMMessage *message1 = [obj1 latestMessage];
EMMessage *message2 = [obj2 latestMessage];
if(message1.timestamp > message2.timestamp) {
return(NSComparisonResult)NSOrderedAscending;
}else {
return(NSComparisonResult)NSOrderedDescending;
}
}];
[self.tableView endHeaderFresh];
[self.tableView reloadData];
}这里有一个地方,再获得会话列表的时候,有几个数据要自己获取一下,头像,名字,最后一条消息内容,最后一条消息时间,消息未读数。其中消息未读数事环信帮我们做好的直接设置显示未读数就好
其中消息最后一条消息内容,最后一条消息时间。要我们获取最后一条消息在给其赋值//在tableView代理中写
cell.detailLabel.attributedText = [[EaseEmotionEscape sharedInstance] attStringFromTextForChatting:[self _latestMessageTitleForConversationModel:model]textFont:cell.detailLabel.font];
cell.timeLabel.text = [self _latestMessageTimeForConversationModel:model];

//获取最后一条消息内容
- (NSString *)_latestMessageTitleForConversationModel:(id<IConversationModel>)conversationModel
{
NSString *latestMessageTitle = @"";
EMMessage *lastMessage = [conversationModel.conversation latestMessage];
if (lastMessage) {
EMMessageBody *messageBody = lastMessage.body;
switch (messageBody.type) {
case EMMessageBodyTypeImage:{
latestMessageTitle = NSEaseLocalizedString(@"message.image1", @"[image]");
} break;
case EMMessageBodyTypeText:{
NSString *didReceiveText = [EaseConvertToCommonEmoticonsHelper
convertToSystemEmoticons:((EMTextMessageBody *)messageBody).text];
latestMessageTitle = didReceiveText;
} break;
case EMMessageBodyTypeVoice:{
latestMessageTitle = NSEaseLocalizedString(@"message.voice1", @"[voice]");
} break;
case EMMessageBodyTypeLocation: {
latestMessageTitle = NSEaseLocalizedString(@"message.location1", @"[location]");
} break;
case EMMessageBodyTypeVideo: {
latestMessageTitle = NSEaseLocalizedString(@"message.video1", @"
[video]");
} break;
case EMMessageBodyTypeFile: {
latestMessageTitle = NSEaseLocalizedString(@"message.file1", @"[file]");
} break;
default: {
} break;
}
}
return latestMessageTitle;
}

//获取最后一条消息时间
- (NSString *)_latestMessageTimeForConversationModel:(id<IConversationModel>)conversationModel
{
NSString *latestMessageTime = @"";
EMMessage *lastMessage = [conversationModel.conversation latestMessage];;
if (lastMessage) {
double timeInterval = lastMessage.timestamp ;
if(timeInterval > 140000000000) {
timeInterval = timeInterval / 1000;
}
NSDateFormatter* formatter = [[NSDateFormatter alloc]init];
[formatter setDateFormat:@"YYYY-MM-dd"];
latestMessageTime = [formatter stringFromDate:[NSDate dateWithTimeIntervalSince1970:timeInterval]];
}
return latestMessageTime;
}[/video]




至于头像名字则要我们使用我没发消息的扩展字段,
这里我们可以获取会话中最后一条来自对方的消息,然后取出扩展字段赋值就好,(ps:有一种情况我刚刚和一个人说话会话中没有来自对方的最后一条消息,这个时候我们要用我们本地自己赋值这个会话的头像和名字)

三、总结

恩,这是这个杂记的第一次写的东西,写了写基本的用法,简单的聊天功能,写的也比较乱,,我会慢慢整理修改的。
 
作者简介:我是windKing,一个iOS开发的小白希望能有一样的人和我一起成长。看的过程中有不明白的地方欢迎联系我qq1662628982。 查看全部
这段时间由于家里的事和工作上的项目太忙好久没写东西了,,,今天先说说集成环信的经验吧,也许会很乱,我会慢慢修改的。新手一枚第一次集成环信,咱们慢慢看算是我自己的一个整理。
 
一、准备工作:

1.环信官网http://www.easemob.com 也可以百度环信还是很好找的。

2.苹果账号,因为集成即时聊天要推送证书所以必须有账号,证书制作我就不在这里说了,可以上网查。

3.在环信创建APP上传推送证书,这样可以了过程很简单的(appkey,推送证书名是有用的可以先记录)。

二、开始集成

1.这里总的说一下

第一点环信提供两套SDK,一套带有实时语音(打电话)版本HyphenateFullSDK,一套没有的HyphenateSDK,正常不是专门聊天的APP都是不用实时语音功能的。

第二点环信给了一套做好的UI叫EaseUI,可以用能减少很多时间,特别是聊天页面自己写比较麻烦,特别是EASYUI中的Model写的都不错。

第三点集成方式可以手动集成SDK,可以用pod集成,手动集成就不说了,下面给出pod命令
pod 'HyphenateSDK', :git => 'https://github.com/easemob/hyphenate-cocoapods.git'
pod 'HyphenateFullSDK', :git => 'https://github.com/easemob/hyphenate-full-cocoapods.git'
pod 'EaseUI' //这个环信官网上没有是我在环信的git上找到的。
这里有一点注意的地方,如果你用pod集成,注意pod更新是会吧你改动的代码刷新掉,可以不直接用EaseUI里的东西,可以写子类,或者刷新掉了,可以用git或者svn,还原修改,这样就能回来了。
这样我们就把SDK集成到我们的项目了。

第四点什么改APP端做什么该服务端做,服务端做的其实很少,只有两个,第一注册环信,这部分一般式绑定在我们注册当前APP用户的要查询服务端数据库,所以服务端做,第二,好友关系,环信用户之间聊天是不需要好友关系的,所以决定了好友关系这部分可以直接用我们的服务端维护。这两点以外,其他的都是我们APP端做,,,

2.开始代码部分(这部分代码前提集成了HyphenateSDK和EaseUI)

首先在AppDelegate中注册环信,直接可用EaseUI中的注册方法

一,注册环信
/*!

* APP启动时注册环信,并登陆当前用户(如果有用户的话)

*

* @param application application description

* @param launchOptions launchOptions description

*/

-(void)startHuanXinEasyUIUseapplication:(UIApplication *)application Options:(NSDictionary *)launchOptions{

//AppKey:注册的AppKey,详细见下面注释。

//apnsCertName:推送证书名(不需要加后缀),详细见下面注释。

NSString *apnsCertName = nil;

#if DEBUG

apnsCertName = @"开发环境测试证书";

#else

apnsCertName = @"发布环境证书";

#endif

//环信appkey存放在UserDefaults

NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];

NSString *appkey = [ud stringForKey:@"identifier_appkey"];

if (!appkey) {

appkey = @"appkey";

[ud setObject:appkey forKey:@"identifier_appkey"];

}

//利用EasyUI启动环信(这里有一个坑,下面这个注册方法,点进去看会吧用户是否同意被加进群组改成NO,这里会导致群组加不上,记得看看,改回来)

[[EaseSDKHelper shareHelper] easemobApplication:application

didFinishLaunchingWithOptions:launchOptions

appkey:appkey

apnsCertName:apnsCertName

otherConfig:@{kSDKConfigEnableConsoleLogger:[NSNumber numberWithBool:YES]}];

//判断当前是否有用户,有用户登陆环信

if (DEF_PERSISTENT_GET_OBJECT(@"userId") != nil && DEF_PERSISTENT_GET_OBJECT(@"userPwd") != nil) {

NSString *accound = [NSString stringWithFormat:@"%@",DEF_PERSISTENT_GET_OBJECT(@"userId")];

NSString *pwd = [NSString stringWithFormat:@"%@",DEF_PERSISTENT_GET_OBJECT(@"userPwd")];

EMError *error = [[EMClient sharedClient] loginWithUsername:accound password:pwd];

[[EMClient sharedClient] addDelegate:self];

if (!error) {

NSLog(@"登录成功");
}
}
}
二、环信好友

聊天就得有好友,刚开始总述中说过好友是咱们服务器来维护的所以不用管,走一个接口就好了,因为我们集成EasyUI所以我们从接口中请求的数据最好都变成EasyUI中用户的Model(EaseUserModel),这样在用环信聊天的是不会那么麻烦,也可以自己写一个Model这样麻烦但是不乱,
#import <Foundation/Foundation.h>

#import "IUserModel.h"

@interface EaseUserModel : NSObject<IUserModel>

@property (strong, nonatomic, readonly) NSString *buddy;//用户名
@property (strong, nonatomic) NSString *nickname;//名字
@property (strong, nonatomic) NSString *avatarURLPath;//头像地址
@property (strong, nonatomic) UIImage *avatarImage;//头像图片

- (instancetype)initWithBuddy:(NSString *)buddy;

@end
中用户名是最有用的是直接用于创建聊天的,名字头像用处不大,因为这些存储都是在本地的,地方一改名字头像你这面可能没反应所以不用这个头像名字,都是使用消息中的扩展字段下面说。

三、环信聊天

聊天界面我推荐直接用EasyUI的,自己写各种消息cell实在麻烦,使用这个直接就是可以聊天的创建方法如下
EaseMessageViewController *viewController = [[EaseMessageViewController alloc] initWithConversationChatter:@"聊天对象的id,或者群组的id" conversationType:聊天的类型(单聊,群聊,聊天室)];
四、环信聊天消息重点*

在发消息过程中是不能传递用户名和头像的,所以我们要给消息添加扩展字段ext,(扩展字段可以其实就是发送消息是附带传递一个json数据),我们可以吧自己的头像和名字在每次发消息的时候发过去,这样聊天时,当前本人可以知道自己的名字和头像,聊天对方会在消息中发过来名字头像,这样两个人都有名字头像了,下面贴下我的扩展字段
- (void)_sendMessage:(EMMessage *)message //发消息的总方法
{
message.ext = @{@"nikeName":_myNikeName,@"headImagePath":_myHeadImagePath}; //添加消息扩展,我的名字,我的头像

if (self.conversation.type == EMConversationTypeGroupChat){
message.chatType = EMChatTypeGroupChat;
}
else if (self.conversation.type == EMConversationTypeChatRoom){
message.chatType = EMChatTypeChatRoom;
}

[self addMessageToDataSource:message
progress:nil];

__weak typeof(self) weakself = self;
[[EMClient sharedClient].chatManager asyncSendMessage:message progress:nil completion:^(EMMessage *aMessage, EMError *aError) {
[weakself.tableView reloadData];
}];
}
五、消息列表

消息列表的获取用环信的获取方法
- (void)tableViewDidTriggerHeaderRefresh
{
NSArray *conversations = [[EMClient sharedClient].chatManager getAllConversations];//本地获取消息
NSArray* sorted = [conversations sortedArrayUsingComparator:
^(EMConversation *obj1, EMConversation* obj2){
EMMessage *message1 = [obj1 latestMessage];
EMMessage *message2 = [obj2 latestMessage];
if(message1.timestamp > message2.timestamp) {
return(NSComparisonResult)NSOrderedAscending;
}else {
return(NSComparisonResult)NSOrderedDescending;
}
}];
[self.tableView endHeaderFresh];
[self.tableView reloadData];
}
这里有一个地方,再获得会话列表的时候,有几个数据要自己获取一下,头像,名字,最后一条消息内容,最后一条消息时间,消息未读数。其中消息未读数事环信帮我们做好的直接设置显示未读数就好
其中消息最后一条消息内容,最后一条消息时间。要我们获取最后一条消息在给其赋值
//在tableView代理中写
cell.detailLabel.attributedText = [[EaseEmotionEscape sharedInstance] attStringFromTextForChatting:[self _latestMessageTitleForConversationModel:model]textFont:cell.detailLabel.font];
cell.timeLabel.text = [self _latestMessageTimeForConversationModel:model];

//获取最后一条消息内容
- (NSString *)_latestMessageTitleForConversationModel:(id<IConversationModel>)conversationModel
{
NSString *latestMessageTitle = @"";
EMMessage *lastMessage = [conversationModel.conversation latestMessage];
if (lastMessage) {
EMMessageBody *messageBody = lastMessage.body;
switch (messageBody.type) {
case EMMessageBodyTypeImage:{
latestMessageTitle = NSEaseLocalizedString(@"message.image1", @"[image]");
} break;
case EMMessageBodyTypeText:{
NSString *didReceiveText = [EaseConvertToCommonEmoticonsHelper
convertToSystemEmoticons:((EMTextMessageBody *)messageBody).text];
latestMessageTitle = didReceiveText;
} break;
case EMMessageBodyTypeVoice:{
latestMessageTitle = NSEaseLocalizedString(@"message.voice1", @"[voice]");
} break;
case EMMessageBodyTypeLocation: {
latestMessageTitle = NSEaseLocalizedString(@"message.location1", @"[location]");
} break;
case EMMessageBodyTypeVideo: {
latestMessageTitle = NSEaseLocalizedString(@"message.video1", @"
[video]");
} break;
case EMMessageBodyTypeFile: {
latestMessageTitle = NSEaseLocalizedString(@"message.file1", @"[file]");
} break;
default: {
} break;
}
}
return latestMessageTitle;
}

//获取最后一条消息时间
- (NSString *)_latestMessageTimeForConversationModel:(id<IConversationModel>)conversationModel
{
NSString *latestMessageTime = @"";
EMMessage *lastMessage = [conversationModel.conversation latestMessage];;
if (lastMessage) {
double timeInterval = lastMessage.timestamp ;
if(timeInterval > 140000000000) {
timeInterval = timeInterval / 1000;
}
NSDateFormatter* formatter = [[NSDateFormatter alloc]init];
[formatter setDateFormat:@"YYYY-MM-dd"];
latestMessageTime = [formatter stringFromDate:[NSDate dateWithTimeIntervalSince1970:timeInterval]];
}
return latestMessageTime;
}[/video]




至于头像名字则要我们使用我没发消息的扩展字段,
这里我们可以获取会话中最后一条来自对方的消息,然后取出扩展字段赋值就好,(ps:有一种情况我刚刚和一个人说话会话中没有来自对方的最后一条消息,这个时候我们要用我们本地自己赋值这个会话的头像和名字)

三、总结

恩,这是这个杂记的第一次写的东西,写了写基本的用法,简单的聊天功能,写的也比较乱,,我会慢慢整理修改的。
 
作者简介:我是windKing,一个iOS开发的小白希望能有一样的人和我一起成长。看的过程中有不明白的地方欢迎联系我qq1662628982。
0
评论

环信SDK集成日记-自定义消息工具条和表情键盘 iOS 表情 环信 iOS集成 环信

beyond 发表了文章 • 1497 次浏览 • 2016-09-12 19:20 • 来自相关话题

今天看了下环信SDK聊天模块下封装的自定义消息工具条以及表情键盘,了解了下iOS自带表情的转码。顺[b]带记录下集成环信SDK遇到的坑。[/b]一、DXMessageToolBar工具条的封装

DXMessageToolBar是加在聊天页面底部的消息工具条,其负责控制四个控件:

1、录音视图DXRecordView
2、输入文本框XHMessageTextView
3、表情键盘DXFaceView
4、更多视图DXChatBarMoreView

DXMessageToolBar不仅要控制这四个UIView之间的切换,负责正确改变视图的位置与大小,还要将一系列的动作事件通过委托传到聊天页面控制器ChatViewController中去处理。

DXRecordView是录制音频的时候展示的视图,通过不断检测音量大小来展示不同的UIImage从而达到动态效果。

XHMessageTextView是继承自UITextView的,其中实现了自定义placeHolder的颜色,由于修改placeHolder是私有方法,因此这里换了种方式实现,那就是通过重写drawRect:方法,在其中将placeHolder绘制到UITextView上面。

DXFaceView表情键盘,它上面加了FacialView。FacialView上面放置了一些列的UIButton,UIButton的标题设置为iOS自带的表情。

DXChatBarMoreView更多视图,上面添加从相册选择照片,调用相机拍摄照片等按钮。

二、表情键盘的封装

表情键盘上放置了一些iOS自带的表情,而且在最后加了一个发送按钮和一个删除按钮,如图:





点击对应的表情,将表情字符串添加到文本框中。点击删除按钮通过委托调用了DXMessageToolBar的方法,其实现方式:- (void)selectedFacialView:(NSString *)str isDelete:(BOOL)isDelete
{
NSString *chatText = self.inputTextView.text;

if (!isDelete && str.length > 0) {
self.inputTextView.text = [NSString stringWithFormat:@"%@%@",chatText,str];
}
else {
if (chatText.length >= 2)
{
NSString *subStr = [chatText substringFromIndex:chatText.length-2];
if ([(DXFaceView *)self.faceView stringIsFace:subStr]) {
self.inputTextView.text = [chatText substringToIndex:chatText.length-2];
[self textViewDidChange:self.inputTextView];
return;
}
}

if (chatText.length > 0) {
self.inputTextView.text = [chatText substringToIndex:chatText.length-1];
}
}

[self textViewDidChange:self.inputTextView];
}由此可见一个表情字符串占两个字符的长度,所以要在点击删除按钮之后判断文本框当前字符串的末尾处是否是表情。

三、iOS自带表情的转码

每个表情都有它自己的编码,通过编码可以拿到它对应的表情字符串:#import <Foundation/Foundation.h>

#define MAKE_Q(x) @#x
#define MAKE_EM(x,y) MAKE_Q(x##y)

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunicode"
#define MAKE_EMOJI(x) MAKE_EM(\U000,x)
#pragma clang diagnostic pop

#define EMOJI_METHOD(x,y) + (NSString *)x { return MAKE_EMOJI(y); }
#define EMOJI_HMETHOD(x) + (NSString *)x;
#define EMOJI_CODE_TO_SYMBOL(x) ((((0x808080F0 | (x & 0x3F000) >> 4) | (x & 0xFC0) << 10) | (x & 0x1C0000) << 18) | (x & 0x3F) << 24);

/*!
@class
@brief iOS内置表情编码处理类
*/
@interface Emoji : NSObject

/*!
@method
@brief unicode编码转换为iOS内置表情字符串
@discussion
@param code iOS内置表情对应的unicode编码值
@result iOS内置表情字符串
*/
+ (NSString *)emojiWithCode:(int)code;

/*!
@method
@brief 获取所有iOS内置表情
@discussion
@result iOS表情字符串数组
*/
+ (NSArray *)allEmoji;
@end

#import "Emoji.h"
#import "EmojiEmoticons.h"

@implementation Emoji
+ (NSString *)emojiWithCode:(int)code {
int sym = EMOJI_CODE_TO_SYMBOL(code);
return [[NSString alloc] initWithBytes:&sym length:sizeof(sym) encoding:NSUTF8StringEncoding];
}
+ (NSArray *)allEmoji {
NSMutableArray *array = [NSMutableArray new];
[array addObjectsFromArray:[EmojiEmoticons allEmoticons]];
return array;
}
@end这里拿表情编码做一系列运算,然后传入[[NSString alloc] initWithBytes:&sym length:sizeof(sym) encoding:NSUTF8StringEncoding];生成对应的字符串的运算方法暂时没找到解释,没搞清楚运算原理。若哪位大神看到出处,敬请告知!

四、集成环信遇到的坑

1、显示用户头像和昵称

环信默认不显示头像和昵称,如果要显示头像,需要自己实现。这里要实现显示头像和昵称,需要发消息的时候在ext这个扩展字段中加入和Android约定好的字段,在接收到消息的时候将这些字段保存到本地数据库中。

这里特别需要注意的一点是:在读取本地数据库的时候,是通过chatter来匹配的话,记得环信的chatter在保存的时候都做了小写处理。所以,在数据库中匹配的时候,最好是大小写不敏感的匹配方式:NSString *sql = [NSString stringWithFormat:@"select * from %@
where upper(chatter)=upper('%@')",_tbName,chatter];2、自定义推送内容

环信默认推送内容是“你有一条新的消息”,如果要自定义内容,需要在在ext扩展字段中添加以下字段:@"em_apns_ext":@{
@"em_push_title":文本内容
},3、发送用户信息给客服

如果要在用户跟客服聊天时能在客服后台看到当前用户的一些基本信息,需要在ext扩展字段中添加以下字段(比如需要用户手机号码和名字):@"weichat":@{
@"visitor":@{
@"phone":userPhone,
@"userNickname":userNickName,
}
},4、发送用户轨迹给客服

如果要在用户点击客服咨询时,比如是点击职位下面的咨询,想将当期职位的信息发给客服,那就是环信的发送用户轨迹信息。这个时候在进入聊天页面时需要单独处理,自动发送一个自己封装的信息:-(void)sendJobInfo:(YLJob *)aJob
{
NSMutableDictionary *ext = [NSMutableDictionary dictionary];
NSDictionary *msgtype = @{@"track":@{@"title":aJob.title?:@"",
@"item_url":aJob.link?:@""}};
[ext setObject:msgtype forKey:@"msgtype"];

EMChatText *text = [[EMChatText alloc] initWithText:[NSString stringWithFormat:@"我想咨询 %@",aJob.title?:@""]];
EMTextMessageBody *body = [[EMTextMessageBody alloc] initWithChatObject:text];
EMMessage *message = [[EMMessage alloc] initWithReceiver:_chatter bodies:[NSArray arrayWithObject:body]];
message.ext = ext;
[[EaseMob sharedInstance].chatManager asyncSendMessage:message progress:nil];

}这里只是简单的使用了环信已经封装好的文本消息的接口,并且在聊天页面的展示方式也将是纯文本的方式Cell的展现方式。如果要以其他样式的Cell展示,需要自定义Cell,并且在展示的时候根据ext中的字段来判断是否是这种自定义消息。

其他具体可用字段名称,请登录环信官网查看官方文档。

参考:
以下是Emoji的表情编码对照表:
Emoji Unicode Tables
 
作者:Code_Ninja 查看全部
今天看了下环信SDK聊天模块下封装的自定义消息工具条以及表情键盘,了解了下iOS自带表情的转码。顺[b]带记录下集成环信SDK遇到的坑。[/b]
一、DXMessageToolBar工具条的封装

DXMessageToolBar是加在聊天页面底部的消息工具条,其负责控制四个控件:

1、录音视图DXRecordView
2、输入文本框XHMessageTextView
3、表情键盘DXFaceView
4、更多视图DXChatBarMoreView

DXMessageToolBar不仅要控制这四个UIView之间的切换,负责正确改变视图的位置与大小,还要将一系列的动作事件通过委托传到聊天页面控制器ChatViewController中去处理。

DXRecordView是录制音频的时候展示的视图,通过不断检测音量大小来展示不同的UIImage从而达到动态效果。

XHMessageTextView是继承自UITextView的,其中实现了自定义placeHolder的颜色,由于修改placeHolder是私有方法,因此这里换了种方式实现,那就是通过重写drawRect:方法,在其中将placeHolder绘制到UITextView上面。

DXFaceView表情键盘,它上面加了FacialView。FacialView上面放置了一些列的UIButton,UIButton的标题设置为iOS自带的表情。

DXChatBarMoreView更多视图,上面添加从相册选择照片,调用相机拍摄照片等按钮。

二、表情键盘的封装

表情键盘上放置了一些iOS自带的表情,而且在最后加了一个发送按钮和一个删除按钮,如图:

141604-f58b918d4fb3aad0.jpg

点击对应的表情,将表情字符串添加到文本框中。点击删除按钮通过委托调用了DXMessageToolBar的方法,其实现方式:
- (void)selectedFacialView:(NSString *)str isDelete:(BOOL)isDelete
{
NSString *chatText = self.inputTextView.text;

if (!isDelete && str.length > 0) {
self.inputTextView.text = [NSString stringWithFormat:@"%@%@",chatText,str];
}
else {
if (chatText.length >= 2)
{
NSString *subStr = [chatText substringFromIndex:chatText.length-2];
if ([(DXFaceView *)self.faceView stringIsFace:subStr]) {
self.inputTextView.text = [chatText substringToIndex:chatText.length-2];
[self textViewDidChange:self.inputTextView];
return;
}
}

if (chatText.length > 0) {
self.inputTextView.text = [chatText substringToIndex:chatText.length-1];
}
}

[self textViewDidChange:self.inputTextView];
}
由此可见一个表情字符串占两个字符的长度,所以要在点击删除按钮之后判断文本框当前字符串的末尾处是否是表情。

三、iOS自带表情的转码

每个表情都有它自己的编码,通过编码可以拿到它对应的表情字符串:
#import <Foundation/Foundation.h>

#define MAKE_Q(x) @#x
#define MAKE_EM(x,y) MAKE_Q(x##y)

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunicode"
#define MAKE_EMOJI(x) MAKE_EM(\U000,x)
#pragma clang diagnostic pop

#define EMOJI_METHOD(x,y) + (NSString *)x { return MAKE_EMOJI(y); }
#define EMOJI_HMETHOD(x) + (NSString *)x;
#define EMOJI_CODE_TO_SYMBOL(x) ((((0x808080F0 | (x & 0x3F000) >> 4) | (x & 0xFC0) << 10) | (x & 0x1C0000) << 18) | (x & 0x3F) << 24);

/*!
@class
@brief iOS内置表情编码处理类
*/
@interface Emoji : NSObject

/*!
@method
@brief unicode编码转换为iOS内置表情字符串
@discussion
@param code iOS内置表情对应的unicode编码值
@result iOS内置表情字符串
*/
+ (NSString *)emojiWithCode:(int)code;

/*!
@method
@brief 获取所有iOS内置表情
@discussion
@result iOS表情字符串数组
*/
+ (NSArray *)allEmoji;
@end

#import "Emoji.h"
#import "EmojiEmoticons.h"

@implementation Emoji
+ (NSString *)emojiWithCode:(int)code {
int sym = EMOJI_CODE_TO_SYMBOL(code);
return [[NSString alloc] initWithBytes:&sym length:sizeof(sym) encoding:NSUTF8StringEncoding];
}
+ (NSArray *)allEmoji {
NSMutableArray *array = [NSMutableArray new];
[array addObjectsFromArray:[EmojiEmoticons allEmoticons]];
return array;
}
@end
这里拿表情编码做一系列运算,然后传入[[NSString alloc] initWithBytes:&sym length:sizeof(sym) encoding:NSUTF8StringEncoding];生成对应的字符串的运算方法暂时没找到解释,没搞清楚运算原理。若哪位大神看到出处,敬请告知!

四、集成环信遇到的坑

1、显示用户头像和昵称

环信默认不显示头像和昵称,如果要显示头像,需要自己实现。这里要实现显示头像和昵称,需要发消息的时候在ext这个扩展字段中加入和Android约定好的字段,在接收到消息的时候将这些字段保存到本地数据库中。

这里特别需要注意的一点是:在读取本地数据库的时候,是通过chatter来匹配的话,记得环信的chatter在保存的时候都做了小写处理。所以,在数据库中匹配的时候,最好是大小写不敏感的匹配方式:
NSString *sql = [NSString stringWithFormat:@"select * from %@ 
where upper(chatter)=upper('%@')",_tbName,chatter];
2、自定义推送内容

环信默认推送内容是“你有一条新的消息”,如果要自定义内容,需要在在ext扩展字段中添加以下字段:
@"em_apns_ext":@{                           
@"em_push_title":文本内容
},
3、发送用户信息给客服

如果要在用户跟客服聊天时能在客服后台看到当前用户的一些基本信息,需要在ext扩展字段中添加以下字段(比如需要用户手机号码和名字):
@"weichat":@{
@"visitor":@{
@"phone":userPhone,
@"userNickname":userNickName,
}
},
4、发送用户轨迹给客服

如果要在用户点击客服咨询时,比如是点击职位下面的咨询,想将当期职位的信息发给客服,那就是环信的发送用户轨迹信息。这个时候在进入聊天页面时需要单独处理,自动发送一个自己封装的信息:
-(void)sendJobInfo:(YLJob *)aJob
{
NSMutableDictionary *ext = [NSMutableDictionary dictionary];
NSDictionary *msgtype = @{@"track":@{@"title":aJob.title?:@"",
@"item_url":aJob.link?:@""}};
[ext setObject:msgtype forKey:@"msgtype"];

EMChatText *text = [[EMChatText alloc] initWithText:[NSString stringWithFormat:@"我想咨询 %@",aJob.title?:@""]];
EMTextMessageBody *body = [[EMTextMessageBody alloc] initWithChatObject:text];
EMMessage *message = [[EMMessage alloc] initWithReceiver:_chatter bodies:[NSArray arrayWithObject:body]];
message.ext = ext;
[[EaseMob sharedInstance].chatManager asyncSendMessage:message progress:nil];

}
这里只是简单的使用了环信已经封装好的文本消息的接口,并且在聊天页面的展示方式也将是纯文本的方式Cell的展现方式。如果要以其他样式的Cell展示,需要自定义Cell,并且在展示的时候根据ext中的字段来判断是否是这种自定义消息。

其他具体可用字段名称,请登录环信官网查看官方文档。

参考:
以下是Emoji的表情编码对照表:
Emoji Unicode Tables
 
作者:Code_Ninja
0
评论

【环信集成笔记】进阶篇-集成环信 ios 2.0,看这篇就够了 环信 环信 iOS集成 iOS 环信集成笔记

beyond 发表了文章 • 1854 次浏览 • 2016-09-12 19:12 • 来自相关话题

一、准备工作
 
1、注册环信帐号注册一个环信账号之后,我们用注册的帐号登陆。然后创建一个应用,会得到一个对应的AppKey,这个AppKey在初始化环信SDK的时候需要用到。(这个去环信官网自己弄环信)
 
2、制作推送证书如果需要做离线推送的功能,需要制作一个推送证书。如果只是需要实现单聊、群聊等功能,可以跳过此步骤。个人建议刚开始接触环信的开发者可以忽略此步骤。制作证书
 
3、下载环信sdk.下的是2.0





二、集成环信的SDK

1、把环信SDK添加到工程中

从环信官网下载下来的是一个压缩包,解压之后,把我们需要的环信SDK,即EaseMobSDK这个文件夹,整个的拖入到我们的工程中。如下图:




在lib文件夹下面有两个静态库,只需要用到一个,根据你的需求选择。libEaseMobClientSDKLite.a不包含实时语音功能,libEaseMobClientSDK.a包含所有功能。2、添加对应的依赖库

向Build Phases → Link Binary With Libraries 中添加依赖库
MobileCoreServices.frameworkCFNetwork.framelibsqlite3.tbdlibstdc++.6.0.9.tbdlibz.tbdlibiconv.tbdlibresolv.tbdlibxml2.tbd
温馨提示:注意不要添加错了,也不能添加少了,添加完毕之后,不要着急,先编译一下。编译成功,则说明没有问题;如果编译报错,则仔细对照上面例举的静态库进行添加,直到编译成功,再进行下一步。
 
3、配置工程
 
3.1 不包含语音静态库的配置方法
(1) 删掉libEaseMobClientSDK.a,保留libEaseMobClientSDKLite.a;
(2) 在Build Settings -> Other Linker Flags 添加”fore_load”和”libEaseMobClientSDKLite.a”的相对路径。
如下图所示:




3.2 包含语音静态库的配置方法

(1) 删掉libEaseMobClientSDKLite.a,保留libEaseMobClientSDK.a;

(2) 在Build Settings -> Other Linker Flags 添加”-ObjC”。

如下图所示:




4、验证SDK是否添加成功
在AppDelegate.m文件中添加环信SDK初始化的方法,记得添加头文件”EaseMob.h”。下面提供了我用的测试AppKey,你可以替换成你自己申请的AppKey。编译成功,则说明你已经正确集成了环信的SDK了。
 
如果编译有问题,可能存在的原因:
(1) 静态库没有添加正确;
(2) 静态库工程配置不正确#define APPKEY @"1101#testrongyun" //环信APPKEY
#define APNSCert @"TestHuanXin" //环信推送证书名称
#import "AppDelegate.h"
#import "EaseMob.h"
@interface AppDelegate ()
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//初始化环信SDK
[[EaseMob sharedInstance] registerSDKWithAppKey:APPKEY apnsCertName:APNSCert];
return YES;
}






三、添加UI文件到你的工程

集成环信2.0UI文件,需要添加的文件,如下图所示:




添加完成之后,如下图所示:




四、设置pch文件的路径
 
文件添加成功之后,编译会报错,因为你没有添加pch文件。自己手动添加pch文件(EaseUI-Prefix.pch),设置一下pch文件的加载路径即可。如下图所示:




在EaseUI-Prefix.pch中添加头文件”EaseUI.h”,如下图:




最后,编译一下,编译成功则说明添加集成UI文件成功。
 
五,搭建基本框架
 
1、新建三个UIViewController
 
新建三个ViewController,继承UIViewController,分别命名为:FirstViewController,SecondViewController,ThirdViewController。如下图所示





2、添加登陆方法

在AppDelegate.m中添加如下代码:#define APPKEY @"1101#testrongyun" //环信APPKEY
#define APNSCert @"TestHuanXin" //环信推送证书名称
#import "AppDelegate.h"
#import "EaseMob.h"
#import "FirstViewController.h"
#import "SecondViewController.h"
#import "ThirdViewController.h"
@interface AppDelegate ()
@end@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
//初始化环信SDK
[[EaseMob sharedInstance] registerSDKWithAppKey:APPKEY apnsCertName:APNSCert];
//异步登陆的方法(这里的账号密码要去环信后台自己注册)
[[EaseMob sharedInstance].chatManager asyncLoginWithUsername:@"账号" password:@"密码" completion:^(NSDictionary *loginInfo, EMError *error) {
if (!error && loginInfo) {
NSLog(@"登陆成功");
[self setUpNav];
}
} onQueue:nil];
return YES;
}
- (void)setUpNav
{
FirstViewController *firstVC = [[FirstViewController alloc] init];
SecondViewController *secondVC = [[SecondViewController alloc] init];
ThirdViewController *thirdVC = [[ThirdViewController alloc] init];
firstVC.title = @"会话列表";
secondVC.title = @"通讯录";
thirdVC.title = @"设置";
UITabBarController *tabBar = [[UITabBarController alloc] init];
tabBar.viewControllers = @[[[UINavigationController alloc] initWithRootViewController:firstVC],
[[UINavigationController alloc] initWithRootViewController:secondVC],
[[UINavigationController alloc] initWithRootViewController:thirdVC]];
self.window.rootViewController = tabBar;
self.window.backgroundColor = [UIColor whiteColor];
}
@end编译一下,看下效果。




六、添加与聊天有关的文件

1、添加GifImage文件2、添加chat文件








添加完成之后,编译一下,把报错的地方全部注释掉,有很多地方需要注释掉,这些地方是因为有些我们不需要的文件没有添加进来。(自己注释比较麻烦)
 
注释好的GifImage和chat文件,下载后无需注释无关代码,可直接使用注释好的文件,
 
七、实现单聊在SecondViewController.m中添加如下代码:#import "SecondViewController.h"#import "ChatViewController.h"@interface SecondViewController (){
NSArray *arrSystem;
NSArray *arrFriends;
}
@property (retain, nonatomic) UITableView *tableView;
@end
@implementation SecondViewController
- (void)viewDidLoad {
[super viewDidLoad];
arrSystem = @[@"申请与通知",@"群聊",@"聊天室"];
_tableView = [[UITableView alloc] initWithFrame:self.view.frame];
_tableView.delegate = self;
_tableView.dataSource = self;
[self.view addSubview:_tableView];
//获取好友列表
[[EaseMob sharedInstance].chatManager asyncFetchBuddyListWithCompletion:^(NSArray *buddyList, EMError *error) {
if (!error) {
NSLog(@"获取成功 -- %@",buddyList);
arrFriends = [NSArray arrayWithArray:buddyList];
[_tableView reloadData];
}
} onQueue:nil];
}
#pragma mark - UITableViewDelegate & UITableViewDataSource
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 2;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (section == 0) {
return arrSystem.count;
} else {
return arrFriends.count;
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *identifier = @"CELL";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
}
switch (indexPath.section) {
case 0:
{
cell.textLabel.text = [arrSystem objectAtIndex:indexPath.row];
cell.imageView.image = [UIImage imageNamed:@"groupPublicHeader"];
break;
}
case 1:
{
EMBuddy *eMBuddy = [arrFriends objectAtIndex:indexPath.row];
cell.textLabel.text = eMBuddy.username;
cell.imageView.image = [UIImage imageNamed:@"chatListCellHead"];
break;
}
default:
break;
}
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
EMBuddy *buddy = [arrFriends objectAtIndex:indexPath.row];
ChatViewController *chatVC = [[ChatViewController alloc] initWithConversationChatter:buddy.username conversationType:eConversationTypeChat];
chatVC.title = buddy.username; //好友的名字
chatVC.hidesBottomBarWhenPushed = YES;
[self.navigationController pushViewController:chatVC animated:YES];
}编译,效果








真机运行一下,可能会报错,





解决方案:




把这个值设置成no

文章作者:环信热心用户樊呵呵 查看全部
一、准备工作
 
1、注册环信帐号注册一个环信账号之后,我们用注册的帐号登陆。然后创建一个应用,会得到一个对应的AppKey,这个AppKey在初始化环信SDK的时候需要用到。(这个去环信官网自己弄环信)
 
2、制作推送证书如果需要做离线推送的功能,需要制作一个推送证书。如果只是需要实现单聊、群聊等功能,可以跳过此步骤。个人建议刚开始接触环信的开发者可以忽略此步骤。制作证书
 
3、下载环信sdk.下的是2.0
2267403-9029e76fb6048493.jpg


二、集成环信的SDK

1、把环信SDK添加到工程中

从环信官网下载下来的是一个压缩包,解压之后,把我们需要的环信SDK,即EaseMobSDK这个文件夹,整个的拖入到我们的工程中。如下图:
2267403-2b2574629722ab00.jpg

在lib文件夹下面有两个静态库,只需要用到一个,根据你的需求选择。
libEaseMobClientSDKLite.a不包含实时语音功能,libEaseMobClientSDK.a包含所有功能。
2、添加对应的依赖库

向Build Phases → Link Binary With Libraries 中添加依赖库
  1. MobileCoreServices.framework
  2. CFNetwork.frame
  3. libsqlite3.tbd
  4. libstdc++.6.0.9.tbd
  5. libz.tbd
  6. libiconv.tbd
  7. libresolv.tbd
  8. libxml2.tbd

温馨提示:注意不要添加错了,也不能添加少了,添加完毕之后,不要着急,先编译一下。编译成功,则说明没有问题;如果编译报错,则仔细对照上面例举的静态库进行添加,直到编译成功,再进行下一步。
 
3、配置工程
 
3.1 不包含语音静态库的配置方法
(1) 删掉libEaseMobClientSDK.a,保留libEaseMobClientSDKLite.a;
(2) 在Build Settings -> Other Linker Flags 添加”fore_load”和”libEaseMobClientSDKLite.a”的相对路径。
如下图所示:
2267403-18e5a986d5358977.jpg

3.2 包含语音静态库的配置方法

(1) 删掉libEaseMobClientSDKLite.a,保留libEaseMobClientSDK.a;

(2) 在Build Settings -> Other Linker Flags 添加”-ObjC”。

如下图所示:
2267403-9aa919e2913c9de1.jpg

4、验证SDK是否添加成功
在AppDelegate.m文件中添加环信SDK初始化的方法,记得添加头文件”EaseMob.h”。下面提供了我用的测试AppKey,你可以替换成你自己申请的AppKey。编译成功,则说明你已经正确集成了环信的SDK了。
 
如果编译有问题,可能存在的原因:
(1) 静态库没有添加正确;
(2) 静态库工程配置不正确
#define APPKEY      @"1101#testrongyun"     //环信APPKEY
#define APNSCert @"TestHuanXin" //环信推送证书名称
#import "AppDelegate.h"
#import "EaseMob.h"
@interface AppDelegate ()
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//初始化环信SDK
[[EaseMob sharedInstance] registerSDKWithAppKey:APPKEY apnsCertName:APNSCert];
return YES;
}






三、添加UI文件到你的工程

集成环信2.0UI文件,需要添加的文件,如下图所示:
2267403-8f645439e0dface8.jpg

添加完成之后,如下图所示:
2267403-b2667ca172b29da9.jpg

四、设置pch文件的路径
 
文件添加成功之后,编译会报错,因为你没有添加pch文件。自己手动添加pch文件(EaseUI-Prefix.pch),设置一下pch文件的加载路径即可。如下图所示:
2267403-da9cd01f79acf31a.jpg

在EaseUI-Prefix.pch中添加头文件”EaseUI.h”,如下图:
2267403-0280af41aced006c.jpg

最后,编译一下,编译成功则说明添加集成UI文件成功。
 
五,搭建基本框架
 
1、新建三个UIViewController
 
新建三个ViewController,继承UIViewController,分别命名为:FirstViewController,SecondViewController,ThirdViewController。如下图所示

2267403-ad785fff16328185.jpg

2、添加登陆方法

在AppDelegate.m中添加如下代码:
#define APPKEY      @"1101#testrongyun"     //环信APPKEY
#define APNSCert @"TestHuanXin" //环信推送证书名称
#import "AppDelegate.h"
#import "EaseMob.h"
#import "FirstViewController.h"
#import "SecondViewController.h"
#import "ThirdViewController.h"
@interface AppDelegate ()
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
//初始化环信SDK
[[EaseMob sharedInstance] registerSDKWithAppKey:APPKEY apnsCertName:APNSCert];
//异步登陆的方法(这里的账号密码要去环信后台自己注册)
[[EaseMob sharedInstance].chatManager asyncLoginWithUsername:@"账号" password:@"密码" completion:^(NSDictionary *loginInfo, EMError *error) {
if (!error && loginInfo) {
NSLog(@"登陆成功");
[self setUpNav];
}
} onQueue:nil];
return YES;
}
- (void)setUpNav
{
FirstViewController *firstVC = [[FirstViewController alloc] init];
SecondViewController *secondVC = [[SecondViewController alloc] init];
ThirdViewController *thirdVC = [[ThirdViewController alloc] init];
firstVC.title = @"会话列表";
secondVC.title = @"通讯录";
thirdVC.title = @"设置";
UITabBarController *tabBar = [[UITabBarController alloc] init];
tabBar.viewControllers = @[[[UINavigationController alloc] initWithRootViewController:firstVC],
[[UINavigationController alloc] initWithRootViewController:secondVC],
[[UINavigationController alloc] initWithRootViewController:thirdVC]];
self.window.rootViewController = tabBar;
self.window.backgroundColor = [UIColor whiteColor];
}
@end
编译一下,看下效果。
2267403-687ab41414eac944.png

六、添加与聊天有关的文件

1、添加GifImage文件2、添加chat文件
2267403-7543120b78599164.jpg

2267403-fc7cc6d2aac8ddb8.jpg

添加完成之后,编译一下,把报错的地方全部注释掉,有很多地方需要注释掉,这些地方是因为有些我们不需要的文件没有添加进来。(自己注释比较麻烦)
 
注释好的GifImage和chat文件,下载后无需注释无关代码,可直接使用注释好的文件,
 
七、实现单聊在SecondViewController.m中添加如下代码:
#import "SecondViewController.h"#import "ChatViewController.h"@interface SecondViewController (){
NSArray *arrSystem;
NSArray *arrFriends;
}
@property (retain, nonatomic) UITableView *tableView;
@end
@implementation SecondViewController
- (void)viewDidLoad {
[super viewDidLoad];
arrSystem = @[@"申请与通知",@"群聊",@"聊天室"];
_tableView = [[UITableView alloc] initWithFrame:self.view.frame];
_tableView.delegate = self;
_tableView.dataSource = self;
[self.view addSubview:_tableView];
//获取好友列表
[[EaseMob sharedInstance].chatManager asyncFetchBuddyListWithCompletion:^(NSArray *buddyList, EMError *error) {
if (!error) {
NSLog(@"获取成功 -- %@",buddyList);
arrFriends = [NSArray arrayWithArray:buddyList];
[_tableView reloadData];
}
} onQueue:nil];
}
#pragma mark - UITableViewDelegate & UITableViewDataSource
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 2;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (section == 0) {
return arrSystem.count;
} else {
return arrFriends.count;
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *identifier = @"CELL";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
}
switch (indexPath.section) {
case 0:
{
cell.textLabel.text = [arrSystem objectAtIndex:indexPath.row];
cell.imageView.image = [UIImage imageNamed:@"groupPublicHeader"];
break;
}
case 1:
{
EMBuddy *eMBuddy = [arrFriends objectAtIndex:indexPath.row];
cell.textLabel.text = eMBuddy.username;
cell.imageView.image = [UIImage imageNamed:@"chatListCellHead"];
break;
}
default:
break;
}
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
EMBuddy *buddy = [arrFriends objectAtIndex:indexPath.row];
ChatViewController *chatVC = [[ChatViewController alloc] initWithConversationChatter:buddy.username conversationType:eConversationTypeChat];
chatVC.title = buddy.username; //好友的名字
chatVC.hidesBottomBarWhenPushed = YES;
[self.navigationController pushViewController:chatVC animated:YES];
}
编译,效果
2267403-b19aab1a08432dfb.png

2267403-f96603327daee041.png

真机运行一下,可能会报错,

2267403-683387308562a7d6.jpg

解决方案:
2267403-4a01a8288cd4c024.jpg

把这个值设置成no

文章作者:环信热心用户樊呵呵
0
评论

【推荐】两大APP与云账户红包SDK集成详情及Demon分享 红包SDK iOS 云账户 Android

云账户 发表了文章 • 641 次浏览 • 2016-09-07 10:54 • 来自相关话题

云账户红包SDK3.0已经发布一段时间了!
就在本月,两家老牌重量级APP完成了云账户红包SDK的集成,经过双方严格的联合测试,现在正是发版了!他们就是“工作圈”&“超信”(IOS、安卓市场都可以下载哦,有兴趣的朋友们可以去市场里下载体验一下,绝对nice!)
可能有朋友不太了解这两个百万级用户的产品,这里我给大家正是介绍一下:
工作圈:
g工作圈是用友旗下畅捷通公司开发的一款企业移动办公平台,融合了企业经营管理所需的各项专业服务,是基于工作场景应用集成:公告、审批、任务、工作报告、签到、文件柜、电话会议、企业通讯录、圈子信息交流等组成的企业互联网应用,帮助企业提高沟通协作效率、简化工作流程、降低管理成本。让管理更简单,工作更轻松!
 
超信:
超信是一款基于手机通讯录的短信增强工具,安装超信既可以完美的取代系统短信功能,开机即用,更加简单便捷的与手机通讯录中的联系人互相手法短信。如果双方都安装超信,就能通过手机网络(WiFi、3G、GPRS)与您铜须路中的联系人发送(需小豪少量网络流量)信息、图片、语音和位置等多媒体文件。
以下是云账户在两个产品中的截图:
工作圈&云账户:
超信&云账户:
除了工作圈和超信之外,已经成功集成云账户红包SDK并发版上线的比较有代表性的APP有:拉拉公园、INVITE、全景图片、蜗牛睡眠、捷库工作、领公报等,正在集成中的APP有上百家,其中还有几位神秘大咖在享受我们的云服务和私有化部署服务,这里暂时先保持下神秘!
我们的原则是,先上线、再PR,少吹牛,多干实事!
另外,云账户红包SDK已经满足完美集成在市场上主流的IM,客服SaaS提供商环信、容联云通讯、融云、leancloud、亲加的产品中,开发者如果用了以上IM或客服产品,可以更加快速安全的接入!
云账户红包SDK,更详细的信息各位开发者可以访问云账户官网:www.yunzhanghu.com去了解,官网上提供Demo下载,帮助开发者们更好地体验我们的产品,毕竟“先尝后买,价格公道,童叟无欺” 查看全部
云账户红包SDK3.0已经发布一段时间了!
就在本月,两家老牌重量级APP完成了云账户红包SDK的集成,经过双方严格的联合测试,现在正是发版了!他们就是“工作圈”&“超信”(IOS、安卓市场都可以下载哦,有兴趣的朋友们可以去市场里下载体验一下,绝对nice!)
可能有朋友不太了解这两个百万级用户的产品,这里我给大家正是介绍一下:
工作圈:
g工作圈是用友旗下畅捷通公司开发的一款企业移动办公平台,融合了企业经营管理所需的各项专业服务,是基于工作场景应用集成:公告、审批、任务、工作报告、签到、文件柜、电话会议、企业通讯录、圈子信息交流等组成的企业互联网应用,帮助企业提高沟通协作效率、简化工作流程、降低管理成本。让管理更简单,工作更轻松!
 
超信:
超信是一款基于手机通讯录的短信增强工具,安装超信既可以完美的取代系统短信功能,开机即用,更加简单便捷的与手机通讯录中的联系人互相手法短信。如果双方都安装超信,就能通过手机网络(WiFi、3G、GPRS)与您铜须路中的联系人发送(需小豪少量网络流量)信息、图片、语音和位置等多媒体文件。
以下是云账户在两个产品中的截图:
工作圈&云账户:
超信&云账户:
除了工作圈和超信之外,已经成功集成云账户红包SDK并发版上线的比较有代表性的APP有:拉拉公园、INVITE、全景图片、蜗牛睡眠、捷库工作、领公报等,正在集成中的APP有上百家,其中还有几位神秘大咖在享受我们的云服务和私有化部署服务,这里暂时先保持下神秘!
我们的原则是,先上线、再PR,少吹牛,多干实事!
另外,云账户红包SDK已经满足完美集成在市场上主流的IM,客服SaaS提供商环信、容联云通讯、融云、leancloud、亲加的产品中,开发者如果用了以上IM或客服产品,可以更加快速安全的接入!
云账户红包SDK,更详细的信息各位开发者可以访问云账户官网:www.yunzhanghu.com去了解,官网上提供Demo下载,帮助开发者们更好地体验我们的产品,毕竟“先尝后买,价格公道,童叟无欺”
1
评论

Duplicate interface definition for class 'EaseUI'解决办法 iOS EaseUI swift

※樂※ 发表了文章 • 804 次浏览 • 2016-09-05 12:44 • 来自相关话题

今天项目迁移时发现报 Duplicate interface definition for class 'EaseUI' 错误,然后重新修改了EaseUI-Prefix的指定路径就好了!
今天项目迁移时发现报 Duplicate interface definition for class 'EaseUI' 错误,然后重新修改了EaseUI-Prefix的指定路径就好了!