baoxiang/backend/app/services/system_service.py
2025-12-16 21:39:32 +08:00

726 lines
24 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
系统配置服务 - 增强版
"""
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)