import os from datetime import timedelta import json # 项目根目录 BASE_DIR = os.path.abspath(os.path.dirname(__file__)) def _get_bool_env(key: str, default: bool = False) -> bool: """从环境变量获取布尔值 Args: key: 环境变量名 default: 默认值 Returns: 布尔值 """ value = os.getenv(key) if value is None: return default return value.lower() in ("true", "1", "yes", "on") def _load_env_file(): """加载 .env 文件(模块级别调用)""" try: from dotenv import load_dotenv # 按优先级查找 .env 文件 env_files = [ ".env.local", # 本地配置(最高优先级) f".env.{os.getenv('FLASK_ENV', 'dev')}", # 环境特定配置 ".env", # 通用配置 ] for env_file in env_files: # 构建绝对路径 env_path = os.path.join(os.path.dirname(__file__), env_file) if os.path.exists(env_path): loaded = load_dotenv(env_path) print(f"[ENV] 加载环境配置: {env_path} - {loaded}") return True print("[WARN] 未找到 .env 文件") return False except ImportError: print("[WARN] python-dotenv 未安装,跳过 .env 文件加载") return False # 在定义配置类之前加载环境变量 _load_env_file() class BaseConfig: """基础配置类 - 所有环境共享的配置""" # 应用配置 SECRET_KEY = os.getenv("SECRET_KEY", "dev-secret-key-change-in-production") # 数据库配置 SQLALCHEMY_ENGINE_OPTIONS = { "json_serializer": lambda obj: json.dumps(obj, ensure_ascii=False), # "json_deserializer": json.loads, } SQLALCHEMY_TRACK_MODIFICATIONS = False SQLALCHEMY_RECORD_QUERIES = True SQLALCHEMY_ECHO = False SQLALCHEMY_COMMIT_ON_TEARDOWN = True SQLALCHEMY_SHOW_ERROR_DETAILS = True SQLALCHEMY_DATABASE_URI = os.getenv( "DATABASE_URL", f"sqlite:///{BASE_DIR}/runtime/iti-flask.db" ) # JWT 配置 JWT_SECRET_KEY = os.getenv("JWT_SECRET_KEY", "dev-jwt-secret-key") JWT_TOKEN_LOCATION = ["headers"] JWT_HEADER_NAME = "Authorization" JWT_HEADER_TYPE = "Bearer" JWT_ACCESS_TOKEN_EXPIRES = timedelta(hours=1) JWT_REFRESH_TOKEN_EXPIRES = timedelta(days=30) # 跨域配置 CORS_ORIGINS = ["*"] # 限流配置 RATELIMIT_ENABLED = True RATELIMIT_KEY_PREFIX = "iti_rate_limit_" RATELIMIT_STORAGE_URI = "memory://" RATELIMIT_DEFAULT = "200 per hour" # JSON 序列化配置 JSON_ENSURE_ASCII = False # 支持中文,不转义为 Unicode JSON_SORT_KEYS = False # 不对 key 排序 # 文件上传配置 MAX_CONTENT_LENGTH = 200 * 1024 * 1024 # 200MB,Flask 上传大小限制 # 缓存配置 CACHE_SIMPLE = { # 类型 NullCache | SimpleCache | FileSystemCache | RedisCache | RedisSentinelCache | RedisClusterCache | UWSGICache | MemcachedCache | SASLMemcachedCache | SpreadSASLMemcachedCache "ENABLED": True, "CACHE_TYPE": "SimpleCache", "CACHE_NO_NULL_WARNING": False, "CACHE_ARGS": [], "CACHE_OPTIONS": None, "CACHE_DEFAULT_TIMEOUT": 0, "CACHE_IGNORE_ERRORS": False, "CACHE_THRESHOLD": 500, "CACHE_KEY_PREFIX": "iti_cache_", "CACHE_SOURCE_CHECK": False, # # UWSGI 配置 # "CACHE_UWSGI_NAME": "mycache@localhost:3031", # # MEMCACHED 配置 # "CACHE_MEMCACHED_SERVERS": ["localhost:11211"], # "CACHE_MEMCACHED_USERNAME": "None", # "CACHE_MEMCACHED_PASSWORD": "None", # # REDIS 配置 # "CACHE_REDIS_URL": "redis://localhost:6379/2", # "CACHE_REDIS_HOST": "localhost", # "CACHE_REDIS_PORT": 6379, # "CACHE_REDIS_PASSWORD": None, # "CACHE_REDIS_DB": 0, # "CACHE_REDIS_SENTINELS": ["localhost:26379"], # "CACHE_REDIS_SENTINEL_MASTER": "mymaster", # "CACHE_REDIS_CLUSTER": "localhost:6379,localhost:6380,localhost:6381", # # FILE-SYSTEM 配置 # "CACHE_DIR": r"/tmp/iti_cache", } # Redis缓存配置 CACHE_REDIS = { "ENABLED": False, "CACHE_TYPE": "RedisCache", "CACHE_NO_NULL_WARNING": False, "CACHE_ARGS": [], "CACHE_OPTIONS": None, "CACHE_DEFAULT_TIMEOUT": 0, "CACHE_IGNORE_ERRORS": False, "CACHE_THRESHOLD": 500, "CACHE_KEY_PREFIX": "iti_cache_", "CACHE_SOURCE_CHECK": False, # REDIS 配置 # "CACHE_REDIS_URL": "redis://localhost:6379/0", "CACHE_REDIS_HOST": "localhost", "CACHE_REDIS_PORT": 6379, "CACHE_REDIS_PASSWORD": None, "CACHE_REDIS_DB": 0, # "CACHE_REDIS_SENTINELS": ["localhost:26379"], # "CACHE_REDIS_SENTINEL_MASTER": "mymaster", # "CACHE_REDIS_CLUSTER": "localhost:6379,localhost:6380,localhost:6381", } # 文件存储配置 FILE_STORAGE = { "DEFAULT_STORAGE_TYPE": "local", "MAX_FILE_SIZE": 100 * 1024 * 1024 * 1024, # 100GB "TUS_CHUNK_SIZE": 5 * 1024 * 1024, # 5MB # 本地存储配置 "LOCAL": { "base_path": os.path.join(BASE_DIR, "runtime", "uploads"), }, # 阿里云OSS配置 "ALIYUN_OSS": { "access_key_id": os.getenv("ALIYUN_OSS_ACCESS_KEY_ID"), "access_key_secret": os.getenv("ALIYUN_OSS_ACCESS_KEY_SECRET"), "endpoint": os.getenv( "ALIYUN_OSS_ENDPOINT" ), # 例如:oss-cn-hangzhou.aliyuncs.com "bucket": os.getenv("ALIYUN_OSS_BUCKET"), }, # 腾讯云COS配置 "TENCENT_COS": { "secret_id": os.getenv("TENCENT_COS_SECRET_ID"), "secret_key": os.getenv("TENCENT_COS_SECRET_KEY"), "region": os.getenv("TENCENT_COS_REGION"), # 例如:ap-guangzhou "bucket": os.getenv("TENCENT_COS_BUCKET"), }, # 七牛云Kodo配置 "QINIU_KODO": { "access_key": os.getenv("QINIU_KODO_ACCESS_KEY"), "secret_key": os.getenv("QINIU_KODO_SECRET_KEY"), "bucket": os.getenv("QINIU_KODO_BUCKET"), "domain": os.getenv("QINIU_KODO_DOMAIN"), # CDN域名 }, # 华为云OBS配置 "HUAWEI_OBS": { "access_key_id": os.getenv("HUAWEI_OBS_ACCESS_KEY_ID"), "secret_access_key": os.getenv("HUAWEI_OBS_SECRET_ACCESS_KEY"), "server": os.getenv( "HUAWEI_OBS_SERVER" ), # 例如:obs.cn-north-4.myhuaweicloud.com "bucket": os.getenv("HUAWEI_OBS_BUCKET"), }, } # 是否启用前端渲染 FRONTEND_ENABLED = _get_bool_env("FRONTEND_ENABLED", False) # 前端页面文件路径 FRONTEND_PATH = os.getenv("FRONTEND_PATH", "dist") # 业务日志配置 SYSLOG_MAX_BODY_CHARS = 2048 SYSLOG_ITEMS_SAMPLE = 5 # 权限配置 PERMISSION_CONFIG = { # 超级管理员角色代码(自动跳过所有权限检查) "SUPER_ADMIN_ROLE": "SUPER_ADMIN", # 默认错误消息 "DEFAULT_ERROR_MESSAGE": "无权访问!", # 默认错误代码 "DEFAULT_ERROR_CODE": 403, # 是否启用超级管理员跳过权限检查 "SKIP_SUPER_ADMIN_DEFAULT": True, } # ERP ODBC配置 ERP_ODBC_CONFIG = { "enabled": _get_bool_env("ERP_ODBC_ENABLED", True), # 是否启用ODBC "dsn": "YHC-test", # ODBC数据源名称 "pool_size": 5, # 连接池大小 "max_overflow": 10, # 最大溢出连接数 "pool_timeout": 30, # 获取连接超时(秒) "pool_recycle": 3600, # 连接回收时间(秒) } # ERP API配置 ERP_API_CONFIG = { "base_url": "https://192.168.21.112:8001", # ERP接口地址 "language_code": "zh", # 语言代码 "company_number": "001_1.1", # 公司编号 "username": os.getenv("ERP_API_USERNAME", ""), # 默认用户名(可选) "password": os.getenv("ERP_API_PASSWORD", ""), # 默认密码(可选) "verify_ssl": False, # 是否验证SSL "timeout": 30, # 请求超时时间(秒) } class DevConfig(BaseConfig): """开发环境配置""" DEBUG = True TESTING = False # 开发环境数据库 SQLALCHEMY_DATABASE_URI = os.getenv( "DATABASE_URL", f"sqlite:///{BASE_DIR}/runtime/iti-flask_dev.db" ) SQLALCHEMY_ECHO = True # 开发环境打印 SQL SQLALCHEMY_SHOW_ERROR_DETAILS = True # JSON 配置(开发环境:格式化输出,方便调试) JSON_INDENT = 2 # 缩进 2 个空格 JSON_SEPARATORS = (", ", ": ") # 使用空格分隔 # JWT 开发环境配置 JWT_ACCESS_TOKEN_EXPIRES = timedelta(hours=24) # 开发环境延长有效期 # JWT_ACCESS_TOKEN_EXPIRES = timedelta(seconds=5) # 测试过期 # 限流配置(开发环境较宽松) RATELIMIT_ENABLED = True RATELIMIT_DEFAULT = "1000 per hour" class TestConfig(BaseConfig): """测试环境配置""" DEBUG = False TESTING = True # 测试数据库(使用内存数据库) SQLALCHEMY_DATABASE_URI = "sqlite:///:memory:" SQLALCHEMY_ECHO = False SQLALCHEMY_SHOW_ERROR_DETAILS = False # JSON 配置(测试环境:与生产一致) JSON_INDENT = None # 不缩进 JSON_SEPARATORS = (",", ":") # 紧凑分隔符 # 限流配置 RATELIMIT_ENABLED = False # 测试环境禁用限流 class ProdConfig(BaseConfig): """生产环境配置""" DEBUG = False TESTING = False # 生产环境必须使用环境变量 SECRET_KEY = os.getenv("SECRET_KEY") JWT_SECRET_KEY = os.getenv("JWT_SECRET_KEY") # 生产环境数据库 SQLALCHEMY_DATABASE_URI = os.getenv( "DATABASE_URL", f"sqlite:///{BASE_DIR}/runtime/iti-flask_prod.db" ) # 处理 PostgreSQL URL 格式 if SQLALCHEMY_DATABASE_URI and SQLALCHEMY_DATABASE_URI.startswith("postgres://"): SQLALCHEMY_DATABASE_URI = SQLALCHEMY_DATABASE_URI.replace( "postgres://", "postgresql://", 1 ) SQLALCHEMY_SHOW_ERROR_DETAILS = False # 限流配置(生产环境严格) RATELIMIT_STORAGE_URL = os.getenv("REDIS_URL", "memory://") RATELIMIT_DEFAULT = "100 per hour" # JSON 配置(生产环境:紧凑输出,减小体积) JSON_INDENT = None # 不缩进 JSON_SEPARATORS = (",", ":") # 紧凑分隔符 # 性能优化 SQLALCHEMY_POOL_SIZE = 10 SQLALCHEMY_POOL_TIMEOUT = 30 SQLALCHEMY_POOL_RECYCLE = 3600 # 配置字典 config = { "dev": DevConfig, "test": TestConfig, "prod": ProdConfig, "default": DevConfig, } def get_config(env_name=None): """ 根据环境名称获取配置类 Args: env_name: 环境名称 ('dev', 'test', 'prod') 如果为 None 则从环境变量 FLASK_ENV 读取 Returns: 配置类 """ if env_name is None: env_name = os.getenv("FLASK_ENV", "dev") return config.get(env_name, config["default"])