design.md 4.2 KB

Context

已有 ApiRequestLogMiddleware 全量采集 Web API 至 sys_api_request_log(path、query、request_body、response_body、user_id、username、create_at 等)。operlog/list 将上述数据适配为 RuoYi OperLogVO,权限为 admin/super_admin,CRM 详情 Tab 已调用但未按业务实体过滤。

约束:MySQL 5.7、租户隔离、数据权限、Fresh Mint UI(P01 列表)、中间件与 ApiCryptoMiddleware 顺序不变(Crypto 外层、RequestLog 内层,记录明文 JSON)。

Goals / Non-Goals

Goals:

  • 客户可见:姓名、时间、地址、入参、出参
  • 业务语义可筛、可按 business_id 关联单据
  • 写操作通过 @oper_log 声明;未声明接口保留路径推断兜底
  • 全局操作日志管理页

Non-Goals:

  • 前端 UI 行为埋点(不发 API 的操作)
  • 独立审计子系统(MQ/ES)
  • 与 CRM Java 仓 sys_oper_log 数据合并
  • 本期全量 @oper_log 覆盖所有写接口
  • multipart 文件内容入库

Decisions

D1 方案 A + 语义字段(不新建 sys_oper_log)

在 sys_api_request_log 扩展列,中间件继续全量采集;语义由 @oper_log + ContextVar 注入。理由:基础设施已存在,改动面可控。

D2 语义字段集

列 说明
tenant_no 租户隔离
business_title 模块,如 CRM客户管理
business_type tinyint,对齐 sys_oper_type(0其他 1新增 2修改 3删除 5导出 6导入)
business_id 业务主键,可空
oper_summary 人读摘要,如 修改客户【杭州某某化工】

展示优先级:oper_summary 用于列表摘要;title/businessType 映射 operlog API 字段。

D3 写入链路

  1. 请求进入 → 路由 @oper_log 在 handler 执行前/包装器内设置 ContextVar
  2. call_next 完成 → 中间件 finally 读 ContextVar,与 JWT 字段一并 _persist_log
  3. ContextVar 为空时:business_title/business_type 由 oper_log.py 现有路径规则在读时兜底(落库可为 NULL,或中间件落库时同步写推断值——实现取落库时写推断值以减少读时计算)

D4 请求人姓名

JWT payload 已有 real_name(nick_name)。中间件 _claims_from_auth 扩展解析并写入 create_name;username 仍为 user_no。

D5 oper_summary 模板

{动词}{对象}【{显示名或编号}】,动词与 business_type 一致。装饰器可传 summary 或模板参数;Service 层可在执行业务后更新摘要(二期可选)。

D6 查询与权限

  • operlog/list:DB 层筛选 tenant_no、语义字段;数据权限仍按 user_id
  • 管理页菜单:system:operlog:list;仅 admin/super_admin(与现 monitor API 一致)
  • CRM Tab:business_id + 可选 business_title 精确过滤

D7 记录范围(默认)

  • 记录:经 FastAPI 的业务 API(含 GET 列表)
  • 跳过:OPTIONS、/health、/docs、/openapi.json(与现中间件一致)
  • 不完整:multipart、超大 body、敏感字段脱敏(保持现逻辑)

客户若后续要求「仅写操作」,通过 API_REQUEST_LOG_EXCLUDE_PATHS 配置扩展(本期仅预留 settings 字段,默认空)。

D8 归档

表 sys_api_request_log_his 已存在;本期迁移脚本扩展同名列。定时归档任务记入 tasks 后续项,不阻塞主功能。

Risks / Trade-offs

风险 缓解
日志量膨胀 后续归档 + 可选排除只读路径
未注解接口语义弱 路径推断兜底 + 逐步补注解
响应体含 PII 保持脱敏;详情仅授权角色可见
ContextVar 在子线程丢失 落库用 asyncio.to_thread 时传入已收集的 dict,不依赖子线程读 ContextVar

Migration Plan

  1. 执行 docs/sql/migrations/2026-06-29-operation-audit.sql
  2. 部署后端(中间件 + 装饰器 + API)
  3. 部署前端(管理页 + CRM Tab)
  4. 确认生产 API_REQUEST_LOG_ENABLED=true
  5. 为核心 CRM 写接口补 @oper_log

Open Questions

  • 日志在线保留天数(默认 90 天归档,待客户确认)
  • 响应体是否额外脱敏手机号/身份证(默认保持现状,仅 password/token 类)