Telegram Bot用户超时未响应入库问题:@Scheduled局限与SchedulerService咨询
解决Telegram Bot用户超时1小时精准入库问题
原@Scheduled批量定时任务的问题在于,每小时才检查一次,导致用户超时后可能最多延迟1小时才被入库。改用Spring的TaskScheduler(即你提到的SchedulerService)可以为每个用户单独设置延迟任务,在用户最后活动时间满1小时时立即触发入库,实现精准处理。
实现思路
- 为每个用户维护独立的延迟调度任务,用户每次活动时取消旧任务,重新创建1小时后的新任务
- 用线程安全的映射存储用户ID与对应任务句柄,避免多线程冲突
- 任务触发时再次校验用户状态,确保不会误处理刚活动过的用户
代码实现
1. 核心调度服务类
import org.springframework.scheduling.TaskScheduler; import org.springframework.scheduling.support.ScheduledFuture; import org.springframework.stereotype.Component; import java.time.LocalDateTime; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @Component public class UserInactiveScheduler { private final UserStateService userStateService; private final UserStateManager userStateManager; private final TaskScheduler taskScheduler; // 存储用户ID到调度任务的映射,用于取消旧任务 private final Map<Long, ScheduledFuture<?>> userTaskMap = new ConcurrentHashMap<>(); public UserInactiveScheduler(UserStateService userStateService, UserStateManager userStateManager, TaskScheduler taskScheduler) { this.userStateService = userStateService; this.userStateManager = userStateManager; this.taskScheduler = taskScheduler; } // 用户每次和Bot交互时调用此方法,重置超时计数 public void resetUserInactiveTimer(UserState userState) { // 更新用户最后活动时间 userState.setLastActivityTime(LocalDateTime.now()); userStateManager.updateUserState(userState); // 取消之前的超时任务(如果存在) ScheduledFuture<?> oldTask = userTaskMap.remove(userState.getUserId()); if (oldTask != null && !oldTask.isCancelled()) { oldTask.cancel(false); } // 计算1小时后的执行时间,创建新任务 LocalDateTime executeTime = userState.getLastActivityTime().plusHours(1); ScheduledFuture<?> newTask = taskScheduler.schedule( () -> saveInactiveUser(userState.getUserId()), executeTime ); userTaskMap.put(userState.getUserId(), newTask); } // 单个用户超时后的入库逻辑 private void saveInactiveUser(Long userId) { UserState userState = userStateManager.getUserState(userId); if (userState != null && userState.getLastActivityTime().isBefore(LocalDateTime.now().minusHours(1))) { userStateService.saveUsers(List.of(userState)); userTaskMap.remove(userId); // 可选:从内存中移除用户状态,释放资源 userStateManager.removeUserState(userId); } } }
2. 在用户交互处触发重置逻辑
比如在处理用户消息的服务中,调用重置方法:
import org.springframework.stereotype.Service; @Service public class TelegramMessageHandler { private final UserInactiveScheduler userInactiveScheduler; private final UserStateManager userStateManager; public TelegramMessageHandler(UserInactiveScheduler userInactiveScheduler, UserStateManager userStateManager) { this.userInactiveScheduler = userInactiveScheduler; this.userStateManager = userStateManager; } public void handleUserMessage(Long userId, String messageContent) { // 获取或创建用户状态 UserState userState = userStateManager.getUserState(userId); if (userState == null) { userState = new UserState(userId); userStateManager.addUserState(userState); } // 重置用户超时计时器 userInactiveScheduler.resetUserInactiveTimer(userState); // 其他消息处理逻辑(比如问卷交互)... } }
3. 移除原批量定时任务
原DataBaseListener中的@Scheduled方法可以直接删除,因为现在是针对每个用户的精准调度,不再需要批量检查。
4. 可选:自定义调度线程池
如果用户量较大,可以配置线程池大小,避免任务堆积:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; @Configuration public class SchedulerConfig { @Bean public ThreadPoolTaskScheduler taskScheduler() { ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); scheduler.setPoolSize(10); // 根据实际用户量调整 scheduler.setThreadNamePrefix("user-inactive-scheduler-"); scheduler.initialize(); return scheduler; } }
注意事项
- 用
ConcurrentHashMap存储任务句柄,保证多线程环境下的安全性 - 任务触发时再次校验用户的
lastActivityTime,避免极端情况下的误处理 - 如果用户完成问卷,可以直接调用
saveInactiveUser方法提前入库,无需等待超时
内容的提问来源于stack exchange,提问作者Даня Лайт
相关产品推荐
相关产品推荐

