整理文件

This commit is contained in:
CN-JS-HuiBai
2026-04-07 17:53:03 +08:00
parent 5b775622e1
commit f83ab6745c
401 changed files with 70 additions and 41568 deletions

View File

@@ -0,0 +1,248 @@
<?php
namespace Plugin\UserOnlineDevices\Controllers;
use App\Http\Controllers\PluginController;
use App\Models\User;
use App\Services\DeviceStateService;
use Illuminate\Contracts\View\View;
use Illuminate\Http\Request;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\URL;
class UserOnlineDevicesController extends PluginController
{
public function __construct(
private readonly DeviceStateService $deviceStateService
) {
}
public function summary(Request $request)
{
if ($error = $this->beforePluginAction()) {
return $this->fail($error);
}
return $this->success($this->buildPayload($request->user()));
}
public function panelUrl(Request $request)
{
if ($error = $this->beforePluginAction()) {
return $this->fail($error);
}
$user = $request->user();
$ttlMinutes = max(1, (int) $this->getConfig('signed_url_ttl_minutes', 60));
$expiresAt = now()->addMinutes($ttlMinutes);
return $this->success([
'url' => URL::temporarySignedRoute(
'user-online-devices.panel',
$expiresAt,
['user' => $user->id]
),
'expires_at' => $expiresAt->timestamp,
]);
}
public function adminUsersPage(): View
{
abort_unless($this->isPluginEnabled(), 404);
$securePath = admin_setting('secure_path', admin_setting('frontend_admin_path', hash('crc32b', config('app.key'))));
return view('UserOnlineDevices::admin-users', [
'apiEndpoint' => url('/api/v2/' . $securePath . '/user-online-devices/users'),
'adminHomeUrl' => url('/' . $securePath),
]);
}
public function adminUsers(Request $request)
{
if ($error = $this->beforePluginAction()) {
return $this->fail($error);
}
$current = max(1, (int) $request->input('current', 1));
$pageSize = min(100, max(10, (int) $request->input('pageSize', 20)));
$keyword = trim((string) $request->input('keyword', ''));
$query = User::query()
->select(['id', 'email', 'last_online_at', 'created_at']);
if ($keyword !== '') {
$query->where(function ($builder) use ($keyword) {
$builder->where('email', 'like', '%' . $keyword . '%');
if (is_numeric($keyword)) {
$builder->orWhere('id', (int) $keyword);
}
});
}
$users = $query
->orderByDesc('last_online_at')
->orderByDesc('id')
->paginate($pageSize, ['*'], 'page', $current);
return $this->success($this->buildAdminUsersPayload($users));
}
public function panel(Request $request, int $user): View
{
abort_unless($this->isPluginEnabled(), 404);
$targetUser = User::query()->findOrFail($user);
$payload = $this->buildPayload($targetUser);
$ttlMinutes = max(1, (int) $this->getConfig('signed_url_ttl_minutes', 60));
return view('UserOnlineDevices::panel', [
'payload' => $payload,
'snapshotUrl' => URL::temporarySignedRoute(
'user-online-devices.snapshot',
now()->addMinutes($ttlMinutes),
['user' => $targetUser->id]
),
'refreshInterval' => max(5, (int) $this->getConfig('refresh_interval_seconds', 15)),
]);
}
public function snapshot(Request $request, int $user)
{
abort_unless($this->isPluginEnabled(), 404);
$targetUser = User::query()->findOrFail($user);
return response()->json([
'data' => $this->buildPayload($targetUser),
]);
}
private function buildPayload(User $user): array
{
$devices = $this->deviceStateService->getUsersDevices([$user->id]);
$ips = collect($devices[$user->id] ?? [])
->filter(fn ($ip) => is_string($ip) && $ip !== '')
->unique()
->values();
return [
'user' => [
'id' => $user->id,
'email' => $user->email,
'online_count' => $ips->count(),
'last_online_at' => $user->last_online_at,
],
'online_devices' => $this->formatOnlineDevices($ips),
'active_sessions' => $this->shouldShowActiveSessions()
? $this->formatActiveSessions($user)
: [],
'meta' => [
'generated_at' => time(),
'refresh_interval_seconds' => max(5, (int) $this->getConfig('refresh_interval_seconds', 15)),
'mask_ip' => (bool) $this->getConfig('mask_ip', false),
'show_active_sessions' => $this->shouldShowActiveSessions(),
],
];
}
private function buildAdminUsersPayload(LengthAwarePaginator $users): array
{
$items = $users->getCollection();
$devicesByUser = $this->deviceStateService->getUsersDevices($items->pluck('id')->all());
$users->setCollection($items->map(function (User $user) use ($devicesByUser) {
$ips = collect($devicesByUser[$user->id] ?? [])
->filter(fn ($ip) => is_string($ip) && $ip !== '')
->unique()
->values();
return [
'id' => $user->id,
'email' => $user->email,
'online_count' => $ips->count(),
'online_devices' => $this->formatOnlineDevices($ips),
'last_online_at' => $user->last_online_at?->timestamp,
'created_at' => $user->created_at?->timestamp,
];
}));
return [
'list' => $users->items(),
'pagination' => [
'current' => $users->currentPage(),
'pageSize' => $users->perPage(),
'total' => $users->total(),
'lastPage' => $users->lastPage(),
],
'meta' => [
'generated_at' => time(),
'refresh_interval_seconds' => max(5, (int) $this->getConfig('refresh_interval_seconds', 15)),
'mask_ip' => (bool) $this->getConfig('mask_ip', false),
],
];
}
private function formatOnlineDevices(Collection $ips): array
{
return $ips->values()->map(function (string $ip, int $index) {
return [
'name' => 'Device ' . ($index + 1),
'ip' => $this->maskIp($ip),
'raw_ip' => $ip,
];
})->all();
}
private function formatActiveSessions(User $user): array
{
return $user->tokens()
->where(function ($query) {
$query->whereNull('expires_at')
->orWhere('expires_at', '>', now());
})
->orderByDesc('last_used_at')
->get(['id', 'name', 'last_used_at', 'created_at', 'expires_at'])
->map(function ($token) {
return [
'id' => $token->id,
'device' => $token->name ?: 'Unknown Session',
'last_used_at' => $token->last_used_at?->timestamp,
'created_at' => $token->created_at?->timestamp,
'expires_at' => $token->expires_at?->timestamp,
];
})
->all();
}
private function shouldShowActiveSessions(): bool
{
return (bool) $this->getConfig('show_active_sessions', true);
}
private function maskIp(string $ip): string
{
if (!(bool) $this->getConfig('mask_ip', false)) {
return $ip;
}
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
$parts = explode('.', $ip);
$parts[3] = '*';
return implode('.', $parts);
}
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
$parts = explode(':', $ip);
$count = count($parts);
if ($count > 2) {
$parts[$count - 1] = '****';
$parts[$count - 2] = '****';
}
return implode(':', $parts);
}
return $ip;
}
}