Files
auto_control/doc/API.md
T
butubb 3f041668ec feat: 设备配置二维码——设备池里出码,Agent 扫一下配好
配合设备端 Agent 新增的「扫码配置」:人在机架前、设备还没接进平台时,
不用插线也不用在手机上打字输地址/令牌(还容易漏指纹)。

- `GET /api/devices/qrcode?serial=`:生成配置二维码(PNG data-url),内容为
  {"v":1,"server":…,"token":…,"fingerprint":…,"name":…}
  * 前置:设备端商店已启用(令牌从那儿来),否则 400 并说明去哪儿开
  * `warn` 非空必须显示:①用 localhost 打开平台时,二维码里的地址手机会连不上
    (提示改用局域网地址,_lan_ip() 优先挑 192.168/10.x,**不用** gethostbyname ——
     那会返回 Tailscale 的 100.x 或 docker 的 172.17,手机根本连不上)
    ②设备还没采到指纹 → 扫完平台认不出是哪台
- 设备池每行加「二维码」按钮 → 弹窗显示二维码 + 设备名/地址 + 可展开看原始内容
- requirements 加 qrcode(纯 Python,配合已有的 Pillow 出 PNG,不引前端二维码库)
- 文档:API.md §6.2.2 新增、§6.2.1 名称呈现表补齐(剪贴板/安装弹窗/版本管理/
  安装进度四处);DEVICE_AGENT.md §5.3 新增二维码内容格式(设备端契约)

验证:二维码用 cv2.QRCodeDetector 解回来与 payload 完全一致;手机上实测——
扫 A07 的码 → 相机启动 → 解码 → 保存 → 立刻拉清单,平台认出「A07」。
2026-09-13 16:42:53 +08:00

40 KiB
Raw Blame History

HTTP 接口文档(API)

适用读者:前端开发、外部接入方、排接口问题的运维。 相关文档:ARCHITECTURE.md(分层与装配)、DATA_MODEL.md(数据)、MCP.md(给 AI 的工具层,不是 HTTP)。 代码位置:全部路由在 web/ 包下,10 个蓝图,全部 url_prefix 为空(路径即代码里写的路径)。


目录


1. 通用约定

1.1 认证

  • 基于 Flask-Login session(Cookie)。
  • 未登录访问受保护接口 → 302 重定向到 /login?next=<原路径>(不是 401 JSON)。前端 apiGet/apiPost/... 收到 401/302 会跳登录页。
  • 登录:POST /login(表单 username / password)。失败返回 HTTP 200 + HTML 登录页(带错误文案,不区分"用户不存在/密码错",防枚举)。

1.2 权限

装饰器 语义 未通过时
@login_required 登录即可 302 → /login
@perm_required(PERM_X) 需权限位 tasks / devices / apks / logs 403 {"ok":false,"error":"无权限执行此操作(需要权限: …)"}
@admin_required 仅管理员 403 {"ok":false,"error":"仅管理员可执行此操作"}

管理员 is_admin=true 恒通过权限位检查。前端用 data-perm / _can(perm) 隐藏入口,只是体验优化,安全依赖后端。

1.3 CSRF

  • GET /api/csrf 取 token(存在 session);前端对所有非 GET 请求带 X-CSRF-Token。
  • ⚠️ 当前服务端并未强制校验(web_server.py 只 import 了 _csrf_protect,没有注册 before_request)。即 token 已签发、前端已携带,但伪造请求不会被拦。见 §16 已知问题。

1.4 响应约定

  • 绝大多数接口返回 JSON:成功 {"ok": true, ...},失败 {"ok": false, "error": "中文原因"}(部分接口额外带 msg)。
  • HTTP 状态码与 ok 并存:参数错误用 400,权限 403,不存在 404,冲突 409,依赖不可用 502/503。少数接口用 {"ok":false} + HTTP 200(如 POST /api/jobs/<id>/run 的"任务类型不存在")。以本文每节标注为准。
  • 时间统一字符串:"YYYY-MM-DD HH:MM"(展示)或 "YYYY-MM-DD HH:MM:SS"(cron 相关)。
  • 非 JSON 接口(HTML / 图片 / SSE / MJPEG / zip)见 §14。

2. 接口总索引

共 107 条路由。鉴权 列:— 无、L 登录、T/D/A/G = tasks/devices/apks/logs 权限位、Admin 仅管理员。

2.1 auth(web/auth.py)

方法 路径 鉴权 功能
GET / L 单页应用首页(HTML)
GET /wall L 监控大屏页面(HTML)
GET /login — 登录页(HTML)
POST /login — 登录(表单;成功 302,失败 200 + HTML)
GET /logout L 登出 → 302 /login
GET /api/csrf L 取/生成 CSRF token
GET /api/me L 当前用户信息(id/username/is_admin/perms)

2.2 monitor(web/monitor.py)

方法 路径 鉴权 功能
GET /api/status L 设备全量状态 + 服务器时间 + 前台扫描状态
GET /api/summary L 状态计数 + 异常设备列表(最多 50)
GET /api/health — 健康检查(免登录探活)
POST /api/scan_foreground D 触发前台 App 扫描
GET /api/devices L 在线设备列表:devices(serial 列表) + items([{serial,name,model}],界面显示设备名用)
GET /api/devices/<serial>/apps L 指定设备已安装应用列表
GET /api/device/screenshot L 单张设备截图(PNG)
GET /api/screen/stream D 远程看屏 MJPEG 流
GET /api/screen/thumb D 大屏缩略图(JPEG,带头 X-Screen-State)
GET /api/screen/size D 屏幕原生分辨率
POST /api/screen/tap D 远程点击(可 snap=1 吸附元素)
POST /api/screen/swipe D 远程滑动
POST /api/screen/key D 远程按键(白名单)
POST /api/screen/text D 远程输入文字
POST /api/screen/tap_text D 按屏幕文字点击(UI 树 → OCR 兜底)
POST /api/stop_device D 停止单台设备任务
POST /api/stop_all D 停止全部任务
POST /api/device/clear_error D 清除单台设备异常状态
POST /api/device/clear_all_errors D 清除全部异常(跳过运行中)
POST /api/device/screen_all D 批量亮屏/息屏
POST /api/device/locate D 点亮屏幕 + 在设备上打开定位大字页
POST /api/device/locate/stop D 结束定位
GET /locate — 设备端定位大字页(HTML,免登录)

2.3 tasks(web/tasks_api.py)

方法 路径 鉴权 功能
GET /api/task_types L 全部已注册任务类型
GET /api/actions L 指定 task_type 支持的专属操作
GET /api/jobs L 任务计划列表(含 next_run、coverage)
POST /api/jobs T 新建任务
PUT /api/jobs/<job_id> T 更新任务(白名单字段)
DELETE /api/jobs/<job_id> T 删除任务
POST /api/jobs/<job_id>/run T 立即执行(异步)
POST /api/jobs/<job_id>/toggle T 启用/停用
GET /api/groups L 分组列表
POST /api/groups T 新建分组
PUT /api/groups/<name> T 更新分组
DELETE /api/groups/<name> T 删除分组
GET /api/custom_actions L 自定义动作列表
POST /api/custom_actions T 新建自定义动作
PUT /api/custom_actions/<action_id> T 更新自定义动作
DELETE /api/custom_actions/<action_id> T 删除自定义动作
GET /api/uiauto/status L uiautodev 服务是否在跑
GET /api/uiauto/devices D 抓元素可选设备
GET /api/uiauto/elements D 设备 UI 元素树
GET /api/uiauto/screenshot D uiautodev 截图(JPEG)
POST /api/steps/test D 真机试执行单个步骤

2.4 admin(web/admin_api.py)

方法 路径 鉴权 功能
GET /api/users Admin 用户列表
POST /api/users Admin 新建用户
PUT /api/users/<int:uid> Admin 更新用户(改密/权限/管理员)
DELETE /api/users/<int:uid> Admin 删除用户
GET /api/logs G 读日志文件尾部 N 行

2.5 devices(web/devices_api.py)

方法 路径 鉴权 功能
GET /api/devices/pool D 设备池清单(附实时在线状态 + 设备指纹)
POST /api/devices/pool/add D 添加/更新设备(名称必填唯一,按指纹自动认领换 IP 的设备)
POST /api/devices/pool/rename D 重命名设备(名称唯一)
POST /api/devices/pool/relocate D 人工认领:把记录迁到新地址并同步分组/任务引用
POST /api/devices/pool/remove D 从池中删除
POST /api/devices/pool/toggle D 启用/停用(停用不参与调度)
POST /api/devices/pool/reconnect D 一键重连全部池内网络设备
GET /api/devices/qrcode?serial= D 生成该设备的配置二维码(设备端 Agent 扫码即配好)
POST /api/devices/pool/refresh_models D 批量采集型号
GET /api/devices/discovery D 发现状态 + 待连接 + 断联列表
POST /api/devices/discovery/scan D 手动触发一轮扫描
POST /api/devices/discovery/confirm D 确认连接(待连接 → 设备池)
POST /api/devices/discovery/ignore D 忽略待连接设备
POST /api/devices/discovery/reconnect D 立即重连指定断联设备
POST /api/devices/discovery/settings D 保存发现配置

2.6 apks(web/apks_api.py)

方法 路径 鉴权 功能
GET /api/apks L APK 列表
POST /api/apks/upload A 上传 APK(multipart,字段 file)
DELETE /api/apks/<apk_id> A 删除 APK
POST /api/apks/install A 批量安装到指定设备
GET /api/apks/install/devices A 可安装设备列表
GET /api/apks/install/status L 安装进度

2.7 tools(web/tools_api.py)

方法 路径 鉴权 功能
GET /api/adb/devices Admin 维护终端设备列表(本机 adb + 设备池)
POST /api/adb/cmd Admin 执行 adb 命令(20s 超时;拦截 kill-server/disconnect)
POST /api/tools/clipboard/set Admin 剪贴板注入到多台设备
POST /api/tools/appver Admin 查指定包名在所有在线设备的版本

2.8 tailscale(web/tailscale_api.py,全部 Admin)

方法 路径 功能
GET /api/tailscale/status 配置状态(key/tailnet 是否就绪)
GET /api/tailscale/devices tailnet 设备列表
POST /api/tailscale/devices/<device_id> 更新设备(按字段分发:改名 / 授权 / 密钥不过期)
POST /api/tailscale/devices/<device_id>/ip 设置设备 IPv4(会断开 tailscale 会话)
DELETE /api/tailscale/devices/<device_id> 从 tailnet 移除
POST /api/tailscale/authkey 生成设备接入 auth key

2.9 agent(web/agent_api.py,全部 Admin)

方法 路径 功能
GET/POST /api/agent/config 读写 AI 配置(key 打码回显)
GET /api/agent/devices AI 可用设备(含名称与 busy 标记)
POST /api/agent/run 启动一轮 AI 会话
GET /api/agent/run 运行状态(刷新恢复用)
GET /api/agent/stream SSE 事件流
POST /api/agent/stop 中断当前运行
POST /api/agent/clear 清空对话历史
GET/POST /api/agent/conversations 会话列表 / 新建会话
GET/DELETE /api/agent/conversations/<conv_id> 会话详情 / 删除
POST /api/agent/conversations/<conv_id>/rename 重命名会话
GET /api/agent/experience 经验库列表 + 巡检状态
POST /api/agent/experience/audit 手动触发巡检
POST /api/agent/experience/delete 删除经验(人工确认)
POST /api/agent/experience/keep 保留经验(撤销建议删除)
GET /api/agent/actions 动作库列表
POST /api/agent/actions/save 新增/编辑动作(禁坐标)
POST /api/agent/actions/delete 删除动作

2.10 system(web/system_api.py,全部 Admin)

方法 路径 功能
POST /api/system/backup/export 导出 zip(JSON body)
POST /api/system/backup/preview 上传备份校验预览(multipart,字段 file)
POST /api/system/backup/apply 应用恢复(重启生效)

2.11 device_agent(web/device_agent_api.py)

设备端专用(无登录会话,靠 X-Device-Token 鉴权;平台未启用时统一 404):

方法 路径 功能
GET /api/device/agent/bootstrap 设备拉自己的身份 + 可安装应用清单
GET /api/device/agent/apk/<apk_id> 下载 APK 文件
POST /api/device/agent/report 上报下载/安装结果

管理端(登录 + 应用管理权限):

方法 路径 功能
GET /api/agent-store/config 读配置(含设备令牌)
POST /api/agent-store/config 启用/停用、重置令牌
GET /api/agent-store/logs 设备端安装记录

完整协议、鉴权与版本约定见 DEVICE_AGENT.md。


3. 认证与页面

POST /login

表单 username / password。

  • 成功:login_user + 写 session 的 CSRF token → 302 到 ?next= 或 /
  • 失败:200 + 登录页 HTML(含"用户名或密码错误")

GET /logout · GET /api/csrf · GET /api/me

// GET /api/csrf
{"ok": true, "token": "…"}
// GET /api/me
{"ok": true, "user": {"id": 1, "username": "admin", "is_admin": true,
                      "perms": ["tasks","devices","apks","logs"]}}

管理员返回全部权限位。页面:GET /(单页应用)、GET /wall(大屏)、GET /locate(设备端定位页,免登录,只显示 serial 文本)。


4. 设备状态与运行控制

GET /api/status

监控页 5s 轮询的主接口。

{"ok": true, "server_time": 1700000000.0, "fg_scanning": false, "fg_last_scan": 0,
 "devices": [{
   "serial": "192.168.20.206:5555", "model": "22120RN86C", "device_name": "", "present": true, "ready": true,
   "worker_status": "running", "task_job": "测试抖音评论", "attempt": 1, "max_attempts": 1,
   "current_action": "点击搜索", "progress": {"done": 5, "total": 0, "unit": "操作", "elapsed": 42},
   "foreground_app": "抖音", "last_error": "", "last_warning": "", "end_time": 0
 }]}

worker_status:idle / connecting / running / done / error / released / failed。

GET /api/summary

{"ok": true, "counts": {"running":1,"done":2,"error":0,"idle":2,"failed":0},
 "errors": [{"serial":"…","task":"…","last_error":"…","attempt":3,"updated":1700000000.0}]}

GET /api/health

免登录探活:{"ok":true,"status":"up","time":…,"device_total":3,"device_online":3,"device_running":0,"device_error":0,"jobs":2}(组件异常时 status:"down" + 500)。

运行控制

接口 请求 说明
POST /api/stop_device {"serial":"…"} 无运行中任务 → 400
POST /api/stop_all — 停止全部
POST /api/device/clear_error {"serial":"…"} 运行中/重试等待中拒绝清理
POST /api/device/clear_all_errors — 跳过 running/connecting
POST /api/device/screen_all {"mode":"on"|"off", "serials":[…]} 不传 serials 则对全部在线设备
POST /api/scan_foreground — 已扫描中时返回 {"ok":false}(HTTP 200)
POST /api/device/locate {"serial":"…","show":true} show=true 时在设备上打开 /locate
POST /api/device/locate/stop {"serial":"…"} 结束定位
GET /api/devices — {"ok":true,"devices":["100.100.10.x:5555", …]}
GET /api/devices/<serial>/apps — 该设备已安装应用(包名/版本/路径)

⚠️ /api/device/locate 的 show=true 分支当前会失败(urllib 未导入 → 502);GET /locate 本身返回 500。见 §16。


5. 任务类型 / 任务计划 / 分组 / 自定义动作

GET /api/task_types

{"ok": true, "task_types": [
  {"task_type": "generic_steps", "name": "通用步骤", "description": "…",
   "default_params": {"max_duration": 0}}]}

当前平台只有 generic_steps 一种类型。default_params 不含 steps——步骤只能由编辑器产出。

GET /api/actions?task_type=…

返回该任务类型支持的"专属操作"列表(generic_steps 返回步骤类型清单 STEP_TYPES)。

GET /api/jobs

{"ok": true, "jobs": [{
  "id": "abc123", "name": "刷视频-上午", "task_type": "generic_steps", "enabled": true,
  "target": {"mode": "all"}, "params": {"max_duration": 0, "steps": [ … ]},
  "schedule": {"mode": "once"}, "retry": {"max_attempts": 1, "delay": 60},
  "next_run": "2026-09-11 09:00",
  "coverage": {"mode": "all", "total": 3, "serials": ["192.168.20.206:5555", "…"]}
}], "task_types": [ … ]}
  • next_run:下次真正执行时间(已按运行窗口跳过窗口外触发点);手动/停用为 null
  • coverage:任务覆盖的设备(监控页「任务运行概况」展示用,每次请求现算)——按 target 定义解析,不因设备当前是否空闲而变,也不做离线过滤、不写调度日志:
target.mode coverage.serials
all 设备池全部启用设备
group 分组的 serial ∩ 设备池启用设备(池外的手填 IP 不计入)
serial 仅该 serial(即使不在池中也照实返回)

与调度口径的差异:真正跑的时候走 TaskJob.resolve_serials()(all 取"池内 ∩ 在线",serial/group 默认跳过离线设备)。coverage 是"定义层覆盖面",供人看"这台任务管哪些设备"。

POST /api/jobs

{"name": "刷视频-上午", "task_type": "generic_steps",
 "target": {"mode": "all"},
 "params": {"max_duration": 0, "steps": [{"id":"step_1","type":"open_app","label":"打开抖音",
                                          "params":{"package":"com.ss.android.ugc.aweme"}}]},
 "schedule": {"mode": "cron", "cron": "0 9 * * *"},
 "retry": {"max_attempts": 3, "delay": 60}, "enabled": true}

响应:{"ok": true, "msg": "任务已创建", "job": {…含 coverage…}}

schedule 字段:

字段 说明
mode once 手动 / cron 定时启动 / cron_stop 定时启动+停止
cron 标准 5 段 分 时 日 月 周(周 0/7 = 周日),如 0 */2 * * *
stop_cron cron_stop 必填,到点停止本任务
window 可选运行窗口 {"start":"21:00","end":"09:00"}(支持跨午夜)。窗口外定时与手动执行都不启动

params.steps 要一起传:后端不提供默认步骤、也不做参数校验(编辑器是参数正确性的唯一关卡)。不传 steps 也能建成功,但执行时会立即报错"通用步骤任务没有可执行步骤"。

PUT /api/jobs/<job_id> · DELETE /api/jobs/<job_id>

  • PUT:只更新请求里出现的字段(name/task_type/target/params/schedule/retry/enabled);task_type 必须已注册,否则 400
  • DELETE:{"ok":true,"msg":"任务已删除"};不存在 404

POST /api/jobs/<job_id>/run

立即执行(起后台线程,不阻塞)。HTTP 恒 200,成功与否看 ok:

{"ok": true,  "msg": "任务 刷视频-上午 已触发"}
{"ok": false, "error": "任务类型 douyin_nurture 已不存在(该类型已被删除),请删除此任务或改用现有类型"}

POST /api/jobs/<job_id>/toggle

{"enabled": true|false} → {"ok":true,"msg":"任务已启用"};不存在 404。

分组

接口 请求 响应
GET /api/groups — {"ok":true,"groups":[{"name":"A组","serials":[…],"description":""}]}
POST /api/groups {"name","serials":[],"description"} 名字空/重名 → 400
PUT /api/groups/<name> {"serials":[],"description"} 不存在 → 404
DELETE /api/groups/<name> — 不存在 → 404

自定义动作

接口 请求 说明
GET /api/custom_actions — 列表(按创建时间倒序)
POST /api/custom_actions {"name","icon","steps":[…]} name/steps 空 → 400
PUT /api/custom_actions/<id> 同上(部分更新) 不存在 → 404
DELETE /api/custom_actions/<id> — 不存在 → 404

steps 的 schema 与 generic_steps 的 params.steps 完全一致,见 TASK_DEV.md。


6. 设备池与自动发现

6.1 设备身份:名称 + 指纹

概念 说明
名称 name 必填且唯一,设备在平台里的人可读标识(分组/任务/日志都按它认设备)
serial 设备的当前连接地址(IP:5555 或 USB 序列号)——可变
指纹 fingerprint ro.serialno,识别"同一台物理设备"的稳定标识(只对网络设备采集;USB 的 serial 本身已稳定)

为什么需要:设备池原先拿 serial(IP)当身份,设备一换 IP 就变成"陌生新设备", 旧记录永远连不上,分组与 serial 模式的任务还吊着死地址。现在:

  • 添加/确认设备时读取指纹 → 若命中池中已有设备(同一台换了地址)→ 自动认领
  • 认领 = 迁移原记录(名称/型号/备注/启用状态/添加时间全保留)+ 把 device_group.serials 与 task_job.target.serial 里的旧地址同步换成新地址(库里与内存一起改)
  • 旧地址已断联、指纹也没采过时(自动认领无从匹配)→ 用 pool/relocate 人工认领

指纹在设备在线时自动采集(添加/确认/启动刷新/「采集型号」按钮都会补); 设备列表的「指纹」列:🔑 = 已采集,— = 尚未采集(离线设备采不到)。

6.2 设备池

接口 请求 说明
GET /api/devices/pool — 池内设备 + 实时 online + fingerprint
POST /api/devices/pool/add {"serial","name","note"?} name 必填(空 → 400)、唯一(重名 → 400)。IP:5555 会轻量 adb connect 并读指纹,命中则自动认领(响应 claimed=true + old_serial)
POST /api/devices/pool/rename {"serial","name"} 改名(唯一校验;不存在 → 404)
POST /api/devices/pool/relocate {"old_serial","new_serial"} 人工认领:迁移记录 + 同步引用;旧地址不在池 → 400,新地址已在池 → 400
POST /api/devices/pool/remove {"serial"} 不存在 → 404
POST /api/devices/pool/toggle {"serial","enabled"} 停用则不参与调度
POST /api/devices/pool/reconnect — 后台并发重连全部网络设备
POST /api/devices/pool/refresh_models — 后台批量采型号 + 补齐缺失指纹

6.2.1 名称在界面上的呈现

凡"选择设备 / 展示设备"的地方都以名称为主、地址为辅:

位置 呈现
监控页设备表 名称加粗为主行,serial 作副行(未命名显示橙色"未命名"提醒)
任务运行概况的覆盖设备 chip 名称(无名才退地址),tooltip 里带完整 serial
AI 控制台「目标设备」「观看设备」下拉 名称 · serial · 型号
任务编辑器「指定设备」下拉 名称 · serial
分组编辑的设备勾选列表 名称 · serial
设备池 / 待连接池 / 断联设备表 名称列 + 指纹列
剪贴板注入的设备勾选列表 名称 · serial(无名只显示地址)
应用管理的安装弹窗设备列表 名称 · serial
应用版本管理表格的「设备」列 名称 · serial
应用安装进度表的「设备」列 名称(取自设备池;无名退地址)
MCP de_list_devices 返回 name 字段,提示 AI 汇报时用名称

这几个接口的字段来源:/api/adb/devices、/api/apks/install/devices 走 web/common.py:_merged_device_list()(带 name);/api/tools/appver 把 name 塞进每个设备的结果里。

6.2.2 设备配置二维码(扫码配置)

接口 鉴权 请求 说明
GET /api/devices/qrcode?serial=<地址> 设备权限 — 返回 {png(base64 data-url), payload, warn}

给设备端 Agent 扫的配置二维码,一次写入平台地址 + 设备令牌 + 该设备指纹:

  • payload 是二维码里的原始 JSON:{"v":1,"server":…,"token":…,"fingerprint":…,"name":…}
  • warn 非空时必须显示给操作者,最常见两种:
    • "你是用 localhost 打开平台的" → 二维码里的地址手机会连不上,要改用局域网地址重开本页
    • "这台设备还没采集到指纹" → 扫码后平台认不出它是哪一台
  • 前置条件:先在「应用管理 → 设备端应用商店」启用(令牌从那儿来),否则 400
  • 设备端如何解析见 DEVICE_AGENT.md §5.3

6.3 自动发现

接口 请求 说明
GET /api/devices/discovery — 发现状态 + 待连接 + 池内断联(前端 10s 轮询)。待连接项带 fingerprint 与 match({"serial","name"} = 指纹命中的池内设备)
POST /api/devices/discovery/scan — 后台扫描一轮;已有扫描 → 409
POST /api/devices/discovery/confirm {"serial","name"?} 确认入池。新设备必须有名称(空 → 400);指纹命中已有设备时不需要名称——保留原记录与原名,直接认领到新地址
POST /api/devices/discovery/ignore {"serial"} 从待连接删除
POST /api/devices/discovery/reconnect {"serial"} 只接受池内设备,否则 404
POST /api/devices/discovery/settings {"enabled","subnets","interval","port","auto_claim"} 部分更新,存 app_meta。auto_claim=指纹匹配时自动认领(默认 false:认领会改写分组/任务引用,默认交人工确认;打开后扫描到"同一台设备换了地址"自动迁移)

扫描只做 socket 探测 + 只读校验,不会把设备直接拉进设备池(必须人工确认)。 扫描时会顺带读一遍候选设备的指纹(写入待连接池),用于提示"这台是已有设备换了地址"。

7. 远程看屏与设备操作

接口 请求 说明
GET /api/screen/stream?serial=&q=&fps= — MJPEG 流(multipart/x-mixed-replace)
GET /api/screen/thumb?serial= — 360px 宽 JPEG 缩略图,响应头 X-Screen-State: on/off/unknown
GET /api/screen/size?serial= — 原生分辨率 {"ok":true,"width":…,"height":…}
POST /api/screen/tap {"serial","x","y","snap":1?} snap=1 先吸附到最小可点击元素中心
POST /api/screen/swipe {"serial","x1","y1","x2","y2","duration"?}
POST /api/screen/key {"serial","key"} 白名单:back/home/recent/menu/power/volume_up/volume_down…
POST /api/screen/text {"serial","text"} u2 send_keys(需焦点在输入框)
POST /api/screen/tap_text {"serial","text"} 先 UI 树子串匹配,未命中转 OCR;文字 ≤100 字符

坐标均为设备原生像素(/api/screen/size 给基准)。u2 调用异常统一 503,并清理 60s 连接缓存。


8. 元素抓取与步骤测试

接口 请求 响应要点
GET /api/uiauto/status — uiautodev(:20242)是否在跑,前端据此禁/启用"抓取元素"
GET /api/uiauto/devices — 可选设备列表(uiautodev 设备 + 池内在线补全)
GET /api/uiauto/elements?serial= — 扁平元素列表,每项带 suggested(推荐选择器)、bounds、depth
GET /api/uiauto/screenshot?serial= — JPEG
POST /api/steps/test {"serial","step":{…}} 真机试执行单个步骤 → 命中 / 未找到 / 已执行

GET /api/uiauto/elements 在 uiautodev 不可用时返回 503。设备 dump 慢时可能超时(见 backlog)。


9. 应用管理(APK)

接口 请求 说明
GET /api/apks — 已上传 APK 列表(含解析出的包名/版本/大小)
POST /api/apks/upload multipart file 未选文件 → 400;解析失败 → 500
DELETE /api/apks/<apk_id> — 删文件 + 记录
POST /api/apks/install {"apk_id","serials":[…]} 后台并发 5 台安装;已在装 → 失败提示
GET /api/apks/install/devices — 可安装设备(pool / usb / adb 三个来源)
GET /api/apks/install/status — 安装进度(前端 1.5s 轮询)

每台设备的状态机:pending(等待)→ installing(带文字的实时进度)→ success / failed / skipped。 安装分两段,items[serial].msg 会跟着更新:

  1. 推送到设备:推送中 62%(211.0/336.1 MB · 2.6 MB/s · 剩约 48s) —— 用分块写 (adb exec-in)拿字节级进度;不用 adb push 是因为它只在结束时吐一行汇总, 大包(抖音 336MB≈2 分钟)整个传输过程界面上只能干等
  2. 设备上安装:正在设备上安装...(大包要 1-2 分钟,无进度可读) —— pm install 在设备端解包,系统没给进度接口

参考耗时(336MB 抖音 / 单台):推送 ~134s + 安装 ~65s ≈ 3.3 分钟;多台并发(上限 5)时 带宽共享,整体时间取决于 WiFi。


10. 用户与日志

接口 鉴权 请求 错误
GET /api/users Admin — —
POST /api/users Admin {"username","password","is_admin"?,"perms"?} 用户名/密码空、重名 → 400
PUT /api/users/<int:uid> Admin {"password"?,"is_admin"?,"perms"?} 取消最后一个管理员 → 400;不存在 404
DELETE /api/users/<int:uid> Admin — 删 admin/删自己/删最后一个管理员 → 400
GET /api/logs?file=core.log&lines=300 G — 返回日志尾部 + 可选文件清单

11. 运维工具(adb / 剪贴板 / 应用版本 / Tailscale)

adb

接口 请求 说明
GET /api/adb/devices — 维护终端看到的设备(本机 adb + 设备池合并)
POST /api/adb/cmd {"cmd":"…"} 执行 adb 命令

/api/adb/cmd 的安全约束:命中 kill-server / disconnect 直接拒绝(红线);空命令 400;超 20s 返回"命令执行超时(20s)";与 worker 共用 _ADB_LOCK。

其它

接口 请求 说明
POST /api/tools/clipboard/set {"serials":[…],"text":"…"} ClipInject 通道写入并读回校验;serials 空或非列表 → 400
POST /api/tools/appver {"package":"com.xxx"} 并发查所有在线设备(≤10 并发);包名须匹配 ^[A-Za-z0-9_.]+$

Tailscale(全部 Admin)

接口 请求 说明
GET /api/tailscale/status — 是否已配置 TAILSCALE_API_KEY/TAILSCALE_TAILNET
GET /api/tailscale/devices — 上游 API 失败 → 502
POST /api/tailscale/devices/<id> {"name"?} / {"authorized"?} / {"key_expiry_disabled"?} 按字段分发
POST /api/tailscale/devices/<id>/ip {"ipv4"} 会断开该设备的 tailscale 会话;设备池 serial 就是 tailnet IP,改完需同步设备池
DELETE /api/tailscale/devices/<id> —
POST /api/tailscale/authkey {"description"} description 必须 ASCII;key 只显示一次

12. AI 控制台

配置存在 app_meta(agent_*)。

配置

// GET /api/agent/config
{"ok": true, "api_base": "https://api.deepseek.com", "model": "deepseek-v4-flash-vision-exp",
 "api_key": "(原文)", "api_key_masked": "sk-***abcd", "default_serial": "", "max_steps": "40"}
// POST /api/agent/config  (部分更新)
{"api_base": "…", "model": "…", "api_key": "…", "default_serial": "…", "max_steps": 40}

运行

接口 说明
POST /api/agent/run {"prompt","serial"?,"conversation_id"?} → {"ok":true,"run_id":"8f3a2c9d"}。校验:prompt 空/未配 Key/未配模型/未选设备且无默认 → 400;设备不在池/离线 → 400;设备 busy 或已有 Agent 运行中 → 409
GET /api/agent/run {"ok":true,"state":"idle|running|done","run_id","prompt","serial","started","answer","error","usage":{…},"history":[…]}
GET /api/agent/stream?run_id= SSE,事件见下
POST /api/agent/stop 下一个检查点生效;无运行中任务 → 400
POST /api/agent/clear 清空运行态历史

SSE 事件:

event payload 说明
delta {"text","kind":"content"|"reasoning"} 流式文本增量(正文 / 推理链)
step {"tool","args","image"?} 工具调用完成;image 为缩略截图。伪卡片:tool="🧠 经验记忆"(命中/写入经验)、tool="🧠 动作经验"(命中/沉淀动作)
usage {"prompt_tokens","completion_tokens","total_tokens","calls"} 本轮累计 token,每完成一次模型调用推一次
done {"answer","usage"} 完成
error {"message"} 失败(MCP 不可达时给出明确文案)

空闲时每 15s 发 : keepalive;done/error 后关流。

会话

接口 说明
GET /api/agent/conversations {"conversations":[{"id","title","updated_at","count"}]}
POST /api/agent/conversations 新建空会话 → {"ok":true,"id":"…"}
GET /api/agent/conversations/<conv_id> 消息列表(assistant 消息可带 usage、reasoning);不存在 404
DELETE /api/agent/conversations/<conv_id> 删除会话及其消息
POST /api/agent/conversations/<conv_id>/rename {"title"};空标题 400

经验库 / 动作库

接口 说明
GET /api/agent/experience 经验列表 + 最近巡检结论 + 巡检运行状态
POST /api/agent/experience/audit 手动触发巡检;进行中 → 409
POST /api/agent/experience/delete {"id"} 人工删除(巡检永远不会自动删)
POST /api/agent/experience/keep {"id"} 撤销"建议删除"
GET /api/agent/actions 动作库列表(命名动作 + 元素定位步骤)
POST /api/agent/actions/save {"id"?,"name","app","aliases","params","steps"};含坐标的步骤被拒 → 400
POST /api/agent/actions/delete {"id"}

机制详见 AI_CONSOLE.md。


13. 系统备份

POST /api/system/backup/export

JSON body(不是表单):{"include_apk": true}(默认 true)。响应为 zip 附件:

users.db           # 当前库(MySQL 或回退 SQLite)整库一致快照成的 SQLite 归档
apks/*.apk         # include_apk=true 时
manifest.json      # {format:"auto_control_backup", version:2, created_at, schema_version,
                   #  db_backend, deployment_env, source_db_id, include_apk,
                   #  tables:[{table,label,rows}], apks:[…], coverage_missing?}

SQLite 在这里是备份交换格式而非运行时库:MySQL 下导出时把连接提到 REPEATABLE READ, 保证 12 张表读的是同一时刻。

POST /api/system/backup/preview

multipart 上传 .zip 或 .db(字段 file)→ 校验并暂存:

{"ok": true, "token": "e172dc75e2f7",
 "preview": {"integrity": "ok", "schema_version": 6, "current_schema_version": 6,
             "source_env": "dev", "current_env": "dev", "source_backend": "mysql",
             "source_db_id": "704fbbb0…",
             "tables": [{"table":"agent_action","label":"动作库","rows":5}],
             "missing_optional": [], "extra_tables": [],
             "warnings": ["备份为全量数据,含用户口令哈希、AI 配置里的 API Key 等敏感信息…"]}}
  • 完整性 PRAGMA integrity_check 必须 ok;缺必需表(app_meta/user/task_job/device_group)直接拒绝
  • extra_tables 非空 = 备份含未登记的表(提示去登记)
  • source_env ≠ current_env 时 warnings 会多一条跨环境告警
  • 暂存 TTL 30 分钟,过期自动清理
  • 文件非法 / 未选文件 → 400

POST /api/system/backup/apply

{"token":"…", "force_env_mismatch": false} → 先自动把当前库导出成 data/backups/pre_restore_<ts>.zip(安全网,可直接再导入回来),再把暂存归档落到 data/restore_pending/。

  • 必须重启服务才生效:web_server.py 在 init_db 之后、TaskManager 之前消费该目录
  • 重启时单事务整库替换(DELETE 全表 + 分块 INSERT),失败自动回滚,当前数据不受影响
  • 备份来源环境与当前库不符时默认拒绝(400),要跨环境须显式传 force_env_mismatch: true
  • token 无效/暂存缺失 → 400

14. 设备端应用商店

设备侧三个接口(bootstrap / apk / report)的完整协议、鉴权、错误码与版本约定 见 DEVICE_AGENT.md——那份文档同时是设备端 APK 仓库的对接契约。

管理侧(登录 + 应用管理权限):

GET /api/agent-store/config

{"ok": true, "enabled": false, "token": "…", "token_created_at": "2026-09-13 14:38:11",
 "api_version": 1, "base_url_hint": "http://192.168.20.220:18050"}

base_url_hint 由请求的 Host 回填,直接可作为设备端要填的平台地址。

POST /api/agent-store/config

{"enabled": true} 启用(并自动生成令牌);{"regenerate_token": true} 重置令牌 (旧令牌立即失效)。返回 {"ok": true, "enabled": …, "token": …}。

GET /api/agent-store/logs?limit=50

{"ok": true, "logs": [{"id": 1, "device_name": "A08", "serial": "192.168.20.100:5555",
  "fingerprint": "…", "apk_id": "56b114aa", "package_name": "com.example.clipinject",
  "version_name": "1.0", "action": "install_fail", "message": "MIUI 拦截未确认",
  "created_at": "2026-09-13 14:40:00"}]}

action ∈ download / install_ok / install_fail;记录表最多保留 500 条(自动裁旧)。


15. 非 JSON 响应汇总

方法 路径 响应类型 说明
GET / /wall /login /locate text/html 四个页面(/locate 免登录)
GET /api/device/screenshot image/png 原始截图(Cache-Control: no-store)
GET /api/screen/thumb image/jpeg 360px 缩略图 + X-Screen-State
GET /api/screen/stream multipart/x-mixed-replace MJPEG 实时流
GET /api/uiauto/screenshot image/jpeg 抓元素时的画面
GET /api/agent/stream text/event-stream SSE
POST /api/system/backup/export application/zip + Content-Disposition 附件下载

15. 错误分支速查

码 典型触发
400 参数缺失/非法:任务名空、未知 task_type、分组名空/重名、用户名为空/重名、actions steps 空、备份 token 无效、包名不合法、文字超 100 字符、缺 serial/x/y 等
403 权限位不足 / 非管理员
404 资源不存在:任务 / 分组 / 用户 / 动作 / 待连接记录 / 会话 / 设备不在池
405 方法不匹配(如对只支持 PUT/DELETE 的路径发 GET)
408 POST /api/adb/cmd 超 20s(返回文案"命令执行超时(20s)")
409 冲突:设备忙(AI 不与任务抢设备)、已有 Agent 运行中、巡检进行中、已有发现扫描在跑
500 未预期异常(截图层失败、APK 上传解析失败、备份 IO 失败…)
502 上游依赖失败:Tailscale API、设备定位(/api/device/locate)
503 依赖不可用:get_status 异常、uiautodev 未启动/抓取失败、u2 连接异常

16. 已知问题

问题 影响 位置
GET /locate 返回 500 设备定位大字页打不开(NameError:render_template_string / _esc 未导入) web/monitor.py
POST /api/device/locate 的 show=true 分支失败 无法在设备上打开定位页(urllib 未导入 → 502) web/monitor.py
CSRF 未强制校验 /api/csrf 会发 token、前端会带 X-CSRF-Token,但服务端没有注册校验钩子 → 伪造请求不会被拦 web_server.py
POST /api/agent/run 的设备校验可能被跳过 校验包在 try/except: pass 里,状态服务异常时直接放行(MCP busy 锁兜底) web/agent_api.py

这些均已登记在 backlog/TODO.md。