结构介绍
所有内容放在app目录下
- utils: 通用资源或变量
- api: 所有的接口
- crud: 数据的增删改查具体操作
- db: 数据库初始化以及数据表结构和关系
- models: 定义数据库模型,如绑定数据库的表结构等
- schemas: 为数据校验定义请求与响应数据
- request: 基础请求数据模型(Pydantic模型),定义API接口的原始请求参数
- response: 基础响应数据模型(Pydantic模型),定义API接口的原始响应数据
- full_request: 完整请求包装模型,包含用户信息、业务线、参数等元数据,用于统一请求格式
- full_response: 完整响应包装模型,包含状态码、消息、数据等统一响应格式
- tests: 自测代码
- main.py: 主入口
模型文件位置规范
数据库模型 (SQLModel)
- 位置:
app/models/目录 - 用途: 定义数据库表结构,使用SQLModel
- 示例:
app/models/user_model.py
# app/models/user_model.py
from sqlmodel import SQLModel, Field
from datetime import datetime
class User(SQLModel, table=True):
user_id: str = Field(primary_key=True, description="用户ID")
user_name: str = Field(..., description="用户名")
# ... 其他字段
Pydantic模型 (请求/响应)
- 请求模型位置:
app/schemas/request/目录 - 响应模型位置:
app/schemas/response/目录 - 完整包装模型位置:
app/schemas/full_request/和app/schemas/full_response/目录 - 用途: 定义API接口的请求和响应数据结构
# app/schemas/request/user_request.py - 请求模型
class UserCreateRequest(BaseModel):
user_name: str = Field(..., min_length=1, max_length=50)
user_id: str = Field(..., min_length=1, max_length=20)
# ... 其他字段
# app/schemas/response/user_response.py - 响应模型
class UserResponse(BaseModel):
user_id: str
user_name: str
# ... 其他字段
# app/schemas/full_request/user_full_request.py - 完整请求包装
class UserFullCreateRequest(BaseModel):
user: dict[str, str] | None = None
business: str | None = None
param: str | None = None
data: dict[str, str] | None = None
开发规范
1. 命名规范
1.1 类命名
- 使用 PascalCase(首字母大写)
- 类名应该是名词,具有描述性
# ✅ 正确
class UserService:
class DatabaseConnection:
class TestCalculator:
# ❌ 错误
class userService:
class database_connection:
1.2 常量命名
- 使用 全大写字母,单词间用下划线分隔
# ✅ 正确
MAX_RETRY_COUNT = 3
DEFAULT_TIMEOUT = 30
API_BASE_URL = "https://api.example.com"
# ❌ 错误
maxRetryCount = 3
default_timeout = 30
1.3 变量和函数命名
- 使用 snake_case(全小写,单词间用下划线分隔)
- 变量名应该是名词,函数名应该是动词
# ✅ 正确
user_name = "张三"
def get_user_by_id(user_id: str):
def calculate_test_result():
def validate_input_data():
# ❌ 错误
userName = "张三"
def getUserById(userId: str):
def CalculateTestResult():
1.4 模块和包命名
- 使用 snake_case
- 避免使用下划线开头(除非是私有模块)
# ✅ 正确
user_services.py
test_calculator.py
database_client.py
# ❌ 错误
userServices.py
TestCalculator.py
database-client.py
2. 类型注解规范
2.1 Python 3.10+ 类型注解语法
- 优先使用Python 3.10+的内置类型注解语法
- 复杂类型仍使用
typing模块
# ✅ Python 3.10+ 推荐写法
def get_user_by_id(user_id: str) -> User | None:
pass
def get_users_by_role(role: str, limit: int | None = None) -> list[User]:
pass
def process_user_data(user_data: dict[str, str]) -> User:
pass
def handle_multiple_types(data: str | bytes | dict[str, str]) -> str:
pass
# ✅ 复杂类型仍使用typing模块
from typing import TypeVar, Callable, Union
T = TypeVar('T')
def generic_function(data: list[T], processor: Callable[[T], str]) -> list[str]:
pass
# ❌ 旧版本写法(不推荐)
from typing import Optional, List, Dict
def old_style_function(user_id: str) -> Optional[User]:
pass
def old_style_list_function() -> List[User]:
pass
2.2 类型注解最佳实践
- 使用
str | None替代Optional[str]和Union[str, None] - 使用
list[str]替代List[str] - 使用
dict[str, int]替代Dict[str, int] - 使用
tuple[int, str]替代Tuple[int, str] - 使用
set[str]替代Set[str] - 复杂泛型仍使用
typing模块
2.3 Pydantic模型类型注解
2.3.1 BaseModel 和 Field 说明
- BaseModel: Pydantic的基础模型类,提供数据验证、序列化、文档生成等功能
- Field: 用于定义字段的详细配置,包括验证规则、默认值、描述等
2.3.2 Field 参数说明
...: 表示必填字段,不能为空default: 设置默认值default_factory: 设置默认值工厂函数,用于复杂类型(如list、dict)min_length/max_length: 字符串长度限制min/max: 数值范围限制description: 字段描述,用于API文档alias: 字段别名,用于JSON序列化exclude: 是否在序列化时排除该字段
# app/schemas/request/user_request.py - 请求模型
from pydantic import BaseModel, Field
class UserCreateRequest(BaseModel):
user_name: str = Field(
..., # 必填字段
min_length=1,
max_length=50,
description="用户名,长度1-50个字符"
)
user_id: str = Field(
...,
min_length=1,
max_length=20,
description="用户ID,长度1-20个字符"
)
role: str = Field(..., description="用户角色")
business: str = Field(..., description="业务线")
staff_id: str | None = Field(None, description="员工ID,可选") # 使用 | None 替代 Optional
tags: list[str] = Field(default_factory=list, description="用户标签列表") # 使用 list[str]
metadata: dict[str, str] = Field(default_factory=dict, description="元数据") # 使用 dict[str, str]
# app/schemas/response/user_response.py - 响应模型
from datetime import datetime
class UserResponse(BaseModel):
user_id: str = Field(..., description="用户ID")
user_name: str = Field(..., description="用户名")
role: str = Field(..., description="用户角色")
business: str = Field(..., description="业务线")
staff_id: str | None = Field(None, description="员工ID")
created_at: datetime | None = Field(None, description="创建时间")
2.4 数据库模型类型注解
2.4.1 SQLModel 和 Field 说明
- SQLModel: 结合了SQLAlchemy和Pydantic的模型类,既支持数据库操作又支持数据验证
- Field: 用于定义数据库字段的详细配置,包括主键、索引、约束等
2.4.2 SQLModel Field 参数说明
primary_key=True: 设置主键index=True: 创建数据库索引unique=True: 设置唯一约束nullable=False: 设置非空约束default: 设置默认值default_factory: 设置默认值工厂函数description: 字段描述table=True: 表示该模型对应数据库表
# app/models/user_model.py - 数据库模型
from sqlmodel import SQLModel, Field
from datetime import datetime
class User(SQLModel, table=True):
user_id: str = Field(primary_key=True, description="用户ID,主键")
user_name: str = Field(..., index=True, description="用户名,创建索引")
role: str = Field(..., description="用户角色")
business: str = Field(..., description="业务线")
staff_id: str | None = Field(default=None, unique=True, description="员工ID,唯一") # 使用 | None
created_at: datetime = Field(default_factory=datetime.now, description="创建时间")
updated_at: datetime = Field(default_factory=datetime.now, description="更新时间")
permissions: list[str] = Field(default_factory=list, description="权限列表") # 使用 list[str]
3. 代码注释规范
3.1 文件头部注释
每个Python文件必须在开头包含utf-8注释,其他可自定义,最好有模块描述:
# -*- coding: UTF-8 -*-
# @File : 文件名.py
# @Author: 作者姓名 工号
# @Desc : 文件功能描述
# @Date : 创建日期 YYYY/MM/DD
3.2 函数注释
所有函数必须包含详细的文档字符串,说明功能、参数和返回值:
def get_user_by_id(user_id: str, session: Session) -> User | None:
"""
根据用户ID获取用户信息
Args:
user_id (str): 用户ID
session (Session): 数据库会话对象
Returns:
User | None: 用户对象,如果不存在则返回None
Raises:
ValueError: 当user_id为空时抛出
DatabaseError: 数据库连接错误时抛出
"""
if not user_id:
raise ValueError("用户ID不能为空")
return session.query(User).filter(User.user_id == user_id).first()
def get_users_by_filters(
role: str | None = None,
business: str | None = None,
limit: int = 100
) -> list[User]:
"""
根据过滤条件获取用户列表
Args:
role (str | None): 用户角色过滤条件
business (str | None): 业务线过滤条件
limit (int): 返回结果数量限制,默认100
Returns:
list[User]: 用户列表
Raises:
ValueError: 当limit小于0时抛出
"""
if limit < 0:
raise ValueError("limit不能为负数")
# 实现逻辑
pass
3.3 类注释
所有类必须包含类级别的文档字符串:
class UserService:
"""
用户服务类,提供用户相关的业务逻辑操作
主要功能:
- 用户创建、更新、删除
- 用户信息查询
- 用户权限验证
"""
def __init__(self, session: Session):
"""
初始化用户服务
Args:
session (Session): 数据库会话对象
"""
self.session = session
4. FastAPI 接口开发规范
4.1 路由定义
- 使用有意义的URL路径
- 使用适当的HTTP方法
- 添加标签分组(tags)用于API文档分类
- 注意: 一般路由可以设置prefix前缀,比如v1这种版本信息,用于统一的路由管理,所以prefix最好不在各个模块中定义,最好能够统一管理
4.1.1 标签分组说明
- 作用: tags用于在Swagger/OpenAPI文档中对接口进行分组
- 好处:
- 提高API文档的可读性和组织性
- 便于前端开发人员快速找到相关接口
- 便于接口管理和维护
- 命名规范: 使用复数形式,如
["users"],["tests"],["calculations"]
4.1.2 路由定义示例
# app/api/v1/user_routers.py
from fastapi import APIRouter
# 注意:不在这里定义prefix,prefix在app/api/routers.py中统一管理
user_router = APIRouter(tags=["users"])
@user_router.post("/create_user",
response_model=SuccessResponse[UserResponse],
responses={
422: {"model": FailResponse, "description": "参数错误"},
500: {"model": FailResponse, "description": "服务器内部错误"}
})
async def create_user(request: UserFullCreateRequest):
"""
创建新用户
Args:
request (UserFullCreateRequest): 用户创建请求
Returns:
SuccessResponse[UserResponse]: 创建成功的用户信息
"""
pass
@user_router.get("/get_user_by_id",
response_model=SuccessResponse[UserResponse],
tags=["users"]) # 可以在这里再次指定tags,但通常不需要
async def get_user_by_id(user_id: str):
"""
根据ID获取用户信息
"""
pass
4.1.3 统一路由管理
# app/api/routers.py - 统一管理所有路由的prefix
from fastapi import APIRouter
from app.api.v1 import user_routers, test_api, acc_routes
# 定义统一的API版本和前缀
API_VERSION = "/v1"
PREFIX = "/api"
# 在这里统一定义prefix
api_router = APIRouter()
# 用户相关接口
api_router.include_router(
user_routers.user_router,
prefix=f"{PREFIX}{API_VERSION}/users",
tags=["users"]
)
# 测试相关接口
api_router.include_router(
test_api.test_router,
prefix=f"{PREFIX}{API_VERSION}/tests",
tags=["tests"]
)
# 计算相关接口
api_router.include_router(
acc_routes.acc_router,
prefix=f"{PREFIX}{API_VERSION}/calculations",
tags=["calculations"]
)
4.2 数据校验规范
- 所有请求和响应必须使用Pydantic模型
- 使用类型注解确保类型安全
- 添加适当的验证器
4.2.1 validator 验证器说明
- @validator: 用于自定义字段验证逻辑
- 参数: 字段名,可以是单个字段或多个字段
- 返回值: 验证后的值,可以修改输入值
- 异常: 验证失败时抛出 ValueError
4.2.2 class Config 配置说明
- extra: 控制额外字段的处理方式
"ignore": 忽略多余的字段"allow": 允许多余的字段"forbid": 禁止多余的字段
- schema_extra: 为API文档提供示例数据
- validate_assignment: 是否在赋值时进行验证
- json_encoders: 自定义JSON编码器
# app/schemas/request/user_request.py
from pydantic import BaseModel, Field, validator
class UserCreateRequest(BaseModel):
user_name: str = Field(..., min_length=1, max_length=50, description="用户名")
user_id: str = Field(..., min_length=1, max_length=20, description="用户ID")
role: str = Field(..., description="用户角色")
business: str = Field(..., description="业务线")
staff_id: str | None = Field(None, description="员工ID")
@validator('user_name')
def validate_user_name(cls, v):
"""验证用户名,去除首尾空格"""
if not v.strip():
raise ValueError('用户名不能为空')
return v.strip()
@validator('user_id')
def validate_user_id(cls, v):
"""验证用户ID格式"""
if not v.isalnum():
raise ValueError('用户ID只能包含字母和数字')
return v.lower()
@validator('role')
def validate_role(cls, v):
"""验证用户角色"""
allowed_roles = ['admin', 'developer', 'tester', 'manager']
if v not in allowed_roles:
raise ValueError(f'角色必须是以下之一: {", ".join(allowed_roles)}')
return v
class Config:
extra = "ignore" # 忽略多余的字段
validate_assignment = True # 赋值时进行验证
schema_extra = {
"example": {
"user_name": "张三",
"user_id": "zhangsan001",
"role": "developer",
"business": "AI",
"staff_id": "EMP001"
}
}
4.3 错误处理
- 使用统一的响应格式
- 提供详细的错误信息
- 记录错误日志
from app.schemas.response.full_response import SuccessResponse, FailResponse
from app.utils.log.logger import Logger
logger = Logger.getInstance()
@user_router.post("/create_user")
async def create_user(request: UserFullCreateRequest):
try:
# 业务逻辑
user = user_service.create_user(request)
return SuccessResponse(data=UserResponse(**user.dict()), message="用户创建成功!")
except ValueError as e:
logger.warning(f"参数验证失败: {str(e)}")
return FailResponse(message=f"参数错误: {str(e)}")
except Exception as e:
error_msg = traceback.format_exc()
logger.error(f"创建用户失败: \n{error_msg}")
return FailResponse(message=f"创建用户失败: {str(e)}")
5. 数据库操作规范
5.1 模型定义
- 使用SQLModel进行模型定义
- 添加适当的字段约束
- 使用类型注解
# app/models/user_model.py
from sqlmodel import SQLModel, Field
from datetime import datetime
class User(SQLModel, table=True):
user_id: str = Field(primary_key=True, description="用户ID")
user_name: str = Field(..., description="用户名")
role: str = Field(..., description="用户角色")
department: str = Field(..., description="部门")
staff_id: str | None = Field(default=None, description="工号")
created_at: datetime = Field(default_factory=datetime.now, description="创建时间")
updated_at: datetime = Field(default_factory=datetime.now, description="更新时间")
5.2 CRUD操作
- 使用依赖注入获取数据库会话
- 添加适当的异常处理
- 使用事务确保数据一致性
# app/api/v1/user_routers.py - 依赖注入
def get_user_service(session: Session = Depends(lambda: next(get_session()))):
return UserService(session=session)
# app/services/user_services.py - 业务逻辑
class UserService:
def __init__(self, session: Session):
self.session = session
def create_user(self, user_data: UserCreateRequest) -> User:
"""
创建用户
Args:
user_data (UserCreateRequest): 用户数据
Returns:
User: 创建的用户对象
"""
try:
user = User(**user_data.dict())
self.session.add(user)
self.session.commit()
self.session.refresh(user)
return user
except Exception as e:
self.session.rollback()
raise e
6. 日志规范
6.1 日志级别使用
- DEBUG: 调试信息,开发时使用
- INFO: 一般信息,如请求开始、结束
- WARNING: 警告信息,如参数验证失败
- ERROR: 错误信息,如异常捕获
- CRITICAL: 严重错误,如系统崩溃
6.2 日志格式
logger.info(f"开始处理用户请求: user_id={user_id}")
logger.warning(f"用户输入参数异常: {param_name}={param_value}")
logger.error(f"数据库操作失败: {str(e)}", exc_info=True)
7. 测试规范
7.1 单元测试
- 每个函数都应该有对应的单元测试
- 测试覆盖率不低于80%
- 使用描述性的测试方法名
import pytest
from app.services.user_services import UserService
class TestUserService:
def test_create_user_success(self):
"""测试成功创建用户"""
# 测试代码
def test_create_user_with_invalid_data(self):
"""测试使用无效数据创建用户"""
# 测试代码
8. 性能规范
8.1 数据库查询优化
8.1.1 SQLModel vs 自定义MySQL客户端
- SQLModel: 适用于ORM操作,提供类型安全和自动验证
- 适合:简单的CRUD操作、关系查询、数据验证
- 示例:用户管理、基础数据操作
- 自定义MySQL客户端: 适用于复杂查询和性能优化
- 适合:复杂SQL、批量操作、性能敏感的场景
- 示例:报表查询、数据分析、批量导入
8.1.2 适当的索引
- 主键索引: 自动创建,用于唯一标识
- 唯一索引: 确保字段值唯一性
- 普通索引: 加速查询,特别是WHERE条件
- 复合索引: 多字段组合索引,注意字段顺序
- 覆盖索引: 包含查询所需的所有字段
# 示例:创建适当的索引
class User(SQLModel, table=True):
user_id: str = Field(primary_key=True) # 主键索引
user_name: str = Field(..., index=True) # 普通索引
email: str = Field(..., unique=True) # 唯一索引
business: str = Field(..., index=True) # 普通索引
created_at: datetime = Field(default_factory=datetime.now, index=True) # 时间索引
8.1.3 N+1查询问题
- 问题: 查询主表后,再逐个查询关联表,导致大量数据库查询
- 解决方案: 使用JOIN或批量查询
# ❌ N+1查询问题
users = session.query(User).all()
for user in users:
user_details = session.query(UserDetail).filter(UserDetail.user_id == user.user_id).first()
# ✅ 使用JOIN解决
users_with_details = session.query(User, UserDetail).join(UserDetail).all()
8.1.4 分页查询
- 作用: 避免一次性加载大量数据,提高性能
- 实现: 使用LIMIT和OFFSET
def get_users_paginated(page: int = 1, page_size: int = 20) -> tuple[list[User], int]:
"""
分页查询用户
Args:
page: 页码,从1开始
page_size: 每页数量
Returns:
(用户列表, 总数量)
"""
offset = (page - 1) * page_size
# 查询数据
users = session.query(User).offset(offset).limit(page_size).all()
# 查询总数
total = session.query(User).count()
return users, total
8.2 缓存使用
- 合理使用Redis缓存
- 设置适当的缓存过期时间
- 避免缓存穿透和雪崩
8.2.1 缓存穿透
- 问题: 查询不存在的数据,导致每次请求都访问数据库
- 解决方案:
- 缓存空值(设置较短的过期时间)
- 使用布隆过滤器
- 参数验证
def get_user_by_id(user_id: str) -> User | None:
"""获取用户信息,使用缓存防止穿透"""
cache_key = f"user:{user_id}"
# 先从缓存获取
cached_user = redis_client.get(cache_key)
if cached_user is not None:
if cached_user == "null": # 缓存空值
return None
return User.parse_raw(cached_user)
# 查询数据库
user = session.query(User).filter(User.user_id == user_id).first()
if user is None:
# 缓存空值,防止穿透
redis_client.setex(cache_key, 300, "null") # 5分钟过期
return None
# 缓存用户数据
redis_client.setex(cache_key, 3600, user.json()) # 1小时过期
return user
8.2.2 缓存雪崩
- 问题: 大量缓存同时过期,导致数据库压力激增
- 解决方案:
- 设置随机过期时间
- 使用缓存预热
- 实现缓存更新机制
import random
def get_user_by_id_with_avalanche_protection(user_id: str) -> User | None:
"""获取用户信息,防止缓存雪崩"""
cache_key = f"user:{user_id}"
# 先从缓存获取
cached_user = redis_client.get(cache_key)
if cached_user is not None:
if cached_user == "null":
return None
return User.parse_raw(cached_user)
# 查询数据库
user = session.query(User).filter(User.user_id == user_id).first()
if user is None:
# 随机过期时间,防止雪崩
expire_time = 300 + random.randint(0, 60) # 5-6分钟
redis_client.setex(cache_key, expire_time, "null")
return None
# 随机过期时间
expire_time = 3600 + random.randint(0, 300) # 1小时-1小时5分钟
redis_client.setex(cache_key, expire_time, user.json())
return user

1891

被折叠的 条评论
为什么被折叠?



