Python后端标准分包思想(完整版)

本文整理Python后端(以FastAPI为主,适配Flask)最标准、最通用、企业级的分包架构,包含所有核心分层(schema、controller、service、crud等),明确各层职责、调用关系和目录规范,可直接作为项目搭建模板和学习参考。

一、完整分包体系(核心分层)

Python后端标准分包共8个核心模块,各司其职、层层解耦,完整结构如下(按调用优先级排序):

 app/
 ├── main.py                 # 项目入口(启动服务、注册路由)
 ├── database.py              # 数据库连接配置(引擎、会话、连接池)
 ├── settings.py              # 全局项目配置(环境变量、端口、密钥等)
 ├── schemas/                 # 数据模型层(请求/响应参数、数据校验)
 ├── controllers/             # 接口路由层(接收请求、返回结果)
 ├── services/                # 业务逻辑层(核心业务规则、流程控制)
 ├── crud/                    # 数据库操作层(基础增删改查)
 ├── models/                  # 数据库表模型层(ORM映射、表结构定义)
 ├── utils/                   # 工具函数层(通用工具、与业务无关)
 └── common/                  # 公共模块层(全局常量、异常、公共配置)

二、各层详细职责(人话版,必懂)

1. schemas(数据模型层)

核心职责:只定义数据结构,负责数据的“进”(请求参数)和“出”(响应结果),以及数据校验。

核心内容

  • 基于Pydantic定义模型(请求模型、响应模型)

  • 字段限制(如必填、长度、类型、枚举)

  • 数据序列化/反序列化(与数据库模型、接口参数映射)

  • 请求参数校验(避免非法数据进入业务层)

禁忌:不写业务逻辑、不直接操作数据库、不处理接口请求。

# schemas/pet.py
from pydantic import BaseModel, Field, validator
from datetime import date
from typing import Optional
from enum import Enum

# 枚举类(也可以放在common,这里演示)
class PetStatusEnum(str, Enum):
    available = "available"
    adopted = "adopted"
    pending = "pending"

# 请求模型:创建宠物
class PetCreateRequest(BaseModel):
    name: str = Field(..., min_length=1, max_length=50, description="宠物名字")
    species: str = Field(..., description="品种,如:狗、猫")
    age_months: int = Field(..., ge=0, le=360, description="年龄(月)")
    price: float = Field(..., ge=0, description="领养价格")
    description: Optional[str] = Field(None, max_length=500)
    status: PetStatusEnum = PetStatusEnum.available

    @validator('name')
    def validate_name(cls, v):
        if not v.strip():
            raise ValueError('宠物名字不能为空')
        return v.strip()

# 请求模型:查询宠物列表
class PetQueryParams(BaseModel):
    species: Optional[str] = None
    min_age: Optional[int] = Field(None, ge=0)
    max_age: Optional[int] = Field(None, le=360)
    status: Optional[PetStatusEnum] = None
    page: int = Field(1, ge=1)
    page_size: int = Field(10, ge=1, le=100)

# 响应模型:宠物信息
class PetResponse(BaseModel):
    id: int
    name: str
    species: str
    age_months: int
    price: float
    description: Optional[str]
    status: str
    created_at: str
    updated_at: str

    class Config:
        # 允许从ORM对象转换
        from_attributes = True

# 通用分页响应模型
class PageResponse(BaseModel):
    total: int
    page: int
    page_size: int
    items: list

2. controllers(接口路由层,别名:router/api)

核心职责:作为前端与后端的桥梁,只负责“接请求、收参数、返结果”。

核心内容

  • 定义接口路由

  • 接收前端传递的参数(依赖schemas模型做校验)

  • 调用service层的业务方法,不直接操作数据库

  • 统一返回格式(成功/失败响应体)、捕获接口异常

禁忌:不写复杂业务逻辑、不直接写SQL/ORM语句、不处理数据校验。

# controllers/pet_controller.py
from fastapi import APIRouter, Depends, HTTPException, Query
from typing import List
from sqlalchemy.orm import Session

from database import get_db
from schemas.pet import (
    PetCreateRequest, PetResponse, PetQueryParams, PageResponse
)
from services.pet_service import PetService
from common.response import success_response, error_response
from common.exceptions import BusinessException

# 创建路由器
router = APIRouter(prefix="/api/pets", tags=["宠物管理"])

@router.post("/", response_model=dict, summary="创建宠物")
async def create_pet(
    pet_data: PetCreateRequest,  # 自动校验
    db: Session = Depends(get_db)
):
    """创建新宠物(仅管理员)"""
    try:
        pet_service = PetService(db)
        pet = pet_service.create_pet(pet_data.dict())
        return success_response(data=pet, message="创建成功")
    except BusinessException as e:
        return error_response(code=e.code, message=str(e))
    except Exception as e:
        return error_response(message=f"创建失败:{str(e)}")

@router.get("/", response_model=dict, summary="获取宠物列表")
async def get_pet_list(
    species: Optional[str] = Query(None, description="品种"),
    min_age: Optional[int] = Query(None, ge=0),
    max_age: Optional[int] = Query(None, le=360),
    status: Optional[str] = None,
    page: int = Query(1, ge=1),
    page_size: int = Query(10, ge=1, le=100),
    db: Session = Depends(get_db)
):
    """分页查询宠物列表"""
    try:
        # 组装查询参数
        params = PetQueryParams(
            species=species,
            min_age=min_age,
            max_age=max_age,
            status=status,
            page=page,
            page_size=page_size
        )
        pet_service = PetService(db)
        result = pet_service.get_pet_list(params)
        return success_response(data=result)
    except Exception as e:
        return error_response(message=str(e))

@router.get("/{pet_id}", response_model=dict, summary="获取宠物详情")
async def get_pet_detail(
    pet_id: int,
    db: Session = Depends(get_db)
):
    """根据ID获取宠物详细信息"""
    pet_service = PetService(db)
    pet = pet_service.get_pet_by_id(pet_id)
    if not pet:
        return error_response(code=404, message="宠物不存在")
    return success_response(data=pet)

@router.put("/{pet_id}", summary="更新宠物信息")
async def update_pet(
    pet_id: int,
    pet_data: PetCreateRequest,
    db: Session = Depends(get_db)
):
    """更新宠物信息(仅管理员)"""
    pet_service = PetService(db)
    pet = pet_service.update_pet(pet_id, pet_data.dict())
    if not pet:
        return error_response(code=404, message="宠物不存在")
    return success_response(data=pet, message="更新成功")

@router.delete("/{pet_id}", summary="删除宠物")
async def delete_pet(
    pet_id: int,
    db: Session = Depends(get_db)
):
    """删除宠物(仅管理员)"""
    pet_service = PetService(db)
    success = pet_service.delete_pet(pet_id)
    if not success:
        return error_response(code=404, message="宠物不存在")
    return success_response(message="删除成功")

3. services(业务逻辑层)

核心职责:项目的核心,负责所有业务规则、流程控制和逻辑判断。

核心内容

  • 实现核心业务逻辑(如宠物领养判断、权限校验、流程串联)

  • 组合多个crud操作(如“领养宠物”=查询宠物+查询用户+新增领养记录)

  • 事务控制(如操作失败回滚数据)

  • 调用utils工具函数、common公共常量

禁忌:不直接操作数据库原生语句、不接收前端请求、不定义数据结构。

# services/pet_service.py
from typing import Dict, List, Optional
from sqlalchemy.orm import Session
from datetime import datetime

from crud.pet_crud import PetCRUD
from services.adopt_service import AdoptService
from common.exceptions import BusinessException
from utils.logger import log_business

class PetService:
    """宠物业务逻辑层"""
    
    def __init__(self, db: Session):
        self.db = db
        self.pet_crud = PetCRUD(db)
    
    @log_business("创建宠物")
    def create_pet(self, pet_data: Dict) -> Dict:
        """创建宠物(业务规则:价格不能为负数,年龄不能太大)"""
        # 业务校验
        if pet_data.get('price', 0) < 0:
            raise BusinessException("宠物价格不能为负数")
        
        if pet_data.get('age_months', 0) > 360:
            raise BusinessException("宠物年龄不能超过30岁")
        
        # 调用数据库操作
        pet = self.pet_crud.create(pet_data)
        
        # 记录日志(业务操作)
        print(f"[业务日志] 创建宠物成功:{pet['name']}")
        
        return pet
    
    def get_pet_list(self, query_params) -> Dict:
        """获取宠物列表(支持过滤和分页)"""
        # 构建过滤条件
        filters = {}
        if query_params.species:
            filters['species'] = query_params.species
        if query_params.status:
            filters['status'] = query_params.status
        
        # 年龄范围过滤
        age_range = None
        if query_params.min_age or query_params.max_age:
            age_range = (query_params.min_age, query_params.max_age)
        
        # 查询数据
        pets, total = self.pet_crud.get_list(
            filters=filters,
            age_range=age_range,
            page=query_params.page,
            page_size=query_params.page_size
        )
        
        return {
            "total": total,
            "page": query_params.page,
            "page_size": query_params.page_size,
            "items": pets
        }
    
    def get_pet_by_id(self, pet_id: int) -> Optional[Dict]:
        """根据ID获取宠物详情"""
        return self.pet_crud.get_by_id(pet_id)
    
    @log_business("更新宠物")
    def update_pet(self, pet_id: int, update_data: Dict) -> Optional[Dict]:
        """更新宠物信息(业务规则:已领养的不能修改)"""
        # 先查询宠物
        pet = self.pet_crud.get_by_id(pet_id)
        if not pet:
            return None
        
        # 业务规则:已领养的宠物不能修改
        if pet['status'] == 'adopted':
            raise BusinessException("已领养的宠物无法修改信息")
        
        # 执行更新
        updated_pet = self.pet_crud.update(pet_id, update_data)
        
        return updated_pet
    
    @log_business("删除宠物")
    def delete_pet(self, pet_id: int) -> bool:
        """删除宠物(业务规则:已领养的不能删除)"""
        pet = self.pet_crud.get_by_id(pet_id)
        if not pet:
            return False
        
        if pet['status'] == 'adopted':
            raise BusinessException("已领养的宠物无法删除")
        
        return self.pet_crud.delete(pet_id)
    
    def adopt_pet(self, pet_id: int, user_id: int) -> Dict:
        """
        领养宠物(组合多个CRUD操作,事务控制)
        业务规则:
        1. 宠物必须存在且状态为available
        2. 用户必须存在且信用良好
        3. 创建领养记录
        4. 更新宠物状态为adopted
        """
        # 1. 检查宠物状态
        pet = self.pet_crud.get_by_id(pet_id)
        if not pet:
            raise BusinessException("宠物不存在")
        
        if pet['status'] != 'available':
            raise BusinessException("该宠物已被领养或不可领养")
        
        # 2. 调用用户服务检查用户(示例调用其他service)
        from services.user_service import UserService
        user_service = UserService(self.db)
        user = user_service.get_user_by_id(user_id)
        if not user:
            raise BusinessException("用户不存在")
        
        if user.get('credit_score', 0) < 60:
            raise BusinessException("用户信用分不足,无法领养")
        
        # 3. 创建领养记录
        adopt_service = AdoptService(self.db)
        adopt_record = adopt_service.create_adopt_record({
            'pet_id': pet_id,
            'user_id': user_id,
            'status': 'pending',
            'created_at': datetime.now()
        })
        
        # 4. 更新宠物状态(事务:如果失败要回滚)
        try:
            # 开启事务(由数据库会话管理)
            self.pet_crud.update(pet_id, {'status': 'adopted'})
            self.db.commit()
        except Exception as e:
            self.db.rollback()
            raise BusinessException(f"领养失败:{str(e)}")
        
        return {
            'adopt_id': adopt_record['id'],
            'pet': pet,
            'user': user
        }

4. crud(数据库操作层)

核心职责:只负责最基础的数据库增删改查,提供“原子化”数据库操作能力。

核心内容

  • 封装单表操作(查一条、查列表、新增、修改、删除)

  • 基于SQLAlchemy ORM编写基础操作(不写复杂联表查询,复杂查询可在service层组合)

  • 只关注数据库操作,不关心业务逻辑

禁忌:不写业务判断、不处理接口请求、不定义数据校验规则。

# crud/pet_crud.py
from typing import Dict, List, Optional, Tuple
from sqlalchemy.orm import Session
from sqlalchemy import and_, or_, desc, asc
from models.pet import PetModel

class PetCRUD:
    """宠物数据库操作层 - 只做原子化CRUD"""
    
    def __init__(self, db: Session):
        self.db = db
    
    def create(self, data: Dict) -> Dict:
        """创建单条记录"""
        pet = PetModel(**data)
        self.db.add(pet)
        self.db.commit()
        self.db.refresh(pet)
        return self._to_dict(pet)
    
    def get_by_id(self, pet_id: int) -> Optional[Dict]:
        """根据ID查询"""
        pet = self.db.query(PetModel).filter(PetModel.id == pet_id).first()
        return self._to_dict(pet) if pet else None
    
    def get_list(
        self,
        filters: Dict = None,
        age_range: Tuple[int, int] = None,
        page: int = 1,
        page_size: int = 10,
        order_by: str = "-id"
    ) -> Tuple[List[Dict], int]:
        """
        查询列表(支持过滤、分页、排序)
        返回:(数据列表, 总数量)
        """
        query = self.db.query(PetModel)
        
        # 应用过滤条件
        if filters:
            for key, value in filters.items():
                if hasattr(PetModel, key) and value is not None:
                    query = query.filter(getattr(PetModel, key) == value)
        
        # 年龄范围过滤
        if age_range:
            min_age, max_age = age_range
            if min_age is not None:
                query = query.filter(PetModel.age_months >= min_age)
            if max_age is not None:
                query = query.filter(PetModel.age_months <= max_age)
        
        # 排序(处理 -id 表示降序)
        if order_by.startswith("-"):
            order_field = getattr(PetModel, order_by[1:])
            query = query.order_by(desc(order_field))
        else:
            order_field = getattr(PetModel, order_by)
            query = query.order_by(asc(order_field))
        
        # 获取总数
        total = query.count()
        
        # 分页
        query = query.offset((page - 1) * page_size).limit(page_size)
        pets = query.all()
        
        return [self._to_dict(pet) for pet in pets], total
    
    def update(self, pet_id: int, update_data: Dict) -> Optional[Dict]:
        """更新记录"""
        pet = self.db.query(PetModel).filter(PetModel.id == pet_id).first()
        if not pet:
            return None
        
        for key, value in update_data.items():
            if hasattr(pet, key) and value is not None:
                setattr(pet, key, value)
        
        self.db.commit()
        self.db.refresh(pet)
        return self._to_dict(pet)
    
    def delete(self, pet_id: int) -> bool:
        """删除记录"""
        pet = self.db.query(PetModel).filter(PetModel.id == pet_id).first()
        if not pet:
            return False
        
        self.db.delete(pet)
        self.db.commit()
        return True
    
    def update_status(self, pet_id: int, status: str) -> bool:
        """单独更新状态(原子操作)"""
        pet = self.db.query(PetModel).filter(PetModel.id == pet_id).first()
        if not pet:
            return False
        pet.status = status
        self.db.commit()
        return True
    
    def _to_dict(self, pet: PetModel) -> Dict:
        """ORM对象转字典"""
        return {
            'id': pet.id,
            'name': pet.name,
            'species': pet.species,
            'age_months': pet.age_months,
            'price': float(pet.price) if pet.price else 0,
            'description': pet.description,
            'status': pet.status,
            'created_at': pet.created_at.strftime('%Y-%m-%d %H:%M:%S') if pet.created_at else None,
            'updated_at': pet.updated_at.strftime('%Y-%m-%d %H:%M:%S') if pet.updated_at else None
        }

5. models(数据库表模型层)

核心职责:定义数据库表结构,实现ORM映射(将Python类映射到数据库表)。

核心内容

  • 定义表字段(字段名、类型、主键、外键、默认值)

  • 关联表关系(一对一、一对多、多对多)

  • 与schemas模型做映射(数据库数据 ↔ 接口数据)

禁忌:不写业务逻辑、不处理接口请求、不包含工具方法。

# models/pet.py
from sqlalchemy import Column, Integer, String, Float, DateTime, Text, Enum as SQLEnum
from sqlalchemy.sql import func
from database import Base
import enum


# 宠物状态枚举
class PetStatus(str, enum.Enum):
    AVAILABLE = "available"
    ADOPTED = "adopted"
    PENDING = "pending"

class PetModel(Base):
    """宠物表模型"""
    __tablename__ = "pets"
    
    id = Column(Integer, primary_key=True, index=True, autoincrement=True)
    name = Column(String(50), nullable=False, comment="宠物名字")
    species = Column(String(50), nullable=False, comment="品种")
    age_months = Column(Integer, nullable=False, default=0, comment="年龄(月)")
    price = Column(Float, nullable=False, default=0, comment="领养价格")
    description = Column(Text, nullable=True, comment="描述")
    status = Column(
        SQLEnum(PetStatus),
        nullable=False,
        default=PetStatus.AVAILABLE,
        comment="状态:available/adopted/pending"
    )
    
    # 审计字段
    created_at = Column(DateTime, server_default=func.now(), comment="创建时间")
    updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now(), comment="更新时间")
    
    # 外键关联示例(一对多)
    # user_id = Column(Integer, ForeignKey("users.id"))
    
    def __repr__(self):
        return f"<Pet(id={self.id}, name={self.name}, status={self.status})>"




# models/user.py(用户表示例)
from sqlalchemy import Column, Integer, String, DateTime, Boolean
from sqlalchemy.sql import func
from database import Base

class UserModel(Base):
    __tablename__ = "users"
    
    id = Column(Integer, primary_key=True, index=True)
    username = Column(String(50), unique=True, nullable=False, index=True)
    email = Column(String(100), unique=True, nullable=False)
    hashed_password = Column(String(200), nullable=False)
    credit_score = Column(Integer, default=100)  # 信用分
    is_active = Column(Boolean, default=True)
    created_at = Column(DateTime, server_default=func.now())

6. utils(工具函数层,全名:utilities)

核心职责:存放纯工具、纯函数,与项目业务无关,可复用性极强(换个项目也能直接用)。

核心内容

  • 密码加密/解密(如bcrypt、md5)

  • Token生成/解析(如JWT)

  • 时间格式化(如datetime转字符串、时区转换)

  • 文件处理(上传、下载、压缩、格式转换)

  • 字符串处理(截取、替换、校验)

  • 第三方服务调用(发送短信、邮件、图片上传)

  • 自定义工具函数(如随机数生成、数据格式转换)

关键特点:无状态、纯函数、不依赖项目业务逻辑。

6.1 JWT工具

# utils/jwt_utils.py - JWT工具
import jwt
from datetime import datetime, timedelta
from typing import Dict
from settings import settings

def create_access_token(data: Dict, expires_delta: timedelta = None) -> str:
    """生成JWT token"""
    to_encode = data.copy()
    if expires_delta:
        expire = datetime.utcnow() + expires_delta
    else:
        expire = datetime.utcnow() + timedelta(minutes=15)
    
    to_encode.update({"exp": expire})
    encoded_jwt = jwt.encode(
        to_encode,
        settings.JWT_SECRET_KEY,
        algorithm=settings.JWT_ALGORITHM
    )
    return encoded_jwt

def decode_token(token: str) -> Dict:
    """解析JWT token"""
    try:
        payload = jwt.decode(
            token,
            settings.JWT_SECRET_KEY,
            algorithms=[settings.JWT_ALGORITHM]
        )
        return payload
    except jwt.ExpiredSignatureError:
        raise Exception("Token已过期")
    except jwt.InvalidTokenError:
        raise Exception("无效的Token")

6.2 密码加密工具

# utils/password_utils.py - 密码加密工具
import bcrypt
import hashlib

def hash_password(password: str) -> str:
    """加密密码(bcrypt)"""
    salt = bcrypt.gensalt()
    hashed = bcrypt.hashpw(password.encode('utf-8'), salt)
    return hashed.decode('utf-8')

def verify_password(plain_password: str, hashed_password: str) -> bool:
    """验证密码"""
    return bcrypt.checkpw(
        plain_password.encode('utf-8'),
        hashed_password.encode('utf-8')
    )

def md5_hash(text: str) -> str:
    """MD5哈希(用于非安全场景)"""
    return hashlib.md5(text.encode('utf-8')).hexdigest()

6.3 时间处理工具

# utils/date_utils.py - 时间处理工具
from datetime import datetime, timedelta
from typing import Optional

def format_datetime(dt: datetime, format_str: str = "%Y-%m-%d %H:%M:%S") -> str:
    """格式化时间"""
    if not dt:
        return ""
    return dt.strftime(format_str)

def parse_datetime(date_str: str, format_str: str = "%Y-%m-%d %H:%M:%S") -> Optional[datetime]:
    """解析时间字符串"""
    try:
        return datetime.strptime(date_str, format_str)
    except ValueError:
        return None

def get_age_from_birthday(birthday: datetime) -> int:
    """根据生日计算年龄"""
    today = datetime.now()
    return today.year - birthday.year - ((today.month, today.day) < (birthday.month, birthday.day))

6.4 日志装饰器

# utils/logger.py - 日志装饰器
import functools
from datetime import datetime

def log_business(operation: str):
    """业务日志装饰器"""
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            print(f"[{datetime.now()}] 开始执行业务:{operation}")
            try:
                result = func(*args, **kwargs)
                print(f"[{datetime.now()}] 业务执行成功:{operation}")
                return result
            except Exception as e:
                print(f"[{datetime.now()}] 业务执行失败:{operation}, 错误:{str(e)}")
                raise
        return wrapper
    return decorator

更多推荐