Backend.php 13 KB

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