一、登录接口
<?php
/**
* 登录退出操作
* User: 龙哥·三年风水
* Date: 2024/10/29
* Time: 15:53
*/
namespace app\controller\common;
use app\controller\Emptys;
use app\model\permission\Admin;
use app\model\param\System as SystemModel;
use Email\EmailSender;
use Other\Browser;
use Redis\Redis;
use app\model\common\Token as TokenModel;
use Encipher\Encrypt;
use Workman\GatewayWork;
class Login extends Emptys
{
//验证码展示
public function getCode(){
$re = app()->make(\Other\Captcha::class)->create();
$base64 = 'data:image/png;base64,' . base64_encode($re->getData());
return succ('SUCCESS',$base64);
}
//登录
public function doLogin(){
$param = $this->request->param();
$validate = new \app\validate\common\Login;
$result = $validate->check($param);//参数验证
if (!$result) return err($validate->getError());
//验证码的正确性
if (!app()->make(\Other\Captcha::class)->check($param['captcha_code'])) return err('验证码错误');
//if (time() + 5 < strtotime($param['login_time'])) return err('非法登录');
$resData = Admin::dataFind(['email' => trim($param['username'])], 'id,realname,password,ip,status', true);
//用户信息的正确性
if (empty($resData) && empty($resData['id'])) return err('用户不存在');
if ($resData['status'] !== 1) return err('该账号已被禁用');
$systemParam = SystemModel::dataFind(['id' => 1],'security_password,platform_token_expira');
if ($resData['password'] !== sha1($param['password'] . $systemParam['security_password'])) return err('账号对应的密码错误');
$loginIp = $this->request->header('x-real-ip');
if(!empty($resData['ip'])){
if($resData['ip'] != $loginIp)return err('禁止访问,不在IP白名单中');
}
// 获取浏览器版本
$browserId = Browser::getVersion();
// 获取该用户是否在登录状态下
$tokenId = Redis::select(config('cache.stores.redis.token_db'))->get('token_' . $resData['id']);
// 如果在登录状态下,需要确认IP是否一致
if(!empty($tokenId)){
$tokenValue = explode('|', Encrypt::decryptRsa($tokenId));//解密 --转换token
if(count($tokenValue) > 0){
$tokenSelect = count($tokenValue) == 2 ? sha1(sha1($tokenValue[0]).strtotime($tokenValue[1])) : $tokenValue[0];
// 读取用户之前登录的IP及浏览器
$resTokenLogin = TokenModel::dataFind(['token' => $tokenSelect,'token_type' => 1],'id,random_number,browser_id,login_ip',true);
// 如果之前登录的IP存在 之前登录的IP与现在登录的IP进行对比
if(!empty($resTokenLogin)){
// 如果不相等的情况下,需要告知之前的浏览器做退出动作
if($loginIp != $resTokenLogin['login_ip']){
TokenModel::save(['expire_time' => time()], ['token' => $tokenSelect]);
$this->setToken(3,'PermissionMemberLogout',$resData['id'],$resTokenLogin['random_number'],$resTokenLogin['browser_id'],$resTokenLogin['login_ip'],$tokenSelect);
Redis::select(config('cache.stores.redis.token_db'))->del('token_' . $tokenSelect);
GatewayWork::onRepeatClose($resData['id']);
}else{
// 如果相等的情况下,就需要判断浏览器是否相等啦,如果不相等的情况下,也是需要让之前的账号多退出的动作
if($browserId == $resTokenLogin['browser_id'])return err('已登录,不可再次登录');
if($browserId != $resTokenLogin['browser_id']){
TokenModel::save(['expire_time' => time()], ['token' => $tokenSelect]);
$this->setToken(3,'PermissionMemberLogout',$resData['id'],$resTokenLogin['random_number'],$resTokenLogin['browser_id'],$resTokenLogin['login_ip'],$tokenSelect);
Redis::select(config('cache.stores.redis.token_db'))->del('token_' . $tokenSelect);
GatewayWork::onRepeatClose($resData['id']);
}
}
}
}
}
//写入Token日志
$dataRandomNumber = alnum();
$expireTime = strtotime($param['login_time']) + $systemParam['platform_token_expira'];
$token = $resData['id'].$dataRandomNumber;
$dataToken = sha1( sha1($token). strtotime($param['login_time']));
$this->setToken(1,'CommonLoginDoLogin',$resData['id'],$dataRandomNumber,$browserId,$loginIp,$dataToken,$expireTime);
//加入跨站攻击验证队列
Redis::select(config('cache.stores.redis.token_db'))->setex('token_' . $dataToken,$systemParam['platform_token_expira'],$resData['id']);
Redis::select(config('cache.stores.redis.token_db'))->setex('token_' . $resData['id'],$systemParam['platform_token_expira'],Encrypt::encryptRsa($dataToken));
/*$emailSender = new EmailSender();
$emailSender::send($param['username'],'登录系统',$resData['realname'].'于'.$param['login_time'].'登录系统');*/
return succ('登录成功',Encrypt::encryptRsa($token));
}
/**
* 操作日志记录
* User: 龙哥·三年风水
* Date: 2024/12/12
* Time: 17:11
* @ param $tokenType 操作类型
* @ param $menuName 权限名称
*/
private function setToken($tokenType,$menuName,$adminId,$randomNumber,$browserId,$loginIp,$token,$expireTime = ''){
$data['token_type'] = $tokenType;
$data['menu_name'] = $menuName;
$data['admin_id'] = $adminId;
$data['random_number'] = $randomNumber;
$data['browser_id'] = $browserId;
$data['login_ip'] = $loginIp;
$data['create_time'] = date('Y-m-d',time());
$data['login_time'] = date('Y-m-d H:i:s',time());
$data['expire_time'] = empty($expireTime) ? time() : $expireTime;
$data['token'] = $token;
TokenModel::save($data,[]);
}
}
二、长链接封装
<?php
/**
* 长链接发送信息
* User: 龙哥 三年风水
* Date: 2024/12/21/0021
* Time: 22:52
*/
namespace Workman;
use GatewayClient\Gateway;
class GatewayWork
{
//向客户端发送内容
public static function sendToClient($client_id, $data){
//获取配置文件
$isFormal = config('socket.is_formal');
Gateway::$registerAddress = config("socket.tcp_address.{$isFormal}.address");
Gateway::sendToClient($client_id, $data);
}
//向群推送数据
public static function sendToGroup($group,$message){
//获取配置文件
$isFormal = config('socket.is_formal');
Gateway::$registerAddress = config("socket.tcp_address.{$isFormal}.address");
Gateway::sendToGroup($group,json_encode($message));
}
//批量向客户端发送内容
public static function sendToAll($message, $client_id_array = null){
//获取配置文件
$isFormal = config('socket.is_formal');
Gateway::$registerAddress = config("socket.tcp_address.{$isFormal}.address");
Gateway::sendToAll(json_encode($message), $client_id_array);
}
//获取与 uid 绑定的 client_id 列表
public static function getClientIdByUid($uid){
//获取配置文件
$isFormal = config('socket.is_formal');
Gateway::$registerAddress = config("socket.tcp_address.{$isFormal}.address");
return Gateway::getClientIdByUid($uid);
}
//重复登录关闭客户端
public static function onRepeatClose($userId){
//获取配置文件
$isFormal = config('socket.is_formal');
Gateway::$registerAddress = config("socket.tcp_address.{$isFormal}.address");
$resUser = Gateway::isUidOnline((int)$userId);
if($resUser == 1){
$resClientUser = Gateway::getClientIdByUid((int)$userId);
foreach ($resClientUser as $v){
Gateway::sendToClient($v, json_encode(['type' => 'repeat_close','data' => 'ok']));
Gateway::destoryClient($v);
}
}
}
//正常退出关闭客户端
public static function onArtificialClose($userId){
//获取配置文件
$isFormal = config('socket.is_formal');
Gateway::$registerAddress = config("socket.tcp_address.{$isFormal}.address");
$resUser = Gateway::isUidOnline((int)$userId);
if($resUser == 1){
$resClientUser = Gateway::getClientIdByUid((int)$userId);
foreach ($resClientUser as $v){
Gateway::sendToClient($v, json_encode(['type' => 'artificial_close','data' => 'ok']));
Gateway::destoryClient($v);
}
}
}
//到期退出关闭客户端
public static function onExpireClose($userId){
//获取配置文件
$isFormal = config('socket.is_formal');
Gateway::$registerAddress = config("socket.tcp_address.{$isFormal}.address");
$resUser = Gateway::isUidOnline((int)$userId);
if($resUser == 1){
$resClientUser = Gateway::getClientIdByUid((int)$userId);
foreach ($resClientUser as $v){
Gateway::sendToClient($v, json_encode(['type' => 'expire_close','data' => 'ok']));
Gateway::destoryClient($v);
}
}
}
}
三、提前说明
因最近一段时间比较忙,导致精力不能集中。之前封装的vue-element-admin与thinkphp6追加长链接workman项目开发文档中有些问题。所以今天与明后天彻底解决一下这个问题。请谅解。