swoft 客服系统websocket。即时通讯小功能

发布时间 2023-04-03 17:52:23作者: xyz叶子

 

挥手,握手就不解释了。直接上代码

启动swoft ws的服务

 

 

复制代码
<?php declare(strict_types=1);
/**
 * This file is part of Swoft.
 *
 * @link     https://swoft.org
 * @document https://swoft.org/docs
 * @contact  group@swoft.org
 * @license  https://github.com/swoft-cloud/swoft/blob/master/LICENSE
 */

namespace App\WebSocket;

use Swoft\Http\Message\Request;
use Swoft\Session\Session;
use Swoft\Http\Message\Response;
use Swoft\WebSocket\Server\Annotation\Mapping\OnClose;
use Swoft\WebSocket\Server\Annotation\Mapping\OnMessage;
use Swoft\WebSocket\Server\Annotation\Mapping\OnHandshake;
use Swoft\WebSocket\Server\Annotation\Mapping\OnOpen;
use Swoft\WebSocket\Server\Annotation\Mapping\WsModule;
use Swoole\WebSocket\Frame;
use Swoole\WebSocket\Server;
use function server;

use Swoft\Redis\Exception\RedisException;
use Swoft\Redis\Pool;
use Swoft\Redis\Redis;
use Swoft\Log\Helper\CLog;
use Swoft\Db\DB;
use Swoft\Task\Task;


/**
 * Class EchoModule
 *
 * @WsModule("echo")
 */
class EchoModule
{
    /**
     * @OnOpen()
     * @param Request $request
     * @param int     $fd
     */
    public function onOpen(Request $request, int $fd): void
    {
        Session::current()->push("Opened, welcome #{$fd}!");
    }

    /**
     * @OnMessage()
     * @param Server $server
     * @param Frame  $frame
     * blacklist 黑名单
     * Blockkeywords 屏蔽关键字
     * 检测客服是否在线
     * 判断条件,搜索关键字。匹配自动回复内容
     */
    public function onMessage(Server $server, Frame $frame): void
    {
        CLog::info('talk: %s ', $frame->data);

        $data = json_decode($frame->data, true);

        if ($data['flag'] == 'init') {
            $res = Redis::set('user_fd'.$data['from'], $frame->fd);
        }

        $blacklist = Redis::get('blacklist');

            if (empty($blacklist)) {

                $list = Db::table('db_customer_blacklist')->where('status',1)->select('user_id')->get();

                $blacklist = [];

                foreach ($list as $k => $v) {
                    $blacklist[] = $v['user_id'];
                }

            }

            $blackStr = implode(",", $blacklist);

            if(strpos($blackStr, $data['from']) !== false){
                $server->push($frame->fd, json_encode(['content'=>'卧槽~!被禁言了','from'=>$data['to']]));die;
            }

        $BlockkeywordsStr = Redis::get('Blockkeywords');

            if (empty($Blockkeywords)) {
                $Blocklist = Db::table('db_customer_blockkeyword')->where('status',1)->select('keywords')->get();

                $BlockkeywordsStr = '';

                foreach ($Blocklist as $k1 => $vo) {
                    if ($k1 == 0) {
                        $BlockkeywordsStr .= $vo['keywords'];
                    }else{
                        $BlockkeywordsStr .= ','.$vo['keywords'];
                    } 
                }

            }

            $BlockkeywordsArr = explode(',', $BlockkeywordsStr);

            foreach ($BlockkeywordsArr as $k2 => $voo) {
                        
                if ( strpos($frame->data,trim($voo)) ){
                    $frame->data = str_replace($voo ,'*',$frame->data);
                }

            }

        Task::async('TalkTask', 'UserToCustomerTalkLog', [$data['from'], $data['to'], 1, $data['content']]);

        $customer = Redis::get('user_fd'.$data['to']);

            if (empty($customer)) {
                //$autoReply = Redis::hget($data['content']);

                CLog::info('talk: %s ', '2222');

                $server->push($frame->fd, json_encode(['content'=>'这里是人工智能回复','from'=>$data['to']]));
            }else{

                $server->push($customer, json_encode(['content'=>$data['content'],'from'=>$data['to']]));
                CLog::info('talk: %s ', 'Recv: '.$customer.'--'. $data['content'].'--'.$data['to']);
            }

    }
    /**
     * @onClose()
     * @param Server $server
     * @param int    $fd
     */
    public function onClose(Server $server, int $fd): void
    {
        $res = Redis::del('user_fd'.$fd);

        CLog::info('#' . $fd. 'Redis:' . $res);

        $server->close($fd);
    }

}
复制代码

以上可以去掉 CLog::info('#' . $fd. 'Redis:' . $res);   主要是能在后台看到实时的情况

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
<?php
 
    $MyInfo = ['avatar'=>'https://wx.qlogo.cn/mmopen/vi_32/NhCl8wDjCM0IoZLgjZNtwb8rN26jic4Rxbk8lc0HIfvP3FY6HuZOtrArW45eBnS6htsJ9uMYPC4lxoXYchtwQQA/132','nickname'=>'黑锦鲤','id'=>66296];
    $ToInfo = ['avatar'=>'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif','nickname'=>'客服锦鲤','id'=>151];
 
?>
 
<!DOCTYPE html>
<html>
<head>
    <title>XST-app</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7" />
    <meta name="viewport" content="width=device-width, initial-scale=0.0, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0" />
    <meta name="keywords" content="test" />
    <meta name="description" content="test" />
    <meta name="author" content="XST-APP" />
    <meta content="yes" name="apple-mobile-web-app-capable" />
    <meta content="black" name="apple-mobile-web-app-status-bar-style" />
    <meta content="telephone=no" name="format-detection" />
    <script type="text/javascript" src="./jquery-1.8.3.min.js"></script>
  <style type="text/css">
    body{background:url(/static/images/yuyin_bg.jpg);background-size:100%;}
    @media all and (min-width: 640px) {
        body,html,.wenwen-footer,.speak_window{width:640px!important;margin:0 auto}
        .speak_window,.wenwen-footer{left:50%!important;margin-left:-320px}
    }
    input,button{outline:none;}
    .wenwen-footer{width:100%;position:fixed;bottom:-5px;left:0;background:#fff;padding:3%;border-top:solid 1px #ddd;box-sizing:border-box;}
    .wenwen_btn,.wenwen_help{width:15%;text-align:center;}
    .wenwen_btn img,.wenwen_help img{height:40px;}
    .wenwen_text{height:40px;border-radius:5px;border:solid 1px #636162;box-sizing:border-box;width:66%;text-align:center;overflow:hidden;margin-left:2%;}
    .circle-button{padding:0 5px;}
    .wenwen_text .circle-button{font-size:14px;color:#666;line-height:38px;}
    .write_box{background:#fff;width:100%;height:40px;line-height:40px;}
    .write_box input{height:40px;padding:0 5px;line-height:40px;width:100%;box-sizing:border-box;border:0;}
    .wenwen_help button{width:95%;background:#42929d;color:#fff;border-radius:5px;border:0;height:40px;}
    #wenwen{height:100%;}
    .speak_window{overflow-y:scroll;height:100%;width:100%;position:fixed;top:50px;left:0;}
    .speak_box{margin-bottom:70px;padding:10px;}
    .question,.answer{margin-bottom:1rem;}
    .question{text-align:right;}
    .question>div{display:inline-block;}
    .left{float:left;}
    .right{float:right;}
    .clear{clear:both;}
    .heard_img{height:60px;width:60px;border-radius:5px;overflow:hidden;background:#ddd;}
    .heard_img img{width:100%;height:100%}
    .question_text,.answer_text{box-sizing:border-box;position:relative;display:table-cell;min-height:60px;}
    .question_text{padding-right:20px;}
    .answer_text{padding-left:20px;}
    .question_text p,.answer_text p{border-radius:10px;padding:.5rem;margin:0;font-size:14px;line-height:28px;box-sizing:border-box;vertical-align:middle;display:table-cell;height:30px;word-wrap:break-word;}
    .answer_text p{background:#fff;}
    .question_text p{background:#42929d;color:#fff;text-align:left;}
    .question_text i,.answer_text i{width:0;height:0;border-top:5px solid transparent;border-bottom:5px solid transparent;position:absolute;top:25px;}
    .answer_text i{border-right:10px solid #fff;left:10px;}
    .question_text i{border-left:10px solid #42929d;right:10px;}
    .answer_text p a{color:#42929d;display:inline-block;}
    .write_list{position:absolute;left:0;width:100%;background:#fff;border-top:solid 1px #ddd;padding:5px;line-height:30px;}
  </style>
</head>
 
<body>
<div id="header" class="head">
        <div class="wrap">
                <i class="menu_back"><a href="javascript:history.go(-1);"></a></i>
                <div class="title">
                        <span class="title_d"><p>与   <?php echo $ToInfo['nickname']; ?>   的聊天</p></span>
                        <div class="clear"></div>
                </div>
                <!--i class="menu_share"></i-->
        </div>
</div>
 
<input type="hidden" name="myemail" id="myemail" value="<?php echo $MyInfo['id']; ?>" />
<input type="hidden" name="mynickname" id="mynickname" value="<?php echo $MyInfo['nickname']; ?>" />
<input type="hidden" name="myavatar" id="myavatar" value="<?php echo $MyInfo['avatar']; ?>" />
 
<input type="hidden" name="toemail" id="toemail" value="<?php echo $ToInfo['id']; ?>" />
<input type="hidden" name="tonickname" id="tonickname" value="<?php echo $ToInfo['nickname']; ?>" />
<input type="hidden" name="toavatar" id="toavatar" value="<?php echo $ToInfo['avatar']; ?>" />
 
<!-- 对话内容 -->
<div class="speak_window">
    <div class="speak_box">
 
    </div>
</div>
 
 
<!-- 内容输入-->
<div class="wenwen-footer">
    <div class="wenwen_btn left"><img src="/static/images/jp_btn.png"></div>
    <div class="wenwen_text left">
        <div class="write_box"><input type="text" class="left" onKeyUp="keyup()" maxlength="100" placeholder="请输入信息(100字以内)..." /></div>  
    </div>
    <div class="wenwen_help right">
            <button onClick="send()" class="right">发送</button>
    </div>
    <div style="opacity:0;" class="clear"></div>
</div>
 
 
<script type="text/javascript">
    if ("WebSocket" in window){
        var ws = new WebSocket("ws://47.111.253.56:18308/echo");
        ws.onopen = function(){
            console.log("握手成功");
            var myemail = $("#myemail").val();
            var mynickname = $("#mynickname").val();
            var toemail = $("#toemail").val();
            var arr = {"flag":"init","from":myemail,"to":toemail,"content":mynickname+'需要帮助'};
            var str = JSON.stringify(arr);
            alert(str)
            ws.send(str);
        };
        ws.onmessage = function(e){
            var toemail = $("#toemail").val();
            var toavatar = $("#toavatar").val();
 
            if (e.data) {
                console.log(e.data);
 
                var obj = JSON.parse(e.data);
                 
                console.log(obj.from);
                console.log(toemail);
                console.log(obj.content);
                //但同时与两个人聊天时,可能两个人的消息都会出现在当前窗口,所以此处加个判断,此窗口只接收当前聊天对象的消息,其他则忽略
                //if(obj.from === toemail){
                    var ans  = '<div class="answer"><div class="heard_img left"><img src="'+toavatar+'"></div>';
                        ans += '<div class="answer_text"><p>'+obj.content+'</p><i></i>';
                        ans += '</div></div>';
                        $('.speak_box').append(ans);
                        for_bottom();
                //}
            };
             
        };
        ws.onerror = function(){
            console.log("error");
            var  str  = '<div class="question">';
            str += '<div class="heard_img right"><img src="/static/images/xitong.jpg"></div>';
            str += '<div class="question_text clear"><p>聊天服务器出现异常,暂时无法提供服务。</p><i></i>';
            str += '</div></div>';
            $('.speak_box').append(str);
            $('.write_box input').val('');
            $('.write_box input').focus();
            autoWidth();
            for_bottom();
        };
 
        function send() {
            var content = $('.write_box input').val();
            if(content === ''){
                alert('请输入消息!');
                $('.write_box input').focus();
            }else{
                    var toemail = $("#toemail").val();
                    var myemail = $("#myemail").val();
                    var myavatar = $("#myavatar").val();
                    var arr = {"flag":"msg","to":toemail,"from":myemail,"content":content};
                    var msg = JSON.stringify(arr);
                    console.log(msg);
                    ws.send(msg);   
                    var  str  = '<div class="question">';
                    str += '<div class="heard_img right"><img src="'+myavatar+'"></div>';
                    str += '<div class="question_text clear"><p>'+content+'</p><i></i>';
                    str += '</div></div>';
                $('.speak_box').append(str);
                $('.write_box input').val('');
                $('.write_box input').focus();
                autoWidth();
                for_bottom();
            }
         
        }
    }else{
        alert("您的浏览器不支持 WebSocket!");
    }
            
    function for_bottom(){
    var speak_height = $('.speak_box').height();
    $('.speak_box,.speak_window').animate({scrollTop:speak_height},500);
    }
     
    function autoWidth(){
    $('.question_text').css('max-width',$('.question').width()-60);
    }
     
    autoWidth();
     
</script>
 
</body>
</html>

 

 这个客户端就是为了测试。