186 lines
6.3 KiB
Python
186 lines
6.3 KiB
Python
|
|
"""
|
|||
|
|
定时任务服务
|
|||
|
|
用于处理自动发放低保、自动封盘等定时任务
|
|||
|
|
"""
|
|||
|
|
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()
|