2026-08-26 16:20:55 +08:00

8.5 KiB
Raw Blame History

备考中枢 · 技术约束清单与数据结构说明params.md

版本v2.0 · 2026-08-26 用途:供前端 / 后端 / 测试开发阶段引用,与 architecture.md / openapi.yaml / decisions/ 互为契约。 组织方式垂直切片Handler 层 / Service 层 / 数据访问 + 领域类型层单一契约源zod + zod-openapi


一、技术栈与硬约束(必读铁律)

约束
语言 全 TypeScript全程禁止 any(评审铁律,违反即退回)
后端 Express 5.x + Node.js 24 LTS + ESM
前端 Vue 3.5 + Vite 6 + TS 5.8 + Vue Router 4 + Pinia
数据层 本地 JSON 文件(APP_DATA_DIRDocker volume 挂载)
数据校验 zod 运行时校验(读 JSON 必过,无 any
单一契约源 领域类型由 zod z.infer 产出OpenAPI 契约由 zod-openapi 自动生成;请求校验用同一 zod schema —— 三者同源同步
版本锚定 Node 24 LTS / Express 5.2 / Vue 3.5 / Vite 6 / TS 5.8 / zod 4.x / @asteasolutions/zod-to-openapi 8.x安装后回写精确版
包管理 pnpm 10+monorepo workspace
包格式 ESMExpress 5 原生支持 import
响应格式 统一 ApiResponse<T>code=0 成功code!=0 失败)
API 版本 所有端点 /api/v1/ 前缀
图标 前端图标由设计/架构阶段锁定一套 SVG 图标库并全局统一,不混用API/架构文档不出现 emoji
视觉 禁止紫色→粉色渐变方案
文案 禁止空洞占位文案
单文件 单文件 ≤ 300 行,单一职责,入口只装配零业务,依赖只向下

二、数据目录(APP_DATA_DIR

环境变量 APP_DATA_DIR,默认 ./data。Docker volume 挂载。

${APP_DATA_DIR}/
├── questions.json               # 题库
├── answer-records.json          # 作答记录
├── answer-records-YYYY-MM.json  # 归档分片(阈值后按月切)
├── wrong-questions.json         # 错题本
├── mock-exams.json              # 模考记录
├── shenlun.json                 # 申论
├── tasks.json                   # 备考计划
├── stats.json                   # 统计快照
├── settings.json                # 偏好设置
└── meta.json                    # schema 版本 + 最后写时间

顶层索引结构(通用)

{
  "version": 1,
  "updatedAt": "2026-08-26T12:00:00.000Z",
  "index": { "<id>": 0 },
  "items": []
}

原子写入约定

  • .tmpfs.rename 原子替换(同文件系统 rename 原子)。
  • 单进程内所有写走一个 Promise 队列(互斥),逐个执行。
  • 高频写用防抖批写(< 500ms 合并),内存立即生效。
  • 读入文件必过 zod schema 校验(不可信输入收窄为强类型实体)。

三、领域实体清单8 个)

以下字段均由 packages/shared/src/schemas/*.schema.ts 的 zod schema 定义,类型经 z.infer 产出。表中字段为约定值must-match实际以 zod schema 为准。

实体 关键字段 说明
Question id, subject, module, type, stem, options, answer, analysis, source, difficulty, tags, createdAt/At 题目
AnswerRecord id, questionId, module, correctness, isWrong, userAnswer, wrongReasons, tookMs, source, practiceDate 作答记录(驱动统计)
WrongQuestion id, questionId, module, wrongReasons, wrongCount, lastWrongAt, reviewCount, status, note 错题本(错答自动入本)
MockExam id, title, examType, fullScore, score, rank, durationMin, moduleScores, moduleCorrectRate, examDate 模考
ShenLunEssay id, examId, topic, module, content, wordCount, durationMin, selfRating, aiFeedback 申论
StudyTask id, title, module, planDate, status, recurrence, targetCount, completedCount, note 备考任务
StatsSnapshot id, date, totalAnswered/Correct/Wrong, overallCorrectRate, practiceStreak, moduleStats, wrongByReason 统计聚合快照
AppSettings id('app'), targetScore, dailyQuestionTarget, dailyStudyMinutes, uiTheme, primaryColor, aiEnabled 偏好设置(单例)

枚举清单

  • 科目 SubjectKeyxingce | shenlun
  • 模块 ModuleKeyxingce-shuli | xingce-panduan | xingce-yanyu | xingce-changshi | xingce-ziliao | shenlun-zhuizong | shenlun-zonghe | shenlun-shenlun
  • 题型 QuestionTypesingle | multiple | judge | blank | essay
  • 判定 Correctnesscorrect | wrong | partial | blank
  • 错因 WrongReasonknowledge-gap | concept-confusion | careless | time-pressure | method-unfamiliar | calculation-error | reading-error
  • 错题状态 WrongQuestionStatusopen | resolved | mastered
  • 模考类型 ExamTypenational | province | self
  • 任务状态 TaskStatustodo | doing | done | skipped
  • 任务循环 TaskRecurrenceonce | daily | weekly
  • 主题 uiThemelight | dark(默认 light深蓝主色不采用紫粉渐变

四、统一响应格式

// 成功
{ "code": 0, "data": { ... } }
// 失败
{ "code": 40001, "data": null, "message": "human readable" }

分页响应:

{ "code": 0, "data": { "items": [], "total": 100, "page": 1, "limit": 20, "hasMore": true } }

五、错误码约定

区段 含义
40001 请求参数错误BadRequest
42200 校验失败ValidationErrorzod
40400 资源不存在NotFound
50000 内部错误Internal
50303 功能未启用AI 二期 FEATURE_DISABLED
40900 数据写入冲突(写队列拒绝,罕见)

错误码为联合类型(AppErrorcatch 变量用 unknown 窄化,禁止 any 强转


六、垂直切片分包要点(对照 code-organization.md §3

  • Handler 层(routes/*.routes.ts:路由注册 + zod 请求校验(schema.parse,用收窄后结果)+ 调 service + 组装 ApiResponse<T>,端点变更只落在一个 route 文件。
  • Service 层(services/*.service.ts:跨实体业务逻辑(抽题 / 判定 / 错题入本 / 增量聚合 / 备份还原),不 import req/res不返回 HTTP 响应。
  • 数据访问 + 领域类型(data/ + packages/shared/src/schemas/data/store.ts(读 JSON + 原子写 + 写队列 + zod 校验)、data/file-map.ts;领域类型与请求校验均由 zod schema 单一契约源产出。
  • 前端:页面在 views/ 按产品页面分包dashboard / question-entry / practice / mock-exam / task / news / settings组合式函数在 composables/API 调用统一封装 services/api-client.ts,解析 ApiResponse<T>;类型由 openapi 契约生成(orval / @hey-api/openapi-ts)或复用 packages/sharedz.infer 类型。
  • 组件单文件 ≤ 300 行,单一职责。

七、单一契约源zod + zod-openapi

  • 领域类型packages/shared/src/schemas/*.schema.ts 的 zod schema → z.infer 产出具名类型前后端共用0 any。
  • OpenAPI 契约:同一批 zod schema → zod-openapiOpenAPIRegistry.register / registerPath + OpenApiGeneratorV3.generateDocument)自动生成 openapi.yaml
  • 请求校验route handler 用同一 schema schema.parse(raw)
  • 改字段链路:改 packages/shared/src/schemas/x.schema.ts 一处 → 重新生成 openapi 契约 → 前端重新生成 TS 类型 → route handler 校验自动跟随。类型、契约、校验三者永不失同步。

八、AI 扩展位(二期预留,本期不实现)

  • apps/server/src/services/ai/providers/AiProvider 接口 + NoopProvider 空实现。
  • Question.aiExplanation / aiConfidenceShenLunEssay.aiFeedback 字段已在 zod schema 预留。
  • AppSettings.aiEnabled(恒 false/api/v1/ai/* 本期不挂路由,返回 50303。

九、Phase 2 端到端验证步骤

  1. pnpm install → 核对版本与锚定表一致。
  2. pnpm --filter <server> build 通过strict + 无 any
  3. pnpm --filter <server> devcurl /api/v1/settings 返回 {code:0,data:{...}}
  4. 核心闭环:POST /questionsPOST /practice/submit(答错)→ GET /wrong-questions 见该题 → PATCH /wrong-questions/:id 打错因 → GET /stats/overview 见聚合。
  5. cat ${APP_DATA_DIR}/questions.json 校验为合法 JSON原子写未损坏
  6. 门禁:find apps -name '*.ts' | xargs wc -l 最大 ≤ 300app.ts < 100 行。