Hyperf2.1框架基于websocket协议实现群聊,聊天功能

2023-06-01 00:00:00 功能 框架 协议

功能是聊天,群聊基于用户的聊天,所以我直接在网站的用户中心页面添加这个功能


环境还是那套,需要环境安装教程的可以看我之前的文章。话不多websocket是什么大家也都在百度看到过,我就不过多介绍,我直接上干货,开始做聊天功能步骤流程。


功能涉及知识点

websocket协议

redis集合


功能需要的依赖组件

安装websocket-server组件

composer require hyperf/websocket-server

redis组件包

这个默认框架已经安装,自己安装本地安装redis服务,或者远程别的服务器


添加配置文件 /config/autoload/server.php

[
    'name' => 'ws',
   'type' => Server::SERVER_WEBSOCKET,
    'host' => '0.0.0.0',
    'port' => 9502,
    'sock_type' => SWOOLE_SOCK_TCP,
    'callbacks' => [
           Event::ON_HAND_SHAKE => [Hyperf\WebSocketServer\Server::class, 'onHandShake'],
           Event::ON_MESSAGE => [Hyperf\WebSocketServer\Server::class, 'onMessage'],
           Event::ON_CLOSE => [Hyperf\WebSocketServer\Server::class, 'onClose'],
       ],
]

启动hyperf框架

[[email protected] ~]# cd /home/www/hyperf-skeleton/
[[email protected] hyperf-skeleton]# php bin/hyperf.php start
...
[INFO] WebSocket Server listening at 0.0.0.0:9502
[INFO] HTTP Server listening at 0.0.0.0:9501
...

这就说明websocket启动了, 其他省略


添加websocket路由

//ws
Router::addServer('ws', function () {
    Router::get('/ws', 'App\Controller\WebSocketController');
});


websocket服务端控制器代码 

/app/Controller/WebSocketController.php

<?php
declare(strict_types=1);
namespace App\Controller;
use Hyperf\Contract\OnCloseInterface;
use Hyperf\Contract\OnMessageInterface;
use Hyperf\Contract\OnOpenInterface;
use Swoole\Http\Request;
use Swoole\Server;
use Swoole\Websocket\Frame;
use Swoole\WebSocket\Server as WebSocketServer;
use Hyperf\DbConnection\Db;
use Hyperf\Utils\ApplicationContext;
class WebSocketController implements OnMessageInterface, OnOpenInterface, OnCloseInterface
{
    public function onMessage($server, Frame $frame): void
    {
        $data = json_decode($frame->data,true);
        if(isset($data['type']) && $data['type'] == 'ping') {
            $server->push($frame->fd, 'Ping');//心跳
        }else{
            //心跳刷新缓存
            $redis = ApplicationContext::getContainer()->get(\Hyperf\Redis\Redis::class);
            //获取所有的客户端id
            $fdList = $redis->sMembers('websocket_zongscan');
            //如果当前客户端在客户端集合中,就刷新
            if (in_array($frame->fd, $fdList)) {
                $redis->sAdd('websocket_zongscan', $frame->fd);
                $redis->expire('websocket_zongscan', 3600);
            }
            //绑定用户信息
            $user = Db::table('user')->where('user_id',$data['uid'])->first();
            $data['username'] = $user->username;
            $data['face'] = $user->face;
            if(count($fdList)) {
                foreach ($fdList as $k => $v) {
                    $server->push(intval($v),json_encode($data));
                    echo "线程:$frame->fd 向线程 $v 发送信息\\n";
                }
            }
        }
    }
    public function onClose($server, int $fd, int $reactorId): void
    {
        //删掉客户端id
        $redis = ApplicationContext::getContainer()->get(\Hyperf\Redis\Redis::class);
        //移除集合中指定的value
        $redis->sRem('websocket_zongscan', $fd);
        var_dump('closed');
    }
    public function onOpen($server, Request $request): void
    {
        //保存客户端id
        $redis = ApplicationContext::getContainer()->get(\Hyperf\Redis\Redis::class);
        $redis->sAdd('websocket_zongscan', $request->fd);
        $redis->expire('websocket_zongscan', 3600);
        $server->push($request->fd, 'Opened');
    }
}


前端代码,我这里是写在用户中心需要登录

/storage/view/home/user/center.blade.php


    <div class="col-md-4 column">

        <div id="talk-window" class="talk-window">

            <div class="tw-title">本站技术讨论 - 聊天窗口<span class="close-tw-window">×</span></div>

            <div class="tw-content">

                <div class="talk-list-div"><a href="javascript:;" id="bottom-link"></a></div>

                <div class="talk-operate-div">

                    <div class="talk-operate-btn-list">可添加emoji表情等附加功能按钮</div>

                    <div class="talk-operate-textarea"><textarea id="msg-text" placeholder="老铁,请打字..."></textarea></div>

                    <div class="send-btn">

                        <button id="sendBtn" class="btn btn-primary button" style="margin-right: 10px;">发送</button>

                    </div>

                </div>

            </div>

        </div>

        <div class="h-clear"></div>

    </div>

其他的就省略了


js

@endsection
@section('js')
    <script>
        var replyData = [
            '[自动回复]您好,我现在有事不在,一会再和您联系',
            '[自动回复]因为工作的关系,曾面对无数好友呼叫不能回应,最痛苦的事莫过于此。如果给我一次机会,我要说三个字:我离开。如果一定要给这三个字加个期限,我希望是:一会儿!',
            '[自动回复]这次是我不经意的离开,却造成我们失之交臂的遗憾。于是我忘记了吃饭,无法再安睡,于是我不甘寂寞急忙归来。',
            '[自动回复]您所呼叫的用户尚在厕所中,稍后请拿厕纸给他!',
            '[自动回复]你所呼叫的用户正在系统整理,请稍后再呼。',
            '[自动回复]你好,我去杀几个人,很快回来。',
            '[自动回复]对不起,你所联系的用户因为太过帅气,已被TC公司删除。详情请咨询110,谢谢,再见。',
            '[自动回复]走开一下,如果3分钟之内还没回请不要发飙,因为我正在对着设相头摆POSE!',
            '[自动回复]有事留下你的真实姓名,家庭住址,电话号码,你的银行账号和密码、我会和你联系!?',
            '[自动回复]计算机正在处理你的信息,请稍候,如果长时间没有响应,请重新启动计算机!',
        ];
        function reply() {
            var num = parseInt(Math.random() * 10);
            var str = '<div class="msg-div"><div style="text-align: center;">'+ getCurrentDate(new Date()) +'</div>';
            str += '<div class="other-msg-div">';
            str += '<div class="header-img-div">';
            str += '<img src="/upload/photo/4.jpg" />';
            str += '</div>';
            str += '<div class="msg-content">';
            str += replyData[num];
            str += '</div>';
            str += '</div>';
            str += '</div>';
            //$("#bottom-link").before(str);
            $("#bottom-link").append(str);
            scrollHeight = $('.talk-list-div').prop("scrollHeight");
            $('.talk-list-div').scrollTop(scrollHeight, 200);
        }
        function getCurrentDate(date){
            var y = date.getFullYear();
            var m = date.getMonth()+1;
            var d = date.getDate();
            var h = date.getHours();
            var min = date.getMinutes();
            var s = date.getSeconds();
            var str=y+'年'+(m<10?('0'+m):m)+'月'+(d<10?('0'+d):d)+'日  '+(h<10?('0'+h):h)+':'+(min<10?('0'+min):min)+':'+(s<10?('0'+s):s);
            return str;
        }
        /*###################*/
        $(function() {
            //=========================初始化====================================
            var $window = $(window);
            var $messageArea = $('#bottom-link');        // 消息显示的区域
            var $inputArea = $('#msg-text');             // 输入消息的区域
            $inputArea.focus();                          // 首先聚焦到输入框
            var connected = false;                      // 用来判断是否连接的标志
            //====================webSocket连接======================
            // 创建一个webSocket连接
            var socket = new WebSocket('ws://'+window.location.host+'/ws');
            // 当webSocket连接成功的回调函数
            socket.onopen = function () {
                console.log("webSocket open");
                connected = true;
            };
            // 断开webSocket连接的回调函数
            socket.onclose = function () {
                console.log("webSocket close");
                connected = false;
            };
            window.setInterval(function () {            //每隔5秒钟发送一次心跳,避免websocket连接因超时而自动断开
                socket.send('{"type":"ping"}');
            }, 5000);
            //=======================接收消息并显示===========================
            // 接受webSocket连接中,来自服务端的消息
            socket.onmessage = function(event) {
                if (event.data == 'Ping'){
                    console.log("ping");
                } else {
                    if (event.data == 'Opened') {
                        console.log("revice:", event.data);
                        var type = 1;
                        var msg = '我加入';
                        setTimeout(function () { reply(); }, 300);
                    } else {
                        // 将服务端发送来的消息进行json解析
                        var data = JSON.parse(event.data);
                        //console.log("revice:" , data);
                        var name = data.username;
                        var face = data.face;
                        var type = 0;
                        var msg = data.message;
                    }
                    // type为0表示有人发消息
                    var $messageDiv;
                    if (type == 0) {
                        var str = '<div class="msg-div">';
                        if (name != '{{$session['username']}}') {
                            str += '<div class="other-msg-div">';
                        }else{
                            str += '<div class="my-msg-div">';
                        }
                        str += '<div class="header-img-div">';
                        str += '<img src="' + face + '" />';
                        str += '</div>';
                        str += '<div class="msg-content">';
                        str += msg;
                        str += '</div>';
                        str += '</div>';
                        str += '</div>';
                        var $messageDiv = $("#bottom-link").data('username', name).append(str);
                        scrollHeight = $('.talk-list-div').prop("scrollHeight");
                        $('.talk-list-div').scrollTop(scrollHeight, 200);
                    } else {// type为1或2表示有人加入或退出
                        var $messageBodyDiv = $('<span style="color:#999999;">').text(msg);
                        $messageDiv = $('<ol>').append($messageBodyDiv);
                    }
                    $messageArea.append($messageDiv);
                }
            }
            //========================发送消息==========================
            // 当回车键敲下
            $window.keydown(function (event) {
                // 13是回车的键位
                if (event.which === 13) {
                    sendMessage();
                    typing = false;
                }
            });
            // 发送按钮点击事件
            $("#sendBtn").click(function () {
                sendMessage();
            });
            // 通过webSocket发送消息到服务端
            function sendMessage () {
                var inputMessage = $inputArea.val();  // 获取输入框的值
                if (inputMessage && connected) {
                    $inputArea.val('');               // 清空输入框的值
                    var messages = '{"uid":"{{$session['user_id']}}","message":"'+ inputMessage + '"}';
                    socket.send(messages);            // 基于WebSocket连接发送消息
                    console.log("send message:" + messages);
                }
            }
        });
    </script>
@parent
@endsection

完事了 就是这么简单  


看看效果

url:http://192.168.1.98:9501/user/center


用两台电脑,两个账号测试

1号电脑

1.png

3.png

2号电脑

2.png

4-77.png


服务端

5.png




相关文章