2026-05-30 07:23:33 +08:00
|
|
|
|
"""审计日志服务模块
|
|
|
|
|
|
|
|
|
|
|
|
负责审计日志的查询和写入。
|
|
|
|
|
|
所有业务模块的写操作(增删改、状态变更等)均通过此服务记录审计日志,
|
|
|
|
|
|
支持操作前后值对比,便于问题追溯和合规审计。
|
|
|
|
|
|
|
|
|
|
|
|
被调用方:audit 路由(日志查询接口);被 reminder、report、system、config、ai 等多个服务调用。
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
2026-05-15 11:47:29 +08:00
|
|
|
|
import json
|
2026-06-19 23:03:14 +08:00
|
|
|
|
import logging
|
|
|
|
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
2026-05-15 11:47:29 +08:00
|
|
|
|
|
2026-05-14 14:02:00 +08:00
|
|
|
|
from sqlalchemy.exc import SQLAlchemyError
|
|
|
|
|
|
from sqlalchemy.orm import Session
|
|
|
|
|
|
|
2026-05-29 11:38:53 +08:00
|
|
|
|
from backend.app.core.error_codes import ErrorCode
|
|
|
|
|
|
from backend.app.core.exceptions import AppException
|
2026-05-15 11:47:29 +08:00
|
|
|
|
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:
|
2026-05-30 07:23:33 +08:00
|
|
|
|
"""审计日志业务服务。
|
|
|
|
|
|
|
|
|
|
|
|
依赖:
|
|
|
|
|
|
- AuditRepository:审计日志数据的读写操作。
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
2026-05-14 14:02:00 +08:00
|
|
|
|
def __init__(self) -> None:
|
2026-05-15 11:47:29 +08:00
|
|
|
|
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:
|
2026-05-30 07:23:33 +08:00
|
|
|
|
"""分页查询审计日志列表。
|
|
|
|
|
|
|
|
|
|
|
|
参数:
|
|
|
|
|
|
session: 数据库会话,不可为 None。
|
|
|
|
|
|
filters: 可选的筛选条件,支持 page_no、page_size 等分页参数。
|
|
|
|
|
|
|
|
|
|
|
|
返回:
|
|
|
|
|
|
包含 total、page_no、page_size、list 的分页结果字典。
|
|
|
|
|
|
|
|
|
|
|
|
被调用方:audit 路由(日志查询接口)。
|
|
|
|
|
|
"""
|
2026-05-29 11:38:53 +08:00
|
|
|
|
if session is None:
|
|
|
|
|
|
raise AppException(code=ErrorCode.PARAM_ERROR, message="数据库会话未初始化", status_code=500)
|
2026-05-15 11:47:29 +08:00
|
|
|
|
filters = filters or {}
|
2026-05-29 11:38:53 +08:00
|
|
|
|
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
|
|
|
|
|
2026-05-15 11:47:29 +08:00
|
|
|
|
def write_log(self, session: Session, payload: dict) -> None:
|
2026-05-30 07:23:33 +08:00
|
|
|
|
"""写入审计日志记录。
|
|
|
|
|
|
|
|
|
|
|
|
参数:
|
|
|
|
|
|
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 等多个服务的写操作调用。
|
|
|
|
|
|
"""
|
2026-05-15 11:47:29 +08:00
|
|
|
|
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:
|
2026-05-30 07:23:33 +08:00
|
|
|
|
"""将任意值序列化为 JSON 字符串,用于存储审计日志的 before/after 值。
|
|
|
|
|
|
|
|
|
|
|
|
参数:
|
|
|
|
|
|
value: 原始值,支持 None、str、dict、list 等类型。
|
|
|
|
|
|
|
|
|
|
|
|
返回:
|
|
|
|
|
|
JSON 字符串,None 值原样返回。
|
|
|
|
|
|
"""
|
2026-05-15 11:47:29 +08:00
|
|
|
|
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()
|