# 公告和消息模块审计报告 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`：对应节点同步更新
