Backend.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  1. <?php
  2. namespace app\common\controller;
  3. use Throwable;
  4. use think\Model;
  5. use think\facade\Db;
  6. use think\facade\Event;
  7. use think\facade\Cookie;
  8. use app\admin\library\Auth;
  9. use think\db\exception\PDOException;
  10. use think\exception\HttpResponseException;
  11. class Backend extends Api
  12. {
  13. /**
  14. * 无需登录的方法,访问本控制器的此方法,无需管理员登录
  15. * @var array
  16. */
  17. protected array $noNeedLogin = [];
  18. /**
  19. * 无需鉴权的方法
  20. * @var array
  21. */
  22. protected array $noNeedPermission = [];
  23. /**
  24. * 新增/编辑时,对前端发送的字段进行排除(忽略不入库)
  25. * @var array|string
  26. */
  27. protected array|string $preExcludeFields = [];
  28. /**
  29. * 权限类实例
  30. * @var Auth
  31. */
  32. protected Auth $auth;
  33. /**
  34. * 模型类实例
  35. * @var object
  36. * @phpstan-var Model
  37. */
  38. protected object $model;
  39. /**
  40. * 权重字段
  41. * @var string
  42. */
  43. protected string $weighField = 'weigh';
  44. /**
  45. * 默认排序
  46. * @var string|array
  47. */
  48. protected string|array $defaultSortField = 'id,desc';
  49. /**
  50. * 表格拖拽排序时,两个权重相等则自动重新整理
  51. * config/buildadmin.php文件中的auto_sort_eq_weight为默认值
  52. * null=取默认值,false=关,true=开
  53. * @var null|bool
  54. */
  55. protected null|bool $autoSortEqWeight = null;
  56. /**
  57. * 快速搜索字段
  58. * @var string|array
  59. */
  60. protected string|array $quickSearchField = 'id';
  61. /**
  62. * 是否开启模型验证
  63. * @var bool
  64. */
  65. protected bool $modelValidate = true;
  66. /**
  67. * 是否开启模型场景验证
  68. * @var bool
  69. */
  70. protected bool $modelSceneValidate = false;
  71. /**
  72. * 关联查询方法名,方法应定义在模型中
  73. * @var array
  74. */
  75. protected array $withJoinTable = [];
  76. /**
  77. * 关联查询JOIN方式
  78. * @var string
  79. */
  80. protected string $withJoinType = 'LEFT';
  81. /**
  82. * 开启数据限制
  83. * false=关闭
  84. * personal=仅限个人
  85. * allAuth=拥有某管理员所有的权限时
  86. * allAuthAndOthers=拥有某管理员所有的权限并且还有其他权限时
  87. * parent=上级分组中的管理员可查
  88. * 指定分组中的管理员可查,比如 $dataLimit = 2;
  89. * 启用请确保数据表内存在 admin_id 字段,可以查询/编辑数据的管理员为admin_id对应的管理员+数据限制所表示的管理员们
  90. * @var bool|string|int
  91. */
  92. protected bool|string|int $dataLimit = false;
  93. /**
  94. * 数据限制字段
  95. * @var string
  96. */
  97. protected string $dataLimitField = 'admin_id';
  98. /**
  99. * 数据限制开启时自动填充字段值为当前管理员id
  100. * @var bool
  101. */
  102. protected bool $dataLimitFieldAutoFill = true;
  103. /**
  104. * 查看请求返回的主表字段控制
  105. * @var string|array
  106. */
  107. protected string|array $indexField = ['*'];
  108. /**
  109. * 引入traits
  110. * traits内实现了index、add、edit等方法
  111. */
  112. use \app\admin\library\traits\Backend;
  113. /**
  114. * 初始化
  115. * @throws Throwable
  116. */
  117. public function initialize(): void
  118. {
  119. parent::initialize();
  120. // 检测数据库连接
  121. try {
  122. Db::execute("SELECT 1");
  123. } catch (PDOException $e) {
  124. $this->error(mb_convert_encoding($e->getMessage(), 'UTF-8', 'UTF-8,GBK,GB2312,BIG5'));
  125. }
  126. $this->auth = Auth::instance();
  127. $routePath = $this->app->request->controllerPath . '/' . $this->request->action(true);
  128. $token = $this->request->server('HTTP_BATOKEN', $this->request->request('batoken', Cookie::get('batoken') ?: false));
  129. if (!action_in_arr($this->noNeedLogin)) {
  130. $this->auth->init($token);
  131. if (!$this->auth->isLogin()) {
  132. $this->error(__('Please login first'), [
  133. 'type' => $this->auth::NEED_LOGIN
  134. ], $this->auth::LOGIN_RESPONSE_CODE);
  135. }
  136. if (!action_in_arr($this->noNeedPermission)) {
  137. if (!$this->auth->check($routePath)) {
  138. $this->error(__('You have no permission'), [], 401);
  139. }
  140. }
  141. } elseif ($token) {
  142. try {
  143. $this->auth->init($token);
  144. } catch (HttpResponseException) {
  145. }
  146. }
  147. // 管理员验权和登录标签位
  148. Event::trigger('backendInit', $this->auth);
  149. }
  150. /**
  151. * 构建查询参数
  152. * @throws Throwable
  153. */
  154. public function queryBuilder(): array
  155. {
  156. if (empty($this->model)) {
  157. return [];
  158. }
  159. $pk = $this->model->getPk();
  160. $quickSearch = $this->request->get("quickSearch/s", '');
  161. $limit = $this->request->get("limit/d", 10);
  162. $order = $this->request->get("order/s", '');
  163. $search = $this->request->get("search/a", []);
  164. $initKey = $this->request->get("initKey/s", $pk);
  165. $initValue = $this->request->get("initValue", '');
  166. $initOperator = $this->request->get("initOperator/s", 'in');
  167. $where = [];
  168. $modelTable = strtolower($this->model->getTable());
  169. $alias[$modelTable] = parse_name(basename(str_replace('\\', '/', get_class($this->model))));
  170. $mainTableAlias = $alias[$modelTable] . '.';
  171. // 快速搜索
  172. if ($quickSearch) {
  173. $quickSearchArr = is_array($this->quickSearchField) ? $this->quickSearchField : explode(',', $this->quickSearchField);
  174. foreach ($quickSearchArr as $k => $v) {
  175. $quickSearchArr[$k] = str_contains($v, '.') ? $v : $mainTableAlias . $v;
  176. }
  177. $where[] = [implode("|", $quickSearchArr), "LIKE", '%' . str_replace('%', '\%', $quickSearch) . '%'];
  178. }
  179. if ($initValue) {
  180. $where[] = [$initKey, $initOperator, $initValue];
  181. $limit = 999999;
  182. }
  183. // 排序
  184. if ($order) {
  185. $order = explode(',', $order);
  186. if (!empty($order[0]) && !empty($order[1]) && ($order[1] == 'asc' || $order[1] == 'desc')) {
  187. $order = [$order[0] => $order[1]];
  188. }
  189. } else {
  190. if (is_array($this->defaultSortField)) {
  191. $order = $this->defaultSortField;
  192. } else {
  193. $order = explode(',', $this->defaultSortField);
  194. if (!empty($order[0]) && !empty($order[1])) {
  195. $order = [$order[0] => $order[1]];
  196. } else {
  197. $order = [$pk => 'desc'];
  198. }
  199. }
  200. }
  201. // 通用搜索组装
  202. foreach ($search as $field) {
  203. if (!is_array($field) || !isset($field['operator']) || !isset($field['field']) || !isset($field['val'])) {
  204. continue;
  205. }
  206. $field['operator'] = $this->getOperatorByAlias($field['operator']);
  207. $fieldName = str_contains($field['field'], '.') ? $field['field'] : $mainTableAlias . $field['field'];
  208. // 日期时间
  209. if (isset($field['render']) && $field['render'] == 'datetime') {
  210. if ($field['operator'] == 'RANGE') {
  211. $datetimeArr = explode(',', $field['val']);
  212. if (!isset($datetimeArr[1])) {
  213. continue;
  214. }
  215. $datetimeArr = array_filter(array_map("strtotime", $datetimeArr));
  216. $where[] = [$fieldName, str_replace('RANGE', 'BETWEEN', $field['operator']), $datetimeArr];
  217. continue;
  218. }
  219. $where[] = [$fieldName, '=', strtotime($field['val'])];
  220. continue;
  221. }
  222. // 范围查询
  223. if ($field['operator'] == 'RANGE' || $field['operator'] == 'NOT RANGE') {
  224. $arr = explode(',', $field['val']);
  225. // 重新确定操作符
  226. if (!isset($arr[0]) || $arr[0] === '') {
  227. $operator = $field['operator'] == 'RANGE' ? '<=' : '>';
  228. $arr = $arr[1];
  229. } elseif (!isset($arr[1]) || $arr[1] === '') {
  230. $operator = $field['operator'] == 'RANGE' ? '>=' : '<';
  231. $arr = $arr[0];
  232. } else {
  233. $operator = str_replace('RANGE', 'BETWEEN', $field['operator']);
  234. }
  235. $where[] = [$fieldName, $operator, $arr];
  236. continue;
  237. }
  238. switch ($field['operator']) {
  239. case '=':
  240. case '<>':
  241. $where[] = [$fieldName, $field['operator'], (string)$field['val']];
  242. break;
  243. case 'LIKE':
  244. case 'NOT LIKE':
  245. $where[] = [$fieldName, $field['operator'], '%' . str_replace('%', '\%', $field['val']) . '%'];
  246. break;
  247. case '>':
  248. case '>=':
  249. case '<':
  250. case '<=':
  251. $where[] = [$fieldName, $field['operator'], intval($field['val'])];
  252. break;
  253. case 'FIND_IN_SET':
  254. if (is_array($field['val'])) {
  255. foreach ($field['val'] as $val) {
  256. $where[] = [$fieldName, 'find in set', $val];
  257. }
  258. } else {
  259. $where[] = [$fieldName, 'find in set', $field['val']];
  260. }
  261. break;
  262. case 'IN':
  263. case 'NOT IN':
  264. $where[] = [$fieldName, $field['operator'], is_array($field['val']) ? $field['val'] : explode(',', $field['val'])];
  265. break;
  266. case 'NULL':
  267. case 'NOT NULL':
  268. $where[] = [$fieldName, strtolower($field['operator']), ''];
  269. break;
  270. }
  271. }
  272. // 数据权限
  273. $dataLimitAdminIds = $this->getDataLimitAdminIds();
  274. if ($dataLimitAdminIds) {
  275. $where[] = [$mainTableAlias . $this->dataLimitField, 'in', $dataLimitAdminIds];
  276. }
  277. return [$where, $alias, $limit, $order];
  278. }
  279. /**
  280. * 数据权限控制-获取有权限访问的管理员Ids
  281. * @throws Throwable
  282. */
  283. protected function getDataLimitAdminIds(): array
  284. {
  285. if (!$this->dataLimit || $this->auth->isSuperAdmin()) {
  286. return [];
  287. }
  288. $adminIds = [];
  289. if ($this->dataLimit == 'parent') {
  290. // 取得当前管理员的下级分组们
  291. $parentGroups = $this->auth->getAdminChildGroups();
  292. if ($parentGroups) {
  293. // 取得分组内的所有管理员
  294. $adminIds = $this->auth->getGroupAdmins($parentGroups);
  295. }
  296. } elseif (is_numeric($this->dataLimit) && $this->dataLimit > 0) {
  297. // 在组内,可查看所有,不在组内,可查看自己的
  298. $adminIds = $this->auth->getGroupAdmins([$this->dataLimit]);
  299. return in_array($this->auth->id, $adminIds) ? [] : [$this->auth->id];
  300. } elseif ($this->dataLimit == 'allAuth' || $this->dataLimit == 'allAuthAndOthers') {
  301. // 取得拥有他所有权限的分组
  302. $allAuthGroups = $this->auth->getAllAuthGroups($this->dataLimit);
  303. // 取得分组内的所有管理员
  304. $adminIds = $this->auth->getGroupAdmins($allAuthGroups);
  305. }
  306. $adminIds[] = $this->auth->id;
  307. return array_unique($adminIds);
  308. }
  309. /**
  310. * 从别名获取原始的逻辑运算符
  311. * @param string $operator 逻辑运算符别名
  312. * @return string 原始的逻辑运算符,无别名则原样返回
  313. */
  314. protected function getOperatorByAlias(string $operator): string
  315. {
  316. $alias = [
  317. 'ne' => '<>',
  318. 'eq' => '=',
  319. 'gt' => '>',
  320. 'egt' => '>=',
  321. 'lt' => '<',
  322. 'elt' => '<=',
  323. ];
  324. return $alias[$operator] ?? $operator;
  325. }
  326. }