feat: rebuild CSP practice workflow, UX and automation
这个提交包含在:
142
docs/API参考.md
普通文件
142
docs/API参考.md
普通文件
@@ -0,0 +1,142 @@
|
||||
# API 参考(v1)
|
||||
|
||||
统一前缀:`/api/v1`
|
||||
|
||||
> Docker/生产推荐通过前端同域反代访问:`/admin139/api/v1/...`
|
||||
|
||||
## 通用约定
|
||||
|
||||
- 鉴权头:`Authorization: Bearer <token>`
|
||||
- 成功响应:`{ "ok": true, "data": ... }`(Auth 接口除外)
|
||||
- 失败响应:`{ "ok": false, "error": "..." }`
|
||||
|
||||
---
|
||||
|
||||
## 1) Auth
|
||||
|
||||
### `POST /auth/register`
|
||||
请求:
|
||||
```json
|
||||
{ "username": "alice", "password": "password123" }
|
||||
```
|
||||
响应:
|
||||
```json
|
||||
{ "ok": true, "user_id": 1, "token": "...", "expires_at": 1730000000 }
|
||||
```
|
||||
|
||||
### `POST /auth/login`
|
||||
请求同上,响应同上。
|
||||
|
||||
---
|
||||
|
||||
## 2) 用户与排行榜
|
||||
|
||||
### `GET /me`(需鉴权)
|
||||
返回当前用户信息。
|
||||
|
||||
### `GET /leaderboard/global?limit=100`
|
||||
返回全站 rating 排行。
|
||||
|
||||
---
|
||||
|
||||
## 3) 题库
|
||||
|
||||
### `GET /problems?q=&tag=&difficulty=&page=&page_size=`
|
||||
返回题目列表。
|
||||
|
||||
### `GET /problems/:id`
|
||||
返回题目详情。`Problem` 结构包含:
|
||||
|
||||
- `statement_url`:原始 PDF 链接
|
||||
- `llm_profile_json`:固定 JSON 字符串(`title/difficulty/answer/explanation/knowledge_points/tags/...`)
|
||||
|
||||
---
|
||||
|
||||
## 4) 提交与在线运行
|
||||
|
||||
### `POST /problems/:id/submit`(需鉴权)
|
||||
请求:
|
||||
```json
|
||||
{
|
||||
"language": "cpp",
|
||||
"code": "#include <bits/stdc++.h> ...",
|
||||
"contest_id": 1
|
||||
}
|
||||
```
|
||||
- `contest_id` 可选;若提交到比赛,需已报名且比赛进行中。
|
||||
|
||||
### `GET /submissions?user_id=&problem_id=&contest_id=&page=&page_size=`
|
||||
返回提交列表。
|
||||
|
||||
### `GET /submissions/:id`
|
||||
返回提交详情(含代码、编译日志、运行日志)。
|
||||
|
||||
### `POST /run/cpp`
|
||||
请求:
|
||||
```json
|
||||
{ "code": "...", "input": "1 2\n" }
|
||||
```
|
||||
返回:运行状态、stdout/stderr、compile_log。
|
||||
|
||||
---
|
||||
|
||||
## 5) 错题本
|
||||
|
||||
### `GET /me/wrong-book`(需鉴权)
|
||||
返回当前用户错题本。
|
||||
|
||||
### `PATCH /me/wrong-book/:problemId`(需鉴权)
|
||||
请求:
|
||||
```json
|
||||
{ "note": "复盘思路" }
|
||||
```
|
||||
|
||||
### `DELETE /me/wrong-book/:problemId`(需鉴权)
|
||||
删除错题项。
|
||||
|
||||
---
|
||||
|
||||
## 6) 模拟竞赛
|
||||
|
||||
### `GET /contests`
|
||||
返回比赛列表。
|
||||
|
||||
### `GET /contests/:id`
|
||||
返回比赛详情与题单;若请求带 token,还会返回 `registered`。
|
||||
|
||||
### `POST /contests/:id/register`(需鉴权)
|
||||
报名比赛。
|
||||
|
||||
### `GET /contests/:id/leaderboard`
|
||||
比赛排行榜(按 solved desc, penalty asc)。
|
||||
|
||||
---
|
||||
|
||||
## 7) 学习知识库
|
||||
|
||||
### `GET /kb/articles`
|
||||
返回知识库文章列表。
|
||||
|
||||
### `GET /kb/articles/:slug`
|
||||
返回文章详情与关联题目。
|
||||
|
||||
---
|
||||
|
||||
## 8) 题库导入任务(PDF + LLM)
|
||||
|
||||
### `GET /import/jobs/latest`
|
||||
返回最近一次导入任务及运行状态(`runner_running`)。
|
||||
|
||||
### `GET /import/jobs/:id`
|
||||
返回指定导入任务详情。
|
||||
|
||||
### `GET /import/jobs/:id/items?status=&page=&page_size=`
|
||||
返回任务明细(每个 PDF 的处理状态、结果或错误)。
|
||||
|
||||
### `POST /import/jobs/run`
|
||||
手动触发导入任务(若已有运行中的任务会返回 `409`)。
|
||||
|
||||
请求体(可选):
|
||||
```json
|
||||
{ "clear_all_problems": true }
|
||||
```
|
||||
133
docs/Docker部署.md
133
docs/Docker部署.md
@@ -1,24 +1,137 @@
|
||||
# Docker Compose 部署
|
||||
# Docker Compose 部署指南
|
||||
|
||||
## 一键启动
|
||||
## 1. 启动
|
||||
|
||||
```bash
|
||||
docker compose up -d --build
|
||||
```
|
||||
|
||||
## 访问
|
||||
查看状态:
|
||||
|
||||
- 前端:http://localhost:7888
|
||||
- 后端(通过前端反代):http://localhost:7888/admin139/api/health
|
||||
- 后端(注册):`POST http://localhost:7888/admin139/api/v1/auth/register`
|
||||
- 后端(登录):`POST http://localhost:7888/admin139/api/v1/auth/login`
|
||||
```bash
|
||||
docker compose ps
|
||||
docker compose logs --tail=100 backend
|
||||
docker compose logs --tail=100 frontend
|
||||
```
|
||||
|
||||
## 数据持久化
|
||||
## 2. 访问地址
|
||||
|
||||
- SQLite 数据库文件:compose volume `csp_data` -> 容器内 `/data/csp.db`
|
||||
- 前端:`http://<服务器IP>:7888/`
|
||||
- 后端健康检查(反代):`http://<服务器IP>:7888/admin139/api/health`
|
||||
|
||||
## 停止
|
||||
示例:
|
||||
|
||||
- `http://caddns.pandoras.work:7888/`
|
||||
- `http://caddns.pandoras.work:7888/admin139/api/health`
|
||||
|
||||
## 3. 端口与持久化
|
||||
|
||||
- `7888:3000`(frontend 对外)
|
||||
- backend 默认不直接暴露端口(经 frontend 反代)
|
||||
- 数据卷:`csp_data` -> `/data/csp.db`
|
||||
|
||||
## 4. 常用运维命令
|
||||
|
||||
```bash
|
||||
docker compose down
|
||||
docker compose up -d --build
|
||||
docker compose restart frontend backend
|
||||
```
|
||||
|
||||
## 4.1 初始化大规模题库(winterant/oi)
|
||||
|
||||
```bash
|
||||
set -a; source .env; set +a
|
||||
python3 scripts/import_winterant_oi.py \
|
||||
--db-path /var/lib/docker/volumes/csp_csp_data/_data/csp.db \
|
||||
--workers 3 \
|
||||
--clear-all-problems
|
||||
```
|
||||
|
||||
当前导入流程为:下载 PDF -> LLM 识别 -> 固定 JSON 落库 + 任务状态落库(内置 500/502/503/504 自动重试)。
|
||||
|
||||
仅下载导入、跳过 LLM:
|
||||
|
||||
```bash
|
||||
python3 scripts/import_winterant_oi.py --skip-llm --workers 3
|
||||
```
|
||||
|
||||
前端可通过 `/imports` 查看导入进度与明细结果。
|
||||
|
||||
请在 `.env` 中配置:
|
||||
|
||||
```env
|
||||
OI_LLM_API_URL=https://one.hao.work/v1/chat/completions
|
||||
OI_LLM_API_KEY=<your_key>
|
||||
OI_LLM_MODEL=qwen3-max
|
||||
OI_LLM_STREAM=true
|
||||
OI_LLM_RETRY_MAX=5
|
||||
OI_LLM_RETRY_SLEEP_SEC=1.5
|
||||
OI_PDF_RETRY_MAX=5
|
||||
OI_PDF_RETRY_SLEEP_SEC=1.5
|
||||
OI_IMPORT_DIRECT_FALLBACK=true
|
||||
OI_IMPORT_PREFER_DIRECT=true
|
||||
OI_IMPORT_AUTO_RUN=true
|
||||
OI_IMPORT_WORKERS=3
|
||||
OI_IMPORT_CLEAR_EXISTING=true
|
||||
OI_IMPORT_CLEAR_SOURCE_PREFIX=winterant/oi
|
||||
OI_IMPORT_CLEAR_ALL_PROBLEMS=false
|
||||
```
|
||||
|
||||
## 5. 故障排查
|
||||
|
||||
### 5.1 无法访问 7888
|
||||
|
||||
1) 检查监听:
|
||||
```bash
|
||||
ss -ltnp | rg 7888
|
||||
```
|
||||
|
||||
2) 检查容器端口映射:
|
||||
```bash
|
||||
docker compose ps
|
||||
```
|
||||
|
||||
3) 本机探活:
|
||||
```bash
|
||||
curl -i http://127.0.0.1:7888/
|
||||
curl -i http://127.0.0.1:7888/admin139/api/health
|
||||
```
|
||||
|
||||
### 5.2 检查是否被 Clash 拦截
|
||||
|
||||
通常 inbound 到 `7888` 不会被 Clash 代理规则拦截,但可按下列方式确认:
|
||||
|
||||
```bash
|
||||
ps -ef | rg clash
|
||||
cat /opt/clash/runtime.yaml | rg "allow-lan|port|socks-port|redir-port"
|
||||
iptables -t nat -S
|
||||
nft list ruleset
|
||||
```
|
||||
|
||||
若你希望 Clash 自身也允许局域网访问(与本项目 7888 端口不是同一件事),可设置:
|
||||
|
||||
- `/opt/clash/runtime.yaml` 中 `allow-lan: true`
|
||||
- 重启 Clash 服务/进程
|
||||
|
||||
### 5.3 Docker 拉取慢/失败
|
||||
|
||||
可为 Docker 配置镜像加速,例如:
|
||||
|
||||
`/etc/docker/daemon.json`
|
||||
```json
|
||||
{
|
||||
"registry-mirrors": ["https://docker.m.daocloud.io"]
|
||||
}
|
||||
```
|
||||
|
||||
然后:
|
||||
```bash
|
||||
systemctl daemon-reload
|
||||
systemctl restart docker
|
||||
```
|
||||
|
||||
## 6. 安全建议
|
||||
|
||||
- 生产环境建议在 Nginx/Caddy 前加 TLS。
|
||||
- 在线编译运行属于高风险能力,建议部署到隔离沙箱执行。
|
||||
|
||||
203
docs/平台总体设计.md
203
docs/平台总体设计.md
@@ -1,174 +1,63 @@
|
||||
# 平台总体设计(草案)
|
||||
# 平台总体设计
|
||||
|
||||
> 目标:面向初学者的 OI/CSP 学习知识库 + 日常练习 + 模拟竞赛系统,并提供在线 C++ 编写/编译/调试能力。前后端分离:Next.js + C++(Drogon) + SQLite。
|
||||
> 目标:围绕 CSP/OI 学习闭环构建一体化系统:知识学习 -> 日常练习 -> 错题复盘 -> 模拟竞赛 -> 排名反馈。
|
||||
|
||||
## 1. 术语与核心对象
|
||||
## 1. 产品模块
|
||||
|
||||
- **用户(User)**:注册/登录;拥有积分、等级、学习进度。
|
||||
- **题目(Problem)**:题面、标签、难度、来源(如 CSP/NOIP/自建)。
|
||||
- **提交(Submission)**:用户对题目的一次代码提交(含编译/运行结果、耗时、内存、得分)。
|
||||
- **练习(Practice)**:非比赛场景的做题记录(可以直接通过 submissions 体现)。
|
||||
- **错题本(WrongBook)**:用户在练习/比赛中未通过的题目集合 + 错因备注。
|
||||
- **比赛(Contest)**:模拟 CSP/NOIP 的比赛;包含题目列表、开始/结束、计分规则。
|
||||
- **排名(Leaderboard)**:全站积分排行、比赛排行。
|
||||
- **知识库(KnowledgeBase)**:学习文章/笔记/专题目录;可关联题目。
|
||||
1. 用户与鉴权
|
||||
2. 题库与在线提交
|
||||
3. 提交记录与结果追踪
|
||||
4. 错题本与复盘
|
||||
5. 模拟竞赛与排行
|
||||
6. 学习知识库
|
||||
7. 在线 C++ 编写/编译/运行
|
||||
|
||||
## 2. 技术架构
|
||||
|
||||
### 2.1 前端
|
||||
### 前端(Next.js)
|
||||
- App Router + TypeScript
|
||||
- 页面模块:`/problems`、`/submissions`、`/wrong-book`、`/contests`、`/kb`、`/run`、`/me`、`/leaderboard`
|
||||
- 通过 `/admin139` 反代后端 API,避免跨域复杂度
|
||||
|
||||
- Next.js(App Router) + TypeScript + Tailwind
|
||||
- 负责:题库/题面、编辑器页面、提交列表、错题本、排行、比赛大厅、知识库阅读
|
||||
### 后端(Drogon C++)
|
||||
- Controller:HTTP 路由与请求校验
|
||||
- Service:业务逻辑(题库/提交/错题本/竞赛/知识库)
|
||||
- Domain:实体与 JSON 序列化
|
||||
- DB:SQLite + 启动迁移 + 演示数据种子
|
||||
|
||||
### 2.2 后端
|
||||
### 数据层(SQLite)
|
||||
- 单文件数据库,便于快速部署
|
||||
- 覆盖用户、题库、提交、错题本、竞赛、知识库完整模型
|
||||
|
||||
- Drogon (HTTP + JSON)
|
||||
- SQLite(单文件数据库,便于部署)
|
||||
- 模块分层(建议):
|
||||
- `controller/`:HTTP 路由
|
||||
- `service/`:业务逻辑
|
||||
- `repo/`:DB 访问(SQL + 映射)
|
||||
- `domain/`:实体与枚举
|
||||
- `judge/`:编译与判题执行器(后续)
|
||||
## 3. 核心业务流
|
||||
|
||||
### 2.3 在线编译/运行(安全边界)
|
||||
### 3.1 练习流
|
||||
1) 用户登录
|
||||
2) 浏览题目并提交
|
||||
3) 后端调用 `g++` 编译并运行样例
|
||||
4) 结果写入 `submissions`
|
||||
5) 若未通过,写入/更新 `wrong_book`
|
||||
6) 若首次 AC,提升 `users.rating`
|
||||
|
||||
- MVP:后端在临时目录中调用 `g++` 编译,并用子进程运行,使用 `ulimit`/超时 kill 做基础限制。
|
||||
- 生产建议:判题/运行必须放在容器或隔离工具(如 nsjail/isolate)中;否则存在逃逸风险。
|
||||
### 3.2 竞赛流
|
||||
1) 用户报名比赛
|
||||
2) 在比赛时间内提交比赛题目
|
||||
3) 按 AC 数与罚时生成排行榜
|
||||
|
||||
## 3. 数据库设计(SQLite)
|
||||
### 3.3 学习流
|
||||
1) 浏览知识库文章
|
||||
2) 查看文章关联题目
|
||||
3) 进入题目页面进行练习
|
||||
|
||||
### 3.1 表清单
|
||||
## 4. 安全与运行边界
|
||||
|
||||
1) `users`
|
||||
- `id` INTEGER PK
|
||||
- `username` TEXT UNIQUE
|
||||
- `password_hash` TEXT
|
||||
- `created_at` INTEGER
|
||||
- `rating` INTEGER DEFAULT 0 (综合积分)
|
||||
- 当前在线运行模块用于开发/教学环境。
|
||||
- 生产环境应将编译运行迁移到受限沙箱,避免宿主机风险。
|
||||
|
||||
2) `problems`
|
||||
- `id` INTEGER PK
|
||||
- `slug` TEXT UNIQUE
|
||||
- `title` TEXT
|
||||
- `statement_md` TEXT
|
||||
- `difficulty` INTEGER
|
||||
- `source` TEXT
|
||||
- `created_at` INTEGER
|
||||
## 5. 可扩展方向
|
||||
|
||||
3) `problem_tags`
|
||||
- `problem_id` INTEGER
|
||||
- `tag` TEXT
|
||||
- PK(`problem_id`,`tag`)
|
||||
- 增加多测试点判题与标准答案管理
|
||||
- 引入更细粒度比赛规则(罚时、封榜、重判)
|
||||
- 支持多语言判题
|
||||
- 增加学习路径与章节化知识图谱
|
||||
|
||||
4) `submissions`
|
||||
- `id` INTEGER PK
|
||||
- `user_id` INTEGER
|
||||
- `problem_id` INTEGER
|
||||
- `language` TEXT (先支持 cpp)
|
||||
- `code` TEXT
|
||||
- `status` TEXT (Pending/Compiling/Running/AC/WA/TLE/MLE/RE/CE)
|
||||
- `score` INTEGER
|
||||
- `time_ms` INTEGER
|
||||
- `memory_kb` INTEGER
|
||||
- `created_at` INTEGER
|
||||
|
||||
5) `wrong_book`
|
||||
- `user_id` INTEGER
|
||||
- `problem_id` INTEGER
|
||||
- `last_submission_id` INTEGER
|
||||
- `note` TEXT
|
||||
- `updated_at` INTEGER
|
||||
- PK(`user_id`,`problem_id`)
|
||||
|
||||
6) `contests`
|
||||
- `id` INTEGER PK
|
||||
- `title` TEXT
|
||||
- `starts_at` INTEGER
|
||||
- `ends_at` INTEGER
|
||||
- `rule_json` TEXT (计分规则/罚时规则)
|
||||
|
||||
7) `contest_problems`
|
||||
- `contest_id` INTEGER
|
||||
- `problem_id` INTEGER
|
||||
- `idx` INTEGER
|
||||
- PK(`contest_id`,`problem_id`)
|
||||
|
||||
8) `contest_registrations`
|
||||
- `contest_id` INTEGER
|
||||
- `user_id` INTEGER
|
||||
- `registered_at` INTEGER
|
||||
- PK(`contest_id`,`user_id`)
|
||||
|
||||
9) `kb_articles`
|
||||
- `id` INTEGER PK
|
||||
- `slug` TEXT UNIQUE
|
||||
- `title` TEXT
|
||||
- `content_md` TEXT
|
||||
- `created_at` INTEGER
|
||||
|
||||
10) `kb_article_links`
|
||||
- `article_id` INTEGER
|
||||
- `problem_id` INTEGER
|
||||
- PK(`article_id`,`problem_id`)
|
||||
|
||||
### 3.2 积分/排行
|
||||
|
||||
- 全站排行:按 `users.rating` 降序。
|
||||
- rating 更新策略(MVP):
|
||||
- 练习 AC:+difficulty * 常数
|
||||
- 比赛:按名次发放奖励分(rule_json 可配置)
|
||||
|
||||
> 该策略后续可替换为 ELO/Codeforces 风格。
|
||||
|
||||
## 4. HTTP API 设计(v1草案)
|
||||
|
||||
统一前缀:`/api/v1`
|
||||
|
||||
### 4.1 Auth
|
||||
- `POST /auth/register` {username,password}
|
||||
- `POST /auth/login` {username,password} -> {token}
|
||||
- 鉴权:`Authorization: Bearer <token>`(MVP 可用 HMAC JWT)
|
||||
|
||||
### 4.2 Problems
|
||||
- `GET /problems?tag=&difficulty=&q=&page=`
|
||||
- `GET /problems/:id`
|
||||
|
||||
### 4.3 Submissions
|
||||
- `POST /problems/:id/submit` {language,code}
|
||||
- `GET /submissions?user_id=&problem_id=&page=`
|
||||
- `GET /submissions/:id`
|
||||
|
||||
### 4.4 WrongBook
|
||||
- `GET /me/wrong-book`
|
||||
- `PATCH /me/wrong-book/:problemId` {note}
|
||||
- `DELETE /me/wrong-book/:problemId`
|
||||
|
||||
### 4.5 Contests
|
||||
- `GET /contests`
|
||||
- `GET /contests/:id`
|
||||
- `POST /contests/:id/register`
|
||||
- `GET /contests/:id/leaderboard`
|
||||
|
||||
### 4.6 Leaderboard
|
||||
- `GET /leaderboard/global`
|
||||
|
||||
### 4.7 Knowledge Base
|
||||
- `GET /kb/articles`
|
||||
- `GET /kb/articles/:slug`
|
||||
|
||||
## 5. 测试策略(TDD)
|
||||
|
||||
- `repo` 层:用内存 SQLite(`:memory:`)+ 迁移脚本,做 CRUD 单测。
|
||||
- `service` 层:对积分、错题本更新等写业务单测。
|
||||
- `controller` 层:Drogon 自带测试/或以 HTTP 集成测试(启动测试服务器,发请求断言 JSON)。
|
||||
|
||||
## 6. 下一步落地顺序(MVP)
|
||||
|
||||
1) 用户注册/登录(token)
|
||||
2) 题库(只读)+ 提交记录入库
|
||||
3) 在线编译(仅编译 + 返回CE/OK),再扩展到运行
|
||||
4) 错题本(由 WA/未通过自动写入)
|
||||
5) 积分与全站排行
|
||||
6) 比赛(创建/报名/排行榜)
|
||||
7) 知识库文章与题目关联
|
||||
|
||||
137
docs/数据库设计.md
普通文件
137
docs/数据库设计.md
普通文件
@@ -0,0 +1,137 @@
|
||||
# 数据库设计(SQLite)
|
||||
|
||||
数据库文件默认位置:`/data/csp.db`(Docker volume `csp_data` 持久化)。
|
||||
|
||||
## 1. 核心表
|
||||
|
||||
### `users`
|
||||
- `id` INTEGER PK AUTOINCREMENT
|
||||
- `username` TEXT UNIQUE
|
||||
- `password_salt` TEXT
|
||||
- `password_hash` TEXT
|
||||
- `rating` INTEGER DEFAULT 0
|
||||
- `created_at` INTEGER
|
||||
|
||||
### `sessions`
|
||||
- `token` TEXT PK
|
||||
- `user_id` INTEGER FK -> users.id
|
||||
- `expires_at` INTEGER
|
||||
- `created_at` INTEGER
|
||||
|
||||
### `problems`
|
||||
- `id` INTEGER PK AUTOINCREMENT
|
||||
- `slug` TEXT UNIQUE
|
||||
- `title` TEXT
|
||||
- `statement_md` TEXT
|
||||
- `difficulty` INTEGER
|
||||
- `source` TEXT
|
||||
- `statement_url` TEXT(原始 PDF/题面链接)
|
||||
- `llm_profile_json` TEXT(固定 JSON:难度、答案、讲解、知识点、标签等)
|
||||
- `sample_input` TEXT
|
||||
- `sample_output` TEXT
|
||||
- `created_at` INTEGER
|
||||
|
||||
### `problem_tags`
|
||||
- `problem_id` INTEGER FK -> problems.id
|
||||
- `tag` TEXT
|
||||
- PK(`problem_id`, `tag`)
|
||||
|
||||
### `submissions`
|
||||
- `id` INTEGER PK AUTOINCREMENT
|
||||
- `user_id` INTEGER FK -> users.id
|
||||
- `problem_id` INTEGER FK -> problems.id
|
||||
- `contest_id` INTEGER NULL FK -> contests.id
|
||||
- `language` TEXT
|
||||
- `code` TEXT
|
||||
- `status` TEXT (`Pending/Compiling/Running/AC/WA/TLE/MLE/RE/CE/Unknown`)
|
||||
- `score` INTEGER
|
||||
- `time_ms` INTEGER
|
||||
- `memory_kb` INTEGER
|
||||
- `compile_log` TEXT
|
||||
- `runtime_log` TEXT
|
||||
- `created_at` INTEGER
|
||||
|
||||
### `wrong_book`
|
||||
- `user_id` INTEGER FK -> users.id
|
||||
- `problem_id` INTEGER FK -> problems.id
|
||||
- `last_submission_id` INTEGER NULL FK -> submissions.id
|
||||
- `note` TEXT
|
||||
- `updated_at` INTEGER
|
||||
- PK(`user_id`, `problem_id`)
|
||||
|
||||
### `contests`
|
||||
- `id` INTEGER PK AUTOINCREMENT
|
||||
- `title` TEXT
|
||||
- `starts_at` INTEGER
|
||||
- `ends_at` INTEGER
|
||||
- `rule_json` TEXT
|
||||
|
||||
### `contest_problems`
|
||||
- `contest_id` INTEGER FK -> contests.id
|
||||
- `problem_id` INTEGER FK -> problems.id
|
||||
- `idx` INTEGER
|
||||
- PK(`contest_id`, `problem_id`)
|
||||
|
||||
### `contest_registrations`
|
||||
- `contest_id` INTEGER FK -> contests.id
|
||||
- `user_id` INTEGER FK -> users.id
|
||||
- `registered_at` INTEGER
|
||||
- PK(`contest_id`, `user_id`)
|
||||
|
||||
### `kb_articles`
|
||||
- `id` INTEGER PK AUTOINCREMENT
|
||||
- `slug` TEXT UNIQUE
|
||||
- `title` TEXT
|
||||
- `content_md` TEXT
|
||||
- `created_at` INTEGER
|
||||
|
||||
### `kb_article_links`
|
||||
- `article_id` INTEGER FK -> kb_articles.id
|
||||
- `problem_id` INTEGER FK -> problems.id
|
||||
- PK(`article_id`, `problem_id`)
|
||||
|
||||
### `import_jobs`
|
||||
- `id` INTEGER PK AUTOINCREMENT
|
||||
- `status` TEXT(`running/success/partial_failed/failed`)
|
||||
- `trigger` TEXT(`auto/manual`)
|
||||
- `total_count` / `processed_count` / `success_count` / `failed_count`
|
||||
- `options_json` TEXT(本次导入参数快照)
|
||||
- `last_error` TEXT
|
||||
- `started_at` / `finished_at` / `updated_at` / `created_at`
|
||||
|
||||
### `import_job_items`
|
||||
- `id` INTEGER PK AUTOINCREMENT
|
||||
- `job_id` INTEGER FK -> import_jobs.id
|
||||
- `source_path` TEXT
|
||||
- `status` TEXT(`queued/running/success/failed`)
|
||||
- `title` TEXT
|
||||
- `difficulty` INTEGER
|
||||
- `problem_id` INTEGER NULL FK -> problems.id
|
||||
- `error_text` TEXT
|
||||
- `started_at` / `finished_at` / `updated_at` / `created_at`
|
||||
- UNIQUE(`job_id`, `source_path`)
|
||||
|
||||
## 2. 关键索引
|
||||
|
||||
- `idx_submissions_user_created_at`
|
||||
- `idx_submissions_problem_created_at`
|
||||
- `idx_submissions_contest_user_created_at`
|
||||
- `idx_problem_tags_tag`
|
||||
- `idx_kb_article_links_problem_id`
|
||||
- `idx_import_jobs_created_at`
|
||||
- `idx_import_jobs_status`
|
||||
- `idx_import_job_items_job_status`
|
||||
|
||||
## 3. 初始化与演示数据
|
||||
|
||||
系统启动时会自动:
|
||||
|
||||
1. 执行 `ApplyMigrations`
|
||||
2. 执行 `SeedDemoData`
|
||||
|
||||
演示数据包含:
|
||||
|
||||
- 基础题目(A+B、Fibonacci、排序)
|
||||
- 题目标签(math/dp/sort 等)
|
||||
- 知识库文章(快速 IO、DP 入门)
|
||||
- 示例模拟赛(含题目关联)
|
||||
44
docs/测试与TDD.md
普通文件
44
docs/测试与TDD.md
普通文件
@@ -0,0 +1,44 @@
|
||||
# 测试与 TDD
|
||||
|
||||
## 1. 测试分层
|
||||
|
||||
后端采用 Catch2,按分层设计测试:
|
||||
|
||||
1. **DB/迁移层**:表结构、索引、迁移兼容性
|
||||
2. **Service 层**:题库、提交判题、错题本、竞赛、知识库业务逻辑
|
||||
3. **Controller 层**:鉴权、参数解析、核心 HTTP 路径
|
||||
|
||||
## 2. 执行命令
|
||||
|
||||
```bash
|
||||
cmake -S . -B build -G Ninja
|
||||
cmake --build build
|
||||
ctest --test-dir build -V
|
||||
```
|
||||
|
||||
## 3. 现有测试覆盖
|
||||
|
||||
- `sqlite_db_test.cc`:迁移后核心表存在
|
||||
- `auth_service_test.cc`:注册/登录/token 校验
|
||||
- `auth_http_test.cc`:Auth 接口
|
||||
- `problem_service_test.cc`:题库查询
|
||||
- `submission_service_test.cc`:提交评测 + 错题本流转
|
||||
- `contest_service_test.cc`:竞赛排行榜逻辑
|
||||
- `kb_service_test.cc`:知识库读取
|
||||
- `problem_http_test.cc`:题库 HTTP
|
||||
- `me_http_test.cc`:个人信息 + 错题本 HTTP
|
||||
- `contest_http_test.cc`:竞赛 HTTP
|
||||
- `submission_http_test.cc`:运行与提交 HTTP
|
||||
|
||||
## 4. TDD 落地流程建议
|
||||
|
||||
1. 先写失败测试(接口契约/业务规则)
|
||||
2. 实现最小功能通过测试
|
||||
3. 重构并保持测试全绿
|
||||
4. 增加边界条件测试(无 token、参数错误、not found、竞赛状态校验)
|
||||
|
||||
## 5. 注意事项
|
||||
|
||||
- 当前判题使用本机 `g++` + `timeout`,用于开发环境。
|
||||
- 生产环境建议接入隔离沙箱(如 isolate/nsjail/容器沙箱)。
|
||||
|
||||
在新工单中引用
屏蔽一个用户