Files
junhong_cmp_fiber/openspec/changes/add-asset-operation-audit-log/design.md
huang fe4c545308
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m55s
修正数据
2026-04-27 18:10:08 +08:00

161 lines
6.8 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.
## Context
当前系统已有 `tb_account_operation_log`,但该模型以账号为中心(如 `target_account_id``target_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_data``after_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_type`system常量