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

162 lines
8.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 备考中枢 · 技术约束清单与数据结构说明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 |
| 包格式 | 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 版本 + 最后写时间
```
### 顶层索引结构(通用)
```jsonc
{
"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深蓝主色不采用紫粉渐变
---
## 四、统一响应格式
```jsonc
// 成功
{ "code": 0, "data": { ... } }
// 失败
{ "code": 40001, "data": null, "message": "human readable" }
```
分页响应
```jsonc
{ "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 | 数据写入冲突写队列拒绝罕见 |
> 错误码为联合类型(`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 端到端验证步骤
1. `pnpm install` 核对版本与锚定表一致
2. `pnpm --filter <server> build` 通过strict + any)。
3. `pnpm --filter <server> dev` `curl /api/v1/settings` 返回 `{code:0,data:{...}}`
4. 核心闭环`POST /questions` `POST /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` 最大 300`app.ts` < 100