hg3399.com|官方网站助力2019 中国开源年会(COSCon'19)顺利启航!


微信图片_20190909152905.jpg

?
COSCon '19正式启动啦!时间:?2019-11-02 09:00 ~ 11-03 17:00
地址:?上海普陀区上海普陀区中山北路3663号华东师范大学(中北校区)
业界最具影响力的开源年度盛会2019中国开源年会 ( COSCon'19 )将于?11月2-3日在华东师范大学 (上海普陀区中山北路校区)由开源社举办。
我们预期会有超过1600人现场参与这次盛会,还会有超过1万名在线的观众,热切围观。感谢许多社区伙伴、企业伙伴和志愿者,携手促使这样规模的 COSCon ?诞生。
本次大会的主题是“开源无疆、携手出航”(Let’s Cross the Boundaries Together!),这也代表我们对于中国开源,走向世界,走向辉煌的殷切期望。
本次大会将持续两天,我们策划的主题包括:开源软件、开源硬件、社区运营与治理、开源教育等方向。也特别希望听到各种自由/开源相关的成功故事和酸甜苦辣的经历。我们诚挚地邀请您的参与!
?
大会亮点

活动亮点.jpg

?
活动报名链接:https://www.bagevent.com/event/5744455?
?
继续阅读 ?

微信图片_20190909152905.jpg

?
COSCon '19正式启动啦!时间:?2019-11-02 09:00 ~ 11-03 17:00
地址:?上海普陀区上海普陀区中山北路3663号华东师范大学(中北校区)
业界最具影响力的开源年度盛会2019中国开源年会 ( COSCon'19 )将于?11月2-3日在华东师范大学 (上海普陀区中山北路校区)由开源社举办。
我们预期会有超过1600人现场参与这次盛会,还会有超过1万名在线的观众,热切围观。感谢许多社区伙伴、企业伙伴和志愿者,携手促使这样规模的 COSCon ?诞生。
本次大会的主题是“开源无疆、携手出航”(Let’s Cross the Boundaries Together!),这也代表我们对于中国开源,走向世界,走向辉煌的殷切期望。
本次大会将持续两天,我们策划的主题包括:开源软件、开源硬件、社区运营与治理、开源教育等方向。也特别希望听到各种自由/开源相关的成功故事和酸甜苦辣的经历。我们诚挚地邀请您的参与!
?
大会亮点

活动亮点.jpg

?
活动报名链接:https://www.bagevent.com/event/5744455?
? 收起阅读 ?

hg3399.com

2019年是5G商用元年,作为第五代通信技术,未来将结合云计算、人工智能、物联网等技术逐渐改变亿万用户的生活消费方式,带来万亿级的产业空间。Gartner预测到2020年,7%的全球通信服务提供商将拥有商业上可行的无线5G服务,2023年5G智能手机将占总销量的51%,谁先拥抱5G谁就能在万亿级的新产业空间里拔得头筹。
当下,短视频、互动直播等音视频应用火爆来袭,抖音、快手等已成为当红流量入口,当移动互联红利消失殆尽,各行业获客成本不断攀升的态势下,5G催生的音视频应用风口已成为兵家必争之地,让各条赛道又迎来弯道超车的好机会。
?同时,音视频应用正在加快与人工智能、5G信息显示等领域的融合,不断催生新业态和商业模式,但对创业者和开发者来说还是挑战诸多:1,5G时代如何选择一家靠谱的云基础设施资源服务厂商?音视频领域有哪些技术要点和哪些典型应用?2,5G时代音视频社交领域有哪些新玩法?3,5G创业如何实现从0—1的快速冷启动?4,产品研发上线后该如何科学运营,拉新、获客、留存,洞悉市场和用户行为实现快速增长……
?
9月21日,坐标北京,中关村创业大街,来这里听一场就够了!
?
hg3399.com|官方网站联合华为举办首届《5G音视频开发创业沙龙》,给创业者们带来5G音视频方面的最新最佳技术实践,分享在5G音视频领域创业,开发、运营、安全、部署等一揽子解决方案,深入解读5G音视频火热背后的技术奥秘和新增长机会。

5G音视频长图.jpg

?

微信二维码.jpg

?
扫码进入 hg3399.com|官方网站&华为@5G沙龙交流群
?
报名链接:http://hdxu.cn/sJ8fm?
?
继续阅读 ?
2019年是5G商用元年,作为第五代通信技术,未来将结合云计算、人工智能、物联网等技术逐渐改变亿万用户的生活消费方式,带来万亿级的产业空间。Gartner预测到2020年,7%的全球通信服务提供商将拥有商业上可行的无线5G服务,2023年5G智能手机将占总销量的51%,谁先拥抱5G谁就能在万亿级的新产业空间里拔得头筹。
当下,短视频、互动直播等音视频应用火爆来袭,抖音、快手等已成为当红流量入口,当移动互联红利消失殆尽,各行业获客成本不断攀升的态势下,5G催生的音视频应用风口已成为兵家必争之地,让各条赛道又迎来弯道超车的好机会。
?同时,音视频应用正在加快与人工智能、5G信息显示等领域的融合,不断催生新业态和商业模式,但对创业者和开发者来说还是挑战诸多:1,5G时代如何选择一家靠谱的云基础设施资源服务厂商?音视频领域有哪些技术要点和哪些典型应用?2,5G时代音视频社交领域有哪些新玩法?3,5G创业如何实现从0—1的快速冷启动?4,产品研发上线后该如何科学运营,拉新、获客、留存,洞悉市场和用户行为实现快速增长……
?
9月21日,坐标北京,中关村创业大街,来这里听一场就够了!
?
hg3399.com|官方网站联合华为举办首届《5G音视频开发创业沙龙》,给创业者们带来5G音视频方面的最新最佳技术实践,分享在5G音视频领域创业,开发、运营、安全、部署等一揽子解决方案,深入解读5G音视频火热背后的技术奥秘和新增长机会。

5G音视频长图.jpg

?

微信二维码.jpg

?
扫码进入 hg3399.com|官方网站&华为@5G沙龙交流群
?
报名链接:http://hdxu.cn/sJ8fm?
? 收起阅读 ?

new

  1. 修改聊天界面titlebar 标题

看LoginActivity intent跳转传参? setTitleName() 这个参数就是title
// 进入主页面
Intent intent = new IntentBuilder(LoginActivity.this) .setTargetClass(ChatActivity.class) .setVisitorInfo(DemoMessageHelper.createVisitorInfo()) .setServiceIMNumber(Preferences.getInstance().getCustomerAccount()) .setScheduleQueue(DemoMessageHelper.createQueueIdentity(queueName)) .setTitleName(titleName) // .setScheduleAgent(DemoMessageHelper.createAgentIdentity("ceshiok1@qq.com")) .setShowUserNick(true) .setBundle(bundle) .build();
startActivity(intent);??????? 2. 设置显示客服头像和昵称
????? (1)首先客服系统 切换到管理员模式--设置--系统开关? 访客端显示客服昵称开关打开。
????? (2)参考DemoHelpe类中,setEaseUIProvider方法,消息判断为接收方receive中的代码,客服发送的消息,从消息扩展可以获取到坐席信息。
?? 参考文档:http://docs.easemob.com/cs/300visitoraccess/extended-message-format#%E6%98%BE%E7%A4%BA%E5%AE%A2%E6%9C%8D%E5%A4%B4%E5%83%8F%E5%92%8C%E6%98%B5%E7%A7%B0
???????? 3.设置显示访客头像:
?????????? (1) 找到DemoHelper 类中,setEaseUIProvider 方法,消息判断为发送方send的代码,可以设置访客头像
?????????? (2)需要修改kefueaseui 库中ChatRow类的代码,如果远程依赖无法修改,建议先使用本地依赖的方式
本地依赖下载地址http://www.easemob.com/download/cs?? 打开链接,页面底部找访客端SDK和Demo,Demo中找到kefueaseui库,手动导入,把之前远程依赖的注释掉,重新添加本地依赖
????????? (3)ChatRow 类中找到setUpBaseView? 之后看对adapter的判断

chatrow.png

把框选位置条件删掉即可
???? 注意:kefueaseui 库,消息布局没有访客昵称控件,如果需要显示访客昵称数据,需要单独先给布局添加昵称控件。
???????????
?
继续阅读 ?
  1. 修改聊天界面titlebar 标题

看LoginActivity intent跳转传参? setTitleName() 这个参数就是title
// 进入主页面
Intent intent = new IntentBuilder(LoginActivity.this) .setTargetClass(ChatActivity.class) .setVisitorInfo(DemoMessageHelper.createVisitorInfo()) .setServiceIMNumber(Preferences.getInstance().getCustomerAccount()) .setScheduleQueue(DemoMessageHelper.createQueueIdentity(queueName)) .setTitleName(titleName) // .setScheduleAgent(DemoMessageHelper.createAgentIdentity("ceshiok1@qq.com")) .setShowUserNick(true) .setBundle(bundle) .build();
startActivity(intent);??????? 2. 设置显示客服头像和昵称
????? (1)首先客服系统 切换到管理员模式--设置--系统开关? 访客端显示客服昵称开关打开。
????? (2)参考DemoHelpe类中,setEaseUIProvider方法,消息判断为接收方receive中的代码,客服发送的消息,从消息扩展可以获取到坐席信息。
?? 参考文档:http://docs.easemob.com/cs/300visitoraccess/extended-message-format#%E6%98%BE%E7%A4%BA%E5%AE%A2%E6%9C%8D%E5%A4%B4%E5%83%8F%E5%92%8C%E6%98%B5%E7%A7%B0
???????? 3.设置显示访客头像:
?????????? (1) 找到DemoHelper 类中,setEaseUIProvider 方法,消息判断为发送方send的代码,可以设置访客头像
?????????? (2)需要修改kefueaseui 库中ChatRow类的代码,如果远程依赖无法修改,建议先使用本地依赖的方式
本地依赖下载地址http://www.easemob.com/download/cs?? 打开链接,页面底部找访客端SDK和Demo,Demo中找到kefueaseui库,手动导入,把之前远程依赖的注释掉,重新添加本地依赖
????????? (3)ChatRow 类中找到setUpBaseView? 之后看对adapter的判断

chatrow.png

把框选位置条件删掉即可
???? 注意:kefueaseui 库,消息布局没有访客昵称控件,如果需要显示访客昵称数据,需要单独先给布局添加昵称控件。
???????????
? 收起阅读 ?

技能组配置

客服系统技能组配置
?? 登录客服系统后,切换到管理员模式--成员管理 ---技能组?? 可以新建技能组,成员管理,可以管理客服(添加或者移除)
??
客服系统技能组配置
?? 登录客服系统后,切换到管理员模式--成员管理 ---技能组?? 可以新建技能组,成员管理,可以管理客服(添加或者移除)
??

iOS SDK 日志文件的导出

hg3399.com|官方网站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
继续阅读 ?
hg3399.com|官方网站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
收起阅读 ?

【源码下载】一款使用hg3399.com|官方网站实现的开源灵魂社交APP(含服务器)

#前言
近期,hg3399.com|官方网站热心开发者-穿裤衩闯天下使用hg3399.com|官方网站IM开发了一款实时聊天应用,包含简单的服务器端,现在正式开源给小伙伴们。感兴趣的同学可以一起搞一下哦,详细介绍请往下看。

猿匹配_logo_副本.png


??上代码
服务器:VMServer
客户端:VMMatch
?
?#VMMatch
猿匹配 —— 国内首个程序猿非严肃婚恋交友应用,让我们一言不合就来场匹配吧
?
#介绍#
首先说下中文名:为什么叫这个名字呢,因为这是一个程序猿(媛)之间匹配交流的应用啊其实这是一个使用hg3399.com|官方网站 IM 开发的一款开源聊天项目,涵盖了时下流行的一些聊天元素,同时已将 IM 功能封装为单独库,可以直接引用,方便使用
项目还处在初期阶段,还有许多功能需要实现,有兴趣的可以一起来
项目资源均来自于互联网,如果有侵权请联系我
?
?#下载体验
猿匹配 小米商店 审核中
猿匹配 Google Play
?
??#项目截图

1.png

2.png

3.png

4.png

5.png

6.png

??
?#开发环境
项目基本属于在最新的Android开发环境下开发,使用Java8的一些新特性,比如Lambda表达式,
然后项目已经适配Android6.x以上的动态权限适配,以及7.x的文件选择,和8.x的通知提醒等;
· Mac OS 10.14.4
· Android Studio 3.3.2
??#项目模块儿
本项目包含两部分:
一部分是项目主模块app,这部分主要包含了项目的业务逻辑,比如匹配、信息修改、设置等
另一部分是封装成library的vmim,这是为了方便大家引用到自己的项目中做的一步封装,不用再去复杂的复制代码和资源等,
只需要将vmim以module导入到自己的项目中就行了,具体使用方式参见项目app模块儿;
?
??#功能与 TODO
IM部分功能
· [x] 链接监听
· [x] 登录注册
· [x] 会话功能
? ? ? 。[x] 置顶
? ? ? 。[x] 标为未读
? ? ? 。[x] 删除与清空
? ? ? 。[x] 草稿功能
· [x] 消息功能
? ? ? 。[x] 下拉加载更多
? ? ? 。[x] 消息复制(仅文字类消息)
? ? ? 。[x] 消息删除
? ? ? 。[x] 文本+Emoji消息收发
? ? ? 。[x] 大表情消息收发
? ? ? 。[x] 图片消息
? ? ? ? ~[x] 查看大图
? ? ? ? ~[ ] 保存图片
? ? ? 。[x] 语音消息
? ? ? ? ~[x] 语音录制
? ? ? ? ~[x] 语音播放(可暂停,波形待优化)
? ? ? ? ~[x] 听筒和扬声器播放切换
? ? ? 。[x] 语音实时通话功能
? ? ? 。[x] 视频实时通话功能
? ? ? 。[x] 通话过程中的娱乐消息收发
? ? ? ? ~[x] 骰子
? ? ? ? ~[x] 石头剪刀布
? ? ? ? ~[x] 大表情
? ? ? 。[x] 昵称头像处理(通过回调实现)
App部分功能
· [x] 登录注册(包括业务逻辑和 IM 逻辑)
· [x] 匹配
? ? ? 。[x] 提交匹配信息
? ? ? 。[x] 拉取匹配信息
· [x] 聊天(这里直接加载 IM 模块儿)
· [x] 我的
? ? ? 。[x] 个人信息展示
? ? ? 。[x] 上传头像
? ? ? 。[x] 设置昵称
? ? ? 。[x] 设置签名
· [x] 设置
? ? ? 。[x] 个人信息设置
? ? ? 。[x] 通知提醒
? ? ? 。[x] 聊天
? ? ? 。[ ] 隐私(随业务部分一起完善)
? ? ? 。[ ] 通用(随业务部分一起完善)
? ? ? 。[ ] 帮助反馈(随业务部分一起完善)
? ? ? 。[x] 关于
? ? ? 。[x] 退出
· [ ] 社区
? ? ? 。[ ] 发布
? ? ? 。[ ] 评论
? ? ? 。[ ] 收藏
? ? ? 。[ ] 关注
发布功能
· [x] 多渠道打包
· [x] 签名配置
· [x] 开发与线上环境配置
· [x] 敏感信息保护
?
??#配置运行
1.首先复制config.default.gradle到config.gradle
2.配置下config.gradlehg3399.com|官方网站appkey以及bugly统计Id
3.正式打包需要配置下签名信息,同时将签名文件放置在项目根目录
?
??#参与贡献
如果你有什么好的想法,或者好的实现,可以通过下边的步骤参与进来,让我们一起把这个项目做得更好,欢迎参与
1.Fork本仓库
2.新建feature_xxx分支 (单独创建一个实现你自己想法的分支)
3.提交代码
4.新建Pull Request
5.等待我们的Review & Merge
?
?#关联项目
服务器端由nodejs实现,地址见这里 VMServer
?
??#VMServer
是为Android开源项目VMMatch项目(中文名猿匹配)实现的服务端
?
??#简介
这个项目包含两部分
· 根目录:服务逻辑及API接口实现
· client目录:前端界面,和服务器端代码端放置在同一仓库下(暂未实现)
?
?#使用
简单介绍下运行环境及部署方法
1.安装nodejs开发时使用的是v10.16.0版本
2.需要安装mongodb并启动,开发使用版本4.0.10
3.下载项目到服务器,可以下载压缩包,或者用git clone命令
4.复制config_default.js到config.js,可根据自己需要修改配置文件
5.安装依赖
npm install

6.全局安装pm2
npm install pm2 -g
?
7.运行 vmshell.sh
?
hg3399.com|官方网站冬冬_副本.jpg

扫码备注【开源项目】邀你加入hg3399.com|官方网站开源社群
?
转载自https://blog.melove.net/develop-open-source-im-match-and-server/?
?
继续阅读 ?
#前言
近期,hg3399.com|官方网站热心开发者-穿裤衩闯天下使用hg3399.com|官方网站IM开发了一款实时聊天应用,包含简单的服务器端,现在正式开源给小伙伴们。感兴趣的同学可以一起搞一下哦,详细介绍请往下看。

猿匹配_logo_副本.png


??上代码
服务器:VMServer
客户端:VMMatch
?
?#VMMatch
猿匹配 —— 国内首个程序猿非严肃婚恋交友应用,让我们一言不合就来场匹配吧
?
#介绍#
首先说下中文名:为什么叫这个名字呢,因为这是一个程序猿(媛)之间匹配交流的应用啊其实这是一个使用hg3399.com|官方网站 IM 开发的一款开源聊天项目,涵盖了时下流行的一些聊天元素,同时已将 IM 功能封装为单独库,可以直接引用,方便使用
项目还处在初期阶段,还有许多功能需要实现,有兴趣的可以一起来
项目资源均来自于互联网,如果有侵权请联系我
?
?#下载体验
猿匹配 小米商店 审核中
猿匹配 Google Play
?
??#项目截图

1.png

2.png

3.png

4.png

5.png

6.png

??
?#开发环境
项目基本属于在最新的Android开发环境下开发,使用Java8的一些新特性,比如Lambda表达式,
然后项目已经适配Android6.x以上的动态权限适配,以及7.x的文件选择,和8.x的通知提醒等;
· Mac OS 10.14.4
· Android Studio 3.3.2
??#项目模块儿
本项目包含两部分:
一部分是项目主模块app,这部分主要包含了项目的业务逻辑,比如匹配、信息修改、设置等
另一部分是封装成library的vmim,这是为了方便大家引用到自己的项目中做的一步封装,不用再去复杂的复制代码和资源等,
只需要将vmim以module导入到自己的项目中就行了,具体使用方式参见项目app模块儿;
?
??#功能与 TODO
IM部分功能
· [x] 链接监听
· [x] 登录注册
· [x] 会话功能
? ? ? 。[x] 置顶
? ? ? 。[x] 标为未读
? ? ? 。[x] 删除与清空
? ? ? 。[x] 草稿功能
· [x] 消息功能
? ? ? 。[x] 下拉加载更多
? ? ? 。[x] 消息复制(仅文字类消息)
? ? ? 。[x] 消息删除
? ? ? 。[x] 文本+Emoji消息收发
? ? ? 。[x] 大表情消息收发
? ? ? 。[x] 图片消息
? ? ? ? ~[x] 查看大图
? ? ? ? ~[ ] 保存图片
? ? ? 。[x] 语音消息
? ? ? ? ~[x] 语音录制
? ? ? ? ~[x] 语音播放(可暂停,波形待优化)
? ? ? ? ~[x] 听筒和扬声器播放切换
? ? ? 。[x] 语音实时通话功能
? ? ? 。[x] 视频实时通话功能
? ? ? 。[x] 通话过程中的娱乐消息收发
? ? ? ? ~[x] 骰子
? ? ? ? ~[x] 石头剪刀布
? ? ? ? ~[x] 大表情
? ? ? 。[x] 昵称头像处理(通过回调实现)
App部分功能
· [x] 登录注册(包括业务逻辑和 IM 逻辑)
· [x] 匹配
? ? ? 。[x] 提交匹配信息
? ? ? 。[x] 拉取匹配信息
· [x] 聊天(这里直接加载 IM 模块儿)
· [x] 我的
? ? ? 。[x] 个人信息展示
? ? ? 。[x] 上传头像
? ? ? 。[x] 设置昵称
? ? ? 。[x] 设置签名
· [x] 设置
? ? ? 。[x] 个人信息设置
? ? ? 。[x] 通知提醒
? ? ? 。[x] 聊天
? ? ? 。[ ] 隐私(随业务部分一起完善)
? ? ? 。[ ] 通用(随业务部分一起完善)
? ? ? 。[ ] 帮助反馈(随业务部分一起完善)
? ? ? 。[x] 关于
? ? ? 。[x] 退出
· [ ] 社区
? ? ? 。[ ] 发布
? ? ? 。[ ] 评论
? ? ? 。[ ] 收藏
? ? ? 。[ ] 关注
发布功能
· [x] 多渠道打包
· [x] 签名配置
· [x] 开发与线上环境配置
· [x] 敏感信息保护
?
??#配置运行
1.首先复制config.default.gradle到config.gradle
2.配置下config.gradlehg3399.com|官方网站appkey以及bugly统计Id
3.正式打包需要配置下签名信息,同时将签名文件放置在项目根目录
?
??#参与贡献
如果你有什么好的想法,或者好的实现,可以通过下边的步骤参与进来,让我们一起把这个项目做得更好,欢迎参与
1.Fork本仓库
2.新建feature_xxx分支 (单独创建一个实现你自己想法的分支)
3.提交代码
4.新建Pull Request
5.等待我们的Review & Merge
?
?#关联项目
服务器端由nodejs实现,地址见这里 VMServer
?
??#VMServer
是为Android开源项目VMMatch项目(中文名猿匹配)实现的服务端
?
??#简介
这个项目包含两部分
· 根目录:服务逻辑及API接口实现
· client目录:前端界面,和服务器端代码端放置在同一仓库下(暂未实现)
?
?#使用
简单介绍下运行环境及部署方法
1.安装nodejs开发时使用的是v10.16.0版本
2.需要安装mongodb并启动,开发使用版本4.0.10
3.下载项目到服务器,可以下载压缩包,或者用git clone命令
4.复制config_default.js到config.js,可根据自己需要修改配置文件
5.安装依赖
npm install

6.全局安装pm2
npm install pm2 -g
?
7.运行 vmshell.sh
?
hg3399.com|官方网站冬冬_副本.jpg

扫码备注【开源项目】邀你加入hg3399.com|官方网站开源社群
?
转载自https://blog.melove.net/develop-open-source-im-match-and-server/?
? 收起阅读 ?

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

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;
}
收起阅读 ?

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

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

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

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

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

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

《拍拍二手》微信小程序之hg3399.com|官方网站接入

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

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

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

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

1.jpg


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

2.jpg


以下是关键信息哦!!!

3.jpg


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

4.jpg


测试走起
用户测试?
在hg3399.com|官方网站后台创建用户,在小程序端登录 (用户demo1 密码:123456)
5.jpg


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

6.jpg


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

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

7.jpg


② hg3399.com|官方网站后台给群组发送测试消息

8.jpg


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

10.jpg

?
核心源码阅读

11.jpg

?
以上是hg3399.com|官方网站sdk 基础代码结构。 通过简单阅读会发现:hg3399.com|官方网站的scoket 通信也使用了微信小程序暴露的scoket 通信 (猜想 android、ios 其他端也有对应的scoket通信)
hg3399.com|官方网站的api包装在connection.js 组件中,如果某些api没有,咱们可以扩展connection 中的方法
hg3399.com|官方网站核心代码阅读完成后,发现没有涉及到缓存。看来缓存的处理是在对应的业务逻辑中。
设想:
消息应该在哪里缓存
哪里进行会话链接的监听注册
hg3399.com|官方网站demo 代码阅读
会话、群组
通过前面提到的方式,大家可以在小程序控制台抓取到用户收到的会话和群组消息
会话
app.js
hg3399.com|官方网站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(...)。当用户重新打开后,再次注册hg3399.com|官方网站监听即可。
拍拍二手闲置交易平台,主要集成的是文本聊天功能。
hg3399.com|官方网站登录 例如 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("hg3399.com|官方网站创建连接成功")
},
error: function (res) {
console.log("hg3399.com|官方网站创建连接失败")
}
};
WebIM.conn.open(options);
chat 会话
hg3399.com|官方网站的会话列表存储在本地,并没有调用服务器端数据
 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
})
通过以上代码得出结论: hg3399.com|官方网站的会话是通过遍历用户id+对方id 构成的数据。
那群组和聊天室的怎么处理呢?
hg3399.com|官方网站小程序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
从本地缓存中获取聊天记录,并展示
// hg3399.com|官方网站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, '')) // hg3399.com|官方网站表情处理
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 对方hg3399.com|官方网站uin+自己的uin
// 注: 群组聊天 key 群组id\聊天室id+对方hg3399.com|官方网站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: ''
})
}
},

// hg3399.com|官方网站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 对方hg3399.com|官方网站uin+自己的uin
// 注: 群组聊天 key 群组id\聊天室id+对方hg3399.com|官方网站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)
}
})
}
},
hg3399.com|官方网站聊天页面,聊天数据全部存储在缓存当中,跟进聊天类型的不同,主要需要调整缓存的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');
}
};
```
会话列表如何实现?
答: 通过接口获取hg3399.com|官方网站的群组列表,通过自己的服务器端补全对应的会话信息。
回顾
整个hg3399.com|官方网站接入,整体围绕 假设-->猜想-->实践完成的。仔细阅读官网,会为大家节约很多时间
作者:贾慧斌
文章来源:简书

?
继续阅读 ?
拍拍二手闲置平台,可以将自己的闲置物品进行转让或者捐赠。想和卖家达成共识就需要涉及IM聊天。拍拍二手闲置平台目前接入的是hg3399.com|官方网站IM聊天。下面我将从三个阶段带大家玩转hg3399.com|官方网站IM会话。
前期
初识IM聊天
带着问题去调研

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

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

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

1.jpg


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

2.jpg


以下是关键信息哦!!!

3.jpg


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

4.jpg


测试走起
用户测试?
在hg3399.com|官方网站后台创建用户,在小程序端登录 (用户demo1 密码:123456)
5.jpg


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

6.jpg


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

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

7.jpg


② hg3399.com|官方网站后台给群组发送测试消息

8.jpg


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

10.jpg

?
核心源码阅读

11.jpg

?
以上是hg3399.com|官方网站sdk 基础代码结构。 通过简单阅读会发现:hg3399.com|官方网站的scoket 通信也使用了微信小程序暴露的scoket 通信 (猜想 android、ios 其他端也有对应的scoket通信)
hg3399.com|官方网站的api包装在connection.js 组件中,如果某些api没有,咱们可以扩展connection 中的方法
hg3399.com|官方网站核心代码阅读完成后,发现没有涉及到缓存。看来缓存的处理是在对应的业务逻辑中。
设想:
消息应该在哪里缓存
哪里进行会话链接的监听注册
hg3399.com|官方网站demo 代码阅读
会话、群组
通过前面提到的方式,大家可以在小程序控制台抓取到用户收到的会话和群组消息
会话
app.js
hg3399.com|官方网站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(...)。当用户重新打开后,再次注册hg3399.com|官方网站监听即可。
拍拍二手闲置交易平台,主要集成的是文本聊天功能。
hg3399.com|官方网站登录 例如 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("hg3399.com|官方网站创建连接成功")
},
error: function (res) {
console.log("hg3399.com|官方网站创建连接失败")
}
};
WebIM.conn.open(options);
chat 会话
hg3399.com|官方网站的会话列表存储在本地,并没有调用服务器端数据
 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
})
通过以上代码得出结论: hg3399.com|官方网站的会话是通过遍历用户id+对方id 构成的数据。
那群组和聊天室的怎么处理呢?
hg3399.com|官方网站小程序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
从本地缓存中获取聊天记录,并展示
// hg3399.com|官方网站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, '')) // hg3399.com|官方网站表情处理
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 对方hg3399.com|官方网站uin+自己的uin
// 注: 群组聊天 key 群组id\聊天室id+对方hg3399.com|官方网站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: ''
})
}
},

// hg3399.com|官方网站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 对方hg3399.com|官方网站uin+自己的uin
// 注: 群组聊天 key 群组id\聊天室id+对方hg3399.com|官方网站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)
}
})
}
},
hg3399.com|官方网站聊天页面,聊天数据全部存储在缓存当中,跟进聊天类型的不同,主要需要调整缓存的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');
}
};
```
会话列表如何实现?
答: 通过接口获取hg3399.com|官方网站的群组列表,通过自己的服务器端补全对应的会话信息。
回顾
整个hg3399.com|官方网站接入,整体围绕 假设-->猜想-->实践完成的。仔细阅读官网,会为大家节约很多时间
作者:贾慧斌
文章来源:简书

? 收起阅读 ?

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

前言

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,感谢你的支持和贡献。
?
继续阅读 ?
前言

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,感谢你的支持和贡献。
? 收起阅读 ?

GitHub 开源跨平台神器 Electron 实践

认识 Electron

Electron是由GitHub开发,用HTML、CSS 和 JavaScript来构建跨平台桌面应用程序的一个开源库。Electron通过将Chromium和Node.js合并到同一个运行时环境中,并将其打包为Mac、Windows和Linux系统下的应用。Electron于2013年作为构建GitHub上可编程的文本编辑器Atom的框架而被开发出来。

这不意味着Electron是绑定了GUI库的JavaScript。相反,Electron使用Web页面作为它的GUI,所以你能把它看作成一个被JavaScript控制的,精简版的Chromium浏览器。

Electron的版本更新很频繁,基本保持在1周发布一个小版本,每季度发布一个大版本。除了稳定版外还有Beta版和Nightly(最新功能试用版),Chromium更新时,Electron也会跟着更新。

为什么选择Electron

如今的桌面应用软件基本都需要跨平台运行,类似于MFC、Duilib等技术都无法满足需求。当今的跨平台桌面应用软件开发以使用QT,Electron较多。

QT跨平台开发

Qt是一个跨平台C++图形用户界面应用程序开发框架。它既可以开发GUI程序,也可用于开发非GUI程序,比如控制台工具和服务器。作为使用C++语言开发的框架,他的优缺点十分明显。

优点:

运行效率高;

架构健壮,性能强大。

缺点:

开发周期长;

需要开发者具有C++编程能力;

QT是一款收费软件,如果不想缴费购买License,又想用QT开发商业(闭源)程序,必须遵守LGPL协议,开源使用了LGPL库的源代码。

Electron桌面软件开发

Electron最早用于开发GitHub上的可编程文本编辑器Atom,它是一个借助Node.js和Chromium, 利用HTML/CSS/JavaScript语言创建桌面应用的框架。与之类似的还有NW.js, 但是NW.js社区发展基本处于停滞状态,更新也较慢。

优点:

使用JavaScript语言作为开发语言,方便前端开发者轻松开发桌面应用,原C++/Java语言开发者,也可以很快入手开发;

方便调试,提供了浏览器的开发者工具,轻松断点调试;

丰富的Web前端UI资源,可以快速制作绚丽的界面;

快速构建,迭代开发。最复杂的底层浏览器部分Electron已经帮你搞定,你只需要负责上层界面及业务逻辑的开发。Electron还提供了热更新功能,只需加载更新模块,会自动帮你检查更新并后台下载;

崩溃日志报告。轻松收集崩溃日志,定位错误代码;

C++插件扩展;

代码开源。Electron是GitHub上的开源项目,开发者有疑问可以在GitHub社区(https://github.com/electron/electron)上直接提issue,高级开发者可以修改Electron底层代码,订制自己的Elcetron。

缺点:

打包文件太大。Electron毕竟是一个浏览器,最小的应用安装包也要几十兆大小;

无法代码加密。和Web开发类似,使用者可以在开发者工具看到应用的客户端代码,商业软件需要代码加密的可以选择重要功能在服务端实现,桌面应用请求,或使用Node文件实现;

运行耗资源。浏览器通病,Electron应用也是多进程系统,启动几个Electron应用还好,如果太多会造成机器卡顿;

不支持XP系统,Node.js并不支持XP系统。

综上,如果你想快速的开发出炫酷的桌面应用,而又对系统限制不大,建议你选择Electron,如果你是一个前端开发人员,又想制作桌面应用,建议你选择Electron。

创建一个简单的应用

环境安装

Electron应用本质上是一个Node.js应用程序,需要安装Node.js,到官网(http://Node.js.cn/download/)安装即可。安装完后,在命令行窗口中分别输入node -v和npm -v来查看Node和NPM的版本。

初始化应用
与Node.js模块相同,应用的入口为package.json 文件,该文件可以在一个文件夹下使用npm init命令,按照提示填充各项信息生成。 一个最基本的Electron 应用一般来说会有如下的目录结构:

1.png

?main.js是主进程,完成窗口的创建,url或html文件的加载。GitHub上提供了一个简单的Electron应用https://github.com/electron/el ... t.git,可供学习参考。

使用C++插件扩展功能
对于复杂的业务逻辑,可以开发成C++插件Node,C++插件主要完成一些复杂的逻辑功能,供Electron调用。Electron对于C++生成的Node插件引用功能来自于Node.js,可以使用require() 函数加载到工程中,像普通的模块一样使用。JavaScript 与C++ 库之间接口使用V8引擎,如下图所示:

2.jpg

?插件开发环境

C++插件的开发需要安装node-gyp、Python 2.76,Windows下开发还要安装Visual Studio。
每个插件都有一个工程文件binging.gyp,配置了源文件、include路径及链接库,目标文件,使用的编译器等,格式如下:

3.png

?
C++与JavaScript通过V8交互执行的整体过程如下图所示:

4.jpg

?
C++可以使用Napi接口,模块的加载使用宏NODEAPIMODULE(hello, Init),导出的JavaScript接口在Init中定义,示例如下:

5.png

?
编译C++插件使用命令如下:

6.png

?
生成的C++插件为node文件,如hello.node 在JavaScript中调用C++插件直接使用require函数,代码hello.js如下:

7.png

?JavaScript就可以调用C++的接口了,执行命令node hello.js,输出"world"。

C++中调用JavaScript传递的回调函数需要使用libuv库,libuv实现了Node.js的事件循环、工作线程、以及平台所有的的异步操作的C库。 具体参考示例代码https://github.com/nodejs/node-addon-examples

Electron打包

Electron应用打包可以使用electron-builder和electron-packager,推荐使用electron-builder,打包命令为npm run builder,可以使用参数配置生成的安装包的操作系统。

hg3399.com|官方网站IM桌面端

hg3399.com|官方网站的IM桌面端SDK提供了JavaScript接口,并且使用Electron框架开发的示例Demo,可以让任何一个前端人员在极短时间内搭建出一款同时在Mac、Windows上运行的即时通讯软件,拥有单聊、群聊和聊天室功能,支持文字、表情、图片、音视频等消息格式,开发时间短、界面美观,可以为开发者提供方便快捷的桌面端即时通讯解决方案。

下载地址:http://www.easemob.com/download/im
集成说明:http://docs-im.easemob.com/im/pc/intro/integration

作者:李小明,现就职于hg3399.com|官方网站,高级软件开发工程师,负责IM桌面端软件的研发,以C++、Node.js为开发语言,从事多年桌面软件开发经验,对行业前沿技术永远不懈追求。
继续阅读 ?
认识 Electron

Electron是由GitHub开发,用HTML、CSS 和 JavaScript来构建跨平台桌面应用程序的一个开源库。Electron通过将Chromium和Node.js合并到同一个运行时环境中,并将其打包为Mac、Windows和Linux系统下的应用。Electron于2013年作为构建GitHub上可编程的文本编辑器Atom的框架而被开发出来。

这不意味着Electron是绑定了GUI库的JavaScript。相反,Electron使用Web页面作为它的GUI,所以你能把它看作成一个被JavaScript控制的,精简版的Chromium浏览器。

Electron的版本更新很频繁,基本保持在1周发布一个小版本,每季度发布一个大版本。除了稳定版外还有Beta版和Nightly(最新功能试用版),Chromium更新时,Electron也会跟着更新。

为什么选择Electron

如今的桌面应用软件基本都需要跨平台运行,类似于MFC、Duilib等技术都无法满足需求。当今的跨平台桌面应用软件开发以使用QT,Electron较多。

QT跨平台开发

Qt是一个跨平台C++图形用户界面应用程序开发框架。它既可以开发GUI程序,也可用于开发非GUI程序,比如控制台工具和服务器。作为使用C++语言开发的框架,他的优缺点十分明显。

优点:

运行效率高;

架构健壮,性能强大。

缺点:

开发周期长;

需要开发者具有C++编程能力;

QT是一款收费软件,如果不想缴费购买License,又想用QT开发商业(闭源)程序,必须遵守LGPL协议,开源使用了LGPL库的源代码。

Electron桌面软件开发

Electron最早用于开发GitHub上的可编程文本编辑器Atom,它是一个借助Node.js和Chromium, 利用HTML/CSS/JavaScript语言创建桌面应用的框架。与之类似的还有NW.js, 但是NW.js社区发展基本处于停滞状态,更新也较慢。

优点:

使用JavaScript语言作为开发语言,方便前端开发者轻松开发桌面应用,原C++/Java语言开发者,也可以很快入手开发;

方便调试,提供了浏览器的开发者工具,轻松断点调试;

丰富的Web前端UI资源,可以快速制作绚丽的界面;

快速构建,迭代开发。最复杂的底层浏览器部分Electron已经帮你搞定,你只需要负责上层界面及业务逻辑的开发。Electron还提供了热更新功能,只需加载更新模块,会自动帮你检查更新并后台下载;

崩溃日志报告。轻松收集崩溃日志,定位错误代码;

C++插件扩展;

代码开源。Electron是GitHub上的开源项目,开发者有疑问可以在GitHub社区(https://github.com/electron/electron)上直接提issue,高级开发者可以修改Electron底层代码,订制自己的Elcetron。

缺点:

打包文件太大。Electron毕竟是一个浏览器,最小的应用安装包也要几十兆大小;

无法代码加密。和Web开发类似,使用者可以在开发者工具看到应用的客户端代码,商业软件需要代码加密的可以选择重要功能在服务端实现,桌面应用请求,或使用Node文件实现;

运行耗资源。浏览器通病,Electron应用也是多进程系统,启动几个Electron应用还好,如果太多会造成机器卡顿;

不支持XP系统,Node.js并不支持XP系统。

综上,如果你想快速的开发出炫酷的桌面应用,而又对系统限制不大,建议你选择Electron,如果你是一个前端开发人员,又想制作桌面应用,建议你选择Electron。

创建一个简单的应用

环境安装

Electron应用本质上是一个Node.js应用程序,需要安装Node.js,到官网(http://Node.js.cn/download/)安装即可。安装完后,在命令行窗口中分别输入node -v和npm -v来查看Node和NPM的版本。

初始化应用
与Node.js模块相同,应用的入口为package.json 文件,该文件可以在一个文件夹下使用npm init命令,按照提示填充各项信息生成。 一个最基本的Electron 应用一般来说会有如下的目录结构:

1.png

?main.js是主进程,完成窗口的创建,url或html文件的加载。GitHub上提供了一个简单的Electron应用https://github.com/electron/el ... t.git,可供学习参考。

使用C++插件扩展功能
对于复杂的业务逻辑,可以开发成C++插件Node,C++插件主要完成一些复杂的逻辑功能,供Electron调用。Electron对于C++生成的Node插件引用功能来自于Node.js,可以使用require() 函数加载到工程中,像普通的模块一样使用。JavaScript 与C++ 库之间接口使用V8引擎,如下图所示:

2.jpg

?插件开发环境

C++插件的开发需要安装node-gyp、Python 2.76,Windows下开发还要安装Visual Studio。
每个插件都有一个工程文件binging.gyp,配置了源文件、include路径及链接库,目标文件,使用的编译器等,格式如下:

3.png

?
C++与JavaScript通过V8交互执行的整体过程如下图所示:

4.jpg

?
C++可以使用Napi接口,模块的加载使用宏NODEAPIMODULE(hello, Init),导出的JavaScript接口在Init中定义,示例如下:

5.png

?
编译C++插件使用命令如下:

6.png

?
生成的C++插件为node文件,如hello.node 在JavaScript中调用C++插件直接使用require函数,代码hello.js如下:

7.png

?JavaScript就可以调用C++的接口了,执行命令node hello.js,输出"world"。

C++中调用JavaScript传递的回调函数需要使用libuv库,libuv实现了Node.js的事件循环、工作线程、以及平台所有的的异步操作的C库。 具体参考示例代码https://github.com/nodejs/node-addon-examples

Electron打包

Electron应用打包可以使用electron-builder和electron-packager,推荐使用electron-builder,打包命令为npm run builder,可以使用参数配置生成的安装包的操作系统。

hg3399.com|官方网站IM桌面端

hg3399.com|官方网站的IM桌面端SDK提供了JavaScript接口,并且使用Electron框架开发的示例Demo,可以让任何一个前端人员在极短时间内搭建出一款同时在Mac、Windows上运行的即时通讯软件,拥有单聊、群聊和聊天室功能,支持文字、表情、图片、音视频等消息格式,开发时间短、界面美观,可以为开发者提供方便快捷的桌面端即时通讯解决方案。

下载地址:http://www.easemob.com/download/im
集成说明:http://docs-im.easemob.com/im/pc/intro/integration

作者:李小明,现就职于hg3399.com|官方网站,高级软件开发工程师,负责IM桌面端软件的研发,以C++、Node.js为开发语言,从事多年桌面软件开发经验,对行业前沿技术永远不懈追求。 收起阅读 ?

收藏了~阿里巴巴程序员常用的 15 款开发者工具

从人工到自动化,从重复到创新,技术演进的历程中,伴随着开发者工具类产品的发展。

阿里巴巴将自身在各类业务场景下的技术积淀,通过开源、云上实现或工具等形式对外开放,本文将精选了一些阿里巴巴的开发者工具,希望能帮助开发者们提高开发效率、更优雅的写代码。

由于开发者涉及的技术领域众多,笔者仅从自己熟悉的领域,以后端开发者的视角盘点平时可能用得到的工具。每个工具按照以下几点进行介绍:

工具名称和简介

使用场景

使用教程

获取方式

一、Java 线上诊断工具 Arthas

Arthas 阿里巴巴 2018 年 9 月开源的一款 Java 线上诊断工具。

工具的使用场景:

这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception?

我改的代码为什么没有执行到?难道是我没 commit?分支搞错了?

遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗?

线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现!

是否有一个全局视角来查看系统的运行状况?

有什么办法可以监控到 JVM 的实时运行状态?

Arthas 支持 JDK 6+,支持 Linux/Mac/Windows,采用命令行交互模式,同时提供丰富的 Tab 自动补全功能,进一步方便进行问题的定位和诊断。

使用教程:

基础教程:

https://alibaba.github.io/arth ... asics

进阶教程:

https://alibaba.github.io/arth ... anced

获取方式:(免费)

开源地址:

https://github.com/alibaba/arthas

二、IDE 插件 Cloud Toolkit

Cloud Toolkit是一款 IDE 插件,可以帮助开发者更高效地开发、测试、诊断并部署应用。通过 Cloud Toolkit,开发者能够方便地将本地应用一键部署到任意机器(本地或云端),并内置 Arthas 诊断、高效执行终端命令和 SQL 等,提供 IntelliJ IDEA 版,Eclipse 版,PyCharm 版和 Maven 版。

工具的使用场景:

每次修改完代码后,是否正在经历反复地打包?

在 Maven 、Git 以及其他运维脚本和工具的之间频繁切换?

采用 SCP 工具上传?使用 XShell 或 SecureCRT 登陆服务器?替换部署包?重启?

文件上传到服务器指定目录,在各种 FTP、SCP 工具之间频繁切换 ?

使用教程:

IntelliJ IDEA 版:

https://help.aliyun.com/document_detail/98762.html

Eclipse 版:

https://help.aliyun.com/document_detail/29970.html

PyCharm 版:

https://help.aliyun.com/docume ... .html

Maven 版:

https://help.aliyun.com/docume ... .html

获取方式:(免费) 工具地址:

https://www.aliyun.com/product/cloudtoolkit

三、混沌实验注入工具 ChaosBlade

ChaosBlade是一款遵循混沌工程实验原理,提供丰富故障场景实现,帮助分布式系统提升容错性和可恢复性的混沌工程工具,可实现底层故障的注入,提供了延迟、异常、返回特定值、修改参数值、重复调用和 try-catch 块异常等异常场景。

工具的使用场景:

微服务的容错能力不易衡量?

容器编排配置是否合理无法验证?

PaaS 层健壮性的测试工作无从入手?

使用教程:

https://github.com/chaosblade-io/chaosblade/wiki/ 新手指南

获取方式:(免费)

开源地址:

https://github.com/chaosblade-io/chaosblade/wiki/ 新手指南

四、Java 代码规约扫描插件

该插件用于检测 Java 代码中存在的不规范的位置,并给予提示。规约插件是采用 Kotlin 语言开发。

使用教程:

IDEA 插件使用文档:

https://github.com/alibaba/p3c/wiki/IDEA 插件使用文档

Eclipse 插件使用文档:

https://github.com/alibaba/p3c/wiki/Eclipse 插件使用文档

获取方式:(免费)

开源地址:

https://github.com/alibaba/p3c

五、应用实时监控工具 ARMS

ARMS是一款 APM 类的监控工具,提供前端、应用、自定义监控 3 类监控选项,可快速构建实时的应用性能和业务监控能力。

工具的使用场景:

晚上 10 点收到 37 条报警信息,你却无从下手?

当我们发现问题的时候,客户 / 业务方已经发起投诉?

每个月花几十万买服务器,却无法保障用户体验?

使用教程:

前端监控接入:

https://help.aliyun.com/documentdetail/106086.html

应用监控接入:

https://help.aliyun.com/documentdetail/63796.html

自定义监控:

https://help.aliyun.com/document_detail/47474.html

获取方式:(收费)

工具地址:

https://www.aliyun.com/product/arms

六、静态开源站点搭建工具 Docsite

Docsite一款集官网、文档、博客和社区为一体的静态开源站点的解决方案,具有简单易上手、上手不撒手的特质,同时支持 react 和静态渲染、PC 端和移动端、支持中英文国际化、SEO、markdown 文档、全局站点搜索、站点风格自定义、页面自定义等功能。

使用教程:

https://docsite.js.org/zh-cn/d ... .html

获取方式:(免费)

项目地址:

https://github.com/txd-team/docsite

七、Android 平台上的秒级编译方案 Freeline

Freeline 可以充分利用缓存文件,在几秒钟内迅速地对代码的改动进行编译并部署到设备上,有效地减少了日常开发中的大量重新编译与安装的耗时。Freeline 最快捷的使用方法就是直接安装 Android Studio 插件。

使用教程:

https://github.com/alibaba/fre ... zh.md

获取方式:(免费)

项目地址:

https://github.com/alibaba/freeline

八、性能测试工具 PTS

PTS可以模拟大量用户访问业务的场景,任务随时发起,免去搭建和维护成本,支持 JMeter 脚本转化为 PTS 压测,同样支持原生 JMeter 引擎进行压测。

使用教程:

https://help.aliyun.com/document_detail/70290.html

获取方式:(收费)

工具地址:

https://www.aliyun.com/product/pts

九、云效开发者工具 KT

KT 可以简化在 Kubernetes 下进行联调测试的复杂度,提高基于 Kubernetes 的研发效率。

使用教程:

https://yq.aliyun.com/articles/690519

获取方式:(免费)

工具地址:

https://yq.aliyun.com/download/3393

十、架构可视化工具 AHAS

AHAS为 K8s 等容器环境提供了架构可视化的功能,同时,具有故障注入式高可用能力评测和一键流控降级等功能,可以快速低成本的提升应用可用性。

工具的使用场景:

服务化改造过程中,想精确的了解资源实例的构成和交互情况,实现架构的可视化?

想引入真实的故障场景和演练模型?

低门槛获得流控、降级功能?

使用教程:

https://help.aliyun.com/document_detail/90323.html

获取方式:(免费)

工具地址:

https://www.aliyun.com/product/ahas

十一、数据处理工具 EasyExcel

EasyExcel 是一个用来对 Java 进行解析、生成 Excel 的框架,它重写了 poi 对 07 版 Excel 的解析,原本一个 3M 的 Excel 用 POI sax 需要 100M 左右内存,EasyExcel 可降低到 KB 级别,并且再大的 excel 也不会出现内存溢出的情况。03 版依赖 POI 的 sax 模式。在上层做了模型转换的封装,让使用者更加简单方便。

使用教程:

https://github.com/alibaba/eas ... rt.md

获取方式:(开源)

https://github.com/alibaba/easyexcel

十二、iOS 类工具 HandyJSON

HandyJSON 是一个用于 Swift 语言中的 JSON 序列化 / 反序列化库。

与其他流行的 Swift JSON 库相比,HandyJSON 的特点是,它支持纯 Swift 类,使用也简单。它反序列化时 (把 JSON 转换为 Model) 不要求 Model 从 NSObject 继承 (因为它不是基于 KVC 机制),也不要求你为 Model 定义一个 Mapping 函数。只要你定义好 Model 类,声明它服从 HandyJSON 协议,HandyJSON 就能自行以各个属性的属性名为 Key,从 JSON 串中解析值。

使用教程:

https://github.com/alibaba/Han ... cn.md

获取方式:(开源)

https://github.com/alibaba/HandyJSON

十三、云上资源和应用部署工具 EDAS Serverless

EDAS Serverless一款基于 Kubernetes,面向应用和微服务的 Serverless 平台。用户无需管理和维护集群与服务器,即可通过镜像、WAR 包和 JAR 包,快速创建原生支持 Kubernetes 的容器应用,同时支持 Spring Cloud 和 Dubbo 等主流微服务框架。

使用教程:

https://help.aliyun.com/docume ... .html

获取方式:(公测期间免费)

https://help.aliyun.com/document_detail/97792.html

十四、数据库连接池 Druid

Druid 是 Java 语言下的数据库连接池,它能够提供强大的监控和扩展功能。

使用教程:

https://github.com/alibaba/druid/wiki/ 常见问题

获取方式:(开源)

http://central.maven.org/maven2/com/alibaba/druid/

十五、Java 工具集 Dragonwell

Alibaba Dragonwell 是阿里巴巴内部 OpenJDK 定制版 AJDK 的开源版本, AJDK 为在线电商,金融,物流做了结合业务场景的优化,运行在超大规模的,100,000+ 服务器的阿里巴巴数据中心。 Alibaba Dragonwell 与 Java SE 标准兼容,目前仅支持 Linux/x86_64 平台。

使用教程:

https://github.com/alibaba/dragonwell8/wiki/ 阿里巴巴 Dragonwell8 用户指南

获取方式:(开源)

https://github.com/alibaba/dragonwell8


上一篇: Java首度承认PK失败,愿永久服软Python!
?
继续阅读 ?
从人工到自动化,从重复到创新,技术演进的历程中,伴随着开发者工具类产品的发展。

阿里巴巴将自身在各类业务场景下的技术积淀,通过开源、云上实现或工具等形式对外开放,本文将精选了一些阿里巴巴的开发者工具,希望能帮助开发者们提高开发效率、更优雅的写代码。

由于开发者涉及的技术领域众多,笔者仅从自己熟悉的领域,以后端开发者的视角盘点平时可能用得到的工具。每个工具按照以下几点进行介绍:

工具名称和简介

使用场景

使用教程

获取方式

一、Java 线上诊断工具 Arthas

Arthas 阿里巴巴 2018 年 9 月开源的一款 Java 线上诊断工具。

工具的使用场景:

这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception?

我改的代码为什么没有执行到?难道是我没 commit?分支搞错了?

遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗?

线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现!

是否有一个全局视角来查看系统的运行状况?

有什么办法可以监控到 JVM 的实时运行状态?

Arthas 支持 JDK 6+,支持 Linux/Mac/Windows,采用命令行交互模式,同时提供丰富的 Tab 自动补全功能,进一步方便进行问题的定位和诊断。

使用教程:

基础教程:

https://alibaba.github.io/arth ... asics

进阶教程:

https://alibaba.github.io/arth ... anced

获取方式:(免费)

开源地址:

https://github.com/alibaba/arthas

二、IDE 插件 Cloud Toolkit

Cloud Toolkit是一款 IDE 插件,可以帮助开发者更高效地开发、测试、诊断并部署应用。通过 Cloud Toolkit,开发者能够方便地将本地应用一键部署到任意机器(本地或云端),并内置 Arthas 诊断、高效执行终端命令和 SQL 等,提供 IntelliJ IDEA 版,Eclipse 版,PyCharm 版和 Maven 版。

工具的使用场景:

每次修改完代码后,是否正在经历反复地打包?

在 Maven 、Git 以及其他运维脚本和工具的之间频繁切换?

采用 SCP 工具上传?使用 XShell 或 SecureCRT 登陆服务器?替换部署包?重启?

文件上传到服务器指定目录,在各种 FTP、SCP 工具之间频繁切换 ?

使用教程:

IntelliJ IDEA 版:

https://help.aliyun.com/document_detail/98762.html

Eclipse 版:

https://help.aliyun.com/document_detail/29970.html

PyCharm 版:

https://help.aliyun.com/docume ... .html

Maven 版:

https://help.aliyun.com/docume ... .html

获取方式:(免费) 工具地址:

https://www.aliyun.com/product/cloudtoolkit

三、混沌实验注入工具 ChaosBlade

ChaosBlade是一款遵循混沌工程实验原理,提供丰富故障场景实现,帮助分布式系统提升容错性和可恢复性的混沌工程工具,可实现底层故障的注入,提供了延迟、异常、返回特定值、修改参数值、重复调用和 try-catch 块异常等异常场景。

工具的使用场景:

微服务的容错能力不易衡量?

容器编排配置是否合理无法验证?

PaaS 层健壮性的测试工作无从入手?

使用教程:

https://github.com/chaosblade-io/chaosblade/wiki/ 新手指南

获取方式:(免费)

开源地址:

https://github.com/chaosblade-io/chaosblade/wiki/ 新手指南

四、Java 代码规约扫描插件

该插件用于检测 Java 代码中存在的不规范的位置,并给予提示。规约插件是采用 Kotlin 语言开发。

使用教程:

IDEA 插件使用文档:

https://github.com/alibaba/p3c/wiki/IDEA 插件使用文档

Eclipse 插件使用文档:

https://github.com/alibaba/p3c/wiki/Eclipse 插件使用文档

获取方式:(免费)

开源地址:

https://github.com/alibaba/p3c

五、应用实时监控工具 ARMS

ARMS是一款 APM 类的监控工具,提供前端、应用、自定义监控 3 类监控选项,可快速构建实时的应用性能和业务监控能力。

工具的使用场景:

晚上 10 点收到 37 条报警信息,你却无从下手?

当我们发现问题的时候,客户 / 业务方已经发起投诉?

每个月花几十万买服务器,却无法保障用户体验?

使用教程:

前端监控接入:

https://help.aliyun.com/documentdetail/106086.html

应用监控接入:

https://help.aliyun.com/documentdetail/63796.html

自定义监控:

https://help.aliyun.com/document_detail/47474.html

获取方式:(收费)

工具地址:

https://www.aliyun.com/product/arms

六、静态开源站点搭建工具 Docsite

Docsite一款集官网、文档、博客和社区为一体的静态开源站点的解决方案,具有简单易上手、上手不撒手的特质,同时支持 react 和静态渲染、PC 端和移动端、支持中英文国际化、SEO、markdown 文档、全局站点搜索、站点风格自定义、页面自定义等功能。

使用教程:

https://docsite.js.org/zh-cn/d ... .html

获取方式:(免费)

项目地址:

https://github.com/txd-team/docsite

七、Android 平台上的秒级编译方案 Freeline

Freeline 可以充分利用缓存文件,在几秒钟内迅速地对代码的改动进行编译并部署到设备上,有效地减少了日常开发中的大量重新编译与安装的耗时。Freeline 最快捷的使用方法就是直接安装 Android Studio 插件。

使用教程:

https://github.com/alibaba/fre ... zh.md

获取方式:(免费)

项目地址:

https://github.com/alibaba/freeline

八、性能测试工具 PTS

PTS可以模拟大量用户访问业务的场景,任务随时发起,免去搭建和维护成本,支持 JMeter 脚本转化为 PTS 压测,同样支持原生 JMeter 引擎进行压测。

使用教程:

https://help.aliyun.com/document_detail/70290.html

获取方式:(收费)

工具地址:

https://www.aliyun.com/product/pts

九、云效开发者工具 KT

KT 可以简化在 Kubernetes 下进行联调测试的复杂度,提高基于 Kubernetes 的研发效率。

使用教程:

https://yq.aliyun.com/articles/690519

获取方式:(免费)

工具地址:

https://yq.aliyun.com/download/3393

十、架构可视化工具 AHAS

AHAS为 K8s 等容器环境提供了架构可视化的功能,同时,具有故障注入式高可用能力评测和一键流控降级等功能,可以快速低成本的提升应用可用性。

工具的使用场景:

服务化改造过程中,想精确的了解资源实例的构成和交互情况,实现架构的可视化?

想引入真实的故障场景和演练模型?

低门槛获得流控、降级功能?

使用教程:

https://help.aliyun.com/document_detail/90323.html

获取方式:(免费)

工具地址:

https://www.aliyun.com/product/ahas

十一、数据处理工具 EasyExcel

EasyExcel 是一个用来对 Java 进行解析、生成 Excel 的框架,它重写了 poi 对 07 版 Excel 的解析,原本一个 3M 的 Excel 用 POI sax 需要 100M 左右内存,EasyExcel 可降低到 KB 级别,并且再大的 excel 也不会出现内存溢出的情况。03 版依赖 POI 的 sax 模式。在上层做了模型转换的封装,让使用者更加简单方便。

使用教程:

https://github.com/alibaba/eas ... rt.md

获取方式:(开源)

https://github.com/alibaba/easyexcel

十二、iOS 类工具 HandyJSON

HandyJSON 是一个用于 Swift 语言中的 JSON 序列化 / 反序列化库。

与其他流行的 Swift JSON 库相比,HandyJSON 的特点是,它支持纯 Swift 类,使用也简单。它反序列化时 (把 JSON 转换为 Model) 不要求 Model 从 NSObject 继承 (因为它不是基于 KVC 机制),也不要求你为 Model 定义一个 Mapping 函数。只要你定义好 Model 类,声明它服从 HandyJSON 协议,HandyJSON 就能自行以各个属性的属性名为 Key,从 JSON 串中解析值。

使用教程:

https://github.com/alibaba/Han ... cn.md

获取方式:(开源)

https://github.com/alibaba/HandyJSON

十三、云上资源和应用部署工具 EDAS Serverless

EDAS Serverless一款基于 Kubernetes,面向应用和微服务的 Serverless 平台。用户无需管理和维护集群与服务器,即可通过镜像、WAR 包和 JAR 包,快速创建原生支持 Kubernetes 的容器应用,同时支持 Spring Cloud 和 Dubbo 等主流微服务框架。

使用教程:

https://help.aliyun.com/docume ... .html

获取方式:(公测期间免费)

https://help.aliyun.com/document_detail/97792.html

十四、数据库连接池 Druid

Druid 是 Java 语言下的数据库连接池,它能够提供强大的监控和扩展功能。

使用教程:

https://github.com/alibaba/druid/wiki/ 常见问题

获取方式:(开源)

http://central.maven.org/maven2/com/alibaba/druid/

十五、Java 工具集 Dragonwell

Alibaba Dragonwell 是阿里巴巴内部 OpenJDK 定制版 AJDK 的开源版本, AJDK 为在线电商,金融,物流做了结合业务场景的优化,运行在超大规模的,100,000+ 服务器的阿里巴巴数据中心。 Alibaba Dragonwell 与 Java SE 标准兼容,目前仅支持 Linux/x86_64 平台。

使用教程:

https://github.com/alibaba/dragonwell8/wiki/ 阿里巴巴 Dragonwell8 用户指南

获取方式:(开源)

https://github.com/alibaba/dragonwell8


上一篇: Java首度承认PK失败,愿永久服软Python!
? 收起阅读 ?

史上最完整的官方Oracle OCP中文文教材,快来下载吧!!

内含文件:
1、Oracle Database 11g:SQL 基础 学生指南第1 册?
2、Oracle Database 11g:SQL 基础 学生指南第2 册?
3、Oracle Database 11g:数据库管理- 课堂练习I 学生指南第1 册
4、Oracle Database 11g:数据库管理- 课堂练习II 学生指南第1 册?
5、Oracle Database 11g:数据库管理- 课堂练习I 学生指南第2 册?
6、Oracle Database 11g:数据库管理- 课堂练习II 学生指南第2 册?
7、Oracle Da等等
继续阅读 ?
内含文件:
1、Oracle Database 11g:SQL 基础 学生指南第1 册?
2、Oracle Database 11g:SQL 基础 学生指南第2 册?
3、Oracle Database 11g:数据库管理- 课堂练习I 学生指南第1 册
4、Oracle Database 11g:数据库管理- 课堂练习II 学生指南第1 册?
5、Oracle Database 11g:数据库管理- 课堂练习I 学生指南第2 册?
6、Oracle Database 11g:数据库管理- 课堂练习II 学生指南第2 册?
7、Oracle Da等等 收起阅读 ?

手!慢!无!价值1980的数据分析教程,终终终于免费啦!!!

对比互联网各个岗位的裁员程度可以发现,数据分析相关岗位正在不断的扩招,已经成为了这波逆流中的黑马,什么原因导致的数据分析人才如此紧缺?
因为数据分析是大势所趋,未来的发展空间会大有可为。随着5G网络即将商用,企业每天将会产生海量的数据,BAT日均数据更是达到了PB的级别,数据分析相关岗位才会存在着巨大的需求缺口。
长此以往,企业要用尽可能少的人才,来满足尽可能多岗位的诉求,可以这么说,数据分析将会是每个程序员个人能力最重要的补充,也是BAT这类大公司急招人才的必备技能。
但是一提数据分析,很多人就觉得无从下手,知识点零散总是抓不住重点,学习起来相当吃力。这有一份廖雪峰大神历时3个月打磨出来的《数据分析必备技能》的视频学习资料,由浅入深系统化的讲解,内容详尽。基本囊括了平时学习工作中经常用到的分析方式,这份不可或缺的宝贵资料原价值1980元,现在,关注公众号cainiao_xueyuan就可以免费领取(仅限300名)。

学完这套资料可以给你将会得到哪些收获?

1. 总时长>48个小时的干货内容,每天2小时,20天掌握数据分析必备技能;
2. 对照自己掌握知识点进行查缺补漏,帮助你扫除知识盲区、重构知识体系。
具体详细的资料内容:
1 数学理论基础 ? ? ? ? ??
01.数据挖掘之数学基础02.数学基础之微积分
03.机器学习之线性回归
04.机器学习之逻辑回归
05.朴素贝叶斯
06.机器学习之决策树
07.机器学习之集成学习
2 必备Python基础 ? ? ? ? ? ?
01.Python语言介绍、发展、特色02.概念介绍:Python解释器
03.Python函数及高级特性
04.交互环境介绍:启动和退出交互环境
05.Python基础语法及模块
3 高效scrapy爬虫框架 ? ? ? ? ??
01.scrapy简介02.scrapy选择器
03.创建scrapy爬虫
04.下载器与爬虫中间件
05.突破反爬虫机制与策略
06.使用管道 pipelines ? ? ? ?
4 Excel数据处理 ? ? ? ? ? ?
01.认识数据表的字段和记录02.使用Excel制作数据表
03.指定常用数据类型
04.Excel导入网站数据、文本数据
05.Excel数据清洗、筛选
06.Excel数据抽样和计算

5 使用SQL实现数据操作
01SQL基础语法
02.SQL表连接
03.SQL普通函数
04.SQL窗口函数
05.SQL优化?

长按扫码 添加微信,领取干货视频

微信图片_20190507095431.jpg


Ps:学习资料由"开课吧"友情提供。
继续阅读 ?
对比互联网各个岗位的裁员程度可以发现,数据分析相关岗位正在不断的扩招,已经成为了这波逆流中的黑马,什么原因导致的数据分析人才如此紧缺?
因为数据分析是大势所趋,未来的发展空间会大有可为。随着5G网络即将商用,企业每天将会产生海量的数据,BAT日均数据更是达到了PB的级别,数据分析相关岗位才会存在着巨大的需求缺口。
长此以往,企业要用尽可能少的人才,来满足尽可能多岗位的诉求,可以这么说,数据分析将会是每个程序员个人能力最重要的补充,也是BAT这类大公司急招人才的必备技能。
但是一提数据分析,很多人就觉得无从下手,知识点零散总是抓不住重点,学习起来相当吃力。这有一份廖雪峰大神历时3个月打磨出来的《数据分析必备技能》的视频学习资料,由浅入深系统化的讲解,内容详尽。基本囊括了平时学习工作中经常用到的分析方式,这份不可或缺的宝贵资料原价值1980元,现在,关注公众号cainiao_xueyuan就可以免费领取(仅限300名)。

学完这套资料可以给你将会得到哪些收获?

1. 总时长>48个小时的干货内容,每天2小时,20天掌握数据分析必备技能;
2. 对照自己掌握知识点进行查缺补漏,帮助你扫除知识盲区、重构知识体系。
具体详细的资料内容:
1 数学理论基础 ? ? ? ? ??
01.数据挖掘之数学基础02.数学基础之微积分
03.机器学习之线性回归
04.机器学习之逻辑回归
05.朴素贝叶斯
06.机器学习之决策树
07.机器学习之集成学习
2 必备Python基础 ? ? ? ? ? ?
01.Python语言介绍、发展、特色02.概念介绍:Python解释器
03.Python函数及高级特性
04.交互环境介绍:启动和退出交互环境
05.Python基础语法及模块
3 高效scrapy爬虫框架 ? ? ? ? ??
01.scrapy简介02.scrapy选择器
03.创建scrapy爬虫
04.下载器与爬虫中间件
05.突破反爬虫机制与策略
06.使用管道 pipelines ? ? ? ?
4 Excel数据处理 ? ? ? ? ? ?
01.认识数据表的字段和记录02.使用Excel制作数据表
03.指定常用数据类型
04.Excel导入网站数据、文本数据
05.Excel数据清洗、筛选
06.Excel数据抽样和计算

5 使用SQL实现数据操作
01SQL基础语法
02.SQL表连接
03.SQL普通函数
04.SQL窗口函数
05.SQL优化?

长按扫码 添加微信,领取干货视频

微信图片_20190507095431.jpg


Ps:学习资料由"开课吧"友情提供。 收起阅读 ?

vue仿微信网页端聊天室|仿微信客户端vue版

基于Vue2.5.6+Vuex+vue-router+vue-gemini-scrollbar+swiper+elementUI等技术开发的仿微信web端聊天室,实现了发送消息、表情(动图),图片、视频预览,仿微信右键菜单、网页截图可直接粘贴至编辑框发送。
https://www.cnblogs.com/xiaoyan2017/p/10793728.html
?

009360截图20190429230828577.png


002360截图20190429225404663.png


003360截图20190429225544303.png


004360截图20190429225824919.png


005360截图20190429225945884.png


006360截图20190429230254807.png


007360截图20190429230405160.png


008360截图20190429230519310.png


012360截图20190429231530721.png


013360截图20190429231625431.png


014360截图20190429231721255.png


015360截图20190429231737056.png


016360截图20190429231834918.png

?
/*
* 页面地址路由js
*/
import Vue from 'vue'
import Router from 'vue-router'
import store from '../vuex'

// 通过改写router.go方法,当new Router 实例就包含back方法
Router.prototype.back = function(){
window.history.go(-1)
}

Vue.use(Router)

const router = new Router({
routes: [
// 登录、注册
{
path: '/login',
component: resolve => require(['../views/auth/login'], resolve),
meta: { hideSideBar: true },
},
{
path: '/register',
component: resolve => require(['../views/auth/register'], resolve),
meta: { hideSideBar: true },
},

// 首页、通讯录、动态圈
{
path: '/',
redirect: '/chat',
component: resolve => require(['../views/index'], resolve),
meta: { requireAuth: true },
},
{
path: '/contact',
redirect: '/contact/new-friends',
component: resolve => require(['../views/contact'], resolve),
meta: { requireAuth: true },
},
{
path: '/contact/new-friends',
component: resolve => require(['../views/contact/new-friends'], resolve),
meta: { requireAuth: true },
},
{
path: '/contact/uinfo',
component: resolve => require(['../views/contact/uinfo'], resolve),
},
{
path: '/qzone',
component: resolve => require(['../views/qzone'], resolve),
},
{
path: '/qzone/write',
component: resolve => require(['../views/qzone/write'], resolve),
meta: { requireAuth: true },
},
{
path: '/my',
component: resolve => require(['../views/my'], resolve),
meta: { requireAuth: true },
},

// 聊天页面
{
path: '/chat',
component: resolve => require(['../views/chat/group-chat'], resolve),
meta: { requireAuth: true }
},
{
path: '/chat/single-chat',
component: resolve => require(['../views/chat/single-chat'], resolve),
meta: { requireAuth: true }
},
{
path: '/chat/group-info',
component: resolve => require(['../views/chat/group-info'], resolve),
meta: { requireAuth: true }
}

]
});

// 注册全局钩子(拦截登录状态)
router.beforeEach((to, from, next) => {
const token = store.state.token
// 判断该路由地址是否需要登录权限
if(to.meta.requireAuth){
// 判断token是否存在
if(token){
next()
}else{
next()
// 未登录授权
wcPop({
content: '还未登录授权!', anim: 'shake', style: 'background:#e03b30;color:#fff;', time: 2,
end: function(){
next({ path: '/login' })
}
});
}
}else{
next()
}
})

export default router
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

import {mm} from '../common.js'

export default new Vuex.Store({
state: {
user: window.sessionStorage.getItem('user'),
token: window.sessionStorage.getItem('token'), //登录标识
onlineStatus: { status: 'online', text: '在线' }, //用户在线状态 【 online:在线、 offline:离开、 busy:忙碌、 invisible:隐身】
},
mutations: {
// 将token存储到sessionStorage
SET_TOKEN (state, data){
state.token = data;
window.sessionStorage.setItem('token', data);
},
// 获取用户名
SET_USER (state, data){
state.user = data;
window.sessionStorage.setItem('user', data);
},
// 退出
LOGOUT (state){
state.user = null;
state.token = null;
window.sessionStorage.removeItem('user');
window.sessionStorage.removeItem('token');
},
},
getters:{}
})

// 这种写法也ok
// export default () => {
// return new Vuex.Store({
// state: {},
// mutations: {},
// actions: {},
// })
// }

20180817002157557.jpg

欢迎大家一起交流、学习 ?Q:282310962 ?wx:xy190310
?
继续阅读 ?
基于Vue2.5.6+Vuex+vue-router+vue-gemini-scrollbar+swiper+elementUI等技术开发的仿微信web端聊天室,实现了发送消息、表情(动图),图片、视频预览,仿微信右键菜单、网页截图可直接粘贴至编辑框发送。
https://www.cnblogs.com/xiaoyan2017/p/10793728.html
?

009360截图20190429230828577.png


002360截图20190429225404663.png


003360截图20190429225544303.png


004360截图20190429225824919.png


005360截图20190429225945884.png


006360截图20190429230254807.png


007360截图20190429230405160.png


008360截图20190429230519310.png


012360截图20190429231530721.png


013360截图20190429231625431.png


014360截图20190429231721255.png


015360截图20190429231737056.png


016360截图20190429231834918.png

?
/*
* 页面地址路由js
*/
import Vue from 'vue'
import Router from 'vue-router'
import store from '../vuex'

// 通过改写router.go方法,当new Router 实例就包含back方法
Router.prototype.back = function(){
window.history.go(-1)
}

Vue.use(Router)

const router = new Router({
routes: [
// 登录、注册
{
path: '/login',
component: resolve => require(['../views/auth/login'], resolve),
meta: { hideSideBar: true },
},
{
path: '/register',
component: resolve => require(['../views/auth/register'], resolve),
meta: { hideSideBar: true },
},

// 首页、通讯录、动态圈
{
path: '/',
redirect: '/chat',
component: resolve => require(['../views/index'], resolve),
meta: { requireAuth: true },
},
{
path: '/contact',
redirect: '/contact/new-friends',
component: resolve => require(['../views/contact'], resolve),
meta: { requireAuth: true },
},
{
path: '/contact/new-friends',
component: resolve => require(['../views/contact/new-friends'], resolve),
meta: { requireAuth: true },
},
{
path: '/contact/uinfo',
component: resolve => require(['../views/contact/uinfo'], resolve),
},
{
path: '/qzone',
component: resolve => require(['../views/qzone'], resolve),
},
{
path: '/qzone/write',
component: resolve => require(['../views/qzone/write'], resolve),
meta: { requireAuth: true },
},
{
path: '/my',
component: resolve => require(['../views/my'], resolve),
meta: { requireAuth: true },
},

// 聊天页面
{
path: '/chat',
component: resolve => require(['../views/chat/group-chat'], resolve),
meta: { requireAuth: true }
},
{
path: '/chat/single-chat',
component: resolve => require(['../views/chat/single-chat'], resolve),
meta: { requireAuth: true }
},
{
path: '/chat/group-info',
component: resolve => require(['../views/chat/group-info'], resolve),
meta: { requireAuth: true }
}

]
});

// 注册全局钩子(拦截登录状态)
router.beforeEach((to, from, next) => {
const token = store.state.token
// 判断该路由地址是否需要登录权限
if(to.meta.requireAuth){
// 判断token是否存在
if(token){
next()
}else{
next()
// 未登录授权
wcPop({
content: '还未登录授权!', anim: 'shake', style: 'background:#e03b30;color:#fff;', time: 2,
end: function(){
next({ path: '/login' })
}
});
}
}else{
next()
}
})

export default router
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

import {mm} from '../common.js'

export default new Vuex.Store({
state: {
user: window.sessionStorage.getItem('user'),
token: window.sessionStorage.getItem('token'), //登录标识
onlineStatus: { status: 'online', text: '在线' }, //用户在线状态 【 online:在线、 offline:离开、 busy:忙碌、 invisible:隐身】
},
mutations: {
// 将token存储到sessionStorage
SET_TOKEN (state, data){
state.token = data;
window.sessionStorage.setItem('token', data);
},
// 获取用户名
SET_USER (state, data){
state.user = data;
window.sessionStorage.setItem('user', data);
},
// 退出
LOGOUT (state){
state.user = null;
state.token = null;
window.sessionStorage.removeItem('user');
window.sessionStorage.removeItem('token');
},
},
getters:{}
})

// 这种写法也ok
// export default () => {
// return new Vuex.Store({
// state: {},
// mutations: {},
// actions: {},
// })
// }

20180817002157557.jpg

欢迎大家一起交流、学习 ?Q:282310962 ?wx:xy190310
? 收起阅读 ?

打击电信诈骗保障客户安全,hg3399.com|官方网站客服云6大安全机制让骗子无所遁形!

近日,hg3399.com|官方网站收到海淀网警和用户的反馈,部分不法分子通过注册hg3399.com|官方网站客服云获取专业客服工具在各大论坛和二手交易平台:如百度贴吧、58同城“转转”等进行电信诈骗,不法分子冒充平台的专业客服人员诱导客户先行付款进行诈骗,造成了很恶劣的社会影响。

2.jpg



hg3399.com|官方网站第一时间获悉即敏锐的采取行动,一期关停450个诈骗账号信息,同时推出6大安全机制让骗子无所遁形。

hg3399.com|官方网站客服云防诈骗六大安全机制:

1.注册环节:单手机号15天内只能注册一个账号,以防反复账号行骗。

2.使用环节:关停H5聊天窗口,需申请审核后才能打开。

3.聊天环节:敏感词预警,在用户聊天记录过程中系统监控到提前设置的关键词,会触发预警给到运营人员,进行账户关停。

4.账号限制:实时操作异常账号关闭,一经发现,立即关停账号

5.黑名单机制:加入黑名单的注册手机号,永久无法再注册hg3399.com|官方网站

6.报警机制:第一时间反馈所有诈骗信息至公安网警等相关部门

同时,hg3399.com|官方网站也开通了反诈骗投诉信箱:Antifraud@easemob.com 和 400专线:400-622-1776,我们的工作人员会第一时间解决所有用户碰到的所有潜在欺诈问题。

维护网络安全繁荣是hg3399.com|官方网站义不容辞的社会责任,hg3399.com|官方网站会一如既往和网警网安等部门密切合作,打击电信诈骗,保护客户安全,hg3399.com|官方网站客服云一直在路上!
继续阅读 ?
近日,hg3399.com|官方网站收到海淀网警和用户的反馈,部分不法分子通过注册hg3399.com|官方网站客服云获取专业客服工具在各大论坛和二手交易平台:如百度贴吧、58同城“转转”等进行电信诈骗,不法分子冒充平台的专业客服人员诱导客户先行付款进行诈骗,造成了很恶劣的社会影响。

2.jpg



hg3399.com|官方网站第一时间获悉即敏锐的采取行动,一期关停450个诈骗账号信息,同时推出6大安全机制让骗子无所遁形。

hg3399.com|官方网站客服云防诈骗六大安全机制:

1.注册环节:单手机号15天内只能注册一个账号,以防反复账号行骗。

2.使用环节:关停H5聊天窗口,需申请审核后才能打开。

3.聊天环节:敏感词预警,在用户聊天记录过程中系统监控到提前设置的关键词,会触发预警给到运营人员,进行账户关停。

4.账号限制:实时操作异常账号关闭,一经发现,立即关停账号

5.黑名单机制:加入黑名单的注册手机号,永久无法再注册hg3399.com|官方网站

6.报警机制:第一时间反馈所有诈骗信息至公安网警等相关部门

同时,hg3399.com|官方网站也开通了反诈骗投诉信箱:Antifraud@easemob.com 和 400专线:400-622-1776,我们的工作人员会第一时间解决所有用户碰到的所有潜在欺诈问题。

维护网络安全繁荣是hg3399.com|官方网站义不容辞的社会责任,hg3399.com|官方网站会一如既往和网警网安等部门密切合作,打击电信诈骗,保护客户安全,hg3399.com|官方网站客服云一直在路上! 收起阅读 ?

同一个网站,手机端跟电脑端显示不同是怎么实现的?

同一个网站,手机端跟电脑端不同是怎么实现的?

常见的方式有三种:

1,自适应网站

同一套代码,自动实现手机端和电脑端的布局自动调整。例如:openGPS.cn?网站现在大部分页面已经支持自适应展示,手机端电脑端都可以访问本站内容,正常阅读。自适应站点,往往是对CSS布局的重点考虑,本站使用的是BootStrap这个前端样式组件实现的自适应布局。

2,网站二级目录

这种是早期网站比较喜欢的做法,因为其实这是一个网站。早期网站往往是使用虚拟主机(也叫空间)发布,一个空间只能放一个网站,所以这种做法在早期特别流行。这种结构本质还是一个网站,但是针对手机电脑客户端单独做了往往对应的一套目录,例如:

电脑站点地址一般是:www.domain.com/xxxxxxx

手机站点地址往往是:www.domain.com/m/xxxxxxx

3,手机站点使用二级域名,电脑手机各一套2套站点代码


这种做法,工作量跟二级目录基本相似,严格来说代码量稍微多点。由于是2套代码,所以发布时候也得配备2套域名,不过要求两套站点连接同一个数据库来实现数据统一。例如:

电脑端网站域名是:www.domain.com

手机端网站域名是:m.domain.com




原文地址:?https://www.opengps.cn/Blog/View.aspx?id=302?文章的更新编辑依此链接为准。欢迎关注源站原创文章!
?
继续阅读 ?
同一个网站,手机端跟电脑端不同是怎么实现的?

常见的方式有三种:

1,自适应网站

同一套代码,自动实现手机端和电脑端的布局自动调整。例如:openGPS.cn?网站现在大部分页面已经支持自适应展示,手机端电脑端都可以访问本站内容,正常阅读。自适应站点,往往是对CSS布局的重点考虑,本站使用的是BootStrap这个前端样式组件实现的自适应布局。

2,网站二级目录

这种是早期网站比较喜欢的做法,因为其实这是一个网站。早期网站往往是使用虚拟主机(也叫空间)发布,一个空间只能放一个网站,所以这种做法在早期特别流行。这种结构本质还是一个网站,但是针对手机电脑客户端单独做了往往对应的一套目录,例如:

电脑站点地址一般是:www.domain.com/xxxxxxx

手机站点地址往往是:www.domain.com/m/xxxxxxx

3,手机站点使用二级域名,电脑手机各一套2套站点代码


这种做法,工作量跟二级目录基本相似,严格来说代码量稍微多点。由于是2套代码,所以发布时候也得配备2套域名,不过要求两套站点连接同一个数据库来实现数据统一。例如:

电脑端网站域名是:www.domain.com

手机端网站域名是:m.domain.com




原文地址:?https://www.opengps.cn/Blog/View.aspx?id=302?文章的更新编辑依此链接为准。欢迎关注源站原创文章!
? 收起阅读 ?

在微信小程序里实现聊天室

第一次搞小程序,老板让我实现一个聊天室功能,压力山大啊。
花了几天时间研究比较了一下方案,最后基于hg3399.com|官方网站的小程序SDK 开发了一个聊天室。
?
准备工作
  1. 下载hg3399.com|官方网站?小程序demo+sdk
    git clone https://github.com/easemob/webim-weixin-xcx
  2. 创建一个文件夹,将 demo 中的文件 comps、images、sdk、utils 拷贝到新的文件,文件目录说明
    ml.png

集成
  1. 登录hg3399.com|官方网站没什么可说的,这里选择的是使用?username/password?登录,和demo中的一样,文件没有进行任何更改
    login.png
  2. 在app.js 中注册的?WebIM.conn.listen, 然后在 登陆成功的回调?onOpened?设置的跳转页面,并将登陆的?username?赋给?myName,传到新的页面中使用
    tz.png
  3. 修改?roomlist.js?获取聊天室列表,是分页获取的,这里先偷个懒,获取了第一页 20 个聊天室
    getroom.png
    然后将listChatrooms()?分别在onLoad、onShow?内,更改下,将原有的?listGroups()?替换掉
  4. 然后在roomlist.wxml?修改对应的 变量绑定名称
    listui.png
    list.png
  5. demo中的group.js 中,获取到的是当前登陆账号已加入的群组,咱们做的是聊天室功能,所以需要有一个加入的操作,找roomlist.js 中找到 into_room: function (event),然后填写加入聊天室的方法, 我是直接在当前这个里面加的跳转到聊天页面,并将当前登陆的IDmyName,聊天室IDgroupID,聊天室名称your 传给新页面
    joinrom.png
    Ex:监听是否加入聊天室成功的回调是在 onPresence 中,type:memberJoinChatRoomSuccess,正常是监听这个回调跳转页面,有点麻烦就直接这样吧
  6. 到会话页面后,需要修改一下对应的消息格式,在comps/chat/suit 目录下,将里面的文件对应的 js 文件根据文档给聊天室发送消息 格式进行修改,聊天室消息和群组消息不同,所以我目前是直接将getSendToParam()、isGroupChat() 注释,改成下面这样,demo 中下面还有代码的,这里就用 …… 代替了
    send.png
    chat.png
    就这样了,简单集成聊天室功能,demo中的UI 是开源的,可以根据自己的需求更改~下面是具体实现过程。代码也放在github 上了,有需要的兄弟自取。demo下载地址:https://github.com/lizgDonkey/room-xcx

继续阅读 ?
第一次搞小程序,老板让我实现一个聊天室功能,压力山大啊。
花了几天时间研究比较了一下方案,最后基于hg3399.com|官方网站的小程序SDK 开发了一个聊天室。
?
准备工作
  1. 下载hg3399.com|官方网站?小程序demo+sdk
    git clone https://github.com/easemob/webim-weixin-xcx
  2. 创建一个文件夹,将 demo 中的文件 comps、images、sdk、utils 拷贝到新的文件,文件目录说明
    ml.png

集成
  1. 登录hg3399.com|官方网站没什么可说的,这里选择的是使用?username/password?登录,和demo中的一样,文件没有进行任何更改
    login.png
  2. 在app.js 中注册的?WebIM.conn.listen, 然后在 登陆成功的回调?onOpened?设置的跳转页面,并将登陆的?username?赋给?myName,传到新的页面中使用
    tz.png
  3. 修改?roomlist.js?获取聊天室列表,是分页获取的,这里先偷个懒,获取了第一页 20 个聊天室
    getroom.png
    然后将listChatrooms()?分别在onLoad、onShow?内,更改下,将原有的?listGroups()?替换掉
  4. 然后在roomlist.wxml?修改对应的 变量绑定名称
    listui.png
    list.png
  5. demo中的group.js 中,获取到的是当前登陆账号已加入的群组,咱们做的是聊天室功能,所以需要有一个加入的操作,找roomlist.js 中找到 into_room: function (event),然后填写加入聊天室的方法, 我是直接在当前这个里面加的跳转到聊天页面,并将当前登陆的IDmyName,聊天室IDgroupID,聊天室名称your 传给新页面
    joinrom.png
    Ex:监听是否加入聊天室成功的回调是在 onPresence 中,type:memberJoinChatRoomSuccess,正常是监听这个回调跳转页面,有点麻烦就直接这样吧
  6. 到会话页面后,需要修改一下对应的消息格式,在comps/chat/suit 目录下,将里面的文件对应的 js 文件根据文档给聊天室发送消息 格式进行修改,聊天室消息和群组消息不同,所以我目前是直接将getSendToParam()、isGroupChat() 注释,改成下面这样,demo 中下面还有代码的,这里就用 …… 代替了
    send.png
    chat.png
    就这样了,简单集成聊天室功能,demo中的UI 是开源的,可以根据自己的需求更改~下面是具体实现过程。代码也放在github 上了,有需要的兄弟自取。demo下载地址:https://github.com/lizgDonkey/room-xcx

收起阅读 ?

(客服云)iOS访客端集成常见问题(非报错)

1、UI上很多地方显示英文,比如聊天页面的工具栏
显示英文1.png

把客服demo中配置的国际化文件添加到您自己的工程中。拖之前要打开国际化文件,全部选中这三个,再进行拖入。
显示英文2.png

?
2、进入聊天页面没有加载聊天记录
这种情况一般出现在只使用了 HDMessageViewController 没有使用 HDChatViewController 的时候
在HDMessageViewController 的 viewDidLoad 方法中, 将 [self tableViewDidTriggerHeaderRefresh]; 的注释打开,再在这之前
加上 self.showRefreshHeader = YES; 这句代码
?
3、发送表情却显示字符串
访客端表情符号.png

把下面这段代码添加到appdelegate中就可以了
[[HDEmotionEscape sharedInstance] setEaseEmotionEscapePattern:@"\\[[^\\[\\]]{1,3}\\]"];
[[HDEmotionEscape sharedInstance] setEaseEmotionEscapeDictionary:[HDConvertToCommonEmoticonsHelper emotionsDictionary]];
?
4、文本消息,收发双方的布局不一样,如图
文本消息布局错误1.png

参考一下截图修改即可
文本消息布局错误2.png


5、客服能收到访客的消息,访客收不到客服的消息
(1)客服和im同时使用的话,初始化sdk、登录、登出用的是im的api会出现这种情况。必须使用客服的api。
(2)IM sdk升级为客服sdk,不兼容导致的,这种情况可以线上发起会话咨询。
? ? ?
6、发送的消息,出现在聊天页面的左侧
一般是由于当前访客没有登录或者登录失败,断点仔细检查下。
继续阅读 ?
1、UI上很多地方显示英文,比如聊天页面的工具栏
显示英文1.png

把客服demo中配置的国际化文件添加到您自己的工程中。拖之前要打开国际化文件,全部选中这三个,再进行拖入。
显示英文2.png

?
2、进入聊天页面没有加载聊天记录
这种情况一般出现在只使用了 HDMessageViewController 没有使用 HDChatViewController 的时候
在HDMessageViewController 的 viewDidLoad 方法中, 将 [self tableViewDidTriggerHeaderRefresh]; 的注释打开,再在这之前
加上 self.showRefreshHeader = YES; 这句代码
?
3、发送表情却显示字符串
访客端表情符号.png

把下面这段代码添加到appdelegate中就可以了
[[HDEmotionEscape sharedInstance] setEaseEmotionEscapePattern:@"\\[[^\\[\\]]{1,3}\\]"];
[[HDEmotionEscape sharedInstance] setEaseEmotionEscapeDictionary:[HDConvertToCommonEmoticonsHelper emotionsDictionary]];
?
4、文本消息,收发双方的布局不一样,如图
文本消息布局错误1.png

参考一下截图修改即可
文本消息布局错误2.png


5、客服能收到访客的消息,访客收不到客服的消息
(1)客服和im同时使用的话,初始化sdk、登录、登出用的是im的api会出现这种情况。必须使用客服的api。
(2)IM sdk升级为客服sdk,不兼容导致的,这种情况可以线上发起会话咨询。
? ? ?
6、发送的消息,出现在聊天页面的左侧
一般是由于当前访客没有登录或者登录失败,断点仔细检查下。 收起阅读 ?

(客服云)iOS访客端集成常见报错(总有一款适合你)

注意:向自己工程中添加hg3399.com|官方网站SDK和UI文件的时候,不要直接向xcode中拖拽添加,先把SDK和UI文件粘贴到自己工程的finder目录中,再从finder中向xcode中拖拽添加,避免出现找不到SDK或者UI文件的情况。
?
1、很多同学在首次“导入SDK”或“更新SDK重新导入SDK”后,Xcode运行报以下的error:
dyld: Library not loaded: @rpath/Hyphenate.framework/Hyphenate
? Referenced from: /Users/shenchong/Library/Developer/CoreSimulator/Devices/C768FE68-6E79-40C8-8AD1-FFFC434D51A9/data/Containers/Bundle/Application/41EA9A48-4DD5-4AA4-AB3F-139CFE036532/CallBackTest.app/CallBackTest
? Reason: image not found
? ? ? ?这个原因是工程未加载到 framework,正确的处理方式是在TARGETS → General → Embedded Binaries 中添加HelpDesk.framework和Hyphenate.framework依赖库,且 Linked Frameworks and Libraries中依赖库的Status必须是Required。
1访客端_image_not_found.png

?
2、运行之后,自变量为nil,这就有可能是因为上面所说的依赖库的status设置为了Optional,需要改成Required。
2访客端自变量为nil.png

?
3、打包后上传到appstore报错
(1)ERROR ITMS-90535: "Unexpected CFBundleExecutable Key. The bundle at 'Payload/toy.app/HelpDeskUIResource.bundle' does not contain a bundle executable. If this bundle intentionally does not contain an executable, consider removing the CFBundleExecutable key from its Info.plist and using a CFBundlePackageType of BNDL. If this bundle is part of a third-party framework, consider contacting the developer of the framework for an update to address this issue."
方法:把HelpDeskUIResource.bundle里的Info.plist删掉就即可。
3访客端打包90535.png

(2)This bundle is invalid. The value for key CFBundleShortVersionString ‘1.2.2.1’in the Info.plist must be a period-separated list of at most three non-negative integers.?
4访客端打包90060.png

把sdk里的plist文件的版本号改成3位数即可
5访客端打包1.2_.2_.1位置_.png

(3)Invalid Mach-O Format.The Mach-O in bundle “SMYG.app/Frameworks/Hyphenate.framework” isn’t consistent with the Mach-O in the main bundle.The main bundle Mach-O contains armv7(bitcode) and arm64(bitcode),while the nested bundle Mach-O contains armv7(machine code) and arm64(machine code).Verify that all of the targets for a platform have a consistent value for the ENABLE_BITCODE build setting.”
6访客端打包90636.png

将TARGETS-Build Settings-Enable Bitcode改为NO
7访客端打包bitcode改为NO.png

(4)还有很多同学打包失败,看不出什么原因
8访客端打包需剔除.png

那么可以先看看有没有按照文档剔除x86_64 i386两个平台
文档链接:http://docs.easemob.com/cs/300visitoraccess/iossdk#%E4%B8%8A%E4%BC%A0appstore%E4%BB%A5%E5%8F%8A%E6%89%93%E5%8C%85ipa%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9
?
4、那么剔除x86_64 i386时会遇到can't open input file的错误,这是因为cd的路径错误,把“/HelpDesk.framework”删掉。是cd到framework所在的路径,不是cd到framework
9访客端剔除cd错误.png

?
5、下图中的报错,需要在pch文件添加如下判断,hg3399.com|官方网站的和自己的头文件都引入到#ifdef内部
? ?#ifdef __OBJC__
? ?#endif
10pch加判断1.png

11pch加判断2.png

?
6、集成hg3399.com|官方网站HelpDeskUI的时候,由于HelpDeskUI内部使用了第三方库,如果与开发者第三方库产生冲突,可将HelpDeskUI中冲突的第三方库删除,如果第三方库中的接口有升级的部分,请酌情进行升级。
12第三方库冲突.png

?
7、集成1.2.2版本demo中的HelpDeskUI,Masonry报错:Passing ‘CGFloat’(aka ‘double’) to parameter of incompatible type ‘__strong id’
需要在pch中添加#define MAS_SHORTHAND_GLOBALS
注意:要在#import "Masonry.h"之前添加此宏定义
13访客端Masonry报错.png
继续阅读 ?
注意:向自己工程中添加hg3399.com|官方网站SDK和UI文件的时候,不要直接向xcode中拖拽添加,先把SDK和UI文件粘贴到自己工程的finder目录中,再从finder中向xcode中拖拽添加,避免出现找不到SDK或者UI文件的情况。
?
1、很多同学在首次“导入SDK”或“更新SDK重新导入SDK”后,Xcode运行报以下的error:
dyld: Library not loaded: @rpath/Hyphenate.framework/Hyphenate
? Referenced from: /Users/shenchong/Library/Developer/CoreSimulator/Devices/C768FE68-6E79-40C8-8AD1-FFFC434D51A9/data/Containers/Bundle/Application/41EA9A48-4DD5-4AA4-AB3F-139CFE036532/CallBackTest.app/CallBackTest
? Reason: image not found
? ? ? ?这个原因是工程未加载到 framework,正确的处理方式是在TARGETS → General → Embedded Binaries 中添加HelpDesk.framework和Hyphenate.framework依赖库,且 Linked Frameworks and Libraries中依赖库的Status必须是Required。
1访客端_image_not_found.png

?
2、运行之后,自变量为nil,这就有可能是因为上面所说的依赖库的status设置为了Optional,需要改成Required。
2访客端自变量为nil.png

?
3、打包后上传到appstore报错
(1)ERROR ITMS-90535: "Unexpected CFBundleExecutable Key. The bundle at 'Payload/toy.app/HelpDeskUIResource.bundle' does not contain a bundle executable. If this bundle intentionally does not contain an executable, consider removing the CFBundleExecutable key from its Info.plist and using a CFBundlePackageType of BNDL. If this bundle is part of a third-party framework, consider contacting the developer of the framework for an update to address this issue."
方法:把HelpDeskUIResource.bundle里的Info.plist删掉就即可。
3访客端打包90535.png

(2)This bundle is invalid. The value for key CFBundleShortVersionString ‘1.2.2.1’in the Info.plist must be a period-separated list of at most three non-negative integers.?
4访客端打包90060.png

把sdk里的plist文件的版本号改成3位数即可
5访客端打包1.2_.2_.1位置_.png

(3)Invalid Mach-O Format.The Mach-O in bundle “SMYG.app/Frameworks/Hyphenate.framework” isn’t consistent with the Mach-O in the main bundle.The main bundle Mach-O contains armv7(bitcode) and arm64(bitcode),while the nested bundle Mach-O contains armv7(machine code) and arm64(machine code).Verify that all of the targets for a platform have a consistent value for the ENABLE_BITCODE build setting.”
6访客端打包90636.png

将TARGETS-Build Settings-Enable Bitcode改为NO
7访客端打包bitcode改为NO.png

(4)还有很多同学打包失败,看不出什么原因
8访客端打包需剔除.png

那么可以先看看有没有按照文档剔除x86_64 i386两个平台
文档链接:http://docs.easemob.com/cs/300visitoraccess/iossdk#%E4%B8%8A%E4%BC%A0appstore%E4%BB%A5%E5%8F%8A%E6%89%93%E5%8C%85ipa%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9
?
4、那么剔除x86_64 i386时会遇到can't open input file的错误,这是因为cd的路径错误,把“/HelpDesk.framework”删掉。是cd到framework所在的路径,不是cd到framework
9访客端剔除cd错误.png

?
5、下图中的报错,需要在pch文件添加如下判断,hg3399.com|官方网站的和自己的头文件都引入到#ifdef内部
? ?#ifdef __OBJC__
? ?#endif
10pch加判断1.png

11pch加判断2.png

?
6、集成hg3399.com|官方网站HelpDeskUI的时候,由于HelpDeskUI内部使用了第三方库,如果与开发者第三方库产生冲突,可将HelpDeskUI中冲突的第三方库删除,如果第三方库中的接口有升级的部分,请酌情进行升级。
12第三方库冲突.png

?
7、集成1.2.2版本demo中的HelpDeskUI,Masonry报错:Passing ‘CGFloat’(aka ‘double’) to parameter of incompatible type ‘__strong id’
需要在pch中添加#define MAS_SHORTHAND_GLOBALS
注意:要在#import "Masonry.h"之前添加此宏定义
13访客端Masonry报错.png
收起阅读 ?

(客服云)iOS访客端怎么判断会话是否结束

1、联系商务开通【会话创建、接起、结束】功能
2、在 cmdMessagesDidReceive 方法中做如下判断,返回ServiceSessionClosedEvent,则是会话已结束,如图:
判断会话是否结束.png

代码:
if ([message.body?isKindOfClass:[EMCmdMessageBody class]]) {
EMCmdMessageBody *_bb = (EMCmdMessageBody *)message.body;
if ([_bb.action?isEqualToString:@"ServiceSessionCreatedEvent"]) {
NSLog(@"hhhhh--Creat");
} else if ([_bb.action?isEqualToString:@"ServiceSessionOpenedEvent"]) {
NSLog(@"hhhhh--Open");
} else if ([_bb.action?isEqualToString:@"ServiceSessionClosedEvent"]) {
NSLog(@"hhhhh--Close");
}
}
?
继续阅读 ?
1、联系商务开通【会话创建、接起、结束】功能
2、在 cmdMessagesDidReceive 方法中做如下判断,返回ServiceSessionClosedEvent,则是会话已结束,如图:
判断会话是否结束.png

代码:
if ([message.body?isKindOfClass:[EMCmdMessageBody class]]) {
EMCmdMessageBody *_bb = (EMCmdMessageBody *)message.body;
if ([_bb.action?isEqualToString:@"ServiceSessionCreatedEvent"]) {
NSLog(@"hhhhh--Creat");
} else if ([_bb.action?isEqualToString:@"ServiceSessionOpenedEvent"]) {
NSLog(@"hhhhh--Open");
} else if ([_bb.action?isEqualToString:@"ServiceSessionClosedEvent"]) {
NSLog(@"hhhhh--Close");
}
}
? 收起阅读 ?

(客服云)iOS访客端点击订单消息

1、在HDMessageCell.m 的
- (void)_setupSubviewsWithType:(EMMessageBodyType)messageType
? ? ? ? ? ? ? ? ? ? ? isSender:(BOOL)isSender
? ? ? ? ? ? ? ? ? ? ? ? ?model:(id)model
方法中给orderBgView 添加手势
点击订单消息1.png
UITapGestureRecognizer *tapRecognizer3 = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(orderImageViewTapAction:)];
[_bubbleView.orderBgView addGestureRecognizer:tapRecognizer3];
?
2、在HDMessageCell.m 中添加手势点击事件
点击订单消息2.png
- (void)orderImageViewTapAction:(UITapGestureRecognizer *)tapRecognizer
{
if ([_delegate respondsToSelector:@selector(messageCellSelected:)]) {
[_delegate messageCellSelected:_model];
}
}
?
3、在HDMessageViewController的?
? ?- (void)messageCellSelected:(id)model 方法中添加订单消息的判断
点击订单消息3.png
代码:
if ([HDMessageHelper getMessageExtType:model.message] == HDExtOrderMsg) {
// 订单消息携带的扩展
NSDictionary *dic = model.message.ext;
NSLog(@"点击了订单消息");
}
继续阅读 ?
1、在HDMessageCell.m 的
- (void)_setupSubviewsWithType:(EMMessageBodyType)messageType
? ? ? ? ? ? ? ? ? ? ? isSender:(BOOL)isSender
? ? ? ? ? ? ? ? ? ? ? ? ?model:(id)model
方法中给orderBgView 添加手势
点击订单消息1.png
UITapGestureRecognizer *tapRecognizer3 = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(orderImageViewTapAction:)];
[_bubbleView.orderBgView addGestureRecognizer:tapRecognizer3];
?
2、在HDMessageCell.m 中添加手势点击事件
点击订单消息2.png
- (void)orderImageViewTapAction:(UITapGestureRecognizer *)tapRecognizer
{
if ([_delegate respondsToSelector:@selector(messageCellSelected:)]) {
[_delegate messageCellSelected:_model];
}
}
?
3、在HDMessageViewController的?
? ?- (void)messageCellSelected:(id)model 方法中添加订单消息的判断
点击订单消息3.png
代码:
if ([HDMessageHelper getMessageExtType:model.message] == HDExtOrderMsg) {
// 订单消息携带的扩展
NSDictionary *dic = model.message.ext;
NSLog(@"点击了订单消息");
}
收起阅读 ?

(客服云)IOS访客端设置访客昵称头像

1.在HDMessageViewController.h 中添加访客昵称、头像的属性
1.png
// 访客昵称
@property (nonatomic, strong) NSString *sendName;
// 访客头像(url)
@property (nonatomic, strong) NSString *sendAvatarUrl;
// 访客头像(本地图片)
@property (nonatomic, strong) UIImage *sendAvatarImage;
?
2.在HDMessageViewController.m ? - (NSArray *)formatMessages:(NSArray *)messages 方法中添加判断
2.png
if (isSender) {
if (self.sendName) {
model.nickname = self.sendName;
}
// 加载网络头像
if (self.sendAvatarUrl) {
model.avatarURLPath = self.sendAvatarUrl;
}
// 加载本地头像
if (self.sendAvatarImage) {
model.avatarImage = self.sendAvatarImage;
model.avatarURLPath = nil;
}
}
?
3.在初始化聊天页面的时候,传入访客的昵称、头像即可。
(可选择url或者本地头像图片)
3.png
ctrl.sendName = @"访客昵称";
ctrl.sendAvatarImage = [UIImage imageNamed:@"测试图片"];
// chat.sendAvatarUrl = @"";
继续阅读 ?
1.在HDMessageViewController.h 中添加访客昵称、头像的属性
1.png
// 访客昵称
@property (nonatomic, strong) NSString *sendName;
// 访客头像(url)
@property (nonatomic, strong) NSString *sendAvatarUrl;
// 访客头像(本地图片)
@property (nonatomic, strong) UIImage *sendAvatarImage;
?
2.在HDMessageViewController.m ? - (NSArray *)formatMessages:(NSArray *)messages 方法中添加判断
2.png
if (isSender) {
if (self.sendName) {
model.nickname = self.sendName;
}
// 加载网络头像
if (self.sendAvatarUrl) {
model.avatarURLPath = self.sendAvatarUrl;
}
// 加载本地头像
if (self.sendAvatarImage) {
model.avatarImage = self.sendAvatarImage;
model.avatarURLPath = nil;
}
}
?
3.在初始化聊天页面的时候,传入访客的昵称、头像即可。
(可选择url或者本地头像图片)
3.png
ctrl.sendName = @"访客昵称";
ctrl.sendAvatarImage = [UIImage imageNamed:@"测试图片"];
// chat.sendAvatarUrl = @"";
收起阅读 ?

(客服云)iOS访客端设置客服系统头像

0、在客服系统内 管理员模式--设置--企业基本信息 处上传企业logo
? ?在 管理员模式--设置--系统开关--系统开关--访客端显示客服昵称 处打开开关
客服系统头像1.png

客服系统头像2.png


1、在HDIMessageModel.h 中添加客服系统头像url属性
客服系统头像3.png
@property (strong, nonatomic) NSString *officialAccountURL;
?
2、在HDMessageModel.h 中添加客服系统头像url属性
客服系统头像4.png
@property (strong, nonatomic) NSString *officialAccountURL;
?
3、在HDMessageModel.m类 ?- (instancetype)initWitMessage:(HDMessage *)message方法中添加 ?
客服系统头像5.png
NSDictionary *officialAccount = [NSDictionary dictionary];
if ([weichat objectForKey:@"official_account"]) {
officialAccount = [weichat valueForKey:@"official_account"];
if ([officialAccount objectForKey:@"img"]) {
self.officialAccountURL = [[@"https:" stringByAppendingString:[officialAccount objectForKey:@"img"]] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
}
}
?
4、在HDBaseMessageCell.m类 ?- (void)setModel:(id)model方法中 修改代码?
(“系统消息”改成您自己客服系统中设置的调度员昵称)
客服系统头像6.png
if (model.avatarURLPath) {
if (model.nickname) {
if ([model.nickname isEqualToString:@"系统消息"]) {
if (model.officialAccountURL) {
[self.avatarView sd_setImageWithURL:[NSURL URLWithString:model.officialAccountURL] placeholderImage:model.avatarImage];
}
} else {
[self.avatarView sd_setImageWithURL:[NSURL URLWithString:model.avatarURLPath] placeholderImage:model.avatarImage];
}
}

} else {
self.avatarView.image = model.avatarImage;
}
继续阅读 ?
0、在客服系统内 管理员模式--设置--企业基本信息 处上传企业logo
? ?在 管理员模式--设置--系统开关--系统开关--访客端显示客服昵称 处打开开关
客服系统头像1.png

客服系统头像2.png


1、在HDIMessageModel.h 中添加客服系统头像url属性
客服系统头像3.png
@property (strong, nonatomic) NSString *officialAccountURL;
?
2、在HDMessageModel.h 中添加客服系统头像url属性
客服系统头像4.png
@property (strong, nonatomic) NSString *officialAccountURL;
?
3、在HDMessageModel.m类 ?- (instancetype)initWitMessage:(HDMessage *)message方法中添加 ?
客服系统头像5.png
NSDictionary *officialAccount = [NSDictionary dictionary];
if ([weichat objectForKey:@"official_account"]) {
officialAccount = [weichat valueForKey:@"official_account"];
if ([officialAccount objectForKey:@"img"]) {
self.officialAccountURL = [[@"https:" stringByAppendingString:[officialAccount objectForKey:@"img"]] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
}
}
?
4、在HDBaseMessageCell.m类 ?- (void)setModel:(id)model方法中 修改代码?
(“系统消息”改成您自己客服系统中设置的调度员昵称)
客服系统头像6.png
if (model.avatarURLPath) {
if (model.nickname) {
if ([model.nickname isEqualToString:@"系统消息"]) {
if (model.officialAccountURL) {
[self.avatarView sd_setImageWithURL:[NSURL URLWithString:model.officialAccountURL] placeholderImage:model.avatarImage];
}
} else {
[self.avatarView sd_setImageWithURL:[NSURL URLWithString:model.avatarURLPath] placeholderImage:model.avatarImage];
}
}

} else {
self.avatarView.image = model.avatarImage;
}
收起阅读 ?

(客服云)iOS访客端获取机器人欢迎语

注意:
0、代码中有两个拼接的url显示不全,我在评论有补充。
1、此文档只支持获取单机器人的欢迎语,多机器人会获取第一个机器人的欢迎语。
2、此文档暂不支持获取多媒体和图文消息类型的机器人欢迎语。
3、老版机器人与新版机器人集成方法不同,集成前需区分好。
机器人欢迎语1.png

机器人欢迎语2.png

?
一、先到客服系统配置机器人欢迎语
1、老版机器人设置方案:
管理员模式--智能机器人--机器人设置--自动回复--欢迎语,开启开关并添加欢迎语
机器人欢迎语3.png

2、新版(企业版)机器人设置方案:
(1)管理员模式--智能机器人--机器人设置--基础设置,点击“机器人管理”跳转到机器人管理页面
(2)机器人设置--自动回复--欢迎语,开启开关并添加欢迎语
机器人欢迎语4.png

?
二、iOS端获取、解析机器人欢迎语
在HDMessageViewController.m类的 -(void)viewDidLoad 方法最后调用robotWelcome或者newRobotWelcome 方法即可。其余客户端插入消息的逻辑自行处理
//获取老版机器人欢迎语
- (void)robotWelcome
{
// kDefaultTenantId:租户id
// kDefaultCustomerName:IM服务号
NSString *urlStr = [NSString stringWithFormat:@"https://kefu.easemob.com/v1/Te ... ot%3B, kDefaultTenantId];
NSString *newStr = [urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSURL *url = [NSURL URLWithString:newStr];
NSURLRequest *requst = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:10];
//异步链接(形式1,较少用)
[NSURLConnection sendAsynchronousRequest:requst queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {

// 解析
NSString *result =[[ NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
//同样的可以替换字符
NSLog(@"result-----%@", result);
NSString *str = [result stringByReplacingOccurrencesOfString:@""" withString:@"\""];
NSString *str1 = [str stringByReplacingOccurrencesOfString:@"\"{" withString:@"{"];
NSString *str2 = [str1 stringByReplacingOccurrencesOfString:@"}\"" withString:@"}"];
// JSON字符串转字典
NSDictionary *dic = [self dictionaryWithJsonString:str2];
// 取消息的ext
NSLog(@"dic---%@",dic);

NSString *robotText = nil;
NSDictionary *dicExt = [NSDictionary dictionary];
if ([[dic objectForKey:@"greetingText"] isKindOfClass:[NSString class]]) {

robotText = [dic objectForKey:@"greetingText"];
} else {
dicExt = [[dic objectForKey:@"greetingText"] objectForKey:@"ext"];
}

//构建消息
EMTextMessageBody *bdy = [[EMTextMessageBody alloc] initWithText:robotText];
NSString *from = [[HDClient sharedClient] currentUsername];
HDMessage *message = [[HDMessage alloc] initWithConversationID:kDefaultCustomerName from:from to:kDefaultCustomerName body:bdy];
message.ext = dicExt;
message.direction = 1;
message.status = HDMessageStatusSuccessed;
// 消息添加到UI
[self addMessageToDataSource:message progress:nil];
// 消息插入到会话
HDError *pError;
[self.conversation addMessage:message error:&pError];
}];
}


//获取新版(企业版)机器人欢迎语
- (void)newRobotWelcome
{
[[HDClient sharedClient] accessToken];
// 以下信息换成自己的
// kDefaultTenantId:租户id
// kDefaultOrgName:appkey中#的前半部分
// kDefaultAppName:appkey中#的后半部分
// kDefaultCustomerName:IM服务号
NSString *imToken = [HDClient sharedClient].accessToken;
NSString *urlStr = [NSString stringWithFormat:@"https://kefu.easemob.com/v1/we ... ot%3B,kDefaultTenantId, kDefaultOrgName, kDefaultAppName, kDefaultCustomerName, imToken];
NSString *newStr = [urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSLog(@"newStr---%@", newStr);
NSURL *url = [NSURL URLWithString:newStr];
NSURLRequest *requst = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:10];
//异步链接(形式1,较少用)
[NSURLConnection sendAsynchronousRequest:requst queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
// 解析
NSString *result = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
//同样的可以替换字符
NSString *str = [result stringByReplacingOccurrencesOfString:@"&quot;" withString:@"\""];
NSString *str1 = [str stringByReplacingOccurrencesOfString:@"\"{" withString:@"{"];
NSString *str2 = [str1 stringByReplacingOccurrencesOfString:@"}\"" withString:@"}"];
// JSON字符串转字典
NSDictionary *dic = [self dictionaryWithJsonString:str2];
// 取消息的ext
NSString *robotText = nil;
NSDictionary *dicExt = [NSDictionary dictionary];
if ([[[dic objectForKey:@"entity"] objectForKey:@"greetingText"] isKindOfClass:[NSString class]]) {
robotText = [[dic objectForKey:@"entity"] objectForKey:@"greetingText"];
} else {
dicExt = [[[dic objectForKey:@"entity"] objectForKey:@"greetingText"] objectForKey:@"ext"];
}
//构建消息
NSLog(@"dicExt---%@",dicExt);
EMTextMessageBody *bdy = [[EMTextMessageBody alloc] initWithText:robotText];
NSString *from = [[HDClient sharedClient] currentUsername];

HDMessage *message = [[HDMessage alloc] initWithConversationID:kDefaultCustomerName from:from to:kDefaultCustomerName body:bdy];
message.ext = dicExt;
message.direction = 1;
message.status = HDMessageStatusSuccessed;
// 消息添加到UI
[self addMessageToDataSource:message progress:nil];
// 消息插入到会话
HDError *pError;
[self.conversation addMessage:message error:&pError];
}];
}

// JSON字符串转化为字典
- (NSDictionary *)dictionaryWithJsonString:(NSString *)jsonString
{
if (jsonString == nil) {
return nil;
}
NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
NSError *err;
NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:jsonData
options:NSJSONReadingMutableContainers
error:&err];
if(err)
{
NSLog(@"json解析失败:%@",err);
return nil;
}
return dic;
}

继续阅读 ?
注意:
0、代码中有两个拼接的url显示不全,我在评论有补充。
1、此文档只支持获取单机器人的欢迎语,多机器人会获取第一个机器人的欢迎语。
2、此文档暂不支持获取多媒体和图文消息类型的机器人欢迎语。
3、老版机器人与新版机器人集成方法不同,集成前需区分好。
机器人欢迎语1.png

机器人欢迎语2.png

?
一、先到客服系统配置机器人欢迎语
1、老版机器人设置方案:
管理员模式--智能机器人--机器人设置--自动回复--欢迎语,开启开关并添加欢迎语
机器人欢迎语3.png

2、新版(企业版)机器人设置方案:
(1)管理员模式--智能机器人--机器人设置--基础设置,点击“机器人管理”跳转到机器人管理页面
(2)机器人设置--自动回复--欢迎语,开启开关并添加欢迎语
机器人欢迎语4.png

?
二、iOS端获取、解析机器人欢迎语
在HDMessageViewController.m类的 -(void)viewDidLoad 方法最后调用robotWelcome或者newRobotWelcome 方法即可。其余客户端插入消息的逻辑自行处理
//获取老版机器人欢迎语
- (void)robotWelcome
{
// kDefaultTenantId:租户id
// kDefaultCustomerName:IM服务号
NSString *urlStr = [NSString stringWithFormat:@"https://kefu.easemob.com/v1/Te ... ot%3B, kDefaultTenantId];
NSString *newStr = [urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSURL *url = [NSURL URLWithString:newStr];
NSURLRequest *requst = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:10];
//异步链接(形式1,较少用)
[NSURLConnection sendAsynchronousRequest:requst queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {

// 解析
NSString *result =[[ NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
//同样的可以替换字符
NSLog(@"result-----%@", result);
NSString *str = [result stringByReplacingOccurrencesOfString:@""" withString:@"\""];
NSString *str1 = [str stringByReplacingOccurrencesOfString:@"\"{" withString:@"{"];
NSString *str2 = [str1 stringByReplacingOccurrencesOfString:@"}\"" withString:@"}"];
// JSON字符串转字典
NSDictionary *dic = [self dictionaryWithJsonString:str2];
// 取消息的ext
NSLog(@"dic---%@",dic);

NSString *robotText = nil;
NSDictionary *dicExt = [NSDictionary dictionary];
if ([[dic objectForKey:@"greetingText"] isKindOfClass:[NSString class]]) {

robotText = [dic objectForKey:@"greetingText"];
} else {
dicExt = [[dic objectForKey:@"greetingText"] objectForKey:@"ext"];
}

//构建消息
EMTextMessageBody *bdy = [[EMTextMessageBody alloc] initWithText:robotText];
NSString *from = [[HDClient sharedClient] currentUsername];
HDMessage *message = [[HDMessage alloc] initWithConversationID:kDefaultCustomerName from:from to:kDefaultCustomerName body:bdy];
message.ext = dicExt;
message.direction = 1;
message.status = HDMessageStatusSuccessed;
// 消息添加到UI
[self addMessageToDataSource:message progress:nil];
// 消息插入到会话
HDError *pError;
[self.conversation addMessage:message error:&pError];
}];
}


//获取新版(企业版)机器人欢迎语
- (void)newRobotWelcome
{
[[HDClient sharedClient] accessToken];
// 以下信息换成自己的
// kDefaultTenantId:租户id
// kDefaultOrgName:appkey中#的前半部分
// kDefaultAppName:appkey中#的后半部分
// kDefaultCustomerName:IM服务号
NSString *imToken = [HDClient sharedClient].accessToken;
NSString *urlStr = [NSString stringWithFormat:@"https://kefu.easemob.com/v1/we ... ot%3B,kDefaultTenantId, kDefaultOrgName, kDefaultAppName, kDefaultCustomerName, imToken];
NSString *newStr = [urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSLog(@"newStr---%@", newStr);
NSURL *url = [NSURL URLWithString:newStr];
NSURLRequest *requst = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:10];
//异步链接(形式1,较少用)
[NSURLConnection sendAsynchronousRequest:requst queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
// 解析
NSString *result = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
//同样的可以替换字符
NSString *str = [result stringByReplacingOccurrencesOfString:@"&quot;" withString:@"\""];
NSString *str1 = [str stringByReplacingOccurrencesOfString:@"\"{" withString:@"{"];
NSString *str2 = [str1 stringByReplacingOccurrencesOfString:@"}\"" withString:@"}"];
// JSON字符串转字典
NSDictionary *dic = [self dictionaryWithJsonString:str2];
// 取消息的ext
NSString *robotText = nil;
NSDictionary *dicExt = [NSDictionary dictionary];
if ([[[dic objectForKey:@"entity"] objectForKey:@"greetingText"] isKindOfClass:[NSString class]]) {
robotText = [[dic objectForKey:@"entity"] objectForKey:@"greetingText"];
} else {
dicExt = [[[dic objectForKey:@"entity"] objectForKey:@"greetingText"] objectForKey:@"ext"];
}
//构建消息
NSLog(@"dicExt---%@",dicExt);
EMTextMessageBody *bdy = [[EMTextMessageBody alloc] initWithText:robotText];
NSString *from = [[HDClient sharedClient] currentUsername];

HDMessage *message = [[HDMessage alloc] initWithConversationID:kDefaultCustomerName from:from to:kDefaultCustomerName body:bdy];
message.ext = dicExt;
message.direction = 1;
message.status = HDMessageStatusSuccessed;
// 消息添加到UI
[self addMessageToDataSource:message progress:nil];
// 消息插入到会话
HDError *pError;
[self.conversation addMessage:message error:&pError];
}];
}

// JSON字符串转化为字典
- (NSDictionary *)dictionaryWithJsonString:(NSString *)jsonString
{
if (jsonString == nil) {
return nil;
}
NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
NSError *err;
NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:jsonData
options:NSJSONReadingMutableContainers
error:&err];
if(err)
{
NSLog(@"json解析失败:%@",err);
return nil;
}
return dic;
}

收起阅读 ?