refactor: P4a 服务层改造地基(BizError + base_repository + base_service)
- src/core/exceptions.py:BizError/NotFoundError/PermissionDeniedError - 全局异常处理器接入 BizError → R.failed(含事务回滚语义) - src/common/base_repository.py:泛型 CRUD 仓储基类 - src/common/base_service.py:服务基类(入参数据/出参数据范式) 【验收】BizError 端到端转换为 R.failed(code=403/ok=false);应用启动 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude
parent
d33dc1fc34
commit
a6e094378c
@@ -0,0 +1,65 @@
|
||||
"""
|
||||
仓储基类
|
||||
=============================================================
|
||||
|
||||
封装通用 CRUD 查询。业务模块的 Repository 继承此类并指定 model。
|
||||
依赖 BaseModel 的通用字段(id / is_delete / create_* / update_*)。
|
||||
|
||||
设计要点:
|
||||
- 查询默认过滤已删除记录(is_delete == 0)
|
||||
- 不持有事务:保存沿用 BaseDB.save(),由 AOP 事务拦截器统一提交
|
||||
"""
|
||||
|
||||
from typing import Any, List, Optional
|
||||
|
||||
from sqlalchemy import and_
|
||||
|
||||
|
||||
class BaseRepository:
|
||||
"""泛型仓储基类"""
|
||||
|
||||
#: 子类指定模型类
|
||||
model = None
|
||||
|
||||
@classmethod
|
||||
def get_by_id(cls, obj_id) -> Optional[Any]:
|
||||
"""按主键查询未删除记录;ID 无效时返回 None"""
|
||||
if not obj_id or int(obj_id) <= 0:
|
||||
return None
|
||||
return cls.model.query.filter(
|
||||
and_(cls.model.id == int(obj_id), cls.model.is_delete == 0)
|
||||
).first()
|
||||
|
||||
@classmethod
|
||||
def get_all_active(cls, order_by=None) -> List[Any]:
|
||||
"""查询全部未删除记录"""
|
||||
query = cls.model.query.filter(cls.model.is_delete == 0)
|
||||
if order_by is not None:
|
||||
query = query.order_by(order_by)
|
||||
return query.all()
|
||||
|
||||
@classmethod
|
||||
def paginate(cls, query, page: int, page_size: int):
|
||||
"""分页查询:返回 (items, total, pages)"""
|
||||
total = query.count()
|
||||
pages = (total + page_size - 1) // page_size if total > 0 else 0
|
||||
items = query.limit(page_size).offset((page - 1) * page_size).all()
|
||||
return items, total, pages
|
||||
|
||||
@classmethod
|
||||
def update_by_id(cls, obj_id, data: dict) -> int:
|
||||
"""按主键更新字段,返回影响行数"""
|
||||
return cls.model.query.filter_by(id=obj_id).update(data)
|
||||
|
||||
@classmethod
|
||||
def create(cls, **kwargs) -> Any:
|
||||
"""创建并保存实例,返回实例对象"""
|
||||
obj = cls.model(**kwargs)
|
||||
obj.save()
|
||||
return obj
|
||||
|
||||
@classmethod
|
||||
def save(cls, obj) -> Any:
|
||||
"""保存实例,返回实例对象"""
|
||||
obj.save()
|
||||
return obj
|
||||
@@ -0,0 +1,33 @@
|
||||
"""
|
||||
服务基类
|
||||
=============================================================
|
||||
|
||||
提供服务层通用骨架。服务层编码规范:
|
||||
- 函数入参为数据(dict / 标量),出参为纯数据
|
||||
- 业务失败通过 `raise BizError` 表达(见 src/core/exceptions.py)
|
||||
- 不直接读取 request、不返回 R 对象(request 解析与 R 包装由 router 层负责)
|
||||
- 数据访问走 repository(见 src/common/base_repository.py)
|
||||
|
||||
使用示例:
|
||||
class PositionService(BaseService):
|
||||
repository = PositionRepository
|
||||
|
||||
def add_position(self, data):
|
||||
...
|
||||
"""
|
||||
|
||||
from src.common.base_repository import BaseRepository
|
||||
|
||||
|
||||
class BaseService:
|
||||
"""服务基类"""
|
||||
|
||||
#: 子类指定仓储类
|
||||
repository: BaseRepository = None
|
||||
|
||||
@classmethod
|
||||
def get_info(cls, obj_id):
|
||||
"""通用:按 ID 查询模型对象"""
|
||||
if cls.repository is None:
|
||||
return None
|
||||
return cls.repository.get_by_id(obj_id)
|
||||
@@ -0,0 +1,44 @@
|
||||
"""
|
||||
业务异常定义
|
||||
=============================================================
|
||||
|
||||
业务代码通过 `raise BizError(...)` 表达业务失败,由全局异常处理器
|
||||
统一转换为 R.failed 响应。业务层不再 `return R.failed`,使服务函数
|
||||
保持「入参数据、出参数据」的纯函数形态(可单元测试)。
|
||||
|
||||
使用示例:
|
||||
from src.core.exceptions import BizError
|
||||
|
||||
def add_position(data):
|
||||
form = PositionForm(data=data)
|
||||
if not form.validate():
|
||||
raise BizError(regular.get_err(form))
|
||||
position.save()
|
||||
"""
|
||||
|
||||
from src.common.constants.common import MESSAGE_FAIL
|
||||
|
||||
|
||||
class BizError(Exception):
|
||||
"""业务异常:统一转为 R.failed 响应"""
|
||||
|
||||
def __init__(self, msg: str = MESSAGE_FAIL, code: int = 1, data=None, **kwargs):
|
||||
self.msg = msg
|
||||
self.code = code
|
||||
self.data = data
|
||||
self.kwargs = kwargs
|
||||
super().__init__(msg)
|
||||
|
||||
|
||||
class NotFoundError(BizError):
|
||||
"""资源不存在(默认提示)"""
|
||||
|
||||
def __init__(self, msg: str = "资源不存在", **kwargs):
|
||||
super().__init__(msg=msg, **kwargs)
|
||||
|
||||
|
||||
class PermissionDeniedError(BizError):
|
||||
"""权限不足"""
|
||||
|
||||
def __init__(self, msg: str = "权限不足", code: int = 403, **kwargs):
|
||||
super().__init__(msg=msg, code=code, **kwargs)
|
||||
Reference in New Issue
Block a user