File size: 3,843 Bytes
3a6c182
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
"""
MiroFish Backend - Flask应用工厂
"""

import os
import warnings

# 抑制 multiprocessing resource_tracker 的警告(来自第三方库如 transformers)
# 需要在所有其他导入之前设置
warnings.filterwarnings("ignore", message=".*resource_tracker.*")

from flask import Flask, request
from flask_cors import CORS

from .config import Config
from .services.supabase_jobs import sync_enabled as supabase_sync_enabled
from .utils.logger import setup_logger, get_logger


def create_app(config_class=Config):
    """Flask应用工厂函数"""
    app = Flask(__name__)
    app.config.from_object(config_class)
    
    # 设置JSON编码:确保中文直接显示(而不是 \uXXXX 格式)
    # Flask >= 2.3 使用 app.json.ensure_ascii,旧版本使用 JSON_AS_ASCII 配置
    if hasattr(app, 'json') and hasattr(app.json, 'ensure_ascii'):
        app.json.ensure_ascii = False
    
    # 设置日志
    logger = setup_logger('mirofish')
    
    # 只在 reloader 子进程中打印启动信息(避免 debug 模式下打印两次)
    is_reloader_process = os.environ.get('WERKZEUG_RUN_MAIN') == 'true'
    debug_mode = app.config.get('DEBUG', False)
    should_log_startup = not debug_mode or is_reloader_process
    
    if should_log_startup:
        logger.info("=" * 50)
        logger.info("MiroFish Backend 启动中...")
        logger.info("=" * 50)
    
    # CORS: allow custom pipeline auth headers from static sites / other origins
    CORS(
        app,
        resources={
            r"/api/*": {
                "origins": "*",
                "allow_headers": [
                    "Content-Type",
                    "Authorization",
                    "X-Mirofish-User-Id",
                    "X-Pipeline-Client-Secret",
                ],
            }
        },
    )
    
    # 注册模拟进程清理函数(确保服务器关闭时终止所有模拟进程)
    from .services.simulation_runner import SimulationRunner
    SimulationRunner.register_cleanup()
    if should_log_startup:
        logger.info("已注册模拟进程清理函数")
    
    # 请求日志中间件
    @app.before_request
    def log_request():
        logger = get_logger('mirofish.request')
        logger.debug(f"请求: {request.method} {request.path}")
        if request.content_type and 'json' in request.content_type:
            logger.debug(f"请求体: {request.get_json(silent=True)}")
    
    @app.after_request
    def log_response(response):
        logger = get_logger('mirofish.request')
        logger.debug(f"响应: {response.status_code}")
        return response
    
    # 注册蓝图
    from .api import graph_bp, simulation_bp, report_bp, pipeline_bp, billing_bp
    app.register_blueprint(graph_bp, url_prefix='/api/graph')
    app.register_blueprint(simulation_bp, url_prefix='/api/simulation')
    app.register_blueprint(report_bp, url_prefix='/api/report')
    app.register_blueprint(pipeline_bp, url_prefix='/api/pipeline')
    app.register_blueprint(billing_bp, url_prefix='/api/billing')
    
    # 健康检查
    @app.route('/health')
    def health():
        return {
            'status': 'ok',
            'service': 'MiroFish Backend',
            'supabase_sync': supabase_sync_enabled(),
            'pipeline_client_secret_configured': bool((Config.PIPELINE_CLIENT_SECRET or '').strip()),
        }
    
    if should_log_startup:
        if supabase_sync_enabled():
            logger.info("Supabase cloud sync: ENABLED (simulation_runs + reports)")
        else:
            logger.warning(
                "Supabase cloud sync: DISABLED — set SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY "
                "so runs appear in Past simulations and in the database."
            )
        logger.info("MiroFish Backend 启动完成")
    
    return app