-- 设备唯一键 UNIQUE(uuid) → UNIQUE(user_id, uuid)(F3:同机换账号 403 死结) -- -- device_id 是客户端按「安装」生成并持久的,跨账号复用;全局 UNIQUE(uuid) 使同一台 -- 机器登第二个账号时 RegisterIfAbsent 永远 403 → ConnectNode 判 DEVICE_NOT_REGISTERED, -- 且提示的「重新登录」无法自救。改为按用户隔离:同一物理设备在每个账号下各有一行。 -- 顺手落地 migration 16 头注释里推迟的两件事:本唯一键改造 + platform CHECK 加 linux -- (normalizePlatform 已接受 linux,但旧 CHECK 会拒 INSERT)。 -- -- SQLite 无法删除列级 UNIQUE,须重建表。sessions 对 devices 有 ON DELETE CASCADE 外键 -- 且连接开启 foreign_keys=ON:直接 DROP devices 会级联清空全部会话(=全员被登出)。 -- 安全次序(单事务内成立,不依赖 PRAGMA foreign_keys=OFF): -- ① RENAME devices → devices_old(sessions 的 FK 定义随 rename 跟走到 devices_old); -- ② 建新 devices(复合唯一键),按原 id 复制数据; -- ③ RENAME sessions → sessions_old,建新 sessions(FK 指向新 devices),复制数据; -- ④ 先 DROP 子表 sessions_old,再 DROP 已无引用的 devices_old;重建全部索引。 ALTER TABLE devices RENAME TO devices_old; CREATE TABLE devices ( id INTEGER PRIMARY KEY AUTOINCREMENT, uuid TEXT NOT NULL, user_id INTEGER NOT NULL, name TEXT NOT NULL, platform TEXT NOT NULL CHECK (platform IN ('ios','android','windows','macos','linux')), last_seen DATETIME NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, client_version TEXT NULL, totp_trusted_until DATETIME NULL, dp_uuid TEXT NULL, UNIQUE (user_id, uuid), FOREIGN KEY (user_id) REFERENCES users(id) ); INSERT INTO devices (id, uuid, user_id, name, platform, last_seen, created_at, client_version, totp_trusted_until, dp_uuid) SELECT id, uuid, user_id, name, platform, last_seen, created_at, client_version, totp_trusted_until, dp_uuid FROM devices_old; ALTER TABLE sessions RENAME TO sessions_old; CREATE TABLE sessions ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, device_id INTEGER NOT NULL, refresh_jti TEXT NOT NULL UNIQUE, client_ip TEXT NULL, client_version TEXT NULL, created_at DATETIME NOT NULL, last_active DATETIME NULL, revoked_at DATETIME NULL, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY (device_id) REFERENCES devices(id) ON DELETE CASCADE ); INSERT INTO sessions (id, user_id, device_id, refresh_jti, client_ip, client_version, created_at, last_active, revoked_at) SELECT id, user_id, device_id, refresh_jti, client_ip, client_version, created_at, last_active, revoked_at FROM sessions_old; DROP TABLE sessions_old; DROP TABLE devices_old; CREATE INDEX idx_devices_user ON devices (user_id); CREATE UNIQUE INDEX idx_devices_dp_uuid ON devices (dp_uuid); CREATE INDEX idx_sessions_user ON sessions (user_id); CREATE INDEX idx_sessions_device ON sessions (device_id);