- Contract.php: 返回合约账户余额(balance_contract) - My.php: 地址管理增加BTC/ETH - AppContract.php: 一键平仓(closeall) - AppProxy.php: 代理专属注册链接 + 分级权限(L1/L2) - site.php: 手续费减半(0.018→0.009) - agent_permission_setup.sql: 代理权限SQL - crypto_news_crawler.py: 新闻自动采集脚本
410 lines
16 KiB
PHP
Executable File
410 lines
16 KiB
PHP
Executable File
<?php
|
|
/**
|
|
* This file is part of workerman.
|
|
*
|
|
* Licensed under The MIT License
|
|
* For full copyright and license information, please see the MIT-LICENSE.txt
|
|
* Redistributions of files must retain the above copyright notice.
|
|
*
|
|
* @author walkor<walkor@workerman.net>
|
|
* @copyright walkor<walkor@workerman.net>
|
|
* @link http://www.workerman.net/
|
|
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
|
*/
|
|
|
|
/**
|
|
* 用于检测业务代码死循环或者长时间阻塞等问题
|
|
* 如果发现业务卡死,可以将下面declare打开(去掉//注释),并执行php start.php reload
|
|
* 然后观察一段时间workerman.log看是否有process_timeout异常
|
|
*/
|
|
|
|
namespace addons\kefu\library\GatewayWorker\Applications\KeFu;
|
|
|
|
//declare(ticks=1);
|
|
|
|
use addons\kefu\library\Common;
|
|
use GatewayWorker\Lib\Gateway;
|
|
use Workerman\Lib\Timer;
|
|
use think\Db;
|
|
|
|
/**
|
|
* 主逻辑
|
|
* 主要是处理 onConnect onMessage onClose 三个方法
|
|
* onConnect 和 onClose 如果不需要可以不用实现并删除
|
|
*/
|
|
class Events
|
|
{
|
|
/**
|
|
* WebSocket 链接成功
|
|
*
|
|
* @param int $client_id data
|
|
* @param $[data] [websocket握手时的http头数据,包含get、server等变量]
|
|
*/
|
|
public static function onWebSocketConnect($client_id, $data)
|
|
{
|
|
|
|
// 安全检查
|
|
array_walk_recursive($data, ['addons\kefu\library\Common', 'checkVariable']);
|
|
|
|
$now_time = time();
|
|
$initialize_data = [];
|
|
$initialize_data['chat_name'] = Db::name('kefu_config')->where('name', 'chat_name')->value('value');
|
|
$agreement = (stripos($data['server']['HTTP_ORIGIN'], 'https://') === false) ? 'http://' : 'https://';
|
|
$_SESSION['cdn_url'] = $agreement . $data['server']['SERVER_NAME']; //设置服务器域名
|
|
$kefu_config = get_addon_config('kefu');
|
|
|
|
$upload = \app\common\model\Config::upload();
|
|
// 上传信息配置后
|
|
\think\Hook::listen("upload_config_init", $upload);
|
|
$_SESSION['cdn_url'] = $upload['cdnurl'] ? $upload['cdnurl'] : $_SESSION['cdn_url'];
|
|
|
|
|
|
// 获取连接人信息
|
|
$token_info = false;
|
|
|
|
if (!isset($data['get']['modulename'])) {
|
|
|
|
Gateway::sendToClient($client_id, json_encode([
|
|
'code' => 0,
|
|
'msgtype' => 'clear',
|
|
'msg' => $initialize_data['chat_name'] . ' 模块未知',
|
|
]));
|
|
return;
|
|
}
|
|
|
|
if ($data['get']['modulename'] == 'admin' && isset($data['get']['token'])) {
|
|
// 验证管理员身份
|
|
$token_info = Common::checkAdmin($data['get']['token']);
|
|
|
|
// 设置定时器,定时检测管理员身份是否过期
|
|
$_SESSION['auth_timer_id'] = Timer::add(30, function ($client_id, $token) {
|
|
$token_info = Common::checkAdmin($token);
|
|
if (!$token_info) {
|
|
Gateway::closeClient($client_id);
|
|
}
|
|
}, [$client_id, $data['get']['token']]);
|
|
|
|
} elseif ($data['get']['modulename'] != 'admin' && isset($data['get']['token'])) {
|
|
// 验证FA用户身份
|
|
$user_id = Common::checkFaUser($data['get']['token']);
|
|
if ($user_id) {
|
|
// 验证KeFu用户身份
|
|
$token_info = Common::checkKefuUser('', $user_id);
|
|
if ($token_info) {
|
|
// 设置定时器,定时检测用户身份是否过期
|
|
$_SESSION['auth_timer_id'] = Timer::add(60, function ($client_id, $token) {
|
|
$user_id = Common::checkFaUser($token);
|
|
if (!$user_id) {
|
|
Gateway::closeClient($client_id);
|
|
}
|
|
}, [$client_id, $data['get']['token']]);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
if ($data['get']['modulename'] != 'admin' && isset($data['get']['kefu_tourists_token']) && !$token_info) {
|
|
// 验证KeFu用户身份
|
|
$token_info = Common::checkKefuUser($data['get']['kefu_tourists_token'], 0);
|
|
}
|
|
|
|
if ($token_info) {
|
|
|
|
if (isset($token_info['token'])) {
|
|
unset($token_info['token']);
|
|
}
|
|
|
|
if (isset($token_info['blacklist']) && $token_info['blacklist']) {
|
|
Gateway::sendToClient($client_id, json_encode([
|
|
'code' => 0,
|
|
'msgtype' => 'clear',
|
|
'msg' => $initialize_data['chat_name'] . ' 黑名单用户!',
|
|
]));
|
|
return;
|
|
}
|
|
|
|
Gateway::bindUid($client_id, $token_info['user_id']);
|
|
$_SESSION['user_id'] = $token_info['user_id'];
|
|
} else {
|
|
|
|
Gateway::sendToClient($client_id, json_encode([
|
|
'code' => 0,
|
|
'msgtype' => 'clear',
|
|
'msg' => $initialize_data['chat_name'] . ' 无法识别链接用户身份,请重新登录!',
|
|
]));
|
|
return;
|
|
}
|
|
|
|
if ($data['get']['modulename'] == 'admin') {
|
|
|
|
// 读取会话列表
|
|
$session = Db::name('kefu_session')
|
|
->alias('s')
|
|
->field('s.*,CONCAT(u.id,"||user") as session_user,u.user_id as fu_user_id,u.avatar,u.nickname,u.wechat_openid,fu.avatar as fu_avatar,fu.nickname as fu_nickname')
|
|
->join('kefu_user u', 'u.id=s.user_id')
|
|
->join('user fu', 'u.user_id=fu.id', 'LEFT')
|
|
->where('s.csr_id', $token_info['id'])
|
|
->where('s.deletetime', null)
|
|
->limit(40)
|
|
->order('s.createtime desc')
|
|
->select();
|
|
|
|
$session = array_reverse($session, false); // 会话分组时数组键将被逆转,最终给到前台的则是可以直接for in的数组
|
|
|
|
// 会话列表分组 在线的且上次消息时间在最近的-放入对话中 不在线的或者上次消息时间较久的放入最近沟通
|
|
$session_temp = [];
|
|
foreach ($session as $key => $value) {
|
|
|
|
// 最后一条聊天记录
|
|
$last_message = Db::name('kefu_record')
|
|
->where('session_id', $value['id'])
|
|
->order('createtime desc')
|
|
->find();
|
|
|
|
$value['last_message'] = Common::formatMessage($last_message);
|
|
$value['last_time'] = Common::formatSessionTime(isset($last_message['createtime']) ? $last_message['createtime'] : null);
|
|
|
|
$value['online'] = $value['wechat_openid'] ? 1 : Gateway::isUidOnline($value['session_user']);
|
|
$value['avatar'] = $value['fu_avatar'] ? $value['fu_avatar'] : $value['avatar'];
|
|
$value['nickname'] = $value['fu_nickname'] ? $value['fu_nickname'] : $value['nickname'];
|
|
$value['avatar'] = Common::imgSrcFill($value['avatar'], true);
|
|
|
|
// 用户发来的未读消息数
|
|
$value['unread_msg_count'] = Db::name('kefu_record')
|
|
->where('session_id', $value['id'])
|
|
->where('sender_identity', 1)
|
|
->where('sender_id', $value['user_id'])
|
|
->where('status', 0)
|
|
->count('id');
|
|
|
|
$last_time = isset($last_message['createtime']) ? $last_message['createtime'] : $value['createtime'];
|
|
|
|
$dialogue_time = $value['wechat_openid'] ? 600 : 43200; // 这个时间内的会话计入会话中
|
|
|
|
if ($value['online'] || ($now_time - $last_time < $dialogue_time) || $value['unread_msg_count'] > 0) {
|
|
$session_temp['dialogue'][] = $value;
|
|
} else {
|
|
$session_temp['recently'][] = $value;
|
|
}
|
|
}
|
|
|
|
// 客服上线
|
|
$reception_count = isset($session_temp['dialogue']) ? count($session_temp['dialogue']) : false;
|
|
if ($reception_count) {
|
|
Db::name('kefu_csr_config')->where('admin_id', $token_info['id'])->update([
|
|
'reception_count' => $reception_count,
|
|
]);
|
|
}
|
|
|
|
// 获取访问中(邀请中)的用户->查询对应的用户信息
|
|
$invitation = Gateway::getAllUidList();
|
|
$invitation_user_ids = [];
|
|
|
|
foreach ($invitation as $key => $value) {
|
|
$invitation_user_id = explode('||', $value);
|
|
|
|
if (isset($invitation_user_id[1]) && $invitation_user_id[1] != 'csr' && (int)$invitation_user_id[0] > 0) {
|
|
$invitation_user_ids[] = (int)$invitation_user_id[0];
|
|
}
|
|
}
|
|
|
|
$invitation_user_ids = implode(',', $invitation_user_ids);
|
|
$invitation = Db::name('kefu_user')
|
|
->alias('u')
|
|
->field('u.id,u.avatar,u.nickname,u.createtime,s.id as sid,fu.avatar as fu_avatar,fu.nickname as fu_nickname')
|
|
->join('user fu', 'u.user_id=fu.id', 'LEFT')
|
|
->join('kefu_session s', 's.user_id=u.id', 'LEFT')
|
|
->whereIn('u.id', $invitation_user_ids)
|
|
->where('s.id', null)
|
|
->select();
|
|
|
|
foreach ($invitation as $key => $value) {
|
|
|
|
/*$trajectory = Db::name('kefu_trajectory')
|
|
->where('user_id', $value['id'])
|
|
->order('id desc')
|
|
->find();*/
|
|
|
|
$invitation[$key]['id'] = 'invitation||' . $value['id'];
|
|
$invitation[$key]['avatar'] = $value['fu_avatar'] ? $value['fu_avatar'] : $value['avatar'];
|
|
$invitation[$key]['avatar'] = Common::imgSrcFill($invitation[$key]['avatar'], true);
|
|
$invitation[$key]['nickname'] = $value['fu_nickname'] ? $value['fu_nickname'] : $value['nickname'];
|
|
$invitation[$key]['online'] = 1;
|
|
$invitation[$key]['unread_msg_count'] = 0;
|
|
$invitation[$key]['last_message'] = '';
|
|
$invitation[$key]['session_user'] = $value['id'] . '||user';
|
|
$invitation[$key]['last_time'] = Common::formatSessionTime($value['createtime']);
|
|
}
|
|
|
|
$session_temp['invitation'] = $invitation;
|
|
|
|
$initialize_data['session'] = $session_temp;
|
|
|
|
// 获取状态
|
|
$token_info['status_text'] = Common::csrStatus(null);
|
|
$tourists = 'not';
|
|
|
|
} else {
|
|
|
|
if (!Db::name('kefu_session')->where('user_id', $token_info['id'])->value('id')) {
|
|
|
|
// 无客服游客-供前台建立会话
|
|
$avatar = $token_info['fu_avatar'] ? $token_info['fu_avatar'] : $token_info['avatar'];
|
|
$tourists = [
|
|
'id' => 'invitation||' . $token_info['id'],
|
|
'avatar' => Common::imgSrcFill($avatar, true),
|
|
'nickname' => $token_info['fu_nickname'] ? $token_info['fu_nickname'] : $token_info['nickname'],
|
|
'online' => 1,
|
|
'unread_msg_count' => 0,
|
|
'session_user' => $token_info['id'] . '||user',
|
|
'last_message' => '',
|
|
'last_time' => Common::formatSessionTime($token_info['createtime']),
|
|
];
|
|
} else {
|
|
$tourists = 'not';
|
|
}
|
|
}
|
|
|
|
$initialize_data['modulename'] = $data['get']['modulename'];
|
|
$initialize_data['user_info'] = $token_info;
|
|
$initialize_data['new_msg'] = Common::getUnreadMessages($_SESSION['user_id'], true);
|
|
|
|
// 向当前client_id发送数据
|
|
Gateway::sendToClient($client_id, json_encode(['msgtype' => 'initialize', 'data' => $initialize_data]));
|
|
|
|
// 向所有人发送
|
|
Gateway::sendToAll(json_encode([
|
|
'msgtype' => 'online',
|
|
'user_id' => $_SESSION['user_id'],
|
|
'user_name' => isset($token_info['fu_nickname']) ? ($token_info['fu_nickname'] . '(' . $token_info['nickname'] . ')') : $token_info['nickname'],
|
|
'tourists' => $tourists,
|
|
'modulename' => $data['get']['modulename'],
|
|
]));
|
|
}
|
|
|
|
/**
|
|
* 当客户端发来消息时触发
|
|
* @param int $client_id 连接id
|
|
* @param mixed $message 具体消息
|
|
*/
|
|
public static function onMessage($client_id, $message)
|
|
{
|
|
$chat_name = Db::name('kefu_config')->where('name', 'chat_name')->value('value');
|
|
|
|
// 分发到控制器
|
|
$data = json_decode($message, true);
|
|
|
|
// 安全检查
|
|
array_walk_recursive($data, ['addons\kefu\library\Common', 'checkVariable']);
|
|
|
|
if (!is_array($data) || !isset($data['c']) || !isset($data['a'])) {
|
|
|
|
common::showMsg($client_id, $chat_name . ' 错误的请求!');
|
|
return;
|
|
}
|
|
|
|
if ($data['c'] == 'clear') {
|
|
Gateway::closeClient($client_id);
|
|
return '';
|
|
}
|
|
|
|
$filename = __DIR__ . '/controller/' . $data['c'] . '.php'; //载入文件类似/controller/index.php
|
|
|
|
if (file_exists($filename)) {
|
|
|
|
require_once $filename;
|
|
|
|
/*
|
|
检查要访问的类是否存在
|
|
*/
|
|
if (!class_exists($data['c'], false)) {
|
|
|
|
common::showMsg($client_id, $chat_name . ' 您访问的控制器不存在!');
|
|
return;
|
|
}
|
|
} else {
|
|
|
|
common::showMsg($client_id, $chat_name . ' 您访问的文件并存在!');
|
|
return;
|
|
}
|
|
|
|
$o = new $data['c'](); // 新建对象
|
|
|
|
if (!method_exists($o, $data['a'])) {
|
|
|
|
common::showMsg($client_id, $chat_name . ' 您访问的方法并存在!');
|
|
return;
|
|
}
|
|
|
|
$data['data'] = isset($data['data']) ? $data['data'] : '';
|
|
|
|
call_user_func_array([$o, $data['a']], [$client_id, $data['data']]); //调用对象$o($c)里的方法$a
|
|
}
|
|
|
|
/**
|
|
* 当用户断开连接时触发
|
|
* @param int $client_id 连接id
|
|
*/
|
|
public static function onClose($client_id)
|
|
{
|
|
if (isset($_SESSION['auth_timer_id'])) {
|
|
Timer::del($_SESSION['auth_timer_id']);
|
|
}
|
|
|
|
// 向所有人发送
|
|
if (isset($_SESSION['user_id'])) {
|
|
|
|
// 此user_id下还有其他链接
|
|
try {
|
|
if (Gateway::getClientIdByUid($_SESSION['user_id'])) {
|
|
return;
|
|
}
|
|
} catch (\Exception $e) {
|
|
|
|
}
|
|
|
|
$user_info = Common::userInfo($_SESSION['user_id']);
|
|
|
|
if ($user_info['source'] == 'user') {
|
|
|
|
$csr_id = Db::name('kefu_session')->where('user_id', $user_info['id'])->value('csr_id');
|
|
|
|
if ($csr_id) {
|
|
|
|
$reception_count = Db::name('kefu_csr_config')
|
|
->where('admin_id', $csr_id)
|
|
->value('reception_count');
|
|
|
|
if ($reception_count > 0) {
|
|
Db::name('kefu_csr_config')->where('admin_id', $csr_id)->setDec('reception_count');
|
|
}
|
|
|
|
}
|
|
|
|
} elseif ($user_info['source'] == 'csr' && $user_info['status'] == 3) {
|
|
// 客服保持在线
|
|
$keep_alive = Db::name('kefu_csr_config')->where('admin_id', $user_info['id'])->value('keep_alive');
|
|
if ($keep_alive) {
|
|
return;
|
|
}
|
|
|
|
Common::csrStatus(0);
|
|
|
|
// 客服下线
|
|
Db::name('kefu_csr_config')->where('admin_id', $user_info['id'])->update([
|
|
'reception_count' => 0,
|
|
]);
|
|
}
|
|
|
|
Gateway::sendToAll(json_encode([
|
|
'msgtype' => 'offline',
|
|
'user_id' => $_SESSION['user_id'],
|
|
]));
|
|
|
|
}
|
|
|
|
Db::clear();
|
|
}
|
|
|
|
}
|