linkos 虫情数据同步
parent
e422c3f896
commit
8fbb3d22d8
|
|
@ -0,0 +1,140 @@
|
|||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Iot\Linkos\FarmClient;
|
||||
use App\Models\Device;
|
||||
use App\Models\FarmWormPhoto;
|
||||
use App\Models\FarmWormReport;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Throwable;
|
||||
|
||||
class LinkosFarmSyncCommand extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'linkos-farm:sync';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'linkos 虫情设备数据同步';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$this->info('------------------------------------------');
|
||||
$this->info(now());
|
||||
try {
|
||||
$this->sync();
|
||||
} catch (Throwable $e) {
|
||||
report($e);
|
||||
}
|
||||
$this->info('------------------------------------------');
|
||||
}
|
||||
|
||||
protected function sync(): void
|
||||
{
|
||||
// 接口接口限制,每分钟最多访问6次,因此每次访问后需休眠10秒
|
||||
$client = new FarmClient('xunwang', 'qwer1234');
|
||||
|
||||
$now = now();
|
||||
|
||||
/** @var \Illuminate\Database\Eloquent\Collection */
|
||||
$devices = Device::poweredBy('link-os')
|
||||
->where('type', Device::TYPE_INSECT)
|
||||
->whereIn('state', [Device::STATE_ONLINE, Device::STATE_OFFLINE])
|
||||
->get();
|
||||
|
||||
if ($devices->isEmpty()) {
|
||||
$this->warn('没有找到虫情设备');
|
||||
return;
|
||||
}
|
||||
|
||||
$this->info('==================================');
|
||||
$this->info('尝试更新设备状态...');
|
||||
$realTimeData = $client->realTimeData($devices->pluck('sn')->all());
|
||||
foreach ($realTimeData as $item) {
|
||||
foreach ($devices as $device) {
|
||||
if ($item['deviceAddr'] != $device->sn) {
|
||||
continue;
|
||||
}
|
||||
// 更新设备状态
|
||||
$device->update([
|
||||
'state' => $item['status'] === 'online' ? Device::STATE_ONLINE : Device::STATE_OFFLINE,
|
||||
]);
|
||||
}
|
||||
}
|
||||
$this->info("设备状态更新完成");
|
||||
|
||||
$this->info('==================================');
|
||||
|
||||
$this->info('尝试同步虫情区域统计...');
|
||||
for ($i=2; $i > 0; $i--) {
|
||||
$reportedAt = $now->copy()->subDays($i);
|
||||
|
||||
$statistics = collect(
|
||||
$client->wormStatistics(
|
||||
'E05F10DAIB6F4I4977IB95FI82554A48DE7C',
|
||||
$reportedAt->copy()->startOfDay(),
|
||||
$reportedAt->copy()->endOfDay(),
|
||||
)
|
||||
)->mapWithKeys(fn ($item) => [$item['deviceAddr'] => $item['wornData']]);
|
||||
|
||||
foreach ($devices as $device) {
|
||||
$data = $statistics[$device->sn] ?? [];
|
||||
|
||||
FarmWormReport::updateOrCreate([
|
||||
'device_id' => $device->id,
|
||||
'reported_at' => $reportedAt->toDateString(),
|
||||
], [
|
||||
'worm_num' => collect($data)->sum('num'),
|
||||
'data' => $data,
|
||||
]);
|
||||
}
|
||||
}
|
||||
$this->info("同步虫情区域统计完成");
|
||||
|
||||
$this->info('==================================');
|
||||
|
||||
// 接口请求次数
|
||||
$requests = 4;
|
||||
|
||||
// 同步最近7天的分析报表记录
|
||||
$this->info('尝试同步分析报表记录...');
|
||||
foreach ($devices->pluck('sn') as $sn) {
|
||||
$data = $client->wormAnalyseData($sn, $now->copy()->subDays(7), $now, 1, 100);
|
||||
|
||||
foreach ($data['rows'] as $item) {
|
||||
foreach ($devices as $device) {
|
||||
if ($item['deviceAddr'] != $device->sn) {
|
||||
continue;
|
||||
}
|
||||
FarmWormPhoto::updateOrCreate([
|
||||
'device_id' => $device->id,
|
||||
'uploaded_at' => $item['createTime'],
|
||||
], [
|
||||
'url' => $item['analyseCoordUrl'] ?: $item['imagesUrl'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
$requests++;
|
||||
// 接口请求频率: 每分钟6次
|
||||
if ($requests == 6) {
|
||||
$requests = 0;
|
||||
sleep(61);
|
||||
}
|
||||
}
|
||||
$this->info("同步分析报表记录完成");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class LinkosFarmException extends RuntimeException
|
||||
{
|
||||
}
|
||||
|
|
@ -0,0 +1,153 @@
|
|||
<?php
|
||||
|
||||
namespace App\Iot\Linkos;
|
||||
|
||||
use App\Exceptions\LinkosFarmException;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
class FarmClient
|
||||
{
|
||||
public const ENDPOINT_URL = 'http://api.farm.0531yun.cn';
|
||||
|
||||
/**
|
||||
* 授权令牌
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $token;
|
||||
|
||||
/**
|
||||
* 授权令牌有效期
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $expires;
|
||||
|
||||
public function __construct(
|
||||
protected readonly string $loginName,
|
||||
protected readonly string $loginPwd,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取设备列表
|
||||
*/
|
||||
public function devices(?string $groupId = null, ?string $deviceType = null): array
|
||||
{
|
||||
$result = $this->get('/api/v2.0/entrance/device/getsysUserDevice', [
|
||||
'groupId' => $groupId,
|
||||
'deviceType' => $deviceType,
|
||||
]);
|
||||
|
||||
return $result['data'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户区域
|
||||
*/
|
||||
public function groups(?string $groupName = null): array
|
||||
{
|
||||
$result = $this->get('/api/v2.0/entrance/group/getsysUserGroup', [
|
||||
'groupName' => $groupName,
|
||||
]);
|
||||
|
||||
return $result['data'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取设备实时数据
|
||||
*/
|
||||
public function realTimeData(array $devices = []): array
|
||||
{
|
||||
$result = $this->get('/api/v2.0/entrance/device/getRealTimeData', [
|
||||
'deviceAddrs' => implode(',', $devices),
|
||||
]);
|
||||
|
||||
return $result['data'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 虫情区域统计
|
||||
*/
|
||||
public function wormStatistics(string $groupId, Carbon $beginTime, Carbon $endTime): array
|
||||
{
|
||||
$result = $this->get('/api/v2.0/worm/deviceData/getWormStatisticsByGroup', [
|
||||
'groupId' => $groupId,
|
||||
'beginTime' => $beginTime->toDateTimeString(),
|
||||
'endTime' => $endTime->toDateTimeString(),
|
||||
]);
|
||||
|
||||
return $result['data'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 虫情设备分析报表
|
||||
*/
|
||||
public function wormAnalyseData(string $device, Carbon $beginTime, Carbon $endTime, int $pages, int $limit = 100): array
|
||||
{
|
||||
$result = $this->get('/api/v2.0/worm/deviceData/getWormDataList', [
|
||||
'deviceAddr' => $device,
|
||||
'beginTime' => $beginTime->toDateTimeString(),
|
||||
'endTime' => $endTime->toDateTimeString(),
|
||||
'pages' => $pages,
|
||||
'limit' => $limit,
|
||||
]);
|
||||
|
||||
return $result['data'];
|
||||
}
|
||||
|
||||
public function get(string $url, array $query = []): array
|
||||
{
|
||||
return $this->request('GET', $url, [
|
||||
'query' => $query,
|
||||
]);
|
||||
}
|
||||
|
||||
public function post(string $url, array $data = []): array
|
||||
{
|
||||
return $this->request('POST', $url, [
|
||||
'json' => $data,
|
||||
]);
|
||||
}
|
||||
|
||||
protected function request(string $method, string $url, array $options = []): array
|
||||
{
|
||||
$headers = [
|
||||
'Content-Type' => 'application/json',
|
||||
];
|
||||
if ($url !== '/api/v2.0/entrance/user/userLogin') {
|
||||
$headers['token'] = $this->token();
|
||||
}
|
||||
|
||||
/** @var \Illuminate\Http\Client\Response */
|
||||
$response = Http::withHeaders($headers)->baseUrl(self::ENDPOINT_URL)->send($method, $url, $options);
|
||||
|
||||
$json = $response->throw()->json();
|
||||
|
||||
if (data_get($json, 'code') === 1000) {
|
||||
return $json;
|
||||
}
|
||||
|
||||
throw new LinkosFarmException(
|
||||
data_get($json, 'message', '出错啦'),
|
||||
data_get($json, 'code', 0),
|
||||
);
|
||||
}
|
||||
|
||||
protected function token(): string
|
||||
{
|
||||
if ($this->token && $this->expires > now()->unix() + 30) {
|
||||
return $this->token;
|
||||
}
|
||||
|
||||
$result = $this->post('/api/v2.0/entrance/user/userLogin', [
|
||||
'loginName' => $this->loginName,
|
||||
'loginPwd' => $this->loginPwd,
|
||||
]);
|
||||
|
||||
$this->expires = $result['data']['expDate'];
|
||||
|
||||
return $this->token = $result['data']['token'];
|
||||
}
|
||||
}
|
||||
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use EloquentFilter\Filterable;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class Device extends Model
|
||||
|
|
@ -37,7 +37,6 @@ class Device extends Model
|
|||
$query->whereHas('factory', fn ($query) => $query->where('key', $factory));
|
||||
}
|
||||
|
||||
|
||||
protected function serializeDate(\DateTimeInterface $date){
|
||||
return $date->format('Y-m-d H:i:s');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class FarmWormPhoto extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $casts = [
|
||||
'uploaded_at' => 'date',
|
||||
];
|
||||
|
||||
protected $fillable = [
|
||||
'device_id', 'url', 'uploaded_at',
|
||||
];
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class FarmWormReport extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $casts = [
|
||||
'data' => 'json',
|
||||
'reported_at' => 'date',
|
||||
];
|
||||
|
||||
protected $fillable = [
|
||||
'device_id', 'worm_num', 'data', 'reported_at',
|
||||
];
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('farm_worm_reports', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('device_id');
|
||||
$table->unsignedInteger('worm_num')->comment('虫子数量');
|
||||
$table->json('data')->nullable();
|
||||
$table->date('reported_at')->comment('报告日期');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('farm_worm_reports');
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('farm_worm_photos', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('device_id');
|
||||
$table->text('url')->nullable();
|
||||
$table->timestamp('uploaded_at')->comment('上传时间');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('farm_worm_photos');
|
||||
}
|
||||
};
|
||||
Loading…
Reference in New Issue