191 lines
6.3 KiB
PHP
191 lines
6.3 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Admin;
|
|
|
|
use App\Http\Controllers\Concerns\PortalAware;
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\DsChainLog;
|
|
use App\Models\PageVisit;
|
|
use App\Models\User;
|
|
use App\Support\AgentScope;
|
|
use Carbon\Carbon;
|
|
use Illuminate\Http\Request;
|
|
|
|
class PageVisitController extends Controller
|
|
{
|
|
use PortalAware;
|
|
|
|
public function index()
|
|
{
|
|
$agents = $this->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', '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 ?: '',
|
|
'domain' => $v->domain ?: '',
|
|
'referer' => $v->referer ?: '',
|
|
'created_at' => optional($v->created_at)?->toDateTimeString(),
|
|
])->values(),
|
|
]);
|
|
}
|
|
|
|
public function logs(Request $request)
|
|
{
|
|
$uid = strtoupper(preg_replace('/[^0-9A-Fa-f]/', '', (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);
|
|
}
|
|
$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()],
|
|
};
|
|
}
|
|
}
|