chore: commit .claude/ project-level config and skills

Remove .claude/ from .gitignore — project-level Claude Code
configuration should be shared with the team.
This commit is contained in:
wren
2026-04-27 16:58:19 +08:00
parent 535d97a70c
commit 72a9b8e393
2 changed files with 872 additions and 3 deletions
+870
View File
@@ -0,0 +1,870 @@
---
name: coding-standards
description: 项目编码规范。在编写任何业务代码(控制器、服务层、模型、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 → ServiceIXxxService 接口 + 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 可追踪。
```python
# 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 → 返回响应。
### 依赖获取方式
服务层依赖有两种来源:
1. **无状态服务**:在控制器 `Init` 中直接实例化,endpoint 通过闭包访问
2. **有状态 / lifespan 初始化的服务**:在 endpoint 内通过 `RequestObj.app.state.xxx` 直接获取,不需要额外方法
```python
# 示例 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())
```
```python
# 示例 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)。**
```python
# 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`,继承对应接口。**方法数必须与接口层严格一致,不允许多余的辅助方法。**
```python
# 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
```
### 服务层铁律
1. **接口层定义了多少个方法,实现层就精确对应多少个方法**——不能增减
2. **不允许在实现类中添加任何私有方法**(如 `_build_query``_validate``_on_xxx` 等),除非用户主动要求。如果逻辑复杂需要拆分,应当拆到独立的工具类或 Model 的方法中;如果需要内部回调,使用 `Init` 中的闭包函数代替私有方法
3. **不允许类外部的模块级函数**——`from fastapi_common.fastapi_common_logger import logger` 是唯一的例外
4. **日志通道由调用栈自动推断**,无需手动指定
## 模型层 (`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 的组装由服务层负责。
```python
# 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
```
### 模型层铁律
1. **充血方法只做数据库操作**——查询、插入、更新、删除,不包含业务逻辑
2. **禁止引用 DTO/VO/BO**——方法参数使用原始类型(str、int 等)+ session,返回值为模型实例或原始类型
3. **禁止转换方法**——不允许 `ToVo()``FromDto()``UpdateFromDto()` 等,VO 组装由服务层负责
4. **充血方法使用 `@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 模块异常(大驼峰文件名)
└── ...
```
### 异常类定义
```python
# fastapi_common/fastapi_common_web/exception/InterceptorException.py
"""拦截器模块异常。"""
from fastapi_common.fastapi_common_web.exception.Base.BusinessException import BusinessException
class InterceptorException(BusinessException):
"""拦截器模块业务异常。"""
```
### 使用方式
```python
# 在服务层实现中使用模块自己的异常类
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, "用户名或密码错误")
```
### 异常层铁律
1. **禁止直接使用 `BusinessException` 抛异常**——每个模块必须用自己的 `XxxException`
2. 异常文件放在 `fastapi_common/fastapi_common_web/exception/` 下,**不放在业务模块内部**
3. 异常类只继承,不添加额外属性或方法(除非有特殊需求)
4. 全局异常中间件会捕获所有 `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 中取值,以原始类型传给服务层。**字段必须小驼峰命名。**
```python
# 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 字段统一为小驼峰。
```python
# 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。
```python
# 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 铁律
1. **DTO 只允许出现在控制器层**——服务层、模型层、Task/Handler 层禁止引用 DTO
2. **VO 允许在控制器层和服务层使用**——服务层负责从 Model 实例组装 VO 并返回给控制器
3. **Model 层充血但不转换**——允许数据库操作方法(`@classmethod`),但禁止引用 DTO/VO/BO,禁止 `ToVo()``FromDto()` 等转换方法
4. **服务层入参使用原始类型**——控制器负责从 DTO 中拆解出原始类型传给服务层
5. **BO 用于跨层传递**——Service → Task/Handler 传递数据时必须用 BO 包裹验证,BO 不进入控制器层
6. **控制器路由必须显式声明 `response_model`**——`response_model=Result[list[XxxVO]]`,确保 OpenAPI 文档准确
7. **DTO/VO/BO 字段必须使用小驼峰命名(lowerCamelCase**——如 `userId``userName``createdAt`,禁止蛇形命名(`user_id`)或大驼峰命名(`UserId`
## 统一响应格式
所有接口统一返回 `Result` 结构。HTTP 标准码条目的 HTTP 响应码为真实状态码,纯业务码条目 HTTP 响应码为 200。
```python
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))
```
## 日志规范
```python
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 专用配置(最高优先级)
```
### 加载优先级(后覆盖前)
1. `app.toml` — 基础配置
2. `app.{APP_ENV}.toml` — 环境特定配置
3. `app.ai.toml` — AI 专用配置(最高优先级)
4. Docker `-e` 等已有环境变量 — 最高优先级(`setdefault` 不覆盖)
### TOML 配置文件格式
```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`
```python
# 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` 会被自动导出为模块级变量,业务代码直接导入使用:
```python
# 任何模块中直接导入配置值
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
```
### 新增配置项流程
1.`app.toml` 中添加 `[SECTION].KEY`(展平后为 `SECTION_KEY`
2.`_settings.py` 中对应的 Settings 类添加字段(含类型和默认值)
3. 需要环境差异的配置在 `app.development.toml` / `app.production.toml` 中覆盖
4. 敏感值在环境特定 TOML 中配置或通过 Docker `-e` 注入,**不写默认值**
5. 如需计算属性(如 URL 拼接),在 Settings 类中用 `@property` 定义
6. **必须在 `__init__.pyi` 类型存根中声明新变量**,否则 IDE 无法识别动态导出的模块级变量
### 类型存根 (`__init__.pyi`)
由于 `__init__.py` 通过 `_export_settings()` 动态导出变量,IDE 无法静态推断类型。因此 `__init__.pyi` 必须同步维护,为每个导出变量提供类型声明:
```python
# 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
必须同时添加 **两级** 路径,缺一不可:
```python
_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` 列表中添加入口:
```python
controller_packages = [
"fastapi_xxx.controllers",
]
```
## 2. controllers 包级鉴权机制
控制器注册器 `register_controllers` 的扫描逻辑:
1. 导入 `controller_packages` 中的每个包
2. 如果包的 `__init__.py` 中有 `router = APIRouter(...)` 变量,将其记录为**包级路由器**
3. 递归遍历子包和模块,子包有 `router` 也会被记录
4. 注册 `BaseController` 子类时,通过 `resolve_target_router` **沿包路径向上查找**最近的包级路由器
5. 包级路由器上的 `dependencies` 会自动应用到其下所有控制器的路由
### 需要鉴权的控制器
`controllers/__init__.py` 中定义带 `dependencies``router`
```python
"""控制器包(需要 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:
```python
"""认证控制器包(无鉴权)。"""
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
```
```python
# 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 ← 小驼峰
```
+2 -3
View File
@@ -57,6 +57,5 @@ coverage.xml
# Rules cache
rules/**/__pycache__/
# Claude
.claude/
CLAUDE.md
# Claude (committed — project-level config)
# CLAUDE.md is committed