CheckSpecificMenus.php 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. <?php
  2. namespace App\Console\Commands;
  3. use App\Module\System\Models\AdminMenu;
  4. use Illuminate\Console\Command;
  5. use Illuminate\Support\Facades\Route;
  6. use Illuminate\Support\Str;
  7. /**
  8. * 检查特定后台菜单命令
  9. *
  10. * 检查特定ID的后台菜单项是否指向有效的控制器,支持批量检查和删除无效菜单
  11. */
  12. class CheckSpecificMenus extends Command
  13. {
  14. /**
  15. * The name and signature of the console command.
  16. *
  17. * @var string
  18. */
  19. protected $signature = 'admin:check-specific-menus {id?} {--all : 检查所有菜单项} {--delete : 是否删除无效的菜单项}';
  20. /**
  21. * The console command description.
  22. *
  23. * @var string
  24. */
  25. protected $description = '检查特定ID的菜单项是否指向有效的控制器,并可选择删除无效的菜单项';
  26. /**
  27. * 已知的有效路由前缀
  28. *
  29. * @var array
  30. */
  31. protected $validPrefixes = [
  32. 'auth', 'dashboard', 'helpers', 'media', 'settings', 'logs', 'config'
  33. ];
  34. /**
  35. * Execute the console command.
  36. *
  37. * @return int
  38. */
  39. public function handle()
  40. {
  41. $id = $this->argument('id');
  42. $checkAll = $this->option('all');
  43. if (!$id && !$checkAll) {
  44. $this->error('请提供菜单ID或使用--all选项检查所有菜单项');
  45. return 1;
  46. }
  47. // 获取所有路由
  48. $routes = Route::getRoutes();
  49. $routeUris = [];
  50. foreach ($routes as $route) {
  51. if (Str::startsWith($route->uri(), 'admin/')) {
  52. $routeUris[] = str_replace('admin/', '', $route->uri());
  53. }
  54. }
  55. // 获取所有模块的控制器
  56. $modules = [
  57. 'Activity', 'Article', 'Farm', 'File', 'Fund', 'Game', 'GameItems',
  58. 'OAuth', 'Pet', 'Sms', 'System', 'Task', 'Team', 'User'
  59. ];
  60. $controllerUris = [];
  61. foreach ($modules as $module) {
  62. $controllerDir = app_path("Module/{$module}/AdminControllers");
  63. if (!is_dir($controllerDir)) {
  64. continue;
  65. }
  66. $controllers = array_filter(scandir($controllerDir), function($file) {
  67. return !in_array($file, ['.', '..']) &&
  68. pathinfo($file, PATHINFO_EXTENSION) === 'php' &&
  69. !Str::contains($file, 'Helper');
  70. });
  71. foreach ($controllers as $controller) {
  72. $controllerName = pathinfo($controller, PATHINFO_FILENAME);
  73. $uri = $this->getControllerUri($controllerName);
  74. $controllerUris[] = $uri;
  75. }
  76. }
  77. if ($id) {
  78. // 检查特定ID的菜单项
  79. $menu = AdminMenu::find($id);
  80. if (!$menu) {
  81. $this->error("未找到ID为{$id}的菜单项");
  82. return 1;
  83. }
  84. $this->info("检查ID为{$id}的菜单项: {$menu->title}, URI: {$menu->uri}");
  85. $isValid = $this->isValidMenu($menu, $routeUris, $controllerUris);
  86. if ($isValid) {
  87. $this->info("菜单项指向有效的控制器");
  88. } else {
  89. $this->error("菜单项不指向有效的控制器");
  90. if ($this->option('delete') || $this->confirm('是否要删除此菜单项?')) {
  91. // 获取子菜单
  92. $childMenus = AdminMenu::where('parent_id', $menu->id)->get();
  93. // 更新子菜单的父ID
  94. foreach ($childMenus as $childMenu) {
  95. $childMenu->parent_id = $menu->parent_id;
  96. $childMenu->save();
  97. $this->info(" - 更新子菜单: ID = {$childMenu->id}, 标题 = {$childMenu->title}, 新父ID = {$menu->parent_id}");
  98. }
  99. // 删除菜单项
  100. $menu->delete();
  101. $this->info(" - 已删除菜单项: ID = {$menu->id}, 标题 = {$menu->title}, URI = {$menu->uri}");
  102. }
  103. }
  104. } else {
  105. // 检查所有菜单项
  106. $menus = AdminMenu::all();
  107. $invalidMenus = [];
  108. foreach ($menus as $menu) {
  109. // 跳过空URI的菜单项(父菜单)
  110. if (empty($menu->uri)) {
  111. continue;
  112. }
  113. $isValid = $this->isValidMenu($menu, $routeUris, $controllerUris);
  114. if (!$isValid) {
  115. $invalidMenus[] = $menu;
  116. }
  117. }
  118. // 显示无效的菜单项
  119. if (count($invalidMenus) > 0) {
  120. $this->info("\n发现 " . count($invalidMenus) . " 个无效的菜单项:");
  121. $table = [];
  122. foreach ($invalidMenus as $menu) {
  123. $table[] = [
  124. 'id' => $menu->id,
  125. 'title' => $menu->title,
  126. 'uri' => $menu->uri,
  127. 'parent_id' => $menu->parent_id
  128. ];
  129. }
  130. $this->table(['ID', '标题', 'URI', '父ID'], $table);
  131. // 删除无效的菜单项
  132. if ($this->option('delete') || $this->confirm('是否要删除这些无效的菜单项?')) {
  133. foreach ($invalidMenus as $menu) {
  134. // 获取子菜单
  135. $childMenus = AdminMenu::where('parent_id', $menu->id)->get();
  136. // 更新子菜单的父ID
  137. foreach ($childMenus as $childMenu) {
  138. $childMenu->parent_id = $menu->parent_id;
  139. $childMenu->save();
  140. $this->info(" - 更新子菜单: ID = {$childMenu->id}, 标题 = {$childMenu->title}, 新父ID = {$menu->parent_id}");
  141. }
  142. // 删除菜单项
  143. $menu->delete();
  144. $this->info(" - 删除菜单项: ID = {$menu->id}, 标题 = {$menu->title}, URI = {$menu->uri}");
  145. }
  146. $this->info("\n已删除 " . count($invalidMenus) . " 个无效的菜单项");
  147. }
  148. } else {
  149. $this->info("\n所有菜单项都指向有效的控制器!");
  150. }
  151. }
  152. return 0;
  153. }
  154. /**
  155. * 检查菜单项是否有效
  156. *
  157. * @param AdminMenu $menu
  158. * @param array $routeUris
  159. * @param array $controllerUris
  160. * @return bool
  161. */
  162. protected function isValidMenu(AdminMenu $menu, array $routeUris, array $controllerUris): bool
  163. {
  164. // 跳过空URI的菜单项(父菜单)
  165. if (empty($menu->uri)) {
  166. return true;
  167. }
  168. // 跳过已知有效的路由前缀
  169. foreach ($this->validPrefixes as $prefix) {
  170. if (Str::startsWith($menu->uri, $prefix)) {
  171. return true;
  172. }
  173. }
  174. // 检查URI是否存在于路由中
  175. $uri = ltrim($menu->uri, '/');
  176. // 检查路由
  177. foreach ($routeUris as $routeUri) {
  178. if (Str::startsWith($routeUri, $uri) || $routeUri === $uri) {
  179. return true;
  180. }
  181. }
  182. // 检查控制器
  183. foreach ($controllerUris as $controllerUri) {
  184. if ($controllerUri === $uri) {
  185. return true;
  186. }
  187. }
  188. return false;
  189. }
  190. /**
  191. * 根据控制器名称猜测URI
  192. *
  193. * @param string $controllerName
  194. * @return string
  195. */
  196. protected function getControllerUri(string $controllerName): string
  197. {
  198. // 移除Controller后缀
  199. $name = str_replace('Controller', '', $controllerName);
  200. // 转换为kebab-case
  201. $uri = Str::kebab($name);
  202. return $uri;
  203. }
  204. }