小程序即时通讯聊天控件(一)

小程序即时通讯(一)输入组件及使用WebSocket通信

转载请注明出处:https://blog.csdn.net/sinat_27612147/article/details/78456363

最新更新日志

2019-12-24

  • 简化本地服务启动流程。在项目根目录下启动终端,使用npm run server来启动本地WebSocket服务。

2019-09-05

  • 现已更新2.x.x版本!!!较大的版本更新呐!!!大家可以在master分支更新!

相对1.0.x及之前的版本,2.x.x新增的特性

  • 全面使用ES6语法,异步操作使用Promise和async语法糖,让代码更符合语义!(需要较新的微信开发工具,并开启"增强编译"
  • 文本输入功能已使用Component组件化,比之前的版本性能更好!
  • 最低支持微信基础库版本为2.6.1(之前的最低支持是1.4.0)
  • 修复了一些场景下的问题。比如播放语音消息时,退出聊天界面,依旧会播放语音的问题。
  • 2.x.x文档,过些日子再更新吧。有能力的同学现在就可以看新的代码啦,大部分是语法的更新和组件化。我近期比较忙,很抱歉各位同学。

IM模板功能:

  • 目前项目中已使用webSocket,实现了IM的通信功能!目前包括会话列表页面、会话页面及好友页面。支持使用nodejs开启本地WebSocket服务。详见下方文档。
  • 支持发送文本、图片、语音,支持输入法的emoji表情
  • 支持拍照,图库选择图片、图片预览
  • 支持切换到文本输入时,显示发送按钮。
  • 支持语音播放及播放动画。
  • 支持配置录制语音的最短及最长时间。
  • 支持配置自定义事件。
  • 支持聊天消息按时间排序。
  • 支持发送消息后,页面回弹到最底部。
  • 使用了最新的语音播放接口,同时兼容了低版本的语音播放接口。
  • 消息发送中、发送成功、发送失败的状态更新
  • 支持消息发送失败情况下,点击重发按钮重新发送
  • 优化时间气泡显示逻辑,相邻信息大于5分钟显示后者信息的时间
  • 在页面最上方增加了会话状态的UI展示
  • 自定义功能,显示自定义气泡。
  • 通过解析语音或图片消息信息,优先读取本地文件。
  • 实现了文件存储算法,保证10M存储空间内的语音和图片文件均为最新。
  • 各消息类型和各功能均已模块化,让你在浏览代码时愉悦轻松。(其实这算不上组件特性。。。)

IM模板不支持的功能:

  • 如果要使用群聊,目前的UI中,头像旁并没有展示成员昵称。
  • 本地没有存储历史聊天消息。这个原因请看文章结尾。
  • 目前WebSocket所有功能仅供学习和参考,若想商用,请自行开发。
  • 目前不支持以插件方式使用。

学习或使用该项目,需要你具备哪些条件

  • 需要你熟悉ES6的语法规范,以及设计模式,否则该项目对你来说门槛很高。
  • websocket原理对新手来说,不必深入掌握,但需要你知晓WebSocket的常见API及其用法,详见小程序WebSocket。
  • 了解npm依赖的安装,命令的执行(这块主要用于启动本地的WebSocket服务)

整体效果图(加载有些慢)

我们先来看下效果 (因录制软件问题,图中的一些按钮的变色了,线条也少了很多像素。。。)

  • 发送图片和图片预览
    发送图片和图片预览
  • 消息重发和发送自定义消息
    消息重发和发送自定义消息
  • 发送语音消息
    发送语音消息

聊天输入组件

近期一直在做微信小程序,业务上要求在小程序里实现即时通讯的功能。这部分功能需要用到文本和语音输入及一些语音相关的手势操作。所以我写了一个控件来处理这些操作。
聊天输入组件和会话页面组件是两个不同的组件,分别处理不同的业务。

控件样式

控件样式

功能

  • 切换输入方式(文本或语音)
  • 获取输入的文本信息
  • 语音输入及取消语音输入
  • 语音消息录制时长过短过长的判断
  • 支持发送图片(拍照或选择图库图片)和其他自定义拓展内容

注意:SDK仅支持微信基础库1.4.0及以上版本。

输入组件这部分内容我会从集成、编写控件两个部分来讲解。毕竟大部分人都是想尽快集成来着,所以先说说集成部分。


输入组件的集成

一、导入SDK相关文件

输入组件相关文件在modules/chat-inputimage文件夹下,示例页面是pages/chat-input/chat-input

聊天输入组件和会话页面组件所有你需要集成的文件,打包后大小在65kb左右,已经很小了。需要注意的是,项目中的原有.gif文件夹已经迁移到了别的仓库,image文件夹中有两张用于测试的用户头像,也可以删除掉。


二、集成到会话页面

1. 在会话页面中导入chat-input文件
  • 在聊天页的js文件中导入 let chatInput = require('../../modules/chat-input/chat-input');
  • 在聊天页的wxml文件中引入布局
<import src="../../modules/chat-input/chat-input.wxml"/> 
<template is="chat-input" data="{{inputObj:inputObj,textMessage:textMessage,showVoicePart:true}}"/>
  • 在聊天页的wxss文件中引入样式表@import "../../modules/chat-input/chat-input.wxss";
    根据你的路径来导入这些内容</>
2. 初始化chatInput
chatInput.init(page, {systemInfo: wx.getSystemInfoSync(),minVoiceTime: 1,//秒,最小录音时长,小于该值则弹出‘说话时间太短’maxVoiceTime: 60,//秒,最大录音时长,大于该值则按60秒处理startTimeDown: 56,//秒,开始倒计时时间,录音时长达到该值时弹窗界面更新为倒计时弹窗format:'mp3',//录音格式,有效值:mp3或aac,仅在基础库1.6.0及以上生效,低版本不生效sendButtonBgColor: 'mediumseagreen',//发送按钮的背景色sendButtonTextColor: 'white',//发送按钮的文本颜色extraArr: [{picName: 'choose_picture',description: '照片'}, {picName: 'take_photos',description: '拍摄'}, {picName: 'close_chat',description: '自定义功能'}],tabbarHeigth: 48});
  • page:这个是指当前的page。
  • systemInfo必填。手机的系统信息,用于控件的适配。
  • minVoiceTime: 最小录音时长,秒,小于该值则弹出‘说话时间太短’
  • maxVoiceTime: 最大录音时长,秒,填写的数值大于该值则按60秒处理。录音时长如果超过该值,则会保存最大时长的录音,并弹出‘说话时间超时’并终止录音
  • startTimeDown: 开始倒计时时间,秒,录音时长达到该值时弹窗界面更新为倒计时弹窗
  • extraArr:非必填。点击右侧加号时,显示的自定义功能。picName元素的名字就是对应image/chat/extra文件夹下的png格式的图片名称,用于展示自定义功能的图片。description元素用于展示自定义功能的文字说明。
  • tabbarHeight:非必填。这个也是用于适配。如果你的小程序有tabbar,那么需要填写这个字段,填48就行。如果你的小程序没有tabbar,那么就不要填写这个字段。原因我会在第二篇讲到。
  • format: 录音格式,有效值:mp3或aac,仅在基础库1.6.0及以上生效,低版本不生效,当然也不会报错。
  • sendButtonBgColor: 发送按钮的背景色
  • sendButtonTextColor: 发送按钮的文本颜色
3. 监听获取输入的信息

在初始化控件之后,监听信息的输入,即可获取到指定类型的信息

文本信息:
//文本信息的输入监听
chatInput.setTextMessageListener(function (e) {let content = e.detail.value;//输入的文本信息});  
语音信息:
//获取录音之后的音频临时文件
chatInput.recordVoiceListener(function (res, duration) {let tempFilePath = res.tempFilePath;//语音临时文件的路径let vDuration = duration;//录音时长});
//监听录音状态chatInput.setVoiceRecordStatusListener(function (status) {switch (status) {case chatInput.VRStatus.START://开始录音break;case chatInput.VRStatus.SUCCESS://录音成功break;case chatInput.VRStatus.CANCEL://取消录音break;case chatInput.VRStatus.SHORT://录音时长太短break;case chatInput.VRStatus.UNAUTH://未授权录音功能break;case chatInput.VRStatus.FAIL://录音失败(已经授权了)break;}})
自定义功能:
//收起自定义功能窗口
chatInput.closeExtraView();//自定义功能点击事件
chatInput.clickExtraListener(function (e) {let itemIndex = parseInt(e.currentTarget.dataset.index);//点击的自定义功能索引if (itemIndex === 2) {that.myFun();//其他的自定义功能return;}//选择图片或拍照wx.chooseImage({count: 1, // 默认9sizeType: ['compressed'],sourceType: itemIndex === 0 ? ['album'] : ['camera'],success: function (res) {let tempFilePath = res.tempFilePaths[0];}});});
//新增右下角加号button点击事件
chatInput.setExtraButtonClickListener(function (dismiss) {console.log('Extra弹窗是否消失', dismiss);})

至此,输入组件SDK的集成就完成了!

客户端WebSocket功能及会话页面

效果图

  • 会话列表

  • 会话页面

  • 好友页面

会话页面,我将UI封装成了多个template,最后使用chat-item.wxml即可,UI相关的代码都放到了chat-page文件夹中;加载方面的UI放到了loading文件夹中;image文件夹中也新增了几张图片。

对于即时通讯方面的sdk,我是用的WebSocket,当然这部分内容仅供参考。你可以引入常见的sdk,比如腾讯的、网易的。

目前实现了三个页面。会话列表页面、好友列表页面、会话页面。

会话列表页面功能:

  • 显示好友头像、昵称、最新一条消息内容及时间、未读计数展示。
  • 实时更新好友最新一条消息及时间,实时更新未读计数。
  • 从会话页面回退到会话列表页面后,会刷新列表页面。

好友页面功能:

  • 显示好友头像昵称。

好友页面未实现功能:

并未实现发起聊天功能,仅供演示,如有需要,请自行实现。

会话页面功能:

  • 支持发送文本、语音、图片及自定义类型消息。因发送文件类型的消息需要上传文件的服务器,所以目前仅支持发送文本消息和自定义类型消息。如果你自己配置好了上传文件的服务器,那么就可以发送语音和图片消息了。

重要功能模块的流程图

有网友建议画个流程图,梳理下项目中的各部分关系。

这部分的东西包括WebSocket写完之后发现,并没有很多难点,所以我只说下使用时要注意的几点。

会话列表页面

示例页面是pages/chat-list/chat-list

  • IM连接和会话列表页收发消息流程图:

IM连接和收发消息流程图

代码非常简单。

// pages/chat-list/chat-list.js/*** 会话列表页面*/
Page({/*** 页面的初始数据*/data: {conversations: []},/*** 生命周期函数--监听页面加载*/onLoad: function (options) {},toChat: function (e) {let item = e.currentTarget.dataset.item;delete item.latestMsg;delete item.unread;wx.navigateTo({url: `../chat/chat?friend=${JSON.stringify(item)}`});},/*** 生命周期函数--监听页面显示*/onShow: function () {getApp().getIMHandler().setOnReceiveMessageListener({listener: (msg) => {console.log('会话列表', msg);msg.type === 'get-conversations' && this.setData({conversations: msg.conversations.map(item => this.getConversationsItem(item))})}});getApp().getIMHandler().sendMsg({content: {type: 'get-conversations',userId: getApp().globalData.userInfo.userId//这里获取的userInfo,就是在建立webSocket连接时服务器返回的,作为你的身份信息。}, success: () => {console.log('获取会话列表消息发送成功');},fail: (res) => {console.log('获取会话列表失败', res);}});},getConversationsItem(item) {let {latestMsg, ...msg} = item;return Object.assign(msg, JSON.parse(latestMsg));}
});

就这些代码。结合流程图来看的话,相信你肯定能看懂。看不懂也没事,能理解思想就行,就是 注册监听、发起请求、回调监听、渲染页面。

好友列表页面

示例页面是pages/friends/friends

同会话列表页面,没什么好说的。

会话页面

示例页面是pages/chat/chat

  • 会话页面发送消息流程图:

会话页面发送消息

chat文件夹下,我封装了多个类,用于管理消息类型的收发和展示。

MsgManager是一个收发消息的工厂类,用于统一管理所有消息类型的收发。见msg-manager.js

import VoiceManager from "./msg-type/voice-manager";//语音消息的收发和展示相关类
import TextManager from "./msg-type/text-manager";//文本类型消息的收发和展示相关类
import ImageManager from "./msg-type/image-manager";//图片类型消息的收发和展示相关类
import CustomManager from "./msg-type/custom-manager";//自定义类型消息的收发和展示相关类
import IMOperator from "./im-operator";//im行为管理类export default class MsgManager {constructor(page) {this.voiceManager = new VoiceManager(page);this.textManager = new TextManager(page);this.imageManager = new ImageManager(page);this.customManager = new CustomManager(page);}showMsg({msg}) {let tempManager = null;switch (msg.type) {case IMOperator.VoiceType:tempManager = this.voiceManager;break;case IMOperator.ImageType:tempManager = this.imageManager;break;case IMOperator.TextType:case IMOperator.CustomType:tempManager = this.textManager;}tempManager.showMsg({msg});}sendMsg({type = IMOperator.TextType, content, duration}) {let tempManager = null;switch (type) {case IMOperator.VoiceType:tempManager = this.voiceManager;break;case IMOperator.ImageType:tempManager = this.imageManager;break;case IMOperator.CustomType:tempManager = this.customManager;break;case IMOperator.TextType:tempManager = this.textManager;}tempManager.sendOneMsg(content, duration);}stopAllVoice() {this.voiceManager.stopAllVoicePlay();}
}

小程序基础库1.6.0以后不再维护的语音播放和录制接口,我进行了兼容处理。

篇幅问题,各类型消息的相关类代码我就不贴了。想看的去下载吧。

缓存机制和文件类型消息的展示机制

  • 缓存和展示机制:在展示语音或图片类型的消息时,我会优先加载已经存储在本地的文件。在文件类型消息(如语音、图片消息)的showMsg()方法中先是取消息的本地路径const localVoicePath = FileManager.get(msg),如果没有获取到本地路径,就会按照消息中的文件路径信息去下载该文件,并存储下来。这里代码没有贴出来,大家理解意思就行。
    那取到的值是什么时候设置的呢?是在发送或接收消息成功后(此时文件已下载成功),以消息的saveKey为key,存储成功返回的savedFilePath为data,建立消息和本地存储路径的映射关系,如:FileManager.set(msg, savedFilePath);
    这里的saveKey是以消息id msgId 和好友id friendId,拼接而成的。

  • 存储溢出算法:在存储文件时,我参考了Android LruCache的思想编写了算法,保证在小程序10M存储限制的前提下,存储新的文件,如果溢出了,就移除最旧的文件(跟LruCache溢出时去除不常用文件的思想不太一样)。算法具体内容见小程序性能优化——文件的本地存储10M优化算法。

const MAX_SIZE = 10400000;
let wholeSize = 0;
setTimeout(() => {wx.getSavedFileList({success: savedFileInfo => {let {fileList} = savedFileInfo;!!fileList && fileList.forEach(item => {wholeSize += item.size;});}});
});function saveFileRule(tempFilePath, cbOk, cbError) {wx.getFileInfo({filePath: tempFilePath,success: tempFailInfo => {let tempFileSize = tempFailInfo.size;// console.log('本地临时文件大小', tempFileSize);if (tempFileSize > MAX_SIZE) {typeof cbError === "function" && cbError('文件过大');return;}wx.getSavedFileList({success: savedFileInfo => {let {fileList} = savedFileInfo;if (!fileList) {typeof cbError === "function" && cbError('获取到的fileList为空,请检查你的wx.getSavedFileList()函数的success返回值');return;}//这里计算需要移除的总文件大小let sizeNeedRemove = wholeSize + tempFileSize - MAX_SIZE;if (sizeNeedRemove >= 0) {//按时间戳排序,方便后续移除文件fileList.sort(function (item1, item2) {return item1.createTime - item2.createTime;});let sizeCount = 0;for (let i = 0, len = fileList.length; i < len; i++) {if ((sizeCount += fileList[i].size) >= sizeNeedRemove) {for (let j = 0; j < i; j++) {wx.removeSavedFile({filePath: fileList[j].filePath,success: function () {wholeSize -= fileList[j].size;}});}break;}}}wx.saveFile({tempFilePath: tempFilePath,success: res => {wholeSize += tempFileSize;typeof cbOk === "function" && cbOk(res.savedFilePath);},fail: cbError});},fail: cbError});}});
}module.exports = {saveFileRule
};

IIMHandler抽象类 i-im-handler.js

这个类是IM-SDK的接口规范类。你可以通过继承这个类,然后在子类中实现细节,这样的话可以很方便的接入其他的第三方IM-SDK。

/*** 由于JavaScript没有接口的概念,所以我编写了这个IM基类* 将你自己的IM的实现类继承这个类就可以了* 我把IM通信的常用方法封装在这里,* 有些实现了具体细节,但有些没实现,是作为抽象函数,由子类去实现细节,这点是大家需要注意的*/
export default class IIMHandler {constructor() {this._isLogin = false;this._msgQueue = [];this._receiveListener = null;}/*** 创建IM连接* @param options 传入你建立连接时需要的配置信息,比如url*/createConnection({options}) {// 作为抽象函数}/*** 发送消息* @param content 需要发送的消息,是一个对象,如{type:'text',content:'abc'}* @param success 发送成功回调* @param fail 发送失败回调*/sendMsg({content, success, fail}) {if (this._isLogin) {this._sendMsgImp({content, success, fail});} else {this._msgQueue.push(content);}}/*** 消息接收监听函数* @param listener*/setOnReceiveMessageListener({listener}) {this._receiveListener = listener;}closeConnection() {// 作为抽象函数}_sendMsgImp({content, success, fail}) {// 作为抽象函数}
}

IIMHandler实现类 web-socket-handler-imp.js

这个文件位于modules文件夹下。

这个是IM-SDK的WebSocket实现类,所有的WebSocket基本操作都封装到了这个类中。我截取了重要的几处代码。

import IIMHandler from "../interface/i-im-handler";export default class WebSocketHandlerImp extends IIMHandler{constructor() {super();this._onSocketMessage();}/*** 创建WebSocket连接* 如:this.imWebSocket = new IMWebSocket();*    this.imWebSocket.createSocket({url: 'ws://10.4.97.87:8001'});* 如果你使用本地服务器来测试,那么这里的url需要用ws,而不是wss,因为用wss无法成功连接到本地服务器* @param options 建立连接时需要的配置信息,这里是传入的url,即你的服务端地址,端口号不是必需的。*/createConnection({options}) {!this._isLogin && wx.connectSocket({url: options.url,header: {'content-type': 'application/json'},method: 'GET'});}_sendMsgImp({content, success, fail}) {wx.sendSocketMessage({data: JSON.stringify(content), success: () => {success && success(content);},fail: (res) => {fail && fail(res);}});}/*** 关闭webSocket*/closeConnection() {wx.closeSocket();}/*** webSocket是在这里接收消息的* 在socket连接成功时,服务器会主动给客户端推送一条消息类型为login的信息,携带了用户的基本信息,如id,头像和昵称。* 在login信息接收前发送的所有消息,都会被推到msgQueue队列中,在登录成功后会自动重新发送。* 这里我进行了事件的分发,接收到非login类型的消息,会回调监听函数。* @private*/_onSocketMessage() {wx.onSocketMessage((res) => {let msg = JSON.parse(res.data);if ('login' === msg.type) {this._isLogin = true;getApp().globalData.userInfo = msg.userInfo;getApp().globalData.friendsId = msg.friendsId;if (this._msgQueue.length) {let temp;while (temp = this._msgQueue.shift()) {this.sendMsg({content: {...temp, userId: msg.userInfo.userId}});}}} else {this._receiveListener && this._receiveListener(msg);}})}}

毕竟要面向接口编程嘛,这样的话,你使用第三方的IM-SDK的话,就可以直接继承这个IIMHandler,按接口规范在子类中实现细节就可以了。

也很简单,对吧。

在App.js中初始化IMWebSocket

这里面有一个IMFactory,是一个工厂类,它的create()方法返回的是WebSocketHandlerImp,这个工厂类的代码我就不贴了。

//app.js
import IMFactory from "./modules/im-sdk/im-factory";App({globalData: {userInfo: {},},getIMHandler() {return this.iIMHandler;},onLaunch() {this.iIMHandler = IMFactory.create();},onHide() {// this.iIMHandler.closeConnection();},onShow() {this.iIMHandler.createConnection({options: {url: 'ws://10.4.94.185:8001'}});}
});

IM模拟类 im-operator.js

最后重点说下IM的控制类 IMOperator。

现在在创建IMOperator时,需要你额外传入好友信息,这个信息应该是在会话列表点击时传入的,如下所示:

 /*** 生命周期函数--监听页面加载*/onLoad: function (options) {const friend = JSON.parse(options.friend);this.initData();wx.setNavigationBarTitle({title: friend.friendName});this.imOperator = new IMOperator(this, friend);//额外传入好友信息......},

生成发送的数据的文本

记住,你所有发送的消息和接收到的消息,都是以文本消息的形式,只是在渲染的时候解析,生成不同的消息类型来展示!!!

 createChatItemContent({type = IMOperator.TextType, content = '', duration} = {}) {if (!content.replace(/^\s*|\s*$/g, '')) return;return {content,type,conversationId: 0,//会话id,目前未用到userId: getApp().globalData.userInfo.userId,friendId: this.getFriendId(),//好友idduration};}

这是生成发送数据的文本的方法。它会返回一个对象。

  • type: 消息类型 TextType/VoiceType/ImageType/CustomType
  • content: 需要发送的原始文本信息。可以是文字、语音文件路径、图片文件路径。
  • duration: 语音时长。如果是语音类型,则需要传这个字段。

生成消息对象

除自定义消息类型外,其他的无论是自己发送的消息,还是好友的消息,在UI上渲染时,都是以该消息对象的格式来统一的。

createNormalChatItem({type = IMOperator.TextType, content = '', isMy = true, duration} = {}) {if (!content) return;const currentTimestamp = Date.now();const time = dealChatTime(currentTimestamp, this._latestTImestamp);let obj = {msgId: 0,//消息idfriendId: this.getFriendId(),//好友idisMy: isMy,//我发送的消息?showTime: time.ifShowTime,//是否显示该次发送时间time: time.timeStr,//发送时间 如 09:15,timestamp: currentTimestamp,//该条数据的时间戳,一般用于排序type: type,//内容的类型,目前有这几种类型: text/voice/image/custom | 文本/语音/图片/自定义content: content,// 显示的内容,根据不同的类型,在这里填充不同的信息。headUrl: isMy ? this._myHeadUrl : this._otherHeadUrl,//显示的头像,自己或好友的。sendStatus: 'success',//发送状态,目前有这几种状态:sending/success/failed | 发送中/发送成功/发送失败voiceDuration: duration,//语音时长 单位秒isPlaying: false,//语音是否正在播放};obj.saveKey = obj.friendId + '_' + obj.msgId;//saveKey是存储文件时的keyreturn obj;}
  • type:消息类型
  • content:需要发送的IM消息,是由createChatItemContent生成的JSON格式字符串。
  • isMy:是否是我自己的消息。
  • duration:语音时长。如果是语音类型,则需要传这个字段。

生成自定义消息类型对象

自定义消息类型的UI类似于会话页面中的展示聊天时间的UI。

static createCustomChatItem() {return {timestamp: Date.now(),type: IMOperator.CustomType,content: '会话已关闭'}}

发送数据接口

发送数据这块目前已经实现了WebSocket通信。

支持发送文本、语音、图片及自定义类型消息。不过因发送文件类型的消息需要上传文件的服务器,所以目前仅支持发送文本消息和自定义类型消息。如果你自己配置好了上传文件的服务器,那么就可以发送语音和图片消息了。

以发送文本消息为例:

首先在输入组件的文本输入监听回调接口中调用this.msgManager的发送消息方法sendMsg()

chatInput.setTextMessageListener((e) => {let content = e.detail.value;this.msgManager.sendMsg({type: IMOperator.TextType, content});});

sendMsg方法中会去判断发送的消息类型,最终都会调用下面的IM发送接口。

onSimulateSendMsg({content, success, fail}) {//这里content即为要发送的数据//注意:这里的content是一个对象了,不再是一个JSON格式的字符串。这样可以在发送消息的底层接口中统一处理。getApp().getIMHandler().sendMsg({content,success: (content) => {//这个content格式一样,也是一个对象const item = this.createNormalChatItem(content);this._latestTImestamp = item.timestamp;success && success(item);},fail});}
  • content:这里的content是一个对象,类似于:{“content”:“233”,“type”:“text”},是由该类中createChatItemContent方法生成的。
  • success:发送成功回调,我这里返回了createNormalChatItem生成的消息对象。
  • fail:发送失败回调,你可以自行传参。

其他消息的发送方式都是与之类似的。

会话页面接收数据接口

关于会话页面消息是怎么接收到的,下面的展示了一个完整的主要流程。

  • 会话页面接收消息流程图:

会话页面接收消息流程图

下面贴的是核心代码

onSimulateReceiveMsg(cbOk) {getApp().getIMHandler().setOnReceiveMessageListener({listener: (msg) => {if (!msg) {return;}msg.isMy = msg.msgUserId === getApp().globalData.userInfo.userId;const item = this.createNormalChatItem(msg);// const item = this.createNormalChatItem({type: 'voice', content: '上传文件返回的语音文件路径', isMy: false});// const item = this.createNormalChatItem({type: 'image', content: '上传文件返回的图片文件路径', isMy: false});this._latestTImestamp = item.timestamp;//这里是收到好友消息的回调函数,建议传入的item是 由 createNormalChatItem 方法生成的。cbOk && cbOk(item);}});}
  • cbOk:接收到消息的回调,这里我也是模拟的,返回了由this.createNormalChatItem({type: 'text', content: '这是模拟好友回复的消息', isMy: false})生成的消息对象

在上面发送数据接口代码中可以看到,在接收到数据时,先使用createNormalChatItem来生成消息类型数据,然后回调onSimulateReceiveMsgCb函数,即可完成数据的接收。

我是在chat.jsonLoad生命周期中注册的监听:

onLoad: function (options) {const friend = JSON.parse(options.friend);console.log(friend);this.initData();wx.setNavigationBarTitle({title: friend.friendName});this.imOperator = new IMOperator(this, friend);this.UI = new UI(this);this.msgManager = new MsgManager(this);this.imOperator.onSimulateReceiveMsg((msg) => {//执行到这步时,好友的消息早已经接收到并生成了消息类型数据msg,接下来要做的就是将数据渲染到页面上了//showMsg()是用于渲染消息类型数据的。this.msgManager.showMsg({msg})});this.UI.updateChatStatus('正在聊天中...');},

渲染消息列表

我使用chatItems来存储所有的消息类型,包括自定义消息类型。
布局怎么写的我就不讲了,有关UI渲染的代码,我全部放在了ui.js中,自己去看下吧,也都很简单。

配合使用输入组件。

最上面说的输入组件,有各种交互情况下的事件回调,在回调函数中处理对应逻辑即可。这部分的所有代码我都放到了chat.js中。

以上就是客户端WebSocket及会话页面的所有难点内容

服务端WebSocket实现的业务功能

服务器端是用nodejs开发的,配合客户端实现了简单的IM消息展示逻辑。webSocket所有功能仅供学习和参考,若想商用,请自行开发。

请务必阅读这部分内容,新手请自行学习依赖的安装和gulp的初级使用。项目的运行是没有问题的!!

如何安装使用

1.开发者工具导入项目
修改app.js文件中下面配置的url为你本地网络ip
this.imWebSocket.createSocket({url: 'ws://10.4.97.87:8001'});
2. 搭建本地WebSocket服务
项目根目录下启动Terminal
需先安装依赖 npm install
执行 npm run server  即可开启WebSocket服务
3. 使用开发者工具运行项目

nodejs-websocket具体API详见https://www.npmjs.com/package/nodejs-websocket

服务端简单实现了两人实时聊天功能,获取历史消息,获取会话列表、好友列表。需要注意的是,目前消息都是在内存中,重启服务后所有数据会重置!
目前只能两人聊天,而且当两个人同时在线时,后面一个人重新连接了,就会登上另外一个人的号,所以会出现自己发消息,自己收到自己的消息的情况。这时候你可以重启WebSocket服务,两台设备重新进下小程序就可以了。

服务端代码就不贴了。

最后说几句

小程序适合开发轻量级应用,不管你怎么推崇小程序,它都是有性能上的瓶颈问题的。比如说一个页面中加载大量图片会导致占用惊人的内存空间,长列表的无法复用控件问题、重复setData()造成页面闪动以及存储空间限制在10M等等。因此,使用小程序开发IM,不建议将历史消息存储在本地,也不建议单个页面中加载大量的聊天消息,更何况是有很多图片的聊天消息!这将造成页面卡顿,影响小程序的响应速度!而这些东西目前是无法从技术上优化的!

不过有些东西是可以优化的。我想你的小程序肯定有这样的使用场景,点击一个按钮跳转到另外一个页面,然后在向服务器请求获取数据。因为要先渲染空页面再渲染数据,页面偶尔会闪烁。其实这个是可以优化的。即在点击按钮时就请求数据,这样的话,就能利用页面跳转的200ms左右的时间来完成数据的预加载。如果协议返回够快的话,页面在打开时会立刻渲染出数据。不过问题也就来了,直接这样做的话会让你的页面混有不相干的协议,违法单一职责。那么我为此编写了一个小程序预加载框架,让你既能在页面跳转前就预加载数据,又不会破坏项目的结构。哈哈,为自己打一波广告!

使用中有什么问题的话,可以在博客或GitHub上联系我。我会及时回复。

谢谢大家!

项目下载地址

github地址

合作

小程序技术交流请加QQ群:821711186

如有合作意向或是想要推广您的产品,可加QQ:1178545208


LINK

Questions 常见问题及解决方案

ChangeLog 更新日志

LICENSE

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

微信小程序开发聊天室——实时聊天,支持图片预览

第一次写小程序&#xff0c;老板就让我用websoket写个聊天对话&#xff0c;群聊这种。第一次写聊天功能&#xff0c;第一次用websoket,第一次用小程序&#xff0c;这是在考验我吗&#xff1f;不过我还是研究了一下&#xff0c;终于实现了。 首先看一下界面&#xff0c;界面很简…

微信聊天小程序——(四、聊天页面)

四、聊天页面 步骤一、聊天页面的搭建 效果图&#xff1a; 思路&#xff1a; 聊天页面的搭建 首先在聊天页面获取我们所有的好友信息其次当点击我们对应好友&#xff0c;进入聊天页面 注意传递我们此时的聊天记录表id&#xff0c;方便之后进行添加聊天记录聊天页面下方布局发…

在微信小程序中如何支持使用流模式(stream),打造ChatGPT实时回复机器人,最详细讲解。

有开发过ChatGPT相关应用的都知道&#xff0c;小程序是不支持流式请求的&#xff0c;目前市面上大多数开发者的解决方案都是使用websocket来解决。 还有一部分开发者是小程序嵌套网页解决这个问题&#xff0c;前者对于我们软件销售型的团队来说&#xff0c;交付会很麻烦&#…

chatgpt写微信小程序

作为微信小程序开发者&#xff0c;您的任务是使用微信小程序原生开发&#xff0c;编写一个计数器页面&#xff0c;请回复满足以下要求的代码: 创建一个包含wxml、js、wxss和json文件的微信小程序页面&#xff0c;并在其中实现一个计数器页面。视图中显示的文本应为中文。请注意…

基于ChatGPT的智能问答、ai绘图微信小程序思路

ChatGPT ![在这里插入图片描述](https://img-blog.csdnimg.cn/186d9ecc453b48be9f19c467da7c3f07.jpeg ChatGPT是openai公司的一个人工智能机器人产品&#xff0c;目前已经升级到4.0版本。其因便捷高效&#xff0c;已经在大学生、IT届、科研界等领域广为流传。但是直接进入其…

微信小程序ChatGpt流式响应

最近用微信小程序做chatGpt的Ai对话&#xff0c;其中重要的一点就是流式响应&#xff0c;现在分享处理逻辑&#xff0c;先给演示图。 下面是关键代码实现逻辑 const that this;const requestTask wx.request({url: xxxxx,responseType: "arraybuffer",method: POST…

微信小程序_把chatgpt聊天数据复制到剪切板

文章目录 ⭐ 前言⭐ 开始网格背景样式配置对话框样式配置复制到剪切板 ⭐ 结束 ⭐ 前言 大家好&#xff0c;我是yma16&#xff0c;不止前端&#xff0c;本文将介绍微信小程序中 chatgpt聊天页面设计和复制聊天数据。 往期微信小程序文章 小程序自定义微信昵称和头像 小程序制作…

KeepChatGPT:让ChatGPT畅聊无阻的浏览器插件

ChatGPT是一款强大的自然语言处理模型&#xff0c;但在使用过程中经常出现网络报错、刷新网页等问题&#xff0c;让使用体验大打折扣。而今天介绍的KeepChatGPT浏览器插件则能够有效解决这些问题&#xff0c;让ChatGPT畅聊无阻&#xff01; KeepChatGPT是一款解决ChatGPT畅聊过…

浏览器插件的使用

善于使用浏览器插件&#xff0c;能起到高效上网的作用。 Microsoft Edge 是全球广受欢迎的浏览器&#xff0c;浏览器本身具有快速、简单和轻量级的特点。一流的性能系统和访问速度极大提升您的浏览体验。 对于浏览器的用户来说&#xff0c;安装一些实用的插件&#xff0c;能让…

CHATGPT精选插件

CHATGPT是帮你做事&#xff0c;而不是替你做事 1、联网插件&#xff1a; 使用Webpilot插件联网时还可以同时使用其它两种插件&#xff08;一次可以同时使用三个插件&#xff09;&#xff0c;而使用Web Browsing插件功能联网时无法使用插件功能&#xff08;联网功能和插件只能…

这款工具被网友玩疯了!我又玩物丧志了...

羿阁 发自 凹非寺量子位 | 公众号 QbitAI 一款新的聊天AI被网友们玩疯了。 能直接生成代码、会自动修复bug、在线问诊、模仿莎士比亚风格写作……各种话题都能hold住&#xff0c;它就是OpenAI刚刚推出的——ChatGPT。 有脑洞大开的网友甚至用它来设计游戏&#xff1a;先用ChatG…

OpenAI发布ChatGPT!手把手debug代码!

卷友们好&#xff0c;我是rumor。 已经好久没看OpenAI的官网[1]了&#xff0c;但今天冥冥之中感觉受到了什么召唤&#xff0c;心想GPT4什么时候发布&#xff0c;莫名地就打开了&#xff0c;果然有料&#xff1a; 试用&#xff1a;https://chat.openai.com/ 它把魔抓又伸向对话了…

【哪些工作会被ChatGPT取代?】我用ChatGPT全自动化生成代码进行了深度分析

【哪些工作会被ChatGPT取代?】我用ChatGPT全自动化生成代码进行了深度分析 前言 配置好ChatGPT的几个小时之后&#xff0c;我发现了一个宝藏网页https://github.com/f/awesome-chatgpt-prompts&#xff0c;也是我这篇文章的灵感所在&#xff0c;github里面给出让chatgpt扮演…

GPT-3/ChatGPT 复现的经验教训

为什么所有公开的对 GPT-3 的复现都失败了&#xff1f;我们应该在哪些任务上使用 GPT-3.5 或 ChatGPT&#xff1f; 对于那些想要复现一个属于自己的 GPT-3 或 ChatGPT 的人而言&#xff0c;第一个问题是关键的。第二个问题则对那些想要使用它们的人是重要的&#xff08;下文提…

Day921.chatGPT

chatGPT Hi&#xff0c;我是阿昌&#xff0c;今天学习记录的是关于chatGPT的内容。 一、什么是chatGPT ChatGPT&#xff08;全名&#xff1a;Chat Generative Pre-trained Transformer&#xff09;&#xff0c;ChatGPT 是一种基于 GPT (Generative Pre-trained Transformer)…

面向开发人员的 ChatGPT 提示词教程中文版 - ChatGPT 版

面向开发人员的 ChatGPT 提示词教程中文版 - ChatGPT 版 1. 指南1-1. 提示的指南1-2. 配置1-3. 提示语原则原则 1: 写出清晰而具体的指示技巧 1: 使用分隔符来清楚地表明输入的不同部分技巧 2: 要求提供结构化的输出技巧 3: 要求模型检查条件是否得到满足技巧 4: "少许样本…

ChatGPT支持第三方plugins,并且推出了网络浏览器和代码解释器两个插件

2023年3月23日&#xff0c;OpenAI实现了对ChatGPT插件的初步支持&#xff0c;还推出了两个插件&#xff0c;一个网络浏览器和代码解释器&#xff0c;并且开源了知识库检索插件的代码&#xff0c;任何有信息的开发者都可以自行托管&#xff0c;以增强ChatGPT的功能。 在目前已实…

06-发送短信验证码实现登录功能

1、发送短信验证码实现登录功能的流程 1.1、获取验证码流程 1.2、登录流程 1.3、页面带有图形验证码的流程 2、 注册登录二合一页面的开发 2.1、将src目录下的App.vue页面上通用显示的删掉 2.2、在router目录下的index.js文件中通过懒加载的方式添加login.vue页面 对于rout…

手把手教你使用短信验证码接口

因为闪速码短信平台可以免费赠送200条使用&#xff0c;所以本文档以闪速码为列讲解。 一、账号注册、登录 一、注册、登录闪速码&#xff08;www.shansuma.com&#xff09;&#xff0c;进行实名认证。实名认证分为个人认证和企业认证&#xff0c;值得注意的是&#xff1a;个人…

最好用的发短信(验证码、语音短信)接口

使用阿里大鱼短信接口 注册 进入大鱼页面&#xff0c;如果没有账号&#xff0c;则自行注册&#xff0c;再此不在过多详述。注册完成或者有账号的则在首页中点击加入“加入阿里大鱼”&#xff0c;如下&#xff1a; 创建引用 点击“管理中心”&#xff0c;然后再点击右上角…