IdentityManagement.php 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. <?php
  2. declare(strict_types=1);
  3. namespace app\miniapi\service;
  4. use app\miniapi\model\MiniUser;
  5. use app\miniapi\model\MiniGrant;
  6. use app\miniapi\http\Fields;
  7. use app\miniapi\http\Input;
  8. use app\miniapi\http\Pagination;
  9. use app\miniapi\support\PhoneNumber;
  10. use app\miniapi\exception\ApiException;
  11. use plugin\saiadmin\app\model\water\WaterOperator;
  12. final class IdentityManagement
  13. {
  14. private function authorize(int $admin): void
  15. {
  16. if ($admin !== 1 || !WaterOperator::query()->where('id', $admin)->where('status', 1)->whereNull('delete_time')->exists()) {
  17. throw new ApiException(40301, '仅超级管理员可管理小程序身份', 403);
  18. }
  19. }
  20. public function users(int $admin, array $input): array
  21. {
  22. $this->authorize($admin);
  23. $page = new Pagination($input);
  24. $q = MiniUser::query();
  25. if (isset($input['user_id'])) {
  26. $q->where('id', Fields::id($input, 'user_id'));
  27. }
  28. $total = (clone $q)->count();
  29. return $page->result($q->orderByDesc('id')->offset($page->offset())->limit($page->pageSize)->get()->map(function ($u) {
  30. return [
  31. 'id' => (int) $u->id,
  32. 'nickname' => $u->nickname,
  33. 'phone' => PhoneNumber::mask((string) ($u->phone ?? '')),
  34. 'status' => (int) $u->status,
  35. 'created_at' => $u->created_at,
  36. 'grants' => MiniGrant::query()->where('user_id', $u->id)->get([
  37. 'id',
  38. 'role',
  39. 'resource_id',
  40. 'status',
  41. ])->toArray(),
  42. ];
  43. })->all(), $total);
  44. }
  45. public function grant(int $admin, array $input): array
  46. {
  47. $this->authorize($admin);
  48. Fields::only($input, [
  49. 'user_id',
  50. 'role',
  51. 'resource_id',
  52. 'status',
  53. ]);
  54. $user = Fields::id($input, 'user_id');
  55. $role = Fields::choice($input, 'role', Access::roles());
  56. $resource = Fields::id($input, 'resource_id');
  57. $status = Input::integer($input['status'] ?? 1, 'status', 0, 1);
  58. return (new MiniUser())->getConnection()->transaction(function () use ($admin, $user, $role, $resource, $status) {
  59. if (!MiniUser::query()->where('id', $user)->lockForUpdate()->first()) {
  60. throw new ApiException(40401, '小程序用户不存在', 404);
  61. }
  62. if ($status === 1 && MiniGrant::query()->where('user_id', $user)->where('status', 1)
  63. ->where(function ($query) use ($role, $resource) {
  64. $query->where('role', '!=', $role)->orWhere('resource_id', '!=', $resource);
  65. })->exists()) {
  66. throw new ApiException(40903, '一个小程序账号只能绑定一个业务身份,请先停用原身份', 409);
  67. }
  68. $model = Access::resourceModel($role);
  69. if (!$model::query()->where('id', $resource)->exists()) {
  70. throw new ApiException(40401, '业务档案不存在', 404);
  71. }
  72. $row = MiniGrant::query()->updateOrCreate(
  73. [
  74. 'user_id' => $user,
  75. 'role' => $role,
  76. 'resource_id' => $resource,
  77. ],
  78. [
  79. 'status' => $status,
  80. 'created_by' => $admin,
  81. 'created_at' => date('Y-m-d H:i:s'),
  82. 'updated_at' => date('Y-m-d H:i:s'),
  83. ]
  84. );
  85. Commands::audit('admin', $admin, 'identity.grant', (int) $row->id, [
  86. 'user_id' => $user,
  87. 'role' => $role,
  88. 'resource_id' => $resource,
  89. 'status' => $status,
  90. ]);
  91. return [
  92. 'id' => (int) $row->id,
  93. 'status' => $status,
  94. ];
  95. });
  96. }
  97. /**
  98. * Binds a phone to an existing account so SMS login reaches the same user and therefore the
  99. * same granted identity. This never creates an account and never changes a grant.
  100. */
  101. public function bindPhone(int $admin, array $input): array
  102. {
  103. $this->authorize($admin);
  104. Fields::only($input, [
  105. 'user_id',
  106. 'phone',
  107. ]);
  108. $user = Fields::id($input, 'user_id');
  109. $phone = SmsLogin::mobile($input);
  110. return (new MiniUser())->getConnection()->transaction(function () use ($admin, $user, $phone) {
  111. if (!MiniUser::query()->where('id', $user)->lockForUpdate()->first()) {
  112. throw new ApiException(40401, '小程序用户不存在', 404);
  113. }
  114. if (MiniUser::query()->where('phone', $phone)->where('id', '!=', $user)->lockForUpdate()->exists()) {
  115. throw new ApiException(40903, '该手机号已绑定其他小程序账号,请先解绑', 409);
  116. }
  117. MiniUser::query()->where('id', $user)->update([
  118. 'phone' => $phone,
  119. 'updated_at' => date('Y-m-d H:i:s'),
  120. ]);
  121. Commands::audit('admin', $admin, 'identity.bind_phone', $user, [
  122. 'user_id' => $user,
  123. 'phone' => PhoneNumber::mask($phone),
  124. ]);
  125. return [
  126. 'user_id' => $user,
  127. 'phone' => PhoneNumber::mask($phone),
  128. ];
  129. });
  130. }
  131. }