Files
auto_control/doc/DEVELOPMENT.md
T
butubb 24d57d3b96 docs: doc/ 全量重整——按现状重写并建立文档索引;项目统一更名 auto_control
背景:文档长期落后于代码(Tab 数、任务类型、接口示例等多处与现状不符),
且信息分散重复。这次按当前代码状态逐篇重写,并建立统一的文档体系。

新增
- doc/README.md:文档总索引(文档地图 / 推荐阅读路径 / **文档维护约定**)
- doc/DATA_MODEL.md:数据模型(7 张模型表 + 5 张非模型表、迁移机制、app_meta 键、
  数据目录、备份覆盖清单与双向自检)
- doc/AI_CONSOLE.md:AI 控制台机制(会话与 SSE、经验库/动作库蒸馏与召回、巡检、
  Markdown 渲染、推理链、token 统计、故障排查)

重写(按现状,去掉过时与重复)
- README.md:7 个 Tab、18 种步骤、设备生命周期、调度/窗口语义、常见问题;修掉
  「6 个 Tab / 分组为顶级 Tab」等过时内容与损坏的目录树
- doc/ARCHITECTURE.md:补启动装配顺序(import 期副作用、A~G 七阶段)、线程与锁清单、
  设备状态机、调度全链路、前端结构与实时通道、设计决策、**已知缺陷与踩坑清单**、扩展点
- doc/API.md:按蓝图重建「接口总索引」(107 条路由含鉴权)+ 分域详细说明 +
  非 JSON 响应汇总 + 错误分支速查
- doc/TASK_DEV.md:18 种步骤全表(参数/默认值/语义)、容器与公共参数、
  选择器与 XPath 序号语义、抓取器建议规则、新增任务类型骨架
- doc/DEPLOY.md:容器入口 start.sh 三件事、发布流程与检查清单、备份覆盖红线、
  按现象分类的故障排查
- doc/DEVELOPMENT.md:流程/红线/本地开发/**测试与写测试的约定**/配置速查/文档同步
- doc/MCP.md:19 个工具的参数级清单、坐标空间、写门控三连、安全与审计
- doc/MCP_DESIGN.md、doc/AI_TASK_GEN.md:标注设计 vs 实现现状,补交叉链接
- doc/backlog/TODO.md:新增「已知缺陷」小节(含复现与影响)+ 已完成留档
- .env.example:按代码实际读取的键重写(补 USB/DISCOVERY/MCP/AGENT,删死配置)

其它
- 项目名统一 auto_control:README/文档/scripts/pack.py 产物名;代码内的
  doc 章节引用(templates/admin/monitor.html)同步更新
- 校验:16 篇文档 156 条相对链接全部可解析;文档中的关键数字与代码核对一致
  (19 个 MCP 工具 / 18 种步骤 / 12 张备份表 / 1 种任务类型)
2026-09-10 22:19:18 +08:00

237 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 开发手册(DEVELOPMENT)
> 适用读者:所有参与 `auto_control` 开发的人。**动手前先读 §2 技术红线**。
> 相关文档:[ARCHITECTURE.md](ARCHITECTURE.md)(架构,先懂再改)、[DATA_MODEL.md](DATA_MODEL.md)(表结构)、[doc/README.md](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"。
---
## 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 探测 |
| 4 | **adb key 保持历史 key 不变** | 设备信任该 key,换 key 全部 `unauthorized` | 部署沿用 `~/.android/adbkey` |
| 5 | **生产(220)默认只读** | 生产事故成本高 | 任何写操作(pull/重启/改文件)都需负责人确认 |
| 6 | **新增持久化表必须登记备份覆盖清单** | 漏登记 = 等于没备份 | `core/system_backup.py` 的 `SUMMARY_TABLES` + `TABLE_LABELS`,详见 [DEPLOY.md](DEPLOY.md) §5.2 |
| 7 | **功能/配置/接口改动必须同步文档** | 文档落后会误导开发与运维 | 见 §6;索引 [doc/README.md](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 安装
```bash
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 启动
```bash
python web_server.py
# 访问 http://localhost:18050/ admin / admin123
```
需要 AI 控制台 / MCP 时,**另起一个进程**:
```bash
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 可用) |
| 停止设备/清异常 | 监控页「停止全部 / 停止选中 / 清除全部异常」 |
> **改了数据库/配置想复原**:删 `data/users.db*` 会丢数据,别这么干;用「系统 → 备份/导入」或先手工复制一份 `data/`。
### 3.4 写测试的约定(重要)
本仓库没有单元测试框架,验证靠"跑起来 + 真实调用"。写验证脚本时**必须**:
- **不要碰真实数据**:需要会话/任务/分组时**自建**(如 `POST /api/agent/conversations` 建专属会话),绝不要依赖"当前选中项",也不要删自己没建的东西
- **改配置前后都要回读校验**:改前 GET 存原值,收尾写回后**再 GET 比对**,不一致要显式报错
- 临时数据用完即删,并核对"集合已复原"
> 教训:曾用浏览器脚本跑 AI 控制台冒烟,脚本清空 `localStorage` 后前端自动选中了**用户最近的会话**,收尾的"删除测试会话"把用户真实会话删了;同一脚本还把 AI 配置改成了假值。恢复手段见 [DATA_MODEL.md](DATA_MODEL.md) §7 与 git 历史。
---
## 4. 配置速查
### 4.1 配置文件与优先级
- `config.py`:**程序级常量**(端口、路径、USB 远程 adb 等),改它要重启
- `.env`(项目根,不入 git):**密钥与可覆盖配置**,逐行解析 + `os.environ.setdefault`(**真实环境变量优先**)
- `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` | 代码计算 | 备份相关 |
| `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 调度器**(测试用) |
| `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](backlog/TODO.md)) |
| `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](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](API.md)(路由索引表 + 详细小节)
### 5.2 新增数据库字段/表
- 模型改 `core/models.py`;新表 `create_all()` 会建
- **老库**要在 `SCHEMA_MIGRATIONS` 里加迁移(版本号递增 + SQL)
- **新增表**:登记进 `core/system_backup.py` 的 `SUMMARY_TABLES` + `TABLE_LABELS`(**红线**)
- 更新 [DATA_MODEL.md](DATA_MODEL.md) 与 [DEPLOY.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}.js` |
新增 JS 模块:建文件 → 在 `monitor.html` 里按依赖顺序加 `<script src>` → 更新 [ARCHITECTURE.md](ARCHITECTURE.md) §6.3 与本文 §5.3。
### 5.4 新增步骤类型 / 任务类型
见 [TASK_DEV.md](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](MCP.md)。
### 5.6 新增常驻线程
参考 `core/device_discovery.py` 的 `init_app(app)` / `shutdown()` 模式;更新 [ARCHITECTURE.md](ARCHITECTURE.md) §3。
---
## 6. 文档同步(红线)
**任何功能 / 配置 / 接口 / 表结构的增删改,都要在同一个 commit 里更新对应文档。**
| 改动类型 | 必须更新 |
|---------|---------|
| HTTP 接口 | [API.md](API.md) |
| 表结构 / 迁移 | [DATA_MODEL.md](DATA_MODEL.md)(+ 备份覆盖清单) |
| `config.py` / `.env` 键 | 本文 §4 + [DEPLOY.md](DEPLOY.md) + `.env.example` |
| 页面 Tab / 子分栏 / 前端模块 | [ARCHITECTURE.md](ARCHITECTURE.md) §6 + 本文 §5.3 |
| 任务类型 / 步骤 schema | [TASK_DEV.md](TASK_DEV.md) |
| 常驻线程 / 装配顺序 | [ARCHITECTURE.md](ARCHITECTURE.md) §2-3 |
| MCP 工具 | [MCP.md](MCP.md) + [MCP_DESIGN.md](MCP_DESIGN.md) |
| 对外接入约定 | [staffdeck/KNOWLEDGE_BASE.md](staffdeck/KNOWLEDGE_BASE.md) |
| 暂缓项 / 已知问题 | [backlog/TODO.md](backlog/TODO.md) |
新增文档时:登记进 [doc/README.md](README.md) §1 与根 [README](../README.md) 的文档索引。
历史文档([STF_REMOVAL.md](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](ARCHITECTURE.md) §9.2。