feat(worker): add device registration_id cleanup worker
Add DeviceCleanupWorker to periodically clean up stale device tokens that haven't been used within the retention period. This prevents accumulation of invalid registration_ids and maintains data hygiene. - New DeviceCleanupConfig with enabled, retention_days (3), and interval_minutes (360) - New repository methods DeleteStaleDevices and DeleteOldestActiveDevice - Updated RegisterDevice to evict oldest active device when reaching limit - Reduced MaxDevicesPerUser from 10 to 3 per user - Worker integrated into app startup/shutdown lifecycle
This commit is contained in:
@@ -31,6 +31,12 @@ type DeviceTokenRepository interface {
|
||||
GetDeviceCountByUserID(userID int64) (int64, error)
|
||||
GetActiveDeviceCountByUserID(userID int64) (int64, error)
|
||||
DeleteInactiveDevices(before time.Time) error
|
||||
// DeleteStaleDevices 删除超过指定时间未使用的设备(按 last_used_at 判断,不限 is_active)
|
||||
// 用于周期性清理长期不活跃的 registration_id,last_used_at 为 NULL 的设备不清理
|
||||
DeleteStaleDevices(before time.Time) error
|
||||
// TrimActiveDevices 将用户活跃设备数量收敛到 keep 以内,删除超额的最旧设备
|
||||
// 返回实际删除的记录数。用于设备数限制(如限制从 10 改为 3 后的存量收敛)
|
||||
TrimActiveDevices(userID string, keep int) (int64, error)
|
||||
}
|
||||
|
||||
// deviceTokenRepository 设备Token仓储实现
|
||||
@@ -200,3 +206,31 @@ func (r *deviceTokenRepository) DeleteInactiveDevices(before time.Time) error {
|
||||
return r.db.Where("is_active = ? AND last_used_at < ?", false, before).
|
||||
Delete(&model.DeviceToken{}).Error
|
||||
}
|
||||
|
||||
// DeleteStaleDevices 删除超过指定时间未使用的设备(按 last_used_at 判断,不限 is_active)
|
||||
// last_used_at 为 NULL 的设备不清理(可能是刚注册尚未使用)
|
||||
func (r *deviceTokenRepository) DeleteStaleDevices(before time.Time) error {
|
||||
return r.db.Where("last_used_at IS NOT NULL AND last_used_at < ?", before).
|
||||
Delete(&model.DeviceToken{}).Error
|
||||
}
|
||||
|
||||
// TrimActiveDevices 将用户活跃设备数量收敛到 keep 以内,删除超额的最旧设备
|
||||
// 按 last_used_at 降序保留最新的 keep 条活跃记录,删除其余活跃记录。
|
||||
// keep <= 0 时不删除任何记录。返回实际删除的记录数。
|
||||
// 用于设备数限制(新注册超限淘汰 + 历史存量超额收敛)。
|
||||
func (r *deviceTokenRepository) TrimActiveDevices(userID string, keep int) (int64, error) {
|
||||
if keep <= 0 {
|
||||
return 0, nil
|
||||
}
|
||||
// 子查询:取该用户最新的 keep 条活跃记录 ID
|
||||
subQuery := r.db.Model(&model.DeviceToken{}).
|
||||
Select("id").
|
||||
Where("user_id = ? AND is_active = ?", userID, true).
|
||||
Order("last_used_at DESC NULLS LAST").
|
||||
Limit(keep)
|
||||
|
||||
// 删除不在保留集合内的活跃记录
|
||||
result := r.db.Where("user_id = ? AND is_active = ? AND id NOT IN (?)", userID, true, subQuery).
|
||||
Delete(&model.DeviceToken{})
|
||||
return result.RowsAffected, result.Error
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user