Files
shanxia2.0/docs/superpowers/specs/2026-06-29-operation-audit-design.md
T
2026-06-29 11:15:39 +08:00

10 KiB
Raw Blame History

操作审计与业务语义日志 — 设计文档

日期: 2026-06-29
状态: 已批准(待实现)
OpenSpec: openspec/changes/operation-audit-enhancement/
前置: 现有 ApiRequestLogMiddleware、sys_api_request_log、operlog/list API
界面模式: P01 标准列表(操作日志管理页)

目标

满足客户对「用户操作可追溯」的要求,在不重建审计体系的前提下:

  1. 继续全量 API 采集 — 接口地址、入参、出参、时间、操作人
  2. 补齐请求人姓名与租户 — create_name(nick_name)、tenant_no
  3. 增加业务语义字段 — 模块、操作类型、业务主键、可读摘要
  4. 提供全局管理页 — 筛选、分页、详情
  5. CRM 详情 Tab — 按 business_id 精确查日志

已确认业务规则

# 规则 决策
D1 总体方案 方案 A + 语义字段;不新建 sys_oper_log 表
D2 采集范围 默认全 API(含 GET 列表);跳过 health/docs/OPTIONS
D3 写操作语义 @oper_log 装饰器声明;只读走路径推断
D4 请求人展示 列表/详情 姓名优先(create_name),账号为 username
D5 入参出参 已在中间件记录;详情完整展示(受截断限制)
D6 文件上传 multipart 不记文件体
D7 权限 operlog API 与菜单:admin / super_admin + 数据权限
D8 归档 本期只做表结构扩展;定时归档为后续项
D9 business_type 对齐 sys_oper_type:0/1/2/3/5/6
D10 oper_summary 模板 {动词}{对象}【{名或编号}】

非目标(本期不做)

  • 前端 UI 埋点(不发 API 的行为)
  • MQ/ES 独立审计平台
  • 与 CRM Java 仓 sys_oper_log 数据合并
  • 全模块写接口 @oper_log 一次性覆盖
  • multipart 文件内容入库
  • 响应体手机号/身份证扩展脱敏(待客户确认)

现状与差距

已实现

能力 实现位置
中间件采集 backend/app/core/api_request_log_middleware.py
落库表 sys_api_request_log、sys_api_request_log_his
查询 API POST /api/web/monitor/operlog/list、/detail
CRM 嵌入 CustomerDetailDialog、contract/detail Tab
默认开关 API_REQUEST_LOG_ENABLED=true

字段映射(客户需求 → 现状)

客户项 表字段 OperLog 字段 现状
接口地址 path operUrl ✅
入参 query_string + request_body operParam ✅(有例外)
出参 response_body jsonResult ✅(有截断)
时间 create_at operTime ✅
账号 username operName 兜底 ✅
姓名 create_name operName 优先 ⚠️ 未写入
模块/类型/单据 — title、businessType ⚠️ 仅读时推断
可读摘要 — — ❌
租户 — — ❌

采集限制(保持)

场景 处理
body > 10MB [body omitted: N bytes]
文本截断 API_REQUEST_LOG_MAX_BODY 65536 字符
原始读取 API_REQUEST_LOG_MAX_BODY_BYTES 131072
敏感键 password/token 等 → ***
multipart [multipart/form-data body omitted]

数据模型扩展

sys_api_request_log / sys_api_request_log_his 新增列

字段 类型 说明
tenant_no varchar(64) 租户编号,JWT 写入
business_title varchar(100) 模块标题,如 CRM客户管理
business_type tinyint 0其他 1新增 2修改 3删除 5导出 6导入
business_id varchar(64) NULL 业务主键
oper_summary varchar(500) NULL 人读摘要

建议索引:

  • idx_arl_tenant_createat (tenant_no, create_at)
  • idx_arl_business (business_id, business_title, create_at)

business_type 枚举

值 含义 典型场景
0 其他 登录、只读查询
1 新增 create/add
2 修改 update/edit/审批/转移
3 删除 delete/remove
5 导出 export
6 导入 import

存储样式示例

修改客户(有 @oper_log)

tenant_no        = shanxia2.0
business_title   = CRM客户管理
business_type    = 2
business_id      = SXCU0000000000000123
oper_summary     = 修改客户【杭州某某化工有限公司】
path             = /api/web/crm/customer/update
http_method      = POST
request_body     = {"id":"SXCU...","customerName":"杭州某某化工有限公司",...}
response_body    = {"code":200,"message":"success",...}
user_id          = d3000000000000000000000000000001
username         = liming
create_name      = 李明
create_at        = 2026-06-29 14:32:05
ip_address       = 192.168.1.10
duration_ms      = 45
status_code      = 200

客户列表(无装饰器,推断兜底)

business_title   = CRM客户管理
business_type    = 0
business_id      = NULL
oper_summary     = NULL
path             = /api/web/crm/customer/list
request_body     = {"page":1,"pageSize":10}
response_body    = {"code":200,"data":{...}}

导出(business_id 可空)

business_title   = CRM客户管理
business_type    = 5
business_id      = NULL
oper_summary     = 导出客户列表

架构与数据流

flowchart LR
  Client[Web / 小程序] --> Crypto[ApiCryptoMiddleware]
  Crypto --> LogMW[ApiRequestLogMiddleware]
  LogMW --> Route[FastAPI Route]
  Route --> Decorator["@oper_log → ContextVar"]
  Decorator --> Service[Service / CRUD]
  Service --> Route
  Route --> LogMW
  LogMW --> DB[(sys_api_request_log)]
  DB --> OperAPI[operlog/list detail]
  OperAPI --> AdminPage[操作日志管理页]
  OperAPI --> CRMTab[CRM 详情 Tab]

中间件与加解密顺序

main.py 注册顺序:先 ApiRequestLogMiddleware,后 ApiCryptoMiddleware → Crypto 为外层。RequestLog 在解密后的明文上记录 JSON。

ContextVar 与装饰器

app/core/context.py 扩展:

oper_log_meta: ContextVar[dict] = ContextVar("oper_log_meta", default=None)

@oper_log 参数(建议):

参数 说明
title business_title
business_type int
business_id_param 从 path/query/body 取 id 的键名
summary 固定摘要或带占位符
summary_from 从 body 字段组装摘要的可选映射

装饰器在 handler 入口解析参数并 oper_log_meta.set(...);中间件 finally 读取后落库。

子线程注意: _persist_log 经 asyncio.to_thread 执行时,须在主线程先把 ContextVar 值拷贝进 kwargs,避免线程内丢失。

路径推断兜底(无装饰器)

沿用 oper_log.py 中 _MODULE_TITLE_RULES、_ACTION_TYPE_MAP;中间件落库时若 ContextVar 为空则写入推断结果。


API

现有(增强)

方法 路径 变更
POST /api/web/monitor/operlog/list 增加 businessId、businessTitle、tenant_no DB 筛选;title/businessType 读落库列
POST /api/web/monitor/operlog/detail operName = create_name 优先

OperLogListBody 扩展字段

字段 类型 说明
businessId string? 业务主键
businessTitle string? 模块模糊
beginTime / endTime string? 时间范围(可选本期)

前端

操作日志管理页

项 值
路径建议 web-admin/src/views/system/operlog/index.vue
Hash /#/system/operlog(以菜单 SQL 为准)
模式 P01,参考 views/system/unit/index.vue
权限 system:operlog:list、system:operlog:query

列表列: 模块、操作类型(Tag)、操作人、操作时间、摘要、状态、耗时、操作(详情)

筛选: 模块、操作类型、操作人、状态、时间范围

详情弹窗: 接口地址、IP、入参(operParam)、出参(jsonResult)、错误信息

CRM Tab 改造

页面 变更
CustomerDetailDialog listOperLog({ businessId: customerId, pageSize: ... })
contract/detail listOperLog({ businessId: contractId, ... })

移除「拉全量 + 前端 filter path/body」逻辑。

菜单 SQL

挂于「系统管理」下,order_num 在字典/岗位等之后;menuCode 示例 system:operlog:list。


配置

环境变量 默认 说明
API_REQUEST_LOG_ENABLED true 生产必须为 true
API_REQUEST_LOG_MAX_BODY 65536 文本字符截断
API_REQUEST_LOG_MAX_BODY_BYTES 131072 读取字节上限
API_REQUEST_LOG_EXCLUDE_PATHS 空(本期新增) 逗号分隔 path 前缀排除

迁移

脚本: docs/sql/migrations/2026-06-29-operation-audit.sql

  1. ALTER TABLE sys_api_request_log 增加五列 + 索引
  2. 同步 ALTER sys_api_request_log_his
  3. 插入菜单与 super_admin 角色菜单(确定性 id)
  4. 同步 docs/sql/shanxia2.0.sql

实施阶段

阶段 内容 估时
1 SQL + ORM + 中间件姓名/租户/语义 1 天
2 ContextVar + @oper_log + CRM 写接口样例 1 天
3 operlog API + 管理页 + CRM Tab 1~2 天
4 验收 + graphify 0.5 天

验收清单

  • 修改客户后 sys_api_request_log 含 create_name、path、request_body、response_body
  • 有 @oper_log 的记录含 business_title、business_type、business_id、oper_summary
  • 管理页可筛选、详情可看入参出参
  • 客户详情 Tab 仅显示该 business_id 日志
  • 非 admin 无法访问 operlog API
  • 租户 A 看不到租户 B 日志
  • multipart 上传不产生文件二进制日志

待客户确认

  1. 「所有操作」范围 — 当前默认全 API;若仅写操作需配置排除规则
  2. 日志保留天数 — 建议 90 天热数据 + 归档
  3. 响应体 PII — 是否扩展脱敏手机号/身份证

参考

  • backend/app/core/api_request_log_middleware.py
  • backend/app/crud/monitor/oper_log.py
  • backend/app/api/monitor.py
  • docs/sql/sx.sql → sys_oper_log(RuoYi 对照)
  • openspec/changes/operation-audit-enhancement/