# 架构决策：BusinessEvent 业务事件落库

> 日期：2026-08-01
>
> 状态：已采纳，待生产数据迁移
>
> Owner Agent：Backend Core（报告/留资写入点联动 Report Media Backend）
>
> Service Lock：`BusinessEvent*`、预约/报告/留资事件调用点、工作台报告漏斗
>
> Paired QA：Core Flow QA + Report Share QA
>
> Data Model Review Required：Yes
>
> Access / Privacy Review Required：Yes

## 1. 决策背景

此前本体中的 Event 多数只表达“动作发生后应存在的事实”，并没有统一事件表。报告打开仅写应用日志，工作台只能由前端临时拼预约、报告和留资，无法可靠支持经营漏斗、StoreCustomer 时间线、回访归因和复购统计。

## 2. 冻结模型

`BusinessEvent` 是不可变事实，不是业务对象当前状态，也不是消息队列。

| 字段 | 语义 |
|---|---|
| `event_id` | 随机事件 ID；不使用数据库自增 ID 做外部关联 |
| `event_type` / `event_version` | 过去时事件名与 schema 版本 |
| `store_id` | 强制门店数据范围 |
| `store_customer_id` | 可选门店客户稳定关系，供客户时间线使用 |
| `aggregate_type` / `aggregate_id` | 事件对应的 Appointment、Report 或 ReportLead |
| `actor_user_id` / `actor_role` | 操作人快照；匿名公开事件可空 |
| `source` | customer / admin / public_report / system / migration |
| `occurred_at` | 业务事实发生时间 |
| `metadata_json` | 服务端白名单低敏维度，不接受原始请求体 |
| `idempotency_key` | 可选稳定键，保证一个业务结果只落一条 |
| `create_time` | 事件入库时间 |

事件表只追加，不提供更新、软删除或通用明细 API。

## 3. 第一批真实事件

| 事件 | 可靠触发点 | 幂等口径 |
|---|---|---|
| `appointment_created` | 预约保存成功 | appointment ID |
| `appointment_status_changed` | 合法状态迁移保存成功 | appointment ID + 目标状态 |
| `service_started` | `new -> doing` | appointment ID |
| `service_completed` | `doing -> done` | appointment ID |
| `report_submitted` | 报告及图片保存成功 | report ID |
| `report_sent` | 门店员工首次显式确认宠主实际收到 | report ID；`report_sent:{reportId}` |
| `report_opened` / `report_reopened` | 公开报告打开埋点 | 每次有效打开；统计按 report ID 去重 |
| `lead_submitted` | 每次留资提交成功 | 可重复记录；漏斗按 lead ID 去重 |

> 2026-08-02 增量决策：`report_sent` 已通过显式确认状态机可靠落地；复制链接、二维码和预览不构成发送。详见《架构决策-报告确认发送状态-2026-08-02》。`follow_up_completed`、`rebook_created` 仍无可靠状态机或归因字段，本阶段明确不伪造。

## 4. 隐私边界

- 事件表不得保存手机号、报告 token 或 token hash、媒体 URL、IP、openid/unionid、备注、内容全文、密码、密钥或任意请求体。
- 报告打开接口只用 token 在服务端定位 Report，事件仅保存内部 report ID；日志继续只记 SHA-256 前 8 位 hex。
- 管理端只开放按当前会话 `storeId` 聚合的漏斗结果，不开放事件明细。

## 5. 一致性与迁移

- 预约、状态迁移、报告提交与事件写入处于同一数据库事务。
- 留资沿用现有并发幂等事务边界；重复提交可产生动作事件，漏斗按 lead ID 去重，不放大转化数。
- 历史迁移只回填可从数据库确定的预约创建、报告提交、由报告确认的服务完成和留资提交。
- 历史报告打开与开始服务时间无法可靠恢复，不做猜测性回填。

迁移顺序：

1. `20260801_split_service_identity.sql`
2. `20260801_create_store_customer.sql`
3. `20260801_create_business_event.sql`

## 6. BusinessEvent 与 AuditLog 边界

BusinessEvent 记录“经营上发生了什么”；AuditLog 记录“谁对受保护配置或数据做了什么变更”。本阶段先完成服务闭环事实，后续在门店开通、员工权限、设置修改和敏感操作进入生产化时建立独立 AuditLog，不把安全审计字段混入经营事件。

## 7. 验收

- `mvn test` 全绿，迁移 SQL 集成测试通过。
- `npm run build`（admin）通过，工作台“已打开”来自事件汇总。
- customer 访问工作台事件汇总为 403；boss/staff 只能读取本店。
- 事件 metadata 隐私单测证明不含原始 token、手机号或 IP。
- 本体校验通过且 controller/entity 0 漂移。
