Files
NextEdu/docs/architecture/audit/announcements-messages-audit-report.md
SpecialX fde711ce46 feat(announcements,messaging): 公告与消息模块审计重构 — i18n + Error Boundary + a11y
- 新增审计报告 docs/architecture/audit/announcements-messages-audit-report.md
- 新增中英双语 i18n 字典 announcements.json / messages.json(11/13 个命名空间)
- 重构所有 announcements 和 messaging 组件接入 next-intl(useTranslations)
- 所有页面 page.tsx 使用 generateMetadata + getTranslations 替代硬编码 metadata
- 新增 7 个 error.tsx 错误边界(4 公告 + 3 消息),统一 EmptyState + i18n + 重试
- a11y 改进:announcement-card / message-list / notification-dropdown 添加 aria-label
- 同步架构图 004 和 005:i18n.messages 清单 + 已知问题修复记录
2026-06-22 16:02:07 +08:00

24 KiB
Raw Permalink Blame History

公告和消息模块审计报告

审查日期2026-06-22 审查范围:src/modules/announcements/**src/modules/messaging/**src/modules/notifications/**src/app/(dashboard)/announcements/**src/app/(dashboard)/admin/announcements/**src/app/(dashboard)/messages/** 架构图参考:docs/architecture/004_architecture_impact_map.md §2.13 / §2.14 / §2.16、docs/architecture/005_architecture_data.json


一、现有实现概要

1.1 文件分布

路径 文件数 说明
路由层 - 用户端公告 src/app/(dashboard)/announcements/ 2 个 page.tsx + 1 个 loading.tsx 列表 + 详情,所有角色共用
路由层 - 管理端公告 src/app/(dashboard)/admin/announcements/ 2 个 page.tsx + 1 个 loading.tsx 管理列表 + 编辑
路由层 - 消息 src/app/(dashboard)/messages/ 3 个 page.tsx + 3 个 loading.tsx + 1 个 error.tsx 列表 + 详情 + 撰写
模块层 - announcements src/modules/announcements/ 4 个核心文件 + 5 个组件 actions(296行) / data-access(197行) / types(61行) / schema(45行)
模块层 - messaging src/modules/messaging/ 4 个核心文件 + 6 个组件 actions(312行) / data-access(246行) / types(52行) / schema(44行)
模块层 - notifications src/modules/notifications/ 6 个核心文件 + 5 个渠道文件 actions(159行) / data-access(174行) / dispatcher(152行) / preferences(191行) / types(153行)

1.2 数据流

[Route] /announcements/page.tsx
   └─▶ announcements/data-access.getAnnouncements (status=published, audience={gradeId,classId})
         └─▶ classes/data-access.getClassGradeId / getStudentActiveClassId / getStudentActiveGradeId

[Route] /admin/announcements/page.tsx
   ├─▶ announcements/data-access.getAnnouncements
   ├─▶ school/data-access.getGrades
   └─▶ classes/data-access.getAdminClasses
   (页面层直接编排 3 个模块的 data-access

[Route] /messages/page.tsx
   ├─▶ messaging/data-access.getMessages
   └─▶ notifications/data-access.getNotifications
   (页面层直接编排 2 个模块的 data-access

[Route] /messages/compose/page.tsx
   └─▶ messaging/data-access.getRecipients
         └─▶ classes/data-access.getStudentIdsByClassIds / getTeacherIdsByClassIds / getClassesByGradeId / getStudentActiveClassId
         └─▶ users/data-access.getUserNamesByIds

[Action] announcements/actions.createAnnouncementAction
   └─▶ notifications.sendBatchNotifications (发布公告时批量通知)

[Action] messaging/actions.sendMessageAction
   └─▶ notifications.dispatcher.sendNotification (发消息时通知收件人)

1.3 架构图记录情况

004_architecture_impact_map.md 对三个模块的记录较为完整:

  • §2.13 messaging记录了 P0-4 / P1-5 已修复的双向依赖问题,文件清单准确
  • §2.14 notifications记录了渠道抽象和从 messaging 迁移的历史
  • §2.16 announcements记录了模块职责和依赖关系

但存在以下遗漏

  • 未记录 messaging 组件目录下 notification-dropdown.tsxunread-message-badge.tsx 两个组件
  • 未记录 announcements 模块的 components/ 子目录5 个组件文件未在文件清单中列出)
  • 未记录消息列表的客户端搜索行为(getMessagesAction 在客户端被调用)
  • 未记录通知下拉菜单的 30 秒轮询机制

二、现存问题与原因分析

2.1 国际化完全缺失P0

位置 问题 违反规则
announcements/components/announcement-list.tsx L24-29 "All" / "Published" / "Draft" / "Archived" 硬编码 "所有用户可见文本必须适配 i18n使用 next-intl提取翻译键"
announcements/components/announcement-detail.tsx L29-38 STATUS_LABEL / TYPE_LABEL 全英文硬编码 同上
announcements/components/announcement-card.tsx L9-28 STATUS_LABEL / TYPE_LABEL 重复定义且硬编码 同上
announcements/components/announcement-form.tsx L86,92,98,108 "New Announcement" / "Title" / "Content" 等硬编码 同上
messaging/components/message-list.tsx L81-88 "Inbox" / "Sent" / "Compose" 硬编码 同上
messaging/components/message-detail.tsx L38,74,99-106 "From" / "To" / "Message" / "New" / "Read" / "Sent" 硬编码 同上
messaging/components/message-compose.tsx L78,84,102,113 "Reply" / "New Message" / "To" / "Subject" 硬编码 同上
messaging/components/notification-list.tsx L25-30,69-70 TYPE_LABEL 硬编码,"Notifications" 标题硬编码 同上
messaging/components/notification-dropdown.tsx L113 "Notifications" / "Mark all read" 硬编码 同上
src/shared/i18n/messages/ announcements.jsonmessages.json 翻译文件结构不完整
i18n/request.ts L22-29 未加载 announcements/messages 翻译文件 翻译文件未注册

后果:所有用户可见文本无法切换语言,中文用户看到全英文界面,严重影响 K12 学校教师/家长/学生的使用体验。同一组件中 STATUS_LABEL 重复定义card 和 detail 各一份),维护成本高。

2.2 角色硬编码与配置驱动缺失P0

位置 代码 违反规则
layout/config/navigation.ts L39 NAV_CONFIG: Partial<Record<Role, NavItem[]>> 按角色分组 "前端权限判断统一使用 usePermission().hasPermission(),严禁出现 role === 'xxx' 硬编码"
同上 L99-103, L247-251, L307-311, L343-347 admin/teacher/student/parent 各自配置 AnnouncementsMessages 导航项 配置未抽象,新增角色需复制粘贴

后果:新增角色(如 grade_head 已存在)无法享受公告/消息导航;导航配置按角色而非权限驱动,违反"配置驱动设计"原则。

2.3 架构分层页面层越权编排P1

位置 问题 违反规则
admin/announcements/page.tsx L33-37 页面层 Promise.all 调用 announcements/school/classes 三个模块的 data-access "app/ 只能调用 modules/ 的 Server Actions 和 data-access" — 虽语法允许,但编排逻辑应在模块 actions 层完成
messages/page.tsx L17-20 页面层并行调用 messaging 和 notifications 两个模块的 data-access 同上
announcements/page.tsx L27-76 resolveAudience 函数包含 50 行业务逻辑(根据 dataScope 解析受众) 纯逻辑应抽为 hooks 或 data-access 层函数
announcements 模块无 getAdminAnnouncementsPageData 编排函数 缺失编排层 "模块标准结构"要求 actions.ts 承担编排职责

后果:页面层臃肿、逻辑不可复用、不可测试;多个页面需要相同数据时需复制编排逻辑。

2.4 模块间组件耦合P1

位置 问题 违反规则
messaging/components/notification-list.tsx L16 直接 import type { Notification, NotificationType } from "@/modules/notifications/types" "模块内部组件绝不直接 import 其他业务模块的 actions 或 data-access只能通过注入的接口调用"
messaging/components/notification-dropdown.tsx L27 同上,直接 import notifications 模块类型 同上
messaging/components/notification-list.tsx L15 直接 import ../actions 中的 markAllNotificationsAsReadAction / markNotificationAsReadAction messaging 模块的 actions re-export 了 notifications 的 actions造成职责混乱
messaging/actions.ts L196-248 messaging 模块定义了 6 个通知相关 ActiongetNotificationsAction / markNotificationAsReadAction 等) 通知 Action 应由 notifications 模块提供messaging 仅负责私信

后果messaging 和 notifications 模块在 UI 层和 Action 层深度耦合无法独立替换或测试notifications 模块的 UI 组件无法复用到其他场景。

2.5 错误边界缺失P1

位置 问题 违反规则
src/app/(dashboard)/announcements/error.tsx 缺失 "每个独立的数据区块必须用 React Error Boundary 包裹"
src/app/(dashboard)/announcements/[id]/error.tsx 缺失 同上
src/app/(dashboard)/admin/announcements/error.tsx 缺失 同上
src/app/(dashboard)/admin/announcements/[id]/error.tsx 缺失 同上
src/app/(dashboard)/messages/[id]/error.tsx 缺失 同上
src/app/(dashboard)/messages/compose/error.tsx 缺失 同上
src/app/(dashboard)/admin/announcements/loading.tsx 缺失(仅有用户端 loading 加载骨架屏不完整

后果:数据加载失败时整页崩溃,用户体验差;无权限访问时显示原始错误而非友好提示。

2.6 通知轮询性能问题P1

位置 问题 违反规则
notification-dropdown.tsx L65-68 每 30 秒轮询 getNotificationsAction + getUnreadNotificationCountAction "性能:优先使用 React Server Components 获取初始数据"
unread-message-badge.tsx L31-33 每 60 秒轮询 getUnreadMessageCountAction 同上
两个组件未使用 RSC 初始数据 客户端首次渲染无数据,需等待轮询 "客户端组件仅负责交互"

后果:多用户同时在线时,每分钟产生大量无效请求;首屏渲染时无数据,显示空状态闪烁。

2.7 公告表单校验不足P1

位置 问题 违反规则
schema.ts L9-10 targetGradeId / targetClassId 为 optional未根据 type 做条件必填校验 "输入使用 Zod 验证,验证失败返回结构化错误"
announcement-form.tsx L49-54 type === "grade" 时不强制选择年级,type === "class" 时不强制选择班级 同上
actions.ts L43-61 resolveTargetUserIdstype === "grade"targetGradeId 为空时返回空数组,公告无人接收 数据完整性缺失

后果:管理员可能创建无受众的公告,发布公告后无人收到通知,且无任何错误提示。

2.8 消息列表搜索逻辑复杂且无分页 UIP1

位置 问题 违反规则
message-list.tsx L38-58 客户端 useEffect + setTimeout 防抖搜索,但未取消已发出的请求 "可测试性:数据获取、计算、格式化等纯逻辑全部放入纯函数或 hooks"
同上 L71-74 filtered 在客户端再次过滤 displayMessages,与已搜索结果重复过滤 逻辑冗余
同上 L17 初始加载 pageSize: 50,但无分页 UI超过 50 条无法查看 "明确处理空数据、无权限、网络异常等边界状态"
messages/page.tsx L18 一次性加载 50 条消息,无虚拟滚动 性能问题

后果:消息超过 50 条时用户无法查看历史;搜索逻辑与 UI 混合,无法单独测试。

2.9 无权限与空状态处理不友好P1

位置 问题 违反规则
所有页面 requirePermission 抛出 PermissionDeniedError 后,由上层 error.tsx 处理,但无专门的无权限空状态 "明确处理空数据、无权限、网络异常等边界状态"
message-list.tsx L116-127 空状态文本硬编码且未区分"无权限"与"无数据" 同上
notification-list.tsx L80-86 通知空状态未提供"去设置通知偏好"等引导操作 用户体验不完整

后果:用户无法区分"无数据"和"无权限",无法找到下一步操作引导。

2.10 可访问性问题P2

位置 问题 违反规则
message-list.tsx L104-110 搜索框无 aria-label,仅靠 placeholder "可访问性a11y语义化标签、ARIA 属性、键盘导航"
notification-dropdown.tsx L139-144 DropdownMenuItemonSelect 阻止默认行为后手动调用 handleMarkRead,键盘导航时焦点处理不明确 同上
announcement-card.tsx L66-72 整个 Card 作为链接,但无 aria-label 描述跳转目标 同上
notification-list.tsx L118-124 "Mark as read" 按钮无 aria-label,屏幕阅读器无法识别 同上

后果:视障用户无法有效使用公告和消息功能,不符合 WCAG 2.1 AA 标准。

2.11 监控埋点缺失P2

位置 问题 违反规则
announcements/actions.ts 发布/归档/删除公告无埋点 "监控:方案中预留关键操作埋点接口"
messaging/actions.ts 发送/删除消息无埋点 同上
notifications/data-access.ts L167-173 console.info 输出发送日志,无结构化埋点 同上

后果:无法追踪公告阅读率、消息回复率等关键指标;通知发送失败无法告警。

2.12 消息软删除无事务P2

位置 问题 违反规则
messaging/data-access.ts L180-191 deleteMessage 执行两个独立的 UPDATEsenderDeletedAt + receiverDeletedAt无事务 "安全性:所有敏感数据查询必须在 data-access 层结合当前用户权限过滤"
同上 两个 UPDATE 之间可能部分失败,导致数据不一致 数据完整性问题

后果:发送方删除后接收方可能仍可见,或反之,造成数据不一致。

2.13 测试覆盖不足P2

位置 问题 违反规则
tests/e2e/announcements.spec.ts 仅 2 个测试(未登录重定向 + 登录后可见),无管理端测试 "可测试性"
tests/e2e/ 无 messaging 模块 E2E 测试 同上
src/modules/announcements/ 无单元测试 同上
src/modules/messaging/ 无单元测试 同上
src/modules/notifications/ 无单元测试 同上

后果:重构时无回归保障,关键业务逻辑(权限过滤、受众解析、通知分发)错误无法及时发现。


三、行业差距对比

3.1 公告模块差距

功能 行业优秀实践 当前状态 影响
公告分类标签 支持自定义标签(紧急、活动、政策),可按标签筛选 仅 typeschool/grade/class和 status无标签 教师无法快速筛选紧急公告
已读回执 显示已读/未读用户列表,支持提醒未读 无已读回执,仅通知发送 管理员无法知道公告是否被阅读
富文本编辑 支持富文本、图片、附件 仅纯文本 Textarea 公告内容单调,无法插入图片
定时发布 支持指定时间自动发布 publishedAt 字段存在但表单未暴露 管理员无法提前安排公告
公告置顶 支持置顶重要公告 无置顶功能 重要公告可能被新公告淹没
多渠道推送 站内 + 短信 + 邮件 + 微信 已实现多渠道notifications 模块) 已达标
评论互动 支持公告下评论或确认收到 无互动功能 无法收集公告反馈

3.2 消息模块差距

功能 行业优秀实践 当前状态 影响
消息分组 按联系人分组显示对话 仅按时间列表,无对话分组 教师与同一家长的来回消息散落各处
实时推送 WebSocket / SSE 实时推送 30/60 秒轮询 消息延迟最高 30 秒,服务器压力大
消息草稿 支持草稿自动保存 无草稿功能 用户意外离开页面内容丢失
附件支持 支持发送文件附件 仅纯文本 无法发送作业截图等
消息星标 支持标记重要消息 无星标功能 重要消息无法快速找回
消息模板 支持常用消息模板 无模板 教师重复输入相同内容
群发消息 支持按班级/年级群发 仅支持单发 教师需逐个发送通知
消息搜索 全文搜索 + 按联系人/时间筛选 仅关键词搜索 subject + content 无法按联系人筛选历史消息
已读回执 实时显示对方已读状态 readAt 字段,无实时更新 发送方不知道消息是否被看到

3.3 通知模块差距

功能 行业优秀实践 当前状态 影响
通知分类管理 支持按类型分组(作业/成绩/公告/消息) 仅按时间列表,类型仅作为 Badge 用户无法快速找到特定类型通知
通知静音 支持单类通知静音 quietHours 但仅全局免打扰 用户想静音作业通知但保留成绩通知无法实现
通知归档 支持归档已处理通知 仅标记已读,无归档 通知列表越来越长
通知优先级 支持高/中/低优先级 无优先级 紧急通知被普通通知淹没
桌面推送 支持浏览器桌面通知 仅站内下拉 用户不打开页面就收不到通知

3.4 多角色体验差距

角色 痛点 当前状态 影响
admin 公告管理需切换到独立页面 /admin/announcements/announcements 分离 管理员查看用户视角需切换路由
teacher 消息收件人列表无法搜索 MessageCompose 仅 Select 下拉 班级多时难以找到目标家长
parent 无法主动给教师发消息 依赖 getRecipients 返回的列表 家长需等待教师先发消息才能回复
student 公告无"确认收到"按钮 仅被动查看 学校无法确认学生是否看到公告

四、改进优先级建议

P0紧急影响核心功能与安全

  1. i18n 全覆盖:创建 announcements.jsonmessages.json 翻译文件,重构所有组件使用 useTranslations 替换硬编码文本,更新 i18n/request.ts 加载新文件。
  2. 消除角色硬编码:将 NAV_CONFIG 改为权限驱动配置,公告和消息导航项仅声明 permission,不按角色分组。
  3. 补充错误边界:为所有缺失的页面添加 error.tsx,区分"无权限"、"未找到"、"网络错误"三种状态。

P1重要影响架构与体验

  1. 解耦 messaging 与 notifications:将通知相关组件(notification-list.tsxnotification-dropdown.tsx)迁移至 notifications 模块messaging 模块仅保留私信组件;通过 Context 注入数据服务接口。
  2. 页面编排下沉:在 announcements 和 messaging 模块新增 getAdminAnnouncementsPageData / getMessagesPageData 编排函数,页面层仅调用单一函数。
  3. 公告表单条件校验:使用 Zod superRefine 根据 type 强制要求 targetGradeId / targetClassId
  4. 消息列表分页与虚拟滚动:添加分页 UI超过 50 条时支持加载更多;搜索逻辑抽离为 useMessageSearch hook。
  5. 通知实时推送:将 30 秒轮询替换为 SSE 或 WebSocket减少无效请求首屏使用 RSC 获取初始数据。
  6. 消息软删除事务化:使用数据库事务包裹 senderDeletedAtreceiverDeletedAt 更新。

P2优化提升完整性与可维护性

  1. a11y 改进:为搜索框、按钮、链接添加 aria-label;确保键盘导航完整。
  2. 监控埋点:在关键 Action 中预留 trackEvent 接口,记录发布公告、发送消息、标记已读等操作。
  3. 测试覆盖:补充 messaging 模块 E2E 测试;为 resolveTargetUserIdsgetRecipientsselectChannels 等纯函数添加单元测试。
  4. 行业功能补齐:公告已读回执、消息分组对话、消息草稿、通知优先级(按业务优先级逐步实施)。
  5. 架构图同步:补充 announcements 组件目录、messaging 的 notification-dropdown/unread-message-badge 组件、客户端搜索行为、轮询机制。

五、架构图同步说明

本次审计发现架构图存在以下遗漏,需补充:

5.1 004_architecture_impact_map.md 需补充

§2.13 messaging 模块文件清单

  • 当前记录:actions.ts 276 行 / data-access.ts / schema.ts 41 行
  • 实际状态:actions.ts 312 行 / data-access.ts 246 行 / schema.ts 44 行 / types.ts 52 行
  • 遗漏组件components/notification-dropdown.tsxcomponents/unread-message-badge.tsx 未在文件清单中列出
  • 遗漏行为notification-dropdown.tsx 每 30 秒轮询、unread-message-badge.tsx 每 60 秒轮询

§2.16 announcements 模块文件清单

  • 当前记录:仅列出 actions/data-access/schema/types
  • 遗漏组件目录components/ 下 5 个组件(admin-announcements-view.tsxannouncement-card.tsxannouncement-detail.tsxannouncement-form.tsxannouncement-list.tsx)未列出

§2.13 messaging 依赖关系

  • 遗漏messaging/components/notification-list.tsxnotification-dropdown.tsx 直接 import @/modules/notifications/types,存在跨模块 UI 类型依赖

5.2 005_architecture_data.json 需补充

  • modules.messaging.components 数组缺少 notification-dropdown.tsxunread-message-badge.tsx 两个节点
  • modules.announcements.components 数组完全缺失5 个组件节点未记录)
  • modules.messaging.exports 缺少 UnreadMessageBadge 组件导出
  • routes 节点中 /messages 路由的 dataAccess 字段未记录客户端搜索行为(getMessagesAction 在客户端被调用)

5.3 无需修改的部分

  • §2.14 notifications 模块记录完整准确
  • P0-4 / P1-5 修复历史记录准确
  • 依赖矩阵§3中 messaging → notifications 的单向依赖记录正确