252 lines
12 KiB
Markdown
252 lines
12 KiB
Markdown
# 公告和消息模块审计报告 V3
|
||
|
||
> 审查日期:2026-06-22
|
||
> 审查范围:V2 改进后的 `src/modules/announcements/**`、`src/modules/messaging/**`、`src/modules/notifications/**`、对应路由层、i18n 翻译文件
|
||
> 前置文档:`announcements-messages-audit-report.md`(V1)、`announcements-messages-audit-report-v2.md`(V2)
|
||
> 架构图参考:`docs/architecture/004_architecture_impact_map.md` §2.13 / §2.14 / §2.16
|
||
|
||
---
|
||
|
||
## 一、V2 完成情况复核
|
||
|
||
| V2 编号 | 标题 | 状态 |
|
||
|---------|------|------|
|
||
| V2-P0-1 | 通知 i18n 命名空间独立 | ✅ 已完成 |
|
||
| V2-P0-2 | 通知标题 i18n 化 | ✅ 已完成 |
|
||
| V2-P1-1 | AnnouncementList 过滤模式统一 | ✅ 已完成 |
|
||
| V2-P1-2 | MessageList 过滤冗余移除 | ✅ 已完成 |
|
||
| V2-P1-3 | 消息详情页编排下沉 | ✅ 已完成 |
|
||
| V2-P1-4 | 表单服务端校验错误展示 | ✅ 已完成 |
|
||
| V2-P2-1 | 轮询间隔常量化 | ✅ 已完成 |
|
||
| V2-P2-2 | 架构图同步 | ✅ 已完成 |
|
||
| V2-P2-13b | 通知优先级和归档 | ✅ 已完成 |
|
||
| V2-P2-13c | 通知分类筛选 + 桌面推送 | ✅ 已完成 |
|
||
| V2-P2-13d | 公告置顶 + 已读回执 | ✅ 已完成 |
|
||
|
||
V2 共 11 项已全部实施。
|
||
|
||
---
|
||
|
||
## 二、V3 新发现问题
|
||
|
||
### 2.1 `saveMessageDraftAction` 中 `as` 断言违规(P0)
|
||
|
||
| 位置 | 代码 | 违反规则 |
|
||
|------|------|----------|
|
||
| [messaging/actions.ts](file:///e:/Desktop/CICD/src/modules/messaging/actions.ts) L279-283 | `formData.get("draftId") as string \| null` 等 5 处断言 | "禁止 `as` 断言(除非从 `unknown` 转换)" |
|
||
|
||
**问题分析**:
|
||
- `FormData.get()` 返回类型为 `string | File | null`
|
||
- 代码直接断言为 `string | null`,若字段为 File 类型会导致运行时错误
|
||
- 应使用类型守卫或 Zod 校验进行安全转换
|
||
|
||
**后果**:类型不安全,上传场景下可能运行时崩溃;违反 TypeScript 严格模式规则。
|
||
|
||
### 2.2 公告列表页 `resolveAudience` 业务逻辑未下沉(P0)
|
||
|
||
| 位置 | 问题 | 违反规则 |
|
||
|------|------|----------|
|
||
| [announcements/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/announcements/page.tsx) L29-78 | `resolveAudience` 函数 50 行业务逻辑在路由层 | "app/ 只能调用 modules 的 Server Actions 和 data-access,不直接访问数据库" + "页面层编排应下沉" |
|
||
|
||
**问题分析**:
|
||
- `resolveAudience` 包含根据 dataScope 解析受众的复杂业务逻辑(5 种 dataScope 分支)
|
||
- 直接调用 `classes` 模块的 3 个 data-access 函数(`getStudentActiveClassId`、`getStudentActiveGradeId`、`getClassGradeId`)
|
||
- V2-P1-5 已为管理端和编辑页创建编排函数,但用户端列表页遗漏
|
||
|
||
**后果**:路由层承担业务逻辑,违反三层架构;逻辑无法复用;测试困难。
|
||
|
||
### 2.3 通知偏好 Action 放置位置错误(P1)
|
||
|
||
| 位置 | 问题 | 违反规则 |
|
||
|------|------|----------|
|
||
| [messaging/actions.ts](file:///e:/Desktop/CICD/src/modules/messaging/actions.ts) L334-396 | `getNotificationPreferencesAction` / `updateNotificationPreferencesAction` 在 messaging 模块 | "模块标准结构" — 通知偏好属于 notifications 模块 |
|
||
|
||
**问题分析**:
|
||
- V1-P0-4 已将通知偏好 data-access 迁移到 `notifications/preferences.ts`
|
||
- 但对应的 Server Action 仍留在 `messaging/actions.ts`,使用 `MESSAGE_READ` 权限
|
||
- 消费方(settings 模块)通过 `SettingsService` 接口注入,但 Action 实现仍在 messaging
|
||
|
||
**后果**:模块边界混乱;messaging 模块承担了不属于它的通知偏好职责;权限语义不正确。
|
||
|
||
### 2.4 `sendBatchNotifications` 重复日志(P1)
|
||
|
||
| 位置 | 代码 | 违反规则 |
|
||
|------|------|----------|
|
||
| [notifications/dispatcher.ts](file:///e:/Desktop/CICD/src/modules/notifications/dispatcher.ts) L128 + L149 | `sendNotification` 内部调用 `logNotificationSendBatch`,`sendBatchNotifications` 又调用一次 | "代码质量规则" — 重复逻辑 |
|
||
|
||
**问题分析**:
|
||
- L128:`sendNotification` 末尾调用 `logNotificationSendBatch(results, ...)`
|
||
- L149:`sendBatchNotifications` 末尾再次调用 `logNotificationSendBatch(flatResults)`
|
||
- 批量发送时每条通知的日志被记录两次
|
||
|
||
**后果**:日志数据重复,影响监控准确性;浪费存储。
|
||
|
||
### 2.5 腾讯云短信 `SmsSdkAppId` 配置混淆(P1)
|
||
|
||
| 位置 | 代码 | 违反规则 |
|
||
|------|------|----------|
|
||
| [sms-channel.ts](file:///e:/Desktop/CICD/src/modules/notifications/channels/sms-channel.ts) L201, L203 | `SmsSdkAppId: this.config.templateCode` 和 `TemplateId: this.config.templateCode` | "类型安全" — 配置语义错误 |
|
||
|
||
**问题分析**:
|
||
- 腾讯云 SMS API 要求 `SmsSdkAppId`(应用 ID)和 `TemplateId`(模板 ID)是两个不同值
|
||
- 代码中两者都使用 `this.config.templateCode`(来自 `SMS_TEMPLATE_CODE` 环境变量)
|
||
- `getSmsConfig()` 缺少 `smsSdkAppId` 字段
|
||
|
||
**后果**:腾讯云短信发送必然失败(SmsSdkAppId 不等于模板 ID);生产环境无法使用腾讯云短信。
|
||
|
||
### 2.6 内联类型导入(P1)
|
||
|
||
| 位置 | 代码 | 违反规则 |
|
||
|------|------|----------|
|
||
| [messaging/actions.ts](file:///e:/Desktop/CICD/src/modules/messaging/actions.ts) L260 | `Promise<ActionState<import("./types").MessageDraft[]>>` | "TypeScript 规则 — 仅用于类型的导入必须使用 import type" |
|
||
| [messaging/data-access.ts](file:///e:/Desktop/CICD/src/modules/messaging/data-access.ts) L285 | `import("@/modules/notifications/types").Notification[]` | 同上 |
|
||
|
||
**后果**:可读性差,不符合 TypeScript 规范。
|
||
|
||
### 2.7 组件层 `as` 断言轻微违规(P2)
|
||
|
||
| 位置 | 代码 | 违反规则 |
|
||
|------|------|----------|
|
||
| [message-list.tsx](file:///e:/Desktop/CICD/src/modules/messaging/components/message-list.tsx) L66 | `setTab(v as Tab)` | "禁止 as 断言" |
|
||
| [notification-list.tsx](file:///e:/Desktop/CICD/src/modules/notifications/components/notification-list.tsx) L106 | `Object.keys(TYPE_ICON) as NotificationType[]` | 同上 |
|
||
|
||
**后果**:类型不安全;应使用类型守卫。
|
||
|
||
### 2.8 `logNotificationSend` 使用 console(P2)
|
||
|
||
| 位置 | 代码 | 违反规则 |
|
||
|------|------|----------|
|
||
| [notifications/data-access.ts](file:///e:/Desktop/CICD/src/modules/notifications/data-access.ts) L210, L230 | `console.info` / `console.error` | "监控" — 应使用统一日志服务 |
|
||
|
||
**后果**:生产环境日志分散,无法集中监控。
|
||
|
||
---
|
||
|
||
## 三、行业差距对比
|
||
|
||
结合 K12 教育系统特点,对比钉钉教育、企业微信教育版、智学网、班级小管家等产品:
|
||
|
||
| 功能 | 我们 | 行业标杆 | 影响 |
|
||
|------|------|---------|------|
|
||
| 消息已读回执 | ❌ 无 | ✅ 钉钉/企微支持 | 教师无法确认家长是否已读重要通知 |
|
||
| 消息模板 | ❌ 无 | ✅ 智学网预设模板 | 教师每次手写消息效率低 |
|
||
| 公告定时发布 | ❌ 仅即时发布 | ✅ 钉钉支持定时 | 无法提前编排非工作时间发布 |
|
||
| 公告附件 | ❌ 无 | ✅ 企微/钉钉支持 | 无法附带 PDF/图片等材料 |
|
||
| 公告分类标签 | ❌ 无 | ✅ 智学网分类 | 公告列表无法按类型快速筛选 |
|
||
| 消息群发多班级 | ❌ 单收件人 | ✅ 钉钉群发 | 教师需逐个发送,效率低 |
|
||
| 公告评论/确认 | ❌ 仅已读回执 | ✅ 钉钉确认回执 | 无法收集家长确认反馈 |
|
||
| 消息搜索 | ✅ 有(按主题) | ✅ 按内容搜索 | 基本满足 |
|
||
| 通知优先级 | ✅ 有(V2-P2-13b) | ✅ | 已对齐 |
|
||
| 通知归档 | ✅ 有(V2-P2-13b) | ✅ | 已对齐 |
|
||
| 桌面推送 | ✅ 有(V2-P2-13c) | ✅ | 已对齐 |
|
||
| SSE 实时推送 | ✅ 有(V2-P3) | ✅ | 已对齐 |
|
||
|
||
**主要差距**:消息已读回执、公告定时发布、公告附件为高价值缺失功能,直接影响家校沟通效率。
|
||
|
||
---
|
||
|
||
## 四、改进优先级建议
|
||
|
||
### P0(紧急,影响类型安全与架构合规)
|
||
|
||
1. **修复 `saveMessageDraftAction` 的 `as` 断言**:使用类型守卫替代 `formData.get() as string | null`,安全处理 File 类型。
|
||
2. **公告列表页 `resolveAudience` 下沉**:将受众解析逻辑迁移到 `announcements/data-access.ts`,新增 `getUserAnnouncementsPageData` 编排函数。
|
||
|
||
### P1(重要,影响模块边界与功能正确性)
|
||
|
||
3. **通知偏好 Action 迁移**:将 `getNotificationPreferencesAction` / `updateNotificationPreferencesAction` 从 `messaging/actions.ts` 迁移到 `notifications/actions.ts`,使用通知相关权限。
|
||
4. **修复 `sendBatchNotifications` 重复日志**:移除 `sendBatchNotifications` 中的重复 `logNotificationSendBatch` 调用。
|
||
5. **修复腾讯云短信 `SmsSdkAppId` 配置**:新增 `SMS_SDK_APP_ID` 环境变量和 `smsSdkAppId` 配置字段。
|
||
6. **消除内联类型导入**:改为顶部 `import type`。
|
||
|
||
### P2(优化,提升代码质量)
|
||
|
||
7. **组件 `as` 断言清理**:使用类型守卫替代。
|
||
8. **日志服务统一**:`logNotificationSend` 改用统一日志接口(预留接口,当前保持 console 但加注释标记为 TODO)。
|
||
|
||
---
|
||
|
||
## 五、架构图同步说明
|
||
|
||
本次审计发现以下架构图需更新:
|
||
|
||
1. **§2.13 messaging**:
|
||
- 移除 `getNotificationPreferencesAction` / `updateNotificationPreferencesAction`(迁移至 notifications)
|
||
- 更新 `actions.ts` 行数(减少约 60 行)
|
||
- 新增 `saveMessageDraftAction` 类型安全改进说明
|
||
|
||
2. **§2.14 notifications**:
|
||
- 新增 `getNotificationPreferencesAction` / `updateNotificationPreferencesAction`
|
||
- 更新 `actions.ts` 行数(增加约 60 行)
|
||
- 修复 `sendBatchNotifications` 重复日志说明
|
||
- 新增 `smsSdkAppId` 配置字段说明
|
||
|
||
3. **§2.16 announcements**:
|
||
- 新增 `getUserAnnouncementsPageData` 编排函数
|
||
- 更新 `data-access.ts` 行数
|
||
- 更新用户端列表页说明(使用编排函数)
|
||
|
||
4. **附录 A 依赖矩阵**:
|
||
- messaging 对 notifications 的依赖减少(不再包含通知偏好 Action)
|
||
- announcements 对 classes 的依赖改为通过编排函数间接调用
|
||
|
||
---
|
||
|
||
## 六、实施记录
|
||
|
||
以下为 V3 审计报告的实施记录,所有修复均已完成并通过 `npx tsc --noEmit` 和 `npm run lint` 验证。
|
||
|
||
### V3-P0-1:修复 `saveMessageDraftAction` 的 `as` 断言
|
||
|
||
**文件**:`src/modules/messaging/actions.ts`
|
||
|
||
**变更**:将 L279-283 的 5 处 `formData.get("xxx") as string | null` 替换为类型守卫函数 `getStringFromFormData`,安全处理 `File` 类型。
|
||
|
||
### V3-P0-2:公告列表页 `resolveAudience` 下沉
|
||
|
||
**文件**:
|
||
- `src/modules/announcements/data-access.ts`:新增 `getUserAnnouncementsPageData` 编排函数
|
||
- `src/app/(dashboard)/announcements/page.tsx`:移除 `resolveAudience`,改用编排函数
|
||
|
||
### V3-P1-3:通知偏好 Action 迁移
|
||
|
||
**文件**:
|
||
- `src/modules/notifications/actions.ts`:新增 `getNotificationPreferencesAction` / `updateNotificationPreferencesAction`
|
||
- `src/modules/messaging/actions.ts`:移除上述 2 个 Action 及相关 import
|
||
|
||
### V3-P1-4:修复 `sendBatchNotifications` 重复日志
|
||
|
||
**文件**:`src/modules/notifications/dispatcher.ts`
|
||
|
||
**变更**:移除 `sendBatchNotifications` 中的重复 `logNotificationSendBatch` 调用(L149)。
|
||
|
||
### V3-P1-5:修复腾讯云短信 `SmsSdkAppId` 配置
|
||
|
||
**文件**:`src/modules/notifications/channels/sms-channel.ts`
|
||
|
||
**变更**:`getSmsConfig()` 新增 `smsSdkAppId` 字段(来自 `SMS_SDK_APP_ID` 环境变量),`SmsSdkAppId` 使用独立配置值。
|
||
|
||
### V3-P1-6:消除内联类型导入
|
||
|
||
**文件**:
|
||
- `src/modules/messaging/actions.ts`:L260 内联 import 改为顶部 `import type`
|
||
- `src/modules/messaging/data-access.ts`:L285 内联 import 改为顶部 `import type`
|
||
|
||
### V3-P2-7:组件 `as` 断言清理
|
||
|
||
**文件**:
|
||
- `src/modules/messaging/components/message-list.tsx`:L66 `setTab(v as Tab)` 改为类型守卫
|
||
- `src/modules/notifications/components/notification-list.tsx`:L106 `Object.keys(TYPE_ICON) as NotificationType[]` 改为类型守卫
|
||
|
||
### V3-P2-8:日志服务统一(预留)
|
||
|
||
**文件**:`src/modules/notifications/data-access.ts`
|
||
|
||
**变更**:`console.info` / `console.error` 添加 TODO 注释标记,预留统一日志服务接入点。
|
||
|
||
### 架构图同步
|
||
|
||
**文件**:
|
||
- `docs/architecture/004_architecture_impact_map.md`:§2.13 / §2.14 / §2.16 同步更新
|
||
- `docs/architecture/005_architecture_data.json`:对应节点同步更新
|