""" API 路由层 — Flask 应用工厂 本模块实现 Flask 应用工厂模式,负责: - 创建和配置 Flask 应用实例 - 初始化核心服务(同步服务) - 注册所有 API Blueprint - 配置前端静态文件路由 核心路由模块: - chat_routes.py : 问答接口 (/chat, /rag, /rag/stream, /search) - kb_routes.py : 向量库管理 (/collections, /documents/sync) - document_routes.py: 文档管理 (/documents/upload, /documents/list) - sync_routes.py : 同步服务 (/sync, /sync/status) - image_routes.py : 图片服务 (/images/*) - exam_pkg/api.py : 出题系统 (/exam/generate, /exam/grade) 架构说明: - 会话管理、审计日志、反馈系统由后端服务负责 - 权限验证由后端网关完成(Header 注入) - RAG 服务无状态,不存储用户数据 Example: >>> from api import create_app >>> app = create_app() >>> app.run(host='0.0.0.0', port=5001) """ import sys import os import logging from typing import Optional # 确保项目根目录在路径中 PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) if PROJECT_ROOT not in sys.path: sys.path.insert(0, PROJECT_ROOT) logger = logging.getLogger(__name__) def create_app() -> 'Flask': """ Flask 应用工厂函数 创建并配置 Flask 应用实例,完成以下初始化步骤: 1. 创建 Flask 应用,配置 CORS 2. 初始化 Repository(会话存储) 3. 初始化核心服务(同步服务) 4. 注册所有 API Blueprint 5. 配置前端静态文件路由 6. 执行生产环境配置校验 Returns: Flask: 配置完成的 Flask 应用实例 Example: >>> app = create_app() >>> app.run(host='0.0.0.0', port=5001) """ from flask import Flask, send_from_directory from flask_cors import CORS from config import ENABLE_SESSION, ENABLE_FEEDBACK, IS_PROD # 静态文件目录(前端) static_folder = os.path.join(PROJECT_ROOT, 'chat-ui') app = Flask(__name__, static_folder=static_folder, static_url_path='') # CORS 配置:生产环境限制来源,开发环境允许全部 if IS_PROD: cors_origins = os.environ.get('CORS_ORIGINS', '').split(',') if os.environ.get('CORS_ORIGINS') else [] if cors_origins: CORS(app, origins=cors_origins) else: CORS(app) # 未配置时仍允许全部,但记录警告 logger.warning("生产环境未配置 CORS_ORIGINS,CORS 允许所有来源") else: CORS(app) # ==================== Repository 依赖注入 ==================== # 会话存储:开发环境用SQLite,生产环境无状态 if ENABLE_SESSION: from repositories.sqlite_session_repo import SQLiteSessionRepo app.session_repo = SQLiteSessionRepo() logger.info("会话存储: SQLite (开发环境)") else: from repositories.stateless_session_repo import StatelessSessionRepo app.session_repo = StatelessSessionRepo() logger.info("会话存储: 无状态 (生产环境)") # ==================== 核心服务初始化 ==================== # 同步服务 try: from knowledge.sync import KnowledgeSyncService from config import DOCUMENTS_PATH sync_service = KnowledgeSyncService(documents_path=DOCUMENTS_PATH) app.config['SYNC_SERVICE'] = sync_service logger.info("知识库同步服务已初始化") except Exception as e: app.config['SYNC_SERVICE'] = None logger.warning(f"知识库同步服务未启用: {e}") # 会话管理器(仅开发环境) if ENABLE_SESSION: try: from services.session import SessionManager session_manager = SessionManager() app.config['SESSION_MANAGER'] = session_manager logger.info("会话管理器已初始化") except Exception as e: app.config['SESSION_MANAGER'] = None logger.warning(f"会话管理器初始化失败: {e}") # ==================== 注册 Blueprint ==================== # 核心 API from api.chat_routes import chat_bp from api.kb_routes import kb_bp from api.document_routes import document_bp from api.sync_routes import sync_bp app.register_blueprint(chat_bp) app.register_blueprint(kb_bp) app.register_blueprint(document_bp) app.register_blueprint(sync_bp) # 图片服务 from api.image_routes import image_bp app.register_blueprint(image_bp) # 异步任务查询 from api.task_routes import task_bp app.register_blueprint(task_bp) # 健康检查 from api.auth_routes import auth_bp app.register_blueprint(auth_bp) # 会话管理(仅开发环境) if ENABLE_SESSION: try: from api.session_routes import session_bp app.register_blueprint(session_bp) logger.info("会话管理 API 已启用") except ImportError as e: logger.info(f"会话管理 API 未加载: {e}") # 审计日志(仅开发环境,依赖 session.db) if ENABLE_SESSION: try: from api.audit_routes import audit_bp app.register_blueprint(audit_bp) logger.info("审计日志 API 已启用") except ImportError as e: logger.info(f"审计日志 API 未加载: {e}") # 反馈系统(开发和生产环境都启用) if ENABLE_FEEDBACK: try: from api.feedback_routes import feedback_bp app.register_blueprint(feedback_bp) logger.info("反馈系统 API 已启用") except ImportError as e: logger.info(f"反馈系统 API 未加载: {e}") # 出题系统(可选) try: from exam_pkg.api import exam_bp app.register_blueprint(exam_bp, url_prefix='/exam') logger.info("出题系统 API 已启用: /exam") except ImportError as e: logger.info(f"出题系统 API 未加载: {e}") # ==================== 生产环境启动校验 ==================== if IS_PROD: _validate_production_config() # ==================== 前端静态文件路由 ==================== # 首页 @app.route('/') def serve_index(): """首页""" return send_from_directory(static_folder, 'index.html') # 静态文件(需要明确指定,避免与 API 路由冲突) @app.route('/app.js') def serve_app_js(): return send_from_directory(static_folder, 'app.js') @app.route('/style.css') def serve_style_css(): return send_from_directory(static_folder, 'style.css') @app.route('/exam.html') def serve_exam_html(): return send_from_directory(static_folder, 'exam.html') @app.route('/exam.js') def serve_exam_js(): return send_from_directory(static_folder, 'exam.js') @app.route('/api-test.html') def serve_api_test_html(): return send_from_directory(static_folder, 'api-test.html') @app.route('/api-test.js') def serve_api_test_js(): return send_from_directory(static_folder, 'api-test.js') # ==================== 启动信息 ==================== _print_startup_info(app) return app def _validate_production_config() -> None: """ 生产环境启动前配置校验 检查必要的配置项是否存在: - DASHSCOPE_API_KEY: 大模型调用必需 Raises: RuntimeError: 缺少必需的配置项 """ import os from config import DASHSCOPE_API_KEY # 检查环境变量或配置文件中的 API Key has_key = os.getenv("DASHSCOPE_API_KEY") or os.environ.get("DASHSCOPE_API_KEY") or DASHSCOPE_API_KEY if not has_key or has_key == "": raise RuntimeError("Missing DASHSCOPE_API_KEY in production environment") logger.info("Configuration validated") def _print_startup_info(app: 'Flask') -> None: """ 打印启动信息摘要 在应用启动时输出注册的路由数量和主要端点列表, 便于运维人员确认服务状态。 Args: app: Flask 应用实例 """ route_count = len([rule for rule in app.url_map.iter_rules() if rule.endpoint != 'static']) logger.info(f"应用初始化完成,共注册 {route_count} 个路由") logger.info(" 问答接口: /chat, /rag, /rag/stream, /search") logger.info(" 向量库: /collections, /collections/") logger.info(" 文档管理: /documents/upload, /documents/list, /documents/*") logger.info(" 切片管理: /chunks/*") logger.info(" 同步服务: /sync, /sync/status") logger.info(" 图片服务: /images/*") logger.info(" 任务查询: /tasks, /tasks/, /tasks//progress") logger.info(" 健康检查: /health")