8.5 KiB
8.5 KiB
备考中枢 · 技术约束清单与数据结构说明(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_DIR,Docker 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) |
| 包格式 | ESM(Express 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": []
}
原子写入约定
- 写
.tmp后fs.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 | 偏好设置(单例) |
枚举清单
- 科目
SubjectKey:xingce|shenlun - 模块
ModuleKey:xingce-shuli|xingce-panduan|xingce-yanyu|xingce-changshi|xingce-ziliao|shenlun-zhuizong|shenlun-zonghe|shenlun-shenlun - 题型
QuestionType:single|multiple|judge|blank|essay - 判定
Correctness:correct|wrong|partial|blank - 错因
WrongReason:knowledge-gap|concept-confusion|careless|time-pressure|method-unfamiliar|calculation-error|reading-error - 错题状态
WrongQuestionStatus:open|resolved|mastered - 模考类型
ExamType:national|province|self - 任务状态
TaskStatus:todo|doing|done|skipped - 任务循环
TaskRecurrence:once|daily|weekly - 主题
uiTheme:light|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 | 校验失败(ValidationError,zod) |
| 40400 | 资源不存在(NotFound) |
| 50000 | 内部错误(Internal) |
| 50303 | 功能未启用(AI 二期 FEATURE_DISABLED) |
| 40900 | 数据写入冲突(写队列拒绝,罕见) |
错误码为联合类型(
AppError),catch 变量用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/shared的z.infer类型。 - 组件单文件 ≤ 300 行,单一职责。
七、单一契约源(zod + zod-openapi)
- 领域类型:
packages/shared/src/schemas/*.schema.ts的 zod schema →z.infer产出具名类型,前后端共用,0 any。 - OpenAPI 契约:同一批 zod schema →
zod-openapi(OpenAPIRegistry.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 / aiConfidence、ShenLunEssay.aiFeedback字段已在 zod schema 预留。AppSettings.aiEnabled(恒 false),/api/v1/ai/*本期不挂路由,返回 50303。
九、Phase 2 端到端验证步骤
pnpm install→ 核对版本与锚定表一致。pnpm --filter <server> build通过(strict + 无 any)。pnpm --filter <server> dev→curl /api/v1/settings返回{code:0,data:{...}}。- 核心闭环:
POST /questions→POST /practice/submit(答错)→GET /wrong-questions见该题 →PATCH /wrong-questions/:id打错因 →GET /stats/overview见聚合。 cat ${APP_DATA_DIR}/questions.json校验为合法 JSON(原子写未损坏)。- 门禁:
find apps -name '*.ts' | xargs wc -l最大 ≤ 300;app.ts< 100 行。