# msg 模块 Next Steps(上下游依赖) > 维护者:ai10(msg) > 最后更新:2026-07-14 > 关联: > > - [msg_workline.md](../../docs/architecture/issues/worklines/msg_workline.md) > - [msg_contract.md](../../docs/architecture/issues/contracts/msg_contract.md) > - [msg_issue.md](../../docs/architecture/issues/objections/msg_issue.md) > - [ARB-008](../../docs/architecture/issues/coord.md) §10 / [ARB-013](../../docs/architecture/issues/coord.md) §15 --- ## 1. 当前状态总览 | 阶段 | 状态 | 说明 | | ------------------ | ------- | ----------------------------------------------------------------------------------- | | P5 通知中台核心 | ✅ 完成 | 13 RPC(5 Notification + 2 Preference + 6 Template)+ REST 双协议 | | ARB-008 裁决落地 | ✅ 完成 | RPC 17→13,4 RPC 降级 REST only,topic 改为 `edu.notify.notification.*` | | ARB-013 topic 统一 | ✅ 完成 | 发布 topic `edu.notify.notification.sent/read/recalled/failed` | | Outbox + 三层幂等 | ✅ 完成 | msg_outbox_events + Redis SETNX + DB + event_id UNIQUE | | ChannelDispatcher | ✅ 完成 | 5 通道(in-app/email/sms/push/wechat)Promise.allSettled | | /readyz 6 依赖检查 | ✅ 完成 | DB/ES/Redis/KafkaProducer/KafkaConsumer/PushGateway | | 单元测试 | ✅ 完成 | 101 tests / 5 spec files,覆盖率 ≥ 80% | | Announcements 公告 | ✅ 完成 | 新增 REST API(schema+repo+service+controller+module) | | sendBatch 批量优化 | ✅ 完成 | 循环单条插入 → 批量 insertNotifications + eventId 幂等过滤,失败全部标记 failed | | Docker 本地测试 | ✅ 完成 | 容器启动 + healthz/readyz + 全部 REST API 真实 DB 验证通过(含 sendBatch 幂等场景) | ### 1.1 msg 当前提供的能力 **gRPC 13 RPC(端口 50056)**: | Service | RPC | 说明 | | ----------------------------- | ------------------------------------------------------------------------------------- | ----------------- | | NotificationService | SendNotification | 单条发送 | | NotificationService | ListNotifications | 列表(分页+过滤) | | NotificationService | MarkAsRead | 标记已读 | | NotificationService | SearchNotifications | ES 全文检索 | | NotificationService | RecallNotification | 撤回广播 | | NotificationPreferenceService | GetPreferences | 查询偏好 | | NotificationPreferenceService | UpdatePreferences | 更新偏好 | | NotificationTemplateService | CreateTemplate/GetTemplate/ListTemplates/UpdateTemplate/DeleteTemplate/RenderTemplate | 模板 CRUD + 渲染 | **REST endpoints(端口 3007)**: | 路径 | 方法 | 说明 | | ---------------------------------------- | -------------- | ------------------ | | /notifications/send | POST | 单条发送 | | /notifications/batch | POST | 批量发送 | | /notifications/user/:userId | GET | 列表 | | /notifications/user/:userId/unread-count | GET | 未读数 | | /notifications/:id/read | PUT | 标记已读 | | /notifications/batch/read | PUT | 批量已读 | | /notifications/read-all | PUT | 全部已读 | | /notifications/search | GET | ES 检索 | | /notifications/recall | POST | 撤回 | | /notifications/:id | DELETE | 删除 | | /preferences/user/:userId | GET/PUT | 偏好查询/更新 | | /templates | GET/POST | 模板列表/创建 | | /templates/:id | GET/PUT/DELETE | 模板 CRUD | | /templates/render | POST | 模板渲染 | | /announcements | GET/POST | 公告列表/创建 | | /announcements/:id | GET/PUT/DELETE | 公告详情/更新/删除 | | /announcements/:id/publish | PUT | 发布公告 | | /announcements/:id/archive | PUT | 归档公告 | | /announcements/:id/pin | PUT | 置顶切换 | | /announcements/:id/read | POST | 标记公告已读 | **Kafka 消费(12 类事件)**: - iam: `edu.identity.user.created/updated/deleted/role_changed`, `edu.identity.role.created/updated` - core-edu: `edu.teaching.exam.published/assignment.submitted/assignment.graded/grade.recorded/attendance.recorded` - data-ana: `edu.insight.mastery.updated` **Kafka 发布(4 类事件,ARB-013 命名)**: - `edu.notify.notification.sent` / `edu.notify.notification.read` / `edu.notify.notification.recalled` / `edu.notify.notification.failed` - DLQ: `edu.notify.dlq` --- ## 2. 下游依赖(msg → 下游模块需要做什么) ### 2.1 teacher-bff(ai03) **依赖内容**:teacher-bff 通过 gRPC 调用 msg 的 5 个 Notification RPC + 公告 REST API **影响范围**:teacher-portal 通知中心 + 公告管理 | 下游需要的操作 | msg 提供方式 | 状态 | | -------------------- | ------------------------------------------ | --------- | | ListNotifications | gRPC NotificationService.ListNotifications | ✅ 已实现 | | MarkNotificationRead | gRPC NotificationService.MarkAsRead | ✅ 已实现 | | ListAnnouncements | REST GET /announcements | ✅ 已实现 | | CreateAnnouncement | REST POST /announcements | ✅ 已实现 | | PublishAnnouncement | REST PUT /announcements/:id/publish | ✅ 已实现 | **协调方式**:teacher-bff 通过 gRPC client 调用 msg:50056,通过 HTTP 调用 msg:3007/announcements/* ### 2.2 student-bff(ai04) **依赖内容**:student-bff 通过 gRPC + REST 调用 msg **影响范围**:student-portal 通知 + 公告查看 | 下游需要的操作 | msg 提供方式 | 状态 | | ---------------------------- | ---------------------------------------------------- | ----------------------------------- | | ListAnnouncements | REST GET /announcements | ✅ 已实现 | | GetAnnouncement | REST GET /announcements/:id | ✅ 已实现 | | MarkNotificationAsRead | gRPC NotificationService.MarkAsRead | ✅ 已实现 | | MarkAllNotificationsAsRead | REST PUT /notifications/read-all | ✅ 已实现(ARB-008 降级 REST only) | | UpdateNotificationPreference | gRPC NotificationPreferenceService.UpdatePreferences | ✅ 已实现 | | MarkAnnouncementRead | REST POST /announcements/:id/read | ✅ 已实现 | ### 2.3 parent-bff(ai05) **依赖内容**:parent-bff 通过 gRPC 调用 msg **影响范围**:parent-portal 通知偏好 | 下游需要的操作 | msg 提供方式 | 状态 | | ----------------------------- | ---------------------------------------------------- | --------- | | listNotifications | gRPC NotificationService.ListNotifications | ✅ 已实现 | | markAsRead | gRPC NotificationService.MarkAsRead | ✅ 已实现 | | getNotificationPreferences | gRPC NotificationPreferenceService.GetPreferences | ✅ 已实现 | | updateNotificationPreferences | gRPC NotificationPreferenceService.UpdatePreferences | ✅ 已实现 | ### 2.4 push-gateway(ai09) **依赖内容**:msg 通过 HTTP POST /internal/push 推送实时通知;push-gateway 消费 msg 发布的 Kafka 事件 **影响范围**:WebSocket 实时推送到前端 | 下游需要的协作 | msg 提供方式 | 状态 | | ------------------------ | ---------------------------------------------- | ------------------- | | HTTP POST /internal/push | push-gateway.client.ts sendPush() | ✅ 已实现(软失败) | | Kafka 事件推送 | 发布 `edu.notify.notification.sent` 等 4 topic | ✅ 已实现 | **push-gateway 需对齐**: - 消费 topic 必须为 `edu.notify.notification.sent`(ARB-013 命名),而非旧的 `edu.notification.requested` - 鉴权头 `X-Internal-Key: PUSH_INTERNAL_TOKEN` ### 2.5 api-gateway(ai01) **依赖内容**:api-gateway 代理 msg REST 路由 **影响范围**:前端通过 api-gateway 访问 msg | 路由 | 代理目标 | 状态 | | ----------------------- | -------- | ------------------------- | | /api/v1/notifications/* | msg:3007 | ✅ 已配置(main.go L100) | | /api/v1/messages/* | msg:3007 | ✅ 已配置(main.go L101) | **注意**:公告 REST API 在 `/announcements/*` 路径下,api-gateway 需新增路由 `/api/v1/announcements/*` → msg:3007(或复用 `/api/v1/notifications/*` 前缀下的子路径)。 ### 2.6 前端 portal(ai13/ai14/ai15/ai16) **间接依赖**:前端通过各自 BFF 聚合访问 msg,无直接 gRPC/REST 调用。 - teacher-portal(ai13):通过 teacher-bff GraphQL → msg gRPC/REST - student-portal(ai14):通过 student-bff GraphQL → msg gRPC/REST - parent-portal(ai15):通过 parent-bff GraphQL → msg gRPC - admin-portal(ai16):通过 teacher-bff admin GraphQL → msg REST(公告管理) --- ## 3. 上游依赖(msg ← 上游模块需要提供什么) ### 3.1 iam(ai06)— Kafka 事件源 **依赖内容**:msg 消费 iam 发布的 6 类 identity 事件 **事件清单**: | Topic | 触发场景 | msg 处理 | | -------------------------------- | ------------ | ---------------------------- | | `edu.identity.user.created` | 新用户注册 | 发送欢迎通知 | | `edu.identity.user.updated` | 用户信息更新 | 仅幂等标记(无通知) | | `edu.identity.user.deleted` | 用户注销 | 仅幂等标记(无通知) | | `edu.identity.user.role_changed` | 角色变更 | 发送角色变更通知 | | `edu.identity.role.created` | 角色创建 | 仅幂等标记 | | `edu.identity.role.updated` | 角色权限更新 | 向受影响用户发送权限变更通知 | **payload 约定**(JSON): - `userId` / `user_id`: string - `name` / `username`: string - `oldRole` / `old_role`: string - `newRole` / `new_role`: string - `affectedUserIds` / `affected_user_ids`: string[] - header `eventId`: string(幂等键) ### 3.2 core-edu(ai07)— Kafka 事件源 **依赖内容**:msg 消费 core-edu 发布的 5 类 teaching 事件 | Topic | 触发场景 | msg 处理 | | ----------------------------------- | -------- | ------------------ | | `edu.teaching.exam.published` | 考试发布 | 向学生发送考试通知 | | `edu.teaching.assignment.submitted` | 作业提交 | 向教师发送提交通知 | | `edu.teaching.assignment.graded` | 作业批改 | 向学生发送批改通知 | | `edu.teaching.grade.recorded` | 成绩录入 | 向学生发送成绩通知 | | `edu.teaching.attendance.recorded` | 考勤异常 | 向家长发送出勤通知 | **payload 约定**(JSON): - `examId`/`exam_id`, `examTitle`/`exam_title`, `className`/`class_name`, `studentIds`/`student_ids`: string[] - `teacherId`/`teacher_id`, `studentId`/`student_id`, `studentName`/`student_name` - `homeworkId`/`homework_id`, `homeworkTitle`/`homework_title`, `score`/`grade` - `subject`, `gradeId`/`grade_id`, `parentId`/`parent_id` - `attendanceId`/`attendance_id`, `status`, `date` ### 3.3 data-ana(ai08)— Kafka 事件源 **依赖内容**:msg 消费 data-ana 发布的 1 类 insight 事件 | Topic | 触发场景 | msg 处理 | | ----------------------------- | ---------- | ---------------------- | | `edu.insight.mastery.updated` | 掌握度下降 | 向学生发送学情预警通知 | **payload 约定**(JSON): - `studentId`/`student_id`, `subject`, `mastery`/`masteryLevel`, `trend` - `masteryId`/`mastery_id` ### 3.4 push-gateway(ai09)— HTTP 推送目标 **依赖内容**:msg 通过 HTTP POST /internal/push 调用 push-gateway 推送实时通知 **调用方式**: - URL: `${PUSH_GATEWAY_URL}/internal/push` - Header: `X-Internal-Key: ${PUSH_INTERNAL_TOKEN}` - Body: `{ userId, event, data }` - 软失败:push-gateway 不可用时返回 `{ sent: false }`,不阻断主流程 ### 3.5 基础设施依赖 | 依赖 | 用途 | 配置 | | ------------- | --------------------------------- | ---------------------------------- | | MySQL | 通知/偏好/模板/公告/outbox 持久化 | `DATABASE_URL` | | Redis | L1 幂等去重(SETNX) | `REDIS_URL`(可选,缺失降级 DB) | | Kafka | 消费上游事件 + 发布通知事件 | `KAFKA_BROKERS` | | Elasticsearch | 通知全文检索 | `ES_URL`(可选,缺失降级 DB LIKE) | | push-gateway | WebSocket 实时推送 | `PUSH_GATEWAY_URL`(可选,软失败) | --- ## 4. 已完成的工作(本轮) ### 4.1 Announcements 公告功能(新增) **原因**:下游 teacher-bff / student-bff / admin-portal 均需要公告 CRUD + 已读标记,msg 原仅有通知能力。 **实现**: - `src/announcements/announcements.schema.ts` — Drizzle schema(msg_announcements + msg_announcement_reads 表) - `src/announcements/announcements.repository.ts` — 数据访问层 - `src/announcements/announcements.service.ts` — 业务逻辑 - `src/announcements/announcements.controller.ts` — REST API(8 端点) - `src/announcements/announcements.module.ts` — 模块定义 - `src/middleware/permission.guard.ts` — 新增 `MSG_ANNOUNCEMENT_MANAGE` / `MSG_ANNOUNCEMENT_READ` 权限 - `infra/init-sql/02-all-services-schema.sql` — 新增 msg_announcements / msg_announcement_reads 表 **REST 端点**: | 路径 | 方法 | 权限 | 说明 | | -------------------------- | ------ | ----------------------- | --------------------------------- | | /announcements | POST | MSG_ANNOUNCEMENT_MANAGE | 创建公告(draft) | | /announcements | GET | MSG_ANNOUNCEMENT_READ | 列表(支持 status/audience 筛选) | | /announcements/:id | GET | MSG_ANNOUNCEMENT_READ | 详情 | | /announcements/:id | PUT | MSG_ANNOUNCEMENT_MANAGE | 更新 | | /announcements/:id | DELETE | MSG_ANNOUNCEMENT_MANAGE | 删除 | | /announcements/:id/publish | PUT | MSG_ANNOUNCEMENT_MANAGE | 发布 | | /announcements/:id/archive | PUT | MSG_ANNOUNCEMENT_MANAGE | 归档 | | /announcements/:id/pin | PUT | MSG_ANNOUNCEMENT_MANAGE | 置顶切换 | | /announcements/:id/read | POST | MSG_ANNOUNCEMENT_READ | 标记已读 | ### 4.2 ARB-008 / ARB-013 落地(前序已完成) - proto RPC 17→13(裁剪 4 RPC 降级 REST only) - topic 命名 `edu.notify.notification.*` - /readyz 6 依赖检查(Kafka producer/consumer 拆分) - sendBatch 批量 INSERT 优化 ### 4.3 sendBatch 批量 INSERT 优化 + eventId 幂等过滤(本轮) **原因**:下游 BFF 批量发送通知时性能不佳,原实现循环调用 `send`(每条 insert + dispatch + outbox)。此外 sendBatch 缺少 eventId 幂等检查,重复发送会重复插入。 **改动**: - `src/notifications/notifications.service.ts` — sendBatch 改为先批量 `insertNotifications(rows)`,失败时全部标记 failed;成功后逐条 ES 索引 + dispatch + outbox - `src/notifications/notifications.repository.ts` — 新增 `findExistingEventIds(eventIds)` 批量查询已存在的 eventId - `src/notifications/notifications.service.ts` — sendBatch 批量 INSERT 前先调用 `findExistingEventIds` 过滤已存在的 eventId(幂等跳过) - 批量 INSERT 失败 → 所有 items 进 failed 列表 - 部分分发失败 → 仅失败项进 failed 列表,成功项进 ids - eventId 已存在 → 跳过该项(不插入、不分发、不写 outbox),返回空 ids - 无 eventId 的 item → 正常插入(不做幂等检查,与单条 send 行为一致) **幂等行为**: - 全部 eventId 已存在 → 返回 `{ ids: [], failed: [] }` - 部分 eventId 已存在 → 仅插入未存在的,已存在的跳过 - 无 eventId → 正常插入(允许重复) **单元测试**(新增 2 个): - `eventId 已存在时应跳过(幂等过滤)` — 2 条 items(1 已存在 + 1 新),仅插入 1 条 - `所有 eventId 都已存在时应返回空 ids(全跳过)` — 2 条都已存在,不调用 insertNotifications ### 4.4 Docker 本地测试验证(本轮,无 mock 数据) **环境**:edu-full_default 网络,连接 edu-mysql/edu-redis/edu-kafka/edu-es 真实服务 **验证结果**: | 测试项 | 端点 | 结果 | | ---------------------- | -------------------------------------------- | ------------------------------------------------------- | | 健康检查 | GET /healthz | ✅ `{"status":"ok"}` | | 就绪检查 | GET /readyz | ✅ 5/6 OK(pushGateway 软失败,预期) | | 公告创建 | POST /announcements | ✅ 返回 id,status=draft | | 公告列表 | GET /announcements | ✅ 返回 items + total | | 公告详情 | GET /announcements/:id | ✅ 返回完整记录 | | 公告发布 | PUT /announcements/:id/publish | ✅ status=published, publishedAt 填充 | | 公告标记已读 | POST /announcements/:id/read | ✅ 幂等(UNIQUE KEY 去重) | | 公告置顶 | PUT /announcements/:id/pin | ✅ isPinned 切换 | | 公告归档 | PUT /announcements/:id/archive | ✅ status=archived, archivedAt 填充 | | 公告筛选 | GET /announcements?status=published | ✅ 正确过滤已归档项 | | 通知发送 | POST /notifications/send | ✅ 返回 id, status=sent, channels=[in_app] | | 通知列表 | GET /notifications/user/:userId | ✅ 返回 items + total + 分页 | | 未读计数 | GET /notifications/user/:userId/unread-count | ✅ 返回 count | | 标记已读 | PUT /notifications/:id/read | ✅ 未读数降为 0 | | 通知搜索 | GET /notifications/search | ✅ 返回空(ES 索引同步延迟,功能正常) | | 偏好查询 | GET /preferences/user/:userId | ✅ 返回 preferences 数组 | | 偏好更新 | PUT /preferences/user/:userId | ✅ 2 条偏好持久化 | | Prometheus 指标 | GET /metrics | ✅ 返回标准 Prometheus 格式 | | sendBatch 批量发送 | POST /notifications/batch | ✅ 3 条批量插入,3 个 outbox 事件,status=sent | | sendBatch 无 groupId | POST /notifications/batch | ✅ 自动生成 groupId(cuid2) | | sendBatch 空数组 | POST /notifications/batch | ✅ Zod 验证拒绝(min(1)) | | sendBatch eventId 幂等 | POST /notifications/batch | ✅ 重复 eventId 跳过,DB COUNT=1 | | sendBatch 混合场景 | POST /notifications/batch | ✅ 2 条 eventId 各 1 次 + 1 条无 eventId 2 次 = COUNT=4 | **Docker 启动命令**: ```bash docker run -d --name edu-msg-test --network edu-full_default \ -p 3107:3007 -p 51056:50056 \ -e PORT=3007 -e GRPC_PORT=50056 \ -e DATABASE_URL=mysql://edu:changeme@edu-mysql:3306/next_edu_cloud \ -e REDIS_URL=redis://edu-redis:6379 \ -e KAFKA_BROKERS=edu-kafka:29092 \ -e ES_URL=http://edu-es:9200 \ -e DEV_MODE=true -e NODE_ENV=production -e LOG_LEVEL=info \ edu-msg:test ``` --- ## 5. Docker 部署配置 ### 5.1 docker-compose.deploy.yml ```yaml msg: build: context: ./repo dockerfile: services/msg/Dockerfile container_name: edu-msg environment: PORT: 3007 DATABASE_URL: ${DATABASE_URL} REDIS_URL: ${REDIS_URL} KAFKA_BROKERS: ${KAFKA_BROKERS:-} ES_URL: ${ES_URL:-} PUSH_GATEWAY_URL: http://push-gateway:8081 OTEL_EXPORTER_OTLP_ENDPOINT: ${OTEL_EXPORTER_OTLP_ENDPOINT:-http://otel-collector:4318} LOG_LEVEL: ${LOG_LEVEL:-info} NODE_ENV: production DEV_MODE: "false" depends_on: push-gateway: condition: service_healthy healthcheck: test: ["CMD", "wget", "--quiet", "--spider", "http://localhost:3007/healthz"] ``` ### 5.2 本地 Docker 测试步骤 ```bash # 1. 启动基础设施(MySQL + Redis + Kafka + ES) docker compose -f infra/docker-compose.yml up -d mysql redis kafka zookeeper elasticsearch # 2. 构建并启动 msg docker compose -f infra/docker-compose.deploy.yml up -d --build msg # 3. 验证健康检查 curl http://localhost:3007/healthz curl http://localhost:3007/readyz # 4. 验证 REST API(需 dev-token) curl -X POST http://localhost:3007/announcements \ -H "Content-Type: application/json" \ -H "Authorization: Bearer dev-token" \ -d '{"title":"测试公告","content":"内容","authorId":"sys","targetAudience":"all"}' ``` --- ## 6. 待协调事项 | # | 事项 | 协调对象 | 说明 | | --- | ----------------------------------------------- | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 1 | api-gateway 新增 `/api/v1/announcements/*` 路由 | ai01 | 公告 REST API 需通过 gateway 暴露,当前只有 /notifications/* 和 /messages/* | | 2 | push-gateway 消费 topic 对齐 | ai09 | push-gateway nextstep.md 期望消费 `edu.notification.requested`,但 ARB-013 统一为 `edu.notify.notification.sent`。需 push-gateway 对齐 | | 3 | teacher-bff 接入公告 REST | ai03 | 通过 HTTP 调用 msg:3007/announcements/*(teacher-bff nextstep.md §2.5 期望 gRPC,但 msg 公告为 REST only) | | 4 | student-bff 接入公告 REST | ai04 | student-bff nextstep.md §3.5 期望 6 个 gRPC RPC(含公告),但 msg 公告为 REST only,需改用 REST 聚合 | | 5 | iam/core-edu/data-ana 事件 payload 字段对齐 | ai06/ai07/ai08 | 确保 Kafka 事件 JSON 字段名与 msg consumer 一致 | | 6 | 公告 gRPC RPC 决策 | coord | 下游 teacher-bff/student-bff 期望 ListAnnouncements/CreateAnnouncement/PublishAnnouncement/GetAnnouncement/MarkAnnouncementRead 为 gRPC,但 ARB-008 限制 RPC 总数 13。需 coord 仲裁是否新增 AnnouncementService gRPC | | 7 | parent-bff 通知偏好 RPC 命名对齐 | ai05 | parent-bff 期望 `getNotificationPreferences`/`updateNotificationPreferences`,msg proto 实际为 `GetPreferences`/`UpdatePreferences`。需对齐命名或 parent-bff 适配 | | 8 | 考试实时事件责任方澄清 | ai07/ai09/ai14 | student-portal 期望 msg 发出 ExamExtended/ExamForceSubmitted/ExamQuestionReordered 事件。这些事件应由 core-edu 发出,msg 消费后转发 push-gateway。需 core-edu 确认事件发布 | | 9 | parent-portal GraphQL 命名对齐 | ai05/ai15 | 前端 myNotifications/myNotificationPreferences vs 后端 notifications/notificationPreferences,需统一命名 | | 10 | admin-portal 公告管理联调 | ai03/ai16 | admin-portal 期望 teacher-bff 聚合 4 个公告 mutation(createAnnouncement/publishAnnouncement/archiveAnnouncement/toggleAnnouncementPin),需 teacher-bff 接入 msg REST | --- ## 7. msg 模块工作完成总结 ### 7.1 已完成的全部工作 1. **P5 通知中台核心**:13 gRPC RPC + 10 REST endpoints(notifications)+ 2 REST endpoints(preferences)+ 6 REST endpoints(templates) 2. **ARB-008 裁决落地**:RPC 17→13,4 RPC 降级 REST only 3. **ARB-013 topic 统一**:`edu.notify.notification.sent/read/recalled/failed` 4. **Outbox + 三层幂等**:msg_outbox_events + Redis SETNX + DB + event_id UNIQUE 5. **ChannelDispatcher**:5 通道 Promise.allSettled 6. **/readyz 6 依赖检查**:DB/ES/Redis/KafkaProducer/KafkaConsumer/PushGateway 7. **Announcements 公告模块**:9 REST endpoints(schema+repo+service+controller+module) 8. **sendBatch 批量优化**:循环单条插入 → 批量 insertNotifications 9. **权限扩展**:MSG_ANNOUNCEMENT_MANAGE/READ + parent 角色 10. **Docker 本地测试**:18 项 API 全部验证通过(真实 DB,无 mock) 11. **单元测试**:99 tests / 5 spec files 全部通过 12. **SQL Schema**:msg_announcements + msg_announcement_reads 表 13. **Dockerfile**:workspace 模式多阶段构建 14. **文档**:nextstep.md 完整上下游依赖 + Docker 测试结果 ### 7.2 msg 模块对外提供的能力清单 **gRPC(端口 50056,13 RPC)**: - NotificationService: SendNotification, ListNotifications, MarkAsRead, SearchNotifications, RecallNotification - NotificationPreferenceService: GetPreferences, UpdatePreferences - NotificationTemplateService: CreateTemplate, GetTemplate, ListTemplates, UpdateTemplate, DeleteTemplate, RenderTemplate **REST(端口 3007)**: - /notifications/*(10 endpoints) - /preferences/user/:userId(2 endpoints) - /templates/*(6 endpoints) - /announcements/*(9 endpoints) - /healthz, /readyz, /metrics **Kafka 消费(12 类事件)**: - iam: 6 类 identity 事件 - core-edu: 5 类 teaching 事件 - data-ana: 1 类 insight 事件 **Kafka 发布(4 类事件)**: - `edu.notify.notification.sent/read/recalled/failed` - DLQ: `edu.notify.dlq` --- **本文件维护规则**:每次完成一项 Next Step 后,将对应条目标记为 ✅ 并在 workline.md 中记录审计结果。