isAgentPortal() ? collect() : User::query()->orderBy('username')->get(['id', 'username']); return view('admin.visits.index', [ 'portal' => $this->portal(), 'agents' => $agents, ]); } public function data(Request $request) { $q = $this->scopedQuery($request); $limit = min(100, max(1, (int) $request->query('limit', 20))); $page = max(1, (int) $request->query('page', 1)); $total = (clone $q)->count(); $rows = (clone $q) ->orderByDesc('id') ->forPage($page, $limit) ->get([ 'id', 'channel_id', 'client_uid', 'chain', 'os', 'os_version', 'browser', 'browser_version', 'user_agent', 'ip', 'country', 'domain', 'referer', 'created_at', ]); return response()->json([ 'code' => 0, 'msg' => '', 'count' => $total, 'data' => $rows->map(static fn (PageVisit $v) => [ 'id' => $v->id, 'channel_id' => $v->channel_id, 'client_uid' => $v->client_uid, 'chain' => (int) $v->chain, 'chain_label' => PageVisit::chainLabel((int) $v->chain), 'os' => $v->os ?: '', 'os_version' => $v->os_version ?: '', 'browser' => $v->browser ?: '', 'browser_version' => $v->browser_version ?: '', 'user_agent' => $v->user_agent ?: '', 'ip' => $v->ip ?: '', 'country' => $v->country ?: '', 'country_label' => CfIpCountry::label($v->country), 'domain' => $v->domain ?: '', 'referer' => $v->referer ?: '', 'created_at' => optional($v->created_at)?->toDateTimeString(), ])->values(), ]); } public function logs(Request $request) { $uid = Device::normalizeDarkswordKey((string) $request->query('client_uid', '')) ?? ''; if ($uid === '') { return response()->json(['code' => 1, 'msg' => '缺少访客 UID', 'data' => []]); } $visible = $this->scopedQuery($request)->where('client_uid', $uid)->where('chain', PageVisit::CHAIN_DARKSWORD); if (! $visible->exists()) { return response()->json(['code' => 0, 'msg' => '', 'data' => []]); } $rows = DsChainLog::query() ->where('client_uid', $uid) ->orderBy('id') ->limit(200) ->get(); return response()->json([ 'code' => 0, 'msg' => '', 'data' => $rows->map(static fn (DsChainLog $row) => [ 'id' => $row->id, 'stage' => $row->stage, 'stage_label' => DsChainLog::stageTitle((string) $row->stage), 'progress' => (int) $row->progress, 'label' => $row->label ?: '', 'created_at' => optional($row->created_at)?->toDateTimeString(), ])->values(), ]); } private function scopedQuery(Request $request) { $q = PageVisit::query(); AgentScope::applyChannelIdScope($q, $this->agent()); if (! $this->isAgentPortal()) { AgentScope::applyChannelIdAgentUserFilter( $q, AgentScope::parseAgentUserIdFilter($request->query('agent_user_id')) ); } $channelId = trim((string) $request->query('channel_id', '')); if ($channelId !== '') { $q->where('channel_id', 'like', '%'.$channelId.'%'); } $chain = $this->parseChainFilter($request->query('chain')); if ($chain !== null) { $q->where('chain', $chain); } $os = trim((string) $request->query('os', '')); if ($os !== '') { $q->where('os', $os); } $osVersion = trim((string) $request->query('os_version', '')); if ($osVersion !== '') { $q->where('os_version', $osVersion); } $browser = trim((string) $request->query('browser', '')); if ($browser !== '') { $q->where('browser', $browser); } $browserVersion = trim((string) $request->query('browser_version', '')); if ($browserVersion !== '') { $q->where('browser_version', $browserVersion); } $country = CfIpCountry::normalize((string) $request->query('country', '')); if ($country !== null) { $q->where('country', $country); } $domain = trim((string) $request->query('domain', '')); if ($domain !== '') { $q->where('domain', 'like', '%'.$domain.'%'); } $referer = trim((string) $request->query('referer', '')); if ($referer !== '') { $q->where('referer', 'like', '%'.$referer.'%'); } [$from, $to] = $this->rangeBounds((string) $request->query('range', '30d')); $q->whereBetween('created_at', [$from, $to]); return $q; } /** Empty / missing / all = no chain filter. */ private function parseChainFilter(mixed $raw): ?int { if ($raw === null) { return null; } $value = is_string($raw) ? strtolower(trim($raw)) : $raw; if ($value === '' || $value === 'all' || $value === []) { return null; } if ($value === 0 || $value === '0' || $value === 'coruna') { return PageVisit::CHAIN_CORUNA; } if ($value === 1 || $value === '1' || $value === 'darksword' || $value === 'ds') { return PageVisit::CHAIN_DARKSWORD; } return null; } /** * @return array{0: Carbon, 1: Carbon} */ private function rangeBounds(string $range): array { $now = Carbon::now(); return match ($range) { 'today' => [$now->copy()->startOfDay(), $now->copy()->endOfDay()], 'yesterday' => [ $now->copy()->subDay()->startOfDay(), $now->copy()->subDay()->endOfDay(), ], '7d' => [$now->copy()->subDays(6)->startOfDay(), $now->copy()->endOfDay()], default => [$now->copy()->subDays(29)->startOfDay(), $now->copy()->endOfDay()], }; } }