暂存一下,防止丢失
This commit is contained in:
107
docs/tech-inapp-notifications/前端联调与验收契约.md
Normal file
107
docs/tech-inapp-notifications/前端联调与验收契约.md
Normal file
@@ -0,0 +1,107 @@
|
||||
# 公共站内通知前端联调与验收契约
|
||||
|
||||
## 交付边界
|
||||
|
||||
本文是后台管理端、代理端和 C 端的框架无关契约。当前仓库不包含前端源码,因此页面组件、状态管理和浏览器联调必须在对应前端仓库实施;本文不把契约完成表述为前端实现或人工验收完成。
|
||||
|
||||
第一版固定使用 HTTP 轮询,不使用 WebSocket/SSE,也不维护 Redis 未读计数。
|
||||
|
||||
## 未读轮询与徽标
|
||||
|
||||
后台布局和 C 端消息入口挂载后立即请求各自的 `unread-count`,之后每 30 秒刷新:
|
||||
|
||||
1. 页面变为不可见时暂停计时器。
|
||||
2. 页面恢复可见时立即刷新一次,再恢复 30 秒周期。
|
||||
3. 请求失败时保留上一次成功值,不改写为 0;后续周期静默重试。
|
||||
4. 组件卸载时必须清理计时器,避免重复轮询。
|
||||
|
||||
徽标固定宽度,验收矩阵如下:
|
||||
|
||||
| `count` | `display_count` | 展示 |
|
||||
|---:|---|---|
|
||||
| 0 | `0` | 隐藏徽标 |
|
||||
| 1 | `1` | 显示 1 |
|
||||
| 99 | `99` | 显示 99 |
|
||||
| 100 | `99+` | 显示 99+ |
|
||||
|
||||
前端直接使用后端 `display_count`,不自行重复计算上限。
|
||||
|
||||
## 后台铃铛、抽屉和通知中心
|
||||
|
||||
铃铛点击后使用 `GET /api/admin/notifications?page=1&page_size=10` 加载最近 10 条。抽屉提供“全部、审批、临期、同步、系统”入口,对应 `category` 为空或 `approval/expiry/sync/system`。
|
||||
|
||||
完整通知中心使用服务端参数:
|
||||
|
||||
- `category`:`approval/expiry/sync/system`
|
||||
- `type`:稳定通知类型
|
||||
- `severity`:`info/warning/error/critical`
|
||||
- `is_read`:已读状态
|
||||
- `page/page_size`:服务端分页,默认 20、最大 50
|
||||
|
||||
“全部已读”调用 `PUT /api/admin/notifications/read-all`。当前分类为空时提交空对象;在分类视图中提交对应 `category`,成功后使用 `updated_count` 更新提示并重新拉取列表、汇总和未读数。
|
||||
|
||||
加载时保留已有内容并展示局部加载状态;首次空结果展示空态;请求失败展示重试入口,不把上一页数据伪装成新筛选结果。
|
||||
|
||||
## 点击、已读和受控目标
|
||||
|
||||
点击顺序固定为:
|
||||
|
||||
1. 立即进入已读视觉状态。
|
||||
2. 调用 `PUT /api/admin/notifications/:id/read`。
|
||||
3. 调用 `GET /api/admin/notifications/:id/target`。
|
||||
4. 只有 `available=true` 且 `target_type` 在前端白名单内时,使用结构化 `target_id/target_key` 构造站内路由。
|
||||
|
||||
已读请求失败时以下一次服务端刷新为准;目标解析失败或不可用不恢复未读。响应不包含 URL,前端禁止把 `target_id`、`target_key` 当作路径或完整地址直接跳转。
|
||||
|
||||
目标白名单:
|
||||
|
||||
| `target_type` | 结构化标识 | 页面语义 |
|
||||
|---|---|---|
|
||||
| `refund_detail` | `target_id` | 退款详情 |
|
||||
| `agent_recharge_detail` | `target_id` | 代理充值详情 |
|
||||
| `wecom_approval_detail` | `target_id` | 企微审批详情 |
|
||||
| `iot_card_detail` | `target_id` | 物联网卡详情 |
|
||||
| `device_detail` | `target_id` | 设备详情 |
|
||||
| `expiring_asset_list` | `target_id` | 指定店铺的临期资产列表 |
|
||||
| `shop_fund_summary` | `target_id` | 店铺资金概况 |
|
||||
| `integration_log` | `target_key` | 外部集成记录 |
|
||||
| `system_config` | `target_key` | 受控系统配置 |
|
||||
|
||||
未知类型、空 `target_type` 或 `available=false` 只展示正文,不跳转、不回退到自由 URL。
|
||||
|
||||
示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"target_type": "shop_fund_summary",
|
||||
"target_id": 42,
|
||||
"available": true
|
||||
},
|
||||
"timestamp": "2026-07-24T11:00:00+08:00"
|
||||
}
|
||||
```
|
||||
|
||||
## C 端简化通知中心
|
||||
|
||||
C 端只调用:
|
||||
|
||||
- `GET /api/c/v1/notifications/unread-count`
|
||||
- `GET /api/c/v1/notifications?page=&page_size=`
|
||||
- `PUT /api/c/v1/notifications/read-all`
|
||||
- `PUT /api/c/v1/notifications/:id/read`
|
||||
|
||||
C 端不展示后台筛选、分类汇总、同步或系统运维消息。列表只呈现当前个人客户可见的已开放审批、套餐、订单和资产业务通知。无通知时展示空态;网络失败保留上一成功结果并允许重试。
|
||||
|
||||
## 联调验收矩阵
|
||||
|
||||
- 后台账号只能读取和修改自己的通知,构造其他通知 ID 不泄露事实。
|
||||
- 个人客户之间完全隔离,C 端不能通过 ID 已读其他客户通知。
|
||||
- 过期通知不进入列表、未读数、分类汇总或受控目标解析。
|
||||
- 静态 `/read-all`、`/unread-count`、`/unread-summary` 不被 `/:id` 路由吞掉。
|
||||
- 筛选、翻页、全部已读后,列表、汇总和徽标最终一致。
|
||||
- 权限变化、目标删除和未知引用均显示正文但不跳转。
|
||||
- 0、1、99、100 四个徽标边界无布局抖动。
|
||||
- 页面隐藏时无轮询,恢复后立即刷新;失败期间未读数不闪回零。
|
||||
81
docs/tech-inapp-notifications/功能总结.md
Normal file
81
docs/tech-inapp-notifications/功能总结.md
Normal file
@@ -0,0 +1,81 @@
|
||||
# 公共站内通知功能总结
|
||||
|
||||
> 当前状态:任务 2.1~2.4 后台、个人客户通知闭环和后台动态接收人已完成代码交付;受控跳转、保留清理和完整前端契约仍按后续任务实施。
|
||||
|
||||
## 本次交付范围
|
||||
|
||||
本阶段交付明确后台账号的通知中心纵向闭环:公共 Outbox 事件经现有 `outbox:deliver` Worker 消费后,为指定且仍启用的后台账号幂等写入一条纯文本通知。当前登录账号只能查询自己的未读数、固定分类汇总和筛选分页列表,并可把自己的一条、指定类别或全部通知幂等标记为已读。
|
||||
|
||||
本阶段不实现目标跳转解析、前端组件、WebSocket 或具体业务触发规则,也不复制公共 Outbox 与 Relay。
|
||||
|
||||
## 事件与幂等契约
|
||||
|
||||
- 稳定事件类型:`notification.admin.direct.requested`。
|
||||
- 个人客户稳定事件类型:`notification.personal_customer.direct.requested`。
|
||||
- 后台动态接收人事件类型:`notification.admin.dynamic.requested`。
|
||||
- 载荷版本:`1`。
|
||||
- 结构化载荷包含后台接收账号 ID、注册通知类型、模板数据、可选受控资源引用和过期时间;调用公共队列时保持 struct/map 载荷,不传预序列化 `[]byte`。
|
||||
- 当前内置通知类型为 `system.notice`,标题和正文均由代码内固定模板生成,不接受业务载荷传入任意正文;未知字段、HTML、任意 HTTP URL、超长文本和明显敏感内容会被拒绝。
|
||||
- 个人客户首个受控类型为 `package.expiring`,使用固定纯文本模板,只允许 `package` 或 `asset` 资源引用;`system.notice` 只向后台账号开放。
|
||||
- 接收账号必须启用且未软删除;不可用账号记录安全日志并跳过,不把通知写入失败反向传播到原业务事务。
|
||||
- `event_id + recipient_kind + recipient_id` 唯一约束与 `ON CONFLICT DO NOTHING` 共同保证重复或并发消费最多生成一条通知。
|
||||
|
||||
后台动态事件使用 `target_kind + target_id` 指定受控目标,当前支持:
|
||||
|
||||
- `account`:业务事件携带的稳定真实申请人账号 ID,消费时复核账号仍启用且未删除。
|
||||
- `platform_role`:批量解析当前启用的平台角色、有效账号角色关系,以及仍启用未删除的超管/平台账号。
|
||||
- `shop`:复用 UR#96 接缝,只解析目标店铺当前启用主账号和当前仍可用业务员,不读取父级、祖先、创建人或代理数据权限。
|
||||
|
||||
解析结果按稳定账号 ID 去重和排序。同一事件对每个接收人独立幂等写入;暂无可用接收人记录 `resolution=no_recipient` 并正常结束,数据库故障继续返回 Worker 错误。已经生成的通知保持原接收人事实,关系后续变化不会转移历史通知。
|
||||
|
||||
## 数据与查询
|
||||
|
||||
迁移 `000168_create_notification` 新建 `tb_notification`,不使用外键。表内固化通知类别、类型、级别、纯文本标题正文、受控引用、首次已读时间、过期时间和创建时间,并通过 CHECK 约束保护接收人、引用与已读状态一致性。
|
||||
|
||||
后台查询始终绑定当前认证账号和 `recipient_kind=account`:
|
||||
|
||||
- `GET /api/admin/notifications/unread-count`:返回准确 `count` 与徽标 `display_count`,超过 99 显示 `99+`。
|
||||
- `GET /api/admin/notifications/unread-summary`:使用单条 PostgreSQL 条件聚合返回 `total/approval/expiry/sync/system` 五个固定未读计数。
|
||||
- `GET /api/admin/notifications`:只返回未过期通知,支持类别、类型、级别和已读状态 AND 组合筛选,按 `created_at DESC, id DESC` 排序,默认每页 20、最大 50,页码最大 10000。
|
||||
- `PUT /api/admin/notifications/read-all`:类别为空时更新当前账号全部未过期未读通知,指定有效类别时只更新该类别并返回实际更新数;重复调用返回零更新且保持成功。
|
||||
- `PUT /api/admin/notifications/:id/read`:仅首次更新当前账号自己的未过期未读通知;不存在、属于别人或已经已读均幂等成功,不泄露通知是否存在,也不覆盖首次 `read_at`。
|
||||
- `GET /api/admin/notifications/:id/target`:先固定当前账号查询通知,再返回 `target_type/target_id/target_key/available` 结构化白名单目标;不返回 URL,别人通知、不存在通知和过期通知统一返回不可用。
|
||||
|
||||
目标注册表覆盖退款、代理充值、企微审批、物联网卡、设备、临期资产列表、店铺资金概况、外部集成和系统配置。退款、卡、设备、店铺、外部集成和系统配置会复核当前数据权限与资源存在性;尚未交付下游业务表的代理充值和企微审批目标先失败关闭为 `available=false`。`card_sync` 统一映射到外部集成目标,不指向不存在的同步执行页面。未知引用仅展示正文。
|
||||
|
||||
个人客户接口固定绑定认证上下文中的 `customer_id`,请求 DTO 不包含接收人字段:
|
||||
|
||||
- `GET /api/c/v1/notifications/unread-count`:返回当前客户可见业务通知的准确未读数和 `display_count`。
|
||||
- `GET /api/c/v1/notifications`:提供默认 20、最大 50 的简化分页并固定倒序,不暴露后台筛选或分类汇总参数。
|
||||
- `PUT /api/c/v1/notifications/read-all`:幂等更新当前客户可见的全部未过期未读业务通知并返回实际更新数。
|
||||
- `PUT /api/c/v1/notifications/:id/read`:幂等更新当前客户的一条可见通知,跨客户 ID 与不存在 ID 使用相同成功语义。
|
||||
|
||||
C 端查询和更新同时限制 `recipient_kind=personal_customer`、当前客户、未过期、业务类别及开放类型白名单;`sync/system` 和未对 C 端开放的类型不会进入结果,也不能被 C 端已读接口修改。
|
||||
|
||||
## 审计与安全边界
|
||||
|
||||
- `tb_notification` 是通知投递和已读状态的权威事实;通知不能替代资金、审批、套餐等业务 Domain Ledger。
|
||||
- 本阶段无外部系统调用,因此不写 Integration Log;可靠输入继续使用公共 Outbox。
|
||||
- 按测试环境 Change 的临时决定,本阶段不接入 Audit Event Writer。普通列表、未读数和已读进入 Access Log;生产发布前由任务 6.5 重新评审通知失败与系统告警治理。
|
||||
- 用户接口不接受 `recipient_id`,后台账号权限也不能查看或修改其他接收人的通知。
|
||||
|
||||
## 验证与发布
|
||||
|
||||
- 已执行 `gofmt`、`git diff --check`,并通过 `go build ./cmd/api ./cmd/worker ./cmd/gendocs`。
|
||||
- 按本次测试环境里程碑豁免,未新增或运行 `_test.go`,也未执行真实 PostgreSQL、Redis/Asynq 或 HTTP 集成验证;这些证据统一延期到任务 6.1 和 6.3,当前状态不得表述为生产验收通过。
|
||||
- `000168` 的 down 迁移只允许空表回滚;一旦产生通知事实,必须停止生产者并前向修复,不允许降级删表清除事实。
|
||||
|
||||
通知展示与保留策略由代码统一执行:审批结果不自动过期、保留 365 天;套餐临期必须携带业务到期时间并保留 180 天;同步异常默认最多展示 30 天、保留 180 天;系统告警默认展示 30 天且最长 365 天、保留 365 天。
|
||||
|
||||
Worker 每天 02:15 调度 `notification:cleanup`,按类别、创建时间和通知主键,每批最多 500 条、每类每次最多 20 批执行 PostgreSQL CTE 删除。任务可中断重跑,只删除 `tb_notification`,不级联业务资源、Outbox、Integration Log 或审计事实。
|
||||
|
||||
无接收人以 `resolution=no_recipient` 正常结束。接收人解析、模板和展示策略失败只记录事件 ID、通知类型、失败类别及安全目标标识,不记录模板数据、回调、Token、Secret 或 URL;瞬时错误由 Asynq 有限重试,残缺正文不会入库。统一 Audit Event 管理性写入按本 Change 冻结到任务 6.5。
|
||||
|
||||
## 后续任务
|
||||
|
||||
- 2.7~2.8:前端契约、OpenAPI/中文文档最终收口和测试环境发布检查。
|
||||
|
||||
后台与 C 端的完整交互、目标白名单、示例和验收矩阵见 [前端联调与验收契约](前端联调与验收契约.md)。
|
||||
|
||||
测试环境发布顺序、运行门禁、恢复策略和下游事件接入方式见 [发布与下游接入清单](发布与下游接入清单.md)。
|
||||
- 2.7~2.8:前端契约、OpenAPI/文档最终生成、Worker/模板与测试环境发布检查。
|
||||
47
docs/tech-inapp-notifications/发布与下游接入清单.md
Normal file
47
docs/tech-inapp-notifications/发布与下游接入清单.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# 公共站内通知发布与下游接入清单
|
||||
|
||||
## 下游生产者契约
|
||||
|
||||
业务事务必须先生成稳定 `event_id`,并与业务事实在同一事务写入公共 Outbox。载荷版本固定为 `1`,调用 `EnqueueTask` 时传 struct 或 map,禁止传预序列化 `[]byte`。
|
||||
|
||||
明确后台账号事件使用 `notification.admin.direct.requested`,载荷为 `AdminDirectPayload`;个人客户事件使用 `notification.personal_customer.direct.requested`;后台动态事件使用 `notification.admin.dynamic.requested`,目标只允许:
|
||||
|
||||
- `account + 稳定真实申请人账号 ID`
|
||||
- `platform_role + 平台角色 ID`
|
||||
- `shop + 目标店铺 ID`
|
||||
|
||||
动态店铺目标只产生当前启用主账号和当前可用业务员,不沿层级扩散。无接收人是正常终态。同一事件必须复用原 `event_id`,不得在重试时生成新 ID。
|
||||
|
||||
新增业务通知类型必须在代码注册表中明确:稳定类型、类别、级别、固定纯文本模板、允许模板字段、接收人类型和允许 `ref_type`。不得透传任意标题、正文、HTML、URL、Token、Secret、回调原文或长期附件地址。
|
||||
|
||||
## 目标与期限
|
||||
|
||||
通知只保存受控 `ref_type/ref_id/ref_key`。后台目标接口只返回前端白名单 `target_type` 和结构化 ID/Key,并再次复核当前权限;拥有通知不授予资源权限。
|
||||
|
||||
- 审批结果不自动过期,数据保留 365 天。
|
||||
- 套餐临期必须携带业务到期时间,数据保留 180 天。
|
||||
- 同步异常默认最多展示 30 天,数据保留 180 天。
|
||||
- 系统告警默认展示 30 天、最长 365 天,数据保留 365 天。
|
||||
|
||||
## 测试环境发布顺序
|
||||
|
||||
1. 进入维护窗口并确认下游生产者尚未启用。
|
||||
2. 执行迁移 `000168_create_notification` 和 `000169_add_shop_business_owner`,核对无外键表、唯一索引、查询索引及店铺业务员普通索引。
|
||||
3. 发布 Worker,确认三个通知 Outbox 事件消费者、公共 `outbox:deliver` Handler 和每天 02:15 的 `notification:cleanup` 已注册。
|
||||
4. 发布 API,核对后台、C 端路由和 OpenAPI;静态 `/read-all`、`/unread-count`、`/unread-summary` 必须可达。
|
||||
5. 发布匹配的前端版本并按前端联调契约验收。
|
||||
6. 最后启用 UR#33、UR#97、审批结果等下游生产者,避免消费者未就绪时制造不可见积压。
|
||||
|
||||
## 运行门禁与恢复
|
||||
|
||||
- 监控公共 Outbox pending/delivering/final failed、Asynq 重试与失败、通知 `no_recipient`、模板/解析失败和每日清理删除数。
|
||||
- 出现永久模板错误、持续数据库错误、Outbox 积压或清理长期失败时,先停止对应下游生产者,不删除业务事实和已写通知。
|
||||
- 已入队但未完成的事件继续使用原 `event_id` 恢复;不得要求用户重复提交或给同一业务生成新事件。
|
||||
- 通知表已有事实后禁止执行 down 删除;应用回滚保留 `tb_notification` 和店铺业务员字段,修复后前向恢复。
|
||||
- 不清理 Audit Event、Integration Log、Domain Ledger 或 Outbox,不使用通知列表替代业务审计。
|
||||
|
||||
## 当前验证状态
|
||||
|
||||
已完成路由、RouteSpec、集中式文档 Handler、Worker 消费者、模板、清理 Handler/调度和组合根的静态核对;已生成 `docs/admin-openapi.yaml`,并通过 `gofmt`、`git diff --check`、`go build ./...` 和 OpenSpec 校验。
|
||||
|
||||
按测试环境 Change 豁免,尚未执行真实 PostgreSQL 迁移、Redis/Relay/Asynq 端到端、真实认证 HTTP、并发重复消费或浏览器人工验收;这些门禁保持在任务 6.1、6.3、6.6,不能据此声明生产验收通过。
|
||||
Reference in New Issue
Block a user