Compare commits
9 Commits
26fba3816b
...
96987222aa
| Author | SHA1 | Date |
|---|---|---|
|
|
96987222aa | |
|
|
53c44e8165 | |
|
|
b46e1ff1c6 | |
|
|
939a24a9a7 | |
|
|
96195debda | |
|
|
efbbcb78c1 | |
|
|
a0b2bf0ce6 | |
|
|
5e19467e8d | |
|
|
cd90308c97 |
|
|
@ -0,0 +1,114 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Enums\DeviceStatus;
|
||||||
|
use App\Enums\DeviceType;
|
||||||
|
use App\Models\Device;
|
||||||
|
use App\Iot\MoJing\HttpClient as MoJingHttpClient;
|
||||||
|
use App\Iot\Qly\HttpClient as QlyHttpClient;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
|
class MonitorDeviceHealthCommand extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The name and signature of the console command.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'monitor-device-health {supplier}';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The console command description.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = '监控设备心跳检查';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
switch ($this->argument('supplier')) {
|
||||||
|
// 移动千里眼
|
||||||
|
case 'yidong':
|
||||||
|
$this->checkHealthBySupplierYidong();
|
||||||
|
break;
|
||||||
|
|
||||||
|
// 电信魔镜
|
||||||
|
case 'dianxin':
|
||||||
|
$this->checkHealthBySupplierDianxin();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function checkHealthBySupplierYidong()
|
||||||
|
{
|
||||||
|
$client = new QlyHttpClient(
|
||||||
|
config('services.ydqly.appid'),
|
||||||
|
config('services.ydqly.secret'),
|
||||||
|
config('services.ydqly.rsa'),
|
||||||
|
);
|
||||||
|
|
||||||
|
$page = 1;
|
||||||
|
$pageSize = 100;
|
||||||
|
|
||||||
|
do {
|
||||||
|
$result = $client->post(
|
||||||
|
'/v3/open/api/device/list',
|
||||||
|
[
|
||||||
|
'page' => $page,
|
||||||
|
'pageSize' => $pageSize,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($result['data'] as $item) {
|
||||||
|
Device::where('sn', $item['deviceId'])
|
||||||
|
->where('supplier_key', 'device-supplier-yidong')
|
||||||
|
->where('type', DeviceType::Monitor)
|
||||||
|
->whereIn('status', [DeviceStatus::Online, DeviceStatus::Offline])
|
||||||
|
->update([
|
||||||
|
'status' => $item['deviceStatus'] === 1 ? DeviceStatus::Online : DeviceStatus::Offline,
|
||||||
|
'updated_at' => now(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$page++;
|
||||||
|
} while (count($result['data']) === $pageSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function checkHealthBySupplierDianxin(): void
|
||||||
|
{
|
||||||
|
$client = new MoJingHttpClient(
|
||||||
|
config('services.dxmj.app_id'),
|
||||||
|
config('services.dxmj.app_secret'),
|
||||||
|
);
|
||||||
|
|
||||||
|
$result = $client->get(
|
||||||
|
"/api/v3/channel/listByOrgId",
|
||||||
|
[
|
||||||
|
'id' => '1736649747126530049',
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($result['data'] as $item) {
|
||||||
|
Device::where('sn', $item['channelcode'])
|
||||||
|
->where('supplier_key', 'device-supplier-dianxin')
|
||||||
|
->where('type', DeviceType::Monitor)
|
||||||
|
->whereIn('status', [DeviceStatus::Online, DeviceStatus::Offline])
|
||||||
|
->update([
|
||||||
|
'extends' => json_encode([
|
||||||
|
'ip' => '',
|
||||||
|
'port' => '',
|
||||||
|
'username' => '',
|
||||||
|
'password' => '',
|
||||||
|
'passage' => $item['citId'],
|
||||||
|
'rtsp_url' => '',
|
||||||
|
]),
|
||||||
|
'status' => $item['channelstatus'] === 1 ? DeviceStatus::Online : DeviceStatus::Offline,
|
||||||
|
'updated_at' => now(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -22,6 +22,14 @@ class Kernel extends ConsoleKernel
|
||||||
$schedule->command(Commands\Linkos\WormReportCommand::class)
|
$schedule->command(Commands\Linkos\WormReportCommand::class)
|
||||||
->hourlyAt(15)
|
->hourlyAt(15)
|
||||||
->runInBackground();
|
->runInBackground();
|
||||||
|
|
||||||
|
$schedule->command(Commands\MonitorDeviceHealthCommand::class, ['yidong'])
|
||||||
|
->everyFiveMinutes()
|
||||||
|
->runInBackground();
|
||||||
|
|
||||||
|
$schedule->command(Commands\MonitorDeviceHealthCommand::class, ['dianxin'])
|
||||||
|
->everyFiveMinutes()
|
||||||
|
->runInBackground();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Exceptions;
|
||||||
|
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
|
class MoJingException extends RuntimeException
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Exceptions;
|
||||||
|
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
|
class QlyException extends RuntimeException
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
@ -9,6 +9,8 @@ use App\Helpers\Paginator;
|
||||||
use App\Http\Requestes\DeviceRequest;
|
use App\Http\Requestes\DeviceRequest;
|
||||||
use App\Http\Resources\DeviceResource;
|
use App\Http\Resources\DeviceResource;
|
||||||
use App\Http\Resources\WormPhotoResource;
|
use App\Http\Resources\WormPhotoResource;
|
||||||
|
use App\Iot\MoJing\HttpClient as MoJingHttpClient;
|
||||||
|
use App\Iot\Qly\HttpClient as QlyHttpClient;
|
||||||
use App\Models\AgriculturalBase;
|
use App\Models\AgriculturalBase;
|
||||||
use App\Models\Device;
|
use App\Models\Device;
|
||||||
use App\Models\InsecticidalLampDailyReport;
|
use App\Models\InsecticidalLampDailyReport;
|
||||||
|
|
@ -725,4 +727,112 @@ class DeviceController extends Controller
|
||||||
|
|
||||||
return WormPhotoResource::collection($wormPhotos);
|
return WormPhotoResource::collection($wormPhotos);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 监控设备直播地址
|
||||||
|
*/
|
||||||
|
public function live(int $id, BiAngDeviceService $biangDeviceService)
|
||||||
|
{
|
||||||
|
$device = Device::where('type', DeviceType::Monitor)->findOrFail($id);
|
||||||
|
|
||||||
|
switch ($device->supplier_key) {
|
||||||
|
case 'device-supplier-biang':
|
||||||
|
$client = $biangDeviceService->buildHttpClient($device);
|
||||||
|
|
||||||
|
// 直播地址
|
||||||
|
$address = $client->getMonitorPalyAddress($device->sn);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'type' => 'm3u8',
|
||||||
|
'address' => (string) $address,
|
||||||
|
// 有效期 60 分钟
|
||||||
|
'expires' => 3540,
|
||||||
|
];
|
||||||
|
|
||||||
|
// 中国移动千里眼
|
||||||
|
case 'device-supplier-yidong':
|
||||||
|
$client = new QlyHttpClient(
|
||||||
|
config('services.ydqly.appid'),
|
||||||
|
config('services.ydqly.secret'),
|
||||||
|
config('services.ydqly.rsa'),
|
||||||
|
);
|
||||||
|
$result = $client->post(
|
||||||
|
'/v3/open/api/websdk/player',
|
||||||
|
[
|
||||||
|
'deviceId' => $device->sn,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'type' => 'iframe',
|
||||||
|
'address' => (string) data_get($result, 'data.url'),
|
||||||
|
'expires' => data_get($result, 'data.expiresIn'),
|
||||||
|
];
|
||||||
|
|
||||||
|
// 中国电信魔镜
|
||||||
|
case 'device-supplier-dianxin':
|
||||||
|
$address = '';
|
||||||
|
|
||||||
|
if ($channelId = data_get($device->extends, 'passage')) {
|
||||||
|
$client = new MoJingHttpClient(
|
||||||
|
config('services.dxmj.app_id'),
|
||||||
|
config('services.dxmj.app_secret'),
|
||||||
|
);
|
||||||
|
$result = $client->get(
|
||||||
|
"/api/v3/channel/RealTimeVideo/{$channelId}",
|
||||||
|
[
|
||||||
|
'transType' => 4,
|
||||||
|
'natType' => 1,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
$address = data_get($result, 'data.playUrl');
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'type' => 'flv',
|
||||||
|
'address' => (string) $address,
|
||||||
|
// 有效期 29 分钟
|
||||||
|
'expires' => 1740,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 直播地址
|
||||||
|
$address = '';
|
||||||
|
|
||||||
|
if ($rtspUrl = data_get($device->extends, 'rtsp_url')) {
|
||||||
|
$value = Setting::where('slug', 'ffmpeg_websocket_ip')->value('value');
|
||||||
|
|
||||||
|
$wsConfig = $value ? json_decode($value, true) : [];
|
||||||
|
if (! is_array($wsConfig)) {
|
||||||
|
$wsConfig = [];
|
||||||
|
}
|
||||||
|
$wsConfig = array_merge([
|
||||||
|
'host' => '',
|
||||||
|
'ip' => '127.0.0.1',
|
||||||
|
'port'=> '80',
|
||||||
|
'ssl' => false,
|
||||||
|
], $wsConfig);
|
||||||
|
|
||||||
|
if ($wsConfig['ssl'] && $wsConfig['host']) {
|
||||||
|
$address = sprintf(
|
||||||
|
'wss://%s/rtsp?url=%s',
|
||||||
|
$wsConfig['host'],
|
||||||
|
base64_encode($rtspUrl),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$address = sprintf(
|
||||||
|
'ws://%s:%s/rtsp?url=%s',
|
||||||
|
$wsConfig['ip'],
|
||||||
|
$wsConfig['port'],
|
||||||
|
base64_encode($rtspUrl),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'type' => 'flv',
|
||||||
|
'address' => $address,
|
||||||
|
'expires' => 0,
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Iot\MoJing;
|
||||||
|
|
||||||
|
use App\Exceptions\MoJingException;
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 电信魔镜
|
||||||
|
*/
|
||||||
|
class HttpClient
|
||||||
|
{
|
||||||
|
const ENDPOINT_URL = 'https://mojing.sctel.com.cn';
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
protected readonly string $appId,
|
||||||
|
protected readonly string $appSecret,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get(string $url, array $query = []): array
|
||||||
|
{
|
||||||
|
return $this->request('GET', $url, [
|
||||||
|
'query' => $query,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function request(string $method, string $url, array $options = []): array
|
||||||
|
{
|
||||||
|
$timestamp = now()->getTimestampMs();
|
||||||
|
|
||||||
|
$sign = md5($this->appId.$this->appSecret.$timestamp);
|
||||||
|
|
||||||
|
/** @var \Illuminate\Http\Client\Response */
|
||||||
|
$response = Http::baseUrl(static::ENDPOINT_URL)->withHeaders([
|
||||||
|
'Authorization' => base64_encode(
|
||||||
|
json_encode([
|
||||||
|
'appId' => $this->appId,
|
||||||
|
'time' => $timestamp,
|
||||||
|
'sign' => $sign,
|
||||||
|
])
|
||||||
|
),
|
||||||
|
])->send($method, $url, $options);
|
||||||
|
|
||||||
|
$result = $response->throw()->json();
|
||||||
|
|
||||||
|
$code = data_get($result, 'code', '-1');
|
||||||
|
if ($code === 200) {
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new MoJingException(
|
||||||
|
data_get($result, 'message', '请求失败').', 错误码: '.$code,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,129 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Iot\Qly;
|
||||||
|
|
||||||
|
use App\Exceptions\QlyException;
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移动千里眼
|
||||||
|
*/
|
||||||
|
class HttpClient
|
||||||
|
{
|
||||||
|
const ENDPOINT_URL = 'https://open.andmu.cn';
|
||||||
|
|
||||||
|
const ENDPOINT_VERSION = '1.0.0';
|
||||||
|
|
||||||
|
protected $token;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
protected readonly string $appid,
|
||||||
|
protected readonly string $secret,
|
||||||
|
protected readonly string $rsa,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function post(string $url, array $data = [], array $headers = []): array
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return $this->request('POST', $url, [
|
||||||
|
'headers' => $headers,
|
||||||
|
'json' => $data,
|
||||||
|
]);
|
||||||
|
} catch (QlyException $e) {
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function request(string $method, string $url, array $options = []): array
|
||||||
|
{
|
||||||
|
beginning:
|
||||||
|
$data = [];
|
||||||
|
if (array_key_exists('json', $options)) {
|
||||||
|
$data = $options['json'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$headers = array_merge([
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
'appid' => $this->appid,
|
||||||
|
'md5' => md5($data ? json_encode($data) : "{}"),
|
||||||
|
'timestamp' => (string) now()->getTimestampMs(),
|
||||||
|
'version' => static::ENDPOINT_VERSION,
|
||||||
|
], $options['headers'] ?? []);
|
||||||
|
|
||||||
|
if (Str::start($url, '/') === '/v3/open/api/token') {
|
||||||
|
unset($headers['token']);
|
||||||
|
} else {
|
||||||
|
$headers['token'] = isset($headers['token']) ? $headers['token'] : $this->token();
|
||||||
|
}
|
||||||
|
|
||||||
|
ksort($headers);
|
||||||
|
|
||||||
|
$headers['signature'] = $this->sign(json_encode(
|
||||||
|
Arr::only($headers, ['appid', 'md5', 'timestamp', 'token', 'version'])
|
||||||
|
));
|
||||||
|
|
||||||
|
$options['headers'] = $headers;
|
||||||
|
|
||||||
|
/** @var \Illuminate\Http\Client\Response */
|
||||||
|
$response = Http::baseUrl(static::ENDPOINT_URL)->send($method, $url, $options);
|
||||||
|
|
||||||
|
$result = $response->throw()->json();
|
||||||
|
|
||||||
|
$resultCode = data_get($result, 'resultCode', '-1');
|
||||||
|
if ($resultCode === '000000') {
|
||||||
|
return $result;
|
||||||
|
} elseif ($resultCode === '11504') {
|
||||||
|
$this->token(true);
|
||||||
|
goto beginning;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new QlyException(
|
||||||
|
data_get($result, 'resultMsg', '请求失败').', 错误码: '.$resultCode,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function token(bool $refresh = false): string
|
||||||
|
{
|
||||||
|
$key = "ydqly_tokens:{$this->appid}";
|
||||||
|
|
||||||
|
if (! $refresh && $this->token ??= Cache::get($key)) {
|
||||||
|
return $this->token;
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = $this->post(
|
||||||
|
'/v3/open/api/token',
|
||||||
|
array_merge([
|
||||||
|
'sig' => md5($this->appid.$this->secret),
|
||||||
|
'operatorType' => 1,
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->token = data_get($result, 'data.token');
|
||||||
|
|
||||||
|
$ttl = (int) data_get($result, 'data.expires_in', 0) - 120;
|
||||||
|
if ($ttl > 0) {
|
||||||
|
Cache::put($key, $this->token, $ttl);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->token;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成签名
|
||||||
|
*/
|
||||||
|
protected function sign(string $message): string
|
||||||
|
{
|
||||||
|
// rsa 私钥
|
||||||
|
$pem = "-----BEGIN PRIVATE KEY-----\n" .
|
||||||
|
wordwrap($this->rsa, 64, "\n", true) .
|
||||||
|
"\n-----END PRIVATE KEY-----";
|
||||||
|
|
||||||
|
openssl_sign($message, $signature, $pem);
|
||||||
|
|
||||||
|
return base64_encode($signature);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -23,7 +23,7 @@ class Device extends Model
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
'type' => DeviceType::class,
|
'type' => DeviceType::class,
|
||||||
'status' => DeviceStatus::class,
|
'status' => DeviceStatus::class,
|
||||||
'extends' => 'array',
|
'extends' => 'json',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
|
|
|
||||||
|
|
@ -36,4 +36,16 @@ return [
|
||||||
'secret' => env('LINKOS_API_SECRET'),
|
'secret' => env('LINKOS_API_SECRET'),
|
||||||
],
|
],
|
||||||
|
|
||||||
|
// 中国移动千里眼
|
||||||
|
'ydqly' => [
|
||||||
|
'appid' => env('YDQLY_APPID'),
|
||||||
|
'secret' => env('YDQLY_SECRET'),
|
||||||
|
'rsa' => env('YDQLY_RSA'),
|
||||||
|
],
|
||||||
|
|
||||||
|
// 中国电信魔镜
|
||||||
|
'dxmj' => [
|
||||||
|
'app_id' => env('DXMJ_APP_ID'),
|
||||||
|
'app_secret' => env('DXMJ_APP_SECRET'),
|
||||||
|
],
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,8 @@ class KeywordsTableSeeder extends Seeder
|
||||||
['key' => 'device-supplier-linkos', 'name' => '慧联无限', 'value' => ''],
|
['key' => 'device-supplier-linkos', 'name' => '慧联无限', 'value' => ''],
|
||||||
['key' => 'device-supplier-biang', 'name' => '比昂', 'value' => ''],
|
['key' => 'device-supplier-biang', 'name' => '比昂', 'value' => ''],
|
||||||
['key' => 'device-supplier-yunfei', 'name' => '云飞', 'value' => ''],
|
['key' => 'device-supplier-yunfei', 'name' => '云飞', 'value' => ''],
|
||||||
|
['key' => 'device-supplier-yidong', 'name' => '移动', 'value' => ''],
|
||||||
|
['key' => 'device-supplier-dianxing', 'name' => '电信', 'value' => ''],
|
||||||
['key' => 'device-supplier-other', 'name' => '其它', 'value' => ''],
|
['key' => 'device-supplier-other', 'name' => '其它', 'value' => ''],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,7 @@ Route::group([
|
||||||
Route::apiResource('devices', DeviceController::class)->names('device');
|
Route::apiResource('devices', DeviceController::class)->names('device');
|
||||||
Route::get('devices/{device}/worm-statics', [DeviceController::class, 'wormStatics']);
|
Route::get('devices/{device}/worm-statics', [DeviceController::class, 'wormStatics']);
|
||||||
Route::get('devices/{device}/worm-photos', [DeviceController::class, 'wormPhotos']);
|
Route::get('devices/{device}/worm-photos', [DeviceController::class, 'wormPhotos']);
|
||||||
|
Route::get('devices/{device}/live', [DeviceController::class, 'live']);
|
||||||
Route::put('devices-update-recommend/{device}', [DeviceController::class, 'updateRecommendStatus']);
|
Route::put('devices-update-recommend/{device}', [DeviceController::class, 'updateRecommendStatus']);
|
||||||
Route::get('devices-num', [DeviceController::class, 'typeStatusNum'])->name('device.type_status_num');
|
Route::get('devices-num', [DeviceController::class, 'typeStatusNum'])->name('device.type_status_num');
|
||||||
Route::get('monitoring-data', [DeviceController::class, 'timeZoneList']);
|
Route::get('monitoring-data', [DeviceController::class, 'timeZoneList']);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue