- Update architecture impact map, data, feature checklist, gap audit - Add audit reports for dashboard, exam-homework, grades-diagnostic, settings-profile, textbooks - Update bug reports (admin, teacher, lesson-preparation, others, shared) - Update coding standards, DR plan, design docs, and README
13 KiB
设置和个人信息模块审计报告 v2
审查日期:2026-06-22 审查范围:
src/modules/settings/**、src/app/(dashboard)/settings/**、src/app/(dashboard)/admin/settings/**、src/app/(dashboard)/profile/**上一版本:settings-profile-audit-report.md(v1,P0/P1/P2 共 13 项已全部完成) 架构图参考:docs/architecture/004_architecture_impact_map.md§2.23、docs/architecture/005_architecture_data.json
一、v1 完成情况回顾
v1 报告中的 13 项改进建议已全部完成:
| 编号 | 优先级 | 标题 | 状态 |
|---|---|---|---|
| P0-1 | P0 | 创建 settings i18n 命名空间 | ✅ 已完成 |
| P0-2 | P0 | 消除跨模块 action 直调(SettingsService 接口) | ✅ 已完成 |
| P0-3 | P0 | AdminSettingsView 接入真实数据层 | ✅ 已完成(新增 system_settings 表 + data-access + actions) |
| P1-4 | P1 | 配置驱动角色路由 | ✅ 已完成 |
| P1-5 | P1 | 分区 Error Boundary + Suspense | ✅ 已完成 |
| P1-6 | P1 | Profile 页面拆分 | ✅ 已完成 |
| P1-7 | P1 | 移除 as 断言 |
✅ 已完成 |
| P2-8 | P2 | 头像上传 | ✅ 已完成(AvatarUpload + actions-avatar) |
| P2-9 | P2 | 2FA / 会话管理 | ✅ 已完成(SecurityCenterCard + actions-security) |
| P2-10 | P2 | 通知测试按钮 | ✅ 已完成(sendTestNotificationAction) |
| P2-11 | P2 | 语言切换集成 | ✅ 已完成(ThemePreferencesCard 集成 LocaleSwitcher) |
| P2-12 | P2 | 埋点接口 | ✅ 已完成(SettingsService.trackEvent 预留) |
| P2-13 | P2 | a11y 修复 | ✅ 已完成 |
二、v2 新发现的问题
2.1 安全中心 2FA 为纯占位实现(P0)
| 位置 | 问题 | 严重性 |
|---|---|---|
| actions-security.ts L21-46 | toggleTwoFactorAction 仅将 twoFactorEnabled 写入 system_settings 表,未接入 TOTP 密钥绑定、一次性码校验、备份码生成等真实 2FA 流程 |
P0 |
| security-center-card.tsx L105-120 | 用户开启 2FA 后立即显示"已启用",但实际登录时不会要求二次验证,造成虚假安全感 | P0 |
| 同文件 L70 注释 | "占位实现,仅记录用户偏好" — 注释承认未接入真实流程 | P0 |
后果:用户以为启用了 2FA 但实际无效;安全合规审计会失败。
建议:在 v2 中要么 (a) 完整实现 TOTP 流程(绑定 authenticator + 验证一次性码 + 备份码),要么 (b) 将开关改为"即将推出"禁用状态,避免误导。
2.2 通知测试按钮为纯占位实现(P1)
| 位置 | 问题 | 严重性 |
|---|---|---|
| actions-notifications.ts L29-39 | sendTestNotificationAction 仅 console.info + Promise.resolve(),未调用真实通知发送服务 |
P1 |
| notification-preferences-form.tsx L119-133 | 点击测试按钮后总是显示"测试通知已发送",但用户不会收到任何通知 | P1 |
后果:用户以为测试通知已发送但收不到,无法真正验证渠道配置。
建议:接入 notifications/dispatcher.ts 的真实发送逻辑,或暂时将按钮改为禁用状态并标注"功能开发中"。
2.3 头像上传未清理旧文件(P1)
| 位置 | 问题 | 严重性 |
|---|---|---|
| actions-avatar.ts L15-34 | updateUserAvatarAction 更新 users.image 字段后,旧头像文件仍留在文件存储中,无清理逻辑 |
P1 |
| avatar-upload.tsx L108-124 | handleRemove 调用 removeUserAvatarAction 仅清空 users.image,未删除实际文件 |
P1 |
后果:存储成本累积;孤儿文件无法回收。
建议:在 removeUserAvatarAction 和 updateUserAvatarAction 中,更新数据库前先记录旧 URL,更新成功后异步调用 files/data-access.deleteFile 清理旧文件。
2.4 SecurityCenterCard 缺少"登出其他会话"功能(P1)
| 位置 | 问题 | 严重性 |
|---|---|---|
| security-center-card.tsx 全文 | 仅展示登录历史,无法远程登出其他设备的会话 | P1 |
| actions-security.ts 全文 | 无 revokeSessionAction 或类似 Server Action |
P1 |
后果:用户发现可疑登录后无法主动处置,只能修改密码被动应对。
建议:新增 revokeSessionAction(sessionToken: string),删除 sessions 表对应记录;UI 在每条登录历史旁显示"登出"按钮(当前会话除外)。
2.5 AdminSettingsView 缺少表单变更检测(P1)
| 位置 | 问题 | 严重性 |
|---|---|---|
| admin-settings-view.tsx L107-122 | handleSave 无条件保存,即使用户未修改任何字段也会触发 upsert 全部 16 个设置项 |
P1 |
| 同文件 L415 | "Reset" 按钮直接 setValues(DEFAULT_VALUES) 而非恢复到加载时的值,会丢失未保存的服务端数据 |
P1 |
后果:无谓的数据库写入;Reset 语义错误。
建议:维护 dirty 状态(JSON.stringify(values) !== JSON.stringify(loadedValues)),Save 按钮禁用直到 dirty;Reset 恢复到 loadedValues 而非 DEFAULT_VALUES。
2.6 i18n 键 settings.profile.avatar 在 profilePage 命名空间下缺失(P2)
| 位置 | 问题 | 严重性 |
|---|---|---|
| profile/page.tsx | 使用 <AvatarUpload> 但页面其他文本使用 settings.profilePage.* 命名空间,而 AvatarUpload 内部使用 settings.profile.avatar.*,命名空间不一致 |
P2 |
后果:i18n 命名空间结构混乱,维护时易混淆。
建议:统一为 settings.profile.avatar.* 或 settings.profilePage.avatar.*,二选一。
2.7 SecurityCenterCard 未传递 currentDeviceLabel(P2)
| 位置 | 问题 | 严重性 |
|---|---|---|
| settings-view.tsx L182 | <SecurityCenterCard /> 未传递 currentDeviceLabel prop |
P2 |
| security-center-card.tsx L192-194 | isCurrent 判断永远为 false,"当前会话"徽章永远不会显示 |
P2 |
后果:用户无法在登录历史中识别当前会话。
建议:在 Server Component 层获取 headers().get("user-agent"),通过 props 传递到 SecurityCenterCard。
2.8 头像上传未限制文件名长度(P2)
| 位置 | 问题 | 严重性 |
|---|---|---|
| avatar-upload.tsx L49-57 | validateFile 仅校验类型和大小,未校验文件名长度 |
P2 |
后果:超长文件名可能导致数据库 varchar 字段截断或存储错误。
建议:添加 file.name.length > 255 校验。
2.9 通知偏好表单未做 dirty 检测(P2)
| 位置 | 问题 | 严重性 |
|---|---|---|
| notification-preferences-form.tsx L98-117 | Save 按钮始终可点击,无 dirty 检测 | P2 |
后果:用户误点 Save 触发不必要的 Server Action 调用。
建议:维护 dirty 状态,Save 按钮在无变更时禁用。
2.10 AdminSettingsView 文件行数接近上限(P2)
| 位置 | 问题 | 严重性 |
|---|---|---|
| admin-settings-view.tsx | 425 行,接近 500 行建议上限 | P2 |
后果:可读性下降,维护困难。
建议:将 4 个 Card 拆分为独立子组件(SchoolInfoCard / SecurityPolicyCard / FileUploadCard / NotificationConfigCard),主组件仅负责表单状态和提交逻辑。
2.11 缺少单元测试(P2)
| 位置 | 问题 | 严重性 |
|---|---|---|
src/modules/settings/**/*.test.ts |
整个 settings 模块无任何单元测试文件 | P2 |
后果:重构无回归保障;纯函数(toSettingItem、parseUserAgent、formatRelativeTime)无法独立验证。
建议:为以下纯函数添加单元测试:
actions-system-settings.ts的toSettingItem(值类型转换)security-center-card.tsx的parseUserAgent、formatRelativeTimelib/student-overview-data.ts的buildStudentOverviewData、computeStudentStats
2.12 2FA 状态查询存在 N+1 问题(P2)
| 位置 | 问题 | 严重性 |
|---|---|---|
| actions-security.ts L48-62 | getTwoFactorStatus 对每个用户分别查询 3 次 system_settings 表(enabled / method / enabledAt),共 3 次 DB 往返 |
P2 |
后果:每次加载安全中心页面额外 3 次 DB 查询。
建议:使用 getSystemSettingsByCategory("security_policy") 一次查询所有 security_policy 分类下的设置,在内存中过滤当前用户的键。
三、改进优先级建议(v2)
P0(紧急,影响安全/合规)
- 2FA 真实实现或禁用开关:要么完整实现 TOTP 流程,要么将开关改为"即将推出"禁用状态,避免虚假安全感。
P1(重要,影响功能完整性)
- 通知测试按钮接入真实发送逻辑:调用
notifications/dispatcher.ts发送真实通知,或暂时禁用按钮。 - 头像上传清理旧文件:在
removeUserAvatarAction和updateUserAvatarAction中添加旧文件清理逻辑。 - 会话远程登出:新增
revokeSessionAction,UI 添加"登出"按钮。 - AdminSettingsView 表单 dirty 检测:Save 按钮在无变更时禁用;Reset 恢复到加载值。
P2(优化,提升质量)
- 统一 i18n 命名空间:
settings.profile.avatar与settings.profilePage二选一。 - SecurityCenterCard 传递 currentDeviceLabel:Server Component 层获取 user-agent 传入。
- 头像上传文件名长度校验:添加
file.name.length > 255校验。 - 通知偏好表单 dirty 检测:Save 按钮在无变更时禁用。
- AdminSettingsView 拆分子组件:4 个 Card 拆分为独立组件。
- 添加单元测试:为纯函数添加测试覆盖。
- 2FA 状态查询优化:合并 3 次 DB 查询为 1 次。
四、v2 实施计划
4.1 P0:2FA 真实实现或禁用
方案选择:考虑到完整 TOTP 实现需要额外的库(otplib)和 UI(QR 码扫描、备份码展示),v2 阶段先将开关改为"即将推出"禁用状态,避免虚假安全感。完整 TOTP 实现留待 v3。
改动范围:
security-center-card.tsx:Switch 添加disabled属性,显示"即将推出"徽章- i18n:添加
twoFactor.comingSoon键
4.2 P1:通知测试按钮接入真实逻辑
方案选择:调用 notifications/dispatcher.ts 的 dispatchNotification 函数发送真实通知。
改动范围:
actions-notifications.ts:导入dispatchNotification,根据 channel 调用对应渠道- 失败时返回具体错误信息
4.3 P1:头像上传清理旧文件
改动范围:
actions-avatar.ts:在更新前记录旧 image URL,更新成功后调用files/data-access.deleteFileByUrl清理- 需要先确认
files/data-access是否有deleteFileByUrl函数,若无则新增
4.4 P1:会话远程登出
改动范围:
actions-security.ts:新增revokeSessionAction(sessionToken: string)security-center-card.tsx:每条登录历史旁添加"登出"按钮(当前会话除外)- i18n:添加
recentLogins.revoke/revokeSuccess/revokeFailure键
4.5 P1:AdminSettingsView dirty 检测
改动范围:
admin-settings-view.tsx:维护loadedValues状态,计算isDirty,Save 按钮禁用逻辑,Reset 恢复到loadedValues
4.6 P2:其他优化项
逐项实施,每项改动范围较小,详见各小节。
五、架构图同步说明
v2 改动完成后需同步更新:
5.1 004_architecture_impact_map.md §2.23
- 更新"已知问题":标注 v2 新增/修复项
- 更新"文件清单":新增测试文件、拆分后的子组件
5.2 005_architecture_data.json
modules.settings.exports:新增revokeSessionAction等modules.settings.knownIssues:更新 v2 状态dependencyMatrix:settings → notifications 依赖(通知测试真实发送)
六、验收标准
v2 完成后应满足:
npm run lint零错误(warnings 可接受)npx tsc --noEmit零错误- 2FA 开关为禁用状态或完整 TOTP 实现(二选一)
- 通知测试按钮发送真实通知或禁用(二选一)
- 头像更换/删除后旧文件被清理
- 安全中心可远程登出其他会话
- AdminSettingsView Save 按钮在无变更时禁用
- 至少 3 个纯函数有单元测试
- 架构图 004/005 已同步更新