16 KiB
msg 对接契约
负责人:ai10 关联:matrix.md、msg.proto、events.proto、02-architecture-design.md 仲裁依赖:ISSUE-008(topic 命名)、ISSUE-009(RPC 数量)、ISSUE-013(events.proto 补齐)
§1 我提供什么(对外接口)
1.1 gRPC 接口
端口:50056 | proto 包名:
next_edu_cloud.msg.v1| proto 文件:msg.proto⚠️ ISSUE-009 待仲裁:ai-allocation.md 规定 13 RPC,02-architecture-design.md 设计 17 RPC。下表列出设计文档完整 17 RPC,标注基线 13 RPC(✅基线 / ➕扩展待仲裁)。coord 裁决后裁剪或放宽。
NotificationService(9 RPC:5 基线 + 4 扩展)
| 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} |
✅基线 | 撤回广播 |
NotificationPreferenceService(2 RPC 基线)
| RPC | 请求 | 响应 | 基线/扩展 | 说明 |
|---|---|---|---|---|
| GetPreferences | GetPreferencesRequest{user_id} |
GetPreferencesResponse{preferences[]} |
✅基线 | 偏好查询 |
| UpdatePreferences | UpdatePreferencesRequest{user_id, preferences[]} |
Empty |
✅基线 | 偏好更新 |
注:02-architecture-design.md 设计 2 RPC;原 contract 版本的 GetPreferenceByChannel / ListPreferences 暂移除(待 ISSUE-009 仲裁是否保留)
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-producerDLQ:消费失败超 3 次投递edu.notification.dlq(见 02-architecture-design.md §5,待补充)
1.5 错误码前缀
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 个)
§2 我消费什么(依赖上游)
2.1 gRPC 调用(同步)
| 调用方 | 对方服务 | 协议 | RPC | 用途 | 状态 |
|---|---|---|---|---|---|
| msg → push | push-gateway | gRPC | PushService.Push | 实时推送通知到在线用户 | P5 待实现(D5) |
当前降级:fetch POST
/internal/push(见 notifications.service.ts L179) 调用方向澄清(ISSUE-005):004 §4.1 表述"push-gateway → Msg"有歧义,实际方向是 msg → push-gateway
2.2 Kafka 事件订阅(异步)
⚠️ 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 裁决后统一。
| 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 仅为降级)。
§3 就绪信号
3.1 我依赖的上游就绪标志
| 标志 | 提供方 | 阻塞性 | 说明 |
|---|---|---|---|
| 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 RPC 可调用(基线 5 + 扩展 4,待 ISSUE-009 仲裁)
- NotificationPreferenceService 2 RPC 可调用
- NotificationTemplateService RPC 可调用(基线 4 + 扩展 2,含 RenderTemplate)
edu.notification.sent/read/recalled/failedtopic 可发布(待 ISSUE-008 仲裁命名)- /readyz 返回 6 项依赖状态(DB/ES/Redis/Kafka producer/Kafka consumer/PushGateway)
- 测试覆盖率 ≥ 80%
§4 Mock 策略
4.1 我提供的 mock(供下游)
在 msg 真实服务就绪前,为下游(teacher-bff / student-bff / parent-bff / push-gateway)提供 mock:
- 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(开发期间)
在真实上游就绪前,msg 使用以下 mock:
- 业务事件: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 单向) |