## ADDED Requirements ### Requirement: API 请求全量采集 系统 SHALL 通过 `ApiRequestLogMiddleware` 记录经 FastAPI 处理的 API 请求,并写入 `sys_api_request_log`。每条记录 SHALL 包含 HTTP 方法、路径(接口地址)、Query 字符串、请求体、响应体、HTTP 状态码、耗时、IP、User-Agent、请求时间(`create_at`)、用户 ID、用户账号(`user_no`)及用户姓名(`nick_name` → `create_name`)。 #### Scenario: 记录业务 API 入参与出参 - **WHEN** 已认证用户调用 JSON 业务接口(如 `POST /api/web/crm/customer/update`) - **THEN** 系统持久化 `path`、`query_string`、`request_body`、`response_body` - **AND** `create_at` 为请求完成时刻 - **AND** `create_name` 为 JWT 中的 `real_name` #### Scenario: 敏感字段脱敏 - **WHEN** 请求或响应 JSON 包含 `password`、`token`、`authorization` 等敏感键 - **THEN** 落库值 SHALL 为 `***` 而非明文 #### Scenario: 跳过高噪与健康检查路径 - **WHEN** 请求为 `OPTIONS` 或路径为 `/health`、`/docs`、`/openapi.json` - **THEN** 系统 SHALL 不写入 `sys_api_request_log` #### Scenario: 文件上传不记录文件体 - **WHEN** 请求 Content-Type 为 `multipart/form-data` - **THEN** `request_body` SHALL 标记为省略说明而非文件二进制内容 ### Requirement: 业务语义字段落库 系统 SHALL 在 `sys_api_request_log` 存储业务语义列:`tenant_no`、`business_title`、`business_type`、`business_id`、`oper_summary`。写操作接口 SHOULD 通过 `@oper_log` 声明上述字段;未声明时系统 SHALL 使用路径与方法推断规则填充 `business_title` 与 `business_type`(`business_id` 与 `oper_summary` 可为空)。 #### Scenario: 装饰器声明修改客户 - **WHEN** 带 `@oper_log(title="CRM客户管理", business_type=2, business_id_param="id", summary="修改客户【{name}】")` 的更新接口被调用且 ContextVar 已设置 - **THEN** 落库 `business_title=CRM客户管理`、`business_type=2` - **AND** `business_id` 为请求中的客户 id - **AND** `oper_summary` 为可读摘要文本 #### Scenario: 只读列表无装饰器 - **WHEN** 用户调用客户列表 GET 接口且路由无 `@oper_log` - **THEN** 系统仍记录 path 与入参出参 - **AND** `business_title` 由路径推断(如 `CRM客户管理`) - **AND** `business_type` 为 `0`(其他) #### Scenario: business_type 枚举 - **WHEN** 系统写入 `business_type` - **THEN** 值 SHALL 属于:0其他、1新增、2修改、3删除、5导出、6导入(与 `sys_oper_type` 字典一致) ### Requirement: 租户隔离 操作日志查询 SHALL 按当前用户 `tenant_no` 过滤 `sys_api_request_log.tenant_no`。中间件落库时 SHALL 从 JWT 写入 `tenant_no`。 #### Scenario: 租户数据不可见 - **WHEN** 租户 A 的管理员查询操作日志 - **THEN** 结果 SHALL 不包含租户 B 的记录 ### Requirement: 操作日志查询 API 系统 SHALL 提供 `POST /api/web/monitor/operlog/list` 与 `POST /api/web/monitor/operlog/detail`,将日志适配为 RuoYi `OperLogVO` 结构:`operUrl`(path)、`operParam`(request_body)、`jsonResult`(response_body)、`operName`(create_name 优先)、`operTime`、`title`(business_title)、`businessType`。 #### Scenario: 列表筛选业务实体 - **WHEN** 请求体包含 `businessId` 与可选 `businessTitle` - **THEN** 返回匹配 `business_id` 的日志分页列表 #### Scenario: 详情含完整入参出参 - **WHEN** 管理员请求某条日志详情 - **THEN** 响应 SHALL 包含完整 `operParam` 与 `jsonResult`(受库内截断上限约束) #### Scenario: 数据权限 - **WHEN** 非 super_admin 用户查询列表 - **THEN** 结果 SHALL 受数据权限约束(按操作人 `user_id` 范围) #### Scenario: 访问控制 - **WHEN** 非 admin/super_admin 用户调用 operlog API - **THEN** 系统 SHALL 拒绝访问 ### Requirement: 操作日志管理页 web-admin SHALL 提供操作日志标准列表页(界面模式 P01),支持按模块、操作类型、操作人、状态、时间范围筛选;详情展示接口地址、入参、出参与摘要。按钮与表格 SHALL 符合 Fresh Mint 与 `web-admin-crud-dialog` 规范。 #### Scenario: 管理员查看日志列表 - **WHEN** 已授权的 admin 用户打开操作日志页 - **THEN** 展示分页列表,列含模块、类型、操作人、时间、摘要、状态 - **AND** 可打开详情查看 `operParam` 与 `jsonResult` ### Requirement: CRM 业务详情操作日志 Tab CRM 客户详情与合同详情中的操作日志 Tab SHALL 按 `business_id` 调用 operlog 列表,不再拉取全量日志后前端过滤。 #### Scenario: 客户详情日志 - **WHEN** 用户在某客户详情打开「操作日志」Tab - **THEN** 仅展示 `business_id` 等于该客户 id 的日志 ### Requirement: 配置与生产开关 `API_REQUEST_LOG_ENABLED` 在生产环境 SHALL 为 `true`。body 截断上限 SHALL 保持可配置(`API_REQUEST_LOG_MAX_BODY`、`API_REQUEST_LOG_MAX_BODY_BYTES`)。 #### Scenario: 关闭采集 - **WHEN** `API_REQUEST_LOG_ENABLED=false` - **THEN** 中间件 SHALL 不写入数据库