签入版本

This commit is contained in:
2026-09-12 23:12:15 +08:00
commit 91998056ca
1841 changed files with 341325 additions and 0 deletions
@@ -0,0 +1,187 @@
# Service层规范
## 概述
Service 层是业务逻辑层,各模块 Service 继承 `BaseService[Model]` 获得通用 CRUD 能力。子类只需声明差异点(repo、model、过滤字段、排序、唯一性等),即可获得完整的增删改查接口。
:::tip 设计模式
BaseService 采用模板方法模式:基类定义流程骨架,子类通过声明差异点和覆盖钩子定制行为。
:::
## PositionService 示例
以岗位模块为例,`src/modules/system/position/service.py` 完整内容:
```python
from typing import Optional
from core.base_service import BaseService
from modules.system.position.models import Position
from modules.system.position.repository import position_repo
from modules.system.user.models import User
from modules.system.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',)` |
:::tip 单例模式
每个模块在文件末尾创建 Service 实例(如 `position_service`),供 Endpoint 层使用。
:::
## 可覆盖钩子
### 查询钩子
```python
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 data
```
### 写操作钩子
```python
def _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 fields
```
## batch_delete_with_r 静态方法
```python
@staticmethod
def batch_delete_with_r(repo: BaseRepository, ids) -> R:
"""按逗号分隔ID软删除并返回标准R响应"""
id_list = parse_id_list(str(ids))
if not id_list:
return R.failed("记录ID不存在")
count = repo.batch_delete(ids)
if count != len(id_list):
return R.failed("记录不存在")
return R.ok(msg="本次共删除{0}条数据".format(count))
```
:::tip 复用规则
自定义 Service 的删除方法应复用 `BaseService.batch_delete_with_r(repo, ids)`,保证删除响应文案全仓一致。禁止直接调用 `repo.batch_delete` 后自行封装 R。
:::
## 同步/异步约定
| 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 |
:::warning 禁止混用
- 同步 `def` Service 中禁止直接调用异步方法
- 异步 `async def` Endpoint 中禁止直接调用同步 DB 查询(用 `asyncio.to_thread` 包裹)
:::
## serialize_maps 枚举映射
```python
class ArticleService(BaseService[Article]):
# 字典编码方式(推荐)
serialize_maps = {
'source': 'article_source', # 从数据字典取 {value: label}
}
# 字面字典方式(历史兼容)
serialize_maps = {
'status': {1: '在用', 2: '停用'},
}
```
:::tip 序列化结果
`serialize_maps` 中的字段会在序列化时自动补一个 `{field}Text` 字段。例如 `source=1` 会生成 `sourceText="原创"`。
:::
## 总结
Service 层通过继承 `BaseService[Model]` 获得通用 CRUD 能力。子类声明差异点(repo/model/page_like_fields/page_eq_fields/page_order_by/unique_fields/serialize_maps),覆盖钩子(_apply_page_filters/_before_delete/_serialize 等)定制业务行为。删除操作统一复用 `batch_delete_with_r` 静态方法。模块级单例模式供 Endpoint 调用。