定时任务
This commit is contained in:
parent
bf44e4abb4
commit
78ada91ff5
|
|
@ -6,6 +6,7 @@
|
|||
namespace App\Controller\Admin;
|
||||
|
||||
use App\Controller\AbstractController;
|
||||
use Hyperf\Contract\LengthAwarePaginatorInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
abstract class Base extends AbstractController
|
||||
|
|
@ -15,6 +16,11 @@ abstract class Base extends AbstractController
|
|||
return $this->request->getAttribute("account_id");
|
||||
}
|
||||
|
||||
public function uuid(): string
|
||||
{
|
||||
return $this->account()['uuid'];
|
||||
}
|
||||
|
||||
public function account()
|
||||
{
|
||||
return $this->request->getAttribute("account");
|
||||
|
|
@ -23,34 +29,42 @@ abstract class Base extends AbstractController
|
|||
/**
|
||||
* Author: cfn <cfn@leapy.cn>
|
||||
* @param array|string $msg
|
||||
* @param array|null $data
|
||||
* @param $count
|
||||
* @param $summary
|
||||
* @param array|LengthAwarePaginatorInterface|null $data
|
||||
* @param null $count
|
||||
* @param null $summary
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function success(array|string $msg = "success", array|null $data = null, $count = null, $summary = null)
|
||||
public function success(array|string $msg = "success", LengthAwarePaginatorInterface|array|null $data = null, $count = null, $summary = null)
|
||||
{
|
||||
if (!is_string($msg)) {
|
||||
$data = $msg;
|
||||
$msg = "success";
|
||||
}
|
||||
if ($data instanceof LengthAwarePaginatorInterface) {
|
||||
$count = $data->total();
|
||||
$data = $data->items();
|
||||
}
|
||||
return $this->response(0, $msg, $data, $count, $summary);
|
||||
}
|
||||
|
||||
/**
|
||||
* Author: cfn <cfn@leapy.cn>
|
||||
* @param array|string $msg
|
||||
* @param array|null $data
|
||||
* @param $count
|
||||
* @param $summary
|
||||
* @param LengthAwarePaginatorInterface|array|null $data
|
||||
* @param null $count
|
||||
* @param null $summary
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function error(array|string $msg = "error", array|null $data = null, $count = null, $summary = null)
|
||||
public function error(array|string $msg = "error", LengthAwarePaginatorInterface|array|null $data = null, $count = null, $summary = null)
|
||||
{
|
||||
if (!is_string($msg)) {
|
||||
$data = $msg;
|
||||
$msg = "error";
|
||||
}
|
||||
if ($data instanceof LengthAwarePaginatorInterface) {
|
||||
$count = $data->total();
|
||||
$data = $data->items();
|
||||
}
|
||||
return $this->response(1, $msg, $data, $count, $summary);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ use App\Annotation\Auth;
|
|||
use App\Event\LogEvent;
|
||||
use App\Model\Account;
|
||||
use App\Model\AccountLog;
|
||||
use App\Model\Online;
|
||||
use App\Utils\Param;
|
||||
use App\Utils\Str;
|
||||
use App\Utils\Token;
|
||||
|
|
@ -77,16 +78,17 @@ class Login extends Base
|
|||
return $this->error("账号或者密码错误!");
|
||||
}
|
||||
// 商户ID
|
||||
$uuid = Str::uuid();
|
||||
$tData['account_id'] = $account['account_id'];
|
||||
$tData['account_type'] = $account['account_type'];
|
||||
$tData['belong_id'] = $account['belong_id'];
|
||||
$tData['username'] = $account['username'];
|
||||
$tData['master_flag'] = $account['master_flag'];
|
||||
$tData['uuid'] = $uuid;
|
||||
|
||||
$uuid = Str::uuid();
|
||||
$token = Token::buildToken(['uuid' => $uuid, 'time' => time() + config("jwt.ttl")], config("jwt.ttl"));
|
||||
// 记录登录日志
|
||||
$this->eventDispatcher->dispatch(new LogEvent($tData, $uuid));
|
||||
$this->eventDispatcher->dispatch(new LogEvent($tData));
|
||||
// 根据账号所属角色缓存相应的权限数据
|
||||
$auths = Account::getAuths($account['account_id'], $account['account_type'], $account['master_flag']);
|
||||
$redis->set("AUTH:" . $account['account_id'], json_encode($auths));
|
||||
|
|
@ -113,6 +115,13 @@ class Login extends Base
|
|||
#[Auth(needAuth: false)]
|
||||
public function logout()
|
||||
{
|
||||
// 退出登录状态
|
||||
$container = ApplicationContext::getContainer();
|
||||
$redis = $container->get(\Hyperf\Redis\Redis::class);
|
||||
$redis->del("USER:" . $this->uuid());
|
||||
$redis->del("AUTH:" . $this->account()['account_id']);
|
||||
// 设置离线
|
||||
Online::leave($this->uuid());
|
||||
return $this->success("登出成功");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ namespace App\Controller\Admin;
|
|||
use App\Annotation\Auth;
|
||||
use App\Model\Menu as mModel;
|
||||
use App\Model\Dept as dModel;
|
||||
use App\Model\Online;
|
||||
use App\Model\Online as oModel;
|
||||
use App\Model\Role as rModel;
|
||||
use App\Utils\AppInfoHelper;
|
||||
|
|
@ -18,6 +19,7 @@ use App\Utils\MemoryHelper;
|
|||
use App\Utils\Param;
|
||||
use App\Utils\RedisInfoHelper;
|
||||
use App\Utils\SystemHelper;
|
||||
use Hyperf\Context\ApplicationContext;
|
||||
use Hyperf\HttpServer\Annotation\Controller;
|
||||
use Hyperf\HttpServer\Annotation\DeleteMapping;
|
||||
use Hyperf\HttpServer\Annotation\GetMapping;
|
||||
|
|
@ -30,6 +32,8 @@ use App\Model\Post as pModel;
|
|||
use App\Request\Post as pRequest;
|
||||
use App\Model\Account as aModel;
|
||||
use App\Request\Account as aRequest;
|
||||
use App\Model\Crontab as cModel;
|
||||
use App\Model\CrontabLog as clModel;
|
||||
|
||||
#[Controller(prefix: "admin")]
|
||||
class System extends Base
|
||||
|
|
@ -365,4 +369,66 @@ class System extends Base
|
|||
}
|
||||
return $this->success('在线用户', $paginate->items(), $paginate->total());
|
||||
}
|
||||
|
||||
#[GetMapping(path: "online/quit")]
|
||||
#[Auth(auth: "online:quit")]
|
||||
public function onlineQuit()
|
||||
{
|
||||
$param = Param::only(['session_id' => '']);
|
||||
// 退出登录状态
|
||||
$online = Online::getByUuid($param['session_id']);
|
||||
if (empty($online) || $online['status'] == 0) {
|
||||
return $this->success("用户已不在线");
|
||||
}
|
||||
$container = ApplicationContext::getContainer();
|
||||
$redis = $container->get(\Hyperf\Redis\Redis::class);
|
||||
$redis->del("USER:" . $param['session_id']);
|
||||
$redis->del("AUTH:" . $online['account_id']);
|
||||
// 设置离线
|
||||
Online::leave($param['session_id']);
|
||||
return $this->success("强退成功");
|
||||
}
|
||||
|
||||
#[GetMapping(path: "crontab/list")]
|
||||
#[Auth(auth: "crontab:list")]
|
||||
public function crontabList()
|
||||
{
|
||||
$param = Param::only(['crontab_name', 'enable', 'limit' => 10]);
|
||||
return $this->success("任务列表", cModel::list($param));
|
||||
}
|
||||
|
||||
#[GetMapping(path: "crontab/option")]
|
||||
#[Auth(needAuth: false)]
|
||||
public function crontabOption()
|
||||
{
|
||||
return $this->success("任务列表", cModel::options());
|
||||
}
|
||||
|
||||
#[PostMapping(path: "crontab/add")]
|
||||
#[Auth(auth: "crontab:add")]
|
||||
public function crontabAdd()
|
||||
{
|
||||
$data = Param::only(['crontab_name' => '', 'rule', 'callback', 'memo', 'singleton', 'params', 'skip_log', 'enable' => 1]);
|
||||
$res = cModel::add($data);
|
||||
return $res ? $this->success("操作成功") : $this->error("操作失败");
|
||||
}
|
||||
|
||||
#[PutMapping(path: "crontab/edit")]
|
||||
#[Auth(auth: "crontab:edit")]
|
||||
public function crontabEdit()
|
||||
{
|
||||
$data = Param::only(['crontab_id' => '', 'crontab_name' => '', 'rule', 'callback', 'memo', 'singleton', 'params', 'skip_log', 'enable' => 1]);
|
||||
$res = cModel::edit($data);
|
||||
return $res ? $this->success("操作成功") : $this->error("操作失败");
|
||||
}
|
||||
|
||||
#[DeleteMapping(path: "crontab/del")]
|
||||
#[Auth(auth: "crontab:del")]
|
||||
public function crontabDel()
|
||||
{
|
||||
$ids = $this->request->input("ids", "");
|
||||
if (!$ids) return $this->error("操作失败");
|
||||
$res = cModel::del($ids);
|
||||
return $res ? $this->success("操作成功") : $this->error("操作失败");
|
||||
}
|
||||
}
|
||||
|
|
@ -7,5 +7,5 @@ namespace App\Event;
|
|||
|
||||
class LogEvent
|
||||
{
|
||||
public function __construct(public array $data, public string $uuid){}
|
||||
public function __construct(public array $data){}
|
||||
}
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
<?php
|
||||
/**
|
||||
* Author: cfn <cfn@leapy.cn>
|
||||
*/
|
||||
|
||||
namespace App\Event;
|
||||
|
||||
use App\Model\Online;
|
||||
use App\Utils\Token;
|
||||
use Hyperf\Context\ApplicationContext;
|
||||
use Hyperf\Contract\OnCloseInterface;
|
||||
use Hyperf\Contract\OnMessageInterface;
|
||||
use Hyperf\Contract\OnOpenInterface;
|
||||
use Hyperf\WebSocketServer\Context;
|
||||
|
||||
/**
|
||||
* 商户端socket
|
||||
* Author: cfn <cfn@leapy.cn>
|
||||
*/
|
||||
class WebSocket implements OnMessageInterface, OnOpenInterface, OnCloseInterface
|
||||
{
|
||||
/**
|
||||
* 连接关闭
|
||||
* Author: cfn <cfn@leapy.cn>
|
||||
* @param $server
|
||||
* @param int $fd
|
||||
* @param int $reactorId
|
||||
* @return void
|
||||
*/
|
||||
public function onClose($server, int $fd, int $reactorId): void
|
||||
{
|
||||
$uuid = Context::get('uuid', "");
|
||||
Online::leave($uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* 连接时
|
||||
* Author: cfn <cfn@leapy.cn>
|
||||
* @param $server
|
||||
* @param $request
|
||||
* @return void
|
||||
*/
|
||||
public function onOpen($server, $request): void
|
||||
{
|
||||
$token = $request->get['Authorization'];
|
||||
$result = Token::parseToken(str_replace("Bearer ", "", $token));
|
||||
|
||||
$container = ApplicationContext::getContainer();
|
||||
$redis = $container->get(\Hyperf\Redis\Redis::class);
|
||||
$account = !empty($result) ? json_decode($redis->get("USER:" . $result['uuid']), true) : null;
|
||||
if (!empty($account)) {
|
||||
Online::live($result['uuid'], $request->fd);
|
||||
Context::set('account', $account);
|
||||
Context::set('uuid', $result['uuid']);
|
||||
$server->push($request->fd, 'connection successful');
|
||||
return;
|
||||
}
|
||||
$server->push($request->fd, 'connection refused');
|
||||
}
|
||||
|
||||
/**
|
||||
* 当接收到信息
|
||||
* Author: cfn <cfn@leapy.cn>
|
||||
* @param $server
|
||||
* @param $frame
|
||||
* @return void
|
||||
*/
|
||||
public function onMessage($server, $frame): void
|
||||
{
|
||||
if (strtolower($frame->data) == "ping") {
|
||||
$server->push($frame->fd, 'pong');
|
||||
return;
|
||||
}
|
||||
$server->push($frame->fd, 'Recv: ' . $frame->data);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
/**
|
||||
* Author: cfn <cfn@leapy.cn>
|
||||
*/
|
||||
|
||||
namespace App\Event;
|
||||
|
||||
|
||||
use App\Model\Online;
|
||||
|
||||
class WebSocketServer
|
||||
{
|
||||
// 访问启动时,关闭之前的连接
|
||||
public function onStart(): void
|
||||
{
|
||||
Online::closeAll();
|
||||
}
|
||||
|
||||
// 服务关闭,关闭之前的连接
|
||||
public function onShutdown(): void
|
||||
{
|
||||
Online::closeAll();
|
||||
}
|
||||
}
|
||||
|
|
@ -22,7 +22,7 @@ class LogHandleListener implements ListenerInterface
|
|||
public function process(object $event): void
|
||||
{
|
||||
Online::insert([
|
||||
'session_id' => $event->uuid,
|
||||
'session_id' => $event->data['uuid'],
|
||||
'account_type' => $event->data['account_type'],
|
||||
'belong_id' => $event->data['belong_id'],
|
||||
'account_id' => $event->data['account_id'],
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Middleware;
|
||||
|
||||
use App\Model\Online;
|
||||
use App\Utils\Token;
|
||||
use Hyperf\Context\ApplicationContext;
|
||||
use Hyperf\Context\Context;
|
||||
|
|
@ -43,7 +44,13 @@ class JWTMiddleware implements MiddlewareInterface
|
|||
try {
|
||||
$token = $request->getHeaderLine("Authorization", "");
|
||||
$result = Token::parseToken(str_replace("Bearer ", "", $token));
|
||||
$account = !empty($result) ? json_decode($redis->get("USER:" . $result['uuid']), true) : null;
|
||||
if (!empty($result)) {
|
||||
$user = $redis->get("USER:" . $result['uuid']);
|
||||
if (!empty($user)) {
|
||||
$account = json_decode($user, true);
|
||||
}
|
||||
}
|
||||
// 判断登录状态是否强退
|
||||
if (!empty($account)) {
|
||||
// 是否登录
|
||||
$request = $request->withAttribute("isLogin", true);
|
||||
|
|
@ -52,6 +59,8 @@ class JWTMiddleware implements MiddlewareInterface
|
|||
$request = $request->withAttribute("account_id", $account['account_id']);
|
||||
// 基础信息
|
||||
$request = $request->withAttribute("account", $account);
|
||||
// 如果账户离线恢复在线
|
||||
Online::reLive($result['uuid']);
|
||||
}
|
||||
} catch (\Exception $exception) {
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,62 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Model;
|
||||
|
||||
/**
|
||||
* @property int $crontab_id
|
||||
* @property string $crontab_name
|
||||
* @property string $rule
|
||||
* @property string $callback
|
||||
* @property string $memo
|
||||
* @property string $params
|
||||
* @property int $enable
|
||||
* @property int $singleton
|
||||
* @property int $skip_log
|
||||
* @property string $create_time
|
||||
* @property string $update_time
|
||||
* @property string $deleted_at
|
||||
*/
|
||||
class Crontab extends Model
|
||||
{
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*/
|
||||
protected ?string $table = 'crontab';
|
||||
|
||||
protected string $primaryKey = 'crontab_id';
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*/
|
||||
protected array $fillable = [];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast to native types.
|
||||
*/
|
||||
protected array $casts = ['crontab_id' => 'integer', 'enable' => 'integer', 'singleton' => 'integer', 'skip_log' => 'integer'];
|
||||
|
||||
public static function list(array $param)
|
||||
{
|
||||
return self::query()
|
||||
->when(isset($param['enable']), function ($query) use ($param) {
|
||||
$query->where('enable', $param['enable']);
|
||||
})
|
||||
->when(isset($param['crontab_name']), function ($query) use ($param) {
|
||||
$query->where('crontab_name', 'like', '%' . $param['crontab_name'] . '%');
|
||||
})
|
||||
->select(['crontab_id', 'crontab_name', 'enable', 'singleton', 'skip_log', 'rule', 'callback', 'params', 'memo', 'create_time'])
|
||||
->orderByDesc('crontab_id')
|
||||
->paginate((int)$param['limit']);
|
||||
}
|
||||
|
||||
public static function options()
|
||||
{
|
||||
return self::query()
|
||||
->orderByDesc('crontab_id')
|
||||
->orderByDesc('crontab_id')
|
||||
->select(['crontab_id', 'crontab_name'])
|
||||
->get();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Model;
|
||||
|
||||
/**
|
||||
* @property int $log_id
|
||||
* @property int $crontab_id
|
||||
* @property string $crontab_name
|
||||
* @property string $callback
|
||||
* @property string $duration
|
||||
* @property int $status
|
||||
* @property string $result
|
||||
* @property string $create_time
|
||||
* @property string $deleted_at
|
||||
*/
|
||||
class CrontabLog extends Model
|
||||
{
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*/
|
||||
protected ?string $table = 'crontab_log';
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*/
|
||||
protected array $fillable = [];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast to native types.
|
||||
*/
|
||||
protected array $casts = ['log_id' => 'integer', 'crontab_id' => 'integer', 'status' => 'integer'];
|
||||
}
|
||||
|
|
@ -4,6 +4,9 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Model;
|
||||
|
||||
use App\Utils\Ip;
|
||||
use Swoole\Http\Request;
|
||||
|
||||
/**
|
||||
* @property int $online_id
|
||||
* @property string $session_id
|
||||
|
|
@ -50,6 +53,41 @@ class Online extends Model
|
|||
->paginate((int)$param['limit']);
|
||||
}
|
||||
|
||||
public static function closeAll(): int
|
||||
{
|
||||
return self::where("status", 1)
|
||||
->where("online_time", ">", date("Y-m-d H:i:s", time() - 24 * 60 * 60))
|
||||
->update(['status' => 0, 'offline_time' => date("Y-m-d H:i:s")]);
|
||||
}
|
||||
|
||||
public static function leave(string $uuid): int
|
||||
{
|
||||
return self::where('session_id', $uuid)
|
||||
->update(['status' => 0, 'offline_time' => date("Y-m-d H:i:s")]);
|
||||
}
|
||||
|
||||
public static function live(string $uuid, int $fd): int
|
||||
{
|
||||
return self::where("session_id", $uuid)
|
||||
->where("status", 1)
|
||||
->update(['fd' => $fd, 'update_time' => date("Y-m-d H:i:s")]);
|
||||
}
|
||||
|
||||
public static function reLive(string $uuid): bool
|
||||
{
|
||||
$online = self::getByUuid($uuid);
|
||||
if ($online['status'] == 0) {
|
||||
return (bool)self::where("session_id", $uuid)
|
||||
->update(['update_time' => date("Y-m-d H:i:s"), 'status' => 1]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function getByUuid(string $uuid)
|
||||
{
|
||||
return self::where("session_id", $uuid)->first(['status', 'session_id', 'account_id']);
|
||||
}
|
||||
|
||||
public function account()
|
||||
{
|
||||
return $this->hasOne(Account::class, 'account_id', 'account_id');
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
|
|
@ -9,8 +10,9 @@ declare(strict_types=1);
|
|||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
use Hyperf\Server\Event;
|
||||
use Hyperf\Server\Server;
|
||||
use Hyperf\Server\ServerInterface;
|
||||
use Swoole\Constant;
|
||||
use function Hyperf\Support\env;
|
||||
|
||||
|
|
@ -19,9 +21,9 @@ return [
|
|||
'servers' => [
|
||||
[
|
||||
'name' => 'http',
|
||||
'type' => Server::SERVER_HTTP,
|
||||
'type' => ServerInterface::SERVER_HTTP,
|
||||
'host' => '0.0.0.0',
|
||||
'port' => (int) env('APP_PORT'),
|
||||
'port' => (int)env('APP_PORT', 9501),
|
||||
'sock_type' => SWOOLE_SOCK_TCP,
|
||||
'callbacks' => [
|
||||
Event::ON_REQUEST => [Hyperf\HttpServer\Server::class, 'onRequest'],
|
||||
|
|
@ -30,6 +32,20 @@ return [
|
|||
'enable_request_lifecycle' => false,
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'ws',
|
||||
'type' => ServerInterface::SERVER_WEBSOCKET,
|
||||
'host' => '0.0.0.0',
|
||||
'port' => (int)env('WS_PORT', 9502),
|
||||
'sock_type' => SWOOLE_SOCK_TCP,
|
||||
'callbacks' => [
|
||||
Event::ON_HAND_SHAKE => [Hyperf\WebSocketServer\Server::class, 'onHandShake'],
|
||||
Event::ON_MESSAGE => [Hyperf\WebSocketServer\Server::class, 'onMessage'],
|
||||
Event::ON_CLOSE => [Hyperf\WebSocketServer\Server::class, 'onClose'],
|
||||
Event::ON_START => [App\Event\WebSocketServer::class, 'onStart'],
|
||||
Event::ON_SHUTDOWN => [App\Event\WebSocketServer::class, 'onShutdown'],
|
||||
],
|
||||
],
|
||||
],
|
||||
'settings' => [
|
||||
Constant::OPTION_ENABLE_COROUTINE => true,
|
||||
|
|
@ -47,6 +63,7 @@ return [
|
|||
'enable_static_handler' => true,
|
||||
],
|
||||
'callbacks' => [
|
||||
Event::ON_BEFORE_START => [Hyperf\Framework\Bootstrap\ServerStartCallback::class, 'beforeStart'],
|
||||
Event::ON_WORKER_START => [Hyperf\Framework\Bootstrap\WorkerStartCallback::class, 'onWorkerStart'],
|
||||
Event::ON_PIPE_MESSAGE => [Hyperf\Framework\Bootstrap\PipeMessageCallback::class, 'onPipeMessage'],
|
||||
Event::ON_WORKER_EXIT => [Hyperf\Framework\Bootstrap\WorkerExitCallback::class, 'onWorkerExit'],
|
||||
|
|
|
|||
|
|
@ -16,3 +16,7 @@ Router::addRoute(['GET', 'POST', 'HEAD'], '/', 'App\Controller\IndexController@i
|
|||
Router::get('/favicon.ico', function () {
|
||||
return '';
|
||||
});
|
||||
|
||||
Router::addServer('ws', function () {
|
||||
Router::get('/ws', 'App\Event\WebSocket');
|
||||
});
|
||||
Loading…
Reference in New Issue