# 个人信息配置和设置模块审计报告 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 | `` 直接渲染,无 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 已同步更新