# 已知问题速查 > 索引式速查手册:场景 → 技术/规则映射 > 架构规则见 [../architecture/004_architecture_impact_map.md](../architecture/004_architecture_impact_map.md) 与 [.trae/rules/project_rules.md](../../.trae/rules/project_rules.md) > 原始版本归档于 [../architecture/audit/archive/known-issues_v1.md](../architecture/audit/archive/known-issues_v1.md) --- ## 一、全局经验 ### 1.1 i18n | 场景 | 规则 | | ------------------------ | -------------------------------------------------------------------------------- | | key 命名 | 禁止包含 `.`,不同字段用驼峰,同字段多角色变体用嵌套对象 | | 动态 key 调用 | `t()` 参数为动态变量时必须包含完整嵌套路径(`t(\`type.${type}\`)`而非`t(type)`) | | ICU 参数 | 单花括号 `{level}`,多参数直接并列 `{count} 题 · {subject}` | | Namespace 与文件名一致性 | namespace 必须与文件名严格一致 | | 翻译文件对称性 | zh-CN 与 en 必须同步新增/删除 key | | 硬编码中文 | 所有用户可见文本必须走 i18n(AI Prompt 中文常量允许) | | i18n dead 节清理 | 删除组件时同步清理对应的 i18n key | | 动态键翻译守卫 | 缺失 key 时降级返回 key 本身而非抛错 | ### 1.2 Next.js 配置与运行时 | 场景 | 技术/规则 | | --------------------------------- | ---------------------------------------------------------------------------------- | | Node.js 服务端驱动 | `serverExternalPackages: ["mysql2"]` 标记为外部包 | | 输出模式 | `output: "standalone"` | | Tailwind v4 排除非源码 | `globals.css` 中 `@source not "../../docs";` 防止 docs 字符串被扫描成类 | | 可选依赖 | 动态 import + `/* webpackIgnore: true */`(如 `@upstash/redis`) | | 环境变量驱动加载 | `if (env.DRIVER === "redis") { const { Redis } = await import(...) }` | | next-auth useSession SSR | 客户端用 `useSession()` 时需 `SessionProvider` + 等待 `status === "authenticated"` | | useSearchParams Hydration | 包裹 `` 或在 useEffect 中读取 | | Edge Runtime 限制 | proxy.ts 不能导入 Node.js 模块,用 Web Crypto API | | React StrictMode 与 Server Action | StrictMode 双调用不影响 Server Action(idempotent 设计) | ### 1.3 Server/Client Component 边界 | 场景 | 规则 | | --------------------------- | --------------------------------------------------------------------------------------------------------------------- | | "use server" 文件 | 只能导出 `async function`;常量放 `types.ts`/`constants.ts`;同步函数放 `lib/`;禁止值 re-export;类型 re-export 允许 | | Client Component 依赖隔离 | 不得 import server-only 文件;`import { db }` 的文件不得被客户端 import | | Server Component 传递函数 | 禁止向 Client Component 传递函数,改传纯数据 + 在客户端调用 Server Action | | Server Component 传递纯数据 | 序列化日期用 ISO string,不用 Date 对象 | | useState SSR/CSR 一致性 | 初始值必须可序列化且在服务端和客户端一致 | ### 1.4 数据库与 Drizzle | 场景 | 规则 | | -------------------------- | ----------------------------------------------------------------------------------------------- | | schema.ts 修改后 | 必须 `npm run db:push` 或 `npm run db:migrate` 同步数据库 | | `Unknown column` 报错 | 先 `SHOW COLUMNS FROM 表名` 对比实际结构与 schema.ts | | 迁移文件管理 | 必须录入 `_journal.json`;编号唯一不可复用;`__drizzle_migrations` 表必须记录 hash | | 手动补迁移流程 | 创建 `.sql` → 更新 journal → 计算 sha256 插入 migrations 表 → 验证 `No new migrations to apply` | | 字段映射 | TS 属性名与 DB 列名不同时,验证 SQL 用 DB 列名,ORM 查询用 TS 属性名 | | 子查询字段 alias | Drizzle 子查询字段必须显式 alias,否则类型推断失败 | | FULLTEXT 索引声明 | drizzle-kit 无法生成,迁移文件手动追加 `CREATE FULLTEXT INDEX` + 同步 snapshot | | MATCH AGAINST BOOLEAN MODE | 转义特殊字符 `+ - < > ( ) ~ * " '`,转 `+word1* +word2*` 格式 | ### 1.5 状态管理 | 场景 | 技术 | | ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------- | | 5 层状态模型 | L1 URL(nuqs) · L2 Server(TanStack Query) · L3 Client(Zustand slice) · L4 Global UI(Zustand ui-store + ModalRoot) · L5 Form(react-hook-form + zodResolver) | | Zustand selector | 单字段 selector 优于 `useShallow` 多字段包装 | | useOptimistic | 乐观更新(消息星标、成绩编辑、考勤提交),`addOptimistic` 必须在 transition 或 action 内调用 | | useOptimistic 不适用场景 | 需要服务端返回判断成功的操作(如撤回校验时间窗口) | | React 19 useTransition | 配合 useOptimistic 自动管理乐观状态与回滚 | ### 1.6 性能优化 | 场景 | 技术 | | ---------------------- | --------------------------------------------------------------------------- | | 批量 INSERT | `db.insert().values([...])` 单次写入 | | 批量 UPDATE | `sql.join` + `CASE WHEN` 单次执行 | | 递归删除 | 批量收集后代 ID + `inArray` 单次删除,禁止递归逐个 | | 动态导入重 UI 库 | `xxx-inner.tsx` + `xxx.tsx` lazy wrapper(tiptap / @xyflow/react / AI SDK) | | ReactFlowProvider | 留在外层同步渲染,保证 lazy 子组件 hook 可用 | | 请求级去重 | React `cache()` 包装(auth 相关 4 函数) | | 跨请求缓存 | 不用 `unstable_cache`(权限数据易变) | | 高 LIMIT 调低 | 非分页场景默认 LIMIT ≤ 100 | | 内存 filter 改 SQL | `inArray(field, ids)` 下推 | | 统计走 SQL 聚合 | `COUNT(*)`/`SUM(CASE WHEN ...)` 替代拉全表内存循环 | | chart props 模块级常量 | 12 个 recharts 图表提取 `CHART_MARGIN` | | 高频子组件 React.memo | `AssignmentCard` / `StatusBadge` / `EmptyState` | | 虚拟化大表 | `@tanstack/react-virtual` | ### 1.7 设计令牌 | 场景 | 规则 | | -------------------- | --------------------------------------------------------------------- | | 禁止硬编码颜色 | TSX/TS/CSS 中不得出现 `#hex`,用 `hsl(var(--*))` 或 Tailwind 类 | | 禁止硬编码字体 | 不得出现 `'Inter'`/`'Fraunces'`/`'JetBrains Mono'` 字面量 | | 禁止硬编码字号 | 不得出现 `font-size: Npx`,用 `var(--font-size-1~9)` | | 禁止 Tailwind 任意值 | 不得使用 `w-[Npx]`/`h-[Npx]`,映射到 `--space-*` | | 豁免场景 | PWA manifest、邮件 HTML 内联样式、图表 SVG 固定画布、loading.tsx 骨架 | | 豁免注释格式 | `// eslint-disable-next-line no-restricted-syntax -- ` | | 白名单文件 | `primitive.css`、`email-channel.ts`、`manifest.ts` | ### 1.8 TypeScript | 场景 | 规则 | | ---------------- | ------------------------------------------------------------------------------- | | 禁止 `any` | 未知类型用 `unknown` + 类型守卫 | | 禁止 `as` 断言 | 除非从 `unknown` 转换或测试中(需注释原因) | | 类型守卫优先 | JSON.parse 结果经 `unknown` 中转 + 守卫;localStorage/URL param/HTTP 响应需守卫 | | 模块私有类型守卫 | 放 `lib/type-guards.ts`,导出 `isX` / `toX` | | 函数返回值 | 必须显式标注,特别是 `Promise` | | 仅类型导入 | `import type` | | 可选链后禁止 `!` | `a?.b!` 禁止 | | 非空断言 `!` | 数组取值后判空再访问;`split` 结果用 `??` 兜底 | | 空对象赋值 | 显式标注 `const empty: Record = {}` | ### 1.9 API 路由 | 场景 | 规则 | | ---------------- | ------------------------------------------------------------------------------------------------------ | | 响应信封 | `{ success, message?, errorCode?, data? }` 与 `ActionState` 对齐 | | 错误→状态码 | PermissionDeniedError→403 / NotFoundError→404 / ValidationError→400 / BusinessError→400 / 其他→500 | | 统一异常处理 | `withApiErrorHandler(handler)` HOF 消除重复 try/catch | | Action 转 API | `apiFromAction(result, options?)` 通过 `errorCode` 驱动状态码 | | SSE 头 | `Content-Type: text/event-stream` + `Cache-Control: no-cache, no-transform` + `Connection: keep-alive` | | SSE 错误格式 | `{ type: "error", message }` | | SSE 结束信号 | `[DONE]` | | JSDoc 注释 | 禁止 `**/` 序列(破坏注释解析) | | Permissions 类型 | `Permissions` 是常量对象,`Permission` 是联合类型,不可混用 | ### 1.10 日志系统 | 场景 | 规则 | | ------------------ | ---------------------------------------------------------------------------------------------- | | 服务端日志 | `createModuleLogger("module-name")` + pino,禁止 `console.*` | | 日志参数顺序 | `log.error({ err: error, userId }, "操作描述")` | | 错误对象字段名 | `err`(不是 `error`) | | 模块级 logger 命名 | kebab-case(`audit` / `exams` / `client-error`) | | ESLint no-console | 服务端强制,客户端 track-event 改为 no-op | | 客户端错误上报 | 通过 track-event 上报,不导入服务端 logger | | Request ID | proxy.ts 注入 + `withRequestContext` 包装 Server Action + `getRequestContext().requestId` 读取 | ### 1.11 测试 | 场景 | 规则 | | ---------------------------- | --------------------------------------------------------------------------------- | | Vitest + Next.js 16 缓存测试 | server-only alias 到空 stub;`cache()` 包装函数测试需 mock | | Playwright 检测 | 检查 HTTP 状态码 + pageerror(循环外注册一次)+ 可见错误元素,不检查 HTML content | | testTimeout | ts-morph 解析慢,arch-scan 测试设置 60000ms | ### 1.12 ESLint 与验证命令 | 场景 | 命令/规则 | | -------------------- | ---------------------------------------------------------------------- | | 类型检查 | `npx tsc --noEmit` | | Lint | `npm run lint` | | 开发服务器 | `npm run dev` | | 生产构建 | `npm run build` | | 同步 schema | `npm run db:push` | | 执行迁移 | `npm run db:migrate` | | 验证 schema 一致性 | `node scripts/verify-modules-schema.mjs` | | 检查 i18n key 含 `.` | Grep `^\s*"[a-zA-Z_]+\.[a-zA-Z_\.]+"\s*:` glob `**/messages/**/*.json` | | arch.db 扫描 | `npm run arch:scan` | | arch.db 查询 | `npm run arch:query -- ` | | ESLint 零警告 | 所有修改文件必须 0 errors 0 warnings | | 自定义规则加载 | Windows 路径用 `path.join`,不用 `path.posix.join` | --- ## 二、模块经验 ### 2.1 lesson-preparation(备课) | 场景 | 规则 | | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------ | | Zustand store 拆 slice | mutation 方法包装 history(`pushHistory` + `set`);`updateNodePosition` 不推 history;hydrate/replaceDoc 清空历史;history 栈上限 `MAX_HISTORY` | | 撤销/重做快捷键 | `metaKey \|\| ctrlKey` 区分 Mac;Shift+Z 或 Y 重做;`preventDefault` | | 自动保存失败 UI | toast.error + 断网时不触发 + 监听 online/offline + saveError 显示重试按钮 | | 发布前预览 3 步 | `select → preview → confirm` 状态机;总分 useMemo;确认页警告文案 | | Tiptap Image 扩展 | `inline: false, allowBase64: false`;`editor.commands.setImage`;不自建上传组件 | | Tiptap SSR | `immediatelyRender: false` 避免 hydration mismatch | | 附件表 Date 转换 | data-access 返回 Date,service 转换为 ISO string | | 打印视图 | `print:hidden` / `print:static` / `break-inside-avoid` / `print:fixed` | | 素材库 picker | 复用 `use-file-upload` + 通过 service 调用 + `FileTargetType` 扩展 | | Server Action form action 适配 | `duplicateLessonPlanFormAction(formData)` 内部调用 + `redirect()` | | 知识点按需加载 | 对话框 Tab 内 useEffect 调用,不首屏 Promise.all | | AI 模块纯服务端 | `import "server-only"` + Zod 验证 + 失败降级返回空 | | 版本对比/一致性校验 | 纯函数 `diffDocuments` / `checkConsistency` / `computeCurriculumCoverage` | | 移动端切换 | `useMediaQuery` 在 readonly-view,避免服务端 useState 默认值不一致 | | 类型扩展 | 新增可选字段 `stage?` / `differentiation?`,不破坏旧数据 | | chapterId null | `kp.chapterId ?? null`(`string \| undefined` → `string \| null`) | | React Flow 移除 | 改为三列布局(sidebar + paper area + detail panel),纸感界面 | | 师生互动节点 | 渲染为对话 + '师'/'生' 角色标签 + 虚线分隔 | | 节点展开/收起 | 左侧栏、右侧面板、右键菜单三处入口一致 | | 右键菜单 | 节点操作(展开/收起、移动、复制、删除)+ AI 辅助(生成内容、优化表达、差异化建议、分层提问) | | 排版差异 | Fraunces 16px 主文(近黑)+ Inter 13.5px 节点(暖灰)+ 左 2px 细线 | ### 2.2 exams / homework / grades | 场景 | 规则 | | ------------- | ------------------------------------------------------------------------ | | AI 出题 | `exams/ai-pipeline.ts` + `createAiChatCompletion` + JSON 解析 + Zod 校验 | | 作业化考试 | `sourceExamId` 外键关联;`homeworkSubmissions` + `homeworkAnswers` 表 | | 批改事务 | `db.transaction` 包裹多步写 | | 成绩批量更新 | `useOptimistic` | | FULLTEXT 检索 | questions 表 `content_text` STORED 生成列 + `MATCH AGAINST BOOLEAN MODE` | ### 2.3 messaging / notifications | 场景 | 规则 | | ---------------- | -------------------------------------------------------------------- | | 消息星标乐观更新 | `useOptimistic` + `useTransition` + `addOptimisticStarred` | | 消息列表星标 Map | `useOptimistic>`(key=messageId) | | 撤回不乐观更新 | 需服务端返回判断 2 分钟窗口 | | 通知批量化 | `createNotifications(items)` 单次 INSERT;in_app 批量 + 其他渠道并行 | | 通知偏好 | 已从 messaging 迁移至 notifications | | fan-out 分页 | `getAllUserIds(limit=1000, offset)` 分页遍历 | ### 2.4 attendance | 场景 | 规则 | | ----------------------- | ------------------------------------------------------------------------------------------ | | 考勤提交乐观更新 | `useOptimistic` 保留 form action 模式(`useFormStatus` 依赖 form action promise) | | 不用 useTransition 包裹 | 会导致 form action 立即返回、`useFormStatus` 不可靠 | ### 2.5 school / classes / scheduling | 场景 | 规则 | | ------------------------ | ------------------------------------------------------------------------------------ | | school data-access 拆分 | `data-access-{departments,grades,subjects,classrooms,semesters,schools}.ts` + barrel | | 角色判断迁出 data-access | `lib/school-permissions.ts` 导出 `getSchoolsForUser(userId)` | | promoteGrades 事务 | `db.transaction` 包裹 select + update 循环 | | classSchedule 写入口 | 统一到 scheduling 模块 | | 跨模块批量接口 | `getGradeNamesByIds(gradeIds): Promise>` | | A-06 JOIN 改批量查询 | 移除 `.leftJoin(textbooks, ...)`,改 `getTextbookTitlesByIds(ids)` + Map 合并 | | 单条查询用单 ID 接口 | `getClassNameById(classId)` 替代 `db.select().from(classes)` | ### 2.6 audit / rbac | 场景 | 规则 | | -------------------- | ------------------------------------------------------------------------------------------------------------------------- | | 破坏性操作用专属权限 | `AUDIT_LOG_PURGE` ≠ `AUDIT_LOG_READ` | | 权限点注册全链路 | `permissions.ts` → `ROLE_PERMISSIONS_SEED` → `permission-bitmap.ts` → `permission-catalog.ts` → i18n `rbac.json` 五处同步 | | 审计日志保留管理 | `AUDIT_RETENTION_MANAGE` 权限点 | ### 2.7 parent | 场景 | 规则 | | ------------------ | ------------------------------------------------------------------- | | parent actions.ts | app/ 必须调用 `@/modules/parent/actions`,不直接 import data-access | | 路由权限校验 | 同时校验 `parentId` + `studentId` 防止信息泄露 | | dashboard 数据获取 | `Promise.all` / `Promise.allSettled` 并行 | | dashboard 组件 | 用 Server Component,除非需要 `useMemo` | ### 2.8 shared | 场景 | 规则 | | ----------------------- | ---------------------------------------------------------------------------------------------- | | session.ts 单一入口 | shared/lib/\* 不再静态 import @/auth | | dynamic import 打破循环 | `session.ts` 内部 `dynamic import("@/auth")` | | 重复 helper 提取 | `serializeDate` / `downloadBase64File` / `getInitials` / `isRecord` / `extractQuestionPreview` | | ROLE_NAMES 常量 | 替代 DB 查询中硬编码角色名 | | api-response 工具 | `apiSuccess` / `apiError` / `apiFromAction` / `withApiErrorHandler` | | sse 工具 | `formatSseEvent` / `createSseResponse` / `createSseError` | --- ## 三、工作经验日志 > 按时间倒序,50 条上限。AI 发现更好方案时可更新本节。 ### 2026-07-07 | 时间 | 模块 | 经验 | | ----- | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 22:30 | 全局 | 企业级架构规范化 P2 完成:CI 新增 Unit test 阶段;创建 /api/health 端点 + Dockerfile HEALTHCHECK;安装 @next/bundle-analyzer(`npm run analyze` 触发);noUncheckedIndexedAccess 评估 419 错误,推迟到独立迭代(记入 tech-debt TD-P1-001) | | 22:10 | 全局 | 企业级架构规范化 P1 完成:创建 LICENSE(专有协议)/CHANGELOG/CONTRIBUTING/SECURITY 大仓文档;安装 husky + lint-staged + commitlint,pre-commit 运行 eslint+prettier,commit-msg 校验 conventional commits 格式;填充 tech-debt.md(P0/P1/P2 分级 + 已解决项) | | 21:50 | 全局 | 企业级架构规范化 P0 完成:arch:scan @public JSDoc 标记豁免机制(12 个登录前/公开/内部工具 Action);递归 CTE 识别间接调用链(requireAiPermission 等辅助函数);修复 9 个真违规(parent 5 个 requireAuth→requirePermission + settings 1 个显式校验 + 3 个 @public 豁免);拆分 5 个超长文件(schema.ts 2245→29+27子文件、invalidation-map.ts 1195→50+6子文件、messaging/actions.ts 973→47+5子文件、textbooks/data-access.ts 907→15+6子文件、questions/data-access.ts 828→48+4子文件);违规数 32→0 | | 21:30 | 全局 | 004 架构文档 V3 重构:从"影响地图"升级为"架构设计文档",14 章节 912 行。核心 mermaid 图:系统边界图 + 三层架构图 + 模块依赖全景图(35 模块分层)+ 认证 sequenceDiagram + 权限系统架构图 + 数据访问分层图 + 5 层状态模型图 + 跨模块协作 sequenceDiagram + 考试全生命周期 sequenceDiagram(跨 4 模块)+ 令牌分层架构图 + API 路由分类图 + 错误→状态码映射图 + userId/permission 影响链。模块细节移至各模块 README,004 聚焦架构层面:技术栈选型/分层规则/依赖深度统计/循环依赖处理/权限 3 道防线/缓存策略/状态层选用规则/聚合层模式/事件驱动闭环/硬性约束/性能原则/安全原则/架构决策记录 | | 20:56 | 全模块 | 35 个模块 README 重写为完整架构文档:每个含 mermaid 架构图(graph TB 分层)+ 2-3 个 流程图(sequenceDiagram/flowchart)+ 技术栈表格 + 文件结构(行数来自 arch.db)+ 依赖关系双向表 + 关键约束 + 架构决策表。标杆 textbooks/README.md 251 行。arch:query 提供真实数据(模块统计/文件清单/依赖关系/技术标签)。并行子代理执行时 arch:scan 可能因竞争报 FOREIGN KEY 错误,串行执行正常 | | 12:56 | onboarding/parent/messaging | data-access 审计 Phase 0 P0 修复完成(6 项):onboarding 加 server-only + DB 操作下沉;parent 新建 actions.ts(6 个 Server Action);messaging data-access.ts(1089 行)拆分为 5 子文件 + barrel | | 12:27 | 全局 | data-access 审计 v1 完成:86 文件 ~30 actions,5 维度 4 严重级别,生成 230 issues(17 P0/48 P1/105 P2/60 P3),5 子代理并行审查 | | 10:45 | 全局 | cacheFn 迁移审查完成:302 cacheFn 块全部正确放置(Raw + Wrapper 配对),9 处 POS-ERR 为 JSDoc/`};` 结尾的误报 | | 09:49 | 全局 | V5 状态管理重构 Phase 4-5 完成:5 层状态模型(URL/Server/Client/UI/Form)+ notify 替换规则 + ModalRoot 命令式 registry | | 08:13 | lesson-preparation/messaging | `as` 断言违规修复:11 个 `isXxxBlockData` 类型守卫 + `BLOCK_DATA_GUARDS` registry,替换 14 处 `as` 断言 | ### 2026-07-06 | 时间 | 模块 | 经验 | | ----- | ---- | ------------------------------------------------------------------------------------------------- | | 23:50 | 全局 | 文档体系重设计计划制定:arch.db 扫描器 + 004 瘦身 + 005 废弃 + known-issues 精简 + 35 模块 README | ### 2026-07-05 | 时间 | 模块 | 经验 | | ---- | --------------------------- | ---------------------------------------------------------------------------------------------------- | | 全天 | 全局 | V5 状态管理重构 Phase 1-3 完成:notify 替换为 zustand + ModalRoot;useDialogState 评估结论(不采用) | | 全天 | 全局 | 性能预算重构:Phase 1 配置基线 + Phase 2 Bundle 预算 + Phase 3 数据层优化 + Phase 4 组件渲染优化 | | 全天 | questions/search | FULLTEXT 索引 + MATCH AGAINST BOOLEAN MODE 替代 LIKE '%xxx%' | | 全天 | notifications | fan-out 批量化:`getAllUserIds` 分页 + `createNotifications` 批量 INSERT | | 全天 | 全局 | 高 LIMIT 调低:attendance 5000→100、adaptive-practice 1000→100、questions 1000→100 | | 全天 | messaging/grades/attendance | useOptimistic + useTransition 乐观更新改造 | ### 2026-07-04 | 时间 | 模块 | 经验 | | ---- | ---- | -------------------------------------------------------------------------------------------- | | 全天 | 全局 | API 规范化重构:响应信封 + 错误→状态码映射 + SSE 工具 + withApiErrorHandler HOF | | 全天 | 全局 | 设计令牌专项重构:双层令牌架构(Primitive + Semantic)+ ESLint 强制约束 | | 全天 | 全局 | PWA 支持:manifest.ts + viewport + 触控友好(panOnDrag/zoomOnPinch/zoomOnDoubleClick=false) | ### 2026-07-02 | 时间 | 模块 | 经验 | | ---- | ------------------ | ------------------------------------------------------------------------------------- | | 全天 | lesson-preparation | V5 备课模块场景缺口修复:Zustand slice + history + 自动保存 + Tiptap Image + 打印视图 | ### 2026-06-26 | 时间 | 模块 | 经验 | | ---- | ------- | ------------------------------------------------------------------------------------------ | | 全天 | student | 学生端路由权限校验修复:`homework:submit` → `CLASS_READ`;新增 `loading.tsx` + `error.tsx` | | 全天 | classes | teacher/classes/\* 路由权限校验修复:统一 `CLASS_READ` | ### 2026-06-24 | 时间 | 模块 | 经验 | | ---- | ------ | ------------------------------------------------------------------------ | | 全天 | grades | 成绩录入支持按试卷录入:`getExamsForGradeEntry` / `getExamForGradeEntry` | | 全天 | shared | `ROLE_NAMES` 常量替代 DB 硬编码角色名;`extractQuestionPreview` 共享工具 | ### 2026-06-23 | 时间 | 模块 | 经验 | | ---- | ------ | ------------------------------------------------------------------------------------------------------------------- | | 全天 | shared | Action 错误处理统一:`handleActionError` / `safeActionCall` / `BusinessError` / `NotFoundError` / `ValidationError` | ### 2026-06-22 | 时间 | 模块 | 经验 | | ---- | ------- | -------------------------------- | | 全天 | classes | teacher/classes 路由权限审计修复 | ### 2026-06-17 | 时间 | 模块 | 经验 | | ---- | ---- | ------------------------------------------------------------------ | | 全天 | 全局 | 004 架构影响地图首次编写(4227 行,后于 2026-07-07 瘦身至 458 行) |