Files
wecom_it_smart_desk/docs/02-技术文档/技术架构/designdocs/sysdesign.md
T
Simon facc04aa65 chore: docs 结构整改 + compose 双目录对齐(合并重建提交)
本提交为 .git 对象库损坏后的重建提交,内容等价于原先三个本地提交
(5e2fd4c2 / 57a53c98 / 5d7e1873)的累积结果,未做任何额外改动。

一、docs 结构整改(整改 #14)
根因:重构时新结构为 untracked 文件,执行 git stash(未带 -u)未纳入,
随后 git reset 拉回 HEAD 旧 tracked 树,导致旧树复活、新旧两棵目录
树并存于 docs/,共 791 文件、双分类体系冲突。

修复动作:
- b2 同名异主题文件改名迁移保全 9 个
- C 类 39 个孤立文件按主题正确归类
- A/B1 类 222 个重复文件删除(新结构已有内容副本)
- 9 个旧独有空目录删除
- 270 处内部引用按 verified 映射改写
- 整改记录 #14 登记于 04-运维文档/部署运维

结果:docs 791 → 569 文件,顶层仅规范 8 类 + 治理文件,单树恢复。
残留:约 20 处指向从未存在文件的陈旧死链,归入独立文档卫生任务。

二、compose 双目录对齐(消除踩坑 A)
- docker-compose.yml:nginx 前端挂载全部由根目录 frontend-*/dist
  改为 src/frontend-*/dist(h5 / agent / admin / terminal)
- docker-compose.dev.yml:dev 服务 build context 与卷同步改 src/
- 效果:本地 docker compose up 不再把根目录 stale dist 挂回,
  与线上一致,分叉隐患消除(已 docker compose config 校验通过)

防复发铁律:
- 重构须提交;仓库修复须 git stash -u 或先 commit
- 新结构须 git add 并提交,避免再次 untracked 复活
- H5 改动只动 src/frontend-h5/,禁改根目录遗留 frontend-*/
2026-08-07 22:31:32 +08:00

26 KiB
Raw Blame History

管理后台日志体系 — 系统设计 + 任务分解

阶段standard SOP 第二阶段(架构设计,输入给工程师 寇豆码) 架构师:高见远(software-architect)|日期2026-07-09 上游输入(已 Read 并核对):

  • 产品+设计文档:docs/04-功能设计/系统日志与审计日志-产品与设计.md(许清楚)
  • PRD 增补:01-产品文档/08-集成生态/PRD-REQ-集成-002-管理后台-v1.2.md §「管理后台日志体系」
  • 架构增补:docs/02-技术文档/技术架构/IT智能服务台-系统架构设计文档v2.md §7.1.1 日志体系
  • 运行期日志设计:docs/06-安全审计/审计报告-安全审计/健康检查+错误码+日志结构化.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-logsconfig_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_logsaudit_logs(config_change 事件)
权限 A 沿用 require_admin(建议后续命名为 config_log:read,本迭代不纳入 RBAC 重构);B 沿用 audit_log:read:allD 用 require_admin
新增依赖 无(前端无新 npm 包;后端用标准库 pathlib/os/glob + logging.RotatingFileHandler + fastapi.responses.StreamingResponse

二、实现方案与框架选型

难点与对策

  1. A 增加筛选而不改表/不加索引config_change_logs 已有 idx_ccl_config_keyidx_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.vueruntime-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-logsGET /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,新增 RotatingFileHandlerJSON 格式)写入 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/logsenvironment 增加 RUNTIME_LOG_DIR=/app/logsLOG_FORMAT=json

注:api/audit_logs.pymodels/audit_log.pyservices/audit_log_service.pymodels/config_change_log.pyschemas/admin.py(ConfigHistoryItem) 本次均不修改,直接复用。


四、接口设计(含请求/响应 JSON 示例)

统一响应体:{ "code": 0, "message": "success", "data": {...} };错误 code != 0。 时间参数统一 ISO8601(如 2026-07-09T00:00:00+08:002026-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

响应

{
  "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

响应(节选)

{
  "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

列表响应

{
  "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 运行期日志 文件系统(不建表) 每行 JSONtimestamp, 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-deskbind 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 服务改动)

  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:00Z)。
  5. keyword:对整行 JSON 文本(或 message)做子串匹配,支持 trace_id / employee_id 检索。
  6. 分页:收集命中行 → 时间倒序 → 切片(page/page_size)。短期限制:全量读入内存再切片;若单文件超大,后续接 Loki(已在审计报告中规划)。
  7. download=true:将命中行原样(每行一条 JSON)写入 StreamingResponseContent-Disposition 文件名带时间戳。

七、权限设计

页面/接口 权限 现状 本次
A GET /api/admin/system-logs require_adminadmin 角色) 已有 沿用;建议后续明确命名为 config_log:read(与 B 一并纳入 RBAC 梳理,本迭代不做)
B GET /api/admin/audit-logs audit_log:read:alladmin/auditor 已有(require_permission 复用,零改动
D GET /api/admin/runtime-logs require_adminadmin 角色) 新增 新增端点用 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 < CRITICALD 的 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/toalias 注入),与 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 页;#104T4 旧名)D 页(前后端);T5 双写回归;T6 联调权限。

T1 — 侧边栏重命名 + 路由入口(A 改名、B/D 新增)

  • 涉及文件frontend-admin/src/components/Sidebar.vuefrontend-admin/src/router/index.ts
  • 依赖:无
  • 优先级P1
  • 验收要点:① 侧边栏「监控与数据」出现「配置变更历史(A)」「安全审计日志(B)」「运行期日志(D)」三个独立入口,无「系统日志」歧义统称;② 路由 audit-logsruntime-logs 已注册且 meta.title 正确;③ A 路由 title 改为「配置变更历史」。

T2 — A 页筛选(前端控件 + 后端参数)

  • 及文件frontend-admin/src/views/SystemLogs.vuefrontend-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 角色被拦截(后端已保证)。

#104(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(已改,新增 RUNTIME_LOG_DIR)、backend/app/api/router.py(已改,挂载 router
    • 部署:docker-compose.yml(已改,backend volumes 增加 ${RUNTIME_LOG_HOST_DIR:-/var/log/wecom-it-desk}:/app/logsenvironment 增加 RUNTIME_LOG_DIR=/app/logs + LOG_FORMAT=json
  • 依赖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_logsaudit_logs(config_change 事件) 均新增对应记录;② 两表记录数一致、内容可对应;③ 不在任一侧删除/合并。

T6 — 联调与权限核对

  • 涉及文件:全链路(A/B/D 页面 + 接口)
  • 依赖T2、T3、#104T5 可并行)
  • 优先级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

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

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)

附录 C — 变更记录

版本 日期 变更说明
v1.3 2026-07-09 初稿(高见远)
v1.3.1 2026-08-04 任务编号对齐:T4 → #104(T4 旧名),与项目状态看板 / 任务说明书保持一致;T4 段「涉及文件」由「(新增)」批量调整为「(已落地)」,反映代码已存在的现状;保留原文件路径与依赖信息便于回溯设计