Merge worktree branch merge-15-modules-to-main-5ug5xJ

This commit is contained in:
SpecialX
2026-07-10 15:28:20 +08:00
parent 60d7173545
commit df62ffc176
51 changed files with 11559 additions and 1908 deletions

View File

@@ -1,47 +1,95 @@
# msg 对接契约
> 负责人ai10
> 关联:[matrix.md](./matrix.md)、[msg.proto](../../../packages/shared-proto/proto/msg.proto)、[events.proto](../../../packages/shared-proto/proto/events.proto)
> 关联:[matrix.md](../matrix.md)、[msg.proto](../../../packages/shared-proto/proto/msg.proto)、[events.proto](../../../packages/shared-proto/proto/events.proto)、[02-architecture-design.md](../../../services/msg/docs/02-architecture-design.md)
> 仲裁依赖ISSUE-008topic 命名、ISSUE-009RPC 数量、ISSUE-013events.proto 补齐)
---
## §1 我提供什么(对外接口)
### 1.1 gRPC 接口(如有)
### 1.1 gRPC 接口
| Service | RPC | 请求 | 响应 | 端口 |
| ----------------------------- | ---------------------- | ----------------------------- | --------------------------- | ----- |
| NotificationService | SendNotification | SendNotificationRequest | Notification | 50056 |
| NotificationService | ListNotifications | ListNotificationsRequest | ListNotificationsResponse | 50056 |
| NotificationService | MarkAsRead | MarkAsReadRequest | MarkAsReadResponse | 50056 |
| NotificationService | SearchNotifications | SearchNotificationsRequest | SearchNotificationsResponse | 50056 |
| NotificationService | RecallNotification | RecallNotificationRequest | RecallNotificationResponse | 50056 |
| NotificationPreferenceService | GetPreference | GetPreferenceRequest | NotificationPreference | 50056 |
| NotificationPreferenceService | UpdatePreference | UpdatePreferenceRequest | NotificationPreference | 50056 |
| NotificationPreferenceService | GetPreferenceByChannel | GetPreferenceByChannelRequest | ChannelPreference | 50056 |
| NotificationPreferenceService | ListPreferences | ListPreferencesRequest | ListPreferencesResponse | 50056 |
| NotificationTemplateService | CreateTemplate | CreateTemplateRequest | NotificationTemplate | 50056 |
| NotificationTemplateService | GetTemplate | GetTemplateRequest | NotificationTemplate | 50056 |
| NotificationTemplateService | ListTemplates | ListTemplatesRequest | ListTemplatesResponse | 50056 |
| NotificationTemplateService | RenderTemplate | RenderTemplateRequest | RenderedTemplate | 50056 |
> 端口50056 | proto 包名:`next_edu_cloud.msg.v1` | proto 文件:[msg.proto](../../../packages/shared-proto/proto/msg.proto)
>
> **⚠️ ISSUE-009 待仲裁**ai-allocation.md 规定 13 RPC02-architecture-design.md 设计 17 RPC。下表列出设计文档完整 17 RPC标注基线 13 RPC✅基线 / 扩展待仲裁。coord 裁决后裁剪或放宽。
### 1.2 HTTP 端点(如有
#### NotificationService9 RPC5 基线 + 4 扩展
无对外 HTTP 端点,仅 gRPC。
| RPC | 请求 | 响应 | 基线/扩展 | 说明 |
| --------------------- | ---------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------- | --------- | ----------------- |
| SendNotification | `SendNotificationRequest{user_id, type, title, content, channel, metadata?, related_entity_type?, related_entity_id?}` | `Notification` | ✅基线 | 单条发送 |
| BatchSendNotification | `BatchSendNotificationRequest{items[], group_id?}` | `BatchSendNotificationResponse{ids[], failed[]}` | ➕扩展 | 批量发送 |
| ListNotifications | `ListNotificationsRequest{user_id, only_unread?, type?, page?, page_size?}` | `ListNotificationsResponse{notifications[], total}` | ✅基线 | 列表(补分页) |
| GetUnreadCount | `GetUnreadCountRequest{user_id}` | `GetUnreadCountResponse{count}` | ➕扩展 | 未读数Redis |
| MarkAsRead | `MarkAsReadRequest{id, user_id}` | `Empty` | ✅基线 | 标记已读 |
| BatchMarkAsRead | `BatchMarkAsReadRequest{ids[], user_id}` | `Empty` | ➕扩展 | 批量已读 |
| MarkAllAsRead | `MarkAllAsReadRequest{user_id, before?}` | `Empty` | ➕扩展 | 全部已读 |
| SearchNotifications | `SearchNotificationsRequest{user_id, query, type?, page?, page_size?}` | `SearchNotificationsResponse{notifications[], total}` | ✅基线 | ES 全文检索 |
| RecallNotification | `RecallNotificationRequest{group_id, reason?}` | `RecallNotificationResponse{recalled_count}` | ✅基线 | 撤回广播 |
### 1.3 GraphQL schema如 BFF
#### NotificationPreferenceService2 RPC 基线
不适用。
| RPC | 请求 | 响应 | 基线/扩展 | 说明 |
| ------------------ | -------------------------------------------------- | --------------------------------------- | --------- | -------- |
| GetPreferences | `GetPreferencesRequest{user_id}` | `GetPreferencesResponse{preferences[]}` | ✅基线 | 偏好查询 |
| UpdatePreferences | `UpdatePreferencesRequest{user_id, preferences[]}` | `Empty` | ✅基线 | 偏好更新 |
### 1.4 Kafka 事件发布(如有
> **注**02-architecture-design.md 设计 2 RPC原 contract 版本的 GetPreferenceByChannel / ListPreferences 暂移除(待 ISSUE-009 仲裁是否保留
| Topic | Event | 消费方 |
| --------------------------- | ------------------------------------------------------ | ----------------------- |
| edu.msg.notification.events | NotificationEventaction: sent/read/recalled/failed | push-gateway / data-ana |
#### NotificationTemplateService4 RPC4 基线 + 2 扩展)
| RPC | 请求 | 响应 | 基线/扩展 | 说明 |
| -------------- | -------------------------------------------------------------------------------------------------- | -------------------------------------- | --------- | ---------- |
| CreateTemplate | `CreateTemplateRequest{code, type, title_template, content_template, default_channels, variables}` | `NotificationTemplate` | ✅基线 | 创建模板 |
| GetTemplate | `GetTemplateRequest{id}` | `NotificationTemplate` | ✅基线 | 查询模板 |
| ListTemplates | `ListTemplatesRequest{type?, status?}` | `ListTemplatesResponse{templates[]}` | ✅基线 | 模板列表 |
| UpdateTemplate | `UpdateTemplateRequest{id, ...}` | `NotificationTemplate` | ➕扩展 | 更新模板 |
| DeleteTemplate | `DeleteTemplateRequest{id}` | `Empty` | ➕扩展 | 删除模板 |
| RenderTemplate | `RenderTemplateRequest{code, variables, locale?}` | `RenderedNotification{title, content}` | ✅基线 | 渲染模板 |
> **汇总**:基线 13 RPC9 基线表中 ✅ × 5 + 偏好 ✅ × 2 + 模板 ✅ × 4 = 11... 实际基线 = NotificationService 5 + Preference 2 + Template 4 = 11。**注**:若严格按 ai-allocation 13 RPC基线应为 13此处设计文档 11 基线 + 6 扩展 = 17与 13 预算差 4。待 ISSUE-009 仲裁后最终确认。
### 1.2 HTTP 端点
> msg 对外以 gRPC 为主BFF 通过 gRPC 调用)。以下 REST 端点为过渡期/管理端使用,最终将逐步迁移至 gRPC。
| Method | Path | 权限 | 说明 |
| ------ | ---------------------------------------- | ----------------------- | -------------------- |
| POST | /notifications/send | MSG_NOTIFICATION_SEND | 单条发送 |
| POST | /notifications/batch | MSG_NOTIFICATION_SEND | 批量发送 |
| GET | /notifications/user/:userId | MSG_NOTIFICATION_READ | 用户通知列表 |
| GET | /notifications/user/:userId/unread-count | MSG_NOTIFICATION_READ | 未读数Redis |
| PUT | /notifications/:id/read | MSG_NOTIFICATION_READ | 标记已读 |
| GET | /notifications/search | MSG_NOTIFICATION_READ | ES 全文检索 |
| GET | /preferences/user/:userId | MSG_NOTIFICATION_READ | 用户偏好 |
| PUT | /preferences/user/:userId | MSG_NOTIFICATION_MANAGE | 更新偏好 |
| GET | /templates | MSG_NOTIFICATION_MANAGE | 模板列表 |
| POST | /templates | MSG_NOTIFICATION_MANAGE | 创建模板 |
### 1.3 GraphQL schema
不适用msg 是业务服务,非 BFF
### 1.4 Kafka 事件发布
> **⚠️ ISSUE-008 待仲裁**topic 命名存在三套约定per-event / aggregate / aggregate+action。本表采用 02-architecture-design.md §5.2 约定per-event topic与 004 §7.2 一致。coord 裁决后统一。
| Topic | Event Type | 触发时机 | 消费方 | Outbox |
| ---------------------------- | --------------------------- | -------------- | ------------------------------ | ------ |
| `edu.notification.sent` | NotificationSent | 通知发送成功 | push-gateway / data-ana | ✅ |
| `edu.notification.read` | NotificationRead | 通知被标记已读 | data-ana | ✅ |
| `edu.notification.recalled` | NotificationRecalled | 通知被撤回 | push-gateway删除已推送消息 | ✅ |
| `edu.notification.failed` | NotificationFailed | 通知投递失败 | data-ana监控告警 | ✅ |
> **Producer 幂等**`idempotent=true` + `transactionalId=msg-producer`
> **DLQ**:消费失败超 3 次投递 `edu.notification.dlq`(见 02-architecture-design.md §5待补充
### 1.5 错误码前缀
`MSG_`如 MSG_TEMPLATE_NOT_FOUND、MSG_CHANNEL_DISABLED、MSG_RATE_LIMITED
`MSG_`完整清单见 02-architecture-design.md §6.2
- MSG_VALIDATION_ERROR / MSG_NOT_FOUND / MSG_PERMISSION_DENIED / MSG_CONFLICT / MSG_BUSINESS_ERROR / MSG_DATABASE_ERROR / MSG_INTERNAL_ERROR当前已实现 7 个)
- MSG_ES_UNAVAILABLE / MSG_REDIS_UNAVAILABLE / MSG_PUSH_GATEWAY_UNAVAILABLE / MSG_TEMPLATE_NOT_FOUND / MSG_TEMPLATE_RENDER_ERROR / MSG_RATE_LIMIT_EXCEEDEDP5 新增 6 个)
---
@@ -49,22 +97,39 @@
### 2.1 gRPC 调用(同步)
无主动 gRPC 调用上游。msg 通过 Kafka 事件被动接收业务事件后触发通知。
| 调用方 | 对方服务 | 协议 | RPC | 用途 | 状态 |
| ------------ | ------------ | ---- | -------------------- | ---------------------- | ------------------ |
| msg → push | push-gateway | gRPC | PushService.Push | 实时推送通知到在线用户 | P5 待实现D5 |
> 当前降级fetch POST `/internal/push`(见 [notifications.service.ts](../../../services/msg/src/notifications/notifications.service.ts) L179
> **调用方向澄清**ISSUE-005004 §4.1 表述"push-gateway → Msg"有歧义,实际方向是 msg → push-gateway
### 2.2 Kafka 事件订阅(异步)
| Topic | Event | 发布方 | mock 策略 |
| --------------------------- | --------------------------------------------------------- | --------------- | ------------------------------------------------------- |
| edu.iam.user.events | UserEvent | iam (ai06) | iam 就绪前使用本地用户偏好默认值 |
| edu.exam.events | ExamEventaction: created/updated/deleted | core-edu (ai08) | core-edu 就绪前不订阅,使用本地 stub 事件触发 mock 通知 |
| edu.homework.events | HomeworkEventaction: assigned/submitted/graded | core-edu (ai08) | 同上 |
| edu.grade.events | GradeEventaction: recorded/updated | core-edu (ai08) | 同上 |
| edu.class.events | ClassEventaction: transferred | core-edu (ai08) | 同上 |
| edu.data_ana.mastery.events | MasteryEventaction: mastery.updated/warning.triggered | data-ana (ai11) | data-ana 就绪前不订阅,预警通知使用本地 stub |
> **⚠️ ISSUE-008 待仲裁**topic 命名采用 02-architecture-design.md §5.1 约定per-event topic与 004 §7.2 一致)。原 contract 版本的 aggregate topicedu.iam.user.events / edu.exam.events待 coord 裁决后统一。
### 2.3 HTTP 调用(如有)
| Topic | 来源服务 | 处理逻辑 | 触发通知 | 幂等键 | 阻塞性 |
| ----------------------------------- | ------------- | ---------------------------- | -------------------- | ---------- | ------ |
| `edu.identity.user.created` | iam (ai06) | 发送欢迎通知 | welcome 通知 | `event_id` | 🟡 |
| `edu.identity.user.updated` | iam (ai06) | 用户信息变更,清理缓存 | — | `event_id` | 🟡 |
| `edu.identity.user.deleted` | iam (ai06) | 用户删除,归档通知 | — | `event_id` | 🟡 |
| `edu.identity.user.role_changed` | iam (ai06) | 角色变更通知 | system 通知 | `event_id` | 🟡 |
| `edu.identity.role.created` | iam (ai06) | 角色新增(管理通知) | system 通知 | `event_id` | 🟡 |
| `edu.identity.role.updated` | iam (ai06) | 角色权限变更通知 | system 通知 | `event_id` | 🟡 |
| `edu.teaching.exam.published` | core-edu (ai08) | 推送考试通知给班级学生 | exam 通知fan-out | `event_id` | 🟡 |
| `edu.teaching.assignment.submitted` | core-edu (ai08) | 通知教师有学生提交作业 | homework 通知 | `event_id` | 🟡 |
| `edu.teaching.assignment.graded` | core-edu (ai08) | 通知学生作业已批改 | grade 通知 | `event_id` | 🟡 |
| `edu.teaching.grade.recorded` | core-edu (ai08) | 通知学生成绩已录入 | grade 通知 | `event_id` | 🟡 |
| `edu.teaching.attendance.recorded` | core-edu (ai08) | 出勤异常通知家长 | attendance 通知 | `event_id` | 🟡 |
| `edu.insight.mastery.updated` | data-ana (ai11) | 学情掌握度下降,触发预警 | mastery_alert 通知 | `event_id` | 🟡 |
> **⚠️ ISSUE-013 阻塞**events.proto 当前仅有 ClassEvent / ExamEvent / HomeworkEvent / GradeEvent缺 UserEvent / RoleEvent / MasteryEvent / NotificationEvent 4 类 message。msg 消费需 coord 补齐 protoD1。补齐前用通用 JSON payload 解析
>
> **幂等去重**三层防线L1 Redis SETNX `msg:processed:{event_id}` TTL 7d / L2 msg_idempotency 表 / L3 notifications.event_id UNIQUE INDEX
### 2.3 HTTP 调用
无主动 HTTP 调用上游push-gateway 走 gRPCHTTP 仅为降级)。
---
@@ -72,39 +137,60 @@
### 3.1 我依赖的上游就绪标志
- [ ] iam gRPC 50052 启用ai06—— 用于用户通知偏好查询(可选)
- [ ] core-edu gRPC 50053 启用ai08—— 业务事件来源
- [ ] edu.exam.events / edu.homework.events / edu.grade.events / edu.class.events topic 有事件发布ai08
- [ ] data-ana gRPC 50055 启用ai11—— 预警事件来源
- [ ] edu.data_ana.mastery.events topic 有事件发布ai11
| 标志 | 提供方 | 阻塞性 | 说明 |
| ---- | ------ | ------ | ---- |
| events.proto 补 UserEvent/RoleEvent/MasteryEvent/NotificationEvent | coord | 🔴 阻塞 T9 | D1ISSUE-013 |
| events.proto GradeEvent 补 class_id全部补 student_ids[] | coord | 🔴 阻塞 fan-out | D2ISSUE-006 |
| msg.proto 补 5 扩展 RPC + Preference/Template Service | coord | 🔴 阻塞 gRPC | D3-D4ISSUE-009 |
| push-gateway gRPC PushService.Push | ai02 | 🔴 阻塞 T8 | D5 |
| iam 发布 6 类 user/role 事件 | ai06 | 🟡 集成验证 | D6开发期用 mock |
| core-edu 发布 5 类教学事件 | ai08 | 🟡 集成验证 | D7 |
| data-ana 发布 mastery 事件 | ai11 | 🟡 集成验证 | D8 |
| Redis 集群可用 | infra | 🟡 降级 DB | D9 |
### 3.2 我的就绪标志(供下游消费)
- [ ] msg gRPC 50056 启用HealthService.Check 返回 SERVING
- [ ] NotificationService 5 RPC 可调用
- [ ] NotificationPreferenceService 4 RPC 可调用
- [ ] NotificationTemplateService 4 RPC 可调用(含 RenderTemplate 模板渲染
- [ ] edu.msg.notification.events topic 可发布(供 push-gateway 推送
- [ ] NotificationService RPC 可调用(基线 5 + 扩展 4待 ISSUE-009 仲裁)
- [ ] NotificationPreferenceService 2 RPC 可调用
- [ ] NotificationTemplateService RPC 可调用(基线 4 + 扩展 2含 RenderTemplate
- [ ] `edu.notification.sent/read/recalled/failed` topic 可发布(待 ISSUE-008 仲裁命名
- [ ] /readyz 返回 6 项依赖状态DB/ES/Redis/Kafka producer/Kafka consumer/PushGateway
- [ ] 测试覆盖率 ≥ 80%
---
## §4 Mock 策略
### 4.1 我提供的 mock
### 4.1 我提供的 mock(供下游)
在 msg 真实服务就绪前为下游teacher-bff / student-bff / parent-bff / push-gateway提供以下 mock
在 msg 真实服务就绪前为下游teacher-bff / student-bff / parent-bff / push-gateway提供 mock
- **gRPC mock**使用 grpc-mock 拦截 50056 端口
- NotificationService.ListNotifications 返回固定 10 条未读通知
- NotificationService.MarkAsRead 返回 success=true
- NotificationPreferenceService.GetPreference 返回默认偏好in_app+email 开启sms+push 关闭)
- NotificationTemplateService.RenderTemplate 返回固定 title+content
- **Kafka mock**msg 就绪前不发布真实 NotificationEventpush-gateway 使用本地 stub 推送
- **gRPC mock**grpc-mock 拦截 50056 端口
- ListNotifications 返回固定 10 条未读通知
- MarkAsRead 返回 success=true
- GetPreferences 返回默认偏好in_app + email 开启sms + push 关闭)
- RenderTemplate 返回固定 title + content
- **Kafka mock**msg 就绪前不发布真实通知事件push-gateway 使用本地 stub 推送
### 4.2 我消费的 mock
### 4.2 我消费的 mock(开发期间)
在真实上游就绪前msg 使用以下 mock
- 业务事件core-edu/data-ana 就绪前msg 内置定时器发布本地 stub 事件ExamEvent/HomeworkEvent触发 mock 通知流程
- 用户偏好iam 就绪前使用默认偏好(所有用户 in_app 开启)
- 模板渲染:内置 5 个常用模板exam.created / homework.assigned / grade.recorded / warning.triggered / system.notice
- **业务事件**core-edu / data-ana 就绪前msg 内置定时器发布本地 stub 事件ExamEvent / HomeworkEvent触发 mock 通知流程
- **用户偏好**iam 就绪前使用默认偏好(所有用户 in_app 开启)
- **模板渲染**:内置 5 个常用模板exam.published / homework.graded / grade.recorded / mastery.warning / system.notice
- **Push Gateway**ai02 就绪前用 fetch POST /internal/push 降级(当前实现保留)
---
## §5 待仲裁项汇总
| ISSUE | 主题 | 阻塞性 | 当前采用 |
| ----- | ---- | ------ | -------- |
| ISSUE-008 | Kafka topic 命名per-event vs aggregate | 🟡 | per-event02-architecture-design.md §5 + 004 §7.2 |
| ISSUE-009 | RPC 数量13 vs 17 | 🟡 | 1702-architecture-design.md §4.2),标注基线/扩展 |
| ISSUE-013 | events.proto 缺 4 类 message | 🔴 | 用 JSON payload 降级,待 coord 补齐 |
| ISSUE-006 | events.proto P9 字段描述 | 🟡 | 待 coord 修正 |
| ISSUE-010 | markAsRead 权限点 | 🟡 | 以 02-architecture-design.md §6.1 为准READ |
| ISSUE-011 | DB↔ES 降级方向 | 🟡 | 待 coord 仲裁(双向降级 vs 单向) |