Files
auto_control/doc/DEVELOPMENT.md
T
butubb beda0f3779 feat(发布计划): 改按名字发(文件名 名字_日期_编号)+ 账号管理整页下线 + 自动切号一轮发完
发布计划不再绑设备、不再依赖账号台账:上传文件名 `名字_日期_编号`(标题行
`标题 || 正文_名字_日期_编号`),运行时读本机当前抖音昵称取计划;靠循环块
「一轮把本机待发的号跑完」自动切号,一直发到发完。

· 解析/配对换键:右锚定认名字(不许含下划线、不许纯数字 —— 不合法直接拒收并说清原因),
  删掉三处"手机号必须命中台账"的校验,加索引 ix_video_plan_name_date(启动时补建)
· 新步骤三个:读本机账号名字 / 切换账号(**按名字点控件**、切完回读校验、遇登录页停本设备)/
  循环本机待发的号(试过不回头、跑一轮没进展就换号、停止·超时·上限三个干净出口)
· 发布骨架改 18 步:读名字 → 校验账号(cmp_source=account_target)→ 推送(**推送前重读昵称核对**)
  → 抖音流程 → 标记结果;push_release 的 account 默认 name
· 账号管理整页下线:删 core/ledger.py(serial_of 迁到 core/device_pool.py)、6 个 /api/ledger*、
  「账号」页与 ledger.js、设备池「账号」列、设备端账号块;发布计划升为一级页签;
  device_account 表与 42 行历史数据保留、仍在备份清单内(**停用不等于删数据**)
· 人工入口「按名字找设备」:新增 core/account_probe.py(只读探测、跳过在跑任务的设备)
  + POST /api/video_plan/push_by_account,没设备登着的号逐条报出来;
  推送时把"实际是谁发的"写进 posted_serial / posted_device_name
· 通知:新增 task.account.switch_failed / task.account.need_login
· schema v12(posted_*)/ v13(device.cur_account_name / cur_name_at)
· 文档同步:TASK_DEV / API / DATA_MODEL / ARCHITECTURE / NOTIFY / DEVICE_AGENT / DEPLOY /
  DEVELOPMENT / README / .env.example(两个抖音控件选择器抓一次填 .env)
· 新增 scripts/tests/:6 套用例 + run_all.py(临时库 + 假设备,不碰真实数据、不连真机)
2026-10-12 09:02:40 +08:00

16 KiB
Raw Blame History

开发手册(DEVELOPMENT)

适用读者:所有参与 auto_control 开发的人。动手前先读 §2 技术红线。 相关文档:ARCHITECTURE.md(架构,先懂再改)、DATA_MODEL.md(表结构)、doc/README.md(文档索引与维护约定)。


1. 开发流程

1.1 分支策略

分支 用途
dev 开发分支,所有新功能/修复从它切出、合并回它
main 生产分支,只放已确认的稳定版本
fix/xxx · feat/xxx · chore/xxx 单个改动的临时分支(从 dev 切出)

生产环境 = 部署机 192.168.20.220 的 /mnt/data/openstf/auto_control(python-app 容器)。

1.2 git 铁律

所有 git 操作都必须先经项目负责人明确确认,包括但不限于 commit / push(即使 push 到 dev 也要确认)/ merge / rebase / reset / branch -D。

标准流程:

① 本机新建分支          fix/xxx 或 feat/xxx(从 dev 切出)
② 分支上开发 + 自测     跑通本机(服务/接口/页面),能自测就别只靠"看代码没问题"
③ 交负责人确认          ★ 未经确认不合 dev
④ 合并到 dev            确认通过后(保持线性:rebase 后 ff-merge)
⑤ dev 整体就绪          功能齐全、验证完毕
⑥ 合并到 main           ★ 负责人确认后
⑦ 生产 220 部署         git pull → docker restart python-app → 验证(见 DEPLOY.md §3)

每个改动单独分支 + 单独 commit,主题单一,便于评审与回退。不允许"开发完顺手 commit/push"。

修复也走这条流程,包括线上紧急修复(2026-09-14 补:当天两次 APK 安装修复因为"生产卡着" 直接提交在 dev 上、紧接着就合 main 部署,被负责人纠正):

  • 紧急修复照样从 dev 切 fix/xxx 分支——不允许直接 commit/push 到 dev, 哪怕改动只有几行、哪怕生产正卡着
  • 第 ② 步的"自测"要跑通本机服务(起服务/点页面/看日志),不能只有脚本级验证
  • 第 ③ 步的确认要点名两件事:「是否合 dev」和「是否合 main + 部署生产」—— 紧急场景可以一次性问清,但不能把两个节点合并成一次默认同意
  • 线上正卡住时:先恢复(重启/回滚等运维动作,经确认即可执行),再按流程走修复

2. 技术红线(违反会打断共享 adb transport 或造成生产事故)

# 红线 为什么 代码里的体现
1 绝不 adb kill-server 会断掉所有设备的 adb transport,运行中任务全废 core/adb_helper.py 只 connect 不 kill;Web 层硬拦截 kill-server/disconnect 字符串
2 绝不对 IP:5555 设备 adb disconnect 该地址的 adb transport 是共享的 adb_disconnect 全项目零调用方;STFDevice.release() 空实现
3 空闲设备扫描不主动 connect/disconnect 避免扰动共享连接 前台扫描对空闲设备直接返回"空闲";设备发现用 socket 探测
3b (3 的边界)只读 adb -s <serial> shell … 是允许的,它不建立/断开 transport 设备已在 adb devices 里就说明 transport 现成,读一下不扰动任何人 电量采集 core/device_battery.py 就是这么读包括空闲设备在内的全部在线设备(dumpsys battery,实测 0.2~0.35s/台);别"顺手"给它加 connect
4 adb key 保持历史 key 不变 设备信任该 key,换 key 全部 unauthorized 部署沿用 ~/.android/adbkey
5 生产(220)默认只读 生产事故成本高 任何写操作(pull/重启/改文件)都需负责人确认
6 新增持久化表必须进备份覆盖清单 漏登记 = 等于没备份 SUMMARY_TABLES 已由模型元数据自动派生(加了 ORM 表就进清单);手工要做的只有补 TABLE_LABELS 中文标签,详见 DEPLOY.md §5.2
7 功能/配置/接口改动必须同步文档 文档落后会误导开发与运维 见 §6;索引 doc/README.md

其它开发约束

  • web_server.py 以 debug=False 运行:改 core/、tasks/、templates/ 后必须重启;改前端 JS 后强刷浏览器
  • 任务参数放各自 tasks/<app>/ 顶部,不放 config.py
  • 运行时数据不提交 git:data/、logs/ 全是运行时产物
  • 不要移除分页与错峰:监控/列表页已分页(100 台设备只渲染 10 行/页);任务批量触发已错峰(_START_STAGGER_SEC)
  • 不要手工改库结构:走 SCHEMA_MIGRATIONS(模型表)或幂等原生建表

3. 本地开发

3.1 安装

python -m venv .venv
# Windows: .venv\Scripts\activate    Linux/macOS: source .venv/bin/activate
pip install -r requirements.txt

确认 adb key(沿用既有 key,否则设备连不上):ls -la ~/.android/adbkey。

3.2 启动

python web_server.py
# 访问 http://localhost:18050/   admin / admin123

需要 AI 控制台 / MCP 时,另起一个进程:

MCP_ALLOW_WRITE=1 MCP_PLATFORM_USER=admin MCP_PLATFORM_PASS=<密码> \
  python -m mcp_server.mcp_server      # 默认 http://127.0.0.1:8033/mcp

(容器里由 scripts/start.sh 自动拉起,不需要手动。)

3.3 调试

目的 做法
看日志 logs/core.log(adb/调度)、task.log(任务)、web.log(Web/AI)、action.log;或「日志」Tab
看设备/任务状态 监控页;或 GET /api/status、GET /api/health
后端逻辑验证 直接 python -c "…" 调用 core/、tasks/ 的函数(如构造 worker 检查参数合并)
前端验证 无 npm/构建,改完强刷;浏览器控制台看报错
接口 500 巡检 python scripts/regression_test.py(⚠️ 当前在 Windows 上会因 signal.alarm 报错,Linux/macOS 可用)
发布计划(按名字发)用例 python scripts/tests/run_all.py —— 6 套:解析 / 上传配标题 / 读名字与作用域 / 切换账号 / 循环本机待发的号 / 按名字找设备推送。纯临时库 + 假设备,不连真机(见 scripts/tests/run_all.py 顶部)
停止设备/清异常 监控页「停止全部 / 停止选中 / 清除全部异常」
AI 建任务(designer)验证 AI 控制台 →「AI 建任务」选空闲设备跑一轮;脚本方式见 AI_TASK_GEN.md §10.5。草稿只落 app_meta.agent_task_draft(不是任务),验证完 POST /api/agent/task_draft/clear 清掉。注意它会真在设备上点按(导航类动作),且与聊天共用同一个运行槽

改了数据库/配置想复原:删 data/users.db* 会丢数据,别这么干;用「系统 → 备份/导入」或先手工复制一份 data/。

3.4 写测试的约定(重要)

本仓库没有单元测试框架,验证靠"跑起来 + 真实调用"。写验证脚本时必须:

  • 不要碰真实数据:需要会话/任务/分组时自建(如 POST /api/agent/conversations 建专属会话),绝不要依赖"当前选中项",也不要删自己没建的东西
  • 改配置前后都要回读校验:改前 GET 存原值,收尾写回后再 GET 比对,不一致要显式报错
  • 临时数据用完即删,并核对"集合已复原"

教训:曾用浏览器脚本跑 AI 控制台冒烟,脚本清空 localStorage 后前端自动选中了用户最近的会话,收尾的"删除测试会话"把用户真实会话删了;同一脚本还把 AI 配置改成了假值。恢复手段见 DATA_MODEL.md §7 与 git 历史。


4. 配置速查

4.1 配置文件与优先级

  • config.py:程序级常量(端口、路径、USB 远程 adb 等),改它要重启
  • .env(项目根,不入 git):密钥与可覆盖配置,逐行解析 + os.environ.setdefault(真实环境变量优先);数据库目标(DEPLOY_ENV + DB_*)也在这里
  • app_meta(数据库 KV):运行时可改的配置(AI 配置、设备发现参数),在界面上改

4.2 config.py 常量

常量 默认值 来源 说明
ADB_PATH bin/adb/adb(.exe) 按平台自动 不可用 env 覆盖
WEB_HOST / WEB_PORT 0.0.0.0 / 18050 硬编码 18050 避开 Windows 动态端口段
DATA_DIR / APK_DIR data/ / data/apks/ 代码计算
BACKUP_DIR / RESTORE_STAGING_DIR / RESTORE_PENDING_DIR data/backups / data/restore_staging / data/restore_pending 代码计算 备份相关
VIDEO_DIR data/videos DATA_VIDEO_DIR 视频发布计划的素材目录(不进整库备份;测试要指到临时目录)
MAX_CONTENT_LENGTH 2 GiB MAX_UPLOAD_MB 单次上传体积上限;必须大于最大的 APK(实测 336MB),否则 APK 上传会被卡死
DEPLOY_ENV dev .env 可覆盖 声明这套配置连哪个环境的库;与库名绑定(dev→auto_control_dev,prod→auto_control),不符拒绝启动
DB_HOST / DB_PORT / DB_USER / DB_PASSWORD / DB_NAME 空 / 3306 / 空 / 空 / 空 .env 可覆盖 MySQL 目标;DB_HOST 为空则回退 SQLite(生产禁止静默回退,需 DB_ALLOW_SQLITE_FALLBACK=1)
DB_CHARSET / DB_COLLATION utf8mb4 / utf8mb4_bin .env 可覆盖 排序规则必须用 _bin(逐码点比较,等价 SQLite 的大小写敏感语义)
DATABASE_URL 空 .env 可覆盖 完整连接串,优先级最高;脚本临时指向别的库时用
DB_ALLOW_ENV_MISMATCH / DB_ALLOW_SQLITE_FALLBACK 0 .env 逃生阀,见 DEPLOY.md
USB_ADB_HOST / USB_ADB_PORT 100.100.10.1 / 5037 .env 可覆盖 USB 设备所在部署机的远程 adb server
TAILSCALE_API_KEY / TAILSCALE_TAILNET 空 .env Tailscale 管理功能
DISCOVERY_PORT / DISCOVERY_INTERVAL 5555 / 60 .env 可覆盖 设备发现(网段在工具页配置,存 app_meta)
DISCOVERY_SUBNETS 局域网 + Tailscale 网段 硬编码列表(当前无 env 支持) 默认扫描网段
STF_* 空 .env 已废弃,仅历史保留,代码不再使用

4.3 进程读取的环境变量(不在 config.py)

变量 默认 谁读 说明
WEB_SECRET_KEY 未配置则随机 web_server.py 会话密钥,生产必须固定
DISABLE_SCHEDULER 未设 core/task_manager.py 设任意值则不启动 cron 调度器(测试用)
DATA_BACKUP_DIR / DATA_RESTORE_STAGING_DIR / DATA_RESTORE_PENDING_DIR data/backups 等 config.py 备份/恢复目录改到别处;自动化测试必须指到临时目录(否则测试造的待生效恢复任务会在下次重启被当成用户操作消费)
MCP_ENABLED 1 scripts/start.sh =0 则不后台拉起 MCP
MCP_ALLOW_WRITE 0(脚本内强制 1) mcp_server/config.py 写操作总开关
MCP_PLATFORM_URL / _USER / _PASS http://127.0.0.1:18050 / admin / 空 mcp_server/config.py 登录平台的凭据(改过 admin 密码要同步)
MCP_ALLOWED_SERIALS 空=不限 同上 逗号分隔白名单(语义缺口见 backlog)
MCP_HTTP_HOST / _PORT 0.0.0.0 / 8033 同上
MCP_SCREENSHOT_WIDTH / MCP_JPEG_QUALITY 540 / 70 同上 返回给模型的截图层参数
MCP_AUDIT_FILE /var/log/mcp/audit.log(脚本兜底 /tmp/mcp_audit.log) 同上 审计日志路径
AGENT_API_BASE / AGENT_MODEL / AGENT_API_KEY / DEEPSEEK_API_KEY DeepSeek 默认地址 / 默认模型 / 空 mcp_agent/config.py 仅 CLI 用;Web 端 AI 配置优先读数据库 app_meta
AGENT_MCP_URL / AGENT_MAX_STEPS / AGENT_TIMEOUT / AGENT_LANG http://127.0.0.1:8033/mcp / 40 / 120 / zh 同上
ANDROID_ADB_SERVER_ADDRESS / _HOST / _PORT 未设 adb 客户端自身(非本项目代码) 把 adb 调用指向远程 server;两套变量名都要设

mcp_server/config.py 与 mcp_agent/config.py 不读 .env(只读进程环境变量),与根 config.py 的行为不同。


5. 常见开发任务

每一项的"改哪里"清单也见 ARCHITECTURE.md §10。

5.1 新增 HTTP 接口

  1. 在对应功能域的 web/xxx_api.py 加 @bp.route(...) + 鉴权装饰器(@login_required / @perm_required(PERM_X) / @admin_required)
  2. 新蓝图需在 web/__init__.py 的 register_blueprints 里注册
  3. 更新 API.md(路由索引表 + 详细小节)

5.2 新增数据库字段/表

  • 模型改 core/models.py;新表 create_all() 会建(幂等:已是模型就会自动建/补列)
  • 老库要在 SCHEMA_MIGRATIONS 里加迁移(版本号递增 + SQL)
  • 新增表:SUMMARY_TABLES 由模型元数据自动派生(=自动进备份覆盖清单), 仍需手工做的是补 TABLE_LABELS 的中文标签(红线)
  • 更新 DATA_MODEL.md 与 DEPLOY.md §5.2

5.3 修改前端

改什么 文件
页面结构 / 样式 / 引入脚本 templates/admin/monitor.html
公共工具(API/权限/Toast/Tab) static/admin/base.js
列表分页排序 static/admin/list.js
各功能域逻辑 static/admin/{monitor,editor,tasks,tools,apps,admin,agent,system,markdown,dedup,release}.js

新增 JS 模块:建文件 → 在 monitor.html 里按依赖顺序加 <script src> → 更新 ARCHITECTURE.md §6.3 与本文 §5.3。

5.4 新增步骤类型 / 任务类型

见 TASK_DEV.md(步骤类型要同时改后端 STEP_TYPES 与前端 STEP_LIB)。

5.5 新增 MCP 工具

在 mcp_server/mcp_server.py 加 @mcp.tool() 函数;写操作必须挂门控(_check_write → _check_serial → _ensure_device_free);更新 MCP.md。

5.6 新增常驻线程

参考 core/device_discovery.py 的 init_app(app) / shutdown() 模式;更新 ARCHITECTURE.md §3。


6. 文档同步(红线)

任何功能 / 配置 / 接口 / 表结构的增删改,都要在同一个 commit 里更新对应文档。

改动类型 必须更新
HTTP 接口 API.md
表结构 / 迁移 DATA_MODEL.md(+ 备份覆盖清单)
config.py / .env 键 本文 §4 + DEPLOY.md + .env.example
页面 Tab / 子分栏 / 前端模块 ARCHITECTURE.md §6 + 本文 §5.3
任务类型 / 步骤 schema TASK_DEV.md
常驻线程 / 装配顺序 ARCHITECTURE.md §2-3
MCP 工具 MCP.md + MCP_DESIGN.md
对外接入约定 staffdeck/KNOWLEDGE_BASE.md
暂缓项 / 已知问题 backlog/TODO.md

新增文档时:登记进 doc/README.md §1 与根 README 的文档索引。 历史文档(STF_REMOVAL.md)只增不改。


7. 常见坑速查

坑 说明
跨线程访问 DB 必须自推 app context(with app.app_context())
改了不生效 core/、tasks/、templates/ 改动要重启;JS 要强刷
Windows adb 输出 不能用 text=True(非 GBK 字节会崩),要收 bytes 再解码
u2 卡死 u2.connect / d.info 可能永久 hang,必须加超时
抢占死锁 不能在 TaskManager._lock 内调 stop_device
删除不生效 删任务/分组必须显式删行,否则重启会"复活"
前端写了 data-perm 仍可见 它只在 loadMe() 时求值一次;改权限后需刷新
新步骤没生效 后端 STEP_TYPES 与前端 STEP_LIB 要同时改
备份导入后没变化 必须重启服务
.env 不生效 setdefault 语义:已存在的环境变量优先,检查是否被系统环境覆盖

更完整的"代码里写明的坑"见 ARCHITECTURE.md §9.2。