feat(发布计划): 改按名字发(文件名 名字_日期_编号)+ 账号管理整页下线 + 自动切号一轮发完
发布计划不再绑设备、不再依赖账号台账:上传文件名 `名字_日期_编号`(标题行 `标题 || 正文_名字_日期_编号`),运行时读本机当前抖音昵称取计划;靠循环块 「一轮把本机待发的号跑完」自动切号,一直发到发完。 · 解析/配对换键:右锚定认名字(不许含下划线、不许纯数字 —— 不合法直接拒收并说清原因), 删掉三处"手机号必须命中台账"的校验,加索引 ix_video_plan_name_date(启动时补建) · 新步骤三个:读本机账号名字 / 切换账号(**按名字点控件**、切完回读校验、遇登录页停本设备)/ 循环本机待发的号(试过不回头、跑一轮没进展就换号、停止·超时·上限三个干净出口) · 发布骨架改 18 步:读名字 → 校验账号(cmp_source=account_target)→ 推送(**推送前重读昵称核对**) → 抖音流程 → 标记结果;push_release 的 account 默认 name · 账号管理整页下线:删 core/ledger.py(serial_of 迁到 core/device_pool.py)、6 个 /api/ledger*、 「账号」页与 ledger.js、设备池「账号」列、设备端账号块;发布计划升为一级页签; device_account 表与 42 行历史数据保留、仍在备份清单内(**停用不等于删数据**) · 人工入口「按名字找设备」:新增 core/account_probe.py(只读探测、跳过在跑任务的设备) + POST /api/video_plan/push_by_account,没设备登着的号逐条报出来; 推送时把"实际是谁发的"写进 posted_serial / posted_device_name · 通知:新增 task.account.switch_failed / task.account.need_login · schema v12(posted_*)/ v13(device.cur_account_name / cur_name_at) · 文档同步:TASK_DEV / API / DATA_MODEL / ARCHITECTURE / NOTIFY / DEVICE_AGENT / DEPLOY / DEVELOPMENT / README / .env.example(两个抖音控件选择器抓一次填 .env) · 新增 scripts/tests/:6 套用例 + run_all.py(临时库 + 假设备,不碰真实数据、不连真机)
This commit is contained in:
+34
-26
@@ -46,8 +46,8 @@
|
||||
| 13 | `device_install_log` | 设备端应用商店的下载/安装记录(设备上报,见 [DEVICE_AGENT.md](DEVICE_AGENT.md)) |
|
||||
| 14 | `task_step_log` | 任务步骤明细(每次步骤执行一条,见 §2.8;**唯一有无界增长风险的表**,靠保留期清理) |
|
||||
| 15 | `done_mark` | 去重账本:跨设备"已做过"标记(见 §2.9;本清单此前漏列,2026-09-24 补上) |
|
||||
| 16 | `device_account` | 账号台账:一台设备上登录着哪些账号(见 §2.10;任务「条件判断」的取号来源、设备端身份页显示用) |
|
||||
| 17 | `video_plan` | 视频发布计划:账号 × 发布日期 × 编号 → 素材 + 标题 + 发布状态 + 分享链接(见 §2.11) |
|
||||
| 16 | `device_account` | **已停用**:账号台账(见 §2.10;2026-10-12 起页面/接口/任务依赖全部下线,**表和 42 行历史数据保留、仍在备份内**,可回滚) |
|
||||
| 17 | `video_plan` | 视频发布计划:**名字** × 发布日期 × 编号 → 素材 + 标题/正文 + 发布状态 + 分享链接(见 §2.11) |
|
||||
|
||||
> 2026-09-13 之前,`app_meta` 与 4 张 `agent_*` 表是各模块里的裸 `CREATE TABLE`
|
||||
> (不进模型层)。迁 MySQL 时那批 SQL 的 `AUTOINCREMENT`/`TEXT DEFAULT ''`/`TEXT PRIMARY KEY`
|
||||
@@ -128,6 +128,7 @@
|
||||
| `note` | Text | `""` | |
|
||||
| `created_at` | String(20) | `""` | |
|
||||
| `fingerprint` | String(120) | `""` | **设备指纹**(`ro.serialno`,迁移 v5 追加):同一台物理设备换 IP 后据此认领回原记录 |
|
||||
| `cur_account_name` / `cur_name_at` | String(80)/String(20) | `""` | **上次探测到的抖音当前账号名字**(迁移 v13 追加):只有「按名字找设备」(计划页的推送)会写它。**纯缓存** —— 自动任务永远现场读屏幕,缓存旧了也不影响发布的正确性;界面上带时间戳显示 |
|
||||
|
||||
### 2.7 `pending_device` — 待确认的发现设备
|
||||
|
||||
@@ -200,9 +201,20 @@
|
||||
|
||||
⚠️ 这张表**自动进整库备份**(§6 派生规则);接任务步骤见 [TASK_DEV.md](TASK_DEV.md) §4.6。
|
||||
|
||||
### 2.10 `device_account` — 账号台账(一台设备上登录着哪些账号)
|
||||
### 2.10 `device_account` — 账号台账(**已停用,表保留**)
|
||||
|
||||
「账号」页维护;服务层 `core/ledger.py`,接口 `/api/ledger*`(见 [API.md](API.md) §2.15)。
|
||||
> **2026-10-12:账号管理整块下线。** 页面(「账号」页)、接口(`/api/ledger*`)、
|
||||
> 服务层(`core/ledger.py` 已删)、任务取号(`cmp_source=device/all/group`)、
|
||||
> 设备端身份页的账号块(`accounts_b64`)、设备池的「账号 N」列**全部移除**。
|
||||
>
|
||||
> 为什么留着表和这 42 行数据:**删数据不可逆**,而这张表在备份清单里(红线:功能涉及的数据
|
||||
> 必须纳入备份)—— 先停用、确认稳定后再单开一次清理动作。
|
||||
> 发布计划改走**名字**(见 §2.11),不再需要台账。
|
||||
>
|
||||
> 唯一从 `core/ledger.py` 迁走的是 `serial_of(设备名) → 当前地址`(与账号无关),
|
||||
> 现在在 `core/device_pool.py`。
|
||||
|
||||
历史结构(停用前):一台设备上登录着哪些账号。「账号」页维护。
|
||||
|
||||
| 列 | 类型 | 说明 |
|
||||
|----|------|------|
|
||||
@@ -218,44 +230,38 @@
|
||||
| `bio` / `note` | Text | 简介 / 备注 |
|
||||
| `created_at` / `updated_at` | String(20) | 字符串时间(仓库惯例) |
|
||||
|
||||
**三个使用方**:
|
||||
**(停用前的)三个使用方**:① web「账号」页(列表/增删改/从 Excel 粘贴导入);
|
||||
② 任务「条件判断」取号(`cmp_source=device/all/group`);③ 手机端 Agent 的身份大字页(`accounts_b64`)。
|
||||
**三个都已移除**;发布计划现在按**名字**走(§2.11)。
|
||||
|
||||
1. **web「账号」页** —— 列表 / 增删改 / 从 Excel 粘贴导入(解析规则见 `core/ledger.parse_paste`)
|
||||
2. **任务的「条件判断」取号** —— `if_el` 的 `cmp_source`(`device` 本机 / `all` 全部 / `group` 按设备分组),
|
||||
见 [TASK_DEV.md](TASK_DEV.md) §4.2
|
||||
3. **手机端 Agent 的身份大字页** —— 平台推 `accounts_b64`(见 [DEVICE_AGENT.md](DEVICE_AGENT.md) §5.2)
|
||||
⚠️ 历史提醒:`douyin_id` 是纯号,**绝不能当去重身份**(`done_mark.identity` 存的是元素原文、
|
||||
逐字算 key,格式不一致会让去重静默失效)—— 这是当年 `cmp_source` 取号的边界,
|
||||
现在候选值来自"本次要发的名字",同理不得拿去填 `ident_value`。
|
||||
|
||||
⚠️ **`douyin_id` 是纯号,绝不能当去重身份**:`done_mark.identity` 存的是**元素原文**
|
||||
(`抖音号:35377983067`)、逐字算 key,格式不一致会让去重**静默失效**。
|
||||
它只做"比对用的候选值"(运算符用「包含」时纯号是子串)。
|
||||
这张表**仍在整库备份内**(§6 派生规则)—— 停用不等于删数据。
|
||||
|
||||
**唯一性在应用层**(`core/ledger`):同一抖音号不允许两条。没做成 DB 唯一索引的理由:
|
||||
抖音号可能为空,DB 级要写"部分唯一索引"(§4.2 那套 SQLite `WHERE` + MySQL 虚拟生成列),
|
||||
而台账是人工维护的几十条 —— 三处方言适配不划算。
|
||||
### 2.11 `video_plan` — 视频发布计划(**一个名字**在某天要发的一个视频)
|
||||
|
||||
这张表**自动进整库备份**(§6 派生规则)。
|
||||
|
||||
### 2.11 `video_plan` — 视频发布计划(一个账号在某天要发的一个视频)
|
||||
|
||||
「账号 → 发布计划」页维护;服务层 `core/video_plan.py`,接口 `/api/video_plan/*`(见 [API.md](API.md) §2.14),
|
||||
「发布计划」页维护;服务层 `core/video_plan.py`,接口 `/api/video_plan/*`(见 [API.md](API.md) §2.14),
|
||||
任务步骤「发布视频」按它自动发布(见 [TASK_DEV.md](TASK_DEV.md) §4.7)。
|
||||
**素材文件**落在 `data/videos/YYYY-MM/`(文件名 `{sha1[:12]}_{安全原名}`,内容寻址),**不进整库备份**(§6)。
|
||||
|
||||
| 列 | 类型 | 说明 |
|
||||
|----|------|------|
|
||||
| `id` | String(32) PK | uuid 前 8 位 |
|
||||
| `account_id` / `phone` | String(32) | 台账行 id(**不做外键**)/ 配对键(冗余存:账号删了也留痕) |
|
||||
| `device_name` / `nickname` / `douyin_id` / `serial` | String | 账号与设备快照(时间线卡片直接显示,免 join) |
|
||||
| `nickname` | String(80) | **识别键**:上传文件名里的**名字**(= 抖音昵称)。运行时就按它取计划、找设备/账号 |
|
||||
| `account_id` / `phone` / `douyin_id` / `device_name` / `serial` | String | **已停用**(老台账快照,保留列不再写入):账号 id / 手机号 / 抖音号 / 设备号 / 地址 |
|
||||
| `release_date` | String(10) index | **`2026-09-12`** 纯日期(等值比较走索引) |
|
||||
| `seq` / `seq_auto` | Integer / Boolean | 编号从 **1** 起(不用 0 表示"无");`seq_auto`=号是自动分配的 |
|
||||
| `title` | Text | **标题**(发布页的「标题」框;标题 txt 配对写入) |
|
||||
| `body` | Text | **正文/文案**(发布页的文案框,可带 `#话题`)—— 标题 txt 一行里用 `||` 分隔:`标题 || 正文 _手机号_日期_编号`;**标题与正文都为空的行不会被发布** |
|
||||
| `body` | Text | **正文/文案**(发布页的文案框,可带 `#话题`)—— 标题 txt 一行里用 `||` 分隔:`标题 || 正文_名字_日期_编号`;**标题与正文都为空的行不会被发布** |
|
||||
| `video_file` / `video_name` / `video_size` / `video_sha1` | 各自 | 落盘相对路径 / 原始名 / 大小 / 内容指纹(重复上传判据) |
|
||||
| `status` | String(16) index | 见下方状态机 |
|
||||
| `stage` | String(16) | 失败发生在哪一步:`push`/`scan`/`post`/`verify` |
|
||||
| `attempts` | Integer | 尝试次数(上限 3,超了不再自动取) |
|
||||
| `published_at` / `share_url` / `link_at` / `video_deleted_at` | String | 发布时刻 / **作品分享链接** / 抓到链接的时刻 / 素材何时清理 |
|
||||
| `push_verify` / `push_remote` | String(16)/String(200) | **推送后的相册校验**:`ok`=已进相册索引 / `no_index`=文件在但没进索引(相册里可能看不到)/ `nofile`=文件不在;`push_remote` = 推到手机上的绝对路径(删它、排查用)。**文件推上去了 ≠ 相册里点得到它**,所以单独存一列而不是混进 `last_error` |
|
||||
| `posted_serial` / `posted_device_name` | String(120)/String(80) | **实际是哪台设备发的**(推送时现场写)。计划不再绑设备,所以"这条最后是谁发的"只能这么记 —— 排查"怎么发到那台机器上去了"全靠它 |
|
||||
| `last_error` / `note` / `created_at` / `updated_at` | | 失败原因 / 人工备注 / 时间 |
|
||||
|
||||
**状态机**(本表的灵魂,别简化):
|
||||
@@ -276,12 +282,14 @@ unknown 推送之后出的岔子 —— **可能已经发出去了,绝不
|
||||
> 就是重复发布的来源。`stage` 是两者互相转换的唯一依据(`core/video_plan.STAGE_STATUS`)。
|
||||
> 界面上的 `unknown` 卡片标橙 + 硬提示,人工到抖音确认后点「已发出 / 未发出」裁决。
|
||||
|
||||
**唯一性**:`(phone, release_date, seq)` 由**服务层**保证,**不加 DB 唯一索引** ——
|
||||
**唯一性**:`(nickname, release_date, seq)` 由**服务层**保证,**不加 DB 唯一索引** ——
|
||||
`seq` 从 1 起、没有"空值"可言,做部分唯一索引要写三处方言适配 + MySQL 生成列(§4.2),
|
||||
收益不匹配;违反的代价只是低频人工上传产生的重复行(可见、可删)。
|
||||
|
||||
**索引**:`(release_date,status)` 时间线主查询 · `(account_id,release_date)` 账号视角 ·
|
||||
`(phone,release_date,seq)` 配对/幂等 · `(video_file)` 清理时反查引用。
|
||||
**索引**:`(release_date,status)` 时间线主查询 · `(nickname,release_date)` **按名字取计划/查重**(主路径)·
|
||||
`(account_id,release_date)` · `(phone,release_date,seq)` 老台账路径(已停用,保留)· `(video_file)` 清理时反查引用。
|
||||
后三个里带 `account_id`/`phone` 的两个是历史遗留(账号台账停用后不再走),等确认稳定后一并清理。
|
||||
`ix_video_plan_name_date` 由 `_ensure_plain_indexes()` 在启动时补建(`create_all` 不管已有表的新索引)。
|
||||
|
||||
这张表**自动进整库备份**(§6 派生规则);**素材文件不进备份包**,见 §6 与 §7。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user