# Shared 基础设施层审查报告

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

## 概览

- 文件总数：69（不含测试文件）
  - `db/`：3
  - `lib/`：17（新增 4 个 auth 拆分文件）
  - `lib/ai/`：6（新增目录，P2-2 拆分）
  - `hooks/`：7
  - `components/ui/`：34
  - `components/a11y/`：4
  - `components/`（顶层）：4
  - `types/`：2
  - `src/auth.ts`、`src/proxy.ts`：2
- 发现问题数：15
- 严重程度分布：高 3 / 中 9 / 低 3
- 已修复问题：4（auth.ts 拆分、ai.ts 拆分、循环依赖、反向依赖）
- 待修复问题：11（含 schema.ts 拆分 P2-1、onboarding-gate、global-search、proxy.ts 等）

---

## 职责单一性问题

### 1. `src/shared/db/schema.ts`

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

### 2. `src/auth.ts` ✅ 已修复

- **问题**：~~293 行，混合了多种职责~~
  1. NextAuth 配置（providers、callbacks、events）
  2. 密码安全 DB 操作（`getOrCreatePasswordSecurity`、`recordFailedLogin`、`resetFailedLogin`，行 56-130）
  3. 角色规范化工具（`normalizeRole`、`resolvePrimaryRole`，行 13-27）
  4. bcrypt 哈希规范化（`normalizeBcryptHash`，行 29-33）
  5. IP 解析（`resolveClientIp`，行 39-51）
  6. `authorize` 回调内联了限流、锁定检查、密码比对、日志记录全流程（86 行）
- **严重程度**：高
- **修复状态**：2026-06-17 已完成拆分（P1-3）。auth.ts 从 293 行降至 193 行，4 类职责分别迁移到 shared/lib 下的独立文件：
  - `password-security-service.ts`（84 行）- 密码安全 DB 操作
  - `role-utils.ts`（31 行）- 角色规范化
  - `bcrypt-utils.ts`（18 行）- bcrypt 哈希规范化
  - `http-utils.ts`（27 行）- IP 解析（与三个 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 客户端创建与调用（`getAiClient`、`createAiChatCompletion`、`testAiProviderConfig`、`testAiProviderById`）
  5. 错误格式化（`getAiErrorMessage`）
- **严重程度**：中
- **修复状态**：2026-06-17 已完成拆分（P2-2，commit 6588f74）。原 `ai.ts`（218 行）拆分为 `src/shared/lib/ai/` 目录 6 个文件：
  - `payload-parser.ts`（96 行）- 请求负载解析
  - `api-key-crypto.ts`（34 行）- API Key 加密/解密
  - `provider-config.ts`（66 行）- Provider 配置查询
  - `client.ts`（67 行）- AI 客户端创建与调用
  - `errors.ts`（9 行）- 错误格式化
  - `index.ts`（7 行）- 聚合导出
  
  原 `ai.ts` 保留为向后兼容的重导出文件（9 行），调用方无需修改 import 路径。

### 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 层配置或 DB；API 调用通过 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 调用抽取为独立 hook（`useGlobalSearch`）；交互逻辑可拆为 `useSearchKeyboard` 等。

### 6. `src/proxy.ts`

- **问题**：75 行，硬编码了路由-权限映射（`ROUTE_PERMISSIONS`、`API_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/` 存放所有自定义 Hook，`lib/` 存放纯工具函数。该文件其余函数（`mergeA11yProps`、`describeInput`、`loadingAria`）是纯函数，放置正确。
- **严重程度**：低
- **建议**：将 `useA11yId` 迁移到 `shared/hooks/use-a11y-id.ts`，`a11y.ts` 保留纯函数。

---

## 过耦合函数

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

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

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

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

### 3. `authorize`（NextAuth Credentials 回调）@ `src/auth.ts:143-229`

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

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

- **行数**：285（组件函数体）
- **参数数**：0（无 props，内部消费 session）
- **问题**：单组件承担了状态检查、4 步表单、角色推断、API 提交、路由跳转。组件内 9 个 `useState`，3 个 `useEffect`，逻辑密集。
- **建议**：按步骤拆分为 `OnboardingRoleStep`、`OnboardingProfileStep`、`OnboardingRoleDetailStep`、`OnboardingCompleteStep` 子组件；提取 `useOnboarding` hook 封装状态与提交逻辑。

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

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

---

## 模块间依赖问题

### 1. shared 层与 `@/auth` 的循环依赖 ✅ 已修复

- **涉及模块**：`shared/lib/{audit-logger, change-logger, auth-guard}` → `@/auth` → `shared/lib/{login-logger, permissions, password-policy, rate-limit}` + `shared/db`
- **问题类型**：循环依赖
- **问题详情**：
  - `shared/lib/audit-logger.ts`（原行 7）`import { auth } from "@/auth"`
  - `shared/lib/change-logger.ts`（原行 6）`import { auth } from "@/auth"`
  - `shared/lib/auth-guard.ts`（原行 1）`import { auth } from "@/auth"`
  - 而 `src/auth.ts` 反向依赖 `shared/lib/permissions`、`shared/lib/login-logger`、`shared/lib/password-policy`、`shared/lib/rate-limit`、`shared/db`
  
  这构成了 `shared/lib/*` → `auth` → `shared/lib/*` 的循环。
- **修复状态**：2026-06-17 已完成（P0-3）。3 个文件（audit-logger.ts、change-logger.ts、auth-guard.ts）将静态 `import { auth } from "@/auth"` 改为动态 `const { auth } = await import("@/auth")`，打破模块级静态循环依赖。运行时调用链保持不变，但模块加载图无环。

### 2. shared 层对根模块 `@/auth` 的反向依赖 ✅ 已修复

- **涉及模块**：`shared/lib/*` → `@/auth`（根模块）
- **问题类型**：反向依赖
- **问题详情**：`src/auth.ts` 位于项目根目录，属于应用层（非 shared 层）。shared 层应是被依赖方，不应依赖应用层模块。三个文件（audit-logger、change-logger、auth-guard）直接 import `@/auth`，使 shared 层无法独立测试或复用。
- **修复状态**：2026-06-17 已完成（P0-3）。通过动态 import 打破静态反向依赖。shared 层不再有对 `@/auth` 的静态 import，仅保留运行时动态调用（用于获取 session）。

### 3. 三个 logger 重复实现 IP/Header 提取 ✅ 部分修复

- **涉及模块**：`shared/lib/audit-logger`、`shared/lib/change-logger`、`shared/lib/login-logger`、`src/auth.ts`
- **问题类型**：过度耦合（DRY 违反）
- **问题详情**：三个 logger 各自重复实现相同的 IP/User-Agent 提取逻辑：
  - `audit-logger.ts`（行 27-32）：`headerList.get("x-forwarded-for") ?? headerList.get("x-real-ip") ?? "unknown"`
  - `change-logger.ts`（行 27-31）：相同逻辑
  - `login-logger.ts`（行 26-31）：相同逻辑
  - `auth.ts`（原行 39-51）：`resolveClientIp` 也是类似逻辑（取 `x-forwarded-for` 第一段）
  
  四处实现略有差异（auth.ts 取逗号分隔第一段，其他取全值），存在不一致风险。
- **修复状态**：2026-06-17 部分修复（P1-3）。`src/auth.ts` 的 `resolveClientIp` 已迁移到 `shared/lib/http-utils.ts`（27 行），auth.ts 改为从该文件导入。但三个 logger 文件内部的 IP/Header 提取逻辑尚未统一到 `http-utils.ts`（P2 待处理）。

---

## 架构文档改进建议

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` 的依赖，以及这与 `@/auth` 对 `shared/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_PERMISSIONS` 和 `API_PERMISSIONS` 硬编码映射，也未说明这些映射与 `Permissions` 常量的关系。建议在 005 JSON 的 `routes` 节点中补充代理层权限规则。

---

## 附：审查范围文件清单

| 目录 | 文件数 | 最大文件（行数） | 备注 |
|------|--------|------------------|------|
| `src/shared/db/` | 3 | schema.ts (1111) | **超过 1000 行硬性上限**（P2-1 待拆分） |
| `src/shared/lib/` | 17 | password-security-service.ts (84) | ai.ts 已拆分为 ai/ 目录（P2-2）；新增 4 个 auth 拆分文件（P1-3） |
| `src/shared/lib/ai/` | 6 | payload-parser.ts (96) | 新增目录（P2-2 拆分） |
| `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 (193) | ✅ 已从 293 行降至 193 行（P1-3 拆分） |
| `src/proxy.ts` | 1 | proxy.ts (75) | |

> 注：`components/ui/` 下 34 个文件多为 shadcn/ui 标准生成组件（基于 Radix UI），职责单一，未发现结构性问题，故未逐一列入问题清单。`chart.tsx`（329 行）为标准 shadcn chart 组件，行数较高但属框架约定，可接受。
>
> **变更说明**：
> - `src/shared/lib/ai.ts`（原 218 行）已拆分为 `ai/` 目录 6 个文件，原文件保留为 9 行重导出（P2-2）
> - `src/auth.ts`（原 293 行）已拆分出 4 个 shared/lib 文件，自身降至 193 行（P1-3）
> - `src/shared/lib/` 新增：`password-security-service.ts`（84 行）、`role-utils.ts`（31 行）、`bcrypt-utils.ts`（18 行）、`http-utils.ts`（27 行）
