139 lines
5.5 KiB
Python
139 lines
5.5 KiB
Python
"""
|
||
设备连接方案 · 可切换驱动 API
|
||
|
||
供存客宝四端(存客宝/触客宝/AI数智员工/SuperAdmin)与超管 UI 切换「设备连接解决方案」:
|
||
- 列出所有方案 / 当前生效方案
|
||
- 一键切换(全局 / 按项目 / 按设备)
|
||
- 注册/更新/删除自定义方案(预留接口,新方案无需改代码)
|
||
- 健康检查 + 统一执行(验证切换后功能可用)
|
||
|
||
真源:sdk/app/services/connection_provider.py
|
||
文档:开发文档/1、需求/修改/工作手机_设备Agent与基础设施_20260529.md §3.6
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from typing import Any, Dict, Optional
|
||
|
||
from fastapi import APIRouter, HTTPException
|
||
from pydantic import BaseModel, Field
|
||
|
||
from services.connection_provider import (
|
||
connection_provider_manager as mgr,
|
||
SCOPE_GLOBAL,
|
||
)
|
||
|
||
router = APIRouter()
|
||
|
||
|
||
class SwitchRequest(BaseModel):
|
||
provider: str = Field(..., description="目标连接方案 id:jiqing / aochuang / legacy / custom_*")
|
||
scope: str = Field(default=SCOPE_GLOBAL, description="作用域:global / project / device")
|
||
project_id: str = ""
|
||
device_id: str = ""
|
||
|
||
|
||
class RegisterProviderRequest(BaseModel):
|
||
id: str = Field(..., description="方案 id(自定义建议 custom_ 前缀)")
|
||
name: str = ""
|
||
kind: str = Field(default="http", description="native / http")
|
||
enabled: bool = True
|
||
desc: str = ""
|
||
base_url: str = ""
|
||
base_url_env: str = ""
|
||
auth: Optional[Dict[str, Any]] = None
|
||
health: Optional[Dict[str, Any]] = None
|
||
endpoints: Optional[Dict[str, Any]] = None
|
||
|
||
|
||
class ProviderExecuteRequest(BaseModel):
|
||
device_id: str
|
||
script: str = Field(default="wechat", description="平台:wechat/douyin/xhs/...")
|
||
action: str = Field(..., description="规范化动作或平台 action")
|
||
params: Dict[str, Any] = Field(default_factory=dict)
|
||
provider: str = Field(default="", description="强制指定方案;空则按开关解析")
|
||
project_id: str = ""
|
||
timeout: int = 120
|
||
|
||
|
||
@router.get("/connection/providers")
|
||
async def list_connection_providers() -> Dict[str, Any]:
|
||
"""列出所有设备连接方案 + 当前开关。"""
|
||
return {
|
||
"code": 200,
|
||
"data": {
|
||
"providers": mgr.list_providers(),
|
||
"active": mgr.active_config(),
|
||
"scopes": ["global", "project", "device"],
|
||
"doc": "开发文档/1、需求/修改/工作手机_设备Agent与基础设施_20260529.md §3.6",
|
||
},
|
||
}
|
||
|
||
|
||
@router.get("/connection/provider/active")
|
||
async def get_active_provider(device_id: str = "", project_id: str = "") -> Dict[str, Any]:
|
||
"""解析指定作用域当前生效的连接方案(设备 > 项目 > 全局)。"""
|
||
pid = mgr.resolve_active_id(device_id=device_id, project_id=project_id)
|
||
prov = mgr.get(pid)
|
||
return {"code": 200, "data": {
|
||
"active_provider": pid,
|
||
"device_id": device_id,
|
||
"project_id": project_id,
|
||
"meta": prov.meta() if prov else None,
|
||
}}
|
||
|
||
|
||
@router.post("/connection/provider/switch")
|
||
async def switch_connection_provider(req: SwitchRequest) -> Dict[str, Any]:
|
||
"""一键切换设备连接方案(开关)。"""
|
||
try:
|
||
result = mgr.switch(req.provider, scope=req.scope,
|
||
project_id=req.project_id, device_id=req.device_id)
|
||
except ValueError as e:
|
||
raise HTTPException(status_code=400, detail=str(e))
|
||
return {"code": 200, "message": f"已切换到连接方案 {req.provider}", "data": result}
|
||
|
||
|
||
@router.post("/connection/provider/register")
|
||
async def register_connection_provider(req: RegisterProviderRequest) -> Dict[str, Any]:
|
||
"""注册/更新连接方案(预留接口:自定义工作手机方案无需改代码即可接入)。"""
|
||
try:
|
||
meta = mgr.register(req.model_dump(exclude_none=True))
|
||
except ValueError as e:
|
||
raise HTTPException(status_code=400, detail=str(e))
|
||
return {"code": 200, "message": f"连接方案 {req.id} 已登记", "data": meta}
|
||
|
||
|
||
@router.delete("/connection/provider/{provider_id}")
|
||
async def delete_connection_provider(provider_id: str) -> Dict[str, Any]:
|
||
"""删除自定义连接方案(内置方案不可删,改用禁用)。"""
|
||
try:
|
||
result = mgr.remove(provider_id)
|
||
except ValueError as e:
|
||
raise HTTPException(status_code=400, detail=str(e))
|
||
return {"code": 200, "message": f"连接方案 {provider_id} 已删除", "data": result}
|
||
|
||
|
||
@router.get("/connection/provider/{provider_id}/health")
|
||
async def provider_health(provider_id: str) -> Dict[str, Any]:
|
||
"""连接方案健康/连通性检查。"""
|
||
prov = mgr.get(provider_id)
|
||
if not prov:
|
||
raise HTTPException(status_code=404, detail=f"未知连接方案: {provider_id}")
|
||
return {"code": 200, "data": await prov.health()}
|
||
|
||
|
||
@router.post("/connection/provider/execute")
|
||
async def provider_execute(req: ProviderExecuteRequest) -> Dict[str, Any]:
|
||
"""经当前(或指定)连接方案统一执行——验证切换后功能可用。"""
|
||
if req.provider:
|
||
prov = mgr.get(req.provider)
|
||
if not prov:
|
||
raise HTTPException(status_code=404, detail=f"未知连接方案: {req.provider}")
|
||
else:
|
||
prov = mgr.resolve_active(device_id=req.device_id, project_id=req.project_id)
|
||
result = await prov.execute(req.device_id, req.script, req.action, req.params, timeout=req.timeout)
|
||
if isinstance(result, dict):
|
||
result.setdefault("provider", prov.id)
|
||
return {"code": result.get("code", 200) if isinstance(result, dict) else 200, "data": result}
|