baoxiang/backend/app/services/scheduler_service.py

186 lines
6.3 KiB
Python
Raw Normal View History

2025-12-16 18:06:50 +08:00
"""
定时任务服务
用于处理自动发放低保自动封盘等定时任务
"""
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()