支付宝小程序平台的IM聊天插件

文章目录

  • 前言
    • 一、用户端
    • 1.基本展示
    • 2.难处理的点
    • 二、另一用户端
      • 1.前端websocket的整合
      • 2.手机息屏websocket断线问题
      • 2.websocket服务端配置
      • 3.后端整合websocket作为服务端,传输消息给前端
    • 总结


前言

最近工作需求来了个项目,前景为在支付宝平台上发布一个处方插件即小程序插件: 源于我们日常微信聊天的页面,只是旨在重心不同,微信着重于IM日常生活通讯,我的支付宝小程序插件插件重在提供IM问诊功能,以及问诊后,医生进行开方回传给患者用户。


一、用户端

1.基本展示

基本通讯

  • 其基本流程概要便是这个输入框回车发送了。
 <!-- 文字+图片消息模板--><view a:for="{{chatLists}}" a:key="{{index}}" a:for-index="index"><view class={{item.role=='patient'?"answer":"question"}} id='msg-{{index}}'><view class={{item.role=='patient'?"heard_img right" :"heard_img left"}}><image mode="scaleToFill" class="user-profile__avatar" src="{{item.userImgSrc}}" /></view><view class={{item.role=='patient'?"answer_text":"question_text"}} data-index="{{index}}" hidden="{{(item.msg_type==='image')}}"><view class="symbol"></view><view class="info"><text selectable="true">{{item.textMessage}}</text></view><view a:if={{item.isSending}} class="notice"><image class="icon" mode="scaleToFill" src="{{item.icon}}" /></view></view><view class={{item.role=='patient'?"answer_text": "question_text"}} hidden="{{!(item.msg_type==='image')}}"><image  mode="aspectFill" onTap="previewImage" data-index="{{index}}"  style="width:150px; height:170px" src="{{item.textMessage}}" /></view></view>
</view><input a:if="{{inputObj.inputStatus==='text'}}" class="chat-input-style" selection-start="-1" selection-end="-1" cursor="-1" maxlength="500" confirm-type="send" value="{{textMessage}}"adjust-position='{{false}}'focus="{{focus}}"onConfirm="chatInputSendTextMessage" onFocus="chatInputBindFocusEvent" onBlur="chatInputBindBlurEvent" onInput="chatInputGetValueEvent" confirm-hold="{{true}}"	placeholder='想和TA说点什么呢?'cursor-spacing='20'/>

其核心便是这个input组件的onConfirm属性,通过在js中设置方法回调进行将input的value获取,然后把整个页面的消息chatLists进行setData重新渲染即可。

2.难处理的点

如上基本展示是很容易去实现的,只要把握好页面结构html以及css样式即可。但是如下如果是要在支付宝小程序平台下去仿微信这样基于日常IM聊天的效果的体验感,是有点难度的,因为小程序是基于web开发的。**然后需要优化的点便是1.点击额外功能区把页面顶起来。2.在上滑获得聊天记录时,如果点击输入框,弹出键盘时,需要聚焦回到最底部消息 3.滑动页面时候在ios上会有卡顿的现象(估计是帧率的问题) 4.手机息屏1分钟左右,websocket断开的问题,导致无法进行正常通讯 ** 看如下动态图

  • 像这个点击 + 进行把额外操作区顶起来的关键代码如下
 <scroll-view scroll-y="{{true}}" onTouchStart="clickCloseExtra"  class="speak_box"  scroll-top="{{scrollTop}}"  onScroll="viewScroll"  scroll-into-view="{{toView}}"  style="margin-bottom:{{marginBottom}};height:{{chatHeight}}px">
.speak_box{display: block;-webkit-overflow-scrolling: touch;height: 100vh;padding:10px;
}
_page.chatInputExtraClickEvent = function (e) {_page.setData({'inputObj.extraObj.chatInputShowExtra': !_page.data.inputObj.extraObj.chatInputShowExtra,'marginBottom': !_page.data.inputObj.extraObj.chatInputShowExtra?'2.6rem':'.98rem',scrollTop: _page.data.scrollHeight,});extraButtonClickEvent && extraButtonClickEvent(!_page.data.inputObj.extraObj.chatInputShowExtra);};

对于第一点:点击+进行弹起,其关键在于将整个scroll-view的高度100%于整个屏幕(关键height: 100vh),后续采用margin-bottom,监听+号是否被点击事件(_page.data.inputObj.extraObj.chatInputShowExtra)
去解决setData该属性顶起的高度

对于第二点:在上滑获得聊天记录时,如果点击输入框。其主要是触发input的聚焦事件

_page.chatInputBindFocusEvent = function (e) {let messageList = _page.data.chatLists;_page.setData({'inputObj.inputType': 'text','inputObj.extraObj.chatInputShowExtra': false,'marginBottom': '.98rem','scrollTop': _page.data.scrollHeight-550,toView: 'msg-' +(messageList.length-1)});

其关键在于toView值,toview是前面scroll-view容器里的scroll-into-view属性的值,当将它进行setData时,由于前端消息数据变量的chatLists遍历渲染时有对应绑定下表,所以在toView时直接将其定位到最后一个消息的位置。完成下滑效果。
次关键点还有scrollTop,其为是滚动到页面的目标位置的API,这里将’scrollTop’: _page.data.scrollHeight-550这样设置,主要是为了scrollView的聚焦点对应上scrollTop,让后续的+点击可以顶起页面。

对于第三点: 在ios端滑动页面不流畅问题,就很好解决。

viewScroll: function(e){this.data.scrollTop = e.detail.scrollTop;this.data.scrollHeight = e.detail.scrollHeight;// 修复画面上下滑出现微抖动问题//超过阈值进行历史记录回显if(e.detail.scrollTop==0){if(this.time){clearTimeout(this.time);}this.time = setTimeout(()=>{this.setData({scrollTop: e.detail.scrollTop,});},250)},

在监听的scrollView滑动函数viewScroll里增加一个防抖(微妙级别的定时器)即可。

二、另一用户端

因为涉及websocket讲解,一并将上面,手机息屏1分钟左右,websocket断开的问题,导致无法进行正常通讯的解决方案提供

1.前端websocket的整合

let _this = this;// 连接let url = ws_chat+ '?biz=' + biz + '&uid=' + uid + '&name=' + name;my.connectSocket({url: url,data: {},header:{'content-type': 'application/json'},success: (res) => {console.log('WebSocket 连接成功');socket_state =1;},fail: (res) => {my.showToast({type: 'none',content: '无法连接服务器,请刷新...',duration: 1000,});},complete: () => {}});//接受云医服务器传来的数据my.onSocketMessage(function(result) {console.log(result);let resp = JSON.parse(Base64.baseDecode(result.data));if (resp.type == 'heartbeat') {return;}let content = resp.content;if (resp.type == 'text') {Assistant.sendQuestion(content,null,resp.type,false,null,_this,'doctor');}else if (resp.type == 'image') {Assistant.sendQuestion(content, null, resp.type, false, null,_this,'doctor');}else if (resp.type == 'endconsul') {Assistant.sendQuestion(content, null, "text", false, function() {_this.setData({'inputObj.chatInputShowExtra': false,})},_this,'doctor');         }else if(resp.type=='rpconsul'){Assistant.sendQuestion(content, null, 'text', false,function() {},_this,'doctor');}});

自行看代码便可理解。其是简单的一个websocket整合,可自行看支付宝相关文档介绍。

2.手机息屏websocket断线问题

其源于websocket是基于web的,当手机息屏行,可能是手机后台线程也进行闲置,无法继续为websocket提供服务,无法进行心跳继续发送,所以一分钟左右由于心跳无法继续发送就到导致websocket断开,但是重点便是这个websocket在支付宝小程序平台下只是处于断开状态,并没有关闭掉(有区别于微信小程序)。

所以当时查阅支付宝小程序websocket相关机制,1.如果是在websocket长时间心跳无保持的情况下,断开时触发my.onSocketClose的话,然后函数回调进行把websocket对象关闭掉,等待用户手机唤醒屏幕的时候 触发页面的onshow()方法再将websocket重连。 2.或者是直接在手机息屏的时候 触发onHide()方法时,直接将websocket给关闭。

2.websocket服务端配置

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {@Overridepublic void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {registry.addHandler(chatHandler(), "/gjws").addInterceptors(new WebSocketHandShakeInterceptor()).setAllowedOrigins("*");}@Beanpublic WebSocketHandler chatHandler() {return new ChatHandler();}@Beanpublic ServletServerContainerFactoryBean createWebSocketContainer() {ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();container.setMaxTextMessageBufferSize(8192);container.setMaxBinaryMessageBufferSize(8192);return container;}
}

代码中便是你websocket的url上下文,如我的配置url为im.server.url = ws://192.168.0.64:5506/gjws?biz=BIZID&uid=UID

关于nginx上的配置参考如下

location /ws {proxy_pass http://192.168.0.64:5506/gjws;#代理到上面的地址去,proxy_http_version 1.1;proxy_set_header Upgrade $http_upgrade;proxy_set_header Connection "Upgrade";proxy_set_header X-Real-IP $remote_addr;}

3.后端整合websocket作为服务端,传输消息给前端

@Overridepublic ResponseMsg<String> sendMsg(String token, RequestMsg<ReceiveMsgInVo> in) {// 医生端消息通过websocket发送到患者端ResponseMsg<String> response = new ResponseMsg<String>();String url = imServerUrl.replaceAll("BIZID", in.getData().getBiz()).replaceAll("UID", "0");String msg = JSON.toJSONString(in.getData());// base64转码String sm = new String(Base64Utils.encodeToString(msg.getBytes()));try {ImWebsocketClient wc = new ImWebsocketClient(new URI(url));wc.connect();while (wc.getReadyState().ordinal() == 0) {Thread.sleep(200);}if (wc.getReadyState().ordinal() == 1) {logger.info("医生端消息推送成功");wc.send(sm);}wc.close();response.setHead(ResponseHead.buildSuccessHead());response.setData("发送成功");} catch (Exception e) {// TODO Auto-generated catch blocklogger.error(e.getMessage(), e);response.setHead(ResponseHead.buildFailedHead());response.setData("发送失败");}return response;}

大部分系统是基于微服务的,我这边另一用户端回复消息时,是通过接口回调到我这边的服务,我这边将消息封装好ImWebsocketClient wc = new ImWebsocketClient(new URI(url));进行建立连接。建立连接成功后,触发TextWebSocketHandler的afterConnectionEstablished方法

@Overridepublic void afterConnectionEstablished(WebSocketSession session) throws Exception {// TODO Auto-generated method stubsuper.afterConnectionEstablished(session);String biz = (String) session.getAttributes().get("biz");String uid = (String) session.getAttributes().get("uid");logger.info("Login UID2:"+uid);//系统消息连接不处理if("0".equals(uid)) {return;}ChatMessageBean bean = new ChatMessageBean();bean.setBiz(biz);bean.setUid(uid);ChatHelper.addSession(session);RoomMate user = new RoomMate();user.setUid(uid);user.setSid(session.getId());ChatHelper.onLine(user);ChatHelper.joinChatRoom(bean,session.getId());logger.info("Login UID:"+uid);}

此时在前端的websocket建立连接的时候会触犯这个方法,然后进行重写将该对象与websocket session绑定。后续handler通过biz找到聊天室,获取到用户session,发送消息传至前端。其websocket消息发送关键在于session的确认从而找到对应的对象去发送。

@Overrideprotected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {// TODO Auto-generated method stubChatMessageBean bean = JSON.parseObject(Base64Utils.decodeFromString(message.getPayload()), ChatMessageBean.class);if (MessageTypeEnum.LOGIN.getCode().equals(bean.getType())) {logger.info(bean.toString());doLogin(session, bean);} else if (MessageTypeEnum.HEARTBEAT.getCode().equals(bean.getType())) {doHeartBeat(session, bean);} else if (MessageTypeEnum.TEXT.getCode().equals(bean.getType())) {logger.info(bean.toString());doText(session, bean);} else if (MessageTypeEnum.IMAGE.getCode().equals(bean.getType())) {logger.info(bean.toString());doText(session, bean);} else if (MessageTypeEnum.JOIN.getCode().equals(bean.getType())) {logger.info(bean.toString());doJoinChatRoom(session, bean);}  else if (MessageTypeEnum.NEWCONSUL.getCode().equals(bean.getType())) {logger.info(bean.toString());doNewConsul(session, bean);}  else if(MessageTypeEnum.ENDCONSUL.getCode().equals(bean.getType())) {logger.info(bean.toString());doText(session, bean);}  else if(MessageTypeEnum.RPCONSUL.getCode().equals(bean.getType())) {logger.info(bean.toString());doText(session, bean);}}private void doText(WebSocketSession session, ChatMessageBean message) throws IOException {List<RoomMate> mates = new ArrayList<>();//保存消息if(MessageTypeEnum.AUTO.getCode().equals(message.getSource())) {mates = ChatHelper.getAllRoomMates(message.getBiz(),message.getUid());}else {mates = ChatHelper.getOtherRoomMates(message.getBiz(),message.getUid());}logger.info("聊天室"+message.getBiz()+"在线用户:"+ JSON.toJSONString(mates));if(mates.size()>0) {for (RoomMate p : mates) {if(p!=null) {WebSocketSession ws = ChatHelper.getSession(p.getSid());if(ws!=null && ws.isOpen()) {ws.sendMessage(new TextMessage(Base64Utils.encodeToString(message.toString().getBytes("UTF-8"))));}}}}}

如上图,在前端进行websocket消息发送时,会触发handleTextMessage。后续只需对应将消息内容
ws.sendMessage(new TextMessage(Base64Utils.encodeToString(message.toString().getBytes(“UTF-8”))));通过聊天室另一方的userid,然后服务端websocket将消息推送至前端中即可。

总结

提示:这里对文章进行总结:

以上就是今天要讲的内容,本文仅仅简单介绍了部分IM的一个实现,小型通讯量是没问题的,如果后续说有大量的用户去时刻请求,后端可整合netty框架便可。如果有后续下咨询的朋友,请在下文评论区留言。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/250938.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

微信小程序——聊天小程序(从搭建到结束)

具体效果展示&#xff1a; 微信小程序——聊天功能&#xff08;一、环境搭建&#xff09; 微信小程序——聊天功能&#xff08;一、环境搭建&#xff09;_星^0^星的博客-CSDN博客 微信小程序——聊天功能&#xff08;二、账号的注册与登录&#xff09; 微信聊天小程序——&a…

Java-多人聊天小程序

上图啦&#xff01;&#xff01;&#xff01; 首先运行&#xff1a; Server.java 然后启动&#xff1a; Client.java 最后退出客户端&#xff1a; 代码实现 Client package chat;import javax.swing.*;public class Client {public static void main(String[] args) {// 使用…

7步搞懂手写数字识别Mnist

大家好啊&#xff0c;我是董董灿。 图像识别有很多入门项目&#xff0c;其中Mnist 手写数字识别绝对是最受欢迎的。 该项目以数据集小、神经网络简单、任务简单为优势&#xff0c;并且集合了CNN网络中该有的东西&#xff0c;可谓麻雀虽小&#xff0c;五脏俱全。 非常适合新手…

二开项目权限应用全流程-按钮级控制

二开项目权限应用全流程-按钮级控制 员工A和员工B都可以访问同一个页面&#xff08;以员工管理为例&#xff09;&#xff0c;但是员工A可以导出excel&#xff0c;员工B就不可以导出excel(看不到按钮) 思路 用户登陆成功后&#xff0c;用户可以访问的按钮级别权限保存在point…

VISIO使用技巧汇总

0.连接线拐弯或者连接不合适 0-0.Goal ​​​​​​​ 0-1. Automatic connection 0-3.Resolvent 0-3-0.ALTF9选项&#xff0c;取消粘附位置调整 0-3-1.选中线段-选中中心点-shift增加直角调整合适位置

Microsoft Visio 直线连接线

Microsoft Visio 直线连接线 1. 连接线 2. 直线连接线 3. 直线连接线图 References https://yongqiang.blog.csdn.net/

visio画太极图

步骤一 添加两个圆&#xff0c;且大圆的半径是小圆的2倍。 步骤二 往小圆添加一条直线作为直径 步骤三 选中小圆和直径,依次点击开发工具–操作–连接&#xff0c;然后选中连接后的小圆&#xff0c;再依次点击开发工具–操作–修建&#xff0c;可以分离出如下所示的两个…

visio 2007 画直线和矩形

visio 2007 画直线和矩形 1.问题描述 在一些图形中如果直接用连接线&#xff0c;会直接连到一些不理想的位置&#xff0c;而2007中不像2013及其以后那些版本中&#xff0c;有侧边栏能够直接画直线。 2.解决方式 直接选择工具栏中的红圈中的图标 能够生成红圈中的工具栏 然…

Visio对mysql怎么画er图_怎么用Visio画ER图

展开全部 画法如下&#xff1a; 1、由于Visio 2003默认的绘图模板并没有32313133353236313431303231363533e4b893e5b19e31333339653661E-R图这一项&#xff0c;但是画E-R图必须的基本图形Visio 2003还是有的&#xff0c;所以就得先把必要的图形添加到“我的模板”。以添加椭圆和…

Visio2010中设置线为直线

Visio2010中设置线为直线 在Visio2010中默认的线不是直线而是曲线&#xff0c;在画图中需要使用直线时要进行设置&#xff0c;下面介绍Visio2010中设置直线的方法。 1、打开Visio2010&#xff0c;然后点击设计&#xff1a; 2、点击调整大小下面的三角&#xff1a; 3、进入页面…

visio绘制流程图连接线总拐弯

描述 如图所示绘制流程图的连接线总拐弯 很让我强迫症发作 可以看到垂直的连接线总是会自动拐个弯 相关技巧 有说连接线中间点可以控制和增加中间点 或者按住shift 进行调整 这个还没研究明白咋操作不过没解决本质问题 此外还可以右键修改连接线属性 还可以在设计中进行调…

visio插入箭头_visio流程图中画箭头

visio流程图中画箭头 随着社会和经济的发展,电脑visio 2019软件已经成为我们生活中必不可少的一部分。visio 2019软件常常被我们使用于流程图的制作,很多第一次接触的朋友们不知道怎么在visio 2019软件制作流程图,接下来就让小编来教你们吧。 具体如下: 1. 第一步,打开电脑…

visio绘图小技巧

1.如何在图框的任意位置添加点&#xff1f; 先选中x点指令&#xff0c;再按住ctrl键&#xff0c;即可在任意位置画点 2.如何画出锯齿形线段&#xff1f; visio里面好像没有现成的锯齿形线段&#xff0c;所以可以利用直线反复折画&#xff0c;但是这里有个小技巧&#xff0c;就…

Visio简单画图使用方法

Visio使用方法 相信有很多初学者跟我一样&#xff0c;只会使用Word进行简单的画图。本章主要讲述如何使用Visio来画图&#xff08;版本为2010&#xff09; 一、系统流程图、数据流程图、ER图画法 1.打开Visio软件&#xff0c;创建简单模板 2.根据需求点击左侧"基本流程…

设计模式之~外观模式

定义&#xff1a; 为子系统中的一组接口提供一个一致的界面&#xff0c;此模式定义了一个高层接口&#xff0c;这个接口使得这一子系统更加容易使用。 结构图&#xff1a; 区分中介模式&#xff1a; 门面模式对外提供一个接口 中介模式对内提供一个接口 优点&#xff1a; 松耦…

【xv6操作系统】安装、运行与调试

一、构建、装入过程 1.编写“启动代码主体代码”&#xff08;在下载的xv6的原始代码上进行修改&#xff09; 2.源代码进行编译、链接生成系统镜像&#xff08;elf格式的目标文件&#xff09; 3.将系统镜像保存起来&#xff08;如保存到磁盘、flash或者网络服务器上&#xff…

spring入门(面试题)

Spring框架的核心&#xff1a;IoC容器和AOP模块。通过IoC容器管理POJO对象以及他们之间的耦合关系&#xff1b;通过AOP以动态非侵入的方式增强服务。 IoC让相互协作的组件保持松散的耦合&#xff0c;而AOP编程允许你把遍布于应用各层的功能分离出来形成可重用的功能组件。 IO…

spring 入门

Spring 是什么(1) Spring 是一个开源框架.  Spring 为简化企业级应用开发而生. 使用 Spring 可以使简单的 JavaBean 实现以前只有 EJB 才能实现的功能.  Spring 是一个 IOC(DI) 和 AOP 容器框架. Spring 是什么(2) 具体描述 Spring:  轻量级&#xff1a;Spring 是非侵入性…

spring入门--spring入门案例

spring是一个框架&#xff0c;这个框架可以干很多很多的事情。感觉特别吊。但是&#xff0c;对于初学者来说&#xff0c;很难理解spring到底是干什么的。我刚开始的时候也不懂&#xff0c;后来就跟着敲&#xff0c;在后来虽然懂了&#xff0c;但是依然说不明白它到底是干啥的。…

Spring入门示例

开发环境 Spring 4.3.0Myeclipse2015JDK1.8 准备阶段&#xff1a; 1、新建一Spring01项目&#xff0c;然后新建一个lib文件。将下面的添加到lib文件中 2、将lib文件所有的包导入项目 开发步骤&#xff1a; 1、新建一个Hello.java的类 1 package com.proc.bean;2 3 public class…