dingdanquanliucheng/backend/app/services/audit_service.py
2026-06-19 23:03:14 +08:00

119 lines
4.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""审计日志服务模块
负责审计日志的查询和写入。
所有业务模块的写操作(增删改、状态变更等)均通过此服务记录审计日志,
支持操作前后值对比,便于问题追溯和合规审计。
被调用方audit 路由(日志查询接口);被 reminder、report、system、config、ai 等多个服务调用。
"""
import json
import logging
logger = logging.getLogger(__name__)
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
class AuditService:
"""审计日志业务服务。
依赖:
- AuditRepository审计日志数据的读写操作。
"""
def __init__(self) -> None:
self.repository = AuditRepository()
def list_audit_logs(self, session: Session | None = None, filters: dict | None = None) -> dict:
"""分页查询审计日志列表。
参数:
session: 数据库会话,不可为 None。
filters: 可选的筛选条件,支持 page_no、page_size 等分页参数。
返回:
包含 total、page_no、page_size、list 的分页结果字典。
被调用方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
def write_log(self, session: Session, payload: dict) -> None:
"""写入审计日志记录。
参数:
session: 数据库会话(调用方负责 commit/rollback
payload: 日志数据字典,必含 operate_type、biz_type、biz_id
可选 operator_id、operator_name、before_value、after_value、result、remark。
被调用方:被 reminder、report、system、config、ai、order 等多个服务的写操作调用。
"""
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: 原始值,支持 None、str、dict、list 等类型。
返回:
JSON 字符串None 值原样返回。
"""
if value is None:
return None
if isinstance(value, str):
return value
return json.dumps(value, ensure_ascii=False, default=str)
audit_service = AuditService()