1
0
mirror of https://gitee.com/zhc02/timely_service.git synced 2025-06-24 12:05:30 +08:00

初始版本

This commit is contained in:
root 2019-10-30 23:42:08 +08:00
parent 7e13bd505c
commit 1b76d8418f
15 changed files with 1141 additions and 0 deletions

1
application/.htaccess Normal file
View File

@ -0,0 +1 @@
deny from all

14
application/command.php Normal file
View File

@ -0,0 +1,14 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
return [
'app\swoole\command\Chat',
];

14
application/common.php Normal file
View File

@ -0,0 +1,14 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: 流年 <liu21st@gmail.com>
// +----------------------------------------------------------------------
// 应用公共文件

View File

@ -0,0 +1,24 @@
<?php
/**
* Created by PhpStorm.
* User: zhc
* Date: 2019/10/29
* Time: 18:21
*/
namespace app\index\controller;
use think\App;
use think\Controller;
class Base extends Controller
{
public function __construct(App $app = null)
{
parent::__construct($app);
if( !session('kefu_name')){
return $this->redirect('login/login');
}
}
}

View File

@ -0,0 +1,54 @@
<?php
namespace app\index\controller;
use think\Controller;
use think\Db;
use think\facade\Config;
class Index extends Controller
{
public function user()
{
$kefu_code = input('param.kefu_code');
$kefu_info = Db::name('kefu_info')->where('kefu_code', $kefu_code)->find();
if(!$kefu_info){
return '客服不存在';
}
$visitor_id = uniqid($kefu_info['kefu_id']);
$visitor_name = '游客'.$visitor_id;
$visitor_avatar ='/static/common/images/visitor.jpg';
$config= Config::pull('swoole_server');
$port=$config['port'];
$this->assign('port',$port);
$this->assign('code',$kefu_code);
$this->assign('uid',$visitor_id);
$this->assign('name',$visitor_name);
$this->assign('avatar',$visitor_avatar);
return $this->fetch();
}
//获取访客聊天记录
public function getUserChatLog()
{
$uid = input('param.uid');
$kefu_code = input('param.kefu_code');
if (!$uid || !$kefu_code ) {
return '参数错误';
}
$sql = "SELECT * FROM chat_log WHERE ( from_id = '{$uid}' and to_id ='{$kefu_code}') or (from_id = '{$kefu_code}' and to_id ='{$uid}') order by create_time";
$list = Db::query($sql);
if (empty($list)) return json($list);
foreach ($list as $key => $item) {
if (strpos($item['from_id'], 'KF_') === false) {
$list[$key]['log'] = 'visitor';
} else {
$list[$key]['log'] = 'kefu';
}
}
return json(['code'=>200,'data'=>$list,'msg'=>'操作成功']);
}
}

View File

@ -0,0 +1,55 @@
<?php
/**
* Created by PhpStorm.
* User: zhc
* Date: 2019/10/29
* Time: 18:17
*/
namespace app\index\controller;
use think\Controller;
use think\Db;
use think\facade\Config;
class Kefu extends Base
{
public function index(){
$config= Config::pull('swoole_server');
$port=$config['port'];
$this->assign('port',$port);
$this->assign('url',request()->domain().'/index/index/user?kefu_code='.session('kefu_code'));
$this->assign('kefu_name',session('kefu_name'));
$this->assign('kefu_code',session('kefu_code'));
return $this->fetch();
}
public function getQueue(){
$kefu_code = input('param.kefu_code');
$reception_status = input('param.status',1);
$queue = Db::name('visitor_queue')->where('kefu_code',$kefu_code)->where('reception_status',$reception_status)->select();
return json(['code'=>200,'data'=>$queue,'msg'=>'操作成功']);
}
//获取客服聊天记录
public function getUserChatLog()
{
$uid = input('param.uid');
$kefu_code = input('param.kefu_code');
if (!$uid || !$kefu_code ) {
return '参数错误';
}
$sql = "SELECT * FROM chat_log WHERE ( from_id = '{$kefu_code}' and to_id ='{$uid}') or (from_id = '{$uid}' and to_id ='{$kefu_code}') order by create_time";
$list = Db::query($sql);
if (empty($list)) return json(['code'=>200,'data'=>$list,'msg'=>'操作成功']);
foreach ($list as $key => $item) {
if (strpos($item['from_id'], 'KF_') === false) {
$list[$key]['log'] = 'visitor';
} else {
$list[$key]['log'] = 'kefu';
}
}
return json(['code'=>200,'data'=>$list,'msg'=>'操作成功']);
}
}

View File

@ -0,0 +1,73 @@
<?php
/**
* Created by PhpStorm.
* User: zhc
* Date: 2019/10/29
* Time: 16:38
*/
namespace app\index\controller;
use think\Controller;
use think\Db;
use think\facade\Cache;
class Login extends Controller
{
public function Login()
{
return $this->fetch();
}
public function Logining()
{
$name = input('post.name');
$password = input('post.password');
if (empty($name)) {
return $this->error('请输入用户名');
}
if (empty($password)) {
return $this->error('请输入密码');
}
$kefu_info = Db::name('kefu_info')->where('kefu_name', $name)->find();
if ($kefu_info) {
if (md5(trim($password)) != $kefu_info['kefu_password']) {
return $this->error('密码错误');
}
session('kefu_name', $kefu_info['kefu_name']);
session('kefu_code', $kefu_info['kefu_code']);
} else {
//同一个ip 限制注册3个账号
$num = Cache::get(request()->ip());
if ($num > 3) {
return $this->error('同一ip限制注册三个账号');
}
//添加客服
$kefu_data = [
'kefu_code' => uniqid('kefu'),
'kefu_name' => trim($name),
'kefu_avatar' => '/static/common/images/kefu.jpg',
'kefu_password' => md5(trim($password)),
'kefu_status' => 1,
'online_status' => 0,
'create_time' => date('Y-m-d H:i:s'),
'update_time' => date('Y-m-d H:i:s'),
];
Db::name('kefu_info')->insertGetId($kefu_data);
Db::commit();
Cache::inc(request()->ip());
session('kefu_name', $kefu_data['kefu_name']);
session('kefu_code', $kefu_data['kefu_code']);
}
return $this->redirect('kefu/index');
}
public function logout(){
session('kefu_name',null);
session('kefu_code', null);
return $this->redirect('login/login');
}
}

View File

@ -0,0 +1,68 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>游客聊天页面</title>
<link rel="stylesheet" href="/static/common/dist/css/soho.min.css">
</head>
<body>
<!-- layout -->
<div class="layout">
<!-- content -->
<div class="content">
<!-- chat -->
<div class="chat">
<div class="chat-header">
<div class="chat-header-user">
<figure class="avatar avatar-lg">
<img src="/static/common/images/kefu.jpg" class="rounded-circle">
</figure>
<div id="kefu_name">
<h5>请等待连接客服....</h5>
</div>
</div>
</div>
<div class="chat-body">
<div class="messages">
<!-- .no-message -->
</div>
</div>
<div class="chat-footer">
<form action="">
<input type="text" class="form-control" placeholder="请输入内容" aria-label="Recipient's username" aria-describedby="button-addon2">
<div class="form-buttons">
<button class="btn btn-primary btn-floating" type="submit">
<i class="fa fa-send"></i>
</button>
</div>
</form>
</div>
</div>
</div>
</div>
<script>
var uid= '{$uid}';
var uname= '{$name}';
var uavatar= '{$avatar}';
var code= '{$code}';
var port= '{$port}';
</script>
<script src="/static/demo/js/jquery.min.js"></script>
<script src="/static/common/vendor/popper.min.js"></script>
<script src="/static/common/vendor/bootstrap/bootstrap.min.js"></script>
<script src="/static/common/vendor/jquery.nicescroll.min.js"></script>
<script src="/static/common/dist/js/soho.min.js"></script>
<script src="/static/common/dist/js/user.js"></script>
</body>
</html>

View File

@ -0,0 +1,181 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>客服工作台</title>
<link rel="stylesheet" href="/static/common/dist/css/soho.min.css">
</head>
<style>
.hidden {
display: none !important;
}
.show {
display: block !important;
}
</style>
<body>
<!-- page loading -->
<div class="page-loading"></div>
<!-- ./ page loading -->
<!-- layout -->
<div class="layout">
<!-- navigation -->
<nav class="navigation">
<div class="nav-group">
<ul>
<li>
<a class="logo" href="#">
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="33.004px" height="33.003px" viewBox="0 0 33.004 33.003" style="enable-background:new 0 0 33.004 33.003;"
xml:space="preserve">
<g>
<path d="M4.393,4.788c-5.857,5.857-5.858,15.354,0,21.213c4.875,4.875,12.271,5.688,17.994,2.447l10.617,4.161l-4.857-9.998
c3.133-5.697,2.289-12.996-2.539-17.824C19.748-1.072,10.25-1.07,4.393,4.788z M25.317,22.149l0.261,0.512l1.092,2.142l0.006,0.01
l1.717,3.536l-3.748-1.47l-0.037-0.015l-2.352-0.883l-0.582-0.219c-4.773,3.076-11.221,2.526-15.394-1.646
C1.469,19.305,1.469,11.481,6.277,6.672c4.81-4.809,12.634-4.809,17.443,0.001C27.919,10.872,28.451,17.368,25.317,22.149z"/>
<g>
<circle cx="9.835" cy="16.043" r="1.833"/>
<circle cx="15.502" cy="16.043" r="1.833"/>
<circle cx="21.168" cy="16.043" r="1.833"/>
</g>
</g>
<g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g>
</svg>
</a>
</li>
<li>
<a data-navigation-target="chats" class="queue active" href="#" title="当前会话">
<i class="ti-comment-alt"></i>
</a>
</li>
<li id="aaa">
<a data-navigation-target="friends" href="#" class="notifiy_badge" title="历史会话">
<i class="ti-user"></i>
</a>
</li>
<li data-navigation-target="favorites" class="brackets">
</li>
<li>
<a href="{:url('login/logout')}">
<i class="ti-power-off" title="退出"></i>
</a>
</li>
</ul>
</div>
</nav>
<!-- ./ navigation -->
<!-- content -->
<div class="content">
<!-- sidebar group -->
<div class="sidebar-group">
<!-- 当前会话 -->
<div id="chats" class="sidebar active">
<header class="facing-title">
<span>客服名:{$kefu_name}</span>
<p class="status">离线</p>
</header>
<form >
<span>游客测试地址:{$url}</span>
</form>
<div class="sidebar-body">
<ul class="list-group list-group-flush" id="facing">
</ul>
</div>
</div>
<!-- ./ 当前会话-->
<!-- 历史会话 -->
<div id="friends" class="sidebar">
<header>
<span>历史会话</span>
</header>
<div class="sidebar-body">
<ul class="list-group list-group-flush" id ='history'>
</ul>
</div>
</div>
<!-- ./ 历史会话 -->
</div>
<!-- ./ sidebar group -->
<!-- chat -->
<div class="chat">
<div class="chat-header">
<div class="chat-header-user">
<figure class="avatar avatar-lg" id="visitor_avatar">
<!--<img src="/static/common/dist/media/img/man_avatar3.jpg" class="rounded-circle">-->
</figure>
<div id ='visitor_info'>
<h5></h5>
<small class="text-muted">
<i></i>
</small>
</div>
</div>
<div class="chat-header-action">
<ul class="list-inline">
<li class="list-inline-item">
<a href="#" class="btn btn-secondary" data-toggle="dropdown">
<i class="ti-more"></i>
</a>
<div class="dropdown-menu dropdown-menu-right">
<div class="dropdown-divider"></div>
<a href="#" class="dropdown-item">关闭</a>
</div>
</li>
</ul>
</div>
</div>
<!-- .消息 -->
<div class="chat-body">
<div class="messages" >
</div>
<!--<div class="messages hidden" >-->
<!--</div>-->
</div>
<!-- .消息end -->
<div class="chat-footer">
<form action="">
<input type="text" class="form-control" placeholder="请输入内容"
aria-label="Recipient's username" aria-describedby="button-addon2">
<div class="form-buttons">
<button class="btn btn-primary btn-floating" type="submit">
<i class="fa fa-send"></i>
</button>
</div>
</form>
</div>
</div>
<!-- ./ chat -->
</div>
<!-- ./ content -->
</div>
<script>
var code ='{$kefu_code}';
var port= '{$port}';
</script>
<script src="/static/demo/js/jquery.min.js"></script>
<script src="/static/common/vendor/popper.min.js"></script>
<script src="/static/common/vendor/bootstrap/bootstrap.min.js"></script>
<script src="/static/common/vendor/jquery.nicescroll.min.js"></script>
<script src="/static/common/dist/js/soho.min.js"></script>
<script src="/static/common/dist/js/examples.js"></script>
</body>
</html>

View File

@ -0,0 +1,34 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Timely客服登陆</title>
<link rel="stylesheet" href="/static/common/dist/css/soho.min.css">
</head>
<body class="form-membership">
<div class="form-wrapper">
<h5>Timely客服登陆</h5>
<form action="{:url('login/Logining')}" method="post">
<div class="form-group input-group-lg">
<input type="text" name="name" class="form-control" placeholder="输入一个名称,名称已有会要求密码" required autofocus>
</div>
<div class="form-group input-group-lg">
<input type="password" name="password" class="form-control" placeholder="请自行保存密码,下次登陆" required>
</div>
<div class="form-group d-flex justify-content-between">
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" checked="" id="customCheck1">
<label class="custom-control-label" for="customCheck1">记住我</label>
</div>
</div>
<button class="btn btn-primary btn-lg btn-block">进入</button>
</form>
</div>
<script src="https://www.jq22.com/jquery/jquery-1.10.2.js"></script>
<script src="dist/js/soho.min.js"></script>
</body>
</html>

14
application/provider.php Normal file
View File

@ -0,0 +1,14 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
// 应用容器绑定定义
return [
];

View File

@ -0,0 +1,261 @@
<?php
namespace app\swoole\command;
use Swoole\Process;
use think\console\Command;
use think\console\Input;
use think\console\input\Argument;
use think\console\input\Option;
use think\console\Output;
use think\facade\Config;
use think\facade\Env;
use think\swoole\Http as HttpServer;
use think\Container;
use think\swoole\Server as ThinkServer;
/**
* Swoole 命令行支持操作start|stop|restart|reload
* 支持应用配置目录下的swoole_server.php文件进行参数配置
*/
class Chat extends Command
{
protected $config = [];
public function configure()
{
$this->setName('chat')
->addArgument('action', Argument::OPTIONAL, "start|stop|restart|reload", 'start')
->addOption('host', 'H', Option::VALUE_OPTIONAL, 'the host of swoole server.', null)
->addOption('port', 'p', Option::VALUE_OPTIONAL, 'the port of swoole server.', null)
->addOption('daemon', 'd', Option::VALUE_NONE, 'Run the swoole server in daemon mode.')
->setDescription('chat Swoole Server for ThinkPHP');
}
public function execute(Input $input, Output $output)
{
$action = $input->getArgument('action');
if (!in_array($action, ['start', 'stop', 'reload', 'restart'])) {
$output->writeln("<error>Invalid argument action:{$action}, Expected start|stop|restart|reload .</error>");
return false;
}
//timely TIMELY
$brand = <<<EOL
ttttttttttttttttt
ttttttttttttttttt
ttttttttttttttttt
iiiii llll yyyy yyyy
ttttt iiiii mmmmmm mmmmmm eeee llll yyy yyy
ttttt mmmmmmmmm mmmmmmmmm eeee eeee llll yy yy
ttttt iiiii mmmm mmmm mmmm eeee eeee llll yyyyyy
ttttt iiiii mmmm mmmm mmmm eeeeeeeeeeeeeeeee llll yyyy
ttttt iiiii mmmm mmmm mmmm ee llll yyy
ttttt iiiii mmmm mmmm mmmm ee ee llll yyy
ttttt iiiii mmmm mmmm mmmm eeeeeeeeeeeeeeee llll yyy
ttttt iiiii mmmm mmmm mmmm eeeeeeeeeeeeeee llll yy
EOL;
$output->writeln($brand . PHP_EOL);
$this->init();
$this->$action();
}
protected function init()
{
$this->config = Config::pull('swoole_server');
if (empty($this->config['pid_file'])) {
$this->config['pid_file'] = Env::get('runtime_path') . 'swoole_server.pid';
}
// 避免pid混乱
$this->config['pid_file'] .= '_' . $this->getPort();
}
/**
* 启动server
* @access protected
* @return void
*/
protected function start()
{
$pid = $this->getMasterPid();
if ($this->isRunning($pid)) {
$this->output->writeln('<error>swoole server process is already running.</error>');
return false;
}
$this->output->writeln('Starting swoole server...');
if (!empty($this->config['swoole_class'])) {
$class = $this->config['swoole_class'];
if (class_exists($class)) {
$swoole = new $class;
if (!$swoole instanceof ThinkServer) {
$this->output->writeln("<error>Swoole Server Class Must extends \\think\\swoole\\Server</error>");
return false;
}
} else {
$this->output->writeln("<error>Swoole Server Class Not Exists : {$class}</error>");
return false;
}
} else {
$host = $this->getHost();
$port = $this->getPort();
$type = !empty($this->config['type']) ? $this->config['type'] : 'socket';
$mode = !empty($this->config['mode']) ? $this->config['mode'] : SWOOLE_PROCESS;
$sockType = !empty($this->config['sock_type']) ? $this->config['sock_type'] : SWOOLE_SOCK_TCP;
switch ($type) {
case 'socket':
$swooleClass = 'Swoole\Websocket\Server';
break;
case 'http':
$swooleClass = 'Swoole\Http\Server';
break;
default:
$swooleClass = 'Swoole\Server';
}
$swoole = new $swooleClass($host, $port, $mode, $sockType);
// 开启守护进程模式
if ($this->input->hasOption('daemon')) {
$this->config['daemonize'] = true;
}
foreach ($this->config as $name => $val) {
if (0 === strpos($name, 'on')) {
$swoole->on(substr($name, 2), $val);
unset($this->config[$name]);
}
}
// 设置服务器参数
$swoole->set($this->config);
$this->output->writeln("Swoole {$type} server started: <{$host}:{$port}>" . PHP_EOL);
$this->output->writeln('You can exit with <info>`CTRL-C`</info>');
// 启动服务
$swoole->start();
}
}
/**
* 柔性重启server
* @access protected
* @return void
*/
protected function reload()
{
// 柔性重启使用管理PID
$pid = $this->getMasterPid();
if (!$this->isRunning($pid)) {
$this->output->writeln('<error>no swoole server process running.</error>');
return false;
}
$this->output->writeln('Reloading swoole server...');
Process::kill($pid, SIGUSR1);
$this->output->writeln('> success');
}
/**
* 停止server
* @access protected
* @return void
*/
protected function stop()
{
$pid = $this->getMasterPid();
if (!$this->isRunning($pid)) {
$this->output->writeln('<error>no swoole server process running.</error>');
return false;
}
$this->output->writeln('Stopping swoole server...');
Process::kill($pid, SIGTERM);
$this->removePid();
$this->output->writeln('> success');
}
protected function getHost()
{
if ($this->input->hasOption('host')) {
$host = $this->input->getOption('host');
} else {
$host = !empty($this->config['host']) ? $this->config['host'] : '0.0.0.0';
}
return $host;
}
/**
* 删除PID文件
* @access protected
* @return void
*/
protected function removePid()
{
$masterPid = $this->config['pid_file'];
if (is_file($masterPid)) {
unlink($masterPid);
}
}
protected function getPort()
{
if ($this->input->hasOption('port')) {
$port = $this->input->getOption('port');
} else {
$port = !empty($this->config['port']) ? $this->config['port'] : 9501;
}
return $port;
}
/**
* 获取主进程PID
* @access protected
* @return int
*/
protected function getMasterPid()
{
$pidFile = $this->config['pid_file'];
if (is_file($pidFile)) {
$masterPid = (int)file_get_contents($pidFile);
} else {
$masterPid = 0;
}
return $masterPid;
}
/**
* 判断PID是否在运行
* @access protected
* @param int $pid
* @return bool
*/
protected function isRunning($pid)
{
if (empty($pid)) {
return false;
}
return Process::kill($pid, 0);
}
}

View File

@ -0,0 +1,248 @@
<?php
/**
* 信息操作类
* User: zhc
* Date: 2019/10/24
* Time: 10:16
*/
namespace app\swoole\service;
use Logic\VisitorService;
use think\Db;
use think\Exception;
use think\facade\Log;
use Logic\KefuLogic;
use exception\LogicException;
use Logic\ServiceLogic;
use exception\BaseException;
use Logic\QueueLogic;
use Logic\Visitor;
use Logic\ChatLogLogic;
class Event
{
//统一在线
public static $online = [];
// 在线客服
public static $kefu = [];
//在线游客
public static $visitor = [];
//设置客服
public static function setKefu($fd, $uid)
{
if(isset( self::$kefu[$uid])){
self::$kefu[$uid]['fd'] = $fd;
}else{
self::$kefu[$uid]['fd'] = $fd;
self::$kefu[$uid]['visitor_fds'] = [];
}
return self::$kefu;
}
//设置游客
public static function setVisitor($fd, $uid)
{
self::$visitor[$fd]['uid'] = $uid;
self::$visitor[$fd]['bind_kefu_fd'] = '';
self::$visitor[$fd]['bind_kefu_code'] = '';
return self::$visitor;
}
/**
* 客服登录
* @param $fd 客户端标识
* @param $data 请求数据
*/
public static function kefuConnection($fd, $data, $server)
{
try {
#1更新客服状态
$kefu_code = ltrim($data['uid'], 'KF_');
$info = KefuLogic::setKefuOnlineStatus($kefu_code, $fd);
//设置客服信息
self::$online[$fd] = $data['uid'];
self::setKefu($fd, $data['uid']);
return self::reposon($fd, 200, '客服上线成功', [], 'kefu_online');
} catch (BaseException $e) {
throw new BaseException('客服上线错误', 401);
}
}
/**
* 游客登录
* @param $fd 客户端标识
* @param $data 请求数据
*/
public static function visitorConnection($fd, $data, $server)
{
try {
#1.游客队列中添加数据
$queue = [
'visitor_id' => $data['visitor_id'],
'client_id' => $fd,
'visitor_name' => $data['visitor_name'],
'visitor_avatar' => $data['visitor_avatar'],
'visitor_ip' => '127.0.0.1',
'create_time' => date('Y-m-d H:i:s'),
'reception_status' => 0,//等待客服接待状态
];
QueueLogic::updateQueue($queue);
#2.游客信息更新
$data = [
'visitor_id' => $data['visitor_id'],
'client_id' => $fd,
'visitor_name' => $data['visitor_name'],
'visitor_avatar' => $data['visitor_avatar'],
'visitor_ip' => '127.0.0.1',
'create_time' => date('Y-m-d H:i:s'),
'online_status' => 1
];
Visitor::updateCustomer($data);
//设置游客信息
self::$online[$fd] = $data['visitor_id'];
self::setVisitor($fd, $data['visitor_id']);
return self::reposon($fd, 200, '上线成功', [], 'online');
} catch (BaseException $e) {
throw new BaseException('上线错误', 401);
}
}
/**
* 游客连接客服
* @param $fd 客户端标识
* @param $data 请求数据
*/
public static function visitorToKefu($fd, $data, $server)
{
#1.分配客服
$visitor = [
'visitor_id' => $data['uid'],
'visitor_name' => $data['name'],
'visitor_avatar' => $data['avatar'],
'visitor_ip' => '127.0.0.1',
'client_id' => $fd,
'kefu_code'=>$data['kefu_code'],
];
try {
Db::startTrans();
$kefu_info = KefuLogic::distributionKefu($visitor);
Log::record('分配客服数据:' . json_encode($kefu_info));
if ($kefu_info['code'] == 200) {
#1.记录服务日志
$logId = VisitorService::addServiceLog([
'visitor_id' => $visitor['visitor_id'],
'client_id' => $fd,
'visitor_name' => $visitor['visitor_name'],
'visitor_avatar' => $visitor['visitor_avatar'],
'visitor_ip' => $visitor['visitor_ip'],
'kefu_id' => $kefu_info['data']['kefu_id'],
'kefu_code' => ltrim($kefu_info['data']['kefu_code'], 'KF_'),
'start_date' => date('Y-m-d H:i:s'),
]);
try {
if ($server->exist((int)$kefu_info['data']['kefu_client_id']) == false) {
Db::rollback();
return self::reposon($fd, 201, '客服不存在或者客服不在线', [], 'visitorToKefu');
}
$kefu_info['data']['log_id'] = $logId;
// 更新队列表
$update['reception_status'] = 1;//更改连接状态
$update['kefu_code'] = ltrim($kefu_info['data']['kefu_code'], 'KF_');
$update['kefu_client_id'] = $kefu_info['data']['kefu_client_id'];
QueueLogic::updateQueueByCusomerID($visitor['visitor_id'], $update);
#3.绑定客服和游客 bengan
self::$visitor[$fd]['bind_kefu_fd'] = $kefu_info['data']['kefu_client_id'];
self::$visitor[$fd]['bind_kefu_code'] =$kefu_info['data']['kefu_code'];
self::$kefu[$kefu_info['data']['kefu_code']]['visitor_fds'][$visitor['visitor_id']]= $fd;
#end
Db::commit();
return self::reposon($fd, 200, $kefu_info['msg'], $kefu_info['data'], 'visitorToKefu');
} catch (Exception $e) {
Db::rollback();
Log::info('分配客服数据错误信息:' . $e->getMessage());
//取消客服在线状态
KefuLogic::setKefuOnlineStatus(ltrim($kefu_info['data']['kefu_code'], 'KF_'), '', 0);
return self::reposon($fd, 401, '请重新尝试分配客服1', [], 'visitorToKefu');
}
}else if($kefu_info['code'] == 201){
return self::reposon($fd, 201, '客服不存在或者客服不在线', [], 'visitorToKefu');
}
Db::commit();
} catch (BaseException $e) {
Db::rollback();
Log::record('分配客服数据错误信息:' . $e->getMessage());
return self::reposon($fd, 402, '请重新尝试分配客服2', [], 'visitorToKefu');
}
unset($customer, $kefu_info);
}
/**
* 聊天
* @param $fd 客户端标识
* @param $data 请求数据
*/
public static function message($fd, $data, $server)
{
Log::record('聊天信息[' . json_encode($data) . ']');
Log::record('聊天信息1[' . json_encode(self::$online[$fd]) . ']');
Log::record('聊天信息2[' . json_encode(self::$visitor[$fd]) . ']');
Log::record('聊天信息3[' . json_encode(self::$kefu) . ']');
$uid = self::$online[$fd];
try {
//消息入库
$chat_log_id = ChatLogLogic::addChatLog($data);
$message = [
'name' => $data['from_name'],
'avatar' => $data['from_avatar'],
'id' => $data['from_id'],
'time' => date('Y-m-d H:i:s'),
'message' => htmlspecialchars($data['message']),
'log_id' => $chat_log_id
];
if (strstr($uid, "KF_") !== false) {//客服发信息给游客
} else { //游客发送给客服
//获取 客服的fd
if(!isset(self::$visitor[$fd]['bind_kefu_code']) || ($server->exist((int)self::$kefu[self::$visitor[$fd]['bind_kefu_code']]['fd']) == false)){
//更新聊天日志状态
ChatLogLogic::updateSendStatus($chat_log_id, 2);
return self::reposon($fd, 201, '客服离线', $message, 'message');
} else {
$kefu_fd = self::$kefu[self::$visitor[$fd]['bind_kefu_code']]['fd'];
$resut = self::reposon((int)$kefu_fd, 200, '来新信息了', $message, 'chatMessage');
$server->push($resut['fd'], $resut['data']);
}
}
} catch (BaseException $e) {
return self::reposon($fd, 400, '消息发送失败', $data['message'], 'message');
}
return self::reposon($fd, 200, '信息发送成功', $message, 'message');
}
public static function disconnect($fd, $server){
return true;
}
public static function reposon($fd, $code = 200, $msg = "操作成功", $data = '', $cmd = '')
{
$reposon['fd'] = $fd;
$reposon['data'] = json_encode([
'code' => $code,
'msg' => $msg,
'data' => $data,
'cmd' => $cmd,
], JSON_UNESCAPED_UNICODE);
return $reposon;
}
}

View File

@ -0,0 +1,72 @@
<?php
/**
* Created by PhpStorm.
* User: zhc
* Date: 2019/10/23
* Time: 17:20
*/
namespace app\swoole\service;
use think\swoole\Server;
use think\facade\Env;
use exception\BaseException;
use think\facade\Log;
class Service
{
// 事件回调定义
public function onOpen($server, $request)
{
echo "server: handshake success with fd{$request->fd}\n";
}
public function onMessage($server, $frame)
{
echo "receive from {$frame->fd}:{$frame->data},opcode:{$frame->opcode},fin:{$frame->finish}\n";
try {
Log::record('WebSocket请求开始请求信息[' . json_encode($frame) . ']');
$data = json_decode($frame->data, true);
$cmd = $data['cmd'];
$messge = $data['data'];
$resut = Event::$cmd($frame->fd, $messge,$server);
$server->push($resut['fd'], $resut['data']);
} catch (BaseException $e) {
Log::record('WebSocket请求异常,异常信息' . $e->getMessage());
Log::record('WebSocket请求异常,异常信息' . $e->getFile().$e->getLine());
$res = ['code' => $e->getCode(), 'msg' => $e->getMessage(), 'data' => '', 'cmd' => ''];
} catch (\Error $er) {
Log::record('WebSocket请求异常,异常信息' . $er->getMessage());
Log::record('WebSocket请求异常,异常信息' . $er->getFile().$er->getLine());
$res = ['code' => $er->getCode(), 'msg' => $er->getMessage(), 'data' => '', 'cmd' => ''];
} catch (\Exception $era) {
Log::record('WebSocket请求异常,异常信息' . $era->getMessage());
Log::record('WebSocket请求异常,异常信息' . $era->getFile().$era->getLine());
$res = ['code' => $era->getCode(), 'msg' => $era->getMessage(), 'data' => '', 'cmd' => ''];
} catch (\ErrorException $ere) {
Log::record('WebSocket请求异常,异常信息' . $ere->getMessage());
Log::record('WebSocket请求异常,异常信息' . $ere->getFile().$ere->getLine());
$res = ['code' => $ere->getCode(), 'msg' => $ere->getMessage(), 'data' => '', 'cmd' => ''];
}
if(isset($res)){
$server->push($frame->fd, json_encode($res));
}
}
public function onRequest($request, $response)
{
$response->end("<h1>Hello Swoole. #" . rand(1000, 9999) . "</h1>");
}
public function onClose($server, $fd)
{
Log::record('WebSocket关闭请求开始请求信息[' . json_encode($server) . ']');
$resut = Event::disconnect($fd,$server);
echo "client {$fd} closed\n";
}
}

28
application/tags.php Normal file
View File

@ -0,0 +1,28 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
// 应用行为扩展定义文件
return [
// 应用初始化
'app_init' => [],
// 应用开始
'app_begin' => [],
// 模块初始化
'module_init' => [],
// 操作开始执行
'action_begin' => [],
// 视图内容过滤
'view_filter' => [],
// 日志写入
'log_write' => [],
// 应用结束
'app_end' => [],
];