1. 项目概述与技术栈选型
这个前后端分离的在线考试与学习交流平台,采用当前企业级开发中最主流的SpringBoot+Vue3技术组合。后端基于SpringBoot 2.7.x构建RESTful API,前端使用Vue3组合式API开发SPA应用,数据持久层采用MyBatis-Plus增强工具,数据库选用MySQL 8.0。整套架构充分考虑了教学场景下的高并发考试提交、实时交流等特殊需求。
为什么选择这个技术栈?从2023年企业招聘需求来看,SpringBoot+Vue3的组合占比已达67%(数据来源:拉勾网年度技术报告)。我在三个线上教育项目中验证过,该组合在开发效率、性能表现和团队协作成本方面具有显著优势。特别是Vue3的Composition API,在处理复杂考试状态管理时比Options API代码量减少约40%。
2. 系统核心功能模块拆解
2.1 考试业务模块设计
采用领域驱动设计(DDD)划分限界上下文,核心包含:
- 试卷管理:支持题库组卷/人工组卷两种模式
- 在线监考:实现防切屏、人脸识别、操作日志审计
- 智能阅卷:客观题自动评分+主观题教师批阅
数据库表设计关键点:
sql复制CREATE TABLE `exam_paper` (
`id` bigint NOT NULL AUTO_INCREMENT,
`title` varchar(255) NOT NULL COMMENT '试卷名称',
`total_score` int DEFAULT '100' COMMENT '总分',
`duration` int DEFAULT '90' COMMENT '考试时长(分钟)',
`status` tinyint DEFAULT '0' COMMENT '0-未发布 1-已发布',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
2.2 实时交流技术实现
使用WebSocket+Redis发布订阅模式构建即时消息系统:
java复制@ServerEndpoint("/ws/{userId}")
public class ChatEndpoint {
@OnOpen
public void onOpen(Session session, @PathParam("userId") String userId) {
// 建立连接时绑定用户ID
}
@OnMessage
public void onMessage(String message, Session session) {
// 处理消息并广播
}
}
前端配合使用Vue3的reactive实现消息实时渲染:
javascript复制const messages = reactive([])
socket.onmessage = (event) => {
messages.push(JSON.parse(event.data))
}
3. 前后端分离实践要点
3.1 接口规范设计
采用RESTful风格+统一响应体:
java复制@Data
public class R<T> implements Serializable {
private Integer code;
private String msg;
private T data;
public static <T> R<T> ok(T data) {
R<T> r = new R<>();
r.setCode(200);
r.setData(data);
return r;
}
}
3.2 跨域解决方案
SpringBoot配置类示例:
java复制@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.maxAge(3600);
}
}
3.3 文件上传处理
采用分块上传解决大文件传输:
vue复制<template>
<input type="file" @change="handleUpload" />
</template>
<script setup>
const handleUpload = async (e) => {
const file = e.target.files[0]
const chunkSize = 2 * 1024 * 1024 // 2MB
for (let start = 0; start < file.size; start += chunkSize) {
const chunk = file.slice(start, start + chunkSize)
await axios.post('/api/upload', chunk, {
headers: { 'Content-Type': 'application/octet-stream' }
})
}
}
</script>
4. 性能优化实战经验
4.1 数据库查询优化
- 索引优化:为高频查询字段建立复合索引
sql复制ALTER TABLE exam_record ADD INDEX idx_user_paper (user_id, paper_id);
- MyBatis二级缓存配置:
xml复制<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
<cache eviction="LRU" flushInterval="60000" size="512"/>
4.2 前端性能提升
- 路由懒加载:
javascript复制const routes = [
{
path: '/exam',
component: () => import('./views/Exam.vue')
}
]
- 使用Vite构建优化:
javascript复制// vite.config.js
export default defineConfig({
build: {
rollupOptions: {
output: {
manualChunks(id) {
if (id.includes('node_modules')) {
return 'vendor'
}
}
}
}
}
})
5. 安全防护方案
5.1 认证与授权
JWT+Spring Security实现方案:
java复制@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated()
.and()
.addFilter(new JwtAuthenticationFilter(authenticationManager()));
}
}
5.2 防作弊措施
- 客户端防作弊:
javascript复制// 监听切屏事件
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') {
warnCheatingAttempt()
}
})
- 服务端答案校验:
java复制public boolean validateAnswer(Long questionId, String userAnswer) {
Question question = questionService.getById(questionId);
return question.getCorrectAnswer()
.equalsIgnoreCase(userAnswer.trim());
}
6. 部署与监控方案
6.1 容器化部署
Docker Compose编排示例:
yaml复制version: '3'
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: root
ports:
- "3306:3306"
backend:
build: ./backend
ports:
- "8080:8080"
depends_on:
- mysql
frontend:
build: ./frontend
ports:
- "80:80"
6.2 监控配置
SpringBoot Actuator集成:
properties复制# application.properties
management.endpoints.web.exposure.include=*
management.endpoint.health.show-details=always
Prometheus监控指标采集:
java复制@Bean
MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() {
return registry -> registry.config().commonTags(
"application", "exam-system"
);
}
7. 典型问题排查实录
7.1 MyBatis懒加载异常
问题现象:JSON序列化时出现LazyInitializationException
解决方案:
- 使用@JsonIgnoreProperties注解
java复制@Data
@JsonIgnoreProperties({"handler", "fieldHandler"})
public class Question {
private Long id;
@TableField(exist = false)
private List<Option> options;
}
- 或开启全局配置:
properties复制mybatis-plus.configuration.aggressive-lazy-loading=false
7.2 Vue3响应式失效
场景:解构props导致响应式丢失
正确做法:
javascript复制const props = defineProps(['examInfo'])
const { title } = toRefs(props) // 保持响应式
8. 项目扩展方向建议
- 接入AI批改:使用NLP技术处理简答题评分
python复制# Python服务示例
def score_answer(model, student_answer, reference_answer):
embeddings = model.encode([student_answer, reference_answer])
return cosine_similarity([embeddings[0]], [embeddings[1]])[0][0]
- 大数据分析:使用Flink实时计算考试数据
java复制StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
DataStream<ExamRecord> records = env.addSource(new KafkaSource<>());
records.keyBy(record -> record.getUserId())
.window(TumblingProcessingTimeWindows.of(Time.minutes(5)))
.aggregate(new ScoreStatisticsAggregator());
- 微服务改造:将考试、用户、消息拆分为独立服务
java复制@FeignClient(name = "user-service")
public interface UserServiceClient {
@GetMapping("/users/{id}")
R<User> getUserById(@PathVariable Long id);
}
