Files
pangolin/docs/superpowers/plans/2026-06-29-device-session-management.md
T
wangjia 606e5e9e54 docs(plan): P4 收口 —— 每设备归因链路已交付,展示层过滤归 #10
每设备流量归因基础设施由 P1-P3 完整交付(注册→dp_uuid→usage_device_daily,
store 测试已锁)。统计页设备下拉「真过滤」需 per-device 每日曲线 API(现 provider
只给窗口聚合,对不齐月/周/日卡),归入统计整改 #10;端到端需真连客户端人工验证。
不做 ConnectNode 兜底(登录已注册;connect 体无 name/platform)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 06:14:19 +08:00

73 lines
5.7 KiB
Markdown
Raw 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.
# 设备 & 会话管理 + 每设备流量归因 —— 实现计划(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`(本文件)
- [x] `docs/device-session-management-plan.html`(阅读版)
- [x] 登记 `docs/index.html`「实现计划」
- [x] 总方案 `device-session-management-design.html` 加「→ 实现计划」链接
## P1 · 设备注册打通(登录即注册设备)
- [x] 客户端加依赖 `uuid` + `device_info_plus``client/pubspec.yaml`
- [x]`client/lib/services/device_identity.dart``deviceId()`secure storage 读;无则 `Uuid().v4()` 写;读失败 ≠ 没有,不重生成)、`deviceName()`/`platform()``clientVersion()`
- [x] 弃用 `connection_provider.dart``_kDeviceId='mac-001'` → 用 `device_identity`
- [x] `auth_api.dart` login/register 请求体加 `device:{id,name,platform,client_version}`
- [x] `auth/handler.go``loginRequest`/`registerRequest``Device deviceMeta`
- [x] `auth/service.go` Login/Register:成功签发后调 `DeviceRegistrar.RegisterIfAbsent`consumer-side 接口;`devices.Service` 实现;`RegisterIfAbsent` 增返回 `deviceID`);`MaxDevices` 由 plan 解析
- [x] `normalizePlatform``linux`
- [x] `main.go` 构造 `devices.Service` 注入 `authSvc`
- [x] 测试:`device_identity` 单测;auth 集成(登录后 devices 落行)
- [x] 验收:登录后 `GET /v1/me/devices` 非空
## P2 · sessions 表 + 在线/最后登录
- [x] migration `000016_sessions_and_device_meta`mysql+sqlite,含 down):建 sessions 表;devices 加 `client_version`+`totp_trusted_until`
-**拆出**devices 唯一键 `UNIQUE(uuid)``UNIQUE(user_id,uuid)` + platform CHECK 加 `linux` 需 SQLite 表重建(最高风险 DDL),隔离到单独后续迁移(多账户同机=已知降级,linux 注册 best-effort 失败不阻断登录)
- [x]`server/internal/sessions/``Store`Create/Rotate/Revoke/RevokeByDevice/LastLoginByDevice
- [x] `TokenManager.IssueWithJTI`/`RefreshWithJTI`/`ParseRefreshJTI` 外露 refresh JTI
- [x] `auth.Service` 注入 `SessionStore`Login/Register `Create`Refresh `Rotate`+last_activeLogout `Revoke`
- [x] `nodes/handler_grpc.go: ReportUsage` deviceID>0 时 touch `devices.last_seen`
- [x] `devices.Service.ListDevices`+API struct+store join:增 `client_version`/`online`/`last_login`(注入 `LastLoginSource`
- [x] 客户端 `Device` model 加 `clientVersion`/`online`/`lastLogin``account_api` 解析(fromJson 自动)
- [x] 测试:sessions store(3) + ListDevices 计算 + device model(2) + auth 设备/会话;migration sqlite 实库 v16
- [ ] 验收:列表显示在线/版本/最后登录(待 P6 UI + 端到端);停 agent ~3min 转离线
## P3 · 两个操作(强制退出 + 清除增强)
- [x] 新端点 `POST /v1/me/devices/{uuid}/logout`handler + `Service.ForceLogout`):`sessions.RevokeByDevice` + 逐 jti `TokenManager.Revoke`
- [x] `DeleteDevice` 增强:加 `sessions.RevokeByDevice``CredentialRevoker` 改 per-device
- [x] `CredentialRevoker` 接口改 `RevokeDevice(ctx, dpUUID)``nodes.Hub` 实现;`main.go` 注入 hub 替 NoopRevoker
- [x] 客户端 `account_api.forceLogout` + `devicesProvider.forceLogout`
- [x] 测试:ForceLogout 删 jti+标 revoked_atDeleteDevice per-device revoke
- [ ] 验收:强制退出→refresh 失败;清除→设备消失+凭证作废
## P4 · 每设备流量(基础设施已由 P1–P3 交付;展示层收口归 #10)
**结论**:每设备归因**链路已通**——登录注册设备 → connect `EnsureDeviceDpUUID` 命中 →
铸 per-device dp_uuid 下发 sing-box → `ReportUsage``UserDeviceByDpUUID`(deviceID>0)
`AccumulateDeviceUsage``usage_device_daily`store 测试已锁)。
- [x] 链路打通(P1P3 交付);不做 ConnectNode 兜底:登录已注册,且 connect 体只带
device_id、无 name/platform,不宜在此造半残设备行
- [ ] **统计页设备下拉真过滤****归入统计整改 #10**:需新的 per-device **每日曲线 API**
(现 `deviceUsageProvider` 只给单设备窗口聚合,对不齐月/周/日卡 + 14 天折线的每日数据)
- [ ] **端到端**(注册→连接→走流量→`usage_device_daily` 有行):需真连客户端,**人工验证**
## P5 · (未来)2FA 信任设备 —— 本轮不编码
- 占位:清除联动 `totp_trusted_until`P2 已预留列)。
## P6 · UI 重做(DevicesScreen,五端)
- [x] `DevicesScreen` 重做:名称/平台·版本/在线绿点/最后登录 + 本机标 + 行尾 ⋯ 菜单 + 危险确认弹窗;走 token
- [x] 宽/窄屏适配;空/加载态
- [x] l10n`app_text.dart`+`strings_{zh,en}.dart` 新增文案
- [ ] goldenDevicesScreen 新 goldenMac 生成,非 CI-gated
- [x] 测试: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 无需回填(新登录自然铺开)