# 设备移出和邀请码优化 - 共识文档(更新版) ## 需求概述 修复两个 Bug: 1. **Bug 1**:设备B绑定邮箱后被从设备A移除,设备B没有被踢下线 2. **Bug 2**:输入不存在的邀请码时,提示信息不友好 --- ## Bug 1:设备移出后未自动退出 ### 根本原因 设备B绑定邮箱(迁移到邮箱用户)时: - ✅ 数据库更新了设备的 `UserId` - ❌ `DeviceManager` 内存中设备B的 WebSocket 连接仍在**原用户**名下 - ❌ Redis 缓存中设备B的 session 未被清理 解绑设备B时,`KickDevice(用户1, "device-b")` 在用户1的设备列表中找不到 device-b(因为连接还在原用户名下)。 ### 修复方案 **文件1:`bindEmailWithVerificationLogic.go`** 在设备迁移后,踢出旧连接并清理缓存: ```go // 第 139-158 行之后添加 for _, device := range devices { device.UserId = emailUserId err = l.svcCtx.UserModel.UpdateDevice(l.ctx, device) // ...existing code... // 新增:踢出旧连接并清理缓存 l.svcCtx.DeviceManager.KickDevice(u.Id, device.Identifier) deviceCacheKey := fmt.Sprintf("%v:%v", config.DeviceCacheKeyKey, device.Identifier) if sessionId, _ := l.svcCtx.Redis.Get(l.ctx, deviceCacheKey).Result(); sessionId != "" { sessionIdCacheKey := fmt.Sprintf("%v:%v", config.SessionIdKey, sessionId) _ = l.svcCtx.Redis.Del(l.ctx, deviceCacheKey).Err() _ = l.svcCtx.Redis.Del(l.ctx, sessionIdCacheKey).Err() } } ``` **文件2:`unbindDeviceLogic.go`**(防御性修复) 补充 `user_sessions` 清理逻辑,与 `deleteUserDeviceLogic.go` 保持一致: ```go // 第 118-122 行,补充 sessionsKey 清理 if sessionId, rerr := l.svcCtx.Redis.Get(ctx, deviceCacheKey).Result(); rerr == nil && sessionId != "" { _ = l.svcCtx.Redis.Del(ctx, deviceCacheKey).Err() sessionIdCacheKey := fmt.Sprintf("%v:%v", config.SessionIdKey, sessionId) _ = l.svcCtx.Redis.Del(ctx, sessionIdCacheKey).Err() // 新增:清理 user_sessions sessionsKey := fmt.Sprintf("%s%v", config.UserSessionsKeyPrefix, device.UserId) _ = l.svcCtx.Redis.ZRem(ctx, sessionsKey, sessionId).Err() } ``` --- ## Bug 2:邀请码错误提示不友好 ### 根本原因 `bindInviteCodeLogic.go` 中未区分"邀请码不存在"和"数据库错误"。 ### 修复方案 ```go // 第 44-47 行修改为 referrer, err := l.svcCtx.UserModel.FindOneByReferCode(l.ctx, req.InviteCode) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return errors.Wrapf(xerr.NewErrCodeMsg(xerr.InviteCodeError, "无邀请码"), "invite code not found") } logger.WithContext(l.ctx).Error(err) return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query referrer failed: %v", err.Error()) } ``` --- ## 涉及文件汇总 | 文件 | 修改类型 | 优先级 | |------|----------|--------| | `internal/logic/public/user/bindEmailWithVerificationLogic.go` | 核心修复 | 高 | | `internal/logic/public/user/unbindDeviceLogic.go` | 防御性修复 | 中 | | `internal/logic/public/user/bindInviteCodeLogic.go` | Bug 修复 | 中 | --- ## 验收标准 ### Bug 1 验收 - [ ] 设备B绑定邮箱后,设备B的旧 Token 失效 - [ ] 设备B绑定邮箱后,设备B的 WebSocket 连接被断开 - [ ] 在设备A上移除设备B后,设备B立即被踢下线 - [ ] 设备B无法继续使用旧 Token 调用 API ### Bug 2 验收 - [ ] 输入不存在的邀请码时,返回错误码 20009 - [ ] 错误消息显示"无邀请码" --- ## 验证计划 1. **编译验证**:`go build ./...` 2. **手动测试**: - 设备B绑定邮箱 → 检查是否被踢下线 - 设备A移除设备B → 检查设备B是否被踢下线 - 输入无效邀请码 → 检查错误提示