# 效果图


本想把动图放封面的,考虑到手机的长宽比果断剪了这张图。 剪这张图花了20分钟,qwq。
开发的时候这个老表帮了我不少忙,帮忙测试找问题,还别说一开始有很多小问题。在这里感谢这位老表。
目前只支持文字聊天。消息列表页面仿微信,chat页面仿QQ,我真是个带天才哈哈哈。

# 背景

在本文开始前,请确保你对Gateway有一定的了解,并且已经配置好,知道怎么使用。如果这一步有疑问,可以看看这篇帖子TP5整合GatewayWorker。熟悉小程序的websocketApi和页面间通信也是必要的。

本文将介绍小程序利用websocket与后端(TP5+Gateway)交互实现实时的端到端通讯的一种策略,如果有什么疑问或建议,请留言给我。

如果文章对你有帮助,麻烦动动小手指点个赞哈哈。

原文地址

# 环境

  • ThinkPHP_v5.1.39
  • PHP_v7.2.24
  • Gateway-Worker_v3.0.13
  • 开发者工具调试基础库_v2.8.1

#分析

我们很容易可以看出,效果图中分两个页面—-message_list(消息列表页)、chat(聊天页)。所以单从位置来说,用户有两个状态,处于message_list或者chat,我们用一个变量来标识。

# message_list

message_list负责建立连接connect、接收消息onMessage、断线重连reconnect、心跳heartbeat。

message_list的data(省略了部分非相关变量):

data: { onMessageListPage: true, //是否在消息列表页socketOpen: false, //是否开启了websocketcurrent_passive_uid: -1, //当前聊天对象heartbeat_delay: 50000, //心跳时间message_page: 1, //当前消息列表页面,每页20个列表message_list: [],  //用于渲染的消息列表数据unreadcount: 0, //总未读消息数reconnect_delay: 5000, //重连延迟eventChannel: null  //与chat的通信接口}

message_list首先加载用户未读消息数和最新的未读消息,拼凑好塞进message_list,渲染到页面,这个请求走的HTTP。然后,message_list开启websocket,初始化事件。

openWebSocket() {let that = thiswx.connectSocket({url: app.globalData.wss_url,header: {'content-type': 'application/json','from': 'wechatmini'}})wx.onSocketMessage((msg) => {let data = JSON.parse(msg.data)switch (data.errDetail.type) {case 'new_message'://接到新消息时,在消息列表页面,刷新消息列表if (that.data.onMessageListPage) {wx.request({url: app.globalData.url + '/wxgetmessagelist',method: 'post',data: {token: wx.getStorageSync(app.globalData.nameOfCookie),page: that.data.message_page},success: res => {//console.log(res.data)for (let i = 0, n = res.data.errDetail.length; i < n; i++) {res.data.errDetail[i].date = dateTrans(new Date(parseInt(res.data.errDetail[i].timestamp)), new Date())}//console.log(res.data.errDetail)that.setData({message_list: res.data.errDetail})}})//刷新小红点wx.request({url: app.globalData.url + '/wxgetunreadcount',method: 'post',data: {token: wx.getStorageSync(app.globalData.nameOfCookie)},success: res => {that.setData({unreadcount: res.data.errDetail['count']})}})//接到消息时,在聊天页面,如果发消息的是正在聊天的对象,则将新消息传给聊天页} else {const newMsg = data.errDetail.new_messageif (newMsg.active_uid == that.data.current_passive_uid) {that.data.eventChannel.emit('newMsgChannel', { data: newMsg })}}break;case 'message_send':const newMsg = data.errDetail.message_sendthat.data.eventChannel.emit('messageSendChannel', { data: newMsg })break;}})wx.onSocketOpen((msg) => {that.data.socketOpen = truesendMsg({type: 'login',token: wx.getStorageSync(app.globalData.nameOfCookie)})//心跳that.data.pong = setInterval(() => {sendMsg({type: 'pong'})}, that.data.heartbeat_delay);})wx.onSocketClose((msg) => {clearInterval(that.data.pong)//reconnectthat.data.socketOpen = falselet i = setInterval(() => {wx.connectSocket({url: app.globalData.wss_url,header: {'content-type': 'application/json','from': 'wechatmini'}})console.log('reconnect')if (that.data.socketOpen) clearInterval(i)}, that.data.reconnect_delay)})}
//上文中的sendMsg原型
const sendMsg = function(data) {wx.sendSocketMessage({data: JSON.stringify(data)})
}

代码中参杂了其他逻辑,这里总结一下message_list中的websocket事件:

  • 当连接成功开启时,添加心跳,即每隔that.data.heartbeat_delayms,向服务端发送一个数据包{type: ‘pong’},以免连接被干掉。
  • 当连接关闭时,停止心跳,每隔that.data.reconnect_delayms重连一次。
  • 当收到服务端的消息包时,根据包内字段type执行不同的操作(需跟服务端约定)。当收到的是新消息时,如果用户处于chat,并且发送消息的用户是正在聊天的用户,那么将新消息发给chat,否则刷新message_list;当收到的是发送反馈时,将之发给chat。

# chat

chat监听message_list传来的消息,并且如果是新消息,那么发送read给服务端。

chat的data(省略了部分非相关变量):

data: {a_uid: -1, //用户uidp_uid: -1,  //对方uidpage: 1,message: [],  //页面渲染用的数据}

chat首先按页加载历史消息,这里走的也是HTTP。

/* 用户发送消息 */
push() {let that = thislet tosend = {}tosend.type = 'push'tosend.token = wx.getStorageSync(app.globalData.nameOfCookie)tosend.passive_uid = that.data.p_uidtosend.timestamp = new Date().getTime()tosend.content = that.data.texttosend.content_type = 'text'if (that.data.text && that.data.uid != -1) {sendMsg(tosend)}that.data.value = ''that.setData({value: '',disabled: true})}
onLoad: function(options) {let that = thisthat.data.p_uid = options.uidthat.data.a_uid = wx.getStorageSync('uid')getMessage(that)wx.setNavigationBarTitle({title: options.nickname})const eventChannel = this.getOpenerEventChannel()//监听newMsgChanneleventChannel.on('newMsgChannel', function(data) {//新消息加到数组末尾data.data.isSelf = falseconst d = that.data.messaged[d.length] = data.datathat.setData({message: d})let read = {}read.type = 'read'read.token = wx.getStorageSync(app.globalData.nameOfCookie),read.passive_uid = that.data.p_uidread.mid = data.data.midsendMsg(read)//console.log(read)setTimeout(() => {that.pageScrollToBottom()}, 200)})//监听messageSendChanneleventChannel.on('messageSendChannel', function(data) {data.data.isSelf = trueconst d = that.data.messaged[d.length] = data.datathat.setData({message: d})setTimeout(() => {that.pageScrollToBottom()}, 100)})}
//上文中的getMessage原型
const getMessage = function(that) {wx.request({url: app.globalData.url + '/wxGetMessage',method: 'post',data: {token: wx.getStorageSync(app.globalData.nameOfCookie),active_uid: wx.getStorageSync('uid'),passive_uid: that.data.p_uid,page: that.data.page},success: res => {if (res.data.errDetail.length != 0) {let temp = that.data.messagelet pastMsg = res.data.errDetailfor (let i = 0, n = pastMsg.length; i < n; i++) {if (pastMsg[i].active_uid == that.data.a_uid) pastMsg[i].isSelf = trueelse pastMsg[i].isSelf = falsetemp.unshift(pastMsg[i])if (pastMsg[n - i - 1].passive_uid == that.data.a_uid &&pastMsg[n - i - 1].read == 'n') {//如果拉取的历史消息中有未读的,发送readsendMsg({type: 'read',token: wx.getStorageSync(app.globalData.nameOfCookie),mid: pastMsg[n - i - 1].mid})}}that.data.page++;//console.log(temp)that.setData({message: temp})}}})
}

# 服务端

服务端的逻辑比(懒)较(得)简(写)单(了),直接上代码。

<?phpnamespace app\api\controller;use GatewayWorker\Lib\Gateway;
use think\Controller;
use think\Db;
use app\api\model\User as UserModel;class Events extends Controller
{/*** 有消息时 主逻辑处理* @param integer $client_id 连接的客户端* @param mixed $message* @return void*/public static function onMessage($client_id, $message){$message_data = json_decode($message, true);if (!$message_data) {return;}if (isset($message_data['type']) && !empty($message_data['type'])) {switch ($message_data['type']) {// 客户端回应服务端的心跳case 'pong':break;case 'login':$user = UserModel::checkToken($message_data['token']);if ($user) {//用户id与当前连接绑定Gateway::bindUid($client_id, $user->id);Gateway::sendToClient($client_id, json_encode(result_format(0, 'login success')));} else break;break;case 'push':$ATTR = ['token', 'passive_uid', 'timestamp', 'type', 'content_type'];if (wsParamValicate($ATTR, $message_data)) {$user = UserModel::checkToken($message_data['token']);if ($user) {//如果对方在线,则将消息发给他if (1 === Gateway::isUidOnline($message_data['passive_uid'])) {$msg = ['active_uid' => $user->id,'passive_uid' => $message_data['passive_uid'],'timestamp' => $message_data['timestamp'],'content_type' => $message_data['content_type'],'content' => $message_data['content'],'read' => 'n'];$avatar_url = Db::table('user')->where('id', $msg['active_uid'])->field(['avatar_url'])->select();$id = Db::table('message')->insertGetId($msg);$msg['mid'] = $id;$msg['avatar_url'] = $avatar_url[0]['avatar_url'];Gateway::sendToUid($message_data['passive_uid'], json_encode(result_format(0, 'new message', ['type' => 'new_message', 'new_message' => $msg])));//告诉我发送结果Gateway::sendToClient($client_id, json_encode(result_format(0, 'message_send', ['type' => 'message_send', 'message_send' => $msg])));} else {$msg = ['active_uid' => $user->id,'passive_uid' => $message_data['passive_uid'],'timestamp' => $message_data['timestamp'],'content_type' => $message_data['content_type'],'content' => $message_data['content'],'read' => 'n'];$avatar_url = Db::table('user')->where('id', $msg['active_uid'])->field(['avatar_url'])->select();$id = Db::table('message')->insertGetId($msg);$msg['mid'] = $id;$msg['avatar_url'] = $avatar_url[0]['avatar_url'];//告诉我发送结果Gateway::sendToClient($client_id, json_encode(result_format(0, 'message_send', ['type' => 'message_send', 'message_send' => $msg])));}//更新message_list 双方都新增消息列表if (1 == Db::table('messagelist')->where('active_uid', $user->id)->where('passive_uid', $message_data['passive_uid'])->count()) {} else {Db::table('messagelist')->insert(['active_uid' => $user->id,'passive_uid' => $message_data['passive_uid'],'enable' => 'y']);Db::table('messagelist')->insert(['passive_uid' => $user->id,'active_uid' => $message_data['passive_uid'],'enable' => 'y']);}}}break;case 'read':$ATTR = ['token', 'mid'];if (wsParamValicate($ATTR, $message_data)) {$user = UserModel::checkToken($message_data['token']);if ($user) {Db::table('message')->where('passive_uid', $user->id)->where('id', $message_data['mid'])->update(['read' => 'y']);}}break;}}}/*** 当用户连接时触发的方法* @param integer $client_id 连接的客户端* @return void*/public static function onConnect($client_id){Gateway::sendToClient($client_id, json_encode(result_format(0, 'connect success', ['client_id' => $client_id])));}
}

原文地址

微信小程序即时聊天前后端(TP5+Gateway)相关推荐

  1. 【项目实战课】微信小程序图像识别模型前后端部署实战

    欢迎大家来到我们的项目实战课,本期内容是<微信小程序图像识别模型前后端部署实战>.所谓项目实战课,就是以简单的原理回顾+详细的项目实战的模式,针对具体的某一个主题,进行代码级的实战讲解. ...

  2. 小程序服务器搭建前后端交互,手把手带你搭一个简单的微信小程序(包括前后端)...

    开发小程序除了大家能看到的客户端,前端小程序是如何与后端服务器进行数据交互的呢? 本文将通俗易懂的讲一下.这里以nodejs为例来进行讲解 1.首先要在服务器上安装nodejs服务器: ​ wget ...

  3. 小程序服务器搭建前后端交互,微信小程序:request前后端交互 路由跳转 存储数据到本地和获取 小程序登入 授权...

    一 request前后端交互 基本样式 wx.request({ url:'test.php', //仅为示例,并非真实的接口地址 data: { x:'', y:''}, header: {'con ...

  4. 微信小程序即时聊天对话窗口静态源码

    实例描述:静态的源码,需要自己二次开发 适用范围:所有版本微信小程序库 日期 :2019/9/2 前端: <scroll-viewstyle="height:{{height}}vh; ...

  5. 微信小程序 Basic Auth 前后端restful api进行身份验证

    header: {"Content-Type": " application/json",'Authorization': 'Basic ' + base64. ...

  6. 微信小程序websocket聊天室

    背景 最近做了一个微信小程序的即时通讯功能,之前我也做过node.js的websocket服务,不过是在web端应用的socket.io服务.小程序本身对http.websocket等连接均有诸多限制 ...

  7. 微信小程序开发【前端+后端(Java)】附完整源码,拿来接私活简直不要太香

    一.前言 现在微信小程序越来越火了,相信不少人都通过各种途径学习过微信小程序或者尝试开发,作者就是曾经由于兴趣了解开发过微信小程序,所以现在用这篇博客记录我之前开发的一些经验和一些心得吧. 二.主要内 ...

  8. 微信小程序聊天功能PHP,微信小程序实现聊天室

    本文实例为大家分享了微信小程序实现聊天室的具体代码,供大家参考,具体内容如下 正文: 登录 查看详情 {{item}} {{item.messageTime}} {{item.text}} {{ite ...

  9. 微信小程序监听服务器发送消息,微信小程序实时聊天WebSocket

    本文实例为大家分享了微信小程序实时聊天WebSocket的具体代码,供大家参考,具体内容如下 1.所有监听事件先在onload监听. // pages/index/to_news/to_news.js ...

  10. 微信小程序语音聊天智能对话(demo)

    项目中用到了 olami sdk把录音或者文字转化为用户可以理解的json字符串. 效果图 重要jS代码: //手指按下时 语音转文字voiceToChar:function(){var urls = ...

最新文章

  1. 博士如何高效率阅读文献?有哪些技巧可以借鉴?
  2. css3 混合,瞧瞧CSS3的混合模式
  3. 线性表adt的c语言表达,抽象数据类型定义(ADT)
  4. 自编码的matlab代码,深度学习自动编码机MATLAB实现
  5. api网关和esb区别_具有ESB,API管理和Now .. Service Mesh的应用程序网络功能。
  6. history模式监听_面试题:VueRouter中的 hash 模式和 history 模式有什么区别
  7. mysql 不在另一张表_mysql查询在一张表不在另外一张表的记录
  8. 谈谈软件工程设计的艺术
  9. 17R-无重复数字的三位数和去重后最大数
  10. c#窗口操作-句柄操控全解
  11. 烧录工具Android Tool的使用
  12. 思科交换机配置命令大全
  13. 解决 手心输入法 导致 Navicat 闪退问题
  14. 孙鑫 VC++深入详解——学习笔记
  15. 大数据监测及预警系统平台怎么选择的方法参考
  16. OpenCV:计算三角形的角度
  17. 独立t检验和配对t检验_配对学生的t检验是什么?
  18. 最大流、最小费用最大流【模板】
  19. 零基础自学UI设计要看什么书籍和资料
  20. icloud 照片导出_我的照片流和iCloud照片之间有什么区别?

热门文章

  1. 学会这4个Excel实用技巧,数据分析立马高人一等
  2. 三菱PLC程序,汽车厂流水线输送控制系统
  3. 对测试开发工程师的理解
  4. wifi信号衰减与距离关系_WIFI信号的空间如何衰减 WIFI信号的空间衰减介绍【详解】...
  5. 模长,方向余弦,方向角、单位向量和方向导数的计算
  6. endnote软件X9下载安装
  7. Linux U盘检测与速度测试源码
  8. 如何优化MySQL千万级大表,我写了6000字的解读
  9. 面向金融的R语言——Lecture9
  10. php对接工行sdk,工商银行-银企直连签约流程