1. 项目概述与技术栈选型
大学生竞赛管理系统是一个典型的教务管理类应用,需要处理学生报名、作品提交、评委打分、结果公示等核心业务流程。采用SpringBoot+Vue3+MyBatis+MySQL的技术组合,主要基于以下考虑:
-
SpringBoot:作为Java生态中最主流的微服务框架,其自动配置特性可以快速搭建后端服务。实测中,用
spring-boot-starter-web+spring-boot-devtools组合开发,热部署响应时间在1.5秒内,极大提升开发效率。 -
Vue3:相比Vue2,其Composition API更适合复杂前端状态管理。在竞赛评审场景中,评委可能需要同时查看多个作品并进行横向对比,Vue3的
<script setup>语法可以更优雅地实现这类交互。 -
MyBatis:选择它而非JPA的原因是竞赛系统存在大量动态查询需求。例如管理员后台需要支持多条件筛选参赛作品,MyBatis的动态SQL(
<if>,<choose>标签)比JPA的Criteria API更直观。 -
MySQL:作为最成熟的开源关系型数据库,其事务特性(ACID)能确保评分操作的原子性。实测在100个并发评委打分场景下,InnoDB引擎配合
@Transactional注解可稳定处理2000+ TPS。
提示:技术选型时曾对比过PostgreSQL,但考虑到高校IT部门普遍对MySQL运维更熟悉,最终选择后者。若团队熟悉PG,其JSONB类型其实更适合存储竞赛作品的元数据。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 系统架构设计与核心模块
2.1 前后端分离架构
采用经典的B/S架构:
code复制前端(Vue3) <- HTTP/HTTPS -> Nginx <- AJP -> 后端(SpringBoot)
↑
MySQL Cluster
关键配置要点:
- 前端打包后通过
nginx -t校验配置,确保静态资源路由正确 - 后端启用CORS,示例配置:
java复制@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowCredentials(true)
.maxAge(3600);
}
}
2.2 核心功能模块
-
用户中心模块
- 采用RBAC模型,定义
Admin/Teacher/Student三种角色 - 密码存储使用BCrypt算法,Spring Security配置示例:
java复制@Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(11); } - 采用RBAC模型,定义
-
竞赛管理模块
- 使用状态机管理竞赛生命周期:
未开始→报名中→进行中→评审中→已结束 - 关键数据库表设计:
sql复制CREATE TABLE competition ( id BIGINT PRIMARY KEY, name VARCHAR(100) NOT NULL, status ENUM('NOT_STARTED','REGISTERING','ONGOING','JUDGING','ENDED'), max_team_members TINYINT DEFAULT 5 ); - 使用状态机管理竞赛生命周期:
-
作品提交模块
- 支持文件上传(PDF/PPT/ZIP),使用Spring的
MultipartFile - 为防止重复提交,在MySQL建立
(user_id, competition_id)的唯一索引
- 支持文件上传(PDF/PPT/ZIP),使用Spring的
-
评审模块
- 采用分布式锁防止评委重复打分,Redisson实现示例:
java复制RLock lock = redissonClient.getLock("judge:"+workId); try { if(lock.tryLock(1, 10, TimeUnit.SECONDS)) { // 评分逻辑 } } finally { lock.unlock(); }
3. 关键技术实现细节
3.1 Vue3前端工程化
-
项目初始化
bash复制
npm init vue@latest选择Pinia(状态管理)、Router(路由)、ESLint(代码检查)
-
axios封装示例
javascript复制const service = axios.create({ baseURL: import.meta.env.VITE_APP_BASE_API, timeout: 5000 }); service.interceptors.response.use( response => { const res = response.data; if (res.code !== 200) { ElMessage.error(res.msg || 'Error'); return Promise.reject(new Error(res.msg || 'Error')); } return res; } ); -
典型页面逻辑(评委打分页)
vue复制<script setup> const score = ref(0); const submit = async () => { await api.judgeWork(workId.value, score.value); }; </script>
3.2 SpringBoot后端优化
-
MyBatis动态SQL
xml复制<select id="selectWorks" resultType="WorkVO"> SELECT * FROM competition_work <where> <if test="competitionId != null"> AND competition_id = #{competitionId} </if> <if test="status != null"> AND status = #{status} </if> </where> ORDER BY create_time DESC </select> -
分页查询优化
- 使用PageHelper插件:
java复制PageHelper.startPage(pageNum, pageSize); List<Work> works = workMapper.selectWorks(params); return new PageInfo<>(works);- 必须配合
pagehelper-spring-boot-starter依赖
-
事务管理
java复制@Transactional(rollbackFor = Exception.class) public void judgeWork(Long workId, Integer score) { // 1. 更新作品分数 // 2. 记录评分日志 // 3. 更新竞赛统计 }
4. 部署与性能调优
4.1 生产环境部署
-
前端部署
bash复制
npm run build生成的dist目录包含:
index.html- 入口文件assets/- 静态资源- 需要配置Nginx重定向规则:
nginx复制location / { try_files $uri $uri/ /index.html; } -
后端打包
bash复制
mvn clean package -DskipTests生成的jar包可通过
java -jar启动,推荐使用systemd管理:ini复制[Unit] Description=Competition System After=syslog.target [Service] ExecStart=/usr/bin/java -jar /opt/app/competition-system.jar User=appuser [Install] WantedBy=multi-user.target
4.2 MySQL性能优化
-
索引设计
- 评委查询评分记录需要
(judge_id, work_id)联合索引 - 作品列表页需要
(competition_id, status)索引
- 评委查询评分记录需要
-
慢查询监控
sql复制-- 在my.cnf中配置 slow_query_log = 1 slow_query_log_file = /var/log/mysql/mysql-slow.log long_query_time = 1 -
连接池配置
yaml复制spring: datasource: hikari: maximum-pool-size: 20 connection-timeout: 30000 idle-timeout: 600000 max-lifetime: 1800000
4.3 常见问题解决方案
-
Vue3生产环境路由404
- 问题:刷新页面返回404
- 解决:Nginx需配置try_files规则(见4.1节)
-
MyBatis查询结果为空
- 检查点:
- 是否开启
@MapperScan - XML中namespace是否对应接口全限定名
- 字段名是否与resultMap匹配
- 是否开启
- 检查点:
-
SpringBoot跨域失效
- 可能原因:
- 使用了
@CrossOrigin又配置了全局CORS,导致冲突 - 网关层(如Nginx)未正确转发OPTIONS请求
- 使用了
- 可能原因:
-
并发评分数据错乱
- 必须使用
@Transactional+分布式锁双重保障 - 可考虑使用MySQL的
SELECT ... FOR UPDATE
- 必须使用
-
文件上传大小限制
SpringBoot默认限制1MB,需要调整:yaml复制spring: servlet: multipart: max-file-size: 10MB max-request-size: 10MB
5. 扩展功能与二次开发建议
-
微信小程序接入
- 使用uni-app编译到微信平台
- 后端需增加
/api/wx/login接口处理微信授权
-
评审数据可视化
- 集成ECharts展示评分分布
- 示例代码:
javascript复制const chart = echarts.init(dom); chart.setOption({ tooltip: {}, xAxis: { type: 'category' }, yAxis: {}, series: [{ type: 'bar' }] }); -
防作弊检测
- 文本相似度:使用HanLP(需引入依赖)
java复制List<String> keywordList = HanLP.extractKeyword(content, 20);- 图片相似度:OpenCV的pHash算法
-
多级评审流程
- 在competition表增加
review_phase字段 - 使用工作流引擎(如Flowable)管理状态流转
- 在competition表增加
-
国际化支持
Vue3使用i18n:javascript复制const i18n = createI18n({ locale: 'zh', messages: { zh: { submit: '提交' } } });
实际开发中发现,评委端在Edge浏览器下偶尔会出现控制台报错ResizeObserver loop limit exceeded,这是浏览器自身问题,可通过以下方式缓解:
javascript复制const observer = new ResizeObserver(entries => {
window.requestAnimationFrame(() => {
if (!Array.isArray(entries) || !entries.length) return;
// 业务逻辑
});
});
对于需要处理大量作品数据的场景,建议:
- 导出Excel使用阿里巴巴的EasyExcel(避免OOM)
- 分页查询必须配合
COUNT(*) OVER()实现高效总数统计 - 对于千万级数据,考虑使用Elasticsearch加速检索
