Files
junhong_cmp_fiber/openspec/changes/archive/2026-04-30-add-asset-operation-audit-log/design.md
2026-04-30 11:18:13 +08:00

6.8 KiB
Raw Blame History

Context

当前系统已有 tb_account_operation_log,但该模型以账号为中心(如 target_account_idtarget_username),不适合承载资产域(卡/设备)高频敏感操作。现有资产相关写操作分散在多个模块:

  • iot_card:分配、回收、系列绑定、轮询开关、实名策略、手动实名、删除
  • device:删除、分配回收、绑定解绑、系列绑定、设备停复机、网关远程控制
  • asset:统一入口停复机、停用、轮询状态、实名策略
  • auto-stop-resume:轮询触发自动停复机

这些链路当前多为业务日志zap缺少可结构化查询的审计落库无法稳定支持安全审计、操作追责与问题复盘。

约束:

  • 必须遵循 Handler -> Service -> Store -> Model,审计写入在 Service 层触发。
  • 审计能力按业务域拆分,本次仅实现资产域,不做全局万能日志表。
  • 主流程性能不可明显退化,审计写入采用异步非阻塞。
  • 不新增自动化测试文件,采用 PostgreSQL MCP + Postman/curl 手工验证。

Goals / Non-Goals

Goals:

  • 新增资产域专用审计日志模型、存储与服务(tb_asset_operation_log + asset_audit)。
  • 覆盖卡与设备敏感写操作,要求成功/失败/拒绝全部记录。
  • 统一日志字段:操作人、时间、动作、目标资产、变更前后、请求上下文、结果。
  • 在现有模块中实现可复用的审计调用模式,减少重复拼装逻辑。
  • 提供可回滚的迁移方案与明确的手工验收清单。

Non-Goals:

  • 本阶段不实现通用“跨域审计查询平台”。
  • 本阶段不改造账号域日志(tb_account_operation_log)历史数据。
  • 本阶段不新增审计查询 API先完成采集落库查询按 DB 手工验证)。

Decisions

决策1资产域独立日志模型与表

选择:新增 tb_asset_operation_log,不复用 tb_account_operation_log

理由

  • 资产日志与账号日志目标对象不同,字段语义差异大。
  • 独立表可避免“万能表”字段污染,支持未来按域继续拆分(财务/权限等)。
  • 资产操作体量高,独立索引策略可控。

备选方案:复用 tb_account_operation_log 增加 nullable 字段。 放弃原因:语义混杂、索引冲突、后续演进成本高。


决策2统一资产审计服务 + 结构化日志构建器

选择:新增 internal/service/asset_audit,提供统一 LogOperation(ctx, entry);各业务 Service 在成功/失败/拒绝节点调用。

理由

  • 降低每个模块重复组装字段成本。
  • 便于统一控制日志格式、字段裁剪、异常降级。
  • 与现有账号审计模式一致,降低团队理解成本。

备选方案:每个业务 Service 直接写 Store。 放弃原因:重复代码多,格式易漂移,难做统一治理。


决策3全结果态记录success/failed/denied

选择:结果字段使用 result_status(成功/失败/拒绝),并记录 error_code/error_msg(失败或拒绝时)。

理由

  • 仅记录成功不足以支撑安全审计(越权尝试、失败操作同样关键)。
  • 便于风控与运维排查(失败高发点、越权热点)。

备选方案:只记录成功。 放弃原因:审计链不完整。


决策4前后镜像字段规范化

选择before_data/after_data 统一为 JSONB

  • create/绑定类:before_data 可为空,after_data 必填关键字段
  • update/状态流转类:before_dataafter_data 同时记录
  • delete/解绑类:before_data 必填,after_data 可为空或最小状态

理由

  • 满足“从什么变成什么”的核心诉求。
  • 便于后续做差异化检索与审计导出。

决策5依赖注入与接入矩阵

选择:在 bootstrap 注入 assetAuditService 到以下服务:

  • iot_card.Service
  • device.Service
  • iot_card.StopResumeService
  • asset.LifecycleService
  • polling.AssetPollingService(设备轮询开关路径)
  • device_import.Service / iot_card_import.Service(任务创建记录)

理由

  • 关键写操作分布在多个服务,必须覆盖所有真实入口。
  • 避免仅在 Handler 记录导致丢失 Service 内部失败/回滚细节。

决策6异步写入 + 失败不阻断

选择:审计写入使用 Goroutine 异步落库,写入失败仅记录 Error 日志,不影响主业务结果。

理由

  • 符合当前项目审计日志规范。
  • 避免写操作主路径被审计 IO 放大。

补充约束

  • 单条日志大小限制(对大字段做截断/脱敏),避免超大 JSON 影响 DB。
  • 脱敏字段(如 WiFi 密码)仅记录摘要,不明文落库。

决策7常量与操作类型字典化

选择:在 pkg/constants 定义资产审计操作类型、结果类型、目标类型常量,禁止硬编码。

理由

  • 统一语义,避免多模块字符串漂移。
  • 便于统计与后续扩展。

Risks / Trade-offs

  • [风险] 日志量增长导致表膨胀和查询变慢
    缓解:增加组合索引(资产、操作人、时间、结果),后续按月归档策略预留。

  • [风险] before/after 组装不一致,影响可读性
    缓解:统一字段模板与 helper代码评审按固定清单检查。

  • [风险] 异步写入在进程异常退出时丢少量日志
    缓解:接受该权衡;关键安全场景后续可升级为异步队列化写入。

  • [风险] 远程控制接口包含敏感参数(如密码)
    缓解:明确脱敏规则,禁止明文入 after_data

  • [权衡] 全量记录失败/拒绝会增加写入量
    接受:审计完整性优先于少量存储成本。

Migration Plan

  1. 新增模型与迁移:tb_asset_operation_log(含必要索引)。
  2. 新增 asset_audit store/service并在 bootstrap 完成注入。
  3. 按操作矩阵分批接入:
    • 第一批:停复机、绑定解绑、分配回收、停用、实名策略、轮询开关
    • 第二批:远程控制、导入任务创建、删除/批量删除
  4. 补充常量与日志构建 helper统一字段格式与脱敏。
  5. 使用 PostgreSQL MCP + Postman/curl 执行手工验收输出证据请求、响应、DB 快照)。

回滚策略:

  • 业务回滚:关闭审计调用开关(或临时空实现),不影响主流程。
  • 数据回滚:执行 migration down 删除新表。

Open Questions

  • 是否在本期即提供后台审计查询 API还是维持 DB 手工查询到下一期?
  • after_data 的最大存储体积阈值定为多少(如 16KB/32KB
  • 是否需要为“自动停复机(系统触发)”定义专门 operator_typesystem常量