103 lines
3.4 KiB
Python
103 lines
3.4 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 .auth import auth_bp, init_auth
|
||
|
||
logger = setup_logger('fam-core.app')
|
||
|
||
app = Flask(__name__)
|
||
|
||
# 注册蓝图:全部 /api/* 路由(前端已迁云服务器 Caddy:80,NAS 不再托管 SPA 静态文件)
|
||
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(auth_bp)
|
||
|
||
# 登录校验全局拦截(页面未登录 302 /login;/api/* 未登录 401)
|
||
init_auth(app)
|
||
|
||
# 健康检查
|
||
@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}")
|
||
|
||
# 初始化运动监测通知服务(NAS 轮询 SS EventCenter 主路径)
|
||
_notifier = None
|
||
try:
|
||
_notifier = get_motion_notifier()
|
||
_notifier.start()
|
||
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
|
||
|
||
|
||
@app.route('/api/sync/trigger', methods=['POST'])
|
||
def sync_trigger():
|
||
"""手动立即触发一次甲骨文增量同步(服务状态页"立即同步"按钮)。
|
||
|
||
正常情况下后台线程每 30 分钟自动拉一次;这个接口给用户想立刻看到最新数据
|
||
时用,跟后台线程共用同一把拉取锁(oracle_sync._pull_lock),不会并发重复拉。
|
||
"""
|
||
if not _sync:
|
||
return jsonify({"error": "同步服务未初始化"}), 503
|
||
ok = _sync.trigger_now()
|
||
if not ok:
|
||
return jsonify({"status": "failed", "error": _sync.status().get("last_error")}), 502
|
||
return jsonify({"status": "ok", **_sync.status()}), 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)
|