## 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)常量?