Remove .claude/ from .gitignore — project-level Claude Code configuration should be shared with the team.
35 KiB
name, description
| name | description |
|---|---|
| coding-standards | 项目编码规范。在编写任何业务代码(控制器、服务层、模型、DTO/VO、配置)时必须遵循此规范。当用户要求"新增功能"、"写业务代码"、"添加服务"、"创建模型"、"写接口"、"添加 DTO"等涉及编码实现的场景时,都应使用此技能确保代码风格一致。即使用户没有明确提到"规范",只要是在本项目中编写代码,就应当参照此技能。 |
项目编码规范
基础约定
- Python 3.12,使用现代类型语法:
list[str]、str | None(不用List[str]、Optional[str]) - 文件编码 UTF-8
- Ruff 规则:行长 130,
__init__.py中忽略 F401 - 模块顶部第一行写 docstring(三引号),所有类和公开方法都写中文 docstring
- 完全面向对象编程,不允许类外部的模块级业务函数(配置模块除外)
domian是项目约定拼写(不是domain),绝对不要"修正"
项目分层架构
Controller → Service(IXxxService 接口 + XxxServiceImpl 实现)→ Model(充血模型)
每一层的职责清晰:
- 控制器:负责 HTTP 路由,从 DTO 拆值 → 调 Service → 返回响应。显式声明
response_model - 服务层:负责业务编排,调用 Model 的充血方法做持久化,从 Model 实例组装 VO 返回
- 模型层(充血模型):SQLAlchemy 实体 + 数据库操作方法(
@classmethod查询/写入),禁止引用 DTO/VO/BO,禁止转换方法
DTO / VO / BO 分层规则
| 层级 | DTO | VO | BO |
|---|---|---|---|
| Controller | 入参类型注解 | response_model 声明 + 接收 Service 返回 |
— |
| Service | 禁止 | 组装并返回(对接 Controller) | 组装并返回(对接 Task/Handler) |
| Task / Handler | 禁止 | 禁止 | 入参接收 |
| Model | 禁止 | 禁止 | 禁止 |
- DTO(Data Transfer Object):请求入参,只允许出现在控制器层。控制器负责从 DTO 中取值,以原始类型传给服务层
- VO(View Object):响应出参,由服务层负责组装并返回。控制器通过
response_model=Result[list[XxxVO]]显式声明响应模型 - BO(Business Object):业务对象,当服务层需要向 Task / Handler 等其他层级传递数据时,必须用 BO 包裹做验证
- Model 层充血模型:字段定义 + 数据库操作方法(
@classmethod),禁止引用 DTO/VO/BO,禁止转换方法(如ToVo、FromDto等) - 服务层入参使用原始类型(str、int 等),控制器负责 DTO → 原始类型的拆解;服务层返回值使用 VO(对接控制器)或 BO(对接 Task/Handler)
导入规范
所有导入必须使用完整路径,禁止裸名导入(如 from models import User、from domian.Dto import XxxDTO)。即使 sys.path 中已包含模块目录,也必须写完整的包路径,确保代码可读性和 IDE 可追踪。
# fastapi_common 子模块——完整路径
from fastapi_common.fastapi_common_logger import logger
from fastapi_common.fastapi_common_web.controller import BaseController
from fastapi_common.fastapi_common_web.domain.responses import Result, StatusCodeEnum
from fastapi_common.fastapi_common_web.exception.Base.BusinessException import BusinessException
from fastapi_common.fastapi_common_sqlalchemy.base import Base
from fastapi_common.fastapi_common_sqlalchemy.database import GetAsyncSession
# 业务模块内部——也必须完整路径(以 fastapi_xxx 开头)
from fastapi_admin.config.app import APP_NAME
from fastapi_xxx.models import User
from fastapi_xxx.services import IUserService
from fastapi_xxx.services.impl.userServiceImpl import UserServiceImpl
# DTO 只在控制器层导入,VO 允许在控制器层和服务层导入
from fastapi_xxx.domian.Dto.userDto import UserCreateDTO # ← 仅 controllers 层
from fastapi_xxx.domian.vo.userVo import UserVO # ← controllers 层 + services 层
打包与目录约定
- 业务主包目录:
fastapi_modules/fastapi_xxx/(xxx为实际业务模块名) - 业务分层包以裸名导入:
controllers、services、models、domian、handler、tasks pyproject.toml使用setuptools.packages.find自动发现包,不再手工维护packages = [...]- 新增业务模块时,目录命名必须使用下划线(
snake_case),禁止连字符(-)
控制器层 (fastapi_modules/fastapi_xxx/controllers/)
继承 BaseController,在 Init 中用 @self.router.get/post 注册路由。放入 fastapi_modules/fastapi_xxx/controllers/ 目录后自动被扫描注册,无需手动配置。
控制器类内部除了 Init 外不允许定义任何额外方法(包括私有方法)。控制器只负责:取参数 → 调 Service → 返回响应。
依赖获取方式
服务层依赖有两种来源:
- 无状态服务:在控制器
Init中直接实例化,endpoint 通过闭包访问 - 有状态 / lifespan 初始化的服务:在 endpoint 内通过
RequestObj.app.state.xxx直接获取,不需要额外方法
# 示例 1:无状态服务——闭包访问
# fastapi_modules/fastapi_xxx/controllers/userController.py
"""用户控制器。"""
from fastapi_common.fastapi_common_web.controller import BaseController
from fastapi_common.fastapi_common_web.domain.responses import Result
from domian.Dto import UserCreateDTO
from domian.vo import UserVO
from services import IUserService
from services.impl.userServiceImpl import UserServiceImpl
class UserController(BaseController):
"""用户控制器。"""
def __init__(self):
super().__init__(prefix="/user", tags=["用户管理"])
self.UserService: IUserService = UserServiceImpl()
@self.router.get("/{UserId}", response_model=Result[list[UserVO]])
async def GetUser(UserId: int):
"""获取用户详情
根据用户 ID 查询用户信息。
"""
# Service 返回 VO
Data = await self.UserService.GetById(UserId)
return Result.success(data=Data.model_dump())
@self.router.post("/", response_model=Result[list[UserVO]])
async def CreateUser(body: UserCreateDTO):
"""创建用户
DTO 在控制器层拆解为原始类型传入 Service,Service 返回 VO。
"""
Data = await self.UserService.Create(
Name=body.name,
Account=body.account,
Password=body.password,
Phone=body.phone,
)
return Result.success(data=Data.model_dump())
# 示例 2:有状态服务——通过 RequestObj.app.state 直接获取
# fastapi_modules/fastapi_xxx/controllers/callbackController.py
"""回调控制器。"""
from fastapi import Query, Request, Response
from fastapi_common.fastapi_common_web.controller import BaseController
class CallbackController(BaseController):
"""回调控制器。"""
def Init(self):
super().Init(prefix="/callback", tags=["回调"])
@self.router.get("/verify")
async def VerifyCallback(
RequestObj: Request,
Signature: str = Query(..., description="签名"),
Timestamp: str = Query(..., description="时间戳"),
Nonce: str = Query(..., description="随机数"),
):
"""回调 URL 验证"""
Service = RequestObj.app.state.callback_service
Result = await Service.Verify(Signature, Timestamp, Nonce)
return Response(content=Result, media_type="text/plain")
要点:
- endpoint 函数是普通函数(不带
self),通过闭包访问控制器实例属性 - 无状态服务在
Init中直接实例化;有状态服务在 endpoint 内通过RequestObj.app.state直接获取 - 控制器类内部不允许任何额外方法——不需要
_get_xxx辅助方法,直接在 endpoint 中一行取出即可 - docstring 第一行 → OpenAPI summary,后续行 → description
- 所有路由统一挂载在
/api前缀下
服务层 (fastapi_modules/fastapi_xxx/services/)
这是最核心的规范,必须严格遵守。
服务层采用接口/实现分层架构:
目录结构
fastapi_modules/fastapi_xxx/services/
├── __init__.py
├── userService.py # IUserService 接口(抽象类)
├── orderService.py # IOrderService 接口
└── impl/
├── __init__.py
├── userServiceImpl.py # UserServiceImpl 实现
└── orderServiceImpl.py # OrderServiceImpl 实现
接口层命名:IXxxService
接口层使用 I 前缀,继承 ABC,所有方法标记 @abstractmethod。服务层禁止引用 DTO,入参使用原始类型;返回值使用 VO(对接控制器)或 BO(对接 Task/Handler)。
# fastapi_modules/fastapi_xxx/services/userService.py
"""用户服务接口。"""
from abc import ABC, abstractmethod
from domian.vo import UserVO
class IUserService(ABC):
"""用户服务接口。"""
@abstractmethod
async def GetById(self, UserId: int) -> UserVO | None:
"""根据 ID 获取用户。"""
...
@abstractmethod
async def Create(self, Name: str, Account: str, Password: str, Phone: str | None = None) -> UserVO:
"""创建用户。"""
...
@abstractmethod
async def Update(self, UserId: int, **Fields) -> UserVO:
"""更新用户。"""
...
@abstractmethod
async def Delete(self, UserId: int) -> bool:
"""删除用户。"""
...
实现层命名:XxxServiceImpl
实现层放在 services/impl/ 下,类名为 XxxServiceImpl,继承对应接口。方法数必须与接口层严格一致,不允许多余的辅助方法。
# fastapi_modules/fastapi_xxx/services/impl/userServiceImpl.py
"""用户服务实现。"""
from fastapi_common.fastapi_common_logger import logger
from fastapi_common.fastapi_common_sqlalchemy.database import GetAsyncSession
from fastapi_common.fastapi_common_web.domain.responses import StatusCodeEnum
from fastapi_common.fastapi_common_web.exception.XxxException import XxxException
from fastapi_common.fastapi_common_utils.datetime_utils import FormatDatetime
from domian.vo import UserVO
from models import User
from services import IUserService
class UserServiceImpl(IUserService):
"""用户服务实现。"""
async def GetById(self, UserId: int) -> UserVO | None:
"""根据 ID 获取用户。"""
async with GetAsyncSession() as session:
user = await session.get(User, UserId)
if not user:
raise XxxException(StatusCodeEnum.HTTP_404_NOT_FOUND, "用户不存在")
# Service 层负责从 Model 实例组装 VO
return UserVO(
id=user.Id,
name=user.Name,
account=user.Account,
phone=user.Phone,
status=user.Status,
createdAt=FormatDatetime(user.create_time) if user.create_time else None,
)
async def Create(self, Name: str, Account: str, Password: str, Phone: str | None = None) -> UserVO:
"""创建用户。"""
async with GetAsyncSession() as session:
# 调用 Model 充血方法创建用户
user = await User.create(session, name=Name, account=Account, password=Password, phone=Phone)
await session.commit()
logger.info(f"创建用户成功: {user.Id}")
return UserVO(
id=user.Id,
name=user.Name,
account=user.Account,
phone=user.Phone,
status=user.Status,
createdAt=FormatDatetime(user.create_time) if user.create_time else None,
)
async def Update(self, UserId: int, **Fields) -> UserVO:
"""更新用户。"""
async with GetAsyncSession() as session:
user = await session.get(User, UserId)
if not user:
raise XxxException(StatusCodeEnum.HTTP_404_NOT_FOUND, "用户不存在")
for FieldName, Value in Fields.items():
setattr(user, FieldName, Value)
await session.commit()
await session.refresh(user)
return UserVO(
id=user.Id,
name=user.Name,
account=user.Account,
phone=user.Phone,
status=user.Status,
createdAt=FormatDatetime(user.create_time) if user.create_time else None,
)
async def Delete(self, UserId: int) -> bool:
"""删除用户。"""
async with GetAsyncSession() as session:
user = await session.get(User, UserId)
if not user:
raise XxxException(StatusCodeEnum.HTTP_404_NOT_FOUND, "用户不存在")
await session.delete(user)
await session.commit()
logger.info(f"删除用户成功: {UserId}")
return True
服务层铁律
- 接口层定义了多少个方法,实现层就精确对应多少个方法——不能增减
- 不允许在实现类中添加任何私有方法(如
_build_query、_validate、_on_xxx等),除非用户主动要求。如果逻辑复杂需要拆分,应当拆到独立的工具类或 Model 的方法中;如果需要内部回调,使用Init中的闭包函数代替私有方法 - 不允许类外部的模块级函数——
from fastapi_common.fastapi_common_logger import logger是唯一的例外 - 日志通道由调用栈自动推断,无需手动指定
模型层 (fastapi_modules/fastapi_xxx/models/) — 充血模型
SQLAlchemy 2.x 声明式模型,必须继承 BaseModel(来自 fastapi_common.fastapi_common_web.models),不能直接继承 Base。BaseModel 是抽象基类,自动提供三个公共时间字段:
create_time:INSERT 时由数据库写入当前时间update_time:INSERT 和 UPDATE 时自动更新delete_time:默认 NULL,非 NULL 表示已软删除
模型是充血模型,包含字段定义 + 数据库操作方法(@classmethod 查询/写入)。但禁止引用 DTO/VO/BO,禁止转换方法(如 ToVo、FromDto 等)。方法参数和返回值只使用原始类型或模型实例。VO 的组装由服务层负责。
# fastapi_modules/fastapi_xxx/models/user.py
"""用户模型。"""
from sqlalchemy import String, select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import Mapped, mapped_column
from fastapi_common.fastapi_common_web.models import BaseModel
class User(BaseModel):
"""用户表(充血模型)。"""
__tablename__ = "sys_user"
Id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
TenantId: Mapped[int] = mapped_column(comment="租户ID")
Name: Mapped[str] = mapped_column(String(100), comment="用户名")
Account: Mapped[str] = mapped_column(String(100), unique=True, comment="登录账号")
Password: Mapped[str] = mapped_column(String(255), comment="密码哈希")
Phone: Mapped[str | None] = mapped_column(String(20), comment="手机号")
Status: Mapped[int] = mapped_column(default=1, comment="状态: 1启用 0禁用")
# create_time / update_time / delete_time 自动继承自 BaseModel
@classmethod
async def get_by_account(cls, session: AsyncSession, account: str) -> "User | None":
"""按登录账号查询用户。"""
return await session.scalar(select(cls).where(cls.Account == account))
@classmethod
async def create(cls, session: AsyncSession, name: str, account: str, password: str, phone: str | None = None) -> "User":
"""创建用户并刷新返回。"""
user = cls(Name=name, Account=account, Password=password, Phone=phone)
session.add(user)
await session.flush()
await session.refresh(user)
return user
模型层铁律
- 充血方法只做数据库操作——查询、插入、更新、删除,不包含业务逻辑
- 禁止引用 DTO/VO/BO——方法参数使用原始类型(str、int 等)+ session,返回值为模型实例或原始类型
- 禁止转换方法——不允许
ToVo()、FromDto()、UpdateFromDto()等,VO 组装由服务层负责 - 充血方法使用
@classmethod——接收 session 作为第一个参数(除 self 外),不在模型内部管理 session 生命周期
异常层 (fastapi_common/fastapi_common_web/exception/)
每个业务模块必须在 exception/ 下创建自己的异常类,继承 BusinessException,文件名和类名都使用大驼峰命名。模块内抛异常时只使用自己的异常类,最终由全局异常中间件统一捕获并返回 Result 错误响应。
目录结构
fastapi_common/fastapi_common_web/exception/
├── __init__.py
├── Base/
│ ├── __init__.py
│ ├── BusinessException.py ← 基类,所有模块异常继承它
│ └── status_code.py ← StatusCode / StatusCodeEnum
├── InterceptorException.py ← interceptor 模块异常(大驼峰文件名)
├── WxkfException.py ← wxkf 模块异常(大驼峰文件名)
└── ...
异常类定义
# fastapi_common/fastapi_common_web/exception/InterceptorException.py
"""拦截器模块异常。"""
from fastapi_common.fastapi_common_web.exception.Base.BusinessException import BusinessException
class InterceptorException(BusinessException):
"""拦截器模块业务异常。"""
使用方式
# 在服务层实现中使用模块自己的异常类
from fastapi_common.fastapi_common_web.exception.InterceptorException import InterceptorException
from fastapi_common.fastapi_common_web.exception.Base.status_code import StatusCodeEnum
# 抛异常时使用模块专属异常类,而不是直接用 BusinessException
raise InterceptorException(StatusCodeEnum.HTTP_404_NOT_FOUND, "用户不存在")
raise InterceptorException(StatusCodeEnum.HTTP_401_UNAUTHORIZED, "用户名或密码错误")
异常层铁律
- 禁止直接使用
BusinessException抛异常——每个模块必须用自己的XxxException - 异常文件放在
fastapi_common/fastapi_common_web/exception/下,不放在业务模块内部 - 异常类只继承,不添加额外属性或方法(除非有特殊需求)
- 全局异常中间件会捕获所有
BusinessException及其子类,自动转换为Result错误响应
DTO/VO/BO (fastapi_modules/fastapi_xxx/domian/)
使用 Pydantic v2 BaseModel。DTO/VO/BO 的字段必须使用小驼峰命名(lowerCamelCase),这是强制规范,与 fastapi_modules 下的文件命名规范一致,也与前端 JSON 字段命名对齐。
DTO(请求入参)— 只在控制器层
DTO 通过 endpoint 函数参数的类型注解声明,FastAPI 自动识别为请求体模型。DTO 不允许进入服务层,控制器负责从 DTO 中取值,以原始类型传给服务层。字段必须小驼峰命名。
# fastapi_modules/fastapi_xxx/domian/Dto/userDto.py
"""用户 DTO。"""
from pydantic import BaseModel, Field
class UserCreateDTO(BaseModel):
"""创建用户请求。"""
name: str = Field(..., description="用户名", max_length=100)
account: str = Field(..., description="登录账号", max_length=100)
password: str = Field(..., description="密码", min_length=6)
phone: str | None = Field(None, description="手机号")
VO(响应出参)— 控制器层 + 服务层
VO 通过路由装饰器的 response_model=Result[list[XxxVO]] 声明,由服务层组装返回。VO 字段必须使用小驼峰命名(lowerCamelCase),这是强制规范,确保返回给前端的 JSON 字段统一为小驼峰。
# fastapi_modules/fastapi_xxx/domian/vo/userVo.py
"""用户 VO。"""
from pydantic import BaseModel, Field
class UserVO(BaseModel):
"""用户响应。"""
id: int = Field(..., description="用户ID")
name: str = Field(..., description="用户名")
account: str = Field(..., description="登录账号")
phone: str | None = Field(None, description="手机号")
status: int = Field(..., description="状态")
createdAt: str | None = Field(None, description="创建时间")
BO(业务对象)— 用于跨层传递验证
当数据需要从 Service 层传递到 Task / Handler 等其他层级时,必须用 BO 包裹做验证。Service 层内部如果不跨层传递,直接使用原始类型即可,不需要强制使用 BO。
# fastapi_modules/fastapi_xxx/domian/bo/taskSubmitBo.py
"""任务提交 BO。"""
from pydantic import BaseModel, Field
class TaskSubmitBO(BaseModel):
"""任务提交业务对象(Service → Task 跨层传递时使用)。"""
taskId: int = Field(..., description="任务ID")
content: str = Field(..., description="提交内容")
userId: int = Field(..., description="提交用户ID")
DTO/VO/BO 铁律
- DTO 只允许出现在控制器层——服务层、模型层、Task/Handler 层禁止引用 DTO
- VO 允许在控制器层和服务层使用——服务层负责从 Model 实例组装 VO 并返回给控制器
- Model 层充血但不转换——允许数据库操作方法(
@classmethod),但禁止引用 DTO/VO/BO,禁止ToVo()、FromDto()等转换方法 - 服务层入参使用原始类型——控制器负责从 DTO 中拆解出原始类型传给服务层
- BO 用于跨层传递——Service → Task/Handler 传递数据时必须用 BO 包裹验证,BO 不进入控制器层
- 控制器路由必须显式声明
response_model——response_model=Result[list[XxxVO]],确保 OpenAPI 文档准确 - DTO/VO/BO 字段必须使用小驼峰命名(lowerCamelCase)——如
userId、userName、createdAt,禁止蛇形命名(user_id)或大驼峰命名(UserId)
统一响应格式
所有接口统一返回 Result 结构。HTTP 标准码条目的 HTTP 响应码为真实状态码,纯业务码条目 HTTP 响应码为 200。
from fastapi_common.fastapi_common_web.domain.responses import Result, PageResult, StatusCodeEnum
# 成功
return Result.success(Data=UserVo)
# 失败(使用预定义状态码,自带默认消息)
return Result.error(StatusCodeEnum.HTTP_404_NOT_FOUND)
# 失败(自定义消息)
return Result.error(StatusCodeEnum.HTTP_400_BAD_REQUEST, "手机号格式不正确")
# 业务异常(使用模块专属异常类,会被全局异常处理器捕获并自动转换为 Result 响应)
from fastapi_common.fastapi_common_web.exception.XxxException import XxxException
raise XxxException(StatusCodeEnum.HTTP_404_NOT_FOUND, "用户不存在")
# 分页响应
return Result.success(Data=PageResult(list=UserList, pagination=PaginationInfo))
日志规范
from fastapi_common.fastapi_common_logger import logger
# 直接使用,通道由调用栈自动推断
logger.info(f"创建用户成功: {user.Id}")
logger.error(f"查询失败: {str(e)}")
logger.warning(f"用户已存在: {Account}")
通道自动推断规则:/controllers/ → CONTROLLER、/services/ → SERVICE、/middleware/ → MIDDLEWARE、/models/ → MODEL、postgrest → POSTGREST、uvicorn → UVICORN、/db/ 或 database → DB 等。
日志同时写入 App.log(全量)和 app-error.log(WARNING+),按天滚动 + zip 压缩。
配置层 (fastapi_admin/config/)
配置采用 TOML + .env 环境选定 + Pydantic Settings 校验 三层架构:
TOML 文件 → os.environ 注入 → Pydantic Settings 校验 → 模块级变量导出
配置文件结构
backend/
├── .env ← 仅选定环境:APP_ENV=development
├── app.toml ← 基础公共配置(所有环境通用)
├── app.development.toml ← 开发环境差异配置
├── app.production.toml ← 生产环境差异配置
└── app.ai.toml ← AI 专用配置(最高优先级)
加载优先级(后覆盖前)
app.toml— 基础配置app.{APP_ENV}.toml— 环境特定配置app.ai.toml— AI 专用配置(最高优先级)- Docker
-e等已有环境变量 — 最高优先级(setdefault不覆盖)
TOML 配置文件格式
# app.toml — 使用 [SECTION] + KEY 结构,展平后变为 SECTION_KEY 环境变量
[APP]
HOST = "0.0.0.0"
PORT = 8000
CORS_ORIGINS = ["*"]
[JWT]
SECRET_KEY = "your-secret"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
[DB]
HOST = "localhost"
PORT = 5432
NAME = "mydb"
Pydantic Settings 定义(_settings.py)
# fastapi_admin/config/_settings.py
class _Base(BaseSettings):
"""所有 Settings 的基类。"""
model_config = {"env_file": None, "extra": "ignore"}
class AppSettings(_Base):
"""应用基础配置。"""
APP_HOST: str = "0.0.0.0"
APP_PORT: int = 8000
class DbSettings(_Base):
"""数据库配置。"""
DB_HOST: str = ""
DB_PORT: int = 5432
DB_NAME: str = ""
DB_USER: str = ""
DB_PASSWORD: str = ""
@property
def ASYNCPG_DATABASE_URL(self) -> str:
"""计算属性:动态构建数据库 URL。"""
return f"postgresql://{self.DB_USER}:{self.DB_PASSWORD}@{self.DB_HOST}:{self.DB_PORT}/{self.DB_NAME}"
模块级变量导出(__init__.py)
所有 Settings 实例的字段和 @property 会被自动导出为模块级变量,业务代码直接导入使用:
# 任何模块中直接导入配置值
from fastapi_admin.config import APP_NAME, APP_PORT, JWT_SECRET_KEY, ASYNCPG_DATABASE_URL
# 或从特定子模块代理导入
from fastapi_admin.config.db import ASYNCPG_DATABASE_URL
from fastapi_admin.config.app import APP_PORT
新增配置项流程
- 在
app.toml中添加[SECTION].KEY(展平后为SECTION_KEY) - 在
_settings.py中对应的 Settings 类添加字段(含类型和默认值) - 需要环境差异的配置在
app.development.toml/app.production.toml中覆盖 - 敏感值在环境特定 TOML 中配置或通过 Docker
-e注入,不写默认值 - 如需计算属性(如 URL 拼接),在 Settings 类中用
@property定义 - 必须在
__init__.pyi类型存根中声明新变量,否则 IDE 无法识别动态导出的模块级变量
类型存根 (__init__.pyi)
由于 __init__.py 通过 _export_settings() 动态导出变量,IDE 无法静态推断类型。因此 __init__.pyi 必须同步维护,为每个导出变量提供类型声明:
# fastapi_admin/config/__init__.pyi
"""类型存根文件 —— 为 IDE 静态分析提供动态导出变量的类型信息。"""
# APP
APP_ENV: str
APP_NAME: str
APP_PORT: int
# JWT
JWT_SECRET_KEY: str
JWT_ACCESS_TOKEN_EXPIRE_HOURS: int
# DB (PostgreSQL)
DB_HOST: str
DB_PORT: int
ASYNCPG_DATABASE_URL: str # @property 计算属性也要声明
# 常量
ROOT_PATH: str
铁律:在 _settings.py 中新增字段或 @property 后,必须同步在 __init__.pyi 中添加对应的类型声明,否则 IDE 会报 "Cannot find reference" 错误
新增完整功能模块的清单
当新增一个完整的业务模块(如"用户管理")时,需要创建以下文件:
| 序号 | 文件路径 | 作用 |
|---|---|---|
| 1 | fastapi_common/fastapi_common_web/exception/XxxException.py |
模块专属异常类(继承 BusinessException) |
| 2 | fastapi_modules/fastapi_xxx/models/xxx.py |
充血模型(字段定义 + 数据库操作方法,禁止引用 DTO/VO/BO) |
| 3 | fastapi_modules/fastapi_xxx/domian/Dto/xxxDto.py |
请求 DTO(小驼峰文件名) |
| 4 | fastapi_modules/fastapi_xxx/domian/vo/xxxVo.py |
响应 VO(小驼峰文件名) |
| 5 | fastapi_modules/fastapi_xxx/services/xxxService.py |
服务接口 IXxxService(小驼峰文件名) |
| 6 | fastapi_modules/fastapi_xxx/services/impl/xxxServiceImpl.py |
服务实现 XxxServiceImpl(小驼峰文件名) |
| 7 | fastapi_modules/fastapi_xxx/controllers/xxxController.py |
控制器(小驼峰文件名) |
补充规范
以下规范是对通用规范的补充和覆盖,与通用规范冲突时以本节为准。
1. 新增业务模块注册流程
新增一个业务模块(如 fastapi_xxx)需要修改三处:
1.1 backend/fastapi_admin/app.py — 添加 sys.path
必须同时添加 两级 路径,缺一不可:
_FASTAPI_MODULES_DIR = _PROJECT_ROOT / "fastapi_modules" # 使 importlib 能 import fastapi_xxx.controllers
_FASTAPI_XXX_DIR = _PROJECT_ROOT / "fastapi_modules" / "fastapi_xxx" # 使模块内部裸名导入生效(from services import ...)
fastapi_modules目录:让控制器注册器importlib.import_module("fastapi_xxx.controllers")能找到包fastapi_modules/fastapi_xxx目录:让模块内部的裸名导入生效(from services import IAuthService、from domian.Dto import LoginDTO)
1.2 backend/pyproject.toml — setuptools include
在 [tool.setuptools.packages.find] 的 include 列表中添加 "fastapi_xxx*"。
1.3 backend/fastapi_admin/bootstrap_parts/controllers.py — 注册控制器包
在 controller_packages 列表中添加入口:
controller_packages = [
"fastapi_xxx.controllers",
]
2. controllers 包级鉴权机制
控制器注册器 register_controllers 的扫描逻辑:
- 导入
controller_packages中的每个包 - 如果包的
__init__.py中有router = APIRouter(...)变量,将其记录为包级路由器 - 递归遍历子包和模块,子包有
router也会被记录 - 注册
BaseController子类时,通过resolve_target_router沿包路径向上查找最近的包级路由器 - 包级路由器上的
dependencies会自动应用到其下所有控制器的路由
需要鉴权的控制器
在 controllers/__init__.py 中定义带 dependencies 的 router:
"""控制器包(需要 JWT 鉴权)。"""
from typing import Any
from fastapi import APIRouter, Depends, Request
from fastapi_common.fastapi_common_security.security import verify_access_token
async def jwt_auth_dependency(RequestObj: Request) -> dict[str, Any]:
"""JWT 鉴权依赖。"""
return verify_access_token(RequestObj)
router = APIRouter(dependencies=[Depends(jwt_auth_dependency)])
绕过鉴权的子包
在需要免鉴权的子包(如 controllers/auth/)的 __init__.py 中定义一个空 router(无 dependencies),拦截 resolve_target_router 的向上查找,使该子包下的控制器不走父包的鉴权 router:
"""认证控制器包(无鉴权)。"""
from fastapi import APIRouter
router = APIRouter()
原理:resolve_target_router 从当前模块的包路径逐级向上查找 package_routers。如果 controllers/auth/__init__.py 没有 router,查找会继续向上命中 controllers/__init__.py 的带鉴权 router,导致 login 等接口也被鉴权拦截。定义空 router 即可在当前层级截断查找。
3. 文件命名规范
fastapi_modules 下的业务模块 — 统一小驼峰文件名
fastapi_modules/ 下所有业务模块统一使用小驼峰命名(lowerCamelCase),包括:
.py文件名:userInfoController.py、authServiceImpl.py- DTO/VO/BO 的 Pydantic 字段名:
userId、userName、createdAt(禁止蛇形user_id或大驼峰UserId)
类名使用大驼峰命名(UpperCamelCase):UserInfoController、LoginTokenVO、AuthServiceImpl
controllers/
├── __init__.py
├── userInfoController.py ← 小驼峰
└── auth/
├── __init__.py
└── authController.py ← 小驼峰
services/
├── __init__.py
├── userService.py ← 小驼峰(IUserService 接口)
├── impl/
│ └── userServiceImpl.py ← 小驼峰(UserServiceImpl 实现)
└── auth/
├── __init__.py
├── authService.py ← 小驼峰
└── impl/
└── authServiceImpl.py
models/
├── __init__.py
└── user.py ← 小驼峰(单词本身就一个词的不需要驼峰)
domian/
├── Dto/
│ ├── __init__.py
│ ├── loginDto.py ← 小驼峰
│ └── refreshTokenDto.py ← 小驼峰
├── vo/
│ ├── __init__.py
│ ├── loginTokenVo.py ← 小驼峰
│ └── userInfoVo.py ← 小驼峰
└── auth/
├── Dto/
│ ├── loginDto.py
│ └── refreshTokenDto.py
└── vo/
└── loginTokenVo.py
# controllers/userInfoController.py ← 文件名小驼峰
class UserInfoController(BaseController): # ← 类名大驼峰
...
# services/userService.py ← 文件名小驼峰
class IUserService(ABC): # ← 类名大驼峰
...
# services/impl/userServiceImpl.py ← 文件名小驼峰
class UserServiceImpl(IUserService): # ← 类名大驼峰
...
# domian/vo/userInfoVo.py ← 文件名小驼峰
class UserInfoVO(BaseModel): # ← 类名大驼峰
...
fastapi_modules 以外的代码 — Python 蛇形命名
不在 fastapi_modules 下的代码(如 fastapi_common、fastapi_admin 等),除非 skills 中有特定规范,否则必须使用 Python 标准的蛇形命名(snake_case)。
4. 业务模块标准目录结构
fastapi_modules/fastapi_xxx/
├── __init__.py
├── controllers/ ← 包级 __init__.py 挂 JWT 鉴权 router
│ ├── __init__.py ← router = APIRouter(dependencies=[Depends(jwt_auth)])
│ ├── userInfoController.py ← 小驼峰,走包级鉴权
│ └── auth/ ← 免鉴权子包
│ ├── __init__.py ← router = APIRouter()(空,拦截向上查找)
│ └── authController.py ← 小驼峰
├── services/
│ ├── __init__.py ← 导出 IUserService
│ ├── userService.py ← 小驼峰
│ ├── impl/
│ │ └── userServiceImpl.py ← 小驼峰
│ └── auth/ ← 认证子模块
│ ├── __init__.py ← 导出 IAuthService
│ ├── authService.py ← 小驼峰
│ └── impl/
│ └── authServiceImpl.py
├── models/ ← 充血模型
│ ├── __init__.py ← 导出 User 等模型
│ └── user.py ← 小驼峰(单词即一个词时无需驼峰)
├── domian/
│ ├── __init__.py
│ ├── Dto/
│ │ └── __init__.py
│ ├── vo/
│ │ ├── __init__.py
│ │ └── userInfoVo.py ← 小驼峰
│ ├── bo/ ← BO(按需,跨层传递时使用)
│ │ ├── __init__.py
│ │ └── taskSubmitBo.py ← 小驼峰
│ └── auth/ ← 认证子模块
│ ├── __init__.py
│ ├── Dto/
│ │ ├── __init__.py
│ │ ├── loginDto.py ← 小驼峰
│ │ └── refreshTokenDto.py ← 小驼峰
│ └── vo/
│ ├── __init__.py
│ └── loginTokenVo.py ← 小驼峰