ensureTokenTable(); (new AdminAccessService())->bootstrapDefaults(); } public function login(string $mobile, string $password, Request $request): array { $admin = Db::name('admin_users')->where('mobile', $mobile)->find(); if (!$admin || ($admin['status'] ?? 'enabled') !== 'enabled') { throw new \RuntimeException('账号不存在或已停用'); } if (!password_verify($password, (string)$admin['password'])) { throw new \RuntimeException('手机号或密码错误'); } $token = bin2hex(random_bytes(24)); $tokenHash = hash('sha256', $token); $now = date('Y-m-d H:i:s'); $expireTime = date('Y-m-d H:i:s', time() + 7 * 24 * 3600); // Allow concurrent logins across devices/browsers. Only clean up this user's expired tokens. Db::name('admin_api_tokens') ->where('admin_user_id', $admin['id']) ->where('expire_time', '<', $now) ->delete(); Db::name('admin_api_tokens')->insert([ 'admin_user_id' => (int)$admin['id'], 'token_hash' => $tokenHash, 'expire_time' => $expireTime, 'last_active_at' => $now, 'last_ip' => $request->getRealIp(), 'user_agent' => substr((string)$request->header('user-agent', ''), 0, 500), 'created_at' => $now, 'updated_at' => $now, ]); Db::name('admin_users')->where('id', $admin['id'])->update([ 'last_login_at' => $now, 'updated_at' => $now, ]); return [ 'token' => $token, 'admin_info' => $this->adminInfo((int)$admin['id']), ]; } public function logout(Request $request): void { $token = $this->extractToken($request); if ($token === '') { return; } Db::name('admin_api_tokens')->where('token_hash', hash('sha256', $token))->delete(); } public function current(Request $request): ?array { $token = $this->extractToken($request); if ($token === '') { return null; } $record = Db::name('admin_api_tokens') ->where('token_hash', hash('sha256', $token)) ->find(); if (!$record) { return null; } if (!empty($record['expire_time']) && strtotime((string)$record['expire_time']) < time()) { Db::name('admin_api_tokens')->where('id', $record['id'])->delete(); return null; } $admin = Db::name('admin_users')->where('id', $record['admin_user_id'])->find(); if (!$admin || ($admin['status'] ?? 'enabled') !== 'enabled') { return null; } Db::name('admin_api_tokens')->where('id', $record['id'])->update([ 'last_active_at' => date('Y-m-d H:i:s'), 'last_ip' => $request->getRealIp(), 'user_agent' => substr((string)$request->header('user-agent', ''), 0, 500), 'updated_at' => date('Y-m-d H:i:s'), ]); return $this->adminInfo((int)$admin['id']); } public function hasPermission(array $adminInfo, string $permissionCode): bool { if ($permissionCode === '') { return true; } return in_array($permissionCode, $adminInfo['permission_codes'], true); } private function adminInfo(int $adminUserId): array { $admin = Db::name('admin_users')->where('id', $adminUserId)->find(); $roleIds = Db::name('admin_role_relations')->where('admin_user_id', $adminUserId)->column('role_id'); $roles = $roleIds ? Db::name('admin_roles')->whereIn('id', $roleIds)->select()->toArray() : []; $permissionIds = $roleIds ? Db::name('admin_role_permissions')->whereIn('role_id', $roleIds)->column('permission_id') : []; $permissions = $permissionIds ? Db::name('admin_permissions')->whereIn('id', $permissionIds)->select()->toArray() : []; return [ 'id' => (int)($admin['id'] ?? 0), 'name' => $admin['name'] ?? '', 'mobile' => $admin['mobile'] ?? '', 'email' => $admin['email'] ?? '', 'status' => $admin['status'] ?? 'enabled', 'role_names' => array_values(array_map(fn (array $item) => $item['name'], $roles)), 'permission_codes' => array_values(array_unique(array_map(fn (array $item) => $item['code'], $permissions))), ]; } private function extractToken(Request $request): string { $authorization = trim((string)$request->header('authorization', '')); if (preg_match('/^Bearer\s+(.+)$/i', $authorization, $matches)) { return trim($matches[1]); } return ''; } private function ensureTokenTable(): void { Db::execute(<<<'SQL' CREATE TABLE IF NOT EXISTS admin_api_tokens ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, admin_user_id BIGINT UNSIGNED NOT NULL, token_hash VARCHAR(64) NOT NULL, expire_time DATETIME NOT NULL, last_active_at DATETIME NULL DEFAULT NULL, last_ip VARCHAR(64) NOT NULL DEFAULT '', user_agent VARCHAR(500) NOT NULL DEFAULT '', created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_admin_api_tokens_token_hash (token_hash), KEY idx_admin_api_tokens_admin_user_id (admin_user_id), KEY idx_admin_api_tokens_expire_time (expire_time) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='后台登录Token'; SQL); } }