fix(server): 强退设备立即离线 — online 须含活跃会话 + 撤销会话不刷 last_seen
trick bug:强退后被踢设备的下一次会话轮询会先刷 last_seen 再发现 active=false,那一刷 把它顶成「在线」直到 90s 窗口过期。修:① online = last_seen 新 且 有未撤销会话 (ActiveSessionDeviceIDs);被强退设备无活跃会话 → 立即离线。② SessionActive 仅在 active 时才 TouchLastSeen,撤销会话的轮询不再刷新。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -61,6 +61,28 @@ func (s *Store) HasActiveSession(ctx context.Context, userID, deviceID int64) (b
|
||||
return n > 0, nil
|
||||
}
|
||||
|
||||
// ActiveSessionDeviceIDs returns the set of the user's devices that have at least
|
||||
// one non-revoked session. Used to mark a device "online" only while it has a live
|
||||
// session — so a force-logged-out device drops offline immediately, not only after
|
||||
// its last_seen window lapses.
|
||||
func (s *Store) ActiveSessionDeviceIDs(ctx context.Context, userID int64) (map[int64]bool, error) {
|
||||
rows, err := s.db.QueryContext(ctx,
|
||||
`SELECT DISTINCT device_id FROM sessions WHERE user_id=? AND revoked_at IS NULL`, userID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("sessions.ActiveSessionDeviceIDs: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
set := make(map[int64]bool)
|
||||
for rows.Next() {
|
||||
var id int64
|
||||
if err := rows.Scan(&id); err != nil {
|
||||
return nil, fmt.Errorf("sessions.ActiveSessionDeviceIDs scan: %w", err)
|
||||
}
|
||||
set[id] = true
|
||||
}
|
||||
return set, rows.Err()
|
||||
}
|
||||
|
||||
// Revoke marks the session with the given JTI revoked (logout). Idempotent.
|
||||
func (s *Store) Revoke(ctx context.Context, jti string) error {
|
||||
now := time.Now().UTC()
|
||||
|
||||
Reference in New Issue
Block a user