init: 初始化 dpb 桃育种系统代码库
前后端 + 后端 FastAPI 全量源码、部署脚本与文档。
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
"""育种审计日志 模块服务层(§3.10;写操作全程可追溯)"""
|
||||
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.base_schema import AuthSchema, PageResultSchema
|
||||
from app.core.exceptions import CustomException
|
||||
|
||||
from .crud import BreedingAuditCRUD
|
||||
from .model import AuditLogModel
|
||||
from .schema import AuditLogOutSchema, AuditLogQueryParam
|
||||
|
||||
# 供 Service 层在导入汇总/业务路径直接落审计行(不触发 CRUDBase 递归审计)
|
||||
_ALLOWED_ACTIONS = {"CREATE", "UPDATE", "DELETE", "IMPORT"}
|
||||
|
||||
|
||||
def _fmt(v: Any) -> str | None:
|
||||
if v is None:
|
||||
return None
|
||||
s = str(v)
|
||||
return s[:500] if len(s) > 500 else s
|
||||
|
||||
|
||||
class AuditLogService:
|
||||
"""审计日志 —— 只读查询 + 静态写入。写入走 CRUDBase 自动注入,不对外暴露增删改。"""
|
||||
|
||||
def __init__(self, auth: AuthSchema, db: AsyncSession) -> None:
|
||||
self.auth = auth
|
||||
self.db = db
|
||||
|
||||
@staticmethod
|
||||
async def write(
|
||||
db: AsyncSession,
|
||||
*,
|
||||
entity_type: str,
|
||||
entity_id: int | None,
|
||||
action: str,
|
||||
field_name: str | None = None,
|
||||
old_value: Any = None,
|
||||
new_value: Any = None,
|
||||
created_id: int | None = None,
|
||||
operator_id: int | None = None,
|
||||
) -> int:
|
||||
"""直接落一条审计行(不经 CRUDBase,防递归)。返回新行 id。"""
|
||||
if action not in _ALLOWED_ACTIONS:
|
||||
raise CustomException(msg=f"非法审计操作类型: {action}")
|
||||
row = AuditLogModel(
|
||||
entity_type=entity_type[:32] if entity_type else entity_type,
|
||||
entity_id=entity_id,
|
||||
action=action,
|
||||
field_name=(field_name[:64] if field_name else field_name),
|
||||
old_value=_fmt(old_value),
|
||||
new_value=_fmt(new_value),
|
||||
created_id=created_id,
|
||||
operator_id=operator_id,
|
||||
)
|
||||
db.add(row)
|
||||
await db.flush()
|
||||
return int(row.id)
|
||||
|
||||
async def page(
|
||||
self,
|
||||
page_no: int,
|
||||
page_size: int,
|
||||
search: AuditLogQueryParam | None = None,
|
||||
order_by: list[dict[str, str]] | None = None,
|
||||
) -> PageResultSchema[AuditLogOutSchema]:
|
||||
from app.utils.common_util import search_to_dict
|
||||
|
||||
result = await BreedingAuditCRUD(self.auth, self.db).page(
|
||||
offset=(page_no - 1) * page_size,
|
||||
limit=page_size,
|
||||
order_by=order_by or [{"id": "desc"}],
|
||||
search=search_to_dict(search, {}),
|
||||
out_schema=AuditLogOutSchema,
|
||||
)
|
||||
await self._attach_labels(result.items)
|
||||
return result
|
||||
|
||||
async def get_list(self, search: AuditLogQueryParam | None = None) -> list[AuditLogOutSchema]:
|
||||
from app.utils.common_util import search_to_dict
|
||||
|
||||
objs = await BreedingAuditCRUD(self.auth, self.db).get_list(
|
||||
search=search_to_dict(search, {}), order_by=[{"id": "desc"}]
|
||||
)
|
||||
outs = [AuditLogOutSchema.model_validate(o) for o in objs]
|
||||
await self._attach_labels(outs)
|
||||
return outs
|
||||
|
||||
async def detail(self, id: int) -> AuditLogOutSchema:
|
||||
obj = await BreedingAuditCRUD(self.auth, self.db).get(id=id)
|
||||
if not obj:
|
||||
raise CustomException(msg="该审计日志不存在")
|
||||
out = AuditLogOutSchema.model_validate(obj)
|
||||
await self._attach_labels([out])
|
||||
return out
|
||||
|
||||
async def _attach_labels(self, items: list[AuditLogOutSchema]) -> None:
|
||||
op_ids = {getattr(it, "operator_id") for it in items if getattr(it, "operator_id")}
|
||||
if op_ids:
|
||||
from app.api.v1.module_bre.personnel.model import PersonnelModel
|
||||
|
||||
rows = (await self.db.execute(
|
||||
select(PersonnelModel.id, PersonnelModel.name).where(PersonnelModel.id.in_(op_ids))
|
||||
)).all()
|
||||
op_map = {r.id: r.name for r in rows}
|
||||
for it in items:
|
||||
it.operator_name = op_map.get(getattr(it, "operator_id"))
|
||||
|
||||
async def options(self) -> dict[str, Any]:
|
||||
"""前端筛选下拉:实体类型 / 操作类型 去重集合。"""
|
||||
etypes = (await self.db.execute(
|
||||
select(AuditLogModel.entity_type)
|
||||
.where(AuditLogModel.entity_type.isnot(None))
|
||||
.distinct()
|
||||
)).scalars().all()
|
||||
actions = (await self.db.execute(
|
||||
select(AuditLogModel.action)
|
||||
.where(AuditLogModel.action.isnot(None))
|
||||
.distinct()
|
||||
)).scalars().all()
|
||||
return {"entity_types": sorted(etypes), "actions": sorted(actions)}
|
||||
Reference in New Issue
Block a user