feat(devices): P2 sessions 表 + 在线/最后登录/客户端版本
ci-pangolin / Lint — shellcheck (push) Successful in 8s
ci-pangolin / OpenAPI Sync Check (push) Successful in 18s
ci-pangolin / Redline Scan — 脱敏 (UI 文案) (push) Successful in 6s
ci-pangolin / Flutter — analyze + test (push) Successful in 24s
ci-pangolin / Portable SQL — 可移植性 (mysql/sqlite) (push) Successful in 5s
ci-pangolin / Codegen Drift — token 生成物未漂移 (push) Successful in 4s
ci-pangolin / Go — build + test (push) Successful in 11s
ci-pangolin / E2E Smoke — L4 进程级端到端 (push) Successful in 14s
ci-pangolin / Go — integration (mysql/redis testcontainers) (push) Failing after 4m13s
ci-pangolin / Golden — 视觉回归 (components + auth) (push) Successful in 14s

migration 000016(mysql+sqlite,含 down):新增 sessions 表(绑 device+refresh JTI)
+ devices 加 client_version/totp_trusted_until。devices 唯一键改 + platform CHECK
加 linux(需 SQLite 表重建)拆出后续迁移,降风险。

后端:新 internal/sessions Store(Create/Rotate/Revoke/RevokeByDevice/
LastLoginByDevice);TokenManager 外露 refresh JTI(IssueWithJTI/RefreshWithJTI/
ParseRefreshJTI);auth.Service 注入 SessionStore——登录建会话、刷新轮换、登出吊销;
DeviceRegistrar 返回 deviceID;ReportUsage 心跳 touch devices.last_seen(在线判定);
devices.ListDevices 经 LastLoginSource 注入返回 online(last_seen<3min)/client_version/
last_login;RegisterIfAbsent 存 client_version。
客户端:Device model 加 online/clientVersion/lastLogin(fromJson 自动解析)。
测试:sessions store 3 例 + ListDevices 在线/最后登录 + device model 2 例 +
migration v16;全量 go test/flutter test 绿。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-06-29 00:50:24 +08:00
parent 8370ee1eb7
commit 2f298f0a0a
23 changed files with 709 additions and 159 deletions
@@ -5,32 +5,33 @@
## 计划文档(先产出)
- [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` 加「→ 实现计划」链接
- [x] `docs/device-session-management-plan.html`(阅读版)
- [x] 登记 `docs/index.html`「实现计划」
- [x] 总方案 `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` 非空
- [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 表 + 在线/最后登录
- [ ] 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_activeLogout `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 storeJTI surfaceReportUsage touchListDevices 计算;migration sqlite 实库
- [ ] 验收:列表显示在线/版本/最后登录;停 agent ~3min 转离线
- [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 · 两个操作(强制退出 + 清除增强)
- [ ] 新端点 `POST /v1/me/devices/{uuid}/logout`handler + `Service.ForceLogout`):`sessions.RevokeByDevice` + 逐 jti `TokenManager.Revoke`