is_numeric($sleep) ? $sleep : 60, $this->option('sleep')); while (true) { try { $this->sync(); } catch (Throwable $e) { report($e); } sleep($sleep); }; } /** * 执行同步 */ protected function sync(): void { $now = now(); $this->info('------------------------------------------'); $this->info('同步时间: '. $now); /** @var \Illuminate\Database\Eloquent\Collection */ $devices = Device::with(['project']) ->supplierBy("device-supplier-biang") ->whereIn('status', [DeviceStatus::Online, DeviceStatus::Offline]) ->get(); /** @var \App\Models\Device */ foreach ($devices as $device) { if (! in_array($device->type, [ DeviceType::Monitor, DeviceType::Soil, DeviceType::WaterQuality, DeviceType::Meteorological, DeviceType::Worm, DeviceType::InsectSexLure, DeviceType::InsecticidalLamp, ])) { continue; } $this->info('=========================================='); $this->info('设备编号: ' . $device->sn); $this->info('设备名称: ' . $device->name); $this->info('设备类型: ' . match ($device->type) { DeviceType::Monitor => '苗情设备', DeviceType::Soil => '土壤设备', DeviceType::WaterQuality => '水质设备', DeviceType::Meteorological => '气象设备', DeviceType::Worm => '虫情设备', DeviceType::InsectSexLure => '昆虫性诱设备', DeviceType::InsecticidalLamp => '杀虫灯设备', }); try { $httpClient = $this->buildHttpClient($device); switch ($device->type) { case DeviceType::Monitor: $data = $httpClient->getMonitorPalyAddress($device->sn); $device->update([ 'status' => $data ? DeviceStatus::Online : DeviceStatus::Offline, ]); break; case DeviceType::Soil: case DeviceType::WaterQuality: case DeviceType::Meteorological: case DeviceType::InsecticidalLamp: $data = match ($device->type) { DeviceType::Soil => $httpClient->getLatestSoilReport($device->sn), DeviceType::WaterQuality => $httpClient->getLatestWaterDeviceReport($device->sn), DeviceType::Meteorological => $httpClient->getLatestMeteorologicalReport($device->sn), DeviceType::InsecticidalLamp => $httpClient->getLatestLampReport($device->sn), }; if (empty($data)) { $device->update([ 'status' => DeviceStatus::Offline, ]); $this->warn('设备数据: 无'); break; } else { $this->info('设备数据: '.json_encode($data)); } $log = DeviceLog::firstOrCreate([ 'device_id' => $device->id, 'reported_at' => $data['time'], ], [ 'data' => Arr::except($data, ['deviceId', 'time']), ]); $device->update([ 'status' => $now->copy()->subMinutes(60)->lt($log->reported_at) ? DeviceStatus::Online : DeviceStatus::Offline, ]); break; case DeviceType::Worm: case DeviceType::InsectSexLure: $dir = "worm-photos/{$device->id}"; $disk = Storage::disk('public'); $data = $httpClient->getWormPhotos($device->sn, $now->copy()->subHours(24), $now); if (empty($data)) { $device->update([ 'status' => DeviceStatus::Offline, ]); $this->warn('设备数据: 无'); break; } else { $this->info('设备数据: '.json_encode($data)); } foreach ($data['imgUrl'] as $item) { try { // 下载图片 $name = md5($item['url']); if ($ext = pathinfo($item['url'], PATHINFO_EXTENSION)) { $name .= ".{$ext}"; } $path = "{$dir}/{$name}"; $disk = Storage::disk('public'); if (! $disk->exists($path)) { $disk->put($path, file_get_contents($item['url'])); } WormPhoto::updateOrCreate([ 'device_id' => $device->id, 'uploaded_at' => $item['time'], ], [ 'url' => $path, ]); } catch (Throwable $e) { report($e); } } $device->update([ 'status' => count($data['imgUrl'] ?? []) > 0 ? DeviceStatus::Online : DeviceStatus::Offline, ]); break; } switch ($device->status) { case DeviceStatus::Online: $this->info('设备状态: 在线'); break; case DeviceStatus::Offline: $this->warn('设备状态: 离线'); break; } $this->info('同步成功!'); } catch (Throwable $e) { report($e); $this->error('同步失败: '. $e->getMessage()); } $this->info('=========================================='); } $this->info('------------------------------------------'); $this->newLine(); } /** * 创建 HTTP 客户端 */ public function buildHttpClient(Device $device): HttpClient { $config = json_decode($device->project?->value, true); if (! is_array($config)) { throw new RuntimeException('账户信息未找到'); } return new HttpClient($config['username'] ?? '', $config['password'] ?? ''); } }