162 lines
8.5 KiB
Markdown
162 lines
8.5 KiB
Markdown
# 备考中枢 · 技术约束清单与数据结构说明(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 版本 + 最后写时间
|
||
```
|
||
|
||
### 顶层索引结构(通用)
|
||
```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 | 校验失败(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 端到端验证步骤
|
||
|
||
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 行。
|