where('id', $admin)->where('status', 1)->whereNull('delete_time')->exists()) { throw new ApiException(40301, '仅超级管理员可管理小程序身份', 403); } } public function users(int $admin, array $input): array { $this->authorize($admin); $page = new Pagination($input); $q = MiniUser::query(); if (isset($input['user_id'])) { $q->where('id', Fields::id($input, 'user_id')); } $total = (clone $q)->count(); return $page->result($q->orderByDesc('id')->offset($page->offset())->limit($page->pageSize)->get()->map(function ($u) { return [ 'id' => (int) $u->id, 'nickname' => $u->nickname, 'phone' => PhoneNumber::mask((string) ($u->phone ?? '')), 'status' => (int) $u->status, 'created_at' => $u->created_at, 'grants' => MiniGrant::query()->where('user_id', $u->id)->get([ 'id', 'role', 'resource_id', 'status', ])->toArray(), ]; })->all(), $total); } public function grant(int $admin, array $input): array { $this->authorize($admin); Fields::only($input, [ 'user_id', 'role', 'resource_id', 'status', ]); $user = Fields::id($input, 'user_id'); $role = Fields::choice($input, 'role', Access::roles()); $resource = Fields::id($input, 'resource_id'); $status = Input::integer($input['status'] ?? 1, 'status', 0, 1); return (new MiniUser())->getConnection()->transaction(function () use ($admin, $user, $role, $resource, $status) { if (!MiniUser::query()->where('id', $user)->lockForUpdate()->first()) { throw new ApiException(40401, '小程序用户不存在', 404); } if ($status === 1 && MiniGrant::query()->where('user_id', $user)->where('status', 1) ->where(function ($query) use ($role, $resource) { $query->where('role', '!=', $role)->orWhere('resource_id', '!=', $resource); })->exists()) { throw new ApiException(40903, '一个小程序账号只能绑定一个业务身份,请先停用原身份', 409); } $model = Access::resourceModel($role); if (!$model::query()->where('id', $resource)->exists()) { throw new ApiException(40401, '业务档案不存在', 404); } $row = MiniGrant::query()->updateOrCreate( [ 'user_id' => $user, 'role' => $role, 'resource_id' => $resource, ], [ 'status' => $status, 'created_by' => $admin, 'created_at' => date('Y-m-d H:i:s'), 'updated_at' => date('Y-m-d H:i:s'), ] ); Commands::audit('admin', $admin, 'identity.grant', (int) $row->id, [ 'user_id' => $user, 'role' => $role, 'resource_id' => $resource, 'status' => $status, ]); return [ 'id' => (int) $row->id, 'status' => $status, ]; }); } /** * Binds a phone to an existing account so SMS login reaches the same user and therefore the * same granted identity. This never creates an account and never changes a grant. */ public function bindPhone(int $admin, array $input): array { $this->authorize($admin); Fields::only($input, [ 'user_id', 'phone', ]); $user = Fields::id($input, 'user_id'); $phone = SmsLogin::mobile($input); return (new MiniUser())->getConnection()->transaction(function () use ($admin, $user, $phone) { if (!MiniUser::query()->where('id', $user)->lockForUpdate()->first()) { throw new ApiException(40401, '小程序用户不存在', 404); } if (MiniUser::query()->where('phone', $phone)->where('id', '!=', $user)->lockForUpdate()->exists()) { throw new ApiException(40903, '该手机号已绑定其他小程序账号,请先解绑', 409); } MiniUser::query()->where('id', $user)->update([ 'phone' => $phone, 'updated_at' => date('Y-m-d H:i:s'), ]); Commands::audit('admin', $admin, 'identity.bind_phone', $user, [ 'user_id' => $user, 'phone' => PhoneNumber::mask($phone), ]); return [ 'user_id' => $user, 'phone' => PhoneNumber::mask($phone), ]; }); } }