需求:1.加入了用户积分表;2.加入了指令积分扣除功能;3.加入了积分获得与扣除注解。
This commit is contained in:
578
db/points_db.py
Normal file
578
db/points_db.py
Normal file
@@ -0,0 +1,578 @@
|
||||
# -*- 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
|
||||
Reference in New Issue
Block a user