feat(audit): 日活使用统计 + 双 CSV 导出(审计/上下线,列可自定义)
【日活统计:GET /audit/daily-usage】 按【北京时间自然日 × 操作人】聚合,单个 GROUP BY 完成(count(*) FILTER), 不用窗口函数。指标:上线/下线时间、操作次数、登录/登出次数。 ⚠️ 上线/下线时间取【当天首次/末次活动】,刻意不取登录时间: Refresh Token 有效期 7 天,用户不必每天重新登录。按登录算会出现 「登录次数 0、上线时间空,但操作次数 35」——报表自相矛盾。 时间一律按 +08:00 分日与渲染,否则早班(00:00~08:00)操作会掉到前一天。 【CSV 导出:两个端点 + 列自定义】 - /audit/logs/export 整体审计导出,筛选维度与列表页完全一致 - /audit/daily-usage/export 上下线导出,每人一行 - 列清单由后端统一维护并经 /audit/options 下发(log_export_columns / usage_export_columns),前端不硬编码表头,避免两端漂移 - ⚠️ 响应带 UTF-8 BOM:Excel 靠它识别编码,否则中文表头全乱码 - 单次上限 5 万行,超出经 X-Export-Truncated 头告知前端明确提示 (静默截断比报错更危险) - list_audit_logs 与 export_audit_logs 共用 _log_filters, 保证「看到的」与「导出的」永远是同一批数据 【前端】 - 审计页页头新增「人员统计」「导出 CSV」两个按钮,现有表格与筛选零改动 - 人员统计走抽屉(AuditUsagePanel):日期范围+快捷键、日活表格、 上下线次数彩色标签、北京时间渲染、导出前弹列勾选面板 - ExportColumnsModal 为两处导出共用,默认全选
This commit is contained in:
@ -45,6 +45,32 @@ class AuditLogListResponse(BaseModel):
|
||||
total: int
|
||||
|
||||
|
||||
class DailyUsageRow(BaseModel):
|
||||
"""某个操作人在某一天的用量汇总(北京时间自然日)"""
|
||||
day: str # YYYY-MM-DD(北京时间)
|
||||
user_id: str | None = None
|
||||
display_name: str | None = None
|
||||
role: str | None = None
|
||||
|
||||
login_count: int = 0 # 登录次数(当天成功登录)
|
||||
logout_count: int = 0 # 登出次数(当天成功登出)
|
||||
op_count: int = 0 # 操作次数(当天全部审计记录数)
|
||||
|
||||
# ⚠️ 上线/下线时间取【当天首次/末次活动】,不是登录/登出时间:
|
||||
# token 有效期内(refresh 7 天)用户不会重新登录,按登录算会导致
|
||||
# 「登录次数 0 但操作 35 次」这种自相矛盾。
|
||||
first_active_at: datetime | None = None # 上线时间(当天首次活动)
|
||||
last_active_at: datetime | None = None # 下线时间(当天末次活动)
|
||||
|
||||
|
||||
class DailyUsageResponse(BaseModel):
|
||||
"""日活 / 使用统计"""
|
||||
start_date: str
|
||||
end_date: str
|
||||
items: list[DailyUsageRow]
|
||||
total: int # 行数(= 天数 × 人数),不是审计记录数
|
||||
|
||||
|
||||
class AuditOption(BaseModel):
|
||||
"""筛选项(value/label 结构,直接喂给前端下拉)"""
|
||||
value: str
|
||||
@ -55,3 +81,8 @@ class AuditOptionsResponse(BaseModel):
|
||||
"""筛选项集合"""
|
||||
modules: list[AuditOption]
|
||||
actions: list[AuditOption]
|
||||
# 导出可选的列(value=后端列 key,label=中文表头)。
|
||||
# 由后端下发而非前端硬编码:列的中文名与取值口径都在后端,
|
||||
# 两端各写一份迟早会出现"导出的列和页面上的对不上"。
|
||||
log_export_columns: list[AuditOption] = []
|
||||
usage_export_columns: list[AuditOption] = []
|
||||
|
||||
Reference in New Issue
Block a user