From e6f622d6053a022bb40c407390d484b55ae3756b Mon Sep 17 00:00:00 2001 From: wangjia <809946525@qq.com> Date: Wed, 1 Jul 2026 19:26:51 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E8=AE=BE=E5=A4=87=E6=95=B0=E9=87=8F?= =?UTF-8?q?=E9=99=90=E5=88=B6=20+=20=E8=B6=85=E9=99=90=20UX=20=E8=AE=BE?= =?UTF-8?q?=E8=AE=A1=E6=96=87=E6=A1=A3(#16)=20+=20=E7=99=BB=E8=AE=B0=20ind?= =?UTF-8?q?ex?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 沿用家族样式;涵盖决策(登录闸/踢旧/自动清理)、DB(无 schema 变更)、后端 (devices CheckDeviceLimit + auth 登录闸)、前端(DeviceLimitScreen)、验证与不在本轮。 Co-Authored-By: Claude Opus 4.8 --- docs/device-limit-design.html | 119 ++++++++++++++++++++++++++++++++++ docs/index.html | 5 ++ 2 files changed, 124 insertions(+) create mode 100644 docs/device-limit-design.html 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 +
    +
  • store.CountActiveDevices(近 30d)+ store.PruneStaleDevices(删超期僵尸行,best-effort)。
  • +
  • service.CheckDeviceLimit:prune → ResolvePlan → 活跃 count > cap 即 Over,附活跃设备列表。设备登录时已注册,故「超」= activeCount > cap
  • +
+
+
+internal/auth(登录闸,非硬拒登) +
    +
  • 设备仍照常 RegisterIfAbsent(MaxDevices=0) + 建 session(不动 best-effort)。
  • +
  • DeviceRegistrarCheckDeviceLimitrecordLogin 回传 *DeviceLimitLoginOutcome.DeviceLimitLogin 透传;handler tokenPairResponse.device_limit(omitempty)。
  • +
  • main.goauthDeviceRegistrar 适配 devices.CheckDeviceLimit → auth.DeviceLimit
  • +
+
+

登录响应(超限时):

+
{ "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 路线。