注册
环信即时通讯云

环信即时通讯云

单聊、群聊、聊天室...
环信开发文档

环信开发文档

环信FAQ

环信FAQ

集成常见问题及答案
RTE开发者社区

RTE开发者社区

汇聚音视频领域技术干货,分享行业资讯
技术讨论区

技术讨论区

技术交流、答疑
资源下载

资源下载

收集了海量宝藏开发资源
iOS Library

iOS Library

不需要辛辛苦苦的去找轮子, 这里都有
Android Library

Android Library

不需要辛辛苦苦的去找轮子, 这里都有

环信即时通讯云V3.4.1已发布,更快,更稳定的实时音视频

​环信即时通讯云V3.4.1已发布:优化WiFi切4G时与服务器重连速度,优化1V1视频通话,新增音视频弱网检测提示功能。  iOS SDK 更新日志 版本 V3.4.1 2018-05-16 优化: 优化WiFi切4G时与服务...
继续阅读 »
​环信即时通讯云V3.4.1已发布:优化WiFi切4G时与服务器重连速度,优化1V1视频通话,新增音视频弱网检测提示功能。


500932935_wx.jpg


 iOS SDK 更新日志

版本 V3.4.1 2018-05-16


优化:

优化WiFi切4G时与服务器重连速度
优化实时1对1通话

新功能:

新增音视频弱网检测回调
新增加群时填写验证消息
新增聊天室断线时被踢出聊天室回调

请注意:为提供高质量且一致的音视频体验,从3.4.1版本开始,1v1 通话不再与3.1.5及以前版本兼容,请及时升级。
 
Android SDK 更新日志

版本 V3.4.1 2018-05-11


SDK:

修复安卓中会话消息未读数显示不准确的问题;
修复华为推送自动登录情况下有时不可用的问题;
修复app第一次安装运行,初始化过程有可能报nullpointer的问题;
尝试解决sendDeviceToServer()方法crash的问题;
实现音视频弱网检测提示功能;
实现加群时填写验证消息功能;
提供聊天室断线时被踢出聊天室的提醒;
优化1v1 通话;

EaseUI:

更新消息发送逻辑,用于解决发送消息出现重复,顺序颠倒等问题.

请注意:为提供高质量且一致的音视频体验,从3.4.1版本开始,1v1 通话不再与3.1.5及以前版本兼容,请及时升级。
 
版本历史:Android SDK更新日志  ios SDK更新日志
下载地址:SDK下载​ 收起阅读 »

环信客户互动云v5.36已发布:新增历史会话查询方式

环信客户互动云v5.36已发布,支持根据会话创建时间查询历史会话,方便客户相关数据部门统计历史会话信息。    客服模式 历史会话支持【按会话创建时间】查询 支持根据会话创建时间查询历史会话,方便客户相关数据部门统计历史会话信息。 在历史会...
继续阅读 »
   环信客户互动云v5.36已发布,支持根据会话创建时间查询历史会话,方便客户相关数据部门统计历史会话信息。 
 

500604486_banner.jpg




 客服模式

历史会话支持【按会话创建时间】查询

支持根据会话创建时间查询历史会话,方便客户相关数据部门统计历史会话信息。

在历史会话列表页,点击右上角【筛选排序】按钮,弹窗内选择时间段为“会话创建时间”,点击右下角【筛选查询】按钮,即可根据会话创建时间进行筛选。筛选完成后,可在列表页查看相关结果。

历史会话详情资料页工单列表支持点击跳转

历史会话工单列表支持点击跳转,便于快速查询相关工单信息。

在历史会话详情资料页,可查看到当前历史会话相关工单列表,点击工单,可快速打开工单页面,查看工单详情。

注:融合工单功能为标准版/旗舰版增值服务。如需开通,请在“管理员模式 > 工单”页面提交申请,环信商务经理会主动联系您。

会话标签为空时支持保存

当前会话/进行中会话:

当“会话结束时强制添加会话标签”开关关闭时,会话标签支持全部删除,会话标签为空时可以保存;

当“会话结束时强制添加会话标签”开关开启时,点击会话标签,会话标签支持全部删除,为空时可以保存;点击结束会话按钮时,弹出添加会话标签窗口,此时必须至少添加一个会话标签,才可保存并结束会话;

历史会话:

会话标签支持全部删除,为空时可以保存;

会话聊窗与右侧iframe布局支持拖动分配大小

会话模式下(包括会话、历史会话),聊天窗口支持最小宽度340px,iframe窗口支持最小宽度195px,聊天窗口与iframe窗口支持拖动分配大小。

注:管理员模式下,“当前会话 / 历史会话 / 质量检查”会话详情页面支持聊窗与iframe窗口拖动分配大小。

【优化】历史会话质检指标单独标签页显示

“客服模式 > 历史会话“ 页面,会话聊窗右侧工具栏中原质检页签下“查看系统质检指标筛选”相关属性单独为“指标”页签进行显示。 “客服模式 > 质量检查 > 质检记录”页面,以上优化适用。

注:管理员模式下,历史会话与质检页面,以上优化适用。

新增客服结束语设置

新增客服结束语设置,可设置客服手动结束会话时的结束语。

进入“客服模式 > 客服信息”页面,可看到客服问候语下新增结束语设置,可设置客服结束语并可选择是否开启。客服结束语开启,客服手动结束会话时,会向访客自动发送客服结束语,客服无需在结束会话前,进行手动发送,以提高工作效率。

客服手动结束会话时,详细发送规则如下:
  • 客服结束语开启,系统结束语开启,发送客服结束语。
  • 客服结束语关闭,系统结束语开启,发送系统结束语。
  • 客服结束语开启,系统结束语关闭,发送客服结束语。
  • 客服结束语关闭,系统结束语关闭,不发送结束语。


注:管理员模式下,设置管理员客服结束语,管理员手动结束会话时,以上规则适用;其中客服结束语为管理员客服结束语。

管理员模式

【优化】当前会话列表增加渠道及关联显示


在当前会话列表页面,可以查看到会话渠道及关联信息。

进入“管理员模式 > 当前会话”页面,可以在当前会话列表,查看到会话渠道及关联信息。

技能组溢出增加“客服全部隐身/忙碌/离开”规则设置

技能组溢出增加“客服全部隐身/忙碌/离开”规则设置。

进入“管理员模式 > 设置 > 会话分配规则”页面,打开排队规则TAB页,点击右上角【添加规则】按钮,点击“满足以下条件”下拉选项,选择“客服全部隐身 / 客服全部忙碌 / 客服全部离开”状态,即可设置相应状态下的技能组溢出规则。

注:技能组溢出(排队规则)功能为标准版/旗舰版增值服务。如需开通,请提供租户ID并联系环信商务经理。

黑名单列表默认加载全部数据

黑名单列表,默认情况下加载全部数据。

进入“管理员模式 > 客户中心”页面,打开黑名单标签,默认可查看到所有黑名单信息。

企业欢迎语设置文案更新

进入“管理员模式 > 设置 > 系统开关”页面,可看到“企业欢迎语”设置下文案已更新。

更新内容见以下截图:


2_4.jpg



【优化】时间计划-节假日设置日期选择范围更新

节假日设置页面,开始日期及结束日期可选择任意日期。

进入“管理员模式 > 设置 > 时间计划”页面,打开节假日设置页签,可以设置节假日,节假日范围可选择任意日期(结束日期晚于开始日期)。

修复技能组查询问题

支持>1000技能组,技能组数量>1000时,可正常查询并显示。

修复当前会话显示二次修改访客资料问题

二次修改访客资料开启:

集成访客资料中描述信息修改后,打开当前会话,可查看到资料页中访客描述信息为最新访客资料。

修复最大接待数显示问题

管理员修改“管理员”或“自定义角色”最大接待数,相应角色当前会话可实时显示最大接待数。
 
环信客户互动云更新 文档地址
环信客户互动云 登陆地址 收起阅读 »

看了这篇文章你也能自己解决90%的开发问题-环信公开课第26期:客户端日志分析

不管是使用何种编程语言,日志输出几乎无处不在,日志能帮我们追踪问题、 监控状态以及安全审计。作为一名软件开发人员,就必须要学会通过日志分析处理问题。在过去的环信公开课第26期,环信工程师沈冲讲解了环信开发过程中的日志分析处理。   公开课回放视频:https:...
继续阅读 »
    不管是使用何种编程语言,日志输出几乎无处不在,日志能帮我们追踪问题、 监控状态以及安全审计。作为一名软件开发人员,就必须要学会通过日志分析处理问题。在过去的环信公开课第26期,环信工程师沈冲讲解了环信开发过程中的日志分析处理。

 
公开课回放视频:https://v.qq.com/x/page/i0679s0ueso.html
 
 
 1、环信日志介绍
* 如何开启日志打印:
    * 初始化SDK时设置:options.enableConsoleLog = YES;
* 日志保存位置:
    * iOS:
        * 2.x:沙盒/Library/EaseMobLog
        * 3.x:沙盒/Documents/HyphenateSDK/easemobLog/
    * Android:
        * 2.x:/sdcard/Android/data/(您的包名)/(您的appkey)/log/日期/xxx.html
        * 3.x:/sdcard/Android/data/(包名)/(appkey)/core_log/easemob.log
* 如何导出日志:
    * iOS:
        * development:到沙盒中取出日志文件
        * distribution:将手机连上Xcode,bundleid不更换,run后导出沙盒文件,取出日志文件
    * Android:
        * 到本机存储卡中取出日志
    * 通过SDK接口导出:
        * iOS:[[EMClient sharedClient] getLogFilesPathWithCompletion:^(NSString *aPath, EMError *aError) {}];
        * Android:List<EMDeviceInfo> com.hyphenate.chat.EMClient.getLoggedInDevicesFromServer

2、日志分析
* SDK初始化:EMChatClientImpl::init()
* 登录、自动登录、退出登录
    * EMSessionManager::login():
AutoLogin
begin logout ..
* 断网与重连
    * onNetworkChanged():
EMSessionManager::reconnect()
* 被踢、被禁用
    * 被踢:operation : 2 
禁用:operation : 1
* 单聊和群聊的发送消息、已读回执
    * asyncSendMessage
单聊:chattype : CHAT
群聊:chattype : GROUPCHAT
已读回执:chattype : READ_ACK
消息ID:server_id : 475456018394908684
* 接收消息
    * command : NOTICE
消息ID:id : 475456157712910340
* 删除消息:EMDatabase::removeMessage: 475460335331969036
* 删除会话:EMChatManager::removeConversation: 31750912802818 isRemoveMessages: 1
* 群组相关:
    * 创建群组: mucCreate:: retCode: 200
    * 有用户加入群组: operation : INVITE_ACCEPT, operation : PRESENCE
    * 查看群详情、获取群成员列表、获取群公告、群组免打扰
    * 
收到群邀请、接受群邀请:
收到群邀请:operation : INVITE,
接受群邀请:mucInviteDisposeOperation:: retCode: 200
* 聊天室相关:
    * 加入、离开聊天室: 
        * 加入聊天室:EMChatroomManager::joinChatroom
        * 退出聊天室: mucQuit:: retCode: 200
    * 有用户加入聊天室: operation : PRESENCE
    * 有用户离开聊天室: operation : ABSENCE
看完视频你学会了吗?环信公开课每周三下午三点准时开讲https://ke.qq.com/course/293539?tuin=3441d418
  收起阅读 »

不是群主的好友,能否申请加入该群呢?

iOS
不是群主的好友,能否申请加入该群呢?
不是群主的好友,能否申请加入该群呢?

【有奖调查】环信T恤文案征集,你来我们就送!

  环信T恤背面文案征集,欢迎小伙伴留言,7个中文字以内。   送送送T恤!   文案一经采用,送T恤!并且留言楼层是8的尾号(包含8),恭喜你中奖了,环信T恤包邮到家!   中奖规则:   同一账号重复留言只记做一次,小伙伴们构思好文案再留言,珍惜抽奖机会。...
继续阅读 »

微信图片_20180601155239.jpg


 
环信T恤背面文案征集,欢迎小伙伴留言,7个中文字以内。
 
送送送T恤!
 
文案一经采用,送T恤!并且留言楼层是8的尾号(包含8),恭喜你中奖了,环信T恤包邮到家!
 
中奖规则:
 
同一账号重复留言只记做一次,小伙伴们构思好文案再留言,珍惜抽奖机会。

活动时间2018年6月1日-6月10日,最终解释权归环信所有。
 
中奖名单公布:
  1. 楼层6:Eternally(额外奖励,提建议被采纳)
  2. 楼层8:°﹏D.X.F.VIP 
  3. 楼层18:空谷幽兰
  4. 楼层28:skoxe
  5. 楼层38.咚咚

收起阅读 »

Android收发消息原理,如何集成华为、小米等厂商推送?-环信公开课第25期

Android开发的你是否也有这样疑问,应用在Android手机退到后台太久会收不到消息?第三方推送怎么集成?谷歌推送国内能用吗? 5月30日环信研发工程师张松在环信第25期公开课上分享关于Android收发消息的原理,以及推送通知的实现,详细内容如下。   ...
继续阅读 »
Android开发的你是否也有这样疑问,应用在Android手机退到后台太久会收不到消息?第三方推送怎么集成?谷歌推送国内能用吗?

5月30日环信研发工程师张松在环信第25期公开课上分享关于Android收发消息的原理,以及推送通知的实现,详细内容如下。
 

 为什么应用在Android手机上退到后台太久会出现收不到消息的情况?
    当app在后台运行时环信SDK通过一个后台服务保持一条连接环信服务器的长连接,但Android为了解决系统待机性能差的问题,随着Android版本的升级逐渐禁止了app级别的后台服务的运行。所以在一些版本比较高的Android系统上会有接收不到消息的情况。
 
如何解决Android后台收不到消息的问题?
   为了提高消息的到达率,环信SDK增加了对第三方推送服务的支持,包括小米推送、华为推送、Google FCM推送。 (服务端也增加了相应功能)
 
1.Google FCM推送:Firebase Cloud Messaging,用来替代Google的GCM推送服务。
 
FCM推送在国内是无法正常使用的,需要设备上有Google play service和能连接Google服务器的网络。所以该推送服务主要针对海外用户,如果你的app有海外用户,建议你增加对该推送服务的支持。
 
推送消息发送流程:
 


古国.png


环信Server:
1.环信后台配置推送证书
  让环信服务器拥有向你的app发送推送消息的功能
  根据配置的证书名称服务器可以判断设备使用了哪种推送
2.目标设备的推送token
  Android设备通过集成第三方推送SDK可以得到该推送token.
  推送token跟Android设备是一一对应的关系,一个token对应一个Android设备.
  不同推送通道获取到的推送token不同.
3.推送token属于哪个推送通道
  决定了用哪个通道来发送推送消息.
  根据Android设备上传的证书名称确定.
 
Android设备:

1.判断app所在Android设备上支持哪种推送通道
  • 小米推送: 支持小米系统
  • 华为推送: 支持华为系统
  • FCM:需要Google play service和能连接Google服务器的网络

2.集成第三方推送SDK获取推送token
3.将推送token和推送通道名称上传至环信服务器.

 
SDK集成第三方推送
小米推送:
小米推送目前集成在了SDK内部,后续也会放在Demo中实现,让开发者可以自己升级小米推送SDK
1.在app AndroidManifest.xml中添加配置:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.READ_PHONE_STATE" /> <uses-permission android:name="android.permission.GET_TASKS" />
<uses-permission android:name="android.permission.VIBRATE"/>
<permission
android:name="com.hyphenate.chatuidemo.permission.MIPUSH_RECEIVE" android:protectionLevel="signature" />
<!--这里com.hyphenate.chatuidemo改成app的包名-->
<uses-permission android:name="com.hyphenate.chatuidemo.permission.MIPUSH_RECEIVE" />
<!--这里com.hyphenate.chatuidemo改成app的包名-->

<service
android:name="com.xiaomi.mipush.sdk.PushMessageHandler"
android:enabled="true"
android:exported="true"
tools:ignore="ExportedService" />
<service
android:name="com.xiaomi.mipush.sdk.MessageHandleService"
android:enabled="true" />

<receiver
# EMMipushReceiver extends PushMessageReceiver
android:name="com.hyphenate.chat.EMMipushReceiver"
android:exported="true"
tools:ignore="ExportedReceiver">
<intent-filter>
<action android:name="com.xiaomi.mipush.RECEIVE_MESSAGE" />
</intent-filter>
<intent-filter>
<action android:name="com.xiaomi.mipush.MESSAGE_ARRIVED" />
</intent-filter>
<intent-filter>
<action android:name="com.xiaomi.mipush.ERROR" />
</intent-filter>
</receiver>

EMOptions#setMipushConfig(appKey:"2882303761517426801", appSecret:"5381742660801");


SDK处理流程:

根据小米推送SDK提供的方法MiPushClient#shouldUseMIUIPush(Context context)判断当前设备是否支持小米推送
若当前设备支持推送,则调用MiPushClient#registerPush(Context context, appKey, appSecret)获取小米通道推送
 
token
将appKey和token上传至环信服务器,该部分实现位于EMMipushReceiver 中,如果开发者需要自定义小米PushMessageReceiver,请继承自EMMipushReceiver .
 
华为推送
华为推送已放置到demo层实现,方便开发者自己更新华为推送SDK
华为推送SDK做为hmspush 单独module存在
注意修改hmspush module中AndroidManifext.xml文件的appid,两个provider对应authorities
Demo中配置了一个BroadcastReceiver和一个HMSPushHelper
 
FCM推送
增加了compile 'com.google.android.gms:play-services-base:11.4.0',检查当前设备是否支持fcm推送.
设置fcm numberoptions.setFCMNumber("826964054884");
 
 整体工作流程:


2.png


   Android设备手动或自动登录时,判断当前设备支持哪种推送通道,将该通道下获取到的token和证明名称上传至环信服务器,当服务端检测到当前设备不在线,需要向当前设备发送推送消息的时候,根据设备上传的证书名称判断出当前设备使用的哪种推送通道接收推送消息,使用相应推送通道向该设备上传的推送token上发送消息,当前设备即可收到该推送消息.
 
建议:

1.如果你的app有海外用户,建议你在环信SDK上增加对FCM推送的支持
2.建议同时支持小米推送和华为推送,这样在小米和华为设备上都可以接收到推送消息.

 
课程完整回放视频http://v.qq.com/x/page/m0671pnnzjk.html 

 
 
 
  收起阅读 »

每周三下午3点,环信公开课,开课啦!

环信公开课第34期-即时通讯IM开发知识点讲解 【活动时间】:2018年8月01日(周三) 15:00 【活动介绍】 : 【线上直播】IM集成--ios篇   本期将由环信工程师进行im集成现场辅导   有问题,你提,我来答!  【报名地址】:http...
继续阅读 »

InsertPic_(05-08(05-15-10-37-24).jpg


环信公开课第34期-即时通讯IM开发知识点讲解

活动时间】:2018年8月01日(周三) 15:00

活动介绍】 : 【线上直播】IM集成--ios篇

  本期将由环信工程师进行im集成现场辅导   有问题,你提,我来答! 

报名地址】:https://ke.qq.com/course/320169?tuin=3441d418

直播QQ群】: 560329342 (欢迎进群提问,老师将针对学生提问,进行讲解)

  20余万开发者小伙伴在这里,还有最强王者带你飞,快来加入我们一起学习吧!
  如有疑问,请您及时联系我们!
     客服QQ:2379053675 收起阅读 »

环信公开课第24期视频回放-IOS本地通知、远程推送的实现

5月23日周三下午3点,环信公开课第24期如期举行。环信IOS工程师沈冲在公开课上讲解了ios本地通知+远程推送。   公开课大纲  1、本地通知; online 不推离线推送 offline 进入离线队列,推离线推送; 什么时候能收到离线推送: A...
继续阅读 »
5月23日周三下午3点,环信公开课第24期如期举行。环信IOS工程师沈冲在公开课上讲解了ios本地通知+远程推送。
 
公开课大纲

 
1、本地通知;

online 不推离线推送 offline 进入离线队列,推离线推送;

什么时候能收到离线推送:
App在后台被系统kill(150s),或手动kill,进程被杀死(用户离线),推离线推送

实现本地通知:
单例注册监听:
遵守协议: EMChatManagerDelegate
注册代理监听:[[EMClient sharedClient].chatManager addDelegate:self delegateQueue:nil];
实现接收消息回调:- (void)messagesDidReceive:(NSArray *)aMessages;
本地通知实现示例:- (void)showNotificationWithMessage:(EMMessage *)message;

2、离线推送

development:开发环境(连xcode run) production:生成环境(ipa)

创建推送证书上传管理后台,(证书appid+工程bundleid+上传管理后台传入packagename三者一致);
初始化SDK设置证书名称: options.apnsCertName = @"hxdemo2Dev”;
注册推送权限: _registerRemoteNotification;
绑定devicetoken: [[EMClient sharedClient] bindDeviceToken:deviceToken];
登录成功后,将app杀死,给之前登录的ID发消息,测试APNs推送。

3、rest+客户端 调试;
Postman;
rest调用示例;
客户端log。

4、私有API被拒
com.apple.springboard.lockcomplete
更新SDK_>提审
24期公开课视频回放:点击观看
 
环信公开课每周三下午3点,环信公开课不见不散
 
公开课观看地址:点击观看 收起阅读 »

环信webim,收发消息,收发图片,收发表情,未读已读消息数量

大家好,这是根据我们公司的项目需求集成的webim demo,具体效果如上图。 此demo不依赖后台纯属前端demo,且同步聊天记录安卓和ios(只要用户不清楚浏览器缓存),数据全部储存在本地 那么接下来 咱们开始看看代码吧 第一步:     1、注册...
继续阅读 »



QQ截图20180525151449.jpg




QQ图片20180525154227.png




大家好,这是根据我们公司的项目需求集成的webim demo,具体效果如上图。
此demo不依赖后台纯属前端demo,且同步聊天记录安卓和ios(只要用户不清楚浏览器缓存),数据全部储存在本地
那么接下来 咱们开始看看代码吧
第一步:
    1、注册环信即时通信云获得appkey,注册账号之后登录环信后台创建应用就可以得到appkey
第二步:
    2.引用本地文件
    <script type='text/javascript' src='webim.config.js'></script>  
    <script type='text/javascript' src='strophe-1.2.8.min.js'></script>  
    <script type='text/javascript' src='websdk-1.4.11.js'></script> 
这三个文件的顺序 不要修改 ,就这么引入
然后把appkey替换成自己的就可以。
第三步:
    1.注册
     var options = {  
             username: userid,  
             password: password,  
             nickname: nickname,  
             appKey: WebIM.config.appkey,  
             success: function () {//注册成功之后回调函数  
                                      
             },  
             error: function () {},  
             apiUrl: WebIM.config.apiURL  
      };  
     conn.registerUser(options);  
    2.登录
    var options = {  
        apiUrl: WebIM.config.apiURL,  
        user: user,  
        pwd: password,  
        appKey: WebIM.config.appkey  
    };  
    conn.open(options);  
    3.创建连接
    var conn = new WebIM.connection({  
    isMultiLoginSessions: WebIM.config.isMultiLoginSessions,  
    https: typeof WebIM.config.https === 'boolean' ? WebIM.config.https : location.protocol === 'https:',  
    url: WebIM.config.xmppURL,  
    heartBeatWait: WebIM.config.heartBeatWait,  
    autoReconnectNumMax: WebIM.config.autoReconnectNumMax,  
    autoReconnectInterval: WebIM.config.autoReconnectInterval,  
    apiUrl: WebIM.config.apiURL,  
    isAutoLogin: true  
});  
conn.listen({  
    onOpened: function ( message ) {},      //连接成功回调  
    onClosed: function ( message ) {},         //连接关闭回调  
    onTextMessage: function ( message ) {//收到文本消息  
        console.log(message);  
        console.log('收到'+message.from+'发送的消息:'+message.data);  
        setTimeout(function(){
              //这一步或许有人会问为什么要加setTimeout,在这里解释一下,是为了同步执行下去
              var str = message.ext.chatIcon;
              if(str.indexOf("http")>=0){
                        str = str.slice(32);
               }
              else{
                        str = message.ext.chatIcon;
               }
              //这一步是因为ios和安卓发送消息时,人物头像连接一个是http开头地址  一个是不带本域名的地址,所有要做判断
             var getUserItem = localStorage.getItem('user_id');//这个user_id 是ios和安卓的conversation_id 这个id是在我们人才详情页面,点击立即沟通时,存储到用户的id
             然后进入聊天界面,然后模拟点击显示与此用户的界面
       
            if(message.from==getUserItem){
                getNowFormatDate();//时间函数
                showChatMessage(timestamp3,str,message.data,message.id);//此函数为展示消息函数
                //这里执行这一步,是判断如果收到的消息是当前用户,就直接显示在本聊天界面
                detailMessage(timestamp3,message.data,message.from,'text','',message.id,message.from,str,'','','has_read','');//此函数为存储消息函数
                //为了显示消息,这里是对所有收到的消息都做了存储,下文会介绍消息函数各个参数。
            }
            else{//收到的消息不是当前用户
                detailMessage(timestamp3,message.data,message.from,'text','',message.id,message.from,str,'','','no_read','');//存储消息
                var w = localStorage[message.from];//获取本地所有存储用户的消息列表
                var getList = JSON.parse(localStorage[message.from]);//转换成json数组
                for(var i = 0; i<getList.length; i++){
                        var reserve = getList[i].message[0].reserve;//此处是为存储的消息设置的已读、未读状态(上文的has_read,no_read)
                         if(reserve=='no_read'){
                                 var listNumber = Number(getList.length)-Number(i);//这是获取的未读消息的数量
                                 getList[0].message[0].number=listNumber;//此处是把未读消息的数量存储到 该用户第一条消息的number里面       
                                $('.top-list li.'+message.from+'').find('span.notice-badge').show();
                                $('.top-list li.'+message.from+'').find('span.notice-badge').text(listNumber);
                                //此处是显示未读消息的数量,该li的class是收到消息用户的id
                                localStorage[message.from] = JSON.stringify(getList);
                                return false;//然后把消息的未读已读状态更改保存回本地         
                         }
                }
            }
        },0)
    },  
    onEmojiMessage: function ( message ) {//收到表情消息  
        console.log('收到'+message.from+'发送的Emoji'+':'+message.data);  
        //缓存数据  
        for(var i=0;i<message.data.length;i++){  
            var img = message.data[i];  
            var string;  
            if (img.type=='txt') {string = string+img.data;}  
            else{string = string+'<img class="emoji" '+'src="'+img.data +'">';}  
        }  
        string = string.replace('undefined','');  
        console.log(string);  
       //此处的方法同收到文本消息,不过需要赋予字符串emoji表情标签(转化字符串为img标签)
      //下面代码需要拿来出,放到你的$(function(){})里面,放到下面只是为了,解释给读者
      WebIM.Emoji = {  
            path: '../images/faces/',  
            map: {  
                '[):]': 'ee_1.png',  
                '[:D]': 'ee_2.png',  
                '[;)]': 'ee_3.png',  
                '[:-o]': 'ee_4.png',  
                '[:p]': 'ee_5.png',  
                '[(H)]': 'ee_6.png',  
                '[:@]': 'ee_7.png',  
                '[:s]': 'ee_8.png',  
                '[:$]': 'ee_9.png',  
                '[:(]': 'ee_10.png',  
                '[:\'(]': 'ee_11.png',  
                '[:|]': 'ee_12.png',  
                '[(a)]': 'ee_13.png',  
                '[8o|]': 'ee_14.png',  
                '[|]': 'ee_15.png',  
                '[+o(]': 'ee_16.png',  
                '[<o)]': 'ee_17.png',  
                '[|-)]': 'ee_18.png',  
                '[*-)]': 'ee_19.png',  
                '[:-#]': 'ee_20.png',  
                '[:-*]': 'ee_21.png',  
                '[^o)]': 'ee_22.png',  
                '[8-)]': 'ee_23.png',  
                '[(|)]': 'ee_24.png',  
                '[(u)]': 'ee_25.png',  
                '[(S)]': 'ee_26.png',  
                '[(*)]': 'ee_27.png',  
                '[(#)]': 'ee_28.png',  
                '[(R)]': 'ee_29.png',  
                '[({)]': 'ee_30.png',  
                '[(})]': 'ee_31.png',  
                '[(k)]': 'ee_32.png',  
                '[(F)]': 'ee_33.png',  
                '[(W)]': 'ee_34.png',  
                '[(D)]': 'ee_35.png'  
            }  
        }; 
    },  
    onPictureMessage: function ( message ) {//收到图片消息  
        console.log(message);  
        console.log('收到'+message.from+'发送的图片'+':'+message.url);  
        getNowFormatDate();//时间函数
        showChatMessage(timestamp3,str,message.data,message.id);//此函数为展示图片消息函数
    },  
    onCmdMessage: function ( message ) {},     //收到命令消息  
    onAudioMessage: function ( message ) {},   //收到音频消息  
    onLocationMessage: function ( message ) {},//收到位置消息  
    onFileMessage: function ( message ) {//收到文件消息  
        console.log(message);  
        console.log('收到'+message.from+'发送的文件'+':'+message.url);  
    },  
    onVideoMessage: function (message) {  },   //收到视频消息  
    onPresence: function ( message ) {},       //处理“广播”或“发布-订阅”消息,如联系人订阅请求、处理群组、聊天室被踢解散等消息  
    onRoster: function ( message ) {},         //处理好友申请  
    onInviteMessage: function ( message ) {},  //处理群组邀请  
    onOnline: function () {},                  //本机网络连接成功  
    onOffline: function () {},                 //本机网络掉线  
    onError: function ( message ) {},          //失败回调  
    onBlacklistUpdate: function (list) {},   //黑名单变动                    
    onReceivedMessage: function(message){},    //收到消息送达客户端回执  
    onDeliveredMessage: function(message){},   //收到消息送达服务器回执  
    onReadMessage: function(message){
        //此处为收到已读消息的回执
         setTimeout(function(){//为了保持同步操作
                  var getLength = $("#recordchat-main li.item-myself").length
                  //此处获取的长度是,当前聊天窗口的长度,且消息是本人发出的,因为现实已读未读只显示己方消息
                  for (var i = 0; i < getLength ; i++) {
                      var getLiName = $('#recordchat-main li.item-myself:eq('+i+')').attr('id');
                       //获取当前我发出消息的id,此id为user_id也就是上文的conversation_id
                      // console.log(getLiName)
                      var getMid = $('#recordchat-main li.item-myself:eq('+i+')').attr('mid');
                      //此处是获取当前我发出消息的mid,与消息回执的mid进行匹配
                      //console.log(getMid)
                      if(message.mid==getMid){
                        $('#recordchat-main li.item-myself:eq('+i+')').find('.no-read').text('已读');
                        //如果收到回执的消息mid等于当前窗口消息列表的mid,把当前的未读状态改变成已读状态
                      }
                      statusRead('已读',message.mid,getLiName,'');//此函数是把每一条的消息mid存储到本地且存储了状态
                  }
                  
          },0)
},        //收到消息已读回执  
    onCreateGroup: function(message){},        //创建群组成功回执(需调用createGroupNew)  
    onMutedMessage: function(message){}        //如果用户在A群组被禁言,在A群发消息会走这个回调并且消息不会传递给群其它成员  
});  

//此处是左侧联系人列表,此方法是应用的layui的流加载,本想着用户多了,会使用layim+环信集成社区聊天模板
        flow.load({
            elem: '.top-list' //流加载容器
            ,mb:10
            ,isAuto: false
            ,isLazyimg: true
            ,done: function(page, next){ //执行下一页的回调
                //数据插入
                $.ajax({
                    url:'',
                    type:'get',
                    data:{page:page,user_type:2},
                    error:function(data){
                        layer.msg("世上难得两全法,您看是不是您的网络问题,如果不是刷新一下试试哦~")
                    },
                    success:function(data){
                        // console.log(data)
                        var obj = eval('('+data+')');
                        var length = obj.result.length;
                        var pages = length/10;
                        var lis = [];
                        for(var i = 0; i < length; i++)
                        {
                            lis.push('<li class='+obj.result[i].conversation_id+' chatId="'+obj.result[i].conversation_id+'" chatIcon="'+obj.result[i].info.user_img+'" add_time="'+obj.result[i].add_time+'" uid="'+obj.result[i].chat_id+'" chatName = "'+obj.result[i].conversation_id+'" career_name = "'+obj.result[i].info.career_name+'" city = "'+obj.result[i].info.city+'" education = "'+obj.result[i].info.education+'" industry_id = "'+obj.result[i].info.industry_id+'" salary_range = "'+obj.result[i].info.salary_range+'" school_name="'+obj.result[i].info.school_name+'" school_status="'+obj.result[i].info.school_status+'" specialty="'+obj.result[i].info.specialty+'" user_age="'+obj.result[i].info.user_age+'" user_sex="'+obj.result[i].info.user_sex+'" user_name="'+obj.result[i].info.user_name+'"><a href="javascript:;" data-url=""><div class="figure"><img src="'+obj.result[i].info.user_img+'"></div><div class="text"><div class="title"><div class="text-clear"><span class="name">'+obj.result[i].info.user_name+'</span><span class="time">'+obj.result[i].add_time+'</span></div><p class="gray"> '+obj.result[i].info.school_name+' | '+obj.result[i].info.career_name+' | '+obj.result[i].info.education+'</p></div><span class="notice-badge" style="display: none;"></span></div></a></li>')
                        }
                        next(lis.join(''),page< pages); //总页数
                        var getUserId = localStorage.getItem("user_id");
                        //模拟点击 当用户直接从人才列表点击进来
                        for (var i = 0; i < $(".top-list li").length; i++) {
                            var getItem = $('.top-list li:eq('+i+')').attr('chatid');
                            // console.log(getItem)此处是conversation_id
                            if(getUserId==getItem){
                                $('.top-list li:eq('+i+')').trigger("click");
                                var height = $("#recordchat-main").height();
                                $("#chat-list").scrollTop(height);
                                //此处代码是项目中有需求,点击左侧联系人的时候,右侧聊天窗口显示最底部的消息
                            } 
                            if(localStorage[getItem]){
                            var chatSen = localStorage[getItem];
                            //console.log(chatSen)然后在加载左侧结束的同时,获取本地存储所有该用户的聊天记录
                            var chatSenGetItem = JSON.parse(localStorage[getItem]);
                            //console.log(chatSenGetItem)转换改聊天记录为json数组
                            var number = chatSenGetItem[0].message[0].number;
                            //console.log(number)此处为读取存储本地未读的消息
                            if(number!=''){
                            $('.top-list li:eq('+i+')').find("span.notice-badge").show();
                            $('.top-list li:eq('+i+')').find("span.notice-badge").text(number);
                            }
                            else{
                            $('.top-list li:eq('+i+')').find("span.notice-badge").hide();
                            $('.top-list li:eq('+i+')').find("span.notice-badge").text(number);
                            }
                            //未读消息的展示
                            }
                        } 
                    }
                });
            }
        })
//此处是左侧联系人列表点击的时候,由于没有后台支撑,所有我把所有的用户信息,全部存储到li里面,此处只是demo测试,正式版本肯定会有接口支持
        $(".top-list").on("click","li",function(){
            //此处点击切换数据代码已裁剪掉,只写本地存储,im相关   
           var chatId = $(this).attr("class") //上文提到过conversation_id为了li的class
            //获取聊天记录
            if(localStorage[chatId]){
            var localContent = JSON.parse(localStorage[chatId]);//点击获取该用户的所有聊天记录
            if(localContent[0].message[0].number!=''){//此number是未读消息的数量
            localContent[0].message[0].number='';//如果未读消息不为空的话,点击该用户,该用户的未读消息数量清空
            localStorage[chatId]=JSON.stringify(localContent);
            //console.log(localContent)
            }
                var localContent = JSON.parse(localStorage[chatId]);
                //console.log(localContent)
                for (var i = 0; i < localContent.length; i++) {
                    var data = localContent[i].message[0].data;
                    var chatId = localContent[i].message[0].chatId;
                    var from = localContent[i].message[0].from;
                    var time = localContent[i].message[0].time;
                    var type = localContent[i].message[0].type;
                    var filename = localContent[i].message[0].filename;
                    var id = localContent[i].message[0].id;
                    var mid = localContent[i].message[0].mid;
                    var chatIcon = localContent[i].message[0].chatIcon;
                    var reserve = localContent[i].message[0].reserve;
                    if(chatIcon==undefined){
                        chatIcon = '/head_img/12064_15087368603690.png';//如果数据库人物头像丢失,显示默认头像
                    }
                    if(localContent[i].message[0].reserve=='no_read'){
                       // localStorage[chatId].message[0].reserve = 'has_read'
                        localContent[i].message[0].reserve='has_read';
                        //console.log(localContent)
                        localContent[0].message[0].number='';
                        localStorage[chatId]=JSON.stringify(localContent);
                        //console.log(localStorage[chatId])  判断该消息的未读已读状态,点击该用户进行转换状态
                    }
                    var chat_status;
                    // console.log(localStorage[chatId])
                    if(localStorage[id]){
                        var localStatusContent = JSON.parse(localStorage[id]);
                        //console.log(localStatusContent)
                        chat_status = localStatusContent[0].status;
                    }
                    else{
                        //console.log(localStorage[id])
                        chat_status = '未读';
                    }
                    if(from=='me'){
                        if(type=='text'){
                            $("#recordchat-main").append('<li class="item-time"><span class="time">'+time+'</span></li><li class="item-myself" id='+id+' mid='+mid+'><div class="no-read">'+chat_status+'</div><div class="text">'+data+'</div></li>');
                        }
                        else if(type=='picture'){
                            $("#recordchat-main").append('<li class="item-time"><span class="time">'+time+'</span></li><li class="selfPrture item-myself" id='+id+' mid='+mid+'><div class="text"><img class="img_url" src='+data+' alt="内容图片" /></div></li>');
                        }
                    }else{
                        if(type=='text'){
                            $("#recordchat-main").append(
                             '<li class="item-time"><span class="time">'+time+'</span></li>'
                            +'<li class="item-friend" id='+mid+'>'
                            +'<div class="figure"><img src="http://beta.app.first-job-1.com'+chatIcon+'" alt="人物头像" /></div>'//<img src="http://beta.app.first-job-1.com'+message.ext.chatIcon+'">
                            +'<div class="text">'+data+'</div>'
                            +'</li>');
                        }
                        else if(type=='picture'){
                            $("#recordchat-main").append(
                             '<li class="item-time"><span class="time">'+time+'</span></li>'
                            +'<li class="item-friend selfPrture" id='+mid+'>'
                            +'<div class="figure"><img src="http://beta.app.first-job-1.com'+chatIcon+'" alt="人物头像" /></div>'
                            +'<div class="text"><img class="img_url" src='+data+' alt="内容图片" /></div>'
                            +'</li>');
                        }
                    }
                }
                //刷新时读取本地消息 展示在聊天窗口
                //此处为jq写法插入数据,正式版本是改成tpl模板渲染,想想用2句代码就可以展示消息 还是蛮激动的。
            }
            else{
                //console.log("暂无聊天记录")
            }
            $(this).find('span.notice-badge').hide();//隐藏数量小红点
        }) 
//发送文本消息函数
        var sendPrivateText = function(msg_content){
            var name = top.$(".figure>a>img").attr("src");//该企业用户头像
            var chatIcon = name.slice(31);//该企业用户头像地址裁剪
            var chatName = top.$(".figure>span").text();//该企业用户名称
            var chatId = $(".chatId").val();//chat-id
            var id = conn.getUniqueId();
            var msg = new WebIM.message('txt', id);
            msg.set({
                msg: msg_content,
                to: chatId,
                roomType: false,
                chatType: 'singleChat', 
                success: function(id, serverMsgId){
                    $(".chat-input").html("");
                    getNowFormatDate();
                    var emojiMessage = WebIM.utils.parseEmoji(msg_content); //表情解析工具
                   showChatMessage(timestamp3,str,message.data,message.id);
                    var recordchat = document.getElementById('chat-list');
                    recordchat.scrollTop = recordchat.scrollHeight;//发送消息时,滚动条出现在底部
                    //暂时插入数据,tpl绑定模板插入渲染
                    $("#btn-send").addClass("disabled");//发送按钮置灰
                    detailMessage(timestamp3,emojiMessage,"me",'text',id,serverMsgId,chatId,'','未读','','','');  
                },
                fail: function(e){
                    //console.log("fail")
                }
            });

            msg.body.chatType = 'singleChat';
            // msg.setGroup('singleChat');  
            msg.body.ext.chatName= chatName;//传递chatName->ios,java
            msg.body.ext.chatIcon= chatIcon;//传递chatIcon->ios,java
            conn.send(msg.body);
        };
//发送图片消息函数
        var sendPrivateFile = function () {
            var name = top.$(".figure>a>img").attr("src");//该企业用户头像
            var chatIcon = name.slice(31);//该企业用户头像地址裁剪
            var chatName = top.$(".figure>span").text();//该企业用户名称
            var chatId = $(".chatId").val();//chat-id
            var id = conn.getUniqueId();                   // 生成本地消息id  
            var msg = new WebIM.message('img', id);        // 创建图片消息  
            var input = document.getElementById('image');  // 选择图片的input   id必填  
            var file = WebIM.utils.getFileUrl(input);      // 将图片转化为二进制文件  
            var allowType = {'jpg': true,'gif': true,'png': true,'bmp': true};  
            var img_url;  
            if (file.filetype.toLowerCase() in allowType) {  
                var option = {  
                    apiUrl: WebIM.config.apiURL,  
                    file: file,  
                    to: chatId,                       // 接收消息对象  
                    roomType: false,  
                    chatType: 'singleChat',  
                    onFileUploadError: function () {      // 消息上传失败  
                        //console.log('图片发送失败!');  
                    },  
                    onFileUploadComplete: function (aa) {   // 消息上传成功  
                        //console.log('onFileUploadComplete');
                        img_url = aa.uri+"/"+aa.entities[0].uuid;  
                    },  
                    success: function (id, serverMsgId) {                // 消息发送成功 
                        //console.log(id)
                        getNowFormatDate() 
                        showChatMessage(timestamp3,str,message.data,message.id);
                        var recordchat = document.getElementById('chat-list');
                        recordchat.scrollTop = recordchat.scrollHeight;
                        detailMessage(timestamp3,img_url,"me",'picture',file.id,serverMsgId,chatId,'','','','',''); 
                        // showMessage();
                    },  
                    flashUpload: WebIM.flashUpload  
                };  
                msg.set(option); 
                msg.body.chatType = 'singleChat'; 
                msg.body.ext.chatName= chatName;//传递chatName->ios,java
                msg.body.ext.chatIcon= chatIcon;//传递chatIcon->ios,java
                conn.send(msg.body);  
            }  
        };
        //时间函数
        function getNowFormatDate(){
          var timestamp1 = new Date().getTime();//获取时间戳此方法准确
          var timestamp2 = new Date(timestamp1);
          timestamp3 = timestamp2.toLocaleDateString().replace(/\//g, "-") + " " + timestamp2.toTimeString().substr(0, 8);
        }
       //展示消息  根据个人项目需求,正式版本会使用tpl模板 
        function showChatMessage(timestamp3,str,data,id){
            $("#recordchat-main").append(
                             '<li class="item-time"><span class="time">'+timestamp3+'</span></li>'
                            +'<li class="item-friend" id='+id+'>'
                            +'<div class="figure"><img src="http://beta.app.first-job-1.com'+str+'"></div>'
                            +'<div class="text">'+data+'</div>'
                            +'</li>');
        }
     //绑定数据模板
        function detailMessage(timestamp3,data,from,type,id,mid,chatId,chatIcon,status,filename,reserve,number){  
            var localContent = new Array();
            if (localStorage[chatId]) {  
                localContent = JSON.parse(localStorage[chatId]);  
            }  
            localContent[localContent.length]= { 'message':[{ 
                'time':timestamp3,  
                'data':data,//数据  
                'from':from,//谁发的  
                'type':type,//文本类型 text,file,picture  
                'id':id,//消息id  
                'mid':mid,
                'chatId':chatId,
                'chatIcon':chatIcon,
                'status':status,//状态
                'filename':filename, //文件名字 
                'reserve':reserve,//已读未读
                'number':number,//未读数量
            }]};  
            localStorage[chatId] = JSON.stringify(localContent);//存储本地; 
            //console.log(JSON.parse(localStorage[chatId]))
            
        }  
       //根据每一条消息的id存储本地 存储未读已读状态
        function statusRead(status,mid,id,reserve){
            var localStatusContent = new Array();
            if(localStatusContent[id]){
                localStatusContent = JSON.parse(localStorage[id]); 

            }
            localStatusContent[localStatusContent.length]={
                'status':status,
                'mid':mid,
                'id':id,
                'reserve':reserve
            };
            localStorage[id] = JSON.stringify(localStatusContent);
            //console.log(localStorage[id])
        }

项目需求只有表情和图片,文件视频,音频其实都是属于文件的一种,若有帮助请赞赏一下吧。


微信图片_20180525164006.jpg




微信图片_20180525164014.png




  收起阅读 »

【有问必答】有温度,有态度,有速度的IMGeek社区!

5分钟,是一个从提问到解答的总时长,有温度有态度有速度! -IMGeek社区“鲁迅”     IMGeek循着极客们开放、分享、协作、创新的精神...
继续阅读 »
5分钟,是一个从提问到解答的总时长,有温度有态度有速度!
-IMGeek社区“鲁迅”




    IMGeek循着极客们开放、分享、协作、创新的精神,努力构建一个具有服务质量保障(Service Level Assurance , SLA)的社区。

   在IMGeek社区里征集到一批热心的技术专家,得到他们的承诺自愿回复IMGeek社区问题。只要你在IMGeek社区发布问题,专家们将会收到消息提醒,并及时回复。 

   当然,如果你在提交一个问题之前,可以先搜索一下,说不定你要提的问题已经有人提过并且得到解答。这样可以省却不少你的时间。 
 
   最后提醒一下提问的小伙伴,如果您的问题被解决,占用您一秒钟时间将回复设置为最佳回复,方便后面遇到相同问题的同学快速找到答案!

TIM截图20180524175557.png


  现在,从一个提问开始你的IMGeek社区之旅。 收起阅读 »

教您5分钟体验客户互动云

  欢迎各位小伙伴们注册环信客户互动云! 为了帮助您快速了解环信客户互动云系统,我们为您准备了一个“商城”Demo。您可以先使用“商城”Demo进行客服体验。试用期短暂,机不可失,快来集成体验吧!   请先打开客服后台按照以下步骤添加app渠道关联 ...
继续阅读 »
 
欢迎各位小伙伴们注册环信客户互动云!

为了帮助您快速了解环信客户互动云系统,我们为您准备了一个“商城”Demo。您可以先使用“商城”Demo进行客服体验。试用期短暂,机不可失,快来集成体验吧!
 
请先打开客服后台按照以下步骤添加app渠道关联


55b6d3a7688d18c9c580650677277c4f.gif




添加关联后 您就可以模拟客服场景体验啦

 扫码下方二维码,下载并安装访客端DEMO【环信移动客服】APP


6733c1b61baab84b10dda5f97c866bf0.png


 
打开【环信移动客服】APP,点击右下角的【设置】按钮后,点击右上角【扫一扫】,扫描关联app页面下方二维码,将该客服访客端体验DEMO与您的客服账号关联起来,即可体验与客服聊天或与客户聊天
注意:
初次在苹果手机上使用“商城”Demo时,需要进入“设置 > 通用 > 设备管理 > EaseMob Inc.”页面,点击“信任EaseMob Inc.”。
  

您也可以 快速集成环信SDK,体验来自您APP的 真实用户咨询
Android SDK:请参考CEC Android SDK集成
iOS SDK:请参考CEC iOS SDK集成
也可以直接查看APP集成指南


 
 您还可以为您的其他渠道接入客户互动云
  微博快速集成指南 
  微信快速集成指南
  网页快速集成指南

在您使用中遇到任何问题,可从以下 4 个途径得到解答!

开发文档 - 常见问题的解决方案在这里都能找到!
开发文档收录了所有常见问题,并按照“新手上路、客服模式、管理员模式、多渠道集成、第三方系统对接”对内容进行分类,同时,文档右上角提供模糊搜索。

在线技术咨询 - 超快的问题响应机制,专业技术团队在线解答!
点击【客服后台】-【管理员模式】-【技术支持】-【联系客服】,输入您的问题即可。

环信社区- 使用者交流专区,召唤老司机搞定技术难题!
山不在高,有仙则灵,社区不在大,有大神就行!

电话咨询 - 最直接的方式,专职客服一对一解答!
咨询热线:400-612-1986
 

感谢读到这里的您,下方附上最新鲜的集成说明文档,据说看完走桃花呦 收起阅读 »

环信公开课19期回放:快速搭建一套直播答题系统实操

本着对技术的热衷,对环信的眷恋和对党的忠诚,环信生态圈开发者基于环信即时通讯云写了直播答题开源项目-小信竞答。作为国内首个直播答题开源项目,“小信竞答”发表在IMGeek社区就受到了技术小伙伴们的好评,鉴于大家对直播答题的热情,3月29日举办了主题为“直播答题...
继续阅读 »
   本着对技术的热衷,对环信的眷恋和对党的忠诚,环信生态圈开发者基于环信即时通讯云写了直播答题开源项目-小信竞答。作为国内首个直播答题开源项目,“小信竞答”发表在IMGeek社区就受到了技术小伙伴们的好评,鉴于大家对直播答题的热情,3月29日举办了主题为“直播答题开源项目”的环信公开课,直播讲解如何快速搭建一套直播答题系统。
公开课第19期回放
 



 
 
课程大纲:

直播答题技术架构
1.    【同步问答】环信IM下发问题到海量用户确保毫秒级必达
2.    【视频美颜】环信人脸特效实时视频动态捕捉,贴纸美颜多种特效
3.    【无上限聊天室】环信直播聊天室高并发异步架构,弹性扩容应对流量峰值
4.    【智能反垃圾】环信智能反垃圾实时消息过滤及关键词屏蔽,及时规避风险
5.    【红包功能】奖金统计发放
6.    【复活卡、开黑组团】新玩法层出不穷
 “小信竞答”源码分享
1.    运行效果演示,现场互动答题送礼
2.    源码解读,国内首个开源直播答题项目
3.    经验之鉴,快速搭建一套直播答题系统实操

环信直播聊天室
1.    高并发异步架构,弹性扩容应对流量峰值
2.    多种消息格式,文字、表情、位置、扩展消息
3.    实时配置消息分级策略,重要消息优先必答
4.    后台管理审核功能,直播数据统计
5.    智能反垃圾,自定义敏感词过滤
6.    快速集成demo高质量代码示例
7.    聊天室人数无上限

小信竞答项目源码:github源码地址
 
感谢合作伙伴APICLloud,APKBUS,码客,七牛云,icustomer对本期环信公开课的大力支持

第19期公开课2副本.jpg


  收起阅读 »

iOS 自定义cell

简书地址:自定义cell(一)https://www.jianshu.com/p/8befabfdc4f0                自定义cell(二)https://www.jianshu.com/p/85921a5da8d0 ...
继续阅读 »
简书地址:自定义cell(一)https://www.jianshu.com/p/8befabfdc4f0
               自定义cell(二)https://www.jianshu.com/p/85921a5da8d0


QQ20180410-161412@2x.png




QQ20180410-161420@2x.png


  收起阅读 »

【环信征文】|PHP WEBIM整合到项目(一条龙服务包括保存聊天记录到本地数据库)

   我是用think PHP做的项目,我们的需求是用IM跟他人聊天并且保存聊天记录到本地。我把整个环信文件下载了下来放在了项目的js文件里。并且没有做单独的页面渲染直接用的环信原来的页面,所以大部分功能都写在了控制器里最后跳转到IM的页面。 第一步,IM集成...
继续阅读 »
   我是用think PHP做的项目,我们的需求是用IM跟他人聊天并且保存聊天记录到本地。我把整个环信文件下载了下来放在了项目的js文件里。并且没有做单独的页面渲染直接用的环信原来的页面,所以大部分功能都写在了控制器里最后跳转到IM的页面。
第一步,IM集成
  这个可以参照文档,但是这个文档好像是过时的,如果集成失败可以上官方登陆页面 http://webim.easemob.com/ 把页面加载的文件抄下来。
第二步,自动登陆和他人聊天
  此步要先把网站上的用户注册一个环信账号,然后自动登陆跳过环信的登陆页面,然后把对方加为好友(此步如果对方没有环信账号也需要把好友注册一个环信账号)。上代码


微信图片_20180410135435.png



逻辑是这么实现的,因为在控制器里做的操作,所以登陆信息要保存在cookie里。里面的方法都可以在http://api-docs.easemob.com/#/%E8%8E%B7%E5%8F%96token这个环信的服务器端集成Swagger文档里找到,然后用Curl调用,或者一个更简单的方法,有一个完整的类可以用,链接在此https://github.com/easemob/emchat-server-examples/blob/master/emchat-server-php/easemobtest.php,简单的curl是这么用的


微信图片_20180409114955.png



好此步就此完成
第三步,头像、昵称 渲染
   这步我的处理方法是把你网站用户的头像和昵称查出来然后存在cookie里,然后在demojs文件里进行调用,这步主要麻烦的地方是需要在demo.js的文件里找到页面渲染的地方,大约有8个地方(23288,27321,29764,29862,30101,31211,31360,31465)
   这三步完成基本就完成了IM整合进项目了,还有一个保存聊天记录到本地,我没有用官方的接口来下载历史记录,听说下载下来是一个包,而且数据还会有丢失,所以我的做法是每次发送消息调用自己的Ajax来进行保存数据库操作,然后新在打开聊天的时候请求后台获得聊天记录。
  好,我在demojs里找到了2个发送消息时候的方法,一个是发送文字和表情,一个是发送图片。sendText(27399)这个方法是发送文字和表情的


微信图片_20180409135255.png



  msg.boy里面可以获得到你需要保存在数据库里的所有信息,chat_time是因为聊天记录的时间格式是2018/4/9 上午9:53:03这种的所以我自己加上去的。
  pictureChange(22977)这个方法是发送图片的,这里的url非常重要,而且在msg.set在外面拿不到所以我放在了方法里面调用的Ajax,这个url 是
https://a1.easemob.com/1110170708115611/thdsmartapp/chatfiles/d50bf690-3b9f-11e8-8a3c-57a542779a8b
这样的二进制一会需要下载到本地或者上传到你需要的图片存储空间,我放在了七牛上。


微信图片_20180409135626.png



  保存文字信息非常简单,就像下图一样就可以了,下面的做的判断是当消息类型是图片的时候调用一下图片上传方法


微信图片_20180409140143.png





微信图片_20180410135615.png



  这个图片是图片上传方法,我在后台用CURL进行了上传操作,最开始我先把图片保存在了本地然后再上传,后来我找了二进制可以上传,改成了这个样子。(值得注意的是我用CURL调用的url返回的值会有一个红色的符号,所以我用substr给它截掉了,之前没发现怎么都找不到图片。最后发现是database文件的编码有问题改成utf-8之后截取字符串的那步可以去掉了)这里的接口就是一个普通的上传方法然后返回的是图片名称。这样聊天记录也保存完毕了。
  表结构


微信图片_20180409142529.png



  下一步是读取聊天记录releaseChatRecord这个方法是demojs每次你点击聊天窗口都会触发的方法,我把ajax放在这这里。


微信图片_20180409141116.png



  图片里的targetId就是你当天聊天的好友id,自己的id怎么得到不需要说了吧,你肯定是在登陆状态才能聊天。Demo.chatRecord这个就是需要替换的聊天记录,Ajax请求后台


微信图片_20180409141344.png



  这里注意的是要获得聊天记录需要进行红圈里的判断,来分别你给我发的消息和你给我发的消息,我之前犯得错误没判断最后只获得了我给他人发的消息记录。。。循环里的是构造demojs需要用的json数据。这个对象结构可以在releaseChatRecord方法里console.log()出来然后自己拼装。
  这样已经基本完成了最后上成功后的图片


微信图片_20180409142025.png



  还有个问题没处理就是未送达那个文字,因为我构造的数据里没有加上read=true.你可以选择把IM里这个功能关闭或者在数据里加上去。
  这样保存聊天记录的方式是每次发送都进行保存如果人数太多或太频繁可能会出现问题,以后可能会换成调用官网的获得历史记录的接口。
  收起阅读 »

iOS 中会话列表无法在获取消息后自动刷新,只能通过跳转到别的页面返回之后才会刷新。。

      在网上找到相似的答案,-(void)conversationListDidUpdate:(NSArray *)aConversationList{     [self.tableView reloadData]; }说要实现这个方法。。可是这个...
继续阅读 »
      在网上找到相似的答案,-(void)conversationListDidUpdate:(NSArray *)aConversationList{

    [self.tableView reloadData];

}说要实现这个方法。。可是这个方法是EMChatmanagerdelegate中的方法,单实现这个方法并没有走。。
大家有遇到过的吗? 收起阅读 »

基于环信的仿QQ即时通讯的简单实现

概述   今天看了环信的API,就利用下午的时间动手试了试,然后做了一个小Demo。详细 我的博客地址 之前一直想实现聊天的功能,但是感觉有点困难,今天看了环信的API,就利用下午的时间动手试了试,然后做了一个小Demo。 因为没有刻意去做聊...
继续阅读 »
概述
 
今天看了环信的API,就利用下午的时间动手试了试,然后做了一个小Demo。详细

我的博客地址

之前一直想实现聊天的功能,但是感觉有点困难,今天看了环信的API,就利用下午的时间动手试了试,然后做了一个小Demo。
因为没有刻意去做聊天软件,花的时间也不多,然后界面就很简单,都是一些基本知识,如果觉得功能简单,可以自行添加,我这就不多介绍了。

照例先来一波动态演示:

4043475-d16a88926805236a.gif


 
功能很简单,注册用户 —> 用户登录 —> 选择聊天对象 —> 开始聊天

使用到的知识点:
  1. RecyclerView
  2. CardView
  3. 环信的API的简单使用


依赖的库
compile 'com.android.support:appcompat-v7:24.2.1'
compile 'com.android.support:cardview-v7:24.1.1'
compile 'com.android.support:recyclerview-v7:24.0.0'
1、聊天页面

首先是看了郭神的《第二行代码》做了聊天界面,用的是RecyclerView

a. 消息类的封装
public class MSG {
public static final int TYPE_RECEIVED = 0;//消息的类型:接收
public static final int TYPE_SEND = 1; //消息的类型:发送
private String content;//消息的内容
private int type; //消息的类型
public MSG(String content, int type) {
this.content = content;
this.type = type;
}
public String getContent() {
return content;
}
public int getType() {
return type;
}
}
b. RecyclerView子项的布局​
<LinearLayout
android:id="@+id/ll_msg_left"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
<!-- 设置点击效果为水波纹(5.0以上) -->
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:orientation="horizontal"
android:padding="2dp">
<android.support.v7.widget.CardView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:cardCornerRadius="20dp"
app:cardPreventCornerOverlap="false"
app:cardUseCompatPadding="true">
<ImageView
android:layout_width="50dp"
android:layout_height="50dp"
android:scaleType="centerCrop"
android:src="@mipmap/man" />
</android.support.v7.widget.CardView>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/message_left"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_msg_left"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="10dp"
android:textColor="#fff" />
</LinearLayout>
</LinearLayout>
这是左边的部分,至于右边应该也就简单了。我用CardView把ImageView包裹起来,这样比较好看。效果如下:

4043475-76ea5370b4d09d89.png


c. RecyclerView适配器​
 public class MsgAdapter extends RecyclerView.Adapter<MsgAdapter.MyViewHolder> {
private List<MSG> mMsgList;
public MsgAdapter(List<MSG> mMsgList) {
this.mMsgList = mMsgList;
}
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = View.inflate(parent.getContext(), R.layout.item_msg, null);
MyViewHolder holder = new MyViewHolder(view);
return holder;
}
@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
MSG msg = mMsgList.get(position);
if (msg.getType() == MSG.TYPE_RECEIVED){
//如果是收到的消息,显示左边布局,隐藏右边布局
holder.llLeft.setVisibility(View.VISIBLE);
holder.llRight.setVisibility(View.GONE);
holder.tv_Left.setText(msg.getContent());
} else if (msg.getType() == MSG.TYPE_SEND){
//如果是发送的消息,显示右边布局,隐藏左边布局
holder.llLeft.setVisibility(View.GONE);
holder.llRight.setVisibility(View.VISIBLE);
holder.tv_Right.setText(msg.getContent());
}
}
@Override
public int getItemCount() {
return mMsgList.size();
}
static class MyViewHolder extends RecyclerView.ViewHolder{
LinearLayout llLeft;
LinearLayout llRight;
TextView tv_Left;
TextView tv_Right;
public MyViewHolder(View itemView) {
super(itemView);
llLeft = (LinearLayout) itemView.findViewById(R.id.ll_msg_left);
llRight = (LinearLayout) itemView.findViewById(R.id.ll_msg_right);
tv_Left = (TextView) itemView.findViewById(R.id.tv_msg_left);
tv_Right = (TextView) itemView.findViewById(R.id.tv_msg_right);
}
}
}
这部分应该也没什么问题,就是适配器的创建,我之前的文章也讲过 传送门:简单粗暴——RecyclerView

d. RecyclerView初始化

就是一些基本的初始化,我就不赘述了,讲一下添加数据的细节处理
 
  btSend.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String content = etInput.getText().toString().trim();
if (!TextUtils.isEmpty(content)){
...//环信部分的发送消息
MSG msg = new MSG(content, MSG.TYPE_SEND);
mList.add(msg);
//当有新消息时,刷新RecyclerView中的显示
mAdapter.notifyItemInserted(mList.size() - 1);
//将RecyclerView定位到最后一行
mRecyclerView.scrollToPosition(mList.size() - 1);
etInput.setText("");
}
}
});
至此界面已经结束了,接下来就是数据的读取

2. 环信API的简单应用

官网有详细的API介绍 环信及时通讯V3.0,我这里就简单介绍如何简单集成

a. 环信开发账号的注册

环信官网


创建应用得到Appkey后面要用


4043475-e4dd45e05060467f.png



b. SDK导入

你可以直接下载然后拷贝工程的libs目录下

Android Studio可以直接添加依赖
 
将以下代码放到项目根目录的build.gradle文件里
repositories {
maven { url "https://raw.githubusercontent.com/HyphenateInc/Hyphenate-SDK-Android/master/repository" }
}
在你的module的build.gradle里加入以下代码
android {
//use legacy for android 6.0
useLibrary 'org.apache.http.legacy'
}
dependencies {
compile 'com.android.support:appcompat-v7:23.4.0'
//Optional compile for GCM (Google Cloud Messaging).
compile 'com.google.android.gms:play-services-gcm:9.4.0'
compile 'com.hyphenate:hyphenate-sdk:3.2.3'
}
如果想使用不包含音视频通话的sdk,用compile 'com.hyphenate [:hyphenate-sdk-lite:] 3.2.3'
 
c. 清单文件配置
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="Your Package"
android:versionCode="100"
android:versionName="1.0.0">
<!-- Required -->
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.GET_TASKS" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:name="Your Application">
<!-- 设置环信应用的AppKey -->
<meta-data android:name="EASEMOB_APPKEY" android:value="Your AppKey" />
<!-- 声明SDK所需的service SDK核心功能-->
<service android:name="com.hyphenate.chat.EMChatService" android:exported="true"/>
<service android:name="com.hyphenate.chat.EMJobService"
android:permission="android.permission.BIND_JOB_SERVICE"
android:exported="true"
/>
<!-- 声明SDK所需的receiver -->
<receiver android:name="com.hyphenate.chat.EMMonitorReceiver">
<intent-filter>
<action android:name="android.intent.action.PACKAGE_REMOVED"/>
<data android:scheme="package"/>
</intent-filter>
<!-- 可选filter -->
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="android.intent.action.USER_PRESENT" />
</intent-filter>
</receiver>
</application>
</manifest>
APP打包混淆
-keep class com.hyphenate.** {*;}
-dontwarn com.hyphenate.**
d. 初始化SDK
在自定义Application的onCreate中初始化
public class MyApplication extends Application {
private Context appContext;
@Override
public void onCreate() {
super.onCreate();
EMOptions options = new EMOptions();
options.setAcceptInvitationAlways(false);
appContext = this;
int pid = android.os.Process.myPid();
String processAppName = getAppName(pid);
// 如果APP启用了远程的service,此application:onCreate会被调用2次
// 为了防止环信SDK被初始化2次,加此判断会保证SDK被初始化1次
// 默认的APP会在以包名为默认的process name下运行,如果查到的process name不是APP的process name就立即返回
if (processAppName == null || !processAppName.equalsIgnoreCase(appContext.getPackageName())) {
Log.e("--->", "enter the service process!");
// 则此application::onCreate 是被service 调用的,直接返回
return;
}
//初始化
EMClient.getInstance().init(getApplicationContext(), options);
//在做打包混淆时,关闭debug模式,避免消耗不必要的资源
EMClient.getInstance().setDebugMode(true);
}
private String getAppName(int pID) {
String processName = null;
ActivityManager am = (ActivityManager) this.getSystemService(ACTIVITY_SERVICE);
List l = am.getRunningAppProcesses();
Iterator i = l.iterator();
PackageManager pm = this.getPackageManager();
while (i.hasNext()) {
ActivityManager.RunningAppProcessInfo info = (ActivityManager.RunningAppProcessInfo) (i.next());
try {
if (info.pid == pID) {
processName = info.processName;
return processName;
}
} catch (Exception e) {
// Log.d("Process", "Error>> :"+ e.toString());
}
}
return processName;
}
}
e. 注册和登陆

注册要在子线程中执行
//注册失败会抛出HyphenateException
EMClient.getInstance().createAccount(username, pwd);//同步方法
EMClient.getInstance().login(userName,password,new EMCallBack() {//回调
@Override
public void onSuccess() {
EMClient.getInstance().groupManager().loadAllGroups();
EMClient.getInstance().chatManager().loadAllConversations();
Log.d("main", "登录聊天服务器成功!");
}
@Override
public void onProgress(int progress, String status) {
}
@Override
public void onError(int code, String message) {
Log.d("main", "登录聊天服务器失败!");
}
});
f. 发送消息
//创建一条文本消息,content为消息文字内容,toChatUsername为对方用户或者群聊的id,后文皆是如此
EMMessage message = EMMessage.createTxtSendMessage(content, toChatUsername);
//发送消息
EMClient.getInstance().chatManager().sendMessage(message);
g. 接收消息
msgListener = new EMMessageListener() {
@Override
public void onMessageReceived(List<EMMessage> messages) {
//收到消息
String result = messages.get(0).getBody().toString();
String msgReceived = result.substring(5, result.length() - 1);
Log.i(TAG, "onMessageReceived: " + msgReceived);
final MSG msg = new MSG(msgReceived, MSG.TYPE_RECEIVED);
runOnUiThread(new Runnable() {
@Override
public void run() {
mList.add(msg);
mAdapter.notifyDataSetChanged();
mRecyclerView.scrollToPosition(mList.size() - 1);
}
});
}
@Override
public void onCmdMessageReceived(List<EMMessage> messages) {
//收到透传消息
}
@Override
public void onMessageRead(List<EMMessage> list) {
}
@Override
public void onMessageDelivered(List<EMMessage> list) {
}
@Override
public void onMessageChanged(EMMessage message, Object change) {
//消息状态变动
}
};
接收消息的监听器分别需要在OnResume()和OnDestory()方法中注册和取消注册
EMClient.getInstance().chatManager().addMessageListener(msgListener);//注册
EMClient.getInstance().chatManager().removeMessageListener(msgListener);//取消注册
需要注意的是,当接收到消息,需要在主线程中更新适配器,否则会不能及时刷新出来

项目文件截图:


HyTYCEDIj4uugpEvJ59.jpg


到此,一个简单的及时聊天Demo已经完成,功能很简单,如果需要添加额外功能的话,可以自行参考官网,官网给出的教程还是很不错的!

最后希望大家能多多支持我,需要你们的支持喜欢!! 收起阅读 »

源码下载:全国首个直播答题开源项目了解一下!!!(基于环信即时通讯云)

2018年伊始,全民直播答题浪潮来袭,一度被认为是一个新的互联网风口,王思聪凭借在现象级产品《冲顶大会》上疯狂"撒币"一时风光无二,凭借超高奖金和超低门槛吸引了大量网民参与和市场的目光。正因为直播答题是一种通过极低的成本来推动APP获客、保留存、拉活跃的新模式...
继续阅读 »
   2018年伊始,全民直播答题浪潮来袭,一度被认为是一个新的互联网风口,王思聪凭借在现象级产品《冲顶大会》上疯狂"撒币"一时风光无二,凭借超高奖金和超低门槛吸引了大量网民参与和市场的目光。正因为直播答题是一种通过极低的成本来推动APP获客、保留存、拉活跃的新模式,各类直播答题APP如雨后春笋般进入大家的视野,越来越多企业希望赶上这波风口,快速搭建一套直播答题系统。作为一名环信生态圈资深开发者,本着对技术的热衷,对环信的眷恋和对党的忠诚,基于环信即时通讯云写了“小信竞答”这个直播答题开源项目,目前项目源码已全部免费开放,希望对有需求的企业和开发者提供一个思路和参考。



 


微信图片_20180316190105.jpg


 
小信竞答技术架构图:

theFlowChart.png


     整个项目分为管理员端,观众端和服务端,首先在服务端预设好题目,由管理员发起直播开始答题,服务端收到指令将12道题目利用环信IM推送到观众端,观众端收到题目开始答题,将答案返回给服务端由服务端进行判断,如果答题正确进入下一题,答题错误判断是否使用复活卡,这里要注意的是需要加一个复活卡的使用次数判断。
 
   在整个答题过程中,管理员端会定时去服务端查询答题结果,等到全部答题结束,点击结束本次答题,服务端将计算好的结果返回并发放奖金,使用环信IM推送将答题结果推给观众端。
 
小信竞答效果图



微信图片_20180316190206.jpg



关于直播间:
   直播间由直播画面和聊天室两个部分组成,“小信竞答”的聊天室使用环信聊天室,集成比较简单,基础版就能支持5000人在线聊天,增值服务版聊天室人数无上限,可以去环信官网注册一个开发者账号,创建应用将APPKEY替换成自己的;环信直播聊天室可以集成所有市场主流CDN厂商的推拉流功能(腾讯,七牛,UCloud,网宿等)。
 
环信直播聊天室特点  
This is Title
 
1、采用支持高并发的异步架构,轻松应对千万级并发请求; 各项基础服务集群化,确保系统高可用性; 系统冗余度高,容量评估体系完善,弹性扩容应对流量峰值;
2、支持各种消息格式:文字、表情、图片、声音、视频、附件、位置、扩展消息;
3、支持实时配置的消息分级策略,确保重要消息优先必达; 
4、支持直播聊天室后台管理及审核功能,提供直播相关数据统计;
5、提供智能反垃圾和自定义敏感词过滤功能;
6、快速集成,demo提供高质量代码示例,可根据运营情况随时扩展;
7、聊天室人数无上限 
小信竞答项目源码: github源码地址
 
写到最后:
   
     小信竞答源码全部开放,仅供学习和参考,如果作为商业用途,按照广电总局对网络直播答题节目管理的,需要 “网络视听许可证、主持人持证、还有通过审批发放的节目备案号”,三证缺一不可,未持有《信息网络传播视听节目许可证》的任何机构和个人,一律不得开办网络直播答题节目。
 
本月底《环信公开课第19期-直播答题开源项目》将线上讲解“小信竞答”实现思路,手把手教您从零开始搭建一个直播答题项目,扫码加入公开课微信群与大牛面对面交流。

微信图片_20180316190334.jpg


  收起阅读 »

【环信征文】| iOS集成环信推送,最详细流程(证书创建、环信集成、测试)

这几天项目里又用到了环信的推送,虽然之前做过,但是很久不做还是有很多细节没有注意到,所以还是决定从头开始做一遍,把每一个环节都详细记录下来,同样的把每一个坑也记录下来.方便自己以后做的时候忘记哪个流程了可以在看一遍.我很能理解那种遇到问题网上百度一堆类似答案但...
继续阅读 »
这几天项目里又用到了环信的推送,虽然之前做过,但是很久不做还是有很多细节没有注意到,所以还是决定从头开始做一遍,把每一个环节都详细记录下来,同样的把每一个坑也记录下来.方便自己以后做的时候忘记哪个流程了可以在看一遍.我很能理解那种遇到问题网上百度一堆类似答案但是并不好使的情况,所以我会将我在项目中遇到的问题都贴出来,希望能给大家带来些许参考和帮助,

一.推送的原理和流程(着急做推送的可以跳过这一步)
首先给大家推荐一个介绍推送机制很优秀的帖子:http://www.jianshu.com/p/e347f999ed95 ,里面关于本地推送和远程推送的介绍都很详细,至少我看了感觉还是收获很多的.尤其是里面有几张图片不知道是博主在哪里找的,但是真的是一看就透,太赞了,所以我果断盗过来了0.0. 这里我对推送的流程做了一个简单的叙述,力求用最简单的语言能说明整个推送的机制.

先搬过来一张图再说


692194-fc99211b942ffb8c.png



再搬一张:


692194-a1764a5b6ef76592.png




当我们的苹果手机联网的时候,会自动与苹果的服务器建立长连接,长连接的好处有很多,比如系统升级、时间校准、数据传输和响应比较快以及数据可以保持最新状态等功能.上面这两张图片简单的讲述了推送的流程:

- 1.首先我们需要将自己设备的UDID和应用的Bundle Identifier发送到苹果的服务器,然后苹果的服务器会返回给我们一个DeviceToken,这个在我看来就是创建推送证书和描述文件的过程.
- 2.我们将包含手机和应用标示的打包文件上传到做推送的服务器上去,当我们从推送服务器的后台发起推送消息的时候,推送服务器会将我们的DeviceToken和需要发送的消息Message发送到苹果的APNS(Apple push Notification Service)服务器.
- 3.当苹果的服务器收到DeviceToken和需要发送的消息Message时,会根据DeviceToken中的UDID查找设备,根据DeviceToken中的Bundle Identfier查找该应用,并将Message发送到该设备上.

下面是以QQ服务器为栗子说明的即时通讯的机制:


692194-42cd5b5723039e84.png



图片已经说得够详细明了了,我就不插嘴了,下面开始我们的工程.

二.具体流程

我们创建一个名为TestDemo的工程,我是使用Xcode8.1来开发的,工程名为PushDemo,创建好的工程界面如下(Xcode8)


1232108-fecb3186b8c4700d.png



从Xcode8之后,Xcode提供了自动管理证书的功能,这个用起来很方便,我目前在工程中用到的最多的地方就是创建好一个Demo之后,如果想真机运行的话,那么只需要在Team选择框里选择我的开发账户,接下来下面会出现一个加载提示圈,等它加载完了就可以在真机上运行了,这个过程实际上是Xcode使用你当前的BundleId去该账户的开发这中心创建了对应的AppId和描述文件,但是我们既然是作为一个开发流程的记录,就自己来创建这些东西,所以,我们取消选择Automatically manage signing选项.此时界面如下:


1232108-6da09aaa4693b3b3.png




好了,我们要正式开始我们的工作了GO GO GO!
1. 首先我们先去官网创建AppID和描述文件.      

我们是要集成推送的,所以我们需要用到cer文件,这个东西实际上就是苹果给开发者颁发的一个证书,我们需要将它导入到我们的AppId配置里,否则的话是无法集成推送的,还记得安装应该的时候会提示"无法安装为认证发布者的应用"之类的信息么,我猜测这个cer文件就是我们身份的标示,使我们开发的应用可以供人们正常安装使用,关于证书有一篇很详细的帖子,希望了解证书之类信息的看官可以去瞅瞅:http://m.blog.csdn.net/article/details?id=8617788

创建cer文件的流程很简单,打开"钥匙串访问"(虽然很好找,但是还是把图贴出来吧,怕小朋友迷路)


1232108-ab1cf50531874785.png





1232108-6438c3fb50b4aaa5.png




打开钥匙串之后点击"从证书颁发机构请求证书"



1232108-d4a8951b51e6cceb.png



邮箱和常用名随便填写,记住下面的选择框选择"存储到磁盘"


1232108-bda705277e11698e.png




点击存储


1232108-07e46d1757718ea4.png




已经在桌面保存了



1232108-43a584c54d8f6cf2.png




到此,我们已经创建好了cer文件,接着我们去开发者中心创建AppId和描述文件

2. 创建AppId和描述文件

首先进入开发者中心,百度搜索Apple Developer,(哎 真的是详细到家了啊,我都人不下去了)
上图


1232108-cedf60744b970a2e.png




输入开发者账户,登录进去



1232108-2b564af543f98b20.png



你将看到这个页面


1232108-a8daff6248a1238a.png




点击看到:


1232108-e9a6e57aa9930544.png



输入AppId文件名和BundleId


1232108-ae96fe218d56e753.png



选中下面的PushNotifications


1232108-38bf6bb6a67f51d2.png



点击Continue:


1232108-934cf686ea792fe8.png



点击register:


1232108-083327989bcbe786.png



点击Done回到AppId列表页面


1232108-0538ef84dea33c70.png



在AppId列表页面可以看到我们的AppID了


1232108-95b229c6dbadcdac.png




但是,还没有完成,因为我们是要做推送的,所以需要上传我们的cer文件
,点击我们的AppId,在展开的详情里可以看到:


1232108-15f6e6cba67bcf32.png





Push Notification的两个指示灯还是黄色的状态,我们要将它启用,点击Edit,在点开的页面里滑动至底部,记得要选中Push Notification按钮,接着点击上方的开发证书下的创建证书按钮:


1232108-6e60ef33f39c3c3e.png




点击Continue


1232108-20ba8ef5aac404fe.png




点击 choose file:


1232108-a8b6d1f97c9f57a7.png




将我们从开发机构请求的证书传上去,之后点击Register:


1232108-be277d8092ca589a.png




点击Register之后的页面,点击download,将其下载到桌面上,download之后记得点击done完成文件创建:


1232108-70ad59ee048b37de.png




桌面上的文件:


1232108-ea6f4078346a5a1c.png




现在我们就完成了给AppID创建开发者证书,然后我们要给它创建发布者证书,点击Done之后回到AppIds列表,如果找不到的话,点击右边的App IDs


1232108-f3563cf07940ad50.png




点开项目的AppId,此时界面如下,点击最下面的CreateCertificate,开始给AppID创建发布者证书,给AppId创建发布者证书流程跟创建开发者证书是一样的!给AppId创建发布者证书流程跟创建开发者证书是一样的!给AppId创建发布者证书流程跟创建开发者证书是一样的!重要的事情说三遍!!因为我不贴出来创建发布证书的图了,所以各位根据创建开发证书的流程再走一遍就好,同样也要将发布者证书下载到本地.:


1232108-5d146f8fcbf96aa2.png





1232108-f4959adf27635124.png




当创建好之后在回到这个页面时,应该显示如下所示:


1232108-d9e0b2bf3794fad2.png




此时本地我们下载的文件如下:


1232108-651e46a585df0fd2.png




然后将这两个证书拖到钥匙串里,步骤如下:
首先打开钥匙串:


1232108-2ed433418c0ceaf9.png





1232108-4907f4f3f01371e6.png



然后先点击:系统-证书,然后将两个文件拖进去,会提示你输入开机密码,输入就好了(建议添加之前先对这个界面截屏,添加完之后可以对比刚刚添加了那些文件)


1232108-9e9434ee2246e3ce.png




添加完之后是这个样子,画框的是我们的证书


1232108-c71476ca00c853ec.png




然后选择左边的"登录"选项,可以看到我们刚才创建的证书


1232108-b7e1a0917e1bdc6a.png



选中第一个证书,然后右键(你懂得右键的意思),选择导出...


1232108-b020b7d0a48f795d.png




选择导出为P12文件,存储在桌面上,获取到P12文件.对这两个证书进行同样的操作.(记得标题有(Develop)的起名为Product文件,第二个证书导出的时候起名为Develop,名字可以自己定,只是为了区别)


1232108-04d80a4621bc1708.png



然后会提示你输入密码,这里我设置的密码是zx123456,自己设定好一定要记住,一会儿要用.


1232108-409c20593f4f262e.png



然后可以在桌面上看到我们导出的P12文件啦


1232108-964de37f66433eaa.png




现在我们就完成了所有的证书的创建,可以去环信上创建我们的应用啦.

3.创建真机调试文件以及导入到项目中

因为必须要进行真机测试,而且我们关闭了自动管理证书,就导致Xcode8不会自动帮我们生成证书,所以我们要自己创建真机调试证书并导入到项目中去,流程如下:

创建描述文件:


1232108-9dbec2cea23ab8e1.png




选择开发模式,下一步:


1232108-edece2d8c17d8f27.png




选择对应的AppID,选择我们刚才创建的AppId:


1232108-ee9029c89f0c34d2.png




选择开发团队,我一般都是全选的,下一步:


1232108-3744e08d9d10aa55.png




选择真机调试的机器,全选,下一步:


1232108-124184468c7c6531.png




下一步:


1232108-c6c0e50e57add4be.png




将创建好的描述文件下载下来,放到桌面上:


1232108-8a2c9400633c796d.png




创建好的描述文件:


1232108-40bebec424947b9d.png




首先选择debug模式下载的真机调试描述文件:


1232108-a1d8a4c3a0bb0a23.png



选择桌面上刚刚下载的描述文件:


1232108-692279ac01c7d137.png




使用同样的步骤,选择Release模式下的真机调试文件,一模一样的操作,不贴图了.两个文件都导入进去之后,插上真机,就可以进行真机调试了.

4.在环信创建我们的应用

首先百度搜索环信,打开他们的官网,先注册账户,注册过的可以跳过了,上图:
注册的时候选择"注册即时通讯云"


1232108-672cbab760c523f9.png




注册的时候需要填写各种信息,按照格式填写就好了,填写完之后登陆,点击创建应用


1232108-bd67f4f4f35cd764.png




填写应用信息


1232108-4d748bd8c621d0ff.png




填写完如下图咯


1232108-bbdc1fbea11501aa.png




然后需要上传我们的P12文件,图片很清晰- -,不多说,第一次我选择上传的是生产证书:


1232108-6b5a19a243a78839.png




第二次上传开发证书:


1232108-538c02769688cac0.png




至此,我们的证书开发也都上传完了,路漫漫其修远兮,开始集成环信到代码里吧

5.集成环信到项目中

首先在这里下载最新的SDK(截至到写本文时最新的SDK为)

http://www.easemob.com/download/im 环信推送SDK下载链接

点击iOS的最新SDK下载,这里下载的是V3.x的SDK


1232108-279c3c47c46ec724.png




下载到桌面是这个鬼样子


1232108-9d5b7d0ee1c2d404.png




我们只需要将画圈的两个文件夹导进去工程里就好了,其他的用不上


1232108-e8d9522b4f496454.png




导进去之后文件列表是这样,编译会出错别急,慢慢改.


1232108-da727a18407e70d1.png




向项目里添加需要的库


1232108-e0ac523eac1d6042.png




上面的图片是截取的环信官方文档,我添加完是这个样子的:


1232108-5409054190742754.png



方便复制库名的文字:
CoreMedia.framework
AudioToolbox.framework
AVFoundation.framework
MobileCoreServices.framework
ImageIO.framework
libc++.dylib
libz.dylib
libstdc++.6.0.9.dylib
libsqlite3.dylib
(如果使用的是 xcode7,后缀为 tbd。)
这一步很重要,因为SDK 不支持 bitcode,所以要将 Build Settings → Linking → Enable Bitcode 中设置 NO。



1232108-96cd9023e11f17bd.png




command+B编译工程,大量爆红.别着急,修改我们的PCH文件就好了
在PCH文件添加
```
#ifdef __OBJC__
    #import <UIKit/UIKit.h>
#endif
```

将我们所有定义和添加的头文件和宏定义,都放在#ifdef __OBJC__和#endif中间
就可以解决这个问题.

然后在项目里打开推送:


1232108-96cd9023e11f17bd.png




6.测试是否集成成功

首先,我们去环信的后台给我们的应用添加一个用户


1232108-8abe301c76c6c7f4.png




用户名我设置成了:13051698888  密码设置成了:222222


1232108-94c1ef7dbdf3014c.png




接着我们要去appledate.m文件里添加东西了,很重要一步,废话不多说,直接贴出来需要配置的代码,直接拿去用0.0,需要添加的东西我在注释里注释的很明白...
记得要导进去头文件
#import "EMSDK.h"

```
@interface AppDelegate ()<EMChatManagerDelegate>

@end

@implementation AppDelegate


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    //AppKey:注册的AppKey,点击"应用概述"可以看到AppKey,粘贴过来就可以。
    //apnsCertName:推送证书名,填写你的开发证书或者发布证书名,就是上传到环信后台的两个中的一个,什么环境下测试使用什么环境的证书。
    EMOptions *options = [EMOptions optionsWithAppkey:@"1192161108178165#testpushdemo"];
    options.apnsCertName = @"Develop";
    [[EMClient sharedClient] initializeSDKWithOptions:options];

    //登录环信 这里使用的是我刚才在环信后台创建的账户名和密码,使用这个账户登录,到时候如果在后台给客户端发消息的话,就可以找到该用户
    [[EMClient sharedClient] loginWithUsername:@"13051698888"
                                      password:@"222222"
                                    completion:^(NSString *aUsername, EMError *aError) {
                                        if (!aError) {
                                            NSLog(@"环信登陆成功");
                                            EMPushOptions *emoptions = [[EMClient sharedClient] pushOptions];
                                            //设置有消息过来时的显示方式:1.显示收到一条消息 2.显示具体消息内容.
                                            //自己可以测试下
                                            emoptions.displayStyle = EMPushDisplayStyleSimpleBanner;
                                            [[EMClient sharedClient] updatePushOptionsToServer];
                                        } else {
                                            NSLog(@"环信登陆失败");
                                        }
                                    }];
    
    
    /**
     注册APNS离线推送  iOS8 注册APNS
     */
    if ([application respondsToSelector:@selector(registerForRemoteNotifications)]) {
        [application registerForRemoteNotifications];
        UIUserNotificationType notificationTypes = UIUserNotificationTypeBadge |
        UIUserNotificationTypeSound |
        UIUserNotificationTypeAlert;
        UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:notificationTypes categories:nil];
        [application registerUserNotificationSettings:settings];
    }
    else{
        UIRemoteNotificationType notificationTypes = UIRemoteNotificationTypeBadge |
        UIRemoteNotificationTypeSound |
        UIRemoteNotificationTypeAlert;
        [[UIApplication sharedApplication] registerForRemoteNotificationTypes:notificationTypes];
    }
    
    //添加监听在线推送消息
   [[EMClient sharedClient].chatManager addDelegate:self delegateQueue:nil];    
    return YES;
}

//监听环信在线推送消息
- (void)messagesDidReceive:(NSArray *)aMessages{
    
        UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"提示" message:@"收到环信通知" delegate:nil cancelButtonTitle:@"取消" otherButtonTitles:@"确定", nil];
        [alertView show];
        
        //aMessages是一个对象,包含了发过来的所有信息,怎么提取想要的信息我会在后面贴出来.
}

// 将得到的deviceToken传给SDK
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken{
    [[EMClient sharedClient] bindDeviceToken:deviceToken];
}

// 注册deviceToken失败
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error{
    NSLog(@"error -- %@",error);
}

// APP进入后台
- (void)applicationDidEnterBackground:(UIApplication *)application
{
    [[EMClient sharedClient] applicationDidEnterBackground:application];
}

// APP将要从后台返回
- (void)applicationWillEnterForeground:(UIApplication *)application
{
    [[EMClient sharedClient] applicationWillEnterForeground:application];
}
```
上面的几个方法在appdelegate里是必须重写的,不然会直接导致推送不成功.其中.需要重点说明的是:

- 只有在应用完全退出被杀掉的状态下,才可以收到环信推送的通知;
- 如果要发送在线的通知,需要在messagesDidReceive方法里获取到环信推送的消息之后给用户发起一个本地通知,这个大家可以自己研究下.
- 通过设置emoptions.displayStyle = EMPushDisplayStyleSimpleBanner;(上面代码有)可以设置有通知过来的时候的显示方式,显示一个提示或者显示完整的消息.
- 上传证书下面填写的应用包名,指的是你的BundleID !!!!我在这里踩了坑,切记!!.

测试推送:
1. 在应用完全退出的情况下(使用在环信注册的账户登录一次,确认登录成功之后再完全退出),选中我们的用户,点击发送消息:


1232108-8e6a60d144269c67.png




点击发送:


1232108-17f029870d36abef.png




测试结果:


1232108-aa2b808cf5cd1c6d.png





2.程序在线的时候测试推送,还是发送"你好啊",然后我们在messagesDidReceive拦截环信的EMMessage对象,针对EMMessage对象的解析方式如下,完整的抽取环信推送消息的方法:
```
- (void)messagesDidReceive:(NSArray *)aMessages{
    
        UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"提示" message:@"收到环信通知" delegate:nil cancelButtonTitle:@"取消" otherButtonTitles:@"确定", nil];
        [alertView show];
    
        for (EMMessage *message in aMessages) {
            EMMessageBody *msgBody = message.body;
            switch (msgBody.type) {
                case EMMessageBodyTypeText:
                {
                    // 收到的文字消息
                    EMTextMessageBody *textBody = (EMTextMessageBody *)msgBody;
                    NSString *txt = textBody.text;
                    NSLog(@"收到的文字是 txt -- %@",txt);
                }
                    break;
                case EMMessageBodyTypeImage:
                {
                    // 得到一个图片消息body
                    EMImageMessageBody *body = ((EMImageMessageBody *)msgBody);
                    NSLog(@"大图remote路径 -- %@"   ,body.remotePath);
                    NSLog(@"大图local路径 -- %@"    ,body.localPath); // // 需要使用sdk提供的下载方法后才会存在
                    NSLog(@"大图的secret -- %@"    ,body.secretKey);
                    NSLog(@"大图的W -- %f ,大图的H -- %f",body.size.width,body.size.height);
                    NSLog(@"大图的下载状态 -- %u",body.downloadStatus);
    
    
                    // 缩略图sdk会自动下载
                    NSLog(@"小图remote路径 -- %@"   ,body.thumbnailRemotePath);
                    NSLog(@"小图local路径 -- %@"    ,body.thumbnailLocalPath);
                    NSLog(@"小图的secret -- %@"    ,body.thumbnailSecretKey);
                    NSLog(@"小图的W -- %f ,大图的H -- %f",body.thumbnailSize.width,body.thumbnailSize.height);
                    NSLog(@"小图的下载状态 -- %u",body.thumbnailDownloadStatus);
                }
                    break;
                case EMMessageBodyTypeLocation:
                {
                    EMLocationMessageBody *body = (EMLocationMessageBody *)msgBody;
                    NSLog(@"纬度-- %f",body.latitude);
                    NSLog(@"经度-- %f",body.longitude);
                    NSLog(@"地址-- %@",body.address);
                }
                    break;
                case EMMessageBodyTypeVoice:
                {
                    // 音频sdk会自动下载
                    EMVoiceMessageBody *body = (EMVoiceMessageBody *)msgBody;
                    NSLog(@"音频remote路径 -- %@"      ,body.remotePath);
                    NSLog(@"音频local路径 -- %@"       ,body.localPath); // 需要使用sdk提供的下载方法后才会存在(音频会自动调用)
                    NSLog(@"音频的secret -- %@"        ,body.secretKey);
                    NSLog(@"音频文件大小 -- %lld"       ,body.fileLength);
                    NSLog(@"音频文件的下载状态 -- %u"   ,body.downloadStatus);
                    NSLog(@"音频的时间长度 -- %u"      ,body.duration);
                }
                    break;
                case EMMessageBodyTypeVideo:
                {
                    EMVideoMessageBody *body = (EMVideoMessageBody *)msgBody;
    
                    NSLog(@"视频remote路径 -- %@"      ,body.remotePath);
                    NSLog(@"视频local路径 -- %@"       ,body.localPath); // 需要使用sdk提供的下载方法后才会存在
                    NSLog(@"视频的secret -- %@"        ,body.secretKey);
                    NSLog(@"视频文件大小 -- %lld"       ,body.fileLength);
                    NSLog(@"视频文件的下载状态 -- %u"   ,body.downloadStatus);
                    NSLog(@"视频的时间长度 -- %u"      ,body.duration);
                    NSLog(@"视频的W -- %f ,视频的H -- %f", body.thumbnailSize.width, body.thumbnailSize.height);
    
                    // 缩略图sdk会自动下载
                    NSLog(@"缩略图的remote路径 -- %@"     ,body.thumbnailRemotePath);
                    NSLog(@"缩略图的local路径 -- %@"      ,body.thumbnailLocalPath);
                    NSLog(@"缩略图的secret -- %@"        ,body.thumbnailSecretKey);
                    NSLog(@"缩略图的下载状态 -- %u"      ,body.thumbnailDownloadStatus);
                }
                    break;
                case EMMessageBodyTypeFile:
                {
                    EMFileMessageBody *body = (EMFileMessageBody *)msgBody;
                    NSLog(@"文件remote路径 -- %@"      ,body.remotePath);
                    NSLog(@"文件local路径 -- %@"       ,body.localPath); // 需要使用sdk提供的下载方法后才会存在
                    NSLog(@"文件的secret -- %@"        ,body.secretKey);
                    NSLog(@"文件文件大小 -- %lld"       ,body.fileLength);
                    NSLog(@"文件文件的下载状态 -- %u"   ,body.downloadStatus);
                }
                    break;
                    
                default:
                    break;
            }
        }
}
```

发送成功之后打印结果如下:
```
2016-12-01 16:03:26.060088 PushDemo[1392:450230] 收到的文字是 txt -- 你好啊
```
三.结语

至此,我们就成功集成了环信推送到我们的项目中.另外提供一些在做推送的时候经常会用到的小方法
- 设置应用图标右上角数字角标.
```
UIApplication *application = [UIApplication sharedApplication]; [application setApplicationIconBadgeNumber:3];
```
补充一个环信坑:
编译通过后,运行的时候爆炸,提示:dyld: Library not loaded: @rpath/Hyphenate.framework/Hyphenate
需要设置Hyphenate.frameword的Embedded Binariew属性



1232108-0167fe66d3cd3151.png




- 如果推送证书那里没看特别明白的话,提供一个创建推送证书的链接:http://www.jianshu.com/p/78282e16db66


- 设置推送过来时候的_apns_昵称:
```
[[EMClient sharedClient] setApnsNickname:@"推送昵称"];
```

一直在抽时间写这篇博客,平常比较忙没有大块的时间来写和记录这些东西,不过东拼西凑,没事写一点最终还是写完了.希望看到这里的小伙伴都可以成功集成环信推送,有问题可以在下面留言,我看到了肯定会回复的.希望围观的小伙伴可以不吝指点这篇博客中出现的错误,不管是文字错误还是逻辑错误等等,我一定会尽快修正,不给后人留坑.....

给出项目gitHub地址:
https://github.com/TheRuningAnt/PushDemo.git  喜欢的话,记得给小星星哈

排版可能有点问题,原博客地址:http://blog.csdn.net/mumubumaopao/article/details/53423393
  收起阅读 »

环信即时通讯云V3.3.8已发布,新增服务诊断接口,方便自定位接口问题

Android SDK V3.3.8 1.添加服务诊断接口 2. 添加设置音频码率接口 3. 优化重连逻辑,减少重连次数 4. 添加社区版SDK注册用户,创建群组\聊天室达到数量限制的提示 ios SDK V3.3.8 新功能: • 服务诊断接口,...
继续阅读 »
Android SDK V3.3.8 
1.添加服务诊断接口

2. 添加设置音频码率接口

3. 优化重连逻辑,减少重连次数

4. 添加社区版SDK注册用户,创建群组\聊天室达到数量限制的提示

ios SDK V3.3.8 
新功能: 

• 服务诊断接口, demo UI体现在“setting-debug-服务器诊断”


• 设置音频码率, 接口[EMCallOptions maxAudioKbps]


• 添加新的错误码(达到服务器上限),体现在创建用户,创建群组,创建聊天室


功能更新:

[EMClient isLoggedIn]语义有所改变,原意是是否已经完成登录操作,现在的意思是是否成功登录过

服务端诊断接口介绍:
服务诊断功能是为了在用户使用SDK出现登录失败等问题时用于辅助定位哪个接口服务出现问题.

服务诊断流程:
1.未登录账号诊断:
1.输入用户名和密码
2.验证用户名密码的合法性
3.诊断获取DNS列表接口
4.诊断获取Token接口
5.用户登录
7.用户登出
2.已登录账号诊断:
1.获取当前登录账户的账号和密码(无需用户输入)
2.诊断获取DNS列表接口
3.诊断获取Token接口
4.用户登录
说明: 已登录账号诊断无登出操作,以免影响已登录账户的正常使用.

使用说明:
1.Android:
1.登录界面: 右下方 "SDK接口诊断" link.(该link会被软键盘遮挡)
1.用户已登录: "设置-SDK接口诊断"
2.iOS:
“setting-debug-服务器诊断”




更新日志:Android更新 ios更新
SDK下载:下载地址 收起阅读 »

easeUI聊天界面中好友头像是如何传值的

easeUI聊天界面中好友头像是如何传值的
easeUI聊天界面中好友头像是如何传值的

php服务器端怎么获取到用户聊天记录。

public function chatRecord($ql = '', $cursor = '', $limit = 20) { $ql = ! empty ( $ql ) ? "ql=" . $ql : "order+by+t...
继续阅读 »

public function chatRecord($ql = '', $cursor = '', $limit = 20) { $ql = ! empty ( $ql ) ? "ql=" . $ql : "order+by+timestamp+desc"; $cursor = ! empty ( $cursor ) ? "&cursor=" . $cursor : ''; $url = $this->url . "chatmessages?" . $ql . "&limit=" . $limit . $cursor; $access_token = $this->token (); $header [] = 'Authorization: Bearer ' . $access_token; $result = $this->curl ( $url, '', $header, $type = "GET " ); echo $result; }
在网上看别人是这样写的,但不知道$ql的值是什么格式 收起阅读 »

环信客户互动云v5.32已发布,新增质检申诉功能

本次更新的主要内容为: 新增质检申诉功能,用于质检员完成会话质检后,客服可以向打分的质检员发起申诉,提交证据并要求复核质检结果。客服对质检结果发起申诉后,管理员和质检员可以查看客服的质检申诉详情,并对申诉进行处理。 客服模式 新增质检申诉功能 ...
继续阅读 »
本次更新的主要内容为:
新增质检申诉功能,用于质检员完成会话质检后,客服可以向打分的质检员发起申诉,提交证据并要求复核质检结果。客服对质检结果发起申诉后,管理员和质检员可以查看客服的质检申诉详情,并对申诉进行处理。

客服模式

新增质检申诉功能


质检申诉功能用于质检员完成会话质检后,若客服认为质检结果不合理,可以向打分的质检员发起申诉,提交证据并要求复核质检结果。

客服模式新增质量检查模块,包含“质检记录”和“申诉记录”两个页面。在质检记录页面,客服可以查询会话质检结果并发起申诉;在申诉记录页面,客服可以查询申诉的进展状态,与质检员沟通会话相关情况。

查询质检记录

进入“质量检查 > 质检记录”页面,可以查看自己的会话质检结果。支持根据时间段、渠道、质检员、关联、会话标签等指标进行筛选,并支持导出筛选后的质检记录。

在质量检查页面的列表中,点击任意会话,可以查看该会话的详细质检评分和备注。 

001.png


发起质检申诉

在会话详情的“质检”页签,点击“质检申诉”按钮,并填写申诉主题和申诉理由,可以对会话的质检结果发起申诉。

质检申诉将由打分的质检员处理。 

002.png


查询申诉记录

进入“质量检查 > 申诉记录”页面,可以查看自己的申诉记录,包括未开启、处理中和已结束的申诉。

申诉状态:

未开启:客服发起会话的质检申诉后,默认为“未开启”状态。
处理中:质检员开始进行申诉处理后,申诉为“处理中”状态。
已结束:质检员与客服沟通完毕并结束申诉处理后,申诉为“已结束”状态。

在申诉记录列表中,点击任意申诉,可以查看质检员对该申诉的评论,申诉流程的记录,以及提交评论(包含文字和附件)。 

003.png


管理员模式

新增申诉管理功能


客服对质检结果发起申诉后,管理员和质检员可以查看客服的质检申诉详情,并对申诉进行处理。只有原打分的质检员能够修改申诉的会话的质检评分。

进入“管理员模式 > 质量检查 > 申诉管理”页面,查看所有未开启、处理中、已结束的质检申诉单。支持根据申诉单号搜索申诉单,以及支持根据时间段、申诉人筛选申诉单。

申诉状态:

  • 未开启:客服发起会话的质检申诉后,默认为“未开启”状态。

  • 处理中:质检员开始进行申诉处理后,申诉为“处理中”状态。

  • 已结束:质检员与客服沟通完毕并结束申诉处理后,申诉为“已结束”状态。

    004.png



在质检申诉列表中,点击任意申诉,可以查看该申诉的详情,包括创建人、会话ID、申诉主题、申诉理由、评论、申诉流程的记录等。

点击申诉详情右下角的“开始进行”按钮,开始处理该申诉。然后:

点击“会话详情”按钮,进入会话详情页修改质检评分(注:只有原打分的质检员能够修改申诉的会话的质检评分)。
在评论区,输入评论内容,对申诉进行评论。评论对发起申诉的客服可见。

005.png

完成申诉处理后,点击申诉详情右下角的“结束申诉”按钮,将申诉状态修改为“已结束”。

新增“访客完成满意度评价”事件

自定义事件推送功能,新增“访客完成满意度评价”事件。支持在访客完成对会话的满意度评价时,将满意度评价详情(包含满意度评价、会话、访客、客服信息)推送到接收事件的服务器,方便企业对客户满意度进行统计、留档。

推送“访客完成满意度评价”事件:
[list=1]
  • 开通“自定义事件推送”功能。

  • 进入“设置 > 自定义事件推送”页面,点击“创建事件推送”。

  • 填写自定义事件名称、接收事件的服务器地址,勾选需要推送的事件(访客完成满意度评价),并保存。



  • 关于“访客完成满意度评价”事件的消息内容和注释,请查看环信官网文档“自定义事件推送”章节。

    注:自定义事件推送为标准版/旗舰版增值服务。如需开通,请提供租户ID并联系环信商务经理。 
     
    Android客服工作台

    当前版本:V3.7

    新功能/优化:

    • 新增自定义表情功能(增值服务)

    • 优化告警功能部分提醒


     环信客户互动云更新文档地址
    环信客户互动云登陆地址 收起阅读 »

    2017 本土开源大盘点!年度编程语言和开源项目 TOP 榜

    摘要:年度最受国内开发者喜爱的编程语言?最受欢迎的开源项目?为你呈现最详细的国内开发者画像。 在即将过去的2017年,作为程序员的你过得怎么样呢?在这一年中你是否参与过开源项目的创建或运营?今天为大家带来最详细的本土开源现状报告,为你展现国内开发...
    继续阅读 »
    摘要:年度最受国内开发者喜爱的编程语言?最受欢迎的开源项目?为你呈现最详细的国内开发者画像。


    盘点.jpg


    在即将过去的2017年,作为程序员的你过得怎么样呢?在这一年中你是否参与过开源项目的创建或运营?今天为大家带来最详细的本土开源现状报告,为你展现国内开发者喜爱的编程语言、最受欢迎的国内开源项目等丰富信息。

    1. 国内开发者的地域分布



    开发者.png



    2. 国内开发者的性别分布
     



    性别.png



    3. 2017 年新增756 K 开源项目——Star 数增长排行
     



    star.png



    4. 2017 年新增756 K 开源项目——Fork 数增长排行



    fork.png



    5. 2017 年,更多人开始参与开源项目​



    参与.png



    6. 国内开源项目的语言占比是什么情况?​



    预言.png



    7. 国内开源项目的功能分类是什么情况?​



    功能.png



    8.国内开发者最喜爱的编程语言​



    语言.png



    9. 2017 年增速最快的语言



    增长.png



    本文数据由 码云 2017年度数据报告 提供,码云是开源中国旗下的云端协作开发平台,上线四年来用户突破200万,托管项目近300万,已成为经过大规模部署的成熟产品。码云的社区版的私有库完全免费,企业版(5人以下免费)则提供代码托管、项目管理、文档协作的一体化开发服务,竭诚为广大本土开发者服务。 收起阅读 »

    webIM发送的文件,能接收到,但是收到的消息中没有文件大小

    在webIM中发送附件消息,接收到的消息中文件大小为0
    在webIM中发送附件消息,接收到的消息中文件大小为0

    环信客户互动云v5.31已上线,新增随机质检功能,使质检结果更加公平、公正

    本次更新的主要内容为:​优化智能机器人功能,支持为机器人知识规则设置应答策略,使机器人能够根据不同的渠道关联、时间计划发送不同的答案。新增随机质检功能,采取系统对质检样本随机抽样的方法管理质检流程,避免质检作弊,使质检结果更加公平、公正。 环信客户互动云v...
    继续阅读 »
    本次更新的主要内容为:​优化智能机器人功能,支持为机器人知识规则设置应答策略,使机器人能够根据不同的渠道关联、时间计划发送不同的答案。新增随机质检功能,采取系统对质检样本随机抽样的方法管理质检流程,避免质检作弊,使质检结果更加公平、公正。


    环信客户互动云v5.31_产品更新说明

    发布日期:2017-12-25

    客服模式


    客服变更在线状态需要审批

    新增“客服变更在线状态需要审批”功能。当管理员打开“客服变更状态需要审批”开关时,普通客服将在线状态从“空闲”切换为忙碌、离开、隐身状态需要管理员审批;当管理员关闭此开关时,普通客服可以随意切换在线状态。

    管理员切换在线状态不受此开关影响。

    客服变更在线状态:

    开关打开时,客服将在线状态从“空闲”切换为忙碌、离开、隐身状态时,将收到弹窗提醒,需填写状态变更理由,并提交至管理员审批。


    01.png



    管理员审批状态变更:

    管理员收到客服的状态变更请求后,将在消息中心收到系统消息提醒。可以进入“管理员模式 > 审批管理”页面,审批该请求。

    审批通过后,系统自动完成客服的状态切换。同时,系统发送系统消息提醒客服,可在消息中心页面查看。


    02.png




    “客服状态变更需要审批”开关:

    需要对客服的在线状态变更进行审批时,管理员可以进入“管理员模式 > 设置 > 系统开关”页面,打开“客服状态变更需要审批”开关。


    03.png




    待接入会话列表显示坐席名

    待接入页面的会话列表增加一列:坐席名,用于显示会话指定的客服名称。

    注:若会话未指定客服,则只显示技能组名称,不显示客服名称。

    管理员模式

    机器人知识规则

    机器人知识规则新增分类管理、日志管理、和应答策略。日志管理支持管理员查看知识规则相关的日志。分类管理支持将知识规则添加至不同的分类,方便查询和管理知识规则。应答策略为知识规则中的答案的发送策略,支持设置根据不同的渠道关联、时间计划发送不同的答案。

    分类管理

    在机器人的知识规则页面,可以添加、编辑分类,并修改知识规则所在的分类。

    查看分类:

    在分类列表中选择任意分类,查看该分类中的知识规则。

    管理分类:

    在分类列表下方,点击“管理分类”按钮,添加、编辑分类。

    修改分类:

    选中一条或多条知识规则,在弹出窗口中点击“修改分类”按钮,修改选中的知识规则所属的分类。


    04.png



    日志管理

    日志管理支持管理员查看知识规则相关的日志,可以按照时间范围查询,以及导出。

    在知识规则页面,点击“添加规则”右侧的下拉按钮,点击“日志管理”按钮,查看日志。

    应答策略

    支持为知识规则中的答案单独设置发送策略,在添加、编辑知识规则时,均可以设置发送策略。策略包含策略名称、条件(渠道关联和时间计划)、执行(知识规则的答案),当满足策略中的条件时,发送对应的答案给客户。

    当知识规则中有多个答案时,如果不设置发送策略,则默认随机发送其中一个答案。

    编辑知识规则:

    在知识规则页面,点击知识规则右侧的“编辑”按钮,可以编辑知识规则。包括添加/编辑问题、添加/编辑答案。编辑完成后,点击“确认”。


    05.png




    添加发送策略:

    在添加、编辑知识规则页面,点击“添加策略”按钮。
    填写策略名称,选择一个渠道关联(不选择时,策略不生效),选择一个时间计划(不选择时,默认为全部时间),选择知识规则的一条答案,并保存。

    设置好发送策略后,当客户的消息匹配知识规则的问题,并满足渠道关联和时间计划这两个条件时,发送策略中设置的答案给客户。


    05.png




    随机质检

    新增随机质检功能。采取系统对质检样本随机抽样的方法管理质检流程,避免质检作弊,使质检结果更加公平、公正。

    随机质检功能为标准版/旗舰版增值服务,如需开通,请提供租户ID并联系环信商务经理。

    查看质检任务

    质检任务由管理员/质检员创建,质检任务包含系统基于筛选条件随机抽取的客户会话。

    在随机质检页面,管理员可以查看所有质检任务;质检员可以查看所有/自己创建的质检任务。


    07.png




    设置质检权限

    进入“管理员模式 > 设置 > 权限管理”页面,设置质检员查看质检任务的数据权限。
    • 租户:支持查看系统内全部质检任务
    • 客服:只能查看自己创建的质检任务

    08.png

    创建质检任务管理员/质检员每次可以为自己创建一条质检任务。质检任务分为质检中、已完成两个状态。当有质检中的任务时,需要先完成该质检任务,才可以创建新的质检任务。在随机质检页面,点击右上角“创建质检任务”按钮,设置相关质检条件,再次点击“创建质检任务”按钮。系统根据质检条件随机抽取对应数量的会话,并创建质检任务。
    • 任务名称:质检任务的名称。
    • 时间范围:必须为一个月内,如11月1日至11月30日。
    • 会话抽样范围:包含会话来源渠道、技能组、关联、会话标签、参与客服。
    • 会话质检条件:包含首次响应时长、会话时长、平均/单次响应时长、访客/坐席消息数、满意度评价。
    • 抽样数量:必须为1至300之间的数字。

    09.png

    完成质检任务管理员/质检员可以随时对质检中的任务里的会话进行质检评分,完成所有会话的质检评分后,即可完成该质检任务。在随机质检页面,点击自己创建的质检任务,可以查看任务详情以及会话列表。点击任意会话,可查看会话详情,并对会话进行质检评分。

    10.png

    “24小时排队趋势”显示排队的会话详情在“统计查询 > 排队统计”页面,点击“24小时排队趋势”中的柱状图,可以查看对应的排队的会话详情。注:从该版本开始,“排队统计”仅支持筛选最近3个月及当月的统计数据。例如,本月为12月,可筛选9月至12月的数据。Android SDK访客端当前版本:V1.1.3新功能/优化:
    • 支持消息预知功能(增值服务)
    • 支持FCM推送

     
    环信客户互动云更新日志http://docs.easemob.com/cs/releasenote/5.31 

    环信客户互动云登陆地址http://kefu.easemob.com/ 收起阅读 »

    一款基于环信开发的APP-超级家委会

    【产品简介】   “超级家委会”是一款基于环信开发的集活动、聊天、项目管理为一体的APP。 如需项目合作,请联系:石锋 18616870207(微信同号) 【功能介绍】 消息 享畅聊,与微信一样好用的通讯功能,免费即时沟通,但在这里...
    继续阅读 »
    【产品简介】
     
    “超级家委会”是一款基于环信开发的集活动、聊天、项目管理为一体的APP。

    如需项目合作,请联系:石锋 18616870207(微信同号)

    【功能介绍】

    消息

    享畅聊,与微信一样好用的通讯功能,免费即时沟通,但在这里只聊工作,去除娱乐化,支持单聊、普通群聊、活动群聊、部门群聊、团队群聊。


    聊天.jpg



    【单聊】和好友实时沟通
    【群聊】邀请特定的人组建群聊
    【活动群聊】对活动感兴趣的人员可以一起畅所欲言,也可和主办方沟通疑问,活动群聊 页面可一键直达活动
    【部门/团队群聊】提供团队内部聊天功能,随时与同伴和团队沟通和讨论工作,让沟通更顺畅
    【系统消息】实时接收重要信息的通知,如审核申请,报名信息,关注,评论等,并及时做出反馈;
     
    实现功能

    • 好友/群内的文字、表情、图片、位置、收藏、名片发送和接收

    • 查看群信息

    • 快速查找

    • 新建群 

    • 系统消息推送

    • 删除会话

    • 获取未读消息

    • @功能正在实现中...




    聊天功能介绍1.jpg


    聊天功能介绍2.jpg


    聊天功能介绍3.jpg


    聊天功能介绍4.jpg


    聊天功能介绍6.jpg

     
    项目

    提供了包括项目管理、行政办公、教育培训、营销工具等基本应用和丰富的插件功能,通过精心设计打造出体验出色的应用,且每个项目可直达相应群聊,让你和团队成员随时随地参与协作。


    聊天功能介绍8.jpg

     
    【快速立项】明确工作目标并创建项目 ,邀请团队成员加入项目一起来开展工作;
    【任务管理】新建任务,指派执行者和添加参与者,添加并更新相关任务信息;
    【工作安排】创建「日程」来安排会议、 记录外出和预约重点活动等等工作,也随时了解其他项目成员的工作安排;
    【进展同步】通过任务列表随时关注项目进展的更新,进入项目查看具体的进展信息;
    【插件丰富】丰富的移动办公应用接入,任务、签到、审批、笔记、文件、费用、班级、学生老师、课程、消课等,应有尽有。
     
    活动
    您可以参加活动聚会,学习一项新技能,带上孩子发现未知…更特别的是,您还可以自己组织发起活动,呼朋唤友一起玩耍。


    聊天功能介绍9.jpg

     
    【主办方】提供从发布活动、活动推广、报名管理到财务管理等全流程完整解决方案
    【参与者】找到感兴趣的活动,志同道合的朋友,长假远行,家长课堂,周边游,培训课 程,聚会沙龙,冬夏令营,手工课程,多种活动,丰富娱乐生活
    【报名信息 随时查收】支持自定义设置报名表单,报名信息详情可查,支持导出
    【精彩活动 乐享群聊】一起进入活动群聊,可以和其他参与者一起互动
     
    联系人

    • 搜索好友/群 

    • 添加好友/群 

    • 发送/接受好友申请




    聊天功能介绍10.jpg




    聊天功能介绍5.jpg


     
    【技术实现】

    本项目是基于官方最新SDK Hyphenate 进行开发 页面效果基于EasyUI调整 和微信页面效果看齐。
     
    主要实现功能 
     
    1、注册 登录 自动登录 重连 退出登录
    EMError *error = [[EMClient sharedClient] registerWithUsername:@"8001" password:@"111111"];
    if (error==nil) {
    NSLog(@"注册成功");
    }
    EMError *error = [[EMClient sharedClient] loginWithUsername:@"8001" password:@"111111"];
    if (!error) {
    NSLog(@"登录成功");
    }

    自动登录:即首次登录成功后,不需要再次调用登录方法,在下次 APP 启动时,SDK 会自动为您登录。并且如果您自动登录失败,也可以读取到之前的会话信息。

    SDK 中自动登录属性默认是关闭的,需要您在登录成功后设置,以便您在下次 APP 启动时不需要再次调用环信登录,并且能在没有网的情况下得到会话列表。

    EMError *error = [[EMClient sharedClient] loginWithUsername:@"8001" password:@"111111"];
    if (!error)
    {
    [[EMClient sharedClient].options setIsAutoLogin:YES];
    }

    2. 消息
    IM 交互实体,在 SDK 中对应的类型是 EMMessage。EMMessage 由 EMMessageBody 组成。

    构造文字消息 构造图片消息 构造图片消息 构造位置消息 构造语音消息 构造视频消息 构造扩展消息(系统消息)
    构造文字消息
    EMTextMessageBody *body = [[EMTextMessageBody alloc] initWithText:@"要发送的消息"];
    NSString *from = [[EMClient sharedClient] currentUsername];

    //生成Message
    EMMessage *message = [[EMMessage alloc] initWithConversationID:@"6001" from:from to:@"6001" body:body ext:messageExt];
    message.chatType = EMChatTypeChat;// 设置为单聊消息

    构造图片消息
    EMImageMessageBody *body = [[EMImageMessageBody alloc] initWithData:data displayName:@"image.png"];
    // body.compressionRatio = 1.0f; 1.0表示发送原图不压缩。默认值是0.6,压缩的倍数是0.6倍
    NSString *from = [[EMClient sharedClient] currentUsername];

    //生成Message
    EMMessage *message = [[EMMessage alloc] initWithConversationID:@"6001" from:from to:@"6001" body:body ext:messageExt];
    message.chatType = EMChatTypeChat;// 设置为单聊消息

    构造扩展消息
    // 以单聊消息举例
    EMTextMessageBody *body = [[EMTextMessageBody alloc] initWithText:@"要发送的消息"];
    NSString *from = [[EMClient sharedClient] currentUsername];

    //生成Message
    EMMessage *message = [[EMMessage alloc] initWithConversationID:@"6001" from:from to:@"6001" body:body ext:messageExt];
    message.chatType = EMChatTypeChat;// 设置为单聊消息
    //message.chatType = EMChatTypeGroupChat;// 设置为群聊消息
    //message.chatType = EMChatTypeChatRoom;// 设置为聊天室消息
    message.ext = @{@"key":@"value"}; // 扩展消息部分

    扩展消息主要用要系统消息里

    3. 会话

    操作聊天消息 EMMessage 的容器,在 SDK 中对应的类型是 EMConversation。

    新建 、 获取一个会话 (根据 conversationId 创建一个 conversation)
    [[EMClient sharedClient].chatManager getConversation:@"8001" type:EMConversationTypeChat createIfNotExist:YES];
    //EMConversationTypeChat 单聊会话
    //EMConversationTypeGroupChat 群聊会话
    //EMConversationTypeChatRoom 聊天室会话


    getConversation:创建与8001的会话
    type:会话类型
    createIfNotExist:不存在是否创建
     
    4. 删除会话

    删除单个会话
    [[EMClient sharedClient].chatManager deleteConversation:@"8001" isDeleteMessages:YES completion:^(NSString *aConversationId, EMError *aError){
    //code
    }];


    deleteConversation: 删除与8001的会话
    deleteMessages: 删除会话中的消息
     
    5. 获取会话列表

    获取所有会话 (内存中有则从内存中取,否则从db中取)
    NSArray *conversations = [[EMClient sharedClient].chatManager getAllConversations];

    6. 获取会话未读消息 
    [EMConversation unreadMessagesCount]; 显示红点       

    7. 聊天 (发送消息 接收消息 解析普通消息 解析扩展消息)

    登录成功之后才能进行聊天操作。发消息时,单聊和群聊调用的是统一接口,区别只是要设置下 message.chatType。
    /*!
    @property
    @brief 发送消息
    @discussion
    异步方法
    */
    - (void)sendMessage:(EMMessage *)aMessage
    progress:(void (^)(int progress))aProgressBlock
    completion:(void (^)(EMMessage *message, EMError *error))aCompletionBlock;

    //调用:[[EMClient sharedClient].chatManager sendMessage:message progress:nil completion:^(EMMessage *aMessage, EMError *aError) {}];

    //消息回调:EMChatManagerDelegate

    //移除消息回调
    [[EMClient sharedClient].chatManager removeDelegate:self];

    //注册消息回调
    [[EMClient sharedClient].chatManager addDelegate:self delegateQueue:nil];

    /*!
    @method
    @brief 接收到一条及以上非cmd消息
    */
    - (void)messagesDidReceive:(NSArray *)aMessages;

    解析普通消息
    // 收到消息的回调,带有附件类型的消息可以用 SDK 提供的下载附件方法下载(后面会讲到)
    - (void)messagesDidReceive:(NSArray *)aMessages {
    for (EMMessage *message in aMessages) {
    EMMessageBody *msgBody = message.body;
    switch (msgBody.type) {
    case EMMessageBodyTypeText:
    {
    // 收到的文字消息
    EMTextMessageBody *textBody = (EMTextMessageBody *)msgBody;
    NSString *txt = textBody.text;
    NSLog(@"收到的文字是 txt -- %@",txt);
    }
    break;
    case EMMessageBodyTypeImage:

    解析扩展消息
    - (void)cmdMessagesDidReceive:(NSArray *)aCmdMessages {
    for (EMMessage *message in aCmdMessages) {
    // cmd消息中的扩展属性
    NSDictionary *ext = message.ext;
    NSLog(@"cmd消息中的扩展属性是 -- %@",ext)
    }
    }
    // 收到消息回调
    - (void)messagesDidReceive:(NSArray *)aMessages {
    for (EMMessage *message in aMessages) {
    // 消息中的扩展属性
    NSDictionary *ext = message.ext;
    NSLog(@"消息中的扩展属性是 -- %@",ext);
    }
    }



     8. 好友管理 (本项目的好友管理是自己服务器上管理的)

    9. 好友申请

    发送好友申请
     EMError *error = [[EMClient sharedClient].contactManager addContact:@"6001" message:@"我想加您为好友"];
    if (!error) {
    NSLog(@"添加成功");
    }

    监听加好友请求 (项目里面 发送系统消息)
    /*!
    * 用户A发送加用户B为好友的申请,用户B会收到这个回调
    *
    * @param aUsername 用户名
    * @param aMessage 附属信息
    */
    - (void)friendRequestDidReceiveFromUser:(NSString *)aUsername
    message:(NSString *)aMessage;

    同意加好友申请 
    EMError *error = [[EMClient sharedClient].contactManager acceptInvitationForUsername:@"8001"];
    if (!error) {
    NSLog(@"发送同意成功");
    }

    10 群组管理
    创建群 后台创建的
    加入群组 添人进群 退出群组 解散群组 修改群描述 获取群组成员列表

    11 群组管理
    1.后台配置推送证书
    2.代码配置 APNs 使用的推送证书。
    3.代码注册离线推送

    【联系我们】
    如需项目合作,请联系
    石锋 18616870207 (微信同号) 收起阅读 »

    【有奖互动】如果把各编程语言比作各国语言,哪种语言对应中文?

    Quora 问答社区最近有人提了一个有趣的问题: 如果把编程语言比作各国语言,会分别对应什么? 目前点赞最高的回复如下: Python 是英语,迅速成为许多人说的全球“默认”语言。多数情况下,相对容易学习,表达,语法简洁。 C 是中文,非常紧凑和快速,...
    继续阅读 »
    Quora 问答社区最近有人提了一个有趣的问题:


    如果把编程语言比作各国语言,会分别对应什么?


    目前点赞最高的回复如下:


    Python 是英语,迅速成为许多人说的全球“默认”语言。多数情况下,相对容易学习,表达,语法简洁。

    C 是中文,非常紧凑和快速,但很难学习和写作,容易“倒”在起点。
     
    Lisp 是世界语(Esperanto)。只有一小群忠实的人说,并且一直告诉别人这有多棒。

    Ruby 会是法语。这是一种可爱的语言,充满了例外和怪癖。一度受到精英的喜爱。现在虽然快要过时了,但仍然性感。

    Ruby 会是法语,这是一种可爱的语言,充满了例外和怪癖。一度受到精英的喜爱。现在虽然快要过时了,但仍然性感。

    Assembly 是拉丁语。现在几乎没什么人说了,尽管它是大多数现代语言的基础。



    对此,中国小伙伴发表了他们的看法:


    @网友:PHP是中文,想白话文就白话文,想文言文就文言文。


    @网友:Java 是日语,入门很容易,精通很痛苦



    对此问题,你怎么看?欢迎评论参与探讨。评论前5名同学送出环信定制指尖陀螺一个。



    8201EEC6@7B48295(01-02-14-33-46).jpg


      收起阅读 »

    2017环信八大开源项目源码放出:两个已获融资,单个20W+关注

    “这是一家极具极客范的公司”,来过环信公司的每一位客户都这么评价到。有着“连接人与人,连接人与商业”愿景的环信正在一步一步慢慢用技术和场景改变每个人的生活和工作。生有涯而知无涯,业有涯而心无涯,路漫漫其修远,“开源”当上下而求索!  环信成立于2013年,公司...
    继续阅读 »
    “这是一家极具极客范的公司”,来过环信公司的每一位客户都这么评价到。有着“连接人与人,连接人与商业”愿景的环信正在一步一步慢慢用技术和场景改变每个人的生活和工作。生有涯而知无涯,业有涯而心无涯,路漫漫其修远,“开源”当上下而求索!

     环信成立于2013年,公司数位联合创始人都是技术大牛出身,在Symbian,Nokia,Red Hat等知名IT公司都是开源项目的重度参与者,环信的基因便因此打上了“开源”的烙印,在2014年即创办了国内首个即时通讯云开源社区“IMGeek社区”,经过了3年多的深耕,2017年度在环信IMGeek社区涌现了数十个优质开源项目,其中两个开源项目目前已经拿到融资,更有一个项目获得了超过20W+的关注度,环信正通过即时通讯云PaaS平台和客服PaaS平台一步步完善自己的商业梦想,也在一步步的通过开源回馈自己的开发者和产业链上下游的小伙伴。



    微信图片_20171229142123.jpg


    环信公司前台一角


    2017年是企业服务丰收的一年,感谢伙伴们对环信一直以来的支持。18年伊始之际,我们总结了17年间基于环信开发者的八大开源项目,希望能对大家有所帮助。
     
    小马直播间-开源直播APP
     
    推荐理由:基于环信开发的开源直播应用,IMGeek收藏数量3W8,对于有直播需求的小伙伴们值得参考,教你从头开始开发一个移动端直播APP。
     
    互联网直播平台催生了一批批网红大咖,作为程序猿,我们绝不甘于委身幕后做搬砖工,我们一定要闪亮登场!!!做一个属于我们程序猿的IOS版直播平台~~



    小马直播间.png


    应用截图


     实现功能:
    • 创建推流和拉流加速
    • 集成环信IM的聊天室功能
    • 集成UCloud的ULive直播云SDK
    • 在聊天室里集成推流(录制)和拉流(播放)功能
     相关链接:http://www.imgeek.org/article/825307904​ Dolores-基于环信IM开发完整的企业通讯解决方案 推荐理由:对企业而言,初选OA办公系统是为了满足需求,解决当下问题,由于OA办公系统的在公司运作流程中扮演的重要性,安全与隐私等问题急需未雨绸缪,“可定制”、“可私有化部署”的OA办公系统成为了更多企业的首选。 公司想自己开发一套IM系统应该从哪里开始呢? 企业通讯录怎么保持同步呢? 企业通讯录的权限管理应该怎么做?


    89e5f4394605d1a330e558e46e7cc6b0.jpg

    Dolores海报

    相关链接:http://www.imgeek.org/article/825308805  凡信-百尺竿头更进一步,2017凡信携直播、红包而来 推荐理由:自凡信开源以来,更新了3个大版本,从基本聊天功能,到越来越多的开发者创建分支,累计收到了10W+的收藏,帮助了很多开发者进行开发即时通讯应用。 已完成功能:
    • 单聊
    • 群聊
    • 朋友圈
    • 钱包
    • 直播
    • 红包


    凡信.png

    项目运行效果图

    相关链接:http://www.imgeek.org/article/825307930   Slack聊天机器人-环信客服版 推荐理由:17年作为人工智能元年,聊天机器人(Chatbot)在各大行业的应用方兴未艾,国外包括Facebook Messenger、Slack等均引入了聊天机器人,所以毋庸置疑,聊天机器人将成为我们未来生活中不可或缺的一部分,IMGEEK开源社区热心开发者&朝阳区群众“晨星桑”一言不合他就花2小时写了一个Slack的聊天机器人,您可以通过这个项目在Slack上对接客户服务,并且通过一些定制开发能够看见Slack用户的基本信息(比如:昵称、电话、团队名称等),并且可以二维码支付。相关功能:
    • 事件订阅初始化
    • 处理消息的事件
    • 把消息发送到移动客服
    • 处理OAuth回调
    • 把收到的消息发给Slack 用户
     相关链接:http://www.imgeek.org/article/825308711​  泛聊-厉害了我的开发者,基于环信的开源高仿QQ项目 推荐理由:聊天功能做到了极致,展示即时通讯基本功能的实现,包括注册登录,退出登录,联系人列表,添加好友,删除好友,收发消息,消息提醒等功能。


    泛聊.png

    项目运行效果图

    实现功能:
    •  环信SDK的集成与使用
    •  MVP模式的运用
    •  ORM数据库的集成与使用
    •  模块化思想的运用
     相关链接:http://www.imgeek.org/article/825308542​  Baby-开源私密社交应用 推荐理由:私密社交APP,情侣、死党必备的一对一专属聊天应用。作者开发初期下载了市面上所有IM的demo源码跑一遍,从功能、集成难易和消息稳定几个方面对比,最终还是选择了环信。使用环信EaseUi集成,基本上一天就能集成完毕。


    baby.png

    项目运行效果图

    实现功能:
    • 加入Tinker 热修复
    • 加入部分注释
    • 增加长按删除功能
    • 优化Rxbus订阅加载数据
    • 外国友人优化的一些细节
    • 增加了评论功能
    • 优化了相册加载
    • 把登陆注册事件换了个Zip操作符更符合流的思想
    相关链接:http://www.imgeek.org/article/825308850 LITE-IM-环信webim的网页即时聊天推荐理由:你见过最酷炫的WEB聊天项目,LITE-IM不仅能够担当客户服务来使用,还可以用作你网站粘连客户、活跃社区的媒介,提升用户的使用率。


    lym.png

    项目运行效果图

    实现功能:
    • 好友/群内的文字、表情、图片、文件 在线/离线消息发送和接收。
    • 查看群员列表。 
    • 面板内快速查找。 
    • 面板右键自定义事件 
    • 修改签名 
    • 自定义上传背景皮肤 
    • 搜索好友/群 
    • 添加好友/群 
    • 新建群 
    • 消息盒子展示
     相关链接:http://www.imgeek.org/article/825308961  VMTVCall-使用环信3.xSDK 在 TV 端集成音视频通话功能 推荐理由:移动互联网方便了我们生活,移动端随时随地沟通成为了常态,今天我们推荐的是一个可以安装在电视机上进行视频通话的开源项目。


    电视机.jpg

    项目运行效果图

     实现功能
    • 项目首次启动自动注册登录
    • 拨号盘实现
    • 历史通话记录 TODO
    • 视频通话功能(因为电视不需要语音通话以及最小化)
    • 视频通话的录制
    • 通话截图

     相关链接:http://www.imgeek.org/article/825308732
     
    end.
     
    生有涯而知无涯,业有涯而心无涯,路漫漫其修远,当上下而求索。未来,我们还将开源更多。
     
    环信IMGeek社区开源项目地址http://www.imgeek.org/code/
      收起阅读 »

    环信客户互动云v5.30已上线,支持客服主动发起视频邀请

    本次更新的主要内容为:支持客服将个人常用语提交至公共常用语,方便整个客服团队共同维护公共常用语,提升常用语维护相关的工作效率。优化实时视频功能,支持客服在会话过程中主动发起视频邀请。客服模式 支持客服提交公共常用语 支持客服将个人常用语提交至公共常...
    继续阅读 »
    本次更新的主要内容为:支持客服将个人常用语提交至公共常用语,方便整个客服团队共同维护公共常用语,提升常用语维护相关的工作效率。优化实时视频功能,支持客服在会话过程中主动发起视频邀请。
    客服模式

    支持客服提交公共常用语

    支持客服将个人常用语提交至公共常用语,方便整个客服团队共同维护公共常用语,提升常用语维护相关的工作效率。

    提交个人常用语至公共常用语:
    1. 进入常用语页面,将鼠标放在个人常用语上,点击提交按钮 [提交] 。
    2. 选择公共常用语分类,确认常用语的内容(可修改),填写提交理由,并点击“提交”按钮。


    客服将个人常用语提交至公共常用语后,需要管理员在“管理员模式 > 审批管理”页面,对客服的常用语申请进行审批,审批通过后,该常用语展示在公共常用语中。 
     

    001.png



    支持客服主动发起视频邀请

    与app、网页端客户聊天过程中,客服可以主动发起视频邀请,待客户接受视频邀请后,即可开始视频聊天。

    客服发起视频聊天:
    1. 在会话页面,选择客户的会话,点击输入框上方的视频按钮 [发起视频邀请] 。
    2. 等待客户接受视频邀请。


    在视频通话过程中,客服还可以邀请一名客服同事加入视频,或者与客户共享桌面。

    注:“实时视频”功能为增值服务,如需开通,请提供租户ID并联系环信商务经理。 
     

    002.png



    管理员模式

    新增审批管理功能


    新增审批管理功能。支持客服提交申请,由管理员统一进行审批,增强管理员对客服团队的管理能力,加强团队协作。

    目前,支持客服提交个人常用语至公共常用语,由管理员进行审批。审批通过后,自动将该常用语添加至公共常用语,供所有客服使用。后续会增加更多审批功能,敬请期待。

    审批管理页面分类显示所有待审批、已通过、已拒绝的审批申请,点击任意待审批的申请,可查看申请详情,并对其进行审批(通过或拒绝)。

    客服提交申请后,管理员会在消息中心收到提醒;管理员完成审批后,客服可在消息中心查看审批结果。 
     

    003png.png



    新增调度规则设置

    会话调度指会话路由至技能组后,系统将会话分配给技能组内客服的过程。调度规则包含技能组优先级属性等,由管理员设置,用于控制会话调度时分配会话的先后顺序。

    新增调度规则设置,支持管理员设置5个技能组的优先级顺序,对优先级高的技能组的会话进行优先调度。并且,在调度规则中设置了优先级的技能组比剩余的其他技能组优先级高。例如:客服A同时属于技能组B和技能组C,技能组B的优先级高,则优先将技能组B的会话分配给客服A。

    设置并启用调度规则:
    1. 进入“管理员模式 > 设置 > 会话分配规则 > 调度规则”页签。
    2. 在“客服接待能力分配”栏目下,点击“设置技能组优先级”按钮。
    3. 添加优先等级,并设置技能组对应的优先顺序。
    4. 打开“客服接待能力分配”开关。


    注:“调度规则”功能为标准版/旗舰版增值服务,如需开通,请提供租户ID并联系环信商务经理。 
     

    004.png



    支持根据客户信息路由会话

    路由规则新增“客户信息指定”,目前支持按照客户标签将客户会话分配至不同的客服、技能组或机器人。该路由规则可用于优先接待VIP客户,为不同类别的客户提供有区别的客户服务等。

    设置客户信息指定规则:
    1. 进入“管理员模式 > 设置 > 会话分配规则 > 路由规则”页签。
    2. 在“客户信息指定”栏目下,点击“添加客户信息设置”按钮。
    3. 填写规则名称,启用规则,添加客户标签,选择规则指定的机器人、技能组、客服,并保存。
    4. 重复步骤2、步骤3,创建多条客户信息指定规则。
    5. 点击客户信息指定规则右侧的排序按钮,调整规则之间的优先级顺序。



    005.png



    注:暂时不支持在客户信息指定规则中,将客户会话指定给未分组的客服。

    设置完客户信息指定规则后,可在路由规则页签上下拖动各路由规则,调整各路由规则之间的优先级顺序。排在上方的路由规则的优先级高。 
     
    支持筛选所有会话标签的叶节点

    会话标签包含根节点、枝节点、叶节点三种类型。在“统计查询 > 工作量”页面选择根节点或枝节点时,会话数分布(按会话标签维度)显示该节点下所有叶节点标签的会话的总数。

    新增“全选叶标签”按钮 [全选叶节点] ,支持一键选择所有会话标签的叶节点;以及“取消全选叶标签”按钮 [取消全选叶节点] ,支持一键取消选择所有会话标签的叶节点。方便企业查询叶节点标签对应的会话数分布情况。

    在工作量页面,选择“筛选排序 > 指定标签”,点击“全选叶标签”按钮,选择所有会话的叶节点,并确认。会话数分布(按会话标签维度)将显示各个叶节点标签的会话数;导出报表中也包含各个叶节点标签的会话数。 
     

    006.png



    优化首次响应时长的计算方式

    优化系统的首次响应时长的计算方式,更改为客服首条消息时间减去第一位客服的接起时间,不再包含机器人接待的会话时长。 
     
    Android客服工作台

    当前版本:V3.6

    新功能/优化:

    • 新增告警记录功能

    • 支持显示表单消息



    iOS客服工作台

    当前版本:V3.0.1

    新功能/优化:

    • 支持显示表单消息



    iOS SDK访客端

    当前版本:V1.1.6

    新功能/优化:

    • 支持显示客服输入状态(增值服务)



    Web插件访客端

    当前版本:V47.21.1

    新功能/优化:

    • 支持三方视频通话(增值服务)

    • 支持自定义满意度评价提示语



    环信移动客服更新日志http://docs.easemob.com/cs/releasenote/5.30

    环信移动客服登陆地址http://kefu.easemob.com/  
    收起阅读 »

    基于layim+环信webim的网页即时聊天 LITE-IM

    什么是LITE-IM?LITE-IM是一款基于 环信webim3.X 和 layim 开发而成的WEB即时通讯,并且完全的免费和开源 。LITE-IM使用 拥有高并发,长连接永不掉线的即时通讯的行业领头羊环信作为通信介质,再搭配上拥有丰富前端交互的layim,...
    继续阅读 »
    什么是LITE-IM?
    LITE-IM是一款基于 环信webim3.X 和 layim 开发而成的WEB即时通讯,并且完全的免费和开源 。LITE-IM使用 拥有高并发,长连接永不掉线的即时通讯的行业领头羊环信作为通信介质,再搭配上拥有丰富前端交互的layim,让你能够快速的搭建一个现代化的、高度稳定的web即时通讯。

    LITE-IM不仅能够担当客户服务来使用,还可以用作你网站粘连客户、活跃社区的媒介,提升用户的使用率。当然LITE-IM还有许多有待完善之处。

     
    目前已完成的功能有:


    1. 好友/群内的文字、表情、图片、文件 在线/离线消息发送和接收。
    2. 查看群员列表。 
    3. 面板内快速查找。 
    4. 面板右键自定义事件 
    5. 修改签名 
    6. 自定义上传背景皮肤 
    7. 搜索好友/群 
    8. 添加好友/群 
    9. 新建群 
    10. 消息盒子展示
    11. 查看/修改个人信息
    12. 实时获取好友在线状态
    13. 挤下线提醒
    14. 增删改 好友/好友分组
    15. 群管理(增删管理员/修改群名片/单个群员禁言解除禁言/踢人)



    部分截图如下
     
    好友聊天界面




    自定义上传皮肤




    群组




    自定义右键




    消息盒子




    好友




     
     LITE-IM在线体验地址:test.guoshanchina.com

    体验帐号:


    用户名:911088 密码:123456

    用户名:1570855 密码:123456

    用户名:1570845 密码:123456

    用户名:911058 密码:123456

    用户名:910992 密码:123456

    用户名:911067 密码:123456

    用户名:911100 密码:123456

    用户名:911085 密码:123456



     github源码:https://github.com/shmilylbelva/webim 收起阅读 »

    环信CEO刘俊彦直播回放:环信如何重新定义客服软件?-环信公开课第18期

    11月15日,环信联合选型宝举行了环信公开课第18期,环信CEO刘俊彦讲述客户互动云的全媒体化、智能化和营销化如何在企业落地?如何重新定义客服软件?包括APP、网站、微博、微信...客户正通过各种渠道与您发生连接,作为全媒体接入的核心渠道,环信即时通讯云演化的...
    继续阅读 »
    11月15日,环信联合选型宝举行了环信公开课第18期,环信CEO刘俊彦讲述客户互动云的全媒体化、智能化和营销化如何在企业落地?如何重新定义客服软件?包括APP、网站、微博、微信...客户正通过各种渠道与您发生连接,作为全媒体接入的核心渠道,环信即时通讯云演化的六大场景正在如何变革引领着一个崭新的即时通讯云+时代。



    微信图片_20171121165556.jpg


    不能当网红的CEO做不好产品现场


    刘俊彦讲到在直播公开课中讲到:“随着全媒体客服的完善以及AI的逐渐成熟, 环信预测“云通讯+服务云+智能营销”将构成从用户服务到用户营销的完整闭环。因此,2017年 ,环信整合旗下即时通信云、移动客服、智能客服机器人和主动营销产品线,推出环信CEC (Customer Engagement Cloud),向企业提供从客户互动渠道,到客户服务,到精准客户营销 的客户互动全流程解决方案。”同时,刘俊彦认为:“全媒体客服已经从传统客户服务形态的终点转化成了SaaS客户互动形态的起点,随着2017年环信CEC(客户互动云)的发布,人工智能驱动的互动中心(AI-driven Engagement Center)即将来临,整个SaaS客服行业将被重构和赋能。”



    TIM截图20171121171217.png


    环信客户互动云:从用户互动渠道到客户服务再到用户营销的完整闭环


     
    环信CEC(客户互动云)矩阵:
    1. 全渠道客户互动:全面支持网页、微信、微博、APP/IM、工单和呼叫中心等主流客户互动渠道。其中,环信业界领先的IM长连接技术支持千万级并发,保证消息必达,助力企业打造极致的移动端客户服务体验。所有渠道支持双向互动,如主动回呼,多渠道统一推送,基于用户行为的自动营销等,真正将服务通道与营销通道融合,实现客户中心从成本中心向利润中心的升级。
    2. 视频客服:实时双向视频客服,支持Android、iOS、Pad及主流PC和手机浏览器等多平台接入,低延迟,1080P高清,支持客户端和服务器端录制,可控灵活。
    3. 全渠道客服:环信移动客服作为业内广泛使用的客户中心系统,囊括多项行业主流大奖,拥有多项国际PCT专利和国内专利,深受客户好评。环信移动客服产品成熟可靠,功能完善,全面覆盖了全渠道接入管理,客户服务与客户互动管理,运营与运维管理,工单系统,现场管理,智能报表,质检等客户中心功能。
    4. 客户声音:环信客户声音是基于人工智能和大数据挖掘的客户体验透析产品。对来自多个渠道的非结构化客服会话数据进行自然语言解析,主题聚类和情感度建模,挖掘和分析热点话题,发现服务运营问题,寻找畅销或问题产品,洞察销售机会。客户声音系统可以帮助企业识别和改善客户旅程的各个阶段。
    5. 智能客服机器人:环信智能客服机器人不仅在常见的单轮对话能力上表现优异,预装多种行业知识库,还可以快速开发多轮对话,支持人机协作以便在复杂场景下对人工客服提供全面AI辅助支持。同时,环信智能客服机器人的自动学习能力极大的降低了机器人知识库的维护成本。
    6. 精准营销及自动化营销:大数据和AI驱动的营销功能,如自动化消息模板和自动化规则管理及A/B测试,营销计划管理,基于用户行为轨迹、用户画像和用户会话内容的自动化消息和访客CTA(Call To Action)等。

     环信公开课第18期视频回放地址:https://v.qq.com/x/page/a05077ko5ly.html 
     
    环信客户互动云产品试用,公开课合作请添加公开课小助手


    扫码(1).gif


      收起阅读 »

    虾米音乐代码注释“穷逼VIP”,程序员出面致歉“今后我老老实实写代码,正正经经写注释”

    昨日晚间,虾米音乐Mac版客户端中关于VIP会员代码的注释的图片在网上疯传,原因是里面出现了所谓的“穷逼VIP(活动送的那种)”。 虾米音乐程序员在代码注释中赫然将虾米音乐搞活动赠送给用户的VIP称之为“穷逼VIP”。 为与同类型的应用(Q...
    继续阅读 »
    昨日晚间,虾米音乐Mac版客户端中关于VIP会员代码的注释的图片在网上疯传,原因是里面出现了所谓的“穷逼VIP(活动送的那种)”。

    v2-7a509497cec7f78b44923441a95f6b23_hd.jpg


    虾米音乐程序员在代码注释中赫然将虾米音乐搞活动赠送给用户的VIP称之为“穷逼VIP”。

    为与同类型的应用(QQ音乐与网易云音乐等)竞争,虾米音乐经常会推出各种领取VIP的优惠活动,用户领取后享受到的VIP内容和包月付费VIP用户没什么不同,但有时间上的限制。
     
    但无论是领取了还是没领取,感谢或者觉得无所谓的虾米音乐用户们不会想到,虾米音乐程序员会简单粗暴地将这类VIP称之为“穷逼VIP”,英文名是Beggar vip(乞丐VIP)。

    11月19日晚间,媒体报道虾米音乐第一时间做了复盘,替换了有问题的应用包,彻底去除了不当用语,同时修复文件混淆失效的BUG。

    11月20日,虾米音乐前程序员“八座”在知乎回应致歉,并指出注释代码是自己个人行为,本意并没有任何歧视意思,只是想吐槽一些活动规则的复杂,但在吐槽中,丧失了对客户的敬畏。

    111-3.png


    对于这次虾米音乐程序员的做法,心大的网友调侃表示,幸好没有使用虾米音乐,躲过了“穷逼VIP”。一些网友试图从程序员的角度出发,认为这只是程序员沉闷工作下的一种寻找乐趣的手段。愤怒的网友则指责,虾米以及背后的阿里巴巴优越感很强嘛,有种就别搞任何营销活动。 收起阅读 »

    基于环信webIM提供的Demo改造

    本效果图是基于webIM的demo版本修改,主要实现了以下功能 1.实现自动登录,到聊天窗口     解决办法:是在页面自己写参数调用 Demo.conn.open(options) 方法 2.实现最近联系人列表渲染(因为我们没有好友概念,只要跟谁最近聊天了...
    继续阅读 »
    本效果图是基于webIM的demo版本修改,主要实现了以下功能
    1.实现自动登录,到聊天窗口
        解决办法:是在页面自己写参数调用 Demo.conn.open(options) 方法

    2.实现最近联系人列表渲染(因为我们没有好友概念,只要跟谁最近聊天了,就算是好友了)
        解决办法:在页面通过ajax调用后台restFul地址获取联系人,然后循环调用 Demo.conn.onTextMessage(msg)方法,将消息内容为空,在msg里面设置标识为初始化加载,这样就会出现在列表里面

    3.实现联系人:头像、昵称 渲染
       解决办法:很简单就是在刚才上面的方法体里面调用完之后,你的列表就会出现好友列表,这时候你只需要根据环信用户id,直接基于jq替换即可

    4.实现点击联系人 渲染历史聊天记录(在发送消息的时候将聊天记录保存在我们自己的数据库)
       解决办法:在你获取最近联系人这个方法时已经返回了聊天记录列表,你需要在(Demo.chatRecord.环信用户id.messages)这个数组里面把你的消息放在这个数据里面去,然后当用户点击联系人列表时你jq写一个(webim-contact-item)这个样式的click触发方法,在这个方法中你需要写把聊天记录从(Demo.chatRecord.环信用户id.messages) 循环取出来,包装成HTML然后在  $("#wrapper"+Demo.selected).append() 这些HTML即可

    5.实现保存聊天
        解决办法:就是在 sendTxt里面调用我们的ajax方法保存
    大致就这么多,个人不建议基于官方的webIM的Demo 去修改,这次踩过这个坑了

    欢迎各位 集成的朋友来访,目前我这个算是解决,等过段时间我还是自己写一个吧,不打算基于他们的Demo去改造
    在此欢迎去我的博客:   www.kaven.cn 收起阅读 »

    【环信开发者福利】江西省科技馆面向环信开发者公开采购智能硬件!

    随着国家中国智造2025战略实施,智能产品、智能制造与互联网的深度融合、跨业创新,成为家电消费电子行业发展趋势。日前,环信与江西省科技馆达成合作协议,环信作为国内领先的企业级服务商,江西省科技馆将从环信20万+开发者中采购一批代表科技先进力的智能硬件,入驻江西...
    继续阅读 »
    随着国家中国智造2025战略实施,智能产品、智能制造与互联网的深度融合、跨业创新,成为家电消费电子行业发展趋势。日前,环信与江西省科技馆达成合作协议,环信作为国内领先的企业级服务商,江西省科技馆将从环信20万+开发者中采购一批代表科技先进力的智能硬件,入驻江西省科技馆展览,一经录取,将按照市价采购!
    市场上科技馆现状:

    现状一:常设展览和展品缺乏创新,简单模仿、相互雷同的现象普遍,展览主题和设计理念不 明确, 展品更新速度慢,大多数科技馆未能达到《标准》中“年更新率不低于5%”的要求。
    现状二:短期专题展览(临时展览)开发力度弱,即使偶尔举办专题展览,也主要靠从外单位引进,缺乏自主创新
    现状三:重展示、轻教育,重形式,轻体验。

    科技馆分类:

    科技馆分类.png


    江西 南北京


    江西南中国.png


    关于江西省科技馆:


    江西省科学技术馆(简称江西省科技馆)坐落于赣江之滨,毗邻江南文化名楼滕王阁,是中共江西省委、江西省人民政府决策投资兴建的重点建设工程。全馆占地面积4.6万平方米,建筑面积1.6万平方米。场馆建筑突出“超弦生万物,对撞生新态”的设计理念,主体建筑组合在螺旋运动的轨道上,造型新颖、独特、动势飘逸,“游于无穷,寓于无境”。场馆由主展厅、儿童科学乐园、宇宙剧场、国际会议厅、青云厅及世纪广场等组成,是以展览教育为中心,融科技培训、科学报告、科学实验和科技影视为一体的大型科普教育基地,也是展示我省科技、文化、经济、社会发展的和重要窗口。


    采购要求:
    • 基于环信平台开发,具有先进科技代表性。
    采购产品分类:
    • 智能手机
    • 智能家居
    • 可穿戴设备
    • 智能交通
    • 智能医疗
    • 智能玩具
    • 智能机器人

     
    欢迎环信用户踊跃参加,请将产品介绍发送至邮箱mkt@easemob.com,一经录取,将按照市价采购!
    标题《江西省科技馆智能硬件采购-XX》
    邮件内容:产品分类+产品/公司介绍,使用环信开发功能
    附件:产品PPT/照片

    环信优秀智能硬件案例
    小墨机器人

    微信图片_20171108182543.jpg


      收起阅读 »

    环信即时通讯云 V3.3.6已发布,新增多人实时音视频功能

    经过了两个月的内测,环信多人实时音频功能发布,是基于国内领先的环信云通讯技术平台搭建,致力于为用户提供高品质、稳定可靠、随时随地高效沟通的多人视频通讯解决方案。环信多人实时音视频可满足各种日常沟通的应用场景,针对不同场景,只需下载更新到V3.3.6版本SDK即...
    继续阅读 »
    经过了两个月的内测,环信多人实时音频功能发布,是基于国内领先的环信云通讯技术平台搭建,致力于为用户提供高品质、稳定可靠、随时随地高效沟通的多人视频通讯解决方案。环信多人实时音视频可满足各种日常沟通的应用场景,针对不同场景,只需下载更新到V3.3.6版本SDK即刻拥有,赶紧下载体验吧!

     Android SDK 版本 V3.3.6 2017-11-03
     
    新功能:
    • 新增API请查看3.3.6 API变化
    • 使用外接音频输入源进行音视频通话时,设置音频源
    • 提供是否自己处理附件的上传和下载设置项
    • 提供是否自动下载附件类消息附件设置项(缩略图,语音文件)
    • 多人音视频会议功能,详细参考集成文档 多人音视频会议
    优化:
    • 更改 easeui 中 EaseChatRow 实现方式,保证发送消息的回调顺利执行
    修复: 
    • 修复使用华为推送时注册华为推送 token 空指针问题
    • 修复更新聊天室公告时,其他聊天室成员收到回调崩溃问题
    • 修复保存和插入消息时间戳和本地已存在消息的时间戳不同时,导致内存出现重复消息的问题
     iOS SDK ​版本 V3.3.6 2017-11-03 新功能:
    • 多人音视频功能
    • 增加“是否自己实现消息附件上传下载”设置项 [EMOptions isAutoTransferMessageAttachments]
    • 增加“是否自动下载图片和视频缩略图及语音消息”设置项 [EMOptions isAutoDownloadThumbnail]
    功能修复:
    • 1v1实时音视频,接通后,一方静音后,另一方再触发静音操作不起作用;
    • 使用SDK下载接口,如果本地已经有同名文件,新的文件名会在原有文件名后边追加数字;
    • 断网情况下,自动登录无法获取会话
    • 使用SDK上传接口,进度回调第一次会返回100%

     
    版本历史:Android SDK更新日志  ios SDK更新日志
    下载地址:SDK下载​
    收起阅读 »

    【开源项目】Kitura-HuanxinSDK:Swift+Kitura集成环信的开源解决方案

    前言 Swift是Apple为iOS和Mac OS X开发的编程语言,但不止于此。随着Swift的开源,它开始被Apple官方支持在Ubuntu等GNU/Linux平台上运行,并且理论上可以移植到其他任何操作系统上(只要有人为它开发合适的工具链)。进一步的...
    继续阅读 »
    前言

    • Swift是Apple为iOS和Mac OS X开发的编程语言,但不止于此。随着Swift的开源,它开始被Apple官方支持在Ubuntu等GNU/Linux平台上运行,并且理论上可以移植到其他任何操作系统上(只要有人为它开发合适的工具链)。进一步的,它不再限制于用来开发本地/移动App,有人为它开发了Web框架和Web服务器。

    • Kitura是上述Web框架和Web服务器之一。它是由IBM公司开发的,使用Swift语言。不过IBM只是基于Swift.org提供的编译工具链和Swift Package Manager(SPM)开发了一个第三方库,并没有单独开发一个完整的IDE,因此在Linux平台上的开发过程会略显不便。

    • 环信是一个即时通信(IM)服务提供商。如果你的App需要好友私聊、群聊、客服等功能,你就需要一个IM服务。环信已经提供了iOS、Android等平台的SDK,用于客户端直接与环信服务器的交互。除此之外,注册、注销用户等高权限操作不适合从客户端直接发起请求,而应该让客户端先和[你的服务器]交互,再由[你的服务器]去和[环信服务器]交互。由此,服务器端集成是需要开发者自己去完成的。

    • Kitura-HuanxinSDK是用于Swift+Kitura+环信解决方案的服务器端集成。它是对环信服务器端API的简单封装。开发者可以专注于App功能的开发,不用再去逐个研究环信服务器API的细节。




    89e3bc67-c0be-48ac-a10f-de5cdb2e9c31.png


    使用方法
    1.Swift Package Manager
    import PackageDescription
    let package = Package(
    name: "YourProjectName",
    dependencies: [
    .Package(url: "https://github.com/andy1247008998/Kitura-HuanxinSDK.git", majorVersion: 0)
    ]
    )
    2.环信账户信息
    import KituraHuanxinSDK

    //拼接环信URL基址
    let huanxinDomain = "https://a1.easemob.com"
    let huanxinOrgName = "1111222233334444"
    let huanxinAppName = "yourappname"
    let huanxinBaseURL = "\(huanxinDomain)/\(huanxinOrgName)/\(huanxinAppName)"
    //准备好ID和Secret
    let huanxinClientID = "AAAABBBBCCCCDDDDEEEEFFFF"
    let huanxinClientSecret = "AAAABBBBCCCCDDDDEEEEFFFFGGGG"
    3.获取一个Huanxin实例
    let huanxin = Huanxin(domain:huanxinDomain, orgName:huanxinOrgName, appName:huanxinAppName, client_id:huanxinClientID, client_secret:huanxinClientSecret)
    4.注册用户
    huanxin.registerUser(username:username, password:password, nickname:nickname, withToken:true){ registerUserResponse, error in
    print("registerUserResponse is \(registerUserResponse)")
    }

    项目github源码地址:https://github.com/andy1247008998/Kitura-HuanxinSDK 收起阅读 »

    集成环信之后集成动态库上传AppStore怎么破?

    集成环信之后集成动态库上传AppStore怎么破?
    集成环信之后集成动态库上传AppStore怎么破?

    环信即时通讯云V3.3.5 SDK已发布,安全传输升级,支持FCM推送

    10月23日,环信即时通讯云发布了新版本SDK,此次更新的V3.3.5版本增加了传输安全性,AndroidSDK支持FCM推送,FCM是谷歌推出的最新的Android系统级别的消息推送服务(用来替换GCM)。 iOS SDK 更新日志 版本 V3.3.5 2...
    继续阅读 »
    10月23日,环信即时通讯云发布了新版本SDK,此次更新的V3.3.5版本增加了传输安全性,AndroidSDK支持FCM推送,FCM是谷歌推出的最新的Android系统级别的消息推送服务(用来替换GCM)。

    iOS SDK 更新日志

    版本 V3.3.5 2017-10-23

    新功能:

    • 增加了传输安全性
    • 增加广告插件,可以收集用户信息
    优化:
    • 私有部署设置dns的接口
    • 优化私有部署重连逻辑
    • 限制用户名长度为255
    • 需要服务器开通的功能接口返回SERVICE_NOT_ENABLED(505)
    • 添加i386库解决模拟器profile时的编译问题
    功能修复:
    • 修复4G与wifi切换时偶然出现发送消息失败的bug
     Android SDK 更新日志版本 V3.3.5 2017-10-23新功能:
    • 增加了传输安全性;
    • 支持FCM推送;
    优化:
    • 私有部署设置dns的接口;
    • 优化私有部署重连逻辑;
    • 限制用户名长度为255;
    • 需要服务器开通的功能接口返回SERVICE_NOT_ENABLED(505);
    修复:
    • 修复4G与wifi切换时偶然出现发送消息失败的bug;
    • 修复VIVO手机JobService crash问题;

     
    版本历史:Android SDK更新日志  ios SDK更新日志
    下载地址:SDK下载​ 收起阅读 »

    武汉开发者小伙伴看过来,环信讲师线下分享APP如何与AI共舞

    活动介绍 你是否有开发时间短却任务重的窘境? 开发过程中需要做的功能点复杂却又很难下手? 如今人工智能行业备受关注 你了解多少? 作为一名技术人员 如何将自己的工作技能与其相结合并不断学习 本次活动我们的演讲嘉宾将对此类问题 为大家答疑解惑 传授...
    继续阅读 »
    活动介绍

    你是否有开发时间短却任务重的窘境?

    开发过程中需要做的功能点复杂却又很难下手?

    如今人工智能行业备受关注 你了解多少?

    作为一名技术人员

    如何将自己的工作技能与其相结合并不断学习

    本次活动我们的演讲嘉宾将对此类问题

    为大家答疑解惑 传授经验

    "超速玩转"安卓开发

    本次活动邀请到

    名人朋友圈的安卓负责人霍启圣

    环信讲师AIhub人工智能CEO章国良

    现场为各位开发者

    分享干货

    答疑解惑

    只要你来就没有遗憾
     
    活动概况

    2017年10月21日(周六) 14:00-17:00

    001.jpg


    活动流程

    002.jpg


    嘉宾&讲题介绍


    003.jpg




    004.jpg


    活动福利​


    005.jpg


    前20名签到的猿猿

    可获得“安卓巴士6周年”徽章

    现场还有一二三等抽奖环节

    和贴心的伴手礼SendCloud环保手袋一个

    不容错过哦

    活动须知

    请认真填写报名资料用于审核使用

    为保证当天活动质量拒绝空降

    两点活动准时开始请提前签到

    现场签到凭报名成功二维码

    路线指南



    006.jpg


    别愣着 快扫码报名啊

    收起阅读 »

    Vue-cli整合环信WebIM

    [b]非常抱歉以前写的文章给各位带来了麻烦,今天突然看到imgeek推送的邮件才记起这个[/b]其实很简单的,以前是我弄复杂了   1. 在index.html页面引入js文件 <script type='text/javascript' src='s...
    继续阅读 »
    [b]非常抱歉以前写的文章给各位带来了麻烦,今天突然看到imgeek推送的邮件才记起这个[/b]
    其实很简单的,以前是我弄复杂了
     
    1. 在index.html页面引入js文件

    <script type='text/javascript' src='static/webim/webim.config.js'></script>

    <script type='text/javascript' src='static/webim/strophe-1.2.8.js'></script>


    2. 在main.js 赋值 ,  如下图

    QQ图片20180206105624.png


     
    代码如下
    let WebIM = require('easemob-websdk')
    Vue.prototype.$webim = WebIM
    WebIM.config = {
    xmppURL: 'im-api.easemob.com',
    apiURL: (location.protocol === 'https:' ? 'https:' : 'http:') + '//a1.easemob.com',
    appkey: '你的环信appkey',
    https: false,
    isMultiLoginSessions: true,
    isAutoLogin: true,
    isWindowSDK: false,
    isSandBox: false,
    isDebug: false,
    autoReconnectNumMax: 2,
    autoReconnectInterval: 2,
    isWebRTC: (/Firefox/.test(navigator.userAgent) || /WebKit/.test(navigator.userAgent)) && /^https\:$/.test(window.location.protocol),
    heartBeatWait: 4500,
    isHttpDNS: false,
    msgStatus: true,
    delivery: true,
    read: true,
    saveLocal: false,
    encrypt: {
    type: 'none'
    }
    }
    // 创建连接
    const conn = new WebIM.connection({
    isMultiLoginSessions: WebIM.config.isMultiLoginSessions,
    https: typeof WebIM.config.https === 'boolean' ? WebIM.config.https : location.protocol === 'https:',
    url: WebIM.config.xmppURL,
    heartBeatWait: WebIM.config.heartBeatWait,
    autoReconnectNumMax: WebIM.config.autoReconnectNumMax,
    autoReconnectInterval: WebIM.config.autoReconnectInterval,
    apiUrl: WebIM.config.apiURL,
    isAutoLogin: true
    })
    // 添加成功的回调
    conn.listen({
    onOpened: function (message) {
    console.log('连接打开=>', message)
    },
    onClosed: function (message) {
    console.log('连接关闭=>', message)
    },
    onTextMessage: function (message) {
    console.log('收到文本信息message=>', message)
    }
    })
    const options = {
    apiUrl: WebIM.config.apiURL,
    user: null,
    pwd: null,
    appKey: WebIM.config.appkey
    }
    Vue.prototype.$imconn = conn
    Vue.prototype.$imoption = options



    3.  在页面调用全局的赋值即可
     
    this.$imconn.open(this.$imoption)
     
     
     
     
     
     
     
     
     
     
     
      收起阅读 »

    环信公开课17期回放:环信视频客服的六大应用场景和十大黑科技

     9月28日,环信公开课邀请了来自环信音视频老司机符宁老师,为大家解读了实时音视频开发中的坑,以及关于环信视频客服的六大应用场景和十大黑科技。   公开课17期视频回放​     环信音视频老司机符宁 关于环信视频客服: 作为客户服务领域的领...
    继续阅读 »
     9月28日,环信公开课邀请了来自环信音视频老司机符宁老师,为大家解读了实时音视频开发中的坑,以及关于环信视频客服的六大应用场景和十大黑科技。
     
    公开课17期视频回放​
     


     



    微信图片_20171011170210.jpg


    环信音视频老司机符宁


    关于环信视频客服:


    作为客户服务领域的领军企业,环信以Web和APP为载体发布了视频客服产品。基于业界领先的实时音视频通讯技术,为访客及企业客服人员提供双向的实时视频交互能力,支持视频画面展示与回放,语音及文字同步在线畅通交流,画质清晰,低延迟。目前已经大量应用于保险定损、在线教育、商品导购、医疗问诊、远程业务办理、VIP服务等六大场景,消费者可以足不出户便可享受真人面对面VIP服务,帮助企业在提高客户体验的同时极大降低成本和提升客服效率。


    参加环信公开课还有礼品相送:

     恭喜 赵X银,houge,王珏 三位同学同学成为本期公开课幸运观众,获得环信定制版T恤。

    第17期公开课_05.png



    环信视频客服产品试用请点击http://a.eqxiu.com/s/ob8agMUU 收起阅读 »

    环信WebIM2.0版本发布,专为移动端H5打造的通讯SDK

    十年磨一剑 砺得梅花香 -献给在一线开发程序员们  经过了长达三个月的封闭开发,环信WebIM2.0版本在9月22号发布,此次更新的WebIM2.0也被称为“H5版本”,一套Demo同时支持PC和H5,自适应微信/QQ和各种手机浏览器。   W...
    继续阅读 »
    十年磨一剑 砺得梅花香
    -献给在一线开发程序员们
     
    经过了长达三个月的封闭开发,环信WebIM2.0版本在9月22号发布,此次更新的WebIM2.0也被称为“H5版本”,一套Demo同时支持PC和H5,自适应微信/QQ和各种手机浏览器。
     
    WebIM 2.0 (webim-h5) 在1.x的基础上, 主要做了以下更新:
    • 响应式布局, 一套Demo同时支持PC和H5,自适应不同终端屏幕尺寸
    • 完全基于Reac + Redux的单向数据流
    • 引入ant-design组件库,方便开发者后续开发
    • 适配微信/QQ和各种手机浏览器

     
    webim-h5在线体验地址:https://webim-h5.easemob.com
     
    安装
    1.初始化安装
    在/demo下执行 npm i
    2.如果需要同时编辑sdk cd sdk && npm link && cd .. && npm link easemob-websdk
    3.如果需要同时编辑webrtc cd webrtc && npm link && cd .. && npm link easemob-webrtc
    4.运行demo
    cd demo && npm start (requires node@>=6)
    http://localhost:3001
    cd demo && HTTPS=true npm start (webrtc supports HTTPS only)
    https://localhost:3001
    5.发布demo cd demo && npm run build /demo/build 目录下的就是可以运行和部署的版本
    执行npm start时如果出现
    ```
    ./src/config/WebIM.js
    Module not found: Can't resolve 'easemob-websdk/dist/strophe-1.2.8-g.js' in '<YourRootDir>/demo/src/config'
    ```
    FIX: 没有执行 npm link easemob-websdk
     
    3. 执行npm start时如果出现
    > node scripts/start.js

    /Users/wenke/www/web-im/demo/scripts/start.js:23
    const {
    ^

    SyntaxError: Unexpected token {
    at exports.runInThisContext (vm.js:53:16)
    at Module._compile (module.js:373:25)
    at Object.Module._extensions..js (module.js:416:10)
    at Module.load (module.js:343:32)
    at Function.Module._load (module.js:300:12)
    at Function.Module.runMain (module.js:441:10)
    at startup (node.js:139:18)
    at node.js:974:3
    FIX: 请检查node版本是否是v6.0+
     
    4. Redux State 的数据结构如下:
    {
    // ---------------------------------
    // 响应式断点
    // ---------------------------------
    //xs: "480px"
    //sm: "768px"
    //md: "992px"
    //lg: "1200px"
    //xl: "1600px"
    breakpoint: {
    xs: false,
    sm: false,
    md: false,
    lg: false,
    xl: false
    },

    // ---------------------------------
    // UI相关
    // ---------------------------------
    common: {
    fetching: false,
    isGetGroupAlready: true,
    isGetChatRoomAlready: false,
    showGroupRequestModal: false //群主管理加入群消息
    },

    // ---------------------------------
    // 用户登录信息
    // ---------------------------------
    login: {
    username: 'sunylt',
    password: null,
    token: "YWMtZ0m-opwTEeeS-e0Ko59rsU1-S6DcShHjkNXh_7qs2vV",
    fetching: false,
    error: false
    isLogin: true,
    },

    // ---------------------------------
    // 多语言
    // ---------------------------------
    i18n: {

    // 已配置语言
    translations: {
    cn: {},
    us: {},
    },

    // 当前语言
    locale: "cn"
    },


    // 注册信息,没进行注册操作为{}
    register: {
    username: "123abcdbb",
    password: "123",
    fetching: false,
    registerError: null
    },

    // 预留 暂无用
    contacts: {}

    // 预留 暂无用
    im: {}

    // ---------------------------------
    // 数据实体
    // ---------------------------------
    entities: {
    // 好友
    roster: {
    byName: {
    name: { subscription,jid, ask, name, groups }
    ...
    },
    names: ['lwz2' ...],
    // 好友列表在此,因为好友列表来源于roster,息息相关
    friends: ,
    },
    // 群组
    group: {
    loadingFailed: <Boolean>,
    isLoading: <Booleadn>,
    rightSiderOffset: <Number>, //控制右侧群组管理面板
    byId: {
    groupId: {groupid, groupname},
    ....
    },
    names: [groupName_#-#_groupId, ....]
    },
    // 聊天室
    chatroom: {
    byId: {
    chatId: {chatId, name, owner, affiliations_count}
    ...
    },
    names: [chatName_#-#_chatId, ....]
    },
    // 陌生人
    stranger: {
    },

    // 群组成员信息
    groupMember: {
    groupId: {
    muted: {byName: {}},//群主可见,禁言列表
    byName: {
    name: {name: <String: name>, affiliation: 'member'}
    },
    names: ,
    admins: , //群管理员可见
    },
    ...
    }

    // 订阅通知
    subscribe: {
    byFrom: {}
    },

    // 黑名单列表
    blacklist: {
    byName: {}
    name:
    },

    // 消息
    message: {

    // 所有消息
    byId: {
    mid: {"type":"chat|groupchat|chatroom|stranger|error", "chatId": <String: chatId>},
    ...
    }

    // 单聊消息列表
    chat: {
    chatId: [
    {message},
    ...
    ]
    },

    // 群组消息列表
    groupChat: {
    chatId: [
    {message},
    ...
    ]
    },

    // 聊天室消息列表
    chatroom: {
    chatId: [
    {message},
    ...
    ]
    },

    // 陌生人消息列表
    stranger: {
    chatId: [
    {message},
    ...
    ]
    },

    // 预留 暂无用
    extra: {}

    // 未读消息记录
    unread: {
    // 好友
    chat: {
    chatId: <Number: unreadNum>,
    ...
    },
    // 群组
    groupchat: {
    chatId: <Number: unreadNum>,
    ...
    },
    // 聊天室
    chatroom: {
    chatId: <Number: unreadNum>,
    ...
    },
    // 陌生人
    stranger: {
    chatId: <Number: unreadNum>,
    ...
    }
    }

    // 自己发的消息mid跟本地id对照
    byMid: {
    messageId: {id: <String: localId>},
    ...
    },
    },

    // 加入群申请
    groupRequest: {
    byGid: {}
    }
    }
    }
    收起阅读 »

    环信助力九个秘书打造全国中小企业人才共享管家

       九个秘书APP(北京九秘科技有限公司)是国内领先的人才共享企业级SAAS服务互联网科技公司。公司创立于2015年,总部坐落于北京市中关村,河南九秘科技有限公司为其全资子公司,主要承担城市联营、市场孵化、区域经营等职能,地址位于郑州市中原区泰隆大厦。公司于...
    继续阅读 »
       九个秘书APP(北京九秘科技有限公司)是国内领先的人才共享企业级SAAS服务互联网科技公司。公司创立于2015年,总部坐落于北京市中关村,河南九秘科技有限公司为其全资子公司,主要承担城市联营、市场孵化、区域经营等职能,地址位于郑州市中原区泰隆大厦。公司于2016年10月26日获得了天明集团1000万元战略投资。

    TIM截图20170915151952.png


     
    九个秘书-中小企业人才共享管家
    遇到的挑战:
       九个秘书APP技术总监卢志涛表示:“九个秘书调研期间技术团队对于聊天功能评估了很久,当时考虑是自己编写还是使用第三方的sdk。对于初创公司来讲,IM这一块由自己来开发不仅时间不允许,技术沉淀也不够,即便开发出来稳定性也很难保证。所以当时在相对于比较成熟的几款IM服务中,通过服务质量,稳定性,开发团队以及和业务需求进行匹配,技术部门进行了评断,在第三方sdk中精心筛选出了环信。”
     
    环信解决方案:
      九个秘书作为环信早期的用户,从15年就开始接入环信聊天功能,环信聊天能实现消息及时到达以及可扩展性良好,在接入时也遇到些问题,环信成立单独的项目技术支持的QQ群,来帮助九个秘书解决集成中遇到的问题,环信在帮助用户集成sdk时真的很尽心尽力,大大缩短了项目开发周期,为能用上这样贴心的服务点赞!”
     
    价值体现:
      九个秘书使用环信IM实现客户之间的相互聊天功能,以及群功能,同时为了丰富聊天的互动性,以及环信的可扩展性,在聊天中实现了收发红包,以及打赏用户的功能。”
     
    关于九个秘书:
       公司积极响应国家主席习近平提出的“推动互联网和实体经济深度融合”网络强国战略,立足全球共享经济大风口,探索城乡人才结构失衡、资源配置不均的互联网解决方案,成功研发人才共享企业级SAAS服务平台——九个秘书,让海量专业人才汇聚平台,通过远程协同智能办公作业模式,实现人才与企业按需雇佣,主要将一二线城市高端人才共享给四五线城市中小企业,解决一二线城市人才集中、四五线城市人才匮乏的差异化需求。

       公司旗下拥有“九个秘书、九秘微城、九秘商城”三大产品线,将围绕“人才共享、本地生活、特产直营”三大核心业务,为生产、地产、酒店、餐饮、健康、旅游、休闲、娱乐、教育等行业,提供“品牌策划、商务直播、产品分销、品牌推广、品牌投资”一站式项目众包解决方案,深度解决企业“产品销售难、品牌推广难、渠道建设难、人才招聘难、成本降低难”五大难题,为企业的日常经营和可持续发展打通上下游云服务生态链,全方位推动中国实体经济互联网+转型! 收起阅读 »

    环信客户互动云荣获2017年度全媒体智能客服最佳解决方案奖

       2017年9月10日下午14时,由中国电子商会呼叫中心与客户关系管理专业委员会主办、北京易训天下咨询服务有限公司承办、贵阳市服务外包及呼叫中心产业发展办公室联合承办的“2017年度(第十三届)中国最佳呼叫中心颁奖暨行业大会”表彰活动之“2017...
    继续阅读 »
     

    1505358834689837.jpg


     2017年9月10日下午14时,由中国电子商会呼叫中心与客户关系管理专业委员会主办、北京易训天下咨询服务有限公司承办、贵阳市服务外包及呼叫中心产业发展办公室联合承办的“2017年度(第十三届)中国最佳呼叫中心颁奖暨行业大会”表彰活动之“2017年度中国呼叫中心产业交流峰会”在人民大会堂成功举办。本次峰会有来自电信、银行、保险、证券、汽车、旅游、电子商务、服务外包等不同行业的近400位中高层领导参会。本次大会重点表彰了国内呼叫中心产业优秀单位及个人,树立卓越行业形象,深度激活呼叫中心产业内上下游优秀企业与品牌,促使产业内部积极跨界融合及健康持续发展。



    1505358843352383.jpg


     峰会期间,北京易掌云峰科技有限公司(环信)CEO刘俊彦先生以一家软件供应商的视角,将看到的行业最新趋势、最新变革与在座嘉宾共享,并提出三个趋势:从语音和电话座席向全媒体座席的升级趋势、服务座席向我们的营销座席升级的趋势、人工座席向人工智能座席的趋势。演讲围绕以上三个趋势提出问题、解答问题,引发现场在座嘉宾阵阵掌声。

    155584684647789014.jpg


    环信荣获2017年度全媒体智能客服最佳解决方案奖
     
    环信客户互动云(Customer Engagement Cloud)支持全媒体接入,包括网页在线客服、社交媒体客服(微博、微信)、APP内置客服和呼叫中心等多种渠道均可一键接入。基于环信业界领先的IM长连接技术保证消息必达,并通过智能客服机器人技术降低人工客服工作量。同时,基于人工智能和大数据挖掘的客户旅程透析产品"环信客户声音"能够帮助企业优化运营,提高跨渠道客服体验。环信CEC为企业提供了从客户互动渠道、到客户服务、再到精准营销的一体化全流程客户互动解决方案。 收起阅读 »

    【环信征文】祭天时不同程序员的不同杀法

    这两天一张“还杀了一个程序员祭天”的截图火了,很多同行在纷纷谴责这位无良老板的同时把矛头也指向了我,已经遇到好几个人问我:“作为程序员和一个杀程序员祭天的老板做校友是什么体验?” 在此我想帮我的校友洗一次地,你们注意这个更新文案中只说“还杀了一个程序员祭天”...
    继续阅读 »
    这两天一张“还杀了一个程序员祭天”的截图火了,很多同行在纷纷谴责这位无良老板的同时把矛头也指向了我,已经遇到好几个人问我:“作为程序员和一个杀程序员祭天的老板做校友是什么体验?”

    在此我想帮我的校友洗一次地,你们注意这个更新文案中只说“还杀了一个程序员祭天”没说是怎么杀的,为啥你们就脑补出砍头、活埋、六马分尸(女程序员可能是五马分尸)这些血腥的杀法了呢?为啥不觉得是老板奖励了程序员一辈子无福消受的酒肉烟钱把程序员的造化瞬间耗尽了呢?

    喝酒醉死

    诗云:李白当年水底眠,惟留诗篇万口传。码农入坑捞大饼,只给别人做笑谈。

    程序员健康的第一大杀手是酒。

    杭州有位程序员,虽然技术牛B,酒瘾也大。他荣升CTO之际恰逢母校80周年校庆,在收到了“欢迎杰出校友出席庆典”的邀请函后,坐上了开往合肥的动车,不料当动车开到当涂的时候,肝癌、酒精肝、胃穿孔同时发病,程序员被酒杀死了。

    程序员死后,开启了上帝视角。他看到了祖师爷李白——我且问你,诗人怎么成了程序员的祖师爷?原来李白在醉酒之后,于采石矶低头看见水中月亮的倒影,便纵身一跃,跳进了一万里扬子大江捞月亮,从此李白变成了当涂县太白镇的衣冠冢,虽然尸骨无存,万幸香火不断。然后程序员又看见了建国后,上海美术电影制片厂拍了一个一群Monkey跳进小河里捞月亮的动画片。最后程序员看见了当代,老板在臭水坑里画了一个大饼,在项目上线后,用酒把一群Code Monkey灌醉,然后让Code Monkey争先恐后入坑捞大饼的故事。李白就这样成了Code Monkey的祖师爷。

    有诗为证:天上月亮只一个,举杯对影便成三。醉生梦死伤身体,诗人一醉变诗仙。

    程序员明白了喝酒有害健康的道理之后就复活了,从此粉碎了老板想让他喝酒醉死祭天的阴谋。

    吃肉撑死

    诗云:反式脂肪胆固醇,催肥激素更无伦。工作午餐多吃素,青山怕葬黑发人。

    程序员健康的第二大杀手是肉。

    北京有位程序员,虽然技术牛B,烟瘾也大。他荣升CTO之际恰逢母校80周年校庆,在收到了“欢迎杰出校友出席庆典”的邀请函后,坐上了开往合肥的动车,不料当动车开到淮南的时候,高血压、脂肪肝、直肠癌同时发病,程序员被肉杀死了。

    程序员死后,开启了上帝视角。他看到赵王中了秦国的反间计,逼走了廉颇,因此长平之战赵国惨败。于是“赵王思复得廉颇,廉颇亦思复用于赵”,赵王派使者来到魏国问廉颇“尚能饭否?”廉颇非常高兴,一顿饭吃了十斤肉,披甲上马,不料因为肉吃得太多坏了肚子,和使者谈话的时候连上三次厕所。于是赵王没有重新重用廉颇,廉颇又从魏国流亡到了楚国,最终在八公山逝世,赵国不久也就成了《阿房宫赋》里轻描淡写的一句“燕赵之收藏,……,输来其间”。

    有诗为证:廉颇在魏思邯郸,星落淮南八公山。吃肉太多伤身体,收藏不该来其间。

    程序员明白了吃肉有害健康的道理之后就复活了,从此粉碎了老板想让他吃肉撑死祭天的阴谋。

    抽烟呛死

    诗云:八旗守城似金汤,为何英军入镇江。皆因大烟未烧尽,留下祸根毒四方。

    程序员健康的第三大杀手是烟。

    厦门有位程序员,虽然技术牛B,烟瘾也大。他荣升CTO之际恰逢母校80周年校庆,在收到了“欢迎杰出校友出席庆典”的邀请函后,坐上了开往合肥的动车,不料当动车开到镇江的时候,肺癌、肺气肿、口腔癌同时发病,程序员被烟杀死了。

    程序员死后,开启了上帝视角。他看到古代镇江是长江门户,自古便是兵家必争之地。民族英雄郑成功三次从厦门誓师北伐,每一次都势如破竹,但无一例外在镇江败北。镇江之所以易守难攻,不完全依赖长江天险,更靠八旗子弟骁勇善战。不料后来在鸦片战争中镇江被英军轻而易举攻破,原来两广总督林则徐虎门销烟的时候有一箱子烟忘了销毁,流传到了镇江,八旗子弟吸烟之后“头痛头晕加恶心,眼花耳响不认人。脸色苍白出虚汗,浑身乱颤腿抽筋”,导致镇江“几无可以御敌之兵”。

    有诗为证:编程早知世事艰,码农为何气如山。吞云吐雾伤身体,总督岂能不销烟。

    程序员明白了吸烟有害健康的道理之后就复活了,从此粉碎了老板想让他抽烟呛死祭天的阴谋。

    拿钱砸死

    诗云:劝君莫要聚宝盆,富可敌国害死人。若是贪财能保命,周庄沈厅可不焚。

    程序员健康的第四大杀手是钱。

    上海有位程序员,虽然技术牛B,钱瘾也大。他荣升CTO之际恰逢母校80周年校庆,在收到了“欢迎杰出校友出席庆典”的邀请函后,坐上了开往合肥的动车,不料当动车开到苏州的时候,失眠、抑郁症、谋财害命同时发病,程序员被钱杀死了。

    程序员死后,开启了上帝视角。他看到古代周庄有个大财主沈万三,家里有个聚宝盆,全国的金银财宝都会流到他的手里,导致了朱元璋的嫉恨。朱元璋没收了沈万三的粮食,烧毁了沈万三的房子,给他一个金碗,还给他盖了一座金屋子。让他白天拿着金碗讨饭,夜里睡在金屋子里。因为没人设施捧金碗的乞丐,黄金的导热能力强比热容又小,沈万三死于饥寒交迫。

    有诗为证:但愿有头生白发,何忧无地觅青蚨。贪财无厌伤身体,拿命卖钱太糊涂。

    程序员明白了贪财有害健康的道理之后就复活了,从此粉碎了老板想让他拿钱砸死祭天的阴谋。 收起阅读 »

    【环信征文】集成环信,并实现消息免打扰

    现在大多数社交app都有消息免打扰功能,因为环信SDK主要是对即时通讯模块进行封装,因此如果要实现消息免打扰功能则需要开发者自己对此功能逻辑进行处理。 先解释一下,本人项目中对消息免打扰功能的定义:正常情况下,我们收到的每一条聊天消息都会收到小红点、声音、震动...
    继续阅读 »
    现在大多数社交app都有消息免打扰功能,因为环信SDK主要是对即时通讯模块进行封装,因此如果要实现消息免打扰功能则需要开发者自己对此功能逻辑进行处理。
    先解释一下,本人项目中对消息免打扰功能的定义:正常情况下,我们收到的每一条聊天消息都会收到小红点、声音、震动的提示。如果对某个好友设置消息免打扰功能,则只提示小红点,声音和震动则不再提示。

    下面简述结合环信SDK时,此功能的实现方法。
    环信将即时聊天的所有功能分为四大模块进行管理:
    //聊天模块:
    [EMClient sharedClient].chatManager
    //好友模块 :
    [EMClient sharedClient].contactManager
    //群组模块 :
    [EMClient sharedClient].groupManager
    //聊天室模块:
    [EMClient sharedClient].roomManager
    消息免打扰的功能借助聊天模块[EMClient sharedClient].chatManager的API就能实现。
    一般来说,在app内不管当前在哪个界面,只要收到消息都需要被判断是否需要免打扰,因此可以在appdelegate里写如下代码,app通常都有tabBarController,那么也可以让tabBarController来实现如下代码。
    首先让控制器tabBarController遵守代理EMChatManagerDelegate
    然后成为代理
    [[EMClient sharedClient].chatManager addDelegate:self delegateQueue:nil];

    EMChatManagerDelegate中有一个代理方法
    - (void)didReceiveMessages:(NSArray *)aMessages;
    实现此代理方法
    - (void)didReceiveMessages:(NSArray *)aMessages{
    [self setupUnreadMessageCount];
    EMMessage *message = aMessages[0];
    NSString *sendPerson = message.from;
    BHNavigatiomController *imNaviCV = self.viewControllers[1];
    BHConversationListController *converCV = imNaviCV.viewControllers[0];
    [converCV refreshDataSource];//只要收到消息就从服务器拿
    UIApplicationState state = [[UIApplication sharedApplication] applicationState];
    for (NSString *hx_name in AppEngine.IMDataCent.data_ExcuseFriendsData) {
    if ([hx_name isEqualToString:sendPerson]) {
    return;
    }
    }
    switch (state) {
    case UIApplicationStateActive:
    [self playSoundAndVibration];
    break;
    case UIApplicationStateInactive:
    [self playSoundAndVibration];
    break;
    case UIApplicationStateBackground:
    [self showNotificationWithMessage:message];
    break;
    default:
    break;
    }
    }
    方法中有一个aMessages参数。这个参数是一个消息组,每一个元素都是EMMessage类型的实例。
    因为通常都只有一条信息。因此只需要取出EMMessage *message = aMessages[0];
    EMMessage类中有许多属性,因此根据一条信息基本可以获取想知道的所有信息。这里我们只需要知道此消息的发送方即可,也就是发送方的环信idNSString *sendPerson = message.from;下面代码中有一AppEngine.IMDataCent.data_ExcuseFriendsData,这个数组里面装的都是已经被设置消息免打扰的好友的环信id,是请求自己服务器获得的,获取的代码最好在一打开app时,就及时获取到。然后根据对好友免打扰设置的操作,访问后台接口进行增删,并刷新数组与后端保持一致即可。
    通过[hx_name isEqualToString:sendPerson],遍历免打扰数组与当前消息的发送方环信id,就可以知道是否需要免打扰了。
     
    下面附上完整代码
    //
    // BHTabBarController.m
    // ShangHeYiYang
    //
    // Created by LiBohan on 2017/8/24.
    // Copyright © 2017年 xxxxx. All rights reserved.
    //


    //两次提示的默认间隔
    static const CGFloat kDefaultPlaySoundInterval = 3.0;
    static NSString *kMessageType = @"MessageType";
    static NSString *kConversationChatter = @"ConversationChatter";
    static NSString *kGroupName = @"GroupName";


    #import "BHTabBarController.h"
    #import <UserNotifications/UserNotifications.h>
    #import "BHConversationListController.h"

    @interface BHTabBarController ()<EMChatManagerDelegate,EMContactManagerDelegate>

    @property (strong, nonatomic) NSDate *lastPlaySoundDate;

    @end

    @implementation BHTabBarController

    - (void)viewDidLoad {
    [super viewDidLoad];

    [DemoCallManager sharedManager].mainController = self;

    [[EMClient sharedClient].chatManager addDelegate:self delegateQueue:nil];

    [[EMClient sharedClient].contactManager addDelegate:self delegateQueue:nil];

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(setupUnreadMessageCount) name:@"setupUnreadMessageCount" object:nil];

    }

    -(void)friendshipDidRemoveByUser:(NSString *)aUsername{

    // __weak __typeof(&*self)weakSelf = self;

    dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0/*延迟执行时间*/ * NSEC_PER_SEC));

    dispatch_after(delay, dispatch_get_main_queue(), ^{
    [[NSNotificationCenter defaultCenter] postNotificationName:contactReloadData object:nil];
    });


    // if ([AppEngine.mainDataCent.data_UserData.data_HxID isEqualToString:aUsername]) {


    //删除好友成功后,再删除聊天会话
    [[EMClient sharedClient].chatManager deleteConversation:aUsername isDeleteMessages:YES completion:^(NSString *aConversationId, EMError *aError) {

    if (!aError) {
    //删除聊天会话,成功后,刷新聊天会话列表
    dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0/*延迟执行时间*/ * NSEC_PER_SEC));

    dispatch_after(delay, dispatch_get_main_queue(), ^{
    [[NSNotificationCenter defaultCenter] postNotificationName:talkBtnClickThenUpdateConversionlist object:nil];
    });


    }

    }];
    }

    // 统计未读消息数
    -(void)setupUnreadMessageCount
    {
    NSArray *conversations = [[EMClient sharedClient].chatManager getAllConversations];
    NSInteger unreadCount = 0;
    for (EMConversation *conversation in conversations) {
    unreadCount += conversation.unreadMessagesCount;
    }

    NSArray *tabBarItems = self.tabBar.items;

    UITabBarItem *conlistTabBarItem = [tabBarItems objectAtIndex:1];
    if (unreadCount > 0) {
    conlistTabBarItem.badgeValue = [NSString stringWithFormat:@"%i",(int)unreadCount];
    }else{
    conlistTabBarItem.badgeValue = nil;
    }


    // UIApplication *application = [UIApplication sharedApplication];
    // [application setApplicationIconBadgeNumber:unreadCount];
    }


    - (void)cmdMessagesDidReceive:(NSArray *)aCmdMessages {
    for (EMMessage *message in aCmdMessages) {
    EMCmdMessageBody *body = (EMCmdMessageBody *)message.body;
    NSLog(@"收到的action是 -- %@",body.action);


    if (body.action == nil) {

    return;

    }

    NSData *jsonData = [body.action dataUsingEncoding:NSUTF8StringEncoding];

    NSError *err;

    NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:&err];

    if(err) {

    NSLog(@"json解析失败:%@",err);

    return;

    }

    NSNumber *num = dic[@"count"];

    NSString *str = [num stringValue];

    [AppEngine.IMDataCent requestUpdateUnreadNumberWithUnreadNumber:str];

    }


    }

    - (void)didReceiveMessages:(NSArray *)aMessages{

    // [self refresh];

    [self setupUnreadMessageCount];

    EMMessage *message = aMessages[0];

    NSString *sendPerson = message.from;

    BHNavigatiomController *imNaviCV = self.viewControllers[1];

    BHConversationListController *converCV = imNaviCV.viewControllers[0];

    // [converCV refresh];

    [converCV refreshDataSource];//只要收到消息就从服务器拿

    UIApplicationState state = [[UIApplication sharedApplication] applicationState];

    for (NSString *hx_name in AppEngine.IMDataCent.data_ExcuseFriendsData) {

    if ([hx_name isEqualToString:sendPerson]) {
    return;
    }

    }

    switch (state) {
    case UIApplicationStateActive:
    [self playSoundAndVibration];
    break;
    case UIApplicationStateInactive:
    [self playSoundAndVibration];
    break;
    case UIApplicationStateBackground:
    [self showNotificationWithMessage:message];
    break;
    default:
    break;
    }

    }

    - (void)playSoundAndVibration{
    NSTimeInterval timeInterval = [[NSDate date]
    timeIntervalSinceDate:self.lastPlaySoundDate];
    if (timeInterval < kDefaultPlaySoundInterval) {
    //如果距离上次响铃和震动时间太短, 则跳过响铃
    NSLog(@"skip ringing & vibration %@, %@", [NSDate date], self.lastPlaySoundDate);
    return;
    }

    //保存最后一次响铃时间
    self.lastPlaySoundDate = [NSDate date];

    // 收到消息时,播放音频
    [[EMCDDeviceManager sharedInstance] playNewMessageSound];
    // 收到消息时,震动
    [[EMCDDeviceManager sharedInstance] playVibration];
    }


    - (void)showNotificationWithMessage:(EMMessage *)message
    {
    EMPushOptions *options = [[EMClient sharedClient] pushOptions];
    NSString *alertBody = nil;
    if (options.displayStyle == EMPushDisplayStyleMessageSummary) {
    EMMessageBody *messageBody = message.body;
    NSString *messageStr = nil;
    switch (messageBody.type) {
    case EMMessageBodyTypeText:
    {
    messageStr = ((EMTextMessageBody *)messageBody).text;
    }
    break;
    case EMMessageBodyTypeImage:
    {
    messageStr = NSLocalizedString(@"message.image", @"Image");
    }
    break;
    case EMMessageBodyTypeLocation:
    {
    messageStr = NSLocalizedString(@"message.location", @"Location");
    }
    break;
    case EMMessageBodyTypeVoice:
    {
    messageStr = NSLocalizedString(@"message.voice", @"Voice");
    }
    break;
    case EMMessageBodyTypeVideo:{
    messageStr = NSLocalizedString(@"message.video", @"Video");
    }
    break;
    default:
    break;
    }

    do {
    // NSString *title = [[UserProfileManager sharedInstance] getNickNameWithUsername:message.from];
    NSString *title = @"大佬";

    if (message.chatType == EMChatTypeGroupChat) {
    NSDictionary *ext = message.ext;
    if (ext && ext[kGroupMessageAtList]) {
    id target = ext[kGroupMessageAtList];
    if ([target isKindOfClass:[NSString class]]) {
    if ([kGroupMessageAtAll compare:target options:NSCaseInsensitiveSearch] == NSOrderedSame) {
    alertBody = [NSString stringWithFormat:@"%@%@", title, NSLocalizedString(@"group.atPushTitle", @" @ me in the group")];
    break;
    }
    }
    else if ([target isKindOfClass:[NSArray class]]) {
    NSArray *atTargets = (NSArray*)target;
    if ([atTargets containsObject:[EMClient sharedClient].currentUsername]) {
    alertBody = [NSString stringWithFormat:@"%@%@", title, NSLocalizedString(@"group.atPushTitle", @" @ me in the group")];
    break;
    }
    }
    }
    NSArray *groupArray = [[EMClient sharedClient].groupManager getJoinedGroups];
    for (EMGroup *group in groupArray) {
    if ([group.groupId isEqualToString:message.conversationId]) {
    title = [NSString stringWithFormat:@"%@(%@)", message.from, group.subject];
    break;
    }
    }
    }
    else if (message.chatType == EMChatTypeChatRoom)
    {
    NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
    NSString *key = [NSString stringWithFormat:@"OnceJoinedChatrooms_%@", [[EMClient sharedClient] currentUsername]];
    NSMutableDictionary *chatrooms = [NSMutableDictionary dictionaryWithDictionary:[ud objectForKey:key]];
    NSString *chatroomName = [chatrooms objectForKey:message.conversationId];
    if (chatroomName)
    {
    title = [NSString stringWithFormat:@"%@(%@)", message.from, chatroomName];
    }
    }

    alertBody = [NSString stringWithFormat:@"%@:%@", title, messageStr];
    } while (0);
    }
    else{
    // alertBody = NSLocalizedString(@"receiveMessage", @"you have a new message");
    alertBody = @"您有一条消息";
    }

    NSTimeInterval timeInterval = [[NSDate date] timeIntervalSinceDate:self.lastPlaySoundDate];
    BOOL playSound = NO;
    if (!self.lastPlaySoundDate || timeInterval >= kDefaultPlaySoundInterval) {
    self.lastPlaySoundDate = [NSDate date];
    playSound = YES;
    }

    NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
    [userInfo setObject:[NSNumber numberWithInt:message.chatType] forKey:kMessageType];
    [userInfo setObject:message.conversationId forKey:kConversationChatter];

    //发送本地推送
    if (NSClassFromString(@"UNUserNotificationCenter")) {
    UNTimeIntervalNotificationTrigger *trigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:0.01 repeats:NO];
    UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
    if (playSound) {
    content.sound = [UNNotificationSound defaultSound];
    }
    content.body =alertBody;
    content.userInfo = userInfo;
    UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:message.messageId content:content trigger:trigger];
    [[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:request withCompletionHandler:nil];
    }
    else {
    UILocalNotification *notification = [[UILocalNotification alloc] init];
    notification.fireDate = [NSDate date]; //触发通知的时间
    notification.alertBody = alertBody;
    notification.alertAction = NSLocalizedString(@"open", @"Open");
    notification.timeZone = [NSTimeZone defaultTimeZone];
    if (playSound) {
    notification.soundName = UILocalNotificationDefaultSoundName;
    }
    notification.userInfo = userInfo;

    //发送通知
    [[UIApplication sharedApplication] scheduleLocalNotification:notification];
    }
    }

    - (void)messagesDidDeliver:(NSArray *)aMessages{

    NSLog(@"sf");
    }


    - (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
    }

    /*
    #pragma mark - Navigation

    // In a storyboard-based application, you will often want to do a little preparation before navigation
    - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    // Get the new view controller using [segue destinationViewController].
    // Pass the selected object to the new view controller.
    }
    */

    @end


     
     
    本人github:https://github.com/BHAreslee
    本人简书:http://www.jianshu.com/u/bb53043aaa00
    以上就是集成环信时暂时发现的问题,欢迎大家分享你们遇到的问题,也欢迎加入QQ群:372251359,一起讨论交流即时通讯的问题。
    本人微信公众号:放心安慰剂


    qrcode_for_gh_bc92a063b4a2_430.jpg

    收起阅读 »

    【环信招聘】Android、ios、H5前端、后台工程师快到碗里来!

    这里的每一项技术都会直接帮助平台上的App开发者,并在真正的应用里得到实践检验。 我们不仅是技术的研发者,也是技术的输出方。 这里的每一项技术都在改变世界! 欢迎加入!我们支持开源运动! 来环信,和我们一起用技术改变世界!  以下职位工作地点是在河南郑...
    继续阅读 »
    这里的每一项技术都会直接帮助平台上的App开发者,并在真正的应用里得到实践检验。

    我们不仅是技术的研发者,也是技术的输出方。

    这里的每一项技术都在改变世界!

    欢迎加入!我们支持开源运动!

    来环信,和我们一起用技术改变世界!

     以下职位工作地点是在河南郑州开发,小伙伴们简历赶紧砸过来吧!内推邮箱地址duc@easemob.com
     
    iOS工程师

    岗位职责:
    1. 负责IM SDK开发与相关应用的开发;
    2. 对客户提供解决方案和必要的技术支持;

    任职资格:
    1. 熟悉iOS开发环境、iOS App开发规范和App开发流程;
    2. 熟练使用iOS开发核心库,有一定的文档编写能力;
    3. 熟悉iOS SDK中布局、网络、数据库、HTTP协议、XML/JSON解析等;
    4. 掌握iOS平台的模块化设计架构,能够设计出恰当的应用方案;
    5. 熟悉iOS平台的内存管理机制,懂得内存优化技术;
    6. 能够与产品经理、设计师、用户进行深入的沟通和交流;
    7. 有即时通讯平台开发经验者优先考虑;
    8. 两年以上开发经验,其中一年以上iOS应用开发经验;
    9. Github 使用者、熟悉c/c++开发,有开源项目贡献者优先考虑。


    安卓工程师

    岗位职责:
    1. 负责IM SDK开发与相关应用的开发;
    2. 对客户提供解决方案和必要的技术支持;

    任职资格:
    1. 熟悉android开发环境、android应用开发规范和开发流程;
    2. 熟练使用android SDK,熟悉JNI开发,有一定的文档编写能力;
    3. 熟悉android SDK中布局、网络、数据库、HTTP协议、XML/JSON解析等;
    4. 掌握Android 平台的模块化设计架构,能够设计出恰当的应用方案;
    5. 熟悉 Android 平台的内存管理机制,懂得内存优化技术;
    6. 能够与产品经理、设计师、用户进行深入的沟通和交流;
    7. 有即时通讯平台开发经验者优先考虑;
    8. 两年以上开发经验,其中一年以上android应用开发经验;
    9. Github 使用者、熟悉c/c++开发,有开源项目贡献者优先考虑。


    H5前端工程师

    工作职责
    1.环信Web、IM、SDK等的开发设计 
    2.环信业务后台管理系统的开发设计
    任职资格
    1.有web前端经验,熟悉流行的前端技术, 包括但不限于bootstrap、html5、css3、saas、less、jQuery、bower、grunt、webpack、reactJs 
    2.有H5移动端开发经验 
    3.熟悉Ajax, Rest等原理和使用方式 
    4.熟悉Http, WebSocket, Spdy等协议 
    5.深刻理解Web标准, 对可用性、可访问性等相关知识有实际的了解和实践经验 
    6.有一定的设计美感 
    7.简历请同时提供以往项目地址,以及github地址或技术博客地址 
    加分项: 
    1.熟悉ruby、python、bash、nodejs等脚本语言优先考虑 
    2.有开源社区经验者优先考虑


    后台工程师
    工作职责
    1.负责持续改进后端服务,打造业界领先的即时通讯云服务; 
    2.或负责持续优化服务性能,提高后端服务的承载能力; 
    3.或负责完成后端服务的多机房改造,为全球用户提供可靠实时的即时通讯服务;
    任职资格
    1.熟悉Java、Erlang或C/C++其中两种语言,有Unix/Linux平台相关开发经验; 
    2.熟悉网络通信机制及常用数据传输协议; 
    3.算法基础扎实,有ACM、TopCoder或其他比赛经验者优先; 
    4.熟悉数据库相关开发,了解MySQL数据库,有HBase、Redis或其他NoSQL相关使用经验者优先; 
    5.有较强的解决问题能力,能够承受压力情况下解决线上问题; 
    6.能够带领团队成员,研究并攻克技术难关; 
    7.需要有开放共享的心态,接受开源思想,有Github创建、维护或参与经验更好; 
    8.5年以上开发工作经验;


    项目经理(项目实施)-IM
    您的责任:
    1.确保项目目标的实现,领导项目团队准时、优质的完成全部工作。
    2.及时有效地与客户沟通,了解项目的整体需求,与客户保持持续的联系,及时反馈阶段性的成果,并及时向研发更新客户提出的合理需求。
    3.制定开发计划文档,量化任务,并合理分配给相关人员。
    4.跟踪项目进度,有效的提高代码质量,并及时与研发、实施部署人员以及QA之间沟通,保证项目以及附属文档的完整和规范。
    我们的要求:
    1. 正规大学本科及以上学历
    2.5年以上软件行业经验,3年以上开发/项目管理工作经验
    3.能够独立完成中型项目的整体设计、任务计划和开发进度的管理工作
    4.熟悉框架设计、系统设计、数据库设计、编码测试等软件工程知识和规范
    5.精通软件开发流程,了解linux/unix系统,并能够熟练使用系统设计、数据库设计工具
    6.对软件多层结构、以及各层间使用的软件技术有较全面的了解 ;      
    7.具有开发队伍的管理能力和经验;
    8.具备技术文档编写能力;
    9.有乙方项目经理经历,能够与甲方顺畅沟通。
    10..有互联网工作经历的优先。

    iOS工程师

    岗位职责:
    1. 负责IM SDK开发与相关应用的开发;
    2. 对客户提供解决方案和必要的技术支持;

    任职资格:
    1. 熟悉iOS开发环境、iOS App开发规范和App开发流程;
    2. 熟练使用iOS开发核心库,有一定的文档编写能力;
    3. 熟悉iOS SDK中布局、网络、数据库、HTTP协议、XML/JSON解析等;
    4. 掌握iOS平台的模块化设计架构,能够设计出恰当的应用方案;
    5. 熟悉iOS平台的内存管理机制,懂得内存优化技术;
    6. 能够与产品经理、设计师、用户进行深入的沟通和交流;
    7. 有即时通讯平台开发经验者优先考虑;
    8. 两年以上开发经验,其中一年以上iOS应用开发经验;
    9. Github 使用者、熟悉c/c++开发,有开源项目贡献者优先考虑。


    安卓工程师

    岗位职责:
    1. 负责IM SDK开发与相关应用的开发;
    2. 对客户提供解决方案和必要的技术支持;

    任职资格:
    1. 熟悉android开发环境、android应用开发规范和开发流程;
    2. 熟练使用android SDK,熟悉JNI开发,有一定的文档编写能力;
    3. 熟悉android SDK中布局、网络、数据库、HTTP协议、XML/JSON解析等;
    4. 掌握Android 平台的模块化设计架构,能够设计出恰当的应用方案;
    5. 熟悉 Android 平台的内存管理机制,懂得内存优化技术;
    6. 能够与产品经理、设计师、用户进行深入的沟通和交流;
    7. 有即时通讯平台开发经验者优先考虑;
    8. 两年以上开发经验,其中一年以上android应用开发经验;
    9. Github 使用者、熟悉c/c++开发,有开源项目贡献者优先考虑。


    H5前端工程师

    工作职责
    1.环信Web、IM、SDK等的开发设计 
    2.环信业务后台管理系统的开发设计
    任职资格
    1.有web前端经验,熟悉流行的前端技术, 包括但不限于bootstrap、html5、css3、saas、less、jQuery、bower、grunt、webpack、reactJs 
    2.有H5移动端开发经验 
    3.熟悉Ajax, Rest等原理和使用方式 
    4.熟悉Http, WebSocket, Spdy等协议 
    5.深刻理解Web标准, 对可用性、可访问性等相关知识有实际的了解和实践经验 
    6.有一定的设计美感 
    7.简历请同时提供以往项目地址,以及github地址或技术博客地址 
    加分项: 
    1.熟悉ruby、python、bash、nodejs等脚本语言优先考虑 
    2.有开源社区经验者优先考虑


    后台工程师
    工作职责
    1.负责持续改进后端服务,打造业界领先的即时通讯云服务; 
    2.或负责持续优化服务性能,提高后端服务的承载能力; 
    3.或负责完成后端服务的多机房改造,为全球用户提供可靠实时的即时通讯服务;
    任职资格
    1.熟悉Java、Erlang或C/C++其中两种语言,有Unix/Linux平台相关开发经验; 
    2.熟悉网络通信机制及常用数据传输协议; 
    3.算法基础扎实,有ACM、TopCoder或其他比赛经验者优先; 
    4.熟悉数据库相关开发,了解MySQL数据库,有HBase、Redis或其他NoSQL相关使用经验者优先; 
    5.有较强的解决问题能力,能够承受压力情况下解决线上问题; 
    6.能够带领团队成员,研究并攻克技术难关; 
    7.需要有开放共享的心态,接受开源思想,有Github创建、维护或参与经验更好; 
    8.5年以上开发工作经验;


    项目经理(项目实施)-IM
    您的责任:
    1.确保项目目标的实现,领导项目团队准时、优质的完成全部工作。
    2.及时有效地与客户沟通,了解项目的整体需求,与客户保持持续的联系,及时反馈阶段性的成果,并及时向研发更新客户提出的合理需求。
    3.制定开发计划文档,量化任务,并合理分配给相关人员。
    4.跟踪项目进度,有效的提高代码质量,并及时与研发、实施部署人员以及QA之间沟通,保证项目以及附属文档的完整和规范。
    我们的要求:
    1. 正规大学本科及以上学历
    2.5年以上软件行业经验,3年以上开发/项目管理工作经验
    3.能够独立完成中型项目的整体设计、任务计划和开发进度的管理工作
    4.熟悉框架设计、系统设计、数据库设计、编码测试等软件工程知识和规范
    5.精通软件开发流程,了解linux/unix系统,并能够熟练使用系统设计、数据库设计工具
    6.对软件多层结构、以及各层间使用的软件技术有较全面的了解 ;      
    7.具有开发队伍的管理能力和经验;
    8.具备技术文档编写能力;
    9.有乙方项目经理经历,能够与甲方顺畅沟通。
    10..有互联网工作经历的优先。 收起阅读 »

    【环信征文】基于环信开发一个医疗APP-Ⅰ

    公司又有了新项目,依然是含有即时通讯功能模块的项目。在经历了上个项目对环信sdk的集成后,对环信EaseUI有了大概的了解。这次果断还是集成环信,一回生二回熟,最主要的还是对环信IM稳定性非常放心!项目医疗类的项目,角色分医生和患者,双方都可主动发起会话,但如...
    继续阅读 »
    公司又有了新项目,依然是含有即时通讯功能模块的项目。在经历了上个项目对环信sdk的集成后,对环信EaseUI有了大概的了解。这次果断还是集成环信,一回生二回熟,最主要的还是对环信IM稳定性非常放心!
    项目医疗类的项目,角色分医生和患者,双方都可主动发起会话,但如果是患者找医生聊天,必须先经过预约,并只能在预约时间区间内才能和医生发送聊天消息、图片、语音、实时音视频。项目基于环信最V3.3.4版本开发,首先在会话界面自定义一个类比如BHChatViewController,继承自EaseMessageViewController类,基本上一个简单的界面就有了。以下就是EaseMessageViewController类的发送各种消息的方法,那么根据需要只需重写以下方法即可。
    /*!
    @method
    @brief 发送文本消息
    @discussion
    @param text 文本消息
    @result
    */
    - (void)sendTextMessage:(NSString *)text;

    /*!
    @method
    @brief 发送文本消息
    @discussion
    @param text 文本消息
    @param ext 扩展信息
    @result
    */
    - (void)sendTextMessage:(NSString *)text withExt:(NSDictionary*)ext;

    /*!
    @method
    @brief 发送图片消息
    @discussion
    @param image 发送图片
    @result
    */
    - (void)sendImageMessage:(UIImage *)image;

    /*!
    @method
    @brief 发送位置消息
    @discussion
    @param latitude 经度
    @param longitude 纬度
    @param address 地址
    @result
    */
    - (void)sendLocationMessageLatitude:(double)latitude
    longitude:(double)longitude
    andAddress:(NSString *)address;

    /*!
    @method
    @brief 发送语音消息
    @discussion
    @param localPath 语音本地地址
    @param duration 时长
    @result
    */
    - (void)sendVoiceMessageWithLocalPath:(NSString *)localPath
    duration:(NSInteger)duration;

    /*!
    @method
    @brief 发送视频消息
    @discussion
    @param url 视频url
    @result
    */
    - (void)sendVideoMessageWithURL:(NSURL *)url;
    我在对发送图片的操作进行处理的时候,发现当拍照发图时,走这个方法
    //当你拍照发图时,走这个方法
    - (void)sendImageMessage:(UIImage *)image;
    但当从相册选择图片发送时,会发现,不走上面的方法了。
    仔细检查代码发现走了EaseMessageViewController.m的如下方法


    环信2.png


    然而这个方法,环信并没有放在EaseMessageViewController.h成为公开方法。我们只需手动粘贴方法到.h,然后在自己的子类重写就可以了。

    还有一个关于更多下图更多功能区域的问题


    EaseChatBarMoreView.png


    如上图所示,相册、拍照、视频等附加功能按钮,环信用EaseChatBarMoreView类来管理的。
    如果需要增加功能按钮用这个方法
    /*!
    @method
    @brief 新增一个新的功能按钮
    @discussion
    @param image 按钮图片
    @param highLightedImage 高亮图片
    @param title 按钮标题
    @result
    */
    - (void)insertItemWithImage:(UIImage*)image
    highlightedImage:(UIImage*)highLightedImage
    title:(NSString*)title;
    移除某个功能按钮用这个方法
    /*!
    @method
    @brief 根据索引删除功能按钮
    @discussion
    @param index 按钮索引
    @result
    */
    - (void)removeItematIndex:(NSInteger)index;
    修改一个功能按钮用这个方法
    /*!
    @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;
    可当你添加按钮,或者修改按钮时,会发现按钮的名字设置不了
    然后检查环信内部的实现,发现title的值在方法里根本就没用到?!
    索性不用updateItemWithImage这个方法了,直接去改内部代码。
    修改代码如下

    到EaseChatBarMoreView.m修改- (void)setupSubviewsForType:(EMChatToolbarType)type方法。改动的部分在代码后面有标注。
    - (void)setupSubviewsForType:(EMChatToolbarType)type
    {
    //self.backgroundColor = [UIColor clearColor];
    self.accessibilityIdentifier = @"more_view";

    _scrollview = [[UIScrollView alloc] init];
    _scrollview.pagingEnabled = YES;
    _scrollview.showsHorizontalScrollIndicator = NO;
    _scrollview.showsVerticalScrollIndicator = NO;
    _scrollview.delegate = self;
    [self addSubview:_scrollview];

    _pageControl = [[UIPageControl alloc] init];
    _pageControl.currentPage = 0;
    _pageControl.numberOfPages = 1;
    [self addSubview:_pageControl];

    CGFloat insets = (self.frame.size.width - 4 * CHAT_BUTTON_SIZE) / 5;

    _photoButton =[UIButton buttonWithType:UIButtonTypeCustom];
    [_photoButton setTitle:@"相册" forState:UIControlStateNormal];//改动
    [_photoButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];//改动
    _photoButton.titleLabel.font = [UIFont systemFontOfSize: 12.0];//改动
    _photoButton.imageEdgeInsets = UIEdgeInsetsMake(-10, 0, 20, 0);//改动
    _photoButton.titleEdgeInsets = UIEdgeInsetsMake(14, -60, -20, 0);//改动
    _photoButton.accessibilityIdentifier = @"image";
    [_photoButton setFrame:CGRectMake(insets, 10, CHAT_BUTTON_SIZE , CHAT_BUTTON_SIZE+10)];//改动
    [_photoButton setImage:[UIImage easeImageNamed:@"EaseUIResource.bundle/chatBar_colorMore_photo"] forState:UIControlStateNormal];
    [_photoButton setImage:[UIImage easeImageNamed:@"EaseUIResource.bundle/chatBar_colorMore_photoSelected"] forState:UIControlStateHighlighted];
    [_photoButton addTarget:self action:@selector(photoAction) forControlEvents:UIControlEventTouchUpInside];
    _photoButton.tag = MOREVIEW_BUTTON_TAG;
    [_scrollview addSubview:_photoButton];

    _locationButton =[UIButton buttonWithType:UIButtonTypeCustom];
    [_locationButton setTitle:@"位置" forState:UIControlStateNormal];//改动
    [_locationButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];//改动
    _locationButton.titleLabel.font = [UIFont systemFontOfSize: 12.0];//改动
    _locationButton.imageEdgeInsets = UIEdgeInsetsMake(-10, 0, 20, 0);//改动
    _locationButton.titleEdgeInsets = UIEdgeInsetsMake(14, -60, -20, 0);//改动
    _locationButton.accessibilityIdentifier = @"location";
    [_locationButton setFrame:CGRectMake(insets * 2 + CHAT_BUTTON_SIZE, 10, CHAT_BUTTON_SIZE , CHAT_BUTTON_SIZE+10)];//
    [_locationButton setImage:[UIImage easeImageNamed:@"EaseUIResource.bundle/chatBar_colorMore_location"] forState:UIControlStateNormal];
    [_locationButton setImage:[UIImage easeImageNamed:@"EaseUIResource.bundle/chatBar_colorMore_locationSelected"] forState:UIControlStateHighlighted];
    [_locationButton addTarget:self action:@selector(locationAction) forControlEvents:UIControlEventTouchUpInside];
    _locationButton.tag = MOREVIEW_BUTTON_TAG + 1;
    [_scrollview addSubview:_locationButton];

    _takePicButton =[UIButton buttonWithType:UIButtonTypeCustom];
    [_takePicButton setTitle:@"拍照" forState:UIControlStateNormal];//改动
    [_takePicButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];//改动
    _takePicButton.titleLabel.font = [UIFont systemFontOfSize: 12.0];//改动
    _takePicButton.imageEdgeInsets = UIEdgeInsetsMake(-10, 0, 20, 0);//改动
    _takePicButton.titleEdgeInsets = UIEdgeInsetsMake(14, -60, -20, 0);//改动
    [_takePicButton setFrame:CGRectMake(insets * 3 + CHAT_BUTTON_SIZE * 2, 10, CHAT_BUTTON_SIZE , CHAT_BUTTON_SIZE+10)];//改动
    [_takePicButton setImage:[UIImage easeImageNamed:@"EaseUIResource.bundle/chatBar_colorMore_camera"] forState:UIControlStateNormal];
    [_takePicButton setImage:[UIImage easeImageNamed:@"EaseUIResource.bundle/chatBar_colorMore_cameraSelected"] forState:UIControlStateHighlighted];
    [_takePicButton addTarget:self action:@selector(takePicAction) forControlEvents:UIControlEventTouchUpInside];
    _takePicButton.tag = MOREVIEW_BUTTON_TAG + 2;
    _maxIndex = 2;
    [_scrollview addSubview:_takePicButton];

    CGRect frame = self.frame;
    if (type == EMChatToolbarTypeChat) {
    frame.size.height = 150;
    _audioCallButton =[UIButton buttonWithType:UIButtonTypeCustom];
    [_audioCallButton setTitle:@"语音" forState:UIControlStateNormal];//改动
    [_audioCallButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];//改动
    _audioCallButton.titleLabel.font = [UIFont systemFontOfSize: 12.0];//改动
    _audioCallButton.imageEdgeInsets = UIEdgeInsetsMake(-10, 0, 20, 0);//改动
    _audioCallButton.titleEdgeInsets = UIEdgeInsetsMake(14, -60, -20, 0);//改动
    [_audioCallButton setFrame:CGRectMake(insets * 4 + CHAT_BUTTON_SIZE * 3, 10, CHAT_BUTTON_SIZE , CHAT_BUTTON_SIZE+10)];//
    [_audioCallButton setImage:[UIImage easeImageNamed:@"EaseUIResource.bundle/chatBar_colorMore_audioCall"] forState:UIControlStateNormal];
    [_audioCallButton setImage:[UIImage easeImageNamed:@"EaseUIResource.bundle/chatBar_colorMore_audioCallSelected"] forState:UIControlStateHighlighted];
    [_audioCallButton addTarget:self action:@selector(takeAudioCallAction) forControlEvents:UIControlEventTouchUpInside];
    _audioCallButton.tag = MOREVIEW_BUTTON_TAG + 3;
    [_scrollview addSubview:_audioCallButton];

    _videoCallButton =[UIButton buttonWithType:UIButtonTypeCustom];
    [_videoCallButton setTitle:@"视频" forState:UIControlStateNormal];//改动
    [_videoCallButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];//改动
    _videoCallButton.titleLabel.font = [UIFont systemFontOfSize: 12.0];//改动
    _videoCallButton.imageEdgeInsets = UIEdgeInsetsMake(-10, 0, 20, 0);//改动
    _videoCallButton.titleEdgeInsets = UIEdgeInsetsMake(14, -60, -20, 0);//改动
    [_videoCallButton setFrame:CGRectMake(insets, 10 * 2 + CHAT_BUTTON_SIZE + 10, CHAT_BUTTON_SIZE , CHAT_BUTTON_SIZE+10)];//
    [_videoCallButton setImage:[UIImage easeImageNamed:@"EaseUIResource.bundle/chatBar_colorMore_videoCall"] forState:UIControlStateNormal];
    [_videoCallButton setImage:[UIImage easeImageNamed:@"EaseUIResource.bundle/chatBar_colorMore_videoCallSelected"] forState:UIControlStateHighlighted];
    [_videoCallButton addTarget:self action:@selector(takeVideoCallAction) forControlEvents:UIControlEventTouchUpInside];
    _videoCallButton.tag =MOREVIEW_BUTTON_TAG + 4;
    _maxIndex = 4;
    [_scrollview addSubview:_videoCallButton];
    }
    else if (type == EMChatToolbarTypeGroup)
    {
    frame.size.height = 80;
    }
    self.frame = frame;
    _scrollview.frame = CGRectMake(0, 0, CGRectGetWidth(frame), CGRectGetHeight(frame));
    _pageControl.frame = CGRectMake(0, CGRectGetHeight(frame) - 20, CGRectGetWidth(frame), 20);
    _pageControl.hidden = _pageControl.numberOfPages<=1;
    }




    本人github:https://github.com/BHAreslee
    本人简书:http://www.jianshu.com/u/bb53043aaa00
    以上就是集成环信时暂时发现的问题,欢迎大家分享你们遇到的问题,也欢迎加入QQ群:372251359,一起讨论交流即时通讯的问题。
    本人微信公众号:放心安慰剂


    qrcode_for_gh_bc92a063b4a2_430.jpg


     
    项目完整源码 收起阅读 »

    android studio 2.3.3 最新 中文 汉化包 韩梦飞沙 安卓工作室 美化包

    韩梦飞沙  韩亚飞  313134555@qq.com  yue31313  han_meng_fei_sha 汉化包 百度云盘 下载地址:https://pan.baidu.com/s/1pLjwyeB  最新最详细全面最牛逼的汉化!稳定无BUG!设置界面...
    继续阅读 »
    韩梦飞沙  韩亚飞  313134555@qq.com  yue31313  han_meng_fei_sha

    汉化包 百度云盘 下载地址:https://pan.baidu.com/s/1pLjwyeB 
    最新最详细全面最牛逼的汉化!稳定无BUG!设置界面可以打开!不会报错!中英对照!界面酷炫!
    使用汉化美化包,让你的开发工具IDE 不再单调普通,彰显个性,与众不同!
    中文的 菜单工具设置信息,让你一眼看懂,让你用好这款开发软件,最大发挥它的作用!
      收起阅读 »