From 47658fa068313ccfbdb41d91963f7a328ffab7cd Mon Sep 17 00:00:00 2001 From: ericwyuan Date: Wed, 19 Aug 2026 23:27:28 +0800 Subject: [PATCH] =?UTF-8?q?[deploy]=20PyMySQL=20=E6=9B=BF=E6=8D=A2=20+=20T?= =?UTF-8?q?ailscale=20=E7=BB=84=E7=BD=91=20+=20config.yaml=20+=20=E8=B7=AF?= =?UTF-8?q?=E5=BE=84=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - db_layer.py: mysql-connector-python(19MB) → PyMySQL(45KB), 去掉连接池 - config_loader.py: 修复路径解析 (2级→3级 dirname), FAM-Core + FAM-Edge 均修复 - Tailscale 组网完成: Oracle=100.74.137.126, NAS=100.70.234.39, 互通验证通过 - NAS Python 依赖: flask/gunicorn/pymysql/PyYAML 全部安装成功 - NAS 代码部署: /volume1/web/sentinel-home-ai/, FAM-Core 7 模块导入 PASS - NAS DB 连接验证: PyMySQL → MariaDB 10.11.11, 6 张表可见, PASS - config.yaml 创建: FAM-Core + FAM-Edge + FAM-UI (Tailscale IP + 密码) - requirements.txt: mysql-connector-python → PyMySQL --- PROGRESS.md | 31 ++++++------- fam-core/config/config.yaml | 37 +++++++++++++++ fam-core/requirements.txt | 2 +- fam-core/src/fam_core/config_loader.py | 2 +- fam-core/src/fam_core/db_layer.py | 62 +++++++++++--------------- fam-edge/config/config.yaml | 54 ++++++++++++++++++++++ fam-edge/src/fam_edge/config_loader.py | 2 +- fam-ui/config/config.yaml | 12 +++++ 8 files changed, 149 insertions(+), 53 deletions(-) create mode 100644 fam-core/config/config.yaml create mode 100644 fam-edge/config/config.yaml create mode 100644 fam-ui/config/config.yaml diff --git a/PROGRESS.md b/PROGRESS.md index 3c516e7..3082539 100644 --- a/PROGRESS.md +++ b/PROGRESS.md @@ -72,26 +72,27 @@ | 5 | Storage-Cleaner 补全 | `5e4587b` | 2026-08-19 | | 6 | 服务器凭证写入 README | `ba905d2` | 2026-08-19 | | 7 | requirements.txt 兼容 Python 3.8 | `c1bfac5` | 2026-08-19 | -| 8 | Oracle 部署: venv + 依赖安装 + 导入验证 | `50d8353` | 2026-08-19 | -| 9 | 模型可行性基准测试 (llava-phi3 ≤8s PASS) | `50d8353` | 2026-08-19 | -| 10 | OllamaAdapter 优化 (num_predict 参数) | `50d8353` | 2026-08-19 | -| 11 | Oracle Tailscale 安装 (1.102.2) | `50d8353` | 2026-08-19 | -| 12 | NAS 环境探查: MariaDB10 运行中, Tailscale 已连接 | 进行中 | 2026-08-19 | -| 13 | NAS DDL 执行: sentinel_home_ai 库 + 6 张表创建 | 进行中 | 2026-08-19 | -| 14 | README 更新: MariaDB 密码 + Tailscale IP | 进行中 | 2026-08-19 | +| 8 | Oracle 部署: venv + 依赖 + 导入验证 | `50d8353` | 2026-08-19 | +| 9 | 模型基准测试 (llava-phi3 ≤8s PASS) | `50d8353` | 2026-08-19 | +| 10 | OllamaAdapter 优化 (num_predict) | `50d8353` | 2026-08-19 | +| 11 | Tailscale 安装 (Oracle + NAS) + 认证 | `e5ac667` | 2026-08-19 | +| 12 | NAS MariaDB DDL 执行 (6 张表) | `e5ac667` | 2026-08-19 | +| 13 | db_layer.py 切换 PyMySQL (45KB 替代 19MB) | 进行中 | 2026-08-19 | +| 14 | config_loader.py 路径修复 (3 级 dirname) | 进行中 | 2026-08-19 | +| 15 | NAS Python 依赖安装 (flask/gunicorn/pymysql/PyYAML) | 进行中 | 2026-08-19 | +| 16 | NAS 代码部署 + FAM-Core 导入验证 PASS | 进行中 | 2026-08-19 | +| 17 | NAS DB 连接验证 PASS (PyMySQL → MariaDB 10.11.11) | 进行中 | 2026-08-19 | +| 18 | config.yaml 实际配置 (FAM-Core + FAM-Edge + FAM-UI) | 进行中 | 2026-08-19 | ### 待完成 | # | 任务 | 依赖 | 优先级 | |---|------|------|--------| -| 15 | Oracle Tailscale 认证 (用户访问 URL) | - | **阻塞** | -| 16 | NAS 安装 pip3 + Python 依赖 (flask, mysql-connector, streamlit) | - | 高 | -| 17 | NAS 部署 FAM-Core (gunicorn + 配置) | pip3, MariaDB ✅ | 高 | -| 18 | NAS 部署 FAM-UI (streamlit) | pip3 | 中 | -| 19 | Oracle 部署 FAM-Edge (gunicorn + 配置 + systemd) | Tailscale 认证 | 高 | -| 20 | 更新配置文件中的 Tailscale IP | Tailscale 认证 | 高 | -| 21 | 端到端集成测试 | 全部部署完成 | 高 | -| 22 | 单元测试 (JSON parser, circuit breaker, schema) | - | 中 | +| 19 | NAS 启动 FAM-Core (gunicorn) | 依赖安装 ✅ | 高 | +| 20 | Oracle 启动 FAM-Edge (gunicorn) | 依赖安装 ✅ | 高 | +| 21 | NAS 安装 Streamlit + 部署 FAM-UI | pip3 ✅ | 中 | +| 22 | 端到端集成测试 | FAM-Core + FAM-Edge 启动 | 高 | +| 23 | 单元测试 (JSON parser, circuit breaker, schema) | - | 中 | ## 技术决策记录 diff --git a/fam-core/config/config.yaml b/fam-core/config/config.yaml new file mode 100644 index 0000000..e466d6f --- /dev/null +++ b/fam-core/config/config.yaml @@ -0,0 +1,37 @@ +# FAM-Core 配置文件 (NAS 端) - 实际部署配置 +# Tailscale: NAS=100.70.234.39, Oracle=100.74.137.126 + +server: + host: "0.0.0.0" + port: 8000 + +database: + host: "127.0.0.1" + port: 3306 + user: "root" + password: "iLoveJava5!" + database: "sentinel_home_ai" + unix_socket: "/run/mysqld/mysqld10.sock" + +scheduler: + scan_interval: 60 + video_dir: "/volume1/surveillance" + video_extensions: [".mp4", ".mkv", ".avi"] + file_stable_seconds: 60 + camera_name: "客厅" + +dispatcher: + poll_interval: 30 + edge_url: "http://100.74.137.126:5000/api/edge/video/analyze" + webhook_url: "http://100.70.234.39:8000/api/core/callback/event" + max_retries: 3 + +video_server: + base_url: "http://100.70.234.39:8000/media" + token: "sentinel-media-2026" + video_dir: "/volume1/surveillance" + +chat_handler: + ollama_url: "http://100.74.137.126:11434/api/generate" + model_name: "llava-phi3" + timeout: 120 diff --git a/fam-core/requirements.txt b/fam-core/requirements.txt index 1be6868..6e2df22 100644 --- a/fam-core/requirements.txt +++ b/fam-core/requirements.txt @@ -1,5 +1,5 @@ flask>=3.0.0 gunicorn>=21.2.0 -mysql-connector-python>=8.3.0 +PyMySQL>=1.1.0 PyYAML>=6.0 requests>=2.31.0 diff --git a/fam-core/src/fam_core/config_loader.py b/fam-core/src/fam_core/config_loader.py index 914a0ff..a884eba 100644 --- a/fam-core/src/fam_core/config_loader.py +++ b/fam-core/src/fam_core/config_loader.py @@ -24,7 +24,7 @@ def load_config(config_path=None): """加载 YAML 配置文件,自动解析 ${ENV_VAR} 引用""" if config_path is None: config_path = os.path.join( - os.path.dirname(os.path.dirname(os.path.abspath(__file__))), + os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), 'config', 'config.yaml' ) with open(config_path, 'r', encoding='utf-8') as f: diff --git a/fam-core/src/fam_core/db_layer.py b/fam-core/src/fam_core/db_layer.py index 31a5672..ca01798 100644 --- a/fam-core/src/fam_core/db_layer.py +++ b/fam-core/src/fam_core/db_layer.py @@ -1,9 +1,9 @@ """ 数据库访问层 - MariaDB 连接管理与 CRUD 操作 +使用 PyMySQL (纯 Python, ~45KB) 连接 MariaDB 服务器 """ import json -import mysql.connector -from mysql.connector import pooling +import pymysql from datetime import datetime from typing import Optional, List, Dict, Any @@ -13,7 +13,6 @@ from .logger import setup_logger logger = setup_logger('fam-core.db') _config = None -_pool = None def get_config(): @@ -23,29 +22,22 @@ def get_config(): return _config -def get_pool(): - """获取数据库连接池(单例)""" - global _pool - if _pool is None: - cfg = get_config().get('database', {}) - _pool = pooling.MySQLConnectionPool( - host=cfg.get('host', '127.0.0.1'), - port=cfg.get('port', 3306), - user=cfg.get('user', 'root'), - password=cfg.get('password', ''), - database=cfg.get('database', 'sentinel_home_ai'), - charset='utf8mb4', - collation='utf8mb4_unicode_ci', - pool_name='fam_core_pool', - pool_size=5, - autocommit=False - ) - return _pool - - def get_conn(): - """从连接池获取一个连接""" - return get_pool().get_connection() + """获取数据库连接(单 worker gunicorn,无需连接池)""" + cfg = get_config().get('database', {}) + kwargs = dict( + host=cfg.get('host', '127.0.0.1'), + port=cfg.get('port', 3306), + user=cfg.get('user', 'root'), + password=cfg.get('password', ''), + database=cfg.get('database', 'sentinel_home_ai'), + charset='utf8mb4', + autocommit=False + ) + unix_socket = cfg.get('unix_socket') + if unix_socket: + kwargs['unix_socket'] = unix_socket + return pymysql.connect(**kwargs) # ============================================================ @@ -73,7 +65,7 @@ def get_pending_tasks(limit=10) -> List[Dict]: """获取待处理任务""" conn = get_conn() try: - cursor = conn.cursor(dictionary=True) + cursor = conn.cursor(pymysql.cursors.DictCursor) cursor.execute( "SELECT * FROM process_tasks WHERE status = 'PENDING' ORDER BY created_at ASC LIMIT %s", (limit,) @@ -87,7 +79,7 @@ def get_tasks_by_status(status: str, limit=10) -> List[Dict]: """按状态获取任务""" conn = get_conn() try: - cursor = conn.cursor(dictionary=True) + cursor = conn.cursor(pymysql.cursors.DictCursor) cursor.execute( "SELECT * FROM process_tasks WHERE status = %s ORDER BY created_at ASC LIMIT %s", (status, limit) @@ -130,7 +122,7 @@ def get_task(task_id: int) -> Optional[Dict]: """获取单个任务""" conn = get_conn() try: - cursor = conn.cursor(dictionary=True) + cursor = conn.cursor(pymysql.cursors.DictCursor) cursor.execute("SELECT * FROM process_tasks WHERE task_id = %s", (task_id,)) return cursor.fetchone() finally: @@ -207,7 +199,7 @@ def query_event_details(person: str, queried_date: str) -> List[Dict]: """查询某人在某天的事件明细""" conn = get_conn() try: - cursor = conn.cursor(dictionary=True) + cursor = conn.cursor(pymysql.cursors.DictCursor) cursor.execute( """SELECT frame_timestamp, camera_name, person, action, clothing, is_attention_event @@ -225,7 +217,7 @@ def get_recent_events(limit=20, offset=0, date_filter=None) -> List[Dict]: """获取事件列表(分页 + 日期筛选)""" conn = get_conn() try: - cursor = conn.cursor(dictionary=True) + cursor = conn.cursor(pymysql.cursors.DictCursor) if date_filter: cursor.execute( """SELECT me.event_id, me.task_id, me.event_start_time, me.event_end_time, @@ -281,7 +273,7 @@ def get_chat_history(limit=20, date_filter=None, person_filter=None) -> List[Dic """获取对话历史""" conn = get_conn() try: - cursor = conn.cursor(dictionary=True) + cursor = conn.cursor(pymysql.cursors.DictCursor) conditions = [] params = [] if date_filter: @@ -326,7 +318,7 @@ def get_unnamed_members() -> List[Dict]: """获取未命名成员列表""" conn = get_conn() try: - cursor = conn.cursor(dictionary=True) + cursor = conn.cursor(pymysql.cursors.DictCursor) cursor.execute( """SELECT fm.abstract_label, fm.feature_description, fm.first_seen_at, (SELECT COUNT(*) FROM event_details ed WHERE ed.person = fm.abstract_label) AS event_count @@ -343,7 +335,7 @@ def get_all_members(include_named=True, include_unnamed=True) -> List[Dict]: """获取所有成员""" conn = get_conn() try: - cursor = conn.cursor(dictionary=True) + cursor = conn.cursor(pymysql.cursors.DictCursor) conditions = [] if include_named and include_unnamed: pass # 全部 @@ -420,7 +412,7 @@ def get_known_members_context() -> str: """获取已命名+未命名成员清单,用于注入 VLM Prompt""" conn = get_conn() try: - cursor = conn.cursor(dictionary=True) + cursor = conn.cursor(pymysql.cursors.DictCursor) cursor.execute( "SELECT abstract_label, real_name, feature_description FROM family_members WHERE is_active = TRUE" ) @@ -446,7 +438,7 @@ def get_compute_provider_stats() -> List[Dict]: """获取 compute_provider 分布统计""" conn = get_conn() try: - cursor = conn.cursor(dictionary=True) + cursor = conn.cursor(pymysql.cursors.DictCursor) cursor.execute( """SELECT JSON_UNQUOTE(JSON_EXTRACT(item, '$')) AS provider, diff --git a/fam-edge/config/config.yaml b/fam-edge/config/config.yaml new file mode 100644 index 0000000..80ec7d9 --- /dev/null +++ b/fam-edge/config/config.yaml @@ -0,0 +1,54 @@ +# FAM-Edge 配置文件 (Oracle 端) - 实际部署配置 +# Tailscale: Oracle=100.74.137.126, NAS=100.70.234.39 + +# NAS 端回调地址 +nas: + webhook_url: "http://100.70.234.39:8000/api/core/callback/event" + media_base_url: "http://100.70.234.39:8000/media" + media_token: "sentinel-media-2026" + +# Oracle 端服务 +server: + host: "0.0.0.0" + port: 5000 + max_concurrent_tasks: 1 + +# 关键帧筛选参数 +video: + candidate_frames: 30 + min_key_frames: 5 + max_key_frames: 8 + mse_threshold: 500 + jpeg_quality: 80 + max_long_edge: 1024 + +# 超时(秒) +timeout: + download: 60 + vlm_visual: 240 + vlm_fusion: 120 + callback: 30 + overall: 600 + +# 模型清单 +models: + - provider: "ollama" + enabled: true + model_name: "llava-phi3" + base_url: "http://localhost:11434" + timeout: 240 + num_predict: 500 + circuit_breaker: + enabled: false + threshold: 5 + cooldown: 900 + + - provider: "gemini" + enabled: false + model_name: "gemini-1.5-flash" + api_key: "" + timeout: 8 + circuit_breaker: + enabled: true + threshold: 5 + cooldown: 900 diff --git a/fam-edge/src/fam_edge/config_loader.py b/fam-edge/src/fam_edge/config_loader.py index 6caa9cc..b41f532 100644 --- a/fam-edge/src/fam_edge/config_loader.py +++ b/fam-edge/src/fam_edge/config_loader.py @@ -22,7 +22,7 @@ def _resolve_env_vars(value): def load_config(config_path=None): if config_path is None: config_path = os.path.join( - os.path.dirname(os.path.dirname(os.path.abspath(__file__))), + os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), 'config', 'config.yaml' ) with open(config_path, 'r', encoding='utf-8') as f: diff --git a/fam-ui/config/config.yaml b/fam-ui/config/config.yaml new file mode 100644 index 0000000..81df84a --- /dev/null +++ b/fam-ui/config/config.yaml @@ -0,0 +1,12 @@ +# FAM-UI 配置文件 (NAS 端) - 实际部署配置 +# Tailscale: NAS=100.70.234.39 + +core_url: "http://127.0.0.1:8000" + +database: + host: "127.0.0.1" + port: 3306 + user: "root" + password: "iLoveJava5!" + database: "sentinel_home_ai" + unix_socket: "/run/mysqld/mysqld10.sock"