一、平台侧(账号 → 发布计划页) - 新表 video_plan(schema v9→v10):账号×发布日期×编号 → 素材 + 标题 + 发布状态 + 分享链接; 状态机 pending/ready/pushing/publishing/done/failed/unknown/skipped(**failed 与 unknown 必须分开**: 推送阶段的失败可安全重试;碰过抖音之后的岔子只能算"结果未知",绝不自动重发) - 素材上传:文件名 `手机号_日期_编号`(编号可省)解析配对;标题 txt `标题内容_手机号_日期_编号`; 内容寻址落盘 data/videos/YYYY-MM/(sha1 分块算,同名不存两份),**不进整库备份**但进 manifest 反查 - 新蓝图 web/video_plan_api.py:上传/时间线/统计/单条增删改/推送到手机/标记结果/裁决/链接导出 CSV/ 任务列表与一键新建、**就地编辑**(GET/PUT /tasks/<id>)、**一键推送**(POST /push_all,按设备分组、设备内串行) - 账号页拆子分栏(台账 / 发布计划)+ static/admin/release.js;清理 job(04:41 僵尸回收+过期行、04:47 素材文件) - 上传体积:MAX_CONTENT_LENGTH(默认 2GiB)+ 413 JSON + nginx client_max_body_size(修现有 APK 上传隐患) 二、任务侧(平台推素材,抖音流程你自己写) - 新步骤 push_release「推送发布视频」:原子占位 → adb push → **touch 改成"现在"** → 清旧目录同名副本 → 触发扫描并**按路径**校验相册索引 → 标题写进剪贴板;默认目录 /sdcard/DCIM/Camera - 新步骤 mark_release「标记发布结果」:回写 done/failed/unknown,成功时抓作品分享链接、删手机素材 - input_text 支持 text_source=release_title(自动取计划标题 + 回读校验); if_el 的候选值来源新增 release(**本机当前发布计划**的抖音号/昵称,发布前校验"登的是不是要发的号") - build_release_steps 骨架 15 步:⓪ 亮屏 → ① 打开抖音(等首页) → ② 点「我」→ ③ 等抖音号出现 → ④ 条件判断(账号) → then ⑤ 推送 ⑥⑦⑧⑨⑩⑪⑫ 抖音点击/填标题 → ⑬ 标记 / else 发通知跳过 三、修(推送这一路的检测机制) - **uiautomator2 3.x 的 d.shell() 返回 ShellResponse(tuple 子类)不是 str**:`'x' in resp` 恒 False、 `.strip()` 不存在 → "推上去的文件大小不对"每次都判失败(文件其实推上去了)、相册校验永远报没进、 删除确认永远判没删掉。新增 publish_flow._sh() 统一取 .output;大小改成解析 ls -l 的大小列 - **adb push 保留本地 mtime** → 推 3 天前上传的素材在按时间排序的相册里排不到最前, "点第一个 = 刚推的那个"不成立 → 推完 touch - 相册校验**按路径**比(MediaStore 的 _data 会把目录小写、/storage/emulated/0 ≡ /sdcard), 只比文件名会被老目录的同名残留骗过去 - 屏幕没亮就启动抖音会永远停在启动页(UI 树为空)→ 后面"点我/等抖音号"必然 miss, 最后报成误导人的"账号不符" → 骨架第一步固定加「亮屏」,open_app 等「首页」出现 四、其它 - core/ledger.serial_of():设备名 → 当前地址(设备换 IP 后快照是错的) - 通知事件 task.video.published / task.video.failed;备份清单加 video_plan 与素材统计 - 文档同步:DATA_MODEL §2.11 + schema v10、API(新接口与语义)、TASK_DEV §4.7 专章、 ARCHITECTURE(账号页子分栏/release.js/两个 job)、DEPLOY(表数/nginx)、NOTIFY、DEVELOPMENT、README
258 lines
16 KiB
Markdown
258 lines
16 KiB
Markdown
# 开发手册(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"。
|
||
|
||
**修复也走这条流程,包括线上紧急修复**(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](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 可用) |
|
||
| 停止设备/清异常 | 监控页「停止全部 / 停止选中 / 清除全部异常」 |
|
||
| AI 建任务(designer)验证 | AI 控制台 →「AI 建任务」选**空闲**设备跑一轮;脚本方式见 [AI_TASK_GEN.md](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](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](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](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)
|
||
- **新增表**:`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,dedup,ledger,release}.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。
|