diff --git a/db/stats_db.py b/db/stats_db.py index 9d17703..b1e7a43 100644 --- a/db/stats_db.py +++ b/db/stats_db.py @@ -353,188 +353,120 @@ class StatsDBOperator(BaseDBOperator): """ return self.execute_query(sql, (error_id,), fetch_one=True) - def get_dashboard_summary(self, days: int = 7) -> Dict: - """获取仪表盘摘要数据 - - Args: - days: 统计天数 + def get_dashboard_summary(self, days: int = 7) -> Dict[str, Any]: + """获取仪表盘摘要数据""" + try: + # 计算日期范围 + end_date = datetime.now() + start_date = end_date - timedelta(days=days) - Returns: - 仪表盘摘要数据 - """ - # 获取时间范围 - end_date = datetime.now() - start_date = end_date - timedelta(days=days) - start_date_str = start_date.strftime("%Y-%m-%d") - - # 1. 总调用次数 - total_calls_sql = """ - SELECT SUM(total_calls) as total_calls - FROM t_plugin_stats - WHERE stat_date >= %s - """ - total_calls_result = self.execute_query(total_calls_sql, (start_date_str,), fetch_one=True) - total_calls = total_calls_result['total_calls'] if total_calls_result and total_calls_result['total_calls'] else 0 - - # 2. 成功率 - success_rate_sql = """ - SELECT SUM(success_calls) as success_calls, SUM(total_calls) as total_calls - FROM t_plugin_stats - WHERE stat_date >= %s - """ - success_rate_result = self.execute_query(success_rate_sql, (start_date_str,), fetch_one=True) - success_rate = 0 - if success_rate_result and success_rate_result['total_calls']: - success_rate = (success_rate_result['success_calls'] / success_rate_result['total_calls']) * 100 - - # 3. 活跃用户数 - active_users_sql = """ - SELECT COUNT(DISTINCT user_id) as active_users - FROM t_user_stats - WHERE last_used_at >= %s - """ - active_users_result = self.execute_query(active_users_sql, (start_date_str,), fetch_one=True) - active_users = active_users_result['active_users'] if active_users_result else 0 - - # 4. 活跃群组数 - active_groups_sql = """ - SELECT COUNT(DISTINCT group_id) as active_groups - FROM t_group_stats - WHERE last_used_at >= %s - """ - active_groups_result = self.execute_query(active_groups_sql, (start_date_str,), fetch_one=True) - active_groups = active_groups_result['active_groups'] if active_groups_result else 0 - - # 5. 错误数量 - error_count_sql = """ - SELECT COUNT(*) as error_count - FROM t_error_logs - WHERE created_at >= %s - """ - error_count_result = self.execute_query(error_count_sql, (start_date_str,), fetch_one=True) - error_count = error_count_result['error_count'] if error_count_result else 0 - - # 6. 平均响应时间 - avg_response_time_sql = """ - SELECT AVG(avg_process_time) as avg_response_time - FROM t_plugin_stats - WHERE stat_date >= %s - """ - avg_response_time_result = self.execute_query(avg_response_time_sql, (start_date_str,), fetch_one=True) - avg_response_time = avg_response_time_result['avg_response_time'] if avg_response_time_result and avg_response_time_result['avg_response_time'] else 0 - - # 7. 最常用的插件 - top_plugins_sql = """ - SELECT plugin_name, SUM(total_calls) as total_calls - FROM t_plugin_stats - WHERE stat_date >= %s - GROUP BY plugin_name - ORDER BY total_calls DESC - LIMIT 5 - """ - top_plugins = self.execute_query(top_plugins_sql, (start_date_str,)) or [] - - # 8. 最活跃的用户 - top_users_sql = """ - SELECT user_id, SUM(total_calls) as total_calls - FROM t_user_stats - WHERE last_used_at >= %s - GROUP BY user_id - ORDER BY total_calls DESC - LIMIT 5 - """ - top_users = self.execute_query(top_users_sql, (start_date_str,)) or [] - - # 9. 最活跃的群组 - top_groups_sql = """ - SELECT group_id, SUM(total_calls) as total_calls - FROM t_group_stats - WHERE last_used_at >= %s - GROUP BY group_id - ORDER BY total_calls DESC - LIMIT 5 - """ - top_groups = self.execute_query(top_groups_sql, (start_date_str,)) or [] - - # 返回汇总数据 - return { - "total_calls": total_calls, - "success_rate": success_rate, - "active_users": active_users, - "active_groups": active_groups, - "error_count": error_count, - "avg_response_time": avg_response_time, - "top_plugins": top_plugins, - "top_users": top_users, - "top_groups": top_groups - } + # 获取总调用次数 + 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]: - """获取插件使用趋势数据 - - Args: - plugin_name: 插件名称,为空则获取所有插件 - days: 统计天数 + 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) - Returns: - 插件使用趋势数据 - """ - # 获取时间范围内的每一天 - end_date = datetime.now().date() - start_date = end_date - timedelta(days=days-1) # 包含今天,所以减1 - - if plugin_name: - # 获取特定插件的趋势 - sql = """ - SELECT stat_date, SUM(total_calls) as total_calls, - SUM(success_calls) as success_calls, - SUM(failed_calls) as failed_calls - FROM t_plugin_stats - WHERE plugin_name = %s AND stat_date >= %s - GROUP BY stat_date - ORDER BY stat_date + # 构建查询条件 + 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 """ - params = (plugin_name, start_date) - else: - # 获取所有插件的趋势 - sql = """ - SELECT stat_date, SUM(total_calls) as total_calls, - SUM(success_calls) as success_calls, - SUM(failed_calls) as failed_calls - FROM t_plugin_stats - WHERE stat_date >= %s - GROUP BY stat_date - ORDER BY stat_date - """ - params = (start_date,) - - results = self.execute_query(sql, params) or [] - - # 将结果转换为按日期的字典 - trend_by_date = {r['stat_date'].strftime('%Y-%m-%d'): r for r in results} - - # 确保每一天都有数据 - trend_data = [] - current_date = start_date - while current_date <= end_date: - date_str = current_date.strftime('%Y-%m-%d') - if date_str in trend_by_date: - data = trend_by_date[date_str] - trend_data.append({ - 'date': date_str, - 'total_calls': data['total_calls'], - 'success_calls': data['success_calls'], - 'failed_calls': data['failed_calls'], - 'success_rate': (data['success_calls'] / data['total_calls'] * 100) if data['total_calls'] > 0 else 0 - }) - else: - trend_data.append({ - 'date': date_str, - 'total_calls': 0, - 'success_calls': 0, - 'failed_calls': 0, - 'success_rate': 0 - }) - current_date += timedelta(days=1) - - return trend_data \ No newline at end of file + + 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 [] \ No newline at end of file diff --git a/plugins/stats_dashboard/dashboard_server.py b/plugins/stats_dashboard/dashboard_server.py index 0248c50..0a82a30 100644 --- a/plugins/stats_dashboard/dashboard_server.py +++ b/plugins/stats_dashboard/dashboard_server.py @@ -112,6 +112,10 @@ class DashboardServer: trend = self.stats_db.get_plugin_trend(plugin_name, days) return jsonify({"success": True, "data": trend}) + @app.route('/errors') + def errors(): + return render_template('errors.html') + return app def run(self) -> None: diff --git a/plugins/stats_dashboard/templates/base.html b/plugins/stats_dashboard/templates/base.html index da9a0cf..43041b5 100644 --- a/plugins/stats_dashboard/templates/base.html +++ b/plugins/stats_dashboard/templates/base.html @@ -82,25 +82,24 @@ background-color="#545c64" text-color="#fff" active-text-color="#ffd04b" - @select="handleSelect" - router> - + @select="handleSelect"> + 首页概览 - - + + 插件统计 - + 用户统计 - + 群组统计 - + 错误日志 diff --git a/plugins/stats_dashboard/templates/index.html b/plugins/stats_dashboard/templates/index.html index dd11f5d..283d691 100644 --- a/plugins/stats_dashboard/templates/index.html +++ b/plugins/stats_dashboard/templates/index.html @@ -232,49 +232,102 @@ } // 准备数据 - const labels = trendData.map(item => item.stat_date); - const totalData = trendData.map(item => item.total_calls); - const successData = trendData.map(item => item.success_calls); - const errorData = trendData.map(item => item.error_calls); + const dates = trendData.map(item => item.date); + const callCounts = trendData.map(item => item.call_count); + const successRates = trendData.map(item => item.success_rate); - // 创建新图表 + // 创建图表 this.charts.trendChart = new Chart(ctx, { type: 'line', data: { - labels: labels, + labels: dates, datasets: [ { - label: '总调用', - data: totalData, - fill: false, - backgroundColor: 'rgba(54, 162, 235, 0.6)', + label: '调用次数', + data: callCounts, + backgroundColor: 'rgba(54, 162, 235, 0.2)', borderColor: 'rgba(54, 162, 235, 1)', - tension: 0.1 + borderWidth: 1, + yAxisID: 'y-axis-1' }, { - label: '成功调用', - data: successData, - fill: false, - backgroundColor: 'rgba(75, 192, 192, 0.6)', - borderColor: 'rgba(75, 192, 192, 1)', - tension: 0.1 - }, - { - label: '失败调用', - data: errorData, - fill: false, - backgroundColor: 'rgba(255, 99, 132, 0.6)', + label: '成功率(%)', + data: successRates, + backgroundColor: 'rgba(255, 99, 132, 0.2)', borderColor: 'rgba(255, 99, 132, 1)', - tension: 0.1 + borderWidth: 1, + yAxisID: 'y-axis-2' } ] }, options: { responsive: true, scales: { - y: { - beginAtZero: true - } + yAxes: [ + { + id: 'y-axis-1', + type: 'linear', + position: 'left', + ticks: { + beginAtZero: true + }, + scaleLabel: { + display: true, + labelString: '调用次数' + } + }, + { + id: 'y-axis-2', + type: 'linear', + position: 'right', + ticks: { + beginAtZero: true, + max: 100 + }, + scaleLabel: { + display: true, + labelString: '成功率(%)' + } + } + ] + } + } + }); + }, + renderSuccessRateChart() { + const ctx = document.getElementById('successRateChart').getContext('2d'); + + // 销毁旧图表 + if (this.charts.successRateChart) { + this.charts.successRateChart.destroy(); + } + + // 准备数据 + const plugins = this.pluginStats.map(item => item.plugin_name); + const successRates = this.pluginStats.map(item => item.success_rate); + + // 创建图表 + this.charts.successRateChart = new Chart(ctx, { + type: 'bar', + data: { + labels: plugins, + datasets: [{ + label: '成功率(%)', + data: successRates, + backgroundColor: 'rgba(75, 192, 192, 0.2)', + borderColor: 'rgba(75, 192, 192, 1)', + borderWidth: 1 + }] + }, + options: { + responsive: true, + scales: { + yAxes: [{ + ticks: { + beginAtZero: true, + max: 100 + } + }] } } });