|
|
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 = {
|
|
|
"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"])
|