# 个人信息配置和设置模块审计报告 v3

> 审查日期：2026-06-22
> 审查范围：`src/modules/settings/**`、`src/app/(dashboard)/settings/**`、`src/app/(dashboard)/admin/settings/**`、`src/app/(dashboard)/profile/**`
> 上一版本：`settings-profile-audit-report-v2.md`（v2，12 项已全部完成）
> 架构图参考：`docs/architecture/004_architecture_impact_map.md` §2.23、`docs/architecture/005_architecture_data.json`

---

## 一、现有实现概要

### 1.1 文件分布

| 层 | 路径 | 文件数 | 说明 |
|----|------|--------|------|
| 路由层 - 通用设置 | `src/app/(dashboard)/settings/` | `page.tsx` + `error.tsx` + `loading.tsx` | 角色分发到 SettingsView，通过 SettingsServiceProvider 注入服务 |
| 路由层 - 管理员系统设置 | `src/app/(dashboard)/admin/settings/` | `page.tsx` | 仅 admin 可访问，渲染 AdminSettingsView |
| 路由层 - 安全设置 | `src/app/(dashboard)/settings/security/` | `page.tsx` + `error.tsx` + `loading.tsx` | 独立密码修改页 |
| 路由层 - 个人资料 | `src/app/(dashboard)/profile/` | `page.tsx` + `error.tsx` + `loading.tsx` | 个人资料展示页（159 行） |
| 模块层 - actions | 7 个文件 | 详见下表 | 全部使用 `requirePermission()` |
| 模块层 - data-access | 3 个文件 | 详见下表 | 全部 `server-only` |
| 模块层 - types | `types.ts`（75 行） | 1 | AiProvider 类型 + SettingsService 接口 |
| 模块层 - 组件 | 12 个组件 | 详见下表 | |
| 模块层 - lib | 3 个纯函数文件 + 3 个测试 | | |
| 模块层 - config | `role-settings-config.tsx`（84 行） | 1 | 配置驱动角色路由 |
| i18n | `zh-CN/settings.json` + `en/settings.json` | 2 | 完整翻译 |

### 1.2 v1/v2 完成情况回顾

v1 报告 13 项 + v2 报告 12 项改进建议已全部完成：

- ✅ i18n 命名空间创建（settings.json 中英文）
- ✅ SettingsService 接口 + Context 注入（消除跨模块 action 直调）
- ✅ AdminSettingsView 接入真实数据层（system_settings 表）
- ✅ 配置驱动角色路由（ROLE_SETTINGS_CONFIG）
- ✅ 分区 Error Boundary + Suspense 骨架屏
- ✅ Profile 页面拆分（ProfileStudentOverview / ProfileTeacherOverview）
- ✅ 头像上传 + 旧文件清理
- ✅ 2FA 完整 TOTP 实现（非占位）
- ✅ 通知测试按钮接入真实 dispatcher
- ✅ 会话远程登出
- ✅ AdminSettingsView dirty 检测
- ✅ 通知偏好表单 dirty 检测
- ✅ 单元测试（totp / student-overview-data / security-utils）

### 1.3 架构图记录情况

`004_architecture_impact_map.md` §2.23 和 `005_architecture_data.json` 的 settings 节点记录完整，包含所有 actions / data-access / components / config / lib / types 的导出、依赖关系和已知问题状态。架构图与实际代码基本一致。

---

## 二、现存问题与原因分析

### 2.1 ProfileStudentOverview / ProfileTeacherOverview 跨模块 data-access 直调（P0）

| 位置 | 问题 | 违反规则 |
|------|------|----------|
| [profile-student-overview.tsx](file:///e:/Desktop/CICD/src/modules/settings/components/profile-student-overview.tsx) L8-9 | `import { getStudentClasses, getStudentSchedule } from "@/modules/classes/data-access"` / `import { getStudentDashboardGrades, getStudentHomeworkAssignments } from "@/modules/homework/data-access"` | "模块内部组件绝不直接 import 其他业务模块的 actions 或 data-access（只能通过注入的接口调用）" |
| [profile-teacher-overview.tsx](file:///e:/Desktop/CICD/src/modules/settings/components/profile-teacher-overview.tsx) L6 | `import { getTeacherClasses, getTeacherTeachingSubjects } from "@/modules/classes/data-access"` | 同上 |
| [profile-student-overview.tsx](file:///e:/Desktop/CICD/src/modules/settings/components/profile-student-overview.tsx) L4-7 | `import { StudentGradesCard } ... from "@/modules/dashboard/components/student-dashboard/*"` | 组件层跨模块直接 import dashboard 组件，耦合度高 |

**原因**：Profile 概览组件作为 Server Component 直接编排 classes/homework/dashboard 模块的数据获取和组件渲染，未通过接口抽象。

**后果**：classes/homework/dashboard 模块的 data-access 签名变更会直接破坏 settings 组件；settings 模块无法独立测试（mock classes/homework data-access 困难）；无法在不修改 settings 组件的前提下替换数据源。

### 2.2 profile/page.tsx 角色硬编码（P0）

| 位置 | 问题 | 违反规则 |
|------|------|----------|
| [profile/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/profile/page.tsx) L37 | `const isStudent = roles.includes("student")` | "前端权限判断统一使用 usePermission().hasPermission()，严禁出现 role === 'xxx' 硬编码" |
| 同文件 L38 | `const isTeacher = roles.includes("teacher")` | 同上 |

**原因**：Profile 页面通过 `roles.includes()` 判断角色来决定渲染学生/教师概览区块，未使用权限点或配置驱动。

**后果**：新增角色（如 grade_head）需修改页面代码；角色与概览区块的映射关系不可配置；违反项目硬编码禁令。

### 2.3 SecurityCenterCard 超出组件行数上限（P1）

| 位置 | 问题 | 违反规则 |
|------|------|----------|
| [security-center-card.tsx](file:///e:/Desktop/CICD/src/modules/settings/components/security-center-card.tsx) | 645 行 | "React 组件：建议 ≤ 500 行（复杂表单/大型表格可放宽至 800 行）" |

**原因**：单个组件文件混合了 2FA 启用流程、2FA 关闭流程、备份码重新生成流程、最近登录历史列表、远程登出 5 个独立交互区块，以及 3 个 Dialog 的状态管理和 JSX。

**后果**：可读性下降，维护困难；难以独立测试各交互区块；修改一个流程容易影响其他流程。

**建议**：拆分为 `SecurityTwoFactorSection`（2FA 启用/关闭/备份码）、`SecurityRecentLoginsSection`（登录历史 + 远程登出），主组件仅负责数据加载和组合。

### 2.4 ai-provider-settings-card.tsx 超出组件行数上限（P1）

| 位置 | 问题 | 违反规则 |
|------|------|----------|
| [ai-provider-settings-card.tsx](file:///e:/Desktop/CICD/src/modules/settings/components/ai-provider-settings-card.tsx) | 529 行 | 同上 |

**原因**：单个组件文件混合了 Provider 列表选择、表单编辑、测试、保存、删除 5 个交互流程，以及 visibility/isDefault 表单字段。

**后果**：可读性下降，维护困难。

**建议**：将 Provider 选择器和 keyStatus 显示拆分为 `AiProviderSelector`，表单主体保留在主组件，删除确认 Dialog 拆为 `AiProviderDeleteDialog`。

### 2.5 AdminSettingsView 未拆分为子组件（P1）

| 位置 | 问题 | 违反规则 |
|------|------|----------|
| [admin-settings-view.tsx](file:///e:/Desktop/CICD/src/modules/settings/components/admin-settings-view.tsx) | 444 行，4 个 Card 内联在单文件中 | v2 报告建议拆分为 `SchoolInfoCard` / `SecurityPolicyCard` / `FileUploadCard` / `NotificationConfigCard` |

**原因**：v2 报告已建议拆分但未实施。

**后果**：4 个 Card 的表单字段和状态更新逻辑混合在主组件中，难以独立测试和复用。

### 2.6 settings/page.tsx metadata 硬编码英文（P1）

| 位置 | 问题 | 违反规则 |
|------|------|----------|
| [settings/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/settings/page.tsx) L19-21 | `metadata = { title: "Settings" }` 硬编码英文 | "所有用户可见文本必须适配 i18n（使用 next-intl），提取翻译键" |

**原因**：使用静态 `metadata` 导出而非 `generateMetadata` + `getTranslations`。

**后果**：中文环境下浏览器标签页显示英文 "Settings"。

### 2.7 i18n 命名空间不一致（P1）

| 位置 | 问题 | 违反规则 |
|------|------|----------|
| [avatar-upload.tsx](file:///e:/Desktop/CICD/src/modules/settings/components/avatar-upload.tsx) L40 | `useTranslations("settings.profile.avatar")` | 命名空间为 `settings.profile.avatar` |
| [profile/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/profile/page.tsx) L22 | `getTranslations("settings.profilePage")` | 命名空间为 `settings.profilePage` |
| [profile-settings-form.tsx](file:///e:/Desktop/CICD/src/modules/settings/components/profile-settings-form.tsx) L39 | `useTranslations("settings.profile")` | 命名空间为 `settings.profile` |

**原因**：三处使用了三种不同的 i18n 命名空间根（`settings.profile` / `settings.profilePage` / `settings.profile.avatar`），v2 报告已指出但未统一。

**后果**：i18n 命名空间结构混乱，维护时易混淆；翻译键分散在多个命名空间下。

**建议**：统一为 `settings.profile.*`（AvatarUpload 改为 `settings.profile.avatar.*`，profile/page.tsx 改为 `settings.profile.*`），将 `profilePage` 命名空间下的键合并到 `profile` 下。

### 2.8 缺少 toSettingItem 单元测试（P2）

| 位置 | 问题 | 违反规则 |
|------|------|----------|
| `src/modules/settings/actions-system-settings.ts` L58-75 | `toSettingItem` 纯函数无单元测试 | v2 报告建议添加 |

**原因**：v2 建议未实施。

**后果**：值类型转换逻辑（string/number/boolean/json）无回归保障。

### 2.9 profile 页 AvatarUpload 未包裹 Error Boundary（P2）

| 位置 | 问题 | 违反规则 |
|------|------|----------|
| [profile/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/profile/page.tsx) L53-57 | `<AvatarUpload>` 直接渲染，无 Error Boundary 包裹 | "每个独立的数据区块必须用 React Error Boundary 包裹" |

**原因**：仅学生/教师概览区块包裹了 Error Boundary，AvatarUpload 区块遗漏。

**后果**：头像上传失败（网络异常/文件服务不可用）会导致整页崩溃。

### 2.10 settings-view.tsx 直接 import next-auth/react signOut（P2）

| 位置 | 问题 | 违反规则 |
|------|------|----------|
| [settings-view.tsx](file:///e:/Desktop/CICD/src/modules/settings/components/settings-view.tsx) L8 | `import { signOut } from "next-auth/react"` | 模块内部组件直接耦合认证实现 |

**原因**：登出按钮直接调用 next-auth 的客户端 signOut。

**后果**：settings 模块耦合 next-auth 实现；如未来更换认证方案需修改 settings 组件。

**建议**：将 `signOut` 调用封装为 settings 模块自身的 action 或通过 props 注入。

---

## 三、行业差距对比

### 3.1 与优秀 K12 产品的差距

| 维度 | 优秀实践（Google Classroom / PowerSchool / Veracross） | 当前状态 | 差距影响 |
|------|--------------------------------------------------------|----------|----------|
| **设置信息架构** | 统一入口，按角色动态显示分组，支持搜索 | ✅ 已统一入口，配置驱动角色路由 | 已达标 |
| **个人资料** | 头像上传 + 字段级权限可见性 | ✅ 头像上传已实现；字段级权限可见性未实现 | 学生可能看到不该看的字段（如自己的手机号由家长管理） |
| **安全中心** | 2FA、会话列表、登录历史、密码泄露检测 | ✅ 2FA TOTP + 会话登出 + 登录历史 | 已达标，密码泄露检测（HaveIBeenPwned）未集成 |
| **通知偏好** | 按事件类型细分，支持渠道矩阵 + 免打扰 + 测试 | ✅ 全部已实现 | 已达标 |
| **主题/语言** | 主题切换 + 语言切换同页 | ✅ 已集成 | 已达标 |
| **AI 配置** | 多 Provider + 测试 + 用量统计 | ✅ 多 Provider + 测试，无用量统计 | 教育机构无法监控 AI 成本（中长期计划） |
| **空状态/骨架屏** | 每个数据区块独立骨架屏 + 空状态 | ✅ 已实现分区 Suspense + 骨架屏 | 已达标 |
| **设置搜索** | 设置项较多时支持快速搜索 | ❌ 未实现 | 设置项目前 4 个标签页，数量尚可，中长期可考虑 |

### 3.2 多角色使用习惯

| 角色 | 优秀实践 | 当前状态 |
|------|----------|----------|
| **admin** | 系统设置与个人设置在同一入口的不同分组 | ✅ `/settings` 个人设置 + `/admin/settings` 系统设置分离 |
| **teacher** | 设置页可快速跳转常用教学功能 | ✅ 有 QuickLinksCard |
| **parent** | 设置页可切换查看不同孩子的通知偏好 | ❌ 仅一套偏好，无法按孩子细分（中长期计划） |
| **student** | 设置页简洁，无系统配置 | ✅ 简洁 |

---

## 四、改进优先级建议

### P0（紧急，影响架构合规）

1. **消除 Profile 概览组件跨模块 data-access 直调**：将 `ProfileStudentOverview` / `ProfileTeacherOverview` 改为通过 props 接收数据，由页面层（app 层）编排 classes/homework data-access 并注入。同时将 dashboard 组件引用改为通过 children/props 传入或抽取为 shared 组件。
2. **消除 profile/page.tsx 角色硬编码**：将 `roles.includes("student")` / `roles.includes("teacher")` 改为配置驱动或权限点判断。

### P1（重要，影响可维护性）

3. **拆分 SecurityCenterCard**：645 行 → 拆分为 `SecurityTwoFactorSection` + `SecurityRecentLoginsSection`，主组件负责数据加载和组合。
4. **拆分 ai-provider-settings-card.tsx**：529 行 → 拆分 Provider 选择器和删除确认 Dialog。
5. **拆分 AdminSettingsView**：4 个 Card 拆分为独立子组件。
6. **settings/page.tsx metadata i18n 化**：改为 `generateMetadata` + `getTranslations`。
7. **统一 i18n 命名空间**：将 `settings.profilePage.*` 合并到 `settings.profile.*`，AvatarUpload 保持 `settings.profile.avatar.*`。

### P2（优化，提升质量）

8. **添加 toSettingItem 单元测试**。
9. **profile 页 AvatarUpload 包裹 Error Boundary**。
10. **封装 signOut 调用**：通过 props 或 action 注入，解耦 next-auth。

---

## 五、架构图同步说明

本次审计发现架构图需补充/修改以下节点：

### 5.1 `004_architecture_impact_map.md` §2.23

- **修改"已知问题"**：新增 v3 发现的 3 项 P0/P1 问题（Profile 概览跨模块 data-access 直调 / profile 角色硬编码 / SecurityCenterCard 超行数上限）
- **更新"文件清单"**：新增拆分后的子组件（SecurityTwoFactorSection / SecurityRecentLoginsSection / AiProviderSelector / SchoolInfoCard / SecurityPolicyCard / FileUploadCard / NotificationConfigCard）
- **更新行数**：SecurityCenterCard 拆分后行数变化

### 5.2 `005_architecture_data.json` settings 节点

- **`modules.settings.knownIssues`**：新增 v3 问题状态
- **`modules.settings.exports.components`**：新增拆分后的子组件
- **`dependencyMatrix`**：settings → classes/homework/dashboard 的依赖类型标注为"组件层直调（待修复）"

---

## 六、验收标准

v3 完成后应满足：

1. `npm run lint` 零错误（warnings 可接受）
2. `npx tsc --noEmit` 零错误
3. Profile 概览组件不直接 import classes/homework/dashboard 模块
4. profile/page.tsx 无 `roles.includes()` 硬编码
5. SecurityCenterCard 拆分后主文件 ≤ 300 行
6. ai-provider-settings-card.tsx 拆分后 ≤ 400 行
7. AdminSettingsView 拆分为 4 个子 Card 组件
8. settings/page.tsx 使用 generateMetadata
9. i18n 命名空间统一为 `settings.profile.*`
10. toSettingItem 有单元测试
11. AvatarUpload 被 Error Boundary 包裹
12. 架构图 004/005 已同步更新
