218 lines
6.9 KiB
Python
218 lines
6.9 KiB
Python
#!/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)}")
|
||
|
||
|
||
# 導入模型在需要時才進行,避免循環導入 |