thinkphp+workerman=在线客服系统
最近公司业务部门需求要上线在线客服系统,需要在线和客服实时沟通。第一时间想到用websocket实时通讯现将开发过程记录如下
1、下载thinkphp5.0,官网有如何下载,这里不再赘述
2、安装workerman依赖,命令为,等待安装完成vendor目录下多出“workerman”的目录
composer require workerman/gateway-worker
workerman依赖安装
看到目录即表示安装成功
3、新建自定义命令,新建文件/application/common/command/Workerman.php文件。这个文件是用来启动websocket服务用的切记不能在这个文件内实现业务逻辑
<?php
namespace app\common\command;
use app\workerman\Events;
use think\console\Command;
use GatewayWorker\BusinessWorker;
use GatewayWorker\Gateway;
use GatewayWorker\Register;
use think\console\Input;
use think\console\input\Argument;
use think\console\input\Option;
use think\console\Output;
use Workerman\Worker;
class Workerman extends Command{
protected function configure()
{
$this->setName("workerman")
->addArgument("action",Argument::OPTIONAL,"action start|stop|restart")
->addArgument('type',Argument::OPTIONAL,"d -d")
->setDescription('workerman chat');
}
protected function execute(Input $input, Output $output)
{
global $argv;
$action = trim($input->getArgument("action"));
$type = trim($input->getArgument('type')) ? '-d' : '';
$argv[0] = 'chat';
$argv[1] = $action;
$argv[2] = $type ? '-d' : '';
$this->start();
}
private function start(){
$this->startGateWay();
$this->startBusinessWorker();
$this->startRegister();
Worker::runAll();
}
private function startBusinessWorker(){
$worker = new BusinessWorker();
$worker->name = "BusinessWorker";
$worker->count = 1;
$worker->registerAddress = "127.0.0.1:1236";
$worker->eventHandler = Events::class;
}
private function startGateWay(){
$context = [
//特别注意这里的证书是用来支持wss协议的一定要写绝对路径,且证书发布的时候要放到服务器对应位置否则会提示证书出错
'ssl' => [
'local_cert' => '/etc/cert/server.pem',
'local_pk' => '/etc/cert/private.key',
'verify_peer' => false
]
];
$gateway = new Gateway("websocket://0.0.0.0:8282",$context);
$gateway->transport = 'ssl';
$gateway->name = "Gateway";
$gateway->count = 1;
$gateway->lanIp = '127.0.0.1';
$gateway->startPort = 2300;
// $gateway->pingInterval = 30;
// $gateway->pingNotResponseLimit = 0;
// $gateway->pingData = '{"type":"@heart@"}';
$gateway->registerAddress = '127.0.0.1:1236';
}
private function startRegister(){
new Register('text://0.0.0.0:1236');
}
}
?>
4、新建业务处理逻辑页面 /application/workerman/Events.php
<?php
namespace app\workerman;
use GatewayWorker\Lib\Gateway;
class Events{
//当服务启动时调用
public static function onWorkerStart($businessWorker){
// echo 'onWorkerStart';
}
// 当客户端连接的时候要求绑定用户uid,有客服端连接的时候服务器马上给客户端反馈消息要求客户端进行用户绑定动作
/**
* @param $client_id
*/
public static function onConnect($client_id)
{
$arr = [
"type"=>'init',
'client_id'=>$client_id
];
Gateway::sendToClient($client_id,json_encode($arr));
}
public static function onWebSocketConnect($client_id, $data)
{
}
// 当收到客户端消息时处理逻辑
public static function onMessage($client_id, $message)
{
// 解析客户端发送的消息
$message_data = json_decode($message,true);
if(!$message_data){
return;
}
$jsonarr = [];
foreach ($message_data as $key=>$v) {
$jsonarr[$key] = $v;
}
// 根据不同的消息类型进行相应的操作
switch (strval($jsonarr['type'])){
case "regcustomer":// 客服注册服务器
Gateway::bindUid($client_id,"10000");
$arrdata = [
"type" => "initsuccess",
"onlinecount" => Gateway::getAllClientCount()
];
Gateway::sendToUid("10000",json_encode($arrdata));
break;
case "reg":// 用户注册服务器
Gateway::bindUid($client_id,$jsonarr["uid"]);
$arrdata = [
"type" => "mymsg",
"face"=> "https://img.XXXXX.com/uploads/20190916/1568628529101634.jpeg",
"name"=> "客服",
"date"=> formateTimeStamp(time()),
"msg" => "您好,客服在线时间:工作日9:00至18:00,其它时间请留言,上线后第一时间给您回复!",
"isregmsg"=>true,
"ctype" => 1,
"id" => 1
];
Gateway::sendToUid($jsonarr["uid"],json_encode($arrdata));
break;
case "customermsg"://客服发送的消息
$arrdata = [
"type" => "mymsg",
"face"=> "https://img.XXXXX.com/uploads/20190916/1568628529101634.jpeg",
"name"=> "客服",
"date"=> formateTimeStamp(time()),
"msg" => $jsonarr["senddata"],
"ctype" => 1,
"id" => 10000
];
if(Gateway::isUidOnline($jsonarr["uid"])){
Gateway::sendToUid($jsonarr["uid"],json_encode($arrdata));
}else{
$arrdata = [
"type" => "offline"
];
Gateway::sendToUid("10000",json_encode($arrdata));
}
break;
case "msg":// 客户端发送的消息
if($jsonarr["contentType"] == "txt"){
$arrdata = [
"type" => "savemsg",
"face"=> "https://img.XXXXX.com/uploads/20190916/1568628529671121.jpeg",
"name"=> "我",
"date"=> formateTimeStamp(time()),
"msg" => $jsonarr["senddata"],
"ctype" => 1,
"id" => $jsonarr["uid"]
];
Gateway::sendToUid($jsonarr["uid"],json_encode($arrdata));
$arrtoservice = [
"type" => "mymsg",
"name"=> $jsonarr["name"],
"hname"=> $jsonarr["hname"],
"tel"=> $jsonarr["tel"],
"date"=> date("m.d H:i",time()),
"msg" => $jsonarr["senddata"],
"ctype" => 1,
"id" => $jsonarr["uid"]
];
if(Gateway::isUidOnline("10000")){
Gateway::sendToUid("10000",json_encode($arrtoservice));
}
}
if(strval($jsonarr["contentType"])=="img"){
$arrdata = [
"type" => "savemsg",
"face"=> "https://img.XXXXX.com/uploads/20190916/1568628529671121.jpeg",
"name"=> "我",
"date"=> formateTimeStamp(time()),
"msg" => $jsonarr["senddata"],
"ctype" => 2,
"id" => $jsonarr["uid"]
];
Gateway::sendToUid($jsonarr["uid"],json_encode($arrdata));
$arrtoservice = [
"type" => "mymsg",
"name"=> $jsonarr["name"],
"hname"=> $jsonarr["hname"],
"tel"=> $jsonarr["tel"],
"date"=> date("m.d H:i",time()),
"msg" => $jsonarr["senddata"],
"ctype" => 2,
"id" => $jsonarr["uid"]
];
if(Gateway::isUidOnline("10000")){
Gateway::sendToUid("10000",json_encode($arrtoservice));
}
}
break;
case "keepConnect":
break;
default:
echo $message_data['type'];
echo strval($message_data['type']) =="bind";
break;
}
}
public static function onClose($client_id)
{
}
}
?>
5、测试服务是否能正常启动,命令行进入根目录执行命令
php think workerman start
如果出现下图则恭喜你已经成功启动服务了
测试启动服务
常用命令如下:
// 启动服务
php think workerman start
// 重启服务
php think workerman restart
// 停止服务
php think workerman stop
// 以进程守护的方式启动
php think workerman start /d
最后一步 部署
首先要有一台服务器(没服务器就不要玩这个了)因为大部分的虚拟空间都不支持指定运行目录,而thinkphp需要指定public为运行目录,这里我以ubuntu + Apache为运行环境
1、首先配置好服务器php运行环境,我的php版本配置为7.0.。如何配置环境就不做叙述了不是这里的重点
2、设置好虚拟站点目录,设置好public为运行目录
3、上传开发好的文件到虚拟站点根目录
4、配置站点https访问协议
5、上传证书server.pem和private.key文件到服务器路径/etc/cert/文件夹下面,注意 这里的文件名字一定要和Workerman.php内证书的名字一致
6、命令行进入站点根目录运行启动服务器代码
php think workerman start /d
看到和本地一样启动成功的界面显示即表示成功部署好了服务端
用web端测试下服务端
//这里一定要用域名去连接服务器,因为要做证书验证
var wsserver = "wss://socket.XXXX.com:8282/";
// 连接聊天服务器
function connectws(wsserver) {
try{
ws = new WebSocket(wsserver);
}catch(e){
alert(e);
}
ws.onmessage = function(event) {
var data = event.data;
// 处理数据
var servermsg = JSON.parse(data);
// console.log(servermsg);
switch(servermsg.type){
case "init":
var msg = '{"type":"regcustomer"}';
ws.send(msg);
break;
case "initsuccess":
console.log("客服注册成功,在线人数"+servermsg.onlinecount,301);
break;
case "mymsg":
// 收到消息时处理逻辑
break;
case "offline":
alert("该用户已经下线,消息将以留言的形式提供给对方");
break;
default:
console.log(servermsg,307);
break;
}
};
ws.onopen = function () {
isonline = true;
};
ws.onerror = function (e) {
isonline = false;
alert("聊天服务器断开连接");
};
}
整合好后后台客服就愉快的上线了
前台用web 、app等都可以进行整合 这类的IM很多可以结合自己的项目和逻辑进行二次开发;
完!
注意:(重要的事情说三遍)
服务器的证书文件一定要放而且必须和workerman.php内的文件名一样;
服务器的证书文件一定要放而且必须和workerman.php内的文件名一样;
服务器的证书文件一定要放而且必须和workerman.php内的文件名一样;