diff --git a/docs/device-limit-design.html b/docs/device-limit-design.html new file mode 100644 index 0000000..a92a0ba --- /dev/null +++ b/docs/device-limit-design.html @@ -0,0 +1,119 @@ + + + + + +设备数量限制 + 超限 UX(设计 · #16) + + + +
+← 文档索引 + +

设备数量限制 + 超限 UX(设计 · #16)

+

Pangolin · 真正启用套餐设备上限 + 非硬拒登的「移除设备」体验 · 2026-07-01

+ +
+plans.max_devices(free 1 / pro 3 / team 10)过去只展示、不强制:登录注册写死 +MaxDevices=0CheckDeviceQuota 是死代码。本次真正启用上限,按卡在登录(非硬拒登) ++ 选择移除/一键踢最旧 + 服务端自动清理久不活跃落地。 +
+ +

1. 关键决策

+ + + + + + + +
维度选择理由
执行点登录(+ 连接 backstop 待硬化)登录照常签发 token,超限只返回 device_limit 信号、客户端挡在「移除设备」页;不硬拒登。
解决方式选择移除 + 一键「移除最久未用」兼顾控制与便捷;移除即调现成 DeleteDevice(撤销会话+删行+召回 dp_uuid 凭证→设备立即离线登出)。
免费重装 churn服务端自动清理久不活跃count 只算近 staleWindow=30dlast_seen)活跃设备;登录 best-effort 删超期僵尸行 → 重装刷新的 UUID 自愈,不锁死用户。
+ +

2. 数据库

+

无 schema 变更:复用 plans.max_devicesdevices.last_seensessions。仅新增常量 staleWindow=30d。 +活跃计数 SELECT COUNT(1) FROM devices WHERE user_id=? AND last_seen IS NOT NULL AND last_seen > now-30d

+

另:本次把 pro 上限从 5 改为 3(seed + 迁移 000019;线上需 migrate up 生效)。

+ +

3. 后端

+
+internal/devices + +
+
+internal/auth(登录闸,非硬拒登) + +
+

登录响应(超限时):

+
{ "access_token":"…", "refresh_token":"…", "expires_in":900,
+  "device_limit": { "max_devices":1,
+    "devices":[ {"uuid":"…","name":"iPhone","platform":"ios","last_seen":"2026-06-30T…Z"} ] } }
+

待硬化 连接 backstop(ConnectNode 拒超限)本轮从简未做:登录闸是服务端权威信号、客户端据此自挡;后续在 Entitlement + NodeStore 加 count 做服务端硬拦。

+ +

4. 前端

+ + +

5. 验证

+ + +

6. 不在本轮

+ + +
+ + diff --git a/docs/index.html b/docs/index.html index 2563a4e..ed10a7d 100644 --- a/docs/index.html +++ b/docs/index.html @@ -44,6 +44,11 @@

设计方案 / Specs

+ +
设备数量限制 + 超限 UX HTML
+
真正启用套餐设备上限(free 1 / pro 3 / team 10):卡在登录(非硬拒登,返回 device_limit 信号)+ 选择移除/一键踢最旧 + 服务端按 last_seen 自动清理久不活跃。复用现成 DeleteDevice/ResolvePlan。无 DB schema 变更。#16。
+
docs/device-limit-design.html
+
设备 & 会话管理 + 每设备流量归因 HTML
「我的设备」管理(名称/平台/客户端版本/在线/最后登录 + 强制退出/清除登录信息)+ 控制面 sessions 表绑设备 + 数据面每设备 dp_uuid 记账。核心洞察:后端大半已建但 RegisterIfAbsent 未接线致 devices 表空。P1–P5 路线。