butubb 65bab7e25c fix: schema 迁移幂等兜底——create_all 已建列而 schema_version 未记录时重复 ALTER 报 duplicate column,遇错回滚并照记版本自愈
典型场景:create_all 已按当前模型把列/表直接建好(如 perms、device.model),
而 schema_version 又因历史中断没记录,导致每次启动重复 ALTER 报错。
duplicate column name 说明列已存在=迁移目标已达成:回滚本次语句后仍记录
版本号,一次启动即自愈;其它异常才中止本批迁移。
2026-09-09 14:16:15 +08:00

设备自动化后台(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.x IP
  • 新增设备在后台「工具 → 设备池管理」添加 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/

首次使用流程

  1. 登录后台 → 用 admin/admin123 登录,建议立即修改密码
  2. 添加设备 → "工具 → 设备池管理"添加设备(serial 形如 100.100.10.20:5555,自动连接并采集型号)
  3. 查看设备 → 首页"监控"Tab 展示设备池状态(在线/型号/任务)
  4. 创建分组(可选)→ "分组"Tab 按批次/项目给设备分组
  5. 创建任务 → "任务"Tab 新建任务,选择任务类型、目标设备、参数、调度
  6. 执行任务 → 任务列表点"立即执行",或在"监控"Tab 勾选设备批量操作(亮屏/息屏/停止)
  7. 查看日志 → "日志"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)
S
Description
No description provided
Readme
4.8 MiB
Languages
Python 64.5%
JavaScript 26.1%
HTML 8.7%
Shell 0.4%
CSS 0.3%