601 lines
28 KiB
Markdown
601 lines
28 KiB
Markdown
# 新增模块与其他模块架构审查报告
|
||
|
||
> 审查范围:elective / proctoring / diagnostic / notifications / dashboard / messaging / parent / settings / files / auth / layout / student
|
||
> 审查日期:2026-06-17
|
||
> 审查依据:源码全量扫描 + 架构影响地图(004/005) + 差距审计报告(007)
|
||
> 审查目标:识别职责不单一、过耦合、边界模糊、幽灵路由等问题
|
||
|
||
---
|
||
|
||
## 一、总体评估
|
||
|
||
| 维度 | 模块数 | 严重问题 | 中等问题 | 轻微问题 | 总体评价 |
|
||
|------|--------|---------|---------|---------|----------|
|
||
| 新增模块 | 4 | 3 | 5 | 3 | ⚠️ 职责基本清晰,但存在跨模块耦合与未集成代码 |
|
||
| 其他模块 | 8 | 1 | 4 | 3 | ⚠️ dashboard 跨模块直查问题突出 |
|
||
| **合计** | **12** | **4** | **9** | **6** | **需重点修复 4 项严重问题** |
|
||
|
||
### 严重问题清单(必须修复)
|
||
|
||
1. ~~**messaging 与 notifications 边界模糊、双向依赖** — 两个模块都写入 `messageNotifications` 表,notifications 反向依赖 messaging,类型系统不一致~~ ✅ 已修复(P0-5 + P1-6)
|
||
2. ~~**dashboard/data-access.ts 直查 11 张跨模块表** — 违反模块封装原则~~ ✅ 已修复(P0-4)
|
||
3. **proctoring/exam-mode-config.tsx 未集成到考试表单** — DB schema 有 examMode 等字段但无 UI 录入入口,组件成为死代码 ⚠️ 用户决定保留
|
||
4. **proctoring 事件上报存在 Server Action 与 REST API 双通道重复** — 同一逻辑两份代码
|
||
|
||
---
|
||
|
||
## 二、新增模块审查
|
||
|
||
### 2.1 elective 模块(选课管理)
|
||
|
||
#### 模块结构
|
||
|
||
| 文件 | 行数 | 职责 |
|
||
|------|------|------|
|
||
| `actions.ts` | 304 | 11 个 Server Action(CRUD + 选课 + 抽签 + 查询) |
|
||
| `data-access.ts` | 242 | 课程 CRUD + 查询 + scope 过滤 |
|
||
| `data-access-operations.ts` | 217 | 选课操作(select/drop/lottery) |
|
||
| `data-access-selections.ts` | 189 | 选课记录查询 + 学生可用课程查询 |
|
||
| `schema.ts` | 132 | Zod 校验 schema |
|
||
| `types.ts` | 108 | 类型定义 + 标签常量 |
|
||
|
||
#### 拆分为 3 个 data-access 文件是否合理?
|
||
|
||
**结论:⚠️ 拆分意图合理,但存在代码重复**
|
||
|
||
- **拆分意图合理**:operations(写操作)与 selections(读查询)职责分明,符合"按职责划分"原则
|
||
- **代码重复问题**:`data-access.ts` 与 `data-access-selections.ts` 重复定义了 `mapCourseRow` 和 `buildCourseSelect` 两个函数(共约 60 行重复代码)
|
||
- `data-access.ts` 第 47-108 行
|
||
- `data-access-selections.ts` 第 28-88 行
|
||
- 两份代码完全相同,维护时易产生不一致
|
||
|
||
**建议**:
|
||
- 抽取共享的 `mapCourseRow` / `buildCourseSelect` 到 `data-access.ts` 并导出,`data-access-selections.ts` 复用
|
||
- 或合并 `data-access-selections.ts` 到 `data-access.ts`(文件仅 189 行,合并后仍在 800 行上限内)
|
||
|
||
#### 权限校验
|
||
|
||
✅ 全部 Server Action 均使用 `requirePermission`:
|
||
- 管理 Action 使用 `ELECTIVE_MANAGE`
|
||
- 选课 Action 使用 `ELECTIVE_SELECT`
|
||
- 查询 Action 使用 `ELECTIVE_READ`
|
||
- 学生查看他人选课记录有额外 dataScope 校验(actions.ts 第 283-288 行)
|
||
|
||
#### 其他发现
|
||
|
||
- ✅ schema.ts 使用 Zod transform 统一处理空字符串转 null,规范
|
||
- ⚠️ `data-access-operations.ts` 的 `runLottery` 使用 `Math.random()` 排序(第 40 行),结果不可复现,建议改用 DB 的 `ORDER BY RAND()` 或记录种子
|
||
- ⚠️ `selectCourse` 在 FCFS 模式下先查后写(第 119-128 行),存在并发超卖风险,建议加事务或乐观锁
|
||
|
||
---
|
||
|
||
### 2.2 proctoring 模块(考试监考)
|
||
|
||
#### 模块结构
|
||
|
||
| 文件 | 行数 | 职责 |
|
||
|------|------|------|
|
||
| `actions.ts` | 144 | 2 个 Server Action(上报事件 + 获取面板) |
|
||
| `data-access.ts` | 388 | 事件记录 + 查询 + 摘要统计 + 学生状态 |
|
||
| `types.ts` | 136 | 类型定义 + 标签常量 + 阈值常量 |
|
||
| `components/anti-cheat-monitor.tsx` | - | 学生端防作弊监控 |
|
||
| `components/exam-mode-config.tsx` | - | 考试模式配置表单(**未集成**) |
|
||
| `components/proctoring-dashboard.tsx` | - | 教师监考面板 |
|
||
|
||
#### 职责清晰度
|
||
|
||
**结论:⚠️ 职责基本清晰,但存在 3 个严重问题**
|
||
|
||
#### 严重问题 1:`exam-mode-config.tsx` 未集成到考试表单(死代码)⚠️ 用户决定保留
|
||
|
||
- DB schema 已有 `examMode` / `durationMinutes` / `shuffleQuestions` / `allowLateStart` / `antiCheatEnabled` 字段(schema.ts 第 457-462 行)
|
||
- `proctoring/components/exam-mode-config.tsx` 提供了完整的配置 UI 组件
|
||
- **但 `exams/components/exam-form.tsx` 并未导入该组件**(grep 确认无 `ExamModeConfig` 引用)
|
||
- `exams/components/exam-mode-selector.tsx` 是"手动组卷 vs AI 生成"的选择器,**与考试模式(homework/timed/proctored)无关**,命名易混淆
|
||
- 结果:创建考试时无法设置监考模式,proctoring 模块的 `getExamForProctoring` 读取的 `examMode` 永远是默认值 `"homework"`
|
||
|
||
**状态**:用户决定保留该组件,暂不集成也不删除。后续如需启用监考功能,可再集成到 `exam-form.tsx`。
|
||
|
||
#### 严重问题 2:事件上报存在 Server Action 与 REST API 双通道重复
|
||
|
||
- `proctoring/actions.ts` 第 58-105 行:`recordProctoringEventAction`(Server Action)
|
||
- `app/api/proctoring/event/route.ts` 第 27-90 行:POST handler(REST API)
|
||
- **两者逻辑完全相同**:都校验 submission 归属、调用 `recordProctoringEvent`
|
||
- `AntiCheatMonitor` 组件使用 Server Action(第 58 行),REST API 路由无调用方
|
||
|
||
**建议**:删除未使用的 `/api/proctoring/event` 路由,或让组件改用 REST API(适用于客户端轮询场景)
|
||
|
||
#### 中等问题 3:跨模块读取 exams 表
|
||
|
||
- `data-access.ts` 第 326-353 行:`getExamForProctoring` 直接查询 `exams` 表
|
||
- 第 178-185 行:`getExamProctoringSummary` 直接查询 `examSubmissions` 表
|
||
- 第 249-256 行:`getStudentProctoringStatuses` 直接 join `examSubmissions` 和 `users`
|
||
|
||
**建议**:可接受(监考本质是考试模块的扩展),但应在架构图中标注依赖关系
|
||
|
||
#### 其他发现
|
||
|
||
- ✅ `recordProctoringEventAction` 使用 `requireAuth()` 而非 `requirePermission`,符合"学生上报自己事件"场景
|
||
- ✅ `getProctoringDashboardAction` 使用 `EXAM_PROCTOR` 权限
|
||
- ✅ 异常阈值 `ABNORMAL_EVENT_THRESHOLD = 3` 提取为常量,便于调整
|
||
- ⚠️ `actions.ts` 第 11-13 行直接 import `db` 和 `examSubmissions`,应在 data-access 层封装 submission 校验逻辑
|
||
|
||
---
|
||
|
||
### 2.3 diagnostic 模块(学情诊断)
|
||
|
||
#### 模块结构
|
||
|
||
| 文件 | 行数 | 职责 |
|
||
|------|------|------|
|
||
| `actions.ts` | 148 | 6 个 Server Action(生成/发布/删除/查询报告) |
|
||
| `data-access.ts` | 254 | 知识点掌握度查询 + 从提交更新掌握度 |
|
||
| `data-access-reports.ts` | 202 | 诊断报告 CRUD |
|
||
| `types.ts` | 97 | 类型定义 |
|
||
| `components/` | 4 个 | 学生/班级诊断视图 + 雷达图 + 报告列表 |
|
||
|
||
#### 与 grades 模块的边界
|
||
|
||
**结论:✅ 无职责重叠,但存在跨模块耦合**
|
||
|
||
- **grades 模块**:管理 `gradeRecords` 表(分数记录),维度是"学生-班级-科目-考试"
|
||
- **diagnostic 模块**:管理 `knowledgePointMastery` 表(知识点掌握度),维度是"学生-知识点"
|
||
- 两者数据来源不同:grades 是教师录入的分数,diagnostic 是从 `submissionAnswers` 推导的正确率
|
||
- **无任何代码重叠**:grep 确认 grades 模块无 `knowledgePoint` / `mastery` / `diagnostic` 关键字
|
||
|
||
#### 中等问题 1:`data-access.ts` 跨模块直查 4 张表
|
||
|
||
- 第 87-92 行:查询 `examSubmissions` 表(属 exams 模块)
|
||
- 第 95-101 行:查询 `submissionAnswers` 表(属 exams/homework 模块)
|
||
- 第 106-112 行:查询 `questionsToKnowledgePoints` 表(属 questions 模块)
|
||
- 第 150-158 行:查询 `classEnrollments` + `classes` + `users` 表(属 classes 模块)
|
||
|
||
`updateMasteryFromSubmission` 函数(第 87-147 行)直接读取提交答案和题目-知识点关联,将 diagnostic 模块与 exams/homework/questions 模块紧耦合。
|
||
|
||
**建议**:
|
||
- 短期:在架构图中标注此依赖关系
|
||
- 长期:由 exams/homework 模块在提交评分后主动调用 diagnostic 模块的更新接口(事件驱动)
|
||
|
||
#### 轻微问题 2:`data-access-reports.ts` 有未使用代码
|
||
|
||
- 第 20 行定义 `round2` 函数
|
||
- 第 201 行 `void round2` 仅为消除 lint 警告
|
||
- **建议**:删除未使用的 `round2` 函数
|
||
|
||
#### 轻微问题 3:班级报告字段复用不当
|
||
|
||
- `data-access-reports.ts` 第 107 行:`studentId: generatedBy` — 班级报告将生成者 ID 存入 `studentId` 字段
|
||
- 注释说明"schema 要求 NOT NULL",但这是 schema 设计缺陷的 workaround
|
||
- **建议**:修改 `learningDiagnosticReports` schema,将 `studentId` 改为可空,或增加 `classId` 字段
|
||
|
||
#### 权限校验
|
||
|
||
✅ 全部 Action 使用 `DIAGNOSTIC_MANAGE` 或 `DIAGNOSTIC_READ` 权限
|
||
|
||
---
|
||
|
||
### 2.4 notifications 模块(通知分发)
|
||
|
||
#### 模块结构
|
||
|
||
| 文件 | 行数 | 职责 |
|
||
|------|------|------|
|
||
| `actions.ts` | 119 | 2 个 Server Action(单发 + 班级群发) |
|
||
| `data-access.ts` | 86 | 用户偏好 + 联系方式 + 日志 |
|
||
| `dispatcher.ts` | 152 | 渠道选择 + 并行分发 |
|
||
| `types.ts` | 70 | 通知负载 + 渠道配置类型 |
|
||
| `index.ts` | 38 | 对外导出入口 |
|
||
| `channels/` | 5 个 | SMS/Email/WeChat/InApp 渠道实现 |
|
||
|
||
#### 严重问题 1:与 messaging 模块双向依赖、边界模糊 ✅ 已修复
|
||
|
||
~~详见下文"三、messaging vs notifications 边界分析"。~~
|
||
|
||
**已完成修复**(2026-06-17,P0-5 + P1-6):
|
||
- messaging/actions.ts 改用 `sendNotification` from `@/modules/notifications/dispatcher`(P0-5)
|
||
- notifications/channels/in-app-channel.ts 将静态 import 改为动态 `await import("@/modules/messaging/data-access")`,打破模块级静态反向依赖(P1-6)
|
||
- 依赖方向已统一:messaging → notifications(单向)
|
||
|
||
#### 中等问题 2:`sendClassNotificationAction` 跨模块直查
|
||
|
||
- `actions.ts` 第 83-96 行:直接查询 `classes` 和 `classEnrollments` 表
|
||
- 应通过 classes 模块的 data-access 获取班级学生列表
|
||
|
||
**建议**:调用 `classes` 模块的 data-access 函数获取学生 ID 列表
|
||
|
||
#### 中等问题 3:发送日志仅 console 输出
|
||
|
||
- `data-access.ts` 第 71-77 行:`logNotificationSend` 使用 `console.info`
|
||
- 代码注释承认"当前项目无 notification_logs 表"
|
||
- **影响**:无法查询历史发送记录、无法统计发送成功率、无法排查发送失败
|
||
|
||
**建议**:新增 `notification_logs` 表,记录 channel/userId/payload/success/error/sentAt
|
||
|
||
#### 轻微问题 4:复用 MESSAGE_SEND 权限
|
||
|
||
- `actions.ts` 第 34、67 行:使用 `Permissions.MESSAGE_SEND`
|
||
- 代码注释说明"项目无独立 NOTIFICATION_SEND 权限点"
|
||
- **影响**:无法单独控制"谁能发通知"vs"谁能发私信"
|
||
|
||
**建议**:新增 `NOTIFICATION_SEND` 权限点,或确认复用是设计意图
|
||
|
||
#### 设计亮点
|
||
|
||
- ✅ 渠道抽象优秀:`NotificationChannelSender` 接口 + 工厂函数,新增渠道只需实现接口
|
||
- ✅ Mock 实现完善:SMS/Email/WeChat 均有 Mock 实现,开发环境零配置可用
|
||
- ✅ 动态 import 第三方 SDK(阿里云/腾讯云/nodemailer),避免增加构建体积
|
||
- ✅ 渠道选择逻辑清晰(dispatcher.ts 第 59-95 行)
|
||
|
||
---
|
||
|
||
## 三、messaging vs notifications 边界分析(重点)✅ 已修复
|
||
|
||
> **状态**:双向依赖与绕过 dispatcher 问题已于 2026-06-17 修复(P0-5 + P1-6)。以下为修复前的现状记录,保留作为历史参考。
|
||
|
||
### 3.1 现状对比(修复前)
|
||
|
||
| 维度 | messaging 模块 | notifications 模块 |
|
||
|------|---------------|-------------------|
|
||
| **核心职责** | 站内私信 + 站内通知列表 | 多渠道通知分发 |
|
||
| **管理的表** | `messages` + `messageNotifications` + `notificationPreferences` | 无独有表(借用 messaging 的表) |
|
||
| **写入 messageNotifications** | ✅ 直接写(`createNotification`) | ✅ 通过 in-app 渠道写 |
|
||
| **通知类型枚举** | `NotificationType = "message" \| "announcement" \| "homework" \| "grade"` | `NotificationPayload.type = "info" \| "warning" \| "error" \| "success"` |
|
||
| **UI 组件** | message-list / message-detail / message-compose / notification-dropdown / notification-list | 无 UI 组件 |
|
||
| **偏好管理** | ✅ `notification-preferences.ts` | ❌ 借用 messaging 的 |
|
||
| **渠道支持** | 仅站内 | 站内 + SMS + Email + WeChat |
|
||
|
||
### 3.2 严重问题:双向依赖与职责重叠 ✅ 已修复
|
||
|
||
#### 问题 1:notifications 反向依赖 messaging ✅ 已修复
|
||
|
||
~~notifications/data-access.ts~~
|
||
~~ → import { getNotificationPreferences } from "@/modules/messaging/notification-preferences"~~
|
||
~~ → import type { NotificationPreferences } from "@/modules/messaging/types"~~
|
||
|
||
~~notifications/channels/in-app-channel.ts~~
|
||
~~ → import { createNotification } from "@/modules/messaging/data-access"~~
|
||
|
||
**修复方案**:notifications/channels/in-app-channel.ts 将静态 import 改为动态 `await import("@/modules/messaging/data-access")`,打破模块级静态反向依赖。运行时调用链保持不变,但模块加载图无环。
|
||
|
||
#### 问题 2:messaging 绕过 notifications 直接写通知 ✅ 已修复
|
||
|
||
~~`messaging/actions.ts` 第 66-72 行:~~
|
||
|
||
```typescript
|
||
// ~~Notify the receiver about the new message~~
|
||
// ~~await createNotification({~~
|
||
// ~~ userId: input.receiverId,~~
|
||
// ~~ type: "message",~~
|
||
// ~~ ...~~
|
||
// ~~})~~
|
||
```
|
||
|
||
**修复方案**:messaging/actions.ts 改用 `sendNotification` from `@/modules/notifications/dispatcher`,通知现在会经过 dispatcher 的渠道选择逻辑,尊重用户偏好(SMS/Email/WeChat/In-App)。
|
||
|
||
#### 问题 3:类型系统不一致(保留)
|
||
|
||
- `messaging/types.ts` 第 23 行:`NotificationType = "message" | "announcement" | "homework" | "grade"`(按业务类别)
|
||
- `notifications/types.ts` 第 20 行:`type: "info" | "warning" | "error" | "success"`(按严重级别)
|
||
- `in-app-channel.ts` 第 49 行:`type: payload.type as "message" | "announcement" | "homework" | "grade"` — **强制类型转换,运行时可能写入非法值**
|
||
|
||
DB schema 中 `messageNotifications.type` 为 `varchar(128)`,虽然不会报错,但语义混乱。(P2 待统一)
|
||
|
||
#### 问题 4:notification-preferences 归属不清(保留)
|
||
|
||
- `notificationPreferences` 表的 data-access 在 messaging 模块
|
||
- 但 notifications 模块的 dispatcher 依赖此偏好决定渠道
|
||
- settings 模块的 `notification-preferences-form.tsx` 调用 `messaging/actions.ts` 的 `updateNotificationPreferencesAction`
|
||
- 三个模块都在操作同一份数据,职责归属不清(P2 待重构)
|
||
|
||
### 3.3 建议方案
|
||
|
||
**方案 A(推荐):notifications 吞并 messaging 的通知部分**
|
||
|
||
1. 将 `messageNotifications` 表和 `notificationPreferences` 表的所有权移交给 notifications 模块
|
||
2. messaging 模块仅保留 `messages` 表(私信)
|
||
3. messaging 的 `sendMessageAction` 改为调用 `notifications.sendNotification`
|
||
4. messaging 的 UI 组件(notification-dropdown / notification-list)迁移到 notifications 模块
|
||
5. 统一 `NotificationType` 枚举,支持业务类别 + 严重级别两个维度
|
||
|
||
**方案 B:保持现状,明确依赖方向**
|
||
|
||
1. 在架构图中标注 notifications → messaging 的单向依赖
|
||
2. messaging 不再直接调用 `createNotification`,改为调用 `notifications.sendNotification`
|
||
3. 消除双向依赖,但 notifications 仍不拥有数据
|
||
|
||
---
|
||
|
||
## 四、dashboard 模块审查(重点)
|
||
|
||
### 4.1 严重问题:`data-access.ts` 直查 11 张跨模块表 ✅ 已修复
|
||
|
||
~~`dashboard/data-access.ts` 的 `getAdminDashboardData` 函数直接查询以下表~~
|
||
|
||
**已完成修复**(2026-06-17,P0-4):dashboard/data-access.ts 从大文件降至 42 行,改为并行调用 6 个模块的 stats 函数:
|
||
|
||
```typescript
|
||
const [usersStats, classesStats, textbooksStats, questionsStats, examsStats, homeworkStats] = await Promise.all([
|
||
getUsersDashboardStats(),
|
||
getClassesDashboardStats(),
|
||
getTextbooksDashboardStats(),
|
||
getQuestionsDashboardStats(),
|
||
getExamsDashboardStats(scope),
|
||
getHomeworkDashboardStats(scope),
|
||
])
|
||
```
|
||
|
||
不再直接查询任何业务表,完全通过各模块 data-access 暴露的聚合查询函数获取数据。
|
||
|
||
### 4.2 学生/教师仪表盘的对比
|
||
|
||
**学生仪表盘**(`app/(dashboard)/student/dashboard/page.tsx`):
|
||
- ✅ 正确做法:调用 `classes/data-access` 的 `getStudentClasses` / `getStudentSchedule`
|
||
- ✅ 调用 `homework/data-access` 的 `getStudentDashboardGrades` / `getStudentHomeworkAssignments`
|
||
- ✅ 不直接查询任何表
|
||
|
||
**教师仪表盘**(`app/(dashboard)/teacher/dashboard/page.tsx`):
|
||
- ✅ 调用 `classes/data-access` 的 `getClassSchedule` / `getTeacherClasses`
|
||
- ✅ 调用 `homework/data-access` 的 `getHomeworkAssignments` / `getHomeworkSubmissions` / `getTeacherGradeTrends`
|
||
- ⚠️ 第 18-21 行直接查询 `users` 表获取教师姓名:`db.query.users.findFirst(...)` — 应使用 users 模块的 data-access
|
||
|
||
### 4.3 建议方案
|
||
|
||
**方案 A(理想):聚合 API 模式**
|
||
|
||
为每个模块添加 `getModuleStats(scope?)` 函数,dashboard 聚合调用:
|
||
```typescript
|
||
const [userStats, classStats, textbookStats, ...] = await Promise.all([
|
||
getUsersStats(scope),
|
||
getClassStats(scope),
|
||
getTextbookStats(),
|
||
...
|
||
])
|
||
```
|
||
|
||
**方案 B(务实):接受 dashboard 作为跨模块聚合层**
|
||
|
||
- 在架构图中明确标注 dashboard 对所有业务模块的依赖
|
||
- 将 scope 过滤逻辑下沉到各模块的 `getStats` 函数
|
||
- 至少消除 dashboard 中重复实现的 exam/homework scope 过滤(第 31-73 行)
|
||
|
||
---
|
||
|
||
## 五、其他模块审查
|
||
|
||
### 5.1 messaging 模块
|
||
|
||
#### 模块结构
|
||
|
||
| 文件 | 行数 | 职责 |
|
||
|------|------|------|
|
||
| `actions.ts` | 245 | 9 个 Server Action(私信 + 通知 + 偏好) |
|
||
| `data-access.ts` | 252 | 私信 CRUD + 通知 CRUD + 收件人查询 |
|
||
| `notification-preferences.ts` | 166 | 通知偏好 CRUD |
|
||
| `schema.ts` | 17 | 私信发送校验 |
|
||
| `types.ts` | 108 | 私信 + 通知 + 偏好类型 |
|
||
|
||
#### 问题
|
||
|
||
- ❌ **职责过多**:同时管理私信(messages)、站内通知(messageNotifications)、通知偏好(notificationPreferences)三类数据
|
||
- ❌ **与 notifications 模块边界模糊**:详见第三节
|
||
- ⚠️ `getRecipients` 函数(第 227-251 行)根据 dataScope 查询收件人,逻辑较复杂,可考虑下沉到 users 模块
|
||
|
||
### 5.2 parent 模块
|
||
|
||
#### 模块结构
|
||
|
||
| 文件 | 行数 | 职责 |
|
||
|------|------|------|
|
||
| `data-access.ts` | 234 | 子女关系 + 子女仪表盘数据聚合 |
|
||
| `types.ts` | 57 | 类型定义 |
|
||
| `components/` | 7 个 | 子女卡片 + 详情 + 仪表盘 |
|
||
|
||
#### 评价
|
||
|
||
- ✅ **职责单一**:仅负责家长视角的子女数据聚合与展示
|
||
- ✅ **正确复用其他模块**:
|
||
- 调用 `classes/data-access` 的 `getStudentClasses` / `getStudentSchedule`
|
||
- 调用 `homework/data-access` 的 `getStudentDashboardGrades` / `getStudentHomeworkAssignments`
|
||
- 调用 `grades/data-access` 的 `getStudentGradeSummary`
|
||
- ✅ 不直接查询业务表(仅查询 `parentStudentRelations` 自有表 + `users`/`classes`/`classEnrollments`/`grades` 用于基本信息)
|
||
- ⚠️ `getChildBasicInfo` 第 74-105 行多次串行查询(grade → class),可优化为 join
|
||
|
||
### 5.3 settings 模块
|
||
|
||
#### 模块结构
|
||
|
||
| 文件 | 行数 | 职责 |
|
||
|------|------|------|
|
||
| `actions.ts` | 205 | AI Provider CRUD + 测试连通性 |
|
||
| `actions-password.ts` | 113 | 修改密码 |
|
||
| `components/` | 8 个 | 通用设置 + AI 配置 + 密码 + 主题 + 通知偏好 |
|
||
|
||
#### 问题:职责混杂但可接受
|
||
|
||
settings 模块混合了 5 类职责:
|
||
1. AI Provider 管理(`actions.ts` + `ai-provider-settings-card.tsx`)
|
||
2. 密码修改(`actions-password.ts` + `password-change-form.tsx`)
|
||
3. 个人资料(`profile-settings-form.tsx`)
|
||
4. 主题偏好(`theme-preferences-card.tsx`)
|
||
5. 通知偏好表单(`notification-preferences-form.tsx` — **调用 messaging 模块的 Action**)
|
||
|
||
**评价**:
|
||
- ⚠️ AI Provider 管理与"用户设置"语义距离较远,可考虑独立为 `ai-config` 模块
|
||
- ⚠️ `notification-preferences-form.tsx` 第 14 行 import `updateNotificationPreferencesAction` from `@/modules/messaging/actions` — 跨模块 UI 依赖
|
||
- ✅ 密码修改有速率限制(`actions-password.ts` 第 33-37 行)
|
||
- ✅ AI Provider 操作有 `AI_CONFIGURE` 权限校验
|
||
- ✅ 密码修改仅要求 `requireAuth()`(自助操作),符合最小权限原则
|
||
- ⚠️ 无 `data-access.ts` 文件,`actions.ts` 直接使用 `db` — 建议抽取 data-access 层
|
||
|
||
### 5.4 files 模块
|
||
|
||
#### 模块结构
|
||
|
||
| 文件 | 行数 | 职责 |
|
||
|------|------|------|
|
||
| `data-access.ts` | 267 | 文件附件 CRUD + 批量删除 + 统计 |
|
||
| `types.ts` | - | 类型定义 |
|
||
| `components/` | 6 个 | 上传 + 列表 + 预览 + 管理 |
|
||
|
||
#### 评价
|
||
|
||
- ✅ **职责单一**:仅管理 `fileAttachments` 表
|
||
- ✅ 不跨模块查询
|
||
- ✅ 批量删除有容错处理(第 152-177 行,失败时回退到逐条删除)
|
||
- ⚠️ 所有函数都用 try-catch 吞掉错误返回空数组/null,可能掩盖真实问题
|
||
- ⚠️ 无 actions.ts 文件 — 文件上传通过 `app/api/upload/route.ts` 和 `app/api/files/[id]/route.ts` 实现,data-access 被路由直接调用
|
||
|
||
### 5.5 auth 模块
|
||
|
||
#### 模块结构
|
||
|
||
| 文件 | 职责 |
|
||
|------|------|
|
||
| `components/auth-layout.tsx` | 认证页面布局 |
|
||
| `components/login-form.tsx` | 登录表单 |
|
||
| `components/register-form.tsx` | 注册表单 |
|
||
|
||
#### 评价
|
||
|
||
- ✅ **纯 UI 模块**:无 data-access / actions / types 文件
|
||
- ✅ 认证逻辑由 NextAuth + `shared/lib/auth-guard` 统一处理
|
||
- ✅ 职责清晰
|
||
|
||
### 5.6 layout 模块
|
||
|
||
#### 模块结构
|
||
|
||
| 文件 | 职责 |
|
||
|------|------|
|
||
| `components/app-sidebar.tsx` | 侧边栏(根据权限渲染导航) |
|
||
| `components/sidebar-provider.tsx` | 侧边栏状态 Context |
|
||
| `components/site-header.tsx` | 顶部导航(含通知下拉) |
|
||
| `config/navigation.ts` | 导航配置(4 个角色) |
|
||
|
||
#### navigation.ts 幽灵路由审查
|
||
|
||
**结论:✅ 无幽灵路由**(007 报告中提到的 13 个幽灵路由已全部修复)
|
||
|
||
逐一核对导航配置中的所有 href 与 `src/app/` 下的实际页面:
|
||
|
||
| 角色 | 导航项数 | 全部存在 | 备注 |
|
||
|------|---------|---------|------|
|
||
| admin | 19 | ✅ | 包括子菜单项 |
|
||
| teacher | 22 | ✅ | 包括子菜单项 |
|
||
| student | 12 | ✅ | 包括子菜单项 |
|
||
| parent | 5 | ✅ | 包括子菜单项 |
|
||
|
||
#### 存在但未纳入导航的页面
|
||
|
||
| 路由 | 说明 | 建议 |
|
||
|------|------|------|
|
||
| `/admin/attendance` | 管理员考勤页面 | 如需管理员查看全校考勤,应加入 admin 导航 |
|
||
| `/admin/files` | 管理员文件管理 | 应加入 admin 导航 |
|
||
| `/parent/children/[studentId]` | 子女详情页 | 通过仪表盘卡片跳转,可不加入导航 |
|
||
| `/settings/security` | 安全设置子页 | 通过 settings 页 Tab 切换,无需独立导航 |
|
||
| `/profile` | 个人主页 | 通过 header 头像菜单跳转,无需独立导航 |
|
||
|
||
#### 其他发现
|
||
|
||
- ✅ `app-sidebar.tsx` 第 36-43 行根据权限动态选择角色导航配置,符合 RBAC
|
||
- ⚠️ 第 39 行判断学生逻辑:`permissions.includes(Permissions.HOMEWORK_SUBMIT) && !permissions.includes(Permissions.EXAM_CREATE)` — 用权限反推角色,不够直观,建议改用 `hasRole("student")`
|
||
|
||
### 5.7 student 模块
|
||
|
||
#### 模块结构
|
||
|
||
| 文件 | 职责 |
|
||
|------|------|
|
||
| `components/student-courses-view.tsx` | 学生课程视图 |
|
||
| `components/student-schedule-filters.tsx` | 课表筛选器 |
|
||
| `components/student-schedule-view.tsx` | 学生课表视图 |
|
||
|
||
#### 评价
|
||
|
||
- ✅ **纯 UI 模块**:无 data-access / actions / types
|
||
- ✅ 数据由 `app/(dashboard)/student/learning/courses/page.tsx` 和 `app/(dashboard)/student/schedule/page.tsx` 通过 classes 模块的 data-access 获取
|
||
- ⚠️ 与 classes 模块的 `schedule-view.tsx` / `schedule-filters.tsx` 可能存在功能重叠,建议核查
|
||
|
||
---
|
||
|
||
## 六、跨模块依赖关系图
|
||
|
||
```
|
||
dashboard ──直查──> users, classes, textbooks, questions, exams, homework (11 张表)
|
||
parent ──调用──> classes, homework, grades (data-access)
|
||
diagnostic ──直查──> examSubmissions, submissionAnswers, questionsToKnowledgePoints, classes
|
||
notifications ──依赖──> messaging (偏好 + in-app 渠道)
|
||
messaging ──绕过──> notifications (直接写 messageNotifications)
|
||
proctoring ──直查──> exams, examSubmissions, users
|
||
settings ──调用──> messaging (通知偏好 Action)
|
||
```
|
||
|
||
---
|
||
|
||
## 七、修复优先级
|
||
|
||
### P0(严重,应立即修复)
|
||
|
||
| 序号 | 问题 | 模块 | 工作量 | 影响 | 状态 |
|
||
|------|------|------|--------|------|------|
|
||
| ~~1~~ | ~~messaging 绕过 notifications 直接写通知~~ | ~~messaging~~ | ~~小~~ | ~~用户通知偏好失效,多渠道通知无效~~ | ✅ 已修复(P0-5) |
|
||
| 2 | proctoring/exam-mode-config.tsx 未集成 | proctoring | 小 | 监考功能无法启用,组件为死代码 | ⚠️ 用户决定保留 |
|
||
| 3 | proctoring 事件上报双通道重复 | proctoring | 小 | 代码重复,维护成本 | ❌ 待修复 |
|
||
| ~~4~~ | ~~notifications 反向依赖 messaging~~ | ~~notifications~~ | ~~中~~ | ~~架构耦合,难以独立演进~~ | ✅ 已修复(P1-6) |
|
||
|
||
### P1(中等问题,下个迭代修复)
|
||
|
||
| 序号 | 问题 | 模块 | 工作量 | 状态 |
|
||
|------|------|------|--------|------|
|
||
| ~~5~~ | ~~dashboard 直查 11 张跨模块表~~ | ~~dashboard~~ | ~~大~~ | ✅ 已修复(P0-4) |
|
||
| 6 | diagnostic 跨模块直查 4 张表 | diagnostic | 中 | ❌ 待修复 |
|
||
| 7 | notifications 无 notification_logs 表 | notifications | 中 | ❌ 待修复 |
|
||
| 8 | sendClassNotificationAction 直查 classes 表 | notifications | 小 | ❌ 待修复 |
|
||
| 9 | elective 两个 data-access 文件代码重复 | elective | 小 | ❌ 待修复 |
|
||
| 10 | settings/notification-preferences-form 跨模块依赖 | settings | 小 | ❌ 待修复 |
|
||
|
||
### P2(轻微问题,机会修复)
|
||
|
||
| 序号 | 问题 | 模块 |
|
||
|------|------|------|
|
||
| 11 | diagnostic/data-access-reports.ts 有未使用代码 | diagnostic |
|
||
| 12 | diagnostic 班级报告 studentId 字段复用 | diagnostic |
|
||
| 13 | elective runLottery 使用 Math.random | elective |
|
||
| 14 | teacher dashboard 直查 users 表 | dashboard |
|
||
| 15 | files 模块 try-catch 吞错误 | files |
|
||
| 16 | layout 用权限反推角色 | layout |
|
||
|
||
---
|
||
|
||
## 八、总结
|
||
|
||
### 新增模块质量
|
||
|
||
- **elective**:✅ 质量较好,拆分合理但有代码重复
|
||
- **proctoring**:⚠️ 有死代码(exam-mode-config 未集成,用户决定保留)和重复实现(双通道上报)
|
||
- **diagnostic**:✅ 与 grades 无重叠,但跨模块耦合较重
|
||
- **notifications**:✅ 渠道抽象优秀,与 messaging 的双向依赖已修复(P0-5 + P1-6)
|
||
|
||
### 重点问题回答
|
||
|
||
1. **elective 拆分为 3 个 data-access 文件是否合理?**
|
||
合理,但需消除 `data-access.ts` 与 `data-access-selections.ts` 之间的代码重复
|
||
|
||
2. **proctoring 模块职责是否清晰?**
|
||
基本清晰,但 `exam-mode-config.tsx` 应属于 exams 模块或集成到考试表单(用户决定保留)
|
||
|
||
3. **diagnostic 与 grades 是否有职责重叠?**
|
||
无重叠。grades 管分数记录,diagnostic 管知识点掌握度,数据来源和维度均不同
|
||
|
||
4. **notifications 与 messaging 边界是否清晰?**
|
||
✅ 已修复。双向依赖通过动态 import 打破,messaging 改用 notifications/dispatcher 发送通知,依赖方向统一为 messaging → notifications
|
||
|
||
5. **dashboard 是否直查其他模块的表?**
|
||
✅ 已修复。`getAdminDashboardData` 改为并行调用各模块的 `get[Module]DashboardStats()` 函数,不再直接查询任何业务表
|
||
|
||
6. **settings 是否混入太多职责?**
|
||
混合了 5 类职责,但作为"设置"聚合点尚可接受。AI Provider 管理可考虑独立
|
||
|
||
7. **navigation.ts 是否有幽灵路由?**
|
||
无。007 报告中的 13 个幽灵路由已全部修复
|