feat: auto committed
This commit is contained in:
@@ -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-008(topic 命名)、ISSUE-009(RPC 数量)、ISSUE-013(events.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 RPC,02-architecture-design.md 设计 17 RPC。下表列出设计文档完整 17 RPC,标注基线 13 RPC(✅基线 / ➕扩展待仲裁)。coord 裁决后裁剪或放宽。
|
||||
|
||||
### 1.2 HTTP 端点(如有)
|
||||
#### NotificationService(9 RPC:5 基线 + 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)
|
||||
#### NotificationPreferenceService(2 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 | NotificationEvent(action: sent/read/recalled/failed) | push-gateway / data-ana |
|
||||
#### NotificationTemplateService(4 RPC:4 基线 + 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 RPC(9 基线表中 ✅ × 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_EXCEEDED(P5 新增 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-005):004 §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 | ExamEvent(action: created/updated/deleted) | core-edu (ai08) | core-edu 就绪前不订阅,使用本地 stub 事件触发 mock 通知 |
|
||||
| edu.homework.events | HomeworkEvent(action: assigned/submitted/graded) | core-edu (ai08) | 同上 |
|
||||
| edu.grade.events | GradeEvent(action: recorded/updated) | core-edu (ai08) | 同上 |
|
||||
| edu.class.events | ClassEvent(action: transferred) | core-edu (ai08) | 同上 |
|
||||
| edu.data_ana.mastery.events | MasteryEvent(action: 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 topic(edu.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 补齐 proto(D1)。补齐前用通用 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 走 gRPC,HTTP 仅为降级)。
|
||||
|
||||
---
|
||||
|
||||
@@ -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 | D1,ISSUE-013 |
|
||||
| events.proto GradeEvent 补 class_id;全部补 student_ids[] | coord | 🔴 阻塞 fan-out | D2,ISSUE-006 |
|
||||
| msg.proto 补 5 扩展 RPC + Preference/Template Service | coord | 🔴 阻塞 gRPC | D3-D4,ISSUE-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 就绪前不发布真实 NotificationEvent,push-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-event(02-architecture-design.md §5 + 004 §7.2) |
|
||||
| ISSUE-009 | RPC 数量(13 vs 17) | 🟡 | 17(02-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 单向) |
|
||||
|
||||
@@ -6,19 +6,189 @@
|
||||
|
||||
---
|
||||
|
||||
## 问题列表
|
||||
## §0 已有仲裁核查(ai10 复核)
|
||||
|
||||
<!--
|
||||
追加条目格式:
|
||||
> 本节核查 01-understanding.md / 02-architecture-design.md 中引用的已有仲裁,对照源码/proto 验证准确性。
|
||||
|
||||
### ISSUE-[编号]-[AI标识]:[标题]
|
||||
### ISSUE-001-ai10:M8 ZodError 已修复 — 核查通过
|
||||
|
||||
- **提请方**:aiXX
|
||||
- **日期**:YYYY-MM-DD
|
||||
- **类型**:契约不明确 / 工作量超批 / 前置依赖缺失 / 编号冲突 / 其他
|
||||
- **描述**:[详细描述问题]
|
||||
- **建议方案**:[AI 的建议]
|
||||
- **状态**:待 coord 仲裁 / 已裁决(见 coord.md §X)
|
||||
-->
|
||||
- **提请方**:ai10
|
||||
- **日期**:2026-07-10
|
||||
- **类型**:仲裁核查
|
||||
- **描述**:01-understanding.md M8 称"GlobalErrorFilter 已识别 ZodError 返回 400"。核查 [global-error.filter.ts](../../../services/msg/src/shared/errors/global-error.filter.ts) L32-42:`else if (exception instanceof ZodError) { statusCode = 400; ... }`,确实已处理,返回 `MSG_VALIDATION_ERROR` + 400。
|
||||
- **核查结论**:✅ 仲裁准确,M8 标记"已修复"无误
|
||||
- **状态**:已裁决(核查通过,无需处理)
|
||||
|
||||
(暂无问题)
|
||||
### ISSUE-002-ai10:M12 proto 包名规范 — 核查通过
|
||||
|
||||
- **提请方**:ai10
|
||||
- **日期**:2026-07-10
|
||||
- **类型**:仲裁核查
|
||||
- **描述**:01-understanding.md M12 称"实际 `next_edu_cloud.msg.v1` 符合规范"。核查 [msg.proto](../../../packages/shared-proto/proto/msg.proto) L3:`package next_edu_cloud.msg.v1;`,与 01-understanding.md 描述一致。
|
||||
- **核查结论**:✅ 仲裁准确,但存在规则冲突(见 ISSUE-007)
|
||||
- **状态**:已裁决(核查通过)
|
||||
|
||||
### ISSUE-003-ai10:M2 msg 必须有 Outbox — 核查通过
|
||||
|
||||
- **提请方**:ai10
|
||||
- **日期**:2026-07-10
|
||||
- **类型**:仲裁核查
|
||||
- **描述**:01-understanding.md M2 称"004 §7.2 明确 msg 生产 `edu.notification.events`,msg 必须有 Outbox"。核查 004 §7.3 L638 `NotificationRequested` 事件(Msg 投递通知到多渠道)+ known-issues §msg L346"Outbox 强制"。
|
||||
- **核查结论**:✅ 仲裁准确,msg 必须实现 Outbox(P5 强制)
|
||||
- **状态**:已裁决(核查通过)
|
||||
|
||||
### ISSUE-004-ai10:core-edu 事件 topic 命名统一 — 核查部分通过
|
||||
|
||||
- **提请方**:ai10
|
||||
- **日期**:2026-07-10
|
||||
- **类型**:仲裁核查
|
||||
- **描述**:02-architecture-design.md §7.2 P11 称"coord 已仲裁采用 `edu.teaching.*` 新约定"。核查:
|
||||
- 004 §7.2 L623-625 使用 `edu.teaching.assignment.submitted` / `edu.teaching.exam.published` / `edu.teaching.grade.recorded`(新约定)✅
|
||||
- [events.proto](../../../packages/shared-proto/proto/events.proto) L9-13 注释仍用 `edu.exam.events` / `edu.homework.events`(旧约定)❌ 未同步
|
||||
- [matrix.md](../matrix.md) §4 L112-115 使用 `edu.exam.events` / `edu.homework.events`(旧约定)❌ 未同步
|
||||
- **核查结论**:⚠️ 仲裁已作出但未全量同步,events.proto 注释与 matrix.md 仍用旧约定,需 coord 统一更新
|
||||
- **状态**:待 coord 同步(见 ISSUE-008)
|
||||
|
||||
### ISSUE-005-ai10:Push Gateway 调用方向歧义 — 核查通过
|
||||
|
||||
- **提请方**:ai10
|
||||
- **日期**:2026-07-10
|
||||
- **类型**:仲裁核查
|
||||
- **描述**:02-architecture-design.md §7.2 P10 称"004 §4.1 写 PushGW→Msg,实际是 Msg→PushGW"。核查 004 §4.1 L413:`push-gateway → Msg | gRPC | 推送通道建立`,方向确实反了。实际流程是 msg 调 push-gateway 的 gRPC PushService.Push(见 [notifications.service.ts](../../../services/msg/src/notifications/notifications.service.ts) L179 fetch POST /internal/push 降级实现)。
|
||||
- **核查结论**:✅ 歧义确认,建议 coord 修正 004 §4.1 表述为"Msg → push-gateway (gRPC)"
|
||||
- **状态**:待 coord 修正 004
|
||||
|
||||
---
|
||||
|
||||
## §1 新发现问题(ai10 提请)
|
||||
|
||||
### ISSUE-006-ai10:events.proto P9 字段描述不准确
|
||||
|
||||
- **提请方**:ai10
|
||||
- **日期**:2026-07-10
|
||||
- **类型**:契约不明确
|
||||
- **描述**:02-architecture-design.md §7.2 P9 称"events.proto ExamEvent / HomeworkEvent / GradeEvent 需补 `class_id` / `student_ids[]` 字段"。核查 events.proto:
|
||||
- `ExamEvent` L32 **已有** `class_id` 字段 ✅
|
||||
- `HomeworkEvent` L45 **已有** `class_id` 字段 ✅
|
||||
- `GradeEvent` L50-59 **无** `class_id`(仅有 `student_id`)❌
|
||||
- 三者均**无** `student_ids[]`(复数,用于 fan-out 广播)❌
|
||||
- **建议方案**:修正 P9 表述为"`GradeEvent` 需补 `class_id`;全部事件需补 `student_ids[]` 字段(msg fan-out 广播通知需要)"
|
||||
- **状态**:待 coord 仲裁
|
||||
|
||||
### ISSUE-007-ai10:proto 包名规则冲突(project_rules vs 实际)
|
||||
|
||||
- **提请方**:ai10
|
||||
- **日期**:2026-07-10
|
||||
- **类型**:契约不明确
|
||||
- **描述**:[project_rules §5](../../../.trae/rules/project_rules.md) 规定"包名规范:`edu.<domain>.v1`(如 `edu.iam.v1`、`edu.core_edu.v1`)",但实际所有 proto 文件使用 `next_edu_cloud.<domain>.v1`(如 msg.proto L3 `next_edu_cloud.msg.v1`、events.proto L3 `next_edu_cloud.events.v1`)。01-understanding.md 称"coord 已裁决采用 `next_edu_cloud.*`",但 project_rules §5 未同步更新,仍写 `edu.<domain>.v1`。
|
||||
- **建议方案**:coord 统一裁决,二选一:
|
||||
- 方案 A:更新 project_rules §5 为 `next_edu_cloud.<domain>.v1`(与实际 proto 一致)
|
||||
- 方案 B:重命名所有 proto package 为 `edu.<domain>.v1`(与规则一致,但改动大)
|
||||
- **状态**:待 coord 仲裁
|
||||
|
||||
### ISSUE-008-ai10:Kafka topic 命名三套约定并存
|
||||
|
||||
- **提请方**:ai10
|
||||
- **日期**:2026-07-10
|
||||
- **类型**:契约不明确
|
||||
- **描述**:msg 相关的 Kafka topic 命名存在三套约定:
|
||||
- **约定 A(004 §7.2,per-event topic)**:`edu.identity.user.created` / `edu.teaching.exam.published` / `edu.notification.sent`
|
||||
- **约定 B(matrix.md §4 + events.proto 注释,aggregate topic)**:`edu.iam.user.events` / `edu.exam.events` / `edu.notification.requested`
|
||||
- **约定 C(msg_contract.md §1.4,aggregate topic + action 字段)**:`edu.msg.notification.events`(action: sent/read/recalled/failed)
|
||||
- 02-architecture-design.md §5.1/§5.2 采用约定 A;msg_contract.md §1.4 采用约定 C;matrix.md 采用约定 B。known-issues §全局 L182 已标记此冲突。
|
||||
- **建议方案**:coord 统一为一套约定。ai10 倾向约定 A(per-event topic),理由:
|
||||
- 004 §7.2 已采用,是架构设计意图唯一源
|
||||
- per-event topic 便于消费者按需订阅,避免反序列化无关事件
|
||||
- 与 NotificationSent / NotificationRead 等事件命名(PascalCase)对齐
|
||||
- **状态**:待 coord 仲裁
|
||||
|
||||
### ISSUE-009-ai10:RPC 数量超预算(17 vs 13)
|
||||
|
||||
- **提请方**:ai10
|
||||
- **日期**:2026-07-10
|
||||
- **类型**:工作量超批
|
||||
- **描述**:[ai-allocation.md §3.2](../../ai-allocation.md) L112 与 [matrix.md](../matrix.md) §2 L90 均规定 msg 为"3 Service 13 RPC"。但 02-architecture-design.md §4.2 列出 17 RPC:
|
||||
- NotificationService 9 RPC(SendNotification / BatchSendNotification / ListNotifications / GetUnreadCount / MarkAsRead / BatchMarkAsRead / MarkAllAsRead / SearchNotifications / RecallNotification)
|
||||
- NotificationPreferenceService 2 RPC(GetPreferences / UpdatePreferences)
|
||||
- NotificationTemplateService 6 RPC(CreateTemplate / GetTemplate / ListTemplates / UpdateTemplate / DeleteTemplate / RenderTemplate)
|
||||
- 而 msg_contract.md §1.1 列出 13 RPC(分布不同:5+4+4),两文档互相不一致
|
||||
- **建议方案**:coord 裁决 RPC 范围,二选一:
|
||||
- 方案 A:维持 13 RPC 预算,02-architecture-design.md 裁剪至 13(移除 BatchSendNotification / GetUnreadCount / BatchMarkAsRead / MarkAllAsRead / UpdateTemplate / DeleteTemplate,降级为 REST only 或合并)
|
||||
- 方案 B:放宽至 17 RPC,同步更新 ai-allocation.md + matrix.md + msg_contract.md
|
||||
- **状态**:待 coord 仲裁
|
||||
|
||||
### ISSUE-010-ai10:markAsRead 权限点与设计不一致
|
||||
|
||||
- **提请方**:ai10
|
||||
- **日期**:2026-07-10
|
||||
- **类型**:契约不明确
|
||||
- **描述**:[notifications.controller.ts](../../../services/msg/src/notifications/notifications.controller.ts) L74 markAsRead 使用 `MSG_NOTIFICATION_MANAGE` 权限,但 02-architecture-design.md §6.1 L730 规定 markAsRead 应使用 `MSG_NOTIFICATION_READ`。MANAGE 权限通常给管理员,学生标记自己通知已读不应需要 MANAGE 权限。
|
||||
- **建议方案**:以 02-architecture-design.md §6.1 为准(READ),P5 实现时修正 controller 权限点
|
||||
- **状态**:待 coord 确认
|
||||
|
||||
### ISSUE-011-ai10:DB→ES 降级方向与 ai-allocation §5 相反
|
||||
|
||||
- **提请方**:ai10
|
||||
- **日期**:2026-07-10
|
||||
- **类型**:契约不明确
|
||||
- **描述**:
|
||||
- [ai-allocation.md §5](../../ai-allocation.md) ai10 设计重点要求"ES 降级查询策略(**DB 不可用时走 ES 索引**)"——即 DB 故障时 ES 作为读模型兜底
|
||||
- 02-architecture-design.md §3.2.2 / §3.4 描述"**ES 不可用时降级到 MySQL LIKE 查询**"——即 ES 故障时 DB 兜底
|
||||
- 01-understanding.md M16 称"无 DB→ES 降级读路径"
|
||||
- 三处描述方向相反,需统一
|
||||
- **建议方案**:ai10 倾向双向降级(两种故障场景都覆盖):
|
||||
- ES 故障 → DB LIKE 查询(设计文档已覆盖)
|
||||
- DB 故障 → ES 只读模式(ai-allocation 要求,设计文档需补充)
|
||||
- 但 DB 故障时写操作无法降级(必须等 DB 恢复),仅读操作可走 ES
|
||||
- **状态**:待 coord 仲裁
|
||||
|
||||
### ISSUE-012-ai10:设计文档缺 DLQ 与三层幂等防线
|
||||
|
||||
- **提请方**:ai10
|
||||
- **日期**:2026-07-10
|
||||
- **类型**:前置依赖缺失
|
||||
- **描述**:[known-issues §msg](../../../docs/troubleshooting/known-issues.md) L344 / L356 已记录两项 ai10 设计点,但 02-architecture-design.md 未覆盖:
|
||||
- **三层幂等防线**(L344):L1 Redis SETNX / L2 msg_idempotency 表 / L3 notifications.source_event_id 唯一索引。设计文档 §5.5 仅描述两层(Redis + DB UNIQUE),缺中间层 msg_idempotency 表
|
||||
- **死信队列**(L356):消费失败超 3 次投递 `edu.notification.dlq`。设计文档 §5 完全未提及 DLQ 设计
|
||||
- **建议方案**:02-architecture-design.md 补充:
|
||||
- §3.1 补 `msg_idempotency` 表 schema(中间层)
|
||||
- §5.5 改为三层幂等防线
|
||||
- §5 补 DLQ 设计(重试 3 次后投递 `edu.notification.dlq` + 告警)
|
||||
- **状态**:待 coord 确认(非阻塞,ai10 自行补充设计文档即可)
|
||||
|
||||
### ISSUE-013-ai10:events.proto 缺 4 类 message 阻塞 msg 消费
|
||||
|
||||
- **提请方**:ai10
|
||||
- **日期**:2026-07-10
|
||||
- **类型**:前置依赖缺失
|
||||
- **描述**:[events.proto](../../../packages/shared-proto/proto/events.proto) 仅有 ClassEvent / ExamEvent / HomeworkEvent / GradeEvent 4 个 message。msg 消费还需要:
|
||||
- `UserEvent`(iam 发布 user.created/updated/deleted/role_changed)— 阻塞欢迎通知/角色变更通知
|
||||
- `RoleEvent`(iam 发布 role.created/updated)— 阻塞角色变更通知
|
||||
- `MasteryEvent`(data-ana 发布 mastery.updated)— 阻塞学情预警通知
|
||||
- `NotificationEvent`(msg 发布 notification.sent/read/recalled/failed)— 阻塞 push-gateway 消费 msg 事件
|
||||
- **建议方案**:coord 维护 shared-proto,在 P5 启动前补齐这 4 个 message。msg 在 proto 补齐前用通用 JSON payload 解析(A6 假设)
|
||||
- **状态**:待 coord 仲裁(🔴 阻塞 P5 消费链路)
|
||||
|
||||
### ISSUE-014-ai10:msg_contract.md 与 02-architecture-design.md RPC 清单不一致
|
||||
|
||||
- **提请方**:ai10
|
||||
- **日期**:2026-07-10
|
||||
- **类型**:契约不明确
|
||||
- **描述**:两文档 RPC 清单存在差异:
|
||||
- msg_contract.md 独有(02 缺):GetPreferenceByChannel、ListPreferences
|
||||
- 02-architecture-design.md 独有(contract 缺):BatchSendNotification、GetUnreadCount、BatchMarkAsRead、MarkAllAsRead、UpdateTemplate、DeleteTemplate
|
||||
- 即使忽略 ISSUE-009 的数量问题,两文档的 RPC 组合也不同
|
||||
- **建议方案**:待 ISSUE-009 仲裁后,统一两文档 RPC 清单
|
||||
- **状态**:待 coord 仲裁(依赖 ISSUE-009)
|
||||
|
||||
### ISSUE-015-ai10:msg_contract.md Kafka 发布事件与设计文档不一致
|
||||
|
||||
- **提请方**:ai10
|
||||
- **日期**:2026-07-10
|
||||
- **类型**:契约不明确
|
||||
- **描述**:
|
||||
- msg_contract.md §1.4:`edu.msg.notification.events` topic,单一 NotificationEvent 含 action 字段
|
||||
- 02-architecture-design.md §5.2:4 个 per-event topic(`edu.notification.sent` / `edu.notification.read` / `edu.notification.recalled` / `edu.notification.failed`)
|
||||
- matrix.md §4 L118:`edu.notification.requested`(第三种命名)
|
||||
- **建议方案**:待 ISSUE-008 仲裁 topic 命名约定后统一
|
||||
- **状态**:待 coord 仲裁(依赖 ISSUE-008)
|
||||
|
||||
@@ -1,45 +1,316 @@
|
||||
# msg 工作排期
|
||||
|
||||
> 负责人:ai10
|
||||
> 关联:[workline.md](../workline.md)、[coord.md](../coord.md)、[contracts/msg_contract.md](../contracts/msg_contract.md)
|
||||
> 模式:全并行(各 AI 一口气完成 P2-P6 全部代码,最后统一集成测试)
|
||||
> 关联:[workline.md](../workline.md)、[coord.md](../coord.md)、[contracts/msg_contract.md](../contracts/msg_contract.md)、[02-architecture-design.md §10](../../../services/msg/docs/02-architecture-design.md)
|
||||
> 模式:全并行(各 AI 一口气完成 P2-P6 全部代码,开发期间用 mock,最后统一集成测试)
|
||||
|
||||
---
|
||||
|
||||
## §1 总览
|
||||
|
||||
msg 是消息服务,提供 NotificationService、PreferenceService、TemplateService,并基于 Outbox 模式发布消息事件。全阶段目标:P2 服务骨架+Outbox → P3 三大 Service 实现 → P4-P6 持续优化。
|
||||
msg 是消息通知中台(P5),提供 NotificationService + NotificationPreferenceService + NotificationTemplateService 三服务,基于 Outbox 模式发布通知事件,消费 iam/core-edu/data-ana 共 12 类事件触发多渠道通知。
|
||||
|
||||
**全阶段目标**:
|
||||
- P2-P3:服务骨架补全(schema 迁移 + Outbox + Kafka 基础设施 + mock 消费)
|
||||
- P4:三大 Service 主体实现(Notification + Preference + Template)+ ChannelDispatcher 多渠道
|
||||
- P5:gRPC 50056 启用 + PushGatewayClient gRPC + 12 类事件 consumer + ES mapping
|
||||
- P6:测试覆盖 ≥ 80% + /readyz 硬化 + 黄金模板对齐 + README 修正
|
||||
|
||||
**批次归属**:批次 4(P5),依赖批次 3 content(ai09)就绪后启动,预估 13 天。
|
||||
|
||||
---
|
||||
|
||||
## §2 全阶段甘特图(P2-P6,各 AI 自行细化)
|
||||
## §2 全阶段甘特图(P2-P6)
|
||||
|
||||
```mermaid
|
||||
gantt
|
||||
title ai10 msg 全阶段排期
|
||||
title ai10 msg 全阶段排期(13 天)
|
||||
dateFormat YYYY-MM-DD
|
||||
axisFormat %m-%d
|
||||
|
||||
section P2-P6
|
||||
[阶段任务] :a10a, 2026-07-10, Xd
|
||||
```
|
||||
section P2-P3 骨架补全
|
||||
T1-T2 schema迁移(notifications+preferences字段) :crit, a1, 2026-07-10, 1d
|
||||
T3 新建msg_notification_templates表 :a2, after a1, 1d
|
||||
T4 新建msg_outbox_events+Publisher worker :crit, a3, after a1, 2d
|
||||
T5 新建shared/kafka(producer+consumer骨架) :crit, a4, after a3, 1d
|
||||
T6 引入ioredis+IdempotencyGuard(SETNX) :a5, after a3, 1d
|
||||
|
||||
> **注意**:以上为 coord 初始规划,ai10 接管后必须自行细化为完整 P2-P6 排期。
|
||||
section P4 主体实现
|
||||
T7 ChannelDispatcher多渠道抽象 :crit, a6, after a4, 2d
|
||||
T10 重构notifications.service(移除同步fetch) :a7, after a6, 1d
|
||||
T11 batchMarkAsRead/markAllAsRead/recall/getUnreadCount :a8, after a7, 1d
|
||||
T12 NotificationPreference CRUD :a9, after a7, 1d
|
||||
T13 NotificationTemplate CRUD+render :a10, after a7, 1d
|
||||
T15 createBatch改批量INSERT :a11, after a7, 1d
|
||||
|
||||
section P5 gRPC+事件+ES
|
||||
T8 PushGatewayClient gRPC(替代fetch降级) :crit, a12, after a8, 1d
|
||||
T9 12类Kafka事件consumer(iam/core-edu/data-ana) :crit, a13, after a12, 2d
|
||||
T14 ES索引mapping+ensureIndex+同步 :a14, after a12, 1d
|
||||
gRPC 50056启用+3 Service 13 RPC :crit, a15, after a13, 1d
|
||||
|
||||
section P6 硬化与对齐
|
||||
T16 NotificationsModule补exports :a16, after a15, 1d
|
||||
T17 /readyz多依赖(DB/ES/Redis/Kafka/PushGW) :a17, after a15, 1d
|
||||
T19 统一关闭到LifecycleService :a18, after a15, 1d
|
||||
T20-T21 DB改getDb()+ID改cuid2 :a19, after a15, 1d
|
||||
T22 单元测试覆盖≥80% :crit, a20, after a19, 2d
|
||||
T23 修正README与实现对齐 :a21, after a20, 1d
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## §3 详细任务
|
||||
|
||||
### 全阶段任务
|
||||
### P2-P3 骨架补全
|
||||
|
||||
#### T1-T2:schema 迁移(notifications + preferences 字段扩展)
|
||||
|
||||
- **负责人**:ai10
|
||||
- **交付物**:⚠️ 由 ai10 自行补充
|
||||
- **依赖**:见 [contracts/msg_contract.md](../contracts/msg_contract.md)
|
||||
- **验收标准**:⚠️ 由 ai10 自行补充
|
||||
- **依赖**:无(msg 独占 DB)
|
||||
- **交付物**:
|
||||
- `msg_notifications` 表新增 status / metadata / related_entity_type / related_entity_id / group_id / sender_id / template_id / event_id / updated_at 字段 + 6 个索引
|
||||
- `msg_notification_preferences` 表补齐 created_at / updated_at + 新增 frequency_limit / quiet_hours_start / quiet_hours_end / quiet_hours_timezone 字段
|
||||
- **验收标准**:Drizzle schema 定义更新,迁移脚本可执行,索引符合 02-architecture-design.md §3.1.1 / §3.1.2
|
||||
|
||||
#### T3:新建 msg_notification_templates 表
|
||||
|
||||
- **负责人**:ai10
|
||||
- **依赖**:T1-T2
|
||||
- **交付物**:`msg_notification_templates` 表 schema(code + type + title_template + content_template + default_channels + variables + locale + status),UNIQUE INDEX `(code, locale)`
|
||||
- **验收标准**:schema 定义 + 迁移脚本,符合 02-architecture-design.md §3.1.3
|
||||
|
||||
#### T4:新建 msg_outbox_events 表 + Outbox Publisher worker
|
||||
|
||||
- **负责人**:ai10
|
||||
- **依赖**:T1-T2
|
||||
- **交付物**:
|
||||
- `msg_outbox_events` 表 schema(event_id PK + aggregate_type + aggregate_id + event_type + topic + payload + status + retry_count + created_at + published_at + next_retry_at)
|
||||
- `shared/outbox/outbox.publisher.ts`(独立 worker,每 1s 轮询 PENDING 事件投递 Kafka)
|
||||
- `shared/outbox/outbox.schema.ts`(Drizzle schema)
|
||||
- **验收标准**:Outbox Publisher 可轮询 + 投递 + 更新 status=SENT,符合 02-architecture-design.md §3.1.4 / §5.4
|
||||
- **关联 ISSUE**:ISSUE-003(Outbox 强制)
|
||||
|
||||
#### T5:新建 shared/kafka/(producer + consumer 骨架)
|
||||
|
||||
- **负责人**:ai10
|
||||
- **依赖**:T4
|
||||
- **交付物**:
|
||||
- `shared/kafka/kafka.producer.ts`(idempotent=true + transactionalId=msg-producer)
|
||||
- `shared/kafka/kafka.consumer.ts`(consumer group = msg-service)
|
||||
- `shared/kafka/topic-map.ts`(PRODUCER_TOPIC_MAP + CONSUMER_TOPICS)
|
||||
- **验收标准**:producer 可投递消息,consumer 可订阅 topic,符合 02-architecture-design.md §5.3
|
||||
|
||||
#### T6:引入 ioredis + IdempotencyGuard(SETNX)
|
||||
|
||||
- **负责人**:ai10
|
||||
- **依赖**:无
|
||||
- **交付物**:
|
||||
- `shared/redis/redis.client.ts`(ioredis 客户端,连接 REDIS_URL)
|
||||
- `shared/redis/idempotency.guard.ts`(SETNX `msg:processed:{event_id}` TTL 7 天)
|
||||
- `shared/redis/read-bitmap.ts`(已读位图 BITCOUNT / GETBIT)
|
||||
- env.ts 补 REDIS_URL 必填校验
|
||||
- **验收标准**:IdempotencyGuard SETNX 原子去重,Redis 不可用时降级到 DB 唯一索引
|
||||
- **关联 ISSUE**:ISSUE-012(三层幂等防线,补 msg_idempotency 表中间层)
|
||||
|
||||
### P4 主体实现
|
||||
|
||||
#### T7:ChannelDispatcher 多渠道抽象
|
||||
|
||||
- **负责人**:ai10
|
||||
- **依赖**:T5、T6
|
||||
- **交付物**:
|
||||
- `channels/notification-channel.interface.ts`(NotificationChannel 接口)
|
||||
- `channels/in-app.channel.ts`(站内信,写 MySQL)
|
||||
- `channels/email.channel.ts`(邮件,SMTP,异步队列)
|
||||
- `channels/sms.channel.ts`(短信,HTTP API)
|
||||
- `channels/wechat.channel.ts`(微信,HTTP API)
|
||||
- `channels/push.channel.ts`(推送,调 PushGatewayClient)
|
||||
- `channels/channel-dispatcher.ts`(Promise.allSettled 并行投递 + in_app 总是发送)
|
||||
- **验收标准**:新增渠道只需实现接口 + 注册,符合 02-architecture-design.md §12
|
||||
|
||||
#### T10:重构 notifications.service.ts
|
||||
|
||||
- **负责人**:ai10
|
||||
- **依赖**:T4、T5、T7
|
||||
- **交付物**:重构 notifications.service.ts,移除同步 fetch push-gateway,改为 ChannelDispatcher + Outbox 事务
|
||||
- **验收标准**:send 方法走 BEGIN TX → INSERT notifications + INSERT outbox → COMMIT → ChannelDispatcher.dispatch
|
||||
|
||||
#### T11:新增端点(batchMarkAsRead / markAllAsRead / recall / getUnreadCount)
|
||||
|
||||
- **负责人**:ai10
|
||||
- **依赖**:T10
|
||||
- **交付物**:controller + service 新增 4 个端点
|
||||
- **验收标准**:符合 02-architecture-design.md §4.1 REST API 表
|
||||
- **关联 ISSUE**:ISSUE-010(markAsRead 权限改 READ)
|
||||
|
||||
#### T12:NotificationPreference CRUD
|
||||
|
||||
- **负责人**:ai10
|
||||
- **依赖**:T1-T2
|
||||
- **交付物**:`preferences/` 目录(controller + service + repository + schema + dto)
|
||||
- **验收标准**:GET / PUT preferences 端点可用
|
||||
|
||||
#### T13:NotificationTemplate CRUD + render
|
||||
|
||||
- **负责人**:ai10
|
||||
- **依赖**:T3
|
||||
- **交付物**:`templates/` 目录(controller + service + repository + schema + dto),含 `{{variable}}` 占位符替换渲染
|
||||
- **验收标准**:CreateTemplate / GetTemplate / ListTemplates / RenderTemplate 可用
|
||||
|
||||
#### T15:createBatch 改批量 INSERT
|
||||
|
||||
- **负责人**:ai10
|
||||
- **依赖**:T10
|
||||
- **交付物**:createBatch 改为 `db.insert(notifications).values([...])` 批量 INSERT
|
||||
- **验收标准**:1 万条广播通知 < 5s(性能验收)
|
||||
|
||||
### P5 gRPC + 事件 + ES
|
||||
|
||||
#### T8:PushGatewayClient gRPC
|
||||
|
||||
- **负责人**:ai10
|
||||
- **依赖**:D5(push-gateway 提供 gRPC PushService.Push)
|
||||
- **交付物**:`shared/push/push-gateway.client.ts`(gRPC 调用,替代 fetch POST /internal/push 降级)
|
||||
- **验收标准**:gRPC 调用 push-gateway PushService.Push,降级模式保留(push-gateway 不可用时走 in_app)
|
||||
- **关联 ISSUE**:ISSUE-005(调用方向澄清)
|
||||
|
||||
#### T9:12 类 Kafka 事件 consumer
|
||||
|
||||
- **负责人**:ai10
|
||||
- **依赖**:T5、T6、D1-D2(events.proto 补齐 message + 字段)
|
||||
- **交付物**:
|
||||
- `shared/kafka/consumers/iam.consumer.ts`(6 类 user/role 事件)
|
||||
- `shared/kafka/consumers/core-edu.consumer.ts`(5 类 exam/homework/grade/attendance 事件)
|
||||
- `shared/kafka/consumers/data-ana.consumer.ts`(1 类 mastery 事件)
|
||||
- 每个 consumer 走 IdempotencyGuard → NotificationService.createNotificationFromEvent
|
||||
- **验收标准**:12 类事件均可消费 + 幂等去重 + fan-out 通知
|
||||
- **关联 ISSUE**:ISSUE-013(events.proto 缺 4 类 message,阻塞)
|
||||
|
||||
#### T14:ES 索引 mapping + ensureIndex + 同步
|
||||
|
||||
- **负责人**:ai10
|
||||
- **依赖**:无
|
||||
- **交付物**:
|
||||
- `config/elasticsearch.ts` 补 `notifications` 索引 mapping(ik_max_word 分词)
|
||||
- ensureIndex 幂等创建
|
||||
- 数据同步:Outbox 事件触发 ES 索引更新(替代当前同步 safeIndex)
|
||||
- **验收标准**:ES 检索可用,mapping 符合 02-architecture-design.md §3.2.1
|
||||
- **关联 ISSUE**:ISSUE-011(降级方向待仲裁)
|
||||
|
||||
#### gRPC 50056 启用 + 3 Service 13 RPC
|
||||
|
||||
- **负责人**:ai10
|
||||
- **依赖**:D3-D4(msg.proto 补 RPC + Service)
|
||||
- **交付物**:
|
||||
- `notifications.grpc.controller.ts`(NotificationService gRPC)
|
||||
- `preferences.grpc.controller.ts`(NotificationPreferenceService gRPC)
|
||||
- `templates.grpc.controller.ts`(NotificationTemplateService gRPC)
|
||||
- app.module.ts 注册 gRPC server :50056
|
||||
- **验收标准**:HealthService.Check 返回 SERVING,13 RPC 可调用
|
||||
- **关联 ISSUE**:ISSUE-009(RPC 数量待仲裁 13 vs 17)
|
||||
|
||||
### P6 硬化与对齐
|
||||
|
||||
#### T16:NotificationsModule 补 exports
|
||||
|
||||
- **负责人**:ai10
|
||||
- **依赖**:无
|
||||
- **交付物**:notifications.module.ts 补 `exports: [NotificationsService]`
|
||||
- **验收标准**:BFF 可注入 NotificationsService
|
||||
|
||||
#### T17:/readyz 多依赖检查
|
||||
|
||||
- **负责人**:ai10
|
||||
- **依赖**:T4、T5、T6、T8
|
||||
- **交付物**:health.controller.ts /readyz 检查 DB / ES / Redis / Kafka producer / Kafka consumer / PushGateway 6 项依赖
|
||||
- **验收标准**:符合 02-architecture-design.md §6.6 判定规则(DB down → down;Redis/ES/Kafka down → degraded)
|
||||
|
||||
#### T19:统一关闭到 LifecycleService
|
||||
|
||||
- **负责人**:ai10
|
||||
- **依赖**:无
|
||||
- **交付物**:移除 main.ts 重复 closeDb/closeEs,统一到 LifecycleService,8 步关闭序列
|
||||
- **验收标准**:符合 02-architecture-design.md §6.7 优雅关闭顺序
|
||||
|
||||
#### T20-T21:DB 改 getDb() + ID 改 cuid2
|
||||
|
||||
- **负责人**:ai10
|
||||
- **依赖**:无
|
||||
- **交付物**:database.ts 改 getDb() 函数式;service 层 randomUUID → cuid2
|
||||
- **验收标准**:与 classes 黄金模板对齐
|
||||
|
||||
#### T22:单元测试覆盖 ≥ 80%
|
||||
|
||||
- **负责人**:ai10
|
||||
- **依赖**:全部 P4-P5 任务
|
||||
- **交付物**:`*.spec.ts`(Service / Repository / ChannelDispatcher / IdempotencyGuard / OutboxPublisher)
|
||||
- **验收标准**:覆盖率 ≥ 80%
|
||||
|
||||
#### T23:修正 README
|
||||
|
||||
- **负责人**:ai10
|
||||
- **依赖**:全部任务
|
||||
- **交付物**:README.md API 表与实现对齐(PUT /:id/read、补 batch/user/:userId/page 端点、补 env 变量表)
|
||||
- **验收标准**:无文档脱节
|
||||
|
||||
---
|
||||
|
||||
## §4 依赖与就绪信号
|
||||
|
||||
- **我依赖**:⚠️ 由 ai10 自行补充(见 contract.md)
|
||||
- **我的就绪信号**:⚠️ 由 ai10 自行补充
|
||||
### 4.1 我依赖的上游就绪标志
|
||||
|
||||
- [ ] **D1**:events.proto 补 UserEvent / RoleEvent / NotificationEvent / MasteryEvent message(coord 维护)— 🔴 阻塞 T9
|
||||
- [ ] **D2**:events.proto GradeEvent 补 class_id;全部事件补 student_ids[](coord 维护)— 🔴 阻塞 T9 fan-out
|
||||
- [ ] **D3**:msg.proto 补 BatchSendNotification / GetUnreadCount / BatchMarkAsRead / MarkAllAsRead / RecallNotification RPC(coord 维护)— 🔴 阻塞 gRPC(依赖 ISSUE-009 仲裁)
|
||||
- [ ] **D4**:msg.proto 补 NotificationPreferenceService + NotificationTemplateService(coord 维护)— 🔴 阻塞 gRPC
|
||||
- [ ] **D5**:push-gateway 提供 gRPC PushService.Push 方法(ai02)— 🔴 阻塞 T8
|
||||
- [ ] **D6**:iam 发布 6 类 user/role 事件(ai06)— 🟡 不阻塞开发(用 mock),阻塞集成验证
|
||||
- [ ] **D7**:core-edu 发布 5 类教学事件(ai08)— 🟡 同上
|
||||
- [ ] **D8**:data-ana 发布 mastery 事件(ai11)— 🟡 同上
|
||||
- [ ] **D9**:Redis 集群可用(infra 部署)— 🟡 降级到 DB 唯一索引
|
||||
|
||||
### 4.2 我的就绪标志(供下游消费)
|
||||
|
||||
- [ ] msg gRPC 50056 启用(HealthService.Check 返回 SERVING)
|
||||
- [ ] NotificationService RPC 可调用(数量待 ISSUE-009 仲裁)
|
||||
- [ ] NotificationPreferenceService RPC 可调用
|
||||
- [ ] NotificationTemplateService RPC 可调用(含 RenderTemplate)
|
||||
- [ ] `edu.notification.*` topic 可发布(供 push-gateway / data-ana 消费,命名待 ISSUE-008 仲裁)
|
||||
- [ ] /readyz 返回 6 项依赖状态
|
||||
- [ ] 测试覆盖率 ≥ 80%
|
||||
|
||||
---
|
||||
|
||||
## §5 Mock 策略
|
||||
|
||||
### 5.1 我提供的 mock(供下游)
|
||||
|
||||
在 msg 真实服务就绪前,为下游(teacher-bff / student-bff / parent-bff / push-gateway)提供 mock:
|
||||
|
||||
- **gRPC mock**:grpc-mock 拦截 50056 端口
|
||||
- ListNotifications 返回固定 10 条未读通知
|
||||
- MarkAsRead 返回 success=true
|
||||
- GetPreference 返回默认偏好(in_app + email 开启,sms + push 关闭)
|
||||
- RenderTemplate 返回固定 title + content
|
||||
- **Kafka mock**:msg 就绪前不发布真实通知事件,push-gateway 使用本地 stub 推送
|
||||
|
||||
### 5.2 我消费的 mock(开发期间)
|
||||
|
||||
在真实上游就绪前,msg 使用以下 mock:
|
||||
|
||||
- **业务事件**:core-edu / data-ana 就绪前,msg 内置定时器发布本地 stub 事件(ExamEvent / HomeworkEvent),触发 mock 通知流程验证 consumer 链路
|
||||
- **用户偏好**:iam 就绪前使用默认偏好(所有用户 in_app 开启)
|
||||
- **模板渲染**:内置 5 个常用模板(exam.published / homework.graded / grade.recorded / mastery.warning / system.notice)
|
||||
- **Push Gateway**:ai02 就绪前用 fetch POST /internal/push 降级(当前实现保留)
|
||||
|
||||
---
|
||||
|
||||
## §6 风险与缓解
|
||||
|
||||
| 风险 | 影响 | 缓解 |
|
||||
| ---- | ---- | ---- |
|
||||
| events.proto 补齐延迟(D1-D2) | T9 consumer 无法验证 | 开发期用 stub 事件,proto 补齐后切换 |
|
||||
| RPC 数量仲裁延迟(ISSUE-009) | gRPC controller 实现范围不确定 | 先实现 13 RPC 基线,仲裁后增减 |
|
||||
| topic 命名仲裁延迟(ISSUE-008) | Kafka producer/consumer topic 不确定 | 开发期用 02-architecture-design.md §5.3 的 TOPIC_MAP,仲裁后统一 |
|
||||
| push-gateway gRPC 延迟(D5) | T8 无法验证 | 保留 fetch POST 降级,gRPC 就绪后切换 |
|
||||
|
||||
Reference in New Issue
Block a user