# -*- coding: utf-8 -*- """ 统计看板服务器 - 使用Flask蓝图重构版 """ import os import sys import threading from loguru import logger from db.contacts_db import ContactsDBOperator from db.message_storage import MessageStorageDB from db.stats_db import StatsDBOperator from flask import Flask, send_from_directory import toml from wechat_ipad import WechatAPIClient # 添加项目根目录到系统路径,确保可以导入项目模块 sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))) class DashboardServer: """统计看板服务器""" def __init__(self, host: str = None, port: int = None, username: str = None, password: str = None, robot_instance=None): # 加载配置文件 self.config = self._load_dashboard_config() # 优先使用传入的参数,其次使用配置文件中的参数 self.host = host or self.config.get("server", {}).get("host", "0.0.0.0") self.port = port or self.config.get("server", {}).get("port", 8888) self.username = username or self.config.get("auth", {}).get("username", "admin") self.password = password or self.config.get("auth", {}).get("password", "admin123") self.LOG = logger self.LOG.info(f"Dashboard配置加载完成: 服务器将运行在 {self.host}:{self.port}") # 如果提供了robot实例,则使用其对象 if robot_instance: self.db_manager = robot_instance.db_manager self.stats_db = StatsDBOperator(self.db_manager) self.message_storage = MessageStorageDB(self.db_manager) self.contact_db: ContactsDBOperator = ContactsDBOperator(self.db_manager) # 获取联系人管理器实例 self.contact_manager = robot_instance.contact_manager self.plugin_manager = robot_instance.plugin_manager self.plugin_registry = robot_instance.plugin_registry self.client: WechatAPIClient = robot_instance.ipad_bot self.robot = robot_instance # # 设置用户信息 # self.user_data = { # # "wx_id": user_data.get("wxid", ""), # # "nickname": user_data.get("nickName", "未知用户"), # # "avatar": user_data.get("smallHeadImgUrl", "logo.png"), # 使用小头像URL # # "mobile": user_data.get("mobile", ""), # # "home": f"{user_data.get('province', '')}-{user_data.get('city', '')}" # 组合省市信息 # "wxid": robot_instance.wx_id, # "nickName": robot_instance.nickname, # "smallHeadImgUrl": robot_instance.head_image, # "mobile": robot_instance.phone # } self.LOG.info("使用Robot实例的对象进行初始化") else: self.LOG.error("未提供Robot实例,Dashboard无法正常工作") raise ValueError("必须提供Robot实例") self.app = self._create_app() self._stop_event = threading.Event() self._server = None # 存储服务器实例 def _load_dashboard_config(self): """加载Dashboard配置文件""" try: config_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'config.toml') if os.path.exists(config_path): with open(config_path, 'r', encoding='utf-8') as f: return toml.load(f) else: # 如果配置文件不存在,创建默认配置 default_config = { "server": {"host": "0.0.0.0", "port": 8888}, "auth": {"username": "admin", "password": "admin123"} } with open(config_path, 'w', encoding='utf-8') as f: toml.dump(default_config, f) return default_config except Exception as e: self.LOG.error(f"加载Dashboard配置文件失败: {e}") # 返回默认配置 return { "server": {"host": "0.0.0.0", "port": 8888}, "auth": {"username": "admin", "password": "admin123"} } def _create_app(self) -> Flask: """创建Flask应用""" # 指定模板文件夹路径 template_folder = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'templates') app = Flask(__name__, template_folder=template_folder) app.secret_key = "stats_dashboard_secret_key" # 禁用模板缓存,使修改HTML文件后立即生效 app.config['TEMPLATES_AUTO_RELOAD'] = True # 将dashboard_server实例设置为app的属性 app.dashboard_server = self # 配置静态文件访问 static_folder = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'static') @app.route('/static/') def serve_static(filename): return send_from_directory(static_folder, filename) # 获取项目根目录下的static/images目录 project_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) images_dir = os.path.join(project_root, "static", "images") # 确保目录存在 os.makedirs(images_dir, exist_ok=True) @app.route('/static/images/') def serve_images(filename): return send_from_directory(images_dir, filename) # 添加一个路由处理favicon请求 @app.route('/favicon.ico') def favicon(): return send_from_directory(os.path.join(app.root_path, 'static'), 'favicon.ico', mimetype='image/vnd.microsoft.icon') # 注册蓝图 self._register_blueprints(app) return app def _register_blueprints(self, app): """注册所有蓝图""" # 在函数内部导入蓝图,避免循环导入 from admin.dashboard.blueprints.auth import auth_bp from admin.dashboard.blueprints.contacts import contacts_bp from admin.dashboard.blueprints.robot import robot_bp from admin.dashboard.blueprints.messages import messages_bp from admin.dashboard.blueprints.stats import stats_bp from admin.dashboard.blueprints.system import system_bp from admin.dashboard.blueprints.main import main_bp from admin.dashboard.blueprints.plugin_routes import plugin_routes from admin.dashboard.blueprints.virtual_group import virtual_group_bp # 在app.register_blueprint部分添加 app.register_blueprint(virtual_group_bp, url_prefix='/virtual_group') app.register_blueprint(auth_bp) app.register_blueprint(main_bp) app.register_blueprint(contacts_bp) app.register_blueprint(robot_bp) app.register_blueprint(messages_bp) app.register_blueprint(stats_bp) app.register_blueprint(system_bp) app.register_blueprint(plugin_routes) self.LOG.info("所有蓝图已注册") def run(self): """运行服务器""" from werkzeug.serving import make_server self.LOG.info(f"启动服务器: {self.host}:{self.port}") try: # 使用线程安全的方式运行服务器 self._server = make_server(self.host, self.port, self.app) self._server.serve_forever() except Exception as e: self.LOG.error(f"服务器运行失败: {e}") self._stop_event.set() def stop(self): """停止服务器""" self.LOG.info("正在停止服务器...") self._stop_event.set() # 使用werkzeug服务器的关闭方法 if self._server: self._server.shutdown() self.LOG.info("服务器已停止") def get_current_user_info(self): """获取当前登录的微信用户信息""" try: if not self.client: self.LOG.error("client实例不可用,无法获取当前用户信息") return {"success": False, "message": "实例不可用"} # 获取当前登录的微信ID # 从新的resp格式中获取用户信息 user_data = {} if not user_data: return {"success": False, "message": "未获取到用户数据"} return { "success": True, "data": { "wx_id": user_data.get("wxid", ""), "nickname": user_data.get("nickName", "未知用户"), "avatar": user_data.get("smallHeadImgUrl", "logo.png"), # 使用小头像URL "mobile": user_data.get("mobile", ""), "home": f"{user_data.get('province', '')}-{user_data.get('city', '')}" # 组合省市信息 } } except Exception as e: self.LOG.error(f"获取当前用户信息失败: {e}") return {"success": False, "message": f"获取用户信息出错: {str(e)}"}