diff --git a/app/Console/Commands/BiAng/DeviceLogDailyReportCommand.php b/app/Console/Commands/BiAng/DeviceLogDailyReportCommand.php new file mode 100644 index 0000000..8990b64 --- /dev/null +++ b/app/Console/Commands/BiAng/DeviceLogDailyReportCommand.php @@ -0,0 +1,132 @@ + is_numeric($sleep) ? $sleep : 300, $this->option('sleep')); + + while (true) { + /** @var \Illuminate\Database\Eloquent\Collection */ + $devices = Device::supplierBy("device-supplier-biang")->get(); + + foreach ($devices as $device) { + $this->createReport($device); + } + + sleep($sleep); + }; + } + + protected function createReport(Device $device): void + { + [$lastReportedAt, $latestReportedAt] = value(function (Device $device) { + $lastReportedAt = null; + + $latestReportedAt = null; + + switch ($device->type) { + case DeviceType::Meteorological: + $lastReportedAt = MeteorologicalMonitoringDailyLog::where('device_id', $device->id) + ->latest('monitored_at') + ->value('monitored_at'); + + $lastReportedAt ??= MeteorologicalMonitoringLog::where('device_id', $device->id) + ->oldest('monitored_at') + ->value('monitored_at'); + + if ($lastReportedAt) { + $latestReportedAt = MeteorologicalMonitoringLog::where('device_id', $device->id) + ->latest('monitored_at') + ->value('monitored_at'); + } + break; + + case DeviceType::Soil: + $lastReportedAt = SoilMonitoringDailyLog::where('device_id', $device->id) + ->latest('monitored_at') + ->value('monitored_at'); + + $lastReportedAt ??= SoilMonitoringLog::where('device_id', $device->id) + ->oldest('monitored_at') + ->value('monitored_at'); + + if ($lastReportedAt) { + $latestReportedAt = SoilMonitoringLog::where('device_id', $device->id) + ->latest('monitored_at') + ->value('monitored_at'); + } + break; + + case DeviceType::InsecticidalLamp: + $lastReportedAt = InsecticidalLampDailyReport::where('device_id', $device->id) + ->latest('reported_at') + ->value('reported_at'); + + $lastReportedAt ??= InsecticidalLampReport::where('device_id', $device->id) + ->oldest('reported_at') + ->value('reported_at'); + + if ($lastReportedAt) { + $latestReportedAt = InsecticidalLampReport::where('device_id', $device->id) + ->latest('reported_at') + ->value('reported_at'); + } + break; + } + + return [$lastReportedAt, $latestReportedAt]; + }, $device); + + if ($lastReportedAt === null || $latestReportedAt === null) { + return; + } + + $service = new BiAngDeviceService(); + + /** @var \Carbon\Carbon */ + $startAt = $lastReportedAt->copy()->startOfDay(); + + do { + $service->createDailyReport($device, $startAt->copy()); + + $startAt->addDay(); + } while ($latestReportedAt->gte($startAt)); + } +} diff --git a/app/Console/Commands/BiAng/DeviceLogReportCommand.php b/app/Console/Commands/BiAng/DeviceLogReportCommand.php new file mode 100644 index 0000000..92b1ee9 --- /dev/null +++ b/app/Console/Commands/BiAng/DeviceLogReportCommand.php @@ -0,0 +1,86 @@ + is_numeric($sleep) ? $sleep : 300, $this->option('sleep')); + + while (true) { + /** @var \Illuminate\Database\Eloquent\Collection */ + $devices = Device::supplierBy("device-supplier-biang")->get(); + + foreach ($devices as $device) { + $this->createReport($device); + } + + sleep($sleep); + }; + } + + /** + * 创建比昂设备报告 + */ + protected function createReport(Device $device): void + { + $lastReportedAt = match ($device->type) { + DeviceType::Soil => SoilMonitoringLog::where('device_id', $device->id)->latest('monitored_at')->value('monitored_at'), + DeviceType::Meteorological => MeteorologicalMonitoringLog::where('device_id', $device->id)->latest('monitored_at')->value('monitored_at'), + DeviceType::InsecticidalLamp => InsecticidalLampReport::where('device_id', $device->id)->latest('reported_at')->value('reported_at'), + default => null, + }; + + if (is_null($lastReportedAt ??= DeviceLog::where('device_id', $device->id)->oldest('reported_at')->value('reported_at'))) { + return; + } + + if (is_null($latestReportedAt = DeviceLog::where('device_id', $device->id)->latest('reported_at')->value('reported_at'))) { + return; + } + + $service = new BiAngDeviceService(); + + /** @var \Carbon\Carbon */ + $startAt = $lastReportedAt->copy()->startOfHour(); + + do { + $service->createReport($device, $startAt->copy()); + + $startAt->addHour(); + } while ($latestReportedAt->gte($startAt)); + } +} diff --git a/app/Console/Commands/BiAng/DeviceLogSyncCommand.php b/app/Console/Commands/BiAng/DeviceLogSyncCommand.php new file mode 100644 index 0000000..64b58bd --- /dev/null +++ b/app/Console/Commands/BiAng/DeviceLogSyncCommand.php @@ -0,0 +1,182 @@ + is_numeric($sleep) ? $sleep : 60, $this->option('sleep')); + + while (true) { + $this->sync(); + + 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::Soil, + 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::Soil => '土壤设备', + DeviceType::Meteorological => '气象设备', + DeviceType::Worm => '虫情设备', + DeviceType::InsectSexLure => '昆虫性诱设备', + DeviceType::InsecticidalLamp => '杀虫灯设备', + }); + + try { + $httpClient = $this->buildHttpClient($device); + + switch ($device->type) { + case DeviceType::Soil: + $data = $httpClient->getLatestSoilReport($device->sn); + + $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::Meteorological: + $data = $httpClient->getLatestMeteorologicalReport($device->sn); + + $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::InsecticidalLamp: + $data = $httpClient->getLatestLampReport($device->sn); + + $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: + $data = $httpClient->getWormPhotos($device->sn, $now->copy()->subHours(24), $now); + + // foreach ($data['imgUrl'] as $item) { + // WormPhoto::firstOrCreate([ + // 'device_id' => $device->id, + // 'uploaded_at' => $item['time'], + // ], [ + // 'url' => $item['url'], + // ]); + // } + + $device->update([ + 'status' => count($data['imgUrl'] ?? []) > 0 ? DeviceStatus::Online : DeviceStatus::Offline, + ]); + 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'] ?? ''); + } +} diff --git a/app/Console/Commands/BiAng/WormStatisticsSyncCommand.php b/app/Console/Commands/BiAng/WormStatisticsSyncCommand.php new file mode 100644 index 0000000..9b6c04b --- /dev/null +++ b/app/Console/Commands/BiAng/WormStatisticsSyncCommand.php @@ -0,0 +1,92 @@ +sync(); + } + + protected function sync(): void + { + /** @var \Illuminate\Database\Eloquent\Collection */ + $devices = Device::with(['project']) + ->supplierBy('device-supplier-biang') + ->where('type', DeviceType::Worm) + ->whereIn('status', [DeviceStatus::Online, DeviceStatus::Offline]) + ->get(); + + if ($devices->isEmpty()) { + $this->warn('没有找到虫情设备'); + return; + } + + $today = today(); + + foreach ($devices as $device) { + $this->info('=================================='); + + $latestReportedAt = WormReport::Where('device_id', $device->id)->latest('reported_at')->value('reported_at'); + + $start = $latestReportedAt ? $latestReportedAt->copy() : $today->copy()->subDays(179); + + $days = $start->diffInDays($today, false); + + if ($days > 179) { + $end = $start->copy()->addDays(179); + } else { + if ($days < 2) { + $start = $today->copy()->subDays(2); + } + $end = $today->copy(); + } + $this->info('设备编号: '.$device->sn); + $this->info('开始日期: '.$start->toDateString()); + $this->info('结束日期: '.$end->toDateString()); + + $statistics = (new BiAngDeviceService())->getWormStatistics($device, $start, $end); + + foreach (data_get($statistics, 'data.records', []) as $item) { + $data = collect(Arr::except($item, '日期'))->map(fn ($v, $k) => ['name' => $k, 'num' => $v]); + + WormReport::updateOrCreate([ + 'device_id' => $device->id, + 'reported_at' => $item['日期'], + ], [ + 'agricultural_base_id' => $device->agricultural_base_id, + 'worm_num' => $data->sum('num'), + 'data' => $data->values(), + ]); + } + + $this->info('=================================='); + } + } +} diff --git a/app/Console/Commands/SoilMonitoringLogFixCommand.php b/app/Console/Commands/SoilMonitoringLogFixCommand.php deleted file mode 100644 index d82fce2..0000000 --- a/app/Console/Commands/SoilMonitoringLogFixCommand.php +++ /dev/null @@ -1,93 +0,0 @@ -subHour()->startOfHour(); - - if ($hour = $this->argument('hour')) { - $time = Carbon::createFromFormat('Y-m-d H:i:s', $hour)->startOfHour(); - } - - $devices = Device::where('type', DeviceType::Soil) - ->where('status', DeviceStatus::Online) - ->get(); - - foreach ($devices as $device) { - $last = SoilMonitoringLog::where('device_id', $device->id) - ->where('monitored_at', $time->copy()->subHour()) - ->first(); - - if ($last === null) { - continue; - } - - try { - $log = SoilMonitoringLog::firstOrCreate([ - 'device_id' => $device->id, - 'monitored_at' => $time, - ], [ - 'agricultural_base_id' => $device->agricultural_base_id, - ]); - - foreach ([ - 'conductivity', - 'humidity', - 'temperature', - 'n', - 'p', - 'k', - ] as $key) { - if (is_null($log->{$key})) { - $log->{$key} = $last->{$key}; - } - } - - if ($log->isDirty()) { - $log->is_filled = true; - } - - $log->save(); - - if ($log->wasChanged()) { - $linkosDeviceLogService->handleSoilMonitoringDailyLog($device, $time); - } - } catch (Throwable $e) { - report($e); - } - } - - return Command::SUCCESS; - } -} diff --git a/app/Console/Commands/WaterQualityMonitoringLogFixCommand.php b/app/Console/Commands/WaterQualityMonitoringLogFixCommand.php deleted file mode 100644 index 4929839..0000000 --- a/app/Console/Commands/WaterQualityMonitoringLogFixCommand.php +++ /dev/null @@ -1,93 +0,0 @@ -subHour()->startOfHour(); - - if ($hour = $this->argument('hour')) { - $time = Carbon::createFromFormat('Y-m-d H:i:s', $hour)->startOfHour(); - } - - $devices = Device::where('type', DeviceType::WaterQuality) - ->where('status', DeviceStatus::Online) - ->get(); - - foreach ($devices as $device) { - $last = WaterQualityMonitoringLog::where('device_id', $device->id) - ->where('monitored_at', $time->copy()->subHour()) - ->first(); - - if ($last === null) { - continue; - } - - try { - $log = WaterQualityMonitoringLog::firstOrCreate([ - 'device_id' => $device->id, - 'monitored_at' => $time, - ], [ - 'agricultural_base_id' => $device->agricultural_base_id, - ]); - - foreach ([ - 'chlorine', - 'conductivity', - 'oxygen', - 'ph', - 'temperature', - 'turbidity', - ] as $key) { - if (is_null($log->{$key})) { - $log->{$key} = $last->{$key}; - } - } - - if ($log->isDirty()) { - $log->is_filled = true; - } - - $log->save(); - - if ($log->wasChanged()) { - $linkosDeviceLogService->handleWaterQualityMonitoringDailyLog($device, $time); - } - } catch (Throwable $e) { - report($e); - } - } - - return Command::SUCCESS; - } -} diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 8e3a6c0..4e8a7ea 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -15,11 +15,7 @@ class Kernel extends ConsoleKernel */ protected function schedule(Schedule $schedule) { - $schedule->command(Commands\SoilMonitoringLogFixCommand::class) - ->hourly() - ->runInBackground(); - - $schedule->command(Commands\WaterQualityMonitoringLogFixCommand::class) + $schedule->command(Commands\BiAng\WormStatisticsSyncCommand::class) ->hourly() ->runInBackground(); } diff --git a/app/Enums/DeviceType.php b/app/Enums/DeviceType.php index e6525d8..548c381 100644 --- a/app/Enums/DeviceType.php +++ b/app/Enums/DeviceType.php @@ -8,6 +8,9 @@ enum DeviceType: int case Soil = 2; // 土壤设备 case WaterQuality = 3; // 水质设备 case Meteorological = 4; // 气象设备 + case Worm = 5; // 虫情设备 + case InsectSexLure = 6; // 昆虫性诱设备 + case InsecticidalLamp = 7; // 杀虫灯设备 /** * @return string @@ -27,6 +30,9 @@ enum DeviceType: int static::Soil->value => '土壤设备', static::WaterQuality->value => '水质设备', static::Meteorological->value => '气象设备', + static::Worm->value => '虫情设备', + static::InsectSexLure->value => '昆虫性诱设备', + static::InsecticidalLamp->value => '杀虫灯设备', ]; } } diff --git a/app/Exceptions/BiAngException.php b/app/Exceptions/BiAngException.php new file mode 100644 index 0000000..7726028 --- /dev/null +++ b/app/Exceptions/BiAngException.php @@ -0,0 +1,9 @@ +filter($request->all())->sort(); - $list = $query->paginate(Paginator::resolvePerPage('per_page', 20, 50)); + $list = $query->paginate(Paginator::resolvePerPage('per_page', 20)); return $this->json(AgriculturalBaseResource::collection($list)); } diff --git a/app/Http/Controllers/Callback/YunFeiController.php b/app/Http/Controllers/Callback/YunFeiController.php new file mode 100644 index 0000000..52b4c0f --- /dev/null +++ b/app/Http/Controllers/Callback/YunFeiController.php @@ -0,0 +1,1158 @@ + '金龟子', + '2' => '夜蛾', + '3' => '二点委夜蛾', + '4' => '梨剑纹夜蛾', + '5' => '杨扇舟蛾', + '6' => '舟蛾', + '7' => '旋幽夜蛾', + '8' => '蝼蛄', + '9' => '歩甲', + '10' => '螟蛾', + '11' => '毛黄鳃金龟', + '12' => '尺蛾', + '13' => '剑纹夜蛾', + '14' => '粉缘钻夜蛾', + '15' => '夜蛾科', + '16' => '七星瓢虫', + '17' => '棉铃虫', + '18' => '蜻蜓', + '19' => '蚊', + '20' => '东方粘虫', + '21' => '叶蝉', + '22' => '春尺蠖', + '23' => '雄性春尺蠖', + '24' => '杨小舟蛾', + '25' => '甘蓝夜蛾', + '26' => '小地老虎', + '27' => '两点尼夜蛾', + '28' => '柳阴翅斑螟', + '29' => '桑褶翅尺蛾', + '30' => '宽胫夜蛾', + '31' => '尺蠖', + '32' => '一点钻夜蛾', + '33' => '天蛾', + '34' => '裳夜蛾', + '35' => '灯蛾', + '36' => '美国白蛾', + '37' => '八字白眉天蛾', + '38' => '陌夜蛾', + '39' => '豆天蛾', + '40' => '麦蛾', + '41' => '围连环夜蛾', + '42' => '亚美尺蛾', + '43' => '梨星毛虫', + '44' => '银锭夜蛾', + '45' => '黄臀灯蛾', + '46' => '大螟', + '47' => '燕尾舟蛾', + '48' => '榆津尺蛾', + '49' => '朽木夜蛾', + '50' => '黄地老虎', + '51' => '白钩粘夜蛾', + '52' => '桃蛀螟', + '53' => '甜菜夜蛾', + '54' => '斜纹夜蛾', + '55' => '蚀夜蛾', + '56' => '淡银锭夜蛾', + '57' => '齿美冬夜蛾', + '58' => '一点金刚钻', + '59' => '胡桃豹夜蛾', + '60' => '桑剑纹夜蛾', + '61' => '蓝目天蛾', + '62' => '黑绒鳃金龟', + '63' => '烟青虫', + '64' => '暗黑鳃金龟', + '65' => '中华绒金龟', + '66' => '八字地老虎', + '67' => '榆绿天蛾', + '68' => '红星雪灯蛾', + '69' => '雀纹天蛾', + '70' => '铜绿丽金龟', + '71' => '水龟虫/水龟', + '72' => '曲线尼夜蛾', + '73' => '粘虫', + '74' => '瘦银锭夜蛾', + '75' => '红天蛾', + '76' => '鳃金龟', + '77' => '大黑鳃金龟', + '78' => '大地老虎', + '79' => '玉米螟', + '80' => '赤角盲蝽', + '81' => '槐尺蛾', + '82' => '银纹夜蛾', + '83' => '天牛', + '84' => '乏夜蛾', + '85' => '丁香天蛾', + '86' => '构月天蛾', + '87' => '虎甲', + '88' => '劳氏粘虫', + '89' => '白薯天蛾', + '90' => '广鹿蛾', + '91' => '二十八星瓢虫', + '92' => '腮金龟', + '93' => '人纹污夜蛾', + '94' => '叩甲', + '95' => '楸蠹野螟', + '96' => '丝绵木金星尺蛾', + '97' => '红缘灯蛾', + '98' => '黄褐丽金龟', + '99' => '螟蛾科', + '100' => '红棕灰夜蛾', + '102' => '广鹿灯蛾', + '103' => '蝽', + '104' => '蜂', + '105' => '大造桥虫', + '106' => '童剑纹夜蛾', + '107' => '晃剑纹夜蛾', + '108' => '钩粘虫', + '109' => '直影夜蛾', + '110' => '毛黄绢金龟', + '111' => '乌氏小尾天蚕蛾', + '112' => '褐边绿刺蛾', + '113' => '广鹿舟蛾', + '115' => '龙虱', + '116' => '双带盘瓢虫', + '117' => '槲犹冬夜蛾', + '118' => '洋槐天蛾', + '119' => '弧角散纹夜蛾', + '120' => '黄脉天蛾', + '121' => '葡萄天蛾', + '122' => '桃六点天蛾', + '123' => '异色瓢虫', + '124' => '榆黄足毒蛾', + '125' => '客来夜蛾', + '126' => '桦尺蛾', + '127' => '草地螟', + '128' => '细条纹野螟', + '129' => '污灯蛾属', + '130' => '杨二尾舟蛾', + '131' => '克什杆野螟', + '132' => '筱客来夜蛾', + '133' => '栗六点天蛾', + '134' => '紫光盾天蛾', + '135' => '款冬玉米螟', + '136' => '草蛉', + '137' => '亚麻篱灯蛾', + '138' => '扁连环夜蛾', + '139' => '圣蜣螂', + '140' => '白钩粘虫', + '141' => '苇实夜蛾', + '142' => '姬蜂', + '143' => '秘夜蛾', + '144' => '织网夜蛾', + '145' => '深色白眉天蛾', + '146' => '短扇舟蛾', + '147' => '白须天蛾', + '148' => '歌梦尼夜蛾', + '149' => '海安夜蛾', + '150' => '满丫纹夜蛾', + '151' => '蟋蟀', + '152' => '双斑青步甲', + '153' => '白条夜蛾', + '154' => '蟪蛄', + '155' => '负子蝽', + '156' => '脊青步甲', + '157' => '宽斑青步甲', + '158' => '稻纵卷叶螟', + '159' => '淡剑夜蛾', + '160' => '甜菜白带野螟', + '161' => '樗蚕', + '162' => '蒙古寒蝉', + '163' => '中带三角夜蛾', + '164' => '蝗虫', + '165' => '多色异丽金龟', + '166' => '白色小卷蛾', + '167' => '狭边青步甲', + '168' => '棉卷叶野螟', + '169' => '豆荚野螟', + '170' => '麻小食心虫', + '171' => '星斑虎甲', + '172' => '黄缘龙虱', + '173' => '无斑弧丽金龟', + '174' => '白额鹰翅天蛾', + '175' => '日本真龙虱', + '176' => '山东云斑螟', + '177' => '小文夜蛾', + '178' => '三条蛀野螟', + '179' => '榆掌舟蛾', + '180' => '刺槐掌舟蛾', + '181' => '星绒天蛾', + '182' => '杨剑舟蛾', + '183' => '刀夜蛾', + '184' => '红节天蛾', + '185' => '星白雪灯蛾', + '186' => '桃剑纹夜蛾', + '187' => '谐夜蛾', + '188' => '小剑纹夜蛾', + '189' => '鸣鸣蝉', + '190' => '姬夜蛾', + '191' => '落叶松毛虫', + '192' => '苹六点天蛾', + '193' => '四斑绢野螟', + '194' => '甘薯天蛾', + '195' => '小线角木蠹蛾', + '196' => '三斑蕊夜蛾', + '197' => '白雪灯蛾', + '198' => '黄刺蛾', + '199' => '茶翅蝽', + '200' => '杨树枯叶蛾', + '201' => '标瑙夜蛾', + '202' => '瓜绢野螟', + '203' => '稻绿蝽', + '204' => '杨雪毒蛾', + '205' => '榆白边舟蛾', + '206' => '扁刺蛾', + '207' => '绒黏夜蛾', + '208' => '庸肖毛翅夜蛾', + '209' => '中华婪步甲', + '210' => '褐黄前锹甲', + '211' => '旱柳原野螟', + '212' => '巨影夜蛾', + '213' => '食蚜蝇', + '214' => '双斑葬甲', + '215' => '黄毒蛾', + '216' => '婪步甲', + '217' => '土甲', + '218' => '中华真地鳖', + '219' => '紫线夜蛾', + '220' => '小黄鳃金龟', + '221' => '中华真土鳖', + '222' => '云斑虎甲', + '223' => '中华黧尺蛾', + '224' => '中华绿刺蛾', + '225' => '巨豹纹尺蛾', + '226' => '多斑豹蠹蛾', + '227' => '桑尺蛾', + '228' => '灰直纹螟', + '229' => '中国绿刺蛾', + '230' => '云杉梢斑螟', + '231' => '桑绢野螟', + '232' => '黄杨绢野螟', + '233' => '突背斑红蝽', + '234' => '高粱条螟', + '235' => '小麦负泥虫', + '236' => '苹掌舟蛾', + '237' => '绒粘夜蛾', + '238' => '灰白灯蛾', + '239' => '隐丫纹夜蛾', + '240' => '满纹夜蛾', + '241' => '黑剑狼夜蛾', + '242' => '蜣螂', + '243' => '福婆鳃金龟', + '244' => '雨尺蛾', + '245' => '优美苔蛾', + '246' => '黄斑野螟', + '247' => '疆夜蛾', + '248' => '六点天蛾', + '249' => '斜线夜蛾', + '250' => '石榴巾夜蛾', + '251' => '绒星天蛾', + '252' => '霜天蛾', + '253' => '大田鳖', + '254' => '灰双纹螟', + '255' => '青尺蛾', + '256' => '二线绿尺蛾', + '257' => '散纹夜蛾', + '258' => '红双线尺蛾', + '259' => '胞短栉夜蛾', + '260' => '飞虱科', + '261' => '桃多斑野螟', + '262' => '甜菜青野螟', + '263' => '核桃鹰翅天蛾', + '264' => '角顶尺蛾', + '265' => '葡萄缺角天蛾', + '266' => '绿尾大蚕蛾', + '267' => '杨褐枯叶蝶', + '268' => '双云尺蛾', + '269' => '斑拟兜夜蛾', + '270' => '阿莎尺蛾', + '271' => '榄绿岐角螟', + '272' => '青革土蝽', + '273' => '核桃美舟蛾', + '274' => '斑点卷叶螟', + '275' => '黄褐箩纹蛾', + '276' => '白环红天蛾', + '277' => '白腹网丛螟', + '278' => '枯叶蛾', + '279' => '丹日明夜蛾', + '280' => '仿白边舟蛾', + '281' => '槐羽舟蛾', + '282' => '草地贪夜蛾', + '283' => '环夜蛾', + '284' => '尘尺蛾', + '285' => '黄二星舟蛾', + '286' => '榆木蠹蛾', + '287' => '水黾', + '288' => '银装冬夜蛾', + '289' => '饰奇尺蛾', + '290' => '枯叶蝶', + '291' => '步甲', + '292' => '阔胸禾犀金龟', + '293' => '眼斑钩蛾', + '294' => '三开蜣螂', + '295' => '金星步甲', + '296' => '残夜蛾', + '297' => '野蚕蛾', + '298' => '芦苇豹蠹蛾', + '299' => '华晓扁犀金龟', + '300' => '灰胸突鳃金龟', + '301' => '龟纹瓢虫', + '302' => '麻皮蝽', + '303' => '斑须蝽', + '304' => '斜斑虎甲', + '305' => '地鳖', + '306' => '叶甲', + '307' => '燕夜蛾', + '308' => '黑纹北灯蛾', + '309' => '网夜蛾', + '310' => '棘翅夜蛾', + '311' => '规尺蛾', + '312' => '苜蓿银纹夜蛾', + '313' => '拟扇舟蛾', + '314' => '丁目大蚕蛾', + '315' => '金黄蛾', + '316' => '黄星雪灯蛾', + '317' => '暗纹紫褐螟', + '318' => '白眉天蛾', + '319' => '黄板盘瓢虫', + '320' => '玫岐角螟', + '321' => '枯黄贡尺蛾', + '322' => '小豆长喙天蛾', + '323' => '橙拟灯蛾', + '324' => '粉蝶灯蛾', + '325' => '纹散丽灯蛾', + '326' => '雪尾尺蛾', + '327' => '鹰翅天蛾', + '328' => '波纹蛾', + '329' => '黑条灰灯蛾', + '330' => '八点灰灯蛾', + '331' => '间纹弦夜蛾', + '332' => '缤夜蛾', + '333' => '樟蚕', + '334' => '乌夜蛾', + '335' => '亮棲夜蛾', + '336' => '鸟嘴壶夜蛾', + '337' => '榕透翅毒蛾', + '338' => '四星负葬甲', + '339' => '魔目夜蛾', + '340' => '华尾天蚕蛾', + '341' => '洋麻钩蛾', + '342' => '旋皮夜蛾', + '343' => '奇尺蛾', + '344' => '土元', + '345' => '肯髯须夜蛾', + '346' => '苹果卷叶蛾', + '347' => '矛夜蛾', + '348' => '斜线燕蛾', + '349' => '红秘夜蛾', + '350' => '桃红猎夜蛾', + '351' => '帕委夜蛾', + '352' => '奚毛胫夜蛾', + '353' => '委夜蛾', + '354' => '角翅舟蛾', + '355' => '戟盗毒蛾', + '356' => '瓢虫', + '357' => '隐尺蛾', + '359' => '金龟', + '360' => '花金龟', + '361' => '白褐缘尾尺蛾', + '362' => '金黄螟', + '363' => '云斑白条天牛', + '364' => '瓦矛夜蛾', + '365' => '二化螟', + '366' => '菜粉蝶', + '367' => '杨枯叶蛾', + '368' => '淡剑纹夜蛾', + '369' => '焰夜蛾', + '370' => '二点织螟', + '371' => '白钩小卷蛾', + '372' => '女贞天蛾', + '373' => '锹甲', + '374' => '艾锥额野螟', + '375' => '白星花金龟', + '376' => '毒蛾', + '377' => '二点织蛾', + '378' => '萝藦艳情尺蛾', + '379' => '苹枯叶蛾', + '380' => '哈展尺蛾', + '381' => '中金弧夜蛾', + '382' => '黄斑盘瓢虫', + '383' => '缘黄毒蛾', + '384' => '蠹蛾', + '385' => '土鳖', + '386' => '人纹污灯蛾', + '387' => '松黑天蛾', + '388' => '蚜虫', + '389' => '萝藦艳青尺蛾', + '390' => '苹蚂舟蛾', + '391' => '木橑尺蛾', + '392' => '红蝽', + '393' => '掌尺蛾', + '394' => '肖浑黄灯蛾', + '395' => '阳污灯蛾', + '396' => '黄缘苔蛾', + '397' => '丽金龟', + '398' => '犀金龟', + '399' => '卷蛾', + '400' => '钩蛾', + '401' => '红尾大蚕蛾', + '402' => '刺蛾', + '403' => '玫瑰巾夜蛾', + '404' => '双委夜蛾', + '405' => '油松毛虫', + '406' => '朝鲜东蚁蛉', + '407' => '焦边尺蛾', + '408' => '黾蝽', + '409' => '苔蛾', + '411' => '蜚蠊', + '412' => '小蠹', + '413' => '桔小实蝇', + '414' => '水龟甲', + '415' => '大蚊', + '416' => '华北蝼蛄', + '417' => '豹纹卷野螟', + '418' => '长蝽', + '419' => '蠼螋', + '420' => '缘蝽', + '421' => '粪金龟', + '422' => '隐翅虫', + '423' => '象甲', + '424' => '猎蝽', + '425' => '东方蝼蛄', + '426' => '油葫芦', + '427' => '阔胫鰓金龟', + '428' => '棉大造桥虫', + '429' => '绒毛曲斑青地甲', + '430' => '葬甲', + '431' => '珀蝽', + '432' => '怀羽舟蛾', + '433' => '大云鳃金龟', + '434' => '赏夜蛾', + '435' => '甜菜夜螟', + '436' => '红带新鹿蛾', + '437' => '亚洲玉米螟', + '438' => '白肩天蛾', + '439' => '双斑辉尺蛾', + '441' => '构星天蛾', + '442' => '双叉犀牛龟', + '444' => '蚁', + '445' => '金黄娟金龟', + '446' => '黑色蔗龟', + '447' => '蝇', + '448' => '石蛾', + '449' => '螳螂', + '450' => '胡蜂', + '451' => '蝉', + '452' => '大斑波纹蛾', + '453' => '黑图夜蛾', + '454' => '黄星尺灯蛾', + '455' => '天蚕蛾', + '457' => '揪野螟', + '458' => '蚁蛉', + '459' => '绿盲蝽', + '460' => '丝棉木金星大尺蛾', + '461' => '蛱蝶', + '462' => '毛黄金龟', + '464' => '蚙', + '465' => '叩头甲', + '466' => '褐蛉', + '467' => '谷蠧', + '468' => '吉丁虫', + '469' => '东亚飞蝗', + '470' => '白雪灰灯蛾', + '471' => '泛尺蛾', + '472' => '蚊科', + '473' => '摇蚊', + '474' => '野螟', + '475' => '草螟', + '476' => '拟步甲', + '477' => '麦蝽', + '478' => '黑带食蚜蝇', + '479' => '东方蜜蜂', + '481' => '吉丁甲', + '482' => '金边龙虱', + '483' => '划蝽', + '484' => '叶蜂', + '485' => '中华扁锹', + '486' => '浮金龟', + '487' => '杂似谷盗', + '488' => '双齿蝼步甲', + '489' => '大团扇春蜓', + '490' => '爪哇异食蚜蝇', + '491' => '夹竹桃天蛾', + '492' => '大水龟甲', + '493' => '桑天牛', + '494' => '红脊长蝽', + '495' => '豆娘', + '496' => '蝼步甲', + '497' => '仰泳蝽', + '498' => '蝽蜓', + '499' => '印度谷螟', + '500' => '叶甲科', + '501' => '棉大卷叶螟', + '502' => '伪叶甲', + '503' => '屁步甲', + '504' => '异粉蝶灯蛾', + '505' => '白禾螟', + '506' => '棕色腮金龟', + '507' => '芋双线天蛾', + '508' => '盲蝽', + '509' => '鲜黄鳃金龟', + '511' => '红腹白灯蛾', + '512' => '长缘天蛾', + '514' => '箩纹蛾', + '515' => '叉斜带毒蛾', + '516' => '丝绵木尺蛾', + '517' => '绿刺蛾', + '518' => '谷类大蚊', + '519' => '臭椿皮夜蛾', + '521' => '星白灯蛾', + '522' => '白蜡绢野螟', + '523' => '蟑螂(蜚蠊,标号:411)', + '524' => '枣六点天蛾', + '525' => '虻', + '526' => '紫斑谷螟', + '527' => '豆荚斑螟', + '528' => '红缘翅斑蛾', + '529' => '柳毒蛾', + '530' => '兀鲁夜蛾', + '531' => '蜜蜂', + '532' => '麦牧野螟', + '533' => '紫线尺蛾', + '534' => '内夜蛾', + '535' => '粉纹夜蛾', + '536' => '金黄绢金龟', + '537' => '掌舟蛾', + '538' => '丝棉木尺蛾', + '539' => '绢金龟', + '540' => '枣桃六点天蛾', + '541' => '黏虫', + '542' => '条背天蛾', + '543' => '臭椿皮蛾', + '544' => '甘薯绮夜蛾', + '545' => '茶黄毒蛾', + '546' => '葡萄透翅蛾', + '547' => '白二尾舟蛾', + '548' => '八点灯蛾', + '549' => '绿尾天蚕蛾', + '550' => '萝纹蛾', + '551' => '豹纹木蠧蛾', + '552' => '舞虻', + '553' => '茎蜂', + '554' => '大草蛉', + '555' => '胡麻斑星尺蛾', + '556' => '蜉蝣', + '557' => '尖尾尺蛾', + '558' => '黄扬绢野螟', + '559' => '蚕蛾', + '562' => '琴纹尺蛾', + '563' => '双斜线尺蛾', + '564' => '白夜蛾', + '565' => '黄褐丽金闹', + '566' => '谷盗', + '567' => '三化螟', + '568' => '绿尺蛾', + '569' => '昏舟蛾', + '570' => '柳干蠹蛾', + '571' => '霜尺蛾', + '572' => '斑虻', + '573' => '银光草螟', + '574' => '菜蛾', + '575' => '豆荚螟', + '576' => '苜蓿夜蛾', + '577' => '白肾夜蛾', + '578' => '白肾灰夜蛾', + '579' => '纯白草螟', + '580' => '茸喙丽金龟', + '581' => '蛇蛉', + '582' => '巨蝼步甲', + '583' => '蜣蝇', + '584' => '贯众伸喙尺蛾', + '585' => '小褐髯须夜蛾', + '586' => '泰山簇斑螟', + '587' => '白桦角须野螟', + '588' => '红黄野螟', + '589' => '黑点蚀叶野螟', + '590' => '木蠹蛾', + '591' => '白线散纹夜蛾', + '592' => '灰蝶尺蛾', + '593' => '杠柳原野螟', + '594' => '幔折线尺蛾', + '595' => '丽蝇', + '596' => '并脉岐角螟蛾', + '597' => '柿星尺蛾', + '598' => '黑斑蚀叶野螟', + '599' => '黄连木尺蠖', + '601' => '黑皱鳃金龟', + '602' => '中华薄翅天牛', + '603' => '锈红金龟', + '604' => '盾天蛾', + '605' => '短斑普猎蝽', + '606' => '线委夜蛾', + '607' => '云纹虎甲', + '608' => '漆黑污灯蛾', + '609' => '松毛虫', + '610' => '紫带姬尺蛾', + '611' => '螳蛉', + '612' => '花胫绿纹蝗', + '613' => '东北巾夜蛾', + '614' => '柯基纹丛螟', + '615' => '菜氏猛叩甲', + '616' => '星尺蛾', + '617' => '华扁犀金龟', + '618' => '环纹夜蛾', + '619' => '金星尺蛾', + '620' => '旅游瓢虫', + '621' => '鼠天蛾', + '622' => '紫条尺蛾', + '623' => '明痣苔蛾', + '624' => '褐网尺蛾', + '625' => '三叉地老虎', + '626' => '肖二线绿尺蛾', + '627' => '四星尺蛾', + '628' => '红黏夜蛾', + '629' => '小花波尺蛾', + '630' => '朽木甲', + '631' => '灰猎夜蛾', + '632' => '稻巢草螟', + '633' => '指角麦蛾', + '634' => '金盅尺蛾', + '635' => '截翅尺蛾', + '636' => '双线尺蛾', + '637' => '平紫脖夜蛾', + '638' => '沤泊波纹蛾', + '639' => '白污灯蛾', + '641' => '格庶尺蛾', + '642' => '斑蛾', + '643' => '羽舟蛾', + '644' => '苹小卷叶蛾', + '645' => '稻水螟', + '646' => '苹烟尺蛾', + '647' => '蚕豆紫脖蛾', + '648' => '平夜蛾', + '649' => '焚紫脖夜蛾', + '650' => '贯众伸喙野螟', + '651' => '尖瓣灰纹卷蛾', + '652' => '摊巨冬夜蛾', + '653' => '草莓尺蛾', + '654' => '白缘苇野螟', + '655' => '核桃星尺蛾', + '656' => '郭公虫', + '657' => '明纹柏松毛虫', + '658' => '褐线尺蛾', + '659' => '旋歧夜蛾', + '660' => '黄山松毛虫', + '661' => '直紫脖夜蛾', + '662' => '红云翅斑螟', + '663' => '黄灯蛾', + '664' => '家蝇', + '665' => '黄翅缀叶野螟', + '666' => '枣核六点天蛾', + '668' => '长眉眼尺蛾', + '669' => '夏枯草展须野螟', + '670' => '白背飞虱', + '671' => '灰飞虱', + '672' => '褐飞虱', + '673' => '网纹夜蛾', + '674' => '斑螟', + '675' => '核桃目尺蛾', + '676' => '灰棕金羽蛾', + '677' => '艾棕麦蛾', + '678' => '蝶角蛉', + '679' => '豹蠹蛾', + '681' => '云星黄毒蛾', + '682' => '锯线尺蛾', + '683' => '紫斑黄带蛾', + '684' => '绿蝇', + '685' => '大云斑鳃金龟', + '686' => '血红雪苔蛾', + '687' => '果蝇', + '688' => '纹散灯蛾', + '689' => '排点灯蛾', + '690' => '白钩黏夜蛾', + '691' => '黄毛赛金龟', + '692' => '新林舟蛾', + '693' => '井夜蛾', + '694' => '花实夜蛾', + '695' => '棉褐带卷蛾', + '696' => '卷叶蛾', + '697' => '王氏樗蛾', + '698' => '刺槐外斑尺蛾', + '699' => '懈毛胫夜蛾', + '700' => '糜夜蛾', + '701' => '东方小垫甲', + '702' => '蚁形甲', + '703' => '十二斑褐菌瓢虫', + '704' => '明治苔蛾', + '705' => '葡萄卷叶野螟', + '706' => '大青叶蝉', + '707' => '绢野螟', + '708' => '柳圆叶甲', + '709' => '蜘蛛', + '710' => '隐翅甲', + '711' => '钩月天蛾', + '712' => '寄生蜂', + '713' => '苍蝇', + '714' => '灰直纵螟', + '715' => '倭委夜蛾', + '716' => '小菜蛾', + '717' => '小地老虎螟', + '718' => '中黑盲蝽', + '719' => '粪蜣', + '721' => '壶夜蛾', + '722' => '螽斯', + '723' => '食叶', + '724' => '平台珠蝼步甲', + '725' => '李枯叶蛾', + '726' => '春蜓', + '727' => '点斑雪灯蛾', + '728' => '黄臂灯蛾', + '729' => '旅裳夜蛾', + '730' => '三角夜蛾', + '731' => '蠋步甲', + '732' => '红羽舟蛾', + '733' => '黑蚱蝉', + '734' => '中华黛尺蛾', + '735' => '苜蓿尺蛾', + '736' => '沤伯波纹蛾', + '737' => '灰灯蛾', + '738' => '双裂类荚斑螟', + '739' => '蓝纹尾蟌', + '740' => '莴苣冬夜蛾', + '741' => '两色绮夜蛾', + '742' => '豹灯蛾', + '743' => '四眼尺蛾', + '744' => '核桃六点天蛾', + '746' => '黄柄脉锦斑蛾', + '747' => '甜菜白带螟', + '748' => '大隐翅虫', + '749' => '羽蛾', + '750' => '灰褐带蛾', + '751' => '带蛾', + '752' => '窗胸萤', + '753' => '蝶灯蛾', + '754' => '黄蜻', + '755' => '伊贝鹿蛾', + '756' => '褐顶毒蛾', + '757' => '愕蚕蛾', + '758' => '黄野螟', + '759' => '云尺蛾', + '761' => '马蜂', + '762' => '赤条黄带蛾', + '764' => '猩红雪苔蛾', + '765' => '齿蛉', + '766' => '草蝉', + '767' => '鬼脸天蛾', + '768' => '小造桥虫', + '769' => '异美苔蛾', + '770' => '旋目夜蛾', + '771' => '稻飞虱', + '772' => '小阔胫鳃金龟', + '773' => '杨卷叶螟', + '774' => '龟纹夜蛾', + '776' => '白灯蛾', + '777' => '绿翠尺蛾', + '779' => '食虫虻', + '780' => '邑蝽', + '781' => '青翅毒隐翅甲', + '782' => '黑条灰天蛾', + '783' => '黄腹鹿蛾', + '784' => '蓟马', + '785' => '蚂蚁', + '786' => '啮虫', + '787' => '小蜂', + '788' => '粉虱', + '789' => '蛾蠓', + '790' => '跳甲', + '791' => '木虱', + '792' => '黑带灰灯蛾', + '793' => '水鼓甲', + '794' => '细蜂', + '795' => '网蝽', + '796' => '鹿蛾', + '797' => '茧蜂', + '798' => '实蝇', + '799' => '茎甲', + '801' => '浩波纹蛾', + '802' => '红尾天蚕蛾', + '803' => '槐黑星虎天牛', + '804' => '赤巢螟', + '805' => '日愕蚕蛾', + '806' => '变色夜蛾', + '807' => '掌夜蛾', + '808' => '宽夜蛾', + '809' => '二尾舟蛾', + '810' => '茶须野螟', + '811' => '白带符夜蛾', + '812' => '白蚁', + '813' => '中华鼻蜡蝉', + '814' => '黄斑青步甲', + '815' => '茶柄脉锦斑蛾', + '816' => '双斑天蛾', + '817' => '中华斑杧', + '818' => '污刺胸猎蝽', + '819' => '奇裂附步甲', + '820' => '大光蠊', + '821' => '蚱蜢', + '822' => '纹眼尺蛾', + '823' => '栉尾尺蛾', + '824' => '木撩尺蛾', + '825' => '黄灰网尺蛾', + '826' => '幕僚尺蛾', + '827' => '青球箩纹蛾', + '828' => '暗脉艳苔蛾', + '829' => '窄黄缘绿刺蛾', + '830' => '黄星尺蛾', + '831' => '锤角细蜂', + '832' => '行军蚁', + '833' => '沫蝉', + '834' => '赭黄长须蛾', + '835' => '稻摇蚊', + '836' => '稻盲蝽', + '837' => '基黑纹丛螟', + '838' => '蚜蝇', + '839' => '枯叶夜蛾', + '841' => '负泥虫', + '842' => '蛾蜡蝉', + '843' => '粤豹大蚕蛾', + '844' => '东北栎枯叶蛾', + '845' => '仰蝽', + '846' => '毒隐翅甲', + '847' => '黄曲条跳甲', + '848' => '蚊幼虫', + '849' => '黑带污灯蛾', + '850' => '黄褐枯叶蝶', + '851' => '莹叶甲', + '852' => '拟叩甲', + '853' => '泥蜂', + '854' => '蜉蝣幼虫', + '855' => '单鲁夜蛾', + '856' => '干纹夜蛾', + '857' => '斑头蝉', + '858' => '大三角鲁夜蛾', + '859' => '中华岱蝽', + '860' => '豇豆荚螟', + '861' => '中华鹿蛾', + '862' => '三线钩蛾', + '863' => '竹节虫', + '864' => '枯黄惑尺蛾', + '865' => '棘趾野螟', + '866' => '尖锥额野螟', + '868' => '丽刺小卷蛾', + '869' => '日球箩纹蛾', + '870' => '半鲁夜蛾', + '871' => '间纹德夜蛾', + '873' => '贡尺蛾', + '874' => '忍冬双斜卷蛾', + '875' => '瓜夜蛾', + '876' => '褐卷蛾', + '877' => '绿黄毛虫', + '878' => '赭翅臂花金龟', + '879' => '黄痣苔蛾', + '881' => '浅褐彩丽金龟', + '882' => '斑黑麦蛾', + '883' => '褐斑虎丽灯蛾', + '884' => '耳斑蟠尺蛾', + '885' => '北李褐枯叶蛾', + '886' => '纹岐角螟', + '887' => '银二星舟蛾', + '888' => '著蕊尾舟蛾', + '889' => '黄褐网尺蛾', + '890' => '丸尺蛾', + '891' => '粉蝶', + '892' => '乌桕巨天蚕蛾', + '893' => '黑长喙天蛾', + '894' => '伞双突野螟', + '895' => '瘿蚊', + '896' => '芫菁', + '897' => '较高', + '898' => '鹰尺蛾', + '899' => '三环狭野螟', + '900' => '姬蠊', + '901' => '朝尺蛾', + '902' => '紫光箩纹蛾', + '903' => '短啄夜蛾', + '904' => '碧伟蜓', + '905' => '核桃美州蛾', + '906' => '梨娜刺蛾', + '907' => '枯球箩纹蛾', + '908' => '蜓', + '909' => '二星舟蛾', + '910' => '朝线夜蛾', + '911' => '明裙剑夜蛾', + '912' => '白点暗野螟', + '913' => '麟角希夜蛾', + '914' => '眼尺蛾', + '915' => '黄双线尺蛾', + '916' => '联梦尼夜蛾', + '917' => '栎毛虫', + '918' => '宏遗夜蛾', + '919' => '太波纹蛾', + '921' => '小斑红蝽', + '922' => '柳沟胸跳甲', + '923' => '田鳖', + '924' => '条螽', + '925' => '角石蛾', + '926' => '缺叉等翅石蛾', + '927' => '尖双突野螟', + '928' => '锈色负子蝽', + '929' => '茶白毒蛾', + '930' => '硃美苔蛾', + '931' => '齿缘龙虱', + '932' => '醋栗尺蛾', + '933' => '强污灯蛾', + '934' => '稻弄蝶', + '935' => '妃夜蛾', + '936' => '白鳃角金龟', + '937' => '棉褐环野螟', + '938' => '暗褐卷蛾', + '939' => '梯斑谷蛾', + '940' => '点线脉褐蛉', + '941' => '桃展足蛾', + '942' => '草小卷蛾', + '943' => '中黑灰夜蛾', + '944' => '小瘿斑蛾', + '945' => '马铃薯瓢虫', + '946' => '点线锦织蛾', + '947' => '圆臀大黾蝽', + '948' => '双红线兔尺蛾', + '949' => '腰带燕尾舟蛾', + '950' => '密云草蛾', + '951' => '女贞首夜蛾', + '952' => '星天蛾', + '953' => '红毛花萤', + '954' => '库蚊', + '955' => '鲁夜蛾', + '956' => '黄粉甲', + '957' => '赞青尺蛾', + '958' => '窄肾长须夜蛾', + '959' => '污灯蛾', + '961' => '波纹小灰蝶', + '962' => '透翅蛾', + '963' => '苜蓿盲蝽', + '964' => '扭拟灯蛾', + '965' => '安土苔蛾', + '966' => '日污灯蛾', + '967' => '柞褐叶螟', + '968' => '乌柏黄毒蛾', + '969' => '黄绒野螟', + '970' => '浅黄箩舟蛾', + '971' => '半带黄毒蛾', + '972' => '钩翅大蚕蛾', + '973' => '肖叶甲', + '974' => '切叶蜂', + '975' => '女贞尺蛾', + '976' => '单斑多形长角纹石蛾', + '977' => '格蔗尺蛾', + '978' => '象蜡蝉', + '979' => '丽美苔蛾', + '980' => '苹小卷蛾', + '981' => '合欢双条天牛', + '982' => '折带黄毒蛾', + '983' => '齿翅三线天蛾', + '984' => '广屁步甲', + '985' => '栎掌天蛾', + '986' => '异螋', + '987' => '中国假蜉', + '988' => '犬蚊', + '989' => '暗步甲', + '990' => '栗山天牛', + '991' => '净污灯蛾', + '992' => '毛颊斑虎甲', + '993' => '优草螽', + '994' => '斑雅尺蛾', + '995' => '肿须隐翅虫', + '996' => '金盏供肩网蛾', + '997' => '绿翅绢野螟', + '998' => '蚱蝉', + '999' => '八点污灯蛾', + '1001' => '联纹小叶春蜓', + '1002' => '奇烈跗步甲', + '1003' => '尖突水龟虫', + '1004' => '长蠹', + '1005' => '黄足猎蝽', + '1006' => '脊冠叶蝉', + '1007' => '珀色毒蛾', + '1008' => '长角水叶甲', + '1009' => '稻蝗', + '1010' => '白线野蚕蛾', + '1011' => '赤双纹螟', + '1012' => '峦文夜蛾', + '1013' => '草螽', + '1014' => '扶桑四点野螟', + '1015' => '大鳖土蝽', + '1016' => '赤梗天牛', + '1017' => '连星污灯蛾', + '1018' => '华星天牛', + '1019' => '紫苑沟胫野螟', + '1020' => '栎鹰翅天蛾', + '1021' => '折无疆青尺蛾', + '1022' => '一点拟灯蛾', + '1023' => '蜡斑甲', + '1024' => '细蛾', + '1025' => '滨尸葬甲', + '1026' => '白雪苔蛾', + '1027' => '横线镰翅野螟', + '1028' => '褐翅棘趾野螟', + '1029' => '甜菜白带夜蛾', + '1030' => '梨叶斑蛾', + '1031' => '钩白肾夜蛾', + '1032' => '红晕散纹夜蛾', + '1033' => '蜡蝉', + '1034' => '肾纹绿尺蛾', + '1035' => '条螟', + '1036' => '双斑黄毒蛾', + '1037' => '大绿异丽金龟', + '1038' => '双带褐褶龙虱', + '1039' => '金色悦野螟', + '1041' => '槐刺外斑尺蛾', + '1042' => '金斑虎甲', + '1043' => '油桐尺蛾', + '1044' => '元参棘趾野螟', + '1045' => '铜绿异丽金龟', + '1046' => '绿蝽', + '1047' => '黑织叶野螟', + '1048' => '黄斑长朽木甲', + '1049' => '元岑棘趾野螟', + '1050' => '窄吉丁', + '1051' => '美雪苔蛾', + '1052' => '散白蚁', + '1053' => '四斑露尾甲', + '1054' => '红点唇瓢虫', + '1055' => '平烟尺蛾', + '1056' => '白蜡卷须野螟', + '1057' => '印度斑谷螟', + '1058' => '杨芦伸啄野螟', + '1059' => '井脉歧角螟蛾', + '1060' => '菜螟', + '1061' => '苹果蠹蛾', + '1062' => '梨小食心虫', + '1063' => '棉小造桥虫', + '1064' => '龟背天牛', + '1065' => '松墨天牛', + '1066' => '星天牛', + ]; + + /** + * 杀虫灯回调通知 + */ + public function insecticidalLampNotify(Request $request) + { + logger()->debug('杀虫灯回调通知', $request->input()); + + $payload = $request->input('payload'); + + if (! isset($payload['ext'])) { + return; + } + + $device = Device::supplierBy('device-supplier-yunfei')->where('sn', $payload['ext']['imei'])->first(); + + if ($device === null) { + return; + } + + if (! in_array($device->status, [DeviceStatus::Online, DeviceStatus::Offline])) { + return; + } + + switch ($payload['cmd']) { + case 'data': + $device->update([ + 'status' => DeviceStatus::Online, + ]); + + DeviceLog::firstOrCreate([ + 'device_id' => $device->id, + 'reported_at' => Carbon::createFromFormat('YmdHis', $payload['ext']['stamp']), + ], [ + 'data' => $payload['ext'], + ]); + break; + + case 'offline': + $device->update([ + 'status' => DeviceStatus::Offline, + ]); + break; + } + } + + /** + * 测报灯回调通知 + */ + public function wormNotify(Request $request) + { + logger()->debug('虫情设备回调通知', $request->input()); + + $payload = $request->input('payload'); + + if (! isset($payload['ext'])) { + return; + } + + $device = Device::supplierBy('device-supplier-yunfei')->where('sn', $payload['ext']['imei'])->first(); + + if ($device === null) { + return; + } + + if (! in_array($device->status, [DeviceStatus::Online, DeviceStatus::Offline])) { + return; + } + + switch ($payload['cmd']) { + case 'data': + $device->update([ + 'status' => DeviceStatus::Online, + ]); + break; + + case 'offline': + $device->update([ + 'status' => DeviceStatus::Offline, + ]); + break; + } + } + + /** + * 测报灯照片回调通知 + */ + public function wormPhotoNotify(Request $request) + { + logger()->debug('虫情图片通知', $request->input()); + + $device = Device::supplierBy('device-supplier-yunfei')->where('sn', $request->input('imei'))->first(); + + if ($device === null) { + return; + } + + if (! in_array($device->status, [DeviceStatus::Online, DeviceStatus::Offline])) { + return; + } + + DeviceLog::firstOrCreate([ + 'device_id' => $device->id, + 'reported_at' => now(), + ], [ + 'data' => $request->input(), + ]); + } +} diff --git a/app/Http/Controllers/ChartController.php b/app/Http/Controllers/ChartController.php index a401196..0ea8666 100644 --- a/app/Http/Controllers/ChartController.php +++ b/app/Http/Controllers/ChartController.php @@ -8,9 +8,9 @@ use App\Models\RiceShrimpFlow; use App\Models\RiceShrimpIndustry; use App\Models\RiceShrimpPrice; use App\Models\RiceShrimpWeeklyPrice; -use Peidikeji\Keywords\Models\Keywords; use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; +use Peidikeji\Keywords\Models\Keywords; class ChartController extends Controller { @@ -72,7 +72,7 @@ class ChartController extends Controller { $now = now(); - $weeks = Keywords::where('type_key', 'weeks-per-year')->pluck('name', 'id'); + $weeks = Keywords::where('type_key', 'weeks-per-year')->pluck('name', 'key'); $years = RiceShrimpWeeklyPrice::select('year') ->groupBy('year') @@ -105,7 +105,7 @@ class ChartController extends Controller $riceShrimpWeeklyPricesTable = (new RiceShrimpWeeklyPrice)->getTable(); $latestPrice = RiceShrimpWeeklyPrice::query() - ->join($keywordsTable, fn ($join) => $join->on("$riceShrimpWeeklyPricesTable.week", '=', "$keywordsTable.id")) + ->join($keywordsTable, fn ($join) => $join->on("$riceShrimpWeeklyPricesTable.week", '=', DB::raw("$keywordsTable.key::INTEGER"))) ->where("$keywordsTable.type_key", 'weeks-per-year') ->latest("$riceShrimpWeeklyPricesTable.year") ->latest(DB::raw("$keywordsTable.key::INTEGER")) diff --git a/app/Http/Controllers/DeviceController.php b/app/Http/Controllers/DeviceController.php index 216665f..ee54770 100644 --- a/app/Http/Controllers/DeviceController.php +++ b/app/Http/Controllers/DeviceController.php @@ -4,29 +4,35 @@ namespace App\Http\Controllers; use App\Enums\DeviceStatus; use App\Enums\DeviceType; +use App\Enums\OperationType; use App\Helpers\Paginator; use App\Http\Requestes\DeviceRequest; use App\Http\Resources\DeviceResource; -use App\Models\Device; -use App\Models\MeteorologicalMonitoringLog; -use App\Models\MeteorologicalMonitoringDailyLog; -use App\Models\SoilMonitoringLog; -use App\Models\SoilMonitoringDailyLog; -use App\Models\WaterQualityMonitoringLog; -use App\Models\WaterQualityMonitoringDailyLog; use App\Models\AgriculturalBase; -use Illuminate\Http\Request; -use Illuminate\Support\Facades\DB; -use Carbon\Carbon; +use App\Models\Device; +use App\Models\InsecticidalLampDailyReport; +use App\Models\InsecticidalLampReport; +use App\Models\MeteorologicalMonitoringDailyLog; +use App\Models\MeteorologicalMonitoringLog; +use App\Models\SoilMonitoringDailyLog; +use App\Models\SoilMonitoringLog; +use App\Models\WaterQualityMonitoringDailyLog; +use App\Models\WaterQualityMonitoringLog; +use App\Models\WormPhoto; +use App\Models\WormReport; +use App\Services\BiAngDeviceService; use App\Services\OperationLogService; -use App\Enums\OperationType; +use Illuminate\Http\Request; +use Illuminate\Support\Carbon; +use Illuminate\Support\Facades\DB; +use Illuminate\Validation\ValidationException; use Peidikeji\Setting\Models\Setting; class DeviceController extends Controller { public function index(Request $request) { - $query = Device::with('base')->filter($request->input())->orderBy('sort', 'desc'); + $query = Device::with(['base', 'supplier', 'project'])->filter($request->input())->orderBy('sort', 'desc'); $list = $query->paginate(Paginator::resolvePerPage('per_page', 20, 50)); return $this->json(DeviceResource::collection($list)); @@ -51,6 +57,7 @@ class DeviceController extends Controller public function show(Device $device) { + $device->loadMissing(['base', 'supplier', 'project']); return $this->json(DeviceResource::make($device)); } @@ -414,48 +421,46 @@ class DeviceController extends Controller } } - /** - * 查询设备今天(按天),近一周(按天),近一个月(按天) - */ - public function timeZoneList(Request $request){ - $deviceId = $request->input('device_id'); - //不传开始时间,结束时间,则默认是查当天(按小时) - $startTime = $request->input('start_time'); - $endTime = $request->input('end_time'); - $diffDays = 0; - $day = date('Y-m-d'); - //如果传了开始时间和结束时间,计算中间天数 - if($startTime && $endTime){ - if($startTime == $endTime){//查询某一天 - $day = $startTime; - }else{ - $startDay = Carbon::parse($startTime); - $endDay = Carbon::parse($endTime); - $diffDays = $startDay->diffInDays($endDay, false); + public function timeZoneList(Request $request) + { + $request->validate([ + 'device_id' => ['bail', 'required'], + 'start_time' => ['bail', 'nullable', 'date_format:Y-m-d'], + 'end_time' => ['bail', 'nullable', 'date_format:Y-m-d'], + ]); + + $isSameDay = true; + + if ($request->filled('start_time') && $request->filled('end_time')) { + $startTime = Carbon::parse($request->input('start_time'))->startOfDay(); + $endTime = Carbon::parse($request->input('end_time'))->startOfDay(); + + if ($startTime->gt($endTime)) { + throw ValidationException::withMessages([ + 'start_time' => ['开始时间不能大于结束时间'], + ]); } + + // 如果开始时间和结束时间是同一天 + if ($startTime->eq($endTime)) { + $endTime = $startTime->isToday() ? now() : $startTime->copy()->endOfDay(); + } else { + $isSameDay = false; + } + } else { + $endTime = now(); + $startTime = $endTime->copy()->startOfDay(); } - $xKeys = []; - if($diffDays){ - for ($i = 0; $i<=$diffDays; $i++) { - $xKeys[] =(clone $startDay)->addDays($i)->startOfDay()->format('Y-m-d H:i:s'); - } - }else{ - //调整截至到当前小时 - $h = 23; - if($day == date('Y-m-d')){ - $h = date('H'); - } - for ($i = 0; $i < ($h+1); $i++) { - $xKeys[] = $day.' '.str_pad($i, 2, '0', STR_PAD_LEFT).':00:00'; - } - } - $device = Device::find($deviceId); - $modelQuery = null; - $getArr = []; + $device = Device::findOrFail($request->input('device_id')); + + $fields = []; + $monitoringLogs = collect(); + switch ($device->type) { - case DeviceType::Meteorological://气象设备 - $getArr = [ + // 气象设备 + case DeviceType::Meteorological: + $fields = [ 'wind_speed', 'wind_direction', 'wind_degree', @@ -467,17 +472,34 @@ class DeviceController extends Controller 'illumination', 'pm25', 'pm10', + 'rainfall', ]; - if($diffDays) { - $getArr[] = 'daily_rainfall'; - $modelQuery = MeteorologicalMonitoringDailyLog::query()->whereBetween('monitored_at', [$startTime, $endTime]); - }else{ - $getArr[] = 'current_rainfall'; - $modelQuery = MeteorologicalMonitoringLog::query()->whereDate('monitored_at', $day); - } + /** @var \Illuminate\Support\Collection */ + $monitoringLogs = ( + $isSameDay + ? MeteorologicalMonitoringLog::query() + : MeteorologicalMonitoringDailyLog::query() + ) + ->where('device_id', $device->id) + ->whereBetween('monitored_at', [$startTime, $endTime]) + ->get() + ->mapWithKeys(function ($item) use ($fields) { + $key = $item->monitored_at->toDateTimeString(); + $data = collect($fields)->mapWithKeys(fn ($field) => [$field => $field !== 'rainfall' ? $item[$field] : null])->all(); + + if ($item instanceof MeteorologicalMonitoringDailyLog) { + $data['rainfall'] = $item->daily_rainfall; + } else { + $data['rainfall'] = $item->current_rainfall; + } + + return [$key => $data]; + }); break; - case DeviceType::Soil://土壤设备 - $getArr = [ + + // 土壤设备 + case DeviceType::Soil: + $fields = [ 'conductivity', 'humidity', 'temperature', @@ -485,14 +507,25 @@ class DeviceController extends Controller 'p', 'k', ]; - if($diffDays) { - $modelQuery = SoilMonitoringDailyLog::query()->whereBetween('monitored_at', [$startTime, $endTime]); - }else{ - $modelQuery = SoilMonitoringLog::query()->whereDate('monitored_at', $day); - } + /** @var \Illuminate\Support\Collection */ + $monitoringLogs = ( + $isSameDay + ? SoilMonitoringLog::query() + : SoilMonitoringDailyLog::query() + ) + ->where('device_id', $device->id) + ->whereBetween('monitored_at', [$startTime, $endTime]) + ->get() + ->mapWithKeys(function ($item) use ($fields) { + $key = $item->monitored_at->toDateTimeString(); + $data = collect($fields)->mapWithKeys(fn ($field) => [$field => $item[$field]])->all(); + return [$key => $data]; + }); break; - case DeviceType::WaterQuality://水质设备 - $getArr = [ + + // 水质设备 + case DeviceType::WaterQuality: + $fields = [ 'chlorine', 'conductivity', 'oxygen', @@ -500,62 +533,96 @@ class DeviceController extends Controller 'temperature', 'turbidity', ]; - if($diffDays) { - $modelQuery = WaterQualityMonitoringDailyLog::query()->whereBetween('monitored_at', [$startTime, $endTime]); - }else{ - $modelQuery = WaterQualityMonitoringLog::query()->whereDate('monitored_at', $day); - } + /** @var \Illuminate\Support\Collection */ + $monitoringLogs = ( + $isSameDay + ? WaterQualityMonitoringLog::query() + : WaterQualityMonitoringDailyLog::query() + ) + ->where('device_id', $device->id) + ->whereBetween('monitored_at', [$startTime, $endTime]) + ->get() + ->mapWithKeys(function ($item) use ($fields) { + $key = $item->monitored_at->toDateTimeString(); + $data = collect($fields)->mapWithKeys(fn ($field) => [$field => $item[$field]])->all(); + return [$key => $data]; + }); + break; + + // 杀虫灯 + case DeviceType::InsecticidalLamp: + $fields = [ + 'battery_vol', + 'killed_num', + 'air_temperature', + 'air_humidity', + 'charging_vol', + 'high_vol', + ]; + /** @var \Illuminate\Support\Collection */ + $monitoringLogs = ( + $isSameDay + ? InsecticidalLampReport::query() + : InsecticidalLampDailyReport::query() + ) + ->where('device_id', $device->id) + ->whereBetween('reported_at', [$startTime, $endTime]) + ->get() + ->mapWithKeys(function ($item) use ($fields) { + $key = $item->reported_at->toDateTimeString(); + $data = collect($fields)->mapWithKeys(fn ($field) => [$field => $item[$field]])->all(); + return [$key => $data]; + }); break; } - if($modelQuery){ - $datalist = $modelQuery->where('device_id', $deviceId)->get()->keyBy('monitored_at')->toArray(); - } - $data = []; - foreach ($getArr as $column){ - $data[$column] = []; - $_value = null; - foreach($xKeys as $key){ - if($device->type == DeviceType::WaterQuality){//如果是水质设备,则写死假数据 - switch($column){ - case 'chlorine': - $data[$column][$key] = 0.016; - break; - case 'conductivity': - $data[$column][$key] = 563 ;//电导率 - break; - case 'oxygen': - $data[$column][$key] = 0.09;//含氧量 - break; - case 'ph': - $data[$column][$key] = rand(750, 770) / 100; - break; - case 'temperature': - $data[$column][$key] = rand(2400, 2600) / 100; - break; - case 'turbidity': - $data[$column][$key] = 0.33; - break; + + return $this->json( + collect($fields)->mapWithKeys(function ($field) use ($device, $monitoringLogs, $isSameDay, $startTime, $endTime) { + $data = []; + + $beginTime = $startTime->copy(); + + do { + $key = $beginTime->toDateTimeString(); + + $monitoringLog = $monitoringLogs->get($key); + + if (is_null($monitoringLog)) { + // 如果是水质设备,则写死假数据 + if($device->type == DeviceType::WaterQuality){ + switch($field){ + case 'chlorine': + $data[$key] = 0.016; + break; + case 'conductivity': + $data[$key] = rand(560, 565);//电导率 + break; + case 'oxygen': + $data[$key] = 0.09;//含氧量 + break; + case 'ph': + $data[$key] = rand(750, 770) / 100; + break; + case 'temperature': + $data[$key] = rand(2400, 2600) / 100; + break; + case 'turbidity': + $data[$key] = 0.33; + break; + } + } else { + $data[$key] = null; + } + } else { + $data[$key] = $monitoringLog[$field]; } - }else{ - // if($datalist[$key][$column] ?? null){//如果存在数据则暂存该值 - // $_value = $datalist[$key][$column]; - // } - // //判断是否超过离线时间; - // if(true){//未超过, 判断和设备离线时间关系-todo - // $data[$column][$key] = $_value; - // }else{ - $data[$column][$key] = $datalist[$key][$column] ?? null; - // } - } - } - } - //强制统一气象降雨量,日和天字段不统一问题 - if(isset($data['daily_rainfall'])) { - $data['rainfall'] = $data['daily_rainfall']; - }elseif(isset($data['current_rainfall'])){ - $data['rainfall'] = $data['current_rainfall']; - } - return $this->json($data); + + $isSameDay ? $beginTime->addHours(1) : $beginTime->addDays(1); + } while ($beginTime->lte($endTime)); + + return [$field => $data]; + }) + ); } public function getFfmpegServiceIp(){ @@ -571,4 +638,116 @@ class DeviceController extends Controller return $this->json($data); } + + /** + * 虫情统计 + */ + public function wormStatics($id, Request $request) + { + $request->validate([ + 'start_time' => ['bail', 'required_with:end_time', 'date_format:Y-m-d'], + 'end_time' => ['bail', 'required_with:start_time', 'date_format:Y-m-d'], + ], [], [ + 'start_time' => '开始时间', + 'end_time' => '结束时间', + ]); + + // 结束时间 + $endTime = $request->whenFilled( + 'end_time', + fn ($time) => Carbon::parse($time)->startOfDay(), + fn () => now()->startOfDay(), + ); + // 开始时间 + $startTime = $request->whenFilled( + 'start_time', + fn ($time) => Carbon::parse($time)->startOfDay(), + fn () => $endTime->copy()->subDays(6), + ); + + if ($startTime->gt($endTime)) { + throw ValidationException::withMessages([ + 'start_time' => ['开始时间不能大于结束时间'], + ]); + } + + $device = Device::findOrFail($id); + + $wormReports = WormReport::where('device_id', $device->id) + ->whereBetween('reported_at', [$startTime->toDateString(), $endTime->toDateString()]) + ->pluck('worm_num', 'reported_at'); + + $data = []; + + do { + $key = $startTime->toDateString(); + $data[$key] = $wormReports->get($key); + $startTime->addDay(); + } while ($startTime->lte($endTime)); + + return $this->json($data); + } + + /** + * 虫情图片 + */ + public function wormPhotos($id, Request $request, BiAngDeviceService $biAngDeviceService) + { + $request->validate([ + 'start_time' => ['bail', 'required_with:end_time', 'date_format:Y-m-d'], + 'end_time' => ['bail', 'required_with:start_time', 'date_format:Y-m-d'], + ], [], [ + 'start_time' => '开始时间', + 'end_time' => '结束时间', + ]); + + // 结束时间 + $endTime = $request->whenFilled( + 'end_time', + fn ($time) => Carbon::parse($time)->endOfDay(), + fn () => now()->endOfDay(), + ); + // 开始时间 + $startTime = $request->whenFilled( + 'start_time', + fn ($time) => Carbon::parse($time)->startOfDay(), + fn () => $endTime->copy()->startOfDay(), + ); + + if ($startTime->gt($endTime)) { + throw ValidationException::withMessages([ + 'start_time' => ['开始时间不能大于结束时间'], + ]); + } + + $device = Device::findOrFail($id); + + $data = []; + + switch ($device->supplier_key) { + case 'device-supplier-biang': + $result = $biAngDeviceService->getWormPhotos( + $device, + Carbon::parse($request->input('start_time')), + Carbon::parse($request->input('end_time')), + ); + $data = $result['imgUrl']; + break; + + default: + $wormPhotos = WormPhoto::where('device_id', $device->id) + ->whereBetween('uploaded_at', [$startTime, $endTime]) + ->latest('uploaded_at') + ->get(); + + $data = $wormPhotos->map(fn ($item) => [ + 'id' => $item->id, + 'url' => $item->url, + 'time' => $item->created_at->toDateTimeString(), + ]); + break; + } + + return $this->json($data); + } } diff --git a/app/Http/Controllers/WeeksPerYearController.php b/app/Http/Controllers/WeeksPerYearController.php index 5a3baa6..dba7b0a 100644 --- a/app/Http/Controllers/WeeksPerYearController.php +++ b/app/Http/Controllers/WeeksPerYearController.php @@ -10,6 +10,8 @@ class WeeksPerYearController extends Controller { public function __invoke(Request $request) { - return Keywords::select('name', 'id')->where('type_key', 'weeks-per-year')->get(); + $keywords = Keywords::where('type_key', 'weeks-per-year')->pluck('name', 'key'); + + return $keywords->map(fn ($name, $key) => ['id' => $key, 'name' => $name])->values(); } } diff --git a/app/Http/Requestes/DeviceRequest.php b/app/Http/Requestes/DeviceRequest.php index 4c08d4b..ee83de8 100644 --- a/app/Http/Requestes/DeviceRequest.php +++ b/app/Http/Requestes/DeviceRequest.php @@ -22,6 +22,7 @@ class DeviceRequest extends FormRequest 'agricultural_base_id' => 'required|integer|min:0', 'sn' => 'required|string|max:64', 'monitoring_point' => 'required|string|max:100', + 'supplier_key' => 'required', 'extends' => 'required_if:type,1', 'extends.ip' => 'required_if:type,1|string', 'extends.port' => 'required_if:type,1|string', @@ -34,6 +35,13 @@ class DeviceRequest extends FormRequest ]; } + public function attributes() + { + return [ + 'supplier_key' => '设备厂商', + ]; + } + public function messages() { $messages = [ diff --git a/app/Http/Requestes/RiceShrimpWeeklyPriceStoreRequest.php b/app/Http/Requestes/RiceShrimpWeeklyPriceStoreRequest.php index b0d8102..283803d 100644 --- a/app/Http/Requestes/RiceShrimpWeeklyPriceStoreRequest.php +++ b/app/Http/Requestes/RiceShrimpWeeklyPriceStoreRequest.php @@ -22,7 +22,7 @@ class RiceShrimpWeeklyPriceStoreRequest extends FormRequest 'week' => [ 'required', 'int', - Rule::exists(Keywords::class, 'id')->where(function ($query) { + Rule::exists(Keywords::class, 'key')->where(function ($query) { return $query->where('type_key', 'weeks-per-year'); }), ], diff --git a/app/Http/Requestes/RiceShrimpWeeklyPriceUpdateRequest.php b/app/Http/Requestes/RiceShrimpWeeklyPriceUpdateRequest.php index cd9d790..c7eb407 100644 --- a/app/Http/Requestes/RiceShrimpWeeklyPriceUpdateRequest.php +++ b/app/Http/Requestes/RiceShrimpWeeklyPriceUpdateRequest.php @@ -20,7 +20,7 @@ class RiceShrimpWeeklyPriceUpdateRequest extends FormRequest 'week' => [ 'filled', 'int', - Rule::exists(Keywords::class, 'id')->where(function ($query) { + Rule::exists(Keywords::class, 'key')->where(function ($query) { return $query->where('type_key', 'weeks-per-year'); }), ], diff --git a/app/Http/Resources/DeviceResource.php b/app/Http/Resources/DeviceResource.php index 953ab5b..e626942 100644 --- a/app/Http/Resources/DeviceResource.php +++ b/app/Http/Resources/DeviceResource.php @@ -31,6 +31,18 @@ class DeviceResource extends JsonResource 'created_at' => strtotime($this->created_at) ?? 0, //录入时间 'is_recommend' => $this->is_recommend, 'sort' => $this->sort ?? 0, + 'supplier' => $this->whenLoaded('supplier', function () { + return $this->supplier ? [ + 'id' => $this->supplier->key, + 'name' => $this->supplier->name, + ] : null; + }), + 'project' => $this->whenLoaded('project', function () { + return $this->project ? [ + 'id' => $this->project->key, + 'name' => $this->project->name, + ] : null; + }), ]; } } diff --git a/app/Iot/BiAng/HttpClient.php b/app/Iot/BiAng/HttpClient.php new file mode 100644 index 0000000..e91bdb3 --- /dev/null +++ b/app/Iot/BiAng/HttpClient.php @@ -0,0 +1,167 @@ +get( + $this->apiUrl('/api/open-api/open/soilMoisture/getCurrentDeviceData'), + [ + 'deviceId' => $deviceId, + ] + ); + + return $result['data']; + } + + /** + * 获取最新的气象数据 + */ + public function getLatestMeteorologicalReport(string $deviceId) + { + $result = $this->get( + $this->apiUrl('/api/open-api/open/weather/getCurrentDeviceData'), + [ + 'deviceId' => $deviceId, + ] + ); + + return $result['data']; + } + + /** + * 获取最新的杀虫灯数据 + */ + public function getLatestLampReport(string $deviceId) + { + $result = $this->get( + $this->apiUrl2('/open-api/open/getCurrentLampData'), + [ + 'deviceId' => $deviceId, + ] + ); + + return $result['data']; + } + + /** + * 虫情设备/昆虫性诱设备 - 查询某个时间段内的图片 + */ + public function getWormPhotos(string $deviceId, Carbon $start, Carbon $end) + { + $result = $this->get( + $this->apiUrl('/api/open-api/open/getDevicePhotos'), + [ + 'deviceId' => $deviceId, + 'startTime' => $start->toDateString(), + 'endTime' => $end->toDateString(), + ] + ); + + return $result['data']; + } + + /** + * 虫情设备 - (识别款)图片虫数识别统计 + */ + public function getWormStatistics(string $deviceId, Carbon $start, Carbon $end) + { + $result = $this->get( + $this->apiUrl('/api/open-api/open/getAllStatistics'), + [ + 'imei' => $deviceId, + 'startAt' => $start->toDateString(), + 'endAt' => $end->toDateString(), + ] + ); + + return $result['data']; + } + + public function get(string $url, array $query = []): array + { + return $this->request('GET', $url, [ + 'query' => $query, + ]); + } + + /** + * @param string $url + * @param array $data + * @return array + */ + public function post(string $url, array $data = []): array + { + return $this->request('POST', $url, [ + 'json' => $data, + ]); + } + + /** + * @param string $method + * @param string $url + * @param array $options + * @return array + * + * @throws \Illuminate\Http\Client\RequestException + * @throws \RuntimeException + */ + public function request(string $method, string $url, array $options = []): array + { + switch (strtoupper($method)) { + case 'GET': + $options['query'] = array_merge($options['query'], [ + 'username' => $this->username, + 'password' => $this->password, + ]); + break; + + case 'POST': + $options['json'] = array_merge($options['json'], [ + 'username' => $this->username, + 'password' => $this->password, + ]); + break; + } + + /** @var \Illuminate\Http\Client\Response */ + $response = Http::withHeaders([ + 'Content-Type' => 'application/json', + ])->send($method, $url, $options); + + $result = $response->throw()->json(); + + if (data_get($result, 'code') === 200) { + return $result; + } + + throw new BiAngException($result['code'].':'.($result['msg']??'出错啦!'), 500); + } + + protected function apiUrl(string $path): string + { + return 'http://yun.bigdata5s.com'.Str::start($path, '/'); + } + + protected function apiUrl2(string $path): string + { + return 'http://yun-api.bigdata5s.com'.Str::start($path, '/'); + } +} diff --git a/app/Iot/Linkos/HttpClient.php b/app/Iot/Linkos/HttpClient.php new file mode 100644 index 0000000..7900a4e --- /dev/null +++ b/app/Iot/Linkos/HttpClient.php @@ -0,0 +1,160 @@ +post('/api/deviceFlow/v1/list', [ + 'device_id' => $deviceId, + 'start_time' => $start->unix() * 1000, + 'end_time' => $end->unix() * 1000, + 'pageable' => [ + 'page' => $page - 1, + 'size' => $perPage, + ], + ]); + + if (data_get($result, 'success') !== true) { + throw new RuntimeException(data_get($result, 'msg', '出错啦!')); + } + + return $result['data']; + } + + /** + * 设备数据下行 + * + * @param string $deviceId + * @param string $service + * @param array $data + * @param boolean $confirm + * @param boolean $clear + * @param boolean $schedule + * @return array + */ + public function deviceDataDownlink(string $deviceId, string $service, array $data = [], bool $confirm = true, bool $clear = true, bool $schedule = false): array + { + return $this->post('/api/down', [ + 'device_id' => $deviceId, + 'service_id' => $service, + 'parameter' => $data, + 'clear' => (int) $clear, + 'schedule' => (int) $schedule, + 'confirm' => (int) $confirm, + ]); + } + + /** + * 获取设备最新属性数据 + */ + public function getDeviceStatus(string $deviceId, array $props): array + { + $result = $this->get('/api/deviceStatus/v1/getDeviceStatus', [ + 'deviceCode' => $deviceId, + 'prop' => implode(",", $props), + ]); + + if (data_get($result, 'success') !== true) { + throw new RuntimeException(data_get($result, 'msg', '出错啦!')); + } + + return $result['data']; + } + + public function get(string $url, array $query = []): array + { + return $this->request('GET', $url, [ + 'query' => $query, + ]); + } + + /** + * @param string $url + * @param array $data + * @return array + */ + public function post(string $url, array $data = []): array + { + return $this->request('POST', $url, [ + 'json' => $data, + ]); + } + + /** + * @param string $method + * @param string $url + * @param array $options + * @return array + * + * @throws \Illuminate\Http\Client\RequestException + * @throws \RuntimeException + */ + public function request(string $method, string $url, array $options = []): array + { + $nonce = $this->nonce(); + + $timestamp = now()->getTimestampMs(); + + /** @var \Illuminate\Http\Client\Response */ + $response = Http::withHeaders([ + 'Content-Type' => 'application/json', + 'api-key' => $this->apiKey, + 'Nonce' => $nonce, + 'Timestamp' => $timestamp, + 'Signature' => $this->sign(compact('nonce', 'timestamp')), + ])->baseUrl(self::ENDPOINT_URL)->send($method, $url, $options); + + return $response->throw()->json(); + } + + /** + * @param array $data + * @return string + */ + protected function sign(array $data): string + { + return sha1( + sprintf( + '%s%s%s', + $data['nonce'] ?? '', + $data['timestamp'] ?? '', + $this->apiSecret + ) + ); + } + + protected function nonce(): string + { + $nonce = ''; + + for ($i = 0; $i < 8; $i++) { + $nonce .= mt_rand(0, 9); + } + + return $nonce; + } +} diff --git a/app/ModelFilters/DeviceFilter.php b/app/ModelFilters/DeviceFilter.php index a72996b..c825e11 100644 --- a/app/ModelFilters/DeviceFilter.php +++ b/app/ModelFilters/DeviceFilter.php @@ -6,6 +6,11 @@ use EloquentFilter\ModelFilter; class DeviceFilter extends ModelFilter { + public function sn($sn) + { + return $this->where('sn', $sn); + } + public function point($point) { return $this->where('monitoring_point', 'like', $point.'%'); @@ -26,6 +31,16 @@ class DeviceFilter extends ModelFilter return $this->where('status', $status); } + public function supplierKey($supplierKey) + { + return $this->where('supplier_key', $supplierKey); + } + + public function projectKey($projectKey) + { + return $this->where('project_key', $projectKey); + } + public function isRecommend($isRecommend){ return $this->where('is_recommend', $isRecommend); } diff --git a/app/Models/Device.php b/app/Models/Device.php index 3e6b0cd..f9e699a 100644 --- a/app/Models/Device.php +++ b/app/Models/Device.php @@ -2,13 +2,15 @@ namespace App\Models; -use App\Enums\DeviceType; use App\Enums\DeviceStatus; -use EloquentFilter\Filterable; -use Illuminate\Database\Eloquent\Model; +use App\Enums\DeviceType; use Dcat\Admin\Traits\HasDateTimeFormatter; -use Illuminate\Database\Eloquent\Relations\BelongsTo; +use EloquentFilter\Filterable; +use Illuminate\Contracts\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Peidikeji\Keywords\Models\Keywords; class Device extends Model { @@ -37,8 +39,15 @@ class Device extends Model 'created_by', 'updated_by', 'sort', + 'supplier_key', + 'project_key', ]; + public function scopeSupplierBy(Builder $query, string $supplier): void + { + $query->whereHas('supplier', fn ($query) => $query->where('supplier_key', $supplier)); + } + public function base() { return $this->belongsTo(AgriculturalBase::class, 'agricultural_base_id'); @@ -53,4 +62,24 @@ class Device extends Model { return $this->belongsTo(AdminUser::class, 'updated_by'); } + + public function supplier(): BelongsTo + { + return $this->belongsTo(Keywords::class, 'supplier_key', 'key'); + } + + public function project(): BelongsTo + { + return $this->belongsTo(Keywords::class, 'project_key', 'key'); + } + + public function isTypeSoil(): bool + { + return $this->type === DeviceType::Soil; + } + + public function isTypeMeteorological(): bool + { + return $this->type === DeviceType::Meteorological; + } } diff --git a/app/Models/DeviceLog.php b/app/Models/DeviceLog.php new file mode 100644 index 0000000..fff5f0d --- /dev/null +++ b/app/Models/DeviceLog.php @@ -0,0 +1,22 @@ + 'json', + 'reported_at' => 'datetime', + ]; + + protected $fillable = [ + 'device_id', + 'data', + 'reported_at', + ]; +} diff --git a/app/Models/InsecticidalLampDailyReport.php b/app/Models/InsecticidalLampDailyReport.php new file mode 100644 index 0000000..65169f5 --- /dev/null +++ b/app/Models/InsecticidalLampDailyReport.php @@ -0,0 +1,27 @@ + 'date', + ]; + + protected $fillable = [ + 'device_id', + 'agricultural_base_id', + 'battery_vol', + 'killed_num', + 'air_temperature', + 'air_humidity', + 'charging_vol', + 'high_vol', + 'reported_at', + ]; +} diff --git a/app/Models/InsecticidalLampReport.php b/app/Models/InsecticidalLampReport.php new file mode 100644 index 0000000..5e0a513 --- /dev/null +++ b/app/Models/InsecticidalLampReport.php @@ -0,0 +1,27 @@ + 'datetime', + ]; + + protected $fillable = [ + 'device_id', + 'agricultural_base_id', + 'battery_vol', + 'killed_num', + 'air_temperature', + 'air_humidity', + 'charging_vol', + 'high_vol', + 'reported_at', + ]; +} diff --git a/app/Models/MeteorologicalMonitoringDailyLog.php b/app/Models/MeteorologicalMonitoringDailyLog.php index bff3072..793f315 100644 --- a/app/Models/MeteorologicalMonitoringDailyLog.php +++ b/app/Models/MeteorologicalMonitoringDailyLog.php @@ -10,6 +10,15 @@ class MeteorologicalMonitoringDailyLog extends Model { use HasFactory; + const WIND_DIRECTION_NORTH = 0; + const WIND_DIRECTION_NORTHEAST = 1; + const WIND_DIRECTION_EAST = 2; + const WIND_DIRECTION_SOUTHEAST = 3; + const WIND_DIRECTION_SOUTH = 4; + const WIND_DIRECTION_SOUTHWEST = 5; + const WIND_DIRECTION_WEST = 6; + const WIND_DIRECTION_NORTHWEST = 7; + protected $casts = [ 'wind_direction' => WindDirection::class, 'monitored_at' => 'date', diff --git a/app/Models/MeteorologicalMonitoringLog.php b/app/Models/MeteorologicalMonitoringLog.php index 620bd88..6387877 100644 --- a/app/Models/MeteorologicalMonitoringLog.php +++ b/app/Models/MeteorologicalMonitoringLog.php @@ -10,6 +10,15 @@ class MeteorologicalMonitoringLog extends Model { use HasFactory; + const WIND_DIRECTION_NORTH = 0; + const WIND_DIRECTION_NORTHEAST = 1; + const WIND_DIRECTION_EAST = 2; + const WIND_DIRECTION_SOUTHEAST = 3; + const WIND_DIRECTION_SOUTH = 4; + const WIND_DIRECTION_SOUTHWEST = 5; + const WIND_DIRECTION_WEST = 6; + const WIND_DIRECTION_NORTHWEST = 7; + protected $casts = [ 'wind_direction' => WindDirection::class, 'monitored_at' => 'datetime', diff --git a/app/Models/RiceShrimpWeeklyPrice.php b/app/Models/RiceShrimpWeeklyPrice.php index 75174ae..ba2c11c 100644 --- a/app/Models/RiceShrimpWeeklyPrice.php +++ b/app/Models/RiceShrimpWeeklyPrice.php @@ -17,7 +17,7 @@ class RiceShrimpWeeklyPrice extends Model public function weekObj() { - return $this->belongsTo(Keywords::class, 'week'); + return $this->belongsTo(Keywords::class, 'week', 'key'); } public function createdBy() diff --git a/app/Models/WormPhoto.php b/app/Models/WormPhoto.php new file mode 100644 index 0000000..2499bd8 --- /dev/null +++ b/app/Models/WormPhoto.php @@ -0,0 +1,19 @@ + 'datetime', + ]; + + protected $fillable = [ + 'device_id', 'url', 'uploaded_at', + ]; +} diff --git a/app/Models/WormReport.php b/app/Models/WormReport.php new file mode 100644 index 0000000..14cca95 --- /dev/null +++ b/app/Models/WormReport.php @@ -0,0 +1,20 @@ + 'json', + 'reported_at' => 'date', + ]; + + protected $fillable = [ + 'device_id', 'agricultural_base_id', 'worm_num', 'data', 'reported_at', + ]; +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index ab2bcb9..d4d5477 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -6,6 +6,7 @@ use App\Services\LinkosService; use Illuminate\Support\Facades\Schema; use Illuminate\Support\ServiceProvider; use Illuminate\Database\Eloquent\Relations\Relation; +use App\Iot\Linkos\HttpClient as LinkosHttpClient; class AppServiceProvider extends ServiceProvider { @@ -22,6 +23,8 @@ class AppServiceProvider extends ServiceProvider return new LinkosService($config['key'] ?? '', $config['secret'] ?? ''); }); + + $this->registerLinkos(); } /** @@ -48,4 +51,14 @@ class AppServiceProvider extends ServiceProvider // ]); } + + protected function registerLinkos(): void + { + $this->app->singleton(LinkosHttpClient::class, function ($app) { + return new LinkosHttpClient( + (string) $app['config']->get('services.linkos.key'), + (string) $app['config']->get('services.linkos.secret') + ); + }); + } } diff --git a/app/Services/BiAngDeviceService.php b/app/Services/BiAngDeviceService.php new file mode 100644 index 0000000..d536d54 --- /dev/null +++ b/app/Services/BiAngDeviceService.php @@ -0,0 +1,604 @@ +buildHttpClient($device); + } catch (BizException $e) { + return []; + } + + return $httpClient->getWormPhotos($device->sn, $start, $end); + } + + /** + * 虫情设备 - (识别款)图片虫数识别统计 + */ + public function getWormStatistics(Device $device, Carbon $start, Carbon $end): array + { + try { + $httpClient = $this->buildHttpClient($device); + } catch (BizException $e) { + return []; + } + + return $httpClient->getWormStatistics($device->sn, $start, $end); + } + + /** + * 创建设备报告 + */ + public function createReport(Device $device, Carbon $time): void + { + switch ($device->type) { + case DeviceType::Soil: + $this->createSoilReport($device, $time); + break; + + case DeviceType::Meteorological: + $this->createMeteorologicalReport($device, $time); + break; + + case DeviceType::InsecticidalLamp: + $this->createInsecticidalLampReport($device, $time); + break; + } + } + + /** + * 创建土壤设备报告 + */ + protected function createSoilReport(Device $device, Carbon $time): void + { + $reportedAt = $time->copy()->startOfHour(); + + /** @var \Illuminate\Database\Eloquent\Collection */ + $logs = DeviceLog::where('device_id', $device->id) + ->whereBetween('reported_at', [$reportedAt, $reportedAt->copy()->endOfHour()]) + ->oldest('reported_at') + ->get(); + + if ($logs->isEmpty()) { + return; + } + + $attributes = $logs->reduce(function (array $attributes, DeviceLog $log) { + if (is_array($data = $log->data)) { + foreach ($data as $k => $v) { + $attribute = match ($k) { + 'soilAlkalineHydrolyzedNitrogen' => 'n', + 'soilAvailablePotassium' => 'k', + 'soilAvailablePhosphorus' => 'p', + 'soilConductivity' => 'conductivity', + 'soilTemperature' => 'temperature', + 'soilMoisture' => 'humidity', + default => null, + }; + + if ($attribute) { + $attributes[$attribute] = $v; + } + } + } + + return $attributes; + }, []); + + $soilReport = SoilMonitoringLog::where([ + 'device_id' => $device->id, + 'monitored_at' => $reportedAt, + ])->first(); + + if ($soilReport === null) { + $lastSoilReport = SoilMonitoringLog::where([ + 'device_id' => $device->id, + 'monitored_at' => $reportedAt->copy()->subHour(), + ])->first(); + + $soilReport = $lastSoilReport?->replicate() ?: new SoilMonitoringLog(); + + $soilReport->fill([ + 'device_id' => $device->id, + 'monitored_at' => $reportedAt, + 'agricultural_base_id' => $device->agricultural_base_id, + ]); + } + + $soilReport->fill($attributes)->save(); + } + + /** + * 创建气象设备报告 + */ + protected function createMeteorologicalReport(Device $device, Carbon $time): void + { + $reportedAt = $time->copy()->startOfHour(); + + /** @var \Illuminate\Database\Eloquent\Collection */ + $logs = DeviceLog::where('device_id', $device->id) + ->whereBetween('reported_at', [$reportedAt, $reportedAt->copy()->endOfHour()]) + ->oldest('reported_at') + ->get(); + + if ($logs->isEmpty()) { + return; + } + + $attributes = $logs->reduce(function (array $attributes, DeviceLog $log) { + if (is_array($data = $log->data)) { + foreach ($data as $k => $v) { + $attribute = match ($k) { + 'rainfall' => 'moment_rainfall', //瞬时降雨量 + 'lightIntensity' => 'illumination', + 'airTemperature' => 'air_temperature', + 'airHumidity' => 'air_humidity', + 'windDirection' => 'wind_degree', + 'windSpeed' => 'wind_speed', + default => null, + }; + + if ($attribute) { + switch ($attribute) { + case 'moment_rainfall': + $attributes[$attribute] = bcadd($attributes[$attribute] ?? '0.00', $v, 2); + break; + + case 'wind_degree': + $attributes['wind_direction'] = value(function ($v) { + if ($v >= 22.5 && $v < 67.5) { + return MeteorologicalMonitoringLog::WIND_DIRECTION_NORTHEAST; + } elseif ($v >= 67.5 && $v < 112.5) { + return MeteorologicalMonitoringLog::WIND_DIRECTION_EAST; + } elseif ($v >= 112.5 && $v < 157.5) { + return MeteorologicalMonitoringLog::WIND_DIRECTION_SOUTHEAST; + } elseif ($v >= 157.5 && $v < 202.5) { + return MeteorologicalMonitoringLog::WIND_DIRECTION_SOUTH; + } elseif ($v >= 202.5 && $v < 247.5) { + return MeteorologicalMonitoringLog::WIND_DIRECTION_SOUTHWEST; + } elseif ($v >= 247.5 && $v < 292.5) { + return MeteorologicalMonitoringLog::WIND_DIRECTION_WEST; + } elseif ($v >= 292.5 && $v < 337.5) { + return MeteorologicalMonitoringLog::WIND_DIRECTION_NORTHWEST; + } + return MeteorologicalMonitoringLog::WIND_DIRECTION_NORTH; + }, $v); + default: + $attributes[$attribute] = $v; + break; + } + } + } + } + + return $attributes; + }, []); + + # 累计雨量求和 + $attributes['current_rainfall'] = DeviceLog::select(DB::raw("SUM((data->>'rainfall')::NUMERIC) as aggregate"))->where('device_id', $device->id) + ->whereBetween('reported_at', [$reportedAt->copy()->startOfDay(), $reportedAt->copy()->endOfHour()]) + ->value('aggregate') ?: 0; + + $meteorologicalReport = MeteorologicalMonitoringLog::where([ + 'device_id' => $device->id, + 'monitored_at' => $reportedAt, + ])->first(); + + if ($meteorologicalReport === null) { + $lastMeteorologicalReport = MeteorologicalMonitoringLog::where([ + 'device_id' => $device->id, + 'monitored_at' => $reportedAt->copy()->subHour(), + ])->first(); + + $meteorologicalReport = $lastMeteorologicalReport?->replicate() ?: new MeteorologicalMonitoringLog(); + + $meteorologicalReport->fill([ + 'device_id' => $device->id, + 'monitored_at' => $reportedAt, + 'agricultural_base_id' => $device->agricultural_base_id, + ]); + } + + $meteorologicalReport->fill($attributes)->save(); + } + + /** + * 创建杀虫灯设备报告 + */ + protected function createInsecticidalLampReport(Device $device, Carbon $time): void + { + $reportedAt = $time->copy()->startOfHour(); + + /** @var \Illuminate\Database\Eloquent\Collection */ + $logs = DeviceLog::where('device_id', $device->id) + ->whereBetween('reported_at', [$reportedAt, $reportedAt->copy()->endOfHour()]) + ->oldest('reported_at') + ->get(); + + if ($logs->isEmpty()) { + return; + } + + $attributes = value(function ($logs) { + $data = [ + 'vol' => ['sum' => 0, 'count' => 0], + 'sunVol' => ['sum' => 0, 'count' => 0], + 'dct' => ['sum' => 0, 'count' => 0], + 'temp' => ['sum' => 0, 'count' => 0], + 'humidity' => ['sum' => 0, 'count' => 0], + 'highVol' => ['sum' => 0, 'count' => 0], + ]; + + /** @var \App\Models\DeviceLog */ + foreach ($logs as $log) { + if (! is_array($log->data)) { + continue; + } + + foreach ($data as $k => $item) { + $v = $log->data[$k] ?? null; + + if (is_null($v)) { + continue; + } + + $item['sum'] = bcadd($item['sum'], $v, 2); + $item['count']++; + + $data[$k] = $item; + } + } + + $attributes = []; + + foreach ($data as $key => $item) { + $attribute = match ($key) { + 'vol' => 'battery_vol', + 'sunVol' => 'charging_vol', + 'dct' => 'killed_num', + 'temp' => 'air_temperature', + 'humidity' => 'air_humidity', + 'highVol' => 'high_vol', + }; + + if ($item['count'] > 0) { + if ($attribute === 'killed_num') { + $attributes[$attribute] = (int) $item['sum']; + } else { + $attributes[$attribute] = round(bcdiv($item['sum'], $item['count'], 2), 2); + } + } else { + $attributes[$attribute] = null; + } + } + + return $attributes; + }, $logs); + + /** @var \App\Models\InsecticidalLampReport */ + $insecticidalLampReport = InsecticidalLampReport::firstOrNew([ + 'device_id' => $device->id, + 'reported_at' => $reportedAt, + ], [ + 'agricultural_base_id' => $device->agricultural_base_id, + ]); + + $insecticidalLampReport->fill($attributes)->save(); + } + + /** + * 创建设备每日报告 + */ + public function createDailyReport(Device $device, Carbon $time): void + { + switch ($device->type) { + case DeviceType::Meteorological: + $this->createMeteorologicalDailyReport($device, $time); + break; + + case DeviceType::Soil: + $this->createSoilDailyReport($device, $time); + break; + + case DeviceType::InsecticidalLamp: + $this->createInsecticidalLampDailyReport($device, $time); + break; + } + } + + /** + * 创建土壤设备每日报告 + */ + protected function createSoilDailyReport(Device $device, Carbon $date): void + { + /** @var \Illuminate\Database\Eloquent\Collection */ + $soilReports = SoilMonitoringLog::where('device_id', $device->id) + ->whereDate('monitored_at', $date) + ->oldest('monitored_at') + ->get(); + + if ($soilReports->isEmpty()) { + return; + } + + $attributes = value(function ($soilReports) { + $data = [ + 'n' => ['sum' => 0, 'count' => 0], + 'p' => ['sum' => 0, 'count' => 0], + 'k' => ['sum' => 0, 'count' => 0], + 'conductivity' => ['sum' => 0, 'count' => 0], + 'temperature' => ['sum' => 0, 'count' => 0], + 'humidity' => ['sum' => 0, 'count' => 0], + 'moisture' => ['sum' => 0, 'count' => 0], + ]; + + foreach ($soilReports as $soilReport) { + foreach ($data as $k => $item) { + if (is_null($v = $soilReport->{$k})) { + continue; + } + + $item['sum'] = bcadd($item['sum'], $v, 2); + $item['count']++; + + $data[$k] = $item; + } + } + + $attributes = []; + + foreach ($data as $key => $item) { + $attributes[$key] = $item['count'] > 0 ? round(bcdiv($item['sum'], $item['count'], 2), 2) : null; + } + + return $attributes; + }, $soilReports); + + /** @var \App\Models\SoilDailyReport */ + $soilDailyReport = SoilMonitoringDailyLog::firstOrNew([ + 'device_id' => $device->id, + 'monitored_at' => $date->format('Y-m-d'), + ], [ + 'agricultural_base_id' => $device->agricultural_base_id, + ]); + + $soilDailyReport->fill($attributes)->save(); + } + + /** + * 创建气象设备每日报告 + */ + protected function createMeteorologicalDailyReport(Device $device, Carbon $date): void + { + /** @var \Illuminate\Database\Eloquent\Collection */ + $meteorologicalReports = MeteorologicalMonitoringLog::where('device_id', $device->id) + ->whereDate('monitored_at', $date) + ->oldest('monitored_at') + ->get(); + + if ($meteorologicalReports->isEmpty()) { + return; + } + + $attributes = value(function ($meteorologicalReports) { + $data = [ + 'current_rainfall' => 0, + 'illumination' => ['sum' => 0, 'count' => 0], + 'air_temperature' => ['sum' => 0, 'count' => 0], + 'air_humidity' => ['sum' => 0, 'count' => 0], + 'wind_speed' => ['sum' => 0, 'count' => 0], + 'wind_samples' => [], + ]; + + foreach ($meteorologicalReports as $meteorologicalReport) { + foreach ($data as $k => $item) { + if ($k === 'wind_samples') { + if (is_null($meteorologicalReport->wind_degree) || is_null($meteorologicalReport->wind_speed)) { + continue; + } + + $item[] = [ + 'wind_degree' => $meteorologicalReport->wind_degree, // 风向度数 + 'wind_speed' => $meteorologicalReport->wind_speed, // 风速 + ]; + } elseif (! is_null($v = $meteorologicalReport->{$k})) { + if ($k === 'current_rainfall') { + $item = $v; + } else { + $item['sum'] = bcadd($item['sum'], $v, 2); + $item['count']++; + } + } + + $data[$k] = $item; + } + } + + $attributes = []; + + foreach ($data as $key => $item) { + switch ($key) { + case 'current_rainfall': + $attributes['daily_rainfall'] = $item; + break; + case 'wind_samples': + if (! empty($item)) { + $attributes['wind_degree'] = value(function (array $windSamples) { + if (empty($windSamples)) { + return null; + } + + $x = 0; + $y = 0; + + foreach ($windSamples as $sample) { + if ($sample['wind_degree'] == 0 && $sample['wind_speed'] == 0) { + continue; + } + + // 角度转弧度 + $radian = deg2rad($sample['wind_degree']); + + // $x += $sample['wind_speed'] * sin($radian); + // $y += $sample['wind_speed'] * cos($radian); + $x += sin($radian); + $y += cos($radian); + } + + $degree = round(rad2deg(atan2($y, $x))); + + if (($x > 0 || $x < 0) && $y < 0) { + $degree += 180; + } elseif ($x < 0 && $y > 0) { + $degree += 360; + } + + return $degree; + }, $item); + + $attributes['wind_direction'] = value(function ($windDegree) { + if (is_null($windDegree)) { + return null; + } + + if ($windDegree >= 22.5 && $windDegree < 67.5) { + return MeteorologicalMonitoringDailyLog::WIND_DIRECTION_NORTHEAST; + } elseif ($windDegree >= 67.5 && $windDegree < 112.5) { + return MeteorologicalMonitoringDailyLog::WIND_DIRECTION_EAST; + } elseif ($windDegree >= 112.5 && $windDegree < 157.5) { + return MeteorologicalMonitoringDailyLog::WIND_DIRECTION_SOUTHEAST; + } elseif ($windDegree >= 157.5 && $windDegree < 202.5) { + return MeteorologicalMonitoringDailyLog::WIND_DIRECTION_SOUTH; + } elseif ($windDegree >= 202.5 && $windDegree < 247.5) { + return MeteorologicalMonitoringDailyLog::WIND_DIRECTION_SOUTHWEST; + } elseif ($windDegree >= 247.5 && $windDegree < 292.5) { + return MeteorologicalMonitoringDailyLog::WIND_DIRECTION_WEST; + } elseif ($windDegree >= 292.5 && $windDegree < 337.5) { + return MeteorologicalMonitoringDailyLog::WIND_DIRECTION_NORTHWEST; + } + + return MeteorologicalMonitoringDailyLog::WIND_DIRECTION_NORTH; + }, $attributes['wind_degree']); + } + break; + + default: + $attributes[$key] = $item['count'] > 0 ? round(bcdiv($item['sum'], $item['count'], 2), 2) : null; + break; + } + } + + return $attributes; + }, $meteorologicalReports); + + /** @var \App\Models\MeteorologicalMonitoringDailyLog */ + $meteorologicalDailyReport = MeteorologicalMonitoringDailyLog::firstOrNew([ + 'device_id' => $device->id, + 'monitored_at' => $date, + ], [ + 'agricultural_base_id' => $device->agricultural_base_id, + ]); + + $meteorologicalDailyReport->fill($attributes)->save(); + } + + /** + * 杀虫灯每日报告 + */ + protected function createInsecticidalLampDailyReport(Device $device, Carbon $date): void + { + /** @var \Illuminate\Database\Eloquent\Collection */ + $insecticidalLampReports = InsecticidalLampReport::where('device_id', $device->id) + ->whereDate('reported_at', $date) + ->oldest('reported_at') + ->get(); + + if ($insecticidalLampReports->isEmpty()) { + return; + } + + $attributes = value(function ($insecticidalLampReports) { + $data = [ + 'killed_num' => ['sum' => 0, 'count' => 0], + 'battery_vol' => ['sum' => 0, 'count' => 0], + 'air_temperature' => ['sum' => 0, 'count' => 0], + 'air_humidity' => ['sum' => 0, 'count' => 0], + 'charging_vol' => ['sum' => 0, 'count' => 0], + 'high_vol' => ['sum' => 0, 'count' => 0], + ]; + + foreach ($insecticidalLampReports as $insecticidalLampReport) { + foreach ($data as $k => $item) { + if (is_null($v = $insecticidalLampReport->{$k})) { + continue; + } + + $item['sum'] = bcadd($item['sum'], $v, 2); + $item['count']++; + + $data[$k] = $item; + } + } + + $attributes = []; + + foreach ($data as $key => $item) { + if ($item['count'] > 0) { + if ($key === 'killed_num') { + $attributes[$key] = (int) $item['sum']; + } else { + $attributes[$key] = round(bcdiv($item['sum'], $item['count'], 2), 2); + } + } else { + $attributes[$key] = null; + } + } + + return $attributes; + }, $insecticidalLampReports); + + /** @var \App\Models\InsecticidalLampDailyReport */ + $insecticidalLampDailyReport = InsecticidalLampDailyReport::firstOrNew([ + 'device_id' => $device->id, + 'reported_at' => $date->format('Y-m-d'), + ], [ + 'agricultural_base_id' => $device->agricultural_base_id, + ]); + + $insecticidalLampDailyReport->fill($attributes)->save(); + } + + /** + * 创建 HTTP 客户端 + */ + public function buildHttpClient(Device $device): HttpClient + { + $config = json_decode($device->project?->value, true); + + if (! is_array($config)) { + throw new BizException('账户信息未找到'); + } + + return new HttpClient($config['username'] ?? '', $config['password'] ?? ''); + } +} diff --git a/app/Services/LinkosDeviceLogService.php b/app/Services/LinkosDeviceLogService.php index 71834c3..5ac1c52 100644 --- a/app/Services/LinkosDeviceLogService.php +++ b/app/Services/LinkosDeviceLogService.php @@ -2,6 +2,7 @@ namespace App\Services; +use App\Enums\DeviceStatus; use App\Enums\DeviceType; use App\Enums\WindDirection; use App\Exceptions\BizException; @@ -89,6 +90,8 @@ class LinkosDeviceLogService throw new BizException("设备未找到, 设备编号: {$deviceId}"); } + Device::where('sn', $deviceId)->update(['status' => DeviceStatus::Online]); + $log = LinkosDeviceLog::create([ 'device_id' => $deviceId, 'device_unit' => $deviceUnit, @@ -235,7 +238,7 @@ class LinkosDeviceLogService } $log->save(); - + } /** diff --git a/database/migrations/2022_10_20_160129_create_water_quality_monitoring_logs_table.php b/database/migrations/2022_10_20_160129_create_water_quality_monitoring_logs_table.php index 8510797..44b8599 100644 --- a/database/migrations/2022_10_20_160129_create_water_quality_monitoring_logs_table.php +++ b/database/migrations/2022_10_20_160129_create_water_quality_monitoring_logs_table.php @@ -27,7 +27,7 @@ return new class extends Migration $table->timestamps(); $table->index('agricultural_base_id'); - $table->unique(['device_id', 'monitored_at']); + $table->unique(['device_id', 'monitored_at'], 'uq_device_id_monitored_at'); }); } diff --git a/database/migrations/2022_10_21_151702_create_water_quality_monitoring_daily_logs_table.php b/database/migrations/2022_10_21_151702_create_water_quality_monitoring_daily_logs_table.php index 8166ef5..cd55913 100644 --- a/database/migrations/2022_10_21_151702_create_water_quality_monitoring_daily_logs_table.php +++ b/database/migrations/2022_10_21_151702_create_water_quality_monitoring_daily_logs_table.php @@ -27,7 +27,7 @@ return new class extends Migration $table->timestamps(); $table->index('agricultural_base_id'); - $table->unique(['device_id', 'monitored_at']); + $table->unique(['device_id', 'monitored_at'], 'uq_device_id_monitored_at'); }); } diff --git a/database/migrations/2022_10_24_114148_create_soil_monitoring_daily_logs_table.php b/database/migrations/2022_10_24_114148_create_soil_monitoring_daily_logs_table.php index b8d54ff..da3a44b 100644 --- a/database/migrations/2022_10_24_114148_create_soil_monitoring_daily_logs_table.php +++ b/database/migrations/2022_10_24_114148_create_soil_monitoring_daily_logs_table.php @@ -27,7 +27,7 @@ return new class extends Migration $table->timestamps(); $table->index('agricultural_base_id'); - $table->unique(['device_id', 'monitored_at']); + $table->unique(['device_id', 'monitored_at'], 'uq_device_id_monitored_at'); }); } diff --git a/database/migrations/2022_10_24_133245_create_meteorological_monitoring_daily_logs_table.php b/database/migrations/2022_10_24_133245_create_meteorological_monitoring_daily_logs_table.php index b57b40d..078c759 100644 --- a/database/migrations/2022_10_24_133245_create_meteorological_monitoring_daily_logs_table.php +++ b/database/migrations/2022_10_24_133245_create_meteorological_monitoring_daily_logs_table.php @@ -33,7 +33,7 @@ return new class extends Migration $table->timestamps(); $table->index('agricultural_base_id'); - $table->unique(['device_id', 'monitored_at']); + $table->unique(['device_id', 'monitored_at'], 'uq_device_id_monitored_at'); }); } diff --git a/database/migrations/2023_08_012_155228_create_worm_reports_table.php b/database/migrations/2023_08_012_155228_create_worm_reports_table.php new file mode 100644 index 0000000..94f1a1c --- /dev/null +++ b/database/migrations/2023_08_012_155228_create_worm_reports_table.php @@ -0,0 +1,36 @@ +id(); + $table->unsignedBigInteger('device_id'); + $table->unsignedBigInteger('agricultural_base_id')->comment('农业基地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('worm_reports'); + } +}; diff --git a/database/migrations/2023_08_02_145504_add_supplier_key_to_devices_table.php b/database/migrations/2023_08_02_145504_add_supplier_key_to_devices_table.php new file mode 100644 index 0000000..ee5aad9 --- /dev/null +++ b/database/migrations/2023_08_02_145504_add_supplier_key_to_devices_table.php @@ -0,0 +1,32 @@ +string('supplier_key')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('devices', function (Blueprint $table) { + $table->dropColumn(['supplier_key']); + }); + } +}; diff --git a/database/migrations/2023_08_02_222919_create_device_logs_table.php b/database/migrations/2023_08_02_222919_create_device_logs_table.php new file mode 100644 index 0000000..fd9332c --- /dev/null +++ b/database/migrations/2023_08_02_222919_create_device_logs_table.php @@ -0,0 +1,37 @@ +id(); + $table->unsignedBigInteger('device_id'); + $table->json('data')->nullable(); + $table->timestamp('reported_at'); + $table->timestamps(); + + $table->index(['device_id', 'reported_at']); + $table->index('reported_at'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('device_logs'); + } +}; diff --git a/database/migrations/2023_08_03_185845_add_project_key_to_devices_table.php b/database/migrations/2023_08_03_185845_add_project_key_to_devices_table.php new file mode 100644 index 0000000..cbffd8e --- /dev/null +++ b/database/migrations/2023_08_03_185845_add_project_key_to_devices_table.php @@ -0,0 +1,32 @@ +string('project_key')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('devices', function (Blueprint $table) { + $table->dropColumn(['project_key']); + }); + } +}; diff --git a/database/migrations/2023_08_03_193757_change_columns_to_meteorological_monitoring_daily_logs_table.php b/database/migrations/2023_08_03_193757_change_columns_to_meteorological_monitoring_daily_logs_table.php new file mode 100644 index 0000000..fdfdf30 --- /dev/null +++ b/database/migrations/2023_08_03_193757_change_columns_to_meteorological_monitoring_daily_logs_table.php @@ -0,0 +1,32 @@ +decimal('wind_degree', 5, 2)->nullable()->comment('风向度数')->change(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('meteorological_monitoring_daily_logs', function (Blueprint $table) { + $table->integer('wind_degree')->nullable()->comment('风向度数')->change(); + }); + } +}; diff --git a/database/migrations/2023_08_03_194242_change_columns_to_meteorological_monitoring_logs_table.php b/database/migrations/2023_08_03_194242_change_columns_to_meteorological_monitoring_logs_table.php new file mode 100644 index 0000000..8f3e50e --- /dev/null +++ b/database/migrations/2023_08_03_194242_change_columns_to_meteorological_monitoring_logs_table.php @@ -0,0 +1,34 @@ +decimal('wind_degree', 5, 2)->nullable()->comment('风向度数')->change(); + $table->decimal('illumination', 10, 2)->nullable()->comment('光照度 (单位: Lux)')->change(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('meteorological_monitoring_logs', function (Blueprint $table) { + $table->integer('wind_degree')->nullable()->comment('风向度数')->change(); + $table->integer('illumination')->nullable()->comment('光照度 (单位: Lux)')->change(); + }); + } +}; diff --git a/database/migrations/2023_08_03_194825_change_columns_to_soil_monitoring_logs_table.php b/database/migrations/2023_08_03_194825_change_columns_to_soil_monitoring_logs_table.php new file mode 100644 index 0000000..0de36d4 --- /dev/null +++ b/database/migrations/2023_08_03_194825_change_columns_to_soil_monitoring_logs_table.php @@ -0,0 +1,36 @@ +decimal('n', 8, 2)->nullable()->comment('氮 (单位: mg/kg)')->change(); + $table->decimal('p', 8, 2)->nullable()->comment('磷 (单位: mg/kg)')->change(); + $table->decimal('k', 8, 2)->nullable()->comment('钾 (单位: mg/kg)')->change(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('soil_monitoring_logs', function (Blueprint $table) { + $table->integer('n')->nullable()->comment('氮 (单位: mg/kg)')->change(); + $table->integer('p')->nullable()->comment('磷 (单位: mg/kg)')->change(); + $table->integer('k')->nullable()->comment('钾 (单位: mg/kg)')->change(); + }); + } +}; diff --git a/database/migrations/2023_08_11_172337_create_insecticidal_lamp_reports_table.php b/database/migrations/2023_08_11_172337_create_insecticidal_lamp_reports_table.php new file mode 100644 index 0000000..2601f00 --- /dev/null +++ b/database/migrations/2023_08_11_172337_create_insecticidal_lamp_reports_table.php @@ -0,0 +1,40 @@ +id(); + $table->unsignedBigInteger('device_id'); + $table->unsignedBigInteger('agricultural_base_id')->comment('农业基地ID'); + $table->double('battery_vol')->nullable()->comment('蓄电池电压'); + $table->unsignedInteger('killed_num')->default(0)->comment('杀虫数'); + $table->double('air_temperature')->nullable()->comment('大气气温'); + $table->double('air_humidity')->nullable()->comment('大气湿度'); + $table->double('charging_vol')->nullable()->comment('充电电压'); + $table->double('high_vol')->nullable()->comment('高压值'); + $table->timestamp('reported_at')->comment('监控日期'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('insecticidal_lamp_reports'); + } +}; diff --git a/database/migrations/2023_08_12_112337_create_insecticidal_lamp_daily_reports_table.php b/database/migrations/2023_08_12_112337_create_insecticidal_lamp_daily_reports_table.php new file mode 100644 index 0000000..57df60c --- /dev/null +++ b/database/migrations/2023_08_12_112337_create_insecticidal_lamp_daily_reports_table.php @@ -0,0 +1,40 @@ +id(); + $table->unsignedBigInteger('device_id'); + $table->unsignedBigInteger('agricultural_base_id')->comment('农业基地ID'); + $table->double('battery_vol')->nullable()->comment('蓄电池电压'); + $table->unsignedInteger('killed_num')->default(0)->comment('杀虫数'); + $table->double('air_temperature')->nullable()->comment('大气气温'); + $table->double('air_humidity')->nullable()->comment('大气湿度'); + $table->double('charging_vol')->nullable()->comment('充电电压'); + $table->double('high_vol')->nullable()->comment('高压值'); + $table->date('reported_at')->comment('监控日期'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('insecticidal_lamp_reports'); + } +}; diff --git a/database/migrations/2023_08_25_144027_create_worm_photos_table.php b/database/migrations/2023_08_25_144027_create_worm_photos_table.php new file mode 100644 index 0000000..56812b5 --- /dev/null +++ b/database/migrations/2023_08_25_144027_create_worm_photos_table.php @@ -0,0 +1,36 @@ +id(); + $table->unsignedBigInteger('device_id'); + $table->text('url')->nullable(); + $table->timestamp('uploaded_at')->comment('上传时间'); + $table->timestamps(); + + $table->index('uploaded_at'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('worm_photos'); + } +}; diff --git a/database/seeders/KeywordsTableSeeder.php b/database/seeders/KeywordsTableSeeder.php index 60777f9..1cb5914 100644 --- a/database/seeders/KeywordsTableSeeder.php +++ b/database/seeders/KeywordsTableSeeder.php @@ -32,6 +32,62 @@ class KeywordsTableSeeder extends Seeder ['key' => 'crops-cate-lingye', 'name' => '林业', 'type_key' => 'crops-category', 'value' => ''], ['key' => 'crops-cate-activity', 'name' => '其他', 'type_key' => 'crops-category', 'value' => ''], ]], + [ + 'key' => 'industry_key', + 'name' => '产业类型', + 'value' => '', + 'list' => [ + ['key' => 'industry_1', 'name' => '稻渔综合种养', 'value' => 1], + ['key' => 'industry_2', 'name' => '优品柑桔种植', 'value' => 2], + ['key' => 'industry_3', 'name' => '高梁产业', 'value' => 3], + ['key' => 'industry_4', 'name' => '油茶产业', 'value' => 4], + ['key' => 'industry_5', 'name' => '生猪产业分布点', 'value' => 5], + ['key' => 'industry_6', 'name' => '肉羊产业分布点', 'value' => 6], + ], + ], + [ + 'key' => 'device-supplier', + 'name' => '设备供应商', + 'value' => '', + 'list' => [ + ['key' => 'device-supplier-linkos', 'name' => '慧联无限', 'value' => ''], + ['key' => 'device-supplier-biang', 'name' => '比昂', 'value' => ''], + ['key' => 'device-supplier-yunfei', 'name' => '云飞', 'value' => ''], + ['key' => 'device-supplier-other', 'name' => '其它', 'value' => ''], + ], + ], + [ + 'key' => 'device-project', + 'name' => '设备项目', + 'value' => '', + 'list' => [ + [ + 'key' => 'device-project-cyxdnyyq', + 'name' => '成渝现代高效特色农业带合作园区', + 'value' => json_encode([ + 'username' => '成渝现代高效特色农业带合作园区', + 'password' => '888888', + ]), + ], + [ + 'key' => 'device-project-nyncj', + 'name' => '隆昌市农业农村局', + 'value' => json_encode([ + 'username' => '隆昌市农业农村局', + 'password' => '888888', + ]), + ], + [ + 'key' => 'device-project-syqshc', + 'name' => '隆昌市石燕桥镇三合村股份经济联合社', + 'value' => json_encode([ + 'username' => '隆昌市石燕桥镇三合村股份经济联合社', + 'password' => '888888', + ]), + ], + ['key' => 'device-project-other', 'name' => '其它', 'value' => ''], + ], + ] ]; $list[] = value(function () { @@ -53,14 +109,15 @@ class KeywordsTableSeeder extends Seeder private function createKeywords($keywords, $parentType = null) { - foreach ($keywords as $item) { + foreach ($keywords as $i => $item) { if ($parentType) { $type = Keywords::create([ 'name' => $item['name'], - 'key' => $item['key'] ?? $parentType->key.($parentType + 1), + 'key' => $item['key'] ?? $parentType->key.($i + 1), 'type_key' => $parentType->key, 'level' => ($parentType->level ?? 1) + 1, 'parent_id' => $parentType->id, + 'value' => $item['value'], ]); } else { $type = Keywords::create(Arr::except($item, 'list')); diff --git a/routes/api.php b/routes/api.php index e211420..cde88fb 100644 --- a/routes/api.php +++ b/routes/api.php @@ -17,7 +17,9 @@ use Illuminate\Support\Facades\Route; Route::post('auth/login', [AuthController::class, 'login']); -Route::group(['middleware' => 'auth:sanctum'], function () { +Route::group([ + 'middleware' => ['auth:sanctum'], +], function () { Route::post('web/upload', [WebController::class, 'upload']); @@ -54,6 +56,8 @@ Route::group(['middleware' => 'auth:sanctum'], function () { Route::apiResource('crop-flows', CropFlowController::class)->names('crops_flow'); //设备管理 Route::apiResource('devices', DeviceController::class)->names('device'); + Route::get('devices/{device}/worm-statics', [DeviceController::class, 'wormStatics']); + Route::get('devices/{device}/worm-photos', [DeviceController::class, 'wormPhotos']); Route::put('devices-update-recommend/{device}', [DeviceController::class, 'updateRecommendStatus']); Route::get('devices-num', [DeviceController::class, 'typeStatusNum'])->name('device.type_status_num'); Route::get('monitoring-data', [DeviceController::class, 'timeZoneList']); diff --git a/routes/web.php b/routes/web.php index 591cb17..326cda2 100644 --- a/routes/web.php +++ b/routes/web.php @@ -1,11 +1,15 @@ 'third'], function(){ Route::middleware([ApiCustomToken::class])->group(function(){