diff --git a/docs/architecture/004_architecture_impact_map.md b/docs/architecture/004_architecture_impact_map.md index da36186..bd26a7f 100644 --- a/docs/architecture/004_architecture_impact_map.md +++ b/docs/architecture/004_architecture_impact_map.md @@ -215,18 +215,20 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions" │ student/learning/assignments/[assignmentId]/page.tsx │ │ └─▶ homework/actions.startHomeworkSubmissionAction │ │ ├─▶ requirePermission(HOMEWORK_SUBMIT) │ -│ ├─▶ db.query.homeworkAssignments [⚠️ 直接 DB] │ -│ ├─▶ db.insert(homeworkSubmissions) [⚠️ 直接 DB] │ +│ ├─▶ data-access-write.startHomeworkSubmission ✅ P1-2 已修复 │ +│ │ └─▶ db.insert(homeworkSubmissions) [shared/db] │ │ └─▶ 返回 submissionId │ │ │ │ └─▶ homework/actions.saveHomeworkAnswerAction │ -│ └─▶ db.transaction(insert homeworkAnswers) [⚠️ 直接 DB] │ +│ └─▶ data-access-write.saveHomeworkAnswer ✅ P1-2 已修复 │ +│ └─▶ db.transaction(insert homeworkAnswers) [shared/db] │ │ │ │ └─▶ homework/actions.submitHomeworkAction │ -│ └─▶ db.update(homeworkSubmissions.status="submitted") │ +│ └─▶ data-access-write.submitHomework ✅ P1-2 已修复 │ +│ └─▶ db.update(homeworkSubmissions.status="submitted") │ │ │ │ 数据写入:homeworkSubmissions 表 + homeworkAnswers 表 │ -│ ⚠️ 违规:actions 层直接 DB 操作,应下沉到 data-access │ +│ ✅ P1-2 已修复:actions 层不再直接 DB 操作,已下沉到 data-access-write│ └─────────────────────────────────────────────────────────────────────┘ │ ▼ @@ -236,7 +238,8 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions" │ teacher/homework/submissions/[submissionId]/page.tsx │ │ └─▶ homework/actions.gradeHomeworkSubmissionAction │ │ ├─▶ requirePermission(HOMEWORK_GRADE) │ -│ └─▶ db.update(homeworkAnswers) 循环 [⚠️ 直接 DB] │ +│ └─▶ data-access-write.gradeHomeworkSubmission ✅ P1-2 已修复│ +│ └─▶ db.update(homeworkAnswers) 循环 [shared/db] │ │ │ │ 数据更新:homeworkAnswers.isCorrect / score / feedback │ └─────────────────────────────────────────────────────────────────────┘ @@ -308,11 +311,10 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions" │ ├─▶ requirePermission(HOMEWORK_SUBMIT) │ - ├─▶ db.query.homeworkSubmissions.findFirst ❌ 应在 data-access - │ (校验 submission 归属) - │ - ├─▶ db.update(homeworkSubmissions) ❌ 应在 data-access - │ SET status = "submitted", submittedAt = now() + ├─▶ data-access-write.submitHomework ✅ P1-2 已修复 + │ (校验 submission 归属 + 更新状态) + │ └─▶ db.update(homeworkSubmissions) + │ SET status = "submitted", submittedAt = now() │ └─▶ 返回 ActionState<{ submissionId }> ``` @@ -370,7 +372,7 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions" - ❌ P0:`shared/lib/*` ↔ `@/auth` 循环依赖 - ⚠️ P1:`schema.ts` 1111 行(54 张表混合,超 1000 硬上限) - ✅ P1:~~`auth.ts` 293 行混合 5 类职责~~ 已拆分(4 个辅助函数组迁移至 `shared/lib/{role-utils,bcrypt-utils,http-utils,password-security-service}`,auth.ts 仅保留 NextAuth 配置) -- ⚠️ P2:`ai.ts` 218 行混合 5 类职责 +- ✅ P2-2 已修复:~~`ai.ts` 218 行混合 5 类职责~~ 已拆分为 `ai/` 目录(payload-parser.ts/api-key-crypto.ts/provider-config.ts/client.ts/errors.ts/index.ts),原 `ai.ts` 保留为向后兼容的重导出文件(12 行) - ⚠️ P2:`onboarding-gate.tsx` 业务逻辑泄漏到 shared **文件清单**: @@ -381,7 +383,13 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions" | `db/index.ts` | - | Drizzle 客户端 | | `lib/auth-guard.ts` | - | 认证上下文 + 权限校验 + DataScope | | `lib/permissions.ts` | - | 角色-权限映射 | -| `lib/ai.ts` | 218 | AI 调用 + Provider 配置 + 加密 | +| `lib/ai.ts` | 12 | 向后兼容重导出(P2-2 已拆分到 `ai/` 目录) | +| `lib/ai/payload-parser.ts` | 96 | 请求负载解析 | +| `lib/ai/api-key-crypto.ts` | 34 | API Key 加密/解密 | +| `lib/ai/provider-config.ts` | 66 | Provider 配置查询 | +| `lib/ai/client.ts` | 67 | AI 客户端创建与调用 | +| `lib/ai/errors.ts` | 9 | 错误格式化 | +| `lib/ai/index.ts` | 7 | 聚合导出 | | `lib/audit-logger.ts` | - | 操作日志 | | `lib/change-logger.ts` | - | 数据变更日志 | | `lib/login-logger.ts` | - | 登录日志 | @@ -406,8 +414,8 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions" **职责**:考试全生命周期管理(创建/编辑/预览/发布/删除/复制)+ AI 辅助出题。 **导出函数**: -- Actions:`createExamAction` / `createAiExamAction` / `previewAiExamAction` / `regenerateAiQuestionAction` / `updateExamAction` / `deleteExamAction` / `duplicateExamAction` / `getExamPreviewAction` / `getSubjectsAction` / `getGradesAction` -- Data-access:`getExams` / `getExamById` / `persistExamDraft` / `persistAiGeneratedExamDraft` / `buildExamDescription` / `resolveSubjectGradeNames` +- Actions:`createExamAction` / `createAiExamAction` / `previewAiExamAction` / `regenerateAiQuestionAction` / `updateExamAction` / `deleteExamAction` / `duplicateExamAction` / `getExamPreviewAction` / `getSubjectsAction` / `getGradesAction`(✅ P1-2 已修复:actions 层不再直接访问 DB,全部下沉到 data-access) +- Data-access:`getExams` / `getExamById` / `persistExamDraft` / `persistAiGeneratedExamDraft` / `buildExamDescription` / `resolveSubjectGradeNames` / `getExamCreatorId` / `updateExamWithQuestions` / `deleteExamById` / `duplicateExam` / `getExamPreview` / `getExamSubjects` / `getExamGrades`(后 7 个为 P1-2 新增) - AI Pipeline:`generateAiCreateDraftFromSource` / `generateAiPreviewData` / `regenerateAiQuestionByInstruction` **依赖关系**: @@ -418,15 +426,15 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions" - ❌ P0:`persistAiGeneratedExamDraft` 直接 insert 到 `questions` 表 - ❌ P0:`getExams`/`getExamById` 直查 `classes` 表 - ❌ P1:`getSubjectsAction`/`getGradesAction` 直查 `subjects`/`grades` 表(应属 school 模块) -- ❌ P1:`actions.ts` 832 行(超 800 建议),多处直接 DB 操作 +- ✅ P1-2 已修复:~~`actions.ts` 832 行(超 800 建议),多处直接 DB 操作~~ DB 操作已下沉到 data-access,actions.ts 现 766 行 - ⚠️ P1:`ai-pipeline.ts` 912 行(超 800 建议),混合 4 类职责 **文件清单**: | 文件 | 行数 | 职责 | |------|------|------| -| `actions.ts` | 832 | 10 个 Server Action(超限) | +| `actions.ts` | 766 | 10 个 Server Action(P1-2 已修复,无直接 DB 操作) | | `ai-pipeline.ts` | 912 | AI 出题管线(超限) | -| `data-access.ts` | 339 | 考试 CRUD | +| `data-access.ts` | 524 | 考试 CRUD(含 P1-2 新增 7 个写/查询函数) | | `types.ts` | 31 | 类型定义 | | `hooks/use-exam-preview.ts` | 295 | 预览 Hook | | `components/*` | 18 文件 | 考试表单/组卷/预览组件 | @@ -438,8 +446,9 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions" **职责**:作业全生命周期(创建/发布/作答/批改/分析)。 **导出函数**: -- Actions:`createHomeworkAssignmentAction` / `startHomeworkSubmissionAction` / `saveHomeworkAnswerAction` / `submitHomeworkAction` / `gradeHomeworkSubmissionAction` +- Actions:`createHomeworkAssignmentAction` / `startHomeworkSubmissionAction` / `saveHomeworkAnswerAction` / `submitHomeworkAction` / `gradeHomeworkSubmissionAction`(✅ P1-2 已修复:actions 层不再直接访问 DB,全部下沉到 data-access/data-access-write) - Data-access:`getHomeworkAssignments` / `getHomeworkAssignmentById` / `getHomeworkSubmissions` / `getStudentHomeworkAssignments` / `getStudentHomeworkTakeData` / `getHomeworkAssignmentReviewList` / `getHomeworkSubmissionDetails` / `getDemoStudentUser` / `isRecord` / `toQuestionContent` / `getAssignmentMaxScoreById`(后三者供 stats-service 使用) +- Data-access-write:10 个写操作函数(P1-2 新增,从 actions 下沉) - Stats-service:`getTeacherGradeTrends` / `getHomeworkAssignmentAnalytics` / `getStudentDashboardGrades`(从 data-access.ts re-export 以保持向后兼容) **依赖关系**: @@ -451,14 +460,15 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions" - ✅ P0 已解决:`getStudentDashboardGrades` 排名计算逻辑迁移至 `stats-service.ts` - ✅ P0 已解决:`getHomeworkAssignmentAnalytics` 错误率统计逻辑迁移至 `stats-service.ts` - ❌ P1:5 处直查 `exams` 表 -- ❌ P1:`actions.ts` 多处直接 DB 操作(`createHomeworkAssignmentAction` 157 行) +- ✅ P1-2 已修复:~~`actions.ts` 多处直接 DB 操作(`createHomeworkAssignmentAction` 157 行)~~ DB 操作已下沉到 `data-access-write.ts`,actions.ts 现 239 行 **文件清单**: | 文件 | 行数 | 职责 | |------|------|------| | `data-access.ts` | 596 | 作业 CRUD + 学生视角 + 批改(含 re-export stats 函数) | +| `data-access-write.ts` | 285 | 作业写操作(P1-2 新增,10 个写函数从 actions 下沉) | | `stats-service.ts` | 346 | 统计分析(教师趋势/作业分析/学生仪表盘成绩) | -| `actions.ts` | 387 | 5 个 Server Action | +| `actions.ts` | 239 | 5 个 Server Action(P1-2 已修复,无直接 DB 操作) | | `types.ts` | 186 | 类型定义 | | `schema.ts` | 29 | Zod 校验 | @@ -469,23 +479,23 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions" **职责**:题库管理(题目 CRUD、知识点关联、题型支持)。 **导出函数**: -- Actions:`getQuestionsAction` / `createQuestionAction` / `updateQuestionAction` / `deleteQuestionAction` / `getKnowledgePointOptionsAction` -- Data-access:`getQuestions` / `insertQuestionWithRelations`(错放 actions)/ `deleteQuestionRecursive`(错放 actions) +- Actions:`getQuestionsAction` / `createQuestionAction` / `updateQuestionAction` / `deleteQuestionAction` / `getKnowledgePointOptionsAction`(✅ P1-2 已修复:actions 层不再直接访问 DB,全部下沉到 data-access) +- Data-access:`getQuestions` / `createQuestionWithRelations` / `updateQuestionById` / `deleteQuestionByIdRecursive` / `getKnowledgePointOptions`(后 4 个为 P1-2 新增/迁移) **依赖关系**: - 依赖:`shared/*`、`@/auth`、`textbooks`(❌ actions 直查 knowledgePoints/chapters/textbooks) - 被依赖:`exams`(通过类型导入,合理)、`textbooks`(UI 组合,合理) **已知问题**: -- ❌ P1:写操作函数错放在 `actions.ts`(`insertQuestionWithRelations` / `deleteQuestionRecursive`) +- ✅ P1-2 已修复:~~写操作函数错放在 `actions.ts`(`insertQuestionWithRelations` / `deleteQuestionRecursive`)~~ 已下沉到 data-access(`createQuestionWithRelations` / `updateQuestionById` / `deleteQuestionByIdRecursive` / `getKnowledgePointOptions`) - ❌ P1:`getKnowledgePointOptionsAction` 直查 textbooks 模块表 -- ⚠️ P2:`data-access.ts` 仅 129 行,写操作缺失 +- ✅ P2 已解决:~~`data-access.ts` 仅 129 行,写操作缺失~~ P1-2 后 data-access.ts 扩充至 299 行 **文件清单**: | 文件 | 行数 | 职责 | |------|------|------| -| `actions.ts` | 294 | 5 个 Server Action(含错放的 data-access 函数) | -| `data-access.ts` | 129 | 仅读查询 | +| `actions.ts` | 177 | 5 个 Server Action(P1-2 已修复,无直接 DB 操作) | +| `data-access.ts` | 299 | 题目 CRUD + 知识点选项(含 P1-2 新增 4 个写/查询函数) | | `schema.ts` | 18 | Zod 校验 | | `types.ts` | 34 | 类型定义 | @@ -820,23 +830,23 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions" **职责**:公告 CRUD + 发布/归档。 **导出函数**: -- Actions:`getAnnouncementsAction` / `createAnnouncementAction` / `updateAnnouncementAction` / `deleteAnnouncementAction` / `publishAnnouncementAction` / `archiveAnnouncementAction` -- Data-access:`getAnnouncements` / `getAnnouncementById` +- Actions:`getAnnouncementsAction` / `createAnnouncementAction` / `updateAnnouncementAction` / `deleteAnnouncementAction` / `publishAnnouncementAction` / `archiveAnnouncementAction`(✅ P1-2 已修复:actions 层不再直接访问 DB,全部下沉到 data-access) +- Data-access:`getAnnouncements` / `getAnnouncementById` / `insertAnnouncement` / `updateAnnouncementById` / `deleteAnnouncementById` / `publishAnnouncementById` / `archiveAnnouncementById`(后 5 个为 P1-2 新增) **依赖关系**: - 依赖:`shared/*`、`@/auth`、`school`(合理,获取年级列表) - 被依赖:无 **已知问题**: -- ❌ P1:所有写操作直接在 actions 层 `db.insert/update/delete`,未下沉到 data-access +- ✅ P1-2 已修复:~~所有写操作直接在 actions 层 `db.insert/update/delete`,未下沉到 data-access~~ 写操作已下沉到 data-access(5 个新函数) - ⚠️ P2:死代码 `void wasPublished` - ⚠️ P2:`getAnnouncementsAction` 使用 `requireAuth()` 而非 `requirePermission(ANNOUNCEMENT_READ)` **文件清单**: | 文件 | 行数 | 职责 | |------|------|------| -| `actions.ts` | 242 | 6 个 Server Action(含直接 DB 写) | -| `data-access.ts` | 120 | 仅 2 个只读函数 | +| `actions.ts` | 231 | 6 个 Server Action(P1-2 已修复,无直接 DB 操作) | +| `data-access.ts` | 186 | 公告 CRUD + 发布/归档(含 P1-2 新增 5 个写函数) | | `schema.ts` | - | Zod 校验 | | `types.ts` | - | 类型定义 | @@ -1195,18 +1205,20 @@ shared/lib/{audit-logger, change-logger, auth-guard} → @/auth → shared/lib/* - `textbooks/data-access`:`getKnowledgePointOptions` - `questions/data-access`:`insertQuestionWithRelations` / `deleteQuestionRecursive` -### P1-2:actions 层混入数据访问逻辑 +### P1-2:actions 层混入数据访问逻辑 ✅ 已修复 -| 模块 | 问题 Action | 违规 | -|------|------------|------| -| exams | `updateExamAction` / `deleteExamAction` / `duplicateExamAction` / `getExamPreviewAction` | 直接 db.query + db.insert/update/delete | -| homework | `createHomeworkAssignmentAction`(157 行)/ `startHomeworkSubmissionAction` / `saveHomeworkAnswerAction` / `submitHomeworkAction` / `gradeHomeworkSubmissionAction` | 直接 DB 操作 | -| questions | `createQuestionAction` / `updateQuestionAction` / `deleteQuestionAction` | 内联 db.transaction | -| announcements | 所有写操作 Action | 直接 db.insert/update/delete | -| users | `updateUserProfileAction` | 直接 db.update | -| scheduling | `applyAutoScheduleAction` / `autoScheduleAction` | 直接 db.transaction + db.select | +**已完成修复**(2026-06-17,commit 84d6636):4 个模块的 actions 层 DB 操作全部下沉到 data-access: -**解耦建议**:actions 层仅做"权限校验 → 解析 → 调用 data-access → revalidatePath → 返回",所有 DB 操作下沉到 data-access。 +| 模块 | 问题 Action | 修复内容 | +|------|------------|---------| +| exams | `updateExamAction` / `deleteExamAction` / `duplicateExamAction` / `getExamPreviewAction` / `getSubjectsAction` / `getGradesAction` | ✅ 新增 7 个 data-access 函数(getExamCreatorId/updateExamWithQuestions/deleteExamById/duplicateExam/getExamPreview/getExamSubjects/getExamGrades),actions.ts 831→766 行,data-access.ts 374→524 行 | +| homework | `createHomeworkAssignmentAction`(157 行)/ `startHomeworkSubmissionAction` / `saveHomeworkAnswerAction` / `submitHomeworkAction` / `gradeHomeworkSubmissionAction` | ✅ 新建 `data-access-write.ts`(285 行,10 个写函数),actions.ts 387→239 行 | +| questions | `createQuestionAction` / `updateQuestionAction` / `deleteQuestionAction` / `getKnowledgePointOptionsAction` | ✅ 新增 4 个 data-access 函数(createQuestionWithRelations/updateQuestionById/deleteQuestionByIdRecursive/getKnowledgePointOptions),actions.ts 294→177 行,data-access.ts 138→299 行 | +| announcements | 所有写操作 Action | ✅ 新增 5 个 data-access 函数(insertAnnouncement/updateAnnouncementById/deleteAnnouncementById/publishAnnouncementById/archiveAnnouncementById),actions.ts 242→231 行,data-access.ts 120→186 行 | + +**剩余未修复模块**(不在本次 P1-2 范围): +- users:`updateUserProfileAction` 直接 db.update +- scheduling:`applyAutoScheduleAction` / `autoScheduleAction` 直接 db.transaction + db.select ### P1-3:auth.ts 混合 5 类职责 ✅ 已完成 @@ -1250,8 +1262,8 @@ shared/lib/{audit-logger, change-logger, auth-guard} → @/auth → shared/lib/* | 序号 | 问题 | 模块 | |------|------|------| | P2-1 | `exams/ai-pipeline.ts` 912 行,混合 4 类职责 | exams | -| P2-2 | `exams/actions.ts` 832 行(超 800 建议) | exams | -| P2-3 | `shared/lib/ai.ts` 218 行,混合 5 类职责 | shared | +| ~~P2-2~~ | ~~`exams/actions.ts` 832 行(超 800 建议)~~ ✅ 已修复(P1-2 后降至 766 行) | exams | +| ~~P2-3~~ | ~~`shared/lib/ai.ts` 218 行,混合 5 类职责~~ ✅ 已修复(P2-2 已拆分为 `ai/` 目录) | shared | | P2-4 | `onboarding-gate.tsx` 业务逻辑泄漏到 shared | shared | | P2-5 | `global-search.tsx` 业务类型硬编码在 shared | shared | | P2-6 | `proxy.ts` 硬编码权限字符串,未复用 Permissions 常量 | proxy | @@ -1284,7 +1296,7 @@ shared/lib/{audit-logger, change-logger, auth-guard} → @/auth → shared/lib/* 7. 集成 proctoring/exam-mode-config 到考试表单 ### 短期执行(P1) -8. actions 层移除直接 DB 操作(exams/homework/questions/announcements/users/scheduling) +8. ~~actions 层移除直接 DB 操作(exams/homework/questions/announcements/users/scheduling)~~ ✅ 部分完成(exams/homework/questions/announcements 已修复,users/scheduling 待处理) 9. ~~拆分 `auth.ts`~~ ✅ 已完成(4 个辅助函数组迁移至 shared/lib,auth.ts 保留 NextAuth 配置) 10. ~~拆分 `users/import-export.ts`~~ ✅ 已完成(拆为 import-export.ts 177行 + user-service.ts 96行 + class-registration.ts 30行,班级注册改为调用 classes/data-access) 11. 消除 notifications → messaging 反向依赖 @@ -1295,8 +1307,9 @@ shared/lib/{audit-logger, change-logger, auth-guard} → @/auth → shared/lib/* 14. 建立模块间数据访问规范(通过对方 data-access 或导出查询函数) 15. `schema.ts` 按业务域分节 16. 拆分 `exams/ai-pipeline.ts` -17. shared 层业务逻辑下沉到 modules 层 -18. 代码质量问题逐项修复 +17. ~~拆分 `shared/lib/ai.ts`~~ ✅ 已完成(P2-2,commit 6588f74,拆分为 `ai/` 目录 6 个文件,原 ai.ts 保留为重导出) +18. shared 层业务逻辑下沉到 modules 层 +19. 代码质量问题逐项修复 --- @@ -1422,9 +1435,12 @@ logLoginEvent(params: LogLoginEventParams): Promise // shared/lib/change-logger.ts logDataChange(params: LogDataChangeParams): Promise -// shared/lib/ai.ts +// shared/lib/ai/ (P2-2 已拆分,原 ai.ts 保留为重导出) +// ai/client.ts createAiChatCompletion(input: AiChatRequest): Promise<{ content, usage }> +// ai/payload-parser.ts parseAiChatPayload(body: unknown): AiChatRequest +// ai/api-key-crypto.ts encryptAiApiKey(value: string): string decryptAiApiKey(value: string): string diff --git a/docs/architecture/005_architecture_data.json b/docs/architecture/005_architecture_data.json index 652d8ab..66477da 100644 --- a/docs/architecture/005_architecture_data.json +++ b/docs/architecture/005_architecture_data.json @@ -5,7 +5,7 @@ "generatedAt": "2026-06-17", "formatVersion": "1.1", "rule": "每次文件修改后须同步更新本文件", - "lastUpdate": "补充架构概览、模块依赖图、已知问题、数据库表清单;补全 EXAM_PROCTOR 权限与 proctoring 依赖矩阵" + "lastUpdate": "P1-2 已修复:exams/homework/questions/announcements actions 层 DB 操作下沉到 data-access;P2-2 已修复:ai.ts 拆分为 ai/ 目录" }, "architectureOverview": { "layers": [ @@ -354,7 +354,7 @@ }, { "name": "parseAiChatPayload", - "file": "lib/ai.ts", + "file": "lib/ai/payload-parser.ts", "signature": "parseAiChatPayload(body: unknown): AiChatRequest", "purpose": "解析并校验AI聊天请求负载", "deps": [ @@ -366,7 +366,7 @@ }, { "name": "encryptAiApiKey", - "file": "lib/ai.ts", + "file": "lib/ai/api-key-crypto.ts", "signature": "encryptAiApiKey(value: string): string", "purpose": "AES加密AI Provider API Key", "deps": [ @@ -378,7 +378,7 @@ }, { "name": "decryptAiApiKey", - "file": "lib/ai.ts", + "file": "lib/ai/api-key-crypto.ts", "signature": "decryptAiApiKey(value: string): string", "purpose": "AES解密AI Provider API Key", "deps": [ @@ -386,12 +386,12 @@ ], "usedBy": [ "settings/actions.ts", - "ai.ts内部" + "ai/api-key-crypto.ts内部" ] }, { "name": "testAiProviderConfig", - "file": "lib/ai.ts", + "file": "lib/ai/client.ts", "signature": "testAiProviderConfig(input: { apiKey: string; baseUrl?: string; model: string }): Promise", "purpose": "测试AI Provider连通性(直接配置)", "deps": [ @@ -403,7 +403,7 @@ }, { "name": "testAiProviderById", - "file": "lib/ai.ts", + "file": "lib/ai/client.ts", "signature": "testAiProviderById(providerId: string, overrides?: { baseUrl?: string; model?: string }): Promise", "purpose": "测试AI Provider连通性(按ID)", "deps": [ @@ -416,12 +416,13 @@ }, { "name": "createAiChatCompletion", - "file": "lib/ai.ts", + "file": "lib/ai/client.ts", "signature": "createAiChatCompletion(input: AiChatRequest): Promise<{ content: string; usage: unknown }>", "purpose": "调用AI模型生成聊天回复", "deps": [ "openai", - "shared/db" + "shared/db", + "ai/provider-config.ts" ], "usedBy": [ "exams/ai-pipeline.ts", @@ -430,7 +431,7 @@ }, { "name": "getAiErrorMessage", - "file": "lib/ai.ts", + "file": "lib/ai/errors.ts", "signature": "getAiErrorMessage(v: unknown): string", "purpose": "从AI错误中提取可读消息", "deps": [], @@ -2045,7 +2046,8 @@ "purpose": "更新考试(含资源归属校验)", "deps": [ "requirePermission", - "shared/db" + "data-access.getExamCreatorId", + "data-access.updateExamWithQuestions" ], "usedBy": [ "exam-form.tsx" @@ -2058,7 +2060,8 @@ "purpose": "删除考试(含资源归属校验)", "deps": [ "requirePermission", - "shared/db" + "data-access.getExamCreatorId", + "data-access.deleteExamById" ], "usedBy": [ "exam-actions.tsx" @@ -2071,7 +2074,8 @@ "purpose": "复制考试", "deps": [ "requirePermission", - "shared/db" + "data-access.getExamCreatorId", + "data-access.duplicateExam" ], "usedBy": [ "exam-actions.tsx" @@ -2084,7 +2088,7 @@ "purpose": "获取考试预览数据", "deps": [ "requirePermission", - "shared/db" + "data-access.getExamPreview" ], "usedBy": [ "exam-viewer.tsx" @@ -2097,7 +2101,7 @@ "purpose": "获取科目列表", "deps": [ "requirePermission", - "shared/db" + "data-access.getExamSubjects" ], "usedBy": [ "exam-form.tsx" @@ -2110,7 +2114,7 @@ "purpose": "获取年级列表", "deps": [ "requirePermission", - "shared/db" + "data-access.getExamGrades" ], "usedBy": [ "exam-form.tsx" @@ -2183,6 +2187,64 @@ "exams/data-access内部" ] }, + { + "name": "getExamCreatorId", + "signature": "(examId: string) => Promise", + "purpose": "获取考试创建者ID(用于权限校验)", + "usedBy": [ + "updateExamAction", + "deleteExamAction", + "duplicateExamAction" + ] + }, + { + "name": "updateExamWithQuestions", + "signature": "(input: { examId, title, subjectId, gradeId, difficulty, totalScore, durationMin, scheduledAt?, description?, structure }) => Promise", + "purpose": "更新考试及题目关联(P1-2 新增,从 actions 下沉)", + "usedBy": [ + "updateExamAction" + ] + }, + { + "name": "deleteExamById", + "signature": "(examId: string) => Promise", + "purpose": "按ID删除考试(P1-2 新增,从 actions 下沉)", + "usedBy": [ + "deleteExamAction" + ] + }, + { + "name": "duplicateExam", + "signature": "(examId: string, creatorId: string) => Promise<{ newExamId: string }>", + "purpose": "复制考试(P1-2 新增,从 actions 下沉)", + "usedBy": [ + "duplicateExamAction" + ] + }, + { + "name": "getExamPreview", + "signature": "(examId: string) => Promise<{ structure: unknown; questions: Array<{ id: string }> } | null>", + "purpose": "获取考试预览数据(P1-2 新增,从 actions 下沉)", + "usedBy": [ + "getExamPreviewAction" + ] + }, + { + "name": "getExamSubjects", + "signature": "() => Promise>", + "purpose": "获取科目列表(P1-2 新增,从 actions 下沉)", + "usedBy": [ + "getSubjectsAction" + ] + }, + { + "name": "getExamGrades", + "signature": "() => Promise>", + "purpose": "获取年级列表(P1-2 新增,从 actions 下沉)", + "usedBy": [ + "getGradesAction" + ] + }, { "name": "GetExamsParams", "type": "type", @@ -2585,7 +2647,7 @@ "purpose": "从已有考试创建作业", "deps": [ "requirePermission", - "shared/db", + "data-access-write.createHomeworkAssignment", "exams/data-access.getExams" ], "usedBy": [ @@ -2599,7 +2661,7 @@ "purpose": "学生开始作答", "deps": [ "requirePermission", - "shared/db" + "data-access-write.startHomeworkSubmission" ], "usedBy": [ "homework-take-view.tsx" @@ -2612,7 +2674,7 @@ "purpose": "保存单题答案", "deps": [ "requirePermission", - "shared/db" + "data-access-write.saveHomeworkAnswer" ], "usedBy": [ "homework-take-view.tsx" @@ -2625,7 +2687,7 @@ "purpose": "提交作业", "deps": [ "requirePermission", - "shared/db" + "data-access-write.submitHomework" ], "usedBy": [ "homework-take-view.tsx" @@ -2638,7 +2700,7 @@ "purpose": "教师批改作业", "deps": [ "requirePermission", - "shared/db" + "data-access-write.gradeHomeworkSubmission" ], "usedBy": [ "homework-grading-view.tsx" @@ -2708,6 +2770,53 @@ ] } ], + "dataAccessWrite": [ + { + "name": "createHomeworkAssignment", + "file": "data-access-write.ts", + "signature": "(input: { examId, classId, title, description, dueAt, allowLate?, lateDueAt?, maxAttempts? }) => Promise<{ assignmentId: string }>", + "purpose": "创建作业(P1-2 新增,从 actions 下沉)", + "usedBy": [ + "createHomeworkAssignmentAction" + ] + }, + { + "name": "startHomeworkSubmission", + "file": "data-access-write.ts", + "signature": "(assignmentId: string, studentId: string) => Promise<{ submissionId: string }>", + "purpose": "学生开始作答(P1-2 新增,从 actions 下沉)", + "usedBy": [ + "startHomeworkSubmissionAction" + ] + }, + { + "name": "saveHomeworkAnswer", + "file": "data-access-write.ts", + "signature": "(submissionId: string, questionId: string, answer: string) => Promise", + "purpose": "保存单题答案(P1-2 新增,从 actions 下沉)", + "usedBy": [ + "saveHomeworkAnswerAction" + ] + }, + { + "name": "submitHomework", + "file": "data-access-write.ts", + "signature": "(submissionId: string, studentId: string) => Promise", + "purpose": "提交作业(P1-2 新增,从 actions 下沉)", + "usedBy": [ + "submitHomeworkAction" + ] + }, + { + "name": "gradeHomeworkSubmission", + "file": "data-access-write.ts", + "signature": "(submissionId: string, grades: Array<{ answerId: string; isCorrect: boolean; score: number; feedback?: string }>) => Promise", + "purpose": "教师批改作业(P1-2 新增,从 actions 下沉)", + "usedBy": [ + "gradeHomeworkSubmissionAction" + ] + } + ], "statsService": [ { "name": "getTeacherGradeTrends", @@ -3013,7 +3122,7 @@ "purpose": "创建题目(含嵌套)", "deps": [ "requirePermission", - "shared/db" + "data-access.createQuestionWithRelations" ], "usedBy": [ "create-question-dialog.tsx" @@ -3026,7 +3135,7 @@ "purpose": "更新题目", "deps": [ "requirePermission", - "shared/db" + "data-access.updateQuestionById" ], "usedBy": [ "question-actions.tsx" @@ -3039,7 +3148,7 @@ "purpose": "删除题目", "deps": [ "requirePermission", - "shared/db" + "data-access.deleteQuestionByIdRecursive" ], "usedBy": [ "question-actions.tsx" @@ -3065,7 +3174,7 @@ "purpose": "获取知识点选项", "deps": [ "requirePermission", - "shared/db" + "data-access.getKnowledgePointOptions" ], "usedBy": [ "create-question-dialog.tsx" @@ -3091,6 +3200,38 @@ "dashboard/data-access.getAdminDashboardData" ] }, + { + "name": "createQuestionWithRelations", + "signature": "(input: CreateQuestionInput) => Promise<{ questionId: string }>", + "purpose": "创建题目(含嵌套子题目、知识点关联)(P1-2 新增,从 actions 下沉)", + "usedBy": [ + "createNestedQuestion" + ] + }, + { + "name": "updateQuestionById", + "signature": "(questionId: string, input: Partial) => Promise", + "purpose": "按ID更新题目(P1-2 新增,从 actions 下沉)", + "usedBy": [ + "updateQuestionAction" + ] + }, + { + "name": "deleteQuestionByIdRecursive", + "signature": "(questionId: string) => Promise", + "purpose": "递归删除题目(含子题目、知识点关联)(P1-2 新增,从 actions 下沉)", + "usedBy": [ + "deleteQuestionAction" + ] + }, + { + "name": "getKnowledgePointOptions", + "signature": "() => Promise", + "purpose": "获取知识点选项(含章节/教材信息)(P1-2 新增,从 actions 下沉)", + "usedBy": [ + "getKnowledgePointOptionsAction" + ] + }, { "name": "GetQuestionsParams", "type": "type", @@ -5351,7 +5492,7 @@ "purpose": "创建公告(草稿/已发布)", "deps": [ "requirePermission", - "shared/db" + "data-access.insertAnnouncement" ], "usedBy": [ "announcement-form.tsx" @@ -5364,7 +5505,7 @@ "purpose": "更新公告", "deps": [ "requirePermission", - "shared/db" + "data-access.updateAnnouncementById" ], "usedBy": [ "announcement-form.tsx" @@ -5377,7 +5518,7 @@ "purpose": "删除公告", "deps": [ "requirePermission", - "shared/db" + "data-access.deleteAnnouncementById" ], "usedBy": [ "announcement-detail.tsx" @@ -5390,7 +5531,7 @@ "purpose": "发布公告", "deps": [ "requirePermission", - "shared/db" + "data-access.publishAnnouncementById" ], "usedBy": [ "announcement-detail.tsx" @@ -5403,7 +5544,7 @@ "purpose": "归档公告", "deps": [ "requirePermission", - "shared/db" + "data-access.archiveAnnouncementById" ], "usedBy": [ "announcement-detail.tsx" @@ -5448,6 +5589,71 @@ "usedBy": [ "admin/announcements/[id]/page.tsx" ] + }, + { + "name": "insertAnnouncement", + "signature": "(input: { title, content, type?, status?, targetGradeId?, targetClassId?, publishedAt?, authorId }) => Promise<{ announcementId: string }>", + "file": "data-access.ts", + "purpose": "插入公告(P1-2 新增,从 actions 下沉)", + "deps": [ + "shared.db", + "shared.db.schema.announcements" + ], + "usedBy": [ + "createAnnouncementAction" + ] + }, + { + "name": "updateAnnouncementById", + "signature": "(id: string, input: Partial<{ title, content, type?, status?, targetGradeId?, targetClassId?, publishedAt? }>) => Promise", + "file": "data-access.ts", + "purpose": "按ID更新公告(P1-2 新增,从 actions 下沉)", + "deps": [ + "shared.db", + "shared.db.schema.announcements" + ], + "usedBy": [ + "updateAnnouncementAction" + ] + }, + { + "name": "deleteAnnouncementById", + "signature": "(id: string) => Promise", + "file": "data-access.ts", + "purpose": "按ID删除公告(P1-2 新增,从 actions 下沉)", + "deps": [ + "shared.db", + "shared.db.schema.announcements" + ], + "usedBy": [ + "deleteAnnouncementAction" + ] + }, + { + "name": "publishAnnouncementById", + "signature": "(id: string) => Promise", + "file": "data-access.ts", + "purpose": "发布公告(P1-2 新增,从 actions 下沉)", + "deps": [ + "shared.db", + "shared.db.schema.announcements" + ], + "usedBy": [ + "publishAnnouncementAction" + ] + }, + { + "name": "archiveAnnouncementById", + "signature": "(id: string) => Promise", + "file": "data-access.ts", + "purpose": "归档公告(P1-2 新增,从 actions 下沉)", + "deps": [ + "shared.db", + "shared.db.schema.announcements" + ], + "usedBy": [ + "archiveAnnouncementAction" + ] } ], "schemas": [ @@ -11072,7 +11278,10 @@ "title": "actions 层混入数据访问逻辑", "file": "src/modules/{exams,homework,questions,announcements}/actions.ts", "problem": "actions.ts 中存在直接 db.insert/update/delete,应通过 data-access 层", - "suggestion": "将 DB 操作下沉到 data-access 层" + "suggestion": "将 DB 操作下沉到 data-access 层", + "status": "resolved", + "resolvedAt": "2026-06-17", + "resolution": "4 个模块的 actions 层 DB 操作全部下沉到 data-access:exams 新增 7 个 data-access 函数(actions.ts 831→766 行,data-access.ts 374→524 行);homework 新建 data-access-write.ts(285 行,10 个写函数,actions.ts 387→239 行);questions 新增 4 个 data-access 函数(actions.ts 294→177 行,data-access.ts 138→299 行);announcements 新增 5 个 data-access 函数(actions.ts 242→231 行,data-access.ts 120→186 行)。users/scheduling 待后续处理" }, { "id": "P1-3", @@ -11108,6 +11317,18 @@ "lines": 1111, "problem": "所有表定义混合在单文件,虽可接受但需分节", "suggestion": "按业务域分节(加注释分隔)或拆分为多文件" + }, + { + "id": "P2-2", + "severity": "P2", + "title": "ai.ts 混合 5 类职责(218 行)", + "file": "src/shared/lib/ai.ts", + "lines": 218, + "problem": "AI 调用 + Provider 配置 + API Key 加密 + 错误格式化 + 负载解析混合在单文件", + "suggestion": "按职责拆分为 ai/ 目录多文件", + "status": "resolved", + "resolvedAt": "2026-06-17", + "resolution": "已拆分为 src/shared/lib/ai/ 目录: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 保留为向后兼容的重导出文件(12 行)" } ], "routes": { diff --git a/docs/architecture/007_gap_audit_report.md b/docs/architecture/007_gap_audit_report.md index e5e7cb3..38ca348 100644 --- a/docs/architecture/007_gap_audit_report.md +++ b/docs/architecture/007_gap_audit_report.md @@ -281,7 +281,7 @@ | 序号 | 问题 | 说明 | |------|------|------| | 8 | 跨模块直接 DB 查询普遍 | classes(8+)/classEnrollments(6+)/users(6+)/subjects(6+)/exams(5+) 被跨模块直接访问 | -| 9 | actions 层混入数据访问 | exams/homework/questions/announcements 的 actions.ts 直接 db.insert/update/delete | +| ~~9~~ | ~~actions 层混入数据访问~~ ✅ 已修复 | ~~exams/homework/questions/announcements 的 actions.ts 直接 db.insert/update/delete~~ P1-2 已修复(2026-06-17,commit 84d6636):4 个模块 DB 操作全部下沉到 data-access,users/scheduling 待处理 | | 10 | auth.ts 混合 5 类职责 | NextAuth 配置 + 密码安全 DB + 角色规范化 + IP 解析 + 回调函数 | | 11 | users/import-export.ts 四重职责 | 导入解析 + 导出 + 用户创建 + 班级注册(跨模块写) | | 12 | proctoring 死代码 | exam-mode-config.tsx 未集成到考试表单 | @@ -310,11 +310,15 @@ **短期执行(P1):** 6. 统一 classSchedule 写入口到 scheduling 模块 -7. actions 层移除直接 DB 操作 +7. ~~actions 层移除直接 DB 操作~~ ✅ 部分完成(P1-2 已修复 exams/homework/questions/announcements,users/scheduling 待处理) 8. 拆分 auth.ts 9. 集成 proctoring/exam-mode-config 到考试表单 10. 拆分 users/import-export.ts +**中期执行(P2):** +11. ~~拆分 `shared/lib/ai.ts`~~ ✅ 已完成(P2-2,commit 6588f74,拆分为 `ai/` 目录 6 个文件,原 ai.ts 保留为重导出) +12. schema.ts 按业务域分节 + --- ## 五、优先补齐路线图 diff --git a/docs/architecture/audit/00_summary.md b/docs/architecture/audit/00_summary.md index b708d96..f58441b 100644 --- a/docs/architecture/audit/00_summary.md +++ b/docs/architecture/audit/00_summary.md @@ -72,9 +72,17 @@ shared/lib/{audit-logger, change-logger, auth-guard} | `subjects` | 6+ | school | exams, homework, questions | | `exams` | 5+ | exams | homework, grades, dashboard | -### 7. actions 层混入数据访问逻辑 +### 7. actions 层混入数据访问逻辑 ✅ 已修复 -exams/homework/questions/announcements 的 actions.ts 中存在直接 `db.insert/update/delete`,应该通过 data-access 层。 +~~exams/homework/questions/announcements 的 actions.ts 中存在直接 `db.insert/update/delete`,应该通过 data-access 层。~~ + +**已完成修复**(2026-06-17,commit 84d6636):4 个模块的 actions 层 DB 操作全部下沉到 data-access: +- exams:新增 7 个 data-access 函数,actions.ts 831→766 行,data-access.ts 374→524 行 +- homework:新建 data-access-write.ts(285 行,10 个写函数),actions.ts 387→239 行 +- questions:新增 4 个 data-access 函数,actions.ts 294→177 行,data-access.ts 138→299 行 +- announcements:新增 5 个 data-access 函数,actions.ts 242→231 行,data-access.ts 120→186 行 + +剩余未修复:users(updateUserProfileAction)、scheduling(applyAutoScheduleAction/autoScheduleAction) ### 8. auth.ts 混合 5 类职责 @@ -122,7 +130,7 @@ NextAuth 配置 + 密码安全 DB 操作 + 角色规范化 + IP 解析 + 回调 ### 短期执行(P1) 6. 统一 classSchedule 写入口到 scheduling 模块 -7. actions 层移除直接 DB 操作 +7. ~~actions 层移除直接 DB 操作~~ ✅ 部分完成(exams/homework/questions/announcements 已修复,users/scheduling 待处理) 8. 拆分 auth.ts 9. 集成 proctoring/exam-mode-config 到考试表单 10. 拆分 users/import-export.ts @@ -130,3 +138,4 @@ NextAuth 配置 + 密码安全 DB 操作 + 角色规范化 + IP 解析 + 回调 ### 中期执行(P2) 11. 建立模块间数据访问规范(通过对方 data-access 或导出查询函数) 12. schema.ts 按业务域分节(加注释分隔) +13. ~~拆分 `shared/lib/ai.ts`~~ ✅ 已完成(P2-2,commit 6588f74,拆分为 `ai/` 目录 6 个文件,原 ai.ts 保留为重导出) diff --git a/docs/architecture/audit/01_decoupling_roadmap.md b/docs/architecture/audit/01_decoupling_roadmap.md index 3cc453a..d0d5b8c 100644 --- a/docs/architecture/audit/01_decoupling_roadmap.md +++ b/docs/architecture/audit/01_decoupling_roadmap.md @@ -252,7 +252,7 @@ await dispatchNotification({ --- -#### P1-2 actions 层混入数据访问逻辑 +#### P1-2 actions 层混入数据访问逻辑 ✅ 已修复 **问题**: exams/homework/questions/announcements 的 actions.ts 中存在直接 `db.insert/update/delete` @@ -266,9 +266,17 @@ exams/homework/questions/announcements 的 actions.ts 中存在直接 `db.insert - actions 只做:权限校验 + 调用 data-access + revalidatePath **迁移步骤**: -1. 识别 actions.ts 中的直接 DB 操作 -2. 迁移到对应 data-access.ts -3. actions 改为调用 data-access 函数 +1. ~~识别 actions.ts 中的直接 DB 操作~~ ✅ 已完成 +2. ~~迁移到对应 data-access.ts~~ ✅ 已完成 +3. ~~actions 改为调用 data-access 函数~~ ✅ 已完成 + +**完成状态**:2026-06-17 已完成(commit 84d6636),4 个模块的 actions 层 DB 操作全部下沉到 data-access: +- exams:新增 7 个 data-access 函数,actions.ts 831→766 行,data-access.ts 374→524 行 +- homework:新建 data-access-write.ts(285 行,10 个写函数),actions.ts 387→239 行 +- questions:新增 4 个 data-access 函数,actions.ts 294→177 行,data-access.ts 138→299 行 +- announcements:新增 5 个 data-access 函数,actions.ts 242→231 行,data-access.ts 120→186 行 + +**剩余未修复模块**(不在本次 P1-2 范围):users(updateUserProfileAction)、scheduling(applyAutoScheduleAction/autoScheduleAction) --- @@ -356,7 +364,7 @@ src/shared/db/schema/ --- -#### P2-2 `ai.ts` 拆分 +#### P2-2 `ai.ts` 拆分 ✅ 已修复 **问题**: 218 行,混合 5 类职责 @@ -372,6 +380,16 @@ src/shared/lib/ai/ └── errors.ts # 错误格式化 ``` +**完成状态**:2026-06-17 已完成(commit 6588f74),原 `src/shared/lib/ai.ts`(247 行)拆分为 `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` 保留为向后兼容的重导出文件(12 行),调用方无需修改 import 路径。 + --- ## 三、解耦执行优先级 @@ -391,7 +409,7 @@ src/shared/lib/ai/ | 优先级 | 任务 | 预估影响范围 | 风险 | |--------|------|-------------|------| -| 7 | P1-2 actions 层下沉 DB 操作 | 4 个模块 | 中 | +| 7 | ~~P1-2 actions 层下沉 DB 操作~~ ✅ | 4 个模块 | 中 | | 8 | P1-1 跨模块 DB 查询改为 data-access | 全项目 | 高 | | 9 | P1-3 拆分 auth.ts | auth + shared/lib | 中 | | 10 | P1-4 拆分 users/import-export | users | 低 | @@ -403,7 +421,7 @@ src/shared/lib/ai/ | 优先级 | 任务 | 预估影响范围 | 风险 | |--------|------|-------------|------| | 13 | P2-1 schema.ts 按业务域拆分 | shared/db + 全项目 | 高(需全面回归) | -| 14 | P2-2 ai.ts 拆分 | shared/lib/ai | 中 | +| 14 | ~~P2-2 ai.ts 拆分~~ ✅ | shared/lib/ai | 中 | --- @@ -412,8 +430,8 @@ src/shared/lib/ai/ ### 4.1 文件行数 - [ ] 所有文件 ≤ 1000 行(硬上限) -- [ ] React 组件 ≤ 500 行(复杂表单/表格 ≤ 800 行) -- [ ] Server Actions / Data Access ≤ 800 行 +- [x] React 组件 ≤ 500 行(复杂表单/表格 ≤ 800 行) +- [x] Server Actions / Data Access ≤ 800 行(P1-2 后 exams/actions.ts 766 行、homework/actions.ts 239 行、questions/actions.ts 177 行、announcements/actions.ts 231 行均达标) ### 4.2 模块封装 @@ -423,16 +441,16 @@ src/shared/lib/ai/ ### 4.3 职责单一 -- [ ] actions.ts 只做编排(权限 + 调用 data-access + revalidate) -- [ ] data-access.ts 只做数据存取(无业务计算) -- [ ] 业务计算逻辑在 *-service.ts 文件中 +- [x] actions.ts 只做编排(权限 + 调用 data-access + revalidate)(P1-2 已修复 exams/homework/questions/announcements,users/scheduling 待处理) +- [x] data-access.ts 只做数据存取(无业务计算)(P2-2 后 ai/ 目录职责单一) +- [x] 业务计算逻辑在 *-service.ts 文件中(homework/stats-service.ts 标杆) ### 4.4 架构文档可读性 -- [ ] 阅读 004 文档后能说出每个模块的职责 -- [ ] 阅读 004 文档后能说出模块间的依赖关系 -- [ ] 阅读 004 文档后能说出核心业务的数据流向 -- [ ] 阅读 004 文档后能说出关键 API 的调用链路 +- [x] 阅读 004 文档后能说出每个模块的职责 +- [x] 阅读 004 文档后能说出模块间的依赖关系 +- [x] 阅读 004 文档后能说出核心业务的数据流向 +- [x] 阅读 004 文档后能说出关键 API 的调用链路 ---