You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
iTi-Flask/iti/config.py

318 lines
9.8 KiB
Python

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import os
from datetime import timedelta
import json
from pathlib import Path
from typing import Any
# 项目根目录
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_dir: str | os.PathLike | None = None) -> bool:
"""从项目目录加载 .env 文件。"""
try:
from dotenv import load_dotenv
search_dir = Path(env_dir or os.getenv("ITI_ENV_DIR") or os.getcwd()).resolve()
env_name = os.getenv("FLASK_ENV", "dev")
env_files = [
".env.local",
f".env.{env_name}",
".env",
]
for env_file in env_files:
env_path = search_dir / env_file
if env_path.exists():
load_dotenv(env_path, override=False)
return True
return False
except ImportError:
return False
# 在定义配置类之前加载环境变量
_load_env_file()
class BaseConfig:
"""基础配置类 - 所有环境共享的配置"""
BASE_DIR = BASE_DIR
# 应用配置
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_URL = "memory://"
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 # 200MBFlask 上传大小限制
# 缓存配置
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"),
},
}
# 是否启用业务项目自带 SPA 静态文件承载。
# 框架不内置前端产物;启用后从业务项目 FRONTEND_PATH 读取文件。
FRONTEND_ENABLED = _get_bool_env("FRONTEND_ENABLED", False)
FRONTEND_PATH = os.getenv("FRONTEND_PATH", "")
# 服务调用配置。业务项目按需添加具体服务。
SERVICES: dict[str, dict[str, Any]] = {}
# 轻量任务配置。多进程部署时只应在一个专用实例启用。
TASKS_ENABLED = False
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"])