'user_group', 'auth_group_access' => '', 'auth_rule' => 'user_rule', ], $config)); $this->setKeepTime((int)config('buildadmin.user_token_keep_time', 86400)); } public function __get($name): mixed { return $this->model?->$name; } public static function instance(array $options = []): Auth { $request = $options['request'] ?? (function_exists('request') ? request() : null); unset($options['request']); if ($request && !isset($request->userAuth)) { $request->userAuth = new static($options); } return $request && isset($request->userAuth) ? $request->userAuth : new static($options); } public function init($token): bool { $tokenData = Token::get($token); if ($tokenData) { Token::tokenExpirationCheck($tokenData); $userId = $tokenData['user_id']; if ($tokenData['type'] == self::TOKEN_TYPE && $userId > 0) { $this->model = GameHotDataRedis::userModelFromCacheOrDb($userId); if (!$this->model) { $this->setError('Account not exist'); return false; } $status = $this->model->status ?? ''; if ($status === 'disable' || $status === 0 || $status === '0') { $this->setError('Account disabled'); return false; } $channelId = intval($this->model->channel_id ?? 0); if ($channelId > 0 && !$this->isChannelEnabled($channelId)) { $this->setError('Channel disabled'); return false; } $this->token = $token; $this->loginEd = true; return true; } } $this->setError('Token login failed'); $this->reset(); return false; } public function register(string $username, string $password = '', string $phone = '', string $email = '', int $group = 1, array $extend = []): bool { $this->registerDuplicateKind = ''; $request = function_exists('request') ? request() : null; $ip = $request ? $request->getRealIp() : '0.0.0.0'; if ($email && !filter_var($email, FILTER_VALIDATE_EMAIL)) { $this->setError(__('Email')); return false; } $isMobileUsername = MalaysiaMobilePhone::isValid($username); $isNormalUsername = preg_match('/^[a-zA-Z][a-zA-Z0-9_]{2,15}$/', $username) === 1; if ($username && !$isMobileUsername && !$isNormalUsername) { $this->setError(__('Username')); return false; } if (User::where('email', $email)->find() && $email) { $this->registerDuplicateKind = 'email'; $this->setError(__('Email') . ' ' . __('already exists')); return false; } if (User::where('username', $username)->find()) { $this->registerDuplicateKind = 'username'; $this->setError(__('Username') . ' ' . __('already exists')); return false; } if ($phone !== '' && User::where('phone', $phone)->find()) { $this->registerDuplicateKind = 'phone'; $this->setError(__('Mobile') . ' ' . __('already exists')); return false; } $nickname = MalaysiaMobilePhone::maskInNickname($username); $time = time(); $data = [ 'group_id' => $group, 'nickname' => $nickname, 'join_ip' => $ip, 'join_time' => $time, 'last_login_ip' => $ip, 'last_login_time' => $time, 'status' => 1, 'uuid' => User::generateUniquePublicCode10(), 'remark' => User::formatLoginRemark($time, $ip), ]; $data = array_merge(compact('username', 'password', 'phone', 'email'), $data, $extend); $channelIdForRegister = isset($data['channel_id']) ? intval($data['channel_id']) : 0; if ($channelIdForRegister > 0 && !$this->isChannelEnabled($channelIdForRegister)) { $this->setError('Channel disabled'); return false; } Db::startTrans(); try { $this->model = User::create($data); $this->token = Random::uuid(); Token::set($this->token, self::TOKEN_TYPE, $this->model->id, $this->keepTime); Db::commit(); if ($password) { $this->model->resetPassword($this->model->id, $password); } event_trigger('userRegisterSuccess', $this->model); } catch (Throwable $e) { $this->setError($e->getMessage()); Db::rollback(); return false; } return true; } public function login(string $username, string $password, bool $keep): bool { $accountType = false; if (MalaysiaMobilePhone::isValid($username)) { $accountType = 'phone'; } elseif (filter_var($username, FILTER_VALIDATE_EMAIL)) { $accountType = 'email'; } elseif (preg_match('/^[a-zA-Z][a-zA-Z0-9_]{2,15}$/', $username)) { $accountType = 'username'; } if ($accountType) { $this->model = User::where($accountType, $username)->find(); } else { // 兼容历史纯数字账号、带 + 前缀手机号等非标准格式 $this->model = User::where('username', $username)->whereOr('phone', $username)->find(); if (!$this->model && str_starts_with($username, '+')) { $this->model = User::where('phone', substr($username, 1))->find(); } } if (!$this->model) { $this->setError('Account not exist'); return false; } $status = $this->model->status ?? ''; if ($status === 'disable' || $status === 0 || $status === '0') { $this->setError('Account disabled'); return false; } $channelId = intval($this->model->channel_id ?? 0); if ($channelId > 0 && !$this->isChannelEnabled($channelId)) { $this->setError('Channel disabled'); return false; } $userLoginRetry = config('buildadmin.user_login_retry'); if ($userLoginRetry && $this->model->last_login_time) { $lastLoginTs = is_numeric($this->model->last_login_time) ? (int)$this->model->last_login_time : strtotime($this->model->last_login_time); if ($this->model->login_failure > 0 && $lastLoginTs > 0 && time() - $lastLoginTs >= 86400) { $this->model->login_failure = 0; $this->model->save(); $this->model = User::find($this->model->id); } if ($this->model->login_failure >= $userLoginRetry) { $this->setError('Please try again after 1 day'); return false; } } if (!verify_password($password, $this->model->password, ['salt' => $this->model->salt ?? ''])) { $this->loginFailed(); $this->setError('Password is incorrect'); return false; } if (config('buildadmin.user_sso')) { Token::clear(self::TOKEN_TYPE, $this->model->id); Token::clear(self::TOKEN_TYPE . '-refresh', $this->model->id); } if ($keep) $this->setRefreshToken($this->refreshTokenKeepTime); return $this->loginSuccessful(); } public function direct(int $userId): bool { $this->model = User::find($userId); if (!$this->model) return false; if (config('buildadmin.user_sso')) { Token::clear(self::TOKEN_TYPE, $this->model->id); Token::clear(self::TOKEN_TYPE . '-refresh', $this->model->id); } return $this->loginSuccessful(); } public function loginSuccessful(): bool { if (!$this->model) return false; $request = function_exists('request') ? request() : null; $ip = $request ? $request->getRealIp() : '0.0.0.0'; if (!$this->token) { $this->token = Random::uuid(); Token::set($this->token, self::TOKEN_TYPE, $this->model->id, $this->keepTime); } $this->model->startTrans(); try { $this->model->login_failure = 0; $this->model->last_login_time = time(); $this->model->last_login_ip = $ip; $this->model->remark = User::formatLoginRemark(time(), $ip); $this->model->save(); $this->loginEd = true; $this->model->commit(); } catch (Throwable $e) { $this->model->rollback(); if ($this->token) { Token::delete($this->token); $this->token = ''; } $this->setError($e->getMessage()); return false; } return true; } public function loginFailed(): bool { if (!$this->model) return false; $request = function_exists('request') ? request() : null; $ip = $request ? $request->getRealIp() : '0.0.0.0'; $this->model->startTrans(); try { $this->model->login_failure++; $this->model->last_login_time = time(); $this->model->last_login_ip = $ip; $this->model->save(); $this->model->commit(); } catch (Throwable $e) { $this->model->rollback(); } return $this->reset(); } public function logout(): bool { if (!$this->loginEd) { $this->setError('You are not logged in'); return false; } return $this->reset(); } public function isLogin(): bool { return $this->loginEd; } public function getUser(): User { return $this->model; } public function getToken(): string { return $this->token; } public function setRefreshToken(int $keepTime = 0): void { $this->refreshToken = Random::uuid(); Token::set($this->refreshToken, self::TOKEN_TYPE . '-refresh', $this->model->id, $keepTime); } public function getRefreshToken(): string { return $this->refreshToken; } public function getUserInfo(): array { if (!$this->model) return []; $info = $this->model->toArray(); $info = array_intersect_key($info, array_flip($this->getAllowFields())); $info['token'] = $this->getToken(); $info['refresh_token'] = $this->getRefreshToken(); return $info; } public function getAllowFields(): array { return $this->allowFields; } public function setAllowFields($fields): void { $this->allowFields = $fields; } public function setKeepTime(int $keepTime = 0): void { $this->keepTime = $keepTime; } public function check(string $name, int $uid = 0, string $relation = 'or', string $mode = 'url'): bool { return parent::check($name, $uid ?: $this->id, $relation, $mode); } public function getRuleList(int $uid = 0): array { return parent::getRuleList($uid ?: $this->id); } public function getRuleIds(int $uid = 0): array { return parent::getRuleIds($uid ?: $this->id); } public function getMenus(int $uid = 0): array { return parent::getMenus($uid ?: $this->id); } public function isSuperUser(): bool { return in_array('*', $this->getRuleIds()); } public function setError(string $error): Auth { $this->error = $error; return $this; } public function getError(): string { return $this->error ? __($this->error) : ''; } public function getRegisterDuplicateKind(): string { return $this->registerDuplicateKind; } protected function reset(bool $deleteToken = true): bool { if ($deleteToken && $this->token) { Token::delete($this->token); } $this->token = ''; $this->loginEd = false; $this->model = null; $this->refreshToken = ''; $this->registerDuplicateKind = ''; $this->setError(''); $this->setKeepTime((int)config('buildadmin.user_token_keep_time', 86400)); return true; } private function isChannelEnabled(int $channelId): bool { $status = Db::name('channel')->where('id', $channelId)->value('status'); if ($status === null || $status === '') { return false; } return intval($status) === 1; } }