FastAPI+React19 ERP系统实战 第02期

一、搭建环境

1.1 创建Python虚拟环境

切换Python版本:

bash 复制代码
pyenv local 3.12

创建虚拟环境:

bash 复制代码
python -m venv venv

激活虚拟环境:

bash 复制代码
venv\Scripts\activate

1.2 安装FastAPI项目依赖

requirements.txt

bash 复制代码
fastapi==0.109.1
uvicorn==0.24.0
PyYAML==6.0.1
loguru==0.7.2
sqlalchemy>=2.0.25
aiomysql>=0.2.0
cryptography>=42.0.0
colorama>=0.4.6

安装依赖:

bash 复制代码
pip install -r requirements.txt

1.3 第一个FastAPI接口

main.py

python 复制代码
from fastapi import FastAPI

# 创建一个FastAPI的实例对象
app = FastAPI()


# 定义一个简单的接口
@app.get("/")
async def root():
    return {"message": "Hello World"}


# 运行FastAPI应用
if __name__ == "__main__":
    import uvicorn

    uvicorn.run(
        app,
        host="0.0.0.0",
        port=8000,
    )

二、FastAPI通用配置

2.1 配置文件

config.yaml

yaml 复制代码
# =============================================================================
# 应用基础配置
# =============================================================================
app:
  name: "FastAPI通用脚手架"
  version: "1.0.0"
  description: "基于 FastAPI 的通用脚手架"
  author: "源滚滚AI编程"

# =============================================================================
# 服务器配置
# =============================================================================
server:
  host: "0.0.0.0"
  port: 8888
  debug: true
  reload: false
  workers: 1
  log_level: "INFO"

# =============================================================================
# API文档配置
# =============================================================================
docs:
  enabled: true
  docs_url: "/docs"
  redoc_url: "/redoc"
  openapi_url: "/openapi.json"

# =============================================================================
# 日志配置
# =============================================================================
logging:
  level: "INFO"
  format: "detail"
  enable_console: true
  enable_file: true
  dir: "logs"
  file: "app.log"
  error_file: "error.log"
  request_file: "request.log"
  max_size: "100 MB"
  retention: "30 days"
  compression: "gz"
  async: true
  buffer_size: 100
  sensitive_fields: [ "password", "token", "secret", "key", "authorization" ]


# =============================================================================
# 数据库配置
# =============================================================================
database:
  # 数据库连接信息
  username: root
  password: Ygg!@#123456
  host: 192.168.2.6
  port: 3306
  database: fastapi_template_test
  driver: aiomysql

  # 连接池配置
  pool_size: 5
  max_overflow: 10
  pool_timeout: 30
  pool_recycle: 1800  # MySQL默认超时时间是8小时,这里设置30分钟回收一次

  # SQLAlchemy配置
  echo: false  # 是否打印SQL语句
  echo_pool: false  # 是否打印连接池日志
  future: true  # 使用SQLAlchemy 2.0风格的API

  # 其他配置
  encoding: utf8mb4  # MySQL推荐的编码
  auto_flush: false
  auto_commit: false
  expire_on_commit: false

2.2 读取yaml配置

python 复制代码
import yaml

with open("config.yaml", "r", encoding="utf-8") as f:
    config = yaml.safe_load(f)
    print("配置信息:", config)

2.3 加载yaml配置

apps/common/config/yaml_loader.py

python 复制代码
import yaml

from functools import lru_cache
from pathlib import Path
from typing import Any, Dict, Optional, Type, TypeVar, Union, cast

from .base import CONFIG_FILE

T = TypeVar("T")


@lru_cache(maxsize=1)
def _load_yaml(file_path: str = CONFIG_FILE) -> Dict[str, any]:
    """加载yaml配置文件"""
    # 确保配置文件存在
    path = Path(file_path)
    if not path.exists():
        raise FileNotFoundError(f"配置文件不存在:{file_path}")

    # 加载yaml文件
    with open(file_path, "r", encoding="utf-8") as f:
        return yaml.safe_load(f) or {}


def _convert_type(value: Any, target_type: Type[T]) -> Optional[T]:
    """类型转换"""
    # 异常情况
    if value is None:
        return None

    # 如果已经是指定类型
    if isinstance(value, target_type):
        return value

    # 尝试转换
    try:
        if target_type is bool:
            return cast(T, str(value).lower()) in ("true", "1", "yes", "on")
        return target_type(value)
    except (ValueError, TypeError):
        return None


def get_config_value(path: str, default: T = None) -> T:
    """获取配置值,支持默认值和自动类型转换"""
    # 获取配置
    config = _load_yaml()

    # 通过路径获取值
    value = config
    for key in path.split("."):
        if not isinstance(value, dict) or key not in value:
            return default
        value = value[key]

    # 如果没有默认值,直接返回
    if default is None:
        return value

    # 类型转换
    converted = _convert_type(value, type(default))
    return converted if converted is not None else default

2.4 应用配置

apps/common/config/app_config.py

python 复制代码
from .yaml_loader import get_config_value


class AppConfig:
    """应用配置类"""
    name = get_config_value("app.name", "FastAPI Template")
    version = get_config_value("app.version", "0.1.0")
    description = get_config_value("app.description", "A FastAPI template project")
    author = get_config_value("app.author", "源滚滚AI编程")

2.5 服务配置

apps/common/config/server_config.py

python 复制代码
from .yaml_loader import get_config_value


class ServerConfig:
    """服务器配置类"""
    host = get_config_value("server.host", "0.0.0.0")
    port = get_config_value("server.port", 8888)
    debug = get_config_value("server.debug", False)
    reload = get_config_value("server.reload", False)
    workers = get_config_value("server.workers", 1)
    log_level = get_config_value("server.log_level", "INFO")

2.6 接口文档配置

apps/common/config/docs_config.py

python 复制代码
from .yaml_loader import get_config_value


class DocsConfig:
    """文档配置类"""
    enabled = get_config_value("docs.enabled", False)
    docs_url = get_config_value("docs.docs_url", "/docs")
    redoc_url = get_config_value("docs.redoc_url", "/redoc")
    openapi_url = get_config_value("docs.openapi_url", "/openapi.json")

2.7 日志配置

apps/common/config/logging_config.py

python 复制代码
from .yaml_loader import get_config_value

# 默认过滤字段
DEFAULT_SENSITIVE_FIELDS = [
    "password",
    "token",
    "secret",
    "key",
    "authorization",
]


class LoggingConfig:
    """日志配置类"""
    level = get_config_value("logging.level", "INFO")
    format = get_config_value("logging.format", "detail")
    enable_console = get_config_value("logging.enable_console", True)
    enable_file = get_config_value("logging.enable_file", False)
    dir = get_config_value("logging.dir", "logs")
    file = get_config_value("logging.file", "app.log")
    error_file = get_config_value("logging.error_file", "error.log")
    request_file = get_config_value("logging.request_file", "request.log")
    max_size = get_config_value("logging.max_size", "100 MB")
    retention = get_config_value("logging.retention", "30 days")
    compression = get_config_value("logging.compression", "gz")
    async_mode = get_config_value("logging.async", True)
    buffer_size = get_config_value("logging.buffer_size", 100)
    sensitive_fields = get_config_value("logging.sensitive_fields", DEFAULT_SENSITIVE_FIELDS)

2.8 数据库配置

apps/common/config/database_config.py

python 复制代码
from typing import ClassVar, Dict, Any
from urllib.parse import quote_plus

from .yaml_loader import get_config_value


class DatabaseConfig:
    """数据库配置类"""

    # 数据库连接信息
    username: ClassVar[str] = get_config_value("database.username", "root")
    password: ClassVar[str] = get_config_value("database.password", "")
    host: ClassVar[str] = get_config_value("database.host", "localhost")
    port: ClassVar[int] = get_config_value("database.port", 3306)
    database: ClassVar[str] = get_config_value("database.database", "test")
    driver: ClassVar[str] = get_config_value("database.driver", "aiomysql")

    # 连接池配置
    pool_size: ClassVar[int] = get_config_value("database.pool_size", 5)
    max_overflow: ClassVar[int] = get_config_value("database.max_overflow", 10)
    pool_timeout: ClassVar[int] = get_config_value("database.pool_timeout", 30)
    pool_recycle: ClassVar[int] = get_config_value("database.pool_recycle", 1800)

    # SQLAlchemy Engine配置
    echo: ClassVar[bool] = get_config_value("database.echo", False)
    echo_pool: ClassVar[bool] = get_config_value("database.echo_pool", False)
    future: ClassVar[bool] = get_config_value("database.future", True)

    # SQLAlchemy Session配置
    auto_flush: ClassVar[bool] = get_config_value("database.auto_flush", False)
    auto_commit: ClassVar[bool] = get_config_value("database.auto_commit", False)
    expire_on_commit: ClassVar[bool] = get_config_value("database.expire_on_commit", False)

    # MySQL配置
    charset: ClassVar[str] = get_config_value("database.charset", "utf8mb4")

    @classmethod
    def get_url(cls) -> str:
        """获取数据库URL"""
        # 对特殊字符进行编码
        encoded_username = quote_plus(cls.username)
        encoded_password = quote_plus(cls.password)

        return (
            f"mysql+{cls.driver}://"
            f"{encoded_username}:{encoded_password}@"
            f"{cls.host}:{cls.port}/"
            f"{cls.database}"
            f"?charset={cls.charset}"
        )

    @classmethod
    def get_engine_args(cls) -> Dict[str, Any]:
        """获取引擎连接参数"""
        return {
            "echo": cls.echo,
            "echo_pool": cls.echo_pool,
            "pool_size": cls.pool_size,
            "max_overflow": cls.max_overflow,
            "pool_timeout": cls.pool_timeout,
            "pool_recycle": cls.pool_recycle,
            "future": cls.future,
            "pool_pre_ping": True,  # 自动检测连接是否有效
        }

    @classmethod
    def get_session_args(cls) -> Dict[str, Any]:
        """获取会话配置参数"""
        return {
            "autoflush": cls.auto_flush,
            "autocommit": cls.auto_commit,
            "expire_on_commit": cls.expire_on_commit
        }

2.9 配置基础常量

apps/common/config/base.py

python 复制代码
import os

# 配置目录
CONFIG_DIR = os.path.dirname(os.path.abspath(__file__))
# 根目录
ROOT_DIR = os.path.abspath(os.path.join(CONFIG_DIR, '../../..'))
# 配置文件路径
CONFIG_FILE = os.path.join(ROOT_DIR, 'config.yaml')

2.10 检查配置模块

apps/common/config/check.py

python 复制代码
from pathlib import Path
from typing import Dict
from loguru import logger

from .base import CONFIG_FILE
from .yaml_loader import _load_yaml, get_config_value
from .app_config import AppConfig
from .server_config import ServerConfig
from .logging_config import LoggingConfig
from .docs_config import DocsConfig
from .database_config import DatabaseConfig


def check_config() -> Dict[str, bool]:
    """检查配置是否正常"""
    results = {
        'config_file': False,
        'app_config': False,
        'server_config': False,
        'logging_config': False,
        'docs_config': False,
        'database_config': False,
        'all_ok': False
    }

    try:
        # 检查配置文件
        config_path = Path(CONFIG_FILE)
        if config_path.exists() and config_path.is_file():
            try:
                _load_yaml()  # 尝试加载配置文件
                results['config_file'] = True
                logger.info("✓ 配置文件检查通过")
            except Exception as e:
                logger.error(f"× 配置文件格式错误: {str(e)}")
                return results
        else:
            logger.error(f"× 配置文件不存在: {CONFIG_FILE}")
            return results

        # 检查应用配置
        try:
            if AppConfig.name and AppConfig.version:
                results['app_config'] = True
                logger.info("✓ 应用配置检查通过")
            else:
                logger.error("× 应用配置不完整")
        except Exception as e:
            logger.error(f"× 应用配置检查失败: {str(e)}")

        # 检查服务器配置
        try:
            if (isinstance(ServerConfig.port, int) and
                    0 < ServerConfig.port < 65536):
                results['server_config'] = True
                logger.info("✓ 服务器配置检查通过")
            else:
                logger.error("× 服务器端口配置无效")
        except Exception as e:
            logger.error(f"× 服务器配置检查失败: {str(e)}")

        # 检查日志配置
        try:
            log_dir = Path(LoggingConfig.dir)
            if (LoggingConfig.level in ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] and
                    (not LoggingConfig.enable_file or log_dir.parent.exists())):
                results['logging_config'] = True
                logger.info("✓ 日志配置检查通过")
            else:
                logger.error("× 日志配置无效")
        except Exception as e:
            logger.error(f"× 日志配置检查失败: {str(e)}")

        # 检查文档配置
        try:
            if isinstance(DocsConfig.enabled, bool):
                results['docs_config'] = True
                logger.info("✓ 文档配置检查通过")
            else:
                logger.error("× 文档配置无效")
        except Exception as e:
            logger.error(f"× 文档配置检查失败: {str(e)}")

        # 检查数据库配置
        try:
            if (DatabaseConfig.host and
                    isinstance(DatabaseConfig.port, int) and
                    0 < DatabaseConfig.port < 65536 and
                    DatabaseConfig.username and
                    DatabaseConfig.database):
                results['database_config'] = True
                logger.info("✓ 数据库配置检查通过")
            else:
                logger.error("× 数据库配置不完整")
        except Exception as e:
            logger.error(f"× 数据库配置检查失败: {str(e)}")

        # 检查总结果
        results['all_ok'] = all([
            results['config_file'],
            results['app_config'],
            results['server_config'],
            results['logging_config'],
            results['docs_config'],
            results['database_config']
        ])

        if results['all_ok']:
            logger.info("✓ 所有配置检查通过")
        else:
            logger.warning("! 部分配置检查未通过")

    except Exception as e:
        logger.error(f"× 配置检查过程发生错误: {str(e)}")

    return results

2.11 导出模块内容

apps/common/config/__init__.py

python 复制代码
from .app_config import AppConfig
from .server_config import ServerConfig
from .logging_config import LoggingConfig
from .docs_config import DocsConfig
from .yaml_loader import get_config_value
from .base import CONFIG_FILE, ROOT_DIR
from .check import check_config
from .database_config import DatabaseConfig

# 导出
__all__ = [
    "AppConfig",
    "ServerConfig",
    "LoggingConfig",
    "DocsConfig",
    "DatabaseConfig",
    "get_config_value",
    "CONFIG_FILE",
    "ROOT_DIR",
    "check_config",
]

三、FastAPI通用异常类

3.1 异常基类

apps/common/exception/common_exception.py

python 复制代码
from typing import Any


class CommonException(Exception):
    """通用异常基类"""

    def __init__(
            self,
            message: str,
            error_code: int = 10000,
            data: Any = None,
    ):
        self.message = message
        self.status_code = 200
        self.error_code = error_code
        self.data = data
        super().__init__(message)

3.2 不存在异常

apps/common/exception/not_exists_error.py

python 复制代码
from typing import Any
from .common_exception import CommonException


class NotExistsError(CommonException):
    """资源不存在异常类"""

    def __init__(
            self,
            message: str = "资源不存在",
            error_code: int = 10404,
            data: Any = None
    ):
        super().__init__(
            message=message,
            error_code=error_code,
            data=data,
        )

3.3 已存在异常

apps/common/exception/already_exists_error.py

python 复制代码
from typing import Any
from .common_exception import CommonException


class AlreadyExistsError(CommonException):
    """数据已存在异常类"""

    def __init__(
            self,
            message: str = "数据已存在",
            error_code: int = 10402,
            data: Any = None,
    ):
        super().__init__(
            message=message,
            error_code=error_code,
            data=data,
        )

3.4 数据校验异常

apps/common/exception/validation_error.py

python 复制代码
from typing import Any
from .common_exception import CommonException


class ValidationError(CommonException):
    """数据校验异常类"""

    def __init__(
            self,
            message: str = "数据校验失败",
            error_code: int = 10400,
            data: Any = None,
    ):
        super().__init__(
            message=message,
            error_code=error_code,
            data=data,
        )

3.5 权限不足异常

apps/common/exception/permission_denied_error.py

python 复制代码
from typing import Any
from .common_exception import CommonException


class PermissionDeniedError(CommonException):
    """权限不足异常类"""

    def __init__(
            self,
            message: str = "权限不足",
            error_code: int = 10403,
            data: Any = None,
    ):
        super().__init__(
            message=message,
            error_code=error_code,
            data=data
        )

3.6 导出模块内容

apps/common/exception/__init__.py

python 复制代码
from .common_exception import CommonException
from .not_exists_error import NotExistsError
from .already_exists_error import AlreadyExistsError
from .validation_error import ValidationError
from .permission_denied_error import PermissionDeniedError

# 导出
__all__ = [
    "CommonException",
    "NotExistsError",
    "AlreadyExistsError",
    "ValidationError",
    "PermissionDeniedError",
]

三、FastAPI通用日志

3.1 日志格式化

apps/common/logger/log_formatter.py

python 复制代码
# 简单格式: 时间 | 级别 | 消息
SIMPLE_FORMAT = "<green>{time:YYYY-MM-DD HH:mm:ss}</green> | <level>{level: <8}</level> | <cyan>{message}</cyan>"

# 详细格式: 时间 | 级别 | 模块:函数:行号 | 消息
DETAILED_FORMAT = "<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | <level>{level: <8}</level> | <blue>{name}:{function}:{line}</blue> | <cyan>{message}</cyan>"

# JSON格式: 时间 | 级别 | 额外信息 | 消息
JSON_FORMAT = "<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | <level>{level: <8}</level> | <blue>{extra}</blue> | <cyan>{message}</cyan>"

3.2 敏感数据过滤器

apps/common/logger/sensitive_data_filter.py

python 复制代码
import re
from typing import List, Dict, Any, Union, Optional


class SensitiveDataFilter:
    """敏感数据过滤器"""

    def __init__(self, sensitive_fields: List[str]):
        # 预编译正则表达式,提高性能
        patterns = []
        for field in sensitive_fields:
            field = re.escape(field.lower())
            patterns.extend([
                rf'"{field}"\s*:\s*"[^"]*"',  # JSON格式: "password": "value"
                rf'{field}\s*=\s*[^\s&]*',  # 键值对格式: password=value
                rf'{field}\s*:\s*[^\s,]*',  # 冒号分隔: password: value
                rf'{field}\s*=\s*"[^"]*"',  # 带引号的键值对: password="value"
                rf'{field}\s*:\s*"[^"]*"',  # 带引号的冒号分隔: password: "value"
            ])
        self.pattern = re.compile('|'.join(patterns), re.IGNORECASE)

    def filter_record(self, record: Dict[str, Any]) -> bool:
        """过滤日志记录中的敏感信息
        
        Args:
            record: loguru的日志记录字典
            
        Returns:
            bool: 始终返回True以允许记录通过
        """
        message = record.get('message')
        if not isinstance(message, str):
            return True

        def replace(match: re.Match) -> str:
            """替换文本"""
            text = match.group()

            # 处理JSON格式
            if '"' in text and ':' in text:
                key = text.split(':', 1)[0]
                return f'{key}:"***"'

            # 处理键值对格式
            if '=' in text:
                key = text.split('=', 1)[0]
                return f'{key}=***'

            # 处理冒号分隔格式
            if ':' in text:
                key = text.split(':', 1)[0]
                return f'{key}:***'

            return text

        record["message"] = self.pattern.sub(replace, message)
        return True

3.3 初始化日志

apps/common/logger/init.py

python 复制代码
import sys
import colorama
from pathlib import Path
from loguru import logger

from ..config import LoggingConfig
from .log_formatter import DETAILED_FORMAT, JSON_FORMAT
from .sensitive_data_filter import SensitiveDataFilter


def init_logger():
    """初始化日志系统"""
    # 初始化colorama以支持Windows下的彩色输出
    colorama.init()

    # 移除默认的sink
    logger.remove()

    # 创建敏感数据过滤器
    sensitive_filter = SensitiveDataFilter(LoggingConfig.sensitive_fields)

    # 配置控制台日志输出
    if LoggingConfig.enable_console:
        if LoggingConfig.format == "json":
            logger.add(
                sys.stdout,
                format=JSON_FORMAT,
                level=LoggingConfig.level,
                filter=sensitive_filter.filter_record,
                colorize=True,
                serialize=True,
            )
        else:
            logger.add(
                sys.stdout,
                format=DETAILED_FORMAT,
                level=LoggingConfig.level,
                filter=sensitive_filter.filter_record,
                colorize=True,
                serialize=False,
            )

    # 配置文件日志输出
    if LoggingConfig.enable_file:
        # 确保日志目录存在
        log_path = Path(LoggingConfig.dir)
        log_path.mkdir(parents=True, exist_ok=True)

        # 主日志文件
        main_log_path = log_path / LoggingConfig.file
        logger.add(
            str(main_log_path),
            format=DETAILED_FORMAT,
            level=LoggingConfig.level,
            filter=sensitive_filter.filter_record,
            rotation=LoggingConfig.max_size,
            retention=LoggingConfig.retention,
            compression=LoggingConfig.compression,
            serialize=False,
            enqueue=LoggingConfig.async_mode,
            buffering=LoggingConfig.buffer_size,
        )

        # 错误日志文件
        error_log_path = log_path / LoggingConfig.error_file
        logger.add(
            str(error_log_path),
            format=DETAILED_FORMAT,
            level="ERROR",
            filter=sensitive_filter.filter_record,
            rotation=LoggingConfig.max_size,
            retention=LoggingConfig.retention,
            compression=LoggingConfig.compression,
            serialize=False,
            enqueue=LoggingConfig.async_mode,
            buffering=LoggingConfig.buffer_size,
        )

3.4 获取模块日志

apps/common/logger/module_logger.py

python 复制代码
from pathlib import Path
from loguru import logger

from ..config import LoggingConfig
from .log_formatter import DETAILED_FORMAT
from .sensitive_data_filter import SensitiveDataFilter


def get_module_logger(name: str):
    """获取模块级别的日志记录器"""
    # 创建敏感数据过滤器
    sensitive_filter = SensitiveDataFilter(LoggingConfig.sensitive_fields)

    # 创建日志目录
    log_dir = Path(LoggingConfig.dir)
    log_dir.mkdir(parents=True, exist_ok=True)

    # 主日志文件
    log_file = log_dir / f"{name}.log"
    logger.add(
        str(log_file),
        format=DETAILED_FORMAT,
        level=LoggingConfig.level,
        filter=sensitive_filter.filter_record,
        rotation=LoggingConfig.max_size,
        retention=LoggingConfig.retention,
        compression=LoggingConfig.compression,
        enqueue=LoggingConfig.async_mode,
        buffering=LoggingConfig.buffer_size,
    )

    # 错误日志文件
    error_log_file = log_dir / f"{name}_error.log"
    logger.add(
        str(error_log_file),
        format=DETAILED_FORMAT,
        level="ERROR",
        filter=sensitive_filter.filter_record,
        rotation=LoggingConfig.max_size,
        retention=LoggingConfig.retention,
        compression=LoggingConfig.compression,
        enqueue=LoggingConfig.async_mode,
        buffering=LoggingConfig.buffer_size,
    )

    # 返回日志
    return logger.bind(module=name)

3.5 检查日志模块

apps/common/logger/check.py

python 复制代码
from pathlib import Path
from typing import Dict

from loguru import logger

from .log_formatter import DETAILED_FORMAT, JSON_FORMAT
from .sensitive_data_filter import SensitiveDataFilter
from ..config import LoggingConfig


def check_logger() -> Dict[str, bool]:
    """检查日志模块是否正常"""
    results = {
        'logger_ready': False,      # logger是否就绪
        'console_ok': False,        # 控制台输出是否正常
        'file_ok': False,          # 文件输出是否正常
        'formatter_ok': False,      # 格式化器是否正常
        'filter_ok': False,        # 过滤器是否正常
        'all_ok': False            # 所有检查是否都通过
    }

    try:
        # 检查logger是否就绪
        if logger:
            results['logger_ready'] = True
            logger.info("✓ 日志记录器就绪")
        else:
            logger.error("× 日志记录器未就绪")
            return results

        # 检查控制台输出
        if LoggingConfig.enable_console:
            try:
                logger.debug("测试调试日志")
                logger.info("测试信息日志")
                logger.warning("测试警告日志")
                results['console_ok'] = True
                logger.info("✓ 控制台日志输出正常")
            except Exception as e:
                logger.error(f"× 控制台日志输出异常: {str(e)}")
        else:
            results['console_ok'] = True
            logger.info("✓ 控制台日志输出已禁用")

        # 检查文件输出
        if LoggingConfig.enable_file:
            try:
                # 检查日志目录
                log_dir = Path(LoggingConfig.dir)
                if not log_dir.exists():
                    log_dir.mkdir(parents=True, exist_ok=True)

                # 检查日志文件
                log_file = log_dir / LoggingConfig.file
                error_log_file = log_dir / LoggingConfig.error_file

                # 验证文件可写
                if (not log_file.exists() or log_file.is_file()) and \
                   (not error_log_file.exists() or error_log_file.is_file()):
                    results['file_ok'] = True
                    logger.info("✓ 文件日志输出正常")
                else:
                    logger.error("× 日志文件路径无效")
            except Exception as e:
                logger.error(f"× 文件日志输出异常: {str(e)}")
        else:
            results['file_ok'] = True
            logger.info("✓ 文件日志输出已禁用")

        # 检查格式化器
        try:
            # 测试详细格式化器
            if DETAILED_FORMAT and JSON_FORMAT:
                results['formatter_ok'] = True
                logger.info("✓ 日志格式化器正常")
            else:
                logger.error("× 日志格式化器异常")
        except Exception as e:
            logger.error(f"× 日志格式化器检查失败: {str(e)}")

        # 检查过滤器
        try:
            # 创建测试过滤器
            sensitive_filter = SensitiveDataFilter(LoggingConfig.sensitive_fields)
            test_record = {
                "message": "password=123456&token=abcdef",
                "extra": {}
            }
            # 调用过滤器并检查结果
            filter_result = sensitive_filter.filter_record(test_record)
            
            if (filter_result is True and 
                "message" in test_record and 
                "password=123456" not in test_record["message"] and
                "token=abcdef" not in test_record["message"] and
                "password=***" in test_record["message"] and
                "token=***" in test_record["message"]):
                results['filter_ok'] = True
                logger.info("✓ 日志过滤器正常")
            else:
                logger.error("× 日志过滤器异常")
        except Exception as e:
            logger.error(f"× 日志过滤器检查失败: {str(e)}")

        # 检查总结果
        results['all_ok'] = all([
            results['logger_ready'],
            results['console_ok'],
            results['file_ok'],
            results['formatter_ok'],
            results['filter_ok']
        ])

        if results['all_ok']:
            logger.info("✓ 所有日志检查通过")
        else:
            logger.warning("! 部分日志检查未通过")

    except Exception as e:
        logger.error(f"× 日志检查过程发生错误: {str(e)}")

    return results

3.6 导出模块内容

apps/common/logger/__init__.py

python 复制代码
from .log_formatter import DETAILED_FORMAT, JSON_FORMAT
from .sensitive_data_filter import SensitiveDataFilter
from .module_logger import get_module_logger
from .init import init_logger
from .check import check_logger

# 导出
__all__ = [
    "DETAILED_FORMAT",
    "JSON_FORMAT",
    "SensitiveDataFilter",
    "get_module_logger",
    "init_logger",
    "check_logger",
]

四、FastAPI本地化接口文档

4.1 准备静态文件

https://cdn.jsdelivr.net/npm/swagger-ui-dist@5.9.0/swagger-ui.css

apps/common/swagger/static/swagger-ui.css

https://cdn.jsdelivr.net/npm/swagger-ui-dist@5.9.0/swagger-ui-bundle.js

apps/common/swagger/static/swagger-ui-bundle.js

https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js

apps/common/swagger/static/redoc.standalone.js

4.2 自定义Swagger文档

apps/common/swagger/swagger_ui.py

python 复制代码
def get_swagger_ui_html(title: str, openapi_url: str) -> str:
    """
    生成Swagger UI的HTML内容
    """
    return f"""
    <!DOCTYPE html>
    <html>
    <head>
        <title>{title} - API文档</title>
        <meta charset="utf-8"/>
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <link rel="stylesheet" type="text/css" href="/swagger-static/swagger-ui.css" />
    </head>
    <body>
        <div id="swagger-ui"></div>
        <script src="/swagger-static/swagger-ui-bundle.js"></script>
        <script>
            window.onload = function() {{
                const ui = SwaggerUIBundle({{
                    url: '{openapi_url}',
                    dom_id: '#swagger-ui',
                    presets: [
                        SwaggerUIBundle.presets.apis
                    ],
                    plugins: [
                        SwaggerUIBundle.plugins.DownloadUrl
                    ],
                    layout: "BaseLayout",
                    deepLinking: true,
                    showExtensions: true,
                    showCommonExtensions: true,
                    docExpansion: 'list',
                    filter: true,
                    tryItOutEnabled: true,
                    supportedSubmitMethods: ['get', 'post', 'put', 'delete', 'patch', 'head', 'options'],
                    onComplete: function() {{
                        console.log('Swagger UI loaded successfully');
                    }},
                    onFailure: function(error) {{
                        console.error('Swagger UI failed to load:', error);
                    }}
                }});
            }};
        </script>
    </body>
    </html>
    """ 

4.3 自定义Redoc文档

apps/common/swagger/redoc.py

python 复制代码
def get_redoc_html(title: str, openapi_url: str) -> str:
    """
    生成ReDoc的HTML内容
    """
    return f"""
    <!DOCTYPE html>
    <html>
    <head>
        <title>{title} - API文档</title>
        <meta charset="utf-8"/>
        <meta name="viewport" content="width=device-width, initial-scale=1">
    </head>
    <body>
        <div id="redoc"></div>
        <script src="/swagger-static/redoc.standalone.js"></script>
        <script>
            Redoc.init('{openapi_url}', {{
                scrollYOffset: 0,
                hideDownloadButton: false,
                hideHostname: false,
                hideLoading: false,
                nativeScrollbars: false,
                pathInMiddlePanel: true,
                requiredPropsFirst: true,
                sortPropsAlphabetically: true,
                showExtensions: true,
                showObjectSchemaExamples: true,
                theme: {{
                    colors: {{
                        primary: {{
                            main: '#1890ff'
                        }}
                    }}
                }}
            }}, document.getElementById('redoc'));
        </script>
    </body>
    </html>
    """ 

4.4 初始化Swagger

apps/common/swagger/init.py

python 复制代码
from pathlib import Path

from loguru import logger
from fastapi import FastAPI, HTTPException
from fastapi.staticfiles import StaticFiles
from fastapi.responses import HTMLResponse

from ..config import AppConfig, ServerConfig, DocsConfig
from ..exception import NotExistsError
from .swagger_ui import get_swagger_ui_html
from .redoc import get_redoc_html


def mount_static_files(app: FastAPI):
    """挂载静态文件"""
    static_dir = Path(__file__).parent / "static"
    if not static_dir.exists():
        raise NotExistsError(f"静态文件目录不存在: {static_dir}")

    app.mount(
        "/swagger-static",
        StaticFiles(directory=str(static_dir)),
        name="swagger-static"
    )
    logger.info(f"静态文件目录已挂载: {static_dir}")


def setup_swagger_urls(app: FastAPI):
    """设置swagger文档相关路由"""
    # 配置Swagger UI
    if DocsConfig.docs_url:
        @app.get(DocsConfig.docs_url, include_in_schema=False)
        async def swagger_ui_html():
            content = get_swagger_ui_html(
                AppConfig.name,
                DocsConfig.openapi_url,
            )
            return HTMLResponse(content=content)

        logger.info(f"已配置Swagger UI: {DocsConfig.docs_url}")

    # 配置Redoc
    if DocsConfig.redoc_url:
        @app.get(DocsConfig.redoc_url, include_in_schema=False)
        async def redoc_html():
            content = get_redoc_html(
                AppConfig.name,
                DocsConfig.openapi_url,
            )
            return HTMLResponse(content=content)

        logger.info(f"已配置Redoc: {DocsConfig.redoc_url}")


def init_swagger(app: FastAPI):
    """初始化Swagger本地文档"""
    # 挂载静态文件
    mount_static_files(app)
    # 设置Swagger URL
    setup_swagger_urls(app)
    logger.info("Swagger初始化完成")

4.5 导出模块内容

apps/common/swagger/__init__.py

python 复制代码
from .init import init_swagger

__all__ = [
    "init_swagger"
]

五、FastAPI统一返回值类型

5.1 通用响应类型

apps/common/schema/common_response.py

python 复制代码
import uuid

from datetime import datetime
from typing import Any, Optional
from pydantic import BaseModel


class CommonResponse(BaseModel):
    """统一的API响应模型"""
    code: int
    msg: str
    status: bool
    timestamp: str = None
    request_id: str = None
    data: Any = None

    def __init__(self, **kwargs):
        if not kwargs.get("timestamp"):
            kwargs["timestamp"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        if not kwargs.get("request_id"):
            kwargs["request_id"] = str(uuid.uuid4())
        super().__init__(**kwargs)

5.2 成功的响应

apps/common/schema/success.py

python 复制代码
from typing import Any
from .common_response import CommonResponse


def success(
        data: Any = None,
        message: str = "成功",
        timestamp: str = None,
        request_id: str = None,
) -> CommonResponse:
    """成功的响应"""
    return CommonResponse(
        code=10000,
        status=True,
        msg=message,
        data=data,
        timestamp=timestamp,
        request_id=request_id,
    )

5.3 错误的响应

apps/common/schema/error.py

python 复制代码
from typing import Any
from .common_response import CommonResponse


def error(
        error_code: int = 10500,
        message: str = "操作失败",
        data: Any = None,
        request_id: str = None,
        timestamp: str = None
) -> CommonResponse:
    """失败的响应"""
    return CommonResponse(
        code=error_code,
        status=False,
        msg=message,
        data=data,
        request_id=request_id,
        timestamp=timestamp
    )


def error400(
        message: str = "参数错误",
        data: Any = None,
        request_id: str = None,
        timestamp: str = None
) -> CommonResponse:
    return error(
        10400,
        message,
        data,
        request_id,
        timestamp,
    )


def error401(
        message: str = "未授权",
        data: Any = None,
        request_id: str = None,
        timestamp: str = None
) -> CommonResponse:
    return error(
        10401,
        message,
        data,
        request_id,
        timestamp,
    )


def error403(
        message: str = "权限不足",
        data: Any = None,
        request_id: str = None,
        timestamp: str = None
) -> CommonResponse:
    return error(
        10403,
        message,
        data,
        request_id,
        timestamp,
    )


def error404(
        message: str = "资源不存在",
        data: Any = None,
        request_id: str = None,
        timestamp: str = None
) -> CommonResponse:
    return error(
        10404,
        message,
        data,
        request_id,
        timestamp,
    )


def error500(
        message: str = "服务器错误",
        data: Any = None,
        request_id: str = None,
        timestamp: str = None
) -> CommonResponse:
    return error(
        10500,
        message,
        data,
        request_id,
        timestamp,
    )


def error501(
        message: str = "服务未实现",
        data: Any = None,
        request_id: str = None,
        timestamp: str = None
) -> CommonResponse:
    return error(
        10501,
        message,
        data,
        request_id,
        timestamp,
    )

5.4 导出模块内容

python 复制代码
from .common_response import CommonResponse
from .success import success
from .error import (
    error, error400, error401,
    error403, error404, error500,
    error501,
)

__all__ = [
    "CommonResponse",
    "success",
    "error",
    "error400",
    "error401",
    "error403",
    "error404",
    "error500",
    "error501",
]

六、FastAPI数据库模块

6.1 异步会话类

apps/common/database/wrapped_async_session.py

python 复制代码
from typing import Any, Union

from sqlalchemy import text
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.sql.expression import TextClause, Select

SQLStatement = Union[str, TextClause, Select]


class WrappedAsyncSession(AsyncSession):
    """包装的异步会话类,提供自动SQL包装功能"""

    def _wrap_sql(self, statement: SQLStatement) -> Union[TextClause, Select]:
        """包装SQL语句"""
        if isinstance(statement, str):
            return text(statement)
        return statement

    async def execute(self, statement: SQLStatement, *args, **kwargs) -> Any:
        """执行SQL语句"""
        return await super().execute(self._wrap_sql(statement), *args, **kwargs)

    async def scalar(self, statement: SQLStatement, *args, **kwargs) -> Any:
        """执行SQL语句并返回第一个标量结果"""
        return await super().scalar(self._wrap_sql(statement), *args, **kwargs)

    async def scalars(self, statement: SQLStatement, *args, **kwargs) -> Any:
        """执行SQL语句并返回所有标量结果"""
        return await super().scalars(self._wrap_sql(statement), *args, **kwargs)

6.2 数据库引擎

apps/common/database/engine.py

python 复制代码
from contextlib import asynccontextmanager
from typing import Optional, AsyncGenerator, Any, TypeVar, Union, Sequence, Dict
from urllib.parse import quote_plus

from sqlalchemy import text, Table, MetaData, select, insert, update, delete, func
from sqlalchemy.ext.asyncio import create_async_engine, AsyncEngine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.sql.expression import TextClause, Select

from ..config import DatabaseConfig
from .wrapped_async_session import WrappedAsyncSession

T = TypeVar('T')
SQLStatement = Union[str, TextClause, Select]


class Engine:
    """数据库引擎管理类"""

    _instance: Optional["Engine"] = None

    def __new__(cls) -> "Engine":
        """单例模式"""
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance

    def __init__(self):
        """初始化数据库引擎"""
        if not hasattr(self, '_initialized'):
            self._engine: Optional[AsyncEngine] = None
            self._session_factory = None
            self._initialized = True

    @property
    def database_config(self) -> DatabaseConfig:
        """获取数据库配置"""
        return DatabaseConfig

    def _wrap_sql(self, statement: SQLStatement) -> Union[TextClause, Select]:
        """包装SQL语句"""
        if isinstance(statement, str):
            return text(statement)
        return statement

    async def _ensure_database_exists(self) -> None:
        """确保数据库存在,如果不存在则创建"""
        # 构建不带数据库名的URL
        encoded_username = quote_plus(DatabaseConfig.username)
        encoded_password = quote_plus(DatabaseConfig.password)
        base_url = (
            f"mysql+{DatabaseConfig.driver}://"
            f"{encoded_username}:{encoded_password}@"
            f"{DatabaseConfig.host}:{DatabaseConfig.port}/"
            f"?charset={DatabaseConfig.charset}"
        )

        # 创建临时引擎来检查数据库是否存在
        temp_engine = create_async_engine(base_url, **DatabaseConfig.get_engine_args())
        try:
            async with temp_engine.connect() as conn:
                # 检查数据库是否存在
                result = await conn.execute(
                    text(
                        f"SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = '{DatabaseConfig.database}'")
                )
                database_exists = result.first() is not None

                # 如果数据库不存在,创建它
                if not database_exists:
                    await conn.execute(text(f"CREATE DATABASE {DatabaseConfig.database}"))
                    await conn.commit()
        finally:
            await temp_engine.dispose()

    async def get_engine(self) -> AsyncEngine:
        """获取数据库引擎,确保数据库存在"""
        if self._engine is None:
            # 确保数据库存在
            await self._ensure_database_exists()
            # 创建引擎
            self._engine = create_async_engine(
                DatabaseConfig.get_url(),
                **DatabaseConfig.get_engine_args()
            )
        return self._engine

    @property
    async def session_factory(self):
        """获取会话工厂"""
        if self._session_factory is None:
            self._session_factory = sessionmaker(
                await self.get_engine(),
                class_=WrappedAsyncSession,
                **DatabaseConfig.get_session_args()
            )
        return self._session_factory

    async def create_session(self) -> WrappedAsyncSession:
        """创建新的会话"""
        return (await self.session_factory)()

    @asynccontextmanager
    async def session(self) -> AsyncGenerator[WrappedAsyncSession, None]:
        """会话上下文管理器,自动处理会话的创建和关闭"""
        session = await self.create_session()
        try:
            yield session
        finally:
            await session.close()

    @asynccontextmanager
    async def begin(self) -> AsyncGenerator[WrappedAsyncSession, None]:
        """事务上下文管理器,自动处理事务的开始、提交和回滚"""
        async with self.session() as session:
            async with session.begin():
                yield session

    async def execute(self, statement: SQLStatement, *args, **kwargs) -> Any:
        """执行SQL语句并返回结果"""
        async with self.session() as session:
            result = await session.execute(statement, *args, **kwargs)
            return result

    async def scalar(self, statement: SQLStatement, *args, **kwargs) -> Any:
        """执行SQL语句并返回第一个标量结果"""
        async with self.session() as session:
            result = await session.scalar(statement, *args, **kwargs)
            return result

    async def scalars(self, statement: SQLStatement, *args, **kwargs) -> list:
        """执行SQL语句并返回所有标量结果"""
        async with self.session() as session:
            result = await session.scalars(statement, *args, **kwargs)
            return list(result.all())

    async def fetchone(self, statement: SQLStatement, *args, **kwargs) -> Optional[dict]:
        """执行SQL语句并返回第一行结果(字典格式)"""
        result = await self.execute(statement, *args, **kwargs)
        row = result.first()
        if row is None:
            return None
        return dict(row._mapping)

    async def fetchall(self, statement: SQLStatement, *args, **kwargs) -> list[dict]:
        """执行SQL语句并返回所有结果(字典格式)"""
        result = await self.execute(statement, *args, **kwargs)
        return [dict(row._mapping) for row in result.all()]

    async def ping(self) -> bool:
        """测试数据库连接"""
        try:
            await self.scalar("SELECT 1")
            return True
        except Exception as e:
            print(f"数据库连接测试失败: {e}")
            return False

    async def get_version(self) -> str:
        """获取数据库版本"""
        result = await self.scalar("SELECT VERSION()")
        return str(result)

    @classmethod
    def get_instance(cls) -> "Engine":
        """获取数据库引擎实例"""
        return cls()

    async def dispose(self):
        """关闭数据库引擎"""
        if self._engine is not None:
            await self._engine.dispose()
            self._engine = None
            self._session_factory = None

    async def __aenter__(self) -> "Engine":
        """异步上下文管理器入口"""
        return self

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        """异步上下文管理器出口"""
        await self.dispose()

    # === 数据库管理方法 ===
    async def add_database(self, database_name: str):
        """创建数据库"""
        async with self.begin() as session:
            await session.execute(f"CREATE DATABASE IF NOT EXISTS {database_name}")

    async def delete_database(self, database_name: str):
        """删除数据库"""
        async with self.begin() as session:
            await session.execute(f"DROP DATABASE IF EXISTS {database_name}")

    async def add_table(self, metadata: MetaData):
        """创建表"""
        engine = await self.get_engine()
        async with engine.begin() as conn:
            await conn.run_sync(metadata.create_all)

    async def delete_table(self, metadata: MetaData):
        """删除表"""
        engine = await self.get_engine()
        async with engine.begin() as conn:
            await conn.run_sync(metadata.drop_all)

    # === CRUD操作方法 ===
    async def add(self, table: Table, values: Union[Dict, Sequence[Dict]]) -> Any:
        """添加一条或多条记录"""
        stmt = insert(table).values(values)
        async with self.begin() as session:
            result = await session.execute(stmt)
            return result.inserted_primary_key if isinstance(values, dict) else result

    async def update(self, table: Table, where: Any, values: Dict) -> int:
        """更新记录"""
        stmt = update(table).where(where).values(values)
        async with self.begin() as session:
            result = await session.execute(stmt)
            return result.rowcount

    async def delete(self, table: Table, where: Any) -> int:
        """删除记录"""
        stmt = delete(table).where(where)
        async with self.begin() as session:
            result = await session.execute(stmt)
            return result.rowcount

    async def get(self, table: Table, where: Any) -> Optional[Dict]:
        """获取单条记录"""
        stmt = select(table).where(where)
        return await self.fetchone(stmt)

    async def get_all(self, table: Table, where: Any = None, order_by: Any = None) -> list[Dict]:
        """获取所有记录"""
        stmt = select(table)
        if where is not None:
            stmt = stmt.where(where)
        if order_by is not None:
            stmt = stmt.order_by(order_by)
        return await self.fetchall(stmt)

    async def get_page(
            self,
            table: Table,
            page: int = 1,
            page_size: int = 10,
            where: Any = None,
            order_by: Any = None
    ) -> tuple[list[Dict], int]:
        """获取分页记录"""
        # 构建查询语句
        stmt = select(table)
        if where is not None:
            stmt = stmt.where(where)
        if order_by is not None:
            stmt = stmt.order_by(order_by)

        # 计算总数
        count_stmt = select(func.count()).select_from(table)
        if where is not None:
            count_stmt = count_stmt.where(where)
        total = await self.scalar(count_stmt)

        # 分页
        stmt = stmt.offset((page - 1) * page_size).limit(page_size)
        items = await self.fetchall(stmt)

        return items, total

6.3 基础模型

apps/common/database/base.py

python 复制代码
from datetime import datetime
from typing import Any, Dict, List, Optional, Type, TypeVar, Union, Sequence

from sqlalchemy import select, func, and_, Column, Integer, DateTime, text
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
from sqlalchemy.sql import Select

T = TypeVar("T", bound="Base")
ModelType = TypeVar("ModelType", bound="Base")


class Base(DeclarativeBase):
    """基础模型类,提供通用的CRUD操作和基础字段"""

    # 基础字段
    id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    created_at: Mapped[datetime] = mapped_column(
        DateTime,
        nullable=False,
        server_default=text("CURRENT_TIMESTAMP"),
        comment="创建时间"
    )
    updated_at: Mapped[datetime] = mapped_column(
        DateTime,
        nullable=False,
        server_default=text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"),
        comment="更新时间"
    )

    # 查询构建方法
    @classmethod
    def _build_query(
            cls: Type[ModelType],
            filters: Optional[Dict[str, Any]] = None,
            order_by: Optional[Union[str, Sequence[str]]] = None,
            skip: Optional[int] = None,
            limit: Optional[int] = None
    ) -> Select:
        """构建查询语句"""
        query = select(cls)

        # 添加过滤条件
        if filters:
            conditions = []
            for key, value in filters.items():
                if "__" in key:
                    field, op = key.split("__")
                    if not hasattr(cls, field):
                        continue
                    field_attr = getattr(cls, field)
                    if op == "gt":
                        conditions.append(field_attr > value)
                    elif op == "gte":
                        conditions.append(field_attr >= value)
                    elif op == "lt":
                        conditions.append(field_attr < value)
                    elif op == "lte":
                        conditions.append(field_attr <= value)
                    elif op == "in":
                        conditions.append(field_attr.in_(value))
                    elif op == "contains":
                        conditions.append(field_attr.contains(value))
                else:
                    if hasattr(cls, key):
                        conditions.append(getattr(cls, key) == value)
            if conditions:
                query = query.where(and_(*conditions))

        # 添加排序
        if order_by:
            if isinstance(order_by, str):
                order_by = [order_by]
            for field in order_by:
                if field.startswith("-"):
                    query = query.order_by(getattr(cls, field[1:]).desc())
                else:
                    query = query.order_by(getattr(cls, field).asc())

        # 添加分页
        if skip is not None:
            query = query.offset(skip)
        if limit is not None:
            query = query.limit(limit)

        return query

    # 查询方法
    @classmethod
    async def get(cls: Type[ModelType], db: AsyncSession, id: Any) -> Optional[ModelType]:
        """根据ID获取单个记录"""
        result = await db.execute(select(cls).where(cls.id == id))
        return result.scalar_one_or_none()

    @classmethod
    async def get_by(
            cls: Type[ModelType],
            db: AsyncSession,
            **filters
    ) -> Optional[ModelType]:
        """根据条件获取单个记录"""
        query = cls._build_query(filters=filters)
        result = await db.execute(query)
        return result.scalar_one_or_none()

    @classmethod
    async def get_all(
            cls: Type[ModelType],
            db: AsyncSession,
            *,
            skip: Optional[int] = None,
            limit: Optional[int] = None,
            order_by: Optional[Union[str, Sequence[str]]] = None,
            **filters
    ) -> List[ModelType]:
        """获取多个记录,支持分页、排序和过滤"""
        query = cls._build_query(
            filters=filters,
            order_by=order_by,
            skip=skip,
            limit=limit
        )
        result = await db.execute(query)
        return list(result.scalars().all())

    @classmethod
    async def get_page(
            cls: Type[ModelType],
            db: AsyncSession,
            *,
            page: int = 1,
            page_size: int = 10,
            order_by: Optional[Union[str, Sequence[str]]] = None,
            **filters
    ) -> tuple[List[ModelType], int]:
        """获取分页记录"""
        # 计算总数
        count_query = select(func.count()).select_from(cls)
        if filters:
            count_query = cls._build_query(filters=filters)
            count_query = select(func.count()).from_self().select_from(count_query)
        total = await db.scalar(count_query) or 0

        # 获取分页数据
        skip = (page - 1) * page_size
        items = await cls.get_all(
            db,
            skip=skip,
            limit=page_size,
            order_by=order_by,
            **filters
        )

        return items, total

    @classmethod
    async def count(cls: Type[ModelType], db: AsyncSession, **filters) -> int:
        """获取记录总数"""
        query = select(func.count()).select_from(cls)
        if filters:
            conditions = [getattr(cls, k) == v for k, v in filters.items()]
            query = query.where(and_(*conditions))
        result = await db.execute(query)
        return result.scalar_one() or 0

    # 修改方法
    async def add(self, db: AsyncSession) -> ModelType:
        """保存当前记录(创建或更新)"""
        db.add(self)
        try:
            await db.commit()
            await db.refresh(self)
        except Exception:
            await db.rollback()
            raise
        return self

    async def update(self, db: AsyncSession, **kwargs) -> ModelType:
        """更新指定字段"""
        for k, v in kwargs.items():
            if hasattr(self, k):
                setattr(self, k, v)
        return await self.add(db)

    async def delete(self, db: AsyncSession) -> bool:
        """删除当前记录"""
        try:
            await db.delete(self)
            await db.commit()
            return True
        except Exception:
            await db.rollback()
            raise

    # 批量操作方法
    @classmethod
    async def add_many(
            cls: Type[ModelType],
            db: AsyncSession,
            objects: List[Dict[str, Any]]
    ) -> List[ModelType]:
        """批量创建记录"""
        instances = [cls(**obj) for obj in objects]
        db.add_all(instances)
        try:
            await db.commit()
            for instance in instances:
                await db.refresh(instance)
            return instances
        except Exception:
            await db.rollback()
            raise

    @classmethod
    async def update_many(
            cls: Type[ModelType],
            db: AsyncSession,
            ids: List[Any],
            update_data: Dict[str, Any]
    ) -> int:
        """批量更新记录"""
        stmt = (
            cls.__table__.update()
            .where(cls.id.in_(ids))
            .values(**update_data)
        )
        try:
            result = await db.execute(stmt)
            await db.commit()
            return result.rowcount
        except Exception:
            await db.rollback()
            raise

    @classmethod
    async def bulk_delete(cls: Type[ModelType], db: AsyncSession, ids: List[Any]) -> int:
        """批量删除记录"""
        stmt = cls.__table__.delete().where(cls.id.in_(ids))
        try:
            result = await db.execute(stmt)
            await db.commit()
            return result.rowcount
        except Exception:
            await db.rollback()
            raise

6.4 初始化数据库

apps/common/database/init.py

python 复制代码
from loguru import logger

from ..config import DatabaseConfig

from .engine import Engine
from .base import Base


async def init_database(create_tables: bool = True) -> None:
    """初始化数据库"""
    try:
        # 获取数据库引擎实例
        engine = Engine()

        # 获取引擎(这一步会自动创建数据库)
        await engine.get_engine()
        logger.info("✓ 数据库引擎初始化成功")

        # 测试数据库连接
        if await engine.ping():
            logger.info("✓ 数据库连接测试成功")
        else:
            logger.error("× 数据库连接测试失败")
            return

        # 获取数据库版本
        version = await engine.get_version()
        logger.info(f"✓ 数据库版本: {version}")

        # 创建表
        if create_tables:
            await engine.add_table(Base.metadata)
            logger.info("✓ 数据库表创建成功")

        logger.info("✓ 数据库初始化完成")

    except Exception as e:
        logger.error(f"× 数据库初始化失败: {str(e)}")
        raise


async def clear_database(drop_tables: bool = False, drop_database: bool = False):
    """清理数据库资源"""
    try:
        # 获取数据库引擎实例
        engine = Engine()

        # 删除表
        if drop_tables:
            try:
                await engine.delete_table(Base.metadata)
                logger.info("✓ 数据库表删除成功")
            except Exception as e:
                logger.error(f"× 数据库表删除失败: {str(e)}")

        # 删除数据库
        if drop_database:
            try:
                database_name = DatabaseConfig.database
                await engine.delete_database(database_name)
                logger.info("✓ 数据库删除成功")
            except Exception as e:
                logger.error(f"× 数据库删除失败: {str(e)}")

        # 关闭数据库连接
        try:
            await engine.dispose()
            logger.info("✓ 数据库连接关闭成功")
        except Exception as e:
            logger.error(f"× 数据库连接关闭失败: {str(e)}")

        logger.info("✓ 数据库资源清理完成")

    except Exception as e:
        logger.error(f"× 数据库资源清理失败: {str(e)}")
        raise

6.5 检查数据库模块

apps/common/database/check.py

python 复制代码
from typing import Dict

from loguru import logger
from sqlalchemy import text

from .engine import Engine


async def check_database() -> Dict[str, bool]:
    """检查数据库是否正常"""
    results = {
        'engine_ready': False,
        'connection_ok': False,
        'can_query': False,
        'can_write': False,
        'all_ok': False
    }

    try:
        # 获取数据库引擎实例
        engine = Engine()
        results['engine_ready'] = True
        logger.info("✓ 数据库引擎就绪")

        # 检查连接
        if await engine.ping():
            results['connection_ok'] = True
            logger.info("✓ 数据库连接正常")
        else:
            logger.error("× 数据库连接异常")
            return results

        # 检查查询功能
        try:
            version = await engine.get_version()
            results['can_query'] = True
            logger.info(f"✓ 数据库查询正常,当前版本: {version}")
        except Exception as e:
            logger.error(f"× 数据库查询异常: {str(e)}")
            return results

        # 检查写入功能
        try:
            # 创建临时表
            async with engine.begin() as session:
                # 创建测试表
                await session.execute(text("""
                    CREATE TABLE IF NOT EXISTS _db_check_test (
                        id INT PRIMARY KEY AUTO_INCREMENT,
                        test_value VARCHAR(50)
                    )
                """))
                
                # 插入测试数据
                await session.execute(text("""
                    INSERT INTO _db_check_test (test_value) 
                    VALUES ('test_data')
                """))
                
                # 删除测试数据和表
                await session.execute(text("DROP TABLE _db_check_test"))
                
            results['can_write'] = True
            logger.info("✓ 数据库写入正常")
        except Exception as e:
            logger.error(f"× 数据库写入异常: {str(e)}")
            return results

        # 所有检查通过
        results['all_ok'] = all([
            results['engine_ready'],
            results['connection_ok'],
            results['can_query'],
            results['can_write']
        ])
        
        if results['all_ok']:
            logger.info("✓ 数据库所有检查项通过")
        else:
            logger.warning("! 部分数据库检查项未通过")

    except Exception as e:
        logger.error(f"× 数据库检查过程发生错误: {str(e)}")

    return results

6.6 导出数据库模块内容

python 复制代码
from .engine import Engine
from .base import Base
from .init import init_database, clear_database
from .check import check_database

__all__ = [
    'Engine',
    'Base',
    'init_database',
    'check_database',
    'clear_database',
]

七、FastAPI通用路由

7.1 健康检查

apps/common/router/common_router.py

python 复制代码
from fastapi import APIRouter
from ..schema import CommonResponse, success
from ..config import check_config
from ..logger import check_logger
from ..database import check_database

health_check_router = APIRouter(tags=["通用管理"])


@health_check_router.get("/", summary="健康检查", response_model=CommonResponse)
async def health_check():
    """健康检查接口"""
    data = {
        "config": check_config(),
        "logger": check_logger(),
        "database": await check_database()
    }
    return success(data=data)

7.2 初始化路由

apps/common/router/init.py

python 复制代码
from fastapi import FastAPI
from loguru import logger
from .health_check import health_check_router


def init_router(app: FastAPI):
    """初始化路由"""
    logger.info("开始初始化通用路由模块")
    app.include_router(health_check_router)
    logger.info("初始化通用路由模块成功")

7.3 导出路由模块

apps/common/router/__init__.py

python 复制代码
from .init import init_router
from .health_check import health_check_router

__all__ = [
    "init_router",
    "health_check_router"
]

八、项目初始化和运行

8.1 启动任务处理

apps/common/initialize/startup.py

python 复制代码
from datetime import datetime
from loguru import logger

from ..config import AppConfig
from ..exception import CommonException
from ..database import init_database


class StartupHandler:
    """启动任务处理器"""

    @staticmethod
    def print_banner():
        """打印启动信息"""
        logger.info("=" * 35)
        logger.info("FastAPI应用正在启动...")
        logger.info("应用信息:")
        logger.info(f"├── 名称: {AppConfig.name}")
        logger.info(f"├── 版本: {AppConfig.version}")
        logger.info(f"└── 启动时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
        logger.info("=" * 35)

    @staticmethod
    async def execute():
        """执行启动任务"""
        try:
            # 添加需要启动时执行的代码,比如初始化数据库连接,缓存等等
            await init_database()
        except Exception as e:
            logger.error(f"启动任务执行失败: {e}")
            raise CommonException("启动任务执行失败")

        logger.info("启动任务执行完毕")

9.2 关闭任务处理

apps/common/initialize/shutdown.py

python 复制代码
from datetime import datetime
from loguru import logger

from ..config import AppConfig
from ..exception import CommonException
from ..database import clear_database


class ShutdownHandler:
    """关闭任务处理器"""

    @staticmethod
    def print_banner():
        logger.info("=" * 35)
        logger.info("FastAPI应用正在关闭...")
        logger.info(f"└── 关闭时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
        logger.info("=" * 35)

    @staticmethod
    def print_goodbye():
        logger.info("=" * 35)
        logger.info(f"感谢使用 {AppConfig.name} v{AppConfig.version}")
        logger.info("=" * 35)

    @staticmethod
    async def execute():
        """程序关闭之前执行的任务"""
        try:
            # 添加关闭前的清理任务
            # 关闭数据库连接,清理缓存,释放资源等
            await clear_database(drop_tables=False, drop_database=False)
        except Exception as e:
            logger.error(f"执行关闭前的清理任务失败:{e}")
            raise CommonException("执行关闭前的清理任务失败")
        logger.info("关闭前的清理任务执行完毕")

9.3 生命周期管理

python 复制代码
from typing import AsyncGenerator
from contextlib import asynccontextmanager
from fastapi import FastAPI
from loguru import logger

from ..exception import CommonException

from .startup import StartupHandler
from .shutdown import ShutdownHandler


@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
    """应用生命周期管理"""
    # 启动阶段
    StartupHandler.print_banner()
    try:
        await StartupHandler.execute()
    except Exception as e:
        logger.error(f"启动失败: {e}")
        raise CommonException("启动失败")

    # 运行阶段
    yield

    # 关闭阶段
    ShutdownHandler.print_banner()
    try:
        await ShutdownHandler.execute()
        ShutdownHandler.print_goodbye()
    except Exception as e:
        logger.error(f"关闭失败: {e}")
        raise CommonException("关闭失败")

9.4 初始化应用

apps/common/initialize/init.py

python 复制代码
from fastapi import FastAPI
from loguru import logger

from ..config import AppConfig, DocsConfig
from ..logger import init_logger
from ..swagger import init_swagger
from ..router import init_router

from .lifespan import lifespan


def init_app() -> FastAPI:
    """初始化应用"""
    # 初始化日志
    init_logger()
    logger.info("开始初始化应用")

    # 创建应用
    app = FastAPI(
        title=AppConfig.name,
        description=AppConfig.description,
        version=AppConfig.version,
        docs_url=None,
        redoc_url=None,
        openapi_url=DocsConfig.openapi_url,
        lifespan=lifespan,
    )

    # 初始化Swagger
    init_swagger(app)

    # 初始化路由
    init_router(app)

    # 返回
    logger.info("应用初始化完成")
    return app

9.5 拦截标准库日志

apps/common/initialize/intercept_handler.py

python 复制代码
import logging
from loguru import logger
from ..config import ServerConfig


class InterceptHandler(logging.Handler):
    """拦截标准库日志并重定向到loguru"""

    def emit(self, record):
        # 获取对应的loguru级别
        try:
            level = logger.level(record.levelname).name
        except ValueError:
            level = record.levelno

        # 找到发起日志记录的代码文件名、函数名和行号
        frame, depth = logging.currentframe(), 2
        while frame.f_code.co_filename == logging.__file__:
            frame = frame.f_back
            depth += 1

        # 移除多余的换行符
        msg = record.getMessage().strip()
        logger.opt(depth=depth, exception=record.exc_info).log(level, msg)


def setup_logging():
    """配置日志拦截"""
    # 移除所有的默认处理器
    logging.getLogger().handlers = []

    # 设置根日志器
    logging.getLogger().addHandler(InterceptHandler())
    logging.getLogger().setLevel(ServerConfig.log_level)

    # 配置uvicorn日志器
    for _log in ["uvicorn", "uvicorn.error", "uvicorn.access", "fastapi"]:
        _logger = logging.getLogger(_log)
        _logger.handlers = []
        _logger.propagate = False
        _logger.setLevel(ServerConfig.log_level)

9.6 运行程序

apps/common/initialize/run.py

python 复制代码
from fastapi import FastAPI
from ..config import ServerConfig
from .intercept_handler import setup_logging


def run(app: FastAPI):
    """运行FastAPI程序"""
    import uvicorn

    # 设置日志
    setup_logging()

    # 运行服务器
    config = uvicorn.Config(
        app,
        host=ServerConfig.host,
        port=ServerConfig.port,
        log_config=None,  # 禁用uvicorn的默认日志配置
    )
    server = uvicorn.Server(config)
    server.run()

9.7 导出初始化模块

apps/common/initialize/__init__.py

python 复制代码
from .init import init
from .run import run

# 导出
__all__ = [
    'init',
    'run',
]

9.8 导出通用模块

apps/common/__init__.py

python 复制代码
from .initialize import init_app, run
from .config import (
    AppConfig,
    ServerConfig,
    LoggingConfig,
    DocsConfig,
    DatabaseConfig,
    CONFIG_FILE,
    ROOT_DIR
)
from .logger import (
    init_logger,
    get_module_logger,
)
from .database import (
    Engine,
    Base,
)

__all__ = [
    "init_app",
    "run",
    "init_logger",
    "get_module_logger",
    "AppConfig",
    "ServerConfig",
    "LoggingConfig",
    "DocsConfig",
    "CONFIG_FILE",
    "ROOT_DIR",
    "DatabaseConfig",
    "Engine",
    "Base",
]

九、数据库使用示例

9.1 启动FastAPI程序

main.py

python 复制代码
from apps import common

app = common.init_app()

if __name__ == '__main__':
    common.run(app)

9.2 获取数据库配置

python 复制代码
from loguru import logger
from apps import common

common.init_logger()
logger.info(f"数据库配置:host={common.DatabaseConfig.host}", )
logger.info(f"数据库配置:port={common.DatabaseConfig.port}", )
logger.info(f"数据库配置:username={common.DatabaseConfig.username}", )
logger.info(f"数据库配置:database={common.DatabaseConfig.database}", )

9.3 创建数据库引擎

python 复制代码
import asyncio
from apps.common import DatabaseConfig
from sqlalchemy.ext.asyncio import create_async_engine


async def main():
    """主函数"""
    # 创建异步引擎
    engine = create_async_engine(
        DatabaseConfig.get_url(),
        **DatabaseConfig.get_engine_args()
    )
    print("✓ 数据库引擎创建成功")


if __name__ == "__main__":
    asyncio.run(main())

9.4 连接MySQL数据库

python 复制代码
import asyncio

from sqlalchemy import text

from apps.common import DatabaseConfig
from sqlalchemy.ext.asyncio import create_async_engine


async def main():
    """主函数"""
    # 创建异步引擎
    engine = create_async_engine(
        DatabaseConfig.get_url(),
        **DatabaseConfig.get_engine_args()
    )
    print("✓ 数据库引擎创建成功")

    # 使用连接
    async with engine.connect() as conn:
        result = await conn.execute(text("SELECT 1 as test"))
        row = result.fetchone()
        print(f"✓ 数据库连接测试成功: {row[0]}")

    # 确保正确关闭引擎
    await engine.dispose()


if __name__ == "__main__":
    asyncio.run(main())

9.5 查看数据库版本

python 复制代码
import asyncio

from sqlalchemy import text

from apps.common import DatabaseConfig
from sqlalchemy.ext.asyncio import create_async_engine


async def main():
    """主函数"""
    # 创建异步引擎
    engine = create_async_engine(
        DatabaseConfig.get_url(),
        **DatabaseConfig.get_engine_args()
    )
    print("✓ 数据库引擎创建成功")

    # 使用连接
    async with engine.connect() as conn:
        # 获取数据库版本
        result = await conn.execute(text("SELECT VERSION() as version"))
        row = result.fetchone()
        print(f"✓ MySQL版本: {row[0]}")

    # 确保正确关闭引擎
    await engine.dispose()


if __name__ == "__main__":
    asyncio.run(main())

9.6 创建数据库

python 复制代码
import asyncio

from sqlalchemy import text

from apps.common import DatabaseConfig
from sqlalchemy.ext.asyncio import create_async_engine


async def main():
    """主函数"""
    # 创建异步引擎
    engine = create_async_engine(
        DatabaseConfig.get_url(),
        **DatabaseConfig.get_engine_args()
    )
    print("✓ 数据库引擎创建成功")

    # 使用连接
    async with engine.connect() as conn:
        # 创建测试数据库
        await conn.execute(text("CREATE DATABASE IF NOT EXISTS fastapi_template_test"))
        await conn.commit()
        print("✓ 测试数据库创建成功")

    # 确保正确关闭引擎
    await engine.dispose()


if __name__ == "__main__":
    asyncio.run(main())

9.7 单例模式的数据库引擎

python 复制代码
import asyncio
from apps import common


async def main():
    """主函数"""
    engine1 = common.Engine()
    engine2 = common.Engine()
    engine3 = common.Engine.get_instance()

    print(f"engine1 id: {id(engine1)}")
    print(f"engine2 id: {id(engine2)}")
    print(f"engine3 id: {id(engine3)}")
    print(f"是否为同一实例: {engine1 is engine2 is engine3}")

    # 确保正确关闭引擎
    await engine1.dispose()


if __name__ == "__main__":
    asyncio.run(main())

9.8 测试数据库连接

python 复制代码
import asyncio
from apps import common


async def main():
    """主函数"""
    db = common.Engine()

    is_connected = await db.ping()
    print(f"数据库连接状态: {'成功' if is_connected else '失败'}")

    # 确保正确关闭引擎
    await db.dispose()


if __name__ == "__main__":
    asyncio.run(main())

9.9 获取数据库版本简化

python 复制代码
import asyncio
from apps import common


async def main():
    """主函数"""
    db = common.Engine()

    # 获取数据库版本
    version = await db.get_version()
    print(f"数据库版本: {version}")

    # 确保正确关闭引擎
    await db.dispose()


if __name__ == "__main__":
    asyncio.run(main())

9.10 使用上下文管理器

python 复制代码
import asyncio
from apps import common


async def main():
    """主函数"""
    db = common.Engine()

    # 使用会话上下文管理器
    async with db.session() as session:
        # 执行简单查询
        result = await session.execute("SELECT 1")
        value = result.scalar()
        print(f"查询结果: {value}")

    # 确保正确关闭引擎
    await db.dispose()


if __name__ == "__main__":
    asyncio.run(main())

9.11 带参数的查询

python 复制代码
import asyncio
from apps import common


async def main():
    """主函数"""
    db = common.Engine()

    # 使用会话上下文管理器
    async with db.session() as session:
        # 执行带参数的查询
        result = await session.execute(
            "SELECT :value + 1",
            {"value": 41}
        )
        value = result.scalar()
        print(f"计算结果: {value}")

    # 确保正确关闭引擎
    await db.dispose()


if __name__ == "__main__":
    asyncio.run(main())

9.12 使用事务上下文管理器

python 复制代码
import asyncio
from apps import common


async def main():
    """主函数"""
    db = common.Engine()

    # 使用事务上下文管理器
    async with db.begin() as session:
        # 在事务中执行多个操作
        await session.execute("""
                    CREATE TEMPORARY TABLE IF NOT EXISTS temp_test (
                        id INT PRIMARY KEY,
                        name VARCHAR(50)
                    )
                """)

        # 插入数据
        await session.execute(
            "INSERT INTO temp_test (id, name) VALUES (:id, :name)",
            [
                {"id": 1, "name": "测试1"},
                {"id": 2, "name": "测试2"},
                {"id": 3, "name": "测试3"}
            ]
        )

        # 查询数据
        result = await session.execute("SELECT * FROM temp_test ORDER BY id")
        rows = result.all()
        print("\n临时表数据:")
        for row in rows:
            print(f"ID: {row.id}, Name: {row.name}")

    # 确保正确关闭引擎
    await db.dispose()


if __name__ == "__main__":
    asyncio.run(main())

9.13 使用scalar方法

python 复制代码
import asyncio
from apps import common


async def main():
    """主函数"""
    db = common.Engine()

    # 使用事务上下文管理器
    async with db.begin() as session:
        # 在事务中执行多个操作
        await session.execute("""
                    CREATE TEMPORARY TABLE IF NOT EXISTS temp_test (
                        id INT PRIMARY KEY,
                        name VARCHAR(50)
                    )
                """)

        # 插入数据
        await session.execute(
            "INSERT INTO temp_test (id, name) VALUES (:id, :name)",
            [
                {"id": 1, "name": "测试1"},
                {"id": 2, "name": "测试2"},
                {"id": 3, "name": "测试3"}
            ]
        )

        # 查询数据
        result = await session.execute("SELECT * FROM temp_test ORDER BY id")
        rows = result.all()
        print("\n临时表数据:")
        for row in rows:
            print(f"ID: {row.id}, Name: {row.name}")

    # 确保正确关闭引擎
    await db.dispose()


if __name__ == "__main__":
    asyncio.run(main())

9.14 使用scalars方法

python 复制代码
import asyncio
from apps import common


async def main():
    """主函数"""
    db = common.Engine()

    # 使用 scalars() - MySQL 版本
    values = await db.scalars("""
            WITH RECURSIVE numbers AS (
                SELECT 1 as n
                UNION ALL
                SELECT n + 1 FROM numbers WHERE n < 3
            )
            SELECT n FROM numbers
        """)
    print(f"scalars() 结果: {values}")

    # 确保正确关闭引擎
    await db.dispose()


if __name__ == "__main__":
    asyncio.run(main())

9.15 使用fetchone方法

python 复制代码
import asyncio
from apps import common


async def main():
    """主函数"""
    db = common.Engine()

    # 使用 fetchone()
    row = await db.fetchone("SELECT 1 as num, 'test' as text")
    print(f"fetchone() 结果: {row}")

    # 确保正确关闭引擎
    await db.dispose()


if __name__ == "__main__":
    asyncio.run(main())

9.16 使用fetchall方法

python 复制代码
import asyncio
from apps import common


async def main():
    """主函数"""
    db = common.Engine()

    # 使用 fetchall()
    rows = await db.fetchall("""
            WITH RECURSIVE numbers AS (
                SELECT 1 as number
                UNION ALL
                SELECT number + 1 FROM numbers WHERE number < 3
            )
            SELECT 
                number,
                CONCAT('值', number) as text
            FROM numbers
        """)
    print(f"fetchall() 结果: {rows}")

    # 确保正确关闭引擎
    await db.dispose()


if __name__ == "__main__":
    asyncio.run(main())

9.17 使用add_table创建表

python 复制代码
import asyncio

from sqlalchemy import MetaData, Table, Column, Integer, String

from apps import common


async def main():
    """主函数"""
    db = common.Engine()

    # 定义测试表
    metadata = MetaData()
    test_table = Table(
        'test_table',
        metadata,
        Column('id', Integer, primary_key=True),
        Column('name', String(50)),
        Column('value', Integer)
    )

    # 创建表
    await db.add_table(metadata)
    print("✓ 表创建成功")

    # 确保正确关闭引擎
    await db.dispose()


if __name__ == "__main__":
    asyncio.run(main())

9.18 使用add插入单条数据

python 复制代码
import asyncio

from sqlalchemy import MetaData, Table, Column, Integer, String

from apps import common


async def main():
    """主函数"""
    db = common.Engine()

    # 定义测试表
    metadata = MetaData()
    test_table = Table(
        'test_table',
        metadata,
        Column('id', Integer, primary_key=True),
        Column('name', String(50)),
        Column('value', Integer)
    )

    # 删除表
    await db.delete_table(metadata)
    # 创建表
    await db.add_table(metadata)
    print("✓ 表创建成功")

    # 插入单条数据
    result = await db.add(test_table, {
        "name": "test1",
        "value": 100
    })
    print(f"✓ 插入单条数据成功,ID: {result[0]}")

    # 确保正确关闭引擎
    await db.dispose()


if __name__ == "__main__":
    asyncio.run(main())

9.19 使用add插入多条数据

python 复制代码
import asyncio

from sqlalchemy import MetaData, Table, Column, Integer, String

from apps import common


async def main():
    """主函数"""
    db = common.Engine()

    # 定义测试表
    metadata = MetaData()
    test_table = Table(
        'test_table',
        metadata,
        Column('id', Integer, primary_key=True),
        Column('name', String(50)),
        Column('value', Integer)
    )

    # 删除表
    await db.delete_table(metadata)
    # 创建表
    await db.add_table(metadata)
    print("✓ 表创建成功")

    # 批量插入数据
    await db.add(test_table, [
        {"name": "test2", "value": 200},
        {"name": "test3", "value": 300},
        {"name": "test4", "value": 400},
        {"name": "test5", "value": 500}
    ])
    print("✓ 批量插入数据成功")

    # 确保正确关闭引擎
    await db.dispose()


if __name__ == "__main__":
    asyncio.run(main())

9.20 使用get查询单条记录

python 复制代码
import asyncio

from sqlalchemy import MetaData, Table, Column, Integer, String

from apps import common


async def main():
    """主函数"""
    db = common.Engine()

    # 定义测试表
    metadata = MetaData()
    test_table = Table(
        'test_table',
        metadata,
        Column('id', Integer, primary_key=True),
        Column('name', String(50)),
        Column('value', Integer)
    )

    # 删除表
    await db.delete_table(metadata)
    # 创建表
    await db.add_table(metadata)
    print("✓ 表创建成功")

    # 批量插入数据
    await db.add(test_table, [
        {"name": "test1", "value": 100},
        {"name": "test2", "value": 200},
        {"name": "test3", "value": 300},
        {"name": "test4", "value": 400},
        {"name": "test5", "value": 500}
    ])
    print("✓ 批量插入数据成功")

    # 查询单条记录
    row = await db.get(test_table, test_table.c.name == "test1")
    print(f"✓ 查询单条记录: {row}")

    # 确保正确关闭引擎
    await db.dispose()


if __name__ == "__main__":
    asyncio.run(main())

9.21 使用update更新记录

python 复制代码
import asyncio

from sqlalchemy import MetaData, Table, Column, Integer, String

from apps import common


async def main():
    """主函数"""
    db = common.Engine()

    # 定义测试表
    metadata = MetaData()
    test_table = Table(
        'test_table',
        metadata,
        Column('id', Integer, primary_key=True),
        Column('name', String(50)),
        Column('value', Integer)
    )

    # 删除表
    await db.delete_table(metadata)
    # 创建表
    await db.add_table(metadata)
    print("✓ 表创建成功")

    # 批量插入数据
    await db.add(test_table, [
        {"name": "test1", "value": 100},
        {"name": "test2", "value": 200},
        {"name": "test3", "value": 300},
        {"name": "test4", "value": 400},
        {"name": "test5", "value": 500}
    ])
    print("✓ 批量插入数据成功")

    # 更新记录
    count = await db.update(
        test_table,
        test_table.c.name == "test1",
        {"value": 150}
    )
    print(f"✓ 更新记录成功,影响行数: {count}")

    # 查询单条记录
    row = await db.get(test_table, test_table.c.name == "test1")
    print(f"✓ 查询单条记录: {row}")

    # 确保正确关闭引擎
    await db.dispose()


if __name__ == "__main__":
    asyncio.run(main())

9.22 使用get_all查询所有记录

python 复制代码
import asyncio

from sqlalchemy import MetaData, Table, Column, Integer, String, desc

from apps import common


async def main():
    """主函数"""
    db = common.Engine()

    # 定义测试表
    metadata = MetaData()
    test_table = Table(
        'test_table',
        metadata,
        Column('id', Integer, primary_key=True),
        Column('name', String(50)),
        Column('value', Integer)
    )

    # 删除表
    await db.delete_table(metadata)
    # 创建表
    await db.add_table(metadata)
    print("✓ 表创建成功")

    # 批量插入数据
    await db.add(test_table, [
        {"name": "test1", "value": 100},
        {"name": "test2", "value": 200},
        {"name": "test3", "value": 300},
        {"name": "test4", "value": 400},
        {"name": "test5", "value": 500}
    ])
    print("✓ 批量插入数据成功")

    # 查询所有记录(带排序)
    rows = await db.get_all(
        test_table,
        order_by=desc(test_table.c.value)
    )
    print("\n✓ 所有记录(按value降序):")
    for row in rows:
        print(f"  - ID: {row['id']}, Name: {row['name']}, Value: {row['value']}")

    # 确保正确关闭引擎
    await db.dispose()


if __name__ == "__main__":
    asyncio.run(main())

9.23 使用get_page分页查询

python 复制代码
import asyncio

from sqlalchemy import MetaData, Table, Column, Integer, String, desc

from apps import common


async def main():
    """主函数"""
    db = common.Engine()

    # 定义测试表
    metadata = MetaData()
    test_table = Table(
        'test_table',
        metadata,
        Column('id', Integer, primary_key=True),
        Column('name', String(50)),
        Column('value', Integer)
    )

    # 删除表
    await db.delete_table(metadata)
    # 创建表
    await db.add_table(metadata)
    print("✓ 表创建成功")

    # 批量插入数据
    await db.add(test_table, [
        {"name": "test1", "value": 100},
        {"name": "test2", "value": 200},
        {"name": "test3", "value": 300},
        {"name": "test4", "value": 400},
        {"name": "test5", "value": 500}
    ])
    print("✓ 批量插入数据成功")

    # 分页查询
    page_size = 2
    for page in range(1, 4):
        items, total = await db.get_page(
            test_table,
            page=page,
            page_size=page_size,
            order_by=test_table.c.id
        )
        print(f"\n✓ 第{page}页数据(每页{page_size}条,总数{total}条):")
        for item in items:
            print(f"  - ID: {item['id']}, Name: {item['name']}, Value: {item['value']}")

    # 确保正确关闭引擎
    await db.dispose()


if __name__ == "__main__":
    asyncio.run(main())

9.24 使用delete删除数据

python 复制代码
import asyncio

from sqlalchemy import MetaData, Table, Column, Integer, String, desc

from apps import common


async def main():
    """主函数"""
    db = common.Engine()

    # 定义测试表
    metadata = MetaData()
    test_table = Table(
        'test_table',
        metadata,
        Column('id', Integer, primary_key=True),
        Column('name', String(50)),
        Column('value', Integer)
    )

    # 删除表
    await db.delete_table(metadata)
    # 创建表
    await db.add_table(metadata)
    print("✓ 表创建成功")

    # 批量插入数据
    await db.add(test_table, [
        {"name": "test1", "value": 100},
        {"name": "test2", "value": 200},
        {"name": "test3", "value": 300},
        {"name": "test4", "value": 400},
        {"name": "test5", "value": 500}
    ])
    print("✓ 批量插入数据成功")

    # 删除记录
    count = await db.delete(test_table, test_table.c.value > 300)
    print(f"\n✓ 删除记录成功,删除条数: {count}")

    # 确保正确关闭引擎
    await db.dispose()


if __name__ == "__main__":
    asyncio.run(main())

9.25 使用Base模型

python 复制代码
import asyncio
from typing import Optional

from sqlalchemy import Integer, String
from sqlalchemy.orm import Mapped, mapped_column

from apps import common


class User(common.Base):
    """用户模型"""
    __tablename__ = "users"

    # 自定义字段(id、created_at、updated_at 已在基类中定义)
    username: Mapped[str] = mapped_column(String(50), unique=True, nullable=False, comment="用户名")
    email: Mapped[str] = mapped_column(String(100), unique=True, nullable=False, comment="邮箱")
    age: Mapped[Optional[int]] = mapped_column(Integer, default=18, comment="年龄")

    def __repr__(self):
        return f"<User(id={self.id}, username='{self.username}', email='{self.email}', age={self.age})>"


async def main():
    """主函数"""
    db = common.Engine()

    # 删除表
    await db.delete_table(common.Base.metadata)
    # 创建表
    await db.add_table(common.Base.metadata)
    print("✓ 表创建成功")

    # 确保正确关闭引擎
    await db.dispose()


if __name__ == "__main__":
    asyncio.run(main())

9.26 使用模型的add添加数据

python 复制代码
import asyncio
from typing import Optional

from sqlalchemy import Integer, String
from sqlalchemy.orm import Mapped, mapped_column

from apps import common


class User(common.Base):
    """用户模型"""
    __tablename__ = "users"

    # 自定义字段(id、created_at、updated_at 已在基类中定义)
    username: Mapped[str] = mapped_column(String(50), unique=True, nullable=False, comment="用户名")
    email: Mapped[str] = mapped_column(String(100), unique=True, nullable=False, comment="邮箱")
    age: Mapped[Optional[int]] = mapped_column(Integer, default=18, comment="年龄")

    def __repr__(self):
        return f"<User(id={self.id}, username='{self.username}', email='{self.email}', age={self.age})>"


async def main():
    """主函数"""
    db = common.Engine()

    # 删除表
    await db.delete_table(common.Base.metadata)
    # 创建表
    await db.add_table(common.Base.metadata)
    print("✓ 表创建成功")

    async with db.session() as session:
        # 创建单个用户
        alice = User(username="alice", email="alice@example.com", age=25)
        await alice.add(session)
        print(f"   ✓ 创建用户: {alice}")

    # 确保正确关闭引擎
    await db.dispose()


if __name__ == "__main__":
    asyncio.run(main())

9.27 使用模型的add_many添加数据

python 复制代码
import asyncio
from typing import Optional

from sqlalchemy import Integer, String
from sqlalchemy.orm import Mapped, mapped_column

from apps import common


class User(common.Base):
    """用户模型"""
    __tablename__ = "users"

    # 自定义字段(id、created_at、updated_at 已在基类中定义)
    username: Mapped[str] = mapped_column(String(50), unique=True, nullable=False, comment="用户名")
    email: Mapped[str] = mapped_column(String(100), unique=True, nullable=False, comment="邮箱")
    age: Mapped[Optional[int]] = mapped_column(Integer, default=18, comment="年龄")

    def __repr__(self):
        return f"<User(id={self.id}, username='{self.username}', email='{self.email}', age={self.age})>"


async def main():
    """主函数"""
    db = common.Engine()

    # 删除表
    await db.delete_table(common.Base.metadata)
    # 创建表
    await db.add_table(common.Base.metadata)
    print("✓ 表创建成功")

    async with db.session() as session:
        # 批量创建用户
        users_data = [
            {"username": "bob", "email": "bob@example.com", "age": 30},
            {"username": "charlie", "email": "charlie@example.com", "age": 28},
            {"username": "dave", "email": "dave@example.com", "age": 35},
            {"username": "eve", "email": "eve@example.com", "age": 27}
        ]
        users = await User.add_many(session, users_data)
        print(f"   ✓ 批量创建用户: {len(users)} 个")

    # 确保正确关闭引擎
    await db.dispose()


if __name__ == "__main__":
    asyncio.run(main())

9.28 使用模型的get查询数据

python 复制代码
import asyncio
from typing import Optional

from sqlalchemy import Integer, String
from sqlalchemy.orm import Mapped, mapped_column

from apps import common


class User(common.Base):
    """用户模型"""
    __tablename__ = "users"

    # 自定义字段(id、created_at、updated_at 已在基类中定义)
    username: Mapped[str] = mapped_column(String(50), unique=True, nullable=False, comment="用户名")
    email: Mapped[str] = mapped_column(String(100), unique=True, nullable=False, comment="邮箱")
    age: Mapped[Optional[int]] = mapped_column(Integer, default=18, comment="年龄")

    def __repr__(self):
        return f"<User(id={self.id}, username='{self.username}', email='{self.email}', age={self.age})>"


async def main():
    """主函数"""
    db = common.Engine()

    # 删除表
    await db.delete_table(common.Base.metadata)
    # 创建表
    await db.add_table(common.Base.metadata)
    print("✓ 表创建成功")

    async with db.session() as session:
        # 创建单个用户
        alice = User(username="alice", email="alice@example.com", age=25)
        await alice.add(session)

        # 按ID查询
        user = await User.get(session, alice.id)
        print(f"   ✓ 按ID查询: {user}")

    # 确保正确关闭引擎
    await db.dispose()


if __name__ == "__main__":
    asyncio.run(main())

9.29 使用模型的get_by查询数据

python 复制代码
import asyncio
from typing import Optional

from sqlalchemy import Integer, String
from sqlalchemy.orm import Mapped, mapped_column

from apps import common


class User(common.Base):
    """用户模型"""
    __tablename__ = "users"

    # 自定义字段(id、created_at、updated_at 已在基类中定义)
    username: Mapped[str] = mapped_column(String(50), unique=True, nullable=False, comment="用户名")
    email: Mapped[str] = mapped_column(String(100), unique=True, nullable=False, comment="邮箱")
    age: Mapped[Optional[int]] = mapped_column(Integer, default=18, comment="年龄")

    def __repr__(self):
        return f"<User(id={self.id}, username='{self.username}', email='{self.email}', age={self.age})>"


async def main():
    """主函数"""
    db = common.Engine()

    # 删除表
    await db.delete_table(common.Base.metadata)
    # 创建表
    await db.add_table(common.Base.metadata)
    print("✓ 表创建成功")

    async with db.session() as session:
        # 批量创建用户
        users_data = [
            {"username": "bob", "email": "bob@example.com", "age": 30},
            {"username": "charlie", "email": "charlie@example.com", "age": 28},
            {"username": "dave", "email": "dave@example.com", "age": 35},
            {"username": "eve", "email": "eve@example.com", "age": 27}
        ]
        users = await User.add_many(session, users_data)
        print(f"   ✓ 批量创建用户: {len(users)} 个")

        # 按条件查询单个用户
        bob = await User.get_by(session, username="bob")
        print(f"   ✓ 按条件查询: {bob}")

    # 确保正确关闭引擎
    await db.dispose()


if __name__ == "__main__":
    asyncio.run(main())

9.30 使用模型的get_all查询数据

python 复制代码
import asyncio
from typing import Optional

from sqlalchemy import Integer, String
from sqlalchemy.orm import Mapped, mapped_column

from apps import common


class User(common.Base):
    """用户模型"""
    __tablename__ = "users"

    # 自定义字段(id、created_at、updated_at 已在基类中定义)
    username: Mapped[str] = mapped_column(String(50), unique=True, nullable=False, comment="用户名")
    email: Mapped[str] = mapped_column(String(100), unique=True, nullable=False, comment="邮箱")
    age: Mapped[Optional[int]] = mapped_column(Integer, default=18, comment="年龄")

    def __repr__(self):
        return f"<User(id={self.id}, username='{self.username}', email='{self.email}', age={self.age})>"


async def main():
    """主函数"""
    db = common.Engine()

    # 删除表
    await db.delete_table(common.Base.metadata)
    # 创建表
    await db.add_table(common.Base.metadata)
    print("✓ 表创建成功")

    async with db.session() as session:
        # 批量创建用户
        users_data = [
            {"username": "bob", "email": "bob@example.com", "age": 30},
            {"username": "charlie", "email": "charlie@example.com", "age": 28},
            {"username": "dave", "email": "dave@example.com", "age": 35},
            {"username": "eve", "email": "eve@example.com", "age": 27}
        ]
        users = await User.add_many(session, users_data)
        print(f"   ✓ 批量创建用户: {len(users)} 个")

        # 高级过滤查询
        older_users = await User.get_all(
            session,
            age__gt=28,  # 年龄大于28
            order_by="-age"  # 按年龄降序
        )
        print(f"   ✓ 年龄大于28的用户:")
        for user in older_users:
            print(f"      - {user}")

    # 确保正确关闭引擎
    await db.dispose()


if __name__ == "__main__":
    asyncio.run(main())

9.31 使用模型的get_page查询数据

python 复制代码
import asyncio
from typing import Optional

from sqlalchemy import Integer, String
from sqlalchemy.orm import Mapped, mapped_column

from apps import common


class User(common.Base):
    """用户模型"""
    __tablename__ = "users"

    # 自定义字段(id、created_at、updated_at 已在基类中定义)
    username: Mapped[str] = mapped_column(String(50), unique=True, nullable=False, comment="用户名")
    email: Mapped[str] = mapped_column(String(100), unique=True, nullable=False, comment="邮箱")
    age: Mapped[Optional[int]] = mapped_column(Integer, default=18, comment="年龄")

    def __repr__(self):
        return f"<User(id={self.id}, username='{self.username}', email='{self.email}', age={self.age})>"


async def main():
    """主函数"""
    db = common.Engine()

    # 删除表
    await db.delete_table(common.Base.metadata)
    # 创建表
    await db.add_table(common.Base.metadata)
    print("✓ 表创建成功")

    async with db.session() as session:
        # 批量创建用户
        users_data = [
            {"username": "bob", "email": "bob@example.com", "age": 30},
            {"username": "charlie", "email": "charlie@example.com", "age": 28},
            {"username": "dave", "email": "dave@example.com", "age": 35},
            {"username": "eve", "email": "eve@example.com", "age": 27}
        ]
        users = await User.add_many(session, users_data)
        print(f"   ✓ 批量创建用户: {len(users)} 个")

        # 分页查询
        page = 1
        page_size = 2
        items, total = await User.get_page(
            session,
            page=page,
            page_size=page_size,
            order_by=["age", "-username"]  # 多字段排序:先按年龄升序,再按用户名降序
        )
        print(f"\n   ✓ 分页查询(第{page}页,每页{page_size}条,总数{total}条):")
        for item in items:
            print(f"      - {item}")

    # 确保正确关闭引擎
    await db.dispose()


if __name__ == "__main__":
    asyncio.run(main())

9.32 使用模型的update更新数据

python 复制代码
import asyncio
from typing import Optional

from sqlalchemy import Integer, String
from sqlalchemy.orm import Mapped, mapped_column

from apps import common


class User(common.Base):
    """用户模型"""
    __tablename__ = "users"

    # 自定义字段(id、created_at、updated_at 已在基类中定义)
    username: Mapped[str] = mapped_column(String(50), unique=True, nullable=False, comment="用户名")
    email: Mapped[str] = mapped_column(String(100), unique=True, nullable=False, comment="邮箱")
    age: Mapped[Optional[int]] = mapped_column(Integer, default=18, comment="年龄")

    def __repr__(self):
        return f"<User(id={self.id}, username='{self.username}', email='{self.email}', age={self.age})>"


async def main():
    """主函数"""
    db = common.Engine()

    # 删除表
    await db.delete_table(common.Base.metadata)
    # 创建表
    await db.add_table(common.Base.metadata)
    print("✓ 表创建成功")

    async with db.session() as session:
        # 创建单个用户
        alice = User(username="alice", email="alice@example.com", age=25)
        await alice.add(session)
        print(f"   ✓ 创建用户: {alice}")

        # 更新数据
        await alice.update(session, age=26, email="alice.new@example.com")
        print(f"   ✓ 更新用户: {alice}")

    # 确保正确关闭引擎
    await db.dispose()


if __name__ == "__main__":
    asyncio.run(main())

9.33 使用模型的update_many更新数据

python 复制代码
import asyncio
from typing import Optional

from sqlalchemy import Integer, String
from sqlalchemy.orm import Mapped, mapped_column

from apps import common


class User(common.Base):
    """用户模型"""
    __tablename__ = "users"

    # 自定义字段(id、created_at、updated_at 已在基类中定义)
    username: Mapped[str] = mapped_column(String(50), unique=True, nullable=False, comment="用户名")
    email: Mapped[str] = mapped_column(String(100), unique=True, nullable=False, comment="邮箱")
    age: Mapped[Optional[int]] = mapped_column(Integer, default=18, comment="年龄")

    def __repr__(self):
        return f"<User(id={self.id}, username='{self.username}', email='{self.email}', age={self.age})>"


async def main():
    """主函数"""
    db = common.Engine()

    # 删除表
    await db.delete_table(common.Base.metadata)
    # 创建表
    await db.add_table(common.Base.metadata)
    print("✓ 表创建成功")

    async with db.session() as session:
        # 批量创建用户
        users_data = [
            {"username": "bob", "email": "bob@example.com", "age": 30},
            {"username": "charlie", "email": "charlie@example.com", "age": 28},
            {"username": "dave", "email": "dave@example.com", "age": 35},
            {"username": "eve", "email": "eve@example.com", "age": 27}
        ]
        users = await User.add_many(session, users_data)
        print(f"   ✓ 批量创建用户: {len(users)} 个")

        # 批量更新用户
        count = await User.update_many(
            session,
            ids=[1, 2, 3],
            update_data={"age": 40}
        )
        print(f"   ✓ 批量更新用户: {count} 个")

    # 确保正确关闭引擎
    await db.dispose()


if __name__ == "__main__":
    asyncio.run(main())

9.34 使用模型的delete删除数据

python 复制代码
import asyncio
from typing import Optional

from sqlalchemy import Integer, String
from sqlalchemy.orm import Mapped, mapped_column

from apps import common


class User(common.Base):
    """用户模型"""
    __tablename__ = "users"

    # 自定义字段(id、created_at、updated_at 已在基类中定义)
    username: Mapped[str] = mapped_column(String(50), unique=True, nullable=False, comment="用户名")
    email: Mapped[str] = mapped_column(String(100), unique=True, nullable=False, comment="邮箱")
    age: Mapped[Optional[int]] = mapped_column(Integer, default=18, comment="年龄")

    def __repr__(self):
        return f"<User(id={self.id}, username='{self.username}', email='{self.email}', age={self.age})>"


async def main():
    """主函数"""
    db = common.Engine()

    # 删除表
    await db.delete_table(common.Base.metadata)
    # 创建表
    await db.add_table(common.Base.metadata)
    print("✓ 表创建成功")

    async with db.session() as session:
        # 创建单个用户
        alice = User(username="alice", email="alice@example.com", age=25)
        await alice.add(session)
        print(f"   ✓ 创建用户: {alice}")

        # 删除用户
        success = await alice.delete(session)
        print(f"   ✓ 删除用户 {alice.username}: {'成功' if success else '失败'}")

    # 确保正确关闭引擎
    await db.dispose()


if __name__ == "__main__":
    asyncio.run(main())

9.35 使用模型的delete_many删除数据

python 复制代码
import asyncio
from typing import Optional

from sqlalchemy import Integer, String
from sqlalchemy.orm import Mapped, mapped_column

from apps import common


class User(common.Base):
    """用户模型"""
    __tablename__ = "users"

    # 自定义字段(id、created_at、updated_at 已在基类中定义)
    username: Mapped[str] = mapped_column(String(50), unique=True, nullable=False, comment="用户名")
    email: Mapped[str] = mapped_column(String(100), unique=True, nullable=False, comment="邮箱")
    age: Mapped[Optional[int]] = mapped_column(Integer, default=18, comment="年龄")

    def __repr__(self):
        return f"<User(id={self.id}, username='{self.username}', email='{self.email}', age={self.age})>"


async def main():
    """主函数"""
    db = common.Engine()

    # 删除表
    await db.delete_table(common.Base.metadata)
    # 创建表
    await db.add_table(common.Base.metadata)
    print("✓ 表创建成功")

    async with db.session() as session:
        # 批量创建用户
        users_data = [
            {"username": "bob", "email": "bob@example.com", "age": 30},
            {"username": "charlie", "email": "charlie@example.com", "age": 28},
            {"username": "dave", "email": "dave@example.com", "age": 35},
            {"username": "eve", "email": "eve@example.com", "age": 27}
        ]
        users = await User.add_many(session, users_data)
        print(f"   ✓ 批量创建用户: {len(users)} 个")

        # 删除用户
        count = await User.bulk_delete(
            session,
            ids=[1, 2, 3]
        )
        print(f"   ✓ 批量删除用户: {count} 个")

    # 确保正确关闭引擎
    await db.dispose()


if __name__ == "__main__":
    asyncio.run(main())

十、Docker部署

10.1 Docker部署MySQL

启动容器:

bash 复制代码
docker run -d --name mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=Ygg!@#123456 --restart=always mysql:8.0.25

停止容器:

bash 复制代码
docker stop mysql

删除容器:

bash 复制代码
docker rm mysql

10.2 Docker部署Redis

启动容器:

bash 复制代码
docker run -d --name redis -p 6379:6379 --restart=always redis:7-alpine redis-server --appendonly yes --requirepass "Ygg!@#123456"

停止容器:

bash 复制代码
docker stop redis

删除容器:

bash 复制代码
docker rm redis

10.3 启动脚本

start.sh

bash 复制代码
#!/bin/bash

# 设置默认环境变量
export PORT=${PORT:-8888}
export HOST=${HOST:-0.0.0.0}
export WORKERS=${WORKERS:-1}
export LOG_LEVEL=${LOG_LEVEL:-info}
export RELOAD=${RELOAD:-false}

# 创建日志目录
mkdir -p logs

# 等待依赖服务就绪的函数
wait_for_dependencies() {
    # 这里可以添加数据库或其他服务的健康检查
    # 例如: wait-for-it.sh database:5432 -t 60
    echo "Checking dependencies..."
    return 0
}

# 清理函数
cleanup() {
    echo "Received stop signal, shutting down gracefully..."
    kill -TERM "$child" 2>/dev/null
}

# 错误处理
set -e

# 注册清理函数
trap cleanup SIGTERM SIGINT

# 等待依赖服务
wait_for_dependencies

# 启动应用
echo "Starting FastAPI application on $HOST:$PORT"
if [ "$RELOAD" = "true" ]; then
    python -m uvicorn main:app \
        --host "$HOST" \
        --port "$PORT" \
        --reload \
        --log-level "$LOG_LEVEL"
else
    python -m uvicorn main:app \
        --host "$HOST" \
        --port "$PORT" \
        --workers "$WORKERS" \
        --log-level "$LOG_LEVEL"
fi &

child=$!
wait "$child"

10.4 Dockerfile

dockerfile 复制代码
# ==============================
# FastAPI Docker 部署文件
# 特性:多阶段构建、国内源加速、镜像大小优化
# ==============================

# ===============================
# 第一阶段:依赖构建阶段 (Builder)
# ===============================
FROM python:3.12-slim AS builder

# 设置环境变量
ENV PYTHONUNBUFFERED=1 \
    PYTHONDONTWRITEBYTECODE=1 \
    PIP_NO_CACHE_DIR=1 \
    PIP_DISABLE_PIP_VERSION_CHECK=1

# 使用国内源加速
RUN sed -i 's/deb.debian.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apt/sources.list.d/debian.sources && \
    sed -i 's/security.debian.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apt/sources.list.d/debian.sources

# 安装系统依赖和构建工具
RUN apt-get update && apt-get install -y --no-install-recommends \
    build-essential \
    && rm -rf /var/lib/apt/lists/*

# 升级pip并配置国内源
RUN pip install --upgrade pip -i https://pypi.tuna.tsinghua.edu.cn/simple/ && \
    pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple/ && \
    pip config set global.trusted-host pypi.tuna.tsinghua.edu.cn

# 设置工作目录
WORKDIR /build

# 复制依赖文件
COPY requirements.txt .

# 安装Python依赖到临时目录
RUN pip install --user --no-warn-script-location -r requirements.txt

# ===============================
# 第二阶段:运行时镜像 (Runtime)
# ===============================
FROM python:3.12-slim AS runtime

# 设置环境变量
ENV PYTHONUNBUFFERED=1 \
    PYTHONDONTWRITEBYTECODE=1 \
    PYTHONPATH=/app \
    PATH="/home/appuser/.local/bin:$PATH" \
    TZ=Asia/Shanghai \
    PORT=8888 \
    HOST=0.0.0.0 \
    WORKERS=1 \
    LOG_LEVEL=info \
    RELOAD=false

# 使用国内源加速
RUN sed -i 's/deb.debian.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apt/sources.list.d/debian.sources && \
    sed -i 's/security.debian.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apt/sources.list.d/debian.sources

# 安装运行时依赖
RUN apt-get update && apt-get install -y --no-install-recommends \
    curl \
    tzdata \
    dos2unix \
    && rm -rf /var/lib/apt/lists/* \
    && apt-get clean

# 配置时区
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

# 创建非root用户
RUN groupadd --gid 1000 appgroup && \
    useradd --uid 1000 --gid appgroup --shell /bin/bash --create-home appuser

# 设置工作目录
WORKDIR /app

# 从构建阶段复制Python依赖
COPY --from=builder --chown=appuser:appgroup /root/.local /home/appuser/.local

# 复制配置文件
COPY --chown=appuser:appgroup config.yaml .

# 复制应用代码
COPY --chown=appuser:appgroup apps/ ./apps/
COPY --chown=appuser:appgroup main.py ./

# 创建日志目录
RUN mkdir -p logs && chown -R appuser:appgroup /app

# 切换到非root用户
USER appuser

# 健康检查
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
    CMD curl -f http://localhost:${PORT}/ || exit 1

# 暴露端口
EXPOSE ${PORT}

# 复制并设置启动脚本
COPY --chown=appuser:appgroup start.sh .
USER root
RUN dos2unix start.sh && chmod +x start.sh
USER appuser

# 默认启动命令
CMD ["./start.sh"]

10.5 环境变量说明

环境变量 默认值 说明
PORT 8888 服务监听端口
HOST 0.0.0.0 服务监听地址
WORKERS 1 工作进程数(生产环境建议:CPU核心数 * 2 + 1)
LOG_LEVEL info 日志级别(debug/info/warning/error/critical)
RELOAD false 是否启用热重载(开发环境使用)

10.6 开发环境运行

Linux/macOS:

bash 复制代码
docker run -d --name fastapi-dev \
  -p 8888:8888 \
  -v $(pwd)/logs:/app/logs \
  -v $(pwd)/apps:/app/apps \
  -e PORT=8888 \
  -e RELOAD=true \
  -e LOG_LEVEL=debug \
  --restart=unless-stopped \
  fastapi-template:latest

Windows PowerShell:

powershell 复制代码
docker run -d --name fastapi-dev `
  -p 8888:8888 `
  -v ${PWD}/logs:/app/logs `
  -v ${PWD}/apps:/app/apps `
  -e PORT=8888 `
  -e RELOAD=true `
  -e LOG_LEVEL=debug `
  --restart=unless-stopped `
  fastapi-template:latest

10.7 生产环境运行

Linux/macOS:

bash 复制代码
docker run -d --name fastapi-prod \
  -p 8888:8888 \
  -v $(pwd)/logs:/app/logs \
  -e WORKERS=4 \
  -e LOG_LEVEL=warning \
  --restart=unless-stopped \
  fastapi-template:latest

Windows PowerShell:

powershell 复制代码
docker run -d --name fastapi-prod `
  -p 8888:8888 `
  -v ${PWD}/logs:/app/logs `
  -e WORKERS=4 `
  -e LOG_LEVEL=warning `
  --restart=unless-stopped `
  fastapi-template:latest

10.8 查看容器日志

bash 复制代码
# 实时查看日志
docker logs -f fastapi-prod

# 查看最近100行日志
docker logs --tail 100 fastapi-prod

10.9 容器管理

bash 复制代码
# 停止容器
docker stop fastapi-prod

# 启动容器
docker start fastapi-prod

# 重启容器
docker restart fastapi-prod

# 删除容器
docker rm -f fastapi-prod
相关推荐
Python私教10 小时前
FastAPI+React19 ERP系统实战 第01期
react·fastapi
小宁爱Python2 天前
FastAPI+Sqlite+HTML的登录注册与文件上传系统:完整实现指南
sqlite·html·fastapi
fairymt3 天前
LoRA 问答微调与部署全流程:基于 LLaMA-Factory + DeepSeek + FastAPI 打造专属大模型
fastapi
chenkangck5012 天前
Uvicorn 入门详解
python·fastapi
remandancy.h16 天前
FastAPI:(2)开启FastAPI
fastapi
里探16 天前
FastAPI的初步学习(Django用户过来的)
python·django·fastapi
从零开始学习人工智能16 天前
基于FastAPI与Selenium的智能开关状态管理系统实践
selenium·adb·fastapi
Python私教18 天前
FastAPI本地文档的定制技巧
fastapi
沛沛老爹19 天前
Celery+fastAPI/Flask实现高性能应用
python·flask·fastapi·celery·web框架集成