[{"data":1,"prerenderedAt":1053},["ShallowReactive",2],{"lab-/labs/hces":3},{"id":4,"title":5,"author":6,"body":7,"category":1039,"date":1040,"description":22,"extension":1041,"featured":695,"home_position":257,"image":1042,"meta":1043,"navigation":695,"order":680,"path":1044,"seo":1045,"status":1046,"stem":1047,"tags":1048,"__hash__":1052},"content/labs/HCES.md","HCES","sibuchen",{"type":8,"value":9,"toc":1007},"minimark",[10,15,19,23,30,33,36,47,49,52,189,191,194,201,207,209,212,217,223,227,233,237,240,273,276,278,281,285,295,321,325,343,347,350,386,390,401,403,406,409,419,458,462,465,490,494,501,507,509,512,518,626,628,631,634,740,742,746,749,790,794,832,835,874,877,940,943,992,994,997,1003],[11,12,14],"h1",{"id":13},"hces-高并发在线考试系统","HCES — 高并发在线考试系统",[16,17,18],"h2",{"id":18},"项目概述",[20,21,22],"p",{},"HCES（High Concurrency Exam System）是一套面向大规模在线考试场景的全栈 Web 应用。系统支持教师创建考试、管理题库，学生在线答题、实时倒计时、自动阅卷与成绩查询，并通过 Redis 分布式锁和 RabbitMQ 消息队列应对考试开始/结束瞬间的并发洪峰。",[20,24,25,29],{},[26,27,28],"strong",{},"技术栈："," Spring Boot + Vue 3 + MySQL + Redis + RabbitMQ + Docker Compose",[31,32],"hr",{},[16,34,35],{"id":35},"系统架构",[37,38,43],"pre",{"className":39,"code":41,"language":42},[40],"language-text","┌─────────────────────────────────────────────────────────────────┐\n│                        Vue 3 前端 (Vite)                        │\n│   Pinia 状态管理 · Vue Router · Element Plus · Axios            │\n└───────────────────────────┬─────────────────────────────────────┘\n                            │ HTTP REST\n┌───────────────────────────▼─────────────────────────────────────┐\n│                    Spring Boot 后端 (Java)                       │\n│                                                                 │\n│  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────────────┐│\n│  │ 用户模块  │  │ 题目模块  │  │ 考试模块  │  │   通用模块       ││\n│  │ Auth     │  │ Question │  │  Exam    │  │ ApiResponse      ││\n│  │ User     │  │          │  │          │  │ GlobalException  ││\n│  └──────────┘  └──────────┘  └────┬─────┘  └──────────────────┘│\n│                                    │                             │\n│                         ┌──────────┴──────────┐                 │\n│                         │  ExamCommandService  │                 │\n│                         │  (高并发命令写入)      │                 │\n│                         └──┬──────────┬───────┘                 │\n│                            │          │                          │\n└────────────────────────────┼──────────┼──────────────────────────┘\n                             │          │\n              ┌──────────────▼┐    ┌────▼──────────────┐\n              │     Redis     │    │     RabbitMQ      │\n              │  分布式锁      │    │  exam.submit.*    │\n              │  状态缓存      │    │  异步削峰队列      │\n              └───────────────┘    └────────┬──────────┘\n                                            │ 消费者异步落库\n                                   ┌────────▼──────────┐\n                                   │      MySQL 8      │\n                                   │  JPA / Hibernate  │\n                                   └───────────────────┘\n","text",[44,45,41],"code",{"__ignoreMap":46},"",[31,48],{},[16,50,51],{"id":51},"技术选型与职责",[53,54,55,71],"table",{},[56,57,58],"thead",{},[59,60,61,65,68],"tr",{},[62,63,64],"th",{},"组件",[62,66,67],{},"技术",[62,69,70],{},"职责",[72,73,74,86,105,116,127,142,156,167,178],"tbody",{},[59,75,76,80,83],{},[77,78,79],"td",{},"前端框架",[77,81,82],{},"Vue 3 + TypeScript + Vite",[77,84,85],{},"单页应用，学生端/教师端双角色 UI",[59,87,88,91,102],{},[77,89,90],{},"状态管理",[77,92,93,94,97,98,101],{},"Pinia (",[44,95,96],{},"authStore"," / ",[44,99,100],{},"examStore",")",[77,103,104],{},"用户登录态、考试进入态的全局管理",[59,106,107,110,113],{},[77,108,109],{},"UI 组件库",[77,111,112],{},"Element Plus",[77,114,115],{},"表单、表格、消息提示、对话框",[59,117,118,121,124],{},[77,119,120],{},"后端框架",[77,122,123],{},"Spring Boot",[77,125,126],{},"REST API、依赖注入、事务管理",[59,128,129,132,135],{},[77,130,131],{},"ORM",[77,133,134],{},"Spring Data JPA + Hibernate",[77,136,137,138,141],{},"数据持久化，",[44,139,140],{},"ddl-auto: update"," 自动建表",[59,143,144,147,153],{},[77,145,146],{},"缓存/锁",[77,148,149,150,101],{},"Redis 7.2 (",[44,151,152],{},"StringRedisTemplate",[77,154,155],{},"分布式锁防重、学生状态缓存、submitToken 存储",[59,157,158,161,164],{},[77,159,160],{},"消息队列",[77,162,163],{},"RabbitMQ 3.13 (AMQP)",[77,165,166],{},"考试提交异步削峰，Producer → DirectExchange → Consumer",[59,168,169,172,175],{},[77,170,171],{},"数据库",[77,173,174],{},"MySQL 8.0 (utf8mb4)",[77,176,177],{},"业务数据持久存储",[59,179,180,183,186],{},[77,181,182],{},"容器化",[77,184,185],{},"Docker Compose",[77,187,188],{},"一键编排 MySQL + Redis + RabbitMQ，共享内部网络",[31,190],{},[16,192,193],{"id":193},"后端分层设计",[20,195,196,197,200],{},"后端按领域（Domain）组织包结构，每个领域严格遵循 ",[26,198,199],{},"Controller → Service → Repository"," 三层分离：",[37,202,205],{"className":203,"code":204,"language":42},[40],"com.hces\n├── common                        # 通用层\n│   ├── api/ApiResponse           # 统一响应封装 { success, message, data }\n│   ├── controller                # Health / Diagnostic / Time 健康检查\n│   └── exception/GlobalExceptionHandler  # @RestControllerAdvice 全局异常处理\n│\n├── user                          # 用户域\n│   ├── controller/AuthController # POST /api/auth/register, /login\n│   ├── domain/User               # 实体：userId, username, password, role, classId\n│   ├── service/UserService       # 接口：findByUsername, findByUserId, save\n│   └── repository/UserRepository # JPA Repository\n│\n├── question                      # 题目域\n│   ├── controller/TeacherQuestionController  # 教师端题库 CRUD\n│   ├── domain/Question           # 实体：questionId, content, optionsJson, type\n│   ├── service/QuestionService   # 接口 + QuestionServiceImpl\n│   └── repository/QuestionRepository\n│\n└── exam                          # 考试域（核心）\n    ├── controller\n    │   ├── TeacherExamController    # 教师端：创建考试、管理考试列表、删除\n    │   ├── StudentExamController    # 学生端：历史成绩、试卷回顾\n    │   ├── ExamQueryController      # 查询：按考试ID/按班级、含学生状态推导\n    │   └── ExamAccessController     # 高并发入口：进入考试、提交考试\n    ├── domain\n    │   ├── Exam                  # 考试实体（title, startTime, endTime, duration, classId...）\n    │   ├── ExamQuestion          # 考试-题目关联表（score, orderNo）\n    │   ├── ExamAnswerRecord      # 答卷记录（answersJson, score）\n    │   ├── StudentExamRecord     # 学生考试记录（status, enteredAt, submittedAt）\n    │   ├── WrongQuestion         # 错题本\n    │   ├── ExamStatus            # 枚举：NOT_STARTED / ONGOING / ENDED\n    │   └── StudentExamStatus     # 枚举：NOT_ENTERED / ENTERED / SUBMITTED\n    ├── service\n    │   ├── ExamService           # 查询服务 + 状态推导（resolveStatus）\n    │   ├── ExamCommandService    # 命令服务：enterExam / submitExam（带并发控制）\n    │   └── StudentExamRecordService\n    ├── messaging                 # MQ 异步消息层\n    │   ├── ExamMessagingConfig   # DirectExchange + Queue + Binding 声明\n    │   ├── ExamSubmitProducer    # 生产者：发送提交消息到 MQ\n    │   ├── ExamSubmitConsumer    # 消费者：监听队列，算分 + 持久化\n    │   └── ExamSubmitMessage     # 消息体 record\n    └── support/ExamCacheKeys     # Redis Key 统一管理（防硬编码）\n",[44,206,204],{"__ignoreMap":46},[31,208],{},[16,210,211],{"id":211},"核心业务流程",[213,214,216],"h3",{"id":215},"_1-学生进入考试高并发入口","1. 学生进入考试（高并发入口）",[37,218,221],{"className":219,"code":220,"language":42},[40],"学生请求 POST /api/exams/{examId}/enter\n         │\n         ▼\n  ExamCommandServiceImpl.enterExam()\n         │\n         ├── 1. 校验考试是否存在、状态是否为 ONGOING\n         ├── 2. Redis 分布式锁 (examId:userId, TTL=3s)  ← 防重复进入\n         ├── 3. 先查数据库是否已 SUBMITTED，再查 Redis 缓存状态\n         ├── 4. 若已 ENTERED → 直接返回缓存的 submitToken（幂等）\n         ├── 5. 若首次进入 → 写入 StudentExamRecord + Redis 状态缓存\n         │      生成 UUID submitToken 存入 Redis（TTL = 考试时长 + 5min）\n         └── 6. 释放锁，返回 EnterExamResponse\n",[44,222,220],{"__ignoreMap":46},[213,224,226],{"id":225},"_2-学生提交考试mq-异步削峰","2. 学生提交考试（MQ 异步削峰）",[37,228,231],{"className":229,"code":230,"language":42},[40],"学生请求 POST /api/exams/{examId}/submit\n         │\n         ▼\n  ExamCommandServiceImpl.submitExam()\n         │\n         ├── 1. 校验 submitToken（Redis 优先 → DB 兜底）  ← 防非法提交\n         ├── 2. Redis 分布式锁 (examId:userId, TTL=3s)    ← 防重复提交\n         ├── 3. 标记 StudentExamRecord 为 SUBMITTED\n         ├── 4. 更新 Redis 缓存状态为 SUBMITTED\n         ├── 5. ★ 将答卷数据投递到 RabbitMQ（exam.submit.exchange）\n         │        返回 SubmitExamResponse（立即响应，不等算分）\n         └── 6. 释放锁\n                    │\n                    ▼ 异步消费\n         ExamSubmitConsumer.onExamSubmit()  [@RabbitListener]\n         │\n         ├── 1. 序列化答案 → 持久化 ExamAnswerRecord\n         ├── 2. 遍历题目自动阅卷计算成绩\n         ├── 3. 将成绩同步写入 StudentExamRecord.score\n         └── 4. 同步错题本（WrongQuestion）\n",[44,232,230],{"__ignoreMap":46},[213,234,236],{"id":235},"_3-考试状态推导无状态设计","3. 考试状态推导（无状态设计）",[20,238,239],{},"考试状态不在数据库中存储，而是根据时间窗口实时推导：",[37,241,245],{"className":242,"code":243,"language":244,"meta":46,"style":46},"language-java shiki shiki-themes github-light github-dark","// ExamService.resolveStatus()\nif (now.isBefore(exam.startTime))    → NOT_STARTED\nif (now.isAfter(exam.endTime))       → ENDED\nelse                                  → ONGOING\n","java",[44,246,247,255,261,267],{"__ignoreMap":46},[248,249,252],"span",{"class":250,"line":251},"line",1,[248,253,254],{},"// ExamService.resolveStatus()\n",[248,256,258],{"class":250,"line":257},2,[248,259,260],{},"if (now.isBefore(exam.startTime))    → NOT_STARTED\n",[248,262,264],{"class":250,"line":263},3,[248,265,266],{},"if (now.isAfter(exam.endTime))       → ENDED\n",[248,268,270],{"class":250,"line":269},4,[248,271,272],{},"else                                  → ONGOING\n",[20,274,275],{},"这种设计避免了定时任务同步状态的复杂性，任何时刻查询都能得到正确状态。",[31,277],{},[16,279,280],{"id":280},"高并发设计要点",[213,282,284],{"id":283},"redis-分布式锁","Redis 分布式锁",[20,286,287,288,290,291,294],{},"通过 ",[44,289,152],{}," 的 ",[44,292,293],{},"setIfAbsent","（即 Redis SETNX）实现轻量级分布式锁：",[296,297,298,309,315],"ul",{},[299,300,301,304,305,308],"li",{},[26,302,303],{},"锁粒度："," ",[44,306,307],{},"exam:{examId}:user:{userId}:lock","，精确到每个学生的每次操作",[299,310,311,314],{},[26,312,313],{},"TTL："," 3 秒自动过期，防止死锁",[299,316,317,320],{},[26,318,319],{},"作用："," 进入考试和提交考试两个写操作均加锁，保证幂等性",[213,322,324],{"id":323},"redis-状态缓存","Redis 状态缓存",[296,326,327,337,340],{},[299,328,329,330,97,333,336],{},"学生考试状态（",[44,331,332],{},"ENTERED",[44,334,335],{},"SUBMITTED","）缓存在 Redis，TTL = 考试时长 + 5 分钟",[299,338,339],{},"submitToken 缓存在 Redis，提交时先从 Redis 校验，未命中再查 DB",[299,341,342],{},"考试全局状态缓存，减少对考试时间窗口的重复计算",[213,344,346],{"id":345},"rabbitmq-异步削峰","RabbitMQ 异步削峰",[20,348,349],{},"这是应对高并发提交的关键设计：",[296,351,352,358,367,376],{},[299,353,354,357],{},[26,355,356],{},"为什么不直接入库？"," 考试结束瞬间大量学生同时提交，直接写 MySQL 会造成连接池耗尽和锁竞争",[299,359,360,304,363,366],{},[26,361,362],{},"Producer 端：",[44,364,365],{},"submitExam()"," 只做状态标记 + 发送 MQ 消息，立即返回响应（毫秒级）",[299,368,369,304,372,375],{},[26,370,371],{},"Consumer 端：",[44,373,374],{},"@RabbitListener"," 单线程顺序消费，平稳写入 MySQL，自动算分 + 同步错题",[299,377,378,381,382,385],{},[26,379,380],{},"消息可靠性："," 使用持久化队列（",[44,383,384],{},"durable=true","）+ JSON 序列化（替代 Java 原生序列化，避免安全风险）",[213,387,389],{"id":388},"submittoken-防重机制","SubmitToken 防重机制",[296,391,392,395,398],{},[299,393,394],{},"学生进入考试时服务端生成 UUID token，存入 Redis 并返回前端",[299,396,397],{},"提交时必须携带 token，服务端校验 token 匹配后才允许提交",[299,399,400],{},"有效防止了重复提交和跨会话伪造提交",[31,402],{},[16,404,405],{"id":405},"前端设计",[213,407,408],{"id":408},"双角色体系",[20,410,411,412,414,415,418],{},"通过路由守卫和 ",[44,413,96],{}," 中的 ",[44,416,417],{},"role"," 字段区分教师端和学生端：",[53,420,421,434],{},[56,422,423],{},[59,424,425,428,431],{},[62,426,427],{},"角色",[62,429,430],{},"登录后落地页",[62,432,433],{},"页面",[72,435,436,447],{},[59,437,438,441,444],{},[77,439,440],{},"教师",[77,442,443],{},"题库管理",[77,445,446],{},"题库管理、创建考试、发布管理、消息中心",[59,448,449,452,455],{},[77,450,451],{},"学生",[77,453,454],{},"考试总览",[77,456,457],{},"考试总览、在线答题、历史成绩、错题本、消息中心",[213,459,461],{"id":460},"考试页面studentexamview","考试页面（StudentExamView）",[20,463,464],{},"前端考试页面包含完整的答题交互逻辑：",[296,466,467,474,477,484,487],{},[299,468,469,470,473],{},"进入考试 → 调用 ",[44,471,472],{},"enterExam"," 获取 submitToken",[299,475,476],{},"实时倒计时（基于服务端时间偏移校准，防止客户端篡改）",[299,478,479,480,483],{},"逐题切换导航，记录答案到 ",[44,481,482],{},"answers"," ref",[299,485,486],{},"提交时携带 submitToken，提交成功后跳转",[299,488,489],{},"已提交学生无法再次进入（前后端双重校验）",[213,491,493],{"id":492},"api-封装","API 封装",[20,495,496,497,500],{},"前端 API 层按职责拆分，统一通过 ",[44,498,499],{},"http.ts"," 中的 Axios 实例发送请求：",[37,502,505],{"className":503,"code":504,"language":42},[40],"api/\n├── http.ts       # Axios 实例（baseURL、拦截器）\n├── auth.ts       # 登录/注册\n├── exam.ts       # 考试进入/提交/查询\n├── student.ts    # 历史成绩/试卷回顾/错题本\n├── teacher.ts    # 教师端考试管理/题库管理\n└── time.ts       # 服务端时间同步\n",[44,506,504],{"__ignoreMap":46},[31,508],{},[16,510,511],{"id":511},"数据库设计",[20,513,514,515,517],{},"核心表（由 JPA 实体 + ",[44,516,140],{}," 自动映射）：",[53,519,520,533],{},[56,521,522],{},[59,523,524,527,530],{},[62,525,526],{},"表（实体）",[62,528,529],{},"说明",[62,531,532],{},"关键字段",[72,534,535,548,561,574,587,600,613],{},[59,536,537,542,545],{},[77,538,539],{},[44,540,541],{},"user",[77,543,544],{},"用户表",[77,546,547],{},"userId, username, password, role(student/teacher), classId",[59,549,550,555,558],{},[77,551,552],{},[44,553,554],{},"question",[77,556,557],{},"题库表",[77,559,560],{},"questionId, content, optionsJson, type, answer",[59,562,563,568,571],{},[77,564,565],{},[44,566,567],{},"exam",[77,569,570],{},"考试表",[77,572,573],{},"examId, title, startTime, endTime, durationMinutes, classId, createdBy",[59,575,576,581,584],{},[77,577,578],{},[44,579,580],{},"exam_question",[77,582,583],{},"考试-题目关联",[77,585,586],{},"examId, questionId, score, orderNo",[59,588,589,594,597],{},[77,590,591],{},[44,592,593],{},"student_exam_record",[77,595,596],{},"学生考试记录",[77,598,599],{},"examId, userId, status, enteredAt, submittedAt, score, submitToken",[59,601,602,607,610],{},[77,603,604],{},[44,605,606],{},"exam_answer_record",[77,608,609],{},"答卷详情",[77,611,612],{},"examId, userId, answersJson, score, submittedAt",[59,614,615,620,623],{},[77,616,617],{},[44,618,619],{},"wrong_question",[77,621,622],{},"错题本",[77,624,625],{},"userId, examId, questionId, yourAnswer, correctAnswer",[31,627],{},[16,629,630],{"id":630},"部署架构",[20,632,633],{},"使用 Docker Compose 一键部署三个中间件，后端和前端可分别在 IDE / CLI 中启动：",[37,635,639],{"className":636,"code":637,"language":638,"meta":46,"style":46},"language-yaml shiki shiki-themes github-light github-dark","# docker-compose.yml\nservices:\n  06_mysql:       # MySQL 8.0    端口映射 ${MYSQL_PORT}:3306\n  06_redis:       # Redis 7.2    端口映射 ${REDIS_PORT}:6379\n  06_rabbitmq:    # RabbitMQ 3.13  端口映射 ${RABBITMQ_AMQP_PORT}:5672 + 管理后台 ${RABBITMQ_WEB_PORT}:15672\n\nnetworks:\n  06_HCES:        # 三容器共享桥接网络，通过服务名互相通信\n\nvolumes:\n  mysql_data / redis_data / rabbitmq_data  # 持久化数据卷\n","yaml",[44,640,641,647,657,668,678,690,697,705,717,722,730],{"__ignoreMap":46},[248,642,643],{"class":250,"line":251},[248,644,646],{"class":645},"sJ8bj","# docker-compose.yml\n",[248,648,649,653],{"class":250,"line":257},[248,650,652],{"class":651},"s9eBZ","services",[248,654,656],{"class":655},"sVt8B",":\n",[248,658,659,662,665],{"class":250,"line":263},[248,660,661],{"class":651},"  06_mysql",[248,663,664],{"class":655},":       ",[248,666,667],{"class":645},"# MySQL 8.0    端口映射 ${MYSQL_PORT}:3306\n",[248,669,670,673,675],{"class":250,"line":269},[248,671,672],{"class":651},"  06_redis",[248,674,664],{"class":655},[248,676,677],{"class":645},"# Redis 7.2    端口映射 ${REDIS_PORT}:6379\n",[248,679,681,684,687],{"class":250,"line":680},5,[248,682,683],{"class":651},"  06_rabbitmq",[248,685,686],{"class":655},":    ",[248,688,689],{"class":645},"# RabbitMQ 3.13  端口映射 ${RABBITMQ_AMQP_PORT}:5672 + 管理后台 ${RABBITMQ_WEB_PORT}:15672\n",[248,691,693],{"class":250,"line":692},6,[248,694,696],{"emptyLinePlaceholder":695},true,"\n",[248,698,700,703],{"class":250,"line":699},7,[248,701,702],{"class":651},"networks",[248,704,656],{"class":655},[248,706,708,711,714],{"class":250,"line":707},8,[248,709,710],{"class":651},"  06_HCES",[248,712,713],{"class":655},":        ",[248,715,716],{"class":645},"# 三容器共享桥接网络，通过服务名互相通信\n",[248,718,720],{"class":250,"line":719},9,[248,721,696],{"emptyLinePlaceholder":695},[248,723,725,728],{"class":250,"line":724},10,[248,726,727],{"class":651},"volumes",[248,729,656],{"class":655},[248,731,733,737],{"class":250,"line":732},11,[248,734,736],{"class":735},"sZZnC","  mysql_data / redis_data / rabbitmq_data",[248,738,739],{"class":645},"  # 持久化数据卷\n",[31,741],{},[16,743,745],{"id":744},"api-接口一览","API 接口一览",[213,747,748],{"id":748},"认证模块",[53,750,751,763],{},[56,752,753],{},[59,754,755,758,761],{},[62,756,757],{},"方法",[62,759,760],{},"路径",[62,762,529],{},[72,764,765,778],{},[59,766,767,770,775],{},[77,768,769],{},"POST",[77,771,772],{},[44,773,774],{},"/api/auth/register",[77,776,777],{},"用户注册",[59,779,780,782,787],{},[77,781,769],{},[77,783,784],{},[44,785,786],{},"/api/auth/login",[77,788,789],{},"用户登录",[213,791,793],{"id":792},"考试模块高并发","考试模块（高并发）",[53,795,796,806],{},[56,797,798],{},[59,799,800,802,804],{},[62,801,757],{},[62,803,760],{},[62,805,529],{},[72,807,808,820],{},[59,809,810,812,817],{},[77,811,769],{},[77,813,814],{},[44,815,816],{},"/api/exams/{examId}/enter",[77,818,819],{},"进入考试（分布式锁 + 状态缓存 + 幂等）",[59,821,822,824,829],{},[77,823,769],{},[77,825,826],{},[44,827,828],{},"/api/exams/{examId}/submit",[77,830,831],{},"提交考试（submitToken 校验 + MQ 异步落库）",[213,833,834],{"id":834},"考试查询",[53,836,837,847],{},[56,838,839],{},[59,840,841,843,845],{},[62,842,757],{},[62,844,760],{},[62,846,529],{},[72,848,849,862],{},[59,850,851,854,859],{},[77,852,853],{},"GET",[77,855,856],{},[44,857,858],{},"/api/exams/{examId}?userId=",[77,860,861],{},"查询单场考试详情（含学生状态）",[59,863,864,866,871],{},[77,865,853],{},[77,867,868],{},[44,869,870],{},"/api/exams/class/{classId}?userId=",[77,872,873],{},"按班级查询考试列表",[213,875,876],{"id":876},"教师端",[53,878,879,889],{},[56,880,881],{},[59,882,883,885,887],{},[62,884,757],{},[62,886,760],{},[62,888,529],{},[72,890,891,903,915,927],{},[59,892,893,895,900],{},[77,894,769],{},[77,896,897],{},[44,898,899],{},"/api/teacher/exams",[77,901,902],{},"创建考试",[59,904,905,907,912],{},[77,906,853],{},[77,908,909],{},[44,910,911],{},"/api/teacher/exams?creatorUserId=",[77,913,914],{},"查询教师创建的考试",[59,916,917,919,924],{},[77,918,853],{},[77,920,921],{},[44,922,923],{},"/api/teacher/exams/{examId}/questions",[77,925,926],{},"查询考试题目列表",[59,928,929,932,937],{},[77,930,931],{},"DELETE",[77,933,934],{},[44,935,936],{},"/api/teacher/exams/{examId}",[77,938,939],{},"删除考试",[213,941,942],{"id":942},"学生端",[53,944,945,955],{},[56,946,947],{},[59,948,949,951,953],{},[62,950,757],{},[62,952,760],{},[62,954,529],{},[72,956,957,969,981],{},[59,958,959,961,966],{},[77,960,853],{},[77,962,963],{},[44,964,965],{},"/api/student/history-scores?userId=",[77,967,968],{},"查询历史成绩",[59,970,971,973,978],{},[77,972,853],{},[77,974,975],{},[44,976,977],{},"/api/student/exam-review/{examId}?userId=",[77,979,980],{},"试卷回顾",[59,982,983,985,990],{},[77,984,853],{},[77,986,987],{},[44,988,989],{},"/api/student/wrong-questions?userId=",[77,991,622],{},[31,993],{},[16,995,996],{"id":996},"项目结构",[37,998,1001],{"className":999,"code":1000,"language":42},[40],"HCES/\n├── docker/\n│   ├── docker-compose.yml          # 容器编排\n│   └── .env.example                # 环境变量模板\n├── hces-backend/\n│   └── src/main/\n│       ├── java/com/hces/\n│       │   ├── common/             # 通用：ApiResponse, GlobalExceptionHandler\n│       │   ├── user/               # 用户域：AuthController, UserService\n│       │   ├── question/           # 题目域：TeacherQuestionController, QuestionService\n│       │   └── exam/               # 考试域：Controller + Service + Messaging + Repository\n│       └── resources/\n│           └── application.yml     # Spring Boot 配置\n├── hces-frontend/\n│   └── src/\n│       ├── api/                    # HTTP 请求封装（6个模块）\n│       ├── components/             # 侧边栏组件\n│       ├── router/                 # 路由配置（学生端 + 教师端）\n│       ├── stores/                 # Pinia 状态（authStore, examStore）\n│       └── views/                  # 12个页面视图\n└── hces3.sql                       # 数据库初始化脚本\n",[44,1002,1000],{"__ignoreMap":46},[1004,1005,1006],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}",{"title":46,"searchDepth":257,"depth":257,"links":1008},[1009,1010,1011,1012,1013,1018,1024,1029,1030,1031,1038],{"id":18,"depth":257,"text":18},{"id":35,"depth":257,"text":35},{"id":51,"depth":257,"text":51},{"id":193,"depth":257,"text":193},{"id":211,"depth":257,"text":211,"children":1014},[1015,1016,1017],{"id":215,"depth":263,"text":216},{"id":225,"depth":263,"text":226},{"id":235,"depth":263,"text":236},{"id":280,"depth":257,"text":280,"children":1019},[1020,1021,1022,1023],{"id":283,"depth":263,"text":284},{"id":323,"depth":263,"text":324},{"id":345,"depth":263,"text":346},{"id":388,"depth":263,"text":389},{"id":405,"depth":257,"text":405,"children":1025},[1026,1027,1028],{"id":408,"depth":263,"text":408},{"id":460,"depth":263,"text":461},{"id":492,"depth":263,"text":493},{"id":511,"depth":257,"text":511},{"id":630,"depth":257,"text":630},{"id":744,"depth":257,"text":745,"children":1032},[1033,1034,1035,1036,1037],{"id":748,"depth":263,"text":748},{"id":792,"depth":263,"text":793},{"id":834,"depth":263,"text":834},{"id":876,"depth":263,"text":876},{"id":942,"depth":263,"text":942},{"id":996,"depth":257,"text":996},"web","2025-12-17","md",null,{},"/labs/hces",{"title":5,"description":22},"ARCHIVED","labs/HCES",[1039,1049,1050,1051,6],"高并发","全栈","Docker","lHMJqkioJK2bH1_s5hMla5xYYbVLqmF_LeCMu7eeFig",1781104361143]