前面我们讲了 Service 层(业务逻辑),现在是Handler 层------它是连接 HTTP 世界和 C++ 业务逻辑的"接线员"。Handler 不负责具体业务(那是 Service 的事),它只做 3 件事:解析 HTTP 请求 → 调用 Service → 返回 HTTP 响应。
1:本文模块
因为本篇设计的头文件本身只负责声明,所以头文件可以直接忽略不讲。
project-cpp-oj-vibecoding/
├── src/
│ ├── main.cc
│ ├── server/
│ ├── handler/ ← 今天的主角
│ │ ├── auth_handler.hpp/cc ← 4 个函数(注册/登录/登出/me)
│ │ ├── problem_handler.hpp/cc ← 5 个函数(题目 CRUD)
│ │ ├── submit_handler.hpp/cc ← 3 个函数(提交/查询)
│ │ └── admin_handler.hpp/cc ← 3 个函数(测试用例管理)
│ ├── service/ ← Handler 调用 Service
│ ├── model/
│ ├── db/
│ └── utils/
1:Handler层在整个架构中的位置
HTTP 请求(浏览器)
│
▼
┌── Router(路由表)──── 根据 URL 匹配对应的 Handler ──┐
│ │
│ POST /api/login → handle_login() │
│ GET /api/problems → handle_get_problems() │
│ POST /api/submit → handle_submit() │
│ ... │
└──────────┬────────────────────────────────────────────┘
│
▼
┌── Handler ────────────────────────────────────────────┐
│ │
│ ① 从 HTTP 请求中提取参数(URL、body、Cookie) │
│ ② 调用对应的 Service 方法 │
│ ③ 把 Service 返回的数据转成 JSON 写入 HTTP 响应 │
│ │
└──────────┬────────────────────────────────────────────┘
│
▼
┌── Service 层 ──→ 数据库 ──→ 返回数据 ──┐
└────────────────────────────────────────┘
2:Handler层的接线员比喻
| 角色 | 对应代码 | 比喻 |
|---|---|---|
| 电话拨入 | HTTP 请求 (URL + 参数) | 有人打电话来 |
| 总机接线员 | Router | 听对方要找谁,转接到对应分机 |
| 分机 A | auth_handler | "注册 / 登录部" |
| 分机 B | problem_handler | "题目管理部" |
| 分机 C | submit_handler | "提交判题部" |
| 分机 D | admin_handler | "管理员后台部" |
| 具体干活的人 | Service 层 | 真正做事的人 |
| 回复 | HTTP 响应 (JSON) | 告诉对方结果 |
2:路由表------所有API的电话本
在"\src\server\server.cc"中,有一张完整的路由表,把每个URL映射到对应的Handler函数:
cpp
// ============================================================
// 文件名: server.cc
// 作用: 注册所有 API 路由
// 这是整个系统的"电话本"------告诉 Router 每个请求该找谁
// ============================================================
void register_routes(httplib::Server& server) {
// ─── 认证相关(auth_handler) ───
server.Post("/api/register", handle_register); // 注册
server.Post("/api/login", handle_login); // 登录
server.Post("/api/logout", handle_logout); // 登出
server.Get("/api/me", handle_me); // 获取当前用户
// ─── 题目相关(problem_handler) ───
server.Get("/api/problems", handle_get_problems); // 题目列表
server.Get("/api/problems/:id", handle_get_problem); // 题目详情
server.Post("/api/problems", handle_create_problem); // 创建题目
server.Put("/api/problems/:id", handle_update_problem); // 更新题目
server.Delete("/api/problems/:id", handle_delete_problem); // 删除题目
// ─── 提交相关(submit_handler) ───
server.Post("/api/submit", handle_submit); // 提交代码
server.Get("/api/submissions/:id", handle_get_submission); // 查询判题结果
server.Get("/api/submissions", handle_get_submissions); // 提交历史
// ─── 管理相关(admin_handler) ───
server.Get("/api/problems/:id/testcases", handle_get_testcases); // 获取测试用例
server.Post("/api/problems/:id/testcases", handle_add_testcase); // 添加测试用例
server.Delete("/api/problems/:id/testcases/:tc_id", handle_delete_testcase); // 删除测试用例
}
URL参数提取
/api/problems/:id中的:id是什么意思?
cpp
// 当用户访问 /api/problems/42 时
// httplib 会把 42 提取到 req.path_params["id"] 中
void handle_get_problem(const Request& req, Response& res) {
int problem_id = std::stoi(req.path_params.at("id"));
// problem_id = 42
}
这就像 URL 里的"占位符"------`:id` 表示"这里可以是任意数字"。
3:四大Handler概览
| Handler | 方法 | API | 说明 |
|---|---|---|---|
| auth_handler(认证) | handle_register | POST /register | 注册新用户 |
| handle_login | POST /login | 用户登录,设置 Cookie | |
| handle_logout | POST /logout | 用户登出,清除 Cookie | |
| handle_me | GET /me | 获取当前用户信息 | |
| problem_handler(题目) | handle_get_problems | GET /problems | 题目列表(所有人可看) |
| handle_get_problem | GET /problems/:id | 题目详情(含示例用例) | |
| handle_create_problem | POST /problems | 创建题目(仅管理员) | |
| handle_update_problem | PUT /problems/:id | 更新题目(仅管理员) | |
| handle_delete_problem | DELETE /problems/:id | 删除题目(仅管理员) | |
| submit_handler(提交) | handle_submit | POST /submit | 提交代码 |
| handle_get_submission | GET /submissions/:id | 查询单次判题结果 | |
| handle_get_submissions | GET /submissions | 查询提交历史 | |
| admin_handler(管理) | handle_get_testcases | GET /problems/:id/testcases | 查看测试用例 |
| handle_add_testcase | POST /problems/:id/testcases | 添加测试用例 | |
| handle_delete_testcase | DELETE /.../testcases/:tc_id | 删除测试用例 |
4:Handlers共用的辅助函数
在看具体 Handler 之前,先看它们共用的 3 个辅助函数。
1:json_error()------快速返回错误
每个 Handler 文件都定义了自己版本的 `json_error()`:
cpp
// 在 auth_handler.cc / problem_handler.cc / submit_handler.cc / admin_handler.cc 中
// 几乎一模一样
static json json_error(const std::string& msg) {
json j;
j["error"] = msg; // 创建一个 {"error": "错误信息"} 的 JSON
return j;
}
使用场景
cpp
// 不用辅助函数:每次要写 3 行
json j;
j["error"] = "Problem not found";
res.set_content(j.dump(), "application/json");
// 用辅助函数:1 行搞定
res.set_content(json_error("Problem not found").dump(), "application/json");
2:get_session_id()------从Cookie提取Session
cpp
// ============================================================
// 从 HTTP 请求的 Cookie 中提取 session_id
//
// Cookie 的格式是:
// "session_id=a1b2c3d4...; other_cookie=xxx"
//
// 这个函数做的就是:找到 "session_id=" 后面的值
// ============================================================
std::string get_session_id(const httplib::Request& req) {
// 从请求头中获取 Cookie 字符串
auto cookie = req.get_header_value("Cookie");
// 在 Cookie 中查找 "session_id="
auto pos = cookie.find("session_id=");
if (pos == std::string::npos) return ""; // 没找到 → 未登录
pos += 11; // 跳过 "session_id=" 这 11 个字符
// 查找下一个 ";"(Cookie 的分隔符)
auto end = cookie.find(';', pos);
if (end == std::string::npos) {
return cookie.substr(pos); // 没有其他 Cookie 了
}
return cookie.substr(pos, end - pos); // 截取 session_id 的值
}
3:require_auth()------鉴权检查
cpp
// ============================================================
// 鉴权辅助函数:从请求中提取 session_id → 调用 AuthService 鉴权
//
// 任何需要登录才能访问的接口,都要调用这个函数
// 如果返回的 user_id <= 0,说明用户未登录
// ============================================================
SessionInfo require_auth(const httplib::Request& req) {
auto sid = get_session_id(req); // 从 Cookie 拿 session_id
return AuthService::instance().authenticate(sid); // 调用 Service 层鉴权
}
4:require_admin()------管理员检查
各 Handler 实现了自己的管理员检查:
cpp
// 在 problem_handler.cc 中
static bool is_admin(const httplib::Request& req) {
auto user = require_auth(req); // 先鉴权
return user.role == "admin"; // 再检查角色
}
// 在 admin_handler.cc 中
static bool require_admin(const httplib::Request& req) {
auto user = require_auth(req);
return user.role == "admin";
}
5:auth_handler.cc------认证相关(4个函数)
这是 Handler 中最简单的一组------我们已经详细讲过 AuthService,Handler 只是把 HTTP 请求传给它。
1:整体结构
cpp
#include "auth_handler.hpp"
#include "../service/auth_service.hpp"
#include <httplib.h>
#include <json.hpp>
using json = nlohmann::json;
// 辅助函数:构造错误 JSON
static json json_error(const std::string& msg, int code = 0) { ... }
// 辅助函数:从 Cookie 拿 session_id
std::string get_session_id(const httplib::Request& req) { ... }
// 辅助函数:鉴权
SessionInfo require_auth(const httplib::Request& req) { ... }
2:handle_register()------注册
cpp
// ============================================================
// POST /api/register
// 请求体: {"username": "alice", "password": "123456"}
//
// 做了 4 件事:
// 1. 解析 JSON 请求体 → 提取 username 和 password
// 2. 校验参数不为空
// 3. 调用 AuthService::register_user()
// 4. 根据结果返回不同状态码
//
// 状态码:
// 201 Created --- 注册成功
// 400 Bad Request --- 缺少参数
// 409 Conflict --- 用户名已存在
// 500 Internal Server Error --- 服务器错误
// ============================================================
void handle_register(const httplib::Request& req, httplib::Response& res) {
// 第 1 步:解析请求体 JSON
json body;
try { body = json::parse(req.body); }
catch (...) {
res.status = 400; // JSON 格式错误
res.set_content(json_error("Invalid JSON").dump(), "application/json");
return;
}
// 第 2 步:提取参数
auto username = body.value("username", "");
auto password = body.value("password", "");
// 第 3 步:校验
if (username.empty() || password.empty()) {
res.status = 400;
res.set_content(json_error("Username and password required").dump(),
"application/json");
return;
}
// 第 4 步:调用 Service
std::string err;
int user_id = AuthService::instance().register_user(username, password, err);
// 第 5 步:根据结果返回
if (user_id < 0) {
if (err == "Username already exists") {
res.status = 409; // 409 Conflict
} else {
res.status = 500; // 500 服务器错误
}
res.set_content(json_error(err).dump(), "application/json");
return;
}
// 注册成功 → 201 Created
json j;
j["id"] = user_id;
j["username"] = username;
res.status = 201;
res.set_content(j.dump(), "application/json");
}
3:handle_login()------登录
cpp
// ============================================================
// POST /api/login
// 请求体: {"username": "alice", "password": "123456"}
//
// 核心:登录成功后,在 HTTP 响应头中设置 Set-Cookie
// 浏览器收到后会保存这个 Cookie,后续请求自动携带
//
// 状态码:
// 200 OK --- 登录成功,同时设置 Cookie
// 400 Bad Request --- 缺少参数
// 401 Unauthorized --- 用户名或密码错误
// ============================================================
void handle_login(const httplib::Request& req, httplib::Response& res) {
// 解析请求体
json body;
try { body = json::parse(req.body); }
catch (...) {
res.status = 400;
res.set_content(json_error("Invalid JSON").dump(), "application/json");
return;
}
auto username = body.value("username", "");
auto password = body.value("password", "");
if (username.empty() || password.empty()) {
res.status = 400;
res.set_content(json_error("Username and password required").dump(),
"application/json");
return;
}
// 调用 Service 登录
std::string err;
auto session_id = AuthService::instance().login(username, password, err);
if (session_id.empty()) {
res.status = 401; // 401 Unauthorized
res.set_content(json_error(err.empty() ? "Invalid credentials" : err).dump(),
"application/json");
return;
}
// ★ 登录成功!在响应头中设置 Cookie
// Set-Cookie: session_id=a1b2c3d4...; Path=/; HttpOnly
// Path=/ → 整个网站都使用这个 Cookie
// HttpOnly → 浏览器 JS 无法读取 Cookie(防 XSS 攻击)
res.status = 200;
res.set_header("Set-Cookie",
"session_id=" + session_id + "; Path=/; HttpOnly");
json j;
j["message"] = "Login successful";
j["username"] = username;
res.set_content(j.dump(), "application/json");
}
4:handle_logout()------退出
cpp
// ============================================================
// POST /api/logout
//
// 做两件事:
// 1. 删除服务器上的 Session 文件
// 2. 告诉浏览器删除 Cookie(设置 Max-Age=0)
// ============================================================
void handle_logout(const httplib::Request& req, httplib::Response& res) {
auto sid = get_session_id(req); // 从 Cookie 获取 session_id
AuthService::instance().logout(sid); // 删除 Session 文件
// ★ 清除浏览器 Cookie:设置 Max-Age=0 让浏览器立即删除
res.status = 200;
res.set_header("Set-Cookie", "session_id=; Path=/; Max-Age=0");
json j;
j["message"] = "Logged out";
res.set_content(j.dump(), "application/json");
}
5:handle_me()------获取当前用户
cpp
// ============================================================
// GET /api/me
// 获取当前登录用户的信息
//
// 流程:
// 1. require_auth() 从 Cookie 解析 session_id
// 2. 调用 AuthService.authenticate() 返回用户信息
// 3. 如果未登录返回 401
//
// 这是"鉴权"的典型场景------每个请求都需要检查用户是否登录
// ============================================================
void handle_me(const httplib::Request& req, httplib::Response& res) {
auto user = require_auth(req);
if (user.user_id <= 0) {
res.status = 401;
res.set_content(json_error("Not authenticated").dump(),
"application/json");
return;
}
json j;
j["id"] = user.user_id;
j["username"] = user.username;
j["role"] = user.role;
res.status = 200;
res.set_content(j.dump(), "application/json");
}
6:auth_handler的HTTP状态码总结
cpp
注册:
400 ← JSON 格式错误 / 缺少参数
409 ← 用户名已存在
201 ← 注册成功
登录:
400 ← 缺少参数
401 ← 用户名或密码错误
200 ← 登录成功(设置 Cookie)
登出:
200 ← 登出成功(清除 Cookie)
获取当前用户:
401 ← 未登录
200 ← 返回用户信息
6:problem_handler.cc------题目·相关(5个函数)
1:管理员检查
cpp
// 题目 CRUD 中,创建/更新/删除需要管理员权限
static bool is_admin(const httplib::Request& req) {
auto user = require_auth(req); // 先鉴权
return user.role == "admin"; // 再检查角色
}
2:handle_get_problems()------题目列表
cpp
// ============================================================
// GET /api/problems
// 无需登录,任何人都可以看题目列表
//
// 流程:
// 1. 调用 ProblemService::get_problem_list()
// 2. 把返回的 vector<Problem> 转成 JSON 数组
// ============================================================
void handle_get_problems(const httplib::Request& req, httplib::Response& res) {
// 调用 Service 层获取数据
auto problems = ProblemService::instance().get_problem_list();
// 把 C++ 结构体转成 JSON 数组
json j = json::array();
for (auto& p : problems) {
json item;
item["id"] = p.id;
item["title"] = p.title;
item["difficulty"] = p.difficulty;
item["pass_rate"] = p.pass_rate;
item["total_submissions"] = p.total_submissions;
item["accepted"] = p.accepted;
j.push_back(std::move(item));
}
res.status = 200;
res.set_content(j.dump(), "application/json");
// 返回的 JSON 示例:
// [
// {"id":1, "title":"两数相加", "difficulty":"easy",
// "pass_rate":65.5, "total_submissions":200, "accepted":131},
// {"id":2, "title":"反转链表", "difficulty":"medium", ...}
// ]
}
3:handle_get_problem()------题目详细
cpp
// ============================================================
// GET /api/problems/:id
// 获取单道题的详细信息(含示例测试用例)
//
// 例如 GET /api/problems/1 → req.path_params["id"] = "1"
// ============================================================
void handle_get_problem(const httplib::Request& req, httplib::Response& res) {
int problem_id = std::stoi(req.path_params.at("id"));
auto p = ProblemService::instance().get_problem_detail(problem_id);
if (p.id == 0) {
res.status = 404;
res.set_content(json_error("Problem not found").dump(),
"application/json");
return;
}
// 把 Problem 结构体转成 JSON
json j;
j["id"] = p.id;
j["title"] = p.title;
j["description"] = p.description;
j["difficulty"] = p.difficulty;
j["time_limit"] = p.time_limit;
j["memory_limit"] = p.memory_limit;
// 把示例用例也转成 JSON
json samples = json::array();
for (auto& tc : p.sample_cases) {
json s;
s["input"] = tc.input;
s["expected"] = tc.expected;
samples.push_back(std::move(s));
}
j["sample_cases"] = std::move(samples);
res.status = 200;
res.set_content(j.dump(), "application/json");
}
4:管理员操作------创建/更新/删除题目
这三个函数的模式完全一样:检查管理员 → 解析 JSON → 调用 Service → 返回结果
cpp
// ═══════════════════════════════════════════════════════════
// POST /api/problems --- 创建题目(仅管理员)
// 状态码:201 创建成功,403 无权限,400 JSON 错误,500 失败
// ═══════════════════════════════════════════════════════════
void handle_create_problem(const httplib::Request& req, httplib::Response& res) {
// 第 1 步:检查管理员权限
if (!is_admin(req)) {
res.status = 403; // 403 Forbidden
res.set_content(json_error("Forbidden").dump(), "application/json");
return;
}
// 第 2 步:解析请求体
json body;
try { body = json::parse(req.body); }
catch (...) {
res.status = 400;
res.set_content(json_error("Invalid JSON").dump(), "application/json");
return;
}
// 第 3 步:调用 Service(带默认值)
int id = ProblemService::instance().create_problem(
body.value("title", ""),
body.value("description", ""),
body.value("input_desc", ""),
body.value("output_desc", ""),
body.value("difficulty", "easy"), // 默认 easy
body.value("time_limit", 2), // 默认 2 秒
body.value("memory_limit", 256)); // 默认 256MB
if (id < 0) {
res.status = 500;
res.set_content(json_error("Failed to create problem").dump(),
"application/json");
return;
}
// 第 4 步:返回成功
json j;
j["id"] = id;
j["message"] = "Problem created";
res.status = 201; // 201 Created
res.set_content(j.dump(), "application/json");
}
// ═══════════════════════════════════════════════════════════
// PUT /api/problems/:id --- 更新题目(仅管理员)
// 状态码:200 成功,403 无权限
// ═══════════════════════════════════════════════════════════
void handle_update_problem(const httplib::Request& req, httplib::Response& res) {
if (!is_admin(req)) {
res.status = 403;
res.set_content(json_error("Forbidden").dump(), "application/json");
return;
}
// ...(解析 JSON → 调用 Service → 返回 200)
}
// ═══════════════════════════════════════════════════════════
// DELETE /api/problems/:id --- 删除题目(仅管理员)
// 状态码:200 成功,403 无权限
// ═══════════════════════════════════════════════════════════
void handle_delete_problem(const httplib::Request& req, httplib::Response& res) {
if (!is_admin(req)) {
res.status = 403;
res.set_content(json_error("Forbidden").dump(), "application/json");
return;
}
// ...(调用 Service → 返回 200)
}
7:submit_handler.cc------提交判题(3个函数)
这是 Handler 中最复杂的一个,因为处理"提交"涉及多个步骤的校验。
1:handle_submit()------提交代码
cpp
// ============================================================
// POST /api/submit --- 提交代码
// 请求体: {"problem_id": 1, "code": "#include <iostream>..."}
//
// 这道"安检门"有 6 道检查:
// 1. 是否登录?
// 2. JSON 格式正确吗?
// 3. problem_id 和 code 都有吗?
// 4. 代码超 64KB 了吗?
// 5. 10 秒内重复提交了吗?(限流)
// 6. 题目存在吗?
//
// 全部通过后:
// 1. 插入 submissions 表(status=PENDING)
// 2. 把任务放入判题队列(ExecutorService::enqueue())
// 3. 返回 202 Accepted + submission_id
//
// 状态码:
// 202 Accepted --- 提交成功,正在判题
// 401 --- 未登录
// 400 --- 参数错误
// 413 --- 代码太大
// 429 --- 提交太频繁(限流)
// 404 --- 题目不存在
// 503 --- 判题队列满了
// 500 --- 服务器错误
// ============================================================
void handle_submit(const httplib::Request& req, httplib::Response& res) {
// ── 检查 1:是否登录? ──
auto user = require_auth(req);
if (user.user_id <= 0) {
res.status = 401;
res.set_content(json_error("Not authenticated").dump(),
"application/json");
return;
}
// ── 检查 2:JSON 格式正确吗? ──
json body;
try { body = json::parse(req.body); }
catch (...) {
res.status = 400;
res.set_content(json_error("Invalid JSON").dump(), "application/json");
return;
}
// ── 检查 3:参数齐全吗? ──
int problem_id = body.value("problem_id", 0);
std::string code = body.value("code", "");
if (problem_id <= 0 || code.empty()) {
res.status = 400;
res.set_content(json_error("problem_id and code required").dump(),
"application/json");
return;
}
// ── 检查 4:代码超 64KB 了吗? ──
auto max_size = Config::instance().rate_limit().max_code_size;
if (code.size() > (size_t)max_size) {
res.status = 413; // 413 Payload Too Large
res.set_content(
json_error("Code too large (" + std::to_string(code.size())
+ " > " + std::to_string(max_size) + ")").dump(),
"application/json");
return;
}
// ── 检查 5:限流(10 秒内只能提交 1 次) ──
if (!AuthService::instance().check_rate_limit(user.user_id)) {
res.status = 429; // 429 Too Many Requests
res.set_content(
json_error("Rate limited. Please wait before submitting again.")
.dump(),
"application/json");
return;
}
// ── 检查 6:题目存在吗? ──
if (!ProblemService::instance().problem_exists(problem_id)) {
res.status = 404;
res.set_content(json_error("Problem not found").dump(),
"application/json");
return;
}
// ── 全部通过!插入数据库 ──
auto& pool = ConnectionPool::instance();
auto* conn = pool.get();
if (!conn) {
res.status = 500;
res.set_content(json_error("Internal error").dump(), "application/json");
return;
}
std::string q = "INSERT INTO submissions (user_id, problem_id, code, status) "
"VALUES (" + std::to_string(user.user_id) + ", "
+ std::to_string(problem_id) + ", '"
+ escape(code, conn) + "', 'PENDING')";
if (mysql_query(conn, q.c_str()) != 0) {
pool.release(conn);
res.status = 500;
res.set_content(json_error("Failed to create submission").dump(),
"application/json");
return;
}
int submission_id = mysql_insert_id(conn);
pool.release(conn);
// ── 获取题目信息(时间限制、内存限制) ──
auto p = ProblemService::instance().get_problem_detail(problem_id);
// ── 入队判题 ──
bool queued = ExecutorService::instance().enqueue(
submission_id, problem_id, user.user_id, code,
p.time_limit, p.memory_limit);
if (!queued) {
res.status = 503; // 503 Service Unavailable
res.set_content(json_error("Judge queue is full").dump(),
"application/json");
return;
}
// ── 返回 202 Accepted ──
json j;
j["submission_id"] = submission_id;
j["status"] = "PENDING";
res.status = 202; // 202 Accepted:已接受,处理中
res.set_content(j.dump(), "application/json");
}
提交的6道安检门
cpp
用户提交代码 → handle_submit()
│
├── 安检 1:登录了没? → 401
│
├── 安检 2:JSON 格式对了没? → 400
│
├── 安检 3:problem_id 和 code 都有吗? → 400
│
├── 安检 4:代码小于 64KB 吗? → 413
│
├── 安检 5:10 秒内没重复提交吧? → 429
│
├── 安检 6:题目存在吗? → 404
│
├── 全部通过
│ ├── INSERT INTO submissions (PENDING)
│ ├── enqueue() 入队判题
│ └── 返回 202 + submission_id
│
└── 前端收到 submission_id,开始轮询结果
2:handle_get_submission()------查询单词判题结果
cpp
// ============================================================
// GET /api/submissions/:id --- 查询单次提交的判题结果
//
// 前端提交代码后,会轮询这个接口(每隔 1 秒问一次)
// 直到 status 从 PENDING/JUDGING 变成 AC/WA/CE 等最终状态
//
// 如果 WA(答案错误),还会额外查询失败的测试用例的输入和期望输出
// 这样前端可以展示:"第 3 个测试用例期望输出 5,你的输出是 -5"
// ============================================================
void handle_get_submission(const httplib::Request& req, httplib::Response& res) {
auto user = require_auth(req);
if (user.user_id <= 0) {
res.status = 401;
res.set_content(json_error("Not authenticated").dump(), "application/json");
return;
}
int submission_id = std::stoi(req.path_params.at("id"));
// 查 submissions 表
// WHERE id=? AND user_id=? --- 只能查自己的提交,不能看别人的
auto& pool = ConnectionPool::instance();
auto* conn = pool.get();
// ...执行 SQL 查询...
// 如果 failed_case > 0(比如第 3 个用例失败了)
// 额外查询这个测试用例的输入和期望输出
if (failed_case > 0) {
std::string tc_q = "SELECT input, expected FROM testcases "
"WHERE problem_id=" + std::to_string(problem_id)
+ " ORDER BY sort_order, id"
+ " LIMIT " + std::to_string(failed_case - 1) + ", 1";
// 查第 failed_case 个测试用例的输入和期望
// 这样前端可以展示给用户看
}
res.status = 200;
res.set_content(j.dump(), "application/json");
// 返回的 JSON 示例(WA 时):
// {
// "id": 42,
// "problem_id": 1,
// "status": "WA",
// "failed_case": 3,
// "error_msg": "",
// "time_used": 15,
// "memory_used": 2048,
// "failed_input": "1000 2000",
// "failed_expected": "3000"
// }
}
3:handle_get_submissions()------提交历史
cpp
// ============================================================
// GET /api/submissions?problem_id=1 --- 查询某道题的历史提交
//
// 用户在看题目详情时,可以查看自己这道题的所有提交记录
// 参数 problem_id 是可选的------不传就返回所有题的提交
// ============================================================
void handle_get_submissions(const httplib::Request& req, httplib::Response& res) {
auto user = require_auth(req);
if (user.user_id <= 0) {
res.status = 401;
return;
}
int problem_id = 0;
auto prob_str = req.get_param_value("problem_id");
if (!prob_str.empty()) {
problem_id = std::stoi(prob_str); // 从 URL 参数中提取 ?problem_id=1
}
// 查数据库:WHERE user_id=? [AND problem_id=?]
// 按时间倒序,最多返回 50 条
// ...
}
8:admin_handler.cc------测试用例管理(3个函数)
1:handle_get_testcases()------查看测试用例
cpp
// ============================================================
// GET /api/problems/:id/testcases
// 管理员查看某道题的所有测试用例(包括隐藏用例)
//
// 注意:普通用户看题目详情时也会调用 get_testcases()
// 但那里传的是 include_hidden=false,只返回示例用例
// 管理员这里传 true,返回全部
// ============================================================
void handle_get_testcases(const httplib::Request& req, httplib::Response& res) {
if (!require_admin(req)) { // 检查管理员权限
res.status = 403;
res.set_content(json_error("Forbidden").dump(), "application/json");
return;
}
int problem_id = std::stoi(req.path_params.at("id"));
// ★ 第二个参数 true --- 包含隐藏用例
auto cases = ProblemService::instance().get_testcases(problem_id, true);
json j = json::array();
for (auto& tc : cases) {
json item;
item["id"] = tc.id;
item["input"] = tc.input;
item["expected"] = tc.expected;
item["is_sample"] = tc.is_sample; // 管理员知道哪个是示例哪个是隐藏
item["sort_order"] = tc.sort_order;
j.push_back(std::move(item));
}
res.status = 200;
res.set_content(j.dump(), "application/json");
}
2:handle_add_testcase() / handle_delete_testcase()
cpp
// ═══════════════════════════════════════════════════════════
// POST /api/problems/:id/testcases --- 添加测试用例
// 请求体: {"input": "1 2", "expected": "3", "is_sample": true}
// ═══════════════════════════════════════════════════════════
void handle_add_testcase(const httplib::Request& req, httplib::Response& res) {
if (!require_admin(req)) {
res.status = 403;
return;
}
// 解析 JSON → 调用 ProblemService::add_testcase()
// → 返回 201 Created
}
// ═══════════════════════════════════════════════════════════
// DELETE /api/problems/:id/testcases/:tc_id --- 删除测试用例
// ═══════════════════════════════════════════════════════════
void handle_delete_testcase(const httplib::Request& req, httplib::Response& res) {
if (!require_admin(req)) {
res.status = 403;
return;
}
// → 调用 ProblemService::delete_testcase()
// → 返回 200 OK
}
9:一个完整请求的生命周期
cpp
我们跟踪"用户提交代码"的整个过程,看每个角色做了什么:
```
浏览器 POST /api/submit
│
│ 请求体:{"problem_id":1, "code":"#include <iostream>..."}
│ Cookie: session_id=a1b2...
│
▼
┌── Router(server.cc) ──────────────────────────────────┐
│ 匹配 URL: POST /api/submit → handle_submit() │
└──────────────────────────┬──────────────────────────────┘
│
▼
┌── Handler(submit_handler.cc) ─────────────────────────┐
│ ① require_auth() → 从 Cookie 拿 session_id → 鉴权 │
│ ② 解析 JSON → 提取 problem_id 和 code │
│ ③ 检查代码大小(64KB) │
│ ④ 限流检查(10 秒) │
│ ⑤ 检查题目是否存在 │
│ ⑥ INSERT INTO submissions (PENDING) │
│ ⑦ ExecutorService::enqueue() 入队 │
│ ⑧ 返回 202 + submission_id │
└──────────────────────────┬──────────────────────────────┘
│
▼
前端收到 submission_id = 42
│
│ 开始轮询:GET /api/submissions/42
│(每 1 秒问一次)
│
▼
┌── Handler(submit_handler.cc) ─────────────────────────┐
│ SELECT * FROM submissions WHERE id=42 AND user_id=? │
│ 返回当前状态 │
└──────────────────────────┬──────────────────────────────┘
│
┌──────────────┼──────────────┐
▼ ▼ ▼
PENDING JUDGING AC / WA / CE / ...
(还在排队) (正在判题) (最终结果)
10:Handler层设计模式总结
1:请求响应模式
每个 Handler 函数的结构几乎都遵循同样的模板:
cpp
void handle_xxx(const Request& req, Response& res) {
// 1. 检查权限(如果需要)
auto user = require_auth(req);
if (user.user_id <= 0) { res.status = 401; return; }
// 2. 解析请求参数
json body = json::parse(req.body);
int id = body.value("id", 0);
// 3. 调用 Service
auto result = Service::instance().do_something(id);
// 4. 检查错误
if (result failed) { res.status = 500; return; }
// 5. 返回 JSON
json j; j["data"] = result;
res.status = 200;
res.set_content(j.dump(), "application/json");
}
2:状态码映射
| HTTP 状态码 | 含义 | 使用场景 |
|---|---|---|
| 200 OK | 成功 | 查询、更新、删除成功 |
| 201 Created | 创建成功 | 注册、创建题目、添加测试用例 |
| 202 Accepted | 已接受,处理中 | 提交代码(异步判题) |
| 400 Bad Request | 请求参数错误 | JSON 格式错误、缺少必填字段 |
| 401 Unauthorized | 未登录 | 需要登录的接口 |
| 403 Forbidden | 无权限 | 非管理员访问管理接口 |
| 404 Not Found | 资源不存在 | 题目不存在、提交记录不存在 |
| 409 Conflict | 冲突 | 用户名已存在 |
| 413 Payload Too Large | 请求体太大 | 代码超过 64KB |
| 429 Too Many Requests | 请求太频繁 | 限流 |
| 500 Internal Server Error | 服务器错误 | 数据库连接失败等 |
| 503 Service Unavailable | 服务不可用 | 判题队列满了 |
3:错误信息统一格式
所有错误返回的统一格式:
cpp
{"error": "具体的错误信息"}
前端可以统一处理:
javascript
// 前端代码(api.js)
async function api(path, options) {
const res = await fetch(path, options);
const data = await res.json();
if (!res.ok) {
alert(data.error); // 统一显示错误
return null;
}
return data;
}
11:总结
| 技术点 | 在 Handler 层中的体现 |
|---|---|
| HTTP 状态码 | 200/201/202/400/401/403/404/409/413/429/500/503 |
| Cookie 操作 | Set-Cookie 设置和清除 session_id |
| JSON 序列化 | C++ 结构体 → JSON 字符串 → HTTP 响应 |
| 参数提取 | URL 路径参数 (:id)、查询参数 (?problem_id=)、请求体 JSON |
| 权限控制 | require_auth() 和 require_admin() 双层检查 |
| 错误处理 | try-catch 解析 JSON,统一的 json_error () 格式 |
| 异步处理 | 提交返回 202,前端轮询获取最终结果 |