Files
auto_control/doc/DEVELOPMENT.md
T
butubb 98cdc39224 chore: 删除抖音养号任务类型,平台只保留 generic_steps
tasks/douyin/(task_type=douyin_nurture)整体删除,唯一任务类型是 generic_steps。
所有默认值/文档/接口示例同步改成 generic_steps:

- tasks/:删 douyin 包;__init__ 只注册 generic;base.py/generic 注释改为照 generic 抄
- 默认值:core/models.py(列默认 + 旧 JSON 迁移默认)、core/task_manager.py TaskJob、
  web/tasks_api.py 建任务默认、static/admin/tasks.js 新建任务默认
- core/task_manager.py:去掉 douyin 专属的"清理废弃 comment 参数"迁移块,改为**启动时告警**
  仍残留已删类型的任务(只告警不改数据);run_job_now 对已删类型直接返回明确错误,
  不再"报已触发、线程里静默失败"
- 清理残留:douyin_running 状态位(无任何读取方)、core/__init__、core/actions/*、
  core/logger.py 注释里的抖音示例
- 文档:README(特性/目录树/类型表/参数表/示例)、TASK_DEV(目录树/注册说明/模板引用)、
  ARCHITECTURE(注册示例/action 注册表示例)、API.md(task_types 与任务 JSON 示例)、
  AI_TASK_GEN(P1 去掉 douyin 预设)、DEVELOPMENT
- 注:示例里"抖音"作为**App 名**(MCP 列应用、AI 建任务的需求举例)保留,与任务类型无关

自测(全部通过):类型列表只剩 generic_steps;建任务不传类型默认 generic_steps;传
douyin_nurture 被 400 拒;库里塞残留旧类型任务 → 启动日志告警 + 执行返回明确错误 +
不自动删用户数据;前端新建任务下拉 1 项且默认选中、界面建任务成功;监控页卡片两个按钮 +
覆盖设备正常。临时任务/数据验完已清理,任务集合复原。
2026-09-10 21:43:08 +08:00

292 lines
15 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)
面向本项目开发者:开发流程、git 工作流、环境说明、技术红线、本地开发、常见开发任务。
---
## 1. 开发流程与 git 工作流
### 1.1 分支策略
| 分支 | 用途 |
|------|------|
| `dev` | **开发分支**,所有新功能/修复都在这里开发 |
| `main` | **生产分支**(主分支),只放已确认的稳定版本 |
生产环境 = 部署机 `192.168.20.220` 的 `/mnt/data/openstf/auto_control`(python-app 容器运行 `web_server.py`)。
### 1.2 git 操作铁律(重要)
**所有 git 操作都必须先经项目负责人明确确认后才能执行**,包括但不限于:
- `commit` / `push`(**即使 push 到 dev 也要确认**)
- `merge`(dev → main)
- `revert` / `checkout` / `reset` / `branch -D` 等
**标准流程:**
```
1. 在 dev 分支开发、本地测试
2. 完成改动 → 把改动清单 + 建议 commit 信息 列给负责人
3. 负责人确认 → 才能 commit + push dev
4. 需要发布 → 负责人确认后再合并到 main
5. 部署生产 → 负责人明确指示后才 pull 到 220
```
> 不允许"开发完顺手就 commit/push"。即使是一次性小改动,也要先确认。
---
## 2. 环境说明
| 环境 | 位置 | 说明 |
|------|------|------|
| 开发机 | 本机(192.168.20.57) | `.venv` + 本地运行 `web_server.py` |
| 生产机 | 部署机 220 的 `auto_control` | python-app 容器,`network_mode: host` |
| STF 服务 | ~~`192.168.20.220:7100`~~ | 已停用(代码已摘除依赖)。注意:此处"已停用"与 [STF_REMOVAL.md](STF_REMOVAL.md) 的"待人工确认"项矛盾(220 侧是否已 `docker stop stf` 未核实),需以 220 实际为准 |
| adb 容器 | 220 上 `adb`(host 网络 5037) | USB 设备远程 adb server;网络设备补连用 |
| 设备 | Tailscale `100.100.10.x:5555` | Xiaomi 舰队,本机 `100.100.10.2` 在 tailnet 内 |
| uiautodev | 本机 `20242` | 元素抓取服务(web_server 自动拉起) |
### 2.1 adb key(关键)
- 本机 `~/.android/adbkey` 沿用历史 key(原取自 STF adb 容器,全部设备都信任)
- **不要随意更换 key**——设备会变 unauthorized 连不上
- 旧 key 备份在 `~/.android/adbkey.local.bak`
- 生产环境的容器也需要用这把 key(部署时处理)
### 2.2 设备连接方式
- 设备 serial 是 `IP:5555`(Tailscale 地址),**直连**优先(只 connect、绝不 disconnect)
- USB 设备(serial 无冒号):插本机走本地 adb;插 220 走远程 adb server(`USB_ADB_HOST:5037`)
- 本机已在 tailnet 内,直连可靠且快(<1s)
- 设备加入/退出平台:工具 → 设备池管理(SQLite 清单,自动连接 + 型号采集)
### 2.3 配置键速查(`config.py` / `.env`)
`.env` 加载方式:项目根目录逐行解析、`os.environ.setdefault`(环境变量已设则不覆盖)。以下默认值以 `config.py` 为准:
| 键 | 默认值 | 说明 |
|------|------|------|
| `WEB_HOST` / `WEB_PORT` | `0.0.0.0` / `18050` | Web 后台监听(18050 避开 Windows 动态端口范围) |
| `DATA_DIR` / `APK_DIR` | `data/` / `data/apks/` | 持久化数据目录 / APK 存储目录 |
| `BACKUP_DIR` / `RESTORE_STAGING_DIR` / `RESTORE_PENDING_DIR` | `data/backups` / `data/restore_staging` / `data/restore_pending` | 系统备份:导出 zip、导入暂存、待重启生效的恢复目录 |
| `ADB_PATH` | `bin/adb/adb`(Windows 为 `adb.exe`) | 按平台自动识别,代码只拼路径 |
| `USB_ADB_HOST` / `USB_ADB_PORT` | `100.100.10.1` / `5037` | 220 的 adb 容器(host 网络),驱动远程 USB 设备 |
| `DISCOVERY_PORT` / `DISCOVERY_SUBNETS` / `DISCOVERY_INTERVAL` | `5555` / 局域网+Tailscale 网段 / `60` | 设备自动发现;可在工具页设备池面板改,存 `app_meta` `discovery_*` 覆盖默认 |
| `TAILSCALE_API_KEY` / `TAILSCALE_TAILNET` | 空 / 空 | 工具页 Tailscale 管理(官方 API v2) |
| `WEB_SECRET_KEY` | 未配置则随机生成 | 会话密钥(web_server 读 `.env`;不配则重启登录态失效) |
| `STF_URL` / `STF_TOKEN` / `STF_SSH_*` | 废弃 | STF 摘除后仅历史保留,代码不再使用 |
---
## 3. 技术红线(开发限制)—— 违反会打断共享 adb transport,需人工恢复
这些是踩过坑后总结的,**任何修改都不能引入**。违反任何一条都会导致设备连接被全部重建(历史原因:STF provider 共享同一 adb transport,摘除 STF 后仍保留此约束):
1. **绝不 `adb kill-server`**
- 会断开所有设备的 adb transport,全部设备连接被重建,运行中任务中断
- 见 `core/adb_helper.py`
2. **绝不对 `IP:5555` 设备 `adb disconnect`**
- 该地址的 adb transport 是共享的(历史与 STF provider 共用),disconnect 会断掉全部相关连接
- 直连模式下 `release()` 不 disconnect
- 见 `core/device_worker.py` `STFDevice.release()`
3. **空闲设备扫描不主动 connect/disconnect**
- IP:5555 的 transport 由多方共享(历史与 STF provider 共用),外部 connect/disconnect 会扰动共享连接
- `_ForegroundScanner._scan_free` 对空闲设备直接返回"空闲",不碰 adb
- 见 `core/task_manager.py`
4. **adb key 保持历史 key 不变**(见 2.1,设备信任该 key)
5. **直连优先,不引入第三方桥接**(见 2.2)
### 3.1 其他开发限制
- **`web_server.py` 以 `debug=False` 运行,不热重载**:改 `core/`、`tasks/`、`templates/`、`static/` 后必须重启 web_server(前端 HTML 改完强刷浏览器)
- **任务参数放各自 `tasks/<app>/task.py` 顶部,不放 `config.py`**
- **生产环境(220)默认只读**:任何写操作(改文件/重启容器/部署)都必须先经负责人确认
- **数据库是 SQLite**(`data/users.db`,WAL 模式):运行时数据不提交 git
- **前端 JS 已拆分多文件**(均位于 `static/admin/`):`monitor.html` 按 `base.js → markdown.js → list.js → monitor.js → editor.js → tasks.js → tools.js → apps.js → admin.js → agent.js → system.js` 的顺序用 `<script src>` 加载(`markdown.js` 提供 `renderMarkdown()`,AI 控制台回答渲染用;依赖 `base.js` 的 `esc()`,故排在其后、agent.js 之前)
- **监控页/大列表已加分页**:100 台设备也只渲染 10 行/页,不要移除分页逻辑
- **任务批量触发已错峰**(`_START_STAGGER_SEC`):避免大量设备同时启动造成 adb 连接风暴,不要移除
---
## 4. 本地开发手册
### 4.1 首次安装
```bash
# 用 Python 3.12 建 venv(README 推荐版本)
/opt/homebrew/bin/python3.12 -m venv .venv
.venv/bin/pip install -r requirements.txt
# macOS:adb 用系统自带的(bin/adb/adb 是被 gitignore 的符号链接)
ln -sf /opt/homebrew/bin/adb bin/adb/adb
# 确认 adb key(必须是 STF 容器的 key,否则设备连不上)
ls -la ~/.android/adbkey
```
### 4.2 启动
```bash
.venv/bin/python web_server.py
# 访问 http://localhost:18050/ 账号 admin/admin123
```
启动日志看到以下即成功:
```
[INFO] [core.worker] 心跳看门狗已启动
[INFO] [web] 管理后台: http://localhost:18050/ (admin/admin123)
[INFO] [web] 启动服务: http://localhost:18050/
```
### 4.3 测试
- **后端逻辑**:直接 `.venv/bin/python -c "..."` 调用(如 `tasks/`、`core/` 的函数)
- **前端 UI**:Playwright(系统 `python3` 已装),脚本示例见下
- **浏览器冒烟**:切 6 个 tab、开任务编辑器,确认无 JS 错误
```python
# Playwright 冒烟示例(python3 运行)
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
b = p.chromium.launch(headless=True)
pg = b.new_page()
pg.goto("http://127.0.0.1:18050/login")
pg.fill("input[name=username]", "admin")
pg.fill("input[name=password]", "admin123")
pg.click("input[type=submit], button[type=submit]")
pg.wait_for_load_state("networkidle")
# ... 检查各 tab、编辑器
b.close()
```
### 4.4 常用调试
- **看日志**:`logs/` 下 `core.log` / `task.log` / `web.log` / `action.log`(10MB 滚动,保留 5 份)
- **看设备/任务状态**:浏览器监控页,或 `GET /api/status`、`GET /api/health`
- **停止设备/清异常**:监控页工具条用"停止全部 / 停止选中 / 清除全部异常"(`AUTO_RELEASE_STALE_OCCUPY` 等 STF occupy 残留清理配置代码已不再读取);崩溃残留的 worker 状态重启即清零
- **打包项目**:`python scripts/pack.py`
---
## 5. 常见开发任务
### 5.1 新增 App 任务类型
参照 `tasks/generic/` 结构,详见 **[doc/TASK_DEV.md](TASK_DEV.md)**(6 步模板)。
注意:当前平台**只保留 `generic_steps` 一种任务类型**;大多数 App 操作直接用步骤编辑器
编排即可,不必新增任务类型。
```
tasks/<app>/
__init__.py # from . import task
task.py # DEFAULT_PARAMS + Worker + @register_task
actions/ # 专属操作(可选)
```
### 5.2 新增专属操作
在 `tasks/<app>/actions/` 建 `.py`,继承 `BaseAction` + `@register_action(ACTIONS)`,在 `__init__.py` import。
### 5.3 修改前端
| 文件 | 内容 |
|------|------|
| `templates/admin/monitor.html` | HTML 结构 + CSS + `<script src>` 引用 |
| `static/admin/`(base/markdown/list/monitor/editor/tasks/tools/apps/admin/agent/system.js) | 前端 JS(按 monitor.html 中 `<script src>` 顺序拆分加载,功能归属见各文件;`markdown.js` = 轻量 Markdown 渲染器,AI 控制台回答用) |
改完**强刷浏览器**(Cmd/Ctrl+Shift+R),必要时重启 web_server。
### 5.4 新增 API
路由按功能域放在 `web/` 蓝图包(`web/__init__.py` 的 `register_blueprints(app)` 统一注册 10 个蓝图:auth / monitor / tasks / admin / tools / devices / apks / tailscale / agent / system)。
- 新增 API:在对应功能域的 `web/xxx_api.py` 里加 `@bp.route(...)` + 权限装饰器(如 `admin_required`)
- 新建蓝图:需在 `web/__init__.py` 里 import 并加进 `register_blueprints` 的注册元组
- `web_server.py` 只做 app 装配(初始化、`register_blueprints(app)`、常驻线程启动),一般不改
- 最后更新 **[doc/API.md](API.md)**
### 5.5 新增数据库字段/表
- 模型改 `core/models.py`,首次建表用 `create_all()`;SQLAlchemy 模型未写 `__tablename__` 时默认表名 = 小写类名
- **已有数据的老库**:在 `core/models.py` 的 `SCHEMA_MIGRATIONS` 里加迁移(版本号递增 + SQL)
- `app_meta`(KV 配置表)由 `core/models.py` 的 `_migrate_schema()` 建表并维护 `schema_version`
- `agent_conversation` / `agent_experience` / `experience_audit` / `agent_action`(动作经验库)由 `web/agent_api.py` 内的原生 `CREATE TABLE IF NOT EXISTS` 幂等创建(模块内首次用时执行),**不经 `SCHEMA_MIGRATIONS`,无版本管理**
### 5.6 改动必须同步文档
任何功能/配置/接口/页面改动,须与代码同一 commit 同步更新对应文档:
| 改动类型 | 对应文档 |
|------|------|
| HTTP 接口 | [API.md](API.md) |
| 数据表 / schema | [ARCHITECTURE.md](ARCHITECTURE.md) §3.6 |
| `config.py` / `.env` 键增删 | 本文档 §2 + [.env.example](../.env.example) |
| 页面 Tab / 子分栏 / 前端拆分 | [ARCHITECTURE.md](ARCHITECTURE.md) §5 |
| 任务 / 步骤 | [TASK_DEV.md](TASK_DEV.md) |
| 常驻线程 / 进程与装配 | [ARCHITECTURE.md](ARCHITECTURE.md) §7 |
| MCP 工具 | [MCP.md](MCP.md) 与 [MCP_DESIGN.md](MCP_DESIGN.md) |
| 对外接入 / 数字员工知识库 | [staffdeck/KNOWLEDGE_BASE.md](staffdeck/KNOWLEDGE_BASE.md) 与 [staffdeck/JOB_SPEC.md](staffdeck/JOB_SPEC.md) |
> **备份覆盖红线(2026-09-10 新增)**:**新增任何持久化表**(业务数据)时,必须同步把它登记进
> `core/system_backup.py` 的 `SUMMARY_TABLES`(并在 `TABLE_LABELS` 给中文名)+ 更新
> [DEPLOY.md](DEPLOY.md) §3.5 的覆盖清单。理由:清单漏登记 → 导出预览看不到该表 → 会被误判为
> "没有备份"(动作库 `agent_action` 就踩过)。导出侧有覆盖自检、导入侧有未登记表反向告警。
注:STF_REMOVAL.md 是历史迁移记录,不改写。
---
## 6. 发布流程(团队约定,2026-09-10 更新)
**每个改动都走分支,确认后再并 dev;dev 整体就绪后才并 main 上生产。**
```
① 本机新建分支 fix/xxx 或 feat/xxx(从 dev 切出)
② 分支上开发 + 自测 本机跑通(服务/接口/页面)
③ 交负责人确认 ★ 未经确认不合 dev
④ 合并到 dev 确认通过后(fast-forward 或 merge)
⑤ dev 整体就绪 dev 上功能齐全、验证完毕
⑥ 合并到 main ★ 负责人确认后
⑦ 生产 220 部署 git pull → 重启 python-app 容器 → 验证
```
**生产 220 部署(目录 `/mnt/data/openstf/auto_control`,容器 `python-app` 挂到 `/app`)**:
```bash
cd /mnt/data/openstf/auto_control
git fetch origin && git checkout main && git pull --ff-only origin main
docker restart python-app # 入口 scripts/start.sh:依赖守卫 → 拉起 MCP → exec web_server
# 验证:curl -s http://127.0.0.1:18050/api/health ; ss -ltnp | grep -E ':(18050|8033|20242)'
```
**发布前检查**:生产容器 adb key、依赖(新增依赖看 `requirements.txt`)、数据库迁移(`create_all` 自动补新表)、
以及"新增持久化表是否已登记进备份覆盖清单"(见 §5.6 红线)。
**数据迁移**:用平台自带「系统 → 数据备份导出/导入」;导入后需**重启容器**才生效(见 [DEPLOY.md](DEPLOY.md) §3.5)。
---
## 7. 文档索引
| 文档 | 内容 |
|------|------|
| [README](../README.md) | 项目总览、快速上手 |
| **[DEVELOPMENT.md](DEVELOPMENT.md)** | 本文档:流程/准则/限制/手册 |
| [TASK_DEV.md](TASK_DEV.md) | 任务开发指南(新增 App 任务模板) |
| [ARCHITECTURE.md](ARCHITECTURE.md) | 架构详解(分层、数据流、设计决策) |
| [API.md](API.md) | 全部 HTTP 接口说明 |
| [DEPLOY.md](DEPLOY.md) | 部署指南(环境、生产、故障排查) |
| [STF_REMOVAL.md](STF_REMOVAL.md) | 摘除 STF 的历史迁移记录(2026-08,不随现状改写) |
| [MCP.md](MCP.md) | MCP 手机控制使用手册(工具清单/用法) |
| [MCP_DESIGN.md](MCP_DESIGN.md) | MCP 架构与演进设计 |
| [AI_TASK_GEN.md](AI_TASK_GEN.md) | AI 建任务设计文档 |
| [staffdeck/KNOWLEDGE_BASE.md](staffdeck/KNOWLEDGE_BASE.md) | 给 StaffDeck 数字员工的知识库(MCP 接入/工具/约定/红线) |
| [staffdeck/JOB_SPEC.md](staffdeck/JOB_SPEC.md) | 数字员工岗位说明(岗位描述/看板摘要/执行约束) |
| [backlog/TODO.md](backlog/TODO.md) | 待完成项(已确认但暂缓的功能/优化,完成时移出并同步文档) |