feat(发布计划): 视频发布计划(批量上传配对 → 时间线 → 推送到手机 → 发布任务 → 分享链接)
一、平台侧(账号 → 发布计划页) - 新表 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
This commit is contained in:
+51
-1
@@ -62,7 +62,9 @@
|
||||
|
||||
## 2. 接口总索引
|
||||
|
||||
> 共 **107 条路由**。`鉴权` 列:`—` 无、`L` 登录、`T/D/A/G` = tasks/devices/apks/logs 权限位、`Admin` 仅管理员。
|
||||
> 本表列出全部业务路由(页面路由除外;同一路径的不同方法合并成一行)。
|
||||
> `鉴权` 列:`—` 无、`L` 登录、`T/D/A/G` = tasks/devices/apks/logs 权限位、`Admin` 仅管理员。
|
||||
> 想看实际注册了多少条:`python -c "from web_server import app; print(len([r for r in app.url_map.iter_rules() if r.rule.startswith('/api')]))"`。
|
||||
|
||||
### 2.1 auth(`web/auth.py`)
|
||||
|
||||
@@ -143,6 +145,21 @@
|
||||
| PUT | `/api/ledger/<acc_id>` | D | 改一条账号 |
|
||||
| DELETE | `/api/ledger/<acc_id>` | D | 删一条账号 |
|
||||
| POST | `/api/ledger/import` | D | 从表格粘贴文本批量导入(`dry_run=true` 只预览不写库) |
|
||||
| POST | `/api/video_plan/upload_video` | D | 上传单个视频素材(文件名 `手机号_日期_编号`)→ 解析 + 配对 + 入计划 |
|
||||
| POST | `/api/video_plan/upload_titles` | D | 上传标题(txt 文本或文件)→ 配到已有计划行 |
|
||||
| GET | `/api/video_plan/timeline` | D | 发布计划时间线(按日期分组的卡片 + 统计 + 磁盘) |
|
||||
| GET | `/api/video_plan/stats` | D | 今天要发/已发/待标题/过期未发 + 素材占用与磁盘余量 |
|
||||
| GET/PUT/DELETE | `/api/video_plan/<id>` | D | 单条计划:查看 / 改标题 / 删除(连素材文件) |
|
||||
| GET | `/api/video_plan/<id>/video` | D | 预览平台上的素材(文件清理后 → 410) |
|
||||
| POST | `/api/video_plan/<id>/push` | D | **把素材推到手机**(只推送、不发布;后台线程) |
|
||||
| POST | `/api/video_plan/push_all` | D | **一键推送**:某天(默认今天)所有待发布/失败的计划 → 各自手机(每台设备一个线程、设备内串行) |
|
||||
| POST | `/api/video_plan/<id>/mark` | D | 人工标记结果(`done`/`failed`/`unknown`,推完之后用) |
|
||||
| POST | `/api/video_plan/<id>/skip\|retry\|resolve` | D | 跳过 / 重试 / 裁决「结果未知」 |
|
||||
| GET | `/api/video_plan/links` | D | 已发布作品的分享链接(`format=csv` 导出,给铺评论用) |
|
||||
| GET | `/api/video_plan/tasks` | T | 可编辑的任务列表(全部通用步骤任务 + `is_release` 标记:状态/调度/下次运行 + 完整步骤) |
|
||||
| POST | `/api/video_plan/tasks` | T | 一键新建标准发布任务(骨架 15 步:亮屏 → 打开抖音 → 校验账号 → 推送 → 抖音点击 → 填标题 → 标记) |
|
||||
| GET/PUT | `/api/video_plan/tasks/<job_id>` | T | **发布任务的就地编辑**(就在「发布计划」页改名字/目标/时间/启停/步骤) |
|
||||
| POST | `/api/video_plan/tasks/<job_id>/adopt` | T | 给已有任务插上平台两步(推送放最前 / 标记放最后,自己的步骤不动) |
|
||||
|
||||
### 2.4 admin(`web/admin_api.py`)
|
||||
|
||||
@@ -518,6 +535,39 @@
|
||||
> ⚠ 台账里的抖音号是**纯号**,只当"比对用的候选值";**不要**拿它填「去重」的身份元素
|
||||
> (身份是元素原文逐字算 key,格式不同会让去重**静默失效**)。
|
||||
|
||||
### 视频发布计划(「账号 → 发布计划」页)
|
||||
|
||||
表与状态机见 [DATA_MODEL.md](DATA_MODEL.md) §2.11,任务侧怎么自动发布见
|
||||
[TASK_DEV.md](TASK_DEV.md) §4.7;权限 `devices`。
|
||||
|
||||
| 接口 | 请求 | 响应要点 |
|
||||
|------|------|---------|
|
||||
| `POST /api/video_plan/upload_video` | multipart `file`(单文件)+ `replace` | `{ok,name,action:add\|skip\|replace,plan,msg}`;解析/配对不通过 → **400 + `error`(人话原因)**。前端**逐文件串行**发(才能给每个文件一条进度与结果) |
|
||||
| `POST /api/video_plan/upload_titles` | `{text, dry_run}` 或 multipart `file`(txt) | `{ok, counts:{total,attached,skipped,rejected}, rows:[{line,phone,date,seq,title,action,error}], msg}` |
|
||||
| `GET /api/video_plan/timeline` | `?range=today\|week\|all&from=&to=&status=&account_id=&q=&limit=` | `{ok, days:[{date,weekday,total,done,plans:[…]}], stats, disk, today}` |
|
||||
| `GET /api/video_plan/stats` | — | `{ok, stats, disk, today}`;`stats` 里**三个口径都给**:`today_ready`(今天要发几个号,按发布日期)/ `today_done`(今天完成)/ `published_today`(今天实际发的,按 `published_at`,补发也算今天) |
|
||||
| `GET /api/video_plan/<id>` · `PUT` · `DELETE` | `PUT {"title"}` | `PUT` 只改标题(改完 `pending→ready`);`DELETE` **先删行再删素材文件** |
|
||||
| `GET /api/video_plan/<id>/video` | — | 素材预览(`send_file`,支持 Range);文件已被清理 → **410** |
|
||||
| `POST /api/video_plan/<id>/push` | — | **把素材推到手机**(只推送、不发布):推文件 → 触发相册刷新 → 标题写进剪贴板;已推送/结果未知 → 409。发布动作由你写在任务里(见 [TASK_DEV.md](TASK_DEV.md) §4.7) |
|
||||
| `POST /api/video_plan/push_all` | `{date?, retry_failed?}` | **一键推送**:某天(默认今天)`ready`/`failed` 且未超尝试上限的计划,**按设备分组**推(每台设备一个线程、设备内串行;设备间并行)。返回 `{ok,count,devices,no_device,msg}`;没绑上设备地址的条数单独在 `no_device` 里报出来。全部设备都解析不到 → 400 |
|
||||
| `POST /api/video_plan/<id>/mark` | `{status: done\|failed\|unknown, why}` | 人工标记结果(推完之后用):`done` 补 `published_at`;`failed`/`unknown` 把 `why` 记进 `last_error` |
|
||||
| `POST /api/video_plan/<id>/skip` | `{reason}` | 人工跳过 |
|
||||
| `POST /api/video_plan/<id>/retry` | — | `failed`→`ready`,`attempts` 清零 |
|
||||
| `POST /api/video_plan/<id>/resolve` | `{published, note}` | **`unknown` 的人工裁决**:确认真发出去了 → `done`;确认没发 → `failed`(可重试) |
|
||||
| `GET /api/video_plan/links` | `?from=&to=&device=&phone=&format=csv` | 已发布作品的分享链接;CSV 带 BOM(Excel 打开中文不乱码) |
|
||||
| `GET /api/video_plan/tasks` | — | `{ok, tasks:[{id,name,enabled,mode,cron,next_run,target,steps,step_count,schedule,retry,is_release,has_push}], release_count, other_count}`:**全部通用步骤任务**(不止发布任务 —— 只列发布任务的话,手写的抖音发布流程会一条都不显示,人会以为"这页没有能改的地方"),界面按 `is_release`(有 `push_release` **或** `mark_release`,含嵌套)分两组。带完整 `steps` 是为**就地编辑**(抓选择器);`step_count` 数的是**含嵌套**的总步数。权限 `T` |
|
||||
| `POST /api/video_plan/tasks` | `{"name","target":{"mode":"all\|group\|serial","group_name","serial"},"time":"10:00","enabled"}` | 一键新建:骨架 **15 步**(亮屏 → 打开抖音(等首页) → 点「我」→ `if_el(cmp_source=release)` 校验账号 → then: `push_release` → 「+」(预填 `descriptionContains=拍摄`) → 相册/第一个视频/下一步/输入框/发布(**空选择器占位**)→ `input_text(text_source=release_title)` → `mark_release`;else: `notify` 跳过);**走 TaskManager 建**(内存+库一起更新,直接写库调度器不认)。⚠ `target` 分组键名是 **`group_name`**(写成 `group` 会静默解析出 0 台设备);分组/单设备没给具体目标 → 400。权限 `T` |
|
||||
| `GET/PUT /api/video_plan/tasks/<job_id>` | `PUT {name?, time?, target?, steps?, enabled?}` | **发布任务的就地编辑**(「账号 → 发布计划」页里改,不用跳任务页)。只放开这几个字段,**没传的字段原样保留**;`steps` 整块替换但 `params` 里的通用参数(去重有效期、抢占…)**深合并保留**。校验:空名字/坏时间/`steps` 不是数组/什么都不传 → 400,未知任务 → 404。权限 `T`。底层仍是 `TaskManager.update_job()`(内存+库一起改) |
|
||||
| `POST /api/video_plan/tasks/<job_id>/adopt` | — | 给**已有任务**插上平台那两步:`push_release` 放最前、`mark_release` 放最后(**中间你自己的步骤一步不动**;位置理由见 `core.video_plan.insert_release_steps`)。只对「通用步骤」任务;没有步骤 → 400,已经有这两步 → 409,未知任务 → 404。权限 `T` |
|
||||
|
||||
**逐文件上传的幂等**:同一 `(手机号,日期,编号)` 已有行时 —— 文件内容相同 → `action=skip`(不重复落盘);
|
||||
内容不同 → **拒收**,前端给「覆盖」按钮(二次确认后带 `replace=1` 重传)。
|
||||
标题同理:同槽位已有别的标题 → 拒收(不静默覆盖,文案是发出去就改不了的东西)。
|
||||
|
||||
> ⚠ 上传体积:`MAX_CONTENT_LENGTH`(默认 2GiB,`.env` 的 `MAX_UPLOAD_MB` 可调)**必须大于
|
||||
> 「应用管理」最大的 APK**(实测 336MB),否则 APK 上传会被一起卡死;超限返回 **JSON 413**。
|
||||
> 上反代时 nginx 那侧还要 `client_max_body_size`(见 [DEPLOY.md](DEPLOY.md) §6)。
|
||||
|
||||
---
|
||||
|
||||
## 6. 设备池与自动发现
|
||||
|
||||
Reference in New Issue
Block a user