Flask项目配置

配置的重要性:良好的配置管理是项目成功的关键,它决定了应用的行为、性能和安全性。

为什么需要配置管理?

配置管理在软件开发中至关重要:

环境隔离

开发、测试、生产环境使用不同配置

安全保护

敏感信息(API密钥、数据库密码)安全存储

部署灵活

无需修改代码即可改变应用行为

配置层级结构

生产环境配置
最高安全级别,性能优化
测试环境配置
集成测试,接近生产环境
开发环境配置
调试模式,详细日志
基础配置
所有环境共享的通用配置

配置方法比较

方法 优点 缺点 适用场景
app.config直接设置 简单直观 硬编码,不安全 快速原型,临时测试
配置类 结构清晰,支持继承 需要Python代码 大多数Flask项目
配置文件(.py, .cfg) 配置与代码分离 可能有安全风险 不包含敏感信息的配置
环境变量 安全,便于部署 管理复杂 敏感信息,生产环境
配置管理工具 集中管理,版本控制 学习成本 大型微服务架构

基础配置方法

1. 直接设置配置

# 最基本的配置方式
from flask import Flask

app = Flask(__name__)

# 直接设置配置项
app.config['SECRET_KEY'] = 'hardcoded-secret-key'
app.config['DEBUG'] = True
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///app.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['SESSION_COOKIE_SECURE'] = False  # 开发环境可以设置为False
app.config['PERMANENT_SESSION_LIFETIME'] = 3600  # 1小时

# 批量更新配置
app.config.update(
    TESTING=True,
    PROPAGATE_EXCEPTIONS=True
)

# 从字典加载配置
config_dict = {
    'MAX_CONTENT_LENGTH': 16 * 1024 * 1024,  # 16MB文件上传限制
    'UPLOAD_FOLDER': '/path/to/uploads',
    'ALLOWED_EXTENSIONS': {'txt', 'pdf', 'png', 'jpg'}
}
app.config.from_mapping(config_dict)

警告:不要硬编码敏感信息!

密钥、密码等敏感信息永远不要直接写在代码中,应该使用环境变量或安全的配置管理系统。

2. 使用配置类(推荐)

# config.py - 配置类定义
import os
from datetime import timedelta

class Config:
    """基础配置类"""
    # 应用元数据
    APP_NAME = 'My Flask App'
    APP_VERSION = '1.0.0'

    # 安全配置
    SECRET_KEY = os.environ.get('SECRET_KEY') or 'dev-secret-key'

    # 会话配置
    SESSION_COOKIE_NAME = 'flask_session'
    SESSION_COOKIE_HTTPONLY = True
    SESSION_COOKIE_SECURE = False  # 生产环境应为True
    PERMANENT_SESSION_LIFETIME = timedelta(days=7)

    # 文件上传
    MAX_CONTENT_LENGTH = 16 * 1024 * 1024  # 16MB
    UPLOAD_FOLDER = 'uploads'
    ALLOWED_EXTENSIONS = {'txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif'}

    # 数据库配置(基础)
    SQLALCHEMY_TRACK_MODIFICATIONS = False

    # 性能配置
    JSON_SORT_KEYS = False  # 不排序JSON键,提高性能
    JSONIFY_PRETTYPRINT_REGULAR = False  # 生产环境关闭美化

    @staticmethod
    def init_app(app):
        """初始化应用"""
        pass

class DevelopmentConfig(Config):
    """开发环境配置"""
    DEBUG = True
    TESTING = False

    # 数据库
    SQLALCHEMY_DATABASE_URI = os.environ.get('DEV_DATABASE_URL') or \
        'sqlite:///dev.db'

    # 详细日志
    LOG_LEVEL = 'DEBUG'

    # 开发工具
    EXPLAIN_TEMPLATE_LOADING = False

    # 禁用缓存
    SEND_FILE_MAX_AGE_DEFAULT = 0

    # 跨域支持(开发时可能用到)
    CORS_ORIGINS = ['http://localhost:3000', 'http://127.0.0.1:3000']

class TestingConfig(Config):
    """测试环境配置"""
    DEBUG = False
    TESTING = True

    # 测试数据库
    SQLALCHEMY_DATABASE_URI = os.environ.get('TEST_DATABASE_URL') or \
        'sqlite:///:memory:'  # 内存数据库

    # 测试特定配置
    WTF_CSRF_ENABLED = False  # 测试时禁用CSRF
    PRESERVE_CONTEXT_ON_EXCEPTION = False

    # 日志级别
    LOG_LEVEL = 'INFO'

    # 邮件测试
    MAIL_SUPPRESS_SEND = True  # 不实际发送邮件

class ProductionConfig(Config):
    """生产环境配置"""
    DEBUG = False
    TESTING = False

    # 生产数据库
    SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL')

    # 安全增强
    SESSION_COOKIE_SECURE = True
    REMEMBER_COOKIE_SECURE = True
    SESSION_COOKIE_HTTPONLY = True
    SESSION_COOKIE_SAMESITE = 'Lax'

    # 生产环境必须设置SECRET_KEY
    SECRET_KEY = os.environ['SECRET_KEY']  # 强制要求设置

    # 性能优化
    JSONIFY_PRETTYPRINT_REGULAR = False
    SEND_FILE_MAX_AGE_DEFAULT = 31536000  # 1年缓存

    # 日志级别
    LOG_LEVEL = 'WARNING'

    # 服务器配置
    PREFERRED_URL_SCHEME = 'https'
    SERVER_NAME = os.environ.get('SERVER_NAME')

    # 限流配置
    RATELIMIT_ENABLED = True
    RATELIMIT_DEFAULT = "200 per day;50 per hour"

    @classmethod
    def init_app(cls, app):
        """生产环境初始化"""
        Config.init_app(app)

        # 生产环境特定的初始化
        cls.setup_logging(app)
        cls.setup_monitoring(app)
        cls.setup_error_handling(app)

    @staticmethod
    def setup_logging(app):
        """配置生产环境日志"""
        import logging
        from logging.handlers import RotatingFileHandler

        # 创建日志目录
        log_dir = 'logs'
        os.makedirs(log_dir, exist_ok=True)

        # 文件处理器
        file_handler = RotatingFileHandler(
            os.path.join(log_dir, 'app.log'),
            maxBytes=10*1024*1024,  # 10MB
            backupCount=10
        )
        file_handler.setLevel(logging.WARNING)

        # 格式化器
        formatter = logging.Formatter(
            '%(asctime)s - %(name)s - %(levelname)s - %(message)s '
            '[in %(pathname)s:%(lineno)d]'
        )
        file_handler.setFormatter(formatter)

        app.logger.addHandler(file_handler)

    @staticmethod
    def setup_monitoring(app):
        """配置监控"""
        # 这里可以集成Sentry、New Relic等监控工具
        pass

    @staticmethod
    def setup_error_handling(app):
        """配置错误处理"""
        # 生产环境禁用调试信息
        app.config['PROPAGATE_EXCEPTIONS'] = False
        app.config['TRAP_HTTP_EXCEPTIONS'] = True

# 配置映射字典
config = {
    'development': DevelopmentConfig,
    'testing': TestingConfig,
    'production': ProductionConfig,
    'default': DevelopmentConfig
}

# 在应用中加载配置
def create_app(config_name=None):
    """应用工厂函数"""
    app = Flask(__name__)

    # 确定配置
    if config_name is None:
        config_name = os.environ.get('FLASK_ENV', 'default')

    # 加载配置
    app.config.from_object(config[config_name])

    # 初始化应用
    config[config_name].init_app(app)

    return app

3. 从文件加载配置

配置文件示例
必需 可选 机密
# 从Python文件加载
app.config.from_pyfile('config.py')

# 从JSON文件加载
app.config.from_json('config.json')

# 从环境变量指定的文件加载
app.config.from_envvar('FLASK_CONFIG_FILE')

# 示例配置文件:config/production.py
"""
生产环境配置文件
注意:此文件不应包含真正的密钥,密钥应从环境变量读取
"""

import os

# 基础配置
DEBUG = False
TESTING = False
SECRET_KEY = os.environ['SECRET_KEY']

# 数据库配置
SQLALCHEMY_DATABASE_URI = os.environ['DATABASE_URL']
SQLALCHEMY_ENGINE_OPTIONS = {
    'pool_size': 10,
    'pool_recycle': 300,
    'pool_pre_ping': True,
    'max_overflow': 20
}

# Redis配置
REDIS_URL = os.environ.get('REDIS_URL', 'redis://localhost:6379/0')
CACHE_TYPE = 'redis'
CACHE_REDIS_URL = REDIS_URL

# 邮件配置
MAIL_SERVER = os.environ.get('MAIL_SERVER', 'smtp.gmail.com')
MAIL_PORT = int(os.environ.get('MAIL_PORT', 587))
MAIL_USE_TLS = True
MAIL_USERNAME = os.environ.get('MAIL_USERNAME')
MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD')
MAIL_DEFAULT_SENDER = os.environ.get('MAIL_DEFAULT_SENDER')

# 文件上传
MAX_CONTENT_LENGTH = 50 * 1024 * 1024  # 50MB
UPLOAD_FOLDER = '/var/www/uploads'
ALLOWED_EXTENSIONS = {'jpg', 'jpeg', 'png', 'gif', 'pdf', 'doc', 'docx'}

# 安全配置
SESSION_COOKIE_SECURE = True
REMEMBER_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = 'Lax'

# 性能配置
JSON_SORT_KEYS = False
JSONIFY_PRETTYPRINT_REGULAR = False
SEND_FILE_MAX_AGE_DEFAULT = 31536000  # 1年

# 第三方服务API密钥
GOOGLE_MAPS_API_KEY = os.environ.get('GOOGLE_MAPS_API_KEY')
STRIPE_SECRET_KEY = os.environ.get('STRIPE_SECRET_KEY')
STRIPE_PUBLIC_KEY = os.environ.get('STRIPE_PUBLIC_KEY')

# 应用特定配置
ITEMS_PER_PAGE = 20
PASSWORD_RESET_TIMEOUT = 3600  # 1小时
MAX_LOGIN_ATTEMPTS = 5

4. 环境变量配置

# .env 文件示例(开发环境)
# 注意:.env文件不应提交到版本控制系统

# 基础配置
FLASK_APP=app.py
FLASK_ENV=development
FLASK_DEBUG=1

# 安全配置
SECRET_KEY=dev-secret-key-change-in-production
SECURITY_PASSWORD_SALT=dev-salt-change-in-production

# 数据库配置
DATABASE_URL=postgresql://user:password@localhost:5432/dev_db
REDIS_URL=redis://localhost:6379/0

# 邮件配置(开发环境可以使用假邮箱)
MAIL_SERVER=smtp.gmail.com
MAIL_PORT=587
MAIL_USE_TLS=1
MAIL_USERNAME=your-email@gmail.com
MAIL_PASSWORD=your-app-password
MAIL_DEFAULT_SENDER=noreply@example.com

# 第三方服务(开发环境可以使用测试密钥)
GOOGLE_MAPS_API_KEY=AIzaSyDevTestKeyChangeInProduction
STRIPE_SECRET_KEY=sk_test_51...dev
STRIPE_PUBLIC_KEY=pk_test_51...dev

# 应用特定配置
ITEMS_PER_PAGE=10
DEBUG_TOOLBAR_ENABLED=1
# 使用python-dotenv加载环境变量
from dotenv import load_dotenv
import os

# 加载.env文件
load_dotenv()

# 或者在Flask应用中使用
from flask import Flask
from dotenv import load_dotenv

load_dotenv()  # 在创建app之前加载

app = Flask(__name__)

# 从环境变量读取配置
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY')
app.config['SQLALCHEMY_DATABASE_URI'] = os.environ.get('DATABASE_URL')

# 设置默认值
app.config['DEBUG'] = os.environ.get('FLASK_DEBUG', '0').lower() in ['1', 'true', 'yes']
app.config['TESTING'] = os.environ.get('FLASK_TESTING', '0').lower() in ['1', 'true', 'yes']

常用配置项详解

1. 安全配置

安全配置最佳实践

生产环境必须正确配置以下安全选项,以防止常见的安全漏洞。

# 安全配置完整示例
class SecurityConfig:
    """安全配置"""

    # === 会话安全 ===
    # 加密会话数据的密钥
    SECRET_KEY = os.environ['SECRET_KEY']

    # 会话cookie名称
    SESSION_COOKIE_NAME = 'session'

    # 只允许HTTP访问cookie(防止XSS)
    SESSION_COOKIE_HTTPONLY = True

    # 只允许HTTPS传输cookie(生产环境必须为True)
    SESSION_COOKIE_SECURE = True

    # Cookie SameSite策略
    # 'Strict' - 完全禁止跨站请求携带Cookie
    # 'Lax' - 允许部分安全的跨站请求(推荐)
    # 'None' - 允许所有跨站请求(需要Secure=True)
    SESSION_COOKIE_SAMESITE = 'Lax'

    # 会话有效期(秒)
    PERMANENT_SESSION_LIFETIME = 3600  # 1小时

    # === 记住我功能 ===
    REMEMBER_COOKIE_NAME = 'remember_token'
    REMEMBER_COOKIE_DURATION = timedelta(days=30)
    REMEMBER_COOKIE_SECURE = True
    REMEMBER_COOKIE_HTTPONLY = True
    REMEMBER_COOKIE_SAMESITE = 'Lax'

    # === CSRF保护 ===
    # WTF扩展的CSRF配置
    WTF_CSRF_ENABLED = True
    WTF_CSRF_SECRET_KEY = os.environ.get('CSRF_SECRET_KEY') or SECRET_KEY
    WTF_CSRF_TIME_LIMIT = 3600  # 1小时

    # === 密码安全 ===
    # Flask-Security-Too配置
    SECURITY_PASSWORD_SALT = os.environ['SECURITY_PASSWORD_SALT']
    SECURITY_PASSWORD_HASH = 'bcrypt'
    SECURITY_PASSWORD_SINGLE_HASH = False
    SECURITY_PASSWORD_COMPLEXITY_CHECKER = 'zxcvbn'
    SECURITY_PASSWORD_MIN_LENGTH = 8

    # 密码策略
    SECURITY_PASSWORD_REQUIREMENTS = {
        'length': 8,
        'uppercase': 1,
        'lowercase': 1,
        'digits': 1,
        'symbols': 0,  # 不强制要求特殊字符
    }

    # 登录失败限制
    SECURITY_LOGIN_WITHOUT_CONFIRMATION = False
    SECURITY_TRACKABLE = True
    SECURITY_MAX_LOGIN_ATTEMPTS = 5
    SECURITY_LOGIN_ATTEMPTS_WINDOW = 300  # 5分钟

    # === 其他安全头 ===
    # 防止点击劫持
    @property
    def CSP_HEADERS(self):
        return {
            'Content-Security-Policy':
                "default-src 'self'; "
                "script-src 'self' https://cdn.jsdelivr.net; "
                "style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; "
                "img-src 'self' data: https:; "
                "font-src 'self' https://cdn.jsdelivr.net;",
            'X-Frame-Options': 'DENY',
            'X-Content-Type-Options': 'nosniff',
            'X-XSS-Protection': '1; mode=block',
            'Strict-Transport-Security': 'max-age=31536000; includeSubDomains',
            'Referrer-Policy': 'strict-origin-when-cross-origin',
        }

2. 数据库配置

# 数据库配置完整示例
class DatabaseConfig:
    """数据库配置"""

    # === SQLAlchemy配置 ===
    SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL')

    # 是否追踪对象修改(生产环境应为False)
    SQLALCHEMY_TRACK_MODIFICATIONS = False

    # 是否在连接池中预执行ping
    SQLALCHEMY_ENGINE_OPTIONS = {
        'pool_size': 10,           # 连接池大小
        'pool_recycle': 3600,      # 连接回收时间(秒)
        'pool_pre_ping': True,     # 连接前先ping
        'max_overflow': 20,        # 最大溢出连接数
        'pool_timeout': 30,        # 获取连接超时时间
        'echo': False,             # 是否打印SQL语句(开发环境可设为True)
    }

    # === 连接池配置 ===
    @property
    def POSTGRES_POOL_CONFIG(self):
        """PostgreSQL连接池配置"""
        return {
            'min_size': 1,
            'max_size': 20,
            'max_queries': 50000,
            'max_inactive_connection_lifetime': 300.0,
        }

    # === Redis配置 ===
    REDIS_URL = os.environ.get('REDIS_URL', 'redis://localhost:6379/0')

    # Redis作为缓存
    CACHE_TYPE = 'redis'
    CACHE_REDIS_URL = REDIS_URL
    CACHE_DEFAULT_TIMEOUT = 300  # 5分钟

    # Redis作为会话存储
    SESSION_TYPE = 'redis'
    SESSION_REDIS = redis.from_url(REDIS_URL)
    SESSION_PERMANENT = True
    SESSION_USE_SIGNER = True

    # === 数据库迁移配置 ===
    MIGRATIONS_DIR = 'migrations'
    MIGRATIONS_AUTO_GENERATE = True

    # === 备份配置 ===
    @property
    def BACKUP_CONFIG(self):
        """数据库备份配置"""
        return {
            'enabled': True,
            'schedule': '0 2 * * *',  # 每天凌晨2点
            'retention_days': 30,
            'storage_path': '/var/backups/database',
            'notification_email': os.environ.get('BACKUP_NOTIFICATION_EMAIL'),
        }

    # === 监控配置 ===
    @property
    def MONITORING_CONFIG(self):
        """数据库监控配置"""
        return {
            'slow_query_threshold': 1.0,  # 慢查询阈值(秒)
            'max_connections_warning': 80,  # 最大连接数警告阈值
            'enable_query_log': False,      # 是否记录查询日志
        }

3. 邮件配置

# 邮件配置完整示例
class MailConfig:
    """邮件配置"""

    # === 基础配置 ===
    MAIL_SERVER = os.environ.get('MAIL_SERVER', 'smtp.gmail.com')
    MAIL_PORT = int(os.environ.get('MAIL_PORT', 587))
    MAIL_USE_TLS = os.environ.get('MAIL_USE_TLS', 'true').lower() == 'true'
    MAIL_USE_SSL = os.environ.get('MAIL_USE_SSL', 'false').lower() == 'true'

    # 认证信息
    MAIL_USERNAME = os.environ.get('MAIL_USERNAME')
    MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD')
    MAIL_DEFAULT_SENDER = os.environ.get('MAIL_DEFAULT_SENDER', MAIL_USERNAME)

    # === 高级配置 ===
    MAIL_MAX_EMAILS = 100  # 单个连接最大发送邮件数
    MAIL_ASCII_ATTACHMENTS = False  # 是否使用ASCII编码附件
    MAIL_SUPPRESS_SEND = False  # 是否实际发送邮件(测试时设为True)

    # === 连接池配置 ===
    MAIL_POOL_SIZE = 5  # 连接池大小
    MAIL_POOL_RECYCLE = 300  # 连接回收时间(秒)
    MAIL_POOL_TIMEOUT = 30  # 获取连接超时时间

    # === 邮件模板配置 ===
    @property
    def MAIL_TEMPLATES(self):
        """邮件模板配置"""
        return {
            'welcome': {
                'subject': '欢迎加入{app_name}',
                'template': 'emails/welcome.html',
                'priority': 'normal',
            },
            'password_reset': {
                'subject': '重置您的密码',
                'template': 'emails/password_reset.html',
                'priority': 'high',
                'expires': 3600,  # 1小时有效期
            },
            'notification': {
                'subject': '新通知',
                'template': 'emails/notification.html',
                'priority': 'normal',
            },
        }

    # === 邮件队列配置 ===
    @property
    def MAIL_QUEUE_CONFIG(self):
        """邮件队列配置(使用Celery或RQ)"""
        return {
            'enabled': True,
            'queue_name': 'mail',
            'retry_attempts': 3,
            'retry_delay': 60,  # 重试延迟(秒)
            'max_retry_delay': 3600,  # 最大重试延迟
        }

    # === 邮件监控 ===
    @property
    def MAIL_MONITORING(self):
        """邮件发送监控"""
        return {
            'track_opens': True,
            'track_clicks': True,
            'track_bounces': True,
            'webhook_url': os.environ.get('MAIL_WEBHOOK_URL'),
        }

    # === 测试配置 ===
    @classmethod
    def get_test_config(cls):
        """获取测试配置"""
        config = cls()
        config.MAIL_SUPPRESS_SEND = True
        config.MAIL_BACKEND = 'flask_mail.backends.testing'
        return config

扩展配置

1. Flask扩展配置示例

# 常用Flask扩展配置
class ExtensionsConfig:
    """Flask扩展配置"""

    # === Flask-Login ===
    LOGIN_VIEW = 'auth.login'
    LOGIN_MESSAGE = '请先登录以访问此页面'
    LOGIN_MESSAGE_CATEGORY = 'info'
    REFRESH_VIEW = 'auth.refresh'
    REFRESH_MESSAGE = '会话已过期,请重新登录'
    REFRESH_MESSAGE_CATEGORY = 'warning'
    USE_SESSION_FOR_NEXT = True
    REMEMBER_COOKIE_DURATION = timedelta(days=30)
    REMEMBER_COOKIE_REFRESH_EACH_REQUEST = True

    # === Flask-WTF ===
    WTF_CSRF_ENABLED = True
    WTF_CSRF_SECRET_KEY = os.environ.get('CSRF_SECRET_KEY')
    WTF_CSRF_TIME_LIMIT = 3600
    WTF_CSRF_SSL_STRICT = True  # 生产环境应为True
    WTF_I18N_ENABLED = True

    # === Flask-CORS ===
    CORS_ORIGINS = os.environ.get('CORS_ORIGINS', '*').split(',')
    CORS_SUPPORTS_CREDENTIALS = True
    CORS_ALLOW_HEADERS = ['Content-Type', 'Authorization', 'X-Requested-With']
    CORS_EXPOSE_HEADERS = ['Content-Disposition']
    CORS_MAX_AGE = 86400  # 24小时

    # === Flask-Cache ===
    CACHE_TYPE = 'redis'
    CACHE_REDIS_URL = os.environ.get('REDIS_URL')
    CACHE_DEFAULT_TIMEOUT = 300
    CACHE_KEY_PREFIX = 'flask_cache:'
    CACHE_THRESHOLD = 500  # 最大缓存条目数

    # === Flask-Mail ===
    MAIL_SERVER = os.environ.get('MAIL_SERVER')
    MAIL_PORT = int(os.environ.get('MAIL_PORT', 587))
    MAIL_USE_TLS = True
    MAIL_USERNAME = os.environ.get('MAIL_USERNAME')
    MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD')
    MAIL_DEFAULT_SENDER = os.environ.get('MAIL_DEFAULT_SENDER')

    # === Flask-SocketIO ===
    SOCKETIO_ASYNC_MODE = 'eventlet'
    SOCKETIO_CORS_ALLOWED_ORIGINS = os.environ.get('SOCKETIO_ORIGINS', '*')
    SOCKETIO_LOGGER = True
    SOCKETIO_ENGINEIO_LOGGER = True
    SOCKETIO_MESSAGE_QUEUE = os.environ.get('REDIS_URL')

    # === Flask-RESTful ===
    RESTFUL_JSON = {
        'ensure_ascii': False,
        'indent': None,
        'separators': (',', ':'),
    }
    RESTFUL_ERROR_404_HELP = False  # 禁用404时的默认帮助信息

    # === Flask-Limiter ===
    RATELIMIT_ENABLED = True
    RATELIMIT_STORAGE_URL = os.environ.get('REDIS_URL')
    RATELIMIT_STRATEGY = 'fixed-window'
    RATELIMIT_DEFAULT = ["200 per day", "50 per hour"]
    RATELIMIT_HEADERS_ENABLED = True

    # === Flask-Migrate ===
    MIGRATE_DIRECTORY = 'migrations'
    SQLALCHEMY_TRACK_MODIFICATIONS = False

    # === Flask-Babel ===
    BABEL_DEFAULT_LOCALE = 'zh_CN'
    BABEL_DEFAULT_TIMEZONE = 'Asia/Shanghai'
    BABEL_TRANSLATION_DIRECTORIES = 'translations'

    # === Flask-Admin ===
    FLASK_ADMIN_SWATCH = 'flatly'
    FLASK_ADMIN_TEMPLATE_MODE = 'bootstrap3'

    # === 第三方服务配置 ===
    @property
    def THIRD_PARTY_CONFIG(self):
        """第三方服务配置"""
        return {
            'stripe': {
                'secret_key': os.environ.get('STRIPE_SECRET_KEY'),
                'public_key': os.environ.get('STRIPE_PUBLIC_KEY'),
                'webhook_secret': os.environ.get('STRIPE_WEBHOOK_SECRET'),
            },
            'google': {
                'maps_api_key': os.environ.get('GOOGLE_MAPS_API_KEY'),
                'recaptcha_secret': os.environ.get('GOOGLE_RECAPTCHA_SECRET'),
                'recaptcha_site_key': os.environ.get('GOOGLE_RECAPTCHA_SITE_KEY'),
            },
            'aws': {
                'access_key_id': os.environ.get('AWS_ACCESS_KEY_ID'),
                'secret_access_key': os.environ.get('AWS_SECRET_ACCESS_KEY'),
                'region': os.environ.get('AWS_REGION', 'us-east-1'),
                's3_bucket': os.environ.get('AWS_S3_BUCKET'),
            },
            'github': {
                'client_id': os.environ.get('GITHUB_CLIENT_ID'),
                'client_secret': os.environ.get('GITHUB_CLIENT_SECRET'),
            },
            'sentry': {
                'dsn': os.environ.get('SENTRY_DSN'),
                'environment': os.environ.get('SENTRY_ENVIRONMENT', 'production'),
            },
        }

配置管理最佳实践

1. 配置组织结构

# 推荐的项目结构
myapp/
├── config/
│   ├── __init__.py           # 配置工厂
│   ├── base.py              # 基础配置
│   ├── development.py       # 开发环境配置
│   ├── testing.py          # 测试环境配置
│   ├── production.py       # 生产环境配置
│   └── staging.py          # 预发布环境配置
├── instance/
│   └── config.py           # 实例特定配置(不纳入版本控制)
├── app/
│   ├── __init__.py         # 应用工厂
│   ├── models.py
│   ├── views.py
│   └── extensions.py
├── migrations/
├── tests/
├── requirements.txt
├── requirements-dev.txt
├── requirements-prod.txt
├── .env.example            # 环境变量示例
├── .env                    # 本地环境变量(不纳入版本控制)
├── .gitignore
└── README.md

2. 配置工厂模式

工厂模式优势

使用工厂模式可以创建多个应用实例,便于测试和不同环境部署。

# config/__init__.py - 配置工厂
import os
from typing import Dict, Type, Optional
from dotenv import load_dotenv

# 加载环境变量
load_dotenv()

class Config:
    """配置基类"""

    # 基础配置
    APP_NAME = os.environ.get('APP_NAME', 'MyApp')
    VERSION = os.environ.get('APP_VERSION', '1.0.0')

    # 从环境变量读取,提供默认值
    SECRET_KEY = os.environ.get('SECRET_KEY', 'dev-secret-key')
    SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL', 'sqlite:///app.db')

    @classmethod
    def init_app(cls, app):
        """初始化应用"""
        pass

class DevelopmentConfig(Config):
    """开发配置"""
    DEBUG = True
    SQLALCHEMY_ECHO = True

class TestingConfig(Config):
    """测试配置"""
    TESTING = True
    SQLALCHEMY_DATABASE_URI = 'sqlite:///:memory:'

class ProductionConfig(Config):
    """生产配置"""
    DEBUG = False
    # 生产环境必须设置环境变量
    SECRET_KEY = os.environ['SECRET_KEY']
    SQLALCHEMY_DATABASE_URI = os.environ['DATABASE_URL']

class ConfigFactory:
    """配置工厂"""

    _configs: Dict[str, Type[Config]] = {
        'development': DevelopmentConfig,
        'testing': TestingConfig,
        'production': ProductionConfig,
    }

    @classmethod
    def get_config(cls, name: Optional[str] = None) -> Type[Config]:
        """获取配置类"""
        if name is None:
            name = os.environ.get('FLASK_ENV', 'development')

        config_class = cls._configs.get(name.lower())
        if not config_class:
            raise ValueError(f"未知的配置环境: {name}")

        return config_class

    @classmethod
    def register_config(cls, name: str, config_class: Type[Config]):
        """注册新的配置类"""
        cls._configs[name.lower()] = config_class

    @classmethod
    def create_app_config(cls, app, config_name: Optional[str] = None):
        """创建并应用配置到应用"""
        config_class = cls.get_config(config_name)

        # 应用配置
        app.config.from_object(config_class)

        # 初始化应用
        config_class.init_app(app)

        return config_class

# 使用示例
from flask import Flask

def create_app(config_name=None):
    app = Flask(__name__)

    # 从工厂获取配置
    ConfigFactory.create_app_config(app, config_name)

    # 注册蓝图、扩展等
    # ...

    return app

3. 敏感信息管理

安全警告:保护敏感信息

以下信息永远不要提交到版本控制系统:密码、API密钥、私钥、数据库连接字符串等。

# 安全的敏感信息管理
import os
from cryptography.fernet import Fernet
from base64 import b64encode, b64decode

class SecretManager:
    """安全密钥管理器"""

    def __init__(self, key_file='.secrets.key'):
        self.key_file = key_file
        self._load_or_generate_key()

    def _load_or_generate_key(self):
        """加载或生成加密密钥"""
        if os.path.exists(self.key_file):
            with open(self.key_file, 'rb') as f:
                self.key = f.read()
        else:
            self.key = Fernet.generate_key()
            with open(self.key_file, 'wb') as f:
                f.write(self.key)

            # 设置文件权限(仅所有者可读)
            os.chmod(self.key_file, 0o400)

        self.cipher = Fernet(self.key)

    def encrypt(self, plaintext: str) -> str:
        """加密文本"""
        encrypted = self.cipher.encrypt(plaintext.encode())
        return b64encode(encrypted).decode()

    def decrypt(self, encrypted_text: str) -> str:
        """解密文本"""
        encrypted = b64decode(encrypted_text.encode())
        return self.cipher.decrypt(encrypted).decode()

# 使用环境变量和加密
class SecureConfig:
    """安全配置管理"""

    @staticmethod
    def get_secret(key, default=None, required=False):
        """获取安全密钥"""
        value = os.environ.get(key)

        if value is None:
            if required:
                raise ValueError(f"必须设置环境变量: {key}")
            return default

        # 如果是加密值,进行解密
        if value.startswith('ENC:'):
            try:
                manager = SecretManager()
                return manager.decrypt(value[4:])
            except Exception as e:
                raise ValueError(f"解密失败: {key} - {e}")

        return value

    @classmethod
    def load_secure_config(cls, app):
        """加载安全配置"""
        # 数据库配置
        app.config['SQLALCHEMY_DATABASE_URI'] = cls.get_secret(
            'DATABASE_URL', required=True
        )

        # API密钥
        app.config['STRIPE_SECRET_KEY'] = cls.get_secret(
            'STRIPE_SECRET_KEY', required=True
        )

        # 邮件密码
        app.config['MAIL_PASSWORD'] = cls.get_secret(
            'MAIL_PASSWORD', required=True
        )

        # 其他可选配置
        app.config['SENTRY_DSN'] = cls.get_secret('SENTRY_DSN')
        app.config['AWS_SECRET_ACCESS_KEY'] = cls.get_secret('AWS_SECRET_ACCESS_KEY')

# 在生产环境中使用
# 设置环境变量(已加密)
# export DATABASE_URL="ENC:gAAAAABf..."
# export STRIPE_SECRET_KEY="ENC:gAAAAABf..."

环境特定配置

1. 开发环境配置

# config/development.py
import os
from .base import Config

class DevelopmentConfig(Config):
    """开发环境配置"""

    # 基础
    ENV = 'development'
    DEBUG = True
    TESTING = False

    # 数据库
    SQLALCHEMY_DATABASE_URI = os.environ.get('DEV_DATABASE_URL') or \
        'sqlite:///dev.db'
    SQLALCHEMY_ECHO = True  # 打印SQL语句

    # 安全(开发环境放宽限制)
    SESSION_COOKIE_SECURE = False
    WTF_CSRF_ENABLED = False  # 开发时可禁用CSRF

    # 调试工具
    DEBUG_TB_ENABLED = True
    DEBUG_TB_INTERCEPT_REDIRECTS = False
    DEBUG_TB_PANELS = [
        'flask_debugtoolbar.panels.versions.VersionDebugPanel',
        'flask_debugtoolbar.panels.timer.TimerDebugPanel',
        'flask_debugtoolbar.panels.headers.HeaderDebugPanel',
        'flask_debugtoolbar.panels.request_vars.RequestVarsDebugPanel',
        'flask_debugtoolbar.panels.template.TemplateDebugPanel',
        'flask_debugtoolbar.panels.sqlalchemy.SQLAlchemyDebugPanel',
        'flask_debugtoolbar.panels.logger.LoggingPanel',
        'flask_debugtoolbar.panels.profiler.ProfilerDebugPanel',
    ]

    # 热重载
    TEMPLATES_AUTO_RELOAD = True
    EXPLAIN_TEMPLATE_LOADING = True

    # 缓存控制
    SEND_FILE_MAX_AGE_DEFAULT = 0  # 禁用缓存

    # 邮件
    MAIL_SUPPRESS_SEND = True  # 不实际发送邮件
    MAIL_DEBUG = True

    # 日志
    LOG_LEVEL = 'DEBUG'
    LOG_FORMAT = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'

    @classmethod
    def init_app(cls, app):
        """开发环境初始化"""
        super().init_app(app)

        # 启用调试工具栏
        if app.config.get('DEBUG_TB_ENABLED'):
            try:
                from flask_debugtoolbar import DebugToolbarExtension
                toolbar = DebugToolbarExtension(app)
            except ImportError:
                app.logger.warning('Flask-DebugToolbar未安装')

        # 详细的请求日志
        @app.before_request
        def log_request_info():
            app.logger.debug('Headers: %s', dict(request.headers))
            app.logger.debug('Body: %s', request.get_data())

2. 生产环境配置

# config/production.py
import os
import logging
from logging.handlers import RotatingFileHandler, SMTPHandler
from .base import Config

class ProductionConfig(Config):
    """生产环境配置"""

    # 基础
    ENV = 'production'
    DEBUG = False
    TESTING = False

    # 必须从环境变量读取
    SECRET_KEY = os.environ['SECRET_KEY']
    SQLALCHEMY_DATABASE_URI = os.environ['DATABASE_URL']

    # 安全增强
    SESSION_COOKIE_SECURE = True
    REMEMBER_COOKIE_SECURE = True
    SESSION_COOKIE_HTTPONLY = True
    SESSION_COOKIE_SAMESITE = 'Lax'
    PREFERRED_URL_SCHEME = 'https'

    # 数据库连接池
    SQLALCHEMY_ENGINE_OPTIONS = {
        'pool_size': 20,
        'pool_recycle': 3600,
        'pool_pre_ping': True,
        'max_overflow': 10,
        'pool_timeout': 30,
    }

    # 性能优化
    JSON_SORT_KEYS = False
    JSONIFY_PRETTYPRINT_REGULAR = False
    SEND_FILE_MAX_AGE_DEFAULT = 31536000  # 1年缓存

    # 邮件
    MAIL_SERVER = os.environ.get('MAIL_SERVER', 'smtp.gmail.com')
    MAIL_PORT = int(os.environ.get('MAIL_PORT', 587))
    MAIL_USE_TLS = True
    MAIL_USERNAME = os.environ['MAIL_USERNAME']
    MAIL_PASSWORD = os.environ['MAIL_PASSWORD']
    MAIL_DEFAULT_SENDER = os.environ.get('MAIL_DEFAULT_SENDER', MAIL_USERNAME)

    # 日志
    LOG_LEVEL = 'WARNING'
    LOG_FORMAT = '%(asctime)s - %(name)s - %(levelname)s - %(message)s [%(request_id)s]'

    # 限流
    RATELIMIT_ENABLED = True
    RATELIMIT_STORAGE_URL = os.environ.get('REDIS_URL')
    RATELIMIT_DEFAULT = ["200 per day", "50 per hour"]
    RATELIMIT_STRATEGY = 'fixed-window'

    # 错误追踪
    SENTRY_DSN = os.environ.get('SENTRY_DSN')

    # CDN配置
    CDN_DOMAIN = os.environ.get('CDN_DOMAIN')

    @classmethod
    def init_app(cls, app):
        """生产环境初始化"""
        super().init_app(app)

        # 配置日志
        cls.setup_logging(app)

        # 配置监控
        cls.setup_monitoring(app)

        # 配置健康检查
        cls.setup_health_checks(app)

        # 配置安全头
        cls.setup_security_headers(app)

    @staticmethod
    def setup_logging(app):
        """配置生产环境日志"""
        # 创建日志目录
        log_dir = '/var/log/flask'
        os.makedirs(log_dir, exist_ok=True)

        # 文件处理器(按大小轮转)
        file_handler = RotatingFileHandler(
            os.path.join(log_dir, 'app.log'),
            maxBytes=10*1024*1024,  # 10MB
            backupCount=10
        )
        file_handler.setLevel(logging.WARNING)

        # 错误日志单独文件
        error_handler = RotatingFileHandler(
            os.path.join(log_dir, 'error.log'),
            maxBytes=10*1024*1024,
            backupCount=10
        )
        error_handler.setLevel(logging.ERROR)

        # 格式化器
        formatter = logging.Formatter(app.config['LOG_FORMAT'])
        file_handler.setFormatter(formatter)
        error_handler.setFormatter(formatter)

        # 添加处理器
        app.logger.addHandler(file_handler)
        app.logger.addHandler(error_handler)

        # 邮件错误通知
        if app.config.get('ADMIN_EMAIL'):
            mail_handler = SMTPHandler(
                mailhost=(app.config['MAIL_SERVER'], app.config['MAIL_PORT']),
                fromaddr=app.config['MAIL_DEFAULT_SENDER'],
                toaddrs=[app.config['ADMIN_EMAIL']],
                subject='应用错误 - {}'.format(app.config['APP_NAME']),
                credentials=(app.config['MAIL_USERNAME'], app.config['MAIL_PASSWORD']),
                secure=() if app.config.get('MAIL_USE_TLS') else None
            )
            mail_handler.setLevel(logging.ERROR)
            mail_handler.setFormatter(formatter)
            app.logger.addHandler(mail_handler)

        # 设置日志级别
        app.logger.setLevel(app.config['LOG_LEVEL'])

        # 禁用Flask默认的日志处理器
        logging.getLogger('werkzeug').setLevel(logging.WARNING)

    @staticmethod
    def setup_monitoring(app):
        """配置监控"""
        # Sentry错误监控
        if app.config.get('SENTRY_DSN'):
            try:
                import sentry_sdk
                from sentry_sdk.integrations.flask import FlaskIntegration

                sentry_sdk.init(
                    dsn=app.config['SENTRY_DSN'],
                    integrations=[FlaskIntegration()],
                    environment=os.environ.get('SENTRY_ENVIRONMENT', 'production'),
                    release=app.config.get('VERSION', '1.0.0'),
                    traces_sample_rate=0.1,
                )
                app.logger.info('Sentry监控已启用')
            except ImportError:
                app.logger.warning('Sentry未安装,跳过监控配置')

        # New Relic监控
        if os.environ.get('NEW_RELIC_LICENSE_KEY'):
            try:
                import newrelic.agent
                newrelic.agent.initialize('newrelic.ini')
                app.logger.info('New Relic监控已启用')
            except ImportError:
                app.logger.warning('New Relic未安装,跳过监控配置')

    @staticmethod
    def setup_health_checks(app):
        """配置健康检查端点"""
        @app.route('/health')
        def health_check():
            return {'status': 'healthy', 'timestamp': datetime.now().isoformat()}

        @app.route('/metrics')
        @login_required
        @admin_required
        def metrics():
            """应用指标端点"""
            import psutil
            import json

            metrics = {
                'memory': psutil.virtual_memory()._asdict(),
                'cpu': psutil.cpu_percent(interval=1),
                'disk': psutil.disk_usage('/')._asdict(),
                'uptime': time.time() - psutil.boot_time(),
            }

            return json.dumps(metrics)

    @staticmethod
    def setup_security_headers(app):
        """配置安全HTTP头"""
        @app.after_request
        def add_security_headers(response):
            # 安全头
            response.headers['X-Content-Type-Options'] = 'nosniff'
            response.headers['X-Frame-Options'] = 'DENY'
            response.headers['X-XSS-Protection'] = '1; mode=block'
            response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
            response.headers['Referrer-Policy'] = 'strict-origin-when-cross-origin'

            # CSP头(根据实际需求调整)
            if not app.debug:
                response.headers['Content-Security-Policy'] = \
                    "default-src 'self'; " \
                    "script-src 'self' https://cdn.jsdelivr.net; " \
                    "style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; " \
                    "img-src 'self' data: https:; " \
                    "font-src 'self' https://cdn.jsdelivr.net;"

            return response

配置验证

1. 配置验证器

# config/validators.py
import os
import re
from typing import Dict, Any, List, Optional
from urllib.parse import urlparse

class ConfigValidator:
    """配置验证器"""

    @staticmethod
    def validate_database_url(url: str) -> bool:
        """验证数据库URL"""
        try:
            parsed = urlparse(url)

            # 检查协议
            if parsed.scheme not in ['postgresql', 'mysql', 'sqlite']:
                return False

            # 检查必要组件
            if parsed.scheme != 'sqlite' and not parsed.netloc:
                return False

            return True
        except:
            return False

    @staticmethod
    def validate_secret_key(key: str) -> bool:
        """验证密钥强度"""
        if len(key) < 16:
            return False

        # 检查是否包含足够的不同字符类型
        has_upper = any(c.isupper() for c in key)
        has_lower = any(c.islower() for c in key)
        has_digit = any(c.isdigit() for c in key)
        has_special = any(not c.isalnum() for c in key)

        # 至少包含3种字符类型
        types_count = sum([has_upper, has_lower, has_digit, has_special])
        return types_count >= 3

    @staticmethod
    def validate_email(email: str) -> bool:
        """验证邮箱格式"""
        pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
        return re.match(pattern, email) is not None

    @staticmethod
    def validate_url(url: str) -> bool:
        """验证URL格式"""
        try:
            parsed = urlparse(url)
            return all([parsed.scheme, parsed.netloc])
        except:
            return False

    @staticmethod
    def validate_port(port: Any) -> bool:
        """验证端口号"""
        try:
            port_int = int(port)
            return 1 <= port_int <= 65535
        except:
            return False

    @classmethod
    def validate_config(cls, config: Dict[str, Any]) -> List[str]:
        """验证完整配置"""
        errors = []

        # 必需配置项
        required_fields = [
            ('SECRET_KEY', str, cls.validate_secret_key, "必须设置强密码的SECRET_KEY"),
            ('SQLALCHEMY_DATABASE_URI', str, cls.validate_database_url, "无效的数据库URL"),
        ]

        for field, expected_type, validator, error_msg in required_fields:
            if field not in config:
                errors.append(f"缺少必需配置: {field}")
                continue

            value = config[field]

            # 类型检查
            if not isinstance(value, expected_type):
                errors.append(f"{field} 应该是 {expected_type.__name__} 类型")
                continue

            # 自定义验证
            if validator and not validator(value):
                errors.append(f"{field}: {error_msg}")

        # 可选配置验证
        if 'MAIL_DEFAULT_SENDER' in config:
            if not cls.validate_email(config['MAIL_DEFAULT_SENDER']):
                errors.append("MAIL_DEFAULT_SENDER: 无效的邮箱格式")

        if 'SERVER_NAME' in config:
            if not cls.validate_url(f"http://{config['SERVER_NAME']}"):
                errors.append("SERVER_NAME: 无效的服务器名称")

        if 'MAIL_PORT' in config:
            if not cls.validate_port(config['MAIL_PORT']):
                errors.append("MAIL_PORT: 无效的端口号")

        return errors

# 在应用启动时验证配置
def validate_app_config(app):
    """验证应用配置"""
    validator = ConfigValidator()
    errors = validator.validate_config(app.config)

    if errors:
        error_msg = "\n".join(errors)

        if app.debug:
            # 开发环境:直接抛出异常
            raise ValueError(f"配置验证失败:\n{error_msg}")
        else:
            # 生产环境:记录错误并退出
            app.logger.error(f"配置验证失败:\n{error_msg}")

            # 发送警报邮件
            send_alert_email("配置验证失败", error_msg)

            # 优雅退出
            import sys
            sys.exit(1)

    app.logger.info("配置验证通过")

2. 配置检查脚本

#!/usr/bin/env python
# check_config.py - 配置检查脚本

import os
import sys
import argparse
from dotenv import load_dotenv

# 添加项目路径
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))

def check_environment():
    """检查环境变量"""
    print("🔍 检查环境变量...")

    required_vars = [
        ('SECRET_KEY', True),
        ('DATABASE_URL', True),
        ('MAIL_USERNAME', False),
        ('MAIL_PASSWORD', False),
        ('REDIS_URL', False),
    ]

    missing = []
    for var, required in required_vars:
        if var in os.environ:
            if var in ['SECRET_KEY', 'MAIL_PASSWORD']:
                print(f"  ✓ {var}: 已设置 (值隐藏)")
            else:
                print(f"  ✓ {var}: {os.environ[var][:50]}...")
        elif required:
            missing.append(var)
            print(f"  ✗ {var}: 未设置 (必需)")
        else:
            print(f"  ⚠ {var}: 未设置 (可选)")

    return missing

def check_database():
    """检查数据库连接"""
    print("\n🔍 检查数据库连接...")

    try:
        from config import Config
        config = Config()

        if not hasattr(config, 'SQLALCHEMY_DATABASE_URI'):
            print("  ✗ 未配置数据库URL")
            return False

        from sqlalchemy import create_engine
        engine = create_engine(config.SQLALCHEMY_DATABASE_URI)

        with engine.connect() as conn:
            conn.execute("SELECT 1")

        print("  ✓ 数据库连接成功")
        return True
    except Exception as e:
        print(f"  ✗ 数据库连接失败: {e}")
        return False

def check_redis():
    """检查Redis连接"""
    print("\n🔍 检查Redis连接...")

    try:
        import redis

        redis_url = os.environ.get('REDIS_URL', 'redis://localhost:6379/0')
        r = redis.from_url(redis_url)
        r.ping()

        print("  ✓ Redis连接成功")
        return True
    except Exception as e:
        print(f"  ⚠ Redis连接失败: {e}")
        return False

def check_security():
    """检查安全配置"""
    print("\n🔍 检查安全配置...")

    from config.validators import ConfigValidator
    validator = ConfigValidator()

    # 检查SECRET_KEY强度
    secret_key = os.environ.get('SECRET_KEY')
    if secret_key:
        if validator.validate_secret_key(secret_key):
            print("  ✓ SECRET_KEY强度足够")
        else:
            print("  ⚠ SECRET_KEY强度不足,建议使用更复杂的密钥")

    # 检查调试模式
    if os.environ.get('FLASK_DEBUG') == '1':
        print("  ⚠ 调试模式已启用,生产环境请禁用")

    # 检查HTTPS设置
    if os.environ.get('SESSION_COOKIE_SECURE') != 'True':
        print("  ⚠ SESSION_COOKIE_SECURE未启用,生产环境建议启用")

def main():
    """主函数"""
    parser = argparse.ArgumentParser(description='检查项目配置')
    parser.add_argument('--env', help='环境变量文件路径', default='.env')
    args = parser.parse_args()

    # 加载环境变量
    if os.path.exists(args.env):
        load_dotenv(args.env)
        print(f"📁 已加载环境变量文件: {args.env}")
    else:
        print(f"📁 未找到环境变量文件: {args.env},使用系统环境变量")

    print("=" * 50)
    print("🛠 开始配置检查")
    print("=" * 50)

    # 执行检查
    missing_vars = check_environment()
    db_ok = check_database()
    redis_ok = check_redis()
    check_security()

    print("\n" + "=" * 50)
    print("📊 检查结果汇总")
    print("=" * 50)

    # 统计结果
    errors = []
    warnings = []

    if missing_vars:
        errors.append(f"缺少必需环境变量: {', '.join(missing_vars)}")

    if not db_ok:
        errors.append("数据库连接失败")

    if not redis_ok:
        warnings.append("Redis连接失败")

    # 输出结果
    if errors:
        print("❌ 发现错误:")
        for error in errors:
            print(f"  • {error}")

    if warnings:
        print("⚠️ 发现警告:")
        for warning in warnings:
            print(f"  • {warning}")

    if not errors and not warnings:
        print("✅ 所有检查通过,配置正常!")

    print("=" * 50)

    return 0 if not errors else 1

if __name__ == '__main__':
    sys.exit(main())

部署配置

1. Docker配置

# Dockerfile
# 多阶段构建

# 第一阶段:构建阶段
FROM python:3.9-slim as builder

WORKDIR /app

# 安装系统依赖
RUN apt-get update && apt-get install -y \
    gcc \
    g++ \
    libpq-dev \
    && rm -rf /var/lib/apt/lists/*

# 复制依赖文件
COPY requirements.txt .

# 创建虚拟环境并安装依赖
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
RUN pip install --no-cache-dir -r requirements.txt

# 第二阶段:运行阶段
FROM python:3.9-slim

# 安装运行时依赖
RUN apt-get update && apt-get install -y \
    libpq5 \
    && rm -rf /var/lib/apt/lists/*

# 创建非root用户
RUN groupadd -r flask && useradd -r -g flask flask

# 创建应用目录
WORKDIR /app

# 从构建阶段复制虚拟环境
COPY --from=builder /opt/venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"

# 复制应用代码
COPY . .

# 设置权限
RUN chown -R flask:flask /app
USER flask

# 创建必要的目录
RUN mkdir -p /app/logs /app/uploads

# 环境变量
ENV FLASK_APP=app.py
ENV FLASK_ENV=production
ENV PYTHONUNBUFFERED=1
ENV PYTHONPATH=/app

# 暴露端口
EXPOSE 5000

# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
    CMD curl -f http://localhost:5000/health || exit 1

# 启动命令
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "app:create_app()"]
# docker-compose.prod.yml
version: '3.8'

services:
  web:
    build:
      context: .
      dockerfile: Dockerfile
    image: myapp:${TAG:-latest}
    container_name: myapp-web
    restart: unless-stopped
    depends_on:
      - db
      - redis
    environment:
      - FLASK_ENV=production
      - DATABASE_URL=postgresql://${DB_USER}:${DB_PASSWORD}@db:5432/${DB_NAME}
      - REDIS_URL=redis://redis:6379/0
      - SECRET_KEY=${SECRET_KEY}
      - MAIL_USERNAME=${MAIL_USERNAME}
      - MAIL_PASSWORD=${MAIL_PASSWORD}
    env_file:
      - .env.production
    volumes:
      - uploads:/app/uploads
      - logs:/app/logs
    ports:
      - "${HOST_PORT}:5000"
    networks:
      - app-network
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:5000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

  db:
    image: postgres:13-alpine
    container_name: myapp-db
    restart: unless-stopped
    environment:
      POSTGRES_DB: ${DB_NAME}
      POSTGRES_USER: ${DB_USER}
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./docker/postgres/init.sql:/docker-entrypoint-initdb.d/init.sql
    ports:
      - "5432:5432"
    networks:
      - app-network
    command: postgres -c max_connections=100 -c shared_buffers=256MB

  redis:
    image: redis:6-alpine
    container_name: myapp-redis
    restart: unless-stopped
    command: redis-server --appendonly yes
    volumes:
      - redis_data:/data
    ports:
      - "6379:6379"
    networks:
      - app-network

  nginx:
    image: nginx:alpine
    container_name: myapp-nginx
    restart: unless-stopped
    depends_on:
      - web
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./docker/nginx/nginx.conf:/etc/nginx/nginx.conf
      - ./docker/nginx/ssl:/etc/nginx/ssl
      - ./logs/nginx:/var/log/nginx
    networks:
      - app-network

volumes:
  postgres_data:
  redis_data:
  uploads:
  logs:

networks:
  app-network:
    driver: bridge

2. Kubernetes配置

# k8s/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: flask-app-config
data:
  # 应用配置
  FLASK_ENV: "production"
  APP_NAME: "MyFlaskApp"
  LOG_LEVEL: "INFO"

  # 数据库配置(敏感信息使用Secret)
  DB_HOST: "postgres-service"
  DB_PORT: "5432"
  DB_NAME: "production_db"

  # Redis配置
  REDIS_HOST: "redis-service"
  REDIS_PORT: "6379"

  # 邮件配置
  MAIL_SERVER: "smtp.gmail.com"
  MAIL_PORT: "587"
  MAIL_USE_TLS: "true"

# k8s/secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: flask-app-secrets
type: Opaque
stringData:
  # 安全密钥
  SECRET_KEY: "production-secret-key-change-this"

  # 数据库凭据
  DB_USER: "flask_user"
  DB_PASSWORD: "production-db-password"

  # 邮件凭据
  MAIL_USERNAME: "noreply@example.com"
  MAIL_PASSWORD: "mail-app-password"

  # API密钥
  STRIPE_SECRET_KEY: "sk_live_..."
  GOOGLE_MAPS_API_KEY: "AIzaSy..."

# k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: flask-app
  labels:
    app: flask-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: flask-app
  template:
    metadata:
      labels:
        app: flask-app
    spec:
      containers:
      - name: flask-app
        image: myregistry.com/myapp:latest
        ports:
        - containerPort: 5000
        env:
        - name: SECRET_KEY
          valueFrom:
            secretKeyRef:
              name: flask-app-secrets
              key: SECRET_KEY
        - name: DATABASE_URL
          value: "postgresql://$(DB_USER):$(DB_PASSWORD)@$(DB_HOST):$(DB_PORT)/$(DB_NAME)"
        envFrom:
        - configMapRef:
            name: flask-app-config
        - secretRef:
            name: flask-app-secrets
        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"
          limits:
            memory: "512Mi"
            cpu: "500m"
        livenessProbe:
          httpGet:
            path: /health
            port: 5000
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /health
            port: 5000
          initialDelaySeconds: 5
          periodSeconds: 5

常见问题

推荐的多环境配置管理策略:

  1. 使用配置类继承:创建基础配置类,各环境继承并覆盖
  2. 环境变量优先:通过环境变量覆盖配置
  3. 配置文件分离:不同环境使用不同配置文件
  4. 使用配置管理工具:如Consul、etcd、AWS Parameter Store
# 示例:多环境配置管理
import os

# 环境检测
ENV = os.environ.get('FLASK_ENV', 'development')

# 配置文件映射
CONFIG_FILES = {
    'development': 'config/development.py',
    'testing': 'config/testing.py',
    'production': 'config/production.py',
    'staging': 'config/staging.py',
}

def load_config(app):
    """根据环境加载配置"""
    # 1. 从环境变量读取配置名
    config_name = ENV

    # 2. 加载对应配置文件
    config_file = CONFIG_FILES.get(config_name)
    if config_file and os.path.exists(config_file):
        app.config.from_pyfile(config_file)

    # 3. 环境变量覆盖(最高优先级)
    app.config.from_prefixed_env()

    # 4. 验证配置
    validate_config(app.config)

# 或者使用工厂模式
class ConfigFactory:
    @staticmethod
    def create_config(env=None):
        if env is None:
            env = os.environ.get('FLASK_ENV', 'development')

        configs = {
            'dev': DevelopmentConfig,
            'test': TestingConfig,
            'prod': ProductionConfig,
            'staging': StagingConfig,
        }

        return configs.get(env, DevelopmentConfig)()

敏感信息的安全管理:

方法 优点 缺点 适用场景
环境变量 简单,广泛支持 管理复杂,可能泄露 小型到中型项目
加密配置文件 可版本控制 需要密钥管理 需要共享配置
密钥管理服务 安全,集中管理 复杂,有成本 企业级应用
Docker Secrets Docker原生支持 仅限Docker环境 容器化部署
# 使用AWS Secrets Manager
import boto3
import json

def get_secret(secret_name):
    """从AWS Secrets Manager获取密钥"""
    client = boto3.client('secretsmanager')

    try:
        response = client.get_secret_value(SecretId=secret_name)
        secret = json.loads(response['SecretString'])
        return secret
    except Exception as e:
        print(f"获取密钥失败: {e}")
        return None

# 在配置中使用
class AwsConfig:
    @classmethod
    def load_secrets(cls, app):
        """从AWS加载密钥"""
        secrets = get_secret('myapp/production')

        if secrets:
            app.config['DATABASE_URL'] = secrets['database_url']
            app.config['SECRET_KEY'] = secrets['secret_key']
            app.config['STRIPE_SECRET_KEY'] = secrets['stripe_secret_key']

# 使用HashiCorp Vault
import hvac

def get_vault_secret(path):
    """从Vault获取密钥"""
    client = hvac.Client(
        url=os.environ['VAULT_ADDR'],
        token=os.environ['VAULT_TOKEN']
    )

    secret = client.read(path)
    return secret['data']

配置热重载的实现方案:

# 配置热重载实现
import os
import threading
import time
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler

class ConfigReloader:
    """配置热重载器"""

    def __init__(self, app, config_file):
        self.app = app
        self.config_file = config_file
        self.last_modified = os.path.getmtime(config_file)
        self.observer = Observer()
        self.running = False

    def start(self):
        """启动配置监视"""
        event_handler = FileSystemEventHandler()
        event_handler.on_modified = self.on_config_modified

        self.observer.schedule(
            event_handler,
            path=os.path.dirname(self.config_file),
            recursive=False
        )

        self.observer.start()
        self.running = True

        # 启动后台线程
        thread = threading.Thread(target=self.monitor_loop, daemon=True)
        thread.start()

    def stop(self):
        """停止配置监视"""
        self.running = False
        self.observer.stop()
        self.observer.join()

    def on_config_modified(self, event):
        """配置文件修改事件处理"""
        if event.src_path == self.config_file:
            current_modified = os.path.getmtime(self.config_file)

            # 避免重复触发
            if current_modified > self.last_modified + 1:
                self.last_modified = current_modified
                self.reload_config()

    def reload_config(self):
        """重新加载配置"""
        try:
            self.app.logger.info("重新加载配置文件...")

            # 保存当前配置状态
            old_debug = self.app.config.get('DEBUG')
            old_secret = self.app.config.get('SECRET_KEY')

            # 重新加载配置
            self.app.config.from_pyfile(self.config_file, silent=True)

            # 检查敏感配置是否改变
            new_secret = self.app.config.get('SECRET_KEY')
            if new_secret != old_secret:
                self.app.logger.warning("SECRET_KEY已改变,需要重启应用")
                # 这里可以触发重启逻辑

            self.app.logger.info("配置重新加载完成")

            # 触发配置变更事件
            self.app.extensions.get('config_signals', {}).get(
                'config_changed', lambda: None
            )()

        except Exception as e:
            self.app.logger.error(f"配置重新加载失败: {e}")

    def monitor_loop(self):
        """监视循环(用于环境变量等)"""
        while self.running:
            # 检查环境变量变化
            self.check_env_vars()
            time.sleep(60)  # 每分钟检查一次

    def check_env_vars(self):
        """检查环境变量变化"""
        env_vars_to_watch = [
            'DATABASE_URL', 'REDIS_URL', 'MAIL_PASSWORD'
        ]

        for var in env_vars_to_watch:
            old_value = getattr(self, f'last_{var}', None)
            new_value = os.environ.get(var)

            if new_value != old_value:
                setattr(self, f'last_{var}', new_value)
                self.app.logger.info(f"环境变量 {var} 已改变")

                # 更新应用配置
                if var in self.app.config:
                    self.app.config[var] = new_value

# 在应用中使用
app = Flask(__name__)

# 初始化配置重载器
if app.debug:  # 只在开发环境启用
    reloader = ConfigReloader(app, 'config/development.py')
    reloader.start()

# 注册配置变更信号
config_signals = {}
app.extensions['config_signals'] = config_signals

@app.route('/config/reload')
@admin_required
def reload_config():
    """手动触发配置重载"""
    reloader.reload_config()
    return "配置已重新加载"

配置版本控制策略:

# 配置版本管理
import json
import hashlib
from datetime import datetime
from dataclasses import dataclass, asdict
from typing import Dict, Any

@dataclass
class ConfigVersion:
    """配置版本"""
    version: int
    timestamp: datetime
    hash: str
    author: str
    comment: str
    config: Dict[str, Any]

class ConfigVersionControl:
    """配置版本控制"""

    def __init__(self, storage_path='config/versions'):
        self.storage_path = storage_path
        os.makedirs(storage_path, exist_ok=True)

    def save_version(self, config: Dict[str, Any], author: str, comment: str = ""):
        """保存配置版本"""
        # 获取当前版本号
        versions = self.list_versions()
        version = len(versions) + 1

        # 计算配置哈希
        config_json = json.dumps(config, sort_keys=True, default=str)
        config_hash = hashlib.sha256(config_json.encode()).hexdigest()

        # 创建版本对象
        config_version = ConfigVersion(
            version=version,
            timestamp=datetime.now(),
            hash=config_hash,
            author=author,
            comment=comment,
            config=config
        )

        # 保存到文件
        filename = f"config_v{version:03d}_{config_hash[:8]}.json"
        filepath = os.path.join(self.storage_path, filename)

        with open(filepath, 'w') as f:
            json.dump(asdict(config_version), f, indent=2, default=str)

        # 更新最新版本链接
        latest_link = os.path.join(self.storage_path, 'latest.json')
        with open(latest_link, 'w') as f:
            json.dump({'version': version, 'file': filename}, f)

        return config_version

    def list_versions(self) -> list[ConfigVersion]:
        """列出所有配置版本"""
        versions = []

        for filename in os.listdir(self.storage_path):
            if filename.startswith('config_v') and filename.endswith('.json'):
                filepath = os.path.join(self.storage_path, filename)

                with open(filepath, 'r') as f:
                    data = json.load(f)

                # 转换时间戳
                data['timestamp'] = datetime.fromisoformat(data['timestamp'])
                versions.append(ConfigVersion(**data))

        # 按版本号排序
        versions.sort(key=lambda x: x.version)
        return versions

    def get_version(self, version: int) -> ConfigVersion:
        """获取特定版本配置"""
        for config_version in self.list_versions():
            if config_version.version == version:
                return config_version

        raise ValueError(f"版本 {version} 不存在")

    def diff_versions(self, version1: int, version2: int) -> Dict[str, Any]:
        """比较两个版本的差异"""
        v1 = self.get_version(version1)
        v2 = self.get_version(version2)

        diff = {}

        # 比较所有配置项
        all_keys = set(v1.config.keys()) | set(v2.config.keys())

        for key in all_keys:
            val1 = v1.config.get(key)
            val2 = v2.config.get(key)

            if val1 != val2:
                diff[key] = {
                    'from': val1,
                    'to': val2,
                    'changed': True
                }

        return diff

    def rollback(self, version: int, app):
        """回滚到指定版本"""
        config_version = self.get_version(version)

        # 应用配置
        for key, value in config_version.config.items():
            app.config[key] = value

        # 保存回滚记录
        self.save_version(
            config=dict(app.config),
            author='system',
            comment=f'回滚到版本 {version}'
        )

        return config_version

# 使用示例
version_control = ConfigVersionControl()

# 保存配置版本
@app.before_first_request
def save_initial_config():
    """保存初始配置版本"""
    version_control.save_version(
        config=dict(app.config),
        author='system',
        comment='初始配置'
    )

# 配置变更API
@app.route('/api/config/versions')
@admin_required
def list_config_versions():
    """列出配置版本"""
    versions = version_control.list_versions()
    return jsonify([asdict(v) for v in versions])

@app.route('/api/config/diff/<int:v1>/<int:v2>')
@admin_required
def diff_config_versions(v1, v2):
    """比较配置版本差异"""
    diff = version_control.diff_versions(v1, v2)
    return jsonify(diff)

@app.route('/api/config/rollback/<int:version>', methods=['POST'])
@admin_required
def rollback_config(version):
    """回滚配置"""
    version_control.rollback(version, app)
    return jsonify({'message': f'已回滚到版本 {version}'})

配置管理检查清单

  • ✓ 使用环境变量管理敏感信息
  • ✓ 实现配置类继承结构
  • ✓ 为不同环境创建独立配置
  • ✓ 在生产环境禁用调试模式
  • ✓ 配置数据库连接池
  • ✓ 实现配置验证机制
  • ✓ 设置适当的安全HTTP头
  • ✓ 配置日志和监控
  • ✓ 创建健康检查端点
  • ✓ 文档化配置项和默认值

配置工具推荐

Python配置库
  • python-dotenv:加载.env文件
  • dynaconf:动态配置管理
  • pydantic-settings:类型安全的配置
  • everett:灵活的配置库
密钥管理服务
  • AWS Secrets Manager:AWS密钥管理
  • HashiCorp Vault:企业级密钥管理
  • Azure Key Vault:Azure密钥管理
  • Google Secret Manager:GCP密钥管理