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

article/2025/8/25 23:44:08

文章目录

  • 前言
    • 一、用户端
    • 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://chatgpt.dhexx.cn/article/A1mYGW46.shtml

相关文章

使用 Python 编写一个聊天小程序

欢迎关注 “小白玩转Python”&#xff0c;发现更多 “有趣” 本篇文章分享如何用相当简洁的 Python 代码制作一个简单的聊天应用程序。更重要的是&#xff0c;我已经实现了没有任何第三方依赖的代码&#xff01; 首先&#xff0c;我创建了一个聊天服务器&#xff0c;通过它可以…

小米手机解BL锁教程

1.找到设置&#xff0c;找到我的设备 2.点击全部参数&#xff0c;多点几下miui版本&#xff0c;直到弹出开发者模式提醒。 3.返回&#xff0c;找到更多设置 4.找到开发者选项 5.找到设备定状态 6.绑定账号和设备&#xff0c;关机&#xff0c;按开键加音量减&#xff0c;进去fas…

安卓搞机玩机-什么是“锁 ” BL锁 屏幕锁 账号锁 设备锁等分析

相信把玩安卓机型的友友们都大概了解机型的锁是什么概念。但有些友友可能还分不清楚具体锁的概念。今天这个帖子由浅入深的带你了解安卓机型中各种“锁”的概念.这类话题比较敏感。只是大概带你分清楚锁的分类和基本对应的解锁方式. 一.屏幕锁【 图案锁 指纹锁 数字锁 人…

万能小米手机解锁,刷机,默认破解BL锁

写在第一条:下载之前请注意 本软件不支持最新机型,只支持4代和4代以前的机型,请注意 早就想给自己的小米手机刷机了&#xff0c;奈何一直没有门&#xff0c;最后还在求助了万能的淘宝&#xff0c;刚刚在淘宝花了 30大洋买的刷机工具&#xff0c;刷机成功之后&#xff0c;才反…

xiaomi 小米 红米redmi 秒解锁BL锁,不用等,在线秒解锁BL工具介绍

xiaomi 小米 红米redmi 秒解锁BL锁,不用等&#xff0c;在线秒解锁BL Xlaomi Redmi K40 Gaming Xlaomt Poco F3 GT Gaming Xaoml Poco X3 GT Xaoml Redmi Note9 5G Xlaomi Redml Note 10 Pro 5G Xlaoml Redmi Note 10 5G Xlaoml Redml Note 10T 5G Xlaoml Poco M3 Pro 5G Xaoml…

小米手机解BL锁、线刷详细教程,适用于小米全系列手机

[教程] 小米手机解BL锁、线刷详细教程&#xff0c;适用于小米全系列手机 这几天看到论坛里很多人在问怎么线刷&#xff0c;下面我就做个详细的线教程大家看一下高手别喷我哈 此教程只适合刷官方MIUI包 进入正题。 第一步&#xff1a;解BL锁 1.浏览器打开申请解锁小米手机点击立…

android@解bl锁@twrp的刷入和使用问题

文章目录 解bl锁步骤 附:MIUI线刷工具待线刷设备扫面和检查使用方式线刷工具卡住的关闭方法 第三方recoverytwrp解密datadata decrypt清除data后用twrp中刷入文件刷入面具 如何找到合适的TWRP rec ref 解bl锁 不是所有android设备都支持解锁下面是以最为典型的MIUI android设备…

02_Lock锁

首先看一下JUC的重磅武器——锁&#xff08;Lock&#xff09; 相比同步锁&#xff0c;JUC包中的Lock锁的功能更加强大&#xff0c;它提供了各种各样的锁&#xff08;公平锁&#xff0c;非公平锁&#xff0c;共享锁&#xff0c;独占锁……&#xff09;&#xff0c;所以使用起来…

红米note10 pro机型解除“账号锁”的一些操作案例 mtk机型强解bl锁

前言。操作解除锁类案例只限于自己的机型&#xff0c; 因手机号长期不用或者忘记密码导致账号锁出现的问题 机型哦 我经常在csdn网站分享一些玩机中的实测资源和玩机常识。遇到很多玩机的友友&#xff0c;其中我分享的一个资源&#xff0c;这个粉丝朋友下载后在网吧操作不了。…

穷举法解华为bl锁

穷举法解华为bl锁 python3代码测试截图 灵感来自于&#xff1a;https://blog.csdn.net/qq_40169767/article/details/90481748 但是我不懂shell脚本&#xff0c;那个脚本又运行不了&#xff0c;所以我用python写了一个&#xff0c; 穷举要0.05s*9999999999999999/60*60*24*3651…

玩机搞机---关于安卓机型工厂固件 刷机 端口解密 解bl锁 写串 nv损坏 等相关常识

*******工程机和工厂固件方面的常识 可能很多玩机友友对什么是工厂固件比较陌生。那么今天的话题就围绕这个和大家讨论下。其实一般厂家的流程都是在一部机型推放市场之前&#xff0c;需要经过预研企划、研发设计、全面测试等诸多环节。在这一整个改善的全过程中&#xff0c;厂…

小米解锁BL锁(普通解锁)

关于BL锁&#xff0c;BootLoader锁&#xff0c;WIKI上的解释简单一点说就是 启动操作系统的软件&#xff0c;也就是引导程序&#xff0c;用于初始化内存等。 官方的说法是防止篡改程序所造成用户的经济和数据损失&#xff0c;网上的说法是可定义程度高&#xff0c;可卸载系统预…

Android系统刷机教程之解bl锁

由于安卓系统是基于linux开发的&#xff0c;属于嵌入式操作系统。在嵌入式操作系统中&#xff0c;BootLoader是在操作系统内核运行之前运行。可以初始化硬件设备、建立内存空间映射图&#xff0c;从而将系统的软硬件环境带到一个合适状态&#xff0c;以便为最终调用操作系统内核…

oppo r11 r11t解BL锁安装面具magisk详细教程

OPPO r11 r11t是高通骁龙660处理器&#xff0c;这个系列要解bl锁网上有很多教程比较http://rom点7to点cn/jiaochengdetail/16880 解好BL锁后就可以找三方rec&#xff0c;刷入rec 进入三方rec&#xff0c;把小包发送到手机硬盘&#xff0c;安装小包&#xff0c;再次重启进入三方…

华为手机一键解锁工具箱下载 | 华为手机解BL锁软件: 支持解锁bootloader,刷写recovery功能

文章目录 1. 软件介绍2. 特色功能3. 资源站点4. 下载地址5. 软件截图6. 安装教程7. 使用教程7.1. 解锁BL 1. 软件介绍 通过这款华为手机实用工具箱可以对你的华为手机系列进行刷机、解锁等操作&#xff0c;网上这种华为刷机解锁工具比较少&#xff0c;那么这款华为手机实用工具…

小米手机解bl锁正规方法!

申请解锁小米手机http://www.miui.com/unlock/download.html在手机上打开开发者模式&#xff0c;在开发者模式下-》设备解锁状态 然后关了wifi用手机卡上网绑定设备。 之后在菜单上选择清除所有数据&#xff0c;然后进入系统即可。

[HBZ分享] 小米手机如何解BL锁

第一步&#xff1a; 进入【设置—>我的设备–>全部参数–>连续疯狂的点MIUI版本那一行】 第二步&#xff1a;进入【更多设置–>开发者模式】&#xff0c;打开USB调试 与 USB安装 第三步&#xff1a;进入【更多设置–>开发者模式】&#xff0c;进入【设别解锁状…

9008 能 解锁BL_手机刷入面具及twrp教程(包含解bl锁教程)

原文作者:sgq694243467 原文链接:https://www.52pojie.cn/thread-1288591-1-1.html 最近了买黑鲨3pro 因为miui的广告很烦就想刷面具和xp框架屏蔽广告,但是网上似乎没什么教程,不是要收费就是空壳教程。经过一天测试和查找最终自己刷好了面具 root 和xp框架参考链接 https:…

【小白搞机入门】名词集-BootLoader锁(BL锁)

系列说明&#xff1a;由于作者认识浅薄&#xff0c;很多方面不能解释到十全十美&#xff0c;仅供参考。系列中收录的解决办法并非万能&#xff0c;请谨慎使用。 定义 BootLoader锁&#xff0c;以下简称“BL锁”&#xff0c;从字面意义上理解&#xff0c;是手机厂商对BootLoader…