PHP

PHP小程序后端简单实现聊天功能

之前对这个聊天的功能很好奇,今天终于有机会接触了。一番交战之后,才觉得没有想象中的那么神秘。怎么实现的呢?

首先最主要的是需要swoole的服务,其次就是代码功能的实现。主要有以下步骤:

  • 安装swoole拓展服务
  • 修改php配置,开启拓展
  • nginx/apache 代理wss服务
  • php代码实例webscoket监听某个端口
  • 聊天代码实现

由于服务器用了linux宝塔第一步和第二步就不描述了(宝塔真的很香~~)

服务端怎么配置?

由于在小程序里面域名,必须要符合安全域名的要求,所以决定用用同一个域名,wss的时候监听代理其他端口。

比较不喜欢的是,服务器使用的是apache(个人喜欢nginx)

网上找了一下apache怎么配置的方法:

//主要是wss代理,ssl和webscoket监听窗口就不示例了
ProxyRequests Off
ProxyPass /wss ws://127.0.0.1:9501
ProxyPassReverse /wss ws://127.0.0.1:9501

另外nginx配置:

location /ws {
    proxy_pass http://127.0.0.1:9501;
    proxy_read_timeout 60s;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection 'Upgrade';
}

数据库怎么设计?我的需求比较简单,就是一对一聊天就行了,所以数据库设计也非常简单。不同需求也可以设计更好的数据表。首先以一个表示聊天房间表,然后就是房间用户表,最后存放消息的表。

//聊天房间表
CREATE TABLE `pigcms_chat_room` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `store_id` int(11) NOT NULL DEFAULT '0' COMMENT '店铺id',
  `agent_id` varchar(12) NOT NULL DEFAULT '' COMMENT '代理商id',
  `is_store` tinyint(11) NOT NULL DEFAULT '0' COMMENT '0 平台 1 独立版小程序',
  `add_time` int(11) NOT NULL DEFAULT '0',
  `update_time` int(11) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8;

//房间用户表
CREATE TABLE `pigcms_chat_room_user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `room_id` int(11) NOT NULL DEFAULT '0' COMMENT '房间id',
  `user_id` int(11) NOT NULL DEFAULT '0' COMMENT '用户·id',
  `to_user_id` int(11) NOT NULL DEFAULT '0' COMMENT '对方user_id',
  `is_master` tinyint(1) NOT NULL DEFAULT '0' COMMENT '1 客服人员',
  `add_time` int(11) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;

//消息表
CREATE TABLE `pigcms_chat_message` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `room_id` int(11) NOT NULL,
  `send_user_id` int(11) NOT NULL DEFAULT '0' COMMENT '自己user_id',
  `receive_user_id` int(11) NOT NULL DEFAULT '0' COMMENT '对方user_id',
  `type` tinyint(1) NOT NULL DEFAULT '0' COMMENT '1 发送 2 接收',
  `message_type` tinyint(1) NOT NULL DEFAULT '1' COMMENT '消息类型 1 文本 2 图片 3 语音',
  `message_content` text COMMENT '消息内容',
  `is_read` tinyint(1) NOT NULL DEFAULT '0' COMMENT '0 未读 1 已读',
  `add_time` int(11) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4;

php怎么实例webscoket呢?开发文档是这样的,我基本上也是这样写的:

//创建websocket服务器对象,监听0.0.0.0:9501端口
$ws = new Swoole\WebSocket\Server("0.0.0.0", 9501);

//监听WebSocket连接打开事件
$ws->on('open', function ($ws, $request) {
    var_dump($request->fd, $request->get, $request->server);
    $ws->push($request->fd, "hello, welcome\n");
});

//监听WebSocket消息事件
$ws->on('message', function ($ws, $frame) {
    echo "Message: {$frame->data}\n";
    $ws->push($frame->fd, "server: {$frame->data}");
});

//监听WebSocket连接关闭事件
$ws->on('close', function ($ws, $fd) {
    echo "client-{$fd} is closed\n";
});

$ws->start();

//怎么运行这段代码呢
php ws_server.php

//方便操作,最好可以编辑好启动,重启,关闭的linux命令

怎么实现聊天逻辑呢?代码就不放出来了,就简单的说下伪代码吧:

//实例化服务
$this->server = new \swoole_websocket_server("127.0.0.1", 9501, SWOOLE_PROCESS, SWOOLE_SOCK_TCP);

$this->server->set(array(
    'daemonize' => true, //常驻进程,开发调试时可以注释掉
));
//监听第一次连接
$this->server->on('open', function ($server, $request) {
    //第一次连接时候根绝前端的传参确定聊天的对象,以及自己的用户身份
    //webscoket在握手之后会用一个参数fd来标记前端的用户
    //所以第一次连接的时候把fd和uid做个双向绑定操作,连接中的交互可以通过fd找到uid
    //怎么通过uid找到fd,由于是一对一的聊天,每次退出聊天窗口都会断开连接
    //所以不需要做一对多的绑定,只需要一对一的双向绑定即可
    //业务逻辑就是根据fd找到自己的身份,然后确定聊天对象,如果没有聊天记录,就创建一个聊天,然后就是执行获取聊天记录,返回
})
//怎么存储fd和uid呢,我是用了缓存,可以用mysql,只是存到缓存读取比较快
<?php

use think\facade\Cache;

class connect
{
    //设置
    public static function save($fd, $uid)
    {
        Cache::rm('_fd_'.$fd);
        Cache::rm('_uid_'.$uid);

        $data = ['fd'=>$fd,'uid'=>$uid];

        Cache::set('_fd_'.$fd, $data);
        Cache::set('_uid_'.$uid, $data);
    }

    //获取uid
    public static function get_uid($fd)
    {
        $fd = Cache::get('_fd_'.$fd);
        return isset($fd['uid'])&&!empty($fd['uid']) ? $fd['uid'] : 0;
    }

    //获取fd
    public static function get_fd($uid)
    {
        $uid = Cache::get('_uid_'.$uid);
        return isset($uid['fd'])&&!empty($uid['fd']) ? $uid['fd'] : 0;
    }

    //断开连接清除数据
    public static function delete($fd, $uid)
    {
        Cache::rm('_fd_'.$fd);
        Cache::rm('_uid_'.$uid);
    }

}

//存储的时候调用
connect::save($request->fd, $uid);
//监听message消息交互,主要有(获取更多的聊天的记录和接收消息和发送消息)
$this->server->on('message', function ($server, $frame) {
    //连接中的交互用通过fd获取用户id
    $uid = connect::get_uid($frame->fd);
    //然后判断用户发送过来的数据是什么类型(获取聊天记录或者发送消息)
    //发送消息时找到聊天对象,通过uid查找fd,判断对方是否在线
    //如果在线,存储到数据库的消息,就标记为已读,并且把消息推送过去
    $to_fd = connect::get_fd($touser['uid']);
    if (!empty($to_fd)) {
        $receive_message['is_read'] = 1;
    }
    if (!empty($to_fd)) {
        $server->push($to_fd, json_encode($message_data));
    }
    //告诉自己发送成功了
    return $server->push($frame->fd, json_encode(['type'=>'send_success','message'=>'发送成功'.$to_fd]));
})
//监听关闭链接,清掉存在的fd和uid
$this->server->on('close', function ($server, $fd) {
    $uid = connect::get_uid($fd);
    connect::delete($fd, $uid);
});

类似文章