定时任务
This commit is contained in:
parent
78ada91ff5
commit
0437334bb8
|
|
@ -7,6 +7,7 @@ namespace App\Controller\Admin;
|
|||
|
||||
use App\Controller\AbstractController;
|
||||
use Hyperf\Contract\LengthAwarePaginatorInterface;
|
||||
use Hyperf\Database\Model\Collection;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
abstract class Base extends AbstractController
|
||||
|
|
@ -29,12 +30,12 @@ abstract class Base extends AbstractController
|
|||
/**
|
||||
* Author: cfn <cfn@leapy.cn>
|
||||
* @param array|string $msg
|
||||
* @param array|LengthAwarePaginatorInterface|null $data
|
||||
* @param LengthAwarePaginatorInterface|Collection|array|null $data
|
||||
* @param null $count
|
||||
* @param null $summary
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function success(array|string $msg = "success", LengthAwarePaginatorInterface|array|null $data = null, $count = null, $summary = null)
|
||||
public function success(array|string $msg = "success", LengthAwarePaginatorInterface|Collection|array|null $data = null, $count = null, $summary = null)
|
||||
{
|
||||
if (!is_string($msg)) {
|
||||
$data = $msg;
|
||||
|
|
@ -44,6 +45,9 @@ abstract class Base extends AbstractController
|
|||
$count = $data->total();
|
||||
$data = $data->items();
|
||||
}
|
||||
if ($data instanceof Collection) {
|
||||
$data = $data->toArray();
|
||||
}
|
||||
return $this->response(0, $msg, $data, $count, $summary);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,11 +6,20 @@
|
|||
namespace App\Controller\Admin;
|
||||
|
||||
use App\Annotation\Auth;
|
||||
use App\Model\Menu as mModel;
|
||||
use App\Model\Account as aModel;
|
||||
use App\Model\Crontab as cModel;
|
||||
use App\Model\CrontabLog as clModel;
|
||||
use App\Model\Dept as dModel;
|
||||
use App\Model\Menu as mModel;
|
||||
use App\Model\Online;
|
||||
use App\Model\Online as oModel;
|
||||
use App\Model\Post as pModel;
|
||||
use App\Model\Role as rModel;
|
||||
use App\Request\Account as aRequest;
|
||||
use App\Request\Dept as dRequest;
|
||||
use App\Request\Menu as mRequest;
|
||||
use App\Request\Post as pRequest;
|
||||
use App\Request\Role as rRequest;
|
||||
use App\Utils\AppInfoHelper;
|
||||
use App\Utils\CpuHelper;
|
||||
use App\Utils\DiskInfoHelper;
|
||||
|
|
@ -25,15 +34,6 @@ use Hyperf\HttpServer\Annotation\DeleteMapping;
|
|||
use Hyperf\HttpServer\Annotation\GetMapping;
|
||||
use Hyperf\HttpServer\Annotation\PostMapping;
|
||||
use Hyperf\HttpServer\Annotation\PutMapping;
|
||||
use App\Request\Menu as mRequest;
|
||||
use App\Request\Role as rRequest;
|
||||
use App\Request\Dept as dRequest;
|
||||
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
|
||||
|
|
@ -401,6 +401,8 @@ class System extends Base
|
|||
#[Auth(needAuth: false)]
|
||||
public function crontabOption()
|
||||
{
|
||||
var_dump(cModel::options());
|
||||
|
||||
return $this->success("任务列表", cModel::options());
|
||||
}
|
||||
|
||||
|
|
@ -408,18 +410,24 @@ class System extends Base
|
|||
#[Auth(auth: "crontab:add")]
|
||||
public function crontabAdd()
|
||||
{
|
||||
$data = Param::only(['crontab_name' => '', 'rule', 'callback', 'memo', 'singleton', 'params', 'skip_log', 'enable' => 1]);
|
||||
$data = Param::only(['crontab_name' => '', 'rule', 'callback', 'memo', 'singleton', 'skip_log', 'enable' => 1]);
|
||||
$res = cModel::add($data);
|
||||
return $res ? $this->success("操作成功") : $this->error("操作失败");
|
||||
if ($res) {
|
||||
return $this->success("操作成功");
|
||||
}
|
||||
return $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]);
|
||||
$data = Param::only(['crontab_id' => '', 'crontab_name' => '', 'rule', 'callback', 'memo', 'singleton', 'skip_log', 'enable' => 1]);
|
||||
$res = cModel::edit($data);
|
||||
return $res ? $this->success("操作成功") : $this->error("操作失败");
|
||||
if ($res) {
|
||||
return $this->success("操作成功");
|
||||
}
|
||||
return $this->error("操作失败");
|
||||
}
|
||||
|
||||
#[DeleteMapping(path: "crontab/del")]
|
||||
|
|
@ -429,6 +437,35 @@ class System extends Base
|
|||
$ids = $this->request->input("ids", "");
|
||||
if (!$ids) return $this->error("操作失败");
|
||||
$res = cModel::del($ids);
|
||||
if ($res) {
|
||||
return $this->success("操作成功");
|
||||
}
|
||||
return $this->error("操作失败");
|
||||
}
|
||||
|
||||
#[GetMapping(path: "crontab_log/list")]
|
||||
#[Auth(auth: "crontab:log:list")]
|
||||
public function crontabLogList()
|
||||
{
|
||||
$param = Param::only(['crontab_id', 'limit' => 10]);
|
||||
return $this->success("任务日志列表", clModel::list($param));
|
||||
}
|
||||
|
||||
#[DeleteMapping(path: "crontab_log/del")]
|
||||
#[Auth(auth: "crontab:log:del")]
|
||||
public function crontabLogDel()
|
||||
{
|
||||
$param = Param::only(['ids']);
|
||||
$res = clModel::del($param['ids']);
|
||||
return $res ? $this->success("操作成功") : $this->error("操作失败");
|
||||
}
|
||||
|
||||
#[DeleteMapping(path: "crontab_log/remove_all")]
|
||||
#[Auth(auth: "crontab:log:empty")]
|
||||
public function crontabLogEmpty()
|
||||
{
|
||||
$param = Param::only(['crontab_id']);
|
||||
$res = clModel::removeAll($param);
|
||||
return $res ? $this->success("操作成功") : $this->error("操作失败");
|
||||
}
|
||||
}
|
||||
|
|
@ -3,9 +3,11 @@
|
|||
namespace App\Listener;
|
||||
|
||||
use App\Utils\AppInfoHelper;
|
||||
use Hyperf\Event\Annotation\Listener;
|
||||
use Hyperf\Event\Contract\ListenerInterface;
|
||||
use Hyperf\Framework\Event\BootApplication;
|
||||
|
||||
#[Listener]
|
||||
class AppBootListener implements ListenerInterface
|
||||
{
|
||||
public function listen(): array
|
||||
|
|
|
|||
|
|
@ -0,0 +1,93 @@
|
|||
<?php
|
||||
|
||||
namespace App\Listener;
|
||||
|
||||
use Hyperf\Event\Annotation\Listener;
|
||||
use Hyperf\Crontab\Event\AfterExecute;
|
||||
use Hyperf\Crontab\Event\FailToExecute;
|
||||
use Hyperf\Crontab\Event\BeforeExecute;
|
||||
use Hyperf\Event\Contract\ListenerInterface;
|
||||
use App\Model\CrontabLog as clModel;
|
||||
|
||||
#[Listener]
|
||||
class CrontabLogListener implements ListenerInterface
|
||||
{
|
||||
private array $starts = [];
|
||||
|
||||
public function listen(): array
|
||||
{
|
||||
return [
|
||||
BeforeExecute::class,
|
||||
AfterExecute::class,
|
||||
FailToExecute::class,
|
||||
];
|
||||
}
|
||||
|
||||
public function process(object $event): void
|
||||
{
|
||||
if ($event instanceof BeforeExecute) {
|
||||
$key = $this->getKey($event);
|
||||
$this->starts[$key] = microtime(true);
|
||||
}
|
||||
|
||||
if ($event instanceof AfterExecute) {
|
||||
$options = $event->crontab->getOptions();
|
||||
$key = $this->getKey($event);
|
||||
if (isset($this->starts[$key])) {
|
||||
$start = $this->starts[$key];
|
||||
// 清理开始时间 防止内存泄露
|
||||
unset($this->starts[$key]);
|
||||
}
|
||||
if ($options['skip_log']) {
|
||||
clModel::insert([
|
||||
'crontab_id' => $event->crontab->getName(),
|
||||
'crontab_name' => $event->crontab->getMemo(),
|
||||
'callback' => $options['callback'],
|
||||
'duration' => microtime(true) - ($start ?? microtime(true)),
|
||||
'status' => 1,
|
||||
'result' => '执行成功',
|
||||
'create_time' => date("Y-m-d H:i:s")
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($event instanceof FailToExecute) {
|
||||
$options = $event->crontab->getOptions();
|
||||
$key = $this->getKey($event);
|
||||
if (isset($this->starts[$key])) {
|
||||
$start = $this->starts[$key];
|
||||
// 清理开始时间 防止内存泄露
|
||||
unset($this->starts[$key]);
|
||||
}
|
||||
if ($options['skip_log']) {
|
||||
clModel::insert([
|
||||
'crontab_id' => $event->crontab->getName(),
|
||||
'crontab_name' => $event->crontab->getMemo(),
|
||||
'callback' => $options['callback'],
|
||||
'duration' => microtime(true) - ($start ?? microtime(true)),
|
||||
'status' => $event->throwable ? 0 : 1,
|
||||
'result' => $event->throwable?->getMessage() ?? '执行失败',
|
||||
'create_time' => date("Y-m-d H:i:s")
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function getKey(object $event): string
|
||||
{
|
||||
if (property_exists($event, 'crontab') && $event->crontab) {
|
||||
try {
|
||||
$name = method_exists($event->crontab, 'getName')
|
||||
? $event->crontab->getName()
|
||||
: ($event->crontab->name ?? null);
|
||||
if ($name) {
|
||||
return (string) $name;
|
||||
}
|
||||
return spl_object_hash($event->crontab);
|
||||
} catch (\Throwable $e) {
|
||||
return spl_object_hash($event->crontab);
|
||||
}
|
||||
}
|
||||
return spl_object_hash($event);
|
||||
}
|
||||
}
|
||||
|
|
@ -8,8 +8,10 @@ namespace App\Listener;
|
|||
use App\Event\LogEvent;
|
||||
use App\Model\Online;
|
||||
use App\Utils\Ip;
|
||||
use Hyperf\Event\Annotation\Listener;
|
||||
use Hyperf\Event\Contract\ListenerInterface;
|
||||
|
||||
#[Listener]
|
||||
class LogHandleListener implements ListenerInterface
|
||||
{
|
||||
public function listen(): array
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ namespace App\Model;
|
|||
* @property string $rule
|
||||
* @property string $callback
|
||||
* @property string $memo
|
||||
* @property string $params
|
||||
* @property int $enable
|
||||
* @property int $singleton
|
||||
* @property int $skip_log
|
||||
|
|
@ -46,7 +45,7 @@ class Crontab extends Model
|
|||
->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'])
|
||||
->select(['crontab_id', 'crontab_name', 'enable', 'singleton', 'skip_log', 'rule', 'callback', 'memo', 'create_time'])
|
||||
->orderByDesc('crontab_id')
|
||||
->paginate((int)$param['limit']);
|
||||
}
|
||||
|
|
@ -54,9 +53,17 @@ class Crontab extends Model
|
|||
public static function options()
|
||||
{
|
||||
return self::query()
|
||||
->orderByDesc('crontab_id')
|
||||
->orderByDesc('crontab_id')
|
||||
->select(['crontab_id', 'crontab_name'])
|
||||
->orderByDesc('crontab_id')
|
||||
->get();
|
||||
}
|
||||
|
||||
public static function queryEnable()
|
||||
{
|
||||
return self::query()
|
||||
->where('enable', 1)
|
||||
->orderByDesc('crontab_id')
|
||||
->select(['crontab_id', 'crontab_name', 'enable', 'singleton', 'skip_log', 'rule', 'callback', 'memo', 'create_time'])
|
||||
->get();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,15 +5,15 @@ 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
|
||||
* @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
|
||||
{
|
||||
|
|
@ -22,6 +22,8 @@ class CrontabLog extends Model
|
|||
*/
|
||||
protected ?string $table = 'crontab_log';
|
||||
|
||||
protected string $primaryKey = 'log_id';
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*/
|
||||
|
|
@ -31,4 +33,24 @@ class CrontabLog extends Model
|
|||
* The attributes that should be cast to native types.
|
||||
*/
|
||||
protected array $casts = ['log_id' => 'integer', 'crontab_id' => 'integer', 'status' => 'integer'];
|
||||
|
||||
public static function list(array $param)
|
||||
{
|
||||
return self::query()
|
||||
->when(isset($param['crontab_id']), function ($query) use ($param) {
|
||||
$query->where('crontab_id', $param['crontab_id']);
|
||||
})
|
||||
->select(['crontab_id', 'crontab_name', 'log_id', 'callback', 'duration', 'status', 'result', 'create_time'])
|
||||
->orderByDesc('log_id')
|
||||
->paginate((int)$param['limit']);
|
||||
}
|
||||
|
||||
public static function removeAll($param)
|
||||
{
|
||||
return self::query()
|
||||
->when(isset($param['crontab_id']), function ($query) use ($param) {
|
||||
$query->where('crontab_id', $param['crontab_id']);
|
||||
})
|
||||
->delete();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
namespace App\Process;
|
||||
|
||||
use App\Model\Crontab as cModel;
|
||||
use App\Utils\Log;
|
||||
use Hyperf\Crontab\Crontab;
|
||||
use Hyperf\Crontab\Process\CrontabDispatcherProcess as BaseDispatcher;
|
||||
use Hyperf\Crontab\CrontabManager;
|
||||
use Psr\Container\ContainerInterface;
|
||||
|
||||
class CrontabDispatcherProcess extends BaseDispatcher
|
||||
{
|
||||
public function __construct(ContainerInterface $container, CrontabManager $manager)
|
||||
{
|
||||
$this->registerDatabaseTasks($manager);
|
||||
parent::__construct($container);
|
||||
}
|
||||
|
||||
protected function registerDatabaseTasks(CrontabManager $manager): void
|
||||
{
|
||||
try {
|
||||
// 获取所有启用的定时任务
|
||||
$tasks = cModel::queryEnable();
|
||||
foreach ($tasks as $task) {
|
||||
$crontab = (new Crontab())
|
||||
->setName($task->crontab_id)
|
||||
->setMemo($task->crontab_name)
|
||||
->setEnable((bool)$task->enable)
|
||||
->setRule($task->rule)
|
||||
->setSingleton((bool)$task->singleton)
|
||||
->setOnOneServer(true)
|
||||
->setOptions(['skip_log' => $task->skip_log, 'callback' => $task->callback])
|
||||
->setCallback(explode('@', $task->callback));
|
||||
$manager->register($crontab);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
// 记录日志
|
||||
Log::record('动态加载crontab任务失败: ' . $e->getMessage(), 'error');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
class DemoService
|
||||
{
|
||||
public function test()
|
||||
{
|
||||
sleep(rand(1,10));
|
||||
var_dump(date('Y-m-d H:i:s'));
|
||||
}
|
||||
}
|
||||
|
|
@ -3,12 +3,12 @@
|
|||
* Author: cfn <cfn@leapy.cn>
|
||||
*/
|
||||
|
||||
namespace App\Event;
|
||||
namespace App\Service;
|
||||
|
||||
|
||||
use App\Model\Online;
|
||||
|
||||
class WebSocketServer
|
||||
class WebSocketService
|
||||
{
|
||||
// 访问启动时,关闭之前的连接
|
||||
public function onStart(): void
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
<?php
|
||||
/**
|
||||
* Author: cfn <cfn@leapy.cn>
|
||||
*/
|
||||
|
||||
namespace App\Utils;
|
||||
|
||||
use Hyperf\Context\ApplicationContext;
|
||||
use Hyperf\Logger\Logger;
|
||||
use Hyperf\Logger\LoggerFactory;
|
||||
use Monolog\Formatter\LineFormatter;
|
||||
use Monolog\Handler\StreamHandler;
|
||||
use Monolog\Level;
|
||||
|
||||
class Log
|
||||
{
|
||||
public static function getInstance(string $name = 'app')
|
||||
{
|
||||
return ApplicationContext::getContainer()->get(LoggerFactory::class)->get($name);
|
||||
}
|
||||
|
||||
public static function __callStatic($name, $arguments)
|
||||
{
|
||||
self::getInstance()->$name(...$arguments);
|
||||
}
|
||||
|
||||
public static function record($message, string $filename = "info")
|
||||
{
|
||||
self::_save($message, $filename);
|
||||
}
|
||||
|
||||
private static function _save($message, string $filename = 'log')
|
||||
{
|
||||
$log = new Logger('app');
|
||||
|
||||
$filename = $filename . '.log';
|
||||
|
||||
$path = 'runtime/logs/' . date('Y') . "/" . date("m");
|
||||
|
||||
// 有时候运维没给号权限,容易导致写入日志失败
|
||||
self::mkDirs($path);
|
||||
$path = $path . '/' . date("d") . "_". $filename;
|
||||
|
||||
if (gettype($message) == 'array') {
|
||||
$message = json_encode($message, true);
|
||||
}
|
||||
|
||||
$microtime = microtime();
|
||||
$message = '[' . substr($microtime, 0, 8) . '] ' . $message;// 记录毫秒时间
|
||||
|
||||
// finally, create a formatter
|
||||
$formatter = new LineFormatter("[%datetime%] %message% %context%\n", "Y-m-d H:i:s");
|
||||
|
||||
$stream = new StreamHandler($path, Level::Info);
|
||||
$stream->setFormatter($formatter);
|
||||
|
||||
$log->pushHandler($stream);
|
||||
$log->info($message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 给日志文件夹权限
|
||||
* @param string $dir
|
||||
* @param int $mode
|
||||
* @return bool
|
||||
*/
|
||||
private static function mkDirs(string $dir, int $mode = 0777): bool
|
||||
{
|
||||
if (is_dir($dir) || @mkdir($dir, $mode)) {
|
||||
return TRUE;
|
||||
}
|
||||
if (!self::mkdirs(dirname($dir), $mode)) {
|
||||
return FALSE;
|
||||
}
|
||||
return @mkdir($dir, $mode);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
// 是否开启定时任务
|
||||
'enable' => true,
|
||||
];
|
||||
|
|
@ -11,7 +11,5 @@ declare(strict_types=1);
|
|||
*/
|
||||
return [
|
||||
Hyperf\ExceptionHandler\Listener\ErrorExceptionHandler::class,
|
||||
Hyperf\Command\Listener\FailToHandleListener::class,
|
||||
App\Listener\LogHandleListener::class,
|
||||
App\Listener\AppBootListener::class,
|
||||
Hyperf\Command\Listener\FailToHandleListener::class
|
||||
];
|
||||
|
|
|
|||
|
|
@ -10,4 +10,5 @@ declare(strict_types=1);
|
|||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
return [
|
||||
App\Process\CrontabDispatcherProcess::class,
|
||||
];
|
||||
|
|
|
|||
|
|
@ -42,8 +42,8 @@ return [
|
|||
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'],
|
||||
Event::ON_START => [App\Service\WebSocketService::class, 'onStart'],
|
||||
Event::ON_SHUTDOWN => [App\Service\WebSocketService::class, 'onShutdown'],
|
||||
],
|
||||
],
|
||||
],
|
||||
|
|
|
|||
Loading…
Reference in New Issue