# +====================================================================== # | DjangoAdmin 企业数字化开发平台 [ 赋能开发者,助力企业发展 ] # +---------------------------------------------------------------------- # | Copyright (c) 2021-2026 小蚂蚁云团队 版权所有 # +---------------------------------------------------------------------- # | 开源协议: Apache-2.0 # | 重要声明: 未经书面许可,不得移除或修改本框架相关版权标识。 # +---------------------------------------------------------------------- # | 官方网站: https://www.djangoadmin.cn # +---------------------------------------------------------------------- # | 研发团队: 小蚂蚁云团队 荣誉出品 # +====================================================================== """ 数据库配置模块 ============================================================= 负责配置数据库连接及相关参数,包含连接池、SSL 加密等,支持多数据库驱动: mysql(默认)/ postgresql(pgsql)/ mssql / sqlite / oracle。 依赖:config/__init__.py 中的 _load_env_file() 已提前加载 .env """ import os from urllib.parse import quote_plus from config.app import FLASK_SECRET_KEY, FLASK_UPLOAD_DIR # ============================================================ # 数据库环境变量 # ============================================================ def _normalize_driver(driver: str) -> str: """驱动别名归一化:别名 → 规范名;已是规范名的驱动原样返回。 - 规范名:mysql / postgresql / mssql / sqlite / oracle - 别名:postgres、pgsql → postgresql;sqlserver → mssql;sqlite3 → sqlite - oracle 本身即规范名,无需映射(经 fallback 直接透传),表中显式列出便于自文档化 与 scripts/migrate_db.py 共用(make_url 也调用),保证 .env / CLI 两端别名一致。 """ return { 'postgres': 'postgresql', 'pgsql': 'postgresql', 'sqlserver': 'mssql', 'sqlite3': 'sqlite', 'oracle': 'oracle', # 规范名之一,显式列出便于自文档化 }.get(str(driver).lower(), str(driver).lower()) def _sqlite_url(database: str) -> str: """SQLite 连接 URL:DB_DATABASE 即文件路径(内存库用 ':memory:',Windows 反斜杠转正斜杠)。""" if database == ':memory:': return 'sqlite://' return f"sqlite:///{database.replace(chr(92), '/')}" # 数据库驱动(mysql / postgresql / mssql / sqlite / oracle,支持别名见 _normalize_driver) DB_DRIVER = _normalize_driver(os.getenv('DB_DRIVER', 'mysql')) # 数据库地址 DB_HOST = os.getenv('DB_HOST', '127.0.0.1') # 各驱动默认端口(未显式配置 DB_PORT 时按驱动回退) _DEFAULT_PORT = { 'mysql': 3306, 'pgsql': 5432, 'postgresql': 5432, 'mssql': 1433, 'oracle': 1521, 'sqlite': 0, # sqlite 无端口 } # 数据库端口(MySQL默认3306,PostgreSQL默认5432,MSSQL默认1433,Oracle默认1521) DB_PORT = int(os.getenv('DB_PORT', _DEFAULT_PORT.get(DB_DRIVER, 3306))) # 数据库名称 DB_DATABASE = os.getenv('DB_DATABASE', 'djangoadmin.flask.elevue') # 数据库账号 DB_USERNAME = os.getenv('DB_USERNAME', 'root') # 数据库密码 DB_PASSWORD = os.getenv('DB_PASSWORD', '') # 数据表前缀(用于区分同一数据库中不同应用的数据表) DB_PREFIX = os.getenv('DB_PREFIX', 'flask_') # 是否开启调试模式:True-打印SQL日志,False-不打印 DB_DEBUG = os.getenv('DB_DEBUG', 'True') == 'True' # 是否启用数据库SSL加密连接(生产环境建议开启,防止数据明文传输) DB_SSL = os.getenv('DB_SSL', 'False') == 'True' # SSL CA证书路径(用于验证服务端证书,留空则信任系统CA) DB_SSL_CA = os.getenv('DB_SSL_CA', '') # SSL 客户端证书路径(双向认证时使用) DB_SSL_CERT = os.getenv('DB_SSL_CERT', '') # SSL 客户端私钥路径(双向认证时使用) DB_SSL_KEY = os.getenv('DB_SSL_KEY', '') # ============================================================ # 连接池配置 # ============================================================ # 连接池常驻连接数(默认10,生产环境建议根据并发量调整) DB_POOL_SIZE = int(os.getenv('DB_POOL_SIZE', 10)) # 超出pool_size后允许的最大额外连接(默认10,总连接数=pool_size+max_overflow) DB_MAX_OVERFLOW = int(os.getenv('DB_MAX_OVERFLOW', 10)) # 连接自动回收时间/秒(默认1800,应小于MySQL的wait_timeout,防止"MySQL server has gone away") DB_POOL_RECYCLE = int(os.getenv('DB_POOL_RECYCLE', 1800)) # 获取连接的超时时间/秒(默认30,超时抛出TimeoutError) DB_POOL_TIMEOUT = int(os.getenv('DB_POOL_TIMEOUT', 30)) # 使用前是否检测连接存活(默认True,防止使用已被MySQL关闭的连接) DB_POOL_PRE_PING = os.getenv('DB_POOL_PRE_PING', 'True') == 'True' # 方言 → SQLAlchemy URL 前缀映射(多库支持:只改 DB_DRIVER + 账号即可切换) _DB_URI_PREFIX = { 'mysql': 'mysql+pymysql', 'pgsql': 'postgresql+psycopg2', 'postgresql': 'postgresql+psycopg2', 'mssql': 'mssql+pymssql', 'sqlite': 'sqlite', 'oracle': 'oracle+oracledb', } def _build_database_url() -> str: """ 按 DB_DRIVER 构建数据库连接 URL。 支持:mysql(默认)/ postgresql / mssql / sqlite / oracle。 各库差异(端口、账号、ssl、charset、sqlite 无 host、oracle 用 SERVICE_NAME)在此统一封装。 """ prefix = _DB_URI_PREFIX.get(DB_DRIVER, 'mysql+pymysql') # SQLite:无 host/port/账号,DB_DATABASE 即文件路径(内存库用 ':memory:') if DB_DRIVER == 'sqlite': return _sqlite_url(DB_DATABASE) cred = f"{quote_plus(DB_USERNAME)}:{quote_plus(DB_PASSWORD)}@{DB_HOST}:{DB_PORT}" # Oracle:无"数据库名",DB_DATABASE 即 SERVICE_NAME(如 XEPDB1),走 URL 查询参数 if DB_DRIVER == 'oracle': return f"{prefix}://{cred}/?service_name={quote_plus(DB_DATABASE)}" url = f"{prefix}://{cred}/{DB_DATABASE}" # MySQL(含未知驱动回退到 mysql):显式 utf8mb4 保证中文正确 if prefix == 'mysql+pymysql': url += "?charset=utf8mb4" # PostgreSQL:SSL 走 URL 参数(MySQL/MSSQL 的 SSL 在 _build_engine_options / pymssql 内配置) elif DB_DRIVER in ('pgsql', 'postgresql') and DB_SSL: url += "?sslmode=require" return url # 数据库连接URL(方便SQLAlchemy直接使用) DATABASE_URL = _build_database_url() def friendly_db_error(e: Exception) -> str: """ 把数据库连接异常翻译成可读的中文提示。 处理 psycopg2 在中文 Windows 下返回 GBK 错误消息、被按 UTF-8 解码抛出 UnicodeDecodeError 的怪癖:先还原字节并按 GBK 解码,再匹配常见错误。 """ raw = str(e) if isinstance(e, UnicodeDecodeError): # UnicodeDecodeError.args = (encoding, bytes, start, end, reason);args[1] 是原始字节 try: raw = e.args[1].decode('gbk', errors='ignore') except Exception: raw = str(e) low = raw.lower() if any(k in low for k in ('password authentication failed', '28p01', '认证失败', '密码错误', 'ora-01017')): return '数据库认证失败:用户名或密码错误,请检查 .env 的 DB_USERNAME / DB_PASSWORD' # 数据库不存在(PG SQLSTATE 3d000 / "database ... does not exist" / "数据库...不存在") if '3d000' in low or ('database' in low and 'does not exist' in low) or ('数据库' in raw and '不存在' in raw): return '数据库不存在:请先在目标库创建对应数据库(如 CREATE DATABASE <库名>)' # 列/表类型不兼容(PG "type ... does not exist" / "类型...不存在"),区别于"数据库不存在" if ('type' in low and 'does not exist' in low) or ('类型' in raw and '不存在' in raw): return '目标表结构或列类型与数据库不兼容:请检查模型列类型(如 DATETIME 需用通用 DateTime)' # Oracle 服务名/监听解析(ORA-12514 未知服务、ORA-12505 未知 SID、ORA-12154 无法解析连接标识符) if any(k in low for k in ('ora-12514', 'ora-12505', 'ora-12154', 'could not resolve the connect identifier')): return 'Oracle 服务名/监听解析失败:请检查 .env 的 DB_DATABASE(Oracle 填 SERVICE_NAME,如 XEPDB1)与监听配置' if any(k in low for k in ('connection refused', '拒绝连接', 'cannot connect', 'target actively refused', 'ora-12541', 'ora-12560', 'ora-12518', 'ora-12170', 'ora-01034', 'oracle not available', 'no listener')): return '数据库连接被拒绝:请检查 DB_HOST / DB_PORT 与数据库服务是否启动' if any(k in low for k in ('timeout', '超时', 'timed out')): return '数据库连接超时:请检查网络与 DB_HOST / DB_PORT' if any(k in low for k in ('no module named', 'is not installed', '驱动未安装')): return '数据库驱动未安装:请执行 pip install -r requirements.txt 安装对应驱动' return f'数据库连接失败:{raw}' def _build_engine_options(): """ 构建SQLAlchemy引擎选项 包含连接池配置和SSL加密配置。 连接池参数通过SQLALCHEMY_ENGINE_OPTIONS传递, 确保所有连接管理策略集中在一处。 """ options = { 'pool_size': DB_POOL_SIZE, 'max_overflow': DB_MAX_OVERFLOW, 'pool_recycle': DB_POOL_RECYCLE, 'pool_timeout': DB_POOL_TIMEOUT, 'pool_pre_ping': DB_POOL_PRE_PING, } # SSL:MySQL 走 connect_args;PG 走 URL sslmode(_build_database_url);MSSQL 由 pymssql 处理; # Oracle:oracledb thin 模式默认走系统 CA,TLS 用 URL tcp+tls 或在 connect_args 传 ssl_context,本处不做额外处理 if DB_SSL and DB_DRIVER == 'mysql': ssl_config = {} if DB_SSL_CA: ssl_config['ca'] = DB_SSL_CA if DB_SSL_CERT: ssl_config['cert'] = DB_SSL_CERT if DB_SSL_KEY: ssl_config['key'] = DB_SSL_KEY # 无证书路径时使用默认SSL(信任系统CA) if not ssl_config: ssl_config = True options['connect_args'] = {'ssl': ssl_config} return options class DatabaseConfig: """ 数据库配置类 包含 MySQL 连接、SQLAlchemy 设置、路径等配置 """ # ============================================================ # 基础配置 # ============================================================ # 调试模式(从环境变量读取) DEBUG = DB_DEBUG # Flask 秘钥(用于 session 加密等) # 启动时校验:未配置或过短则拒绝启动 if not FLASK_SECRET_KEY or len(FLASK_SECRET_KEY) < 16: raise RuntimeError( "FLASK_SECRET_KEY 未配置或过短!请在 .env 中设置至少16字节的强随机密钥。\n" "生成方式:python -c \"import secrets; print(secrets.token_hex(32))\"" ) SECRET_KEY = FLASK_SECRET_KEY # ============================================================ # CSRF 防护配置 # ============================================================ # 本项目使用 JWT(Authorization 头)认证,非 Cookie/Session, # 天然防 CSRF,因此 WTForms CSRF 保护无需开启。 WTF_CSRF_CHECK_DEFAULT = False WTF_CSRF_ENABLED = False # ============================================================ # 数据库连接配置(按 DB_DRIVER 驱动切换,见 _build_database_url) # ============================================================ SQLALCHEMY_DATABASE_URI = DATABASE_URL # 不追踪对象修改(提升性能) SQLALCHEMY_TRACK_MODIFICATIONS = False # 是否打印 SQL 语句(调试模式下开启) SQLALCHEMY_ECHO = DB_DEBUG # 数据库连接引擎选项(连接池 + SSL加密) SQLALCHEMY_ENGINE_OPTIONS = _build_engine_options() # ============================================================ # 路径配置 # ============================================================ # 配置目录路径 BASE_DIR = os.path.dirname(os.path.abspath(__file__)) # 静态文件目录 STATIC_DIR = os.path.join(BASE_DIR, '../static') # 模板文件目录 TEMPLATE_DIR = os.path.join(BASE_DIR, '../templates') # 文件上传目录 FLASK_UPLOAD_DIR = FLASK_UPLOAD_DIR # 模块测试入口 if __name__ == '__main__': print(f"数据库连接: {DatabaseConfig.SQLALCHEMY_DATABASE_URI}") print(f"调试模式: {DatabaseConfig.DEBUG}") print(f"文件上传目录: {DatabaseConfig.FLASK_UPLOAD_DIR}")