- 操作日志审计机制:audit 上下文(成功与失败都记、独立提交、操作人标量 固化、commit 后查询前置约定),用户/角色全部写操作接入;request_data 按键名的结构化脱敏 + UTF-8 字节边界截断 - 角色管理:CRUD 与启停;admin 禁删禁停、有绑定禁删(FOR UPDATE 锁行)、 编码创建后不可改、停用角色不可再分配 - 日志查询:操作/登录两端点,列表与详情两段式(详情含已脱敏请求参数); 时间参数统一转本地 naive(修混用时区 500 与偏移丢弃) - 用户管理:补齐 reset_password 操作人、编辑空串归一 NULL、并发唯一键兜底 400 - 测试 301 例全绿(单元 223 / 集成 78) Co-Authored-By: Claude Code <noreply@anthropic.com>
68 lines
2.5 KiB
Python
68 lines
2.5 KiB
Python
"""日志查询接口(需求 2.5.1:支持日志查询)。
|
||
|
||
操作日志与登录日志是两个独立端点:两表字段与筛选项不同(前者含模块/操作/
|
||
请求详情,后者含失败原因),合并会让 `action` 等参数产生二义;前端正好是
|
||
两个 tab。操作日志另有单条详情(列表为性能排除 `request_data`)。
|
||
|
||
均要求管理员权限;只读查询不写审计(读操作记日志只会淹没审计表)。
|
||
"""
|
||
|
||
from typing import Annotated
|
||
|
||
from fastapi import APIRouter, Path, Query
|
||
|
||
from app.api.deps import AdminUser, DbSession
|
||
from app.core.response import ApiResponse, PageResult, ok
|
||
from app.schemas.log import (
|
||
LoginLogOut,
|
||
LoginLogQuery,
|
||
OperationLogDetailOut,
|
||
OperationLogOut,
|
||
OperationLogQuery,
|
||
)
|
||
from app.services import log_service
|
||
|
||
operation_router = APIRouter(prefix="/operation-logs", tags=["系统管理 · 日志"])
|
||
login_router = APIRouter(prefix="/login-logs", tags=["系统管理 · 日志"])
|
||
|
||
|
||
@operation_router.get(
|
||
"", response_model=ApiResponse[PageResult[OperationLogOut]], summary="操作日志"
|
||
)
|
||
def list_operation_logs(
|
||
session: DbSession,
|
||
_: AdminUser,
|
||
query: Annotated[OperationLogQuery, Query()],
|
||
) -> ApiResponse[PageResult[OperationLogOut]]:
|
||
"""分页查询操作日志,支持按操作人/模块/操作类型/结果/时间范围筛选。
|
||
|
||
按 id 倒序(时间倒序的稳定实现)。列表**不含** `request_data`(大字段),
|
||
展开详情请调 `GET /operation-logs/{log_id}`。
|
||
"""
|
||
return ok(log_service.list_operation_logs(session, query))
|
||
|
||
|
||
@operation_router.get(
|
||
"/{log_id}", response_model=ApiResponse[OperationLogDetailOut], summary="操作日志详情"
|
||
)
|
||
def get_operation_log(
|
||
session: DbSession,
|
||
_: AdminUser,
|
||
log_id: Annotated[int, Path(ge=1)],
|
||
) -> ApiResponse[OperationLogDetailOut]:
|
||
"""查询单条操作日志详情(含已脱敏的请求参数)。
|
||
|
||
前端展开日志行时按 id 按需加载 —— 避免列表翻页携带大字段。
|
||
"""
|
||
return ok(log_service.get_operation_log(session, log_id))
|
||
|
||
|
||
@login_router.get("", response_model=ApiResponse[PageResult[LoginLogOut]], summary="登录日志")
|
||
def list_login_logs(
|
||
session: DbSession,
|
||
_: AdminUser,
|
||
query: Annotated[LoginLogQuery, Query()],
|
||
) -> ApiResponse[PageResult[LoginLogOut]]:
|
||
"""分页查询登录日志,支持按用户名/IP/结果/时间范围筛选。"""
|
||
return ok(log_service.list_login_logs(session, query))
|