Kamixitong/app/__init__.py
2025-12-12 11:35:14 +08:00

188 lines
7.1 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import os
# 尝试加载.env文件在配置之前加载确保环境变量生效
try:
from dotenv import load_dotenv
if load_dotenv():
print("成功加载.env文件")
else:
print("未找到或无法加载.env文件")
except ImportError:
print("python-dotenv未安装跳过.env文件加载")
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager
from flask_wtf.csrf import CSRFProtect
from flask_cors import CORS
from flask_migrate import Migrate
from config import config
import logging
from logging.handlers import RotatingFileHandler
# 初始化扩展
db = SQLAlchemy()
login_manager = LoginManager()
csrf = CSRFProtect()
cors = CORS()
migrate = Migrate()
def nl2br_filter(text):
"""将换行符转换为<br>标签的过滤器"""
if not text:
return ""
import re
# 转义HTML特殊字符然后将换行符转换为<br>标签
escaped_text = text.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;').replace('"', '&quot;').replace("'", '&#39;')
return escaped_text.replace('\n', '<br>').replace('\r', '')
def format_date_filter(date_string):
"""格式化日期时间的过滤器"""
if not date_string:
return "-"
try:
from datetime import datetime
import re
# 支持多种日期格式
if isinstance(date_string, str):
# 尝试解析常见的日期时间格式
formats = [
"%Y-%m-%d %H:%M:%S",
"%Y-%m-%d %H:%M:%S.%f",
"%Y-%m-%dT%H:%M:%S",
"%Y-%m-%dT%H:%M:%S.%f",
"%Y-%m-%d"
]
parsed_date = None
for fmt in formats:
try:
parsed_date = datetime.strptime(date_string, fmt)
break
except ValueError:
continue
if parsed_date is None:
# 如果所有格式都无法解析,尝试使用 dateutil 解析
try:
from dateutil import parser
parsed_date = parser.parse(date_string)
except:
return date_string # 返回原始字符串
else:
parsed_date = date_string
# 格式化为中文日期时间格式
return parsed_date.strftime("%Y年%m月%d%H:%M:%S")
except Exception as e:
# 出现任何错误都返回原始值
return str(date_string)
def create_app(config_name=None):
# 如果没有指定配置名称,从环境变量获取或使用默认值
if config_name is None:
config_name = os.environ.get('FLASK_ENV', 'default')
app = Flask(__name__,
template_folder=os.path.join(os.path.dirname(os.path.dirname(__file__)), 'app', 'web', 'templates'),
static_folder=os.path.join(os.path.dirname(os.path.dirname(__file__)), 'static'))
# 注册自定义过滤器
app.jinja_env.filters['nl2br'] = nl2br_filter
app.jinja_env.filters['format_date'] = format_date_filter
# 先应用配置对象,再初始化配置
app.config.from_object(config[config_name])
config[config_name].init_app(app)
# 特别确保MAX_CONTENT_LENGTH配置正确应用
max_content_length = app.config.get('MAX_CONTENT_LENGTH', 16 * 1024 * 1024)
app.config['MAX_CONTENT_LENGTH'] = max_content_length
print(f"Setting MAX_CONTENT_LENGTH to: {max_content_length} bytes ({max_content_length / (1024*1024)} MB)")
# 动态设置前端域名配置(支持多种环境变量)
frontend_domain = os.environ.get('FRONTEND_DOMAIN') or os.environ.get('DOMAIN_NAME') or os.environ.get('SERVER_NAME') or ''
# 确保域名不包含协议部分
if frontend_domain.startswith(('http://', 'https://')):
frontend_domain = frontend_domain.split('://', 1)[1]
app.config['FRONTEND_DOMAIN'] = frontend_domain
print(f"Frontend domain set to: {frontend_domain}")
# 初始化扩展
db.init_app(app)
login_manager.init_app(app)
migrate.init_app(app, db)
csrf.init_app(app)
# 配置CORS以支持域名访问 - 包括登录页面
cors.init_app(app, resources={
r"/api/*": {
"origins": ["https://km.taisan.online", "http://km.taisan.online", "http://localhost:5088", "http://127.0.0.1:5088"],
"methods": ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
"allow_headers": ["Content-Type", "Authorization"]
},
r"/login": {
"origins": ["https://km.taisan.online", "http://km.taisan.online", "http://localhost:5088", "http://127.0.0.1:5088"],
"methods": ["GET", "POST", "OPTIONS"],
"allow_headers": ["Content-Type", "X-Requested-With"],
"supports_credentials": True
}
})
# 配置登录管理器
login_manager.login_view = 'web.login' # type: ignore
login_manager.login_message = '请先登录'
login_manager.login_message_category = 'info'
login_manager.id_attribute = 'get_id' # 使用 get_id 方法获取用户ID
login_manager.session_protection = 'strong' # 启用强会话保护
# 注册蓝图
from app.api import api_bp
app.register_blueprint(api_bp, url_prefix=f'/api/{app.config["API_VERSION"]}')
# 对API蓝图豁免CSRF保护因为API有其他认证机制
csrf.exempt(api_bp)
from app.web import web_bp, user_bp
app.register_blueprint(web_bp)
app.register_blueprint(user_bp)
# 注册错误处理器
from app.web.views import register_error_handlers
register_error_handlers(app)
# 配置日志
if not app.debug and not app.testing:
# 确保日志目录存在
if not os.path.exists('logs'):
os.mkdir('logs')
# 配置文件日志处理器
file_handler = RotatingFileHandler('logs/kamaxitong.log', maxBytes=10240, backupCount=10)
file_handler.setFormatter(logging.Formatter(
'%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'
))
file_handler.setLevel(logging.INFO)
app.logger.addHandler(file_handler)
app.logger.setLevel(logging.INFO)
app.logger.info('KaMiXiTong startup')
# 初始化后台任务调度器
try:
from app.utils.scheduler import init_scheduler, start_scheduler
# 初始化调度器(在应用上下文中)
with app.app_context():
scheduler = init_scheduler(app)
# 仅在非测试环境中启动调度器
if not app.testing and not os.environ.get('DISABLE_SCHEDULER', '').lower() == 'true':
start_scheduler()
app.logger.info("后台定时任务调度器已启动")
else:
app.logger.info("后台定时任务调度器已初始化但未启动(测试环境或已禁用)")
except Exception as e:
# 调度器初始化失败不应该阻止应用启动
app.logger.error(f"初始化定时任务调度器失败: {str(e)}", exc_info=True)
return app