---
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 前端文案风格一致。
