mirror of
https://gitee.com/ulthon/ulthon_admin.git
synced 2026-07-09 03:22:51 +08:00
feat(timer): 新增 run_type 调度、host_id 投递和日志清理
T7: TimerBase shouldExecuteTask() - main/auto/all/manual/disabled modes
with two-phase DB row lock for auto mode
T8: TimerControllerBase - host_id param, logStart/logEnd methods
TimerServiceBase - inject host_id into site URLs
T9: TimerLogClean command - php think admin:timer:log:clean --days=30
This commit is contained in:
12
app/admin/model/SystemTimerLog.php
Normal file
12
app/admin/model/SystemTimerLog.php
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace app\admin\model;
|
||||||
|
|
||||||
|
use think\Model;
|
||||||
|
|
||||||
|
class SystemTimerLog extends Model
|
||||||
|
{
|
||||||
|
protected $name = 'system_timer_log';
|
||||||
|
|
||||||
|
protected $autoWriteTimestamp = true;
|
||||||
|
}
|
||||||
11
app/common/command/admin/timer/TimerLogClean.php
Normal file
11
app/common/command/admin/timer/TimerLogClean.php
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace app\common\command\admin\timer;
|
||||||
|
|
||||||
|
use base\common\command\admin\timer\TimerLogCleanBase;
|
||||||
|
|
||||||
|
class TimerLogClean extends TimerLogCleanBase
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -14,6 +14,7 @@ use think\console\input\Option;
|
|||||||
use think\console\Output;
|
use think\console\Output;
|
||||||
use think\facade\Cache;
|
use think\facade\Cache;
|
||||||
use think\facade\Config;
|
use think\facade\Config;
|
||||||
|
use think\facade\Db;
|
||||||
use think\facade\Log;
|
use think\facade\Log;
|
||||||
use Workerman\Http\Client as HttpClient;
|
use Workerman\Http\Client as HttpClient;
|
||||||
use Workerman\Timer;
|
use Workerman\Timer;
|
||||||
@@ -100,6 +101,112 @@ class TimerBase extends Command
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 基于 run_type 判断当前节点是否应执行该任务(仅对 site 类型生效).
|
||||||
|
* 在 Cache 节流之后、实际执行之前调用。
|
||||||
|
*
|
||||||
|
* @param array|\ArrayAccess $task_item 任务实例(含 name/type/frequency 等)
|
||||||
|
* @return bool true=应执行 false=跳过
|
||||||
|
*/
|
||||||
|
protected function shouldExecuteTask($task_item): bool
|
||||||
|
{
|
||||||
|
// call 类型任务不受 run_type 调度层影响
|
||||||
|
if (($task_item['type'] ?? '') === 'call') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$task_name = $task_item['name'] ?? '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
$config = Db::name('system_timer_config')
|
||||||
|
->where('task_name', $task_name)
|
||||||
|
->find();
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
Log::warning('shouldExecuteTask: 查询 system_timer_config 失败 - ' . $e->getMessage());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 无数据库记录时不阻塞(回退到默认行为)
|
||||||
|
if (empty($config)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// status=0 表示任务已禁用
|
||||||
|
if (isset($config['status']) && (int) $config['status'] === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$run_type = $config['run_type'] ?? 'auto';
|
||||||
|
|
||||||
|
switch ($run_type) {
|
||||||
|
case 'main':
|
||||||
|
// 仅主节点执行
|
||||||
|
$master_node = HostService::getMasterNode();
|
||||||
|
$node_id = HostService::getNodeId();
|
||||||
|
|
||||||
|
return ($master_node === $node_id);
|
||||||
|
|
||||||
|
case 'auto':
|
||||||
|
// 两阶段 DB 行锁竞争:竞争成功后释放锁,再执行任务
|
||||||
|
Db::startTrans();
|
||||||
|
try {
|
||||||
|
$row = Db::name('system_timer_config')
|
||||||
|
->where('task_name', $task_name)
|
||||||
|
->lock(true)
|
||||||
|
->find();
|
||||||
|
|
||||||
|
if ($row && !empty($row['last_execute_time']) && $row['last_execute_time'] > 0) {
|
||||||
|
if ((time() - (int) $row['last_execute_time']) < ($task_item['frequency'] ?? 0)) {
|
||||||
|
Db::commit();
|
||||||
|
|
||||||
|
return false; // 另一个节点刚执行过
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Db::name('system_timer_config')
|
||||||
|
->where('task_name', $task_name)
|
||||||
|
->update([
|
||||||
|
'last_execute_node' => HostService::getNodeId(),
|
||||||
|
'last_execute_time' => time(),
|
||||||
|
'update_time' => time(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
Db::commit();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
Db::rollback();
|
||||||
|
Log::warning('shouldExecuteTask auto mode exception: ' . $e->getMessage());
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'all':
|
||||||
|
// 所有节点都执行
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case 'manual':
|
||||||
|
// 手动触发:检查 manual_trigger=1,执行一次后重置为 0
|
||||||
|
if (!empty($config['manual_trigger'])) {
|
||||||
|
Db::name('system_timer_config')
|
||||||
|
->where('task_name', $task_name)
|
||||||
|
->update([
|
||||||
|
'manual_trigger' => 0,
|
||||||
|
'update_time' => time(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// 未知 run_type 不阻塞
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function runParallel()
|
public function runParallel()
|
||||||
{
|
{
|
||||||
// 重新构造命令行参数,以便兼容workerman的命令
|
// 重新构造命令行参数,以便兼容workerman的命令
|
||||||
@@ -152,6 +259,12 @@ class TimerBase extends Command
|
|||||||
$request_item['is_running'] = true;
|
$request_item['is_running'] = true;
|
||||||
$request_item['last_run_time'] = time();
|
$request_item['last_run_time'] = time();
|
||||||
|
|
||||||
|
// run_type 调度检查(节流之后、实际执行之前)
|
||||||
|
if (!$this->shouldExecuteTask($request_item)) {
|
||||||
|
$request_item['is_running'] = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|
||||||
$http->request($host . $request_item['target'], [
|
$http->request($host . $request_item['target'], [
|
||||||
@@ -231,6 +344,11 @@ class TimerBase extends Command
|
|||||||
|
|
||||||
Cache::tag($cache_tag)->set($cache_key, time());
|
Cache::tag($cache_tag)->set($cache_key, time());
|
||||||
|
|
||||||
|
// run_type 调度检查(Cache 节流之后、实际执行之前)
|
||||||
|
if (!$this->shouldExecuteTask($request_item)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
$output->writeln(date('Y-m-d H:i:s') . ': build site request async: ' . $request_item['target']);
|
$output->writeln(date('Y-m-d H:i:s') . ': build site request async: ' . $request_item['target']);
|
||||||
$list_promises[$request_item['name']] = $client->getAsync($request_item['target']);
|
$list_promises[$request_item['name']] = $client->getAsync($request_item['target']);
|
||||||
}
|
}
|
||||||
|
|||||||
34
extend/base/common/command/admin/timer/TimerLogCleanBase.php
Normal file
34
extend/base/common/command/admin/timer/TimerLogCleanBase.php
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace base\common\command\admin\timer;
|
||||||
|
|
||||||
|
use app\common\console\Command;
|
||||||
|
use think\console\Input;
|
||||||
|
use think\console\input\Option;
|
||||||
|
use think\console\Output;
|
||||||
|
use think\facade\Db;
|
||||||
|
|
||||||
|
class TimerLogCleanBase extends Command
|
||||||
|
{
|
||||||
|
protected function configure()
|
||||||
|
{
|
||||||
|
parent::configure();
|
||||||
|
$this->setName('admin:timer:log:clean')
|
||||||
|
->addOption('days', 'd', Option::VALUE_OPTIONAL, '清理多少天前的日志', 30)
|
||||||
|
->setDescription('清理定时器执行日志');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function execute(Input $input, Output $output)
|
||||||
|
{
|
||||||
|
$days = (int) $input->getOption('days');
|
||||||
|
$threshold = time() - ($days * 86400);
|
||||||
|
|
||||||
|
$count = Db::name('system_timer_log')
|
||||||
|
->where('create_time', '<', $threshold)
|
||||||
|
->delete();
|
||||||
|
|
||||||
|
$output->writeln("已清理 {$count} 条超过 {$days} 天的定时器执行日志。");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace base\common\controller;
|
namespace base\common\controller;
|
||||||
|
|
||||||
|
use app\admin\model\SystemTimerLog;
|
||||||
use app\common\controller\ToolsController;
|
use app\common\controller\ToolsController;
|
||||||
use think\facade\Cache;
|
use think\facade\Cache;
|
||||||
|
|
||||||
@@ -13,6 +14,8 @@ class TimerControllerBase extends ToolsController
|
|||||||
|
|
||||||
protected $concurrencyId = 0;
|
protected $concurrencyId = 0;
|
||||||
|
|
||||||
|
protected $hostId = null;
|
||||||
|
|
||||||
public function initialize()
|
public function initialize()
|
||||||
{
|
{
|
||||||
parent::initialize();
|
parent::initialize();
|
||||||
@@ -28,6 +31,8 @@ class TimerControllerBase extends ToolsController
|
|||||||
$this->error('concurrency count error');
|
$this->error('concurrency count error');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->hostId = $this->request->param('host_id', '');
|
||||||
|
|
||||||
if (is_int($this->frequency)) {
|
if (is_int($this->frequency)) {
|
||||||
$this->protectVisit($this->frequency);
|
$this->protectVisit($this->frequency);
|
||||||
}
|
}
|
||||||
@@ -47,4 +52,35 @@ class TimerControllerBase extends ToolsController
|
|||||||
|
|
||||||
Cache::tag($cache_tag)->set($cache_key, time());
|
Cache::tag($cache_tag)->set($cache_key, time());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function logStart(string $taskName): int
|
||||||
|
{
|
||||||
|
$data = [
|
||||||
|
'task_name' => $taskName,
|
||||||
|
'node_id' => $this->hostId,
|
||||||
|
'run_type' => '',
|
||||||
|
'start_time' => time(),
|
||||||
|
'end_time' => 0,
|
||||||
|
'duration' => 0,
|
||||||
|
'status' => 'running',
|
||||||
|
'error_message' => null,
|
||||||
|
'concurrency_id' => $this->concurrencyId,
|
||||||
|
'create_time' => time(),
|
||||||
|
];
|
||||||
|
$log = SystemTimerLog::create($data);
|
||||||
|
return $log->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function logEnd(int $logId, string $status = 'success', ?string $errorMessage = null): void
|
||||||
|
{
|
||||||
|
$log = SystemTimerLog::find($logId);
|
||||||
|
if ($log) {
|
||||||
|
$endTime = time();
|
||||||
|
$log->end_time = $endTime;
|
||||||
|
$log->duration = ($endTime - $log->start_time) * 1000; // ms
|
||||||
|
$log->status = $status;
|
||||||
|
$log->error_message = $errorMessage;
|
||||||
|
$log->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ namespace base\common\service;
|
|||||||
|
|
||||||
use app\admin\model\SystemTimerConfig;
|
use app\admin\model\SystemTimerConfig;
|
||||||
use app\common\model\VirtualModel;
|
use app\common\model\VirtualModel;
|
||||||
|
use app\common\service\HostService;
|
||||||
|
|
||||||
class TimerServiceBase
|
class TimerServiceBase
|
||||||
{
|
{
|
||||||
@@ -117,6 +118,7 @@ class TimerServiceBase
|
|||||||
$params = [
|
$params = [
|
||||||
'concurrency_id' => $i,
|
'concurrency_id' => $i,
|
||||||
'concurrency_count' => $concurrency,
|
'concurrency_count' => $concurrency,
|
||||||
|
'host_id' => HostService::getNodeId(),
|
||||||
];
|
];
|
||||||
// 处理target
|
// 处理target
|
||||||
if ($config_item['type'] == 'site') {
|
if ($config_item['type'] == 'site') {
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ use app\common\command\admin\role\AdminRolePermissionAssign;
|
|||||||
use app\common\command\admin\role\AdminRolePermissionList;
|
use app\common\command\admin\role\AdminRolePermissionList;
|
||||||
use app\common\command\admin\role\AdminRolePermissionRevoke;
|
use app\common\command\admin\role\AdminRolePermissionRevoke;
|
||||||
use app\common\command\admin\stack\AdminStackMode;
|
use app\common\command\admin\stack\AdminStackMode;
|
||||||
|
use app\common\command\admin\timer\TimerLogClean;
|
||||||
use app\common\command\admin\user\AdminUserRoleAssign;
|
use app\common\command\admin\user\AdminUserRoleAssign;
|
||||||
use app\common\command\admin\user\AdminUserRoleList;
|
use app\common\command\admin\user\AdminUserRoleList;
|
||||||
use app\common\command\admin\user\AdminUserRoleRevoke;
|
use app\common\command\admin\user\AdminUserRoleRevoke;
|
||||||
@@ -116,6 +117,7 @@ class UlthonAdminService extends Service
|
|||||||
AdminRolePermissionRevoke::class,
|
AdminRolePermissionRevoke::class,
|
||||||
AdminRolePermissionList::class,
|
AdminRolePermissionList::class,
|
||||||
AdminStackMode::class,
|
AdminStackMode::class,
|
||||||
|
TimerLogClean::class,
|
||||||
AdminUserRoleAssign::class,
|
AdminUserRoleAssign::class,
|
||||||
AdminUserRoleRevoke::class,
|
AdminUserRoleRevoke::class,
|
||||||
AdminUserRoleList::class,
|
AdminUserRoleList::class,
|
||||||
|
|||||||
Reference in New Issue
Block a user