mirror of
https://github.com/cedar2025/Xboard.git
synced 2025-02-02 07:28:13 -05:00
feat: add plugin upload functionality and optimize plugin logic
This commit is contained in:
parent
0141c68167
commit
c370b297d2
@ -192,4 +192,56 @@ class PluginController extends Controller
|
|||||||
], 400);
|
], 400);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传插件
|
||||||
|
*/
|
||||||
|
public function upload(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'file' => [
|
||||||
|
'required',
|
||||||
|
'file',
|
||||||
|
'mimes:zip',
|
||||||
|
'max:10240', // 最大10MB
|
||||||
|
]
|
||||||
|
], [
|
||||||
|
'file.required' => '请选择插件包文件',
|
||||||
|
'file.file' => '无效的文件类型',
|
||||||
|
'file.mimes' => '插件包必须是zip格式',
|
||||||
|
'file.max' => '插件包大小不能超过10MB'
|
||||||
|
]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->pluginManager->upload($request->file('file'));
|
||||||
|
return response()->json([
|
||||||
|
'message' => '插件上传成功'
|
||||||
|
]);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => '插件上传失败:' . $e->getMessage()
|
||||||
|
], 400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除插件
|
||||||
|
*/
|
||||||
|
public function delete(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'code' => 'required|string'
|
||||||
|
]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->pluginManager->delete($request->input('code'));
|
||||||
|
return response()->json([
|
||||||
|
'message' => '插件删除成功'
|
||||||
|
]);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => '插件删除失败:' . $e->getMessage()
|
||||||
|
], 400);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -7,6 +7,7 @@ use App\Http\Controllers\Controller;
|
|||||||
use App\Services\ThemeService;
|
use App\Services\ThemeService;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\File;
|
use Illuminate\Support\Facades\File;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
class ThemeController extends Controller
|
class ThemeController extends Controller
|
||||||
{
|
{
|
||||||
@ -75,7 +76,7 @@ class ThemeController extends Controller
|
|||||||
} catch (ApiException $e) {
|
} catch (ApiException $e) {
|
||||||
throw $e;
|
throw $e;
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
\Log::error('Theme upload failed', [
|
Log::error('Theme upload failed', [
|
||||||
'error' => $e->getMessage(),
|
'error' => $e->getMessage(),
|
||||||
'file' => $request->file('file')?->getClientOriginalName()
|
'file' => $request->file('file')?->getClientOriginalName()
|
||||||
]);
|
]);
|
||||||
|
@ -210,6 +210,8 @@ class AdminRoute
|
|||||||
'prefix' => 'plugin'
|
'prefix' => 'plugin'
|
||||||
], function ($router) {
|
], function ($router) {
|
||||||
$router->get('/getPlugins', [\App\Http\Controllers\V2\Admin\PluginController::class, 'index']);
|
$router->get('/getPlugins', [\App\Http\Controllers\V2\Admin\PluginController::class, 'index']);
|
||||||
|
$router->post('/upload', [\App\Http\Controllers\V2\Admin\PluginController::class, 'upload']);
|
||||||
|
$router->post('/delete', [\App\Http\Controllers\V2\Admin\PluginController::class, 'delete']);
|
||||||
$router->post('install', [\App\Http\Controllers\V2\Admin\PluginController::class, 'install']);
|
$router->post('install', [\App\Http\Controllers\V2\Admin\PluginController::class, 'install']);
|
||||||
$router->post('uninstall', [\App\Http\Controllers\V2\Admin\PluginController::class, 'uninstall']);
|
$router->post('uninstall', [\App\Http\Controllers\V2\Admin\PluginController::class, 'uninstall']);
|
||||||
$router->post('enable', [\App\Http\Controllers\V2\Admin\PluginController::class, 'enable']);
|
$router->post('enable', [\App\Http\Controllers\V2\Admin\PluginController::class, 'enable']);
|
||||||
|
@ -80,11 +80,12 @@ class HookManager
|
|||||||
* 移除钩子监听器
|
* 移除钩子监听器
|
||||||
*
|
*
|
||||||
* @param string $hook 钩子名称
|
* @param string $hook 钩子名称
|
||||||
|
* @param callable|null $callback 回调函数
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public static function remove(string $hook): void
|
public static function remove(string $hook, ?callable $callback = null): void
|
||||||
{
|
{
|
||||||
Eventy::removeAction($hook);
|
Eventy::removeAction($hook, $callback);
|
||||||
Eventy::removeFilter($hook);
|
Eventy::removeFilter($hook, $callback);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -144,6 +144,31 @@ class PluginManager
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除插件
|
||||||
|
*
|
||||||
|
* @param string $pluginCode
|
||||||
|
* @return bool
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function delete(string $pluginCode): bool
|
||||||
|
{
|
||||||
|
// 先卸载插件
|
||||||
|
if (Plugin::where('code', $pluginCode)->exists()) {
|
||||||
|
$this->uninstall($pluginCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
$pluginPath = $this->pluginPath . '/' . $pluginCode;
|
||||||
|
if (!File::exists($pluginPath)) {
|
||||||
|
throw new \Exception('插件不存在');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除插件目录
|
||||||
|
File::deleteDirectory($pluginPath);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 加载插件实例
|
* 加载插件实例
|
||||||
*/
|
*/
|
||||||
@ -183,4 +208,59 @@ class PluginManager
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传插件
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Http\UploadedFile $file
|
||||||
|
* @return bool
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function upload($file): bool
|
||||||
|
{
|
||||||
|
$tmpPath = storage_path('tmp/plugins');
|
||||||
|
if (!File::exists($tmpPath)) {
|
||||||
|
File::makeDirectory($tmpPath, 0755, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$extractPath = $tmpPath . '/' . uniqid();
|
||||||
|
$zip = new \ZipArchive();
|
||||||
|
|
||||||
|
if ($zip->open($file->path()) !== true) {
|
||||||
|
throw new \Exception('无法打开插件包文件');
|
||||||
|
}
|
||||||
|
|
||||||
|
$zip->extractTo($extractPath);
|
||||||
|
$zip->close();
|
||||||
|
|
||||||
|
$configFile = File::glob($extractPath . '/*/config.json');
|
||||||
|
if (empty($configFile)) {
|
||||||
|
$configFile = File::glob($extractPath . '/config.json');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($configFile)) {
|
||||||
|
File::deleteDirectory($extractPath);
|
||||||
|
throw new \Exception('插件包格式错误:缺少配置文件');
|
||||||
|
}
|
||||||
|
|
||||||
|
$pluginPath = dirname(reset($configFile));
|
||||||
|
$config = json_decode(File::get($pluginPath . '/config.json'), true);
|
||||||
|
|
||||||
|
if (!$this->validateConfig($config)) {
|
||||||
|
File::deleteDirectory($extractPath);
|
||||||
|
throw new \Exception('插件配置文件格式错误');
|
||||||
|
}
|
||||||
|
|
||||||
|
$targetPath = $this->pluginPath . '/' . $config['code'];
|
||||||
|
if (File::exists($targetPath)) {
|
||||||
|
File::deleteDirectory($extractPath);
|
||||||
|
throw new \Exception('插件已存在');
|
||||||
|
}
|
||||||
|
|
||||||
|
File::copyDirectory($pluginPath, $targetPath);
|
||||||
|
File::deleteDirectory($pluginPath);
|
||||||
|
File::deleteDirectory($extractPath);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
@ -78,6 +78,7 @@ services:
|
|||||||
- ./.docker/.data/:/www/.docker/.data
|
- ./.docker/.data/:/www/.docker/.data
|
||||||
- ./storage/logs:/www/storage/logs
|
- ./storage/logs:/www/storage/logs
|
||||||
- ./storage/theme:/www/storage/theme
|
- ./storage/theme:/www/storage/theme
|
||||||
|
- ./plugins:/www/plugins
|
||||||
environment:
|
environment:
|
||||||
- docker=true
|
- docker=true
|
||||||
depends_on:
|
depends_on:
|
||||||
@ -96,6 +97,7 @@ services:
|
|||||||
- ./.env:/www/.env
|
- ./.env:/www/.env
|
||||||
- ./.docker/.data/:/www/.docker/.data
|
- ./.docker/.data/:/www/.docker/.data
|
||||||
- ./storage/logs:/www/storage/logs
|
- ./storage/logs:/www/storage/logs
|
||||||
|
- ./plugins:/www/plugins
|
||||||
restart: on-failure
|
restart: on-failure
|
||||||
command: php artisan horizon
|
command: php artisan horizon
|
||||||
networks:
|
networks:
|
||||||
|
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
26
public/assets/admin/locales/en-US.js
vendored
26
public/assets/admin/locales/en-US.js
vendored
@ -188,7 +188,25 @@ window.XBOARD_TRANSLATIONS['en-US'] = {
|
|||||||
"install": "Install",
|
"install": "Install",
|
||||||
"config": "Configure",
|
"config": "Configure",
|
||||||
"enable": "Enable",
|
"enable": "Enable",
|
||||||
"disable": "Disable"
|
"disable": "Disable",
|
||||||
|
"uninstall": "Uninstall"
|
||||||
|
},
|
||||||
|
"upload": {
|
||||||
|
"button": "Upload Plugin",
|
||||||
|
"title": "Upload Plugin",
|
||||||
|
"description": "Upload a plugin package (.zip)",
|
||||||
|
"dragText": "Drag and drop plugin package here, or",
|
||||||
|
"clickText": "browse",
|
||||||
|
"supportText": "Supports .zip files only",
|
||||||
|
"uploading": "Uploading...",
|
||||||
|
"error": {
|
||||||
|
"format": "Only .zip files are supported"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"title": "Delete Plugin",
|
||||||
|
"description": "Are you sure you want to delete this plugin? This action cannot be undone.",
|
||||||
|
"button": "Delete"
|
||||||
},
|
},
|
||||||
"uninstall": {
|
"uninstall": {
|
||||||
"title": "Uninstall Plugin",
|
"title": "Uninstall Plugin",
|
||||||
@ -213,7 +231,11 @@ window.XBOARD_TRANSLATIONS['en-US'] = {
|
|||||||
"disableError": "Failed to disable plugin",
|
"disableError": "Failed to disable plugin",
|
||||||
"configLoadError": "Failed to load plugin configuration",
|
"configLoadError": "Failed to load plugin configuration",
|
||||||
"configSaveSuccess": "Configuration saved successfully",
|
"configSaveSuccess": "Configuration saved successfully",
|
||||||
"configSaveError": "Failed to save configuration"
|
"configSaveError": "Failed to save configuration",
|
||||||
|
"uploadSuccess": "Plugin uploaded successfully",
|
||||||
|
"uploadError": "Failed to upload plugin",
|
||||||
|
"deleteSuccess": "Plugin deleted successfully",
|
||||||
|
"deleteError": "Failed to delete plugin"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
|
24
public/assets/admin/locales/ko-KR.js
vendored
24
public/assets/admin/locales/ko-KR.js
vendored
@ -188,7 +188,25 @@ window.XBOARD_TRANSLATIONS['ko-KR'] = {
|
|||||||
"install": "설치",
|
"install": "설치",
|
||||||
"config": "설정",
|
"config": "설정",
|
||||||
"enable": "활성화",
|
"enable": "활성화",
|
||||||
"disable": "비활성화"
|
"disable": "비활성화",
|
||||||
|
"uninstall": "제거"
|
||||||
|
},
|
||||||
|
"upload": {
|
||||||
|
"button": "플러그인 업로드",
|
||||||
|
"title": "플러그인 업로드",
|
||||||
|
"description": "플러그인 패키지 업로드 (.zip)",
|
||||||
|
"dragText": "플러그인 패키지를 여기에 끌어다 놓거나",
|
||||||
|
"clickText": "찾아보기",
|
||||||
|
"supportText": ".zip 파일만 지원됩니다",
|
||||||
|
"uploading": "업로드 중...",
|
||||||
|
"error": {
|
||||||
|
"format": ".zip 파일만 지원됩니다"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"title": "플러그인 삭제",
|
||||||
|
"description": "이 플러그인을 삭제하시겠습니까? 삭제 후 플러그인 데이터가 삭제됩니다.",
|
||||||
|
"button": "삭제"
|
||||||
},
|
},
|
||||||
"uninstall": {
|
"uninstall": {
|
||||||
"title": "플러그인 제거",
|
"title": "플러그인 제거",
|
||||||
@ -213,7 +231,9 @@ window.XBOARD_TRANSLATIONS['ko-KR'] = {
|
|||||||
"disableError": "플러그인 비활성화에 실패했습니다",
|
"disableError": "플러그인 비활성화에 실패했습니다",
|
||||||
"configLoadError": "플러그인 설정을 불러오는데 실패했습니다",
|
"configLoadError": "플러그인 설정을 불러오는데 실패했습니다",
|
||||||
"configSaveSuccess": "설정이 성공적으로 저장되었습니다",
|
"configSaveSuccess": "설정이 성공적으로 저장되었습니다",
|
||||||
"configSaveError": "설정 저장에 실패했습니다"
|
"configSaveError": "설정 저장에 실패했습니다",
|
||||||
|
"uploadSuccess": "플러그인이 성공적으로 업로드되었습니다",
|
||||||
|
"uploadError": "플러그인 업로드에 실패했습니다"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
|
32
public/assets/admin/locales/zh-CN.js
vendored
32
public/assets/admin/locales/zh-CN.js
vendored
@ -176,9 +176,9 @@ window.XBOARD_TRANSLATIONS['zh-CN'] = {
|
|||||||
"other": "其他"
|
"other": "其他"
|
||||||
},
|
},
|
||||||
"tabs": {
|
"tabs": {
|
||||||
"all": "全部插件",
|
"all": "所有插件",
|
||||||
"installed": "已安装",
|
"installed": "已安装",
|
||||||
"available": "可用插件"
|
"available": "可用"
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"enabled": "已启用",
|
"enabled": "已启用",
|
||||||
@ -188,11 +188,29 @@ window.XBOARD_TRANSLATIONS['zh-CN'] = {
|
|||||||
"install": "安装",
|
"install": "安装",
|
||||||
"config": "配置",
|
"config": "配置",
|
||||||
"enable": "启用",
|
"enable": "启用",
|
||||||
"disable": "禁用"
|
"disable": "禁用",
|
||||||
|
"uninstall": "卸载"
|
||||||
|
},
|
||||||
|
"upload": {
|
||||||
|
"button": "上传插件",
|
||||||
|
"title": "上传插件",
|
||||||
|
"description": "上传插件包 (.zip)",
|
||||||
|
"dragText": "拖拽插件包到此处,或",
|
||||||
|
"clickText": "浏览",
|
||||||
|
"supportText": "仅支持 .zip 格式文件",
|
||||||
|
"uploading": "上传中...",
|
||||||
|
"error": {
|
||||||
|
"format": "仅支持 .zip 格式文件"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"title": "删除插件",
|
||||||
|
"description": "确定要删除此插件吗?此操作无法撤销。",
|
||||||
|
"button": "删除"
|
||||||
},
|
},
|
||||||
"uninstall": {
|
"uninstall": {
|
||||||
"title": "卸载插件",
|
"title": "卸载插件",
|
||||||
"description": "确定要卸载该插件吗?卸载后插件数据将被清除。",
|
"description": "确定要卸载此插件吗?卸载后插件数据将被清除。",
|
||||||
"button": "卸载"
|
"button": "卸载"
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
@ -213,7 +231,11 @@ window.XBOARD_TRANSLATIONS['zh-CN'] = {
|
|||||||
"disableError": "插件禁用失败",
|
"disableError": "插件禁用失败",
|
||||||
"configLoadError": "加载插件配置失败",
|
"configLoadError": "加载插件配置失败",
|
||||||
"configSaveSuccess": "配置保存成功",
|
"configSaveSuccess": "配置保存成功",
|
||||||
"configSaveError": "配置保存失败"
|
"configSaveError": "配置保存失败",
|
||||||
|
"uploadSuccess": "插件上传成功",
|
||||||
|
"uploadError": "插件上传失败",
|
||||||
|
"deleteSuccess": "插件删除成功",
|
||||||
|
"deleteError": "插件删除失败"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
|
2
storage/tmp/.gitignore
vendored
Normal file
2
storage/tmp/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
*
|
||||||
|
!.gitignore
|
Loading…
Reference in New Issue
Block a user