docs(plan): 设备&会话管理 实现计划 P1-P6(md执行源 + html阅读版 + 登记 + 链接)
新增 docs/superpowers/plans/2026-06-29-device-session-management.md(执行真相, checkbox) + docs/device-session-management-plan.html(阅读版);登记进 index.html 「实现计划」;总方案顶部加「→ 实现计划」链接。P1 设备注册打通→P2 sessions表+ 在线→P3 强制退出/清除→P4 每设备流量→P5 2FA(future)→P6 UI重做。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -90,7 +90,7 @@
|
||||
<a class="back" href="index.html">← 文档索引</a>
|
||||
|
||||
<h1>设备 & 会话管理 + 每设备流量归因<span class="small">(设计方案)</span></h1>
|
||||
<p class="sub">Pangolin · 「我的设备」管理 + 控制面会话 + 数据面按设备记账 · 2026-06-28</p>
|
||||
<p class="sub">Pangolin · 「我的设备」管理 + 控制面会话 + 数据面按设备记账 · 2026-06-28 · <a href="device-session-management-plan.html"><b>→ 实现计划 P1–P6</b></a></p>
|
||||
|
||||
<div class="lead">
|
||||
一句话:<b>后端绝大部分已经建好,只是「没接线」</b>。<code>devices</code> 表、每设备数据面凭证 <code>dp_uuid</code>、
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>设备 & 会话管理 + 每设备流量归因(实现计划 P1–P6)</title>
|
||||
<style>
|
||||
:root{
|
||||
--bg:#0f1117; --panel:#171a22; --panel2:#1d2129; --fg:#e6e8ee; --fg2:#a8afbd;
|
||||
--accent:#e0884f; --accent2:#5fb0c9; --ok:#5ec27a; --bad:#e06a6a; --warn:#e0b84f;
|
||||
--border:#272c36; --mono:"SF Mono",ui-monospace,Menlo,Consolas,monospace;
|
||||
--sans:-apple-system,"PingFang SC","Helvetica Neue",Arial,sans-serif;
|
||||
}
|
||||
*{box-sizing:border-box}
|
||||
body{margin:0;background:var(--bg);color:var(--fg);font-family:var(--sans);line-height:1.7;font-size:15px}
|
||||
.wrap{max-width:920px;margin:0 auto;padding:48px 24px 96px}
|
||||
h1{font-size:29px;line-height:1.3;margin:0 0 8px;letter-spacing:-.01em}
|
||||
.sub{color:var(--fg2);font-size:15px;margin:0 0 28px}
|
||||
h2{font-size:20px;margin:42px 0 12px;padding-bottom:8px;border-bottom:1px solid var(--border)}
|
||||
p{margin:10px 0}
|
||||
code{font-family:var(--mono);font-size:.86em;background:var(--panel2);padding:1px 6px;border-radius:5px;color:#f0d9c4}
|
||||
.tag{display:inline-block;font-size:12px;font-weight:600;padding:2px 9px;border-radius:999px;vertical-align:middle}
|
||||
.tag.info{background:rgba(95,176,201,.16);color:var(--accent2)}
|
||||
.tag.warn{background:rgba(224,184,79,.16);color:var(--warn)}
|
||||
.tag.future{background:rgba(168,175,189,.16);color:var(--fg2)}
|
||||
.card{background:var(--panel);border:1px solid var(--border);border-radius:12px;padding:16px 20px;margin:14px 0}
|
||||
.card.p{border-left:3px solid var(--accent)}
|
||||
.card h3{margin:0 0 4px;font-size:17px;color:#fff}
|
||||
.card .goal{color:var(--accent2);font-size:13.5px;margin:0 0 8px}
|
||||
ul{padding-left:20px;margin:8px 0}
|
||||
li{margin:4px 0}
|
||||
li.acc{list-style:none;margin-left:-20px;margin-top:8px;color:var(--ok);font-weight:600;font-size:13.5px}
|
||||
.lead{background:linear-gradient(180deg,rgba(224,136,79,.10),transparent);border:1px solid var(--border);border-radius:12px;padding:18px 20px;margin:0 0 8px}
|
||||
.small{color:var(--fg2);font-size:13px}
|
||||
a{color:var(--accent2)}
|
||||
.back{display:inline-block;margin-bottom:24px;font-size:13px}
|
||||
hr{border:none;border-top:1px solid var(--border);margin:36px 0}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
|
||||
<a class="back" href="index.html">← 文档索引</a>
|
||||
|
||||
<h1>设备 & 会话管理 + 每设备流量归因 · 实现计划<span class="small"> P1–P6</span></h1>
|
||||
<p class="sub">阅读版 · 执行真相源(含 checkbox)为 <code>docs/superpowers/plans/2026-06-29-device-session-management.md</code> · 2026-06-29</p>
|
||||
|
||||
<div class="lead">
|
||||
设计见 <a href="device-session-management-design.html">设备 & 会话管理 总方案</a>。核心仍是「<b>打通已建未接线的链路 + 新增 sessions 表 + UI 重做</b>」,拆成 6 期、每期一刀可独立验收。
|
||||
约束:SQL 中性方言(<code>dialect.Upsert</code>、无 <code>NOW()</code>);UI 走 token 单源;不碰 CI-gated golden 的 Linux 基线;每期 build+test 过即 commit。
|
||||
</div>
|
||||
|
||||
<h2>已核的代码事实(计划依据)</h2>
|
||||
<ul>
|
||||
<li><code>devices.Service.RegisterIfAbsent</code> 已完整实现但<b>无人调用</b>;<code>normalizePlatform</code> 拒 linux;<code>DeleteDevice</code> 已调 <code>CredentialRevoker</code>(当前 NoopRevoker,<b>按 user 不按 device</b>)。</li>
|
||||
<li><code>TokenManager.Issue/Refresh</code> 内部生成 refresh JTI、<b>不外露</b> → 绑 session 须 surface JTI。</li>
|
||||
<li><code>auth.Handler</code> 的 login/register 请求体需加 <code>device</code> 字段;<code>clientIP(r)</code> 已有(供 session IP)。</li>
|
||||
<li><code>auth.NewService(...)</code> 是注入点;<code>nodeSvc.Hub()</code> 将充当 per-device 凭证吊销器。</li>
|
||||
<li>客户端依赖:<code>flutter_secure_storage</code>✓ <code>package_info_plus</code>✓,<b>缺 <code>uuid</code> 与 <code>device_info_plus</code></b>;device_id 现硬编码 <code>'mac-001'</code>。</li>
|
||||
</ul>
|
||||
|
||||
<h2>分期</h2>
|
||||
|
||||
<div class="card p">
|
||||
<h3>P1 · 设备注册打通(登录即注册设备)</h3>
|
||||
<p class="goal">目标:登录/注册成功即写 devices 表 → 「我的设备」显示真实设备。</p>
|
||||
<ul>
|
||||
<li>客户端加依赖 <code>uuid</code> + <code>device_info_plus</code>;新 <code>device_identity.dart</code>(生成+持久 device_id、设备名/平台、客户端版本);弃用硬编码 <code>'mac-001'</code>。</li>
|
||||
<li>login/register 请求体加 <code>device:{id,name,platform,client_version}</code>。</li>
|
||||
<li>后端 <code>auth/handler.go</code> 加 device 字段;<code>auth/service.go</code> 成功签发后调 <code>RegisterIfAbsent</code>(注入 <code>DeviceRegistrar</code> 接口,返回 deviceID 供 P2);<code>normalizePlatform</code> 加 linux;<code>main.go</code> 注入。</li>
|
||||
<li class="acc">✓ 验收:登录后 <code>GET /v1/me/devices</code> 非空、含真实设备名/平台</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card p">
|
||||
<h3>P2 · sessions 表 + 在线/最后登录</h3>
|
||||
<p class="goal">目标:会话落库绑设备;列表显示在线/客户端版本/最后登录。</p>
|
||||
<ul>
|
||||
<li>migration <code>000016</code>:建 sessions 表;devices 加 <code>client_version</code>+<code>totp_trusted_until</code>;唯一键 <code>UNIQUE(uuid)</code>→<code>UNIQUE(user_id,uuid)</code>;platform CHECK 加 linux。</li>
|
||||
<li>新 <code>server/internal/sessions/</code> Store(Create/RotateJTI/RevokeByJTI/RevokeByDevice/…)。</li>
|
||||
<li><code>TokenManager</code> 外露 JTI;<code>auth.Service</code> 注入 sessions:登录 Create、刷新 RotateJTI+last_active、登出 RevokeByJTI。</li>
|
||||
<li><code>ReportUsage</code> touch <code>devices.last_seen</code>(在线判定);<code>ListDevices</code> 返回 online/last_login/client_version;客户端 Device model 加字段。</li>
|
||||
<li class="acc">✓ 验收:列表显示在线/版本/最后登录;停 agent ~3min 转离线</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card p">
|
||||
<h3>P3 · 两个操作(强制退出 + 清除增强)</h3>
|
||||
<p class="goal">目标:强制退出(吊销会话留设备)/ 清除(移除+吊销会话+吊销 dp_uuid)。</p>
|
||||
<ul>
|
||||
<li>新端点 <code>POST /v1/me/devices/{uuid}/logout</code>(ForceLogout:RevokeByDevice + 逐 jti Revoke)。</li>
|
||||
<li><code>DeleteDevice</code> 增强:吊销 session;<code>CredentialRevoker</code> 改 <b>per-device</b>(<code>RevokeDevice(dpUUID)</code>,<code>nodes.Hub</code> 实现,<code>main.go</code> 替 NoopRevoker)。</li>
|
||||
<li>客户端 <code>account_api.forceLogout</code> + provider。</li>
|
||||
<li class="acc">✓ 验收:强制退出→refresh 失败需重登;清除→设备消失 + 数据面凭证作废</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card p">
|
||||
<h3>P4 · 每设备流量验证(打通 usage_device_daily)</h3>
|
||||
<p class="goal">目标:每设备流量真正写库 + 统计页设备下拉真数据。</p>
|
||||
<ul>
|
||||
<li><code>ConnectNode</code> 兜底 <code>RegisterIfAbsent</code>;端到端确认 <code>EnsureDeviceDpUUID</code>→per-device dp_uuid→<code>ReportUsage</code>→<code>AccumulateDeviceUsage</code> 填表。</li>
|
||||
<li>统计页设备下拉接真实设备 + <code>deviceUsageProvider</code>(呼应统计整改 #10)。</li>
|
||||
<li class="acc">✓ 验收:统计页按设备能看到分流量</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>P5 · 2FA 信任设备 <span class="tag future">FUTURE · 本轮不编码</span></h3>
|
||||
<p class="small" style="margin:4px 0 0">占位:清除登录信息联动 <code>totp_trusted_until</code>(列已在 P2 预留),重登需重做 2FA。待 2FA 主流程就绪后单独排。</p>
|
||||
</div>
|
||||
|
||||
<div class="card p">
|
||||
<h3>P6 · UI 重做(DevicesScreen,五端)</h3>
|
||||
<p class="goal">目标:按总方案 §7 视觉稿落地「我的设备」交互。</p>
|
||||
<ul>
|
||||
<li><code>DevicesScreen</code> 重做:名称/平台·版本/在线绿点/最后登录 + 「本机」标 + 行尾 ⋯ 菜单(强制退出/清除·危险红)+ 危险二次确认弹窗;颜色/字号走 token。</li>
|
||||
<li>宽/窄屏适配;空/加载态;l10n(zh+en)新增文案;DevicesScreen 新 golden(Mac 生成,非 CI-gated)。</li>
|
||||
<li class="acc">✓ 验收:截图与 §7 视觉稿一致,五端生效(Flutter 单码)</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<h2>验收(整体)</h2>
|
||||
<ul>
|
||||
<li>后端 <code>go build ./... && go test ./...</code>(含 sqlite 实库)+ <code>./server/run_sqlite_test.sh</code>;客户端 <code>flutter analyze</code> + <code>flutter test</code>。</li>
|
||||
<li>端到端(本机 + pangolin1):登录→设备列表;停 agent→离线;强制退出→refresh 失败;清除→消失+凭证作废;走流量→<code>usage_device_daily</code> 有行。</li>
|
||||
<li><span class="tag warn">生产</span> 线上 <code>000016</code> 迁移在 pangolin1 跑 <code>pangolin-migrate up</code> —— 生产写操作,执行前单独向用户确认。</li>
|
||||
</ul>
|
||||
<p class="small">关联:<a href="device-session-management-design.html">设备 & 会话管理 总方案</a> · <a href="stats-overhaul-plan.html">统计体系整改(#5)</a>(P4 设备下拉)。</p>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -66,6 +66,11 @@
|
||||
</a>
|
||||
|
||||
<h2>实现计划 / Plans</h2>
|
||||
<a class="doc" href="device-session-management-plan.html">
|
||||
<div class="t">设备 & 会话管理 + 每设备流量归因 实现计划(P1–P6)<span class="tag html">HTML</span></div>
|
||||
<div class="d">阅读版;执行真相源为 <code>docs/superpowers/plans/2026-06-29-device-session-management.md</code>(含 checkbox)。P1 设备注册打通 → P2 sessions表+在线/最后登录 → P3 强制退出/清除 → P4 每设备流量 → P5 2FA信任(future) → P6 UI重做。</div>
|
||||
<div class="path">docs/device-session-management-plan.html</div>
|
||||
</a>
|
||||
<a class="doc" href="superpowers/plans/2026-06-22-android-client.md">
|
||||
<div class="t">Android 客户端实现计划 <span class="tag md">MD</span></div>
|
||||
<div class="d">11 个任务:Go 1.24.3 工具链 → 构建 libbox.aar → 解析真实 API → Dart 接线 → 修原生编译 → 模拟器端到端 → 统计 → 切节点 → KillSwitch → 真机验证。</div>
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
# 设备 & 会话管理 + 每设备流量归因 —— 实现计划(P1–P6)
|
||||
|
||||
> 执行真相源(checkbox)。设计见 `docs/device-session-management-design.html`;阅读版 `docs/device-session-management-plan.html`。
|
||||
> SQL 走中性方言(`dialect.Upsert`、无 `NOW()`/`UTC_TIMESTAMP()`);UI 走 token 单源;不碰 CI-gated golden 的 Linux 基线。每期 build+test 过即 commit。
|
||||
|
||||
## 计划文档(先产出)
|
||||
- [x] `docs/superpowers/plans/2026-06-29-device-session-management.md`(本文件)
|
||||
- [ ] `docs/device-session-management-plan.html`(阅读版)
|
||||
- [ ] 登记 `docs/index.html`「实现计划」
|
||||
- [ ] 总方案 `device-session-management-design.html` 加「→ 实现计划」链接
|
||||
|
||||
## P1 · 设备注册打通(登录即注册设备)
|
||||
- [ ] 客户端加依赖 `uuid` + `device_info_plus`(`client/pubspec.yaml`)
|
||||
- [ ] 新 `client/lib/services/device_identity.dart`:`deviceId()`(secure storage 读;无则 `Uuid().v4()` 写;读失败 ≠ 没有,不重生成)、`deviceName()`/`platform()`、`clientVersion()`
|
||||
- [ ] 弃用 `connection_provider.dart` 的 `_kDeviceId='mac-001'` → 用 `device_identity`
|
||||
- [ ] `auth_api.dart` login/register 请求体加 `device:{id,name,platform,client_version}`
|
||||
- [ ] `auth/handler.go`:`loginRequest`/`registerRequest` 加 `Device deviceMeta`
|
||||
- [ ] `auth/service.go` Login/Register:成功签发后调 `DeviceRegistrar.RegisterIfAbsent`(consumer-side 接口;`devices.Service` 实现;`RegisterIfAbsent` 增返回 `deviceID`);`MaxDevices` 由 plan 解析
|
||||
- [ ] `normalizePlatform` 加 `linux`
|
||||
- [ ] `main.go` 构造 `devices.Service` 注入 `authSvc`
|
||||
- [ ] 测试:`device_identity` 单测;auth 集成(登录后 devices 落行)
|
||||
- [ ] 验收:登录后 `GET /v1/me/devices` 非空
|
||||
|
||||
## P2 · sessions 表 + 在线/最后登录
|
||||
- [ ] migration `000016_sessions_and_device_meta`(mysql+sqlite,含 down):建 sessions 表;devices 加 `client_version`+`totp_trusted_until`;唯一键 `UNIQUE(uuid)`→`UNIQUE(user_id,uuid)`;platform CHECK 加 `linux`
|
||||
- [ ] 新 `server/internal/sessions/`:`Store`(Create/RotateJTI/RevokeByJTI/RevokeByDevice/ActiveByUserWithDevice/LastLoginByDevice)
|
||||
- [ ] `TokenManager.Issue`/`Refresh` 外露 refresh JTI
|
||||
- [ ] `auth.Service` 注入 `sessions.Store`:Login/Register `Create`;Refresh `RotateJTI`+last_active;Logout `RevokeByJTI`
|
||||
- [ ] `nodes/handler_grpc.go: ReportUsage` deviceID>0 时 touch `devices.last_seen`
|
||||
- [ ] `devices.Service.ListDevices`+API struct+store join:增 `client_version`/`online`/`last_login`
|
||||
- [ ] 客户端 `Device` model 加 `clientVersion`/`online`/`lastLogin`;`account_api` 解析
|
||||
- [ ] 测试:sessions store;JTI surface;ReportUsage touch;ListDevices 计算;migration sqlite 实库
|
||||
- [ ] 验收:列表显示在线/版本/最后登录;停 agent ~3min 转离线
|
||||
|
||||
## P3 · 两个操作(强制退出 + 清除增强)
|
||||
- [ ] 新端点 `POST /v1/me/devices/{uuid}/logout`(handler + `Service.ForceLogout`):`sessions.RevokeByDevice` + 逐 jti `TokenManager.Revoke`
|
||||
- [ ] `DeleteDevice` 增强:加 `sessions.RevokeByDevice`;`CredentialRevoker` 改 per-device
|
||||
- [ ] `CredentialRevoker` 接口改 `RevokeDevice(ctx, dpUUID)`;`nodes.Hub` 实现;`main.go` 注入 hub 替 NoopRevoker
|
||||
- [ ] 客户端 `account_api.forceLogout` + `devicesProvider.forceLogout`
|
||||
- [ ] 测试:ForceLogout 删 jti+标 revoked_at;DeleteDevice per-device revoke
|
||||
- [ ] 验收:强制退出→refresh 失败;清除→设备消失+凭证作废
|
||||
|
||||
## P4 · 每设备流量验证
|
||||
- [ ] `ConnectNode` 兜底 `RegisterIfAbsent`(device 缺失时)
|
||||
- [ ] 端到端确认 `usage_device_daily` 填充
|
||||
- [ ] 统计页设备下拉接真实设备 + `deviceUsageProvider`(#10)
|
||||
- [ ] 测试:集成(注册→连接→上报→有行);统计页设备下拉 widget
|
||||
- [ ] 验收:统计页按设备看分流量
|
||||
|
||||
## P5 · (未来)2FA 信任设备 —— 本轮不编码
|
||||
- 占位:清除联动 `totp_trusted_until`(P2 已预留列)。
|
||||
|
||||
## P6 · UI 重做(DevicesScreen,五端)
|
||||
- [ ] `DevicesScreen` 重做:名称/平台·版本/在线绿点/最后登录 + 本机标 + 行尾 ⋯ 菜单 + 危险确认弹窗;走 token
|
||||
- [ ] 宽/窄屏适配;空/加载态
|
||||
- [ ] l10n:`app_text.dart`+`strings_{zh,en}.dart` 新增文案
|
||||
- [ ] golden:DevicesScreen 新 golden(Mac 生成,非 CI-gated)
|
||||
- [ ] 测试:widget(在线/离线/菜单/确认/调用)
|
||||
- [ ] 验收:与 §7 视觉稿一致,五端生效
|
||||
|
||||
## Verification
|
||||
- 后端 `go build ./... && go test ./...`(含 sqlite 实库)+ `./server/run_sqlite_test.sh`
|
||||
- 客户端 `flutter analyze` + `flutter test`
|
||||
- 端到端:登录→设备列表;停 agent→离线;强制退出→refresh 失败;清除→消失+凭证作废;走流量→`usage_device_daily` 有行
|
||||
- **线上 000016 迁移在 pangolin1 跑 `pangolin-migrate up` —— 生产写,执行前单独确认**
|
||||
|
||||
## 不在本轮
|
||||
- P5 只占位;存量空 devices 无需回填(新登录自然铺开)
|
||||
Reference in New Issue
Block a user