php+websocket+微信小程序

一、服务端 php

<?php

class Ws
{
    private $ws = null;
    public $redis;

    public function __construct(Request $request = null){
        $this->redis = new Redis();
        $this->redis->connect('127.0.0.1','6379');
        $this->redis->auth('认证密码');
        $this->ws = new \Swoole\WebSocket\Server('0.0.0.0', 9501,SWOOLE_PROCESS,SWOOLE_SOCK_TCP | SWOOLE_SSL);
        $this->ws->set([
            'ssl_cert_file' => '/www/server/panel/vhost/cert/域名文件夹/fullchain.pem', // ssl证书
            'ssl_key_file'=>'/www/server/panel/vhost/cert/域名文件夹/privkey.pem'
        ]);
        $this->ws->on('open',[$this,'onOpen']);
        $this->ws->on('message',[$this,"onMessage"]);
        $this->ws->on('close',[$this,"onClose"]);
        $this->ws->start();
    }

    public function onOpen($ws,$request)  {
        $data = [
            'msg'=>'用户:'.$request->fd.'已连接',
            'client_id'=>$request->fd,
            'type'=>'connect'
        ];
        $ws->push($request->fd,json_encode($data));
    }


    public function onMessage($ws,$frame) {
        $json_data = $frame->data;
        $data = json_decode($json_data,true);
        $msg = [
            'my'        => $data['my'],
            'to'        => $data['to'],
            'userName'  => $data['myName'],
            'userImg'   => $data['myImg'],
            'type'      => $data['type'],
            'client_id' => $frame->fd
        ];
        switch ($data['type']) {
            case 'open':
                $data['msg'] = $data['myName'].'进来了';
                $this->redis->set("user_id_".$data['my'],$frame->fd);
                $this->setUnreadToRead($data['my'],$data['to']);  // 将消息设为已读
                break;
            case 'send':
                $msg['msg'] = $data['msg'];
                $toUser = $this->redis->get($data['to']);
                if(empty($toUser)) {
                    $this->checkUserReadable = false;
                }else {
                    $ws->push($toUser, json_encode($msg,true)); // 推送给接收者
                    $this->checkUserReadable = true;
                }
                $ws->push($frame->fd, json_encode($msg,true)); // 推送给发送者
                $this->setChatRecord($data['my'], $data['to'],$msg); // 保存聊天记录
                break;
            case 'leave':
                $data['msg'] = $data['myName'].'退出去了';
                break;
            default:
                break;
        }

    }

    /*
   * 将消息设为已读
   * 当一个用户打开另一个用户的聊天框时,将所有未读消息设为已读
   * 清楚未读消息中的缓存
   * @from 消息发送者id
   * @to 消息接受者id
   * 返回值,成功将未读消息设为已读则返回true,没有未读消息则返回false
   */
    public function setUnreadToRead($from, $to) {
        $res = $this->redis->hDel('unread_' . $to, $from);
        return (bool)$res;
    }

    /*
      发送消息时保存聊天记录
    * 这里用的redis存储是list数据类型
    * 两个人的聊天用一个list保存
    * @from 消息发送者id
    * @to 消息接受者id
    * @meassage 消息内容
    *
    * 返回值,当前聊天的总聊天记录数
    */
    public function setChatRecord($from, $to, $message) {
        $data = array('from' => $from, 'to' => $to, 'message' => $message, 'sent' => time()/*, 'recd' => 0*/);
        $value = json_encode($data);
        //生成json字符串
        $keyName = 'rec:' . $this->getRecKeyName($from, $to);
        $res = $this->redis->rPush($keyName, $value);
        if (!$this->checkUserReadable) {//消息接受者无法立刻查看时,将消息设置为未读
            $this->cacheUnreadMsg($from, $to);
        }
        return $res;
    }

    /*
    * 当用户不在线时,或者当前没有立刻接收消息时,缓存未读消息,将未读消息的数目和发送者信息存到一个与接受者关联的hash数据中
    * @from 发送消息的用户id
    * @to 接收消息的用户id
    * 返回值,当前两个用户聊天中的未读消息
    */
    private function cacheUnreadMsg($from, $to) {
        return $this->redis->hIncrBy('unread_' . $to, $from, 1);
    }

    /*
   * 当用户上线时,或点开聊天框时,获取未读消息的数目
   * @user 用户id
   * 返回值,一个所有当前用户未读的消息的发送者和数组
   * 数组格式为‘消息发送者id'=>‘未读消息数目'
   */
    public function getUnreadMsgCount($user) {
        return $this->redis->hGetAll('unread_' . $user);
    }


    /*
    * 生成聊天记录的键名,即按大小规则将两个数字排序
    * @from 消息发送者id
    * @to 消息接受者id
    */
    private function getRecKeyName($from, $to) {
        return ($from > $to) ? $to . '_' . $from : $from . '_' . $to;
    }

    //监听WebSocket连接关闭事件。
    public function onClose($ws, $fd) {
       echo $fd.'链接断掉了';
    }

}

new Ws();

二、小程序端代码

1、wxml代码

<view class="chat_list">
  <scroll-view class="chat-content" scroll-y="true" scroll-into-view="{{scrollIntoView}}">
    <view wx:for="{{messages}}" wx:key="{{index}}" class="message-item">
    
      <view class="user_info my_info" wx:if="{{item.my==user_id}}">
        <image src="{{item.userImg}}" class="head_img"></image>
        <view class="my_msg msg">{{item.msg}}</view>
      </view>

      <view class="user_info love_info" wx:else>
        <view class="love_msg msg">{{item.msg}}</view>
        <image src="{{item.userImg}}" class="head_img"></image>
      </view>
    </view>

  </scroll-view>

  <view class="input-box">
    <view class="inner-box flex align_center justify-between">
      <view class="flex align_center">
        <text class="iconfont icon-comment font-max font-c"></text>
        <input class="font-min felx1" placeholder="请输入内容" bindinput='onInput' value="{{inputValue}}" />
      </view>
      <view class="my-btn-s flex center" bindtap='sendMessage'>发送</view>
    </view>
    <view style="height: 90rpx;"></view>
  </view>
</view>

2、js代码

var _Util = require('../../utils/util.js');
var app = getApp();
var httpUrl = app.httpUrl;
Page({
  data: {
    inputValue: '', // 输入框的值
    messages: [], // 聊天记录
    scrollIntoView: '', // 滚动到指定位置
    socketOpen: false, // WebSocket连接状态
    socketMsgQueue: [], // WebSocket消息队列
    url: 'wss://域名:9501', // WebSocket服务器地址
    user_id: 0,
    head_img: '',
    client_id: '',
    page:1
  },
  onLoad: function (options) {
    var userinfo = wx.getStorageSync('userinfo');
    var user_id = userinfo.id;
    this.setData({
      user_id: user_id
    })
    this.connectWebSocket() // 连接WebSocket服务器
  },
  onShow: function () {
    // 加载之前聊过的记录
    this.setData({
      messages: []
    })
    this.getMessageList()
  },

  getMessageList: function () {
    var that = this;
    var user_id = that.data.user_id;
    wx.request({
      url: httpUrl + '/v1/chat/getChatRecordList',
      data: {
        uid: user_id,
        page:that.data.page
      },
      method: 'POST',
      header: {
        'content-type': 'application/json', // 默认值 
        appKey: app.globalData.appKey,
        appSecret: app.globalData.appSecret,
        timestamp: _Util.$router.getThisTime()
      },
      success: res => {
        if (res.data.code == '1000') {
          var list = that.data.messages;
          var data = res.data.data;
          if (data.length != 0) {
            for (var i = 0; i < data.length; i++) {
              list.push(data[i])
            }
            that.data.page += 1;            
            that.setData({
              messages: list
            })
          }
        }
      }
    })
  },

  /**
   * 连接WebSocket服务器
   */
  connectWebSocket: function () {
    var that = this
    wx.connectSocket({
      url: that.data.url,
      success: function (res) {
        console.log('WebSocket连接成功')
      },
      fail: function (res) {
        console.log('WebSocket连接失败:', res)
      }
    })

    wx.onSocketOpen(function (res) {
      console.log('WebSocket连接已打开')
      that.setData({
        socketOpen: true
      })
      // 监听连接事件      
      var userinfo = wx.getStorageSync('userinfo');
      let msg = {
        'to': userinfo.loverid, //对方是谁          
        'my': userinfo.id, //我是谁
        'myName': userinfo.nickname,
        'myImg': userinfo.headimg,
        'type': 'open' //当前状态
      }
      console.log('open:')
      console.log(msg)
      msg = JSON.stringify(msg)
      that.sendSocketMessage(msg)
    })

    wx.onSocketError(function (res) {
      console.log('WebSocket连接打开失败:', res)
    })
    wx.onSocketClose(function (res) {
      console.log('WebSocket连接已关闭:', res)
      that.setData({
        socketOpen: false
      })
    })

    // 接收服务器发送的额数据
    wx.onSocketMessage(function (res) {
      console.log('接收到服务器发送的数据:')
      console.log(res.data)
      var arr_data = JSON.parse(res.data)
      console.log("arr_data:")
      console.log(arr_data)
      var messages = that.data.messages
      console.log('arr_data.type:' + arr_data.type)
      if (arr_data.type == 'connect') {        
        
        return;
        // messages.push(arr_data.msg)
        // that.setData({
        //   messages: messages,
        //   scrollIntoView: 'message-' + messages.length
        // })
      } else if (arr_data.type == 'downline') {
        wx.showToast({
          title: arr_data.msg,
          icon: 'error',
          duration: 1000
        })
        return;
      } else {

      }
      messages.push(arr_data)
      console.log('messages.length:' + messages.length)
      that.setData({
        messages: messages,
        scrollIntoView: 'message-' + messages.length
      })
    })
  },


  /**
   * 发送消息
   */
  sendMessage: function () {
    if (!this.data.socketOpen) {
      wx.showToast({
        title: 'WebSocket未连接1',
        icon: 'none'
      })
      return
    }
    var message = this.data.inputValue
    if (message == '') {
      wx.showToast({
        title: '消息不能为空',
        icon: 'none'
      })
      return
    }
    var userinfo = wx.getStorageSync('userinfo');
    let msg = {
      'to': userinfo.loverid, //对方是谁          
      'my': userinfo.id, //我是谁
      'myName': userinfo.nickname,
      'myImg': userinfo.headimg,
      'type': 'send', //当前状态
      'msg': message
    }
    msg = JSON.stringify(msg)
    this.sendSocketMessage(msg)
    this.setData({
      inputValue: ''
    })
  },

  /**
   * 发送WebSocket消息
   */
  sendSocketMessage: function (message) {
    console.log('socketOpen值为:' + this.data.socketOpen)
    if (this.data.socketOpen) {
      wx.sendSocketMessage({
        data: message
      })
    } else {
      wx.showToast({
        title: 'WebSocket未连接2',
        icon: 'none'
      })
    }
  },
  /**
   * 监听输入框变化
   */
  onInput: function (e) {
    this.setData({
      inputValue: e.detail.value
    })
  },
  onHide: function () {},
  onUnload: function () {
    wx.closeSocket() // 关闭WebSocket连接
  },
  onPullDownRefresh: function () {},
   /**
   * 页面上拉触底事件的处理函数
   */
  onReachBottom: function () {
    var that = this
    setTimeout(function () {
      that.getMessageList()
    }, 1000)
  },
})

3、css代码

page{
	background: #F5F6F7;
}

.chat_list {
  width:94%;
  margin-top:20rpx;
  padding-bottom:100rpx;
}
.user_info {
  width:600rpx;
  height:auto;  
  display: flex;
  margin-top:20rpx;
  position: relative;  
  align-items: center;
}

.my_info {
  float: left;
  margin-left:10rpx;
}

.love_info {
  float:right;  
}
.head_img {
  width:100rpx;
  height:100rpx;
  border-radius: 50%;
  border: 2rpx solid #ffffff;
  margin-left:10rpx;
  margin-top:16rpx;
}

.msg {
  word-break: break-all;
  width:500rpx;
  height:auto;
  line-height: auto;
  
  padding:30rpx;
  font-size: 32rpx;  
  border-radius:20rpx;
  font-family: Arial, sans-serif; /* 使用 Arial 字体或者其他 sans-serif 字体 */   
  font-weight: normal; /* 设置字体粗细为普通 */
  
  
}

.my_msg {
  margin-left:20rpx;
  background: #ffffff;
  border: 2rpx solid #ffffff;
  color: #333; /* 设置字体颜色,可以根据需要进行调整 */
}

.love_msg {
  margin-right:20rpx;
  background: #6d72e8;
  border: 2rpx solid #6d72e8;
  color: #ffffff;
}







.input-box {
  width: 100%;
}
.input-box .inner-box {
	height: 90rpx;
	width: 100%;
	background: white;
	padding: 0 20rpx;
	position: fixed;
	bottom: 0;
}

.input-box .iconfont {
padding-right: 10rpx;
}

.input-box input {
width: 530rpx;
}

.add-talk {
height: 80rpx;
width: 80rpx;
border-radius: 100rpx;
background: #F7C8D0;
position: fixed;
bottom: 280rpx;
right: 30rpx;
}

.end {
justify-content: flex-end;
margin-top: 10px;
}

.commentImg {
width: 18px;
height: 18px;
}

.comment {
margin-right: 10px;
}

.num {
margin-left: 4px;
}

.text-right {
justify-content: flex-end;
}

.delColor {
color: fuchsia;
}

4、php代码获取以往聊天记录

public function __construct(Request $request = null)
    {
        $this -> redis = new \Redis();
        $this -> redis -> connect('127.0.0.1', '6379');
        $this -> redis -> auth('11111');
        parent::__construct($request);
    }


    public function getChatRecordList() {
        $fromUser = $this->params['uid'];
        $userModel = new UserModel();
        $lover_no = $userModel->where('id',$fromUser)->value('lover_no');
        $toUser = $userModel->where(['lover_no'=>$lover_no,'id'=>['neq',$fromUser]])->value('id');
        $page = isset($this->params['page']) ? $this->params['page'] : 1;
        $pageSize = 100;
        if($page == 1) {
            $start = 0;
            $end = $pageSize - 1;
        }else {
            $start = ($page - 1) * $pageSize;
            $end = $start + $pageSize - 1;
        }
        // page:1 start:0 end 7 page:2 start 8 end 15 page:3 start 16 end:23
        $data = $this->getChatRecord($fromUser, $toUser, $start,$end);
        $data_news = [];
        if($data) {
            foreach ($data as $k=>$val) {
                $res = json_decode($val,true);
                $data_news[$k]['my'] = $res['from'];
                $data_news[$k]['to'] = $res['to'];
                $data_news[$k]['userName'] = $res['message']['userName'];
                $data_news[$k]['userImg'] = $res['message']['userImg'];
                $data_news[$k]['type'] = $res['message']['type'];
                $data_news[$k]['msg'] = $res['message']['msg'];
            }
        }

        $this->result($data_news,'1000','获取成功','json');
    }

 /*
    * 获取聊天记录
    * @from 消息发送者id
    * @to 消息接受者id
    * @num 获取的数量
    * 返回值,指定长度的包含聊天记录的数组
    */
    public function getChatRecord($from, $to, $start,$num) {
        $keyName = 'rec:' . $this->getRecKeyName($from, $to);
        $recList = $this->redis->lRange($keyName, $start, (int)($num));
        return $recList;
    }

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小马哥-码农

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值