""" 定时任务服务 用于处理自动发放低保、自动封盘等定时任务 """ import asyncio from datetime import datetime, timedelta from sqlalchemy.orm import Session from ..core.database import get_db, SessionLocal from ..models.user import User, Transaction from ..models.game import Chest, ChestStatus from ..services.user_service import UserService from ..services.game_service import GameService from ..core.config import settings class SchedulerService: """定时任务服务""" @staticmethod async def start_scheduler(): """ 启动定时任务调度器 """ # 创建后台任务运行定时器 asyncio.create_task(SchedulerService._run_daily_allowance_scheduler()) # 创建后台任务扫描过期宝箱 asyncio.create_task(SchedulerService._run_expired_chest_scanner()) @staticmethod async def _run_daily_allowance_scheduler(): """ 运行每日低保发放调度器 每天凌晨1点执行 """ while True: # 获取当前时间 now = datetime.now() # 计算下次执行时间(明天凌晨1点) next_run = now.replace(hour=1, minute=0, second=0, microsecond=0) + timedelta(days=1) # 计算等待时间 sleep_seconds = (next_run - now).total_seconds() # 等待到下次执行时间 await asyncio.sleep(sleep_seconds) # 执行每日低保发放 try: SchedulerService._distribute_daily_allowance() except Exception as e: print(f"每日低保发放失败: {e}") @staticmethod def _distribute_daily_allowance(): """ 发放每日低保 为所有余额低于低保门槛的用户发放低保 """ db_gen = get_db() db: Session = next(db_gen) try: # 获取所有余额低于低保门槛的活跃用户 users = db.query(User).filter( User.balance < settings.game.ALLOWANCE_THRESHOLD, User.status == "ACTIVE" ).all() allowance_amount = settings.game.DAILY_ALLOWANCE success_count = 0 for user in users: try: # 检查用户是否满足领取条件 if SchedulerService._can_claim_allowance(db, user): # 发放低保 user.balance += allowance_amount # 记录交易 transaction = Transaction( user_id=user.id, type="低保", amount=allowance_amount, balance_after=user.balance, description="每日自动发放低保" ) db.add(transaction) success_count += 1 except Exception as e: print(f"为用户 {user.username} 发放低保时出错: {e}") continue # 提交事务 db.commit() print(f"每日低保发放完成,成功为 {success_count} 名用户发放低保") except Exception as e: db.rollback() print(f"每日低保发放过程中出现错误: {e}") finally: # 关闭数据库连接 db.close() @staticmethod def _can_claim_allowance(db: Session, user: User) -> bool: """ 检查用户是否可以领取低保 """ # 检查是否已领取过低保 last_allowance = db.query(Transaction).filter( Transaction.user_id == user.id, Transaction.type == "低保" ).order_by(Transaction.created_at.desc()).first() # 如果从未领取过低保,则可以领取 if not last_allowance: return True # 如果距离上次低保已经超过24小时,则可以领取 time_since_last = datetime.now() - last_allowance.created_at return time_since_last >= timedelta(hours=24) @staticmethod async def _run_expired_chest_scanner(): """ 运行过期宝箱扫描器 每10秒扫描一次,自动锁定过期的宝箱 """ while True: try: # 扫描并锁定过期宝箱 expired_count = SchedulerService._scan_and_lock_expired_chests() if expired_count > 0: print(f"自动锁定 {expired_count} 个过期宝箱") except Exception as e: print(f"扫描过期宝箱时出错: {e}") # 每10秒扫描一次(比1秒更高效) await asyncio.sleep(10) @staticmethod def _scan_and_lock_expired_chests() -> int: """ 扫描并锁定所有过期的宝箱 Returns: 锁定宝箱的数量 """ db = SessionLocal() try: # 获取所有状态为下注中的宝箱 chests = db.query(Chest).filter( Chest.status == ChestStatus.BETTING ).all() # 使用本地时间进行计算(与数据库func.now()保持一致) now = datetime.now() expired_count = 0 expired_chest_ids = [] for chest in chests: # 计算已过时间 elapsed = (now - chest.created_at).total_seconds() # 检查是否过期(增加0.5秒容差) if elapsed >= chest.countdown_seconds - 0.5: # 锁定宝箱 chest.status = ChestStatus.LOCKED chest.locked_at = now expired_count += 1 expired_chest_ids.append(chest.id) if expired_count > 0: db.commit() print(f"已自动锁定 {expired_count} 个过期宝箱: {expired_chest_ids}") return expired_count except Exception as e: db.rollback() print(f"锁定过期宝箱时出错: {e}") return 0 finally: db.close()