1. 项目概述:学科竞赛管理系统的全栈实现
这个基于SpringBoot+Vue的学科竞赛管理系统,本质上是一个典型的"前后端分离+关系型数据库"的Web应用开发案例。我在实际开发这类系统时发现,它完美融合了Java生态的稳定性和前端框架的灵活性——SpringBoot提供RESTful API接口,Vue负责动态交互界面,MySQL作为数据持久层,MyBatis则充当两者间的桥梁。
这类系统通常需要处理几个核心场景:竞赛信息发布、团队报名审核、作品提交评审、成绩统计分析等。以我参与过的高校竞赛平台升级项目为例,系统日均要处理300+团队的报名数据,这对事务管理和并发控制提出了明确要求。采用SpringBoot的自动配置特性,可以快速集成MyBatis-Plus实现高效CRUD,配合Vue的组件化开发,能在两周内搭建出基础管理模块。
提示:新手常犯的错误是直接照搬开源项目结构。实际开发中应根据业务模块划分package,比如将"报名管理"和"评审管理"作为独立子模块,避免后期功能膨胀导致的代码混乱。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术栈选型与架构设计
2.1 后端技术组合解析
SpringBoot 2.7.x + MyBatis-Plus的组合是经过多个项目验证的稳定方案。相较于原生MyBatis,MyBatis-Plus的Lambda查询构建器能减少30%以上的样板代码。我在处理多表关联查询时,通常会这样设计Mapper:
java复制// 竞赛与报名团队的关联查询示例
public interface CompetitionMapper extends BaseMapper<Competition> {
@Select("SELECT c.*, t.team_name FROM competition c " +
"LEFT JOIN team t ON c.id = t.comp_id " +
"WHERE c.status = #{status}")
List<CompetitionTeamVO> selectWithTeams(@Param("status") Integer status);
}
数据库选择MySQL 8.0而非5.7版本,主要看中其CTE(公共表表达式)和窗口函数特性。比如统计各学院参赛人数排名时:
sql复制WITH school_stats AS (
SELECT
school_id,
COUNT(*) AS participant_count,
RANK() OVER(ORDER BY COUNT(*) DESC) AS rank_num
FROM registration
GROUP BY school_id
)
SELECT * FROM school_stats WHERE rank_num <= 5;
2.2 前端技术方案设计
Vue 3的组合式API相比Options API更适合复杂管理界面开发。我在评审模块中采用Pinia进行状态管理,典型store结构如下:
javascript复制// stores/review.js
export const useReviewStore = defineStore('review', () => {
const criteria = ref([]) // 评审标准
const currentWorks = ref([]) // 待评审作品
const loadCriteria = async (compId) => {
const res = await api.get(`/review/criteria/${compId}`)
criteria.value = res.data
}
return { criteria, currentWorks, loadCriteria }
})
对于文件上传这类高频操作,推荐使用axios的拦截器统一处理进度显示:
javascript复制// utils/upload.js
const instance = axios.create({
baseURL: '/api/upload',
timeout: 60000
})
instance.interceptors.request.use(config => {
config.onUploadProgress = progressEvent => {
const percent = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
)
console.log(`上传进度: ${percent}%`)
}
return config
})
3. 核心功能模块实现
3.1 竞赛流程状态机设计
学科竞赛通常包含"筹备→报名→初赛→复赛→公示"等状态变迁。我在项目中采用状态模式实现流程控制:
java复制// 状态接口定义
public interface CompetitionState {
void handlePublish(Competition competition);
void handleCloseRegistration(Competition competition);
// 其他状态方法...
}
// 具体状态实现
public class RegistrationState implements CompetitionState {
@Override
public void handleCloseRegistration(Competition comp) {
if(comp.getTeamCount() < comp.getMinTeams()) {
throw new IllegalStateException("参赛团队不足");
}
comp.setState(new PreliminaryState());
comp.setStatus(COMP_PRELIMINARY);
}
}
配合Spring的@Transactional注解,确保状态变更与数据库操作的事务性:
java复制@Transactional
public void closeRegistration(Long compId) {
Competition comp = repository.findById(compId)
.orElseThrow(() -> new ResourceNotFoundException("竞赛不存在"));
currentStateRegistry.getState(comp.getStatus())
.handleCloseRegistration(comp);
repository.save(comp);
}
3.2 评审系统的分布式锁应用
当多个评委同时评审同一作品时,需要防止分数覆盖。我们采用Redisson实现分布式锁:
java复制public class ReviewService {
private final RedissonClient redisson;
public void submitScore(ReviewDTO dto) {
RLock lock = redisson.getLock("review:" + dto.getWorkId());
try {
if(lock.tryLock(5, 10, TimeUnit.SECONDS)) {
// 检查是否已评分
if(reviewRepo.existsByWorkAndJudge(dto.getWorkId(), dto.getJudgeId())) {
throw new BusinessException("请勿重复评分");
}
// 保存评分
reviewRepo.save(convertToEntity(dto));
// 更新平均分
updateAverageScore(dto.getWorkId());
}
} finally {
lock.unlock();
}
}
}
4. 性能优化实战技巧
4.1 报名数据批量导入优化
处理Excel批量报名时,传统逐条插入方式性能极差。我们采用MyBatis的批量插入语法:
xml复制<insert id="batchInsert" useGeneratedKeys="true" keyProperty="id">
INSERT INTO registration
(comp_id, team_id, member_list, submit_time)
VALUES
<foreach collection="list" item="item" separator=",">
(#{item.compId}, #{item.teamId}, #{item.memberList}, NOW())
</foreach>
</insert>
配合Spring的@Async实现异步处理:
java复制@Async("importExecutor")
public CompletableFuture<Integer> asyncBatchImport(List<Registration> list) {
int count = mapper.batchInsert(list);
return CompletableFuture.completedFuture(count);
}
4.2 评审结果统计的缓存策略
使用Spring Cache注解实现自动缓存:
java复制@Cacheable(value = "resultStats", key = "#compId")
public ResultStatsVO getResultStats(Long compId) {
// 复杂统计计算...
return computeStats(compId);
}
@CacheEvict(value = "resultStats", key = "#compId")
public void publishResults(Long compId) {
// 发布结果逻辑...
}
5. 典型问题排查实录
5.1 文件上传大小限制问题
SpringBoot默认文件上传限制为1MB,需在application.yml中调整:
yaml复制spring:
servlet:
multipart:
max-file-size: 50MB
max-request-size: 100MB
同时Nginx也需要对应调整:
nginx复制client_max_body_size 100m;
5.2 Vue路由的404问题
部署到子路径时,需同步配置:
javascript复制const router = createRouter({
history: createWebHistory('/competition/'),
routes
})
并在打包时设置publicPath:
javascript复制// vue.config.js
module.exports = {
publicPath: process.env.NODE_ENV === 'production'
? '/competition/'
: '/'
}
6. 安全防护方案
6.1 接口防刷策略
采用Guava的RateLimiter实现简单限流:
java复制@Aspect
@Component
public class RateLimitAspect {
private final Map<String, RateLimiter> limiters = new ConcurrentHashMap<>();
@Around("@annotation(rateLimit)")
public Object limit(ProceedingJoinPoint pjp, RateLimit rateLimit) throws Throwable {
String key = rateLimit.value();
limiters.computeIfAbsent(key, k -> RateLimiter.create(rateLimit.permits()));
if(limiters.get(key).tryAcquire()) {
return pjp.proceed();
}
throw new ApiException("操作过于频繁");
}
}
6.2 SQL注入防护
MyBatis-Plus内置了SQL注入防护,但自定义SQL仍需注意:
xml复制<!-- 错误示范 -->
<select id="search">
SELECT * FROM works
WHERE title LIKE '%${keyword}%'
</select>
<!-- 正确做法 -->
<select id="search">
SELECT * FROM works
WHERE title LIKE CONCAT('%', #{keyword}, '%')
</select>
7. 部署与监控方案
7.1 多环境配置管理
使用Spring Profiles区分环境:
yaml复制# application-dev.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/comp_dev
username: devuser
password: dev123
# application-prod.yml
spring:
datasource:
url: jdbc:mysql://prod-db:3306/comp_prod
username: ${DB_USER}
password: ${DB_PASS}
7.2 Prometheus监控集成
添加依赖:
xml复制<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
配置端点:
yaml复制management:
endpoints:
web:
exposure:
include: health,info,prometheus
metrics:
tags:
application: competition-system
8. 项目演进建议
8.1 微服务化改造
当参赛规模突破万人时,可考虑拆分服务:
- 用户服务:处理认证授权
- 竞赛服务:核心业务流程
- 评审服务:独立部署保证稳定性
- 文件服务:集中管理作品文件
8.2 多端适配方案
基于当前技术栈可扩展:
- 微信小程序:复用后端API,Taro框架开发
- 管理端APP:Uniapp打包Android/iOS
- 大屏展示:Vue+ECharts数据可视化
在数据库设计阶段预留扩展字段是个好习惯。我通常在关键表添加json_extend字段:
sql复制ALTER TABLE competition ADD COLUMN
json_extend JSON COMMENT '扩展字段';
这样未来新增属性时,无需频繁修改表结构。对于已经投入使用的系统,这种设计能减少80%以上的ALTER TABLE操作
