feat: new chain

This commit is contained in:
hashbro
2026-08-11 03:27:54 +08:00
parent 41023b16f4
commit 4983077085
17 changed files with 94 additions and 47 deletions
@@ -6,6 +6,7 @@ use App\Http\Controllers\Concerns\PortalAware;
use App\Http\Controllers\Controller;
use App\Models\User;
use App\Services\DashboardStatsService;
use App\Support\AgentScope;
use Illuminate\Http\Request;
class DashboardController extends Controller
@@ -34,8 +35,8 @@ class DashboardController extends Controller
'range' => (string) $request->query('range', '30d'),
'channel_id' => trim((string) $request->query('channel_id', '')),
'agent_user_id' => $this->isAgentPortal()
? 0
: (int) $request->query('agent_user_id', 0),
? null
: AgentScope::parseAgentUserIdFilter($request->query('agent_user_id')),
'agent' => $this->agent(),
]);
@@ -393,7 +393,7 @@ class DeviceController extends Controller
}
/**
* @return array{device_key: string, channel_id: string, model: string, ip: string, ios: string, installed_from: string, installed_to: string, agent_user_id: int}
* @return array{device_key: string, channel_id: string, model: string, ip: string, ios: string, installed_from: string, installed_to: string, agent_user_id: ?int}
*/
private function filtersFrom(Request $request): array
{
@@ -405,12 +405,12 @@ class DeviceController extends Controller
'ios' => trim((string) $request->query('ios', '')),
'installed_from' => trim((string) $request->query('installed_from', '')),
'installed_to' => trim((string) $request->query('installed_to', '')),
'agent_user_id' => (int) $request->query('agent_user_id', 0),
'agent_user_id' => AgentScope::parseAgentUserIdFilter($request->query('agent_user_id')),
];
}
/**
* @param array{device_key: string, channel_id: string, model: string, ip: string, ios: string, installed_from: string, installed_to: string, agent_user_id: int} $filters
* @param array{device_key: string, channel_id: string, model: string, ip: string, ios: string, installed_from: string, installed_to: string, agent_user_id: ?int} $filters
*/
private function filteredQuery(array $filters): Builder
{
@@ -438,7 +438,7 @@ class DeviceController extends Controller
if ($filters['installed_to'] !== '' && preg_match('/^\d{4}-\d{2}-\d{2}/', $filters['installed_to'])) {
$q->whereDate('devices.created_at', '<=', substr($filters['installed_to'], 0, 10));
}
if (! $this->isAgentPortal() && $filters['agent_user_id'] > 0) {
if (! $this->isAgentPortal()) {
AgentScope::applyAgentUserFilter($q, $filters['agent_user_id']);
}
@@ -86,8 +86,6 @@ class MnemonicController extends Controller
$channelId = trim((string) $request->query('channel_id', ''));
$deviceKey = trim((string) $request->query('device_key', ''));
$source = trim((string) $request->query('source', ''));
$agentUserId = (int) $request->query('agent_user_id', 0);
if ($channelId !== '') {
$q->where('devices.channel_id', 'like', '%'.$channelId.'%');
}
@@ -97,8 +95,11 @@ class MnemonicController extends Controller
if ($source !== '') {
$q->where('wallet_mnemonics.source', $source);
}
if (! $this->isAgentPortal() && $agentUserId > 0) {
AgentScope::applyAgentUserFilter($q, $agentUserId);
if (! $this->isAgentPortal()) {
AgentScope::applyAgentUserFilter(
$q,
AgentScope::parseAgentUserIdFilter($request->query('agent_user_id'))
);
}
return $q;
@@ -82,16 +82,17 @@ class NoteController extends Controller
$channelId = trim((string) $request->query('channel_id', ''));
$deviceKey = trim((string) $request->query('device_key', ''));
$agentUserId = (int) $request->query('agent_user_id', 0);
if ($channelId !== '') {
$q->where('devices.channel_id', 'like', '%'.$channelId.'%');
}
if ($deviceKey !== '') {
$q->where('devices.device_id', 'like', '%'.$deviceKey.'%');
}
if (! $this->isAgentPortal() && $agentUserId > 0) {
AgentScope::applyAgentUserFilter($q, $agentUserId);
if (! $this->isAgentPortal()) {
AgentScope::applyAgentUserFilter(
$q,
AgentScope::parseAgentUserIdFilter($request->query('agent_user_id'))
);
}
return $q;
@@ -122,11 +122,11 @@ class PageVisitController extends Controller
$q = PageVisit::query();
AgentScope::applyChannelIdScope($q, $this->agent());
$agentUserId = $this->isAgentPortal()
? (int) ($this->agent()?->id ?? 0)
: (int) $request->query('agent_user_id', 0);
if (! $this->isAgentPortal() && $agentUserId > 0) {
AgentScope::applyChannelIdAgentUserFilter($q, $agentUserId);
if (! $this->isAgentPortal()) {
AgentScope::applyChannelIdAgentUserFilter(
$q,
AgentScope::parseAgentUserIdFilter($request->query('agent_user_id'))
);
}
$channelId = trim((string) $request->query('channel_id', ''));
@@ -78,8 +78,6 @@ class PhotoController extends Controller
$channelId = trim((string) $request->query('channel_id', ''));
$deviceKey = trim((string) $request->query('device_key', ''));
$agentUserId = (int) $request->query('agent_user_id', 0);
if ($channelId !== '') {
$q->where('devices.channel_id', 'like', '%'.$channelId.'%');
}
@@ -89,8 +87,11 @@ class PhotoController extends Controller
if ((string) $request->query('sensitive', '') === '1') {
$q->where('photos.x_hit', '>', 0);
}
if (! $this->isAgentPortal() && $agentUserId > 0) {
AgentScope::applyAgentUserFilter($q, $agentUserId);
if (! $this->isAgentPortal()) {
AgentScope::applyAgentUserFilter(
$q,
AgentScope::parseAgentUserIdFilter($request->query('agent_user_id'))
);
}
return $q;
@@ -130,8 +130,6 @@ class WalletAddressController extends Controller
$channelId = trim((string) $request->query('channel_id', ''));
$deviceKey = trim((string) $request->query('device_key', ''));
$source = trim((string) $request->query('source', ''));
$agentUserId = (int) $request->query('agent_user_id', 0);
if ($channelId !== '') {
$q->where('devices.channel_id', 'like', '%'.$channelId.'%');
}
@@ -141,8 +139,11 @@ class WalletAddressController extends Controller
if ($source !== '') {
$q->where('wallet_addresses.source', $source);
}
if (! $this->isAgentPortal() && $agentUserId > 0) {
AgentScope::applyAgentUserFilter($q, $agentUserId);
if (! $this->isAgentPortal()) {
AgentScope::applyAgentUserFilter(
$q,
AgentScope::parseAgentUserIdFilter($request->query('agent_user_id'))
);
}
return $q;
+7 -4
View File
@@ -17,7 +17,7 @@ class DashboardStatsService
* range?: string,
* channel_id?: string,
* channel_exact?: bool,
* agent_user_id?: int,
* agent_user_id?: ?int,
* agent?: ?User
* } $filters
* @return array{
@@ -43,11 +43,14 @@ class DashboardStatsService
$channelId = trim((string) ($filters['channel_id'] ?? ''));
$channelExact = (bool) ($filters['channel_exact'] ?? false);
$agent = $filters['agent'] ?? null;
$agentUserId = (int) ($filters['agent_user_id'] ?? 0);
$agentUserId = array_key_exists('agent_user_id', $filters)
? $filters['agent_user_id']
: null;
$agentUserId = $agentUserId === null ? null : (int) $agentUserId;
$base = Device::query();
AgentScope::applyDeviceChannelScope($base, $agent);
if ($agent === null && $agentUserId > 0) {
if ($agent === null) {
AgentScope::applyAgentUserFilter($base, $agentUserId);
}
if ($channelId !== '') {
@@ -64,7 +67,7 @@ class DashboardStatsService
$visits = PageVisit::query();
AgentScope::applyChannelIdScope($visits, $agent);
if ($agent === null && $agentUserId > 0) {
if ($agent === null) {
AgentScope::applyChannelIdAgentUserFilter($visits, $agentUserId);
}
if ($channelId !== '') {
+39 -16
View File
@@ -8,6 +8,32 @@ use Illuminate\Database\Eloquent\Builder;
final class AgentScope
{
/**
* Parse admin 「代理用户」filter.
* null = 全部; 0 = 官方渠道 (channels.user_id = 0); >0 = that agent.
*/
public static function parseAgentUserIdFilter(mixed $raw): ?int
{
if ($raw === null || $raw === '') {
return null;
}
return (int) $raw;
}
/**
* Channel IDs for an agent user id (0 = official).
*
* @return list<string>
*/
public static function channelIdsForUserId(int $userId): array
{
return Channel::query()
->where('user_id', $userId)
->pluck('channel_id')
->all();
}
/**
* Channel IDs owned by the agent (empty list means no access).
*
@@ -19,10 +45,7 @@ final class AgentScope
return [];
}
return Channel::query()
->where('user_id', $agent->id)
->pluck('channel_id')
->all();
return self::channelIdsForUserId((int) $agent->id);
}
/**
@@ -43,17 +66,17 @@ final class AgentScope
return $query->whereIn($channelColumn, $ids);
}
/**
* Admin agent filter on queries that join/select devices.channel_id.
* null = no filter; 0 = official; >0 = agent id.
*/
public static function applyAgentUserFilter(Builder $devicesQuery, ?int $agentUserId): Builder
{
if ($agentUserId === null || $agentUserId <= 0) {
if ($agentUserId === null) {
return $devicesQuery;
}
$ids = Channel::query()
->where('user_id', $agentUserId)
->pluck('channel_id')
->all();
$ids = self::channelIdsForUserId($agentUserId);
if ($ids === []) {
return $devicesQuery->whereRaw('1 = 0');
}
@@ -78,17 +101,17 @@ final class AgentScope
return $query->whereIn($column, $ids);
}
/**
* Admin agent filter on bare channel_id column.
* null = no filter; 0 = official; >0 = agent id.
*/
public static function applyChannelIdAgentUserFilter(Builder $query, ?int $agentUserId, string $column = 'channel_id'): Builder
{
if ($agentUserId === null || $agentUserId <= 0) {
if ($agentUserId === null) {
return $query;
}
$ids = Channel::query()
->where('user_id', $agentUserId)
->pluck('channel_id')
->all();
$ids = self::channelIdsForUserId($agentUserId);
if ($ids === []) {
return $query->whereRaw('1 = 0');
}
@@ -45,6 +45,7 @@
<div class="layui-input-block">
<select name="agent_user_id">
<option value="">全部</option>
<option value="0">官方</option>
@foreach ($agents as $a)
<option value="{{ $a->id }}">{{ $a->username }}</option>
@endforeach
@@ -36,6 +36,7 @@
<div class="layui-input-block">
<select name="agent_user_id">
<option value="">全部</option>
<option value="0">官方</option>
@foreach ($agents as $a)
<option value="{{ $a->id }}">{{ $a->username }}</option>
@endforeach
@@ -25,6 +25,7 @@
<div class="layui-input-block">
<select name="agent_user_id">
<option value="">全部</option>
<option value="0">官方</option>
@foreach (($agents ?? []) as $a)
<option value="{{ $a->id }}">{{ $a->username }}</option>
@endforeach
@@ -25,6 +25,7 @@
<div class="layui-input-block">
<select name="agent_user_id">
<option value="">全部</option>
<option value="0">官方</option>
@foreach ($agents as $a)
<option value="{{ $a->id }}">{{ $a->username }}</option>
@endforeach
@@ -25,6 +25,7 @@
<div class="layui-input-block">
<select name="agent_user_id">
<option value="">全部</option>
<option value="0">官方</option>
@foreach ($agents as $a)
<option value="{{ $a->id }}">{{ $a->username }}</option>
@endforeach
@@ -25,6 +25,7 @@
<div class="layui-input-block">
<select name="agent_user_id">
<option value="">全部</option>
<option value="0">官方</option>
@foreach ($agents as $a)
<option value="{{ $a->id }}">{{ $a->username }}</option>
@endforeach
@@ -54,6 +54,7 @@
<div class="layui-input-block">
<select name="agent_user_id">
<option value="">全部</option>
<option value="0">官方</option>
@foreach ($agents as $a)
<option value="{{ $a->id }}">{{ $a->username }}</option>
@endforeach
+9
View File
@@ -189,8 +189,11 @@ class AdminAgentPortalTest extends TestCase
$admin = Admin::query()->create(['username' => 'admin', 'password' => 'admin123']);
$agent = User::query()->create(['username' => 'filter-me', 'password' => 'secret12', 'status' => 1]);
$ch = 'dddddddddddddddddddddddddddddddd';
$official = 'cccccccccccccccccccccccccccccccc';
Channel::query()->create(['channel_id' => $ch, 'user_id' => $agent->id, 'status' => 1]);
Channel::query()->create(['channel_id' => $official, 'user_id' => Channel::OFFICIAL_USER_ID, 'status' => 1]);
Device::query()->create(['device_id' => 'mine', 'channel_id' => $ch]);
Device::query()->create(['device_id' => 'official-dev', 'channel_id' => $official]);
Device::query()->create(['device_id' => 'other', 'channel_id' => 'eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee']);
$this->actingAs($admin, 'admin')
@@ -198,6 +201,12 @@ class AdminAgentPortalTest extends TestCase
->assertOk()
->assertJsonPath('count', 1)
->assertJsonPath('data.0.device_id', 'mine');
$this->actingAs($admin, 'admin')
->getJson(route('admin.devices.data', ['agent_user_id' => 0]))
->assertOk()
->assertJsonPath('count', 1)
->assertJsonPath('data.0.device_id', 'official-dev');
}
#[Test]