# DESIGN v2.0 补充 — 页面体系 / 数据模型 / 逻辑层 / 技术架构

> 定位：**DESIGN.md v1.0 的补充，不替代**。v1 锁定选型与 6 表骨架；本文件补三块 v1 没覆盖的：①对客端 + 管理后台完整页面体系 ②数据表补到 12 张（含逻辑层必备的预约/通知表）③逻辑层状态机与全链路时序整合。
> 状态：v2.0 · 2026-09-19 · 待隽侬过审
> 关联：`DESIGN.md`（v1 选型/架构/6表）｜`逻辑看板.html`（PRD 流程图）｜`02_PRD.md`｜`04_RAG对查报告.md`

---

## 0. 为什么补这三块

v1 的 DESIGN.md 把架构和选型锁死了，但有三处明显不够：

| 缺口 | v1 现状 | 后果 |
|---|---|---|
| 对客端页面 | 只有 `/play/{channel_id}` 一页 | PRD 要求的节目单/直播大厅/回放中心/预告详情全没有，客户进来无落脚点 |
| 数据表 | 6 表，砍了 Team/Researcher/预约/通知 | 逻辑看板的"命中→通知预约用户"无从落地（没预约表就没通知对象） |
| 逻辑层 | 状态机散在 §4/§8，无整合时序 | 注入/命中/恢复/回放四个定时任务谁先谁后、字段怎么流转，看不清 |

本文件逐一补齐。

---

## 1. 页面体系总览

**域名规划**（遵循 netf.top 三站架构，记忆已定）：
- `live.netf.top` = 直播控台工程承载站（本工程）。根路径给对客端 H5，`/console/*` 给管理后台，`/api/*` 接口，`/webhook/*` PolyV 回调。
- `prd.netf.top` = 文档站（不动）。

### 1.1 对客端 H5（观众侧，8 页）

观众路径：**大厅/节目单 → 预告详情(预约) → 命中通知 → 进入直播观看 / 回放观看**。

| # | 页面 | 路由 | 数据来源 | 说明 |
|---|---|---|---|---|
| V1 | H5 直播大厅（首页） | `/` | preview(live/pending) + playback(精选) | 一屏聚合：直播中横幅 + 即将开播 + 精选回放，30s 自动刷新 |
| V2 | 节目单 | `/schedule` | preview(pending/live) 按日期分组 | 多预告卡片，按 planned_at 日期分组，60s 刷新，状态徽章 |
| V3 | 直播大厅 | `/live` | preview(status=live) | 直播中卡片网格 + 在线人数，30s 刷新 |
| V4 | 回放中心 | `/playback` | playback | 搜索 + 板块/品种筛选 + 卡片网格，点击进 V7 |
| V5 | 预告详情 | `/preview/{id}` | preview + researcher + subscription | 预告信息 + 主播 + 板块/品种/适当性门槛 + 倒计时 + **预约按钮**（写 subscription 表） |
| V6 | 直播观看页 | `/play/{channel_id}?gid={guest_id}` | live_room + audience | 嵌 PolyV 直播播放器 + 触发 custom auth 回调 + 房间卡 + 闸门拒绝态 |
| V7 | 回放观看页 | `/vod/{vid}?gid={guest_id}` | playback + audience | 嵌 PolyV 点播播放器 + PlaySafe 授权（同源鉴权） |
| V8 | 直播转跳枢纽 | `/go/{preview_id}?gid={guest_id}` | preview → live_room | 预告命中/直播中时，从预告页"进入直播"经此跳到 V6，带 gid；未开播则提示倒计时 |

**关键交互**：
- 身份（游客模式，阶段1 定）：前端首次进入生成 `guest_id`（uuid v4），存 localStorage。所有页面/预约/观看/WebSocket 均带 gid。阶段3 SSO 后回填 user_code，gid 保留做追溯。
- 预约（V5 → subscription）：观众点"预约"，后端按 gid upsert user（无则建游客记录）→ 写 subscription(preview_id, user_id)。命中后经 MQ 通知。
- 转跳（V8）：预告页"进入直播"按钮 → `/go/{preview_id}?gid=` → 后端查 preview.live_room_id → 302 到 `/play/{channel_id}?gid=`。未到注入窗口提示"未开播"。
- 鉴权（V6/V7）：URL 带 gid，PolyV 播放器加载时回调 `/webhook/polyv/auth`，服务端按 gid 查 user 表适当性/VIP 判定。阶段1 游客 mock 放行（C4/VIP1），阶段3 接真实数据源。

### 1.2 管理后台（运营/管理员侧，8 页，`/console/*`）

| # | 页面 | 路由 | 说明 |
|---|---|---|---|
| A1 | 控制台首页 | `/console` | 看板：今日预告数 / 直播中数 / 待注入数 / 最近回放 / 待办 |
| A2 | 预告管理 | `/console/previews` | 列表 + 状态/时间筛选 + 新建/编辑/取消/复制链接 + 分页10 |
| A3 | 直播间管理 | `/console/rooms` | 列表 + 批量启用/禁用/删除 + 新建(建 PolyV 频道)/编辑/开关 |
| A4 | 回放管理 | `/console/playbacks` | 列表 + 板块/品种筛选 + 编辑分类/封面 |
| A5 | 团队管理 | `/console/teams` | 团队 CRUD（研究员分组） |
| A6 | 研究员管理 | `/console/researchers` | 研究员 CRUD（选团队），作为主播被关联 |
| A7 | 观众/鉴权记录 | `/console/audiences` | 观众访问记录 + 适当性/VIP + 放行/拒绝原因 |
| A8 | 审计日志 | `/console/audit` | 操作留痕（actor/action/target），金融场景必备 |

**角色权限矩阵**（沿用 PRD §3）：

| 角色 | 预告 | 直播间 | 团队/研究员 | 观众记录 | 审计 | 观看 |
|---|---|---|---|---|---|---|
| 运营 | 增删改查 | 增删改查/批量/开关 | 查看 | 查看 | — | — |
| 管理员 | 全部 | 启用/禁用/WEB开关 | 增删改查 | 查看 | 查看 | — |
| 研究员 | — | 被关联为主播 | — | — | — | 出现卡片 |
| 观众 | — | — | — | — | — | 过闸门看 |

---

## 2. 数据模型补全（6 → 12 表）

### 2.1 已有 6 表（DESIGN v1，保留）

`preview` / `live_room` / `playback` / `polyv_channel` / `audience` / `audit_log`

**v1 → v2 字段微调**（预告表补 3 字段，承接逻辑层）：

```sql
-- preview 表追加字段（在 v1 基础上）
ALTER TABLE preview ADD COLUMN min_suit_level TEXT;   -- 最低适当性 C1-C5（预告级门槛）
ALTER TABLE preview ADD COLUMN min_vip_tier   INTEGER DEFAULT 0;  -- 最低 VIP 0/1/2/3
ALTER TABLE preview ADD COLUMN team_id        INTEGER;  -- 主播团队（冗余，便于大厅展示）
```

### 2.2 新增 6 表 DDL

```sql
-- 7. 团队表（研究员分组）
CREATE TABLE team (
    id          INTEGER PRIMARY KEY AUTOINCREMENT,
    name        TEXT NOT NULL UNIQUE,
    intro       TEXT,
    created_at  DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at  DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);

-- 8. 研究员表（主播）
CREATE TABLE researcher (
    id          INTEGER PRIMARY KEY AUTOINCREMENT,
    name        TEXT NOT NULL,
    team_id     INTEGER,
    title       TEXT,                        -- 职称（如：首席分析师）
    avatar_url  TEXT,
    intro       TEXT,
    is_active   BOOLEAN NOT NULL DEFAULT 1,
    created_at  DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at  DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY(team_id) REFERENCES team(id)
);
CREATE INDEX idx_researcher_team ON researcher(team_id);

-- 9. 直播间-研究员 多对多（一场直播可多主播）
CREATE TABLE room_researcher (
    room_id        INTEGER NOT NULL,
    researcher_id  INTEGER NOT NULL,
    PRIMARY KEY(room_id, researcher_id),
    FOREIGN KEY(room_id) REFERENCES live_room(id),
    FOREIGN KEY(researcher_id) REFERENCES researcher(id)
);

-- 10. 业务用户表（适当性/VIP 来源，mock 阶段种子数据）
CREATE TABLE user (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    user_code       TEXT UNIQUE NOT NULL,    -- 业务用户号（观众唯一标识）
    name            TEXT,
    suit_level      TEXT NOT NULL DEFAULT 'C1'   -- 适当性 C1-C5
                    CHECK(suit_level IN ('C1','C2','C3','C4','C5')),
    vip_tier        INTEGER NOT NULL DEFAULT 0,  -- 0普通/1君研/2君行/3君尊享
    status          TEXT NOT NULL DEFAULT 'active'
                    CHECK(status IN ('active','frozen','closed')),
    last_seen       DATETIME,
    created_at      DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at      DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_user_code ON user(user_code);

-- 11. 预约表（命中→通知的前提，逻辑层核心）
CREATE TABLE subscription (
    id           INTEGER PRIMARY KEY AUTOINCREMENT,
    preview_id   INTEGER NOT NULL,
    user_id      INTEGER NOT NULL,
    status       TEXT NOT NULL DEFAULT 'active'   -- active/notified/cancelled
                 CHECK(status IN ('active','notified','cancelled')),
    notified_at  DATETIME,                        -- 命中通知发送时间
    created_at   DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    UNIQUE(preview_id, user_id),
    FOREIGN KEY(preview_id) REFERENCES preview(id),
    FOREIGN KEY(user_id) REFERENCES user(id)
);
CREATE INDEX idx_sub_preview ON subscription(preview_id, status);
CREATE INDEX idx_sub_user ON subscription(user_id);

-- 12. 通知记录表（命中/开播提醒发送留痕）
CREATE TABLE notification (
    id            INTEGER PRIMARY KEY AUTOINCREMENT,
    subscription_id INTEGER,
    user_id       INTEGER NOT NULL,
    preview_id    INTEGER,
    channel       TEXT NOT NULL DEFAULT 'inapp'   -- inapp/sms/wechat
                  CHECK(channel IN ('inapp','sms','wechat')),
    status        TEXT NOT NULL DEFAULT 'pending' -- pending/sent/failed
                  CHECK(status IN ('pending','sent','failed')),
    content       TEXT,
    sent_at       DATETIME,
    created_at    DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY(subscription_id) REFERENCES subscription(id),
    FOREIGN KEY(user_id) REFERENCES user(id),
    FOREIGN KEY(preview_id) REFERENCES preview(id)
);
CREATE INDEX idx_notif_user ON notification(user_id, status);
```

### 2.3 为什么这 6 张不能少

| 表 | 不可少的理由 |
|---|---|
| team / researcher / room_researcher | 逻辑看板数据模型本就有，PRD §4.3 明确"团队/研究员 CRUD + 选团队自动全选"。v1 砍了导致主播无处落 |
| user | 鉴权要按"真实观众身份"查适当性/VIP（RAG P0-①），得有用户主数据。audience 表只记访问流水，user 才是身份源 |
| **subscription** | **"命中后通知预约用户"的前提**。没预约表 = 不知道通知谁。这是 v1 最大的逻辑层漏洞 |
| notification | 通知发送留痕，金融场景审计需要。命中通知发没发、发哪了、成功没，必须可追溯 |

### 2.4 ER 关系（核心链路）

```
team 1──N researcher N──N live_room (room_researcher)
                                    │
preview N──1 live_room              │
   │  (live_room_id)                │
   │                                │
   N──N user (subscription) ──1──N notification
                                    │
preview 1──N playback               │
audience (观众访问流水, channel_id+viewer_id)
audit_log (操作留痕)
polyv_channel (频道缓存)
```

**属性流转主线**（PRD §6 不可丢）：
`preview(板块/品种/适当性/VIP) → 注入 → live_room → 继承 → playback`

---

## 3. 逻辑层状态机（结合 PRD 流程图）

这是 v1 最散的部分，整合成一张时序 + 四个状态机。

### 3.1 预告生命周期状态机（status）

```
        新建
         │
         ▼
      pending ──────(手动取消)────────► cancelled
         │
    T-30min 注入
         │
         ▼
       live ──────(T+30min 恢复)──────► ended
                                         │
                              (回放生成) │
                                         ▼
                                    playback 可检索
```

**状态判定规则**（定时任务 + 事件驱动）：
- `pending`：planned_at 在未来，未进注入窗口
- `live`：进入注入窗口（T-30 ~ T+30）后置为 live
- `ended`：T+30 恢复完成
- `cancelled`：运营手动取消（仅 pending 态可取消）

### 3.2 命中状态机（hit_status）

```
      pending
         │
   命中检测(每1min, T±20min窗口内实际开播?)
    ┌────┴────┐
    ▼         ▼
   hit      missed
    │
 通知预约用户
 (subscription.active → notified + 写 notification)
```

- `hit`：PolyV 频道实际开播时间落在 planned_at ±20min 内
- `missed`：超窗未开播
- 命中后立即触发 notification_service 通知所有 active 预约用户

### 3.3 注入/恢复（injection_service，定时驱动）

| 时刻 | 动作 | 字段变化 |
|---|---|---|
| T-30min | ① 备份 live_room 原信息（name→name_backup）<br>② 调 PolyV `/channel/update` 注入 preview 的标题/封面/简介<br>③ preview.status: pending→live, injected_at=now | live_room.name_backup 填充<br>preview.injected_at |
| T+30min | ① 用 name_backup 恢复 PolyV 频道<br>② preview.status: live→ended, restored_at=now<br>③ 触发回放生成 | preview.restored_at |

### 3.4 回放生成（playback_service，事件驱动）

PolyV 直播结束回调（`/webhook/polyv/status` 收到 ended）→
1. 调 PolyV VOD 列表接口拉回放 vid
2. 生成 playback 记录，**继承** preview 的 sector/variety/cover_url
3. 回放可检索（回放中心 V4）

### 3.5 全链路时序（整合，对应逻辑看板 S0）

```
运营建预告(绑直播间,配板块/品种/适当性/VIP)
    │
    │  [定时任务 T-30min]
    ▼
备份直播间 → 注入预告信息 → status=live
    │
    │  [命中检测 每1min, T±20min窗口]
    ├── hit  → 通知预约用户(subscription→notified + notification)
    └── missed → 预告失效,不通知
    │
    │  [定时任务 T+30min]
    ▼
恢复直播间原信息 → status=ended
    │
    │  [PolyV ended回调]
    ▼
拉回放vid → 生成playback(继承板块/品种) → 回放中心可检索
```

---

## 4. 技术架构

### 4.1 后端模块清单（v1 目录 + 新增 service）

| 层 | 模块 | 职责 | v1/v2 |
|---|---|---|---|
| routes | console.py | 管理后台 8 页 HTML + 页面内 API | v2 扩 |
| routes | viewer.py | 对客端 8 页 HTML | v2 新 |
| routes | preview.py / live.py / playback.py | 预告/直播间/回放 CRUD API | v1 |
| routes | webhook.py | PolyV 回调（auth/status） | v1 |
| services | preview_service | 预告创建 + 状态机推进 | v1 |
| services | injection_service | T-30 注入 + T+30 恢复 | v1 |
| services | hit_service | 命中检测 T±20min | v1 |
| services | playback_service | 回放生成 + 字段继承 | v1 |
| services | live_service | 直播间 + 频道创建编排 | v1 |
| services | **subscription_service** | 预约/取消/查预约用户 | **v2 新** |
| services | **notification_service** | 命中通知发送（inapp/sms/wechat） | **v2 新** |
| services | **researcher_service** | 团队/研究员 CRUD | **v2 新** |
| polyv | sign/client/channel/auth/vod | 签名/频道/授权/VOD | v1 |
| auth | base/mock/real | 鉴权策略 | v1 |
| tasks | scheduler | APScheduler 注册 | v1 扩 |
| models | 12 表 | — | v2 扩 |

### 4.2 PolyV 能力清单（对齐 RAG）

| 能力 | PolyV 接口 | 用在哪 | P0修复 |
|---|---|---|---|
| 创建频道 | `POST /live/v4/channel/create` | 直播间新建 | 请求体加 `masterAuthSetting.authType=custom` |
| 拉地址 | `GET /live/v4/channel/basic/get` | 创建后回填 pushUrl/watchUrl | **v1 P0-1 补漏** |
| 更新频道 | `POST /live/v4/channel/update` | 注入预告信息/恢复 | — |
| 直播授权回调 | 自定义 `customUri` | 观众观看闸门 | **v1 P0-2/3 频道设custom + 识别真实观众** |
| VOD 列表 | `GET /vod/...` | 回放拉取 | — |
| VOD PlaySafe | PlaySafe token | 回放观看授权 | **v1 P0-4 补全** |
| 直播播放器 | `liveplayer.min.js` + `polyvLivePlayer({uid,vid})` | V6 直播观看页 | — |
| 点播播放器 | `vid` + PlaySafe | V7 回放观看页 | — |

### 4.3 定时任务（APScheduler）

| 任务 | 频率 | 动作 | 调 service |
|---|---|---|---|
| 注入扫描 | 每 5min | 扫 planned_at 在未来 30min 内的 pending 预告 → 注入 | injection_service.inject |
| 命中检测 | 每 1min | 对 live 态预告查 PolyV 实际开播 → hit/missed | hit_service.check |
| 通知派发 | 每 1min | 扫 hit 且未通知的预告 → 通知预约用户 | notification_service.dispatch |
| 恢复扫描 | 每 5min | 扫 planned_at 过 30min 的 live 预告 → 恢复 | injection_service.restore |
| 回放拉取 | 事件触发 | PolyV ended 回调 → 拉回放 | playback_service.generate |

### 4.4 鉴权链路（mock → 真实）

**接口**（对 services 透明）：
```python
class AuthProvider(Protocol):
    def check(self, user_code: str, channel_id: str, vid: str | None) -> AuthResult:
        """返回 allow/deny + suit_level + vip_tier + reason"""
```

**阶段1 mock**：`MockAuthProvider` 固定返回 allow + C4 + VIP1，联调全链路。
**阶段2 真实**：`RealAuthProvider` 调公司内部系统查 user 表适当性/VIP。
**判定规则**（合规口径不可改）：
- 适当性 C1<C2<C3<C4<C5，观众 suit_level ≥ 预告 min_suit_level
- VIP 0<1<2<3，观众 vip_tier ≥ 预告 min_vip_tier
- 优先级：适当性 > VIP（适当性不足直接拒，不看 VIP）
- 直播(V6)和回放(V7)同源鉴权

### 4.5 通知能力（候选渠道，待定）

命中后通知预约用户，渠道未定。候选：

| 渠道 | 实现 | 成本 | 优先级 |
|---|---|---|---|
| 站内消息 | H5 大厅红点 + 预告详情提示 | 零成本，最轻 | 默认先做 |
| 短信 | 接短信网关 | 按条计费 | 待定 |
| 微信 | 公众号/小程序模板消息 | 需公众号资质 | 待定 |

**默认方案**：先做站内消息（notification 表 channel=inapp），sms/wechat 预留接口位。**待隽侬确认渠道后填实现。**

### 4.6 回放能力

- 回放生成：PolyV ended 回调 → publish ended 事件 → 拉 VOD vid → playback 表（继承 sector/variety）
- 回放观看(V7)：`/vod/{vid}?gid=` → 后端下发 PlaySafe token → 播放器加载播放
- 回放鉴权：与直播同源（适当性/VIP），走 `/webhook/polyv/auth` 识别 vid

---

## 5. 路由总表（v1 + v2 整合）

### 5.1 对客端 HTML（8 页）
| 方法 | 路径 | 页面 |
|---|---|---|
| GET | `/` | V1 H5 直播大厅 |
| GET | `/schedule` | V2 节目单 |
| GET | `/live` | V3 直播大厅 |
| GET | `/playback` | V4 回放中心 |
| GET | `/preview/{id}` | V5 预告详情 |
| GET | `/play/{channel_id}` | V6 直播观看 |
| GET | `/vod/{vid}` | V7 回放观看 |
| GET | `/go/{preview_id}` | V8 直播转跳枢纽 |

### 5.2 管理后台 HTML（8 页）
| 方法 | 路径 | 页面 |
|---|---|---|
| GET | `/console` | A1 控制台首页 |
| GET | `/console/previews` | A2 预告管理 |
| GET | `/console/rooms` | A3 直播间管理 |
| GET | `/console/playbacks` | A4 回放管理 |
| GET | `/console/teams` | A5 团队管理 |
| GET | `/console/researchers` | A6 研究员管理 |
| GET | `/console/audiences` | A7 观众记录 |
| GET | `/console/audit` | A8 审计日志 |

### 5.3 API（写操作记 audit_log）
| 方法 | 路径 | 说明 |
|---|---|---|
| POST/PUT/DELETE | `/api/previews...` | 预告 CRUD（v1） |
| POST | `/api/previews/{id}/subscribe` | **预约**（v2 新） |
| POST | `/api/previews/{id}/cancel` | 取消预告 |
| GET/POST/PUT | `/api/rooms...` | 直播间 + 频道创建（v1） |
| POST | `/api/rooms/{id}/sync-channel` | 重拉 basic/get |
| GET/POST/PUT/DELETE | `/api/teams` `/api/researchers` | **团队/研究员 CRUD**（v2 新） |
| GET | `/api/playbacks` | 回放列表 |
| GET | `/api/playbacks/{id}/playsafe` | **回放 PlaySafe token**（v2 新） |
| GET | `/api/audiences` | 观众记录 |

### 5.4 PolyV 回调
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | `/webhook/polyv/auth` | 自定义授权（直播+回放同源） |
| POST | `/webhook/polyv/status` | 直播状态变更（开播/结束） |

### 5.5 WebSocket 事件（flask-socketio）
| 事件 | 方向 | 触发 | payload |
|---|---|---|---|
| `connect` | C→S | 前端连 `socket.io?gid={guest_id}` | join 房间 `gid:{guest_id}` |
| `hit` | S→C | 命中检测确认 → publish hit 事件 | `{preview_id, title, channel_id}` |
| `inject` | S→C | T-30 注入完成 | `{preview_id, planned_at}` |
| `started` | S→C | PolyV 回调开播 | `{preview_id, channel_id}` |
| `ended` | S→C | PolyV 回调结束 | `{preview_id}` |
| `recover` | S→C | T+30 恢复完成 | `{preview_id}` |

> 路由：ws_gateway 订阅 Redis Stream `polyv:events:*`，按事件 payload 里的 target_gid 路由到 `gid:{guest_id}` 房间 emit。预约用户 hit 事件按 subscription.user_id→user.guest_id 解析 target。离线用户事件留 Stream，重连后由消费者组 last_id 回溯。

---

## 6. 服务支持（部署形态，沿用 v1 + v2 MQ/WS 扩展）

- WSGI：gunicorn + eventlet worker（flask-socketio 要求），监听 127.0.0.1:8100
- WebSocket：flask-socketio，message_queue 指向 Redis（多 worker 下跨进程广播）
- 服务：`~/.config/systemd/user/web-live-console.service`，Restart=always
- 反代：cloudflared ingress `live.netf.top` → :8100（替换现有 :8095 占位；WebSocket 经 cloudflared 透传）
- DB：SQLite WAL，`/mnt/data/live-console/live_console.db`，单文件备份
- **MQ：独立 Redis 容器 `live-mq`**（redis:7-alpine，`docker run -d --name live-mq --restart=always -p 127.0.0.1:6379:6379 redis:7-alpine`），本机 only，不借用 Dify 的 Redis。Stream key 前缀 `polyv:events:`
- 定时：APScheduler 进程内（与 Flask 同进程）
- 静态：Flask 自管 `/static/`，封面/GIF 上传落 `/mnt/data/live-console/uploads/`
- 环境：`.env`（600），POLYV 三元组 + AUTH_PROVIDER + REDIS_URL=redis://127.0.0.1:6379/0

---

## 7. 待解项与分叉点

| # | 待解项 | 阻断阶段 | 状态/默认方案 |
|---|---|---|---|
| 1 | ~~PolyV 真实凭证~~ | ~~阶段1~~ | **✅ 已到位+已验证（2026-09-19）**：userId/appId/appSecret 落 `.env`（600）。调 `/live/v3/user/category/list` 返回 200 + 3 分类。账号=国泰君安期货。另有"平台公钥"（130hex，04开头 ECC 未压缩公钥）用途待查（RAG+官方文档均未覆盖，疑为回调验签/PlaySafe，不阻塞 API 调用）。详见 §9 |
| 2 | 适当性+VIP 数据源 | 阶段3 | user 表 mock 种子，真实数据源待定 |
| 3 | ~~通知渠道~~ | ~~阶段2~~ | **✅ 已定（2026-09-19）**：Redis Streams MQ + WebSocket 实时推 + 下游消费组（sms/wechat 适配器阶段2）。详见 4.5 |
| 4 | 回放封面来源 | 阶段2 | 继承 preview.cover_url，无则 PolyV 默认 |
| 5 | ~~观众身份传递方式~~ | ~~阶段1~~ | **✅ 已定（2026-09-19）**：阶段1 游客模式（前端生成 guest_id 存 localStorage，免鉴权）；阶段3 接 SSO 回填 user_code，gid 保留追溯。详见 1.1/4.4 |
| 6 | **频道配额**（新发现） | 阶段1 | 账号下已有 **100 个频道（达上限）**，大量测试频道。阶段1 联调可复用现有 unStart 频道（如 3062764/3062770 等），不必新建。正式上线前需清理测试频道腾配额，或确认账号是否支持扩容>100。详见 §9 |

---

## 8. 实施阶段（v1 基础上对齐 v2）

1. **阶段1（骨架走通 + 游客 + MQ 底座）**：12 表建库 + PolyV 集成（签名/创建/basic/get/custom回调/PlaySafe）+ **游客身份（gid 前端生成 + user 表 upsert）** + mock 鉴权（gid 放行）+ **live-mq Redis 容器 + event_bus + ws_gateway + hit/inject/started 事件实时推** + 对客端 V6/V7 观看页 + 管理后台 A1-A3。目标：mock 下跑完 创建频道→游客观看→授权→播放→事件实时推前端。
2. **阶段2（业务完整 + 下游消费）**：预告状态机 + 注入/恢复 + 命中检测 + **预约(subscription)+notification 审计** + 回放 + 对客端 V1-V5/V8 + 管理后台 A4-A8 + **sms/wechat 下游消费适配器**。
3. **阶段3（真实鉴权）**：接适当性/VIP 数据源替换 mock + SSO（gid→user_code 绑定）+ user 表补 phone/openid 字段。

---

## 9. 凭证与频道资源（2026-09-19 到位+验证）

### 9.1 凭证
- **来源**：隽侬从保利威控制台 `console.polyv.net → 开发设置` 提供。
- **存放**：`/mnt/data/live-console/.env`（chmod 600，**严禁进交付物/git/对外文件**）。
- **三元组**：`POLYV_USER_ID` / `POLYV_APP_ID` / `POLYV_APP_SECRET` —— 用于 API 调用签名（MD5：`secret + key升序拼 + secret` → 32位大写）。
- **平台公钥**（`POLYV_PLATFORM_PUBLIC_KEY`）：130 位 hex，`04` 开头 = 未压缩椭圆曲线公钥（secp256k1/r1）。**RAG 库 + 官方 buildSign/getSecretKey 文档均未覆盖**，疑为 PolyV 新版回调验签或 PlaySafe 非对称签名。**不阻塞 API 调用**（API 调用只用 appSecret+MD5）。待首次遇到回调验签/PlaySafe 场景时确认用途。
- **验证**：`POST /live/v3/user/category/list` → HTTP 200，返回 3 分类（默认分类 / 国泰君安期货直播 / R2）。账号归属 = 国泰君安期货。
- **签名实现**：见 `polyv_sign_rule.md`（RAG）+ `/mnt/data/live-console/verify_credential.py`（验证脚本，可复跑）。

### 9.2 频道资源盘点
- **总量**：账号下 **100 个频道（已达配额上限）**。
- **状态分布**：playback(回放) 多数 / end(结束) 次之 / unStart(未开始) 若干 / waiting(等待) 极少。
- **分类**：387883 默认分类 / 399401 国泰君安期货直播（主业务）/ 399402 R2。
- **真实业务频道样本**：盘前Plus+、【斯海淘金】、【能说化道】、国泰君安期货首席研究员直播、直播预告（宏观预期反复…）。
- **测试频道**：大量（新建直播2-17、凑数直播1-4、0509/510-x、418/419/420/421/422 系列等），命名混乱。

### 9.3 对开发的影响
| 影响点 | 处理 |
|---|---|
| 阶段1 联调 | **复用现有 unStart 频道**（如 3062764/3062770/3065878 等），不必新建，省配额 |
| 正式上线前 | 清理测试频道腾配额，或向保利威确认账号是否支持扩容>100 |
| 历史工程旧凭证 | `trae_polyv_export/live-console-backend/app.py` 里硬编码的 appId=`hlq0cj8b30`/userId=`b5c4d3d197` 是**别的账号，作废**，重写工程一律用 `.env` 新凭证 |
| API base | `https://api.polyv.net`（v3/v4 混用，按接口文档选） |

---

_本文件由拾光基于 DESIGN v1 + 逻辑看板 + PRD + RAG 对查起草。与 v1 配套使用，改本文件需同步改代码。_
