92 lines
3.1 KiB
Python
92 lines
3.1 KiB
Python
"""
|
||
FAM-Core 主应用 - Flask 单进程(新架构 v2)
|
||
|
||
承载: Oracle-Sync(每 30 分钟拉取增量镜像)+ Member-Manager + Chat-Handler
|
||
|
||
NAS 不再处理视频:无 Scheduler / Dispatcher / Poller / Event-Receiver / Video-Server。
|
||
所有视频分析在 Oracle 完成,NAS 仅作管理后台拉取展示,CPU 占用大幅降低。
|
||
"""
|
||
import os
|
||
import sys
|
||
|
||
from flask import Flask, jsonify
|
||
|
||
# 确保包路径
|
||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||
|
||
from .config_loader import load_config
|
||
from .logger import setup_logger
|
||
from .oracle_sync import get_sync
|
||
from .motion_notifier.motion_notifier import get_motion_notifier
|
||
from .chat_handler.chat_handler import chat_bp
|
||
from .member_manager.member_manager import member_bp
|
||
from .img_proxy import img_bp
|
||
from .motion_bp import motion_bp
|
||
from .ui_api import ui_bp
|
||
from .static_app import static_bp
|
||
|
||
logger = setup_logger('fam-core.app')
|
||
|
||
app = Flask(__name__)
|
||
|
||
# 注册蓝图:/api/* 系列必须先于 static_bp 注册——static_bp 是通配兜底路由
|
||
# (Vue Router history 模式回退 index.html),排在前面会吞掉 API 请求。
|
||
app.register_blueprint(chat_bp)
|
||
app.register_blueprint(member_bp)
|
||
app.register_blueprint(img_bp)
|
||
app.register_blueprint(motion_bp)
|
||
app.register_blueprint(ui_bp)
|
||
app.register_blueprint(static_bp)
|
||
|
||
# 健康检查
|
||
@app.route('/health', methods=['GET'])
|
||
def health():
|
||
return jsonify({"status": "ok", "service": "fam-core"}), 200
|
||
|
||
|
||
# 初始化后台同步线程(NAS 唯一常驻线程)
|
||
_sync = None
|
||
try:
|
||
_sync = get_sync()
|
||
_sync.start()
|
||
logger.info("Oracle-Sync 已启动")
|
||
# 启动后立刻拉一次,前端无需等待首个周期即有数据
|
||
try:
|
||
_sync.trigger_now()
|
||
logger.info("启动首次同步完成")
|
||
except Exception as e:
|
||
logger.warning(f"启动首次同步失败(后续周期会重试): {e}")
|
||
except Exception as e:
|
||
logger.error(f"Oracle-Sync 启动失败: {e}")
|
||
|
||
# 初始化运动监测通知服务(Webhook 驱动,不轮询;提供推送客户端 + 摄像头名映射)
|
||
_notifier = None
|
||
try:
|
||
_notifier = get_motion_notifier()
|
||
_notifier.start()
|
||
# 启动时一次性从 SS 拉取摄像头名->id 映射(非轮询),失败仅告警,Webhook 仍可运行
|
||
try:
|
||
_notifier.refresh_camera_map()
|
||
logger.info("MotionNotifier 摄像头映射已加载")
|
||
except Exception as e:
|
||
logger.warning(f"摄像头映射加载失败(Webhook 仍可运行,camera_id 可能为空): {e}")
|
||
logger.info("MotionNotifier 已初始化")
|
||
except Exception as e:
|
||
logger.error(f"MotionNotifier 初始化失败: {e}")
|
||
|
||
|
||
@app.route('/api/status', methods=['GET'])
|
||
def status():
|
||
"""系统状态"""
|
||
return jsonify({
|
||
"service": "fam-core",
|
||
"sync": _sync.status() if _sync else {"running": False, "error": "未初始化"},
|
||
"motion": _notifier.status() if _notifier else {"running": False, "error": "未初始化"},
|
||
}), 200
|
||
|
||
|
||
if __name__ == '__main__':
|
||
cfg = load_config()
|
||
port = cfg.get('server', {}).get('port', 8000)
|
||
app.run(host='0.0.0.0', port=port, debug=False)
|