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

46 lines
2.3 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
已有实体/资源/组织等业务域均为「`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
无。