第一次提交
This commit is contained in:
214
config.py
Normal file
214
config.py
Normal file
@@ -0,0 +1,214 @@
|
||||
import os
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
from logging.handlers import TimedRotatingFileHandler
|
||||
|
||||
class Config:
|
||||
"""基础配置类"""
|
||||
# SECRET_KEY必须从环境变量获取,生产环境不允许使用默认值
|
||||
SECRET_KEY = os.environ.get('SECRET_KEY')
|
||||
if not SECRET_KEY:
|
||||
import sys
|
||||
print("严重错误: SECRET_KEY未设置!生产环境必须设置SECRET_KEY环境变量!", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# 数据库配置 - 优先使用环境变量中的DATABASE_URL
|
||||
# 如果未设置环境变量,则使用MySQL默认配置
|
||||
DATABASE_URL = os.environ.get('DATABASE_URL')
|
||||
if not DATABASE_URL:
|
||||
# MySQL默认配置 - 必须从环境变量获取密码
|
||||
DB_HOST = os.environ.get('DB_HOST', 'localhost')
|
||||
DB_PORT = os.environ.get('DB_PORT', '3306')
|
||||
DB_USER = os.environ.get('DB_USER', 'root')
|
||||
DB_PASSWORD = os.environ.get('DB_PASSWORD')
|
||||
if not DB_PASSWORD:
|
||||
import sys
|
||||
print("严重错误: DB_PASSWORD未设置!必须设置数据库密码环境变量!", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
DB_NAME = os.environ.get('DB_NAME', 'kamixitong')
|
||||
DATABASE_URL = f'mysql+pymysql://{DB_USER}:{DB_PASSWORD}@{DB_HOST}:{DB_PORT}/{DB_NAME}?charset=utf8mb4'
|
||||
SQLALCHEMY_DATABASE_URI = DATABASE_URL
|
||||
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
||||
|
||||
# SQLAlchemy 2.0 兼容性设置和连接池优化
|
||||
SQLALCHEMY_ENGINE_OPTIONS = {
|
||||
"future": True,
|
||||
"pool_pre_ping": True, # 连接前检查连接是否有效
|
||||
"pool_size": int(os.environ.get('DB_POOL_SIZE', 10)), # 连接池大小
|
||||
"max_overflow": int(os.environ.get('DB_MAX_OVERFLOW', 20)), # 最大溢出连接数
|
||||
"pool_recycle": int(os.environ.get('DB_POOL_RECYCLE', 3600)), # 连接回收时间(秒)
|
||||
"pool_timeout": int(os.environ.get('DB_POOL_TIMEOUT', 30)), # 获取连接超时时间(秒)
|
||||
"echo": False, # 关闭SQL日志输出(生产环境)
|
||||
"connect_args": {
|
||||
"charset": "utf8mb4",
|
||||
"use_unicode": True,
|
||||
} if os.environ.get('DATABASE_URL', '').startswith('mysql') else {}
|
||||
}
|
||||
|
||||
# 系统基本配置
|
||||
SITE_NAME = os.environ.get('SITE_NAME') or '太一软件官网'
|
||||
ADMIN_EMAIL = os.environ.get('ADMIN_EMAIL') or ''
|
||||
# 前端域名配置 - 支持多种环境变量
|
||||
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]
|
||||
|
||||
# 验证器配置 - 生产环境必须设置
|
||||
AUTH_SECRET_KEY = os.environ.get('AUTH_SECRET_KEY')
|
||||
if not AUTH_SECRET_KEY:
|
||||
import sys
|
||||
print("严重错误: AUTH_SECRET_KEY未设置!生产环境必须设置AUTH_SECRET_KEY环境变量!", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
OFFLINE_CACHE_DAYS = int(os.environ.get('OFFLINE_CACHE_DAYS', 7)) # 离线缓存天数
|
||||
MAX_FAILED_ATTEMPTS = int(os.environ.get('MAX_FAILED_ATTEMPTS', 5)) # 最大失败次数
|
||||
LOCKOUT_MINUTES = int(os.environ.get('LOCKOUT_MINUTES', 10)) # 锁定时间(分钟)
|
||||
MAX_UNBIND_TIMES = int(os.environ.get('MAX_UNBIND_TIMES', 3)) # 最大解绑次数
|
||||
|
||||
# 卡密配置
|
||||
LICENSE_KEY_LENGTH = int(os.environ.get('LICENSE_KEY_LENGTH', 32)) # 卡密长度
|
||||
LICENSE_KEY_PREFIX = os.environ.get('LICENSE_KEY_PREFIX', '') # 卡密前缀
|
||||
TRIAL_PREFIX = os.environ.get('TRIAL_PREFIX') or 'TRIAL_' # 试用卡密前缀
|
||||
|
||||
# API配置
|
||||
API_VERSION = os.environ.get('API_VERSION') or 'v1'
|
||||
ITEMS_PER_PAGE = int(os.environ.get('ITEMS_PER_PAGE', 20))
|
||||
|
||||
# 文件上传配置 - 增加到500MB
|
||||
MAX_CONTENT_LENGTH = int(os.environ.get('MAX_CONTENT_LENGTH', 500 * 1024 * 1024)) # 500MB
|
||||
UPLOAD_FOLDER = os.environ.get('UPLOAD_FOLDER') or 'static/uploads'
|
||||
|
||||
# 会话配置 - 增强的会话管理
|
||||
PERMANENT_SESSION_LIFETIME = timedelta(hours=int(os.environ.get('SESSION_LIFETIME_HOURS', 168))) # 默认延长到7天
|
||||
|
||||
# 添加更多会话相关配置 - 支持域名访问
|
||||
SESSION_COOKIE_SECURE = os.environ.get('SESSION_COOKIE_SECURE', 'False').lower() == 'true' # 在生产环境中设为True
|
||||
SESSION_COOKIE_HTTPONLY = os.environ.get('SESSION_COOKIE_HTTPONLY', 'True').lower() == 'true'
|
||||
SESSION_COOKIE_SAMESITE = os.environ.get('SESSION_COOKIE_SAMESITE', 'Lax') # 改为Lax以提高兼容性
|
||||
|
||||
# 如果配置了域名,设置Cookie域
|
||||
if FRONTEND_DOMAIN:
|
||||
SESSION_COOKIE_DOMAIN = FRONTEND_DOMAIN
|
||||
|
||||
# 记住我功能配置
|
||||
REMEMBER_COOKIE_DURATION = timedelta(days=int(os.environ.get('REMEMBER_COOKIE_DURATION', 30))) # 记住我功能持续30天
|
||||
REMEMBER_COOKIE_SECURE = os.environ.get('REMEMBER_COOKIE_SECURE', 'False').lower() == 'true' # 生产环境中设为True
|
||||
REMEMBER_COOKIE_HTTPONLY = os.environ.get('REMEMBER_COOKIE_HTTPONLY', 'True').lower() == 'true'
|
||||
REMEMBER_COOKIE_SAMESITE = os.environ.get('REMEMBER_COOKIE_SAMESITE', 'Lax')
|
||||
|
||||
# 如果配置了域名,设置记住我Cookie域
|
||||
if FRONTEND_DOMAIN:
|
||||
REMEMBER_COOKIE_DOMAIN = FRONTEND_DOMAIN
|
||||
|
||||
# 日志配置
|
||||
LOG_LEVEL = os.environ.get('LOG_LEVEL', 'INFO')
|
||||
LOG_FILE = os.environ.get('LOG_FILE', 'logs/kamaxitong.log')
|
||||
|
||||
# 支付宝配置
|
||||
ALIPAY_APP_ID = os.environ.get('ALIPAY_APP_ID', '') # 支付宝应用ID
|
||||
ALIPAY_PRIVATE_KEY = os.environ.get('ALIPAY_PRIVATE_KEY', '') # 应用私钥
|
||||
ALIPAY_PUBLIC_KEY = os.environ.get('ALIPAY_PUBLIC_KEY', '') # 支付宝公钥
|
||||
ALIPAY_ALIPAY_PUBLIC_KEY = os.environ.get('ALIPAY_ALIPAY_PUBLIC_KEY', '') # 支付宝平台公钥
|
||||
ALIPAY_GATEWAY = os.environ.get('ALIPAY_GATEWAY', 'https://openapi.alipay.com/gateway.do') # 支付宝网关地址
|
||||
ALIPAY_SIGN_TYPE = 'RSA2' # 签名算法
|
||||
ALIPAY_CHARSET = 'utf-8' # 编码格式
|
||||
ALIPAY_VERSION = '1.0' # API版本号
|
||||
ALIPAY_NOTIFY_URL = os.environ.get('ALIPAY_NOTIFY_URL', '') # 异步通知URL
|
||||
ALIPAY_RETURN_URL = os.environ.get('ALIPAY_RETURN_URL', '') # 同步返回URL
|
||||
ALIPAY_TIMEOUT_EXPRESS = int(os.environ.get('ALIPAY_TIMEOUT_EXPRESS', 30)) # 支付超时时间(分钟)
|
||||
PAYMENT_ENABLED = os.environ.get('PAYMENT_ENABLED', 'False').lower() == 'true' # 是否启用支付功能
|
||||
|
||||
@staticmethod
|
||||
def init_app(app):
|
||||
"""初始化应用配置"""
|
||||
# 确保日志目录存在
|
||||
import os
|
||||
if not os.path.exists('logs'):
|
||||
os.mkdir('logs')
|
||||
|
||||
# 配置日志 - 使用安全的日志处理器,避免Windows文件锁定问题
|
||||
import logging
|
||||
from logging.handlers import TimedRotatingFileHandler
|
||||
import time
|
||||
|
||||
class SafeTimedRotatingFileHandler(TimedRotatingFileHandler):
|
||||
"""安全的日志轮转处理器,解决Windows文件锁定问题"""
|
||||
|
||||
def doRollover(self):
|
||||
"""重写轮转方法,添加重试机制和错误处理"""
|
||||
import time
|
||||
|
||||
# 尝试多次轮转
|
||||
max_retries = 3
|
||||
for attempt in range(max_retries):
|
||||
try:
|
||||
super().doRollover()
|
||||
break # 成功则退出循环
|
||||
except (OSError, PermissionError) as e:
|
||||
if attempt < max_retries - 1:
|
||||
# 等待一段时间后重试
|
||||
time.sleep(0.5 * (attempt + 1))
|
||||
continue
|
||||
else:
|
||||
# 最后一次尝试失败,静默处理
|
||||
import sys
|
||||
print(f"日志轮转失败(已重试{max_retries}次): {str(e)}", file=sys.stderr)
|
||||
except Exception as e:
|
||||
import sys
|
||||
print(f"日志轮转错误: {str(e)}", file=sys.stderr)
|
||||
break
|
||||
|
||||
# 使用安全的日志处理器
|
||||
file_handler = SafeTimedRotatingFileHandler(
|
||||
'logs/kamaxitong.log',
|
||||
when='midnight',
|
||||
interval=1,
|
||||
backupCount=10,
|
||||
encoding='utf-8'
|
||||
)
|
||||
|
||||
file_handler.setFormatter(logging.Formatter(
|
||||
'%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'
|
||||
))
|
||||
file_handler.setLevel(logging.INFO)
|
||||
|
||||
# 清除可能已存在的handler(避免重复)
|
||||
app.logger.handlers.clear()
|
||||
app.logger.addHandler(file_handler)
|
||||
|
||||
app.logger.setLevel(logging.INFO)
|
||||
app.logger.info('KaMiXiTong startup')
|
||||
|
||||
class DevelopmentConfig(Config):
|
||||
"""开发环境配置"""
|
||||
DEBUG = True
|
||||
SQLALCHEMY_ECHO = False # 关闭SQL日志输出以提高性能,需要调试时可临时开启
|
||||
|
||||
class ProductionConfig(Config):
|
||||
"""生产环境配置"""
|
||||
DEBUG = False
|
||||
SQLALCHEMY_ECHO = False
|
||||
|
||||
@staticmethod
|
||||
def init_app(app):
|
||||
# 只调用父类的init_app,避免重复配置日志
|
||||
Config.init_app(app)
|
||||
|
||||
class TestingConfig(Config):
|
||||
"""测试环境配置"""
|
||||
TESTING = True
|
||||
SQLALCHEMY_DATABASE_URI = 'sqlite:///:memory:'
|
||||
WTF_CSRF_ENABLED = False
|
||||
# 为内存数据库禁用连接池配置
|
||||
SQLALCHEMY_ENGINE_OPTIONS = {
|
||||
"future": True,
|
||||
"echo": False,
|
||||
}
|
||||
|
||||
# 配置字典
|
||||
config = {
|
||||
'development': DevelopmentConfig,
|
||||
'production': ProductionConfig,
|
||||
'testing': TestingConfig,
|
||||
'default': DevelopmentConfig
|
||||
}
|
||||
Reference in New Issue
Block a user