Files
wecom_it_smart_desk/docs/03-技术架构/designdocs/sysdesign.md
T
Simon bea288e414 feat: 2026-07-11 全量更新 - 代办集成+会议室预定+知识迭代修复+UI统一+Bug修复
== 已部署上线 (9项) ==
- 代办事项真实数据源集成 (企微审批API 8bug修复链)
- H5/坐席端 Logo样式统一+绿色背景
- 视频引导页修复 (localStorage key v2)
- 坐席端 v9 Vue版本修复 (ElMessage._context)
- 截图按钮 v10 修复 (getDisplayMedia user gesture)
- 扫码样式恢复+H5扫码登录跳转修复
- H5截图快捷键提示

== 代码完成待部署 (3项) ==
- 知识迭代3Bug修复 (#8 POST端点/#7 MERGE幂等/#6 过期检查)
- 会议室预定-小鱼易联终端 (40文件, 40/40测试通过)
- IT资产升级审批推送 (asset_service.py)

== 需求文档 (2项) ==
- 坐席端AI辅助消息框-PRD (4项新功能确认)
- 坐席端布局优化建议 v2.0 (7天计划)

== 新增文档 ==
- 日报-2026-07-11.md
- 知识迭代Bug修复报告-20260711.md
- 会议室预定-部署指南.md
- CHANGELOG.md 更新

== 测试 ==
- test_todo_integration.py: 40/40
- test_meetingroom.py: 40/40
- test_bugfix_ki_suggestions.py: 21/21
2026-07-11 23:13:10 +08:00

502 lines
25 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 管理后台日志体系 — 系统设计 + 任务分解
> **阶段**standard SOP 第二阶段(架构设计,输入给工程师 寇豆码)
> **架构师**:高见远(software-architect)|**日期**2026-07-09
> **上游输入**(已 Read 并核对):
> - 产品+设计文档:`docs/04-功能设计/系统日志与审计日志-产品与设计.md`(许清楚)
> - PRD 增补:`docs/02-产品需求/02-产品需求文档PRD-v1.2-20260704.md` §「管理后台日志体系」
> - 架构增补:`docs/03-技术架构/00-系统架构设计文档-v1.3.md` §7.1.1 日志体系
> - 运行期日志设计:`docs/08-安全审计/审计报告-安全审计/健康检查+错误码+日志结构化.md` §3
> - 真实代码:`frontend-admin/src/{components/Sidebar.vue,router/index.ts,views/SystemLogs.vue,api/admin.ts}`、`backend/app/{api/admin_api.py,api/audit_logs.py,api/router.py,services/admin_service.py,services/audit_log_service.py,models/config_change_log.py,models/audit_log.py,models/automation.py,utils/logging_config.py}`、`docker-compose.yml`
> **设计原则**:沿用现有技术栈,不新建表(A/B/D 均复用既有存储),不引入新 npm/PyPI 依赖;D 后端方案必须可部署、可执行。
---
## 一、设计要点摘要
| 项 | 结论 |
|----|------|
| 技术栈 | 前端 Vue3 + TS + Element Plus + Pinia(沿用);后端 FastAPI + SQLAlchemy(async) + PostgreSQL(沿用)|
| A 配置变更历史 | 复用 `config_change_logs`;后端 `GET /api/admin/system-logs``config_key/changed_by/from/to` 过滤;前端 SystemLogs.vue 加筛选控件;侧边栏改名「配置变更历史」|
| B 安全审计日志 | **后端已存在** `GET /api/admin/audit-logs`(权限 `audit_log:read:all`,参数 `employee_id/action/resource/from/to/page/page_size`);本次**仅前端新建页面 + API 函数**,后端零改动 |
| C 自动化动作日志 | 本次**不做**管理后台可视化(P2,标注后续)|
| D 运行期结构化日志 | 新建前端页 + 后端 `GET /api/admin/runtime-logs`(级别/时间/关键字筛选 + 下载);**落地方案:应用层 JSON 文件日志 + docker-compose 宿主机目录 bind mount**(不挂 docker.sock|
| 双写边界(决策2) | 不改代码,T5 做回归验证:`PUT /api/admin/configs/{key}` 同时写 `config_change_logs``audit_logs`(`config_change` 事件)|
| 权限 | A 沿用 `require_admin`(建议后续命名为 `config_log:read`,本迭代不纳入 RBAC 重构);B 沿用 `audit_log:read:all`D 用 `require_admin` |
| 新增依赖 | 无(前端无新 npm 包;后端用标准库 `pathlib/os/glob` + `logging.RotatingFileHandler` + `fastapi.responses.StreamingResponse`|
---
## 二、实现方案与框架选型
**难点与对策**
1. **A 增加筛选而不改表/不加索引**`config_change_logs` 已有 `idx_ccl_config_key``idx_ccl_changed_at` 索引;`changed_by` 无索引,但配置变更频率低、数据量小,用 `WHERE changed_by = :v`(或 JOIN agents 按姓名匹配)可接受,短期不新增索引(见「待明确」)。
2. **B 零后端改动**:后端 `audit_logs.py` 完整可用,前端补齐页面与 `getAuditLogs` 即可。
3. **D 容器内读日志(核心难点)**:见第六节,采用「应用写文件 + bind mount」而非 docker.sock。
4. **命名歧义消除**:侧边栏/路由将「系统日志」拆为「配置变更历史(A)」与新增「安全审计日志(B)」「运行期日志(D)」。
**架构模式**:前端 视图组件 + API 函数(axios);后端 路由层(FastAPI Router)→ 服务层(service function)→ 数据层(SQLAlchemy / 文件系统)。A/B 走 DB,D 走文件系统。
---
## 三、文件列表(标注 新增 / 修改)
### 前端(frontend-admin/
| 文件 | 类型 | 说明 |
|------|------|------|
| `src/components/Sidebar.vue` | 修改 | 「监控与数据」分组:将原 `系统日志` 改名为 **配置变更历史**;新增 **安全审计日志**(`/audit-logs`)、**运行期日志**(`/runtime-logs`) 两个入口 |
| `src/router/index.ts` | 修改 | A 路由 `meta.title``配置变更历史`;新增 `audit-logs→AuditLogs.vue``runtime-logs→RuntimeLogs.vue` |
| `src/views/SystemLogs.vue` | 修改 | A 页:新增「配置键 / 操作人 / 时间」筛选控件,调用 `getSystemLogs` 新参数 |
| `src/views/AuditLogs.vue` | **新增** | B 页:调用 `getAuditLogs`(级别/操作人/动作/资源/时间筛选 + 分页)|
| `src/views/RuntimeLogs.vue` | **新增** | D 页:级别/时间/关键字筛选 + 分页 + 下载按钮 |
| `src/api/admin.ts` | 修改 | `getSystemLogs` 增参;新增 `getAuditLogs` / `getRuntimeLogs` / `getRuntimeLogDownload` |
### 后端(backend/app/
| 文件 | 类型 | 说明 |
|------|------|------|
| `api/admin_api.py` | 修改 | `GET /system-logs` 增加 `config_key/changed_by/from/to` Query 参数并下传 |
| `services/admin_service.py` | 修改 | `get_system_logs` 增加过滤参数与 WHERE 构造(函数位于文件末尾 ~L1689)|
| `api/runtime_logs.py` | **新增** | 路由前缀 `/admin/runtime-logs``GET /runtime-logs`(列表 + `download=true` 下载)|
| `services/runtime_log_service.py` | **新增** | 读 `RUNTIME_LOG_DIR``*.log`,逐行 JSON 解析、按 level/time/keyword 过滤、分页、生成下载流 |
| `utils/logging_config.py` | 修改 | `setup_logging` 支持 `log_dir`,新增 `RotatingFileHandler`JSON 格式)写入 `RUNTIME_LOG_DIR`,保留 stdout handler |
| `core/config.py` | 修改 | 新增配置项 `RUNTIME_LOG_DIR`(默认 `/app/logs`|
| `api/router.py` | 修改 | 挂载 `runtime_logs_router`(与 `audit_logs_router` 一致,~L243-244 后追加)|
| `docker-compose.yml` | 修改 | backend 服务 `volumes` 增加 `${RUNTIME_LOG_HOST_DIR:-/var/log/wecom-it-desk}:/app/logs``environment` 增加 `RUNTIME_LOG_DIR=/app/logs``LOG_FORMAT=json` |
> 注:`api/audit_logs.py`、`models/audit_log.py`、`services/audit_log_service.py`、`models/config_change_log.py`、`schemas/admin.py`(ConfigHistoryItem) 本次**均不修改**,直接复用。
---
## 四、接口设计(含请求/响应 JSON 示例)
> 统一响应体:`{ "code": 0, "message": "success", "data": {...} }`;错误 `code != 0`。
> 时间参数统一 **ISO8601**(如 `2026-07-09T00:00:00+08:00` 或 `2026-07-09T16:00:00Z`)。
### ① A — 配置变更历史(修改已有)
**请求**
```
GET /api/admin/system-logs
Query:
page int = 1
page_size int = 50 (<=200)
config_key string? 配置键精确/模糊匹配
changed_by string? 操作人 agent_id(前端用坐席下拉取 id 传入)
from string? ISO8601 起始时间(alias from_time
to string? ISO8601 结束时间(alias to_time
```
**响应**
```json
{
"code": 0,
"message": "success",
"data": {
"items": [
{
"id": "3f1a...",
"log_type": "config_change",
"config_key": "auto_reply_enabled",
"old_value": "false",
"new_value": "true",
"changed_by": "a-1001",
"changed_by_name": "张明",
"changed_at": "2026-07-09T14:32:10+08:00"
}
],
"total": 128,
"page": 1,
"page_size": 50
}
}
```
### ② B — 安全审计日志(复用后端,仅前端对接)
**请求**(后端已支持,无需改动)
```
GET /api/admin/audit-logs
Query:
employee_id string?
action string? 如 login / config_change / role_change
resource string? 如 agent / system_config / conversation
from string? ISO8601alias from_time
to string? ISO8601alias to_time
page int = 1
page_size int = 50 (<=500)
Header/Permission: audit_log:read:all
```
**响应**(节选)
```json
{
"code": 0,
"message": "success",
"data": {
"items": [
{
"id": "9b2c...",
"employee_id": "a-1001",
"action": "config_change",
"resource": "system_config",
"resource_id": "auto_reply_enabled",
"details": { "old": "false", "new": "true", "ip": "10.80.0.5" },
"result": "success",
"ip_address": "10.80.0.5",
"user_agent": "Mozilla/5.0 ...",
"created_at": "2026-07-09T14:32:10+08:00"
}
],
"total": 5321,
"page": 1,
"page_size": 50
}
}
```
### ③ D — 运行期结构化日志(新增)
**列表请求**
```
GET /api/admin/runtime-logs
Query:
level string? 枚举 DEBUG/INFO/WWARNING/ERROR/CRITICAL(按 >= 阈值语义过滤)
from string? ISO8601 起始时间(按日志 timestamp 字段)
to string? ISO8601 结束时间
keyword string? 子串匹配(message 或整行,支持 trace_id / employee_id 检索)
page int = 1
page_size int = 100 (可 20/50/100)
download bool = false 为 true 时返回文件流(忽略分页)
Permission: require_admin
```
**列表响应**
```json
{
"code": 0,
"message": "success",
"data": {
"items": [
{
"timestamp": "2026-07-09T06:32:10Z",
"level": "ERROR",
"logger": "app.api.admin_api",
"message": "request_start: POST /api/admin/configs/...",
"module": "admin_api",
"function": "get_system_logs",
"line": 902,
"request_id": "req-abc-123",
"user_id": "a-1001"
}
],
"total": 342,
"page": 1,
"page_size": 100
}
}
```
**下载响应**`download=true`
```
HTTP 200
Content-Type: text/plain; charset=utf-8
Content-Disposition: attachment; filename="runtime-logs-20260709T143200Z.log"
Body: 每行一条原始 JSON 日志(已按筛选条件命中)
```
> 实现用 `StreamingResponse(iter([...]), media_type="text/plain", headers={...})`,逐块吐出,避免大文件占内存。
---
## 五、数据模型 / 字段确认
| 类 | 落点 | 字段(关键)| 本次是否改 |
|----|------|------|------|
| **A** `ConfigChangeLog` | `config_change_logs` | `config_key, old_value, new_value, changed_by, changed_at` | 不改;复用 `ConfigHistoryItem` 响应 schema |
| **B** `AuditLog` | `audit_logs` | `employee_id, action, resource, resource_id, details, result, ip_address, user_agent, created_at` | 不改 |
| **C** `ActionLog` | `auto_action_logs` | `session_id, event, direction, system, request, response, status, latency_ms, error, created_at` | 不改(本次不暴露)|
| **D** 运行期日志 | **文件系统**(不建表)| 每行 JSON`timestamp, level, logger, message, module, function, line, [request_id], [user_id], [extra]` | 由 `logging_config.py` 写出 |
> D 不新建数据库表,符合 PM 决策(D 是应用运行 stdout 日志的「查看」,非新业务数据)。
---
## 六、D 后端落地方案(重点 · 可部署)
### 6.1 结论
**采用「应用层 JSON 文件日志 + docker-compose 宿主机目录 bind mount」方案。**
- 后端容器内部:扩展 `logging_config.setup_logging`,在现有 stdout handler 之外,新增一个 `RotatingFileHandler`,用已有的 `JSONFormatter` 把结构化日志写入 `RUNTIME_LOG_DIR`(容器内默认 `/app/logs/wecom-it-desk.log`,轮转备份 `-1/-2...`)。
- docker-compose:把**宿主机目录**(默认 `/var/log/wecom-it-desk`bind mount 为容器内的 `/app/logs`
- `GET /api/admin/runtime-logs`:直接读 `/app/logs/*.log` 文本文件,逐行 `json.loads`,按 `level` / `from~to`(比较 `timestamp`/ `keyword` 过滤,时间倒序,分页返回;`download=true` 时以 `StreamingResponse` 返回命中行的文本流。
### 6.2 为什么不用 docker.sock / 不读 /var/lib/docker
| 备选方案 | 否决原因 |
|----------|----------|
| 挂载 `/var/run/docker.sock` 调 Docker API 读日志 | ❌ 安全风险(容器逃逸)、可移植性差(NAS/单机不应依赖 Docker API);**本次明确禁止** |
| 读 `/var/lib/docker/containers/<id>/*-json.log` | ❌ 路径含随机容器 ID,每次 `compose up` 变化,无法稳定 bind mount;且 json-file 行格式需二次解析 `{"log":...,"stream":...,"time":...}` |
| **应用层写文件 + bind mount**(选定)| ✅ 路径确定、格式可控(复用现有 `JSONFormatter`)、与 stdout 并存(stdout 仍由 docker `json-file` 捕获供 `docker logs`)、零新依赖、跨环境可移植 |
### 6.3 部署依赖(docker-compose.yml · backend 服务改动)
```yaml
backend:
environment:
# 已有环境变量保留 ...
- RUNTIME_LOG_DIR=/app/logs # 新增:运行期日志写入目录(容器内)
- LOG_FORMAT=${LOG_FORMAT:-json} # 新增:确保 JSON 输出(D 端点按 JSON 解析)
- LOG_LEVEL=${LOG_LEVEL:-INFO}
volumes:
- backend-uploads:/app/uploads
- ${RUNTIME_LOG_HOST_DIR:-/var/log/wecom-it-desk}:/app/logs # 新增:宿主机日志目录 bind mount
```
- 宿主机目录不存在时 compose 自动创建(bind mount 语义)。
- 轮转:`RotatingFileHandler(maxBytes=20*1024*1024, backupCount=5)`,与现有 docker `logging.max-size: "20m"` 对齐。
- 本地开发(非 compose,直接 `uvicorn`):`RUNTIME_LOG_DIR` 默认 `/app/logs`;若该目录不存在,**端点需容错**——返回 `{code:非0, message:"日志目录不可读"}` 或空列表,**不抛 500**(见共享知识)。
### 6.4 读取逻辑要点(给工程师)
1. 遍历 `RUNTIME_LOG_DIR` 下所有 `*.log`(含轮转备份),按文件 mtime 倒序合并。
2. 逐行 `json.loads`;解析失败行跳过(容错)。
3. `level` 过滤:大小写不敏感,按「**>= 阈值**」语义(选 `ERROR` → 显示 ERROR/CRITICAL;选 `INFO` → 显示 INFO+WARNING+ERROR+CRITICAL;选 `DEBUG` → 全部)。
4. `from/to`:取每条日志 `timestamp` 字段(现有 `JSONFormatter` 输出 UTC `Z` 格式),解析为 `datetime` 后比较(兼容 `+08:00``Z`)。
5. `keyword`:对整行 JSON 文本(或 `message`)做子串匹配,支持 `trace_id` / `employee_id` 检索。
6. 分页:收集命中行 → 时间倒序 → 切片(`page/page_size`)。**短期限制**:全量读入内存再切片;若单文件超大,后续接 Loki(已在审计报告中规划)。
7. `download=true`:将命中行原样(每行一条 JSON)写入 `StreamingResponse``Content-Disposition` 文件名带时间戳。
---
## 七、权限设计
| 页面/接口 | 权限 | 现状 | 本次 |
|-----------|------|------|------|
| A `GET /api/admin/system-logs` | `require_admin`(admin 角色)| 已有 | 沿用;**建议后续**明确命名为 `config_log:read`(与 B 一并纳入 RBAC 梳理,本迭代不做)|
| B `GET /api/admin/audit-logs` | `audit_log:read:all`admin/auditor| 已有(`require_permission`| 复用,零改动 |
| D `GET /api/admin/runtime-logs` | `require_admin`(admin 角色)| 新增 | 新增端点用 `require_admin` |
| 双写 `PUT /api/admin/configs/{key}` | 已有 | 维持 A+B 双写 | **不改**T5 验证)|
---
## 八、依赖包
- **前端**:无新增 npm 包(沿用 Element Plus / axios / Pinia)。
- **后端**:无新增 PyPI 包。仅用标准库 `pathlib / os / glob` + `logging.RotatingFileHandler` + `fastapi.responses.StreamingResponse`
---
## 九、共享知识(跨文件约定)
- **日志级别枚举**`DEBUG < INFO < WARNING < ERROR < CRITICAL`D 的 `level` 参数按此序做 >= 阈值过滤)。
- **时间参数**:统一 **ISO8601**(前端用 `toISOString()` 或本地带时区;后端用 `datetime` 解析,兼容 `Z``+08:00`)。
- **统一响应体**`{ code: int, message: string, data: any }`;成功 `code=0`
- **D 日志目录**:统一读 `settings.RUNTIME_LOG_DIR`(默认 `/app/logs`),不在代码里硬编码绝对路径。
- **D 容错**:日志目录不存在 / 无可读文件时,列表返回空 `items` 或友好错误码,**禁止 500**。
- **命名一致性**A 时间参数用 `from/to``alias` 注入),与 B 的 `from/to` 命名保持一致(见「待明确」)。
---
## 十、待明确事项(遗留决策点)
1. **A 时间参数命名**PM 文档用 `changed_from/changed_to`,本设计对齐 B 采用 `from/to`(alias 注入,对前端透明)。若坚持 PM 原文命名需回退——**建议采用 `from/to`**。
2. **A `changed_by` 筛选粒度**:按 `agent_id` 精确匹配(前端坐席下拉取 id);若要支持「按姓名搜」需 JOIN agents 表做 `ILIKE`。本设计采用 agent_id 精确匹配,实现最简。
3. **A `changed_by` 缺索引**:高数据量下建议后续加 `idx_ccl_changed_by`;本次不强制。
4. **A 页面权限命名 `config_log:read`**:本迭代不纳入 RBAC 重构,沿用 `require_admin`;列入后续迭代。
5. **B 待补事件**(敏感数据查询/角色变更/系统配置变更/异常登录 P1,API 调用统计 P2):本次**不实现**,仅保证接口与页面可扩展(后续迭代直接写 `audit_logs` 即可被 B 页展示)。
6. **C 管理后台可视化**:本次不做(P2),标注后续。
7. **E 与 B 衔接**:阶段四「会话日志格式规范」落地后,再在 B 补对应审计事件;本次不实现。
8. **D `level` 语义**:采用「>= 阈值」;若产品要求「精确匹配」需调整(影响前端下拉说明)。
---
## 十一、任务分解清单(有序 · 含依赖 · 验收要点)
> 颗粒度按团队主理人指定:T1 侧边栏+路由;T2 A 筛选;T3 B 页;T4 D 页(前后端);T5 双写回归;T6 联调权限。
### T1 — 侧边栏重命名 + 路由入口(A 改名、B/D 新增)
- **涉及文件**`frontend-admin/src/components/Sidebar.vue``frontend-admin/src/router/index.ts`
- **依赖**:无
- **优先级**P1
- **验收要点**:① 侧边栏「监控与数据」出现「配置变更历史(A)」「安全审计日志(B)」「运行期日志(D)」三个独立入口,无「系统日志」歧义统称;② 路由 `audit-logs``runtime-logs` 已注册且 `meta.title` 正确;③ A 路由 title 改为「配置变更历史」。
### T2 — A 页筛选(前端控件 + 后端参数)
- **涉及文件**`frontend-admin/src/views/SystemLogs.vue``frontend-admin/src/api/admin.ts`(改 `getSystemLogs`)、`backend/app/api/admin_api.py`(改 `GET /system-logs`)、`backend/app/services/admin_service.py`(改 `get_system_logs`)
- **依赖**T1
- **优先级**P1
- **验收要点**:① 前端 A 页有「配置键/操作人/时间」三类筛选控件且与分页联动;② 后端 `GET /api/admin/system-logs` 支持 `config_key/changed_by/from/to` 且过滤正确;③ 空筛选时等价原行为。
### T3 — 新建 B 安全审计日志页(前端,复用后端)
- **涉及文件**`frontend-admin/src/views/AuditLogs.vue`(新增)、`frontend-admin/src/api/admin.ts`(新增 `getAuditLogs`)
- **依赖**T1
- **优先级**P1
- **验收要点**:① 页面可筛选 `employee_id/action/resource/from/to` 并分页展示;② 调通既有 `GET /api/admin/audit-logs`;③ 非 admin/auditor 角色被拦截(后端已保证)。
### T4 — 新建 D 运行期日志页(前端 + 后端,含部署改动)
- **涉及文件**
- 前端:`frontend-admin/src/views/RuntimeLogs.vue`(新增)、`frontend-admin/src/api/admin.ts`(新增 `getRuntimeLogs`/`getRuntimeLogDownload`)
- 后端:`backend/app/api/runtime_logs.py`(新增)、`backend/app/services/runtime_log_service.py`(新增)、`backend/app/utils/logging_config.py`(改)、`backend/app/core/config.py`(改)、`backend/app/api/router.py`(改,挂载 router)
- 部署:`docker-compose.yml`(backend volumes + env)
- **依赖**T1
- **优先级**P0
- **验收要点**:① admin 可按级别+时间+关键字筛选查看日志;② 可下载 `.log`/`.txt` 文件流;③ `logging_config` 写入 `RUNTIME_LOG_DIR` 且 docker-compose 挂载生效;④ 非 admin 调接口返回 403;⑤ 目录不可读时不 500。
### T5 — 双写回归验证(决策2,无代码改动)
- **涉及文件**:无(验证 `PUT /api/admin/configs/{key}` 既有行为)
- **依赖**T2
- **优先级**P0
- **验收要点**:① 修改任一配置项后,`config_change_logs``audit_logs`(`config_change` 事件) 均新增对应记录;② 两表记录数一致、内容可对应;③ 不在任一侧删除/合并。
### T6 — 联调与权限核对
- **涉及文件**:全链路(A/B/D 页面 + 接口)
- **依赖**T2、T3、T4T5 可并行)
- **优先级**P0
- **验收要点**:① A(admin)/B(audit_log:read:all)/D(admin) 权限分别核对通过;② 三个页面在管理后台真实跑通;③ 与 PM 文档 §5 五项决策验收标准逐条对齐。
---
## 十二、对 PM 文档遗留 5 问的回应
1. **D 后端聚合方案定稿** → 已定稿:应用层 JSON 文件日志 + docker-compose 宿主机目录 bind mount(见第六节)。不使用 docker.sock,不读 `/var/lib/docker`。这是短期可执行方案,中期可平滑切 Loki(D 端点抽象出 `RuntimeLogService`,换实现不影响前端)。
2. **B 待补事件是否纳入本次****不建议纳入**。敏感数据查询/角色变更/系统配置变更/异常登录(P1)、API 调用统计(P2) 列入后续迭代;本次仅保证 B 接口与页面可扩展(新事件直接写 `audit_logs` 即被展示)。
3. **C 是否需管理后台可视化查询接口****本次不做**(标注 P2)。`auto_action_logs` 已落表,后续如需可补 `GET /admin/auto-action-logs`(权限待定)。
4. **A 页面权限命名** → 沿用 `require_admin`;建议后续明确为 `config_log:read` 并与 B 一并纳入 RBAC 梳理,本迭代不实现。
5. **E 与 B 衔接** → 阶段四「会话日志格式规范」落地后,再在 B 补对应审计事件;本次不实现。
---
## 附录 A — 类图(Mermaid
```mermaid
classDiagram
direction LR
class ConfigChangeLog {
+String id
+String config_key
+Text old_value
+Text new_value
+String changed_by
+DateTime changed_at
}
class AuditLog {
+String id
+String employee_id
+String action
+String resource
+String resource_id
+JSON details
+String result
+String ip_address
+Text user_agent
+DateTime created_at
}
class RuntimeLogEntry {
+String timestamp
+String level
+String logger
+String message
+String module
+String function
+int line
+String request_id
+String user_id
}
class ConfigHistoryItem {
+String id
+String config_key
+String old_value
+String new_value
+String changed_by
+String changed_by_name
+DateTime changed_at
}
class AdminApi {
+GET /system-logs
}
class AuditApi {
+GET /audit-logs
}
class RuntimeApi {
+GET /runtime-logs
}
class AdminService {
+get_system_logs(db, config_key, changed_by, from, to, page, page_size)
}
class AuditLogService {
+list_audit_logs(db, employee_id, action, resource, from, to, page, page_size)
}
class RuntimeLogService {
+read_runtime_logs(dir, level, from, to, keyword, page, page_size)
+build_runtime_log_stream(dir, level, from, to, keyword)
}
class RuntimeLogStore {
<<filesystem>>
+RUNTIME_LOG_DIR/*.log
}
AdminApi ..> AdminService : 调用
AuditApi ..> AuditLogService : 调用
RuntimeApi ..> RuntimeLogService : 调用
AdminService ..> ConfigChangeLog : SELECT
AuditLogService ..> AuditLog : SELECT
RuntimeLogService ..> RuntimeLogStore : 读文件
ConfigHistoryItem <|.. ConfigChangeLog : 映射
RuntimeLogEntry <|.. RuntimeLogStore : 逐行解析
```
## 附录 B — 时序图(Mermaid
```mermaid
sequenceDiagram
autonumber
participant FE as 前端页面
participant API as admin.ts
participant RT as FastAPI Router
participant SVC as Service
participant DB as PostgreSQL
participant FS as 日志文件(RUNTIME_LOG_DIR)
Note over FE,FS: A 配置变更历史(带筛选)
FE->>API: getSystemLogs({config_key, changed_by, from, to, page, page_size})
API->>RT: GET /api/admin/system-logs?...
RT->>RT: require_admin()
RT->>SVC: get_system_logs(db, filters)
SVC->>DB: SELECT ConfigChangeLog WHERE ... ORDER changed_at DESC
SVC->>DB: JOIN agents 取 changed_by_name
DB-->>SVC: rows
SVC-->>RT: {items, total, page, page_size}
RT-->>FE: {code:0, data}
Note over FE,FS: B 安全审计日志(复用后端)
FE->>API: getAuditLogs({employee_id, action, resource, from, to, page, page_size})
API->>RT: GET /api/admin/audit-logs?...
RT->>RT: require_permission(audit_log:read:all)
RT->>SVC: list_audit_logs(db, filters)
SVC->>DB: SELECT AuditLog WHERE ...
DB-->>SVC: rows
SVC-->>RT: {items, total, page, page_size}
RT-->>FE: {code:0, data}
Note over FE,FS: D 运行期日志(列表)
FE->>API: getRuntimeLogs({level, from, to, keyword, page, page_size})
API->>RT: GET /api/admin/runtime-logs?...
RT->>RT: require_admin()
RT->>SVC: read_runtime_logs(RUNTIME_LOG_DIR, filters)
SVC->>FS: 读 *.log 逐行 json.loads
FS-->>SVC: 命中行
SVC-->>RT: {items, total, page, page_size}
RT-->>FE: {code:0, data}
Note over FE,FS: D 运行期日志(下载)
FE->>API: getRuntimeLogDownload({level, from, to, keyword})
API->>RT: GET /api/admin/runtime-logs?download=true
RT->>RT: require_admin()
RT->>SVC: build_runtime_log_stream(RUNTIME_LOG_DIR, filters)
SVC->>FS: 读 *.log 过滤行
RT-->>FE: StreamingResponse(text/plain, attachment)
```