1.AI 协作文档体系重构(objections/worklines/contracts+matrix.md) 2.coord 仲裁文档(final-decisions/cross-review/final-rulings/orchestration) 3.各服务 01/02 文档补全 4.共享包初始化(shared-ts/shared-go/hooks/ui-components/ui-tokens) 5.Proto 契约补全 6.004 架构影响地图更新 7.端口分配表 8.设计规格文档
192 lines
20 KiB
Markdown
192 lines
20 KiB
Markdown
# 模块理解确认书 — msg
|
||
|
||
> AI 标识:ai05(初版)→ ai10(复核修订,按 ai-allocation.md §3.2 重新分配)
|
||
> 负责模块:msg(P5)
|
||
> 阶段:架构设计外包 · 阶段 1(全局理解)
|
||
> 初版日期:2026-07-09
|
||
> 复核修订日期:2026-07-09(ai10 接手后修正 3 处失实条目 M2/M8/M12,并补充 ai-allocation §5 缺失项)
|
||
> 关联文档:[ai-allocation.md](../../../docs/architecture/ai-allocation.md)、[004 架构影响地图](../../../docs/architecture/004_architecture_impact_map.md)、[pending-features.md](../../../docs/architecture/roadmap/pending-features.md)、[known-issues.md](../../../docs/troubleshooting/known-issues.md)
|
||
|
||
---
|
||
|
||
## 1. 我在架构中的位置
|
||
|
||
- **层级**:业务微服务层(L5),DDD 限界上下文
|
||
- **业务领域**:D5 沟通通知领域(004 §1.1b)
|
||
- **上游**(谁调用我):
|
||
- teacher-bff / student-bff / parent-bff:聚合层调 msg 查询用户通知列表、发送通知
|
||
- api-gateway:REST 转发通知请求
|
||
- Kafka:消费 core-edu / iam 事件触发通知(004 §4.1 `CoreEdu -.事件.-> Msg`、`IAM -.事件.-> Msg`)
|
||
- **下游**(我调用谁):
|
||
- MySQL(写模型主库,独占)
|
||
- Elasticsearch(全文检索,已实现 safeSearch)
|
||
- push-gateway(gRPC 推送通道,004 §4.1 `PushGW → Msg`,当前用 fetch POST /internal/push 降级)
|
||
- **通信方式**:
|
||
- 当前:HTTP REST(Controller,无 gRPC controller)
|
||
- 目标态(004 §4.1 / pending-features P5):gRPC 暴露 `NotificationService`
|
||
- Kafka:消费 core-edu(ExamPublished/HomeworkGraded/GradeRecorded)、iam(UserRegistered)事件(004 §7.3)
|
||
- **端口**:3007(见 [msg env.ts](../src/config/env.ts))
|
||
|
||
## 2. 我的限界上下文
|
||
|
||
- **聚合职责**:管理 Notification(通知)聚合 + NotificationPreference(用户通知偏好)
|
||
- **我的数据属于**:D5 沟通通知领域
|
||
- **我不负责**:
|
||
- 不负责 WebSocket 长连接管理(由 push-gateway 承载)
|
||
- 不负责业务数据变更(仅消费事件触发通知)
|
||
- 不直接访问 core-edu / iam 的数据库
|
||
- **现有骨架领域模块**(1 个,见 [msg/src](../src)):
|
||
- `notifications/`:通知 CRUD + ES 全文检索 + Push Gateway 推送 + 用户偏好(6 端点)
|
||
|
||
## 3. 我与外部的契约
|
||
|
||
- **消费的 proto message**(从 shared-proto):
|
||
- 消费 `events.proto` 的事件 message(ClassEvent/ExamEvent/HomeworkEvent/GradeEvent)
|
||
- **events.proto 当前无 NotificationEvent**,若 msg 发事件需补充
|
||
- **暴露的契约**(见 [msg.proto](../../../packages/shared-proto/proto/msg.proto),包名 `next_edu_cloud.msg.v1`):
|
||
- `NotificationService`:SendNotification / ListNotifications / MarkAsRead / SearchNotifications
|
||
- **当前实现均为 REST,gRPC controller 未实现**
|
||
- **事件契约**:
|
||
- 消费(004 §7.2 / §7.3):
|
||
- `edu.identity.user.created` / `edu.identity.user.updated`(IAM 发,msg 发欢迎通知)
|
||
- `edu.teaching.exam.published`(core-edu 发,msg 推送考试通知给学生)
|
||
- `edu.teaching.assignment.submitted`(core-edu 发,msg 通知教师)
|
||
- `edu.teaching.grade.recorded`(core-edu 发,msg 通知学生)
|
||
- `edu.insight.mastery.updated`(data-ana 发,msg 触发预警)
|
||
- 发布:无明确(pending-features 未要求 msg 发事件)
|
||
- **错误码前缀**:`MSG_*`(MSG_VALIDATION_ERROR / NOT_FOUND / PERMISSION_DENIED / CONFLICT / BUSINESS_ERROR / DATABASE_ERROR / INTERNAL_ERROR,见 [application-error.ts](../src/shared/errors/application-error.ts))
|
||
- **权限点**(3 个,见 [permission.guard.ts](../src/middleware/permission.guard.ts)):
|
||
- `MSG_NOTIFICATION_SEND`、`MSG_NOTIFICATION_READ`、`MSG_NOTIFICATION_MANAGE`
|
||
|
||
## 4. 我的技术栈
|
||
|
||
- 语言:TypeScript 5.6(ESM 模式)
|
||
- 框架:NestJS 10
|
||
- ORM:Drizzle ORM 0.31 + mysql2 3.11
|
||
- 全文检索:@elastic/elasticsearch 8.15(已实现 safeIndex/safeSearch,**无 mapping 定义**,依赖动态 mapping)
|
||
- 消息总线:kafkajs 2.2(**已装依赖但无 consumer/producer 代码**,env.ts 有 KAFKA_BROKERS 默认值)
|
||
- 推送:fetch POST 到 push-gateway `/internal/push`(降级模式,PUSH_GATEWAY_URL 未配置或失败时跳过)
|
||
- 幂等去重:Redis(**env.ts 预留 REDIS_URL 但无 redis 客户端依赖**,pending-features P5 要求 event_id 去重)
|
||
- 可观测:pino + prom-client + OpenTelemetry(三支柱已具备)
|
||
|
||
## 5. 我的阶段归属
|
||
|
||
- **P5 沟通与 AI 阶段**(pending-features §P5):
|
||
- 会话/消息 CRUD + 调 Push Gateway 推送 + 通知偏好
|
||
- 多渠道(站内/SMS/邮件/微信),沿用旧项目 dispatcher 模式
|
||
- Elasticsearch 题库全文检索(从 MySQL 同步)
|
||
- **退出标准**:教师发广播通知 → 全在线学生实时收到(Push Gateway)→ AI 辅助出题流式返回 → 题库全文检索 < 200ms
|
||
- **依赖上游**:P1 黄金模板 classes、P3 core-edu(事件来源)、P5 push-gateway(推送通道,ai01 设计)、P5 ai(无直接依赖)
|
||
|
||
## 6. 我需要对齐的黄金模板项(对照 classes 服务)
|
||
|
||
- [x] 权限装饰器 `@RequirePermission`(3 个 MSG_* 权限点,全部 Controller 方法已覆盖)
|
||
- [x] 错误码前缀统一(`MSG_*`)
|
||
- [x] logger / metrics / tracer 三支柱(已具备)
|
||
- [x] `/healthz` 健康检查(HealthModule 已注册)
|
||
- [⚠️] `/readyz`(**仅检查 DB `SELECT 1`,未检查 ES 连通性**,ES 故障时仍返回 ok)
|
||
- [x] 优雅关闭 SIGTERM(main.ts 已处理:app.close → closeEs → closeDb → shutdownTracer)
|
||
- [ ] 测试覆盖率 ≥ 80%(**当前 0%**,无测试文件)
|
||
- [x] Dockerfile 多阶段构建(已具备)
|
||
- [x] Zod 输入验证(Controller 用 `schema.parse(body)`;**GlobalErrorFilter 已识别 ZodError 返回 400**,见 [global-error.filter.ts](../src/shared/errors/global-error.filter.ts) L32-42,ai10 复核修正)
|
||
- [x] GlobalErrorFilter 统一兜底
|
||
|
||
## 7. 现有骨架差距与待决策(提请 coord 仲裁)
|
||
|
||
| # | 差距 | 影响 | 提请决策 |
|
||
| --- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------- |
|
||
| M1 | **Kafka 消费未实现**:装了 kafkajs 但无 consumer 代码,无 shared/kafka/ 目录 | 004 §7.3 列 msg 消费 5 类事件(ExamPublished/HomeworkGraded/GradeRecorded/UserRegistered/MasteryUpdated) | 阶段 2 设计需补 Kafka consumer + 幂等去重 |
|
||
| M2 | **无 Outbox**:骨架无 shared/outbox/ | 004 §7.2 明确 msg 生产 `edu.notification.events`(NotificationRequested,消费者 push-gateway),**ai10 复核:msg 必须有 Outbox**(004 §7.2/§7.3 已定义,原"无明确"判断失实) | 阶段 2 设计补 Outbox(参照 core-edu outbox.publisher.ts) |
|
||
| M3 | **ES 无 mapping 定义**:依赖动态 mapping,索引名 "notifications" 硬编码在 service | 检索质量不稳定,索引管理缺失 | 阶段 2 设计补 mapping + ensureIndex |
|
||
| M4 | **无独立 repository**:notifications.service.ts 直接用 db,无 repository 抽象 | 与黄金模板分层不一致(iam 有 repository) | 阶段 2 设计补 repository 层 |
|
||
| M5 | **Redis 未引入**:env.ts 预留 REDIS_URL 但无 redis 客户端依赖 | pending-features P5 要求 event_id 去重(Redis SETNX 或 DB 唯一索引) | 阶段 2 设计决策:Redis SETNX vs DB 唯一索引 |
|
||
| M6 | **createBatch 无事务/无批量优化**:for 循环串行调 send,无批量 INSERT | 性能瓶颈(广播场景) | 阶段 2 设计改为批量 INSERT |
|
||
| M7 | **NotificationsModule 缺 exports**:notifications.module.ts 无 `exports: [NotificationsService]` | 未来 BFF 注入受阻 | 阶段 2 设计补 exports |
|
||
| M8 | **~~ZodError 未特殊处理~~(ai10 复核:已修复,失实条目)**:GlobalErrorFilter 已识别 ZodError 返回 400(见 [global-error.filter.ts](../src/shared/errors/global-error.filter.ts) L32-42) | 无(已修复) | ✅ 已修复,无需处理 |
|
||
| M9 | **gRPC 未实现**:proto 定义了 NotificationService,但无 gRPC controller | pending-features P5 未强制 gRPC | 确认 P5 是否启用 gRPC(ai03 提请统一决策) |
|
||
| M10 | **README 与实现脱节**:README API 表标 `POST /notifications/:id/read`,实际是 PUT;漏 batch/user/:userId/user/:userId/page 端点;声称"消费 Kafka 事件"但无代码 | 文档误导 | 阶段 2 设计同步修正 README |
|
||
| M11 | **main.ts 与 LifecycleService 重复关闭资源**:两者都调 closeDb/closeEs | 重复关闭可能报错(虽有 try-catch) | 阶段 2 设计统一关闭逻辑到 LifecycleService |
|
||
| M12 | **~~proto 包名不一致~~(ai10 复核:失实条目)**:实际 `next_edu_cloud.msg.v1` 完全符合 project_rules §5 `next_edu_cloud.<domain>.v1` 规范(coord 已裁决采用 `next_edu_cloud.*` 格式) | 无(命名正确) | ✅ 命名正确,无需处理 |
|
||
|
||
---
|
||
|
||
## 服务审计表 — ai10(msg 复核)
|
||
|
||
> 审计标准对照 [project_rules §3](../../../.trae/rules/project_rules.md) 与 [known-issues §2.2 classes 黄金模板](../../../docs/troubleshooting/known-issues.md)
|
||
|
||
| 服务 | 权限装饰器 | 错误码前缀 | logger | metrics | tracer | /healthz | /readyz | 优雅关闭 | 测试覆盖率 | Dockerfile |
|
||
| ---- | --------------- | ---------- | ------- | -------------- | ------------------------------- | -------- | ---------------- | --------------------------------------- | ---------- | ---------- |
|
||
| msg | ✅ 6 端点全覆盖 | ✅ `MSG_*` | ✅ pino | ✅ prom-client | ✅ OTel + auto-instrumentations | ✅ | ⚠️ 仅 DB 不查 ES | ⚠️ main.ts 与 LifecycleService 重复关闭 | 0% | ✅ |
|
||
|
||
## ai10 复核结论(ai-allocation §5 设计重点对照)
|
||
|
||
ai-allocation.md §5 对 ai10(msg)明确列出 6 项设计重点,对照现有骨架:
|
||
|
||
| ai-allocation §5 设计重点 | 现状 | 阶段 2 是否需设计 |
|
||
| --------------------------------------------------- | ------------------------------ | ----------------- |
|
||
| 通知渠道抽象(站内信/邮件/短信,策略模式) | ❌ 无抽象,硬编码 | ✅ 必须 |
|
||
| ES 降级查询策略(DB 不可用时走 ES 索引) | ❌ 仅 ES 降级,无 DB 降级到 ES | ✅ 必须 |
|
||
| Kafka 消费幂等设计(event_id 去重 via Redis SETNX) | ❌ 无 Kafka 代码 | ✅ 必须 |
|
||
| 与 ai02(push-gateway)的推送通道协议(gRPC) | ⚠️ 用 fetch POST 降级,无 gRPC | ✅ 需 gRPC 化 |
|
||
| 通知模板管理(模板 CRUD + 变量替换) | ❌ 无模板模块 | ✅ 必须 |
|
||
| 已读/未读状态管理(Redis 位图) | ❌ 仅 DB boolean | ✅ 必须 |
|
||
|
||
**新增差距(ai10 补充,原 ai05 文档遗漏)**:
|
||
|
||
| # | 差距 | 影响 |
|
||
| --- | ------------------------------------------------------------------------------- | ----------------------------------------------------- |
|
||
| M13 | **无通知模板模块**:缺 msg_notification_templates 表、模板 CRUD、变量替换、i18n | ai-allocation §5 强制要求 |
|
||
| M14 | **无渠道策略抽象**:send 方法硬编码 channel 判断,无 ChannelStrategy 接口 | 多渠道扩展困难(未来加微信/钉钉需改核心) |
|
||
| M15 | **无 Redis 位图已读/未读**:isRead 仅 DB boolean,无 unread 计数缓存 | 高频未读数查询打 DB,广播场景性能瓶颈 |
|
||
| M16 | **无 DB→ES 降级读路径**:仅有 ES→空降级,无 DB 故障时走 ES 读模型 | ai-allocation §5 要求 DB 不可用走 ES 索引 |
|
||
| M17 | **无投放记录表**:缺 msg_notification_deliveries(per-channel 投递状态) | 无法追踪邮件/短信投递成功失败 |
|
||
| M18 | **无幂等键**:send 无 idempotencyKey 参数,重复请求会重复发通知 | 客户端重试导致重复通知 |
|
||
| M19 | **无通知优先级/分类**:缺 priority/category 字段 | 无法区分紧急通知与普通通知,无法按类退订 |
|
||
| M20 | **无调度/延迟发送**:缺 scheduledAt 字段与调度器 | 未来定时通知、每日摘要无法支持 |
|
||
| M21 | **权限模型过简**:仅 3 个权限点,硬编码 ROLE_PERMISSIONS | 应对齐 iam 的 RBAC + DataScope(DATA_SCOPE=SELF/ALL) |
|
||
|
||
---
|
||
|
||
## 跨模块契约对齐提请(coord 交叉审查)
|
||
|
||
### 接口一致性检查(msg 相关)
|
||
|
||
| 本服务声明 | 对方服务声明 | 是否匹配 | 备注 |
|
||
| -------------------------------------------- | ------------------------------- | --------- | ------------------------------------------------------------------------------------------------ |
|
||
| ai05 msg: 消费 `edu.teaching.exam.published` | ai03 core-edu: 发布考试事件 | ⚠️ 待确认 | ai03 提请 topic 命名统一(004 `edu.teaching.exam.published` vs core-edu 代码 `edu.exam.events`) |
|
||
| ai05 msg: 消费 `edu.identity.user.created` | ai02 iam: 发布用户创建事件 | ⚠️ 待确认 | ai02 设计需确认 topic |
|
||
| ai05 msg: 调 push-gateway `/internal/push` | ai01 push-gateway: 暴露推送端点 | ✅ 已实现 | msg 当前用 fetch POST,push-gateway 已有 /internal/push 端点 |
|
||
|
||
### 全局冲突检查(msg 相关)
|
||
|
||
| 检查项 | 检查结果 | 备注 |
|
||
| -------------------- | --------------- | -------------------------------------------------------------------------------------------------------- |
|
||
| 端口不冲突 | ✅ msg 3007 | 与 iam(3002)/classes(3001)/teacher-bff(3003)/core-edu(3004)/content(3005)/data-ana(3006)/ai(3008) 不冲突 |
|
||
| Topic 不重复 | ⚠️ 待汇总 | msg 仅消费不发布 |
|
||
| 错误码前缀不重叠 | ✅ `MSG_*` 唯一 | 与 iam `IAM_*` / core-edu `CORE_EDU_*` / content `CONTENT_*` 不重叠 |
|
||
| Proto message 不遗漏 | ⚠️ 待确认 | events.proto 无 NotificationEvent,若 msg 发事件需补充 |
|
||
| proto 包名规范 | ⚠️ 不一致 | 实际 `next_edu_cloud.<domain>.v1`,规则要求 `edu.<domain>.v1`,**提请 coord 仲裁**(ai03 已提请) |
|
||
|
||
### 待 coord 仲裁的决策项(msg 相关)
|
||
|
||
1. ~~**proto 包名统一**:`next_edu_cloud.*` vs `edu.*`~~(ai10 复核:coord 已裁决 `next_edu_cloud.*`,本项目全部 proto 已采用,无需再仲裁)
|
||
2. **gRPC 启用时机**:P4/P5 是否启用 gRPC controller,还是继续 REST(影响 content/msg/iam/core-edu 全部服务)
|
||
3. ~~**msg Outbox**:是否需要~~(ai10 复核:004 §7.2 已明确 msg 生产 `edu.notification.events`,**必须有 Outbox**,无需仲裁)
|
||
4. **msg Redis 引入**:env.ts 预留 REDIS_URL 但无依赖,幂等去重用 Redis SETNX 还是 DB 唯一索引(ai10 倾向 Redis SETNX + DB 唯一索引双保险,待 coord 确认 Redis 是否纳入 P5 基础设施)
|
||
5. **events.proto 补充**:msg 发 NotificationRequested 事件需追加 NotificationEvent message(coord 维护 proto)
|
||
|
||
---
|
||
|
||
## 下一步
|
||
|
||
1. ~~等待 coord 审核本确认书~~(ai10 复核修订完成,进入阶段 2)
|
||
2. 进入**阶段 2:模块架构设计文档**,按 ai-allocation.md §7 模板产出 [02-architecture.md](./02-architecture.md):
|
||
- msg 模块架构设计文档(含通知渠道抽象策略模式、ES 降级查询、Kafka 消费幂等、push-gateway 推送协议、通知模板、已读/未读状态管理)
|
||
3. 阶段 2 设计完成后同步更新 README(修正 §M10 文档脱节问题)
|
||
|
||
---
|
||
|
||
**AI Agent**: ai10 (msg)
|
||
**Coordinator**: coord-ai
|
||
**Branch**: 单仓库并行模式(直接 push main)
|