baoxiang/backend/app/services/system_service.py

726 lines
24 KiB
Python
Raw Normal View History

2025-12-16 18:06:50 +08:00
"""
系统配置服务 - 增强版
"""
from sqlalchemy.orm import Session
from sqlalchemy import and_, func
from typing import Dict, Any, List, Optional
2025-12-16 21:39:32 +08:00
from datetime import datetime
2025-12-16 18:06:50 +08:00
from ..models.system import SystemConfig, ConfigType, ConfigCategory
from ..schemas.system import (
2025-12-16 21:39:32 +08:00
SystemConfigCreate,
SystemConfigUpdate,
2025-12-16 18:06:50 +08:00
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)
2025-12-16 21:39:32 +08:00
# 清空配置缓存,确保新配置立即生效
ConfigManager.clear_cache()
2025-12-16 18:06:50 +08:00
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
2025-12-16 21:39:32 +08:00
2025-12-16 18:06:50 +08:00
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)
2025-12-16 21:39:32 +08:00
2025-12-16 18:06:50 +08:00
db.commit()
db.refresh(db_config)
2025-12-16 21:39:32 +08:00
# 清空配置缓存,让配置立即生效
ConfigManager.clear_cache()
2025-12-16 18:06:50 +08:00
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
2025-12-16 21:39:32 +08:00
2025-12-16 18:06:50 +08:00
if not db_config.is_editable:
raise ValueError("该配置项不可删除")
2025-12-16 21:39:32 +08:00
2025-12-16 18:06:50 +08:00
db.delete(db_config)
db.commit()
2025-12-16 21:39:32 +08:00
# 清空配置缓存,让配置立即生效
ConfigManager.clear_cache()
2025-12-16 18:06:50 +08:00
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)
2025-12-16 21:39:32 +08:00
2025-12-16 18:06:50 +08:00
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,
},
2025-12-16 19:03:48 +08:00
{
"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,
},
2025-12-16 18:06:50 +08:00
]
# 游戏逻辑配置
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} 个默认配置项")
2025-12-16 21:39:32 +08:00
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)