1. 项目概述与技术选型
远程考试系统作为教育信息化的核心应用场景,正在经历从传统C/S架构向B/S云端模式的全面转型。我们基于Java SpringBoot+Vue3+MyBatis技术栈实现的这套系统,采用了典型的前后端分离架构,后端使用SpringBoot 2.7.x构建RESTful API服务,前端采用Vue3+TypeScript实现响应式界面,数据持久层使用MyBatis-Plus 3.5.x增强ORM框架,数据库选用MySQL 8.0作为存储引擎。
这套技术组合的选择背后有着深刻的工程考量:SpringBoot的自动配置和起步依赖大幅降低了微服务搭建的复杂度;Vue3的Composition API相比Options API更适合大型应用的状态管理;MyBatis-Plus在基础CRUD之外提供的Lambda查询和分页插件,显著提升了开发效率。实测表明,这套技术栈在并发量500+的在线考试场景下,平均响应时间能控制在300ms以内。
2. 系统架构设计
2.1 前后端分离架构
系统采用严格的前后端分离模式,通过定义清晰的API契约进行协作。前端部署在Nginx服务器,后端服务打包为可执行JAR,这种架构带来三个显著优势:
- 开发解耦:前后端团队可以并行开发,只需约定好接口文档
- 性能优化:静态资源与动态API可分别进行CDN加速和负载均衡
- 技术异构:未来可无缝替换前端框架或后端语言
接口规范示例(考试列表API):
@RestController @RequestMapping("/api/exam") public class ExamController { @GetMapping public Result<Page<ExamVO>> listExams( @RequestParam(required = false) String keyword, @RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10") Integer size) { // 实现逻辑 } }2.2 数据库设计
MySQL数据库设计了12张核心表,这里重点说明几个关键表结构:
考试信息表(exam_info)
CREATE TABLE `exam_info` ( `id` bigint NOT NULL AUTO_INCREMENT, `title` varchar(100) NOT NULL COMMENT '考试名称', `description` text COMMENT '考试说明', `duration` int NOT NULL COMMENT '考试时长(分钟)', `start_time` datetime NOT NULL COMMENT '开始时间', `end_time` datetime NOT NULL COMMENT '结束时间', `status` tinyint NOT NULL DEFAULT '0' COMMENT '0未开始 1进行中 2已结束', `creator_id` bigint NOT NULL COMMENT '创建人ID', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_time` (`start_time`,`end_time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;考生答题记录表(answer_record)
CREATE TABLE `answer_record` ( `id` bigint NOT NULL AUTO_INCREMENT, `exam_id` bigint NOT NULL, `user_id` bigint NOT NULL, `question_id` bigint NOT NULL, `answer` text COMMENT '考生答案', `is_correct` tinyint DEFAULT NULL COMMENT '是否正确', `score` decimal(5,2) DEFAULT NULL COMMENT '得分', `submit_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_exam_user_question` (`exam_id`,`user_id`,`question_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;特别注意:所有时间字段统一使用datetime类型,避免时区问题。在Java实体类中使用@JsonFormat注解规范序列化格式。
3. 核心功能实现
3.1 考试过程控制
考试状态的精确控制是系统的核心难点,我们采用状态机模式进行管理:
public enum ExamStatus { NOT_STARTED(0) { @Override public boolean canStart() { return true; } }, ONGOING(1) { @Override public boolean canSubmit() { return true; } }, FINISHED(2); private final int code; // 状态转换校验逻辑 public void checkTransition(ExamStatus newStatus) { if (this == FINISHED) { throw new IllegalStateException("考试已结束"); } // 其他校验规则... } }3.2 实时防作弊监控
前端通过三个维度实现防作弊:
- 窗口失去焦点检测
- 页面截图检测(使用html2canvas库)
- 操作行为分析
Vue3实现示例:
const onBlur = () => { warningCount.value++ if (warningCount.value > 3) { submitExam({ reason: '异常离开页面' }) } } const captureScreen = async () => { const canvas = await html2canvas(document.body) const imageData = canvas.toDataURL('image/png') await uploadScreenCapture(examId.value, imageData) } // 每30秒随机截图 setInterval(captureScreen, 30000)4. 关键技术实现
4.1 试卷随机组卷算法
采用权重随机算法保证题目分布的合理性:
public List<Question> generatePaper(ExamRule rule) { Map<QuestionType, List<Question>> questionsByType = questionService .groupByType(rule.getKnowledgePoints()); return rule.getQuestionRules().stream() .flatMap(qRule -> { List<Question> candidates = questionsByType.get(qRule.getType()); return randomSelect(candidates, qRule.getCount()).stream(); }) .collect(Collectors.toList()); } private List<Question> randomSelect(List<Question> questions, int count) { // 使用权重随机算法 double[] weights = questions.stream() .mapToDouble(q -> 1.0 / (q.getUsedCount() + 1)) .toArray(); return RandomUtils.weightedRandom(questions, weights, count); }4.2 高并发提交处理
使用Redis+本地缓存二级缓冲应对提交高峰:
- 先写入Redis队列
- 后台线程批量入库
- 最终一致性校验
SpringBoot配置示例:
@Configuration @EnableCaching public class CacheConfig { @Bean public RedisCacheManager cacheManager(RedisConnectionFactory factory) { RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() .entryTtl(Duration.ofMinutes(30)) .disableCachingNullValues(); return RedisCacheManager.builder(factory) .cacheDefaults(config) .transactionAware() .build(); } }5. 部署与性能优化
5.1 容器化部署方案
使用Docker Compose编排服务:
version: '3.8' services: mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: ${DB_PASSWORD} volumes: - mysql_data:/var/lib/mysql redis: image: redis:6-alpine ports: - "6379:6379" backend: build: ./backend ports: - "8080:8080" depends_on: - mysql - redis frontend: build: ./frontend ports: - "80:80"5.2 性能调优参数
关键JVM参数配置(基于JDK17):
-server -Xms2g -Xmx2g -XX:MaxMetaspaceSize=512m -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:ParallelGCThreads=4 -XX:ConcGCThreads=2 -XX:+HeapDumpOnOutOfMemoryErrorNginx优化配置:
worker_processes auto; events { worker_connections 10240; multi_accept on; } http { sendfile on; tcp_nopush on; keepalive_timeout 65; gzip on; server { listen 80; location / { root /usr/share/nginx/html; try_files $uri $uri/ /index.html; } location /api { proxy_pass http://backend:8080; proxy_set_header Host $host; } } }6. 常见问题排查
6.1 考试提交失败
典型错误场景及解决方案:
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 提交时提示"考试已结束" | 服务器时间与客户端不同步 | 统一使用NTP时间同步 |
| 答案保存失败 | 网络波动导致API超时 | 实现本地暂存+自动重试机制 |
| 图片上传失败 | Nginx配置限制 | 调整client_max_body_size |
6.2 性能瓶颈分析
使用Arthas进行诊断的典型流程:
# 1. 启动Arthas java -jar arthas-boot.jar # 2. 监控方法调用 watch com.example.service.ExamService submitAnswer '{params, returnObj}' -x 3 # 3. 生成火焰图 profiler start profiler stop --format html7. 安全防护措施
7.1 API安全防护
采用五层防护体系:
- JWT身份认证
- 接口签名校验
- 请求频率限制
- SQL注入过滤
- XSS防护
SpringSecurity配置示例:
@Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeRequests() .antMatchers("/api/auth/**").permitAll() .anyRequest().authenticated() .and() .addFilter(new JwtAuthenticationFilter(authenticationManager())) .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS); return http.build(); } }7.2 数据加密方案
敏感数据采用分层加密策略:
- 密码:BCrypt强哈希
- 考生答案:AES对称加密
- 传输数据:HTTPS+TLS1.3
密码加密实现:
public class PasswordEncoder { private static final int BCRYPT_STRENGTH = 12; public static String encode(CharSequence rawPassword) { return new BCryptPasswordEncoder(BCRYPT_STRENGTH).encode(rawPassword); } public static boolean matches(CharSequence rawPassword, String encodedPassword) { return new BCryptPasswordEncoder(BCRYPT_STRENGTH) .matches(rawPassword, encodedPassword); } }这套系统在实际部署中经历了多次迭代优化,特别是在高并发场景下,通过引入Redis缓存热点数据、优化MySQL索引结构、前端实施懒加载等策略,成功支撑了万级考生同时在线考试的需求。对于需要深度定制的场景,系统预留了完善的扩展接口,包括自定义题型支持、第三方认证对接等模块化设计。