# 分层架构设计 本章详细描述系统的分层架构设计,包括各层的职责边界、依赖方向、通信方式,并以岗位(Position)模块为实例贯穿说明。 ## 架构概览 :::tip 架构分层原则 采用经典的分层架构,核心原则是:**依赖单向、职责单一、接口隔离**。上层只依赖下层接口,下层不感知上层存在。 ::: ``` ┌─────────────────────────────────────────────────────────────┐ │ HTTP 层 (Handler) │ │ src/modules/{module}/handlers.py │ │ 职责:路由定义 · 权限装饰器 · 操作日志 · 响应返回 │ │ 原则:不含业务逻辑,仅做请求转发 │ └──────────────────────────┬──────────────────────────────────┘ │ 调用 ▼ ┌─────────────────────────────────────────────────────────────┐ │ 业务层 (Service) │ │ src/modules/{module}/service.py │ │ 职责:业务逻辑 · 校验规则 · 字段组装 · 序列化加工 │ │ 原则:不直接操作数据库,通过 Repository 访问数据 │ └──────────────────────────┬──────────────────────────────────┘ │ 调用 ▼ ┌─────────────────────────────────────────────────────────────┐ │ 数据层 (Repository) │ │ src/modules/{module}/repository.py │ │ 职责:数据库 CRUD · 查询条件组装 · 软删除过滤 │ │ 原则:只做数据访问原语,不做业务校验和响应封装 │ └──────────────────────────┬──────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ Model 层 (ORM 模型) │ │ src/modules/{module}/models.py │ │ 职责:表结构定义 · 字段映射 · 序列化方法 │ └──────────────────────────┬──────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ 数据库 (MySQL / PostgreSQL / etc.) │ └─────────────────────────────────────────────────────────────┘ ``` ## 各层详细职责 ### 第一层:HTTP 层(Handler) **目录位置**:`src/modules/{module}/handlers.py` **核心职责**: 1. **权限控制**:通过 `@permission_required` 装饰器声明所需权限 2. **演示模式保护**:通过 `@check_demo` 装饰器阻止演示环境的写操作 3. **操作日志**:通过 `@operation_log` 装饰器记录写操作日志 4. **响应返回**:直接返回 Service 层的 `R` 对象 **设计原则**: - 不包含任何业务逻辑 - 不直接访问数据库 - 仅做请求转发和响应返回 - 权限字符串遵循 `sys:{module}:{action}` 格式 **代码示例**(岗位模块): ```python # src/modules/position/handlers.py # +====================================================================== # | 模块: 岗位请求处理器层 # | 说明: 承接岗位相关的 HTTP 请求,校验权限/演示模式并记录操作日志后, # | 转交岗位服务层处理 # +====================================================================== """岗位请求处理器。""" from common.middleware.access_decorators import check_demo, permission_required from common.middleware.operation_log import operation_log from core import response as R from core.base_handler import BaseHandler from modules.position.service import position_service # ====================================================================== # 查询岗位分页数据 # ====================================================================== class PositionPageHandler(BaseHandler): """查询岗位分页数据。""" @permission_required("sys:position:list") async def get(self): """GET 请求:返回岗位分页列表。""" return await position_service.get_page(self) # ====================================================================== # 查询岗位详情 # ====================================================================== class PositionDetailHandler(BaseHandler): """查询岗位详情。""" @permission_required("sys:position:detail") async def get(self, id): """GET 请求:按 ID 返回岗位详情。 :param id: 岗位 ID(路径参数) """ return R.ok(self, data=position_service.get_detail(id)) # ====================================================================== # 添加岗位 # ====================================================================== class PositionAddHandler(BaseHandler): """添加岗位。""" @permission_required("sys:position:add") @check_demo @operation_log("岗位管理", "添加") async def post(self): """POST 请求:新增岗位。""" return await position_service.add(self) # ====================================================================== # 更新岗位 # ====================================================================== class PositionUpdateHandler(BaseHandler): """更新岗位。""" @permission_required("sys:position:update") @check_demo @operation_log("岗位管理", "更新") async def put(self): """PUT 请求:更新岗位信息。""" return await position_service.update(self) # ====================================================================== # 删除岗位 # ====================================================================== class PositionDeleteHandler(BaseHandler): """删除岗位。""" @permission_required("sys:position:delete") @check_demo @operation_log("岗位管理", "删除") async def delete(self, id): """DELETE 请求:按 ID 删除岗位(软删除)。 :param id: 岗位 ID(路径参数) """ return await position_service.delete(self, id) # ====================================================================== # 设置岗位状态 # ====================================================================== class PositionStatusHandler(BaseHandler): """设置岗位状态。""" @permission_required("sys:position:status") @check_demo @operation_log("岗位管理", "设置状态") async def put(self): """PUT 请求:修改岗位状态(如启用 / 禁用)。""" return await position_service.update_status(self) # ====================================================================== # 获取岗位下拉列表 # ====================================================================== class PositionGetListHandler(BaseHandler): """获取岗位下拉列表。""" async def get(self): """GET 请求:返回岗位下拉列表(无需权限校验)。""" return await position_service.get_options(self) # ====================================================================== # 批量删除岗位 # ====================================================================== class PositionBatchDeleteHandler(BaseHandler): """批量删除岗位。""" @permission_required("sys:position:delete") @check_demo @operation_log("岗位管理", "批量删除") async def post(self): """POST 请求:批量删除岗位(软删除)。""" return await position_service.batch_delete(self) ``` :::tip 装饰器顺序 装饰器按从外到内执行:先 `@permission_required`(鉴权),再 `@check_demo`(演示模式),最后 `@operation_log`(操作日志)。所有 Handler 方法都是 `async def`,因为 Tornado Handler 方法默认异步。 ::: ### 第二层:业务层(Service) **目录位置**:`src/modules/{module}/service.py` **核心职责**: 1. **业务逻辑编排**:组合多个数据操作完成业务流程 2. **唯一性校验**:通过 `unique_fields` 声明唯一约束 3. **字段组装**:`_build_create_fields` / `_build_update_fields` 组装入库字段 4. **序列化加工**:`_serialize` / `_serialize_detail` 加工响应数据 5. **文件处理**:文件字段迁移、URL 补全 6. **前置/后置钩子**:`_before_add` / `_before_update` / `_before_delete` 扩展点 **设计原则**: - 通过 Repository 访问数据库,不直接使用 `db.query()` - 返回 `R` 对象,通过 `self.finish()` 写出 JSON - 通过声明差异点定制通用 CRUD,减少重复代码 **代码示例**(岗位模块): ```python # src/modules/position/service.py # +====================================================================== # | 模块: 岗位业务逻辑层 # | 说明: 定义岗位服务,声明差异点以复用通用 CRUD 流程,并导出服务单例 # +====================================================================== """岗位业务逻辑。""" from core.base_service import BaseService from modules.position.models import Position from modules.position.repository import PositionRepository from modules.position.schemas import PositionSchema, PositionStatusSchema # ====================================================================== # 岗位服务 # ====================================================================== class PositionService(BaseService): """岗位服务。""" # ============================================================ # 基础声明 # ============================================================ model = Position repo_cls = PositionRepository # ============================================================ # 差异点声明 # ============================================================ # 校验模型 create_schema = PositionSchema update_schema = PositionSchema status_schema = PositionStatusSchema # 分页查询字段 page_like_fields = ("name",) page_eq_fields = ("status",) # 分页排序与下拉过滤 page_order_by = (("sort", "asc"),) options_filter = {"status": 1} # ====================================================================== # 服务单例 # ====================================================================== position_service = PositionService() ``` :::tip Service 层的两种模式 简单 CRUD 模块继承 `BaseService`,只需声明差异点即可获得完整的增删改查能力。复杂模块(如用户、角色、菜单)在继承基础上添加自定义方法,或完全自定义 Service 函数。 ::: ### 第三层:数据层(Repository) **目录位置**:`src/modules/{module}/repository.py` **核心职责**: 1. **CRUD 操作**:`create` / `update` / `batch_delete` / `get_by_id` 等 2. **查询构建**:`filter` / `filter_by` / `get_one` / `all` / `count` 3. **分页查询**:`paginate` 返回 `(items, total)` 元组 4. **软删除过滤**:等值方法自动追加 `is_delete=0` 5. **数据清洗**:`_clean_model_data` 将 camelCase 转 snake_case **设计原则**: - 只做数据访问原语,不做 R 封装 - 等值方法自动软删过滤,`filter()` 为原始出口 - Service 层通过 `repo_cls` 关联 Repository 类,基类自动实例化 **代码示例**(岗位模块): ```python # src/modules/position/repository.py # +====================================================================== # | 模块: 岗位数据访问层 # | 说明: 定义岗位仓储,继承 BaseRepository 获得通用 CRUD 与软删除能力 # +====================================================================== """岗位数据访问。""" from core.base_repository import BaseRepository from modules.position.models import Position # ====================================================================== # 岗位仓储 # ====================================================================== class PositionRepository(BaseRepository): """岗位仓储。""" # ============================================================ # 绑定的模型类 # ============================================================ model = Position ``` :::tip Repository 基类提供的方法 `BaseRepository` 提供以下通用方法,子类可直接使用: - **查询**:`get_by_id` / `get_by_ids` / `get_one` / `all` / `count` / `exists` / `exists_by_field` - **分页**:`paginate` 返回 `(items, total)` - **写操作**:`create` / `update` / `batch_delete` - **条件构建**:`filter`(原始出口) / `filter_by`(等值出口,自动软删过滤) ::: ### 第四层:Model 层(ORM 模型) **目录位置**:`src/modules/{module}/models.py` **核心职责**: 1. **表结构定义**:通过 SQLAlchemy Column 定义字段 2. **通用字段继承**:`BaseModel` 提供 id/create_user/create_time/update_user/update_time/is_delete 3. **序列化方法**:`to_dict()` 自动排除敏感字段(password/salt/is_delete),驼峰命名 **代码示例**(岗位模块): ```python # src/modules/position/models.py from sqlalchemy import Column, Integer, String, text from config.database import DB_PREFIX from core.base_model import BaseModel class Position(BaseModel): """岗位模型。""" __tablename__ = DB_PREFIX + "position" __table_comment__ = "岗位表" # 岗位名称 name = Column(String(255), nullable=False, index=True, comment="岗位名称") # 岗位状态:1-在用 2-停用 status = Column(Integer, default=0, server_default=text('0'), index=True, comment="岗位状态") # 岗位排序 sort = Column(Integer, default=0, server_default=text('0'), comment="岗位排序") ``` ### Schema 层(请求校验) **目录位置**:`src/modules/{module}/schemas.py` **核心职责**: 1. **请求参数校验**:使用 Pydantic v2 的 `BaseModel` + `Field` 声明约束 2. **创建/编辑共用**:继承 `BaseSchema`(自带可选 id 字段) 3. **状态更新独立**:单独建类,只含 id + status **代码示例**(岗位模块): ```python # src/modules/position/schemas.py from pydantic import BaseModel, Field from core.base_schema import BaseSchema class PositionSchema(BaseSchema): """岗位创建/编辑表单。""" name: str = Field(..., min_length=1, max_length=150, description="岗位名称") status: int = Field(..., ge=1, le=2, description="岗位状态:1-在用 2-停用") sort: int = Field(..., ge=0, le=99999, description="岗位排序") class PositionStatusSchema(BaseModel): """岗位状态更新表单。""" id: int = Field(..., gt=0, description="岗位ID") status: int = Field(..., ge=1, le=2, description="岗位状态:1-在用 2-停用") ``` ## 路由注册 **目录位置**:`src/api/v1/{module}.py` 每个模块在 `src/api/v1/` 下定义路由文件,导出 `routes` 列表,由 `router.py` 统一聚合。 **代码示例**(岗位模块): ```python # src/api/v1/position.py """岗位相关路由。""" from modules.position.handlers import ( PositionAddHandler, PositionBatchDeleteHandler, PositionDeleteHandler, PositionDetailHandler, PositionGetListHandler, PositionPageHandler, PositionStatusHandler, PositionUpdateHandler, ) routes = [ (r"/position/page", PositionPageHandler), (r"/position/detail/(\d+)", PositionDetailHandler), (r"/position/getPositionList", PositionGetListHandler), (r"/position/add", PositionAddHandler), (r"/position/update", PositionUpdateHandler), (r"/position/delete/(\d+)", PositionDeleteHandler), (r"/position/status", PositionStatusHandler), (r"/position/batchDelete", PositionBatchDeleteHandler), ] ``` `router.py` 中统一注册: ```python # src/api/v1/router.py from api.v1 import position # 导入模块 _RESOURCE_MODULES = [ # ... 其他模块 ... position, # 注册 ] def build_routes() -> list: routes = [] for module in _RESOURCE_MODULES: routes.extend([(V1_PREFIX + path, handler) for path, handler in module.routes]) routes.append((V1_PREFIX + "/health", HealthHandler)) return routes ``` ## 依赖方向 ``` HTTP 层 (Handler) ──依赖──→ Service 层 ──依赖──→ Repository 层 ──依赖──→ Model │ │ │ │ │ │ ▼ ▼ ▼ R (响应封装) R (响应封装) BaseRepository (基类) 装饰器(BaseHandler) BaseRepository SQLAlchemy ORM access_decorators Pydantic Schema ``` :::warning 依赖规则 - HTTP 层只依赖 Service 层,不直接调用 Repository - Service 层通过 Repository 访问数据库,不直接使用 `db.query()` - Repository 层只依赖 Model 和数据库会话 - 下层不感知上层存在,不调用上层方法 ::: ## 完整调用链路示例 以添加岗位为例,展示各层之间的调用关系: ``` POST /api/v1/position/add │ ├─ 1. Handler (handlers.py) │ @permission_required("sys:position:add") │ @check_demo │ @operation_log("岗位管理", "添加") │ async def post(self): │ return await position_service.add(self) │ ├─ 2. Service (service.py → base_service.py) │ async def add(self, handler): │ form = self._validate(handler, self.create_schema) # Pydantic 校验 │ err = self._check_unique(form) # 唯一性校验 │ self._before_add(handler, data) # 前置钩子 │ fields = self._build_create_fields(data) # 字段组装 │ self.repo.create(self.model(**fields)) # 调用 Repository │ return R.ok(handler, msg="添加成功") │ ├─ 3. Repository (base_repository.py) │ def create(self, obj): │ obj.create_user = str(self._uid()) # 自动写入创建人 │ self.db.add(obj) │ self.db.commit() │ return obj │ ├─ 4. Model (base_model.py) │ class Position(BaseModel): │ __tablename__ = "tornado_position" │ name = Column(String(255), ...) │ └─ 5. Database INSERT INTO tornado_position (name, status, sort, create_user, create_time) VALUES ('高级工程师', 1, 10, '1', '2026-09-16 10:00:00') ``` ## 各层文件职责一览 | 文件 | 层次 | 职责 | 是否包含业务逻辑 | |:---|:---:|:---|:---:| | `handlers.py` | HTTP | 路由、权限、日志、请求转发 | 否 | | `schemas.py` | HTTP | 请求参数校验规则 | 否 | | `service.py` | 业务 | 业务逻辑、校验、序列化 | 是 | | `repository.py` | 数据 | 数据库 CRUD、查询构建 | 否 | | `models.py` | 数据 | 表结构定义、字段映射 | 否 | ## 总结 分层架构通过依赖单向和职责单一实现了良好的关注点分离。HTTP 层只关心请求接收和响应返回,Service 层只关心业务逻辑编排,Repository 层只关心数据访问。这种分层使得每一层都可以独立测试、独立替换,系统在模块数量增长时仍能保持清晰的代码结构。以岗位模块为例,完整实现一个 CRUD 模块只需 6 个文件(handlers + schemas + service + repository + models + api路由),其中 Repository 和 Model 几乎零代码,开发者只需关注 Service 层的业务差异点。