Files
cunkebao_v3/Server/application/cunkebao/controller/workphone/GetWorkPhoneStatusController.php
Manus AI 7793093d0a fix: workphone BFF 在 SDK 不可用时降级返回 200
connection/status 不再因 workphone.quwanzhi.com 502 报 500;补 20260530 部署进度。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-30 22:03:04 +08:00

467 lines
15 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace app\cunkebao\controller\workphone;
use app\common\util\WorkPhoneSDK;
use app\cunkebao\controller\BaseController;
use library\ResponseHelper;
/**
* 工作手机SDK状态控制器
*
* 聚合 SDK 服务端的健康状态、连接设备列表、连接协议信息,
* 供存客宝前端「工作手机管理」页面使用。
*/
class GetWorkPhoneStatusController extends BaseController
{
/**
* 获取 SDK 总览(健康 + 设备列表 + 连接状态)
*/
public function index()
{
try {
$sdk = WorkPhoneSDK::getInstance();
$overview = $this->fetchWorkbenchOverview($sdk);
if ($overview) {
return ResponseHelper::success($overview);
}
$health = $sdk->healthCheck();
$devices = $sdk->getDevices();
$connStatus = $this->fetchConnectionStatus($sdk);
$deviceList = $devices['data'] ?? [];
if (!is_array($deviceList)) {
$deviceList = [];
}
$onlineCount = 0;
$offlineCount = 0;
foreach ($deviceList as &$d) {
$status = $d['status'] ?? 'unknown';
if ($status === 'online') {
$onlineCount++;
} else {
$offlineCount++;
}
}
unset($d);
return ResponseHelper::success([
'sdk_online' => ($health['status'] ?? '') === 'healthy',
'sdk_version' => $health['version'] ?? '-',
'ws_online' => $health['devices_online'] ?? 0,
'adb_count' => $health['adb_devices'] ?? 0,
'device_total' => count($deviceList),
'device_online' => $onlineCount,
'device_offline' => $offlineCount,
'devices' => $deviceList,
'connection' => $connStatus,
'summary' => [
'sdk_online' => ($health['status'] ?? '') === 'healthy',
'sdk_version' => $health['version'] ?? '-',
'ws_online' => $health['devices_online'] ?? 0,
'adb_count' => $health['adb_devices'] ?? 0,
'device_total' => count($deviceList),
'device_online' => $onlineCount,
'device_offline' => $offlineCount,
],
'docs' => [],
'architecture_assets' => [],
]);
} catch (\Exception $e) {
return ResponseHelper::success([
'sdk_online' => false,
'sdk_version' => '-',
'ws_online' => 0,
'adb_count' => 0,
'device_total' => 0,
'device_online' => 0,
'device_offline' => 0,
'devices' => [],
'connection' => null,
'summary' => null,
'docs' => [],
'architecture_assets' => [],
'error' => $e->getMessage(),
]);
}
}
/**
* 获取单台设备详情
*/
public function detail()
{
try {
$deviceId = $this->request->param('device_id');
if (!$deviceId) {
return ResponseHelper::error('缺少 device_id');
}
$sdk = WorkPhoneSDK::getInstance();
$result = $sdk->getDevice($deviceId);
return ResponseHelper::success($result['data'] ?? $result);
} catch (\Exception $e) {
return ResponseHelper::error($e->getMessage());
}
}
/**
* 向设备发送截屏指令
*/
public function screenshot()
{
try {
$deviceId = $this->request->param('device_id');
if (!$deviceId) {
return ResponseHelper::error('缺少 device_id');
}
$sdk = WorkPhoneSDK::getInstance();
$result = $sdk->screenshot($deviceId);
return ResponseHelper::success($result['data'] ?? $result);
} catch (\Exception $e) {
return ResponseHelper::error($e->getMessage());
}
}
/**
* 获取设备 AI Brain 状态
*/
public function aiStatus()
{
try {
$deviceId = $this->request->param('device_id');
if (!$deviceId) {
return ResponseHelper::error('缺少 device_id');
}
$sdk = WorkPhoneSDK::getInstance();
$result = $sdk->getAIBrainStatus($deviceId);
return ResponseHelper::success($result['data'] ?? $result);
} catch (\Exception $e) {
return ResponseHelper::error($e->getMessage());
}
}
/**
* 推送 AI 任务到设备队列
*/
public function aiTask()
{
try {
$deviceId = $this->request->param('device_id');
$instruction = $this->request->param('instruction', '');
$priority = (int) $this->request->param('priority', 5);
if (!$deviceId) {
return ResponseHelper::error('缺少 device_id');
}
if ($instruction === '') {
return ResponseHelper::error('缺少 instruction');
}
$sdk = WorkPhoneSDK::getInstance();
$result = $sdk->pushAITask($deviceId, $instruction, $priority);
return ResponseHelper::success($result['data'] ?? $result);
} catch (\Exception $e) {
return ResponseHelper::error($e->getMessage());
}
}
/**
* 推送 AI 常驻指令
*/
public function aiStandingOrder()
{
try {
$deviceId = $this->request->param('device_id');
$order = $this->request->param('order', '');
if (!$deviceId) {
return ResponseHelper::error('缺少 device_id');
}
if ($order === '') {
return ResponseHelper::error('缺少 order');
}
$sdk = WorkPhoneSDK::getInstance();
$result = $sdk->pushAIStandingOrder($deviceId, $order);
return ResponseHelper::success($result['data'] ?? $result);
} catch (\Exception $e) {
return ResponseHelper::error($e->getMessage());
}
}
/**
* 同步执行 AI 自然语言任务
*/
public function aiExecute()
{
try {
$deviceId = $this->request->param('device_id');
$task = $this->request->param('task', '');
$timeout = (int) $this->request->param('timeout', 60);
if (!$deviceId) {
return ResponseHelper::error('缺少 device_id');
}
if ($task === '') {
return ResponseHelper::error('缺少 task');
}
$sdk = WorkPhoneSDK::getInstance();
$result = $sdk->executeAITask($deviceId, $task, $timeout);
return ResponseHelper::success($result['data'] ?? $result);
} catch (\Exception $e) {
return ResponseHelper::error($e->getMessage());
}
}
/**
* 通过 SDK 拉取连接协议状态
*/
private function fetchConnectionStatus(WorkPhoneSDK $sdk): ?array
{
try {
$json = $this->fetchJson($sdk, '/api/v3/connection/status');
return $json['data'] ?? null;
} catch (\Exception $e) {
return null;
}
}
/**
* 工作台统一概览 DTO
*/
private function fetchWorkbenchOverview(WorkPhoneSDK $sdk): ?array
{
try {
$json = $this->fetchJson($sdk, '/api/v3/workbench/overview');
$data = $json['data'] ?? null;
if (!$data) {
return null;
}
return $this->normalizeWorkbenchPayload($sdk, $data);
} catch (\Exception $e) {
return null;
}
}
/**
* 拉取 SDK JSON
*/
private function fetchJson(WorkPhoneSDK $sdk, string $path): ?array
{
$baseUrl = $this->getBaseUrl($sdk);
$url = rtrim($baseUrl, '/') . $path;
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 5,
CURLOPT_HTTPHEADER => ['Accept: application/json'],
]);
$raw = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($code === 200 && $raw) {
return json_decode($raw, true);
}
return null;
}
/**
* 统一把工作手机 SDK 返回的相对链接补全为绝对链接
*/
private function normalizeWorkbenchPayload(WorkPhoneSDK $sdk, array $data): array
{
$baseUrl = rtrim($this->getBaseUrl($sdk), '/');
if (!empty($data['architecture_assets']) && is_array($data['architecture_assets'])) {
foreach ($data['architecture_assets'] as &$asset) {
if (!empty($asset['url']) && strpos($asset['url'], 'http') !== 0) {
$asset['url'] = $baseUrl . $asset['url'];
}
}
unset($asset);
}
if (!empty($data['docs']) && is_array($data['docs'])) {
foreach ($data['docs'] as &$doc) {
if (!empty($doc['raw_url']) && strpos($doc['raw_url'], 'http') !== 0) {
$doc['raw_url'] = $baseUrl . $doc['raw_url'];
}
}
unset($doc);
}
$data['sdk_base_url'] = $baseUrl;
return $data;
}
/**
* 反射获取 SDK baseUrlSDK 构造函数从 env/config 读取)
*/
private function getBaseUrl(WorkPhoneSDK $sdk): string
{
$ref = new \ReflectionClass($sdk);
$prop = $ref->getProperty('baseUrl');
$prop->setAccessible(true);
return $prop->getValue($sdk);
}
/**
* GET /v1/workphone/hook/events — 代理 SDK Hook 事件列表
*/
public function hookEvents()
{
try {
$deviceId = $this->request->param('device_id', null);
$eventType = $this->request->param('event_type', null);
$limit = (int) $this->request->param('limit/d', 50);
$sdk = WorkPhoneSDK::getInstance();
$resp = $sdk->listHookEvents($deviceId, $eventType, $limit);
if (($resp['code'] ?? 500) !== 200) {
return ResponseHelper::error($resp['message'] ?? '拉取失败', (int) ($resp['code'] ?? 500));
}
return ResponseHelper::success($resp['data'] ?? []);
} catch (\Exception $e) {
return ResponseHelper::error($e->getMessage(), 500);
}
}
/**
* POST /v1/workphone/hook/events/sync — 拉取并处理 Hook 事件
*/
public function hookEventsSync()
{
try {
$deviceId = $this->request->param('device_id', null);
$eventType = $this->request->param('event_type', null);
$limit = (int) $this->request->param('limit/d', 50);
$result = \app\common\util\WorkPhoneEventBridge::syncFromSdk($deviceId, $eventType, $limit);
if (($result['code'] ?? 500) !== 200) {
return ResponseHelper::error($result['message'] ?? '同步失败', (int) ($result['code'] ?? 500));
}
return ResponseHelper::success($result['data'] ?? []);
} catch (\Exception $e) {
return ResponseHelper::error($e->getMessage(), 500);
}
}
/** GET /v1/workphone/connection/status */
public function connectionStatus()
{
try {
$resp = WorkPhoneSDK::getInstance()->getConnectionStatus();
if (($resp['code'] ?? 500) !== 200) {
return ResponseHelper::success([
'connected' => false,
'websocket' => false,
'frida' => false,
'sdk_online' => false,
'error' => $resp['message'] ?? 'SDK 不可用',
]);
}
return ResponseHelper::success($resp['data'] ?? $resp);
} catch (\Exception $e) {
return ResponseHelper::success([
'connected' => false,
'sdk_online' => false,
'error' => $e->getMessage(),
]);
}
}
/** GET /v1/workphone/hook/probe?device_id= */
public function hookProbe()
{
try {
$deviceId = (string) $this->request->param('device_id', '');
if ($deviceId === '') {
return ResponseHelper::error('device_id 不能为空', 400);
}
$resp = WorkPhoneSDK::getInstance()->hookProbe($deviceId);
return ResponseHelper::success($resp);
} catch (\Exception $e) {
return ResponseHelper::error($e->getMessage(), 500);
}
}
/** POST /v1/workphone/message/send */
public function messageSend()
{
try {
$deviceId = (string) $this->request->param('device_id', '');
$platform = (string) $this->request->param('platform', 'wechat');
$toId = (string) $this->request->param('to_id', '');
$content = (string) $this->request->param('content', '');
$msgType = (string) $this->request->param('msg_type', 'text');
$channel = $this->request->param('channel', null);
if ($deviceId === '' || $toId === '' || $content === '') {
return ResponseHelper::error('device_id / to_id / content 不能为空', 400);
}
$resp = WorkPhoneSDK::getInstance()->sendMessage(
$deviceId,
$platform,
$toId,
$content,
$msgType,
$channel !== null ? (string) $channel : null
);
if (empty($resp['success']) && ($resp['code'] ?? 200) !== 200) {
return ResponseHelper::error($resp['error'] ?? $resp['message'] ?? '发送失败', 502);
}
return ResponseHelper::success($resp);
} catch (\Exception $e) {
return ResponseHelper::error($e->getMessage(), 500);
}
}
/** POST /v1/workphone/hook/execute */
public function hookExecute()
{
try {
$deviceId = (string) $this->request->param('device_id', '');
$platform = (string) $this->request->param('platform', 'wechat');
$action = (string) $this->request->param('action', '');
$params = $this->request->param('params/a', []);
if (!is_array($params)) {
$params = [];
}
// SDK 要求 params 为 object空数组在 JSON 会变成 [] 导致校验失败
if ($params === []) {
$params = new \stdClass();
}
$hookOnly = (bool) $this->request->param('hook_only/d', 1);
if ($deviceId === '' || $action === '') {
return ResponseHelper::error('device_id / action 不能为空', 400);
}
$resp = WorkPhoneSDK::getInstance()->hookExecute(
$deviceId,
$platform,
$action,
$params,
$hookOnly
);
return ResponseHelper::success($resp);
} catch (\Exception $e) {
return ResponseHelper::error($e->getMessage(), 500);
}
}
}