docs(design): devices 表补全字段 + name来源/三时间戳/按设备限登录数 释疑

§3.2 列出 devices 完整 9 列(含来源迁移 + platform CHECK 需加 linux);
新增 §3.2.1:① name 由客户端 device_info_plus 按平台取(主机名/机型)上报;
② created_at/last_seen/最后登录(session) 三时间戳辨析;③ 设备身份键
(user_id,uuid) + RegisterIfAbsent maxDevices 作「按设备控制登录数」抓手
(累计 vs 同时在线两种口径)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-06-28 23:43:37 +08:00
parent 31ac76e3d5
commit c1994ad048
+64 -5
View File
@@ -141,15 +141,74 @@ INDEX (user_id), INDEX (device_id), UNIQUE (refresh_jti)
</tbody>
</table>
<h3>3.2 <code>devices</code>加列(同迁移</h3>
<h3>3.2 <code>devices</code>(完整字段</h3>
<p>既有 7 列(000001 建表 + 000015 加 dp_uuid+ 本方案新增 2 列(000016)。完整 schema</p>
<pre>
devices(
id INTEGER PK AUTOINCREMENT, -- 000001 内部自增主键
uuid TEXT NOT NULL UNIQUE, -- 000001 客户端生成的稳定设备 UUID(API 对外用它)
user_id NOT NULL, -- 000001 FK users.id(设备归属用户)
name TEXT NOT NULL, -- 000001 设备名(主机名/机型,≤64 runes)
platform TEXT NOT NULL CHECK(...), -- 000001 ios|android|windows|macos|linux *
last_seen DATETIME NULL, -- 000001 数据面最后活跃(connect + ReportUsage touch)→ 在线判定
created_at DATETIME NOT NULL, -- 000001 设备首次注册时间
dp_uuid CHAR(36) NULL UNIQUE, -- 000015 每设备数据面凭证(sing-box VLESS user uuid
client_version TEXT NULL, -- 000016 最近上报的客户端版本(列表展示) ← 新增
totp_trusted_until DATETIME NULL -- 000016 预留:2FA「信任设备」过期点;清除登录时清空 ← 新增
)
INDEX (user_id) -- 000001
UNIQUE INDEX (dp_uuid) -- 000015NULL 不冲突,存量待回填)
</pre>
<table>
<thead><tr><th></th><th>类型</th><th>用途</th></tr></thead>
<thead><tr><th></th><th>类型</th><th>来源</th><th>用途</th></tr></thead>
<tbody>
<tr><td><code>client_version</code></td><td>TEXT NULL</td><td>该设备最近上报的客户端版本,列表展示</td></tr>
<tr><td><code>totp_trusted_until</code></td><td>DATETIME NULL</td><td><b>预留</b>:未来 2FA「信任设备」过期点;清除登录信息时一并清空</td></tr>
<tr><td><code>id</code></td><td>INTEGER PK</td><td>000001</td><td>内部自增主键(sessions/usage_device_daily 用 device_id 引用它)</td></tr>
<tr><td><code>uuid</code></td><td>TEXT UNIQUE</td><td>000001</td><td>客户端生成的稳定设备 UUID<code>/v1/me/devices/{uuid}</code> 路径参数即它</td></tr>
<tr><td><code>user_id</code></td><td>FK users.id</td><td>000001</td><td>设备归属用户</td></tr>
<tr><td><code>name</code></td><td>TEXT</td><td>000001</td><td>设备名(主机名/机型,<code>normalizeName</code> 截断 ≤64 runes</td></tr>
<tr><td><code>platform</code></td><td>TEXT CHECK</td><td>000001</td><td>平台枚举。<b>*现有 CHECK 仅 4 值,需 000016 放宽加 <code>linux</code></b></td></tr>
<tr><td><code>last_seen</code></td><td>DATETIME NULL</td><td>000001</td><td>数据面最后活跃;&lt;3min=在线。由 connect + ReportUsage touch</td></tr>
<tr><td><code>created_at</code></td><td>DATETIME</td><td>000001</td><td>设备首次注册时间(≠最后登录,后者取 session.created_at</td></tr>
<tr><td><code>dp_uuid</code></td><td>CHAR(36) NULL UNIQUE</td><td>000015</td><td>每设备数据面凭证;<code>EnsureDeviceDpUUID</code> 首连铸入,作 sing-box VLESS user uuid</td></tr>
<tr><td><code>client_version</code></td><td>TEXT NULL</td><td><span class="ac-c">000016 新增</span></td><td>该设备最近上报的客户端版本,列表展示</td></tr>
<tr><td><code>totp_trusted_until</code></td><td>DATETIME NULL</td><td><span class="ac-c">000016 新增</span></td><td><b>预留</b>:未来 2FA「信任设备」过期点;清除登录信息时一并清空</td></tr>
</tbody>
</table>
<p class="small">既有列:<code>uuid / user_id / name / platform / last_seen / created_at / dp_uuid(000015)</code><b>设备「最后登录时间」</b>= 该设备最近一条 <code>session.created_at</code>(不复用 last_seen,后者是数据面活跃度)。</p>
<p class="small"><b>*platform CHECK 放宽</b>000001 的约束是 <code>CHECK(platform IN ('ios','android','windows','macos'))</code><u>不含 linux</u>,但桌面客户端支持 Linux。000016 需重建该约束加入 <code>'linux'</code>SQLite 改 CHECK 要走「建新表→拷数据→换名」;MySQL 同步改)。</p>
<h3>3.2.1 字段释疑(name 来源 / 三个时间戳 / 设备 id 与限额)</h3>
<div class="card">
<b><code>name</code> 怎么来</b>:由<b>客户端</b>在登录/注册时填好上报(不是服务端造)。客户端用 <code>device_info_plus</code> 按平台取一个可读名:
<table>
<thead><tr><th>平台</th><th>取值</th><th>示例</th></tr></thead>
<tbody>
<tr><td>macOS / Linux</td><td>主机名(<code>Platform.localHostname</code></td><td><code>Wangjia-MacBook-Pro</code></td></tr>
<tr><td>Windows</td><td>计算机名(<code>WindowsDeviceInfo.computerName</code></td><td><code>DESKTOP-7F3K</code></td></tr>
<tr><td>iOS</td><td>设备名/机型(<code>IosDeviceInfo.name</code><code>.utsname.machine</code></td><td><code>iPhone 15</code></td></tr>
<tr><td>Android</td><td>厂商 + 机型(<code>manufacturer + model</code></td><td><code>Xiaomi 13</code></td></tr>
</tbody>
</table>
服务端 <code>RegisterIfAbsent</code><code>normalizeName</code> 截断 ≤64 runes 存库。后续可允许用户在「我的设备」里改名(PATCH,二期可选)。
</div>
<div class="card">
<b>② 三个时间戳别混</b>——分别答「装没装过 / 此刻在不在用 / 上次什么时候登的」:
<ul>
<li><code>created_at</code>devices= 设备<b>首次注册</b>时间,只写一次。</li>
<li><code>last_seen</code>devices= <b>数据面最后活跃</b>,即「此刻是不是正连着节点走流量」。由 <code>connect</code> + 每条 <code>ReportUsage</code>(连着时 ~60s 一次)touch<code>now - last_seen &lt; 3min</code> ⇒ 「在线」。</li>
<li><b>最后登录</b> = 该设备最近一条 <code>sessions.created_at</code>(auth 层,与是否正在走流量无关)。列表上「在线」红绿点看 <code>last_seen</code>,「最后登录 N 天前」看 session。</li>
</ul>
</div>
<div class="card root">
<b>③ 设备 id 与「按设备控制登录数」</b>(你提的将来需求,本设计已为其留好抓手):
<ul>
<li><b>设备身份键</b> = <code>(user_id, devices.uuid)</code><code>uuid</code> 由客户端生成、持久化在 <code>flutter_secure_storage</code>,同一台机器重装 app 内仍稳定 → 一台真实设备恒等于一行 <code>devices</code>,不会因重登重复计数。</li>
<li><b>限额抓手已就绪</b><code>RegisterIfAbsent(userID, deviceUUID, name, platform, <u>maxDevices</u>)</code> 在注册新设备时已会<b>按套餐上限校验设备数</b><code>maxDevices</code> 来自计划:免费 1 / PRO 5,对应 <code>Me.devicesMax</code>)。超限时拒绝注册新设备(返回「设备数已达上限」),即「按设备控制用户能登录的设备数」。</li>
<li><b>两种限额口径</b>(将来按需选):<b>(a) 累计设备数</b>——devices 行数 ≤ 上限(当前 RegisterIfAbsent 口径,超了要先「清除登录信息」踢掉旧设备);<b>(b) 同时在线/登录数</b>——以 <code>sessions</code>(未撤销)或 <code>last_seen</code> 在线数为准,限「并发」而非「累计」。<code>sessions</code> 表 + <code>devices.id</code> 同时支持这两种统计。</li>
</ul>
</div>
<h3>3.3 <code>usage_device_daily</code>(已存在,本方案使其真正写入)</h3>
<pre>