""" 系统配置服务 - 增强版 """ from sqlalchemy.orm import Session from sqlalchemy import and_, func from typing import Dict, Any, List, Optional from datetime import datetime from ..models.system import SystemConfig, ConfigType, ConfigCategory from ..schemas.system import ( SystemConfigCreate, SystemConfigUpdate, SystemConfigResponse, SystemConfigGroup, SystemConfigSummary ) class SystemService: """系统配置服务""" # 配置分类显示名称映射 CATEGORY_NAMES = { ConfigCategory.GAME_ECONOMY: "游戏经济配置", ConfigCategory.GAME_LOGIC: "游戏逻辑配置", ConfigCategory.SYSTEM_OPERATIONS: "系统运维配置", ConfigCategory.UI_DISPLAY: "界面显示配置", } # 配置分类描述映射 CATEGORY_DESCRIPTIONS = { ConfigCategory.GAME_ECONOMY: "游戏经济相关配置,如奖励、低保、抽水比例等", ConfigCategory.GAME_LOGIC: "游戏逻辑相关配置,如倒计时、活跃宝箱数等", ConfigCategory.SYSTEM_OPERATIONS: "系统运维相关配置,如分页、WebSocket等", ConfigCategory.UI_DISPLAY: "界面显示相关配置", } @staticmethod def get_config(db: Session, key: str) -> Optional[SystemConfig]: """ 根据键获取配置 """ return db.query(SystemConfig).filter(SystemConfig.config_key == key).first() @staticmethod def get_all_configs(db: Session) -> List[SystemConfig]: """ 获取所有配置 """ return db.query(SystemConfig).order_by( SystemConfig.category, SystemConfig.display_order, SystemConfig.config_key ).all() @staticmethod def get_configs_by_category(db: Session, category: ConfigCategory) -> List[SystemConfig]: """ 根据分类获取配置 """ return db.query(SystemConfig).filter( SystemConfig.category == category ).order_by( SystemConfig.display_order, SystemConfig.config_key ).all() @staticmethod def get_config_groups(db: Session) -> List[SystemConfigGroup]: """ 获取配置分组 """ configs = SystemService.get_all_configs(db) groups = {} for config in configs: category = config.category if category not in groups: groups[category] = [] groups[category].append(config) result = [] for category, configs in groups.items(): result.append( SystemConfigGroup( category=category, category_name=SystemService.CATEGORY_NAMES.get(category, str(category)), category_description=SystemService.CATEGORY_DESCRIPTIONS.get(category, ""), configs=[SystemConfigResponse.model_validate(c) for c in configs] ) ) return result @staticmethod def create_config(db: Session, config_data: SystemConfigCreate) -> SystemConfig: """ 创建配置 """ # 检查键是否已存在 existing_config = db.query(SystemConfig).filter( SystemConfig.config_key == config_data.config_key ).first() if existing_config: raise ValueError("配置键已存在") db_config = SystemConfig( config_key=config_data.config_key, config_value=config_data.config_value, config_type=config_data.config_type, category=config_data.category, description=config_data.description, is_editable=config_data.is_editable, is_public=config_data.is_public, display_order=config_data.display_order, ) db.add(db_config) db.commit() db.refresh(db_config) # 清空配置缓存,确保新配置立即生效 ConfigManager.clear_cache() return db_config @staticmethod def update_config(db: Session, key: str, config_data: SystemConfigUpdate) -> Optional[SystemConfig]: """ 更新配置 """ db_config = SystemService.get_config(db, key) if not db_config: return None if not db_config.is_editable: raise ValueError("该配置项不可编辑") update_data = config_data.model_dump(exclude_unset=True) for field, value in update_data.items(): setattr(db_config, field, value) db.commit() db.refresh(db_config) # 清空配置缓存,让配置立即生效 ConfigManager.clear_cache() return db_config @staticmethod def delete_config(db: Session, key: str) -> bool: """ 删除配置 """ db_config = SystemService.get_config(db, key) if not db_config: return False if not db_config.is_editable: raise ValueError("该配置项不可删除") db.delete(db_config) db.commit() # 清空配置缓存,让配置立即生效 ConfigManager.clear_cache() return True @staticmethod def batch_update_configs(db: Session, configs: Dict[str, str]) -> List[SystemConfig]: """ 批量更新配置 """ updated_configs = [] for key, value in configs.items(): config = SystemService.update_config(db, key, SystemConfigUpdate(config_value=value)) if config: updated_configs.append(config) return updated_configs @staticmethod def get_config_summary(db: Session) -> SystemConfigSummary: """ 获取配置概览 """ total_configs = db.query(SystemConfig).count() editable_configs = db.query(SystemConfig).filter(SystemConfig.is_editable == True).count() public_configs = db.query(SystemConfig).filter(SystemConfig.is_public == True).count() # 统计各分类的配置数量 category_counts = {} for category in ConfigCategory: count = db.query(SystemConfig).filter(SystemConfig.category == category).count() category_counts[category.value] = count return SystemConfigSummary( total_configs=total_configs, editable_configs=editable_configs, public_configs=public_configs, category_counts=category_counts ) @staticmethod def get_public_configs(db: Session) -> Dict[str, str]: """ 获取公开配置(供前端使用) """ configs = db.query(SystemConfig).filter(SystemConfig.is_public == True).all() return {c.config_key: c.config_value for c in configs} @staticmethod def get_typed_value(config: SystemConfig) -> Any: """ 根据配置类型获取正确类型的值 """ if config.config_type == ConfigType.NUMBER: try: return float(config.config_value) if '.' in config.config_value else int(config.config_value) except ValueError: return config.config_value elif config.config_type == ConfigType.BOOLEAN: return config.config_value.lower() in ('true', '1', 'yes', 'on') elif config.config_type == ConfigType.JSON: import json try: return json.loads(config.config_value) except json.JSONDecodeError: return config.config_value else: return config.config_value @staticmethod def initialize_default_configs(db: Session): """ 初始化默认系统配置 """ from ..models.system import ConfigType, ConfigCategory # 检查是否已有配置 existing_count = db.query(SystemConfig).count() if existing_count > 0: print(f"系统已有 {existing_count} 个配置项,跳过初始化") return # 游戏经济配置 game_economy_configs = [ { "config_key": "GAME_NEW_USER_REWARD", "config_value": "100000", "config_type": ConfigType.NUMBER, "category": ConfigCategory.GAME_ECONOMY, "description": "新用户注册奖励(分)", "is_editable": True, "is_public": False, "display_order": 1, }, { "config_key": "GAME_DAILY_ALLOWANCE", "config_value": "5000", "config_type": ConfigType.NUMBER, "category": ConfigCategory.GAME_ECONOMY, "description": "每日低保(分)", "is_editable": True, "is_public": False, "display_order": 2, }, { "config_key": "GAME_ALLOWANCE_THRESHOLD", "config_value": "1000", "config_type": ConfigType.NUMBER, "category": ConfigCategory.GAME_ECONOMY, "description": "低保门槛(分)", "is_editable": True, "is_public": False, "display_order": 3, }, { "config_key": "GAME_HOUSE_EDGE", "config_value": "0.10", "config_type": ConfigType.NUMBER, "category": ConfigCategory.GAME_ECONOMY, "description": "总抽水比例", "is_editable": True, "is_public": False, "display_order": 4, }, { "config_key": "GAME_STREAMER_SHARE", "config_value": "0.05", "config_type": ConfigType.NUMBER, "category": ConfigCategory.GAME_ECONOMY, "description": "主播分润比例", "is_editable": True, "is_public": False, "display_order": 5, }, { "config_key": "GAME_PLATFORM_SHARE", "config_value": "0.05", "config_type": ConfigType.NUMBER, "category": ConfigCategory.GAME_ECONOMY, "description": "平台分润比例", "is_editable": True, "is_public": False, "display_order": 6, }, { "config_key": "GAME_DAILY_CHECKIN_REWARD", "config_value": "1000", "config_type": ConfigType.NUMBER, "category": ConfigCategory.GAME_ECONOMY, "description": "每日签到奖励(分)", "is_editable": True, "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, }, ] # 游戏逻辑配置 game_logic_configs = [ { "config_key": "GAME_DEFAULT_COUNTDOWN", "config_value": "300", "config_type": ConfigType.NUMBER, "category": ConfigCategory.GAME_LOGIC, "description": "默认倒计时(秒)", "is_editable": True, "is_public": False, "display_order": 1, }, { "config_key": "GAME_MIN_COUNTDOWN", "config_value": "10", "config_type": ConfigType.NUMBER, "category": ConfigCategory.GAME_LOGIC, "description": "最小倒计时(秒)", "is_editable": True, "is_public": False, "display_order": 2, }, { "config_key": "GAME_MAX_COUNTDOWN", "config_value": "3600", "config_type": ConfigType.NUMBER, "category": ConfigCategory.GAME_LOGIC, "description": "最大倒计时(秒)", "is_editable": True, "is_public": False, "display_order": 3, }, { "config_key": "GAME_DEFAULT_MAX_ACTIVE_CHESTS", "config_value": "10", "config_type": ConfigType.NUMBER, "category": ConfigCategory.GAME_LOGIC, "description": "默认最大活跃宝箱数", "is_editable": True, "is_public": False, "display_order": 4, }, { "config_key": "GAME_MIN_COMMISSION_RATE", "config_value": "0.0", "config_type": ConfigType.NUMBER, "category": ConfigCategory.GAME_LOGIC, "description": "最小主播抽成(%)", "is_editable": True, "is_public": False, "display_order": 5, }, { "config_key": "GAME_MAX_COMMISSION_RATE", "config_value": "100.0", "config_type": ConfigType.NUMBER, "category": ConfigCategory.GAME_LOGIC, "description": "最大主播抽成(%)", "is_editable": True, "is_public": False, "display_order": 6, }, { "config_key": "GAME_DEFAULT_COMMISSION_RATE", "config_value": "5.0", "config_type": ConfigType.NUMBER, "category": ConfigCategory.GAME_LOGIC, "description": "默认主播抽成(%)", "is_editable": True, "is_public": False, "display_order": 7, }, ] # 系统运维配置 system_operations_configs = [ { "config_key": "PAGINATION_DEFAULT_PAGE_SIZE", "config_value": "20", "config_type": ConfigType.NUMBER, "category": ConfigCategory.SYSTEM_OPERATIONS, "description": "默认分页大小", "is_editable": True, "is_public": False, "display_order": 1, }, { "config_key": "PAGINATION_ADMIN_PAGE_SIZE", "config_value": "20", "config_type": ConfigType.NUMBER, "category": ConfigCategory.SYSTEM_OPERATIONS, "description": "管理员页面分页大小", "is_editable": True, "is_public": False, "display_order": 2, }, { "config_key": "PAGINATION_ANNOUNCEMENT_PAGE_SIZE", "config_value": "10", "config_type": ConfigType.NUMBER, "category": ConfigCategory.SYSTEM_OPERATIONS, "description": "公告页面分页大小", "is_editable": True, "is_public": False, "display_order": 3, }, { "config_key": "WS_BROADCAST_INTERVAL", "config_value": "200", "config_type": ConfigType.NUMBER, "category": ConfigCategory.SYSTEM_OPERATIONS, "description": "WebSocket广播间隔(毫秒)", "is_editable": True, "is_public": False, "display_order": 4, }, { "config_key": "WS_HEARTBEAT_INTERVAL", "config_value": "30", "config_type": ConfigType.NUMBER, "category": ConfigCategory.SYSTEM_OPERATIONS, "description": "WebSocket心跳间隔(秒)", "is_editable": True, "is_public": False, "display_order": 5, }, { "config_key": "WS_CONNECTION_TIMEOUT", "config_value": "60", "config_type": ConfigType.NUMBER, "category": ConfigCategory.SYSTEM_OPERATIONS, "description": "WebSocket连接超时(秒)", "is_editable": True, "is_public": False, "display_order": 6, }, ] # 界面显示配置 ui_display_configs = [ { "config_key": "ANNOUNCEMENT_DEFAULT_PRIORITY", "config_value": "0", "config_type": ConfigType.NUMBER, "category": ConfigCategory.UI_DISPLAY, "description": "公告默认优先级", "is_editable": True, "is_public": False, "display_order": 1, }, { "config_key": "ANNOUNCEMENT_MAX_PRIORITY", "config_value": "100", "config_type": ConfigType.NUMBER, "category": ConfigCategory.UI_DISPLAY, "description": "公告最大优先级", "is_editable": True, "is_public": False, "display_order": 2, }, ] # 合并所有配置 all_configs = ( game_economy_configs + game_logic_configs + system_operations_configs + ui_display_configs ) # 创建配置项 created_count = 0 for config_data in all_configs: try: # 检查配置是否已存在 existing = db.query(SystemConfig).filter( SystemConfig.config_key == config_data["config_key"] ).first() if not existing: db_config = SystemConfig(**config_data) db.add(db_config) created_count += 1 except Exception as e: print(f"创建配置 {config_data['config_key']} 失败: {e}") db.commit() print(f"成功初始化 {created_count} 个默认配置项") class ConfigManager: """ 配置管理器 - 统一配置访问接口 提供从数据库动态读取配置的能力,支持缓存 """ # 配置缓存:key=配置键, value=(配置值, 缓存时间) _config_cache = {} _cache_expire_seconds = 300 # 5分钟缓存 @staticmethod def get_game_config(db: Session, config_key: str, default_value: Any = None) -> Any: """ 获取游戏配置 - 从数据库读取,支持缓存 Args: db: 数据库会话 config_key: 配置键 default_value: 默认值(当数据库无此配置时使用) Returns: 配置值(已转换为正确类型) """ # 检查缓存 now = datetime.now() if config_key in ConfigManager._config_cache: value, cached_time = ConfigManager._config_cache[config_key] if (now - cached_time).total_seconds() < ConfigManager._cache_expire_seconds: return value # 从数据库读取 config = SystemService.get_config(db, config_key) if config: value = SystemService.get_typed_value(config) else: # 如果数据库中没有,使用默认值 value = default_value if value is None: print(f"警告:配置项 {config_key} 不存在于数据库中,也未提供默认值") # 更新缓存 ConfigManager._config_cache[config_key] = (value, now) return value @staticmethod def clear_cache(): """ 清空配置缓存 在配置更新后调用 """ ConfigManager._config_cache.clear() print("配置缓存已清空") @staticmethod def refresh_config(db: Session, config_key: str): """ 刷新单个配置的缓存 在配置更新后调用 """ if config_key in ConfigManager._config_cache: del ConfigManager._config_cache[config_key] print(f"配置 {config_key} 缓存已刷新") @staticmethod def get_streamer_share(db: Session) -> float: """ 获取主播分润比例 """ return ConfigManager.get_game_config(db, "GAME_STREAMER_SHARE", 0.05) @staticmethod def get_platform_share(db: Session) -> float: """ 获取平台分润比例 """ return ConfigManager.get_game_config(db, "GAME_PLATFORM_SHARE", 0.05) @staticmethod def get_new_user_reward(db: Session) -> int: """ 获取新用户注册奖励 """ return ConfigManager.get_game_config(db, "GAME_NEW_USER_REWARD", 100000) @staticmethod def get_daily_allowance(db: Session) -> int: """ 获取每日低保金额 """ return ConfigManager.get_game_config(db, "GAME_DAILY_ALLOWANCE", 5000) @staticmethod def get_allowance_threshold(db: Session) -> int: """ 获取低保门槛 """ return ConfigManager.get_game_config(db, "GAME_ALLOWANCE_THRESHOLD", 1000) @staticmethod def get_house_edge(db: Session) -> float: """ 获取总抽水比例 """ return ConfigManager.get_game_config(db, "GAME_HOUSE_EDGE", 0.10) @staticmethod def get_default_countdown(db: Session) -> int: """ 获取默认倒计时 """ return ConfigManager.get_game_config(db, "GAME_DEFAULT_COUNTDOWN", 300) @staticmethod def get_min_countdown(db: Session) -> int: """ 获取最小倒计时 """ return ConfigManager.get_game_config(db, "GAME_MIN_COUNTDOWN", 10) @staticmethod def get_max_countdown(db: Session) -> int: """ 获取最大倒计时 """ return ConfigManager.get_game_config(db, "GAME_MAX_COUNTDOWN", 3600) @staticmethod def get_default_max_active_chests(db: Session) -> int: """ 获取默认最大活跃宝箱数 """ return ConfigManager.get_game_config(db, "GAME_DEFAULT_MAX_ACTIVE_CHESTS", 10) @staticmethod def get_min_commission_rate(db: Session) -> float: """ 获取最小主播抽成 """ return ConfigManager.get_game_config(db, "GAME_MIN_COMMISSION_RATE", 0.0) @staticmethod def get_max_commission_rate(db: Session) -> float: """ 获取最大主播抽成 """ return ConfigManager.get_game_config(db, "GAME_MAX_COMMISSION_RATE", 100.0) @staticmethod def get_default_commission_rate(db: Session) -> float: """ 获取默认主播抽成 """ return ConfigManager.get_game_config(db, "GAME_DEFAULT_COMMISSION_RATE", 5.0) @staticmethod def get_daily_checkin_reward(db: Session) -> int: """ 获取每日签到奖励 """ return ConfigManager.get_game_config(db, "GAME_DAILY_CHECKIN_REWARD", 1000) @staticmethod def get_balance_zero_reward(db: Session) -> int: """ 获取余额清零自动发放金额 """ return ConfigManager.get_game_config(db, "BALANCE_ZERO_REWARD_AMOUNT", 10000) @staticmethod def get_allowance_reset_time(db: Session) -> str: """ 获取低保每日刷新时间 """ return ConfigManager.get_game_config(db, "ALLOWANCE_RESET_TIME", "00:00") @staticmethod def get_ws_broadcast_interval(db: Session) -> int: """ 获取WebSocket广播间隔(毫秒) """ return ConfigManager.get_game_config(db, "WS_BROADCAST_INTERVAL", 200) @staticmethod def get_ws_heartbeat_interval(db: Session) -> int: """ 获取WebSocket心跳间隔(秒) """ return ConfigManager.get_game_config(db, "WS_HEARTBEAT_INTERVAL", 30) @staticmethod def get_ws_connection_timeout(db: Session) -> int: """ 获取WebSocket连接超时(秒) """ return ConfigManager.get_game_config(db, "WS_CONNECTION_TIMEOUT", 60)