Files
NextEdu/docs/architecture/audit/shared-audit.md
SpecialX f8dfd1dddd docs: 全项目架构审查与文档体系重写
- 全项目逐文件审查: 4 份审计报告(shared/core-business/management/new-modules)
- 重写 004 架构影响地图: 图优先 + 模块依赖图 + 数据流 + 调用链 + 问题分级
- 更新 005 结构化数据: 新增 architectureOverview/moduleDependencyGraph/knownIssues/dbTables 节点
- 更新 006 功能清单: 143 项功能标注实现状态, P0 覆盖率 80%->92%
- 更新 007 差距审计: v2->v3, P0 完成 69%->84%, 新增架构技术债章节
- 更新 001 项目概览: 6 角色/54 权限/26 模块/54 表
- 新增 docs/README.md 文档索引
- 归档 11 份过时文档(002x2/003/designx8) 标注
- 更新 work_log
2026-06-17 21:51:32 +08:00

14 KiB
Raw Blame History

Shared 基础设施层审查报告

审查日期2026-06-17 审查范围:src/shared/db、lib、hooks、components、types+ src/auth.ts + src/proxy.ts 审查依据:职责单一性、函数复杂度、模块间耦合、架构文档完整性

概览

  • 文件总数69不含测试文件
    • db/3
    • lib/13
    • hooks/7
    • components/ui/34
    • components/a11y/4
    • components/顶层4
    • types/2
    • src/auth.tssrc/proxy.ts2
  • 发现问题数15
  • 严重程度分布:高 3 / 中 9 / 低 3

职责单一性问题

1. src/shared/db/schema.ts

  • 问题:单个文件包含 54 张表定义,共 1111 行,超过项目规则中"任何文件不超过 1000 行"的硬性上限。文件涵盖用户、认证、题库、教学、学校、班级、考试、作业、AI、公告、审计、成绩、文件、课程计划、消息、考勤、排课、选课、监考、学情诊断等十余个业务域。此外分节编号混乱section 12Parent-Student Relations行 958出现在 section 14bNotification Preferences行 934之后P2 段落与主编号交错。
  • 严重程度:高
  • 建议:按业务域拆分为多个 schema 文件(如 schema/auth.tsschema/academic.tsschema/exam.tsschema/audit.ts 等),通过 schema/index.ts 聚合导出。同时修正分节编号。

2. src/auth.ts

  • 问题293 行,混合了多种职责:
    1. NextAuth 配置providers、callbacks、events
    2. 密码安全 DB 操作(getOrCreatePasswordSecurityrecordFailedLoginresetFailedLogin,行 56-130——这些是数据访问层逻辑不应内联在认证配置中
    3. 角色规范化工具(normalizeRoleresolvePrimaryRole,行 13-27
    4. bcrypt 哈希规范化(normalizeBcryptHash,行 29-33
    5. IP 解析(resolveClientIp,行 39-51
    6. authorize 回调内联了限流、锁定检查、密码比对、日志记录全流程86 行)
  • 严重程度:高
  • 建议:将密码安全 DB 操作迁移到 shared/lib/password-security-service.ts(与纯函数的 password-policy.ts 区分);角色规范化迁移到 shared/lib/permissions.ts 或新建 shared/lib/role-utils.tsresolveClientIp 迁移到 shared/lib/http-utils.ts(与三个 logger 共用)。

3. src/shared/lib/ai.ts

  • 问题218 行,混合了 5 类职责:

    1. 请求负载解析与校验(parseAiChatPayload,行 70-96
    2. API Key 加密/解密(encryptAiApiKey/decryptAiApiKey,行 104-124
    3. Provider 配置 DB 查询(getAiProviderConfig,行 126-179
    4. AI 客户端创建与调用(getAiClientcreateAiChatCompletiontestAiProviderConfigtestAiProviderById
    5. 错误格式化(getAiErrorMessage

    其中加密/解密与 Provider 配置查询属于数据层,与 AI 调用本身是不同关注点。

  • 严重程度:中

  • 建议:将加密/解密拆到 shared/lib/crypto.ts(通用加密工具);将 Provider 配置查询拆到 shared/lib/ai-provider-repo.ts(数据访问)。ai.ts 保留负载解析与 AI 调用编排。

4. src/shared/components/onboarding-gate.tsx

  • 问题312 行组件,混合了:
    1. 多步表单 UI
    2. 角色推断业务逻辑(行 90-94通过权限反推角色isAdmin/isTeacher/isStudent/isParent),逻辑脆弱且未使用 usePermission().hasRole()
    3. 硬编码教学学科列表(TEACHER_SUBJECTS,行 20——业务数据固化在 shared 基础设施
    4. 直接 fetch("/api/onboarding/status")fetch("/api/onboarding/complete")——耦合特定 API 路由
  • 严重程度:中
  • 建议:角色判断改用 usePermission().hasRole() 或 session 的 role 字段;学科列表迁移到 modules 层配置或 DBAPI 调用通过 Server Action 封装。组件本身可考虑按步骤拆分子组件。

5. src/shared/components/global-search.tsx

  • 问题221 行组件,混合了:
    1. 搜索 UI 与下拉渲染
    2. 硬编码业务类型(ResultType = "question" | "textbook" | "exam" | "announcement",行 12与图标/标签映射(行 30-42——业务知识泄漏到 shared 层
    3. 直接 fetch("/api/search?...")——耦合特定 API 路由与查询协议
    4. 快捷键、点击外部、键盘导航等交互逻辑内联
  • 严重程度:中
  • 建议:搜索结果类型与图标映射应由 API 返回或从 modules 层注入API 调用抽取为独立 hookuseGlobalSearch);交互逻辑可拆为 useSearchKeyboard 等。

6. src/proxy.ts

  • 问题75 行,硬编码了路由-权限映射(ROUTE_PERMISSIONSAPI_PERMISSIONS,行 8-19使用原始字符串如 "school:manage""exam:read"未复用 Permissions 常量,违反项目规则"前端组件禁止硬编码 role/权限"的精神。resolveDefaultPath(行 21-27将角色到默认路径的业务映射硬编码在代理中。
  • 严重程度:中
  • 建议:权限字符串改用 Permissions.SCHOOL_MANAGE 等常量;路由权限映射迁移到 shared/lib/route-permissions.ts 配置文件;角色-路径映射迁移到 modules 层或路由配置。

7. src/shared/lib/a11y.ts

  • 问题useA11yId(行 7-10是一个 React Hook但放置在 lib/ 目录而非 hooks/ 目录。项目约定 hooks/ 存放所有自定义 Hooklib/ 存放纯工具函数。该文件其余函数(mergeA11yPropsdescribeInputloadingAria)是纯函数,放置正确。
  • 严重程度:低
  • 建议:将 useA11yId 迁移到 shared/hooks/use-a11y-id.tsa11y.ts 保留纯函数。

过耦合函数

1. resolveDataScope @ src/shared/lib/auth-guard.ts:64-130

  • 行数67
  • 参数数2userId: string, roleNames: string[]
  • 问题:单个函数内根据角色分支查询 4 张不同的表(gradesclassesclassSubjectTeachersparentStudentRelations),将权限范围解析与数据访问混合。每个角色分支的查询逻辑独立,新增角色需修改此函数,违反开闭原则。
  • 建议:将各角色的数据范围查询拆为独立函数(如 resolveTeacherScoperesolveParentScope),或迁移到各模块的 data-access 层,resolveDataScope 仅做分发。

2. getAiProviderConfig @ src/shared/lib/ai.ts:126-179

  • 行数53
  • 参数数1providerId?: string
  • 问题:函数内有三段几乎相同的 DB 查询分支(按 providerId、按 isDefault、fallback每段都 select 相同的字段、解密 apiKey、返回相同结构存在明显代码重复。
  • 建议:提取公共 mapProviderRow(row) 函数,三个分支简化为查询条件不同。或合并为单查询带 OR 条件 + 排序优先级。

3. authorizeNextAuth Credentials 回调)@ src/auth.ts:143-229

  • 行数86
  • 参数数1credentials
  • 问题:单函数内串联了:邮箱密码校验 → 速率限制 → DB 用户查询 → 账户锁定检查 → 密码比对 → 失败计数 → 成功重置 → 角色查询 → 返回。流程长且混合了限流、安全策略、认证、日志多个关注点。内部还使用 Promise.all + 动态 import(行 165-168加载 @/shared/db 和 schema写法不寻常。
  • 建议:将流程拆分为 checkRateLimitcheckAccountLockoutverifyPasswordloadUserRoles 等步骤函数,authorize 仅编排。动态 import 改为静态 import。

4. OnboardingGate 组件 @ src/shared/components/onboarding-gate.tsx:27-312

  • 行数285组件函数体
  • 参数数0无 props内部消费 session
  • 问题单组件承担了状态检查、4 步表单、角色推断、API 提交、路由跳转。组件内 9 个 useState3 个 useEffect,逻辑密集。
  • 建议:按步骤拆分为 OnboardingRoleStepOnboardingProfileStepOnboardingRoleDetailStepOnboardingCompleteStep 子组件;提取 useOnboarding hook 封装状态与提交逻辑。

5. GlobalSearch 组件 @ src/shared/components/global-search.tsx:49-221

  • 行数172组件函数体
  • 参数数2className?, placeholder?
  • 问题单组件承担了输入控制、防抖搜索、快捷键监听、点击外部关闭、键盘导航、结果渲染。6 个 useState3 个 useEffect
  • 建议:提取 useGlobalSearch(query) hook 封装搜索请求与状态;提取 useSearchKeyboard 封装快捷键与导航。组件仅负责渲染。

模块间依赖问题

1. shared 层与 @/auth 的循环依赖

  • 涉及模块shared/lib/{audit-logger, change-logger, auth-guard}@/authshared/lib/{login-logger, permissions, password-policy, rate-limit} + shared/db

  • 问题类型:循环依赖

  • 问题详情

    • shared/lib/audit-logger.ts(行 7import { auth } from "@/auth"
    • shared/lib/change-logger.ts(行 6import { auth } from "@/auth"
    • shared/lib/auth-guard.ts(行 1import { auth } from "@/auth"
    • src/auth.ts 反向依赖 shared/lib/permissionsshared/lib/login-loggershared/lib/password-policyshared/lib/rate-limitshared/db

    这构成了 shared/lib/*authshared/lib/* 的循环。虽然 NextAuth 的 auth() 函数是运行时调用而非模块级副作用,目前不会导致运行时错误,但架构上 shared 基础设施层不应反向依赖业务层的认证入口。

  • 建议:将 auth() 的 session 获取抽象为接口或通过参数注入。logger 函数改为接收 session 参数(由调用方传入),而非内部调用 auth()。或创建 shared/lib/session.ts 封装 session 获取,auth.ts 和 logger 都依赖它,打破循环。

2. shared 层对根模块 @/auth 的反向依赖

  • 涉及模块shared/lib/*@/auth(根模块)
  • 问题类型:反向依赖
  • 问题详情src/auth.ts 位于项目根目录,属于应用层(非 shared 层。shared 层应是被依赖方不应依赖应用层模块。三个文件audit-logger、change-logger、auth-guard直接 import @/auth,使 shared 层无法独立测试或复用。
  • 建议:同上,通过依赖注入或提取 shared/lib/session.ts 解耦。

3. 三个 logger 重复实现 IP/Header 提取

  • 涉及模块shared/lib/audit-loggershared/lib/change-loggershared/lib/login-loggersrc/auth.ts

  • 问题类型过度耦合DRY 违反)

  • 问题详情:三个 logger 各自重复实现相同的 IP/User-Agent 提取逻辑:

    • audit-logger.ts(行 27-32headerList.get("x-forwarded-for") ?? headerList.get("x-real-ip") ?? "unknown"
    • change-logger.ts(行 27-31相同逻辑
    • login-logger.ts(行 26-31相同逻辑
    • auth.ts(行 39-51resolveClientIp 也是类似逻辑(取 x-forwarded-for 第一段)

    四处实现略有差异auth.ts 取逗号分隔第一段,其他取全值),存在不一致风险。

  • 建议:提取 shared/lib/http-utils.ts,导出 getClientIp()getUserAgent() 统一复用。


架构文档改进建议

  1. 补充依赖关系图:当前 004 文档以函数/常量为粒度列举导出,但缺少模块间依赖方向的可视化图。建议在 005 JSON 中增加 dependencyMatrix 节点,记录 shared/lib/* → @/auth@/auth → shared/lib/* 等依赖边,并在 004 Markdown 中用 Mermaid 图渲染。本次审查发现的循环依赖shared ↔ auth在当前文档中完全不可见。

  2. 标注循环依赖与反向依赖004/005 文档应明确标注 shared/lib/{audit-logger, change-logger, auth-guard}@/auth 的依赖,以及这与 @/authshared/lib/* 的依赖构成的循环。当前文档将 auth 模块与 shared 模块分别描述,未揭示二者双向依赖。

  3. 修正 schema.ts 分节编号004 文档的"数据库表"章节按表名平铺列举,未反映 schema.ts 源文件中的分节结构。建议文档增加 schema.ts 分节映射表,并修正源文件中 section 12 出现在 section 14b 之后的编号混乱。

  4. 增加 shared 层边界说明004 文档应明确 shared 层"不应依赖应用层模块(如 @/auth"的架构约束,以及哪些文件属于 shared 层的对外公共 API。当前文档未说明 shared 与根模块auth.ts、proxy.ts的边界。

  5. 补充函数复杂度标注005 JSON 中每个函数已有签名记录,但缺少行数与参数数量字段。建议增加 "lines""paramCount" 字段,便于自动识别过耦合函数(如本次发现的 authorize 86 行、resolveDataScope 67 行)。

  6. 记录 proxy.ts 的路由权限映射004 文档未记录 proxy.ts 中的 ROUTE_PERMISSIONSAPI_PERMISSIONS 硬编码映射,也未说明这些映射与 Permissions 常量的关系。建议在 005 JSON 的 routes 节点中补充代理层权限规则。


附:审查范围文件清单

目录 文件数 最大文件(行数) 备注
src/shared/db/ 3 schema.ts (1111) 超过 1000 行硬性上限
src/shared/lib/ 13 ai.ts (218)
src/shared/hooks/ 7 use-aria-live.ts (88)
src/shared/components/ui/ 34 chart.tsx (329) 多为标准 shadcn/ui 组件
src/shared/components/a11y/ 4 focus-trap.tsx (110)
src/shared/components/(顶层) 4 onboarding-gate.tsx (312)
src/shared/types/ 2 permissions.ts (92)
src/auth.ts 1 auth.ts (293)
src/proxy.ts 1 proxy.ts (75)

注:components/ui/ 下 34 个文件多为 shadcn/ui 标准生成组件(基于 Radix UI职责单一未发现结构性问题故未逐一列入问题清单。chart.tsx329 行)为标准 shadcn chart 组件,行数较高但属框架约定,可接受。