# -*- coding: utf-8 -*- """ 积分系统数据库操作类 """ import logging from datetime import datetime from enum import Enum from typing import Dict, List, Optional, Tuple, Any from db.base import BaseDBOperator from db.connection import DBConnectionManager class PointSource(Enum): """积分来源枚举""" CHECKIN = "checkin" # 签到 GAME = "game" # 游戏 ADMIN = "admin" # 管理员操作 TRADE = "trade" # 积分交易 PLUGIN = "plugin" # 插件使用 OTHER = "other" # 其他 class PointsDBOperator(BaseDBOperator): """积分系统数据库操作类""" def __init__(self, db_manager=None): """初始化积分数据库操作类""" super().__init__(db_manager or DBConnectionManager.get_instance()) self.logger = logging.getLogger("PointsDBOperator") # 确保数据库表存在 self._ensure_tables_exist() def _ensure_tables_exist(self): """确保积分相关的数据库表存在""" try: # 创建用户积分表 self.execute_update(""" CREATE TABLE IF NOT EXISTS t_user_points ( id INTEGER PRIMARY KEY AUTO_INCREMENT, user_id VARCHAR(100) NOT NULL, group_id VARCHAR(100) NOT NULL, total_points INTEGER DEFAULT 0, checkin_points INTEGER DEFAULT 0, game_points INTEGER DEFAULT 0, other_points INTEGER DEFAULT 0, last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE(user_id, group_id) ) ENGINE=InnoDB CHARACTER SET utf8mb4; """) # 创建积分交易记录表 self.execute_update(""" CREATE TABLE IF NOT EXISTS t_point_transactions ( id INTEGER PRIMARY KEY AUTO_INCREMENT, user_id VARCHAR(100) NOT NULL, group_id VARCHAR(100) NOT NULL, transaction_type VARCHAR(20) NOT NULL, points INTEGER NOT NULL, source VARCHAR(50) NOT NULL, description TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB CHARACTER SET utf8mb4; """) # 创建功能插件积分配置表 self.execute_update(""" CREATE TABLE IF NOT EXISTS t_plugin_point_config ( id INTEGER PRIMARY KEY AUTO_INCREMENT, plugin_name VARCHAR(100) NOT NULL, points_required INTEGER DEFAULT 0, is_enabled BOOLEAN DEFAULT TRUE, description TEXT, UNIQUE(plugin_name) ) ENGINE=InnoDB CHARACTER SET utf8mb4; """) self.logger.info("积分系统数据库表检查/创建完成") except Exception as e: self.logger.error(f"创建积分系统数据库表失败: {e}") raise def get_user_points(self, user_id: str, group_id: str) -> Dict: """ 获取用户积分信息 Args: user_id: 用户ID group_id: 群组ID Returns: 包含用户积分信息的字典 """ try: # 先尝试从新表获取 result = self.execute_query(""" SELECT * FROM t_user_points WHERE user_id = %s AND group_id = %s """, (user_id, group_id), fetch_one=True) if result: return result # 如果新表没有数据,尝试从旧表迁移数据 return self._migrate_user_points(user_id, group_id) except Exception as e: self.logger.error(f"获取用户积分失败: {e}") return { "user_id": user_id, "group_id": group_id, "total_points": 0, "checkin_points": 0, "game_points": 0, "other_points": 0 } def _migrate_user_points(self, user_id: str, group_id: str) -> Dict: """ 从旧表迁移用户积分数据到新表 Args: user_id: 用户ID group_id: 群组ID Returns: 包含用户积分信息的字典 """ result = { "user_id": user_id, "group_id": group_id, "total_points": 0, "checkin_points": 0, "game_points": 0, "other_points": 0 } try: # 查询签到积分 sign_result = self.execute_query(""" SELECT points FROM t_sign_record WHERE wx_id = %s AND group_id = %s """, (user_id, group_id), fetch_one=True) # 查询游戏积分 game_result = self.execute_query(""" SELECT points FROM t_encyclopedia_players WHERE player_id = %s AND group_id = %s """, (user_id, group_id), fetch_one=True) # 合并积分 checkin_points = sign_result["points"] if sign_result else 0 game_points = game_result["points"] if game_result else 0 total_points = checkin_points + game_points # 更新结果 result["checkin_points"] = checkin_points result["game_points"] = game_points result["total_points"] = total_points # 插入到新表 self.execute_update(""" INSERT INTO t_user_points (user_id, group_id, total_points, checkin_points, game_points, other_points) VALUES (%s, %s, %s, %s, %s, %s) """, (user_id, group_id, total_points, checkin_points, game_points, 0)) # 获取插入后的完整记录 return self.execute_query(""" SELECT * FROM t_user_points WHERE user_id = %s AND group_id = %s """, (user_id, group_id), fetch_one=True) or result except Exception as e: self.logger.error(f"迁移用户积分失败: {e}") return result def add_points(self, user_id: str, group_id: str, points: int, source: PointSource, description: str = None, user_name: str = None) -> Tuple[bool, Dict]: """ 增加用户积分 Args: user_id: 用户ID group_id: 群组ID points: 积分数量 source: 积分来源 description: 描述 user_name: 用户名称 Returns: (成功标志, 用户积分信息) """ if points <= 0: return False, {"error": "积分必须为正数"} try: # 检查用户是否存在 user_exists = self.execute_query(""" SELECT * FROM t_user_points WHERE user_id = %s AND group_id = %s """, (user_id, group_id), fetch_one=True) if not user_exists: # 如果用户不存在,先迁移或创建用户 self._migrate_user_points(user_id, group_id) # 更新积分 source_field = f"{source.value}_points" if source.value in ["checkin", "game"] else "other_points" self.execute_update(f""" UPDATE t_user_points SET total_points = total_points + %s, {source_field} = {source_field} + %s WHERE user_id = %s AND group_id = %s """, (points, points, user_id, group_id)) # 记录交易 self.execute_update(""" INSERT INTO t_point_transactions (user_id, group_id, transaction_type, points, source, description) VALUES (%s, %s, %s, %s, %s, %s) """, (user_id, group_id, "earn", points, source.value, description)) # 同时更新旧表,保持兼容 if source == PointSource.CHECKIN: self.execute_update(""" UPDATE t_sign_record SET points = points + %s WHERE wx_id = %s AND group_id = %s """, (points, user_id, group_id)) elif source == PointSource.GAME: self.execute_update(""" UPDATE t_encyclopedia_players SET points = points + %s WHERE player_id = %s AND group_id = %s """, (points, user_id, group_id)) # 获取更新后的积分信息 updated_points = self.execute_query(""" SELECT * FROM t_user_points WHERE user_id = %s AND group_id = %s """, (user_id, group_id), fetch_one=True) return True, updated_points except Exception as e: self.logger.error(f"增加用户积分失败: {e}") return False, {"error": str(e)} def deduct_points(self, user_id: str, group_id: str, points: int, source: PointSource, description: str = None) -> Tuple[bool, Dict]: """ 扣除用户积分 Args: user_id: 用户ID group_id: 群组ID points: 积分数量 source: 积分来源 description: 描述 Returns: (成功标志, 用户积分信息) """ if points <= 0: return False, {"error": "积分必须为正数"} try: # 检查用户是否存在及积分是否足够 user_points = self.execute_query(""" SELECT * FROM t_user_points WHERE user_id = %s AND group_id = %s """, (user_id, group_id), fetch_one=True) if not user_points: # 如果用户不存在,先迁移或创建用户 user_points = self._migrate_user_points(user_id, group_id) if user_points["total_points"] < points: return False, {"error": "积分不足", "current_points": user_points["total_points"]} # 更新积分 self.execute_update(""" UPDATE t_user_points SET total_points = total_points - %s WHERE user_id = %s AND group_id = %s """, (points, user_id, group_id)) # 记录交易 self.execute_update(""" INSERT INTO t_point_transactions (user_id, group_id, transaction_type, points, source, description) VALUES (%s, %s, %s, %s, %s, %s) """, (user_id, group_id, "spend", -points, source.value, description)) # 获取更新后的积分信息 updated_points = self.execute_query(""" SELECT * FROM t_user_points WHERE user_id = %s AND group_id = %s """, (user_id, group_id), fetch_one=True) return True, updated_points except Exception as e: self.logger.error(f"扣除用户积分失败: {e}") return False, {"error": str(e)} def transfer_points(self, from_user_id: str, to_user_id: str, group_id: str, points: int, description: str = None) -> Tuple[bool, Dict]: """ 转移积分从一个用户到另一个用户 Args: from_user_id: 转出用户ID to_user_id: 转入用户ID group_id: 群组ID points: 积分数量 description: 描述 Returns: (成功标志, 结果信息) """ if points <= 0: return False, {"error": "积分必须为正数"} try: # 先扣除转出用户积分 success, result = self.deduct_points( from_user_id, group_id, points, PointSource.TRADE, f"转账给用户 {to_user_id}: {description}" ) if not success: return False, result # 再增加转入用户积分 success, to_result = self.add_points( to_user_id, group_id, points, PointSource.TRADE, f"收到用户 {from_user_id} 的转账: {description}" ) if not success: # 如果增加失败,回滚扣除操作 self.add_points( from_user_id, group_id, points, PointSource.TRADE, f"转账失败退回: {description}" ) return False, to_result return True, { "from_user": result, "to_user": to_result } except Exception as e: self.logger.error(f"转移用户积分失败: {e}") return False, {"error": str(e)} def get_user_transactions(self, user_id: str, group_id: str, limit: int = 10) -> List[Dict]: """ 获取用户积分交易记录 Args: user_id: 用户ID group_id: 群组ID limit: 记录数量限制 Returns: 交易记录列表 """ try: return self.execute_query(""" SELECT * FROM t_point_transactions WHERE user_id = %s AND group_id = %s ORDER BY created_at DESC LIMIT %s """, (user_id, group_id, limit)) except Exception as e: self.logger.error(f"获取用户交易记录失败: {e}") return [] def get_points_ranking(self, group_id: str, limit: int = 10) -> List[Dict]: """ 获取群组积分排行榜 Args: group_id: 群组ID limit: 记录数量限制 Returns: 排行榜列表 """ try: return self.execute_query(""" SELECT user_id, total_points, checkin_points, game_points, other_points FROM t_user_points WHERE group_id = %s ORDER BY total_points DESC LIMIT %s """, (group_id, limit)) except Exception as e: self.logger.error(f"获取积分排行榜失败: {e}") return [] def get_plugin_config(self, plugin_name: str) -> Optional[Dict]: """ 获取插件积分配置 Args: plugin_name: 插件名称 Returns: 插件积分配置 """ try: return self.execute_query(""" SELECT * FROM t_plugin_point_config WHERE plugin_name = %s """, (plugin_name,), fetch_one=True) except Exception as e: self.logger.error(f"获取插件积分配置失败: {e}") return None def set_plugin_config(self, plugin_name: str, points_required: int, is_enabled: bool = True, description: str = None) -> bool: """ 设置插件积分配置 Args: plugin_name: 插件名称 points_required: 所需积分 is_enabled: 是否启用 description: 描述 Returns: 是否成功 """ try: self.execute_update(""" INSERT INTO t_plugin_point_config (plugin_name, points_required, is_enabled, description) VALUES (%s, %s, %s, %s) ON DUPLICATE KEY UPDATE points_required = VALUES(points_required), is_enabled = VALUES(is_enabled), description = VALUES(description) """, (plugin_name, points_required, is_enabled, description)) return True except Exception as e: self.logger.error(f"设置插件积分配置失败: {e}") return False def get_all_plugin_configs(self) -> List[Dict]: """ 获取所有插件积分配置 Returns: 所有插件积分配置 """ try: return self.execute_query("SELECT * FROM t_plugin_point_config") except Exception as e: self.logger.error(f"获取所有插件积分配置失败: {e}") return [] def check_plugin_points(self, user_id: str, group_id: str, plugin_name: str) -> Tuple[bool, Dict]: """ 检查用户是否有足够积分使用插件 Args: user_id: 用户ID group_id: 群组ID plugin_name: 插件名称 Returns: (是否有足够积分, 结果信息) """ try: # 获取插件积分配置 plugin_config = self.get_plugin_config(plugin_name) # 如果插件未配置或未启用积分限制,直接返回成功 if not plugin_config or not plugin_config["is_enabled"]: return True, {"message": "插件未配置积分限制"} # 获取用户积分 user_points = self.get_user_points(user_id, group_id) # 检查积分是否足够 if user_points["total_points"] < plugin_config["points_required"]: return False, { "error": "积分不足", "current_points": user_points["total_points"], "required_points": plugin_config["points_required"] } return True, { "message": "积分充足", "current_points": user_points["total_points"], "required_points": plugin_config["points_required"] } except Exception as e: self.logger.error(f"检查插件积分失败: {e}") return True, {"error": str(e)} # 出错时默认允许使用 def use_plugin(self, user_id: str, group_id: str, plugin_name: str) -> Tuple[bool, Dict]: """ 使用插件并扣除积分 Args: user_id: 用户ID group_id: 群组ID plugin_name: 插件名称 Returns: (是否成功, 结果信息) """ try: # 先检查积分是否足够 can_use, result = self.check_plugin_points(user_id, group_id, plugin_name) if not can_use: return False, result # 如果插件未配置积分或不需要扣除积分,直接返回成功 if "required_points" not in result or result["required_points"] <= 0: return True, {"message": "无需扣除积分"} # 扣除积分 return self.deduct_points( user_id, group_id, result["required_points"], PointSource.PLUGIN, f"使用插件: {plugin_name}" ) except Exception as e: self.logger.error(f"使用插件扣除积分失败: {e}") return False, {"error": str(e)} def get_user_points_stats(self, group_id: str) -> Dict[str, Any]: """ 获取群组积分统计信息 Args: group_id: 群组ID Returns: 统计信息 """ stats = { "total_users": 0, "total_points": 0, "avg_points": 0, "max_points": 0, "min_points": 0, "checkin_points_total": 0, "game_points_total": 0, "other_points_total": 0 } try: result = self.execute_query(""" SELECT COUNT(*) as total_users, SUM(total_points) as total_points, AVG(total_points) as avg_points, MAX(total_points) as max_points, MIN(total_points) as min_points, SUM(checkin_points) as checkin_points_total, SUM(game_points) as game_points_total, SUM(other_points) as other_points_total FROM t_user_points WHERE group_id = %s """, (group_id,), fetch_one=True) if result: stats.update({k: v or 0 for k, v in result.items()}) return stats except Exception as e: self.logger.error(f"获取群组积分统计信息失败: {e}") return stats