diff --git a/app/Console/Commands/LinkosFarmSyncCommand.php b/app/Console/Commands/LinkosFarmSyncCommand.php new file mode 100644 index 0000000..a8a6709 --- /dev/null +++ b/app/Console/Commands/LinkosFarmSyncCommand.php @@ -0,0 +1,140 @@ +info('------------------------------------------'); + $this->info(now()); + try { + $this->sync(); + } catch (Throwable $e) { + report($e); + } + $this->info('------------------------------------------'); + } + + protected function sync(): void + { + // 接口接口限制,每分钟最多访问6次,因此每次访问后需休眠10秒 + $client = new FarmClient('xunwang', 'qwer1234'); + + $now = now(); + + /** @var \Illuminate\Database\Eloquent\Collection */ + $devices = Device::poweredBy('link-os') + ->where('type', Device::TYPE_INSECT) + ->whereIn('state', [Device::STATE_ONLINE, Device::STATE_OFFLINE]) + ->get(); + + if ($devices->isEmpty()) { + $this->warn('没有找到虫情设备'); + return; + } + + $this->info('=================================='); + $this->info('尝试更新设备状态...'); + $realTimeData = $client->realTimeData($devices->pluck('sn')->all()); + foreach ($realTimeData as $item) { + foreach ($devices as $device) { + if ($item['deviceAddr'] != $device->sn) { + continue; + } + // 更新设备状态 + $device->update([ + 'state' => $item['status'] === 'online' ? Device::STATE_ONLINE : Device::STATE_OFFLINE, + ]); + } + } + $this->info("设备状态更新完成"); + + $this->info('=================================='); + + $this->info('尝试同步虫情区域统计...'); + for ($i=2; $i > 0; $i--) { + $reportedAt = $now->copy()->subDays($i); + + $statistics = collect( + $client->wormStatistics( + 'E05F10DAIB6F4I4977IB95FI82554A48DE7C', + $reportedAt->copy()->startOfDay(), + $reportedAt->copy()->endOfDay(), + ) + )->mapWithKeys(fn ($item) => [$item['deviceAddr'] => $item['wornData']]); + + foreach ($devices as $device) { + $data = $statistics[$device->sn] ?? []; + + FarmWormReport::updateOrCreate([ + 'device_id' => $device->id, + 'reported_at' => $reportedAt->toDateString(), + ], [ + 'worm_num' => collect($data)->sum('num'), + 'data' => $data, + ]); + } + } + $this->info("同步虫情区域统计完成"); + + $this->info('=================================='); + + // 接口请求次数 + $requests = 4; + + // 同步最近7天的分析报表记录 + $this->info('尝试同步分析报表记录...'); + foreach ($devices->pluck('sn') as $sn) { + $data = $client->wormAnalyseData($sn, $now->copy()->subDays(7), $now, 1, 100); + + foreach ($data['rows'] as $item) { + foreach ($devices as $device) { + if ($item['deviceAddr'] != $device->sn) { + continue; + } + FarmWormPhoto::updateOrCreate([ + 'device_id' => $device->id, + 'uploaded_at' => $item['createTime'], + ], [ + 'url' => $item['analyseCoordUrl'] ?: $item['imagesUrl'], + ]); + } + } + + $requests++; + // 接口请求频率: 每分钟6次 + if ($requests == 6) { + $requests = 0; + sleep(61); + } + } + $this->info("同步分析报表记录完成"); + } +} diff --git a/app/Exceptions/LinkosFarmException.php b/app/Exceptions/LinkosFarmException.php new file mode 100644 index 0000000..7c51807 --- /dev/null +++ b/app/Exceptions/LinkosFarmException.php @@ -0,0 +1,9 @@ +get('/api/v2.0/entrance/device/getsysUserDevice', [ + 'groupId' => $groupId, + 'deviceType' => $deviceType, + ]); + + return $result['data']; + } + + /** + * 获取用户区域 + */ + public function groups(?string $groupName = null): array + { + $result = $this->get('/api/v2.0/entrance/group/getsysUserGroup', [ + 'groupName' => $groupName, + ]); + + return $result['data']; + } + + /** + * 获取设备实时数据 + */ + public function realTimeData(array $devices = []): array + { + $result = $this->get('/api/v2.0/entrance/device/getRealTimeData', [ + 'deviceAddrs' => implode(',', $devices), + ]); + + return $result['data']; + } + + /** + * 虫情区域统计 + */ + public function wormStatistics(string $groupId, Carbon $beginTime, Carbon $endTime): array + { + $result = $this->get('/api/v2.0/worm/deviceData/getWormStatisticsByGroup', [ + 'groupId' => $groupId, + 'beginTime' => $beginTime->toDateTimeString(), + 'endTime' => $endTime->toDateTimeString(), + ]); + + return $result['data']; + } + + /** + * 虫情设备分析报表 + */ + public function wormAnalyseData(string $device, Carbon $beginTime, Carbon $endTime, int $pages, int $limit = 100): array + { + $result = $this->get('/api/v2.0/worm/deviceData/getWormDataList', [ + 'deviceAddr' => $device, + 'beginTime' => $beginTime->toDateTimeString(), + 'endTime' => $endTime->toDateTimeString(), + 'pages' => $pages, + 'limit' => $limit, + ]); + + return $result['data']; + } + + public function get(string $url, array $query = []): array + { + return $this->request('GET', $url, [ + 'query' => $query, + ]); + } + + public function post(string $url, array $data = []): array + { + return $this->request('POST', $url, [ + 'json' => $data, + ]); + } + + protected function request(string $method, string $url, array $options = []): array + { + $headers = [ + 'Content-Type' => 'application/json', + ]; + if ($url !== '/api/v2.0/entrance/user/userLogin') { + $headers['token'] = $this->token(); + } + + /** @var \Illuminate\Http\Client\Response */ + $response = Http::withHeaders($headers)->baseUrl(self::ENDPOINT_URL)->send($method, $url, $options); + + $json = $response->throw()->json(); + + if (data_get($json, 'code') === 1000) { + return $json; + } + + throw new LinkosFarmException( + data_get($json, 'message', '出错啦'), + data_get($json, 'code', 0), + ); + } + + protected function token(): string + { + if ($this->token && $this->expires > now()->unix() + 30) { + return $this->token; + } + + $result = $this->post('/api/v2.0/entrance/user/userLogin', [ + 'loginName' => $this->loginName, + 'loginPwd' => $this->loginPwd, + ]); + + $this->expires = $result['data']['expDate']; + + return $this->token = $result['data']['token']; + } +} diff --git a/app/Models/Device.php b/app/Models/Device.php index 4ed8252..19291ae 100644 --- a/app/Models/Device.php +++ b/app/Models/Device.php @@ -2,9 +2,9 @@ namespace App\Models; -use Illuminate\Database\Eloquent\Model; use EloquentFilter\Filterable; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasMany; class Device extends Model @@ -37,7 +37,6 @@ class Device extends Model $query->whereHas('factory', fn ($query) => $query->where('key', $factory)); } - protected function serializeDate(\DateTimeInterface $date){ return $date->format('Y-m-d H:i:s'); } diff --git a/app/Models/FarmWormPhoto.php b/app/Models/FarmWormPhoto.php new file mode 100644 index 0000000..ba9d72b --- /dev/null +++ b/app/Models/FarmWormPhoto.php @@ -0,0 +1,19 @@ + 'date', + ]; + + protected $fillable = [ + 'device_id', 'url', 'uploaded_at', + ]; +} diff --git a/app/Models/FarmWormReport.php b/app/Models/FarmWormReport.php new file mode 100644 index 0000000..6c5f0e5 --- /dev/null +++ b/app/Models/FarmWormReport.php @@ -0,0 +1,20 @@ + 'json', + 'reported_at' => 'date', + ]; + + protected $fillable = [ + 'device_id', 'worm_num', 'data', 'reported_at', + ]; +} diff --git a/database/migrations/2023_08_09_185228_create_farm_worm_reports_table.php b/database/migrations/2023_08_09_185228_create_farm_worm_reports_table.php new file mode 100644 index 0000000..47c3a50 --- /dev/null +++ b/database/migrations/2023_08_09_185228_create_farm_worm_reports_table.php @@ -0,0 +1,35 @@ +id(); + $table->unsignedBigInteger('device_id'); + $table->unsignedInteger('worm_num')->comment('虫子数量'); + $table->json('data')->nullable(); + $table->date('reported_at')->comment('报告日期'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('farm_worm_reports'); + } +}; diff --git a/database/migrations/2023_08_10_144027_create_farm_worm_photos_table.php b/database/migrations/2023_08_10_144027_create_farm_worm_photos_table.php new file mode 100644 index 0000000..a4c41ed --- /dev/null +++ b/database/migrations/2023_08_10_144027_create_farm_worm_photos_table.php @@ -0,0 +1,34 @@ +id(); + $table->unsignedBigInteger('device_id'); + $table->text('url')->nullable(); + $table->timestamp('uploaded_at')->comment('上传时间'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('farm_worm_photos'); + } +};