docs(design): 换用户uuid不变(唯一键改 UNIQUE(user_id,uuid)) + 按设备去重健壮性
§3.2.1 ⑤:device_id 安装级、与账户无关→换用户不变;故 devices 唯一键须从 全局 UNIQUE(uuid) 改为 UNIQUE(user_id,uuid)(000016),同机两账号=两行各自 dp_uuid。⑥:别用终身严格去重,改按活跃/在线计数+N天自动回收+用户自助清理。 §3.2 schema/表同步标注唯一键变更。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -146,7 +146,8 @@ INDEX (user_id), INDEX (device_id), UNIQUE (refresh_jti)
|
||||
<pre>
|
||||
devices(
|
||||
id INTEGER PK AUTOINCREMENT, -- 000001 内部自增主键
|
||||
uuid TEXT NOT NULL UNIQUE, -- 000001 客户端生成的稳定设备 UUID(API 对外用它)
|
||||
uuid TEXT NOT NULL, -- 000001 客户端生成的稳定设备 UUID(API 对外用它)。
|
||||
-- *000016 唯一键改 UNIQUE(user_id,uuid),见 §3.2.1 ⑤
|
||||
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 *
|
||||
@@ -158,12 +159,13 @@ devices(
|
||||
)
|
||||
INDEX (user_id) -- 000001
|
||||
UNIQUE INDEX (dp_uuid) -- 000015(NULL 不冲突,存量待回填)
|
||||
UNIQUE (user_id, uuid) -- 000016 *改:原 000001 是全局 UNIQUE(uuid),换用户需放开为复合键
|
||||
</pre>
|
||||
<table>
|
||||
<thead><tr><th>列</th><th>类型</th><th>来源</th><th>用途</th></tr></thead>
|
||||
<tbody>
|
||||
<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>uuid</code></td><td>TEXT,<b>UNIQUE(user_id,uuid)</b></td><td>000001<br><span class="ac-c">·唯一键 000016</span></td><td>客户端生成的稳定设备 UUID;<code>/v1/me/devices/{uuid}</code> 路径参数即它。安装级、与账户无关,<b>换用户不变</b>(§3.2.1 ⑤)</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>
|
||||
@@ -220,6 +222,27 @@ UNIQUE INDEX (dp_uuid) -- 000015(NULL 不冲突,存量待回填)
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card root">
|
||||
<b>⑤ 换用户 / 共享设备:uuid 不随账户变(含一处 schema 修正)</b>
|
||||
<ul>
|
||||
<li><code>device_id</code> 是<b>安装级</b>的,登录前就生成、与账户无关 → 登出 A 登入 B,<b>uuid 不变</b>,同一台机两个账号上报同一个 device uuid。</li>
|
||||
<li><b>因此唯一键必须是 <code>(user_id, uuid)</code>,不能是全局 <code>uuid UNIQUE</code></b>。现 000001 是 <code>uuid TEXT NOT NULL UNIQUE</code>(全局),B 用同 uuid 注册会撞约束 → <b>000016 改为 <code>UNIQUE(user_id, uuid)</code></b>(dp_uuid 仍全局唯一)。</li>
|
||||
<li>正确模型:同一台机两个账号 = <b>两行 devices</b>(各自 user_id),各自独立 <code>dp_uuid</code> → 流量按 (user, device) 分别归因,互不串。每个用户的「我的设备」只看到自己名下的行。</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<b>⑥ 「按设备去重/限额」的健壮性——别用终身严格去重</b>
|
||||
<p class="small" style="margin-top:4px">uuid 重装可能 churn(见 ④),若以「终身累计 + 严格按 uuid 去重」做限额会偏脆。设计取 best-effort 键 + 策略兜底:</p>
|
||||
<ul>
|
||||
<li><b>iOS 天然稳</b>(Keychain 跨重装保留),churn 主要在 Android。</li>
|
||||
<li><b>按「活跃/在线」计数</b>而非终身累计 → 离线的重装孤儿不占额度(口径 (b))。</li>
|
||||
<li><b>N 天未活跃自动回收</b>(last_seen / 最近 session 过期)→ 孤儿自动老化。</li>
|
||||
<li><b>用户自助</b>「清除登录信息」手动清旧设备。</li>
|
||||
</ul>
|
||||
<p class="small">即:uuid 是 best-effort 稳定键,配「回收 + 自助清理」即可稳健限额,不因偶发 churn 卡死用户。</p>
|
||||
</div>
|
||||
|
||||
<h3>3.3 <code>usage_device_daily</code>(已存在,本方案使其真正写入)</h3>
|
||||
<pre>
|
||||
usage_device_daily(user_id, device_id, date, bytes_up, bytes_down, minutes_used)
|
||||
|
||||
Reference in New Issue
Block a user