mirror of
https://github.com/lkddi/Xboard.git
synced 2026-04-05 12:40:52 +08:00
feat: add multiple hooks, pligun schedule support ,add hook:list artisan command
This commit is contained in:
42
app/Console/Commands/HookList.php
Normal file
42
app/Console/Commands/HookList.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class HookList extends Command
|
||||
{
|
||||
protected $signature = 'hook:list';
|
||||
protected $description = '列出系统支持的所有 hooks(静态扫描代码)';
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$paths = [base_path('app'), base_path('plugins')];
|
||||
$hooks = collect();
|
||||
$pattern = '/HookManager::(call|filter|register|registerFilter)\([\'\"]([a-zA-Z0-9_.-]+)[\'\"]/';
|
||||
|
||||
foreach ($paths as $path) {
|
||||
$files = collect(
|
||||
is_dir($path) ? (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path))) : []
|
||||
)->filter(fn($f) => Str::endsWith($f, '.php'));
|
||||
foreach ($files as $file) {
|
||||
$content = @file_get_contents($file);
|
||||
if ($content && preg_match_all($pattern, $content, $matches)) {
|
||||
foreach ($matches[2] as $hook) {
|
||||
$hooks->push($hook);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$hooks = $hooks->unique()->sort()->values();
|
||||
if ($hooks->isEmpty()) {
|
||||
$this->info('未扫描到任何 hook');
|
||||
} else {
|
||||
$this->info('All Supported Hooks:');
|
||||
foreach ($hooks as $hook) {
|
||||
$this->line(' ' . $hook);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Console;
|
||||
|
||||
use App\Services\Plugin\PluginManager;
|
||||
use App\Utils\CacheKey;
|
||||
use Illuminate\Console\Scheduling\Schedule;
|
||||
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
||||
@@ -25,7 +26,7 @@ class Kernel extends ConsoleKernel
|
||||
* @param \Illuminate\Console\Scheduling\Schedule $schedule
|
||||
* @return void
|
||||
*/
|
||||
protected function schedule(Schedule $schedule)
|
||||
protected function schedule(Schedule $schedule): void
|
||||
{
|
||||
Cache::put(CacheKey::get('SCHEDULE_LAST_CHECK_AT', null), time());
|
||||
// v2board
|
||||
@@ -48,7 +49,10 @@ class Kernel extends ConsoleKernel
|
||||
// 每分钟清理过期的在线状态
|
||||
$schedule->call(function () {
|
||||
app(UserOnlineService::class)->cleanExpiredOnlineStatus();
|
||||
})->everyMinute();
|
||||
})->everyMinute()->name('cleanup:expired-online-status')->onOneServer();
|
||||
|
||||
app(PluginManager::class)->registerPluginSchedules($schedule);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,16 +10,21 @@ use App\Services\OrderService;
|
||||
use App\Services\PaymentService;
|
||||
use App\Services\TelegramService;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Services\Plugin\HookManager;
|
||||
|
||||
class PaymentController extends Controller
|
||||
{
|
||||
public function notify($method, $uuid, Request $request)
|
||||
{
|
||||
HookManager::call('payment.notify.before', [$method, $uuid, $request]);
|
||||
try {
|
||||
$paymentService = new PaymentService($method, null, $uuid);
|
||||
$verify = $paymentService->notify($request->input());
|
||||
if (!$verify)
|
||||
if (!$verify) {
|
||||
HookManager::call('payment.notify.failed', [$method, $uuid, $request]);
|
||||
return $this->fail([422, 'verify error']);
|
||||
}
|
||||
HookManager::call('payment.notify.verified', $verify);
|
||||
if (!$this->handle($verify['trade_no'], $verify['callback_no'])) {
|
||||
return $this->fail([400, 'handle error']);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ use App\Services\TicketService;
|
||||
use App\Utils\Dict;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use App\Services\Plugin\HookManager;
|
||||
|
||||
class TicketController extends Controller
|
||||
{
|
||||
@@ -65,6 +66,7 @@ class TicketController extends Controller
|
||||
throw new \Exception(__('Failed to open ticket'));
|
||||
}
|
||||
DB::commit();
|
||||
HookManager::call('ticket.create.after', $ticket);
|
||||
$this->sendNotify($ticket, $request->input('message'), $request->user()->id);
|
||||
return $this->success(true);
|
||||
}catch(\Exception $e){
|
||||
@@ -103,6 +105,7 @@ class TicketController extends Controller
|
||||
)) {
|
||||
return $this->fail([400, __('Ticket reply failed')]);
|
||||
}
|
||||
HookManager::call('ticket.reply.user.after', [$ticket, $this->getLastMessage($ticket->id)]);
|
||||
$this->sendNotify($ticket, $request->input('message'), $request->user()->id);
|
||||
return $this->success(true);
|
||||
}
|
||||
|
||||
@@ -2,35 +2,36 @@
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Models\Plugin;
|
||||
use App\Services\Plugin\PluginManager;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Middleware to initialize all enabled plugins at the beginning of a request.
|
||||
* It ensures that all plugin hooks, routes, and services are ready.
|
||||
*/
|
||||
class InitializePlugins
|
||||
{
|
||||
protected $pluginManager;
|
||||
protected PluginManager $pluginManager;
|
||||
|
||||
public function __construct(PluginManager $pluginManager)
|
||||
{
|
||||
$this->pluginManager = $pluginManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure $next
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle(Request $request, Closure $next)
|
||||
{
|
||||
try {
|
||||
$plugins = Plugin::query()
|
||||
->where('is_enabled', true)
|
||||
->get();
|
||||
|
||||
foreach ($plugins as $plugin) {
|
||||
$this->pluginManager->enable($plugin->code);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Failed to load plugins: ' . $e->getMessage());
|
||||
}
|
||||
// This single method call handles loading and booting all enabled plugins.
|
||||
// It's safe to call multiple times, as it will only run once per request.
|
||||
$this->pluginManager->initializeEnabledPlugins();
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,19 @@ namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $code
|
||||
* @property string $name
|
||||
* @property string $description
|
||||
* @property string $version
|
||||
* @property string $author
|
||||
* @property string $url
|
||||
* @property string $email
|
||||
* @property string $license
|
||||
* @property string $requires
|
||||
* @property string $config
|
||||
*/
|
||||
class Plugin extends Model
|
||||
{
|
||||
protected $table = 'v2_plugins';
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Services\Auth;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Services\Plugin\HookManager;
|
||||
use App\Utils\CacheKey;
|
||||
use App\Utils\Helper;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
@@ -70,6 +71,7 @@ class LoginService
|
||||
$user->last_login_at = time();
|
||||
$user->save();
|
||||
|
||||
HookManager::call('user.login.after', $user);
|
||||
return [true, $user];
|
||||
}
|
||||
|
||||
@@ -111,6 +113,8 @@ class LoginService
|
||||
return [false, [500, __('Reset failed')]];
|
||||
}
|
||||
|
||||
HookManager::call('user.password.reset.after', $user);
|
||||
|
||||
// 清除邮箱验证码
|
||||
Cache::forget(CacheKey::get('EMAIL_VERIFY_CODE', $email));
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ use App\Models\InviteCode;
|
||||
use App\Models\Plan;
|
||||
use App\Models\User;
|
||||
use App\Services\CaptchaService;
|
||||
use App\Services\Plugin\HookManager;
|
||||
use App\Services\UserService;
|
||||
use App\Utils\CacheKey;
|
||||
use App\Utils\Dict;
|
||||
@@ -141,6 +142,8 @@ class RegisterService
|
||||
return [false, $error];
|
||||
}
|
||||
|
||||
HookManager::call('user.register.before', $request);
|
||||
|
||||
$email = $request->input('email');
|
||||
$password = $request->input('password');
|
||||
$inviteCode = $request->input('invite_code');
|
||||
@@ -164,6 +167,8 @@ class RegisterService
|
||||
return [false, [500, __('Register failed')]];
|
||||
}
|
||||
|
||||
HookManager::call('user.register.after', $user);
|
||||
|
||||
// 清除邮箱验证码
|
||||
if ((int) admin_setting('email_verify', 0)) {
|
||||
Cache::forget(CacheKey::get('EMAIL_VERIFY_CODE', $email));
|
||||
|
||||
@@ -304,6 +304,7 @@ class OrderService
|
||||
public function cancel(): bool
|
||||
{
|
||||
$order = $this->order;
|
||||
HookManager::call('order.cancel.before', $order);
|
||||
try {
|
||||
DB::beginTransaction();
|
||||
$order->status = Order::STATUS_CANCELLED;
|
||||
@@ -317,6 +318,7 @@ class OrderService
|
||||
}
|
||||
}
|
||||
DB::commit();
|
||||
HookManager::call('order.cancel.after', $order);
|
||||
return true;
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
|
||||
@@ -129,7 +129,7 @@ abstract class AbstractPlugin
|
||||
/**
|
||||
* 插件卸载时调用
|
||||
*/
|
||||
public function uninstall(): void
|
||||
public function cleanup(): void
|
||||
{
|
||||
// 插件卸载时的清理逻辑
|
||||
}
|
||||
@@ -181,4 +181,15 @@ abstract class AbstractPlugin
|
||||
{
|
||||
return $this->basePath . '/resources/assets';
|
||||
}
|
||||
|
||||
/**
|
||||
* Register plugin scheduled tasks. Plugins can override this method.
|
||||
*
|
||||
* @param \Illuminate\Console\Scheduling\Schedule $schedule
|
||||
* @return void
|
||||
*/
|
||||
public function schedule(\Illuminate\Console\Scheduling\Schedule $schedule): void
|
||||
{
|
||||
// Plugin can override this method to register scheduled tasks
|
||||
}
|
||||
}
|
||||
@@ -8,11 +8,11 @@ use Illuminate\Support\Facades\App;
|
||||
class HookManager
|
||||
{
|
||||
/**
|
||||
* 存储动作钩子的容器
|
||||
* Container for storing action hooks
|
||||
*
|
||||
* 使用request()存储周期内的钩子数据,避免Octane内存泄漏
|
||||
* Uses request() to store hook data within the cycle to avoid Octane memory leaks
|
||||
*/
|
||||
protected static function getActions(): array
|
||||
public static function getActions(): array
|
||||
{
|
||||
if (!App::has('hook.actions')) {
|
||||
App::instance('hook.actions', []);
|
||||
@@ -22,9 +22,9 @@ class HookManager
|
||||
}
|
||||
|
||||
/**
|
||||
* 存储过滤器钩子的容器
|
||||
* Container for storing filter hooks
|
||||
*/
|
||||
protected static function getFilters(): array
|
||||
public static function getFilters(): array
|
||||
{
|
||||
if (!App::has('hook.filters')) {
|
||||
App::instance('hook.filters', []);
|
||||
@@ -34,7 +34,7 @@ class HookManager
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置动作钩子
|
||||
* Set action hooks
|
||||
*/
|
||||
protected static function setActions(array $actions): void
|
||||
{
|
||||
@@ -42,7 +42,7 @@ class HookManager
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置过滤器钩子
|
||||
* Set filter hooks
|
||||
*/
|
||||
protected static function setFilters(array $filters): void
|
||||
{
|
||||
@@ -50,9 +50,38 @@ class HookManager
|
||||
}
|
||||
|
||||
/**
|
||||
* 拦截响应
|
||||
* Generate unique identifier for callback
|
||||
*
|
||||
* @param callable $callback
|
||||
* @return string
|
||||
*/
|
||||
protected static function getCallableId(callable $callback): string
|
||||
{
|
||||
if (is_object($callback)) {
|
||||
return spl_object_hash($callback);
|
||||
}
|
||||
|
||||
if (is_array($callback) && count($callback) === 2) {
|
||||
[$class, $method] = $callback;
|
||||
|
||||
if (is_object($class)) {
|
||||
return spl_object_hash($class) . '::' . $method;
|
||||
} else {
|
||||
return (string) $class . '::' . $method;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_string($callback)) {
|
||||
return $callback;
|
||||
}
|
||||
|
||||
return 'callable_' . uniqid();
|
||||
}
|
||||
|
||||
/**
|
||||
* Intercept response
|
||||
*
|
||||
* @param SymfonyResponse|string|array $response 新的响应内容
|
||||
* @param SymfonyResponse|string|array $response New response content
|
||||
* @return never
|
||||
* @throws \Exception
|
||||
*/
|
||||
@@ -68,10 +97,10 @@ class HookManager
|
||||
}
|
||||
|
||||
/**
|
||||
* 触发动作钩子
|
||||
* Trigger action hook
|
||||
*
|
||||
* @param string $hook 钩子名称
|
||||
* @param mixed $payload 传递给钩子的数据
|
||||
* @param string $hook Hook name
|
||||
* @param mixed $payload Data passed to hook
|
||||
* @return void
|
||||
*/
|
||||
public static function call(string $hook, mixed $payload = null): void
|
||||
@@ -82,7 +111,6 @@ class HookManager
|
||||
return;
|
||||
}
|
||||
|
||||
// 按优先级排序
|
||||
ksort($actions[$hook]);
|
||||
|
||||
foreach ($actions[$hook] as $callbacks) {
|
||||
@@ -93,11 +121,11 @@ class HookManager
|
||||
}
|
||||
|
||||
/**
|
||||
* 触发过滤器钩子
|
||||
* Trigger filter hook
|
||||
*
|
||||
* @param string $hook 钩子名称
|
||||
* @param mixed $value 要过滤的值
|
||||
* @param mixed ...$args 其他参数
|
||||
* @param string $hook Hook name
|
||||
* @param mixed $value Value to filter
|
||||
* @param mixed ...$args Other parameters
|
||||
* @return mixed
|
||||
*/
|
||||
public static function filter(string $hook, mixed $value, mixed ...$args): mixed
|
||||
@@ -108,7 +136,6 @@ class HookManager
|
||||
return $value;
|
||||
}
|
||||
|
||||
// 按优先级排序
|
||||
ksort($filters[$hook]);
|
||||
|
||||
$result = $value;
|
||||
@@ -122,11 +149,11 @@ class HookManager
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册动作钩子监听器
|
||||
* Register action hook listener
|
||||
*
|
||||
* @param string $hook 钩子名称
|
||||
* @param callable $callback 回调函数
|
||||
* @param int $priority 优先级
|
||||
* @param string $hook Hook name
|
||||
* @param callable $callback Callback function
|
||||
* @param int $priority Priority
|
||||
* @return void
|
||||
*/
|
||||
public static function register(string $hook, callable $callback, int $priority = 20): void
|
||||
@@ -141,18 +168,17 @@ class HookManager
|
||||
$actions[$hook][$priority] = [];
|
||||
}
|
||||
|
||||
// 使用随机键存储回调,避免相同优先级覆盖
|
||||
$actions[$hook][$priority][spl_object_hash($callback)] = $callback;
|
||||
$actions[$hook][$priority][self::getCallableId($callback)] = $callback;
|
||||
|
||||
self::setActions($actions);
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册过滤器钩子
|
||||
* Register filter hook
|
||||
*
|
||||
* @param string $hook 钩子名称
|
||||
* @param callable $callback 回调函数
|
||||
* @param int $priority 优先级
|
||||
* @param string $hook Hook name
|
||||
* @param callable $callback Callback function
|
||||
* @param int $priority Priority
|
||||
* @return void
|
||||
*/
|
||||
public static function registerFilter(string $hook, callable $callback, int $priority = 20): void
|
||||
@@ -167,17 +193,16 @@ class HookManager
|
||||
$filters[$hook][$priority] = [];
|
||||
}
|
||||
|
||||
// 使用随机键存储回调,避免相同优先级覆盖
|
||||
$filters[$hook][$priority][spl_object_hash($callback)] = $callback;
|
||||
$filters[$hook][$priority][self::getCallableId($callback)] = $callback;
|
||||
|
||||
self::setFilters($filters);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除钩子监听器
|
||||
* Remove hook listener
|
||||
*
|
||||
* @param string $hook 钩子名称
|
||||
* @param callable|null $callback 回调函数
|
||||
* @param string $hook Hook name
|
||||
* @param callable|null $callback Callback function
|
||||
* @return void
|
||||
*/
|
||||
public static function remove(string $hook, ?callable $callback = null): void
|
||||
@@ -185,7 +210,6 @@ class HookManager
|
||||
$actions = self::getActions();
|
||||
$filters = self::getFilters();
|
||||
|
||||
// 如果回调为null,直接移除整个钩子
|
||||
if ($callback === null) {
|
||||
if (isset($actions[$hook])) {
|
||||
unset($actions[$hook]);
|
||||
@@ -200,21 +224,17 @@ class HookManager
|
||||
return;
|
||||
}
|
||||
|
||||
// 移除特定回调
|
||||
$callbackId = spl_object_hash($callback);
|
||||
$callbackId = self::getCallableId($callback);
|
||||
|
||||
// 从actions中移除
|
||||
if (isset($actions[$hook])) {
|
||||
foreach ($actions[$hook] as $priority => $callbacks) {
|
||||
if (isset($callbacks[$callbackId])) {
|
||||
unset($actions[$hook][$priority][$callbackId]);
|
||||
|
||||
// 如果优先级下没有回调了,删除该优先级
|
||||
if (empty($actions[$hook][$priority])) {
|
||||
unset($actions[$hook][$priority]);
|
||||
}
|
||||
|
||||
// 如果钩子下没有任何优先级了,删除该钩子
|
||||
if (empty($actions[$hook])) {
|
||||
unset($actions[$hook]);
|
||||
}
|
||||
@@ -223,18 +243,15 @@ class HookManager
|
||||
self::setActions($actions);
|
||||
}
|
||||
|
||||
// 从filters中移除
|
||||
if (isset($filters[$hook])) {
|
||||
foreach ($filters[$hook] as $priority => $callbacks) {
|
||||
if (isset($callbacks[$callbackId])) {
|
||||
unset($filters[$hook][$priority][$callbackId]);
|
||||
|
||||
// 如果优先级下没有回调了,删除该优先级
|
||||
if (empty($filters[$hook][$priority])) {
|
||||
unset($filters[$hook][$priority]);
|
||||
}
|
||||
|
||||
// 如果钩子下没有任何优先级了,删除该钩子
|
||||
if (empty($filters[$hook])) {
|
||||
unset($filters[$hook]);
|
||||
}
|
||||
@@ -245,9 +262,9 @@ class HookManager
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否存在钩子
|
||||
* Check if hook exists
|
||||
*
|
||||
* @param string $hook 钩子名称
|
||||
* @param string $hook Hook name
|
||||
* @return bool
|
||||
*/
|
||||
public static function hasHook(string $hook): bool
|
||||
@@ -259,7 +276,7 @@ class HookManager
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理所有钩子(在Octane重置时调用)
|
||||
* Clear all hooks (called when Octane resets)
|
||||
*/
|
||||
public static function reset(): void
|
||||
{
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Services\Plugin;
|
||||
|
||||
use App\Models\Plugin;
|
||||
use Illuminate\Console\Scheduling\Schedule;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Facades\View;
|
||||
@@ -16,6 +17,7 @@ class PluginManager
|
||||
{
|
||||
protected string $pluginPath;
|
||||
protected array $loadedPlugins = [];
|
||||
protected bool $pluginsInitialized = false;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
@@ -41,7 +43,7 @@ class PluginManager
|
||||
/**
|
||||
* 加载插件类
|
||||
*/
|
||||
protected function loadPlugin(string $pluginCode)
|
||||
protected function loadPlugin(string $pluginCode): ?AbstractPlugin
|
||||
{
|
||||
if (isset($this->loadedPlugins[$pluginCode])) {
|
||||
return $this->loadedPlugins[$pluginCode];
|
||||
@@ -298,9 +300,7 @@ class PluginManager
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
// 初始化插件
|
||||
if (method_exists($plugin, 'boot')) {
|
||||
$plugin->boot();
|
||||
}
|
||||
$plugin->boot();
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -315,7 +315,6 @@ class PluginManager
|
||||
throw new \Exception('Plugin not found');
|
||||
}
|
||||
|
||||
// 更新数据库状态
|
||||
Plugin::query()
|
||||
->where('code', $pluginCode)
|
||||
->update([
|
||||
@@ -323,10 +322,7 @@ class PluginManager
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
// 清理插件
|
||||
if (method_exists($plugin, 'cleanup')) {
|
||||
$plugin->cleanup();
|
||||
}
|
||||
$plugin->cleanup();
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -452,4 +448,91 @@ class PluginManager
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes all enabled plugins from the database.
|
||||
* This method ensures that plugins are loaded, and their routes, views,
|
||||
* and service providers are registered only once per request cycle.
|
||||
*/
|
||||
public function initializeEnabledPlugins(): void
|
||||
{
|
||||
if ($this->pluginsInitialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
$enabledPlugins = Plugin::where('is_enabled', true)->get();
|
||||
|
||||
foreach ($enabledPlugins as $dbPlugin) {
|
||||
try {
|
||||
$pluginCode = $dbPlugin->code;
|
||||
|
||||
$pluginInstance = $this->loadPlugin($pluginCode);
|
||||
if (!$pluginInstance) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!empty($dbPlugin->config)) {
|
||||
$pluginInstance->setConfig(json_decode($dbPlugin->config, true));
|
||||
}
|
||||
|
||||
$this->registerServiceProvider($pluginCode);
|
||||
$this->loadRoutes($pluginCode);
|
||||
$this->loadViews($pluginCode);
|
||||
|
||||
$pluginInstance->boot();
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error("Failed to initialize plugin '{$dbPlugin->code}': " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
$this->pluginsInitialized = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register scheduled tasks for all enabled plugins.
|
||||
* Called from Console Kernel. Only loads main plugin class and config for scheduling.
|
||||
* Avoids full HTTP/plugin boot overhead.
|
||||
*
|
||||
* @param \Illuminate\Console\Scheduling\Schedule $schedule
|
||||
*/
|
||||
public function registerPluginSchedules(Schedule $schedule): void
|
||||
{
|
||||
Plugin::where('is_enabled', true)
|
||||
->get()
|
||||
->each(function ($dbPlugin) use ($schedule) {
|
||||
try {
|
||||
$pluginInstance = $this->loadPlugin($dbPlugin->code);
|
||||
if (!$pluginInstance) {
|
||||
return;
|
||||
}
|
||||
if (!empty($dbPlugin->config)) {
|
||||
$pluginInstance->setConfig(json_decode($dbPlugin->config, true));
|
||||
}
|
||||
$pluginInstance->schedule($schedule);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error("Failed to register schedule for plugin '{$dbPlugin->code}': " . $e->getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all enabled plugin instances.
|
||||
*
|
||||
* This method ensures that all enabled plugins are initialized and then returns them.
|
||||
* It's the central point for accessing active plugins.
|
||||
*
|
||||
* @return array<AbstractPlugin>
|
||||
*/
|
||||
public function getEnabledPlugins(): array
|
||||
{
|
||||
$this->initializeEnabledPlugins();
|
||||
|
||||
$enabledPluginCodes = Plugin::where('is_enabled', true)
|
||||
->pluck('code')
|
||||
->all();
|
||||
|
||||
return array_intersect_key($this->loadedPlugins, array_flip($enabledPluginCodes));
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ namespace App\Services;
|
||||
use App\Models\Server;
|
||||
use App\Models\ServerRoute;
|
||||
use App\Models\User;
|
||||
use App\Services\Plugin\HookManager;
|
||||
use App\Utils\Helper;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
@@ -66,7 +67,7 @@ class ServerService
|
||||
*/
|
||||
public static function getAvailableUsers(array $groupIds)
|
||||
{
|
||||
return User::toBase()
|
||||
$users = User::toBase()
|
||||
->whereIn('group_id', $groupIds)
|
||||
->whereRaw('u + d < transfer_enable')
|
||||
->where(function ($query) {
|
||||
@@ -81,6 +82,7 @@ class ServerService
|
||||
'device_limit'
|
||||
])
|
||||
->get();
|
||||
return HookManager::filter('server.users.get', $users, $groupIds);
|
||||
}
|
||||
|
||||
// 获取路由规则
|
||||
|
||||
@@ -388,7 +388,7 @@ class ThemeService
|
||||
|
||||
} catch (Exception $e) {
|
||||
Log::error('Failed to refresh current theme', [
|
||||
'theme' => $currentTheme ?? 'unknown',
|
||||
'theme' => $currentTheme,
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
return false;
|
||||
|
||||
@@ -9,6 +9,7 @@ use App\Models\TicketMessage;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use App\Services\Plugin\HookManager;
|
||||
|
||||
class TicketService {
|
||||
public function reply($ticket, $message, $userId)
|
||||
@@ -60,6 +61,7 @@ class TicketService {
|
||||
throw new ApiException('工单回复失败');
|
||||
}
|
||||
DB::commit();
|
||||
HookManager::call('ticket.reply.admin.after', [$ticket, $ticketMessage]);
|
||||
}catch(\Exception $e){
|
||||
DB::rollBack();
|
||||
throw $e;
|
||||
|
||||
@@ -9,6 +9,7 @@ use Carbon\Carbon;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use App\Services\Plugin\HookManager;
|
||||
|
||||
/**
|
||||
* Service for handling traffic reset.
|
||||
@@ -60,6 +61,7 @@ class TrafficResetService
|
||||
]);
|
||||
|
||||
$this->clearUserCache($user);
|
||||
HookManager::call('traffic.reset.after', $user);
|
||||
return true;
|
||||
});
|
||||
} catch (\Exception $e) {
|
||||
|
||||
Reference in New Issue
Block a user