MenuService.php 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. <?php
  2. /**
  3. * @Name
  4. * @Description
  5. * @Author 刘学玺
  6. * @Date 2024/8/15 14:30
  7. */
  8. namespace App\Http\Services\Backend\Server\System;
  9. use App\Enums\Common\Status;
  10. use App\Enums\System\MenuType;
  11. use App\Exceptions\ApiException;
  12. use App\Http\Services\Service;
  13. use App\Models\System\Menu;
  14. use Exception;
  15. use Illuminate\Support\Facades\DB;
  16. use Spatie\Permission\Guard;
  17. use Spatie\Permission\Models\Permission;
  18. use Symfony\Component\HttpFoundation\Response;
  19. class MenuService extends Service
  20. {
  21. protected array $selectColumn = ['id', 'name', 'type', 'sort', 'parent_id as parentId', 'path', 'icon', 'component', 'component_name as componentName', 'status', 'visible', 'keep_alive as keepAlive', 'always_show as alwaysShow'];
  22. // protected array $selectAppendColumn = ['created_at as createTime'];
  23. /**
  24. * @throws ApiException
  25. */
  26. public function createMenu(array $data, array $permissions)
  27. {
  28. // 校验父菜单存在
  29. self::validateParentMenu($data['parentId'], null);
  30. // 校验菜单(自己)
  31. self::validateMenu($data['parentId'], $data['name'], null);
  32. // 校验权限标识
  33. $this->validatePermissions($permissions);
  34. // 插入数据库
  35. $menu = self::toModel($data, Menu::class);
  36. self::initMenuProperty($menu);
  37. DB::beginTransaction();
  38. try {
  39. $menu_id = $menu->create($menu->getAttributes())->id;
  40. $this->createPermissions($permissions, $menu_id);
  41. DB::commit();
  42. }
  43. catch (Exception){
  44. DB::rollBack();
  45. self::error('添加失败');
  46. }
  47. return $menu_id;
  48. }
  49. /**
  50. * @throws ApiException
  51. */
  52. public function updateMenu($params): void
  53. {
  54. // 校验更新的菜单是否存在
  55. !self::isExistMenu($params['id']) && self::error('MENU_NOT_EXISTS', Response::HTTP_UNPROCESSABLE_ENTITY);
  56. // 校验父菜单存在
  57. self::validateParentMenu($params['parentId'], $params['id']);
  58. // 校验菜单(自己)
  59. self::validateMenu($params['parentId'], $params['name'], $params['id']);
  60. // 更新到数据库
  61. $menu = self::toModel($params, Menu::class);
  62. self::initMenuProperty($menu);
  63. $menu->update($menu->getAttributes());
  64. }
  65. /**
  66. * @throws ApiException
  67. */
  68. public function deleteMenu($id)
  69. {
  70. // 校验是否还有子菜单
  71. self::validateChildrenMenu($id);
  72. // 校验删除的菜单是否存在
  73. !self::isExistMenu($id) && self::error('MENU_NOT_EXISTS', Response::HTTP_UNPROCESSABLE_ENTITY);
  74. $menu = self::toModel(['id' => $id], Menu::class);
  75. // 标记删除
  76. return $menu->delete();
  77. }
  78. public function getMenu($id): array
  79. {
  80. $menu = Menu::query();
  81. return $menu->select($this->selectColumn)->find($id)->toArray();
  82. }
  83. public function getMenuList($params = []): array
  84. {
  85. $menu = Menu::query();
  86. isset($params['name']) && filled($params['name']) && $menu->whereLike('name', "%{$params['name']}%");
  87. isset($params['status']) && filled($params['status']) && $menu->where('status', $params['status']);
  88. return $menu->orderBy('sort')->select($this->selectColumn)->get()->toArray();
  89. }
  90. public function getSimpleMenuList(): array
  91. {
  92. $menus = $this->getMenuList(['status' => 0]);
  93. // list.sort(Comparator . comparing(MenuDO::getSort));
  94. return self::filterDisableMenus($menus);
  95. }
  96. protected static function isExistMenu(int|array $condition): bool
  97. {
  98. $menu = Menu::query();
  99. is_array($condition) && $menu->where($condition);
  100. is_numeric($condition) && $menu->where('id', $condition);
  101. return $menu->exists();
  102. }
  103. /**
  104. * @throws ApiException
  105. */
  106. protected static function validateChildrenMenu($parentId): void
  107. {
  108. Menu::query()->where('parent_id', $parentId)->count() && self::error('MENU_EXISTS_CHILDREN', Response::HTTP_UNPROCESSABLE_ENTITY);
  109. }
  110. /**
  111. * @throws ApiException
  112. */
  113. protected static function validateParentMenu($parentId, $childId): void
  114. {
  115. if (!$parentId) return;
  116. // 不能设置自己为父菜单
  117. $parentId === $childId && self::error('MENU_PARENT_ERROR', Response::HTTP_UNPROCESSABLE_ENTITY);
  118. $menu = Menu::query()->select('type')->find($parentId);
  119. // 父菜单不存在
  120. !$menu && self::error('MENU_PARENT_NOT_EXISTS', Response::HTTP_UNPROCESSABLE_ENTITY);
  121. // 父菜单必须是目录或者菜单类型
  122. $menu->type != MenuType::DIR && $menu->type != MenuType::MENU && self::error('MENU_PARENT_NOT_DIR_OR_MENU', Response::HTTP_UNPROCESSABLE_ENTITY);
  123. }
  124. /**
  125. * @throws ApiException
  126. */
  127. protected static function validateMenu($parentId, $name, $id): void
  128. {
  129. $where = ['parent_id' => $parentId, 'name' => $name];
  130. $menu = Menu::query()->where($where)->first();
  131. if (is_null($menu)) return;
  132. // 如果 id 为空,说明不用比较是否为相同 id 的菜单
  133. !$id && self::error('MENU_NAME_DUPLICATE', Response::HTTP_UNPROCESSABLE_ENTITY);
  134. $menu->id !== $id && self::error('MENU_NAME_DUPLICATE', Response::HTTP_UNPROCESSABLE_ENTITY);
  135. }
  136. /**
  137. * @throws ApiException
  138. */
  139. protected function validatePermissions(array $permissions, int $menu_id = null): void
  140. {
  141. if (empty($permissions)) return;
  142. $isExists = Permission::query()->whereIn('name', $permissions)->where('guard_name', Guard::getDefaultName(static::class))->exists();
  143. $isExists && empty($menu_id) && self::error('权限标识重复');
  144. }
  145. protected function createPermissions(array $permissions, int $menu_id): void
  146. {
  147. if (empty($permissions)) return;
  148. $permissionsData = [];
  149. $guardName = Guard::getDefaultName(static::class);
  150. $permissionQuery = Permission::query();
  151. foreach ($permissions as $permission) {
  152. $permissionsData[] = ['name' => $permission, 'guard_name' => $guardName];
  153. }
  154. $permissionQuery->insert($permissionsData);
  155. $permissionIds = $permissionQuery->whereIn('name', $permissions)->pluck('id');
  156. $this->createMenuHasPermissions($menu_id, $permissionIds->toArray());
  157. }
  158. protected function createMenuHasPermissions(int $menu_id, array $permission_ids): void
  159. {
  160. $tableNames = config('permission.table_names');
  161. $menuHasPermissionsData = [];
  162. $model_type = Menu::class;
  163. foreach ($permission_ids as $permission_id) {
  164. $menuHasPermissionsData[] = ['permission_id' => $permission_id, 'model_type' => $model_type, 'model_id' => $menu_id];
  165. }
  166. DB::table($tableNames['model_has_permissions'])->insert($menuHasPermissionsData);
  167. // 存在权限并清空权限缓存
  168. // Cache::forget(config('permission.cache.key'));
  169. }
  170. /**
  171. * 初始化菜单的通用属性。
  172. * <p>
  173. * 例如说,只有目录或者菜单类型的菜单,才设置 icon
  174. *
  175. */
  176. private static function initMenuProperty(&$menu): void
  177. {
  178. // 菜单为按钮类型时,无需 component、icon、path 属性,进行置空
  179. if ($menu['type'] === MenuType::BUTTON) {
  180. $menu['component'] = null;
  181. $menu['component_name'] = null;
  182. $menu['icon'] = null;
  183. $menu['path'] = null;
  184. }
  185. }
  186. private static function filterDisableMenus($menus): array
  187. {
  188. if (empty($menus)) return [];
  189. // 遍历 menu 菜单,查找不是禁用的菜单,添加到 enabledMenus 结果
  190. $enabledMenus = [];
  191. // $disabledMenuCache = []; // 存下递归搜索过被禁用的菜单,防止重复的搜索
  192. foreach ($menus as $menu) {
  193. if ($menu['status'] === Status::DISABLE) continue;
  194. $enabledMenus[] = collect($menu)->only(['id', 'status', 'name', 'parentId', 'type']); //['id' => $menu['id'], 'status' => $menu['status'], 'name' => $menu['name'], 'parentId' => $menu['parentId'], 'type' => $menu['type']];
  195. }
  196. return $enabledMenus;
  197. }
  198. }