mirror of
https://github.com/lkddi/Xboard.git
synced 2026-04-24 20:17:32 +08:00
feat: add plugin migrations and fix plan management bugs
- Plugin database migration support - Fix empty prices error in plan management - Plugin update functionality - Custom shadowsocks encryption algorithms
This commit is contained in:
@@ -38,7 +38,7 @@ class PluginController extends Controller
|
||||
],
|
||||
[
|
||||
'value' => Plugin::TYPE_PAYMENT,
|
||||
'label' => '支付方式',
|
||||
'label' => '支付方式',
|
||||
'description' => '提供支付接口的插件,如支付宝、微信支付等',
|
||||
'icon' => '💳'
|
||||
]
|
||||
@@ -52,14 +52,14 @@ class PluginController extends Controller
|
||||
public function index(Request $request)
|
||||
{
|
||||
$type = $request->query('type');
|
||||
|
||||
$installedPlugins = Plugin::when($type, function($query) use ($type) {
|
||||
return $query->byType($type);
|
||||
})
|
||||
|
||||
$installedPlugins = Plugin::when($type, function ($query) use ($type) {
|
||||
return $query->byType($type);
|
||||
})
|
||||
->get()
|
||||
->keyBy('code')
|
||||
->toArray();
|
||||
|
||||
|
||||
$pluginPath = base_path('plugins');
|
||||
$plugins = [];
|
||||
|
||||
@@ -72,19 +72,26 @@ class PluginController extends Controller
|
||||
$config = json_decode(File::get($configFile), true);
|
||||
$code = $config['code'];
|
||||
$pluginType = $config['type'] ?? Plugin::TYPE_FEATURE;
|
||||
|
||||
|
||||
// 如果指定了类型,过滤插件
|
||||
if ($type && $pluginType !== $type) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
$installed = isset($installedPlugins[$code]);
|
||||
$pluginConfig = $installed ? $this->configService->getConfig($code) : ($config['config'] ?? []);
|
||||
$readmeFile = collect(['README.md', 'readme.md'])
|
||||
->map(fn($f) => $directory . '/' . $f)
|
||||
->first(fn($path) => File::exists($path));
|
||||
$readmeContent = $readmeFile ? File::get($readmeFile) : '';
|
||||
|
||||
$needUpgrade = false;
|
||||
if ($installed) {
|
||||
$installedVersion = $installedPlugins[$code]['version'] ?? null;
|
||||
$localVersion = $config['version'] ?? null;
|
||||
if ($installedVersion && $localVersion && version_compare($localVersion, $installedVersion, '>')) {
|
||||
$needUpgrade = true;
|
||||
}
|
||||
}
|
||||
$plugins[] = [
|
||||
'code' => $config['code'],
|
||||
'name' => $config['name'],
|
||||
@@ -98,6 +105,7 @@ class PluginController extends Controller
|
||||
'can_be_deleted' => !in_array($code, Plugin::PROTECTED_PLUGINS),
|
||||
'config' => $pluginConfig,
|
||||
'readme' => $readmeContent,
|
||||
'need_upgrade' => $needUpgrade,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -138,8 +146,16 @@ class PluginController extends Controller
|
||||
'code' => 'required|string'
|
||||
]);
|
||||
|
||||
$code = $request->input('code');
|
||||
$plugin = Plugin::where('code', $code)->first();
|
||||
if ($plugin && $plugin->is_enabled) {
|
||||
return response()->json([
|
||||
'message' => '请先禁用插件后再卸载'
|
||||
], 400);
|
||||
}
|
||||
|
||||
try {
|
||||
$this->pluginManager->uninstall($request->input('code'));
|
||||
$this->pluginManager->uninstall($code);
|
||||
return response()->json([
|
||||
'message' => '插件卸载成功'
|
||||
]);
|
||||
@@ -150,6 +166,26 @@ class PluginController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 升级插件
|
||||
*/
|
||||
public function upgrade(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'code' => 'required|string',
|
||||
]);
|
||||
try {
|
||||
$this->pluginManager->update($request->input('code'));
|
||||
return response()->json([
|
||||
'message' => '插件升级成功'
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json([
|
||||
'message' => '插件升级失败:' . $e->getMessage()
|
||||
], 400);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 启用插件
|
||||
*/
|
||||
|
||||
@@ -257,6 +257,7 @@ class AdminRoute
|
||||
$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']);
|
||||
$router->post('upgrade', [\App\Http\Controllers\V2\Admin\PluginController::class, 'upgrade']);
|
||||
});
|
||||
|
||||
// 流量重置管理
|
||||
|
||||
@@ -19,6 +19,7 @@ use Illuminate\Support\Facades\Log;
|
||||
* @property string $requires
|
||||
* @property string $config
|
||||
* @property string $type
|
||||
* @property boolean $is_enabled
|
||||
*/
|
||||
class Plugin extends Model
|
||||
{
|
||||
|
||||
@@ -137,32 +137,32 @@ class PluginManager
|
||||
*/
|
||||
public function install(string $pluginCode): bool
|
||||
{
|
||||
$configFile = $this->getPluginPath($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 (Plugin::where('code', $pluginCode)->exists()) {
|
||||
throw new \Exception('Plugin already installed');
|
||||
}
|
||||
|
||||
// 检查依赖
|
||||
if (!$this->checkDependencies($config['require'] ?? [])) {
|
||||
throw new \Exception('Dependencies not satisfied');
|
||||
}
|
||||
|
||||
// 运行数据库迁移
|
||||
$this->runMigrations(pluginCode: $pluginCode);
|
||||
|
||||
DB::beginTransaction();
|
||||
try {
|
||||
$configFile = $this->getPluginPath($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 (Plugin::where('code', $pluginCode)->exists()) {
|
||||
throw new \Exception('Plugin already installed');
|
||||
}
|
||||
|
||||
// 检查依赖
|
||||
if (!$this->checkDependencies($config['require'] ?? [])) {
|
||||
throw new \Exception('Dependencies not satisfied');
|
||||
}
|
||||
|
||||
// 运行数据库迁移
|
||||
$this->runMigrations($pluginCode);
|
||||
|
||||
// 提取配置默认值
|
||||
$defaultValues = $this->extractDefaultConfig($config);
|
||||
|
||||
@@ -170,7 +170,7 @@ class PluginManager
|
||||
$plugin = $this->loadPlugin($pluginCode);
|
||||
|
||||
// 注册到数据库
|
||||
$dbPlugin = Plugin::create([
|
||||
Plugin::create([
|
||||
'code' => $pluginCode,
|
||||
'name' => $config['name'],
|
||||
'version' => $config['version'],
|
||||
@@ -191,7 +191,9 @@ class PluginManager
|
||||
DB::commit();
|
||||
return true;
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
if (DB::transactionLevel() > 0) {
|
||||
DB::rollBack();
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
@@ -223,7 +225,22 @@ class PluginManager
|
||||
|
||||
if (File::exists($migrationsPath)) {
|
||||
Artisan::call('migrate', [
|
||||
'--path' => "plugins/{$pluginCode}/database/migrations",
|
||||
'--path' => "plugins/" . Str::studly($pluginCode) . "/database/migrations",
|
||||
'--force' => true
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 回滚插件数据库迁移
|
||||
*/
|
||||
protected function runMigrationsRollback(string $pluginCode): void
|
||||
{
|
||||
$migrationsPath = $this->getPluginPath($pluginCode) . '/database/migrations';
|
||||
|
||||
if (File::exists($migrationsPath)) {
|
||||
Artisan::call('migrate:rollback', [
|
||||
'--path' => "plugins/" . Str::studly($pluginCode) . "/database/migrations",
|
||||
'--force' => true
|
||||
]);
|
||||
}
|
||||
@@ -352,10 +369,8 @@ class PluginManager
|
||||
*/
|
||||
public function uninstall(string $pluginCode): bool
|
||||
{
|
||||
// 先禁用插件
|
||||
$this->disable($pluginCode);
|
||||
|
||||
// 删除数据库记录
|
||||
$this->runMigrationsRollback($pluginCode);
|
||||
Plugin::query()->where('code', $pluginCode)->delete();
|
||||
|
||||
return true;
|
||||
@@ -400,6 +415,62 @@ class PluginManager
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 升级插件
|
||||
*
|
||||
* @param string $pluginCode
|
||||
* @return bool
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function update(string $pluginCode): bool
|
||||
{
|
||||
$dbPlugin = Plugin::where('code', $pluginCode)->first();
|
||||
if (!$dbPlugin) {
|
||||
throw new \Exception('Plugin not installed: ' . $pluginCode);
|
||||
}
|
||||
|
||||
// 获取插件配置文件中的最新版本
|
||||
$configFile = $this->getPluginPath($pluginCode) . '/config.json';
|
||||
if (!File::exists($configFile)) {
|
||||
throw new \Exception('Plugin config file not found');
|
||||
}
|
||||
|
||||
$config = json_decode(File::get($configFile), true);
|
||||
if (!$config || !isset($config['version'])) {
|
||||
throw new \Exception('Invalid plugin config or missing version');
|
||||
}
|
||||
|
||||
$newVersion = $config['version'];
|
||||
$oldVersion = $dbPlugin->version;
|
||||
|
||||
if (version_compare($newVersion, $oldVersion, '<=')) {
|
||||
throw new \Exception('Plugin is already up to date');
|
||||
}
|
||||
|
||||
$this->disable($pluginCode);
|
||||
$this->runMigrations($pluginCode);
|
||||
|
||||
$plugin = $this->loadPlugin($pluginCode);
|
||||
if ($plugin) {
|
||||
if (!empty($dbPlugin->config)) {
|
||||
$plugin->setConfig(json_decode($dbPlugin->config, true));
|
||||
}
|
||||
|
||||
if (method_exists($plugin, 'update')) {
|
||||
$plugin->update($oldVersion, $newVersion);
|
||||
}
|
||||
}
|
||||
|
||||
$dbPlugin->update([
|
||||
'version' => $newVersion,
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
$this->enable($pluginCode);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传插件
|
||||
*
|
||||
@@ -466,6 +537,10 @@ class PluginManager
|
||||
File::deleteDirectory($pluginPath);
|
||||
File::deleteDirectory($extractPath);
|
||||
|
||||
if (Plugin::where('code', $config['code'])->exists()) {
|
||||
return $this->update($config['code']);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user