# API 接口文档 `platform-tools` Web 后台提供 JSON API,所有接口需登录后访问(Flask-Login session 认证)。 **Base URL**:`http://localhost:18050` **认证方式**:Cookie Session(先 POST `/login` 获取 session cookie,后续请求带上) **通用响应格式**: ```json {"ok": true, "data": "..."} {"ok": false, "error": "错误信息"} ``` --- **权限模型**(v2 起): - 所有接口需登录;**查看类 GET 接口**(状态/列表/截图)所有登录用户可用 - **写操作按权限位授权**(管理员拥有全部权限): | 权限位 | 中文 | 覆盖接口 | |--------|------|---------| | `tasks` | 任务管理 | 任务/自定义动作/分组的增删改、启停、立即执行 | | `devices` | 设备控制 | 停止设备、释放占用、清除异常、前台扫描、元素抓取 | | `apks` | 应用管理 | APK 上传、安装、删除 | | `logs` | 日志查看 | `GET /api/logs` | - **用户管理接口仅管理员可用**(普通用户即使被授予业务权限也无法访问) - 无权限访问返回 `403 {"ok": false, "error": "无权限执行此操作..."}` - 当前用户权限查询:`GET /api/me` --- ## 1. 认证 ### POST /login 用户登录。 **请求**(form-data): | 参数 | 类型 | 说明 | |------|------|------| | username | string | 用户名 | | password | string | 密码 | **响应**:成功重定向到 `/`,失败返回登录页(含 error 信息)。 ### GET /logout 登出,重定向到登录页。 --- ## 2. 页面路由 ### GET / 单页应用首页(需登录)。响应头设置 `Cache-Control: no-store` 防止缓存。 ### GET /login 登录页面(GET)。 --- ## 3. 设备状态 ### GET /api/status 获取设备池 + Worker 综合状态(带 5 秒缓存)。 **响应**: ```json { "ok": true, "server_time": 1700000000.0, "fg_scanning": false, "fg_last_scan": 1700000000.0, "devices": [ { "serial": "192.168.1.100:5555", "model": "Pixel 6", "device_name": "测试机1", "present": true, "ready": true, "stf_occupied": false, "owner": "", "worker_status": "idle", "foreground_app": "空闲", "progress": {"done": 5, "total": 80, "unit": "视频", "action_counts": {"like": 3}}, "current_action": "观看视频 6", "last_error": "", "running_job": "", "task_job": "", "attempt": 0 } ] } ``` **worker_status 取值**:`idle` / `connecting` / `running` / `done` / `error` / `failed` / `released` (权限:设备控制) 手动触发前台 App 扫描(后台异步执行,不打扰设备)。 **响应**: ```json {"ok": true, "msg": "扫描已启动"} {"ok": false, "error": "已有扫描在进行中"} ``` ### GET /api/devices 返回所有在线设备 serial 列表(供分组表单勾选用)。 **响应**: ```json {"ok": true, "devices": ["192.168.1.100:5555", "192.168.1.101:5555"]} ``` ### GET /api/device/screenshot 获取设备当前画面截图(PNG)。 **参数**: | 参数 | 类型 | 说明 | |------|------|------| | serial | string | 设备 serial | | t | int | 时间戳(避免缓存,前端自动加) | **响应**:成功返回 `image/png`,失败返回 JSON 错误。 > 用 `adb exec-out screencap -p`,只读操作,任务运行中调用安全。 --- ## 4. 任务类型 ### GET /api/task_types 返回所有已注册任务类型。 **响应**: ```json { "ok": true, "task_types": [ { "task_type": "douyin_nurture", "name": "抖音养号", "description": "自动观看抖音视频...", "default_params": {"watch_count": 80, "...": "..."} }, { "task_type": "generic_steps", "name": "通用步骤", "description": "通过步骤编辑器编排...", "default_params": {"steps": [...]} } ] } ``` ### GET /api/actions 返回指定任务类型支持的专属操作。 **参数**: | 参数 | 类型 | 说明 | |------|------|------| | task_type | string | 任务类型 | **响应**: ```json { "ok": true, "actions": [ {"action_type": "like", "name": "点赞", "description": "...", "default_params": {"rate": 0.3}} ] } ``` --- ## 5. 任务计划 CRUD ### GET /api/jobs 列出所有任务计划。 **响应**: ```json { "ok": true, "jobs": [{"id": "abc123", "name": "抖音养号", "task_type": "douyin_nurture", "next_run": "2026-08-12 09:00", "...": "..."}], "task_types": [...] } ``` `next_run`:下次真正执行时间(已按运行窗口跳过窗口外触发点,格式 `YYYY-MM-DD HH:MM`);手动任务/已停用为 `null`。 (权限:任务管理) 创建任务计划。 **请求**(JSON): ```json { "name": "抖音养号", "task_type": "douyin_nurture", "target": {"mode": "all"}, "params": {"watch_count": 50, "actions": {"like": {"params": {"rate": 0.5}}}}, "schedule": {"mode": "cron", "cron": "0 */2 * * *"}, "retry": {"max_attempts": 3, "delay": 60}, "enabled": true } ``` **响应**: ```json {"ok": true, "msg": "任务已创建", "job": {"id": "abc123", "...": "..."}} ``` `schedule` 字段格式: | 字段 | 说明 | |------|------| | `mode` | `once` 手动 / `cron` 定时启动 / `cron_stop` 定时启动+停止 | | `cron` | 标准 5 段 cron:`分 时 日 月 周`(周 `0`/`7`=周日);如 `0 */2 * * *` 每 2 小时整点 | | `stop_cron` | (cron_stop 必填)到点停止本任务 worker | | `window` | 可选,运行窗口 `{"start": "21:00", "end": "09:00"}`(每天重复,支持跨午夜)。窗口外定时触发和手动执行(`POST /api/jobs/:id/run`)均不启动,手动执行返回错误提示 | (权限:任务管理) 更新任务计划。只需传要更新的字段。 **请求**(JSON): ```json {"params": {"watch_count": 100}} ``` **响应**: ```json {"ok": true, "msg": "任务已更新", "job": {"...": "..."}} ``` (权限:任务管理) 删除任务计划。 **响应**: ```json {"ok": true, "msg": "任务已删除"} ``` (权限:任务管理) 立即执行任务(异步,不阻塞)。 **响应**: ```json {"ok": true, "msg": "任务 抖音养号 已触发"} ``` (权限:任务管理) 启用/停用任务。 **请求**(JSON): ```json {"enabled": false} ``` **响应**: ```json {"ok": true, "msg": "任务已停用"} ``` --- ## 6. 设备分组 ### GET /api/groups 列出所有分组。 **响应**: ```json { "ok": true, "groups": [ {"name": "A组", "serials": ["192.168.1.100:5555"], "description": "测试组"} ] } ``` (权限:任务管理) 创建分组。 **请求**(JSON): ```json {"name": "A组", "serials": ["192.168.1.100:5555"], "description": "测试组"} ``` (权限:任务管理) 更新分组。 **请求**(JSON): ```json {"serials": ["192.168.1.100:5555", "192.168.1.101:5555"], "description": "更新描述"} ``` (权限:任务管理) 删除分组。 --- ## 7. 运行控制 (权限:设备控制) 停止单台设备的 worker(并阻止后续重试)。 **请求**(JSON): ```json {"serial": "192.168.1.100:5555"} ``` **响应**: ```json {"ok": true, "msg": "已发送停止信号给 192.168.1.100:5555"} ``` (权限:设备控制) 停止所有运行中的 worker。 **响应**: ```json {"ok": true, "stopped": ["192.168.1.100:5555", "192.168.1.101:5555"]} ``` (权限:设备控制) 释放当前账户占用的所有 STF 设备(清理用)。 **响应**: ```json {"ok": true, "released": ["192.168.1.100:5555"]} ``` --- ## 8. 用户管理 (仅管理员) 列出所有用户。 **响应**: ```json {"ok": true, "users": [{"id": 1, "username": "admin", "is_admin": true}]} ``` (仅管理员) 创建用户。 **请求**(JSON): ```json {"username": "user1", "password": "pass123", "is_admin": false} ``` (仅管理员) 更新用户(修改密码/管理员权限)。 **请求**(JSON): ```json {"password": "newpass", "is_admin": true} ``` (仅管理员) 删除用户(不能删除 admin 和当前登录用户)。 --- ## 9. 日志 (权限:日志查看) 查看日志文件内容。 **参数**: | 参数 | 类型 | 默认 | 说明 | |------|------|------|------| | file | string | `core.log` | 日志文件名 | | lines | int | 300 | 返回最后 N 行 | **可选文件**:`core.log` / `task.log` / `web.log` / `action.log` **响应**: ```json { "ok": true, "content": "2026-08-08 10:00:00 [INFO] [core.worker] ...", "file": "core.log", "files": {"core": "core.log", "task": "task.log", "web": "web.log", "action": "action.log"} } ``` --- ## 10. 自定义动作 ### GET /api/custom_actions 列出所有自定义动作(步骤打包)。 (权限:任务管理) 创建自定义动作。 **请求**(JSON): ```json {"name": "登录流程", "icon": "📦", "steps": [{"type": "click", "...": "..."}]} ``` (权限:任务管理) 更新自定义动作。 (权限:任务管理) 删除自定义动作。 --- ## 11. 元素抓取(uiauto2) ### GET /api/uiauto/status 探测 uiauto2 本地服务是否运行。 **响应**: ```json {"ok": true, "running": true} ``` (权限:设备控制) 获取 uiauto2 已连接的设备列表。 **响应**: ```json {"ok": true, "devices": [{"serial": "192.168.1.100:5555", "model": "Pixel 6"}]} ``` (权限:设备控制) 通过 uiauto2 获取设备截图(JPEG)。 **参数**: | 参数 | 类型 | 说明 | |------|------|------| | serial | string | 设备 serial | **响应**:成功返回 `image/jpeg`,失败返回 JSON 错误。 (权限:设备控制) 获取设备 UI 元素树。 **参数**: | 参数 | 类型 | 说明 | |------|------|------| | serial | string | 设备 serial | **响应**: ```json { "ok": true, "elements": [ { "depth": 0, "name": "android.widget.FrameLayout", "resource_id": "", "text": "", "description": "", "class": "android.widget.FrameLayout", "bounds": "[0,0][1080,2400]", "suggested": {"type": "xpath", "value": "//*"} } ] } ``` --- ## 12. 应用管理(APK) ### GET /api/apks 列出所有已上传的 APK。 **响应**: ```json { "ok": true, "apks": [ { "id": "abc123", "display_name": "抖音", "package_name": "com.ss.android.ugc.aweme", "version_name": "25.0.0", "version_code": 2500, "size": 104857600, "upload_time": "2026-08-08 10:00:00" } ] } ``` (权限:应用管理) 上传 APK 文件(自动解析包名/版本/应用名)。 **请求**(multipart/form-data): | 字段 | 类型 | 说明 | |------|------|------| | file | file | APK 文件 | **响应**: ```json {"ok": true, "apk": {"...": "..."}, "msg": "上传成功: 抖音"} ``` (权限:应用管理) 删除 APK 文件和记录。 (权限:应用管理) 批量安装 APK 到指定设备。 **请求**(JSON): ```json {"apk_id": "abc123", "serials": ["192.168.1.100:5555", "192.168.1.101:5555"]} ``` **响应**: ```json {"ok": true, "msg": "开始安装 抖音 到 2 台设备"} ``` ### GET /api/apks/install/devices 可安装设备列表:STF 在线设备池 + 本机 adb 设备(**含 USB 有线连接**,serial 无冒号标记 `usb`)。 安装弹窗用此列表,USB 设备安装时跳过 adb connect 直接安装。 **响应**: ```json {"ok": true, "devices": [ {"serial": "100.100.10.11:5555", "model": "22120RN86C", "source": "stf"}, {"serial": "ZY322ABCDEF", "model": "", "source": "usb"} ]} ``` ### GET /api/apks/install/status 获取安装任务实时状态。 **响应**: ```json { "ok": true, "status": { "apk_name": "抖音", "finished": false, "total": 2, "success": 1, "failed": 0, "skipped": 0, "installing": 1, "pending": 0, "items": { "192.168.1.100:5555": {"name": "Pixel 6", "status": "success", "msg": "安装成功(已验证)"}, "192.168.1.101:5555": {"name": "Pixel 7", "status": "installing", "msg": "正在安装..."} } } } ``` --- ## 13. 维护(仅管理员) 维护页两个接口都**仅管理员可用**(普通用户即使有业务权限也访问不了,返回 403)。 ### POST /api/stf/restart 一键重启 STF Docker 容器。通过 SSH 到部署机执行 `docker restart`,随后轮询 STF API 确认恢复。 - 目标与容器名:`STF_SSH_TARGET`(默认 `stf@192.168.20.220`)、`STF_DOCKER_CONTAINER`(默认 `stf`)环境变量配置 - 前置条件:本机可免密 SSH 到部署机 - **有任务在运行时返回 409 拒绝执行**(STF 重启会重置设备占用状态) - 失败返回 502(SSH 不可达/容器名错误);执行超时返回 408 **响应**: ```json {"ok": true, "msg": "STF 容器已重启", "detail": "stf: Up 3 seconds", "api_alive": true} ``` ### GET /api/adb/devices 维护终端设备列表:本地 adb 已连接(含 offline)+ STF 在线设备池(标记 `stf`)。 供终端设备选择器使用——选中后前端自动附加 `-s `。 **响应**: ```json {"ok": true, "devices": [ {"serial": "100.100.10.11:5555", "state": "device"}, {"serial": "100.100.10.13:5555", "state": "offline"}, {"serial": "100.100.10.12:5555", "state": "stf"} ]} ``` ### POST /api/adb/cmd adb 远程终端:用平台 adb 二进制执行任意 adb 命令(20s 超时)。 **请求**(JSON): ```json {"cmd": "adb -s 100.100.10.11:5555 shell ls /sdcard"} ``` (开头的 `adb` 前缀可省略) **安全红线**:包含 `kill-server` / `disconnect` 的命令直接拒绝(400)——会断开 STF provider 共享的 adb transport,导致全部设备被 STF 误判离线。 **响应**: ```json {"ok": true, "stdout": "...", "stderr": "", "code": 0} ``` --- ## 14. 步骤测试(仅管理员可触发,需"设备控制"权限) ### POST /api/steps/test 在指定设备上单步试执行(步骤编辑器"测试此步骤"按钮),验证选择器是否命中。 只读连接(adb connect + u2),不占用/释放 STF,与运行中任务互不干扰。 **请求**(JSON): ```json { "serial": "100.100.10.11:5555", "step": {"type": "click", "label": "测试", "params": {"selector_type": "xpath", "selector_value": "//*[@resource-id=\"x\"]"}} } ``` **响应**: ```json {"ok": true, "msg": "步骤已执行(测试)", "result": "命中"} ``` `result`:`命中` / `未找到` / `已执行`(无选择器命中语义的步骤)。 无"设备控制"权限返回 403。 --- ## 15. Tailscale 管理(仅管理员) 维护页"Tailscale 管理"分区,调用 Tailscale 官方 API v2。所有接口仅管理员可用。 前置:`.env` 配置 `TAILSCALE_API_KEY`(Settings → API Access Tokens)与 `TAILSCALE_TAILNET`(tailnet 名,个人账号一般为邮箱前缀);未配置返回 502 并附提示。 设备 IP 由 tailnet 分配,API 不可修改,列表只读展示。 ### GET /api/tailscale/status 配置状态检查。 **响应**: ```json {"ok": true, "configured": false, "hint": "TAILSCALE_API_KEY(...);TAILSCALE_TAILNET(...)"} ``` ### GET /api/tailscale/devices 列出 tailnet 全部设备。 **响应**: ```json {"ok": true, "devices": [ {"id": "d1", "name": "dev-a", "hostname": "dev-a", "os": "linux", "addresses": ["100.100.10.11"], "authorized": true, "key_expiry_disabled": false, "online": true, "last_seen": "...", "tags": []} ]} ``` ### POST /api/tailscale/devices/:device_id 更新设备(按传入字段分发到 Tailscale 专属端点,`POST /device/{id}` 本身是 405): `name` → `/name` 显示名;`authorized` → `/authorized` 授权开关; `key_expiry_disabled` → `/key`(true=密钥永不过期,即关闭设备密钥验证,恢复后按原定过期时间执行)。 **请求**(JSON): ```json {"key_expiry_disabled": true} ``` ### POST /api/tailscale/devices/:device_id/ip 设置设备的 Tailscale IPv4 地址(未公开端点,实测可用)。 **请求**(JSON): ```json {"ipv4": "100.100.10.16"} ``` ⚠ 改 IP 会断开设备当前 tailscale 会话;平台设备池 serial 随之变化,需同步更新 STF 设备池/分组/任务目标。 ### DELETE /api/tailscale/devices/:device_id 从 tailnet 移除设备(下次上线需重新授权)。 ### POST /api/tailscale/authkey 生成设备接入 auth key(key 只返回一次)。 **请求**(JSON): ```json {"description": "新设备接入", "reusable": false, "ephemeral": false, "preauthorized": true, "expiry_seconds": 3600} ``` **响应**: ```json {"ok": true, "key": "tskey-auth-...", "id": "k1", "expires": "2026-08-11T01:00:00Z"} ```