You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

54 lines
4.3 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<?php
namespace App\Console\Commands;
use App\Models\CameraAlert;
use App\Models\CameraDevice;
use App\Models\CameraMonitorSetting;
use Illuminate\Console\Command;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Mail;
class CameraMonitorSyncCommand extends Command
{
protected $signature = 'camera-monitor:sync';
protected $description = '同步海康摄像头健康状态,仅在需要人工处理时发送邮件';
public function handle(): int
{
$base = rtrim((string) config('services.people_counting.url', env('PEOPLE_COUNTING_URL', '')), '/');
$base = preg_replace('#/api/people-counting$#i', '', $base);
if (! $base) { $this->warn('未配置 PEOPLE_COUNTING_URL'); return self::SUCCESS; }
try { $remote = Http::timeout(10)->acceptJson()->get($base.'/api/device-health'); } catch (\Throwable $e) { $this->error($e->getMessage()); return self::FAILURE; }
if (! $remote->successful()) { $this->error('海康健康接口返回 '.$remote->status()); return self::FAILURE; }
$byKey = collect($remote->json('devices', []))->keyBy(fn ($r) => trim((string) ($r['ip'] ?? '')).'#'.(int) ($r['channel'] ?? 1));
$settings = CameraMonitorSetting::current();
CameraDevice::query()->where('monitor_enabled', true)->with('health')->each(function (CameraDevice $camera) use ($byKey, $settings) {
if ($camera->isInMaintenance()) return;
$r = $byKey->get(trim($camera->ip).'#'.(int) $camera->channel);
if (! $r) return;
$health = $camera->health ?: $camera->health()->firstOrCreate([]);
$status = (string) ($r['healthStatus'] ?? ($r['pushStatus'] ?? ($r['sdkStatus'] ?? 'UNKNOWN')));
$health->fill(['status' => $status, 'sdk_status' => $r['sdkStatus'] ?? null, 'tcp_reachable' => $r['tcpReachable'] ?? null, 'last_pdc_at' => $r['lastPdcAt'] ?? null, 'pdc_count' => $r['pdcCount'] ?? 0, 'push_age_seconds' => (($r['pushAgeSeconds'] ?? -1) >= 0 ? $r['pushAgeSeconds'] : null), 'reconnect_attempts' => $r['autoReconnectFailures'] ?? 0, 'observed_at' => now()])->save();
if (! ($r['manualActionRequired'] ?? false) || ! $camera->notification_enabled || ! $settings->send_manual_action_email) return;
$alert = CameraAlert::query()->where('camera_device_id', $camera->id)->where('alert_type', 'MANUAL_ACTION_REQUIRED')->whereIn('status', ['OPEN','ACKNOWLEDGED'])->latest('id')->first();
if (! $alert) {
$alert = CameraAlert::create(['camera_device_id' => $camera->id, 'venue_id' => $camera->venue_id, 'alert_type' => 'MANUAL_ACTION_REQUIRED', 'status' => 'OPEN', 'severity' => 'critical', 'title' => '摄像头需要人工处理', 'reason' => '自动重连多次失败,请检查设备电源、网络或重启摄像头后手动重试。', 'first_occurred_at' => now(), 'last_occurred_at' => now()]);
$this->notify($alert, $camera, $settings);
} else { $alert->update(['last_occurred_at' => now()]); }
});
return self::SUCCESS;
}
private function notify(CameraAlert $alert, CameraDevice $camera, CameraMonitorSetting $settings): void
{
$tz = $settings->timezone ?: 'Asia/Shanghai'; $now = Carbon::now($tz); $start = Carbon::parse($now->format('Y-m-d').' '.$settings->notification_start_time, $tz); $end = Carbon::parse($now->format('Y-m-d').' '.$settings->notification_end_time, $tz);
if (! $now->between($start, $end, true)) return;
foreach (array_values(array_filter($settings->recipient_emails ?? [])) as $to) {
try { Mail::raw("场馆:".($camera->venue_name ?: '未绑定')."\n摄像头:{$camera->name}\nIP:{$camera->ip}\n通道:{$camera->channel}\n状态:需要人工处理\n最后推送:".($camera->health?->last_pdc_at?->toDateTimeString() ?: '无')."\n原因:自动重连多次失败,请检查设备电源、网络或重启摄像头后到后台手动重试。", function ($m) use ($to) { $m->to($to)->subject('海康摄像头需要人工处理'); }); } catch (\Throwable $e) { $this->error($to.': '.$e->getMessage()); }
$alert->increment('notification_count'); $alert->update(['last_notified_at' => now()]); $alert->notifications()->create(['channel' => 'email', 'receiver' => $to, 'send_status' => 'sent', 'sent_at' => now(), 'attempts' => 1]);
}
}
}