Become a sponsor

Service 层是业务逻辑层,各模块 Service 继承 BaseService 获得通用 CRUD 能力。子类只需声明差异点(repo、model、过滤字段、排序、唯一性等),即可获得完整的增删改查接口。
设计模式
BaseService 采用模板方法模式:基类定义流程骨架,子类通过声明差异点和覆盖钩子定制行为。
以岗位模块为例,src/modules/position/service.py 完整内容:
from typing import Optional
from core.base_service import BaseService
from position.models import Position
from position.repository import position_repo
from user.models import User
from user.repository import user_repo
from utils.request import parse_id_list
class PositionService(BaseService(Position]):
"""岗位业务服务类,继承基础服务获得通用 CRUD 能力"""
# 数据访问与模型
repo = position_repo
model = Position
# 分页差异点
page_like_fields = ('name',)
page_eq_fields = ('status',)
page_order_by = (('sort', 'asc'),)
# 唯一性校验
unique_fields = {'name': '岗位名称不能重复'}
# ============================================================
# 删除前校验
# ============================================================
def _before_delete(self, ids) -> Optional[str]:
"""删除前校验:存在用户引用该岗位时禁止删除,避免用户岗位悬空"""
id_list = parse_id_list(str(ids))
if id_list and user_repo.filter(User.position_id.in_(id_list), User.is_delete == 0).first():
return "存在用户引用该岗位,请先调整用户岗位"
return None
# ============================================================
# 获取岗位数据列表
# ============================================================
def get_position_list(self, request):
"""获取岗位数据列表(不分页,供下拉选择使用)"""
# 与 /page 一致的筛选条件:name 模糊、status 精确;保留软删过滤与排序
query = self._apply_page_filters(self.repo.filter_by(), request)
query = query.order_by(self.model.sort.asc())
return [v.to_dict() for v in query.all()]
# 模块级单例
position_service = PositionService()| 属性 | 类型 | 说明 | 示例 |
|---|---|---|---|
repo | BaseRepository | 数据仓库实例 | position_repo |
model | Model | ORM 模型类 | Position |
page_like_fields | tuple | 分页模糊查询字段 | ('name',) |
page_eq_fields | tuple | 分页等值查询字段 | ('status',) |
page_order_by | tuple | 分页排序规则 | (('sort', 'asc'),) |
unique_fields | dict | 唯一性校验字段 | {'name': '名称重复'} |
serialize_maps | dict | 枚举显示名映射 | {'status': {1: '在用', 2: '停用'}} |
file_fields | tuple | 文件字段 | ('cover', 'image') |
rich_text_fields | tuple | 富文本字段 | ('content',) |
file_dir | str | 文件存储子目录名 | 默认从表名去掉 DB_PREFIX 推导 |
serialize_extra | Callable | 列表/详情共用序列化钩子,返回需合并进字典的额外字段 | — |
detail_serialize | Callable | 详情专用序列化钩子,仅在 get_detail 时合并 | — |
单例模式
每个模块在文件末尾创建 Service 实例(如 position_service),供 Endpoint 层使用。
def _apply_page_filters(self, query, request):
"""组装分页过滤条件"""
# 调用基类默认实现(处理 page_like_fields / page_eq_fields)
query = super()._apply_page_filters(query, request)
# 追加固定过滤
query = query.filter(self.model.status == 1)
return query
def _serialize(self, item) -> Dict[str, Any]:
"""列表行序列化"""
data = item.to_dict()
# 自定义序列化逻辑
return data
def _serialize_detail(self, obj) -> Dict[str, Any]:
"""详情序列化"""
data = self._serialize(obj)
# 详情特有字段
return datadef _before_add(self, request, data) -> None:
"""新增前扩展(如密码哈希)"""
def _before_update(self, request, data) -> None:
"""编辑前扩展"""
def _before_delete(self, ids) -> Optional[str]:
"""删除前校验:返回 None 放行,返回字符串拦截"""
def _build_create_fields(self, request, data) -> Dict[str, Any]:
"""组装新增字段"""
fields = super()._build_create_fields(request, data)
# 追加自定义字段
return fields
def _build_update_fields(self, request, data) -> Dict[str, Any]:
"""组装更新字段"""
fields = super()._build_update_fields(request, data)
# 追加自定义字段
return fieldsBaseService.delete() 和 BaseService.batch_delete() 内部调用 repo.batch_delete(ids) 完成软删除,并统一 R 响应文案:
async def delete(self, handler, ids) -> R:
try:
id_list = list(dict.fromkeys(parse_id_list(str(ids))))
if not id_list:
return R.failed(handler, "记录ID不存在")
if len(self.repo.get_by_ids(id_list)) != len(id_list):
return R.failed(handler, "记录不存在")
err = self._before_delete(ids)
if err:
return R.failed(handler, err)
count = self.repo.batch_delete(ids)
return R.ok(handler, msg="本次共删除{0}条数据".format(count))
finally:
self.repo.close()复用规则
自定义 Service 可通过 _before_delete 钩子添加业务校验,删除响应文案由基类统一处理。
| Service 方法 | 同步/异步 | 原因 |
|---|---|---|
get_page / get_detail / add / update / delete | 同步 def | 纯 DB 操作 |
batch_delete | 异步 async def | 需 await parse_batch_ids(request) |
| 含 Redis 缓存的方法 | 异步 async def | Redis IO 需 await |
禁止混用
def Service 中禁止直接调用异步方法async def Endpoint 中禁止直接调用同步 DB 查询(用 asyncio.to_thread 包裹)class ArticleService(BaseService(Article]):
# 字典编码方式(推荐)
serialize_maps = {
'source': 'article_source', # 从数据字典取 {value: label}
}
# 字面字典方式(历史兼容)
serialize_maps = {
'status': {1: '在用', 2: '停用'},
}序列化结果
serialize_maps 中的字段会在序列化时自动补一个 {field}Text 字段。例如 source=1 会生成 sourceText="原创"。
Service 层通过继承 BaseService 获得通用 CRUD 能力。子类声明差异点(repo_cls/model/page_like_fields/page_eq_fields/page_order_by/unique_fields/serialize_maps),覆盖钩子(_before_delete/_serialize 等)定制业务行为。删除操作由基类统一处理。模块级单例供 Handler 调用。