liuyun 35028a69d2
Some checks are pending
CI / backend (push) Waiting to run
CI / frontend (push) Waiting to run
docs(spec): 归档 in-app-notification + 同步通知主 spec
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-11 22:26:15 +08:00

2.3 KiB
Raw Blame History

Context

已有实体/资源/组织等业务域均为「BaseCrudService + 软删 + 租户隔离 + 资源表授权」的同构结构。通知无长连接需求,用轮询即可。动机见 proposal.md - Why。

Goals / Non-Goals

Goals:

  • 管理员发布公告,本租户用户经顶栏铃铛查看未读、标记已读;未读数轮询刷新。

Non-Goals:

  • 不做定向通知(按用户/角色投递),本期仅广播式公告。
  • 不接邮件/短信,不做 WebSocket 实时推送。

Decisions

D1 — 实体:Notice + NoticeRead

  • Notice(sys_notice):id、tenant_code、title(128)、content(text)、status(enabled/disabled)、created_by、created_at/updated_at/deleted_at;软删,无业务唯一键(无需改名回收)。
  • NoticeRead(sys_notice_read):tenant_code、notice_id、user_id、read_at,唯一 (tenant_code, notice_id, user_id);不软删(追踪实体)。

D2 — 广播式 + enabled 才可见

通知对本租户所有用户可见,status=enabled 才计入未读/列表;disabled 仅管理员列表可见。未读数 = enabled 通知数 − 当前用户已读数(两条 count 相减)。

D3 — 用户侧接口仅登录态,管理侧挂权限

  • 用户侧 GET /notices/mine、GET /notices/unread-count、POST /notices/:id/read、POST /notices/read-all 不加 @RequirePermissions(仅需登录)。
  • 管理侧 GET/POST/PATCH/DELETE /notices 挂 notice:list/create/update/delete + 写操作 @AuditLog('notice', ...)。

D4 — 轮询未读数

前端 notice store 启动 30s 定时器轮询 unread-count(登录后启动、登出/401 停止),路由切换与标记已读后即时刷新;铃铛用 el-badge 显示未读数、el-popover 展示最近通知列表。

Risks / Trade-offs

  • [轮询有延迟] → 30s 内新通知才被看到;站内公告场景可接受,标记已读后即时刷新兜底。
  • [未读数计算随通知量增长] → 用 count 聚合(有 (tenant_code, notice_id, user_id) 索引),不拉全量。

Migration Plan

  1. Prisma migration:新建 sys_notice、sys_notice_read(无回填)。
  2. 代码上线 + pnpm sync:resources 补齐 notice 菜单/接口。
  3. 回滚:删除两张表 + 回滚代码即可。

Open Questions

无。