mirror of
https://gitee.com/zhc02/timely_service.git
synced 2025-06-24 04:01:23 +08:00
初始版本
This commit is contained in:
parent
7e13bd505c
commit
1b76d8418f
1
application/.htaccess
Normal file
1
application/.htaccess
Normal file
@ -0,0 +1 @@
|
||||
deny from all
|
14
application/command.php
Normal file
14
application/command.php
Normal 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
14
application/common.php
Normal 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>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
// 应用公共文件
|
||||
|
||||
|
24
application/index/controller/Base.php
Normal file
24
application/index/controller/Base.php
Normal 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');
|
||||
}
|
||||
}
|
||||
}
|
54
application/index/controller/Index.php
Normal file
54
application/index/controller/Index.php
Normal 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'=>'操作成功']);
|
||||
|
||||
}
|
||||
}
|
55
application/index/controller/Kefu.php
Normal file
55
application/index/controller/Kefu.php
Normal 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'=>'操作成功']);
|
||||
|
||||
}
|
||||
}
|
73
application/index/controller/Login.php
Normal file
73
application/index/controller/Login.php
Normal 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');
|
||||
}
|
||||
}
|
68
application/index/view/index/user.html
Normal file
68
application/index/view/index/user.html
Normal 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>
|
181
application/index/view/kefu/index.html
Normal file
181
application/index/view/kefu/index.html
Normal 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>
|
34
application/index/view/login/login.html
Normal file
34
application/index/view/login/login.html
Normal 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
14
application/provider.php
Normal 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 [
|
||||
];
|
261
application/swoole/command/Chat.php
Normal file
261
application/swoole/command/Chat.php
Normal 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);
|
||||
}
|
||||
}
|
248
application/swoole/service/Event.php
Normal file
248
application/swoole/service/Event.php
Normal 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;
|
||||
}
|
||||
}
|
72
application/swoole/service/Service.php
Normal file
72
application/swoole/service/Service.php
Normal 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
28
application/tags.php
Normal 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' => [],
|
||||
];
|
Loading…
x
Reference in New Issue
Block a user