modify: config sqlite 配置规范

This commit is contained in:
2026-01-31 19:26:28 +08:00
parent ee899b2eb3
commit f7dbe571cc
5 changed files with 2158 additions and 147 deletions
+1953
View File
File diff suppressed because it is too large Load Diff
+20 -24
View File
@@ -1,22 +1,30 @@
[default] [default]
app.name = "{{ cookiecutter.project_name }}" app.name = "{{ cookiecutter.project_name }}"
app.version = "{{ cookiecutter.author_name }}" app.version = "{{ cookiecutter.project_name }}"
app.description = "基于 FastAPI 和 Tortoise-ORM 的 chongming 打包脚手架" app.description = "基于 FastAPI 和 Tortoise-ORM 的 chongming 打包脚手架"
app.debug = false app.debug = false
# env = "production" # env = "production"
[server]
host = "0.0.0.0"
port = 8000
reload = false
workers = 4
[database] [database]
url = "sqlite://db.sqlite3" type = "sqlite"
# PostgreSQL 示例: "postgres://user:pass@localhost:5432/dbname"
# MySQL 示例: "mysql://user:pass@localhost:3306/dbname"
generate_schemas = true # 生产环境使用迁移工具 generate_schemas = true # 生产环境使用迁移工具
[database.sqlite]
engine = "tortoise.backends.sqlite"
file_path = "db.sqlite3"
journal_mode = "WAL"
busy_timeout = 10000
maxsize = 10
minsize = 1
cache_size = -2000
synchronous = "NORMAL"
temp_store = "MEMORY"
mmap_size = 1073741824
foreign_keys = true
auto_vacuum = "INCREMENTAL"
max_cached_statements = 100
readonly = false
[security] [security]
secret_key = "your-secret-key-change-in-production" secret_key = "your-secret-key-change-in-production"
algorithm = "HS256" algorithm = "HS256"
@@ -32,10 +40,6 @@ port = 8000
reload = true reload = true
# workers = 4 # workers = 4
[development.database]
url = "sqlite://dev.db"
generate_schemas = true
[development.logging] [development.logging]
level = "DEBUG" level = "DEBUG"
file = "logs/dev.log" file = "logs/dev.log"
@@ -56,16 +60,12 @@ port = 8000
reload = false reload = false
workers = 1 workers = 1
[production.database]
url = "sqlite://prod.db"
generate_schemas = true
[production.logging] [production.logging]
level = "INFO" level = "INFO"
file = "logs/prod.log" file = "logs/prod.log"
[production.cors] [production.cors]
allow_origins = ["https://yourdomain.com"] allow_origins = ["https://yourdomain.com", "http://localhos:8000", "http://127.0.0.1:8000"]
allow_credentials = true allow_credentials = true
allow_methods = ["GET", "POST", "PUT", "DELETE"] allow_methods = ["GET", "POST", "PUT", "DELETE"]
allow_headers = ["Authorization", "Content-Type"] allow_headers = ["Authorization", "Content-Type"]
@@ -78,9 +78,5 @@ path = ["resources/app.mbank", "resources/plugins.mbank"]
type = "svfs" type = "svfs"
path = "resources/application.vfs" path = "resources/application.vfs"
[redis]
enabled = false
url = "redis://localhost:6379"
[cache] [cache]
default_ttl = 300 default_ttl = 300
@@ -1,22 +1,30 @@
[default] [default]
app.name = "{{ cookiecutter.project_name }}" app.name = "{{ cookiecutter.project_name }}"
app.version = "{{ cookiecutter.author_name }}" app.version = "{{ cookiecutter.project_name }}"
app.description = "基于 FastAPI 和 Tortoise-ORM 的 chongming 打包脚手架" app.description = "基于 FastAPI 和 Tortoise-ORM 的 chongming 打包脚手架"
app.debug = false app.debug = false
env = "production" env = "production"
[server]
host = "0.0.0.0"
port = 8000
reload = false
workers = 4
[database] [database]
url = "sqlite://db.sqlite3" type = "sqlite"
# PostgreSQL 示例: "postgres://user:pass@localhost:5432/dbname"
# MySQL 示例: "mysql://user:pass@localhost:3306/dbname"
generate_schemas = true # 生产环境使用迁移工具 generate_schemas = true # 生产环境使用迁移工具
[database.sqlite]
engine = "tortoise.backends.sqlite"
file_path = "db.sqlite3"
journal_mode = "WAL"
busy_timeout = 10000
maxsize = 10
minsize = 1
cache_size = -2000
synchronous = "NORMAL"
temp_store = "MEMORY"
mmap_size = 1073741824
foreign_keys = true
auto_vacuum = "INCREMENTAL"
max_cached_statements = 100
readonly = false
[security] [security]
secret_key = "your-secret-key-change-in-production" secret_key = "your-secret-key-change-in-production"
algorithm = "HS256" algorithm = "HS256"
@@ -32,10 +40,6 @@ port = 8000
reload = true reload = true
# workers = 4 # workers = 4
[development.database]
url = "sqlite://dev.db"
generate_schemas = true
[development.logging] [development.logging]
level = "DEBUG" level = "DEBUG"
file = "logs/dev.log" file = "logs/dev.log"
@@ -56,16 +60,12 @@ port = 8000
reload = false reload = false
workers = 1 workers = 1
[production.database]
url = "sqlite://prod.db"
generate_schemas = true
[production.logging] [production.logging]
level = "INFO" level = "INFO"
file = "logs/prod.log" file = "logs/prod.log"
[production.cors] [production.cors]
allow_origins = ["https://yourdomain.com"] allow_origins = ["https://yourdomain.com", "http://localhos:8000", "http://127.0.0.1:8000"]
allow_credentials = true allow_credentials = true
allow_methods = ["GET", "POST", "PUT", "DELETE"] allow_methods = ["GET", "POST", "PUT", "DELETE"]
allow_headers = ["Authorization", "Content-Type"] allow_headers = ["Authorization", "Content-Type"]
@@ -78,9 +78,5 @@ path = ["resources/app.mbank", "resources/plugins.mbank"]
type = "svfs" type = "svfs"
path = "resources/application.vfs" path = "resources/application.vfs"
[redis]
enabled = false
url = "redis://localhost:6379"
[cache] [cache]
default_ttl = 300 default_ttl = 300
@@ -1,135 +1,198 @@
""" """
配置管理模块 配置管理模块 - 简化版本
基于 Pydantic Settings 和 TOML 文件 基于 Pydantic Settings 和 TOML 文件
""" """
from typing import Optional, Dict, Any
from typing import Optional, List, Dict, Any
from pydantic_settings import BaseSettings from pydantic_settings import BaseSettings
from pydantic import Field from pydantic import model_validator
import toml import toml
from pathlib import Path from pathlib import Path
import sys
import os
try: try:
from ...utils.launch import args from ...utils.launch import args
except: except:
from utils.launch import args # type: ignore from utils.launch import args # type: ignore
class SQLiteConfig(BaseSettings):
"""SQLite 配置"""
engine: str = "tortoise.backends.sqlite"
file_path: str = "db.sqlite3"
journal_mode: str = "WAL"
busy_timeout: int = 10000
maxsize: int = 10
minsize: int = 1
cache_size: int = -2000
synchronous: str = "NORMAL"
temp_store: str = "MEMORY"
mmap_size: int = 1073741824
foreign_keys: bool = True
auto_vacuum: str = "INCREMENTAL"
max_cached_statements: int = 100
readonly: bool = False
@property
def tortoise_config(self) -> Dict[str, Any]:
return {
"default": {
"engine": self.engine,
"credentials": {
"file_path": self.file_path,
"journal_mode": self.journal_mode,
"busy_timeout": self.busy_timeout,
"maxsize": self.maxsize,
"minsize": self.minsize,
"cache_size": self.cache_size,
"synchronous": self.synchronous,
"temp_store": self.temp_store,
"mmap_size": self.mmap_size,
"foreign_keys": self.foreign_keys,
"auto_vacuum": self.auto_vacuum,
"max_cached_statements": self.max_cached_statements,
"readonly": self.readonly,
},
}
}
class Settings(BaseSettings): class Settings(BaseSettings):
"""应用配置""" """应用配置"""
# 应用信息 # 应用信息
app_name: str = "FastAPI Demo" app_name: str = "FastAPI Demo"
app_version: str = "1.0.0" app_version: str = "1.0.0"
description: str = "FastAPI 应用" description: str = "FastAPI 应用"
# 服务器配置 # 服务器配置
host: str = "0.0.0.0" host: str = "0.0.0.0"
port: int = 8000 port: int = 8000
reload: bool = False reload: bool = False
workers: int = 4 workers: int = 1 # 开发环境默认1,避免与 reload 冲突
# 数据库配置 database_type: str = "sqlite"
database_url: str = "sqlite://db.sqlite3"
generate_schemas: bool = False # SQLite 配置
sqlite_config: SQLiteConfig = SQLiteConfig()
# 环境配置 # 环境配置
env: str = "development" env: str = "development"
debug: bool = False debug: bool = False
# 数据库配置
generate_schemas: bool = False
# 日志配置 # 日志配置
logging_level: str = "INFO" logging_level: str = "INFO"
logging_file: Optional[str] = None logging_file: Optional[str] = None
# CORS 配置 # CORS 配置
cors_allow_origins: list = ["*"] cors_allow_origins: List[str] = ["*"]
cors_allow_credentials: bool = True cors_allow_credentials: bool = True
cors_allow_methods: list = ["*"] cors_allow_methods: List[str] = ["*"]
cors_allow_headers: list = ["*"] cors_allow_headers: List[str] = ["*"]
# 安全配置 # 安全配置
secret_key: str = "your-secret-key-change-in-production" secret_key: str = "your-secret-key-change-in-production"
algorithm: str = "HS256" algorithm: str = "HS256"
access_token_expire_minutes: int = 30 access_token_expire_minutes: int = 30
# 模块系统 # 模块系统
module_system_type: str = "local" module_system_type: str = "local"
module_system_path: list = ["plugins", "app"] module_system_path: List[str] = ["plugins", "app"]
class Config: class Config:
env_file = ".env" env_file = ".env"
extra = "ignore" extra = "ignore"
@model_validator(mode="after")
def validate_settings(self):
"""验证和调整设置"""
# 如果启用 reload,workers 必须为 1
if self.reload and self.workers != 1:
print(f"⚠️ reload 启用,将 workers 从 {self.workers} 调整为 1")
self.workers = 1
return self
@classmethod @classmethod
def from_toml(cls, config_path: Optional[str] = None) -> "Settings": def from_toml(cls, config_path: Optional[str] = None) -> "Settings":
"""从 TOML 文件加载配置""" """从 TOML 文件加载配置 - 简化版本"""
# 1. 确定配置文件路径
if config_path is None: if config_path is None:
# 尝试多个可能的配置路径 config_path = "config.toml"
possible_paths = [
"config.toml", config_file = Path(config_path)
"./config.toml", if not config_file.exists():
"../config.toml", print(f"⚠️ 配置文件不存在: {config_path},使用默认配置")
Path(__file__).parent.parent.parent / "config.toml", return cls()
]
for path in possible_paths:
if Path(path).exists():
config_path = str(path)
print(f"📄 加载配置文件: {config_path}")
break
if config_path is None:
print("⚠️ 未找到配置文件,使用默认配置")
return cls()
try: try:
config_file = Path(config_path) # 2. 加载 TOML 文件
if not config_file.exists():
print(f"⚠️ 配置文件不存在: {config_path}")
return cls()
config_data = toml.load(config_file) config_data = toml.load(config_file)
print(f"📄 加载配置文件: {config_path}")
# 获取默认配置
# 3. 确定环境
default_config = config_data.get("default", {}) default_config = config_data.get("default", {})
# 确定环境
env = default_config.get("env", "development") env = default_config.get("env", "development")
# 获取环境特定配置
env_config = config_data.get(env, {})
# 合并配置
merged_config = {**default_config, **env_config}
# 处理嵌套配置
for section in ["server", "database", "security", "logging", "cors", "module_system"]:
if section in config_data:
if isinstance(config_data[section], dict):
merged_config.update(config_data[section])
# 从环境特定配置中覆盖
for section in ["server", "database", "security", "logging", "cors", "module_system"]:
env_section_key = f"{env}.{section}"
if env_section_key in config_data:
if isinstance(config_data[env_section_key], dict):
merged_config.update(config_data[env_section_key])
print(f"🌍 环境: {env}") print(f"🌍 环境: {env}")
return cls(**merged_config)
# 4. 获取环境特定配置
env_config = config_data.get(env, {})
# 5. 构建配置字典
config_dict = {}
# 5.1 处理默认配置
for key, value in default_config.items():
if key.startswith("app."):
config_dict[key.replace("app.", "app_")] = value
elif key.startswith("server."):
config_dict[key.replace("server.", "")] = value
elif key == "env":
config_dict["env"] = value
# 5.2 处理环境特定配置
for key, value in env_config.items():
if key.startswith("server."):
config_dict[key.replace("server.", "")] = value
elif key == "debug":
config_dict["debug"] = value
# 5.3 处理 database 配置
database_config = {}
if "database" in env_config:
database_type = env_config["database"].get("type", "sqlite")
database_config = env_config["database"].get(database_type, {})
config_dict["database_type"] = database_type
# 5.4 处理 generate_schemas
if "database" in env_config:
db_config = env_config["database"]
if isinstance(db_config, dict) and "generate_schemas" in db_config:
config_dict["generate_schemas"] = db_config["generate_schemas"]
if database_type == "sqlite":
if database_config:
config_dict["sqlite_config"] = SQLiteConfig(**database_config)
return cls(**config_dict)
except Exception as e: except Exception as e:
print(f"❌ 加载配置失败: {e}") print(f"❌ 加载配置失败: {e}")
import traceback import traceback
traceback.print_exc() traceback.print_exc()
return cls() return cls()
# 全局配置实例 # 全局配置实例
_settings = None _settings = None
def get_settings() -> Settings: def get_settings() -> Settings:
"""获取配置实例(单例模式)""" """获取配置实例(单例模式)"""
global _settings global _settings
if _settings is None: if _settings is None:
_settings = Settings.from_toml(args.config) _settings = Settings.from_toml(args.config)
return _settings return _settings
@@ -1,19 +1,21 @@
""" """
Tortoise-ORM 数据库配置 Tortoise-ORM 数据库配置
""" """
from typing import Optional
from tortoise import Tortoise from tortoise import Tortoise
from tortoise.contrib.fastapi import register_tortoise from tortoise.contrib.fastapi import register_tortoise
from .config import get_settings from .config import get_settings
settings = get_settings()
async def init_db(run_mode: str): async def init_db(run_mode: str):
"""初始化数据库连接""" """初始化数据库连接"""
settings = get_settings() db_config = getattr(settings, f"{settings.database_type}_config").tortoise_config
if run_mode == "production": if run_mode == "production":
db_config = { db_config = {
"connections": { "connections": db_config,
"default": settings.database_url
},
"apps": { "apps": {
"models": { "models": {
"models": ["app.models", "aerich.models"], "models": ["app.models", "aerich.models"],
@@ -23,11 +25,10 @@ async def init_db(run_mode: str):
"use_tz": False, "use_tz": False,
"timezone": "UTC", "timezone": "UTC",
} }
elif run_mode == "development": elif run_mode == "development":
db_config = { db_config = {
"connections": { "connections": db_config,
"default": settings.database_url
},
"apps": { "apps": {
"models": { "models": {
"models": ["{{ cookiecutter.project_name }}.app.models", "aerich.models"], "models": ["{{ cookiecutter.project_name }}.app.models", "aerich.models"],
@@ -39,9 +40,9 @@ async def init_db(run_mode: str):
} }
else: else:
raise ValueError("Invalid run mode") raise ValueError("Invalid run mode")
await Tortoise.init(db_config) await Tortoise.init(db_config)
# 根据配置决定是否生成表结构 # 根据配置决定是否生成表结构
if settings.generate_schemas: if settings.generate_schemas:
print("🗃️ 生成数据库表结构...") print("🗃️ 生成数据库表结构...")
@@ -49,41 +50,44 @@ async def init_db(run_mode: str):
print("✅ 数据库表结构生成完成") print("✅ 数据库表结构生成完成")
else: else:
print("⏭️ 跳过数据库表结构生成(建议使用迁移工具)") print("⏭️ 跳过数据库表结构生成(建议使用迁移工具)")
print(f"✅ 数据库连接成功: {settings.database_url}") print(f"✅ 数据库连接成功: {db_config}")
async def close_db(): async def close_db():
"""关闭数据库连接""" """关闭数据库连接"""
await Tortoise.close_connections() await Tortoise.close_connections()
print("✅ 数据库连接已关闭") print("✅ 数据库连接已关闭")
def register_db(app, run_mode: str): def register_db(app, run_mode: str):
"""将数据库注册到 FastAPI 应用""" """将数据库注册到 FastAPI 应用"""
settings = get_settings()
modules_path = "app.models" if run_mode == "production" else "{{ cookiecutter.project_name }}.app.models"
modules_path = "app.models" if run_mode == "production" else "{{ cookiecutter.project_name }}.app.models"
file_path = getattr(settings, f"{settings.database_type}_config").file_path
register_tortoise( register_tortoise(
app, app,
db_url=settings.database_url, db_url=f"sqlite://{file_path}",
modules={"models": [modules_path]}, modules={"models": [modules_path]},
generate_schemas=settings.generate_schemas, generate_schemas=settings.generate_schemas,
add_exception_handlers=True, add_exception_handlers=True,
) )
return app return app
# 在 app/core/database.py 中添加 # 在 app/core/database.py 中添加
from aerich import Command from aerich import Command
async def run_migrations(): async def run_migrations():
"""运行数据库迁移""" """运行数据库迁移"""
try: try:
command = Command( command = Command(
tortoise_config={ tortoise_config={
"connections": { "connections": getattr(
"default": get_settings().database_url settings, f"{settings.database_type}_config"
}, ).tortoise_config,
"apps": { "apps": {
"models": { "models": {
"models": ["app.models", "aerich.models"], "models": ["app.models", "aerich.models"],
@@ -91,13 +95,12 @@ async def run_migrations():
} }
}, },
}, },
app="models" app="models",
) )
# 检查并应用迁移 # 检查并应用迁移
await command.init() await command.init()
await command.upgrade() await command.upgrade()
print("✅ 数据库迁移完成") print("✅ 数据库迁移完成")
except Exception as e: except Exception as e:
print(f"⚠️ 数据库迁移失败: {e}") print(f"⚠️ 数据库迁移失败: {e}")