- markdown.js(新增,无 CDN 依赖):轻量 Markdown 渲染(标题/列表含嵌套/表格/ 代码块/引用/链接…),先 esc() 转义再套标记,模型输出的 HTML 只当文本显示 - agent.js:回答改走 Markdown;推理链改为 <details> 可折叠(流式时展开、正文开始 自动收起、手动点过后不再自动改);单条消息 token 脚注 + 顶栏「本会话累计」 - monitor.html:消息结构加 .reasoning/.agent-usage、顶栏 token 徽标、md 相关样式, 引入 markdown.js(base.js 之后、agent.js 之前) - mcp_agent/agent.py:请求带 stream_options.include_usage,按「每次模型调用」累计 usage(末尾 chunk),on_usage 回调吐累计值;网关不认该参数(400/422/点名)时 自动降级重试一次 - web/agent_api.py:SSE 新增 usage 事件、done 带 usage;推理链与用量随会话落库 (_REASONING_KEEP=6000 截断),回灌模型时只取 role/content - 文档:API.md(usage 事件/done/会话消息字段)、ARCHITECTURE §5.4.1、DEVELOPMENT 前端 JS 清单 自测:假模型端点单测 3/3(正常/降级/多轮累加);Edge headless 全链路 27 项全通过 (真实 Flask+SSE+SQLite,含 XSS 转义、刷新后回看);Markdown 渲染器 18 用例全通过
设备自动化后台(platform-tools)
基于 uiautomator2 + Flask + 自建设备池 的 Android 多设备自动化任务执行平台(已摘除 OpenSTF 依赖)。
提供 Web 管理后台,支持多设备并发任务执行、定时调度、设备分组管理、设备池管理(新增/停用/删除/型号采集)、APK 批量安装、网页远程看屏(MJPEG 实时流 + 触控)、UI 元素抓取等功能。内置抖音养号任务和通用步骤任务,可扩展任意 App 的自动化操作。
目录
快速上手
环境要求
- Python 3.10+(推荐 3.12)
- Windows / Linux / macOS均可(adb 二进制需放对应平台版本到
bin/adb/) - 设备需开启 USB 调试(USB 连上后
adb tcpip 5555转网络调试),并加入 Tailscale 获得100.100.10.xIP - 新增设备在后台「工具 → 设备池管理」添加
IP:5555(自动连接,任务运行时 u2 自动推送 atx-agent)
三步启动
# 1. 安装依赖
pip install -r requirements.txt
# 2. 修改配置(可选):USB 设备在 220 上时配置 USB_ADB_HOST/PORT(默认 100.100.10.1:5037 已可用)
# 密钥类配置放 .env(WEB_SECRET_KEY / TAILSCALE_API_KEY)
# 3. 启动 Web 后台
python web_server.py
启动后访问 http://localhost:18050/,默认账号 admin / admin123。
验证启动
控制台看到以下日志即表示启动成功:
[INFO] [core.worker] 心跳看门狗已启动
[INFO] [core.tm] 从数据库加载 X 个分组, X 个任务
[INFO] [web] 管理后台: http://localhost:18050/ (admin/admin123)
[INFO] [web] 启动服务: http://localhost:18050/
首次使用流程
- 登录后台 → 用
admin/admin123登录,建议立即修改密码 - 添加设备 → "工具 → 设备池管理"添加设备(serial 形如
100.100.10.20:5555,自动连接并采集型号) - 查看设备 → 首页"监控"Tab 展示设备池状态(在线/型号/任务)
- 创建分组(可选)→ "分组"Tab 按批次/项目给设备分组
- 创建任务 → "任务"Tab 新建任务,选择任务类型、目标设备、参数、调度
- 执行任务 → 任务列表点"立即执行",或在"监控"Tab 勾选设备批量操作(亮屏/息屏/停止)
- 查看日志 → "日志"Tab 实时查看运行日志
项目结构
platform-tools/
├── config.py # 根配置(部署配置统一从 .env 读,模板见 .env.example)
├── web_server.py # Flask 入口(app 装配 + 蓝图注册 + 启动,193 行)
├── web/ # Web 层蓝图包(按功能域拆分,路由都在这里)
│ ├── auth.py # 登录/CSRF/权限装饰器/页面路由(/、/wall)
│ ├── monitor.py # 状态/运行控制/设备操作/远程看屏
│ ├── tasks_api.py # 任务计划/分组/自定义动作/元素抓取
│ ├── admin_api.py # 用户管理/日志
│ ├── tools_api.py # adb 终端/剪贴板注入/应用版本
│ ├── devices_api.py # 设备池管理
│ ├── apks_api.py # 应用管理
│ ├── tailscale_api.py # Tailscale 管理
│ ├── common.py # 跨模块共享工具(合并设备列表/屏幕状态)
│ └── context.py # 共享对象注入(mgr/apk_mgr/device_pool)
├── requirements.txt # Python 依赖清单
│
├── core/ # 核心基础设施层
│ ├── logger.py # 统一日志(分文件、10MB 滚动)
│ ├── device_pool.py # 设备池(SQLite 清单 + adb 在线状态 + 型号采集)
│ ├── adb_helper.py # adb 命令封装(全局锁,绝不 kill-server)
│ ├── device_worker.py # BaseWorker 基类 + 设备生命周期 + 心跳看门狗
│ ├── task_manager.py # TaskManager 调度器 + 前台App扫描器
│ ├── u2_helper.py # uiautomator2 通用辅助函数
│ ├── uiauto_helper.py # uiautodev 元素抓取客户端
│ ├── apk_manager.py # APK 上传/解析/批量安装
│ ├── ocr.py # 屏幕 OCR(RapidOCR,条件判断 OCR 选择器)
│ ├── ssh_client.py # SSH 统一执行(paramiko 纯密码 / 免密密钥,预留运维用)
│ ├── tailscale_client.py # Tailscale API v2 客户端
│ ├── models.py # SQLAlchemy 数据模型 + 数据库初始化
│ └── actions/ # 全局 Action 框架
│
├── tasks/ # 任务定义层(每个 App 一个子包)
│ ├── __init__.py # 全局任务注册表
│ ├── base.py # BaseTask 基类 + @register_task 装饰器
│ ├── douyin/ # 抖音养号任务
│ └── generic/ # 通用步骤任务(可视化编辑器编排)
│ └── task.py # 步骤执行引擎(open_app/click/swipe/if_el...)
│
├── templates/admin/ # 前端页面
│ ├── monitor.html # 单页应用(监控/任务/分组/日志/用户/工具)
│ └── login.html # 登录页
│
├── static/admin/ # 前端 JS 模块(monitor.html 按依赖顺序加载)
│ ├── base.js # 通用基础:API/CSRF/权限/Tab切换/子分栏/模态框
│ ├── list.js # 统一列表组件(搜索+分页+排序)
│ ├── monitor.js # 监控页(设备表/截图/异常汇总)
│ ├── editor.js # 步骤编辑器(拖拽/条件判断/元素抓取)
│ ├── tasks.js # 任务 Tab + 自定义动作
│ ├── tools.js # 工具 Tab(剪贴板/设备池管理/adb终端/Tailscale/远程看屏)
│ ├── apps.js # 工具 Tab-应用管理(APK/设备已装应用)
│ ├── admin.js # 管理 Tab(分组/日志/用户)+ 初始化
├── static/fonts/ # 自托管字体(Bricolage Grotesque + IBM Plex Mono)
│
├── data/
│
├── data/ # 运行时数据
│ ├── users.db # SQLite(用户/分组/任务/自定义动作/APK记录)
│ └── apks/ # 上传的 APK 文件存储
│
├── logs/ # 日志文件(自动生成,10MB 滚动保留 5 份)
├── bin/adb/ # adb 可执行文件(Windows: adb.exe + dll)
├── scripts/ # 实用脚本
│ ├── pack.py # 打包项目为 zip(排除运行时产物)
│ └── supervise.sh # 进程守护(崩溃自动重启)
└── doc/ # 项目文档
├── API.md # API 文档
├── ARCHITECTURE.md # 架构详解
├── DEPLOY.md # 部署指南
├── DEVELOPMENT.md # 开发指南
└── TASK_DEV.md # 任务开发指南(新增 App 任务模板)
├── ARCHITECTURE.md # 架构详解
├── DEPLOY.md # 部署指南
└── API.md # API 接口文档
核心概念
设备生命周期
设备池选定设备(TaskManager._running 内存锁保证互斥)
↓
IP:5555 → adb connect 直连;USB(serial 无冒号)→ 本机 adb 或 220 远程 adb server
↓
u2.connect(连接 uiautomator2,自动推送 atx-agent)
↓
Worker.run_task(执行业务逻辑)
↓
释放(不 disconnect,遵守共享 adb transport 红线)
单实例部署下互斥由调度器内存锁保证;多实例场景可扩展 SQLite 行锁(见 doc/STF_REMOVAL.md 阶段 4)。
Worker — 单设备执行线程
每台设备对应一个 Worker 线程,继承 BaseWorker(core/device_worker.py)。基类已封装:
- 设备获取(IP:5555 直连 / USB 远程 adb server,try/finally 保证释放)
- adb 连接 + u2.connect(带 30 秒超时保护)
- 状态上报(实时推送到前端监控大屏)
- 异常捕获(设备离线不重试,其他异常按策略重试)
- stop 停止信号(循环里检查
self.stopped()) - 心跳看门狗(120 秒无心跳自动标记卡死)
子类只需实现 run_task(d) 方法专注业务逻辑。
TaskType — 任务类型
| 任务类型 | task_type | 说明 |
|---|---|---|
| 抖音养号 | douyin_nurture |
自动看视频 + 随机点赞,被退出自动重连 |
| 通用步骤 | generic_steps |
可视化步骤编辑器编排流程,支持循环/点击/滑动等 |
TaskJob — 任务计划
一个 TaskJob 描述"什么时候、在哪些设备上、用什么参数执行什么任务":
| 字段 | 说明 | 示例 |
|---|---|---|
task_type |
任务类型 | "douyin_nurture" |
target |
目标设备 | {"mode": "all"} 或 {"mode": "group", "group_name": "A组"} 或 {"mode": "serial", "serial": "192.168.1.100:5555"} |
params |
任务参数(与默认值深合并) | {"watch_count": 50, "actions": {"like": {"params": {"rate": 0.5}}}} |
schedule |
调度策略 | {"mode": "once"} 或 {"mode": "cron", "cron": "0 9 * * *"} 或 {"mode": "cron_stop", "cron": "0 9 * * *", "stop_cron": "0 18 * * *"} |
retry |
重试策略 | {"max_attempts": 3, "delay": 60} |
enabled |
是否启用 | true |
调度模式
| 模式 | 行为 |
|---|---|
once |
手动执行(前端点"立即执行") |
cron |
定时启动:到 cron 时间点自动启动所有目标设备 |
cron_stop |
定时启停:启动 cron 到点启动,停止 cron 到点停止本任务 worker |
简单设置:编辑任务时选"定时启动/定时启动+停止"后,频率用下拉选择——每天(选时间)、每小时(整点)、每隔 N 小时、每周(选星期+时间)——cron 表达式自动生成,不需要懂 cron 语法。老手可在"自定义 cron(高级)"里直接填。
cron 表达式为标准 5 段格式:分 时 日 月 周(如 0 9 * * * = 每天 9:00,0 */2 * * * = 每 2 小时整点,周 0/7 均为周日)
运行窗口:任务可勾选"启用运行窗口",设置每天允许运行的时间段(如 21:00-09:00 = 晚 9 点到次日早 9 点,支持跨午夜)。窗口外定时触发和手动执行都不会启动(手动执行会提示"当前不在运行窗口内")。典型用法:每小时定时 + 窗口 09:00-21:00,即只在白天每小时跑一次。
设备分组
设备分组存于 SQLite,便于按批次/项目分组下发任务。一个 Job 指定 target.mode="group" 时,调度器展开为组内全部设备。
进度上报
Worker 通过 self.set_progress() 上报通用进度字段,前端统一解析展示:
self.set_progress(done=5, total=80, unit="视频",
action_counts={"like": 3})
前端展示:进度条 + 5/80 视频 + 点赞 3 徽章。
配置说明
所有核心配置在 config.py,任务参数不放在这里(放各自 tasks/xxx.py 顶部)。
| 配置项 | 默认值 | 说明 |
|---|---|---|
ADB_PATH |
自动识别 | adb 二进制路径,自动区分 Windows/Linux |
WEB_HOST |
0.0.0.0 |
Web 监听地址(0.0.0.0 支持局域网访问) |
WEB_PORT |
18050 |
Web 端口(避开 Windows 动态端口范围) |
DATA_DIR |
data/ |
持久化数据目录 |
APK_DIR |
data/apks/ |
APK 文件存储目录 |
USB_ADB_HOST |
100.100.10.1 |
USB 设备所在部署机(220)的 Tailscale IP(USB 设备远程 adb server) |
USB_ADB_PORT |
5037 |
220 adb 容器监听端口(host 网络模式) |
TAILSCALE_API_KEY |
(.env 配置) | Tailscale 管理 API key(Settings → API Access Tokens) |
TAILSCALE_TAILNET |
按邮箱前缀 | tailnet 名/ID |
必须修改的配置:.env 里的 WEB_SECRET_KEY(会话密钥,不入 git);其余均为可选(默认值开箱即用)。
Web 管理后台
页面结构
单页应用(templates/admin/monitor.html),6 个 Tab;任务/工具 Tab 内部再有页内子分栏:
| Tab | 功能 | 可见性 |
|---|---|---|
| 监控 | 设备状态大屏:在线/离线、型号、运行任务、当前动作、进度条、前台 App、截图、勾选批量操作(亮屏/息屏/停止);导航栏「📺 大屏」打开全屏监控墙(/wall,缩略图+统计+时钟,挂墙/电视用) | 所有登录用户(设备操作按钮需"设备控制"权限) |
| 任务 | 子分栏:任务计划(CRUD/启用停用/立即执行/下次运行时间/离线设备自动跳过)、自定义动作(打包复用) | 所有登录用户可看,写操作需"任务管理"权限 |
| 分组 | 设备分组管理:创建/编辑/删除分组 | 所有登录用户可看,写操作需"任务管理"权限 |
| 日志 | 实时日志查看:按模块切换(core/task/web/action) | 需"日志查看"权限 |
| 用户 | 用户管理:创建/删除/修改密码/分配权限 | 仅管理员 |
| 工具 | 子分栏:剪贴板注入、adb 远程终端(快捷命令/自动 -s)、设备池管理(新增/停用/删除/一键重连/型号采集)、远程看屏(MJPEG 实时流 + 点击/滑动/按键/文字)、Tailscale 管理(改名/授权/密钥不过期/设置IP/auth key)、应用管理(APK 上传安装)、应用版本管理(按包名查所有设备版本)、设备已装应用 |
仅管理员 |
任务/工具 Tab 的子分栏会记住上次选中的位置;无权限的 tab 和按钮自动隐藏。
用户与权限
默认账号 admin / admin123(管理员,拥有全部权限)。管理员可在"用户"Tab 创建普通用户并分配权限:
| 权限位 | 说明 | 覆盖功能 |
|---|---|---|
任务管理 tasks |
任务/自定义动作/分组的增删改、启停、立即执行 | 任务 Tab、分组 Tab 的写操作 |
设备控制 devices |
停止设备、释放占用、清除异常、前台扫描、元素抓取 | 监控 Tab 的设备操作按钮、步骤编辑器的"抓取元素" |
应用管理 apks |
APK 上传、安装、删除 | 应用管理/设备已装应用(当前并入工具 Tab,工具页整体仅管理员可见) |
日志查看 logs |
日志页 | 日志 Tab |
规则:
- 管理员拥有全部权限,不受权限位限制
- 查看类接口(设备/任务/分组列表、状态、截图)所有登录用户可用
- 用户管理仅管理员可用;不能删除/取消最后一个管理员
- 无权限的 tab 和按钮在界面上自动隐藏(后端同样拦截,返回 403)
前台 App 扫描
监控页"扫描前台App"按钮,获取所有设备当前前台 App。不打扰设备:
| 设备状态 | 处理方式 |
|---|---|
| worker 运行中(IP:5555) | 复用已有 ADB 连接查询 |
| worker 运行中(USB) | 经 220 远程 adb server 查询 |
| 完全空闲 | 返回"空闲"(不主动 connect,避免扰动共享 adb transport) |
截图功能
监控页每台设备可查看实时截图。用 adb exec-out screencap -p,只读操作,任务运行中也能安全调用(不抢占 u2 的 atx-agent 通道)。
元素抓取
任务编辑器的"抓取元素"按钮可拉取设备当前 UI 元素树,点击元素一键回填选择器。依赖本地运行的 uiautodev 服务(端口 20242),web_server.py 启动时会自动拉起。
任务系统
抖音养号(douyin_nurture)
自动观看抖音视频,按配置随机执行点赞操作。参数(在 tasks/douyin/task.py 顶部):
| 参数 | 默认值 | 说明 |
|---|---|---|
watch_count |
80 | 观看视频数量(0=不限,靠时长停止) |
watch_min / watch_max |
5.0 / 35.0 | 单视频观看时长范围(秒) |
max_duration |
0 | 最大运行时长(秒),0=不限时 |
swipe_min / swipe_max |
0.25 / 0.50 | 上滑手势时长范围(秒) |
gap_min / gap_max |
1.0 / 3.0 | 视频间隔时长范围(秒) |
actions.like.enabled |
true | 是否启用点赞 |
actions.like.params.rate |
0.3 | 点赞概率(0~1) |
actions.like.params.method |
by_element | 点赞方式:by_element(找红心) / double_click(双击) |
终止条件(哪个先到就停):watch_count 数量到 / max_duration 时长到 / 手动停止。
通用步骤(generic_steps)
通过可视化步骤编辑器编排任务流程,worker 按步骤顺序执行。支持的步骤类型:
| 步骤类型 | 说明 |
|---|---|
open_app |
启动 App(指定包名,可选等待首页) |
stop_app |
强制结束 App(am force-stop,清后台,下次打开冷启动) |
screen_on |
亮屏(息屏时唤醒并滑动解锁) |
screen_off |
息屏 |
keep_screen |
保持亮屏/恢复自动息屏(充电时屏幕常亮,适合长任务) |
key_event |
按键:返回/Home/回车/菜单等(退出评论、返回上一页) |
swipe |
滑动(上/下/左/右,可配置时长) |
swipe_until |
滑动直到元素出现(最多 N 次,可选找到后点击) |
click_xy |
点击坐标(屏幕百分比,无选择器时兜底) |
long_click |
长按元素(选择器 + 时长) |
wait_el |
等待元素出现(条件等待,替代固定时长) |
input_text |
输入文字(随机候选/指定文字,可选输入前先清空) |
click |
点击元素(支持 xpath/description/resourceId/text 选择器) |
wait |
等待(可配置时长范围) |
loop |
循环块(含子步骤,可配置循环次数) |
group |
动作组(含子步骤,按序执行一次) |
if_el |
条件判断:找元素(支持 xpath 等选择器或 OCR识别 截屏匹配图片文字),命中执行"找到时"分支,未命中执行"未找到时"分支;OCR 命中可自动点击。分支可嵌套循环/条件判断 |
步骤编辑器特性:操作库按分类分组、卡片可拖拽排序/跨层级嵌套(循环套循环)、☑ 多选打包自定义动作、
"测试此步骤"真机验证、"抓取元素"回填选择器、条件判断的 OCR 识别依赖 rapidocr_onnxruntime(跨平台)。
新增 App 任务
参照 tasks/douyin/ 结构,6 步即可新增一个 App 任务,详见 doc/TASK_DEV.md。
日志系统
日志按模块分文件,自动滚动(10MB 一份,保留 5 份历史):
| 文件 | 模块前缀 | 内容 |
|---|---|---|
logs/core.log |
core.* |
adb/worker/task_manager/设备池 核心程序 |
logs/task.log |
task.* |
任务执行(worker 业务逻辑) |
logs/web.log |
web.* |
Web 请求/管理 |
logs/action.log |
action.* |
操作执行(点赞/评论等) |
使用方式:
from core.logger import get_logger
log = get_logger("task.douyin") # 写入 task.log
log.info(f"[{self.serial}] 开始任务")
Web 后台"日志"Tab 可实时查看各模块日志。
常用脚本
| 脚本 | 用途 |
|---|---|
python web_server.py |
启动 Web 管理后台 |
python scripts/pack.py |
打包项目为 zip(排除日志/数据库/APK) |
bash scripts/supervise.sh |
进程守护(web_server 崩溃自动重启) |
常见问题
端口被占用(WinError 10013/10048)
web_server.py 会自动重试候选端口(原端口 → 127.0.0.1:原端口 → 127.0.0.1:原端口+1~+5)。如果全部失败,检查 Windows 动态端口范围:
netsh interface ipv4 show excludedportrange protocol=tcp
修改 config.py 中的 WEB_PORT 到一个不在排除范围内的端口。
设备显示离线
先确认本机 adb devices 能看到设备(工具 → 设备池管理 → 一键重连);看不到则检查 Tailscale 是否在线、设备是否加入了正确的 tailnet(平台与设备必须在同一 tailnet)。
新增设备后任务不调度它
新设备需在「工具 → 设备池管理」添加(serial 为 IP:5555),仅连上 adb 不会进入设备池。添加后自动连接并采集型号,下一轮任务即可调度。
u2.connect 卡死
u2.connect() 在 atx-agent 无响应时会永久 hang。基类已用 ThreadPoolExecutor + 30 秒超时 保护,超时自动放弃。如果频繁超时,检查设备 atx-agent 是否正常(重启设备或重新推送 atx-agent)。
任务运行中看门狗误杀
看门狗 120 秒无心跳会标记卡死。长耗时操作(如长视频等待)需在循环内周期性调用 self.heartbeat()。
中文输入失败
uiautomator2 默认 IME 不支持中文,需切到 FastInput 输入法:
try:
d.set_fastinput_ime(True)
d.send_keys("中文内容")
finally:
d.set_fastinput_ime(False)
修改 core/ 后不生效
web_server.py 以 debug=False 运行,Python 不会热重载。修改 core/ 目录下的文件后必须重启 web_server 进程。
元素抓取按钮不可用
依赖 uiautodev 服务(端口 20242)。确保已安装 pip install uiautodev。web_server.py 启动时会自动拉起该服务。
更多文档
| 文档 | 内容 |
|---|---|
| doc/DEVELOPMENT.md | 开发手册:开发流程、git 工作流、技术红线、环境、本地开发 |
| doc/TASK_DEV.md | 任务开发指南(新增 App 任务的完整模板和规范) |
| doc/ARCHITECTURE.md | 架构详解(分层设计、数据流、关键设计决策) |
| doc/DEPLOY.md | 部署指南(环境准备、设备池配置、生产部署) |
| doc/STF_REMOVAL.md | STF 摘除迁移记录(阶段 0-3 已完成,含多实例锁方案) |
| doc/API.md | API 接口文档(全部 HTTP 接口说明) |
注意:所有 git 操作(含 push 到 dev)都需负责人确认后才能执行,详见开发手册。修改
core/、tasks/、templates/后需重启服务/强刷浏览器才生效。
技术栈
| 组件 | 用途 |
|---|---|
| Flask + Flask-Login | Web 后台 + 用户认证 |
| Flask-SQLAlchemy | SQLite 数据持久化 |
| APScheduler | 定时任务调度 |
| uiautomator2 | Android UI 自动化 |
| uiautodev | UI 元素抓取(步骤编辑器"抓取元素") |
| pyaxmlparser | APK 元信息解析 |
| 自建设备池 | 设备清单/在线状态/型号(SQLite + adb) |