Files
abot/db/stats_db.py
2025-03-19 13:14:46 +08:00

472 lines
18 KiB
Python
Raw 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 datetime import datetime, timedelta
from typing import Dict, List, Optional, Union, Tuple
from db.base import BaseDBOperator
from db.connection import DBConnectionManager
class StatsDBOperator(BaseDBOperator):
"""统计数据库操作类"""
def __init__(self, db_manager: DBConnectionManager):
super().__init__(db_manager)
def record_plugin_call(self, plugin_name: str, command: str, user_id: str,
group_id: Optional[str], success: bool,
process_time_ms: float) -> bool:
"""记录插件调用信息
Args:
plugin_name: 插件名称
command: 触发的命令
user_id: 用户ID
group_id: 群组ID私聊为None
success: 是否调用成功
process_time_ms: 处理时间(毫秒)
Returns:
是否成功记录
"""
# 1. 更新插件统计汇总表
self._update_plugin_stats(plugin_name, command, success, process_time_ms,
True if group_id else False)
# 2. 更新用户使用统计表
self._update_user_stats(user_id, plugin_name, command, success)
# 3. 如果是群聊,更新群组使用统计表
if group_id:
self._update_group_stats(group_id, plugin_name, command, user_id, success)
return True
def record_error(self, plugin_name: str, command: str, user_id: str,
group_id: Optional[str], error_message: str,
stack_trace: Optional[str] = None) -> bool:
"""记录错误信息
Args:
plugin_name: 插件名称
command: 触发的命令
user_id: 用户ID
group_id: 群组ID私聊为None
error_message: 错误信息
stack_trace: 堆栈跟踪
Returns:
是否成功记录
"""
sql = """
INSERT INTO t_error_logs
(plugin_name, command, user_id, group_id, error_message, stack_trace)
VALUES (%s, %s, %s, %s, %s, %s)
"""
params = (plugin_name, command, user_id, group_id, error_message, stack_trace)
return self.execute_update(sql, params)
def _update_plugin_stats(self, plugin_name: str, command: str, success: bool,
process_time_ms: float, is_group: bool) -> bool:
"""更新插件统计汇总表
Args:
plugin_name: 插件名称
command: 触发的命令
success: 是否调用成功
process_time_ms: 处理时间(毫秒)
is_group: 是否群聊
Returns:
是否成功更新
"""
today = datetime.now().strftime("%Y-%m-%d")
# 先查询当前记录
query_sql = """
SELECT id, total_calls, success_calls, failed_calls, group_calls,
private_calls, avg_process_time
FROM t_plugin_stats
WHERE plugin_name = %s AND command = %s AND stat_date = %s
"""
query_params = (plugin_name, command, today)
result = self.execute_query(query_sql, query_params, fetch_one=True)
if result:
# 更新现有记录
total_calls = result['total_calls'] + 1
success_calls = result['success_calls'] + (1 if success else 0)
failed_calls = result['failed_calls'] + (0 if success else 1)
group_calls = result['group_calls'] + (1 if is_group else 0)
private_calls = result['private_calls'] + (0 if is_group else 1)
# 计算新的平均处理时间
old_avg = result['avg_process_time']
new_avg = ((old_avg * (total_calls - 1)) + process_time_ms) / total_calls
update_sql = """
UPDATE t_plugin_stats
SET total_calls = %s,
success_calls = %s,
failed_calls = %s,
group_calls = %s,
private_calls = %s,
avg_process_time = %s
WHERE id = %s
"""
update_params = (total_calls, success_calls, failed_calls, group_calls,
private_calls, new_avg, result['id'])
return self.execute_update(update_sql, update_params)
else:
# 插入新记录
insert_sql = """
INSERT INTO t_plugin_stats
(plugin_name, command, stat_date, total_calls, success_calls,
failed_calls, group_calls, private_calls, avg_process_time)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)
"""
insert_params = (
plugin_name, command, today, 1,
1 if success else 0,
0 if success else 1,
1 if is_group else 0,
0 if is_group else 1,
process_time_ms
)
return self.execute_update(insert_sql, insert_params)
def _update_user_stats(self, user_id: str, plugin_name: str,
command: str, success: bool) -> bool:
"""更新用户使用统计表
Args:
user_id: 用户ID
plugin_name: 插件名称
command: 触发的命令
success: 是否调用成功
Returns:
是否成功更新
"""
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# 查询是否存在记录
query_sql = """
SELECT id, total_calls, success_calls, failed_calls, first_used_at
FROM t_user_stats
WHERE user_id = %s AND plugin_name = %s AND command = %s
"""
query_params = (user_id, plugin_name, command)
result = self.execute_query(query_sql, query_params, fetch_one=True)
if result:
# 更新现有记录
update_sql = """
UPDATE t_user_stats
SET total_calls = total_calls + 1,
success_calls = success_calls + %s,
failed_calls = failed_calls + %s,
last_used_at = %s
WHERE id = %s
"""
update_params = (1 if success else 0, 0 if success else 1, now, result['id'])
return self.execute_update(update_sql, update_params)
else:
# 插入新记录
insert_sql = """
INSERT INTO t_user_stats
(user_id, plugin_name, command, total_calls, success_calls,
failed_calls, first_used_at, last_used_at)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
"""
insert_params = (
user_id, plugin_name, command, 1,
1 if success else 0,
0 if success else 1,
now, now
)
return self.execute_update(insert_sql, insert_params)
def _update_group_stats(self, group_id: str, plugin_name: str,
command: str, user_id: str, success: bool) -> bool:
"""更新群组使用统计表
Args:
group_id: 群组ID
plugin_name: 插件名称
command: 触发的命令
user_id: 用户ID
success: 是否调用成功
Returns:
是否成功更新
"""
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# 查询是否存在记录
query_sql = """
SELECT id, total_calls, success_calls, failed_calls, unique_users, first_used_at
FROM t_group_stats
WHERE group_id = %s AND plugin_name = %s AND command = %s
"""
query_params = (group_id, plugin_name, command)
result = self.execute_query(query_sql, query_params, fetch_one=True)
# 查询该命令的唯一用户
user_query_sql = """
SELECT COUNT(DISTINCT user_id) as user_count
FROM t_user_stats
WHERE plugin_name = %s AND command = %s
"""
user_query_params = (plugin_name, command)
user_result = self.execute_query(user_query_sql, user_query_params, fetch_one=True)
unique_users = user_result['user_count'] if user_result else 1
if result:
# 更新现有记录
update_sql = """
UPDATE t_group_stats
SET total_calls = total_calls + 1,
success_calls = success_calls + %s,
failed_calls = failed_calls + %s,
unique_users = %s,
last_used_at = %s
WHERE id = %s
"""
update_params = (1 if success else 0, 0 if success else 1,
unique_users, now, result['id'])
return self.execute_update(update_sql, update_params)
else:
# 插入新记录
insert_sql = """
INSERT INTO t_group_stats
(group_id, plugin_name, command, total_calls, success_calls,
failed_calls, unique_users, first_used_at, last_used_at)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)
"""
insert_params = (
group_id, plugin_name, command, 1,
1 if success else 0,
0 if success else 1,
unique_users, now, now
)
return self.execute_update(insert_sql, insert_params)
def get_plugin_stats(self, days: int = 7) -> List[Dict]:
"""获取插件使用统计
Args:
days: 统计天数
Returns:
插件统计列表
"""
sql = """
SELECT plugin_name, command,
SUM(total_calls) as total_calls,
SUM(success_calls) as success_calls,
SUM(failed_calls) as failed_calls,
AVG(avg_process_time) as avg_process_time
FROM t_plugin_stats
WHERE stat_date >= DATE_SUB(CURDATE(), INTERVAL %s DAY)
GROUP BY plugin_name, command
ORDER BY total_calls DESC
"""
return self.execute_query(sql, (days,)) or []
def get_user_stats(self, days: int = 7, limit: int = 20) -> List[Dict]:
"""获取用户使用统计
Args:
days: 统计天数
limit: 返回记录数量限制
Returns:
用户统计列表
"""
sql = """
SELECT user_id,
SUM(total_calls) as total_calls,
COUNT(DISTINCT plugin_name) as used_plugins
FROM t_user_stats
WHERE last_used_at >= DATE_SUB(CURDATE(), INTERVAL %s DAY)
GROUP BY user_id
ORDER BY total_calls DESC
LIMIT %s
"""
return self.execute_query(sql, (days, limit)) or []
def get_group_stats(self, days: int = 7, limit: int = 20) -> List[Dict]:
"""获取群组使用统计
Args:
days: 统计天数
limit: 返回记录数量限制
Returns:
群组统计列表
"""
sql = """
SELECT group_id,
SUM(total_calls) as total_calls,
COUNT(DISTINCT plugin_name) as used_plugins,
MAX(unique_users) as max_unique_users
FROM t_group_stats
WHERE last_used_at >= DATE_SUB(CURDATE(), INTERVAL %s DAY)
GROUP BY group_id
ORDER BY total_calls DESC
LIMIT %s
"""
return self.execute_query(sql, (days, limit)) or []
def get_error_logs(self, days: int = 7, limit: int = 100) -> List[Dict]:
"""获取错误日志
Args:
days: 统计天数
limit: 返回记录数量限制
Returns:
错误日志列表
"""
sql = """
SELECT id, plugin_name, command, user_id, group_id,
error_message, stack_trace, created_at
FROM t_error_logs
WHERE created_at >= DATE_SUB(CURDATE(), INTERVAL %s DAY)
ORDER BY created_at DESC
LIMIT %s
"""
return self.execute_query(sql, (days, limit)) or []
def get_error_detail(self, error_id: int) -> Optional[Dict]:
"""获取错误详情
Args:
error_id: 错误ID
Returns:
错误详情
"""
sql = """
SELECT id, plugin_name, command, user_id, group_id,
error_message, stack_trace, created_at
FROM t_error_logs
WHERE id = %s
"""
return self.execute_query(sql, (error_id,), fetch_one=True)
def get_dashboard_summary(self, days: int = 7) -> Dict[str, Any]:
"""获取仪表盘摘要数据"""
try:
# 计算日期范围
end_date = datetime.now()
start_date = end_date - timedelta(days=days)
# 获取总调用次数
total_calls_query = """
SELECT COUNT(*) as total_calls FROM plugin_calls
WHERE created_at >= ? AND created_at <= ?
"""
total_calls_result = self.db_manager.execute_query(total_calls_query, (start_date, end_date))
total_calls = total_calls_result[0]['total_calls'] if total_calls_result else 0
# 获取成功率
success_rate_query = """
SELECT
ROUND((SUM(CASE WHEN success = 1 THEN 1 ELSE 0 END) * 100.0 / COUNT(*)), 2) as success_rate
FROM plugin_calls
WHERE created_at >= ? AND created_at <= ?
"""
success_rate_result = self.db_manager.execute_query(success_rate_query, (start_date, end_date))
success_rate = success_rate_result[0]['success_rate'] if success_rate_result and success_rate_result[0]['success_rate'] is not None else 0
# 获取活跃用户数
active_users_query = """
SELECT COUNT(DISTINCT user_id) as active_users FROM plugin_calls
WHERE created_at >= ? AND created_at <= ?
"""
active_users_result = self.db_manager.execute_query(active_users_query, (start_date, end_date))
active_users = active_users_result[0]['active_users'] if active_users_result else 0
# 获取活跃群组数
active_groups_query = """
SELECT COUNT(DISTINCT group_id) as active_groups FROM plugin_calls
WHERE created_at >= ? AND created_at <= ? AND group_id != ''
"""
active_groups_result = self.db_manager.execute_query(active_groups_query, (start_date, end_date))
active_groups = active_groups_result[0]['active_groups'] if active_groups_result else 0
return {
"total_calls": total_calls,
"success_rate": success_rate,
"active_users": active_users,
"active_groups": active_groups
}
except Exception as e:
logging.error(f"获取仪表盘摘要数据出错: {e}")
return {
"total_calls": 0,
"success_rate": 0,
"active_users": 0,
"active_groups": 0
}
def get_plugin_trend(self, plugin_name: str = '', days: int = 7) -> List[Dict[str, Any]]:
"""获取插件调用趋势数据"""
try:
# 计算日期范围
end_date = datetime.now()
start_date = end_date - timedelta(days=days)
# 构建查询条件
params = [start_date, end_date]
plugin_condition = ""
if plugin_name:
plugin_condition = "AND plugin_name = ?"
params.append(plugin_name)
# 按日期分组查询
query = f"""
SELECT
date(created_at) as date,
COUNT(*) as call_count,
SUM(CASE WHEN success = 1 THEN 1 ELSE 0 END) as success_count
FROM plugin_calls
WHERE created_at >= ? AND created_at <= ? {plugin_condition}
GROUP BY date(created_at)
ORDER BY date
"""
results = self.db_manager.execute_query(query, tuple(params))
# 确保每一天都有数据
date_format = "%Y-%m-%d"
trend_data = []
# 创建日期范围内的所有日期
current_date = start_date
while current_date <= end_date:
date_str = current_date.strftime(date_format)
# 查找当前日期的数据
day_data = next((item for item in results if item['date'] == date_str), None)
if day_data:
trend_data.append({
"date": date_str,
"call_count": day_data['call_count'],
"success_count": day_data['success_count'],
"success_rate": round(day_data['success_count'] * 100 / day_data['call_count'], 2) if day_data['call_count'] > 0 else 0
})
else:
trend_data.append({
"date": date_str,
"call_count": 0,
"success_count": 0,
"success_rate": 0
})
current_date += timedelta(days=1)
return trend_data
except Exception as e:
logging.error(f"获取插件趋势数据出错: {e}")
return []