#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Flask 應用程式工廠 Author: PANJIT IT Team Created: 2024-01-28 Modified: 2024-01-28 """ import os import redis from flask import Flask, request, make_response from flask_sqlalchemy import SQLAlchemy from flask_cors import CORS from flask_jwt_extended import JWTManager from celery import Celery from app.config import config from app.utils.logger import init_logging # 初始化擴展 db = SQLAlchemy() cors = CORS() jwt = JWTManager() def make_celery(app): """創建 Celery 實例""" celery = Celery( app.import_name, backend=app.config['CELERY_RESULT_BACKEND'], broker=app.config['CELERY_BROKER_URL'] ) celery.conf.update(app.config) class ContextTask(celery.Task): """在 Flask 應用上下文中執行任務""" def __call__(self, *args, **kwargs): with app.app_context(): return self.run(*args, **kwargs) celery.Task = ContextTask return celery def create_app(config_name=None): """應用程式工廠""" app = Flask(__name__) # 載入配置 config_name = config_name or os.getenv('FLASK_ENV', 'default') # 先載入 Dify API 配置 config[config_name].load_dify_config() # 然後載入配置到 Flask app app.config.from_object(config[config_name]) # 初始化必要目錄 config[config_name].init_directories() # 初始化擴展 db.init_app(app) # 不使用 Flask-CORS 避免衝突,使用手動CORS處理 # 初始化 JWT jwt.init_app(app) app.logger.info(f"🔑 [JWT Config] JWT_SECRET_KEY: {app.config.get('JWT_SECRET_KEY')[:10]}...{app.config.get('JWT_SECRET_KEY')[-10:] if app.config.get('JWT_SECRET_KEY') else 'None'}") app.logger.info(f"🔑 [JWT Config] JWT_ACCESS_TOKEN_EXPIRES: {app.config.get('JWT_ACCESS_TOKEN_EXPIRES')}") app.logger.info(f"🔑 [JWT Config] JWT_REFRESH_TOKEN_EXPIRES: {app.config.get('JWT_REFRESH_TOKEN_EXPIRES')}") app.logger.info("🔑 [JWT] Using JWT authentication") # 設定 Redis(用於Celery) try: redis_client = redis.from_url(app.config['REDIS_URL']) app.redis_client = redis_client except Exception as e: app.logger.warning(f"Redis initialization failed: {str(e)}") app.redis_client = None # 初始化日誌 init_logging(app) # 註冊 API 路由 from app.api import api_v1 app.register_blueprint(api_v1) # 註冊錯誤處理器 register_error_handlers(app) # 添加 CORS 響應headers @app.after_request def after_request(response): origin = request.headers.get('Origin') allowed_origins = ['http://localhost:3000', 'http://127.0.0.1:3000', 'http://localhost:3001', 'http://127.0.0.1:3001', 'http://localhost:12010', 'http://127.0.0.1:12010'] if origin and origin in allowed_origins: response.headers['Access-Control-Allow-Origin'] = origin response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization, X-Requested-With' response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, OPTIONS, PATCH' response.headers['Access-Control-Allow-Credentials'] = 'true' response.headers['Access-Control-Max-Age'] = '86400' return response # 處理 OPTIONS 預檢請求 @app.before_request def before_request(): if request.method == 'OPTIONS': response = make_response() origin = request.headers.get('Origin') allowed_origins = ['http://localhost:3000', 'http://127.0.0.1:3000', 'http://localhost:3001', 'http://127.0.0.1:3001', 'http://localhost:12010', 'http://127.0.0.1:12010'] if origin and origin in allowed_origins: response.headers['Access-Control-Allow-Origin'] = origin response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization, X-Requested-With' response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, OPTIONS, PATCH' response.headers['Access-Control-Allow-Credentials'] = 'true' response.headers['Access-Control-Max-Age'] = '86400' return response # 建立資料表 with app.app_context(): # 導入模型 from app.models import User, TranslationJob, JobFile, TranslationCache, APIUsageStats, SystemLog, Notification db.create_all() # 創建默認管理員用戶(如果不存在) create_default_admin() # 創建 Celery 實例 app.celery = make_celery(app) # 初始化 WebSocket from app.websocket import init_websocket app.socketio = init_websocket(app) app.logger.info("Flask application created successfully") return app def register_error_handlers(app): """註冊錯誤處理器""" @app.errorhandler(404) def not_found(error): return { 'success': False, 'error': 'NOT_FOUND', 'message': '請求的資源不存在' }, 404 @app.errorhandler(403) def forbidden(error): return { 'success': False, 'error': 'FORBIDDEN', 'message': '權限不足' }, 403 @app.errorhandler(401) def unauthorized(error): return { 'success': False, 'error': 'UNAUTHORIZED', 'message': '需要認證' }, 401 @app.errorhandler(500) def internal_server_error(error): return { 'success': False, 'error': 'INTERNAL_SERVER_ERROR', 'message': '系統內部錯誤' }, 500 @app.errorhandler(413) def request_entity_too_large(error): return { 'success': False, 'error': 'FILE_TOO_LARGE', 'message': '檔案大小超過限制' }, 413 def create_default_admin(): """創建默認管理員用戶""" try: from app.models import User admin_email = os.environ.get('ADMIN_EMAIL', 'ymirliu@panjit.com.tw') # 檢查是否已存在管理員 admin_user = User.query.filter_by(email=admin_email).first() if not admin_user: # 創建管理員用戶(待 LDAP 登入時完善資訊) admin_user = User( username=admin_email.split('@')[0], display_name='系統管理員', email=admin_email, department='IT', is_admin=True ) db.session.add(admin_user) db.session.commit() print(f"Created default admin user: {admin_email}") except Exception as e: print(f"Failed to create default admin: {str(e)}") # 導入模型在需要時才進行,避免循環導入