一、平台侧(账号 → 发布计划页) - 新表 video_plan(schema v9→v10):账号×发布日期×编号 → 素材 + 标题 + 发布状态 + 分享链接; 状态机 pending/ready/pushing/publishing/done/failed/unknown/skipped(**failed 与 unknown 必须分开**: 推送阶段的失败可安全重试;碰过抖音之后的岔子只能算"结果未知",绝不自动重发) - 素材上传:文件名 `手机号_日期_编号`(编号可省)解析配对;标题 txt `标题内容_手机号_日期_编号`; 内容寻址落盘 data/videos/YYYY-MM/(sha1 分块算,同名不存两份),**不进整库备份**但进 manifest 反查 - 新蓝图 web/video_plan_api.py:上传/时间线/统计/单条增删改/推送到手机/标记结果/裁决/链接导出 CSV/ 任务列表与一键新建、**就地编辑**(GET/PUT /tasks/<id>)、**一键推送**(POST /push_all,按设备分组、设备内串行) - 账号页拆子分栏(台账 / 发布计划)+ static/admin/release.js;清理 job(04:41 僵尸回收+过期行、04:47 素材文件) - 上传体积:MAX_CONTENT_LENGTH(默认 2GiB)+ 413 JSON + nginx client_max_body_size(修现有 APK 上传隐患) 二、任务侧(平台推素材,抖音流程你自己写) - 新步骤 push_release「推送发布视频」:原子占位 → adb push → **touch 改成"现在"** → 清旧目录同名副本 → 触发扫描并**按路径**校验相册索引 → 标题写进剪贴板;默认目录 /sdcard/DCIM/Camera - 新步骤 mark_release「标记发布结果」:回写 done/failed/unknown,成功时抓作品分享链接、删手机素材 - input_text 支持 text_source=release_title(自动取计划标题 + 回读校验); if_el 的候选值来源新增 release(**本机当前发布计划**的抖音号/昵称,发布前校验"登的是不是要发的号") - build_release_steps 骨架 15 步:⓪ 亮屏 → ① 打开抖音(等首页) → ② 点「我」→ ③ 等抖音号出现 → ④ 条件判断(账号) → then ⑤ 推送 ⑥⑦⑧⑨⑩⑪⑫ 抖音点击/填标题 → ⑬ 标记 / else 发通知跳过 三、修(推送这一路的检测机制) - **uiautomator2 3.x 的 d.shell() 返回 ShellResponse(tuple 子类)不是 str**:`'x' in resp` 恒 False、 `.strip()` 不存在 → "推上去的文件大小不对"每次都判失败(文件其实推上去了)、相册校验永远报没进、 删除确认永远判没删掉。新增 publish_flow._sh() 统一取 .output;大小改成解析 ls -l 的大小列 - **adb push 保留本地 mtime** → 推 3 天前上传的素材在按时间排序的相册里排不到最前, "点第一个 = 刚推的那个"不成立 → 推完 touch - 相册校验**按路径**比(MediaStore 的 _data 会把目录小写、/storage/emulated/0 ≡ /sdcard), 只比文件名会被老目录的同名残留骗过去 - 屏幕没亮就启动抖音会永远停在启动页(UI 树为空)→ 后面"点我/等抖音号"必然 miss, 最后报成误导人的"账号不符" → 骨架第一步固定加「亮屏」,open_app 等「首页」出现 四、其它 - core/ledger.serial_of():设备名 → 当前地址(设备换 IP 后快照是错的) - 通知事件 task.video.published / task.video.failed;备份清单加 video_plan 与素材统计 - 文档同步:DATA_MODEL §2.11 + schema v10、API(新接口与语义)、TASK_DEV §4.7 专章、 ARCHITECTURE(账号页子分栏/release.js/两个 job)、DEPLOY(表数/nginx)、NOTIFY、DEVELOPMENT、README
436 lines
30 KiB
Markdown
436 lines
30 KiB
Markdown
# auto_control — Android 多设备自动化任务平台
|
||
|
||
基于 **uiautomator2 + Flask + 自建设备池** 的 Android 多设备自动化平台(已摘除 OpenSTF 依赖,见 [doc/STF_REMOVAL.md](doc/STF_REMOVAL.md))。
|
||
|
||
一台机器上统管一批 Android 设备:定时/手动下发任务、并发执行、实时看屏与远程触控、元素抓取与步骤编排、APK 批量安装、数据备份导出/导入;并通过 **MCP** 把手机控制能力开放给外部 AI(数字员工)。
|
||
|
||
```
|
||
┌──────────── Web 管理后台(单页应用,:18050)────────────┐
|
||
浏览器 ────────────▶ │ 监控 │ 任务 │ 日志 │ 用户 │ 工具 │ AI 控制台 │ 系统 │
|
||
└───────────────────────┬─────────────────────────────────┘
|
||
│ Flask 蓝图(web/)
|
||
┌───────────────────────▼──────┐ ┌──────────────────────┐
|
||
│ 调度 TaskManager │ │ AI Agent(mcp_agent)│
|
||
│ 设备池 device_pool │ └──────────┬───────────┘
|
||
│ Worker(每设备一线程) │ │ MCP :8033
|
||
└───────┬──────────────┬───────┘ ┌──────────▼───────────┐
|
||
│ │ │ MCP Server(de_* 工具)│
|
||
adb 直连 uiautodev └──────────┬───────────┘
|
||
IP:5555 / USB :20242(抓元素) │
|
||
▼ ▼
|
||
Android 设备 ◀──────────────────────── Android 设备
|
||
```
|
||
|
||
---
|
||
|
||
## 目录
|
||
|
||
- [核心能力](#核心能力)
|
||
- [快速上手](#快速上手)
|
||
- [项目结构](#项目结构)
|
||
- [核心概念](#核心概念)
|
||
- [Web 管理后台](#web-管理后台)
|
||
- [任务与步骤](#任务与步骤)
|
||
- [AI 与 MCP](#ai-与-mcp)
|
||
- [配置说明](#配置说明)
|
||
- [日志](#日志)
|
||
- [常见问题](#常见问题)
|
||
- [文档索引](#文档索引)
|
||
|
||
---
|
||
|
||
## 核心能力
|
||
|
||
| 能力 | 说明 |
|
||
|------|------|
|
||
| **多设备并发任务** | 每设备一个 Worker 线程;同一设备同时只跑一个任务(可配置"抢占"打断其他任务) |
|
||
| **设备池** | SQLite 清单 + adb 在线状态;支持手工添加、网段自动发现、一键重连、型号采集、启用/停用 |
|
||
| **电量监控** | 后台每分钟 `dumpsys battery`(只读)采一次电量,**大屏卡片**与**监控页设备列表**都显示(按档位着色 + ⚡ 充电中);低于阈值推 webhook 告警(**充电中不报、"插着却没充电"照报**,掉档/恢复各报一次,不刷屏)。电量只放内存不落库(见 [NOTIFY.md](doc/NOTIFY.md) §3.1) |
|
||
| **任务调度** | 手动 / cron 定时 / 定时启停;运行窗口;失败重试(含端口耗尽类的长退避) |
|
||
| **步骤编辑器** | 可视化拖拽编排 24 种步骤(含循环/条件/OCR/通知/录制回放),可打包成"自定义动作"复用 |
|
||
| **拟人化操作** | 滑动默认走弧线轨迹、位置/幅度/时长每次抖动,且**每台设备有自己的手速与习惯**(同设备风格稳定、设备之间明显不同)——批量跑时不像同步机器人(见 [TASK_DEV.md](doc/TASK_DEV.md) §8.4) |
|
||
| **录制回放** | 「录制手势」步骤 = **纯录制**:**用手指在真机上划**(读 getevent 真触屏)或在网页画面上拖,把轨迹点列原样存下来,回放时**照原路径与时长重放**——不套滑动那套方向/幅度/拟人参数 |
|
||
| **动作配置 / 录制** | 「任务 → 动作配置」:① 配**步骤默认值**(新建步骤预填:滑动时长/幅度/抖动/拟人、点击超时、等待区间…)② **录制动作**——在设备画面上点/划/按键,自动翻译成步骤(点元素记选择器、划一下记方向/幅度/时长),可存成可复用动作,也可只取一个手势回填滑动步骤 |
|
||
| **公共巡检** | 任务级的守护条件(**独立于步骤画布**):每 N 秒检查屏幕亮/熄、元素在不在、前台是不是某 App,命中就点亮/息屏/停本设备/推通知——通知标题正文自己写(见 [TASK_DEV.md](doc/TASK_DEV.md) §4.5) |
|
||
| **去重账本** | 解决"**同一个号被做两次、有的号还没做**":任务里用「条件判断→去重」+「记为已做」,把"这个身份做过了"记进一张**所有设备共享**的账本——多台手机、反复重跑都不会重复做同一个号。「任务 → 去重记录」页能看"谁做过了、还差谁",也能删记录让它重跑(见 [TASK_DEV.md](doc/TASK_DEV.md) §4.6) |
|
||
| **账号台账** | 「账号」页维护"**哪台设备上登着哪些号**"(设备号/手机号/账号名/抖音号/注册时间/卡在机内/可发视频/简介/备注):支持**从 Excel 粘贴批量导入**(表头识别、逐行结果、先预览后写入)。三个用处:① 任务的「条件判断」**直接从这里取号**,不用把几十个号手写进比对值;② 手机端 Agent 的**身份大字页**显示本机账号;③ 设备池里一眼看到每台登记了几个号(见 [DATA_MODEL.md](doc/DATA_MODEL.md) §2.10、[TASK_DEV.md](doc/TASK_DEV.md) §4.2) |
|
||
| **视频发布计划** | 「账号 → 发布计划」页:批量上传视频(文件名 `手机号_日期_编号`)与标题 txt → 自动按 (手机号,日期,编号) 配对到台账账号 → **按日期的时间线**看每天谁要发、发到哪一步。**平台只负责把素材推到手机**(推到 `/sdcard/DCIM/rp/`、触发相册刷新、把标题写进剪贴板),**抖音里怎么发由你自己在任务里写步骤**(步骤顺序:`推送发布视频` → 你的发布步骤 → `标记发布结果`;「发布计划」页顶部有**「发布任务」**块可一键建好骨架 + 看下次运行/启停,`输入文字` 那一步可以选**取值来源=计划标题**自动填文案并回读校验);发完可抓作品的**分享链接**存下来(平台不长期囤视频,链接才是长期资产,也方便后续铺评论:可一键复制、按日期/账号导出 CSV)。状态区分"**可重试**"(推送阶段失败,还没到抖音)与"**结果未知、需人工确认**"(推送之后出的岔子,绝不自动重发)(见 [DATA_MODEL.md](doc/DATA_MODEL.md) §2.11、[TASK_DEV.md](doc/TASK_DEV.md) §4.7) |
|
||
| **元素抓取** | 拉取设备 UI 元素树 → 点选回填选择器;支持"点一下"与"测选择器"真机验证 |
|
||
| **实时看屏** | MJPEG 实时流 + 点击/滑动/按键/文字输入;全屏监控大屏(`/wall`) |
|
||
| **应用管理** | APK 上传/解析/批量安装;设备已装应用与版本查询;剪贴板注入 |
|
||
| **AI 控制台** | 用自然语言驱动 AI 操作指定设备(MCP 工具 + 截图),流式输出、Markdown 渲染、推理链折叠、token 统计;成功操作自动沉淀「经验库 / 动作库」并在相似任务中召回 |
|
||
| **MCP 接入** | 20 个 `de_*` 工具,把手机控制开放给外部 AI;写操作有开关、设备忙时拒绝、全量审计 |
|
||
| **备份导出/导入** | 一键导出 zip(库快照 + manifest + 可选 APK),导入前校验预览、自动预备份、重启生效 |
|
||
| **通知 / Webhook** | 任务成功失败、设备上下线、安装完成、备份恢复等事件推送到企业微信 / 钉钉 / 飞书 / Bark / 自建服务(Slack 用通用 JSON);多条 webhook 各自订阅;聚合+限流防刷屏(见 [doc/NOTIFY.md](doc/NOTIFY.md)) |
|
||
|
||
---
|
||
|
||
## 快速上手
|
||
|
||
### 环境要求
|
||
|
||
- **Python 3.10+**(推荐 3.12)
|
||
- Windows / Linux / macOS 均可(`bin/adb/` 需放对应平台的 adb 二进制)
|
||
- 设备开启 **USB 调试**;网络调试设备建议 `adb tcpip 5555` 并接入同一网络(本项目生产环境走 Tailscale `100.100.10.x`,见 [doc/DEPLOY.md](doc/DEPLOY.md))
|
||
|
||
### 三步启动
|
||
|
||
```bash
|
||
# 1. 安装依赖
|
||
pip install -r requirements.txt
|
||
|
||
# 2. 配置(可选):把 .env.example 复制成 .env,至少填 WEB_SECRET_KEY
|
||
# 密钥类配置一律放 .env(不入 git)
|
||
|
||
# 3. 启动
|
||
python web_server.py
|
||
```
|
||
|
||
启动后访问 **http://localhost:18050/**,默认账号 `admin` / `admin123`(**首次登录请立即改密**)。
|
||
|
||
### 验证启动
|
||
|
||
```
|
||
[INFO] [core.worker] 心跳看门狗已启动
|
||
[INFO] [core.tm] 从数据库加载 X 个分组, X 个任务
|
||
[INFO] [web] uiautodev 服务已启动 (PID=...)
|
||
[INFO] [web] 管理后台: http://localhost:18050/ (admin/admin123)
|
||
[INFO] [web] 设备池预连接完成: X 台在线
|
||
[INFO] [web] 启动服务: http://localhost:18050/
|
||
```
|
||
|
||
### 首次使用
|
||
|
||
1. **登录** → `admin/admin123`,改密
|
||
2. **加设备** → 「工具 → 设备池管理」添加 `IP:5555`(自动连接 + 采集型号);也可用「设备自动发现」扫描网段后确认入池
|
||
3. **看设备** → 「监控」页设备表(在线/型号/任务/进度/前台 App)
|
||
4. **建任务** → 「任务 → 任务计划 → 新建任务」(只有 `generic_steps` 一种类型)→ 拖步骤 → 选目标设备 → 保存
|
||
5. **跑任务** → 任务行「执行」,或在「任务」/「监控」页操作
|
||
6. **看日志** → 「日志」页按模块实时查看
|
||
|
||
---
|
||
|
||
## 项目结构
|
||
|
||
```
|
||
auto_control/
|
||
├── config.py # 程序级配置常量(部署配置从 .env 读,模板 .env.example)
|
||
├── web_server.py # 入口:app 装配 + 蓝图注册 + 恢复消费 + 拉起 uiautodev + 启动
|
||
├── requirements.txt # 依赖清单
|
||
│
|
||
├── web/ # Web 层(蓝图包,路由都在这里,全部无 url_prefix)
|
||
│ ├── auth.py # 登录/登出/CSRF/权限装饰器/页面路由(/、/wall)
|
||
│ ├── monitor.py # 设备状态/运行控制/设备操作/远程看屏(MJPEG、点击、按键)
|
||
│ ├── tasks_api.py # 任务计划/分组/自定义动作/元素抓取/步骤测试
|
||
│ ├── admin_api.py # 用户管理/日志
|
||
│ ├── tools_api.py # adb 终端/剪贴板注入/应用版本查询
|
||
│ ├── devices_api.py # 设备池管理 + 自动发现
|
||
│ ├── apks_api.py # APK 上传/安装/删除
|
||
│ ├── tailscale_api.py # Tailscale 管理(改名/授权/密钥/IP/auth key)
|
||
│ ├── agent_api.py # AI 控制台(会话/SSE/经验库/动作库/巡检)
|
||
│ ├── system_api.py # 系统数据备份导出/导入
|
||
│ ├── notify_api.py # 通知 / Webhook 配置(系统 Tab)
|
||
│ ├── common.py # 跨模块共享工具(合并设备列表、屏幕状态)
|
||
│ └── context.py # 共享对象注入(mgr / apk_mgr / device_pool)
|
||
│
|
||
├── core/ # 基础设施层
|
||
│ ├── models.py # SQLAlchemy 模型 + 建表 + 版本化迁移 + 旧 JSON 迁移
|
||
│ ├── device_pool.py # 设备池(清单/在线状态/型号)
|
||
│ ├── device_worker.py # BaseWorker + 设备生命周期 + 心跳看门狗 + 全局状态表
|
||
│ ├── task_manager.py # 调度器:分组/任务/APScheduler/重试/运行控制
|
||
│ ├── adb_helper.py # adb 命令封装(全局锁,红线:绝不 kill-server)
|
||
│ ├── device_discovery.py # 网段扫描发现设备 → 待连接池
|
||
│ ├── device_battery.py # 设备电量采集(dumpsys 只读)+ 低电量告警
|
||
│ ├── ledger.py # 账号台账(CRUD / 表格粘贴解析 / 给任务取号 / 设备端账号块)
|
||
│ ├── video_plan.py # 视频发布计划(文件名解析配对 / 时间线 / 幂等占位 / 素材清理)
|
||
│ ├── u2_helper.py # uiautomator2 通用辅助(等首页/安全点击等)
|
||
│ ├── uiauto_helper.py # uiautodev 客户端(元素树 + XPath 建议)
|
||
│ ├── ocr.py # 屏幕 OCR(RapidOCR,条件判断用)
|
||
│ ├── clipboard_helper.py # 剪贴板注入(ClipInject 通道)
|
||
│ ├── apk_manager.py # APK 上传/解析/批量安装
|
||
│ ├── system_backup.py # 数据备份导出/导入(重启生效)
|
||
│ ├── humanize.py # 拟人化:弧线滑动/抖动 + 每台设备的"手感"(按 serial 播种)
|
||
│ ├── notifier.py # 通知分发(队列/聚合/限流/适配器)+ notify_events.py 事件目录
|
||
│ ├── step_log.py # 任务步骤明细(异步写线程 + 保留期清理)
|
||
│ ├── patrol.py # 任务级公共巡检(检查项/动作注册表,穿插执行)
|
||
│ ├── step_defaults.py # 步骤默认值(app_meta 单键,出厂值 + 用户覆盖)
|
||
│ ├── tailscale_client.py # Tailscale API v2 客户端
|
||
│ ├── ssh_client.py # SSH 封装(当前无人调用,预留)
|
||
│ ├── logger.py # 分文件日志(core/task/web/action)
|
||
│ └── actions/ # 全局 Action 框架(BaseAction + 注册器)
|
||
│
|
||
├── tasks/ # 任务定义层
|
||
│ ├── base.py # BaseTask + _TASK_TYPES + register_task
|
||
│ └── generic/ # 通用步骤任务(task_type=generic_steps,当前唯一类型)
|
||
│ └── task.py # STEP_TYPES(24 种步骤)+ Worker + 执行器
|
||
│
|
||
├── mcp_server/ # MCP Server(20 个 de_* 工具,:8033)
|
||
│ ├── mcp_server.py # 工具定义 + 平台登录 + 门控
|
||
│ ├── platform_client.py # 平台 HTTP 客户端(复用 Web 账号)
|
||
│ ├── direct_ops.py # 直连设备的 adb/u2 操作
|
||
│ ├── audit.py # 调用审计(JSON Lines)
|
||
│ └── config.py # MCP_* 环境变量
|
||
│
|
||
├── mcp_agent/ # AI Agent 编排层(OpenAI 兼容模型 → MCP 工具)
|
||
│ ├── agent.py # Agent:流式循环 + 工具调用 + 截图 + token 统计
|
||
│ ├── config.py # AGENT_* 环境变量
|
||
│ └── cli.py # 命令行入口
|
||
│
|
||
├── templates/admin/ # 页面
|
||
│ ├── monitor.html # 主单页应用(7 个顶级 Tab + 模态框 + 内联样式)
|
||
│ ├── login.html # 登录页
|
||
│ └── wall.html # 监控大屏(独立页面,自包含)
|
||
├── static/admin/ # 前端 JS(13 个文件,按顺序同步加载,见下)
|
||
├── static/fonts/ # 自托管字体(Bricolage Grotesque + IBM Plex Mono)
|
||
│
|
||
├── data/ # 运行时数据(不入 git)
|
||
│ ├── users.db # SQLite 主库
|
||
│ ├── apks/ # 上传的 APK
|
||
│ ├── backups/ # 导出 zip / 预恢复快照
|
||
│ ├── restore_staging/ # 导入暂存(TTL 30 分钟)
|
||
│ └── restore_pending/ # 待重启生效的恢复任务
|
||
├── logs/ # 日志(10MB 滚动,保留 5 份)
|
||
├── bin/adb/ # adb 二进制
|
||
├── scripts/ # pack.py / start.sh / supervise.sh / regression_test.py
|
||
└── doc/ # 完整文档(入口 → doc/README.md)
|
||
```
|
||
|
||
前端 JS 加载顺序(`templates/admin/monitor.html` 底部,全部为全局脚本,无模块隔离):
|
||
|
||
```
|
||
base.js → markdown.js → list.js → monitor.js → editor.js → tasks.js
|
||
→ tools.js → apps.js → admin.js → agent.js → taskgen.js → system.js → notify.js → steplog.js → recorder.js
|
||
```
|
||
|
||
---
|
||
|
||
## 核心概念
|
||
|
||
### 设备与 Worker
|
||
|
||
一台设备对应一个 `Worker` 线程(`core/device_worker.py` 的 `BaseWorker`)。基类封装了设备获取、adb/u2 连接(带超时保护)、状态上报、异常分类、停止信号、心跳看门狗;子类只实现 `run_task(d)`。
|
||
|
||
执行链路:`设备池选定 → adb connect 直连(IP:5555)或经 220 远程 adb server(USB)→ u2.connect(自动推送 atx-agent)→ run_task → 释放(不断开连接)`。
|
||
|
||
**同一设备同时只允许一个 Worker**:由 `TaskManager._running` 内存锁保证;任务可开 `preempt` 抢占(先停掉正在跑的任务再接管,结束后归还)。
|
||
|
||
### 任务类型(TaskType)
|
||
|
||
| task_type | 名称 | 说明 |
|
||
|-----------|------|------|
|
||
| `generic_steps` | 通用步骤 | 步骤编辑器编排的流程(**当前唯一类型**) |
|
||
|
||
新增专属任务类型的方法见 [doc/TASK_DEV.md](doc/TASK_DEV.md)。
|
||
|
||
### 任务计划(TaskJob)
|
||
|
||
描述"什么任务、跑哪些设备、什么参数、何时跑、失败怎么重试":
|
||
|
||
| 字段 | 说明 |
|
||
|------|------|
|
||
| `task_type` | 任务类型(不传默认 `generic_steps`) |
|
||
| `target` | `{"mode":"all"}` / `{"mode":"group","group_name":"A组"}` / `{"mode":"serial","serial":"100.100.10.20:5555"}` |
|
||
| `params` | 任务参数,与任务类默认值合并;含两个隐藏开关 `skip_offline`(默认 true)、`preempt`(默认 false) |
|
||
| `schedule` | `{"mode":"once"}` / `{"mode":"cron","cron":"0 9 * * *"}` / `{"mode":"cron_stop","cron":...,"stop_cron":...}`,可选 `window` 运行窗口 |
|
||
| `retry` | `{"max_attempts":1,"delay":60}` |
|
||
| `enabled` | 是否参与调度 |
|
||
|
||
**调度模式**:`once` 仅手动;`cron` 到点启动;`cron_stop` 到点启动 + 到点停止。cron 为标准 5 段 `分 时 日 月 周`,编辑器提供"每天/每小时/每隔 N 小时/每周"的可视化选择自动生成,也可手填。
|
||
|
||
**运行窗口**(`schedule.window`,如 `21:00-09:00`,支持跨午夜):窗口外定时触发与手动执行都不启动。
|
||
|
||
**目标解析**(`TaskJob.resolve_serials`):`all` = 设备池 ∩ 在线(开了抢占则取全部在线池内设备);`group` = 分组 ∩ 设备池;`serial` = 指定设备。后两者默认跳过离线设备。
|
||
|
||
### 进度上报
|
||
|
||
Worker 通过 `set_progress()` 上报统一字段,前端统一渲染:
|
||
|
||
```python
|
||
self.set_progress(done=5, total=80, unit="视频", action_counts={"like": 3}, elapsed=120)
|
||
```
|
||
|
||
前端展示为进度条 + `5/80 视频` + 计数徽章 + 运行时长。
|
||
|
||
---
|
||
|
||
## Web 管理后台
|
||
|
||
单页应用(`templates/admin/monitor.html`),**7 个顶级 Tab**;「任务 / 工具 / 系统」内部还有页内子分栏(会记住上次选中位置)。
|
||
|
||
| Tab | 内容 | 可见性 |
|
||
|-----|------|--------|
|
||
| **监控** | 统计卡片 + 设备表(在线/型号/任务状态/进度/前台 App/最近错误,支持排序、搜索、分页、多选批量操作)+ 异常汇总 + **任务运行概况**(每张任务卡:执行任务 / 停用任务 + 覆盖设备彩色 chip);导航栏「📺 大屏」打开 `/wall` | 所有登录用户(设备操作按钮需"设备控制") |
|
||
| **任务** | 子分栏:**任务计划**(CRUD / 启停 / 执行 / 下次运行时间 / 离线跳过 / 步骤编辑器 / **公共巡检**)、**自定义动作**(步骤打包复用)、**动作配置**(步骤默认值 + 录制动作) | 可看;写操作需"任务管理" |
|
||
| **日志** | 子分栏:**文件日志**(文件切换含滚动历史、关键字/级别/时间过滤、命中高亮、下载筛选结果、自动刷新)、**步骤明细**(按设备/任务/结果/时间过滤、按运行归组的概览、导出 CSV) | 需"日志查看" |
|
||
| **用户** | 用户 CRUD、改密、分配权限 | 仅管理员 |
|
||
| **工具** | 8 个子分栏:剪贴板注入 / adb 远程终端 / Tailscale 管理 / 应用管理 / 应用版本管理 / 设备已装应用 / **设备池管理**(含自动发现)/ 设备分组 | 仅管理员 |
|
||
| **AI 控制台** | 会话列表 + 对话区(Markdown 渲染、推理链折叠、token 统计)+ 实时画面(MJPEG)+ 目标设备选择;右上角:经验库 / 动作库 / 模型配置 | 仅管理员 |
|
||
| **系统** | 子分栏:数据备份(导出 zip)/ 导入恢复(上传→校验预览→应用,重启生效) | 仅管理员 |
|
||
|
||
### 权限模型
|
||
|
||
默认账号 `admin/admin123`(管理员,权限不受限)。权限位:
|
||
|
||
| 权限位 | 键 | 覆盖 |
|
||
|--------|----|------|
|
||
| 任务管理 | `tasks` | 任务/分组/自定义动作的写操作 |
|
||
| 设备控制 | `devices` | 停止设备、亮息屏、定位、清异常、前台扫描、元素抓取、步骤测试 |
|
||
| 应用管理 | `apks` | APK 上传/安装/删除 |
|
||
| 日志查看 | `logs` | 日志 Tab |
|
||
|
||
- 查看类接口(设备/任务/分组列表、状态、截图)所有登录用户可用
|
||
- 后端 `@perm_required` / `@admin_required` 拦截并返回 403;前端用 `data-perm` 属性与 `_can(perm)` 隐藏入口(**仅隐藏,安全依赖后端**)
|
||
- 不能删除/降级最后一个管理员
|
||
|
||
---
|
||
|
||
## 任务与步骤
|
||
|
||
`generic_steps` 的执行内容全在 `params.steps`(JSON 数组),由步骤编辑器产出。**没有默认步骤**——空步骤任务执行时会明确报错。
|
||
|
||
**24 种步骤**(完整参数见 [doc/TASK_DEV.md](doc/TASK_DEV.md)):
|
||
|
||
| 类别 | 步骤 |
|
||
|------|------|
|
||
| 屏幕 | `screen_on` 亮屏 · `screen_off` 息屏 · `keep_screen` 保持亮屏 |
|
||
| 应用与输入 | `open_app` 打开 App · `stop_app` 结束 App · `input_text` 输入文字 · `clipboard` 剪贴板注入 · `key_event` 按键 |
|
||
| 交互 | `click` 点击元素 · `click_xy` 点击坐标 · `long_click` 长按 · `swipe` 滑动 · `swipe_until` 滑动直到元素出现 · `wait_el` 等待元素 · `wait` 等待时长 |
|
||
| 流程 | `loop` 循环块 · `group` 动作组 · `if_el` 条件判断(元素 / **OCR 识别** / 屏幕状态 / 前台App / **去重**)· `mark_done` 记为已做(配套去重) |
|
||
|
||
要点:
|
||
|
||
- 每一步都可有 `probability`(0-100,默认 100)决定本次是否执行
|
||
- 容器类步骤(`loop`/`group`/`if_el`)可嵌套,**深度上限 5 层**
|
||
- 选择器支持 `xpath` / `description` / `text` / `resourceId` / `descriptionContains` / `className`(`ocr` 仅条件判断)
|
||
- **优先用文字/id 定位**,坐标 (`click_xy`) 是最脆的方式
|
||
- 未知步骤类型、缺必填参数只告警跳过,不会中断任务链(排查时留意"看起来成功但没做事")
|
||
|
||
---
|
||
|
||
## AI 与 MCP
|
||
|
||
### AI 控制台
|
||
|
||
在「AI 控制台」选一台设备,用自然语言下指令,AI 通过 MCP 工具看屏幕、点按、输入,边做边把过程流式显示出来。配置(模型 / API Key / 默认设备 / 最大步数)存在数据库 `app_meta`,不落 `.env`。
|
||
|
||
自带两个"自进化记忆":
|
||
|
||
- **经验库**:任务成功后把操作套路蒸馏成配方,下次相似任务自动召回注入;每日 03:47 由 AI 巡检建议清理(删除永远需人工确认)
|
||
- **动作库**:把成功步骤沉淀为带元素定位的命名动作(禁坐标),可复用、可编辑
|
||
|
||
详细机制见 [doc/AI_CONSOLE.md](doc/AI_CONSOLE.md)。
|
||
|
||
### MCP(外部 AI 接入)
|
||
|
||
MCP Server 监听 `:8033`,暴露 20 个 `de_*` 工具(截屏、点击、滑动、输入、OCR、元素树、应用管理等)。写操作需 `MCP_ALLOW_WRITE=1`;设备正在跑任务时拒绝(`device_busy`);每次调用写审计日志。
|
||
|
||
```bash
|
||
# 本机手动启动(生产容器由 scripts/start.sh 自动拉起)
|
||
MCP_ALLOW_WRITE=1 MCP_PLATFORM_USER=admin MCP_PLATFORM_PASS=<密码> \
|
||
python -m mcp_server.mcp_server
|
||
```
|
||
|
||
工具清单与接入示例见 [doc/MCP.md](doc/MCP.md)。
|
||
|
||
---
|
||
|
||
## 配置说明
|
||
|
||
**密钥类配置统一放项目根 `.env`**(不入 git;模板见 [.env.example](.env.example))。加载规则:逐行解析后用 `os.environ.setdefault` 注入,**真实环境变量优先**。
|
||
|
||
| 配置 | 默认值 | 说明 |
|
||
|------|--------|------|
|
||
| `WEB_SECRET_KEY` | 未配置则随机 | 会话密钥;不配则每次重启登录态失效(**生产必须固定**) |
|
||
| `WEB_HOST` / `WEB_PORT` | `0.0.0.0` / `18050` | Web 监听(**常量,改 config.py**) |
|
||
| `ADB_PATH` | 自动识别 `bin/adb/` | adb 二进制(常量,按平台自动选) |
|
||
| `DATA_DIR` / `APK_DIR` | `data/` / `data/apks/` | 持久化目录(常量) |
|
||
| `USB_ADB_HOST` / `USB_ADB_PORT` | `100.100.10.1` / `5037` | USB 设备所在的部署机(远程 adb server) |
|
||
| `DISCOVERY_PORT` / `DISCOVERY_INTERVAL` | `5555` / `60` | 设备自动发现(网段也在工具页配置,存 `app_meta`) |
|
||
| `TAILSCALE_API_KEY` / `TAILSCALE_TAILNET` | 空 | Tailscale 管理功能 |
|
||
| `WEB_SECRET_KEY` 之外的一切密钥 | — | 一律进 `.env`,**不要写进 config.py** |
|
||
|
||
> **任务参数不放 config.py**——放各自 `tasks/<name>/task.py` 顶部的 `DEFAULT_PARAMS`。
|
||
|
||
完整键表(含 `MCP_*` / `AGENT_*`)见 [doc/DEVELOPMENT.md](doc/DEVELOPMENT.md) §配置速查。
|
||
|
||
---
|
||
|
||
## 日志
|
||
|
||
按模块分文件,单文件 10MB 滚动、保留 5 份:
|
||
|
||
| 文件 | 前缀 | 内容 |
|
||
|------|------|------|
|
||
| `logs/core.log` | `core.*` | adb / device_worker / task_manager / 设备池 |
|
||
| `logs/task.log` | `task.*` | 任务执行(worker 业务逻辑) |
|
||
| `logs/web.log` | `web.*` | Web 请求与管理操作 |
|
||
| `logs/action.log` | `action.*` | 操作执行 |
|
||
| `logs/notify.log` | `notify` | 通知发送(成功/失败/平台错误码) |
|
||
|
||
```python
|
||
from core.logger import get_logger
|
||
log = get_logger("task.generic") # → logs/task.log
|
||
```
|
||
|
||
「日志」Tab 可在线查看:文件下拉包含 `.1/.2…` 滚动历史(可回溯更早的日志),
|
||
支持关键字(命中高亮)/最低级别/时间范围过滤,点「下载」导出当前筛选结果
|
||
(无筛选就是整个文件)。读取逻辑在 `core/logger.py` 的 `list_log_files()/query_log()/
|
||
read_log_text()`,`file` 参数走白名单,不接受任意路径。
|
||
|
||
「日志 → 步骤明细」是**结构化**的另一半:每一次步骤执行落一行 `task_step_log`
|
||
(设备/任务/步骤路径/结果/耗时/选择器),因此能按设备、任务、结果、时间过滤,
|
||
也能按 `run_id` 归组看"这一次运行为什么失败"、导出 CSV。写入是异步的
|
||
(`core/step_log.py`,任务线程只入队,不阻塞执行),保留 14 天、单次运行最多
|
||
2000 条 —— 这两个上限决定这张表(以及备份包)能长多大。
|
||
|
||
---
|
||
|
||
## 常见问题
|
||
|
||
| 现象 | 处理 |
|
||
|------|------|
|
||
| 端口被占用(WinError 10013/10048) | 启动会自动回退候选端口(`18050` → `127.0.0.1:18050` → `127.0.0.1:18051..18055`);仍失败则改 `WEB_PORT`(注意 Windows 动态端口排除段) |
|
||
| 设备显示离线 | 「工具 → 设备池管理」一键重连;确认设备在线且同一网络(生产走 Tailscale 同 tailnet) |
|
||
| 新加的设备不被调度 | 必须**加入设备池**(仅 adb 连上不算);确认该设备为"启用"状态 |
|
||
| `u2.connect` 卡死 | 基类有 30s 超时保护;频繁超时说明 atx-agent 异常,重启设备或重新推送 |
|
||
| 任务运行中被看门狗标记卡死 | 看门狗 120s 无心跳即判定;长循环内需周期性 `self.heartbeat()` |
|
||
| 任务"成功"但什么都没做 | 步骤类型未知/缺必填参数会被**告警跳过**;查 `logs/task.log` 的 WARNING |
|
||
| 中文输入失败 | u2 默认 IME 不支持中文,需 `set_fastinput_ime(True)`(`input_text` 步骤已处理) |
|
||
| 改了 `core/` 不生效 | 服务以 `debug=False` 运行、不热重载;**必须重启**。前端改动还需强刷浏览器(Ctrl+Shift+R) |
|
||
| 元素抓取按钮不可用 | 依赖本机 uiautodev(:20242),`web_server.py` 启动时自动拉起;未安装时 `pip install uiautodev` |
|
||
|
||
---
|
||
|
||
## 文档索引
|
||
|
||
**完整文档入口 → [doc/README.md](doc/README.md)**(文档地图、阅读路径、维护约定)
|
||
|
||
| 文档 | 内容 |
|
||
|------|------|
|
||
| [doc/ARCHITECTURE.md](doc/ARCHITECTURE.md) | 架构详解(分层、装配顺序、线程模型、设备生命周期、调度链路、设计决策) |
|
||
| [doc/DATA_MODEL.md](doc/DATA_MODEL.md) | 数据模型(表结构、迁移、`app_meta`、数据目录、备份覆盖清单) |
|
||
| [doc/API.md](doc/API.md) | HTTP 接口全量说明 |
|
||
| [doc/TASK_DEV.md](doc/TASK_DEV.md) | 任务与步骤开发(24 种步骤、公共巡检、录制回放、去重、选择器、自定义动作) |
|
||
| [doc/MCP.md](doc/MCP.md) · [doc/MCP_DESIGN.md](doc/MCP_DESIGN.md) | MCP 使用手册 / 设计文档 |
|
||
| [doc/AI_CONSOLE.md](doc/AI_CONSOLE.md) · [doc/AI_TASK_GEN.md](doc/AI_TASK_GEN.md) | AI 控制台机制 / AI 建任务设计(规划) |
|
||
| [doc/DEPLOY.md](doc/DEPLOY.md) | 部署与运维(含生产容器、备份导入、故障排查) |
|
||
| [doc/DEVELOPMENT.md](doc/DEVELOPMENT.md) | 开发手册(git 流程、技术红线、本地开发、文档同步约定) |
|
||
| [doc/STF_REMOVAL.md](doc/STF_REMOVAL.md) | 摘除 OpenSTF 的历史记录 |
|
||
|
||
> ⚠️ **改动必须同步文档**:功能/配置/接口/表结构的任何增删改,都要在同一个 commit 里更新对应文档(红线,详见 [doc/README.md](doc/README.md) §3)。
|
||
> ⚠️ **git 操作需负责人确认**(含 push 到 dev),详见 [doc/DEVELOPMENT.md](doc/DEVELOPMENT.md)。
|
||
|
||
---
|
||
|
||
## 技术栈
|
||
|
||
| 组件 | 用途 |
|
||
|------|------|
|
||
| Flask + Flask-Login + Flask-SQLAlchemy | Web 后台 / 认证 / SQLite ORM |
|
||
| APScheduler | 定时任务调度(任务 + 经验巡检两个独立调度器) |
|
||
| uiautomator2 / uiautodev | Android UI 自动化 / 元素抓取 |
|
||
| rapidocr_onnxruntime (+ opencv-headless) | 屏幕 OCR |
|
||
| pyaxmlparser | APK 元信息解析 |
|
||
| fastmcp | MCP Server(Streamable HTTP)+ AI 控制台 Agent |
|
||
| adb(项目自带 `bin/adb/`) | 设备连接与底层操作 |
|