Files
pangolin/docs/device-session-management-plan.html
T
wangjia 889cff4556 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>
2026-06-29 00:15:35 +08:00

135 lines
8.9 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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>设备 &amp; 会话管理 + 每设备流量归因 · 实现计划<span class="small"> P1P6</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">设备 &amp; 会话管理 总方案</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> StoreCreate/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>ForceLogoutRevokeByDevice + 逐 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>宽/窄屏适配;空/加载态;l10nzh+en)新增文案;DevicesScreen 新 goldenMac 生成,非 CI-gated)。</li>
<li class="acc">✓ 验收:截图与 §7 视觉稿一致,五端生效(Flutter 单码)</li>
</ul>
</div>
<hr>
<h2>验收(整体)</h2>
<ul>
<li>后端 <code>go build ./... &amp;&amp; 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">设备 &amp; 会话管理 总方案</a> · <a href="stats-overhaul-plan.html">统计体系整改(#5</a>P4 设备下拉)。</p>
</div>
</body>
</html>