0
回复

视频录制文件有的能播放 有的不能播放 能播放的文件里面有花屏 版本3.5.5 环信_Android

回复

手浪用户2574613913 发起了问题 • 1 人关注 • 1084 次浏览 • 2019-08-08 15:18 • 来自相关话题

1
最佳

环信SDK发起视频通话 接收方收到后快速点击接听有时会接听失败 环信_Android

lizg 回复了问题 • 2 人关注 • 1033 次浏览 • 2019-08-02 16:44 • 来自相关话题

1
回复

Android如何发送文件?有没有具体案例可以参考一下? 求大神们指点 Android 发送文件

lizg 回复了问题 • 2 人关注 • 1030 次浏览 • 2019-08-01 17:29 • 来自相关话题

0
评论

test

coinci 发表了文章 • 412 次浏览 • 2019-08-01 17:21 • 来自相关话题

1
回复

使用的3.5.6的带视频通话的api,在发起视频通话时 接收不到对方的已接通回调 环信_Android

lizg 回复了问题 • 2 人关注 • 1156 次浏览 • 2019-07-25 16:50 • 来自相关话题

2
回复

环信打包混淆后不能发送消息(文字,图片,视频) 环信_Android

诠释、Javascript   回复了问题 • 3 人关注 • 1532 次浏览 • 2019-07-25 09:59 • 来自相关话题

1
回复

ios 初始化方法崩溃 [[EMClient sharedClient] initializeSDKWithOptions:options]; 环信_iOS

kijieoeew 回复了问题 • 2 人关注 • 1235 次浏览 • 2019-07-18 16:49 • 来自相关话题

2
回复

AndroidStudio编译时出现 (Duplicate zip entry [50.jar:com/hyphenate/chat/EMMirror$MIRROR.class] EMMirror$MIRROR.class

mv 回复了问题 • 2 人关注 • 1350 次浏览 • 2019-07-16 14:51 • 来自相关话题

2
回复

聊天室人多,实时消息回调延迟怎么解决 有专职工程师值守 实时消息回调

beyond 回复了问题 • 3 人关注 • 2382 次浏览 • 2019-07-10 11:41 • 来自相关话题

0
评论

iOS SDK 日志文件的导出 日志

kijieoeew 发表了文章 • 726 次浏览 • 2019-07-05 12:32 • 来自相关话题

环信SDK提供2.x和3.x两个版本。初始化SDK成功,会写入日志文件到本地。

日志文件路径如下:
2.x 已停止维护3.5.4之前(不含3.5.4) 沙箱Documents/HyphenateSDK/easemoblog 3.5.4之后(含3.5.4)    沙箱Library/Application Support/HyphenateSDK/easemobLog 

获取方法:

模拟器 
打印NSHomeDirectory()
      



复制路径,打开Finder前往
     



访问到沙箱目录
      




真机
打开Xcode连接设备,前往Xcode --> Window --> Devices and Simulators
      



进入Devices界面
     



选择Download Container之后会下载到本地一个.xcappdata文件。选中这个文件鼠标右键显示包内容。
     



访问到沙箱目录
      查看全部
环信SDK提供2.x和3.x两个版本。初始化SDK成功,会写入日志文件到本地。

日志文件路径如下:
  • 2.x 已停止维护
  • 3.5.4之前(不含3.5.4) 沙箱Documents/HyphenateSDK/easemoblog 
  • 3.5.4之后(含3.5.4)    沙箱Library/Application Support/HyphenateSDK/easemobLog 


获取方法:

模拟器 
  • 打印NSHomeDirectory()

      
模拟器1.png

  • 复制路径,打开Finder前往

     
模拟器2.png

  • 访问到沙箱目录

      
模拟器3_路径.png


真机
  • 打开Xcode连接设备,前往Xcode --> Window --> Devices and Simulators

      
真机1.png

  • 进入Devices界面

     
真机2.png

  • 选择Download Container之后会下载到本地一个.xcappdata文件。选中这个文件鼠标右键显示包内容。

     
真机3.png

  • 访问到沙箱目录

     
真机4_路径.png
1
回复

iOS示例demo键盘会挡住聊天信息

kijieoeew 回复了问题 • 2 人关注 • 1244 次浏览 • 2019-07-05 11:21 • 来自相关话题

1
回复

请问哪位大佬有webim语音通话的demo,官方的那个demo看的一脸懵 webim语音通话

lizg 回复了问题 • 2 人关注 • 1515 次浏览 • 2019-06-27 10:19 • 来自相关话题

4
回复

点击语音通话和视频通话。没有反应。 环信_iOS

kijieoeew 回复了问题 • 5 人关注 • 4183 次浏览 • 2019-06-26 16:49 • 来自相关话题

1
回复

WEB IM 发起语音通话 WEB语音通话

明明明 回复了问题 • 2 人关注 • 2652 次浏览 • 2019-06-25 23:06 • 来自相关话题

0
回复

环信小程序音频播放 音频播放

回复

用户3794213955 发起了问题 • 1 人关注 • 1215 次浏览 • 2019-06-25 10:33 • 来自相关话题

1
回复

环信onError异常回调 连接状态

lizg 回复了问题 • 2 人关注 • 1400 次浏览 • 2019-06-17 16:38 • 来自相关话题

0
评论

(客服云)iOS访客端接收图文消息不展示封面图片怎么办? 客服云 图文消息

kijieoeew 发表了文章 • 588 次浏览 • 2019-06-10 10:16 • 来自相关话题

将HDBubbleView.h+Article.m中的- (instancetype)initWithDictionary:(NSDictionary *)dic方法改成下面这样就可以了
- (instancetype)initWithDictionary:(NSDictionary *)dic {
self = [super init];
if (dic != nil) {
_type = HDCellTypeSub;
_title = [dic objectForKey:@"title"];
double createTime = [[NSDate date] timeIntervalSince1970] * 1000;
if ([dic objectForKey:@"createdTime"]) {
createTime = [[dic objectForKey:@"createdTime"] doubleValue];
}
_createTime = [self timeFormatter:createTime/1000];
_digest = [dic objectForKey:@"digest"];

// //封面展示缩略图
// NSString *thumbUrl = [dic objectForKey:@"thumbUrl"];
// if (thumbUrl) {
// if (thumbUrl && [thumbUrl hasPrefix:@"http"]) {
// _imageUrl = thumbUrl;
// }else {
// _imageUrl = [NSString stringWithFormat:@"%@%@",[HDClient.sharedClient kefuRestServer], thumbUrl];
// }
// }

// 封面展示原图
NSString *picUrl = [dic objectForKey:@"picurl"];
if (picUrl) {
if (picUrl && [picUrl hasPrefix:@"http"]) {
_imageUrl = picUrl;
}else {
_imageUrl = [NSString stringWithFormat:@"%@%@",[HDClient.sharedClient kefuRestServer], picUrl];
}
}

NSString *detailUrl = [dic objectForKey:@"url"];
if (detailUrl) {
if (detailUrl && [detailUrl hasPrefix:@"http"]) {
_url = detailUrl;
}else {
_url = [NSString stringWithFormat:@"%@%@",[HDClient.sharedClient kefuRestServer], detailUrl];
}
}
}
return self;
} 查看全部
HDBubbleView.h+Article.m中的- (instancetype)initWithDictionary:(NSDictionary *)dic方法改成下面这样就可以了
- (instancetype)initWithDictionary:(NSDictionary *)dic {
self = [super init];
if (dic != nil) {
_type = HDCellTypeSub;
_title = [dic objectForKey:@"title"];
double createTime = [[NSDate date] timeIntervalSince1970] * 1000;
if ([dic objectForKey:@"createdTime"]) {
createTime = [[dic objectForKey:@"createdTime"] doubleValue];
}
_createTime = [self timeFormatter:createTime/1000];
_digest = [dic objectForKey:@"digest"];

// //封面展示缩略图
// NSString *thumbUrl = [dic objectForKey:@"thumbUrl"];
// if (thumbUrl) {
// if (thumbUrl && [thumbUrl hasPrefix:@"http"]) {
// _imageUrl = thumbUrl;
// }else {
// _imageUrl = [NSString stringWithFormat:@"%@%@",[HDClient.sharedClient kefuRestServer], thumbUrl];
// }
// }

// 封面展示原图
NSString *picUrl = [dic objectForKey:@"picurl"];
if (picUrl) {
if (picUrl && [picUrl hasPrefix:@"http"]) {
_imageUrl = picUrl;
}else {
_imageUrl = [NSString stringWithFormat:@"%@%@",[HDClient.sharedClient kefuRestServer], picUrl];
}
}

NSString *detailUrl = [dic objectForKey:@"url"];
if (detailUrl) {
if (detailUrl && [detailUrl hasPrefix:@"http"]) {
_url = detailUrl;
}else {
_url = [NSString stringWithFormat:@"%@%@",[HDClient.sharedClient kefuRestServer], detailUrl];
}
}
}
return self;
}
0
评论

(客服云)企业版机器人如何设置转人工 客服云

kijieoeew 发表了文章 • 671 次浏览 • 2019-06-06 15:59 • 来自相关话题

注意:只购买了企业版机器人,不使用客服系统的同学,可忽略第一、二步。
 
一、管理员模式--智能机器人--机器人设置,选择【转人工设置】,这里可以设置“转人工时间段”、“限制转人工提示语”和“转人工指定技能组”。
不指定技能组,转人工时,会按照 设置--会话分配规则 页面设置的“路由规则”分配给对应的技能组。




 
二、还是这个页面,选择【基础设置】,然后点击【机器人管理】,跳转到机器人管理页面。
    有些同学点击后没反应,那有可能是被浏览器拦截了,手动释放即可。




 
三、来到机器人管理页面,找到机器人设置--转人工设置,先【添加转人工参数】,添加时“参数名”随便写,“参数值”必须是技能组的名称,否则无法转接到此技能组。不添加转人工参数,则无法指定技能组。
然后打开【转人工指令】的开关,配置“转人工指令”以及“参数”
开关开启,但是不指定参数,转人工时,会按照客服系统中 设置--会话分配规则 页面设置的“路由规则”分配给对应的技能组。
注意:在这里开启了“转人工指令”,第1步的“转人工指定技能组”则失效,以这里为准。
         不开启,则以第1步的“转人工指定技能组”的配置为准。 查看全部
注意:只购买了企业版机器人,不使用客服系统的同学,可忽略第一、二步。
 
一、管理员模式--智能机器人--机器人设置,选择【转人工设置】,这里可以设置“转人工时间段”、“限制转人工提示语”和“转人工指定技能组”。
不指定技能组,转人工时,会按照 设置--会话分配规则 页面设置的“路由规则”分配给对应的技能组。
企业版机器人转人工设置1.png

 
二、还是这个页面,选择【基础设置】,然后点击【机器人管理】,跳转到机器人管理页面。
    有些同学点击后没反应,那有可能是被浏览器拦截了,手动释放即可。
企业版机器人转人工设置2.png

 
三、来到机器人管理页面,找到机器人设置--转人工设置,先【添加转人工参数】,添加时“参数名”随便写,“参数值”必须是技能组的名称,否则无法转接到此技能组。不添加转人工参数,则无法指定技能组。
然后打开【转人工指令】的开关,配置“转人工指令”以及“参数”
开关开启,但是不指定参数,转人工时,会按照客服系统中 设置--会话分配规则 页面设置的“路由规则”分配给对应的技能组。
注意:在这里开启了“转人工指令”,第1步的“转人工指定技能组”则失效,以这里为准。
         不开启,则以第1步的“转人工指定技能组”的配置为准。
企业版机器人转人工设置3.png
0
回复

网上遇到ag平台网站不给出该怎么办? 环信_Android

回复

小周豪 发起了问题 • 1 人关注 • 1197 次浏览 • 2019-05-30 16:25 • 来自相关话题

1
评论

《拍拍二手》微信小程序之环信接入 微信小程序

sunshine123 发表了文章 • 1455 次浏览 • 2019-05-30 15:23 • 来自相关话题

拍拍二手闲置平台,可以将自己的闲置物品进行转让或者捐赠。想和卖家达成共识就需要涉及IM聊天。拍拍二手闲置平台目前接入的是环信IM聊天。下面我将从三个阶段带大家玩转环信IM会话。
前期
初识IM聊天
带着问题去调研
必须接入环信吗?除了环信是否可以接入其他即时通信?
环信目前有哪些功能呢?支持微信小程序吗?
如何接入小程序呢?
调研分析
必须接入环信吗?除了环信是否可以接入其他即时通信?
现状: 微信小程序API 提供了WebSocket 方法。
扩展: 如果服务端支持scoket通信,ios\android\H5 也全都支持Im聊天了
备注:专业第三方Im有融云、环信、云之讯等,底层实现均是基于scoket 通信。明白scoket通信后也可以自己写即时通信。
环信目前有哪些功能呢?支持微信小程序吗?
错误想法: 环信就是做im聊天的,咱们上去按照接入文档,开发就能搞定!!!
这种想法是很致命的。在所有的第三方组件接入中,如果我们不能跳出来看待问题,只是为了完成任务而完成任务。那么我们永远是最底层的低级码农。
环信目前是同行业里面做的算不错的。那么他的官网、接入规范都应该有的。微信小程序也是支持的。在后面小编会带领大家一切怎么去阅读一个官网
如何接入小程序?
接入小程序是否需要申请一个账号呢?我直接运行他们的demo可以吗? 怎么去测试呢? 此时我们可以有很多的猜想。我认为在开始接入之前我们应该很好的进行一些思考,答案显而易见。
环信接入思考篇
快即时慢
在工作中,大家会经常遇到第三方组件的接入。当接收到任务后,为了尽快完成任务。上来就google,找攻略,找技巧。往往认为这样做速度是最快的。结果适得其反,做了很多无用的功。我们意识中的快,结果却变成了慢
慢即时快image逆向思维: 任何一个第三方的组件,特别是一个大点的平台,他们为了推出自己的产品,一定会有各种各样的功能支持,接入文档说明。我们放慢速度,将这些资源用上半天的时间进行简单的梳理。后期的开发进度会有很大的提升。
上图是我在接入环信Im后进行的反思。因为在接入环信之前,其他团队成员用了很长的时间联调。假如他们在接入环信聊天之前,了解环信拥有自己的后台,可以直接给用户端发送测试消息;可以直接创建用户、创建聊天室、创建群组。他们还会花费那么久的时间去联调吗?完全不用依赖服务端。不用依赖ios,依赖android。自己使用环信后台,轻轻松松完成各种测试。
环信接入
环信官网注册自己的即时通讯云,并登陆后台






创建自己的应用,并记录关键信息






以下是关键信息哦!!!






备注:
应用标识 应用接入时会使用
IM 用户 可以创建、删除用户、发送消息
群组 可以创建、删除群组信息、发送消息
聊天室 可以创建、删除聊天室、发送消息
tip 通过这个后台管理系统,就可以玩转环信的接入测试了。
从环信下载小程序demo,替换 appkey 进行联调测试






测试走起
用户测试 
在环信后台创建用户,在小程序端登录 (用户demo1 密码:123456)





一对一会话测试
① 在环信后台创建用户demo2
② 点击操作,查看用户好友将demo1和demo2 添加为好友。
③ 在小程序端用demo1给demo2发送测试消息。
④ 退出demo1用户,登录demo2查看是否会接收到demo1发送的会话






由于环信工程师们相信码农的实力,在群组测试和聊天室测试这块为大家留下了想象空间。demo 中群组测试和聊天室测试为明确写出。让我继续带大家飞

群组测试
① 创建群组记录群组id,并给群组添加成员(demo2)






② 环信后台给群组发送测试消息






③ 控制台能收到群组测试消息,怎么展示呢? 请阅读源码解析篇
聊天室测试
① 创建聊天室记录聊天室id,将demo1 设置为超级管理员,demo2设置为管理员
② 聊天室这里没有聊天室消息的发送。请阅读源码解析篇
通过以上4个简单的测试,android、ios、h5、小程序的聊天测试均可以参照以上4点进行顺利的测试。初期就此结束。下面带代价进行源码的解析
中期
看源码前期思考





 
核心源码阅读





 
以上是环信sdk 基础代码结构。 通过简单阅读会发现:环信的scoket 通信也使用了微信小程序暴露的scoket 通信 (猜想 android、ios 其他端也有对应的scoket通信)
环信的api包装在connection.js 组件中,如果某些api没有,咱们可以扩展connection 中的方法
环信核心代码阅读完成后,发现没有涉及到缓存。看来缓存的处理是在对应的业务逻辑中。
设想:
消息应该在哪里缓存
哪里进行会话链接的监听注册
环信demo 代码阅读
会话、群组
通过前面提到的方式,大家可以在小程序控制台抓取到用户收到的会话和群组消息
会话
app.js
环信scoket 注册监听代码在app.js 中
核心代码如下:{
//调用API从本地缓存中获取数据
var that = this
var logs = wx.getStorageSync('logs') || []
logs.unshift(Date.now())
wx.setStorageSync('logs', logs)

WebIM.conn.listen({
onOpened: function (message) {//连接成功回调
// 如果isAutoLogin设置为false,那么必须手动设置上线,否则无法收消息
// 手动上线指的是调用conn.setPresence(); 如果conn初始化时已将isAutoLogin设置为true
// 则无需调用conn.setPresence();
WebIM.conn.setPresence()
},
onPresence: function (message) { //处理“广播”或“发布-订阅”消息,如联系人订阅请求、处理群组、聊天室被踢解散等消息
switch(message.type){
case "unsubscribe":
pages[0].moveFriend(message);
break;
case "subscribe":
if (message.status === '[resp:true]') {
return
} else {
pages[0].handleFriendMsg(message)
}
break;
case "joinChatRoomSuccess":
console.log('Message: ', message);
wx.showToast({
title: "JoinChatRoomSuccess",
});
break;
case "memberJoinChatRoomSuccess":
console.log('memberMessage: ', message);
wx.showToast({
title: "memberJoinChatRoomSuccess",
});
break;
case "memberLeaveChatRoomSuccess":
console.log("LeaveChatRoom");
wx.showToast({
title: "leaveChatRoomSuccess",
});
break;
}
},
onRoster: function (message) { //处理好友申请
var pages = getCurrentPages()
if (pages[0]) {
pages[0].onShow()
}
},

onVideoMessage: function(message){ //视频处理
console.log('onVideoMessage: ', message);
var page = that.getRoomPage()
if (message) {
if (page) {
page.receiveVideo(message, 'video')
} else {
var chatMsg = that.globalData.chatMsg || []
var time = WebIM.time()
var msgData = {
info: {
from: message.from,
to: message.to
},
username: message.from,
yourname: message.from,
msg: {
type: 'video',
data: message.url
},
style: '',
time: time,
mid: 'video' + message.id
}
msgData.style = ''
chatMsg = wx.getStorageSync(msgData.yourname + message.to) || []
chatMsg.push(msgData)
wx.setStorage({
key: msgData.yourname + message.to,
data: chatMsg,
success: function () {
//console.log('success')
}
})
}
}
},

onAudioMessage: function (message) { // 音频处理
console.log('onAudioMessage', message)
var page = that.getRoomPage()
console.log(page)
if (message) {
if (page) {
page.receiveMsg(message, 'audio')
} else {
var chatMsg = that.globalData.chatMsg || []
var value = WebIM.parseEmoji(message.data.replace(/\n/mg, ''))
var time = WebIM.time()
var msgData = {
info: {
from: message.from,
to: message.to
},
username: message.from,
yourname: message.from,
msg: {
type: 'audio',
data: value
},
style: '',
time: time,
mid: 'audio' + message.id
}
console.log("Audio msgData: ", msgData);
chatMsg = wx.getStorageSync(msgData.yourname + message.to) || []
chatMsg.push(msgData)
wx.setStorage({
key: msgData.yourname + message.to,
data: chatMsg,
success: function () {
//console.log('success')
}
})
}
}
},

onLocationMessage: function (message) { // 收到位置信息
console.log("Location message: ", message);
},

onTextMessage: function (message) {//收到文本消息
var page = that.getRoomPage()
console.log(page)
if (message) {
if (page) {
page.receiveMsg(message, 'txt')
} else {
var chatMsg = that.globalData.chatMsg || []
var value = WebIM.parseEmoji(message.data.replace(/\n/mg, ''))
var time = WebIM.time()
var msgData = {
info: {
from: message.from,
to: message.to
},
username: message.from,
yourname: message.from,
msg: {
type: 'txt',
data: value
},
style: '',
time: time,
mid: 'txt' + message.id
}
chatMsg = wx.getStorageSync(msgData.yourname + message.to) || []
chatMsg.push(msgData)
wx.setStorage({
key: msgData.yourname + message.to,
data: chatMsg,
success: function () {
//console.log('success')
}
})
}
}
},
onEmojiMessage: function (message) { //收到表情信息
//console.log('onEmojiMessage',message)
var page = that.getRoomPage()
//console.log(pages)
if (message) {
if (page) {
page.receiveMsg(message, 'emoji')
} else {
var chatMsg = that.globalData.chatMsg || []
var time = WebIM.time()
var msgData = {
info: {
from: message.from,
to: message.to
},
username: message.from,
yourname: message.from,
msg: {
type: 'emoji',
data: message.data
},
style: '',
time: time,
mid: 'emoji' + message.id
}
msgData.style = ''
chatMsg = wx.getStorageSync(msgData.yourname + message.to) || [] //tip 从本地缓存中获取用户的消息 发消息+来源 适用于单人会话 msgData.yourname + message.to+当前登录人 群组/聊天室

chatMsg.push(msgData)
//console.log(chatMsg)
wx.setStorage({
key: msgData.yourname + message.to,
data: chatMsg,
success: function () {
//console.log('success')
}
})
}
}
},
onPictureMessage: function (message) {//收到图片信息
//console.log('Picture',message);
var page = that.getRoomPage()
if (message) {
if (page) {
//console.log("wdawdawdawdqwd")
page.receiveImage(message, 'img')
} else {
var chatMsg = that.globalData.chatMsg || []
var time = WebIM.time()
var msgData = {
info: {
from: message.from,
to: message.to
},
username: message.from,
yourname: message.from,
msg: {
type: 'img',
data: message.url
},
style: '',
time: time,
mid: 'img' + message.id
}
msgData.style = ''
chatMsg = wx.getStorageSync(msgData.yourname + message.to) || []
chatMsg.push(msgData)
wx.setStorage({
key: msgData.yourname + message.to,
data: chatMsg,
success: function () {
//console.log('success')
}
})
}
}
},
// 各种异常
onError: function (error) {
// 16: server-side close the websocket connection
if (error.type == WebIM.statusCode.WEBIM_CONNCTION_DISCONNECTED) {
if (WebIM.conn.autoReconnectNumTotal < WebIM.conn.autoReconnectNumMax) {
return;
}

wx.showToast({
title: 'server-side close the websocket connection',
duration: 1000
});
wx.redirectTo({
url: '../login/login'
});
return;
}

// 8: offline by multi login
if (error.type == WebIM.statusCode.WEBIM_CONNCTION_SERVER_ERROR) {
wx.showToast({
title: 'offline by multi login',
duration: 1000
})
wx.redirectTo({
url: '../login/login'
})
return;
}
},
})


}
实际开发过程中,在微信中,退出小程序,重新进入时,webscoket 通信并没有重新创建链接。存在用户收到不到消息的情况。可以将以上代码封装,例如addHXLIstener(...)。当用户重新打开后,再次注册环信监听即可。
拍拍二手闲置交易平台,主要集成的是文本聊天功能。
环信登录 例如 initLoginHX(); var uin=wx.getStorageSync('hxuin');
var pwd=wx.getStorageSync('hxpwd');
console.log('initHX:' + uin+"||"+pwd);
var options = {
apiUrl: '服务器url',
user: '用户名',// 用户名要是字符
pwd: '密码',
grant_type: 'password',
appKey: 'appkey',
success: function (res) {
console.log("环信创建连接成功")
},
error: function (res) {
console.log("环信创建连接失败")
}
};
WebIM.conn.open(options);chat 会话
环信的会话列表存储在本地,并没有调用服务器端数据
var that = this
var member = wx.getStorageSync('member')
var myName = wx.getStorageSync('myUsername')
var array = []
for (var i = 0; i < member.length; i++) {
if (wx.getStorageSync(member[i].name + myName) != '') {
array.push(wx.getStorageSync(member[i].name + myName)[wx.getStorageSync(member[i].name + myName).length - 1])
}
}
//console.log(array,'1')
this.setData({
arr: array
})通过以上代码得出结论: 环信的会话是通过遍历用户id+对方id 构成的数据。
那群组和聊天室的怎么处理呢?
环信小程序demo中只提供了聊天室列表的获取接口我们可以轻松实现聊天室列表,并没有提供群组列表的获取方式。我们需要在conection中扩展调用群组列表的接口,来实现群组列表。参照聊天室列表获取即可实现。聊天室列表实现方式如下:connection.prototype.getChatRooms = function (options) {

var conn = this,
token = options.accessToken || this.context.accessToken;

if (token) {
var apiUrl = this.apiUrl;
var appName = this.context.appName;
var orgName = this.context.orgName;

if (!appName || !orgName) {
conn.onError({
type: _code.WEBIM_CONNCTION_AUTH_ERROR
});
return;
}

var suc = function (data, xhr) {
typeof options.success === 'function' && options.success(data);
};

var error = function (res, xhr, msg) {
if (res.error && res.error_description) {
conn.onError({
type: _code.WEBIM_CONNCTION_LOAD_CHATROOM_ERROR,
msg: res.error_description,
data: res,
xhr: xhr
});
}
};

var pageInfo = {
pagenum: parseInt(options.pagenum) || 1,
pagesize: parseInt(options.pagesize) || 20
};
// 想要实现群组列表,修改对应接口即可
var opts = {
url: apiUrl + '/' + orgName + '/' + appName + '/chatrooms',
dataType: 'json',
type: 'GET',
header: {'Authorization': 'Bearer ' + token},
data: pageInfo,
success: suc || _utils.emptyfn,
fail: error || _utils.emptyfn
};
wx.request(opts);
} else {
conn.onError({
type: _code.WEBIM_CONNCTION_TOKEN_NOT_ASSIGN_ERROR
});
}chatroom
从本地缓存中获取聊天记录,并展示
// 环信demo 发送消息

sendMessage: function () {

if (!this.data.userMessage.trim()) return;


var that = this
// //console.log(that.data.userMessage)
// //console.log(that.data.sendInfo)
var myName = wx.getStorageSync('myUsername')
var id = WebIM.conn.getUniqueId();
var msg = new WebIM.message('txt', id);
msg.set({
msg: that.data.sendInfo,
to: that.data.yourname,
roomType: false,
success: function (id, serverMsgId) {
console.log('send text message success')
}
});
// //console.log(msg)
console.log("Sending textmessage")
msg.body.chatType = 'singleChat'; // 群组聊天 groupRoom
WebIM.conn.send(msg.body);
// 消息发送完成

if (msg) {
var value = WebIM.parseEmoji(msg.value.replace(/\n/mg, '')) // 环信表情处理
var time = WebIM.time()
var msgData = {
info: {
to: msg.body.to
},
username: that.data.myName,
yourname: msg.body.to,
msg: {
type: msg.type,
data: value
},
style: 'self',
time: time,
mid: msg.id
}
that.data.chatMsg.push(msgData)
// console.log(that.data.chatMsg)

// 存储聊天记录
// 注: 单独单聊天 key 对方环信uin+自己的uin
// 注: 群组聊天 key 群组id\聊天室id+对方环信uin+自己的uin
wx.setStorage({
key: that.data.yourname + myName,
data: that.data.chatMsg,
success: function () {
//console.log('success', that.data)
that.setData({
chatMsg: that.data.chatMsg,
emojiList: [],
inputMessage: ''
})
setTimeout(function () {
that.setData({
toView: that.data.chatMsg[that.data.chatMsg.length - 1].mid
})
}, 100)
}
})
that.setData({
userMessage: ''
})
}
},

// 环信demo 收到消息
receiveMsg: function (msg, type) {
var that = this
var myName = wx.getStorageSync('myUsername')
if (msg.from == that.data.yourname || msg.to == that.data.yourname) {
if (type == 'txt') {
var value = WebIM.parseEmoji(msg.data.replace(/\n/mg, ''))
} else if (type == 'emoji') {
var value = msg.data
} else if(type == 'audio'){
// 如果是音频则请求服务器转码
console.log('Audio Audio msg: ', msg);
var token = msg.accessToken;
console.log('get token: ', token)
var options = {
url: msg.url,
header: {
'X-Requested-With': 'XMLHttpRequest',
'Accept': 'audio/mp3',
'Authorization': 'Bearer ' + token
},
success: function(res){
console.log('downloadFile success Play', res);
// wx.playVoice({
// filePath: res.tempFilePath
// })
msg.url = res.tempFilePath
var msgData = {
info: {
from: msg.from,
to: msg.to
},
username: '',
yourname: msg.from,
msg: {
type: type,
data: value,
url: msg.url
},
style: '',
time: time,
mid: msg.type + msg.id
}

if (msg.from == that.data.yourname) {
msgData.style = ''
msgData.username = msg.from
} else {
msgData.style = 'self'
msgData.username = msg.to
}

var msgArr = that.data.chatMsg;
msgArr.pop();
msgArr.push(msgData);

that.setData({
chatMsg: that.data.chatMsg,
})
console.log("New audio");
},
fail: function(e){
console.log('downloadFile failed', e);
}
};
console.log('Download');
wx.downloadFile(options);
}
//console.log(msg)
//console.log(value)
var time = WebIM.time()
var msgData = {
info: {
from: msg.from,
to: msg.to
},
username: '',
yourname: msg.from,
msg: {
type: type,
data: value,
url: msg.url
},
style: '',
time: time,
mid: msg.type + msg.id
}
console.log('Audio Audio msgData: ', msgData);
if (msg.from == that.data.yourname) {
msgData.style = ''
msgData.username = msg.from
} else {
msgData.style = 'self'
msgData.username = msg.to
}
//console.log(msgData, that.data.chatMsg, that.data)
that.data.chatMsg.push(msgData)

// 存储聊天记录
// 注: 单独单聊天 key 对方环信uin+自己的uin
// 注: 群组聊天 key 群组id\聊天室id+对方环信uin+自己的uin

wx.setStorage({
key: that.data.yourname + myName,
data: that.data.chatMsg,
success: function () {
if(type == 'audio')
return;
//console.log('success', that.data)
that.setData({
chatMsg: that.data.chatMsg,
})
setTimeout(function () {
that.setData({
toView: that.data.chatMsg[that.data.chatMsg.length - 1].mid
})
}, 100)
}
})
}
},
环信聊天页面,聊天数据全部存储在缓存当中,跟进聊天类型的不同,主要需要调整缓存的key。详情如下:
单对单聊天 对方uin+自己的uin
群组聊天(针对某个商品,不需要好友关系,只需要临时聊天) 群组id+对方uin+自己的uin
聊天室(同群组聊天)
问题大杂烩
群组聊天缓存如何存储?
答: 缓存key 设置为 群组id+对方uin+自己的uin
聊天时,如何在聊天中携带扩展信息
答: 消息内容中,ext 支持用户自定义参数传递var option = {
msg: data.userMessage.trim(), // 消息内容
to: data.groupId, // 接收消息对象(聊天室id)
roomType: true,
chatType: 'groupRoom',
from: data.myuin,
ext: {
//todo 需要补充的字符哦
},
success: function () {
console.log('send room text success');
},
fail: function () {
console.log('failed');
}
};
```会话列表如何实现?
答: 通过接口获取环信的群组列表,通过自己的服务器端补全对应的会话信息。
回顾
整个环信接入,整体围绕 假设-->猜想-->实践完成的。仔细阅读官网,会为大家节约很多时间
作者:贾慧斌
文章来源:简书

  查看全部
拍拍二手闲置平台,可以将自己的闲置物品进行转让或者捐赠。想和卖家达成共识就需要涉及IM聊天。拍拍二手闲置平台目前接入的是环信IM聊天。下面我将从三个阶段带大家玩转环信IM会话。
前期
初识IM聊天
带着问题去调研

必须接入环信吗?除了环信是否可以接入其他即时通信?
环信目前有哪些功能呢?支持微信小程序吗?
如何接入小程序呢?
调研分析

必须接入环信吗?除了环信是否可以接入其他即时通信?
现状: 微信小程序API 提供了WebSocket 方法。
扩展: 如果服务端支持scoket通信,ios\android\H5 也全都支持Im聊天了
备注:专业第三方Im有融云、环信、云之讯等,底层实现均是基于scoket 通信。明白scoket通信后也可以自己写即时通信。
环信目前有哪些功能呢?支持微信小程序吗?
错误想法: 环信就是做im聊天的,咱们上去按照接入文档,开发就能搞定!!!
这种想法是很致命的。在所有的第三方组件接入中,如果我们不能跳出来看待问题,只是为了完成任务而完成任务。那么我们永远是最底层的低级码农。
环信目前是同行业里面做的算不错的。那么他的官网、接入规范都应该有的。微信小程序也是支持的。在后面小编会带领大家一切怎么去阅读一个官网
如何接入小程序?
接入小程序是否需要申请一个账号呢?我直接运行他们的demo可以吗? 怎么去测试呢? 此时我们可以有很多的猜想。我认为在开始接入之前我们应该很好的进行一些思考,答案显而易见。
环信接入思考篇
快即时慢

在工作中,大家会经常遇到第三方组件的接入。当接收到任务后,为了尽快完成任务。上来就google,找攻略,找技巧。往往认为这样做速度是最快的。结果适得其反,做了很多无用的功。我们意识中的快,结果却变成了慢
慢即时快image逆向思维: 任何一个第三方的组件,特别是一个大点的平台,他们为了推出自己的产品,一定会有各种各样的功能支持,接入文档说明。我们放慢速度,将这些资源用上半天的时间进行简单的梳理。后期的开发进度会有很大的提升。
上图是我在接入环信Im后进行的反思。因为在接入环信之前,其他团队成员用了很长的时间联调。假如他们在接入环信聊天之前,了解环信拥有自己的后台,可以直接给用户端发送测试消息;可以直接创建用户、创建聊天室、创建群组。他们还会花费那么久的时间去联调吗?完全不用依赖服务端。不用依赖ios,依赖android。自己使用环信后台,轻轻松松完成各种测试。
环信接入
环信官网注册自己的即时通讯云,并登陆后台

1.jpg


创建自己的应用,并记录关键信息

2.jpg


以下是关键信息哦!!!

3.jpg


备注:
应用标识 应用接入时会使用
IM 用户 可以创建、删除用户、发送消息
群组 可以创建、删除群组信息、发送消息
聊天室 可以创建、删除聊天室、发送消息
tip 通过这个后台管理系统,就可以玩转环信的接入测试了。
从环信下载小程序demo,替换 appkey 进行联调测试

4.jpg


测试走起
用户测试 
在环信后台创建用户,在小程序端登录 (用户demo1 密码:123456)
5.jpg


一对一会话测试
① 在环信后台创建用户demo2
② 点击操作,查看用户好友将demo1和demo2 添加为好友。
③ 在小程序端用demo1给demo2发送测试消息。
④ 退出demo1用户,登录demo2查看是否会接收到demo1发送的会话

6.jpg


由于环信工程师们相信码农的实力,在群组测试和聊天室测试这块为大家留下了想象空间。demo 中群组测试和聊天室测试为明确写出。让我继续带大家飞

群组测试
① 创建群组记录群组id,并给群组添加成员(demo2)

7.jpg


② 环信后台给群组发送测试消息

8.jpg


③ 控制台能收到群组测试消息,怎么展示呢? 请阅读源码解析篇
聊天室测试
① 创建聊天室记录聊天室id,将demo1 设置为超级管理员,demo2设置为管理员
② 聊天室这里没有聊天室消息的发送。请阅读源码解析篇
通过以上4个简单的测试,android、ios、h5、小程序的聊天测试均可以参照以上4点进行顺利的测试。初期就此结束。下面带代价进行源码的解析
中期
看源码前期思考

10.jpg

 
核心源码阅读

11.jpg

 
以上是环信sdk 基础代码结构。 通过简单阅读会发现:环信的scoket 通信也使用了微信小程序暴露的scoket 通信 (猜想 android、ios 其他端也有对应的scoket通信)
环信的api包装在connection.js 组件中,如果某些api没有,咱们可以扩展connection 中的方法
环信核心代码阅读完成后,发现没有涉及到缓存。看来缓存的处理是在对应的业务逻辑中。
设想:
消息应该在哪里缓存
哪里进行会话链接的监听注册
环信demo 代码阅读
会话、群组
通过前面提到的方式,大家可以在小程序控制台抓取到用户收到的会话和群组消息
会话
app.js
环信scoket 注册监听代码在app.js 中
核心代码如下:
{
//调用API从本地缓存中获取数据
var that = this
var logs = wx.getStorageSync('logs') || []
logs.unshift(Date.now())
wx.setStorageSync('logs', logs)

WebIM.conn.listen({
onOpened: function (message) {//连接成功回调
// 如果isAutoLogin设置为false,那么必须手动设置上线,否则无法收消息
// 手动上线指的是调用conn.setPresence(); 如果conn初始化时已将isAutoLogin设置为true
// 则无需调用conn.setPresence();
WebIM.conn.setPresence()
},
onPresence: function (message) { //处理“广播”或“发布-订阅”消息,如联系人订阅请求、处理群组、聊天室被踢解散等消息
switch(message.type){
case "unsubscribe":
pages[0].moveFriend(message);
break;
case "subscribe":
if (message.status === '[resp:true]') {
return
} else {
pages[0].handleFriendMsg(message)
}
break;
case "joinChatRoomSuccess":
console.log('Message: ', message);
wx.showToast({
title: "JoinChatRoomSuccess",
});
break;
case "memberJoinChatRoomSuccess":
console.log('memberMessage: ', message);
wx.showToast({
title: "memberJoinChatRoomSuccess",
});
break;
case "memberLeaveChatRoomSuccess":
console.log("LeaveChatRoom");
wx.showToast({
title: "leaveChatRoomSuccess",
});
break;
}
},
onRoster: function (message) { //处理好友申请
var pages = getCurrentPages()
if (pages[0]) {
pages[0].onShow()
}
},

onVideoMessage: function(message){ //视频处理
console.log('onVideoMessage: ', message);
var page = that.getRoomPage()
if (message) {
if (page) {
page.receiveVideo(message, 'video')
} else {
var chatMsg = that.globalData.chatMsg || []
var time = WebIM.time()
var msgData = {
info: {
from: message.from,
to: message.to
},
username: message.from,
yourname: message.from,
msg: {
type: 'video',
data: message.url
},
style: '',
time: time,
mid: 'video' + message.id
}
msgData.style = ''
chatMsg = wx.getStorageSync(msgData.yourname + message.to) || []
chatMsg.push(msgData)
wx.setStorage({
key: msgData.yourname + message.to,
data: chatMsg,
success: function () {
//console.log('success')
}
})
}
}
},

onAudioMessage: function (message) { // 音频处理
console.log('onAudioMessage', message)
var page = that.getRoomPage()
console.log(page)
if (message) {
if (page) {
page.receiveMsg(message, 'audio')
} else {
var chatMsg = that.globalData.chatMsg || []
var value = WebIM.parseEmoji(message.data.replace(/\n/mg, ''))
var time = WebIM.time()
var msgData = {
info: {
from: message.from,
to: message.to
},
username: message.from,
yourname: message.from,
msg: {
type: 'audio',
data: value
},
style: '',
time: time,
mid: 'audio' + message.id
}
console.log("Audio msgData: ", msgData);
chatMsg = wx.getStorageSync(msgData.yourname + message.to) || []
chatMsg.push(msgData)
wx.setStorage({
key: msgData.yourname + message.to,
data: chatMsg,
success: function () {
//console.log('success')
}
})
}
}
},

onLocationMessage: function (message) { // 收到位置信息
console.log("Location message: ", message);
},

onTextMessage: function (message) {//收到文本消息
var page = that.getRoomPage()
console.log(page)
if (message) {
if (page) {
page.receiveMsg(message, 'txt')
} else {
var chatMsg = that.globalData.chatMsg || []
var value = WebIM.parseEmoji(message.data.replace(/\n/mg, ''))
var time = WebIM.time()
var msgData = {
info: {
from: message.from,
to: message.to
},
username: message.from,
yourname: message.from,
msg: {
type: 'txt',
data: value
},
style: '',
time: time,
mid: 'txt' + message.id
}
chatMsg = wx.getStorageSync(msgData.yourname + message.to) || []
chatMsg.push(msgData)
wx.setStorage({
key: msgData.yourname + message.to,
data: chatMsg,
success: function () {
//console.log('success')
}
})
}
}
},
onEmojiMessage: function (message) { //收到表情信息
//console.log('onEmojiMessage',message)
var page = that.getRoomPage()
//console.log(pages)
if (message) {
if (page) {
page.receiveMsg(message, 'emoji')
} else {
var chatMsg = that.globalData.chatMsg || []
var time = WebIM.time()
var msgData = {
info: {
from: message.from,
to: message.to
},
username: message.from,
yourname: message.from,
msg: {
type: 'emoji',
data: message.data
},
style: '',
time: time,
mid: 'emoji' + message.id
}
msgData.style = ''
chatMsg = wx.getStorageSync(msgData.yourname + message.to) || [] //tip 从本地缓存中获取用户的消息 发消息+来源 适用于单人会话 msgData.yourname + message.to+当前登录人 群组/聊天室

chatMsg.push(msgData)
//console.log(chatMsg)
wx.setStorage({
key: msgData.yourname + message.to,
data: chatMsg,
success: function () {
//console.log('success')
}
})
}
}
},
onPictureMessage: function (message) {//收到图片信息
//console.log('Picture',message);
var page = that.getRoomPage()
if (message) {
if (page) {
//console.log("wdawdawdawdqwd")
page.receiveImage(message, 'img')
} else {
var chatMsg = that.globalData.chatMsg || []
var time = WebIM.time()
var msgData = {
info: {
from: message.from,
to: message.to
},
username: message.from,
yourname: message.from,
msg: {
type: 'img',
data: message.url
},
style: '',
time: time,
mid: 'img' + message.id
}
msgData.style = ''
chatMsg = wx.getStorageSync(msgData.yourname + message.to) || []
chatMsg.push(msgData)
wx.setStorage({
key: msgData.yourname + message.to,
data: chatMsg,
success: function () {
//console.log('success')
}
})
}
}
},
// 各种异常
onError: function (error) {
// 16: server-side close the websocket connection
if (error.type == WebIM.statusCode.WEBIM_CONNCTION_DISCONNECTED) {
if (WebIM.conn.autoReconnectNumTotal < WebIM.conn.autoReconnectNumMax) {
return;
}

wx.showToast({
title: 'server-side close the websocket connection',
duration: 1000
});
wx.redirectTo({
url: '../login/login'
});
return;
}

// 8: offline by multi login
if (error.type == WebIM.statusCode.WEBIM_CONNCTION_SERVER_ERROR) {
wx.showToast({
title: 'offline by multi login',
duration: 1000
})
wx.redirectTo({
url: '../login/login'
})
return;
}
},
})


}
实际开发过程中,在微信中,退出小程序,重新进入时,webscoket 通信并没有重新创建链接。存在用户收到不到消息的情况。可以将以上代码封装,例如addHXLIstener(...)。当用户重新打开后,再次注册环信监听即可。
拍拍二手闲置交易平台,主要集成的是文本聊天功能。
环信登录 例如 initLoginHX();
 var uin=wx.getStorageSync('hxuin');
var pwd=wx.getStorageSync('hxpwd');
console.log('initHX:' + uin+"||"+pwd);
var options = {
apiUrl: '服务器url',
user: '用户名',// 用户名要是字符
pwd: '密码',
grant_type: 'password',
appKey: 'appkey',
success: function (res) {
console.log("环信创建连接成功")
},
error: function (res) {
console.log("环信创建连接失败")
}
};
WebIM.conn.open(options);
chat 会话
环信的会话列表存储在本地,并没有调用服务器端数据
 var that = this
var member = wx.getStorageSync('member')
var myName = wx.getStorageSync('myUsername')
var array = []
for (var i = 0; i < member.length; i++) {
if (wx.getStorageSync(member[i].name + myName) != '') {
array.push(wx.getStorageSync(member[i].name + myName)[wx.getStorageSync(member[i].name + myName).length - 1])
}
}
//console.log(array,'1')
this.setData({
arr: array
})
通过以上代码得出结论: 环信的会话是通过遍历用户id+对方id 构成的数据。
那群组和聊天室的怎么处理呢?
环信小程序demo中只提供了聊天室列表的获取接口我们可以轻松实现聊天室列表,并没有提供群组列表的获取方式。我们需要在conection中扩展调用群组列表的接口,来实现群组列表。参照聊天室列表获取即可实现。聊天室列表实现方式如下:
connection.prototype.getChatRooms = function (options) {

var conn = this,
token = options.accessToken || this.context.accessToken;

if (token) {
var apiUrl = this.apiUrl;
var appName = this.context.appName;
var orgName = this.context.orgName;

if (!appName || !orgName) {
conn.onError({
type: _code.WEBIM_CONNCTION_AUTH_ERROR
});
return;
}

var suc = function (data, xhr) {
typeof options.success === 'function' && options.success(data);
};

var error = function (res, xhr, msg) {
if (res.error && res.error_description) {
conn.onError({
type: _code.WEBIM_CONNCTION_LOAD_CHATROOM_ERROR,
msg: res.error_description,
data: res,
xhr: xhr
});
}
};

var pageInfo = {
pagenum: parseInt(options.pagenum) || 1,
pagesize: parseInt(options.pagesize) || 20
};
// 想要实现群组列表,修改对应接口即可
var opts = {
url: apiUrl + '/' + orgName + '/' + appName + '/chatrooms',
dataType: 'json',
type: 'GET',
header: {'Authorization': 'Bearer ' + token},
data: pageInfo,
success: suc || _utils.emptyfn,
fail: error || _utils.emptyfn
};
wx.request(opts);
} else {
conn.onError({
type: _code.WEBIM_CONNCTION_TOKEN_NOT_ASSIGN_ERROR
});
}
chatroom
从本地缓存中获取聊天记录,并展示
// 环信demo 发送消息

sendMessage: function () {

if (!this.data.userMessage.trim()) return;


var that = this
// //console.log(that.data.userMessage)
// //console.log(that.data.sendInfo)
var myName = wx.getStorageSync('myUsername')
var id = WebIM.conn.getUniqueId();
var msg = new WebIM.message('txt', id);
msg.set({
msg: that.data.sendInfo,
to: that.data.yourname,
roomType: false,
success: function (id, serverMsgId) {
console.log('send text message success')
}
});
// //console.log(msg)
console.log("Sending textmessage")
msg.body.chatType = 'singleChat'; // 群组聊天 groupRoom
WebIM.conn.send(msg.body);
// 消息发送完成

if (msg) {
var value = WebIM.parseEmoji(msg.value.replace(/\n/mg, '')) // 环信表情处理
var time = WebIM.time()
var msgData = {
info: {
to: msg.body.to
},
username: that.data.myName,
yourname: msg.body.to,
msg: {
type: msg.type,
data: value
},
style: 'self',
time: time,
mid: msg.id
}
that.data.chatMsg.push(msgData)
// console.log(that.data.chatMsg)

// 存储聊天记录
// 注: 单独单聊天 key 对方环信uin+自己的uin
// 注: 群组聊天 key 群组id\聊天室id+对方环信uin+自己的uin
wx.setStorage({
key: that.data.yourname + myName,
data: that.data.chatMsg,
success: function () {
//console.log('success', that.data)
that.setData({
chatMsg: that.data.chatMsg,
emojiList: [],
inputMessage: ''
})
setTimeout(function () {
that.setData({
toView: that.data.chatMsg[that.data.chatMsg.length - 1].mid
})
}, 100)
}
})
that.setData({
userMessage: ''
})
}
},

// 环信demo 收到消息
receiveMsg: function (msg, type) {
var that = this
var myName = wx.getStorageSync('myUsername')
if (msg.from == that.data.yourname || msg.to == that.data.yourname) {
if (type == 'txt') {
var value = WebIM.parseEmoji(msg.data.replace(/\n/mg, ''))
} else if (type == 'emoji') {
var value = msg.data
} else if(type == 'audio'){
// 如果是音频则请求服务器转码
console.log('Audio Audio msg: ', msg);
var token = msg.accessToken;
console.log('get token: ', token)
var options = {
url: msg.url,
header: {
'X-Requested-With': 'XMLHttpRequest',
'Accept': 'audio/mp3',
'Authorization': 'Bearer ' + token
},
success: function(res){
console.log('downloadFile success Play', res);
// wx.playVoice({
// filePath: res.tempFilePath
// })
msg.url = res.tempFilePath
var msgData = {
info: {
from: msg.from,
to: msg.to
},
username: '',
yourname: msg.from,
msg: {
type: type,
data: value,
url: msg.url
},
style: '',
time: time,
mid: msg.type + msg.id
}

if (msg.from == that.data.yourname) {
msgData.style = ''
msgData.username = msg.from
} else {
msgData.style = 'self'
msgData.username = msg.to
}

var msgArr = that.data.chatMsg;
msgArr.pop();
msgArr.push(msgData);

that.setData({
chatMsg: that.data.chatMsg,
})
console.log("New audio");
},
fail: function(e){
console.log('downloadFile failed', e);
}
};
console.log('Download');
wx.downloadFile(options);
}
//console.log(msg)
//console.log(value)
var time = WebIM.time()
var msgData = {
info: {
from: msg.from,
to: msg.to
},
username: '',
yourname: msg.from,
msg: {
type: type,
data: value,
url: msg.url
},
style: '',
time: time,
mid: msg.type + msg.id
}
console.log('Audio Audio msgData: ', msgData);
if (msg.from == that.data.yourname) {
msgData.style = ''
msgData.username = msg.from
} else {
msgData.style = 'self'
msgData.username = msg.to
}
//console.log(msgData, that.data.chatMsg, that.data)
that.data.chatMsg.push(msgData)

// 存储聊天记录
// 注: 单独单聊天 key 对方环信uin+自己的uin
// 注: 群组聊天 key 群组id\聊天室id+对方环信uin+自己的uin

wx.setStorage({
key: that.data.yourname + myName,
data: that.data.chatMsg,
success: function () {
if(type == 'audio')
return;
//console.log('success', that.data)
that.setData({
chatMsg: that.data.chatMsg,
})
setTimeout(function () {
that.setData({
toView: that.data.chatMsg[that.data.chatMsg.length - 1].mid
})
}, 100)
}
})
}
},
环信聊天页面,聊天数据全部存储在缓存当中,跟进聊天类型的不同,主要需要调整缓存的key。详情如下:
单对单聊天 对方uin+自己的uin
群组聊天(针对某个商品,不需要好友关系,只需要临时聊天) 群组id+对方uin+自己的uin
聊天室(同群组聊天)
问题大杂烩
群组聊天缓存如何存储?
答: 缓存key 设置为 群组id+对方uin+自己的uin
聊天时,如何在聊天中携带扩展信息
答: 消息内容中,ext 支持用户自定义参数传递
var option = {
msg: data.userMessage.trim(), // 消息内容
to: data.groupId, // 接收消息对象(聊天室id)
roomType: true,
chatType: 'groupRoom',
from: data.myuin,
ext: {
//todo 需要补充的字符哦
},
success: function () {
console.log('send room text success');
},
fail: function () {
console.log('failed');
}
};
```
会话列表如何实现?
答: 通过接口获取环信的群组列表,通过自己的服务器端补全对应的会话信息。
回顾
整个环信接入,整体围绕 假设-->猜想-->实践完成的。仔细阅读官网,会为大家节约很多时间
作者:贾慧斌
文章来源:简书

 
3
回复

vue采用类似官方demo的方式集成,登陆后报错stropheConn.setJid is not a function vue

相当可以 回复了问题 • 3 人关注 • 4994 次浏览 • 2019-05-29 15:02 • 来自相关话题

0
评论

重磅开源!业内首个 React Native 转微信小程序引擎 react native

sunshine123 发表了文章 • 1122 次浏览 • 2019-05-29 10:27 • 来自相关话题

前言

Alita 是一套由京东 ARES 多端技术团队打造的 React Native 代码转换引擎工具。它对 React 语法有全新的处理方式,支持在运行时处理 React 语法,实现了 React Native 和微信小程序之间的主要组件对齐,可以用简洁、高效的方式把 React Native 代码转换成微信小程序代码。
Alita 不是一个新框架,不会有额外的学习成本,她只是一套转换引擎工具,可以把 React Native 扩展到微信小程序端,大大降低多终端上的业务开发成本。以后移动端开发者只需要掌握 React Native 技术栈,就可以轻松实现 Android、iOS、Windows、Web(已有开源项目支持)、微信小程序等多端渲染。
 
Alita 项目

开源地址: https://github.com/areslabs/alita
React Native 





 
微信小程序






Alita 具备哪些能力
Alita 的设计目标是要尽可能无损的转换 RN 应用,即使是已经存在的 RN 应用,我们也希望只做少量的修改就可以在微信小程序平台运行,所以这就要求 Alita 必须对 React 语法有足够的支持,包括 JSX 语法,React 生命周期等

JSX 语法

Alita 支持大部分 JSX 语法,这意味着什么呢?意味着你可以使用 React 自由的代码方式以及强大的组件化支持,意味着你可以延用自己的编程习惯,不需要对已有的 RN 代码进行过多修改。这主要得益于 Alita 是在运行时处理 JSX 语法,而不是现在社区上常见的编译时处理。

因此 Alita 没有诸如以下社区其他方案的限制:

JSX 只允许出现的组件的 render 方法中
不能通过 props 传递 JSX 片段或者返回 JSX 的函数
不支持在属性上传递函数

Alita 转换以下代码毫无压力:





生命周期

Alita 支持所有的 React 生命周期。微信小程序本身给组件提供了生命周期,但是这些生命周期在写法和调用上与 React 存在着一些的差异,另外 React 生命周期更加丰富。Alita 在支持 React 生命周期的时候,把它们分为了两类:

第一类: componentDidMount,componentDidUpdate,componentWillUnmount 这 3 个生命周期在微信小程序上有相应的触发时机,比如ready, detached,只需要在微信小程序相关回调触发的时候,调用 React 组件对应的方法即可。
另外一类,在微信小程序端没有直接对应的生命周期,对于这一类生命周期,主要是借助于 Alita 内部嵌入的 mini-react,触发相应的回调。通过这两种方式,Alita 实现了 React 生命周期的对齐。

此外,Alita 抹平了 RN 和微信小程序之间的事件及样式差异,能够无损得将 RN事件和样式传递到微信小程序中。

RN 基本组件和 API

RN 提供了很多基本的组件和 API,这些组件加上 React 开发方式,共同构成了 RN 应用。Alita 除了要对 React 语法进行处理,还必须在预先在微信小程序平台对齐出一套与 RN 等效的组件和 API。比如在 RN 端,请求网络的方式是通过 fetch 方式,但是微信小程序本身并不存在 fetch 方法,就这要求 Alita 必须基于微信小程序的网络 API,在微信小程序上实现一个 fetch 方法。 同样的以 RN 组件 FlatList 为例,当 Alita 把 RN 应用转化为微信小程序代码之后,FlatList 在微信小程序平台并不存在,需要预先在微信小程序平台实现小程序版本的 FlatList 。这个预先处理的过程,我们称之为对齐,对齐的过程包括组件,组件属性,API 等。





Redux

Redux 是 JavaScript 状态容器,提供可预测化的状态管理,并且易于测试,是当前 React 技术栈流行的数据层管理方案。得益于 Alita 运行阶段处理 React 逻辑的设计,Alita 支持将使用 Redux 的 RN 应用转换成微信小程序。

动画

动画是每一个 app 不可或缺的能力,RN 和微信小程序的动画实现差异很大,RN 的动画能力要强于微信小程序,想要完全把 RN 的动画转化至微信小程序的是不可能的。为此我们封装了一套动画组件库,这一套动画组件库涵盖了所有微信小程序的动画能力,所有使用此动画库开发的动画,都可以无损转化到小程序端。
React Native





微信小程序





Alita 原理简介

那么 Alita 是如何将 RN 转换运行在微信小程序上的呢?我们不打算在这篇文章深入剖析,简单从编译阶段和运行阶段来说明。

编译阶段:我们通过静态分析 RN 源码,将其转换为微信小程序可以识别的代码,首先我们会将 JSX 语法转换为微信小程序的 wxml 模块语法,RN 组件在这个阶段会被转换为微信小程序自定义组件,一般会产生微信小程序需要的 4 个文件 wxml, js,json 和 wxss。 此外,我们会保留一份 babel 转译之后的 RN 源码,这份代码里面所有的 JSX 都已经由 React.createElement 替换,运行阶段,会使用这个能被微信小程序的 JavaScript 运行环境识别的源码。

运行阶段:Alita 内部嵌入了一个 mini-react,这个 mini-react 在运行阶段会运行上文所说的转译后的 RN 源码,与 React 一样,递归(React Fiber 之后,不再是递给的方式)的处理组件树,调用组件的 render 方法,调用组件生命周期,计算 context 等。另外 React 在运行的过程中有一个重要的 reconciliation 算法(即 virtual-dom),mini-react 同样提供了简化版本的 reconciliation 来决定组件的销毁与复用。mini-react 执行完之后,最终会输出一个描述视图的数据结构,这份数据结构提供了微信小程序渲染所需要的所有数据。微信小程序通过桥接模块与 mini-react 通信,获取到这一份数据,通过 setData 的方式设置到微信小程序模版上,从而渲染出视图。





Alita 组件库

在项目开发中,仅仅使用 RN 基本组件和 API,是很难满足需要的。我们在使用 Alita 的过程中,积累了很多常用的三端组件,包括ScrollTabView,ViewPager,SegmentedControl等等,我们正在剥离和梳理这些组件,很快会发布兼容三端的 Alita 组件库。此组件库也是我们日后的工作重点之一,我们将会不断优化和扩展新组件。

除了 Alita 组件库,我们还提供了扩展方式,开发者可以很方便的把本团队的基本 UI 组件库扩展到微信小程序端,然后通过 Alita 把使用了这些组件的 RN 应用运行在微信小程序平台。





结语

我们将不断拓展 Alita 的能力,支持更多端能力,如:百度小程序、头条小程序等,继续完善开发者体验,提高开发者效率,帮助更多开发者。

我们也在考察 Flutter 这一新的跨端方案和微信小程序融合转化的可行性。

我们十分重视开源社区的反馈和建议,会不断从中汲取养分,让 Alita 变得更加强大。

意见反馈

如果有任何的意见或者建议,欢迎在 Github 创建 issue,感谢你的支持和贡献。
  查看全部
前言

Alita 是一套由京东 ARES 多端技术团队打造的 React Native 代码转换引擎工具。它对 React 语法有全新的处理方式,支持在运行时处理 React 语法,实现了 React Native 和微信小程序之间的主要组件对齐,可以用简洁、高效的方式把 React Native 代码转换成微信小程序代码。
Alita 不是一个新框架,不会有额外的学习成本,她只是一套转换引擎工具,可以把 React Native 扩展到微信小程序端,大大降低多终端上的业务开发成本。以后移动端开发者只需要掌握 React Native 技术栈,就可以轻松实现 Android、iOS、Windows、Web(已有开源项目支持)、微信小程序等多端渲染。
 
Alita 项目

开源地址: https://github.com/areslabs/alita
React Native 

11.gif

 
微信小程序

1.gif


Alita 具备哪些能力
Alita 的设计目标是要尽可能无损的转换 RN 应用,即使是已经存在的 RN 应用,我们也希望只做少量的修改就可以在微信小程序平台运行,所以这就要求 Alita 必须对 React 语法有足够的支持,包括 JSX 语法,React 生命周期等

JSX 语法

Alita 支持大部分 JSX 语法,这意味着什么呢?意味着你可以使用 React 自由的代码方式以及强大的组件化支持,意味着你可以延用自己的编程习惯,不需要对已有的 RN 代码进行过多修改。这主要得益于 Alita 是在运行时处理 JSX 语法,而不是现在社区上常见的编译时处理。

因此 Alita 没有诸如以下社区其他方案的限制:

JSX 只允许出现的组件的 render 方法中
不能通过 props 传递 JSX 片段或者返回 JSX 的函数
不支持在属性上传递函数

Alita 转换以下代码毫无压力:

2.jpg

生命周期

Alita 支持所有的 React 生命周期。微信小程序本身给组件提供了生命周期,但是这些生命周期在写法和调用上与 React 存在着一些的差异,另外 React 生命周期更加丰富。Alita 在支持 React 生命周期的时候,把它们分为了两类:

第一类: componentDidMount,componentDidUpdate,componentWillUnmount 这 3 个生命周期在微信小程序上有相应的触发时机,比如ready, detached,只需要在微信小程序相关回调触发的时候,调用 React 组件对应的方法即可。
另外一类,在微信小程序端没有直接对应的生命周期,对于这一类生命周期,主要是借助于 Alita 内部嵌入的 mini-react,触发相应的回调。通过这两种方式,Alita 实现了 React 生命周期的对齐。

此外,Alita 抹平了 RN 和微信小程序之间的事件及样式差异,能够无损得将 RN事件和样式传递到微信小程序中。

RN 基本组件和 API

RN 提供了很多基本的组件和 API,这些组件加上 React 开发方式,共同构成了 RN 应用。Alita 除了要对 React 语法进行处理,还必须在预先在微信小程序平台对齐出一套与 RN 等效的组件和 API。比如在 RN 端,请求网络的方式是通过 fetch 方式,但是微信小程序本身并不存在 fetch 方法,就这要求 Alita 必须基于微信小程序的网络 API,在微信小程序上实现一个 fetch 方法。 同样的以 RN 组件 FlatList 为例,当 Alita 把 RN 应用转化为微信小程序代码之后,FlatList 在微信小程序平台并不存在,需要预先在微信小程序平台实现小程序版本的 FlatList 。这个预先处理的过程,我们称之为对齐,对齐的过程包括组件,组件属性,API 等。

3.jpg

Redux

Redux 是 JavaScript 状态容器,提供可预测化的状态管理,并且易于测试,是当前 React 技术栈流行的数据层管理方案。得益于 Alita 运行阶段处理 React 逻辑的设计,Alita 支持将使用 Redux 的 RN 应用转换成微信小程序。

动画

动画是每一个 app 不可或缺的能力,RN 和微信小程序的动画实现差异很大,RN 的动画能力要强于微信小程序,想要完全把 RN 的动画转化至微信小程序的是不可能的。为此我们封装了一套动画组件库,这一套动画组件库涵盖了所有微信小程序的动画能力,所有使用此动画库开发的动画,都可以无损转化到小程序端。
React Native

5.gif

微信小程序

6.gif

Alita 原理简介

那么 Alita 是如何将 RN 转换运行在微信小程序上的呢?我们不打算在这篇文章深入剖析,简单从编译阶段和运行阶段来说明。

编译阶段:我们通过静态分析 RN 源码,将其转换为微信小程序可以识别的代码,首先我们会将 JSX 语法转换为微信小程序的 wxml 模块语法,RN 组件在这个阶段会被转换为微信小程序自定义组件,一般会产生微信小程序需要的 4 个文件 wxml, js,json 和 wxss。 此外,我们会保留一份 babel 转译之后的 RN 源码,这份代码里面所有的 JSX 都已经由 React.createElement 替换,运行阶段,会使用这个能被微信小程序的 JavaScript 运行环境识别的源码。

运行阶段:Alita 内部嵌入了一个 mini-react,这个 mini-react 在运行阶段会运行上文所说的转译后的 RN 源码,与 React 一样,递归(React Fiber 之后,不再是递给的方式)的处理组件树,调用组件的 render 方法,调用组件生命周期,计算 context 等。另外 React 在运行的过程中有一个重要的 reconciliation 算法(即 virtual-dom),mini-react 同样提供了简化版本的 reconciliation 来决定组件的销毁与复用。mini-react 执行完之后,最终会输出一个描述视图的数据结构,这份数据结构提供了微信小程序渲染所需要的所有数据。微信小程序通过桥接模块与 mini-react 通信,获取到这一份数据,通过 setData 的方式设置到微信小程序模版上,从而渲染出视图。

7.jpg

Alita 组件库

在项目开发中,仅仅使用 RN 基本组件和 API,是很难满足需要的。我们在使用 Alita 的过程中,积累了很多常用的三端组件,包括ScrollTabView,ViewPager,SegmentedControl等等,我们正在剥离和梳理这些组件,很快会发布兼容三端的 Alita 组件库。此组件库也是我们日后的工作重点之一,我们将会不断优化和扩展新组件。

除了 Alita 组件库,我们还提供了扩展方式,开发者可以很方便的把本团队的基本 UI 组件库扩展到微信小程序端,然后通过 Alita 把使用了这些组件的 RN 应用运行在微信小程序平台。

8.jpg

结语

我们将不断拓展 Alita 的能力,支持更多端能力,如:百度小程序、头条小程序等,继续完善开发者体验,提高开发者效率,帮助更多开发者。

我们也在考察 Flutter 这一新的跨端方案和微信小程序融合转化的可行性。

我们十分重视开源社区的反馈和建议,会不断从中汲取养分,让 Alita 变得更加强大。

意见反馈

如果有任何的意见或者建议,欢迎在 Github 创建 issue,感谢你的支持和贡献。
 
4
回复

环信Android 即时通信与友盟推送与个推冲突 有专职工程师值守 环信_Android

催眠術 回复了问题 • 4 人关注 • 4316 次浏览 • 2019-05-28 10:33 • 来自相关话题

3
回复

android在使用环信客服的时候重新打开app之后自己的聊天记录消失了 环信_Android 聊天记录丢失

沙漏 回复了问题 • 2 人关注 • 1501 次浏览 • 2019-05-24 09:52 • 来自相关话题