From 384c8df5068dd08f4e845916c1b36d0e3f358293 Mon Sep 17 00:00:00 2001 From: shanshanzhong Date: Thu, 12 Mar 2026 02:19:35 -0700 Subject: [PATCH] =?UTF-8?q?fix:=20=E8=B8=A2=E5=87=BA=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E6=97=B6=E6=B8=85=E9=99=A4=E6=89=80=E6=9C=89=20session?= =?UTF-8?q?=EF=BC=8C=E7=A1=AE=E4=BF=9D=E6=97=A7=20token=20=E7=AB=8B?= =?UTF-8?q?=E5=8D=B3=E5=A4=B1=E6=95=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - kickOfflineByUserDeviceLogic: 管理员踢设备后新增 clearAllSessions, 之前只清单个 WebSocket session,用户可用旧 token 继续访问 - unbindDeviceLogic: 家庭成员被踢时增加踢设备+清 session; 补全 session detail key 清理 Co-Authored-By: claude-flow --- .../user/kickOfflineByUserDeviceLogic.go | 44 +++++++++++++++++++ .../logic/public/user/unbindDeviceLogic.go | 16 ++++++- 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/internal/logic/admin/user/kickOfflineByUserDeviceLogic.go b/internal/logic/admin/user/kickOfflineByUserDeviceLogic.go index 9c4ec82..abd02a1 100644 --- a/internal/logic/admin/user/kickOfflineByUserDeviceLogic.go +++ b/internal/logic/admin/user/kickOfflineByUserDeviceLogic.go @@ -2,7 +2,9 @@ package user import ( "context" + "fmt" + "github.com/perfect-panel/server/internal/config" "github.com/perfect-panel/server/internal/svc" "github.com/perfect-panel/server/internal/types" "github.com/perfect-panel/server/pkg/logger" @@ -38,5 +40,47 @@ func (l *KickOfflineByUserDeviceLogic) KickOfflineByUserDevice(req *types.KickOf return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "update Device error: %v", err.Error()) } + // 清除该用户的所有会话,确保旧 token 失效 + l.clearAllSessions(device.UserId) + return nil } + +// clearAllSessions 清除指定用户的所有会话 +func (l *KickOfflineByUserDeviceLogic) clearAllSessions(userId int64) { + sessionsKey := fmt.Sprintf("%s%v", config.UserSessionsKeyPrefix, userId) + sessions, err := l.svcCtx.Redis.ZRange(l.ctx, sessionsKey, 0, -1).Result() + if err != nil { + l.Errorw("获取用户会话列表失败", + logger.Field("user_id", userId), + logger.Field("error", err.Error()), + ) + return + } + + if len(sessions) == 0 { + return + } + + pipe := l.svcCtx.Redis.TxPipeline() + for _, sessionID := range sessions { + if sessionID == "" { + continue + } + pipe.Del(l.ctx, fmt.Sprintf("%v:%v", config.SessionIdKey, sessionID)) + pipe.Del(l.ctx, fmt.Sprintf("%s:detail:%s", config.SessionIdKey, sessionID)) + } + pipe.Del(l.ctx, sessionsKey) + + if _, err = pipe.Exec(l.ctx); err != nil { + l.Errorw("清理会话缓存失败", + logger.Field("user_id", userId), + logger.Field("error", err.Error()), + ) + } + + l.Infow("[KickOffline] 管理员踢设备-清除所有Session", + logger.Field("user_id", userId), + logger.Field("count", len(sessions)), + ) +} diff --git a/internal/logic/public/user/unbindDeviceLogic.go b/internal/logic/public/user/unbindDeviceLogic.go index f63efa6..4139438 100644 --- a/internal/logic/public/user/unbindDeviceLogic.go +++ b/internal/logic/public/user/unbindDeviceLogic.go @@ -208,7 +208,7 @@ func (l *UnbindDeviceLogic) logoutUnbind(userInfo *user.User, device *user.Devic // 7. 清除该用户所有 session(旧 token 全部失效) l.clearAllSessions(userInfo.Id) - // 8. 清理受影响的家庭成员缓存(家庭解散/转移后成员需感知变化) + // 8. 清理受影响的家庭成员缓存 + 踢设备 + 清 session for _, memberID := range familyMemberIDs { if memberUser, findErr := l.svcCtx.UserModel.FindOne(l.ctx, memberID); findErr == nil { if clearErr := l.svcCtx.UserModel.ClearUserCache(l.ctx, memberUser); clearErr != nil { @@ -218,6 +218,19 @@ func (l *UnbindDeviceLogic) logoutUnbind(userInfo *user.User, device *user.Devic ) } } + + // 踢该成员的所有在线设备 + var memberDevices []user.Device + l.svcCtx.DB.WithContext(l.ctx). + Model(&user.Device{}). + Where("user_id = ?", memberID). + Find(&memberDevices) + for _, d := range memberDevices { + l.svcCtx.DeviceManager.KickDevice(d.UserId, d.Identifier) + } + + // 清除该成员所有 session,确保旧 token 失效 + l.clearAllSessions(memberID) } return nil @@ -268,6 +281,7 @@ func (l *UnbindDeviceLogic) clearAllSessions(userId int64) { continue } pipe.Del(l.ctx, fmt.Sprintf("%v:%v", config.SessionIdKey, sessionID)) + pipe.Del(l.ctx, fmt.Sprintf("%s:detail:%s", config.SessionIdKey, sessionID)) } pipe.Del(l.ctx, sessionsKey)