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

193 lines
8.5 KiB
Python
Raw 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
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)
SECRET_KEY = 'dev-secret-key-change-in-production'
# 数据库配置 - 优先使用环境变量中的DATABASE_URL
import os
# 使用绝对路径确保数据库文件能够正确创建
instance_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'instance')
if not os.path.exists(instance_dir):
os.makedirs(instance_dir)
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL', f'sqlite:///{os.path.join(instance_dir, "kamaxitong.db")}')
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):
"""重写轮转方法,添加错误处理"""
try:
super().doRollover()
except PermissionError as e:
# Windows文件锁定错误静默处理
import sys
print(f"日志轮转被跳过(文件被占用): {str(e)}", file=sys.stderr)
except Exception as e:
import sys
print(f"日志轮转错误: {str(e)}", file=sys.stderr)
# 使用安全的日志处理器
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
}