--- description: app 包内分层编码规范(API / Service / Model / Schema) globs: app/**/* alwaysApply: false --- # 分层编码规范 编辑 `app/` 下代码时遵循以下约定。 ## api/v1(路由层) - 使用 `APIRouter`,按领域分文件(如 `auth.py`),在 `router.py` 聚合。 - `response_model=ApiResponse[...]` 声明响应类型。 - 注入依赖:`DbSession`、`RedisClient`、`CurrentUser`(来自 `app.dependencies`)。 - 捕获 `services` 中的业务异常(如 `AuthError`),转为 `ApiResponse(ok=False, message=...)`。 - 不在路由内写 `select()` / `db.add()`;一行调用 service 后包装 `ApiResponse(ok=True, data=...)`。 - 副作用(写 Redis session)可在路由层于 service 成功后调用,与现有 `auth.py` 一致。 ## services(业务层) - 函数签名显式接收 `AsyncSession` 及业务参数;返回 Pydantic schema 或简单类型。 - 业务可预期失败:`raise XxxError("中文说明")`,由 API 层捕获。 - 使用 `sqlalchemy.select` / `session.get`;密码用 `core.security.hash_password` / `verify_password`。 - 不返回 ORM 给 API 时,用 `UserPublic.model_validate(user)` 等转换。 ## models(ORM) - 继承 `app.database.Base`,`__tablename__` 复数蛇形。 - 使用 SQLAlchemy 2 `Mapped[]` / `mapped_column`。 - 在 `app/models/__init__.py` 导出,供 Alembic `env.py` import。 ## schemas(Pydantic) - **请求**:`Field` 约束;与前端对齐的字段用 `alias` + `populate_by_name=True`(如 `confirmPassword`)。 - **响应**:`model_config = ConfigDict(from_attributes=True)` 以便从 ORM 转换。 - **信封**:所有对外业务 JSON 经 `ApiResponse[T]`,勿在路由返回裸 dict(`/health` 除外仍用 ApiResponse)。 ## core - 仅放无状态的纯函数/工具(JWT、密码、常量前缀)。 - 不依赖 FastAPI、Session。 ## dependencies - 集中定义 `Annotated[..., Depends(...)]` 别名。 - 鉴权逻辑放在 `get_current_user`:先 Redis session,再 JWT `sub` 查库。 ## 命名与风格 - 文件名:领域名蛇形(`auth.py`)。 - API 路径:小写、复数资源或动词路径(`/auth/login`)。 - 用户可见 `message` 使用简体中文,与 aiclient 前端文案风格一致。