- doc/staffdeck/KNOWLEDGE_BASE.md:接入方式(MCP http://<host>:8033/mcp 首选/REST 备选)、服务端配置项、19 个 de_* 工具清单(读写分类)、通用约定(serial/坐标空间/busy 占用锁/错误码)、推荐操作模式与常见配方、红线、当前边界 - doc/staffdeck/JOB_SPEC.md:岗位描述、看板摘要(指标口径+文本/JSON 汇报模板)、岗位执行约束(L0-L3 授权分级/硬红线/操作规范/失败重试/审计)、SOP 工作流、应拒绝与转人工清单 - doc/DEVELOPMENT.md:§7 文档索引与 §5.6 同步映射登记这两份
267 lines
12 KiB
Markdown
267 lines
12 KiB
Markdown
# 开发手册(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 → list.js → monitor.js → editor.js → tasks.js → tools.js → apps.js → admin.js → agent.js → system.js` 的顺序用 `<script src>` 加载
|
||
- **监控页/大列表已加分页**: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/douyin/` 结构,详见 **[doc/TASK_DEV.md](TASK_DEV.md)**(6 步模板)。
|
||
|
||
```
|
||
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/list/monitor/editor/tasks/tools/apps/admin/agent/system.js) | 前端 JS(按 monitor.html 中 `<script src>` 顺序拆分加载,功能归属见各文件) |
|
||
|
||
改完**强刷浏览器**(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` 由 `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) |
|
||
|
||
注:STF_REMOVAL.md 是历史迁移记录,不改写。
|
||
|
||
---
|
||
|
||
## 6. 发布流程
|
||
|
||
详见 **[doc/DEPLOY.md](DEPLOY.md)**。摘要:
|
||
|
||
1. dev 开发测试完成
|
||
2. 负责人确认 → 合并到 main
|
||
3. 负责人确认 → 生产 220 `git pull`
|
||
4. 重启 python-app 容器生效
|
||
|
||
**发布前检查**:生产容器 adb key、依赖、数据库迁移。
|
||
|
||
---
|
||
|
||
## 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) | 数字员工岗位说明(岗位描述/看板摘要/执行约束) |
|