mirror of
https://github.com/cedar2025/Xboard.git
synced 2025-02-02 07:28:13 -05:00
feat: add plugin support
This commit is contained in:
parent
a6b68bb2e5
commit
43faab4e9c
@ -14,6 +14,7 @@ use App\Services\CouponService;
|
|||||||
use App\Services\OrderService;
|
use App\Services\OrderService;
|
||||||
use App\Services\PaymentService;
|
use App\Services\PaymentService;
|
||||||
use App\Services\PlanService;
|
use App\Services\PlanService;
|
||||||
|
use App\Services\Plugin\HookManager;
|
||||||
use App\Services\UserService;
|
use App\Services\UserService;
|
||||||
use App\Utils\Helper;
|
use App\Utils\Helper;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
@ -112,6 +113,7 @@ class OrderController extends Controller
|
|||||||
if (!$order->save()) {
|
if (!$order->save()) {
|
||||||
throw new ApiException(__('Failed to create order'));
|
throw new ApiException(__('Failed to create order'));
|
||||||
}
|
}
|
||||||
|
HookManager::call('order.after_create', $order);
|
||||||
|
|
||||||
return $this->success($order->trade_no);
|
return $this->success($order->trade_no);
|
||||||
});
|
});
|
||||||
|
195
app/Http/Controllers/V2/Admin/PluginController.php
Normal file
195
app/Http/Controllers/V2/Admin/PluginController.php
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\V2\Admin;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\Plugin;
|
||||||
|
use App\Services\Plugin\PluginManager;
|
||||||
|
use App\Services\Plugin\PluginConfigService;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\File;
|
||||||
|
|
||||||
|
class PluginController extends Controller
|
||||||
|
{
|
||||||
|
protected PluginManager $pluginManager;
|
||||||
|
protected PluginConfigService $configService;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
PluginManager $pluginManager,
|
||||||
|
PluginConfigService $configService
|
||||||
|
) {
|
||||||
|
$this->pluginManager = $pluginManager;
|
||||||
|
$this->configService = $configService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取插件列表
|
||||||
|
*/
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
$installedPlugins = Plugin::get()
|
||||||
|
->keyBy('code')
|
||||||
|
->toArray();
|
||||||
|
$pluginPath = base_path('plugins');
|
||||||
|
$plugins = [];
|
||||||
|
|
||||||
|
if (File::exists($pluginPath)) {
|
||||||
|
$directories = File::directories($pluginPath);
|
||||||
|
foreach ($directories as $directory) {
|
||||||
|
$pluginName = basename($directory);
|
||||||
|
$configFile = $directory . '/config.json';
|
||||||
|
if (File::exists($configFile)) {
|
||||||
|
$config = json_decode(File::get($configFile), true);
|
||||||
|
$installed = isset($installedPlugins[$pluginName]);
|
||||||
|
// 使用配置服务获取配置
|
||||||
|
$pluginConfig = $installed ? $this->configService->getConfig($pluginName) : ($config['config'] ?? []);
|
||||||
|
$plugins[] = [
|
||||||
|
'code' => $config['code'],
|
||||||
|
'name' => $config['name'],
|
||||||
|
'version' => $config['version'],
|
||||||
|
'description' => $config['description'],
|
||||||
|
'author' => $config['author'],
|
||||||
|
'is_installed' => $installed,
|
||||||
|
'is_enabled' => $installed ? $installedPlugins[$pluginName]['is_enabled'] : false,
|
||||||
|
'config' => $pluginConfig,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'data' => $plugins
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 安装插件
|
||||||
|
*/
|
||||||
|
public function install(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'code' => 'required|string'
|
||||||
|
]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->pluginManager->install($request->input('code'));
|
||||||
|
return response()->json([
|
||||||
|
'message' => '插件安装成功'
|
||||||
|
]);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => '插件安装失败:' . $e->getMessage()
|
||||||
|
], 400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 卸载插件
|
||||||
|
*/
|
||||||
|
public function uninstall(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'code' => 'required|string'
|
||||||
|
]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->pluginManager->uninstall($request->input('code'));
|
||||||
|
return response()->json([
|
||||||
|
'message' => '插件卸载成功'
|
||||||
|
]);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => '插件卸载失败:' . $e->getMessage()
|
||||||
|
], 400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启用插件
|
||||||
|
*/
|
||||||
|
public function enable(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'code' => 'required|string'
|
||||||
|
]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->pluginManager->enable($request->input('code'));
|
||||||
|
return response()->json([
|
||||||
|
'message' => '插件启用成功'
|
||||||
|
]);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => '插件启用失败:' . $e->getMessage()
|
||||||
|
], 400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 禁用插件
|
||||||
|
*/
|
||||||
|
public function disable(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'code' => 'required|string'
|
||||||
|
]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->pluginManager->disable($request->input('code'));
|
||||||
|
return response()->json([
|
||||||
|
'message' => '插件禁用成功'
|
||||||
|
]);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => '插件禁用失败:' . $e->getMessage()
|
||||||
|
], 400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取插件配置
|
||||||
|
*/
|
||||||
|
public function getConfig(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'code' => 'required|string'
|
||||||
|
]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$config = $this->configService->getConfig($request->input('code'));
|
||||||
|
return response()->json([
|
||||||
|
'data' => $config
|
||||||
|
]);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => '获取配置失败:' . $e->getMessage()
|
||||||
|
], 400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新插件配置
|
||||||
|
*/
|
||||||
|
public function updateConfig(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'code' => 'required|string',
|
||||||
|
'config' => 'required|array'
|
||||||
|
]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->configService->updateConfig(
|
||||||
|
$request->input('code'),
|
||||||
|
$request->input('config')
|
||||||
|
);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => '配置更新成功'
|
||||||
|
]);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => '配置更新失败:' . $e->getMessage()
|
||||||
|
], 400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -17,6 +17,7 @@ use App\Http\Controllers\V2\Admin\PaymentController;
|
|||||||
use App\Http\Controllers\V2\Admin\SystemController;
|
use App\Http\Controllers\V2\Admin\SystemController;
|
||||||
use App\Http\Controllers\V2\Admin\ThemeController;
|
use App\Http\Controllers\V2\Admin\ThemeController;
|
||||||
use Illuminate\Contracts\Routing\Registrar;
|
use Illuminate\Contracts\Routing\Registrar;
|
||||||
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
class AdminRoute
|
class AdminRoute
|
||||||
{
|
{
|
||||||
@ -202,6 +203,20 @@ class AdminRoute
|
|||||||
$router->post('/saveThemeConfig', [ThemeController::class, 'saveThemeConfig']);
|
$router->post('/saveThemeConfig', [ThemeController::class, 'saveThemeConfig']);
|
||||||
$router->post('/getThemeConfig', [ThemeController::class, 'getThemeConfig']);
|
$router->post('/getThemeConfig', [ThemeController::class, 'getThemeConfig']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Plugin
|
||||||
|
$router->group([
|
||||||
|
'prefix' => 'plugin'
|
||||||
|
], function ($router) {
|
||||||
|
$router->get('/getPlugins', [\App\Http\Controllers\V2\Admin\PluginController::class, 'index']);
|
||||||
|
$router->post('install', [\App\Http\Controllers\V2\Admin\PluginController::class, 'install']);
|
||||||
|
$router->post('uninstall', [\App\Http\Controllers\V2\Admin\PluginController::class, 'uninstall']);
|
||||||
|
$router->post('enable', [\App\Http\Controllers\V2\Admin\PluginController::class, 'enable']);
|
||||||
|
$router->post('disable', [\App\Http\Controllers\V2\Admin\PluginController::class, 'disable']);
|
||||||
|
$router->get('config', [\App\Http\Controllers\V2\Admin\PluginController::class, 'getConfig']);
|
||||||
|
$router->post('config', [\App\Http\Controllers\V2\Admin\PluginController::class, 'updateConfig']);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
16
app/Models/Plugin.php
Normal file
16
app/Models/Plugin.php
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class Plugin extends Model
|
||||||
|
{
|
||||||
|
protected $table = 'v2_plugins';
|
||||||
|
|
||||||
|
protected $guarded = [
|
||||||
|
'id',
|
||||||
|
'created_at',
|
||||||
|
'updated_at'
|
||||||
|
];
|
||||||
|
}
|
41
app/Providers/PluginServiceProvider.php
Normal file
41
app/Providers/PluginServiceProvider.php
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Providers;
|
||||||
|
|
||||||
|
use App\Models\Plugin;
|
||||||
|
use App\Services\Plugin\PluginManager;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
class PluginServiceProvider extends ServiceProvider
|
||||||
|
{
|
||||||
|
public function register(): void
|
||||||
|
{
|
||||||
|
$this->app->scoped(PluginManager::class, function ($app) {
|
||||||
|
return new PluginManager();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function boot(): void
|
||||||
|
{
|
||||||
|
|
||||||
|
if (!file_exists(base_path('plugins'))) {
|
||||||
|
mkdir(base_path('plugins'), 0755, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$plugins = Plugin::query()
|
||||||
|
->where('is_enabled', true)
|
||||||
|
->get();
|
||||||
|
|
||||||
|
foreach ($plugins as $plugin) {
|
||||||
|
$manager = $this->app->make(PluginManager::class);
|
||||||
|
$manager->enable($plugin->code);
|
||||||
|
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
\Log::error('Failed to load plugins: ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
56
app/Services/Plugin/AbstractPlugin.php
Normal file
56
app/Services/Plugin/AbstractPlugin.php
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services\Plugin;
|
||||||
|
|
||||||
|
abstract class AbstractPlugin
|
||||||
|
{
|
||||||
|
protected array $config = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插件启动时调用
|
||||||
|
*/
|
||||||
|
public function boot(): void
|
||||||
|
{
|
||||||
|
// 子类实现具体逻辑
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插件禁用时调用
|
||||||
|
*/
|
||||||
|
public function cleanup(): void
|
||||||
|
{
|
||||||
|
// 子类实现具体逻辑
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置配置
|
||||||
|
*/
|
||||||
|
public function setConfig(array $config): void
|
||||||
|
{
|
||||||
|
$this->config = $config;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取配置
|
||||||
|
*/
|
||||||
|
public function getConfig(): array
|
||||||
|
{
|
||||||
|
return $this->config;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册事件监听器
|
||||||
|
*/
|
||||||
|
protected function listen(string $hook, callable $callback): void
|
||||||
|
{
|
||||||
|
HookManager::register($hook, $callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移除事件监听器
|
||||||
|
*/
|
||||||
|
protected function removeListener(string $hook): void
|
||||||
|
{
|
||||||
|
HookManager::remove($hook);
|
||||||
|
}
|
||||||
|
}
|
43
app/Services/Plugin/HookManager.php
Normal file
43
app/Services/Plugin/HookManager.php
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services\Plugin;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Event;
|
||||||
|
|
||||||
|
class HookManager
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 触发钩子
|
||||||
|
*
|
||||||
|
* @param string $hook 钩子名称
|
||||||
|
* @param mixed $payload 传递给钩子的数据
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public static function call(string $hook, mixed $payload = null): mixed
|
||||||
|
{
|
||||||
|
return Event::dispatch($hook, [$payload]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册钩子监听器
|
||||||
|
*
|
||||||
|
* @param string $hook 钩子名称
|
||||||
|
* @param callable $callback 回调函数
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function register(string $hook, callable $callback): void
|
||||||
|
{
|
||||||
|
Event::listen($hook, $callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移除钩子监听器
|
||||||
|
*
|
||||||
|
* @param string $hook 钩子名称
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function remove(string $hook): void
|
||||||
|
{
|
||||||
|
Event::forget($hook);
|
||||||
|
}
|
||||||
|
}
|
103
app/Services/Plugin/PluginConfigService.php
Normal file
103
app/Services/Plugin/PluginConfigService.php
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services\Plugin;
|
||||||
|
|
||||||
|
use App\Models\Plugin;
|
||||||
|
use Illuminate\Support\Facades\File;
|
||||||
|
|
||||||
|
class PluginConfigService
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 获取插件配置
|
||||||
|
*
|
||||||
|
* @param string $pluginCode
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getConfig(string $pluginCode): array
|
||||||
|
{
|
||||||
|
$defaultConfig = $this->getDefaultConfig($pluginCode);
|
||||||
|
if (empty($defaultConfig)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
$dbConfig = $this->getDbConfig($pluginCode);
|
||||||
|
|
||||||
|
$result = [];
|
||||||
|
foreach ($defaultConfig as $key => $item) {
|
||||||
|
$result[$key] = [
|
||||||
|
'type' => $item['type'],
|
||||||
|
'label' => $item['label'] ?? '',
|
||||||
|
'placeholder' => $item['placeholder'] ?? '',
|
||||||
|
'description' => $item['description'] ?? '',
|
||||||
|
'value' => $dbConfig[$key] ?? $item['default']
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新插件配置
|
||||||
|
*
|
||||||
|
* @param string $pluginCode
|
||||||
|
* @param array $config
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function updateConfig(string $pluginCode, array $config): bool
|
||||||
|
{
|
||||||
|
$defaultConfig = $this->getDefaultConfig($pluginCode);
|
||||||
|
if (empty($defaultConfig)) {
|
||||||
|
throw new \Exception('插件配置结构不存在');
|
||||||
|
}
|
||||||
|
$values = [];
|
||||||
|
foreach ($config as $key => $value) {
|
||||||
|
if (!isset($defaultConfig[$key])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$values[$key] = $value;
|
||||||
|
}
|
||||||
|
Plugin::query()
|
||||||
|
->where('code', $pluginCode)
|
||||||
|
->update([
|
||||||
|
'config' => json_encode($values),
|
||||||
|
'updated_at' => now()
|
||||||
|
]);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取插件默认配置
|
||||||
|
*
|
||||||
|
* @param string $pluginCode
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function getDefaultConfig(string $pluginCode): array
|
||||||
|
{
|
||||||
|
$configFile = base_path("plugins/{$pluginCode}/config.json");
|
||||||
|
if (!File::exists($configFile)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$config = json_decode(File::get($configFile), true);
|
||||||
|
return $config['config'] ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取数据库中的配置
|
||||||
|
*
|
||||||
|
* @param string $pluginCode
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function getDbConfig(string $pluginCode): array
|
||||||
|
{
|
||||||
|
$plugin = Plugin::query()
|
||||||
|
->where('code', $pluginCode)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if (!$plugin || empty($plugin->config)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return json_decode($plugin->config, true);
|
||||||
|
}
|
||||||
|
}
|
180
app/Services/Plugin/PluginManager.php
Normal file
180
app/Services/Plugin/PluginManager.php
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services\Plugin;
|
||||||
|
|
||||||
|
use App\Models\Plugin;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\File;
|
||||||
|
|
||||||
|
class PluginManager
|
||||||
|
{
|
||||||
|
protected string $pluginPath;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->pluginPath = base_path('plugins');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 安装插件
|
||||||
|
*/
|
||||||
|
public function install(string $pluginCode): bool
|
||||||
|
{
|
||||||
|
$configFile = $this->pluginPath . '/' . $pluginCode . '/config.json';
|
||||||
|
|
||||||
|
if (!File::exists($configFile)) {
|
||||||
|
throw new \Exception('Plugin config file not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
$config = json_decode(File::get($configFile), true);
|
||||||
|
if (!$this->validateConfig($config)) {
|
||||||
|
throw new \Exception('Invalid plugin config');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查依赖
|
||||||
|
if (!$this->checkDependencies($config['require'] ?? [])) {
|
||||||
|
throw new \Exception('Dependencies not satisfied');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取配置默认值
|
||||||
|
$defaultValues = [];
|
||||||
|
if (isset($config['config']) && is_array($config['config'])) {
|
||||||
|
foreach ($config['config'] as $key => $item) {
|
||||||
|
$defaultValues[$key] = $item['default'] ?? null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册到数据库
|
||||||
|
Plugin::create([
|
||||||
|
'code' => $pluginCode,
|
||||||
|
'name' => $config['name'],
|
||||||
|
'version' => $config['version'],
|
||||||
|
'is_enabled' => false,
|
||||||
|
'config' => json_encode($defaultValues),
|
||||||
|
'installed_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启用插件
|
||||||
|
*/
|
||||||
|
public function enable(string $pluginCode): bool
|
||||||
|
{
|
||||||
|
$plugin = $this->loadPlugin($pluginCode);
|
||||||
|
if (!$plugin) {
|
||||||
|
throw new \Exception('Plugin not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取插件配置
|
||||||
|
$dbPlugin = Plugin::query()
|
||||||
|
->where('code', $pluginCode)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if ($dbPlugin && !empty($dbPlugin->config)) {
|
||||||
|
$plugin->setConfig(json_decode($dbPlugin->config, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新数据库状态
|
||||||
|
Plugin::query()
|
||||||
|
->where('code', $pluginCode)
|
||||||
|
->update([
|
||||||
|
'is_enabled' => true,
|
||||||
|
'updated_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 加载路由
|
||||||
|
$routesFile = $this->pluginPath . '/' . $pluginCode . '/routes/web.php';
|
||||||
|
if (File::exists($routesFile)) {
|
||||||
|
require $routesFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化插件
|
||||||
|
if (method_exists($plugin, 'boot')) {
|
||||||
|
$plugin->boot();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 禁用插件
|
||||||
|
*/
|
||||||
|
public function disable(string $pluginCode): bool
|
||||||
|
{
|
||||||
|
$plugin = $this->loadPlugin($pluginCode);
|
||||||
|
if (!$plugin) {
|
||||||
|
throw new \Exception('Plugin not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新数据库状态
|
||||||
|
Plugin::query()
|
||||||
|
->where('code', $pluginCode)
|
||||||
|
->update([
|
||||||
|
'is_enabled' => false,
|
||||||
|
'updated_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 清理插件
|
||||||
|
if (method_exists($plugin, 'cleanup')) {
|
||||||
|
$plugin->cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 卸载插件
|
||||||
|
*/
|
||||||
|
public function uninstall(string $pluginCode): bool
|
||||||
|
{
|
||||||
|
// 先禁用插件
|
||||||
|
$this->disable($pluginCode);
|
||||||
|
|
||||||
|
// 删除数据库记录
|
||||||
|
Plugin::query()->where('code', $pluginCode)->delete();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载插件实例
|
||||||
|
*/
|
||||||
|
protected function loadPlugin(string $pluginCode)
|
||||||
|
{
|
||||||
|
$pluginFile = $this->pluginPath . '/' . $pluginCode . '/Plugin.php';
|
||||||
|
if (!File::exists($pluginFile)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once $pluginFile;
|
||||||
|
$className = "Plugin\\{$pluginCode}\\Plugin";
|
||||||
|
return new $className();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证配置文件
|
||||||
|
*/
|
||||||
|
protected function validateConfig(array $config): bool
|
||||||
|
{
|
||||||
|
return isset($config['code'])
|
||||||
|
&& isset($config['version'])
|
||||||
|
&& isset($config['description'])
|
||||||
|
&& isset($config['author']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查依赖关系
|
||||||
|
*/
|
||||||
|
protected function checkDependencies(array $requires): bool
|
||||||
|
{
|
||||||
|
foreach ($requires as $package => $version) {
|
||||||
|
if ($package === 'xboard') {
|
||||||
|
// 检查xboard版本
|
||||||
|
// 实现版本比较逻辑
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('v2_plugins', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->string('name');
|
||||||
|
$table->string('code')->unique();
|
||||||
|
$table->string('version', 50);
|
||||||
|
$table->boolean('is_enabled')->default(false);
|
||||||
|
$table->json('config')->nullable();
|
||||||
|
$table->timestamp('installed_at')->nullable();
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('v2_plugins');
|
||||||
|
}
|
||||||
|
};
|
2
plugins/.gitignore
vendored
Normal file
2
plugins/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
*
|
||||||
|
!.gitignore
|
2
public/assets/admin/assets/index.css
vendored
2
public/assets/admin/assets/index.css
vendored
File diff suppressed because one or more lines are too long
14
public/assets/admin/assets/index.js
vendored
14
public/assets/admin/assets/index.js
vendored
File diff suppressed because one or more lines are too long
540
public/assets/admin/assets/vendor.js
vendored
540
public/assets/admin/assets/vendor.js
vendored
File diff suppressed because one or more lines are too long
53
public/assets/admin/locales/en-US.js
vendored
53
public/assets/admin/locales/en-US.js
vendored
@ -151,6 +151,7 @@ window.XBOARD_TRANSLATIONS['en-US'] = {
|
|||||||
"systemConfig": "System Configuration",
|
"systemConfig": "System Configuration",
|
||||||
"themeConfig": "Theme Configuration",
|
"themeConfig": "Theme Configuration",
|
||||||
"noticeManagement": "Notice Management",
|
"noticeManagement": "Notice Management",
|
||||||
|
"pluginManagement": "Plugin Management",
|
||||||
"paymentConfig": "Payment Configuration",
|
"paymentConfig": "Payment Configuration",
|
||||||
"knowledgeManagement": "Knowledge Management",
|
"knowledgeManagement": "Knowledge Management",
|
||||||
"nodeManagement": "Node Management",
|
"nodeManagement": "Node Management",
|
||||||
@ -163,6 +164,58 @@ window.XBOARD_TRANSLATIONS['en-US'] = {
|
|||||||
"userManagement": "User Management",
|
"userManagement": "User Management",
|
||||||
"ticketManagement": "Ticket Management"
|
"ticketManagement": "Ticket Management"
|
||||||
},
|
},
|
||||||
|
"plugin": {
|
||||||
|
"title": "Plugin Management",
|
||||||
|
"description": "Manage and configure system plugins",
|
||||||
|
"search": {
|
||||||
|
"placeholder": "Search plugin name or description..."
|
||||||
|
},
|
||||||
|
"category": {
|
||||||
|
"placeholder": "Select Category",
|
||||||
|
"all": "All",
|
||||||
|
"other": "Other"
|
||||||
|
},
|
||||||
|
"tabs": {
|
||||||
|
"all": "All Plugins",
|
||||||
|
"installed": "Installed",
|
||||||
|
"available": "Available"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"enabled": "Enabled",
|
||||||
|
"disabled": "Disabled"
|
||||||
|
},
|
||||||
|
"button": {
|
||||||
|
"install": "Install",
|
||||||
|
"config": "Configure",
|
||||||
|
"enable": "Enable",
|
||||||
|
"disable": "Disable"
|
||||||
|
},
|
||||||
|
"uninstall": {
|
||||||
|
"title": "Uninstall Plugin",
|
||||||
|
"description": "Are you sure you want to uninstall this plugin? Plugin data will be cleared after uninstallation.",
|
||||||
|
"button": "Uninstall"
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"title": "Configuration",
|
||||||
|
"description": "Modify plugin configuration",
|
||||||
|
"save": "Save",
|
||||||
|
"cancel": "Cancel"
|
||||||
|
},
|
||||||
|
"author": "Author",
|
||||||
|
"messages": {
|
||||||
|
"installSuccess": "Plugin installed successfully",
|
||||||
|
"installError": "Failed to install plugin",
|
||||||
|
"uninstallSuccess": "Plugin uninstalled successfully",
|
||||||
|
"uninstallError": "Failed to uninstall plugin",
|
||||||
|
"enableSuccess": "Plugin enabled successfully",
|
||||||
|
"enableError": "Failed to enable plugin",
|
||||||
|
"disableSuccess": "Plugin disabled successfully",
|
||||||
|
"disableError": "Failed to disable plugin",
|
||||||
|
"configLoadError": "Failed to load plugin configuration",
|
||||||
|
"configSaveSuccess": "Configuration saved successfully",
|
||||||
|
"configSaveError": "Failed to save configuration"
|
||||||
|
}
|
||||||
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"title": "System Settings",
|
"title": "System Settings",
|
||||||
"description": "Manage core system configurations, including site, security, subscription, invite commission, nodes, email, and notifications",
|
"description": "Manage core system configurations, including site, security, subscription, invite commission, nodes, email, and notifications",
|
||||||
|
53
public/assets/admin/locales/ko-KR.js
vendored
53
public/assets/admin/locales/ko-KR.js
vendored
@ -151,6 +151,7 @@ window.XBOARD_TRANSLATIONS['ko-KR'] = {
|
|||||||
"systemConfig": "시스템 설정",
|
"systemConfig": "시스템 설정",
|
||||||
"themeConfig": "테마 설정",
|
"themeConfig": "테마 설정",
|
||||||
"noticeManagement": "공지사항 관리",
|
"noticeManagement": "공지사항 관리",
|
||||||
|
"pluginManagement": "플러그인 관리",
|
||||||
"paymentConfig": "결제 설정",
|
"paymentConfig": "결제 설정",
|
||||||
"knowledgeManagement": "지식 관리",
|
"knowledgeManagement": "지식 관리",
|
||||||
"nodeManagement": "노드 관리",
|
"nodeManagement": "노드 관리",
|
||||||
@ -163,6 +164,58 @@ window.XBOARD_TRANSLATIONS['ko-KR'] = {
|
|||||||
"userManagement": "사용자 관리",
|
"userManagement": "사용자 관리",
|
||||||
"ticketManagement": "티켓 관리"
|
"ticketManagement": "티켓 관리"
|
||||||
},
|
},
|
||||||
|
"plugin": {
|
||||||
|
"title": "플러그인 관리",
|
||||||
|
"description": "시스템 플러그인 관리 및 설정",
|
||||||
|
"search": {
|
||||||
|
"placeholder": "플러그인 이름 또는 설명 검색..."
|
||||||
|
},
|
||||||
|
"category": {
|
||||||
|
"placeholder": "카테고리 선택",
|
||||||
|
"all": "전체",
|
||||||
|
"other": "기타"
|
||||||
|
},
|
||||||
|
"tabs": {
|
||||||
|
"all": "전체 플러그인",
|
||||||
|
"installed": "설치됨",
|
||||||
|
"available": "사용 가능"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"enabled": "활성화됨",
|
||||||
|
"disabled": "비활성화됨"
|
||||||
|
},
|
||||||
|
"button": {
|
||||||
|
"install": "설치",
|
||||||
|
"config": "설정",
|
||||||
|
"enable": "활성화",
|
||||||
|
"disable": "비활성화"
|
||||||
|
},
|
||||||
|
"uninstall": {
|
||||||
|
"title": "플러그인 제거",
|
||||||
|
"description": "이 플러그인을 제거하시겠습니까? 제거 후 플러그인 데이터가 삭제됩니다.",
|
||||||
|
"button": "제거"
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"title": "설정",
|
||||||
|
"description": "플러그인 설정 수정",
|
||||||
|
"save": "저장",
|
||||||
|
"cancel": "취소"
|
||||||
|
},
|
||||||
|
"author": "작성자",
|
||||||
|
"messages": {
|
||||||
|
"installSuccess": "플러그인이 성공적으로 설치되었습니다",
|
||||||
|
"installError": "플러그인 설치에 실패했습니다",
|
||||||
|
"uninstallSuccess": "플러그인이 성공적으로 제거되었습니다",
|
||||||
|
"uninstallError": "플러그인 제거에 실패했습니다",
|
||||||
|
"enableSuccess": "플러그인이 성공적으로 활성화되었습니다",
|
||||||
|
"enableError": "플러그인 활성화에 실패했습니다",
|
||||||
|
"disableSuccess": "플러그인이 성공적으로 비활성화되었습니다",
|
||||||
|
"disableError": "플러그인 비활성화에 실패했습니다",
|
||||||
|
"configLoadError": "플러그인 설정을 불러오는데 실패했습니다",
|
||||||
|
"configSaveSuccess": "설정이 성공적으로 저장되었습니다",
|
||||||
|
"configSaveError": "설정 저장에 실패했습니다"
|
||||||
|
}
|
||||||
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"title": "시스템 설정",
|
"title": "시스템 설정",
|
||||||
"description": "사이트, 보안, 구독, 초대 수수료, 노드, 이메일 및 알림을 포함한 핵심 시스템 구성을 관리합니다",
|
"description": "사이트, 보안, 구독, 초대 수수료, 노드, 이메일 및 알림을 포함한 핵심 시스템 구성을 관리합니다",
|
||||||
|
54
public/assets/admin/locales/zh-CN.js
vendored
54
public/assets/admin/locales/zh-CN.js
vendored
@ -150,6 +150,7 @@ window.XBOARD_TRANSLATIONS['zh-CN'] = {
|
|||||||
"systemManagement": "系统管理",
|
"systemManagement": "系统管理",
|
||||||
"systemConfig": "系统配置",
|
"systemConfig": "系统配置",
|
||||||
"themeConfig": "主题配置",
|
"themeConfig": "主题配置",
|
||||||
|
"pluginManagement": "插件管理",
|
||||||
"noticeManagement": "公告管理",
|
"noticeManagement": "公告管理",
|
||||||
"paymentConfig": "支付配置",
|
"paymentConfig": "支付配置",
|
||||||
"knowledgeManagement": "知识库管理",
|
"knowledgeManagement": "知识库管理",
|
||||||
@ -163,6 +164,58 @@ window.XBOARD_TRANSLATIONS['zh-CN'] = {
|
|||||||
"userManagement": "用户管理",
|
"userManagement": "用户管理",
|
||||||
"ticketManagement": "工单管理"
|
"ticketManagement": "工单管理"
|
||||||
},
|
},
|
||||||
|
"plugin": {
|
||||||
|
"title": "插件管理",
|
||||||
|
"description": "管理和配置系统插件",
|
||||||
|
"search": {
|
||||||
|
"placeholder": "搜索插件名称或描述..."
|
||||||
|
},
|
||||||
|
"category": {
|
||||||
|
"placeholder": "选择分类",
|
||||||
|
"all": "全部",
|
||||||
|
"other": "其他"
|
||||||
|
},
|
||||||
|
"tabs": {
|
||||||
|
"all": "全部插件",
|
||||||
|
"installed": "已安装",
|
||||||
|
"available": "可用插件"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"enabled": "已启用",
|
||||||
|
"disabled": "已禁用"
|
||||||
|
},
|
||||||
|
"button": {
|
||||||
|
"install": "安装",
|
||||||
|
"config": "配置",
|
||||||
|
"enable": "启用",
|
||||||
|
"disable": "禁用"
|
||||||
|
},
|
||||||
|
"uninstall": {
|
||||||
|
"title": "卸载插件",
|
||||||
|
"description": "确定要卸载该插件吗?卸载后插件数据将被清除。",
|
||||||
|
"button": "卸载"
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"title": "配置",
|
||||||
|
"description": "修改插件配置",
|
||||||
|
"save": "保存",
|
||||||
|
"cancel": "取消"
|
||||||
|
},
|
||||||
|
"author": "作者",
|
||||||
|
"messages": {
|
||||||
|
"installSuccess": "插件安装成功",
|
||||||
|
"installError": "插件安装失败",
|
||||||
|
"uninstallSuccess": "插件卸载成功",
|
||||||
|
"uninstallError": "插件卸载失败",
|
||||||
|
"enableSuccess": "插件启用成功",
|
||||||
|
"enableError": "插件启用失败",
|
||||||
|
"disableSuccess": "插件禁用成功",
|
||||||
|
"disableError": "插件禁用失败",
|
||||||
|
"configLoadError": "加载插件配置失败",
|
||||||
|
"configSaveSuccess": "配置保存成功",
|
||||||
|
"configSaveError": "配置保存失败"
|
||||||
|
}
|
||||||
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"title": "系统设置",
|
"title": "系统设置",
|
||||||
"description": "管理系统核心配置,包括站点、安全、订阅、邀请佣金、节点、邮件和通知等设置",
|
"description": "管理系统核心配置,包括站点、安全、订阅、邀请佣金、节点、邮件和通知等设置",
|
||||||
@ -1941,6 +1994,7 @@ window.XBOARD_TRANSLATIONS['zh-CN'] = {
|
|||||||
"dashboard": "仪表盘",
|
"dashboard": "仪表盘",
|
||||||
"systemManagement": "系统管理",
|
"systemManagement": "系统管理",
|
||||||
"systemConfig": "系统配置",
|
"systemConfig": "系统配置",
|
||||||
|
"pluginManagement": "插件管理",
|
||||||
"themeConfig": "主题配置",
|
"themeConfig": "主题配置",
|
||||||
"noticeManagement": "公告管理",
|
"noticeManagement": "公告管理",
|
||||||
"paymentConfig": "支付配置",
|
"paymentConfig": "支付配置",
|
||||||
|
Loading…
Reference in New Issue
Block a user