Curd.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571
  1. <?php
  2. namespace app\controller;
  3. use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
  4. use Illuminate\Database\Query\Builder as QueryBuilder;
  5. use app\common\Auth;
  6. use app\common\Tree;
  7. use app\common\Util;
  8. use support\CustomException;
  9. use support\exception\BusinessException;
  10. use support\Log;
  11. use support\Model;
  12. use support\Request;
  13. use support\Response;
  14. use Tinywan\Jwt\JwtToken;
  15. class Curd
  16. {
  17. /**
  18. * @var Model
  19. */
  20. protected $model = null;
  21. /**
  22. * 数据限制
  23. * 例如当$dataLimit='personal'时将只返回当前管理员的数据
  24. * @var string
  25. */
  26. protected $dataLimit = null;
  27. /**
  28. * 数据限制字段
  29. */
  30. protected $dataLimitField = 'user_id';
  31. /**
  32. * 是否开启写操作验证
  33. */
  34. protected $validate = false;
  35. /**
  36. * 验证类
  37. */
  38. protected $validateClass = '';
  39. /**
  40. * 查询
  41. * @param Request $request
  42. * @return Response
  43. * @throws BusinessException
  44. */
  45. public function select(Request $request): Response
  46. {
  47. [$where, $format, $limit, $field, $order] = $this->selectInput($request);
  48. $query = $this->doSelect($where, $field, $order);
  49. return $this->doFormat($query, $format, $limit);
  50. }
  51. /**
  52. * @Desc 记录详情
  53. * @Author Gorden
  54. * @Date 2024/3/18 14:22
  55. *
  56. * @param Request $request
  57. * @return Response
  58. */
  59. public function info(Request $request): Response
  60. {
  61. $primaryKey = $this->model->getKeyName();
  62. $validateArr = [
  63. $primaryKey => $request->get('id')
  64. ];
  65. if ($this->validate && !$this->validateClass->scene('info')->check($validateArr)) {
  66. return json_fail($this->validateClass->getError());
  67. }
  68. $data = $this->model->where($primaryKey, $request->get('id'))->first();
  69. if (method_exists($this, "afterInfoQuery")) {
  70. $data = call_user_func([$this, "afterInfoQuery"], $data);
  71. }
  72. return json_success('', $data);
  73. }
  74. /**
  75. * 添加
  76. * @param Request $request
  77. * @return Response
  78. * @throws BusinessException
  79. */
  80. public function insert(Request $request): Response
  81. {
  82. if ($this->validate && !$this->validateClass->scene('add')->check($request->post())) {
  83. return json_fail($this->validateClass->getError());
  84. }
  85. try {
  86. $data = $this->insertInput($request);
  87. $this->doInsert($data);
  88. } catch (BusinessException $customException) {
  89. return json_fail($customException->getMessage());
  90. } catch (\Exception $e) {
  91. Log::error("数据写入失败",['msg'=>$e->getMessage()]);
  92. return json_fail('数据写入失败');
  93. }
  94. return json_success('success');
  95. }
  96. /**
  97. * 更新
  98. * @param Request $request
  99. * @return Response
  100. * @throws BusinessException
  101. */
  102. public function update(Request $request): Response
  103. {
  104. if ($this->validate && !$this->validateClass->scene('update')->check($request->post())) {
  105. return json_fail($this->validateClass->getError());
  106. }
  107. try {
  108. [$id, $data] = $this->updateInput($request);
  109. $this->doUpdate($id, $data);
  110. } catch (BusinessException $e) {
  111. return json_fail($e->getMessage());
  112. } catch (\Exception $e) {
  113. Log::error("数据更新失败",['msg'=>$e->getMessage()]);
  114. return json_fail('数据更新失败');
  115. }
  116. return json_success('success');
  117. }
  118. /**
  119. * 删除
  120. * @param Request $request
  121. * @return Response
  122. * @throws BusinessException
  123. */
  124. public function delete(Request $request): Response
  125. {
  126. $ids = $this->deleteInput($request);
  127. $this->doDelete($ids);
  128. return json_success('success');
  129. }
  130. /**
  131. * @Desc 软删除
  132. * @Author Gorden
  133. * @Date 2024/2/26 9:27
  134. *
  135. * @param Request $request
  136. * @return Response
  137. * @throws BusinessException
  138. */
  139. public function softDelete(Request $request): Response
  140. {
  141. $ids = $this->deleteInput($request);
  142. $this->doSoftDelete($ids, ['is_del' => 1]);
  143. return json_success('success');
  144. }
  145. /**
  146. * 查询前置
  147. * @param Request $request
  148. * @return array
  149. * @throws BusinessException
  150. */
  151. protected function selectInput(Request $request): array
  152. {
  153. $field = $request->get('field');
  154. $order = $request->get('order', 'asc');
  155. $format = $request->get('format', 'normal');
  156. $limit = (int)$request->get('pageSize', $format === 'tree' ? 1000 : 10);
  157. $limit = $limit <= 0 ? 10 : $limit;
  158. $order = $order === 'asc' ? 'asc' : 'desc';
  159. $where = $request->get();
  160. $page = (int)$request->get('page');
  161. $page = $page > 0 ? $page : 1;
  162. $table = config('database.connections.mysql.prefix') . $this->model->getTable();
  163. $allow_column = Util::db()->select("desc `$table`");
  164. if (!$allow_column) {
  165. throw new BusinessException('表不存在');
  166. }
  167. $allow_column = array_column($allow_column, 'Field', 'Field');
  168. if (!in_array($field, $allow_column)) {
  169. $field = null;
  170. }
  171. foreach ($where as $column => $value) {
  172. if (
  173. $value === '' || !isset($allow_column[$column]) ||
  174. is_array($value) && (empty($value) || !in_array($value[0], ['null', 'not null']) && !isset($value[1]))
  175. ) {
  176. unset($where[$column]);
  177. }
  178. if (!is_array($value) && substr($value, 0,5) == 'like,') {
  179. $where[$column] = explode(',',$value);
  180. }
  181. }
  182. // 按照数据限制字段返回数据
  183. if ($this->dataLimit === 'personal') {
  184. $where[$this->dataLimitField] = JwtToken::getCurrentId();
  185. } elseif ($this->dataLimit === 'auth') {
  186. $primary_key = $this->model->getKeyName();
  187. if (!Auth::isSupperAdmin() && (!isset($where[$primary_key]) || $this->dataLimitField != $primary_key)) {
  188. $where[$this->dataLimitField] = ['in', Auth::getScopeAdminIds(true)];
  189. }
  190. }
  191. return [$where, $format, $limit, $field, $order, $page];
  192. }
  193. /**
  194. * 指定查询where条件,并没有真正的查询数据库操作
  195. * @param array $where
  196. * @param string|null $field
  197. * @param string $order
  198. * @return EloquentBuilder|QueryBuilder|Model
  199. */
  200. protected function doSelect(array $where, string $field = null, string $order = 'desc')
  201. {
  202. $model = $this->model;
  203. foreach ($where as $column => $value) {
  204. if (is_array($value)) {
  205. if ($value[0] === 'like' || $value[0] === 'not like') {
  206. $model = $model->where($column, $value[0], "%$value[1]%");
  207. } elseif (in_array($value[0], ['>', '=', '<', '<>'])) {
  208. $model = $model->where($column, $value[0], $value[1]);
  209. } elseif ($value[0] == 'in' && !empty($value[1])) {
  210. $valArr = $value[1];
  211. if (is_string($value[1])) {
  212. $valArr = explode(",", trim($value[1]));
  213. }
  214. $model = $model->whereIn($column, $valArr);
  215. } elseif ($value[0] == 'not in' && !empty($value[1])) {
  216. $valArr = $value[1];
  217. if (is_string($value[1])) {
  218. $valArr = explode(",", trim($value[1]));
  219. }
  220. $model = $model->whereNotIn($column, $valArr);
  221. } elseif ($value[0] == 'null') {
  222. $model = $model->whereNull($column);
  223. } elseif ($value[0] == 'not null') {
  224. $model = $model->whereNotNull($column);
  225. } elseif ($value[0] !== '' || $value[1] !== '') {
  226. $model = $model->whereBetween($column, $value);
  227. }
  228. } else {
  229. $model = $model->where($column, $value);
  230. }
  231. }
  232. if ($field) {
  233. $model = $model->orderBy($field, $order);
  234. }
  235. return $model;
  236. }
  237. /**
  238. * 执行真正查询,并返回格式化数据
  239. * @param $query
  240. * @param $format
  241. * @param $limit
  242. * @return Response
  243. */
  244. protected function doFormat($query, $format, $limit): Response
  245. {
  246. $methods = [
  247. 'select' => 'formatSelect',
  248. 'tree' => 'formatTree',
  249. 'table_tree' => 'formatTableTree',
  250. 'normal' => 'formatNormal',
  251. ];
  252. $paginator = $query->paginate($limit);
  253. $total = $paginator->total();
  254. $items = $paginator->items();
  255. if (method_exists($this, "afterQuery")) {
  256. $items = call_user_func([$this, "afterQuery"], $items);
  257. }
  258. $format_function = $methods[$format] ?? 'formatNormal';
  259. return call_user_func([$this, $format_function], $items, $total);
  260. }
  261. /**
  262. * 插入前置方法
  263. * @param Request $request
  264. * @return array
  265. * @throws BusinessException
  266. */
  267. protected function insertInput(Request $request): array
  268. {
  269. $data = $this->inputFilter($request->post());
  270. $password_filed = 'password';
  271. if (isset($data[$password_filed])) {
  272. $data[$password_filed] = Util::passwordHash($data[$password_filed]);
  273. }
  274. if (!Auth::isSupperAdmin() && $this->dataLimit) {
  275. if (!empty($data[$this->dataLimitField])) {
  276. $admin_id = $data[$this->dataLimitField];
  277. if (!in_array($admin_id, Auth::getScopeAdminIds(true))) {
  278. throw new BusinessException('无数据权限');
  279. }
  280. }
  281. }
  282. return $data;
  283. }
  284. /**
  285. * 执行插入
  286. * @param array $data
  287. * @return mixed|null
  288. */
  289. protected function doInsert(array $data)
  290. {
  291. $primary_key = $this->model->getKeyName();
  292. $model_class = get_class($this->model);
  293. $model = new $model_class;
  294. foreach ($data as $key => $val) {
  295. $model->{$key} = $val;
  296. }
  297. $model->save();
  298. return $primary_key ? $model->$primary_key : null;
  299. }
  300. /**
  301. * 更新前置方法
  302. * @param Request $request
  303. * @return array
  304. * @throws BusinessException
  305. */
  306. protected function updateInput(Request $request): array
  307. {
  308. $primary_key = $this->model->getKeyName();
  309. $id = $request->post($primary_key);
  310. $data = $this->inputFilter($request->post());
  311. $model = $this->model->find($id);
  312. if (!$model) {
  313. throw new BusinessException('记录不存在', 2);
  314. }
  315. if (!Auth::isSupperAdmin() && $this->dataLimit) {
  316. $scopeAdminIds = Auth::getScopeAdminIds(true);
  317. $admin_ids = [
  318. $data[$this->dataLimitField] ?? false, // 检查要更新的数据admin_id是否是有权限的值
  319. $model->{$this->dataLimitField} ?? false // 检查要更新的记录的admin_id是否有权限
  320. ];
  321. foreach ($admin_ids as $admin_id) {
  322. if ($admin_id && !in_array($admin_id, $scopeAdminIds)) {
  323. throw new BusinessException('无数据权限');
  324. }
  325. }
  326. }
  327. $password_filed = 'password';
  328. if (isset($data[$password_filed])) {
  329. // 密码为空,则不更新密码
  330. if ($data[$password_filed] === '') {
  331. unset($data[$password_filed]);
  332. } else {
  333. $data[$password_filed] = Util::passwordHash($data[$password_filed]);
  334. }
  335. }
  336. unset($data[$primary_key]);
  337. return [$id, $data];
  338. }
  339. /**
  340. * @Desc 更新单个字段
  341. * @Author Gorden
  342. * @Date 2024/2/28 10:31
  343. *
  344. * @param $primaryValue
  345. * @param $field
  346. * @param $value
  347. * @return Response
  348. */
  349. protected function updateField($primaryValue, $field, $value)
  350. {
  351. try {
  352. $primaryKey = $this->model->getKeyName();
  353. $this->model->where($primaryKey, $primaryValue)->update([$field => $value]);
  354. } catch (\Exception $e) {
  355. return json_fail($e->getMessage());
  356. }
  357. return json_success('success');
  358. }
  359. /**
  360. * 执行更新
  361. * @param $id
  362. * @param $data
  363. * @return void
  364. */
  365. protected function doUpdate($id, $data)
  366. {
  367. $model = $this->model->find($id);
  368. foreach ($data as $key => $val) {
  369. $model->{$key} = $val;
  370. }
  371. $model->save();
  372. }
  373. /**
  374. * 对用户输入表单过滤
  375. * @param array $data
  376. * @return array
  377. * @throws BusinessException
  378. */
  379. protected function inputFilter(array $data): array
  380. {
  381. $table = config('database.connections.mysql.prefix') . $this->model->getTable();
  382. $allow_column = $this->model->getConnection()->select("desc `$table`");
  383. if (!$allow_column) {
  384. throw new BusinessException('表不存在', 2);
  385. }
  386. $columns = array_column($allow_column, 'Type', 'Field');
  387. foreach ($data as $col => $item) {
  388. if (!isset($columns[$col])) {
  389. unset($data[$col]);
  390. continue;
  391. }
  392. // 非字符串类型传空则为null
  393. if ($item === '' && strpos(strtolower($columns[$col]), 'varchar') === false && strpos(strtolower($columns[$col]), 'text') === false) {
  394. $data[$col] = null;
  395. }
  396. if (is_array($item)) {
  397. $data[$col] = implode(',', $item);
  398. }
  399. if ($item != '' && (strpos(strtolower($columns[$col]), 'varchar') || strpos(strtolower($columns[$col]), 'text'))) {
  400. // $data[$col] = htmlspecialchars($item);
  401. }
  402. }
  403. if (empty($data['created_at'])) {
  404. unset($data['created_at']);
  405. }
  406. if (empty($data['updated_at'])) {
  407. unset($data['updated_at']);
  408. }
  409. return $data;
  410. }
  411. /**
  412. * 删除前置方法
  413. * @param Request $request
  414. * @return array
  415. * @throws BusinessException
  416. */
  417. protected function deleteInput(Request $request): array
  418. {
  419. $primary_key = $this->model->getKeyName();
  420. if (!$primary_key) {
  421. throw new BusinessException('该表无主键,不支持删除');
  422. }
  423. $ids = (array)$request->post($primary_key, []);
  424. if (!Auth::isSupperAdmin() && $this->dataLimit) {
  425. $admin_ids = $this->model->where($primary_key, $ids)->pluck($this->dataLimitField)->toArray();
  426. if (array_diff($admin_ids, Auth::getScopeAdminIds(true))) {
  427. throw new BusinessException('无数据权限');
  428. }
  429. }
  430. return $ids;
  431. }
  432. /**
  433. * 执行删除
  434. * @param array $ids
  435. * @return void
  436. */
  437. protected function doDelete(array $ids)
  438. {
  439. if (!$ids) {
  440. return;
  441. }
  442. $primary_key = $this->model->getKeyName();
  443. $this->model->whereIn($primary_key, $ids)->delete();
  444. }
  445. /**
  446. * @Desc 执行软删除
  447. * @Author Gorden
  448. * @Date 2024/2/26 9:27
  449. *
  450. * @param array $ids
  451. * @return void
  452. */
  453. protected function doSoftDelete(array $ids, $data)
  454. {
  455. if (!$ids) {
  456. return;
  457. }
  458. $primary_key = $this->model->getKeyName();
  459. $this->model->whereIn($primary_key, $ids)->update($data);
  460. }
  461. /**
  462. * 格式化树
  463. * @param $items
  464. * @return Response
  465. */
  466. protected function formatTree($items): Response
  467. {
  468. $format_items = [];
  469. foreach ($items as $item) {
  470. $format_items[] = [
  471. 'name' => $item->title ?? $item->name ?? $item->id,
  472. 'value' => (string)$item->id,
  473. 'id' => $item->id,
  474. 'pid' => $item->pid,
  475. ];
  476. }
  477. $tree = new Tree($format_items);
  478. return json_success('success', $tree->getTree());
  479. }
  480. /**
  481. * 格式化表格树
  482. * @param $items
  483. * @return Response
  484. */
  485. protected function formatTableTree($items): Response
  486. {
  487. $tree = new Tree($items);
  488. return json_success('success', $tree->getTree());
  489. }
  490. /**
  491. * 格式化下拉列表
  492. * @param $items
  493. * @return Response
  494. */
  495. protected function formatSelect($items): Response
  496. {
  497. $formatted_items = [];
  498. foreach ($items as $item) {
  499. $formatted_items[] = [
  500. 'name' => $item->title ?? $item->name ?? $item->id,
  501. 'value' => $item->id
  502. ];
  503. }
  504. return json_success('success', $formatted_items);
  505. }
  506. /**
  507. * 通用格式化
  508. * @param $items
  509. * @param $total
  510. * @return Response
  511. */
  512. protected function formatNormal($items, $total): Response
  513. {
  514. $data = [
  515. 'total' => $total,
  516. 'rows' => $items
  517. ];
  518. return json(['code' => 200, 'msg' => 'success', 'data' => $data]);
  519. }
  520. /**
  521. * 查询数据库后置方法,可用于修改数据
  522. * @param mixed $items 原数据
  523. * @return mixed 修改后数据
  524. */
  525. protected function afterQuery($items)
  526. {
  527. return $items;
  528. }
  529. }