RuleController.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  1. <?php
  2. namespace plugin\admin\app\controller;
  3. use plugin\admin\app\common\Tree;
  4. use plugin\admin\app\common\Util;
  5. use plugin\admin\app\model\Role;
  6. use plugin\admin\app\model\Rule;
  7. use support\exception\BusinessException;
  8. use support\Request;
  9. use support\Response;
  10. use Throwable;
  11. /**
  12. * 权限菜单
  13. */
  14. class RuleController extends Crud
  15. {
  16. /**
  17. * 不需要权限的方法
  18. *
  19. * @var string[]
  20. */
  21. protected $noNeedAuth = ['get', 'permission'];
  22. /**
  23. * @var Rule
  24. */
  25. protected $model = null;
  26. /**
  27. * 构造函数
  28. */
  29. public function __construct()
  30. {
  31. $this->model = new Rule;
  32. }
  33. /**
  34. * 浏览
  35. * @return Response
  36. * @throws Throwable
  37. */
  38. public function index(): Response
  39. {
  40. return raw_view('rule/index');
  41. }
  42. /**
  43. * 查询
  44. * @param Request $request
  45. * @return Response
  46. * @throws BusinessException
  47. */
  48. public function select(Request $request): Response
  49. {
  50. $this->syncRules();
  51. return parent::select($request);
  52. }
  53. /**
  54. * 获取菜单
  55. * @param Request $request
  56. * @return Response
  57. */
  58. function get(Request $request): Response
  59. {
  60. $rules = $this->getRules(admin('roles'));
  61. $types = $request->get('type', '0,1');
  62. $types = is_string($types) ? explode(',', $types) : [0, 1];
  63. $items = Rule::orderBy('weight', 'desc')->get()->toArray();
  64. $formatted_items = [];
  65. foreach ($items as $item) {
  66. $item['pid'] = (int)$item['pid'];
  67. $item['name'] = $item['title'];
  68. $item['value'] = $item['id'];
  69. $item['icon'] = $item['icon'] ? "layui-icon {$item['icon']}" : '';
  70. $formatted_items[] = $item;
  71. }
  72. $tree = new Tree($formatted_items);
  73. $tree_items = $tree->getTree();
  74. // 超级管理员权限为 *
  75. if (!in_array('*', $rules)) {
  76. $this->removeNotContain($tree_items, 'id', $rules);
  77. }
  78. $this->removeNotContain($tree_items, 'type', $types);
  79. $menus = $this->empty_filter(Tree::arrayValues($tree_items));
  80. return $this->json(0, 'ok', $menus);
  81. }
  82. private function empty_filter($menus)
  83. {
  84. return array_map(
  85. function ($menu) {
  86. if (isset($menu['children'])) {
  87. $menu['children'] = $this->empty_filter($menu['children']);
  88. }
  89. return $menu;
  90. },
  91. array_values(array_filter(
  92. $menus,
  93. function ($menu) {
  94. return $menu['type'] != 0 || isset($menu['children']) && count($this->empty_filter($menu['children'])) > 0;
  95. }
  96. ))
  97. );
  98. }
  99. /**
  100. * 获取权限
  101. * @param Request $request
  102. * @return Response
  103. */
  104. public function permission(Request $request): Response
  105. {
  106. $rules = $this->getRules(admin('roles'));
  107. // 超级管理员
  108. if (in_array('*', $rules)) {
  109. return $this->json(0, 'ok', ['*']);
  110. }
  111. $keys = Rule::whereIn('id', $rules)->pluck('key');
  112. $permissions = [];
  113. foreach ($keys as $key) {
  114. if (!$key = Util::controllerToUrlPath($key)) {
  115. continue;
  116. }
  117. $code = str_replace('/', '.', trim($key, '/'));
  118. $permissions[] = $code;
  119. }
  120. return $this->json(0, 'ok', $permissions);
  121. }
  122. /**
  123. * 根据类同步规则到数据库
  124. * @return void
  125. */
  126. protected function syncRules()
  127. {
  128. $items = $this->model->where('key', 'like', '%\\\\%')->get()->keyBy('key');
  129. $methods_in_db = [];
  130. $methods_in_files = [];
  131. foreach ($items as $item) {
  132. $class = $item->key;
  133. if (strpos($class, '@')) {
  134. $methods_in_db[$class] = $class;
  135. continue;
  136. }
  137. if (class_exists($class)) {
  138. $reflection = new \ReflectionClass($class);
  139. $properties = $reflection->getDefaultProperties();
  140. $no_need_auth = array_merge($properties['noNeedLogin'] ?? [], $properties['noNeedAuth'] ?? []);
  141. $class = $reflection->getName();
  142. $pid = $item->id;
  143. $methods = $reflection->getMethods(\ReflectionMethod::IS_PUBLIC);
  144. foreach ($methods as $method) {
  145. $method_name = $method->getName();
  146. if (strtolower($method_name) === 'index' || strpos($method_name, '__') === 0 || in_array($method_name, $no_need_auth)) {
  147. continue;
  148. }
  149. $name = "$class@$method_name";
  150. $methods_in_files[$name] = $name;
  151. $title = Util::getCommentFirstLine($method->getDocComment()) ?: $method_name;
  152. $menu = $items[$name] ?? [];
  153. if ($menu) {
  154. if ($menu->title != $title) {
  155. Rule::where('key', $name)->update(['title' => $title]);
  156. }
  157. continue;
  158. }
  159. $menu = new Rule;
  160. $menu->pid = $pid;
  161. $menu->key = $name;
  162. $menu->title = $title;
  163. $menu->type = 2;
  164. $menu->save();
  165. }
  166. }
  167. }
  168. // 从数据库中删除已经不存在的方法
  169. $menu_names_to_del = array_diff($methods_in_db, $methods_in_files);
  170. if ($menu_names_to_del) {
  171. //Rule::whereIn('key', $menu_names_to_del)->delete();
  172. }
  173. }
  174. /**
  175. * 查询前置方法
  176. * @param Request $request
  177. * @return array
  178. * @throws BusinessException
  179. */
  180. protected function selectInput(Request $request): array
  181. {
  182. [$where, $format, $limit, $field, $order] = parent::selectInput($request);
  183. // 允许通过type=0,1格式传递菜单类型
  184. $types = $request->get('type');
  185. if ($types && is_string($types)) {
  186. $where['type'] = ['in', explode(',', $types)];
  187. }
  188. // 默认weight排序
  189. if (!$field) {
  190. $field = 'weight';
  191. $order = 'desc';
  192. }
  193. return [$where, $format, $limit, $field, $order];
  194. }
  195. /**
  196. * 添加
  197. * @param Request $request
  198. * @return Response
  199. * @throws BusinessException|Throwable
  200. */
  201. public function insert(Request $request): Response
  202. {
  203. if ($request->method() === 'GET') {
  204. return raw_view('rule/insert');
  205. }
  206. $data = $this->insertInput($request);
  207. if (empty($data['type'])) {
  208. $data['type'] = strpos($data['key'], '\\') ? 1 : 0;
  209. }
  210. $data['key'] = str_replace('\\\\', '\\', $data['key']);
  211. $key = $data['key'] ?? '';
  212. if ($this->model->where('key', $key)->first()) {
  213. return $this->json(1, "菜单标识 $key 已经存在");
  214. }
  215. $data['pid'] = empty($data['pid']) ? 0 : $data['pid'];
  216. $this->doInsert($data);
  217. return $this->json(0);
  218. }
  219. /**
  220. * 更新
  221. * @param Request $request
  222. * @return Response
  223. * @throws BusinessException|Throwable
  224. */
  225. public function update(Request $request): Response
  226. {
  227. if ($request->method() === 'GET') {
  228. return raw_view('rule/update');
  229. }
  230. [$id, $data] = $this->updateInput($request);
  231. if (!$row = $this->model->find($id)) {
  232. return $this->json(2, '记录不存在');
  233. }
  234. if (isset($data['pid'])) {
  235. $data['pid'] = $data['pid'] ?: 0;
  236. if ($data['pid'] == $row['id']) {
  237. return $this->json(2, '不能将自己设置为上级菜单');
  238. }
  239. }
  240. if (isset($data['key'])) {
  241. $data['key'] = str_replace('\\\\', '\\', $data['key']);
  242. }
  243. $this->doUpdate($id, $data);
  244. return $this->json(0);
  245. }
  246. /**
  247. * 删除
  248. * @param Request $request
  249. * @return Response
  250. */
  251. public function delete(Request $request): Response
  252. {
  253. $ids = $this->deleteInput($request);
  254. // 子规则一起删除
  255. $delete_ids = $children_ids = $ids;
  256. while($children_ids) {
  257. $children_ids = $this->model->whereIn('pid', $children_ids)->pluck('id')->toArray();
  258. $delete_ids = array_merge($delete_ids, $children_ids);
  259. }
  260. $this->doDelete($delete_ids);
  261. return $this->json(0);
  262. }
  263. /**
  264. * 移除不包含某些数据的数组
  265. * @param $array
  266. * @param $key
  267. * @param $values
  268. * @return void
  269. */
  270. protected function removeNotContain(&$array, $key, $values)
  271. {
  272. foreach ($array as $k => &$item) {
  273. if (!is_array($item)) {
  274. continue;
  275. }
  276. if (!$this->arrayContain($item, $key, $values)) {
  277. unset($array[$k]);
  278. } else {
  279. if (!isset($item['children'])) {
  280. continue;
  281. }
  282. $this->removeNotContain($item['children'], $key, $values);
  283. }
  284. }
  285. }
  286. /**
  287. * 判断数组是否包含某些数据
  288. * @param $array
  289. * @param $key
  290. * @param $values
  291. * @return bool
  292. */
  293. protected function arrayContain(&$array, $key, $values): bool
  294. {
  295. if (!is_array($array)) {
  296. return false;
  297. }
  298. if (isset($array[$key]) && in_array($array[$key], $values)) {
  299. return true;
  300. }
  301. if (!isset($array['children'])) {
  302. return false;
  303. }
  304. foreach ($array['children'] as $item) {
  305. if ($this->arrayContain($item, $key, $values)) {
  306. return true;
  307. }
  308. }
  309. return false;
  310. }
  311. /**
  312. * 获取权限规则
  313. * @param $roles
  314. * @return array
  315. */
  316. protected function getRules($roles): array
  317. {
  318. $rules_strings = $roles ? Role::whereIn('id', $roles)->pluck('rules') : [];
  319. $rules = [];
  320. foreach ($rules_strings as $rule_string) {
  321. if (!$rule_string) {
  322. continue;
  323. }
  324. $rules = array_merge($rules, explode(',', $rule_string));
  325. }
  326. return $rules;
  327. }
  328. }