getAllSessions($clientType !== '' ? $clientType : null); $userIds = array_values(array_unique(array_filter(array_map(static fn (array $session): string => (string)($session['id'] ?? ''), $sessions)))); $users = $this->getUsersByIds($userIds); $items = []; foreach ($sessions as $session) { $extra = $session['extra'] ?? []; $userId = (string)($session['id'] ?? ''); $user = $users[$userId] ?? null; $account = $extra['user_name'] ?? $user['user_name'] ?? ''; $realName = $extra['real_name'] ?? $user['real_name'] ?? ''; $sessionIp = $extra['ip'] ?? $user['login_ip'] ?? ''; $roleNames = $user['role_names'] ?? $this->formatExtraNames($extra['roles'] ?? []); if ($roleNames === '' && (int)($extra['is_super'] ?? 0) === 1) { $roleNames = '超级管理员'; } if ($keyword !== '' && !str_contains($account, $keyword) && !str_contains($realName, $keyword)) { continue; } if ($ip !== '' && !str_contains($sessionIp, $ip)) { continue; } $sessionStatus = (string)($session['status'] ?? 'online'); if ($jwt->getBlacklistStorage()->has((string)($session['jti'] ?? ''))) { $sessionStatus = 'offline'; } if ($status !== '' && $sessionStatus !== $status) { continue; } $createdAt = (int)($session['created_at'] ?? $user['login_time'] ?? time()); $lastActiveAt = (int)($session['last_active_at'] ?? $createdAt); $offlineAt = isset($session['offline_at']) ? (int)$session['offline_at'] : null; $durationEndAt = $sessionStatus === 'offline' && $offlineAt !== null ? $offlineAt : time(); $items[] = [ 'jti' => $session['jti'], 'user_id' => $userId, 'account' => $account, 'real_name' => $realName, 'dept_names' => $user['dept_names'] ?? '', 'post_names' => $user['post_names'] ?? '', 'role_names' => $roleNames, 'ip' => $sessionIp, 'ip_location' => $extra['ip_location'] ?? '', 'device' => $extra['os'] ?? $this->getCurrentRequestOs(), 'browser' => $extra['browser'] ?? $this->getCurrentRequestBrowser(), 'status' => $sessionStatus, 'client_type' => $session['client_type'] ?? '', 'login_time' => $createdAt, 'last_active_at' => $lastActiveAt, 'offline_at' => $offlineAt, 'online_seconds' => max(0, $durationEndAt - $createdAt), ]; } usort($items, static fn (array $a, array $b): int => ($b['last_active_at'] ?? 0) <=> ($a['last_active_at'] ?? 0)); $total = count($items); $offset = ($page - 1) * $limit; return [ 'items' => array_slice($items, $offset, $limit), 'total' => $total, ]; } public function kickout(string $jti): bool { return (new JwtToken())->kickoutByJti($jti); } protected function getUsersByIds(array $ids): array { if (empty($ids)) { return []; } $rows = Admin::query() ->withoutGlobalScopes([AccessPermissionScope::class]) ->with(['depts', 'posts', 'roles']) ->whereIn('id', $ids) ->get(); $users = []; foreach ($rows as $row) { $users[(string)$row->id] = [ 'user_name' => $row->user_name, 'real_name' => $row->real_name, 'is_super' => $row->is_super, 'login_ip' => $row->login_ip, 'login_time' => $row->login_time, 'dept_names' => $row->depts->pluck('name')->implode(' / '), 'post_names' => $row->posts->pluck('name')->implode(' / '), 'role_names' => $row->roles->pluck('name')->implode(' / ') ?: ((int)$row->is_super === 1 ? '超级管理员' : ''), ]; } return $users; } protected function formatExtraNames(array $items): string { $names = []; foreach ($items as $item) { if (is_array($item) && !empty($item['name'])) { $names[] = $item['name']; } } return implode(' / ', $names); } protected function getCurrentRequestBrowser(): string { $userAgent = request()?->header('user-agent', '') ?? ''; if (preg_match('/Edg/i', $userAgent)) { return 'Edge'; } if (preg_match('/Firefox/i', $userAgent)) { return 'Firefox'; } if (preg_match('/Chrome/i', $userAgent)) { return 'Chrome'; } if (preg_match('/Safari/i', $userAgent)) { return 'Safari'; } if (preg_match('/Opera/i', $userAgent)) { return 'Opera'; } return ''; } protected function getCurrentRequestOs(): string { $userAgent = request()?->header('user-agent', '') ?? ''; if (preg_match('/win/i', $userAgent)) { return 'Windows'; } if (preg_match('/mac/i', $userAgent)) { return 'Mac'; } if (preg_match('/linux/i', $userAgent)) { return 'Linux'; } return ''; } }