第一次提交哦啊
This commit is contained in:
parent
6793719648
commit
17093cdc98
@ -11,10 +11,11 @@ from ..models.game import Chest, ChestStatus
|
|||||||
from ..services.user_service import UserService
|
from ..services.user_service import UserService
|
||||||
from ..services.game_service import GameService
|
from ..services.game_service import GameService
|
||||||
from ..core.config import settings
|
from ..core.config import settings
|
||||||
|
from ..services.balance_monitor_service import BalanceMonitorService
|
||||||
|
|
||||||
class SchedulerService:
|
class SchedulerService:
|
||||||
"""定时任务服务"""
|
"""定时任务服务"""
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def start_scheduler():
|
async def start_scheduler():
|
||||||
"""
|
"""
|
||||||
@ -24,6 +25,8 @@ class SchedulerService:
|
|||||||
asyncio.create_task(SchedulerService._run_daily_allowance_scheduler())
|
asyncio.create_task(SchedulerService._run_daily_allowance_scheduler())
|
||||||
# 创建后台任务扫描过期宝箱
|
# 创建后台任务扫描过期宝箱
|
||||||
asyncio.create_task(SchedulerService._run_expired_chest_scanner())
|
asyncio.create_task(SchedulerService._run_expired_chest_scanner())
|
||||||
|
# 创建后台任务扫描余额清零用户
|
||||||
|
asyncio.create_task(SchedulerService._run_zero_balance_scanner())
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def _run_daily_allowance_scheduler():
|
async def _run_daily_allowance_scheduler():
|
||||||
@ -183,4 +186,76 @@ class SchedulerService:
|
|||||||
print(f"锁定过期宝箱时出错: {e}")
|
print(f"锁定过期宝箱时出错: {e}")
|
||||||
return 0
|
return 0
|
||||||
finally:
|
finally:
|
||||||
db.close()
|
db.close()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def _run_zero_balance_scanner():
|
||||||
|
"""
|
||||||
|
运行余额清零用户扫描器
|
||||||
|
每天零点执行,扫描遗漏的余额清零用户并发放奖励
|
||||||
|
"""
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
# 获取当前时间
|
||||||
|
now = datetime.now()
|
||||||
|
|
||||||
|
# 计算下次执行时间(每天零点)
|
||||||
|
next_run = now.replace(hour=0, minute=0, second=0, microsecond=0) + timedelta(days=1)
|
||||||
|
|
||||||
|
# 如果是刚过零点(00:00-00:10),执行扫描
|
||||||
|
if now.hour == 0 and now.minute < 10:
|
||||||
|
print("执行余额清零用户扫描任务...")
|
||||||
|
SchedulerService._scan_zero_balance_users()
|
||||||
|
|
||||||
|
# 计算等待时间(到下一个整点)
|
||||||
|
sleep_seconds = (next_run - now).total_seconds()
|
||||||
|
|
||||||
|
# 等待到下次执行时间
|
||||||
|
await asyncio.sleep(sleep_seconds)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"余额清零扫描任务执行出错: {e}")
|
||||||
|
# 出错后等待1小时再试
|
||||||
|
await asyncio.sleep(3600)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _scan_zero_balance_users():
|
||||||
|
"""
|
||||||
|
扫描余额清零用户并发放奖励
|
||||||
|
用于定时任务,确保没有遗漏
|
||||||
|
"""
|
||||||
|
db = SessionLocal()
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 使用BalanceMonitorService扫描
|
||||||
|
processed_count = BalanceMonitorService.scan_zero_balance_users(db)
|
||||||
|
print(f"余额清零扫描完成,处理了 {processed_count} 个用户")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"扫描余额清零用户时出错: {e}")
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def reset_daily_allowance_status():
|
||||||
|
"""
|
||||||
|
重置每日低保领取状态(供手动调用)
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
from ..utils.redis import redis_client
|
||||||
|
|
||||||
|
# 获取所有低保领取状态的Redis键
|
||||||
|
keys = redis_client.keys("daily_allowance:*")
|
||||||
|
|
||||||
|
if keys:
|
||||||
|
# 删除所有低保领取状态
|
||||||
|
deleted_count = redis_client.delete(*keys)
|
||||||
|
print(f"已清理 {deleted_count} 个低保领取状态标记")
|
||||||
|
return deleted_count
|
||||||
|
else:
|
||||||
|
print("无需清理(暂无低保领取状态标记)")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"重置低保状态时出错: {e}")
|
||||||
|
return 0
|
||||||
|
|||||||
@ -302,6 +302,26 @@ class SystemService:
|
|||||||
"is_public": False,
|
"is_public": False,
|
||||||
"display_order": 7,
|
"display_order": 7,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"config_key": "BALANCE_ZERO_REWARD_AMOUNT",
|
||||||
|
"config_value": "10000",
|
||||||
|
"config_type": ConfigType.NUMBER,
|
||||||
|
"category": ConfigCategory.GAME_ECONOMY,
|
||||||
|
"description": "余额清零自动发放金额(分)",
|
||||||
|
"is_editable": True,
|
||||||
|
"is_public": False,
|
||||||
|
"display_order": 8,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"config_key": "ALLOWANCE_RESET_TIME",
|
||||||
|
"config_value": "00:00",
|
||||||
|
"config_type": ConfigType.STRING,
|
||||||
|
"category": ConfigCategory.GAME_ECONOMY,
|
||||||
|
"description": "低保每日刷新时间(HH:MM格式)",
|
||||||
|
"is_editable": True,
|
||||||
|
"is_public": False,
|
||||||
|
"display_order": 9,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
# 游戏逻辑配置
|
# 游戏逻辑配置
|
||||||
|
|||||||
@ -13,6 +13,8 @@ from ..core.config import settings
|
|||||||
game_settings = settings.game
|
game_settings = settings.game
|
||||||
from ..utils.redis import redis_client
|
from ..utils.redis import redis_client
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from ..services.system_service import SystemService
|
||||||
|
from ..models.system import ConfigType
|
||||||
|
|
||||||
|
|
||||||
class UserService:
|
class UserService:
|
||||||
@ -159,6 +161,9 @@ class UserService:
|
|||||||
"""
|
"""
|
||||||
调整用户余额(使用乐观锁)
|
调整用户余额(使用乐观锁)
|
||||||
"""
|
"""
|
||||||
|
# 记录变更前的余额
|
||||||
|
old_balance = user.balance
|
||||||
|
|
||||||
# 如果指定了版本号,进行版本检查
|
# 如果指定了版本号,进行版本检查
|
||||||
if expected_version is not None and user.version != expected_version:
|
if expected_version is not None and user.version != expected_version:
|
||||||
raise ValueError("用户数据已更新,请刷新后重试")
|
raise ValueError("用户数据已更新,请刷新后重试")
|
||||||
@ -200,6 +205,10 @@ class UserService:
|
|||||||
db.add(user)
|
db.add(user)
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
|
# 余额变更后,触发余额监控服务
|
||||||
|
from ..services.balance_monitor_service import BalanceMonitorService
|
||||||
|
BalanceMonitorService.on_balance_changed(db, user, old_balance, user.balance)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -262,16 +271,19 @@ class UserService:
|
|||||||
"""
|
"""
|
||||||
today = datetime.now().date()
|
today = datetime.now().date()
|
||||||
allowance_key = f"daily_allowance:{user.id}:{today.isoformat()}"
|
allowance_key = f"daily_allowance:{user.id}:{today.isoformat()}"
|
||||||
|
|
||||||
# 检查今日是否已领取
|
# 检查今日是否已领取
|
||||||
if redis_client.exists(allowance_key):
|
if redis_client.exists(allowance_key):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# 从数据库获取低保金额
|
||||||
|
daily_allowance = UserService.get_daily_allowance(db)
|
||||||
|
|
||||||
# 发放低保
|
# 发放低保
|
||||||
user.balance += game_settings.DAILY_ALLOWANCE
|
user.balance += daily_allowance
|
||||||
user.version += 1
|
user.version += 1
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
# 通知用户余额更新
|
# 通知用户余额更新
|
||||||
import asyncio
|
import asyncio
|
||||||
try:
|
try:
|
||||||
@ -297,7 +309,7 @@ class UserService:
|
|||||||
db=db,
|
db=db,
|
||||||
user_id=user.id,
|
user_id=user.id,
|
||||||
transaction_type="低保",
|
transaction_type="低保",
|
||||||
amount=game_settings.DAILY_ALLOWANCE,
|
amount=daily_allowance,
|
||||||
balance_after=user.balance,
|
balance_after=user.balance,
|
||||||
description="每日低保"
|
description="每日低保"
|
||||||
)
|
)
|
||||||
@ -324,7 +336,10 @@ class UserService:
|
|||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
today = datetime.now().date()
|
today = datetime.now().date()
|
||||||
allowance_key = f"daily_allowance:{user.id}:{today.isoformat()}"
|
allowance_key = f"daily_allowance:{user.id}:{today.isoformat()}"
|
||||||
|
|
||||||
|
# 从数据库获取低保金额
|
||||||
|
daily_allowance = UserService.get_daily_allowance(db)
|
||||||
|
|
||||||
# 检查今日是否已领取
|
# 检查今日是否已领取
|
||||||
if redis_client.exists(allowance_key):
|
if redis_client.exists(allowance_key):
|
||||||
# 如果今天已领取,下次领取时间为明天
|
# 如果今天已领取,下次领取时间为明天
|
||||||
@ -333,12 +348,47 @@ class UserService:
|
|||||||
return {
|
return {
|
||||||
"can_claim": False,
|
"can_claim": False,
|
||||||
"next_claim_time": next_claim_time.isoformat(),
|
"next_claim_time": next_claim_time.isoformat(),
|
||||||
"daily_allowance": game_settings.DAILY_ALLOWANCE
|
"daily_allowance": daily_allowance
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
# 如果今天未领取,可以立即领取
|
# 如果今天未领取,可以立即领取
|
||||||
return {
|
return {
|
||||||
"can_claim": True,
|
"can_claim": True,
|
||||||
"next_claim_time": datetime.now().isoformat(),
|
"next_claim_time": datetime.now().isoformat(),
|
||||||
"daily_allowance": game_settings.DAILY_ALLOWANCE
|
"daily_allowance": daily_allowance
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_daily_allowance(db: Session) -> int:
|
||||||
|
"""
|
||||||
|
从数据库获取每日低保金额
|
||||||
|
"""
|
||||||
|
config = SystemService.get_config(db, "GAME_DAILY_ALLOWANCE")
|
||||||
|
if config:
|
||||||
|
typed_value = SystemService.get_typed_value(config)
|
||||||
|
return int(typed_value)
|
||||||
|
# 如果数据库中没有配置,返回默认值
|
||||||
|
return game_settings.DAILY_ALLOWANCE
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_balance_zero_reward(db: Session) -> int:
|
||||||
|
"""
|
||||||
|
从数据库获取余额清零自动发放金额
|
||||||
|
"""
|
||||||
|
config = SystemService.get_config(db, "BALANCE_ZERO_REWARD_AMOUNT")
|
||||||
|
if config:
|
||||||
|
typed_value = SystemService.get_typed_value(config)
|
||||||
|
return int(typed_value)
|
||||||
|
# 如果数据库中没有配置,返回默认值10000分
|
||||||
|
return 10000
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_allowance_reset_time(db: Session) -> str:
|
||||||
|
"""
|
||||||
|
从数据库获取低保每日刷新时间
|
||||||
|
"""
|
||||||
|
config = SystemService.get_config(db, "ALLOWANCE_RESET_TIME")
|
||||||
|
if config:
|
||||||
|
return config.config_value
|
||||||
|
# 如果数据库中没有配置,返回默认值
|
||||||
|
return "00:00"
|
||||||
|
|||||||
@ -1658,6 +1658,15 @@ body {
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.allowance-amount {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 12px 0;
|
||||||
|
padding: 8px;
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.allowance-countdown {
|
.allowance-countdown {
|
||||||
margin: 16px 0;
|
margin: 16px 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -34,16 +34,22 @@ const TransactionHistoryPage = () => {
|
|||||||
return '下注';
|
return '下注';
|
||||||
case 'WIN':
|
case 'WIN':
|
||||||
return '获胜奖励';
|
return '获胜奖励';
|
||||||
case 'ALLOWANCE':
|
case '低保':
|
||||||
return '低保';
|
return '低保';
|
||||||
|
case '余额清零奖励':
|
||||||
|
return '余额清零奖励';
|
||||||
case 'ADMIN_ADJUST':
|
case 'ADMIN_ADJUST':
|
||||||
return '管理员调整';
|
return '管理员调整';
|
||||||
|
case '管理员调整':
|
||||||
|
return '管理员调整';
|
||||||
case 'STREAMER_REVENUE':
|
case 'STREAMER_REVENUE':
|
||||||
return '主播分润';
|
return '主播分润';
|
||||||
case 'PLATFORM_REVENUE':
|
case 'PLATFORM_REVENUE':
|
||||||
return '平台抽水';
|
return '平台抽水';
|
||||||
case 'REGISTER':
|
case 'REGISTER':
|
||||||
return '新用户注册奖励';
|
return '新用户注册奖励';
|
||||||
|
case '注册奖励':
|
||||||
|
return '新用户注册奖励';
|
||||||
default:
|
default:
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -92,16 +92,22 @@ const UserProfile = () => {
|
|||||||
return '下注';
|
return '下注';
|
||||||
case 'WIN':
|
case 'WIN':
|
||||||
return '获胜奖励';
|
return '获胜奖励';
|
||||||
case 'ALLOWANCE':
|
case '低保':
|
||||||
return '低保';
|
return '低保';
|
||||||
|
case '余额清零奖励':
|
||||||
|
return '余额清零奖励';
|
||||||
case 'ADMIN_ADJUST':
|
case 'ADMIN_ADJUST':
|
||||||
return '管理员调整';
|
return '管理员调整';
|
||||||
|
case '管理员调整':
|
||||||
|
return '管理员调整';
|
||||||
case 'STREAMER_REVENUE':
|
case 'STREAMER_REVENUE':
|
||||||
return '主播分润';
|
return '主播分润';
|
||||||
case 'PLATFORM_REVENUE':
|
case 'PLATFORM_REVENUE':
|
||||||
return '平台抽水';
|
return '平台抽水';
|
||||||
case 'REGISTER':
|
case 'REGISTER':
|
||||||
return '新用户注册奖励';
|
return '新用户注册奖励';
|
||||||
|
case '注册奖励':
|
||||||
|
return '新用户注册奖励';
|
||||||
default:
|
default:
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
@ -225,13 +231,16 @@ const UserProfile = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 低保信息卡片 */}
|
{/* 低保信息卡片 */}
|
||||||
{user && user.balance < 1000 && (
|
{allowanceInfo && (
|
||||||
<div className="allowance-card">
|
<div className="allowance-card">
|
||||||
<div className="allowance-header">
|
<div className="allowance-header">
|
||||||
<span className="allowance-icon">💰</span>
|
<span className="allowance-icon">💰</span>
|
||||||
<span className="allowance-title">每日低保</span>
|
<span className="allowance-title">每日低保</span>
|
||||||
</div>
|
</div>
|
||||||
{allowanceInfo?.can_claim ? (
|
<div className="allowance-amount">
|
||||||
|
低保金额: {allowanceInfo.daily_allowance ? (allowanceInfo.daily_allowance / 100).toFixed(2) : '50.00'} 元
|
||||||
|
</div>
|
||||||
|
{allowanceInfo.can_claim ? (
|
||||||
<button
|
<button
|
||||||
className="action-btn allowance-claim-btn"
|
className="action-btn allowance-claim-btn"
|
||||||
onClick={handleClaimAllowance}
|
onClick={handleClaimAllowance}
|
||||||
@ -242,11 +251,20 @@ const UserProfile = () => {
|
|||||||
) : (
|
) : (
|
||||||
<div className="allowance-countdown">
|
<div className="allowance-countdown">
|
||||||
<div className="countdown-label">下次可领取:</div>
|
<div className="countdown-label">下次可领取:</div>
|
||||||
<div className="countdown-time">{allowanceInfo?.formatted_time_left}</div>
|
<div className="countdown-time">
|
||||||
|
{allowanceInfo.next_claim_time
|
||||||
|
? new Date(allowanceInfo.next_claim_time).toLocaleString('zh-CN', {
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit'
|
||||||
|
})
|
||||||
|
: '明日00:00'}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="allowance-note">
|
<div className="allowance-note">
|
||||||
系统每天凌晨自动发放低保,无需手动领取
|
每天可领取一次,零点自动刷新
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user