Thinkphp6---workman开发聊天

最近DeepSeek很火,就结合workman开发一个聊天对话功能:

一、安装 think-worker和 think-view

composer require topthink/think-worker
composer require topthink/think-view

版本更新比较快,通过这个安装的 think-worker 是4.0的,但是需要 thinkphp是8.0的

可以安装3.0的:

composer require topthink/think-worker 3.0

安装后会增加两个配置文件:

修改端口:

// 扩展自身需要的配置
'protocol'       => 'websocket', // 协议 支持 tcp udp unix http websocket text
'host'           => '127.0.0.1', // 监听地址
'port'           => 8000, // 监听端口
'socket'         => 'http://127.0.0.1:8000', // 完整监听地址
'context'        => [], // socket 上下文选项
'worker_class'   => 'app\http\Worker', // 自定义Workerman服务类名 支持数组定义多个服务

二、新建工程目录

控制器:

复制代码
<?php
namespace app\http\controller;
use think\App;
use think\facade\View;
/** * Step controller */

class ChatController { /** * list */ public function index() { return View::fetch(); } }
复制代码

视图:

复制代码
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title></title>
<link rel="stylesheet" type="text/css" href="/static/httpchat/font_Icon/iconfont.css">
<script src="/static/admin/jquery/jquery-3.6.0.min.js"></script>
<style>
.userlist li{background: rgb(0 0 0 / 5%);text-align: left;padding: 5px;height: 49px;font-size: 16px;border-bottom: 1px solid rgb(0 0 0 / 5%);}
.userlist li img{width: 40px;height: 40px;padding-right: 5px;}
.message-num{right: 2px;}
.ChatInfoName{height: 50px;border-bottom: 1px solid #D9D9D9;line-height: 50px;font-size: 16px;padding: 0 10px;}
.chatBox-content{height: 498px !important;width: 100%;}
.chatBox-content-demo{width: 100%;height: 370px !important;overflow-y: scroll;}
.div-textarea{width: 490px !important;/*min-height: 20px;*/height: 100px;_height: 120px;padding: 3px;outline: 0;background: #fff;font-size: 14px;line-height: 20px;word-wrap: break-word;overflow-x: hidden;overflow-y: auto;user-modify: read-write-plaintext-only;/*纯文本*/-webkit-user-modify: read-write-plaintext-only;-moz-user-modify: read-write-plaintext-only;}
.div-textarea:focus{box-shadow: 0 0 15px rgba(82, 168, 236, 0.6);}
.clearfloat:after{display:block;clear:both;content:"";visibility:hidden;height:0}
.clearfloat{zoom:1;margin: 10px 10px;}
.clearfloat .right{float: right;}
.author-name{text-align: center;margin: 15px 0 5px 0;color: #888;}
.clearfloat .chat-message{max-width: 252px;text-align: left;padding: 8px 12px;border-radius: 6px;word-wrap:break-word;display: inline-block;position: relative;}
.clearfloat .left .chat-message{background: #D9D9D9;min-height: 36px;}
.clearfloat .left .chat-message:before{position: absolute;content: "";top: 8px;left: -6px;border-top: 10px solid transparent;border-bottom: 10px solid transparent;border-right: 10px solid #D9D9D9;}
.clearfloat .right{text-align: right;}
.clearfloat .right .chat-message{background: #8c85e6;color: #fff;text-align: left;min-height: 36px;}
.clearfloat .right .chat-message:before{position: absolute;content: "";top: 8px;right: -6px;border-top: 10px solid transparent;border-bottom: 10px solid transparent;border-left: 10px solid #8c85e6;}
.clearfloat .chat-avatars{display: inline-block;width: 30px;height: 30px;border-radius: 50%;background: #eee;vertical-align: top;overflow: hidden;}
.clearfloat .chat-avatars>img{width: 30px;height: 30px;}
.clearfloat .left .chat-avatars{margin-right: 10px;}
.clearfloat .right .chat-avatars{margin-left: 10px;}
.chatBox-send{width: 100%;padding: 10px 5px;background: #eee;border-top: 1px #D0D0D0 solid;position: absolute;bottom: 0;left: 0;}
.chatBox-send>div{float: left;}
.chatBox-send>div:nth-of-type(2){font-size: 0;}
.chatBox-send>div button{padding: 1px 5px;margin-left: 3px;}
.chatBox-send>div label{padding: 1px 5px;margin-left: 3px;}
#chat-biaoqing{position: relative;}
.hidden{display: none;}
.biaoqing-photo{width: 200px;height: 160px;background: #ffffff;position: absolute;top: -160px;right: 40px;text-align: left;border-radius: 5px;border: solid 1px #c5c5c5;display: none;}
.biaoqing-photo::before{content: '';position: absolute;border-top: solid 7px #c5c5c5;border-left: solid 9px transparent;border-right: solid 9px transparent;bottom: -7px;right: 36px;}
.biaoqing-photo::after{content: '';position: absolute;border-top: solid 7px #fff;border-left: solid 10px transparent;border-right: solid 10px transparent;bottom: -5px;right: 35px;}
.biaoqing-photo>ul{margin: 0;width: 200px;height: 160px;padding: 3px 2px;list-style: none;}
.biaoqing-photo>ul>li{float: left;height: 30px;margin-left: 2px;}
.emoji-picker-image{display: inline-block;width: 30px;height: 30px;background: url(/static/httpchat/img/bqxtb01.png) no-repeat;background-size: 200px auto;cursor: pointer;}
.biaoqing-photo>ul>li span.emoji-picker-image:hover{border: solid 1px #f5f5f5;}
.chat-message img{width: 220px;height:auto;}
.chat-name{width: 230px;}
/*按钮样式*/.btn-default-styles{outline: none;resize: none;border: none;display: inline-block;padding: 5px 10px;margin-bottom: 0;font-size: 14px;font-weight: 400;line-height: 1.42857143;text-align: center;white-space: nowrap;vertical-align: middle;-ms-touch-action: manipulation;touch-action: manipulation;cursor: pointer;-webkit-user-select: none;-moz-user-select: none;-ms-user-select: none;user-select: none;background-image: none;background: #bbb;color: #fff;border-radius: 4px;}
.btn-default-styles:focus{outline: none;}
.btn-default-styles:hover{background: #c5c5c5;animation: anniu 1s infinite;}
.btn-default-styles:active{box-shadow: 0 2px 3px rgba(0, 0, 0, .2) inset;}
/* 设置滚动条的样式*/::-webkit-scrollbar{width:5px;}
/* 滚动槽*/::-webkit-scrollbar-track{border-radius:10px;}
/* 滚动条滑块*/::-webkit-scrollbar-thumb{border-radius:10px;background:#8C85E6;-webkit-box-shadow:#8C85E6;}
::-webkit-scrollbar-thumb:window-inactive{background: rgba(175, 190, 255, 0.4);}
@media all and (max-width: 768px){.chatBox{position: fixed;top: 0;left: 0;width: 100%;height: 100%;}} @media all and (max-width: 370px){.chat-name{width: 185px;}
.chat-people>div:nth-of-type(2){width: 120px;}
.clearfloat .chat-message{max-width: 240px;}}
</style>
</head>
<body>
<div class="layui-fluid" style="padding: 0">
    <div class="layui-row" style="overflow: hidden;">
        <div class="layui-col-md3 layui-col-lg3 layui-col-xs3 div-user" style="border-right: 1px solid #b4b2b2;height: 549px;overflow-y: scroll;">
            <ul class="userlist" id="chatuser">
                <li lay-id="1">
                    <img src="//tva1.sinaimg.cn/crop.0.0.118.118.180/5db11ff4gw1e77d3nqrv8j203b03cweg.jpg" alt="">
                    <span class="chat-name">管理员</span>
                    <span class="message-num">20</span>
                </li>
            </ul>
        </div>
        <div class="layui-col-md9 layui-col-lg9 layui-col-xs9 ">
            <div id="chatcontent">
                <div class="ChatInfoName">这里是用户昵称</div>
                <div>
                    <div class="chatBox-content">
                        <div class="chatBox-content-demo" id="chatBox-content-demo">


                        </div>
                    </div>
                    <div class="chatBox-send">
                        <div class="div-textarea" contenteditable="true"></div>
                        <div>
                            <button id="chat-biaoqing" class="btn-default-styles">
                                <i class="iconfont icon-biaoqing"></i>
                            </button>
                            <label id="chat-tuxiang" title="发送图片" for="inputImage" class="btn-default-styles">
                                <input type="file" onchange="selectImg(this)" accept="image/jpg,image/jpeg,image/png"
                                       name="file" id="inputImage" class="hidden">
                                <i class="iconfont icon-tuxiang"></i>
                            </label>
                            <button id="chat-fasong" class="btn-default-styles"><i class="iconfont icon-fasong"></i>
                            </button>
                        </div>
                        <div class="biaoqing-photo">
                            <ul>                               
                                <li><span class="emoji-picker-image" style="background-position: -164px -154px;"></span></li>
                            </ul>
                        </div>
                    </div>
                </div>
            </div>

        </div>
    </div>
</div>

</body>
</html>
<script>

    var myavatars = '';
    var myDate =new Date();
    var year=myDate.getFullYear(); //获取当前年份
    var mon=myDate.getMonth()+1; //获取当前月份
    var da=myDate.getDate(); //获取当前日
    var day=myDate.getDay(); //获取当前星期几
    var h=myDate.getHours(); //获取小时
    var m=myDate.getMinutes(); //获取分钟
    var s=myDate.getSeconds(); //获取秒
    var ms=myDate.getMilliseconds();    //获取当前毫秒数(0-999)
    var ld=myDate.toLocaleDateString();    //获取当前日期
    var msgdate=year+'-'+mon+'-'+da+' '+h+':'+m+':'+s;

    

    var from_id = "{$from_id}";
    var to_id = "{$to_id}";

    //进聊天页面
    $("#chatuser li").each(function () {
        $(this).click(function () {
            $("#chatuser li").css('background','rgb(0 0 0 / 5%)')
            $(this).css('background','#ffffff')
            var n = $(this).index();
            to_id = $(this).attr('lay-id')
            // $(".chatBox-head-one").toggle();
            // $(".chatBox-head-two").toggle();
            // $(".chatBox-list").fadeToggle();
            // $(".chatBox-kuang").fadeToggle();

            //传名字
            $(".ChatInfoName").text($(this).children(".chat-name").eq(0).html());

            //赋值头像
            myavatars = $(this).children('img').eq(0).attr("src");
            // //传头像

            //聊天框默认最底部
            $(document).ready(function () {
                $("#chatBox-content-demo").scrollTop($("#chatBox-content-demo")[0].scrollHeight);
            });
            // // 打开websocket
            // webSocket.onopen = function (event) {
            onOpen();
            // };
        })
    });
    // var userlist = [10009,10010,10011,10012,10013,10014,10015,10016,10017];
    /**
     0:未连接
     1:连接成功,可通讯
     2:正在关闭
     3:连接已关闭或无法打开
     */
        //创建一个webSocket 实例
    var webSocket = new WebSocket("ws://192.168.0.113:2345");
    webSocket.onerror = function (event) {
        onError(event);
    };
    // 打开websocket
    webSocket.onopen = function (event) {
        onOpen(event);
    };
    //监听消息
    webSocket.onmessage = function (event) {
        onMessage(event);
    };
    webSocket.onclose = function (event) {
        onClose(event);
    }
    //关闭监听websocket
    function onError(event) {
        // document.getElementById("msg").innerHTML = "<p>关闭</p>";
        console.log("错误: " + event.data);
    }
    function onOpen(event) {
        // console.log("打开: "+sockState());
        var bild = '{"type":"bind","uid":"' + from_id + '"}';
        webSocket.send(bild);
      
        setInterval(function () {
            webSocket.send('heartbeat');    //发送内容不限,只是为了证明客户端还没关闭还在线
        }, 20000)                    //50秒发一次
    }
    function onMessage(event) {
        // console.log(event.data)
        var massage = eval("(" + event.data + ")");

        //这里表示一对一聊天
        if (massage.type == 'text' && massage.mode == 'single') {
            //当前正在聊天的人  如果当前的发送对象id等于该消息来源的id,则为一个人  ,
            if (this.to_id == massage.from_id) {
                // console.log("服务端消息:"+massage.content);
                // $("#chatBox-content-demo").append("<div>我是" + massage.from_id + ":" + massage.content + "</div>");
                $("#chatBox-content-demo").append("<div class=\"clearfloat\">\n" +
                    "                                <div class=\"author-name\">\n" +
                    "                                    <small class=\"chat-date\">"+ massage.datetime +"</small>\n" +
                    "                                </div>\n" +
                    "                                <div class=\"left\">\n" +
                    "                                    <div class=\"chat-avatars\"><img src=\"" + myavatars + "\" alt=\"头像\"/></div>\n" +
                    "                                    <div class=\"chat-message\">" + massage.content + "</div>\n" +
                    "                                </div>\n" +
                    "                            </div>");
                return;
            } else {
                //不是当前聊天的人、将消息提醒发送到对应的列表,并将消息发送到对应页面
                //在数据库请求该人的基本信息,
            }

        }
        //这里表示群聊
        if (massage.type == 'text' && massage.mode == 'group') {

            console.log(massage);
        }

    }
    function onClose(event) {
        // document.getElementById("msg").innerHTML = "<p>他通讯已关闭</p>";
        console.log("关闭: " + sockState());
        webSocket.close();
    }
    function sockState() {
        var status = ['未连接', '连接成功,可通讯', '正在关闭', '连接已关闭或无法打开'];
        return status[webSocket.readyState];
    }
   
    function close(event) {
        webSocket.close();
    }
    document.onkeydown = keyDownSearch;
    function keyDownSearch(e) {
        // 兼容FF和IE和Opera
        var theEvent = e || window.event;
        var code = theEvent.keyCode || theEvent.which || theEvent.charCode;
        if (code == 13) {
            //具体处理函数
            chat_fasong()
            return false;
        }
        return true;
    }


    //未读信息数量为空时
    var totalNum = $(".chat-message-num").html();
    if (totalNum == "") {
        $(".chat-message-num").css("padding", 0);
    }
    $(".message-num").each(function () {
        var wdNum = $(this).html();
        if (wdNum == "") {
            $(this).css("padding", 0);
        }
    });



    //      发送信息
    $("#chat-fasong").click(function () {
        chat_fasong()
    });
    function chat_fasong() {
        var textContent = $(".div-textarea").html().replace(/[\n\r]/g, '<br>')
        if (textContent != "") {
            //发送到服务器
            var msg1 = '{"type":"text","mode":"single","from_id":"' + from_id + '","to_id":"' + to_id + '","content":"' + textContent + '","datetime":"' + msgdate + '"}';
            webSocket.send(msg1);
            $(".chatBox-content-demo").append("<div class=\"clearfloat\">" +
                "<div class=\"author-name\"><small class=\"chat-date\">" + msgdate + "</small> </div> " +
                "<div class=\"right\"> <div class=\"chat-message\"> " + textContent + " </div> " +
                "<div class=\"chat-avatars\"><img src=\"/static/httpchat/img/icon01.png\" alt=\"头像\" /></div> </div> </div>");
            /*"/static/httpchat/img/icon01.png"*/
            //发送后清空输入框
            $(".div-textarea").html("");
            //聊天框默认最底部
            $(document).ready(function () {
                $("#chatBox-content-demo").scrollTop($("#chatBox-content-demo")[0].scrollHeight);
            });
        }
    }

    //      发送表情
    $("#chat-biaoqing").click(function () {
        $(".biaoqing-photo").toggle();
    });
    $(document).click(function () {
        $(".biaoqing-photo").css("display", "none");
    });
    $("#chat-biaoqing").click(function (event) {
        event.stopPropagation();//阻止事件
    });

    $(".emoji-picker-image").each(function () {
        $(this).click(function () {
            var bq = $(this).parent().html();
            //发送到服务器
            var msg2 = '{"type":"text","mode":"single","from_id":"' + from_id + '","to_id":"' + to_id + '","content":"' + bq + '","datetime":"' + msgdate + '"}';
            webSocket.send(msg2);
            $(".chatBox-content-demo").append("<div class=\"clearfloat\">" +
                "<div class=\"author-name\"><small class=\"chat-date\">"+ msgdate +"</small> </div> " +
                "<div class=\"right\"> <div class=\"chat-message\"> " + bq + " </div> " +
                "<div class=\"chat-avatars\"><img src=\"/static/httpchat/img/icon01.png\" alt=\"头像\" /></div> </div> </div>");
            //发送后关闭表情框
            $(".biaoqing-photo").toggle();
            //聊天框默认最底部
            $(document).ready(function () {
                $("#chatBox-content-demo").scrollTop($("#chatBox-content-demo")[0].scrollHeight);
            });
        })
    });

    //      发送图片
    function selectImg(pic) {
        if (!pic.files || !pic.files[0]) {
            return;
        }
        var reader = new FileReader();
        reader.onload = function (evt) {
            var images = evt.target.result;
            var to_img = "<img src=" + images + ">";
            //发送到服务器
            var msg3 = '{"type":"text","mode":"single","from_id":"' + from_id + '","to_id":"' + to_id + '","content":"' + to_img + '","datetime":"' + msgdate + '"}';
            webSocket.send(msg3);

            $(".chatBox-content-demo").append("<div class=\"clearfloat\">" +
                "<div class=\"author-name\"><small class=\"chat-date\">"+ msgdate +"</small> </div> " +
                "<div class=\"right\"> <div class=\"chat-message\"><img src=" + images + "></div> " +
                "<div class=\"chat-avatars\"><img src=\"/static/httpchat/img/icon01.png\" alt=\"头像\" /></div> </div> </div>");
            //聊天框默认最底部
            $(document).ready(function () {
                $("#chatBox-content-demo").scrollTop($("#chatBox-content-demo")[0].scrollHeight);
            });fu
        };
        reader.readAsDataURL(pic.files[0]);

    }

</script>
复制代码

Worker.php

复制代码
<?php

namespace app\controller;

use think\worker\Server;

class Worker extends Server
{
    protected $socket = 'websocket://127.0.0.1:8000';

    public $con;

    public function onConnect($connection)
    {
        $connection->send('链接成功!');
    }

    public function onMessage($connection,$data)
    {

        $connection->send("\n".'服务器接收成功!'."\n");
        $this->con = $connection;
        $this->qianwen($data);
    }

    public function onClose($connection)
    {

    }

    public function onError($connection,$code,$msg)
    {
        echo 'error' . $code  . $msg;

    }

    public function onWorkerStart($worker)
    {

    }

    public function qianwen($question='长恨歌')
    {
        header('Content-Type: text/event-stream');
        header('Cache-Control: no-cache');
        header('X-Accel-Buffering: no');
        header('Connection: keep-alive');
        set_time_limit(0);

        $openai_api_key = 'sk-6666666666666666666666666666';//子空间key
        $url = 'https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation';
        $headers = [
            'Authorization: Bearer ' . $openai_api_key,
            'Content-Type: application/json',
            'X-DashScope-SSE: enable'
        ];
        $params =  [
            'model' => 'farui-plus',
            'input' => ['messages' => []],
            'parameters' => [
                'result_format'=>'message',
                'incremental_output'=>true
            ],
        ];
        $params['input']['messages'][] = [
            'role' => 'user',
            'content' => $question
        ];

        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($params));
        curl_setopt($ch, CURLOPT_HEADER, 0);
        curl_setopt($ch, CURLOPT_TIMEOUT, 0);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);//有时候希望返回的内容作为变量储存,而不是直接输出。这个时候就必需设置curl的CURLOPT_RETURNTRANSFER选项为1或true。
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);//设置这个选项为一个非零值(象 “Location: “)的头,服务器会把它当做HTTP头的一部分发送(注意这是递归的,PHP将发送形如 “Location: “的头)。
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);//curl_exec()获取的信息以文件流的形式返回,而不是直接输出
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
        curl_setopt($ch, CURLOPT_WRITEFUNCTION, function ($ch, $data) {
            $res = substr(explode("\n",$data)[3],5);
            $res = json_decode($res,true);
            if($res['output']){
                $this->dayin($res['output']['choices'][0]['message']['content']);
            }else{
                echo $res['message'];
            }
            return strlen($data);
        });
        curl_exec($ch);
        curl_close($ch);

    }

    public function dayin($msg){
        $this->con->send($msg);
    }

}
复制代码

三、运行worker服务

php think worker:server

四、前端链接

五、参考文档

https://www.cnblogs.com/wjs2019/p/18289547
https://gitcode.csdn.net/663055e2ef0d142cbb2cd889.html

六、如何部署到服务器运行

在linux服务器运行 think\worker\Server服务时,若要关闭终端仍保持运行可使用:

1、使用 nohup 命令

nohup php think worker:server >> worker.log 2>&1 &

nohup会忽略终端关闭时发送的 sighup 信号,配合 & 将进程放入后台运行,并将输出重定向到 worker.log 文件

2、使用 setsid 命令

setsid php think worker:server

‌作用‌:setsid 会为进程创建新会话,使其脱离终端控制,无需依赖 & 符号即可后台运行‌。
‌优点‌:相比 nohup,setsid 更彻底地隔离终端信号。

3、结合 workerman 的守护进程模式

php think worker:server -d

作用‌:-d 参数会以守护进程模式运行服务‌。
‌补充‌:建议配合 nohup 或 setsid 使用,确保终端关闭后仍存活:

nohup php think worker:server -d >> worker.log 2>&1 &

七、如何关闭运行的 Tp6 worker 服务

1、通过进程ID终止服务

第一步:查找进程ID

ps -ef | grep "worker:server"

示例输出:

root 12345 1 0 10:00 ? 00:00:10 php think worker:server

终止进程:

kill -9 12345  # 替换为实际进程ID

作用是强制终止指定进程。

检查进程是否存在:

ps -ef | grep "worker:server"

检查端口监听状态:

netstat -tunlp | grep <服务端口>

2、创建停止服务的Sheell脚本

php think worker:server stop

验证是否生效

ps -ef | grep "worker:server"  # 若进程不存在则成功关闭‌:ml-citation{ref="1,6" data="citationList"}

打完收工!

posted @   帅到要去报警  阅读(30)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话
历史上的今天:
2023-02-27 Elementui---中文官网
2019-02-27 mui---获取入口文件对象
点击右上角即可分享
微信分享提示