dingdanquanliucheng/backend/app/services/audit_service.py

119 lines
4.4 KiB
Python
Raw Normal View History

"""审计日志服务模块
负责审计日志的查询和写入
所有业务模块的写操作增删改状态变更等均通过此服务记录审计日志
支持操作前后值对比便于问题追溯和合规审计
被调用方audit 路由日志查询接口 reminderreportsystemconfigai 等多个服务调用
"""
import json
2026-06-19 23:03:14 +08:00
import logging
logger = logging.getLogger(__name__)
2026-05-14 14:02:00 +08:00
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.orm import Session
from backend.app.core.error_codes import ErrorCode
from backend.app.core.exceptions import AppException
from backend.app.repositories.audit_repository import AuditRepository
2026-05-14 14:02:00 +08:00
2026-05-14 13:51:06 +08:00
class AuditService:
"""审计日志业务服务。
依赖
- AuditRepository审计日志数据的读写操作
"""
2026-05-14 14:02:00 +08:00
def __init__(self) -> None:
self.repository = AuditRepository()
2026-05-14 14:02:00 +08:00
def list_audit_logs(self, session: Session | None = None, filters: dict | None = None) -> dict:
"""分页查询审计日志列表。
参数
session: 数据库会话不可为 None
filters: 可选的筛选条件支持 page_nopage_size 等分页参数
返回
包含 totalpage_nopage_sizelist 的分页结果字典
被调用方audit 路由日志查询接口
"""
if session is None:
raise AppException(code=ErrorCode.PARAM_ERROR, message="数据库会话未初始化", status_code=500)
filters = filters or {}
try:
logs = self.repository.list_logs(session, filters)
page_no = filters.get("page_no", 1)
page_size = filters.get("page_size", 20)
start = max(page_no - 1, 0) * page_size
page_logs = logs[start : start + page_size]
return {
"total": len(logs),
"page_no": page_no,
"page_size": page_size,
"list": [
{
"log_id": item.id,
"operator_name": item.operator_name or "",
"operate_type": item.operate_type,
"biz_type": item.biz_type,
"biz_id": item.biz_id,
"result": item.result,
"operate_time": item.operate_time.strftime("%Y-%m-%d %H:%M:%S") if item.operate_time else "",
"remark": item.remark,
}
for item in page_logs
],
}
except SQLAlchemyError as exc:
raise AppException(code=ErrorCode.SYSTEM_ERROR, message="数据库操作失败", status_code=500) from exc
2026-05-14 13:51:06 +08:00
def write_log(self, session: Session, payload: dict) -> None:
"""写入审计日志记录。
参数
session: 数据库会话调用方负责 commit/rollback
payload: 日志数据字典必含 operate_typebiz_typebiz_id
可选 operator_idoperator_namebefore_valueafter_valueresultremark
被调用方 reminderreportsystemconfigaiorder 等多个服务的写操作调用
"""
before_value = payload.get("before_value")
after_value = payload.get("after_value")
self.repository.create_log(
session,
{
"operator_id": payload.get("operator_id"),
"operator_name": payload.get("operator_name") or "系统",
"operate_type": payload["operate_type"],
"biz_type": payload["biz_type"],
"biz_id": payload["biz_id"],
"before_value": self._serialize_value(before_value),
"after_value": self._serialize_value(after_value),
"result": payload.get("result", "success"),
"remark": payload.get("remark"),
},
)
def _serialize_value(self, value) -> str | None:
"""将任意值序列化为 JSON 字符串,用于存储审计日志的 before/after 值。
参数
value: 原始值支持 Nonestrdictlist 等类型
返回
JSON 字符串None 值原样返回
"""
if value is None:
return None
if isinstance(value, str):
return value
return json.dumps(value, ensure_ascii=False, default=str)
2026-05-14 13:51:06 +08:00
audit_service = AuditService()