From 17093cdc9877cda3eb240bcdf3e56aec8b4c9b66 Mon Sep 17 00:00:00 2001 From: taiyi Date: Tue, 16 Dec 2025 19:03:48 +0800 Subject: [PATCH] =?UTF-8?q?=E7=AC=AC=E4=B8=80=E6=AC=A1=E6=8F=90=E4=BA=A4?= =?UTF-8?q?=E5=93=A6=E5=95=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/services/scheduler_service.py | 79 ++++++++++++++++++- backend/app/services/system_service.py | 20 +++++ backend/app/services/user_service.py | 66 ++++++++++++++-- frontend/src/index.css | 9 +++ frontend/src/pages/TransactionHistoryPage.tsx | 8 +- frontend/src/pages/UserProfile.tsx | 28 +++++-- 6 files changed, 194 insertions(+), 16 deletions(-) diff --git a/backend/app/services/scheduler_service.py b/backend/app/services/scheduler_service.py index 2e7a291..9962a97 100644 --- a/backend/app/services/scheduler_service.py +++ b/backend/app/services/scheduler_service.py @@ -11,10 +11,11 @@ from ..models.game import Chest, ChestStatus from ..services.user_service import UserService from ..services.game_service import GameService from ..core.config import settings +from ..services.balance_monitor_service import BalanceMonitorService class SchedulerService: """定时任务服务""" - + @staticmethod async def start_scheduler(): """ @@ -24,6 +25,8 @@ class SchedulerService: asyncio.create_task(SchedulerService._run_daily_allowance_scheduler()) # 创建后台任务扫描过期宝箱 asyncio.create_task(SchedulerService._run_expired_chest_scanner()) + # 创建后台任务扫描余额清零用户 + asyncio.create_task(SchedulerService._run_zero_balance_scanner()) @staticmethod async def _run_daily_allowance_scheduler(): @@ -183,4 +186,76 @@ class SchedulerService: print(f"锁定过期宝箱时出错: {e}") return 0 finally: - db.close() \ No newline at end of file + 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 diff --git a/backend/app/services/system_service.py b/backend/app/services/system_service.py index ab8968d..8755bdb 100644 --- a/backend/app/services/system_service.py +++ b/backend/app/services/system_service.py @@ -302,6 +302,26 @@ class SystemService: "is_public": False, "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, + }, ] # 游戏逻辑配置 diff --git a/backend/app/services/user_service.py b/backend/app/services/user_service.py index 2582797..e242330 100644 --- a/backend/app/services/user_service.py +++ b/backend/app/services/user_service.py @@ -13,6 +13,8 @@ from ..core.config import settings game_settings = settings.game from ..utils.redis import redis_client from datetime import datetime +from ..services.system_service import SystemService +from ..models.system import ConfigType class UserService: @@ -159,6 +161,9 @@ class UserService: """ 调整用户余额(使用乐观锁) """ + # 记录变更前的余额 + old_balance = user.balance + # 如果指定了版本号,进行版本检查 if expected_version is not None and user.version != expected_version: raise ValueError("用户数据已更新,请刷新后重试") @@ -200,6 +205,10 @@ class UserService: db.add(user) db.commit() + # 余额变更后,触发余额监控服务 + from ..services.balance_monitor_service import BalanceMonitorService + BalanceMonitorService.on_balance_changed(db, user, old_balance, user.balance) + return True @staticmethod @@ -262,16 +271,19 @@ class UserService: """ today = datetime.now().date() allowance_key = f"daily_allowance:{user.id}:{today.isoformat()}" - + # 检查今日是否已领取 if redis_client.exists(allowance_key): return False - + + # 从数据库获取低保金额 + daily_allowance = UserService.get_daily_allowance(db) + # 发放低保 - user.balance += game_settings.DAILY_ALLOWANCE + user.balance += daily_allowance user.version += 1 db.commit() - + # 通知用户余额更新 import asyncio try: @@ -297,7 +309,7 @@ class UserService: db=db, user_id=user.id, transaction_type="低保", - amount=game_settings.DAILY_ALLOWANCE, + amount=daily_allowance, balance_after=user.balance, description="每日低保" ) @@ -324,7 +336,10 @@ class UserService: from datetime import datetime, timedelta today = datetime.now().date() allowance_key = f"daily_allowance:{user.id}:{today.isoformat()}" - + + # 从数据库获取低保金额 + daily_allowance = UserService.get_daily_allowance(db) + # 检查今日是否已领取 if redis_client.exists(allowance_key): # 如果今天已领取,下次领取时间为明天 @@ -333,12 +348,47 @@ class UserService: return { "can_claim": False, "next_claim_time": next_claim_time.isoformat(), - "daily_allowance": game_settings.DAILY_ALLOWANCE + "daily_allowance": daily_allowance } else: # 如果今天未领取,可以立即领取 return { "can_claim": True, "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" diff --git a/frontend/src/index.css b/frontend/src/index.css index bddd331..e5752e0 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -1658,6 +1658,15 @@ body { 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 { margin: 16px 0; } diff --git a/frontend/src/pages/TransactionHistoryPage.tsx b/frontend/src/pages/TransactionHistoryPage.tsx index 7b5ab4d..377c8b8 100644 --- a/frontend/src/pages/TransactionHistoryPage.tsx +++ b/frontend/src/pages/TransactionHistoryPage.tsx @@ -34,16 +34,22 @@ const TransactionHistoryPage = () => { return '下注'; case 'WIN': return '获胜奖励'; - case 'ALLOWANCE': + case '低保': return '低保'; + case '余额清零奖励': + return '余额清零奖励'; case 'ADMIN_ADJUST': return '管理员调整'; + case '管理员调整': + return '管理员调整'; case 'STREAMER_REVENUE': return '主播分润'; case 'PLATFORM_REVENUE': return '平台抽水'; case 'REGISTER': return '新用户注册奖励'; + case '注册奖励': + return '新用户注册奖励'; default: return type; } diff --git a/frontend/src/pages/UserProfile.tsx b/frontend/src/pages/UserProfile.tsx index 0ef4711..76f853b 100644 --- a/frontend/src/pages/UserProfile.tsx +++ b/frontend/src/pages/UserProfile.tsx @@ -92,16 +92,22 @@ const UserProfile = () => { return '下注'; case 'WIN': return '获胜奖励'; - case 'ALLOWANCE': + case '低保': return '低保'; + case '余额清零奖励': + return '余额清零奖励'; case 'ADMIN_ADJUST': return '管理员调整'; + case '管理员调整': + return '管理员调整'; case 'STREAMER_REVENUE': return '主播分润'; case 'PLATFORM_REVENUE': return '平台抽水'; case 'REGISTER': return '新用户注册奖励'; + case '注册奖励': + return '新用户注册奖励'; default: return type; } @@ -225,13 +231,16 @@ const UserProfile = () => { {/* 低保信息卡片 */} - {user && user.balance < 1000 && ( + {allowanceInfo && (
💰 每日低保
- {allowanceInfo?.can_claim ? ( +
+ 低保金额: {allowanceInfo.daily_allowance ? (allowanceInfo.daily_allowance / 100).toFixed(2) : '50.00'} 元 +
+ {allowanceInfo.can_claim ? (
)}