889cff4556
新增 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>
135 lines
8.9 KiB
HTML
135 lines
8.9 KiB
HTML
<!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>
|