1. 项目背景与核心价值
这个全栈项目是典型的"前后端分离+关系型数据库"技术组合的实战案例。SpringBoot作为Java生态中最主流的后端框架,以其约定优于配置的理念大幅降低了项目启动门槛;Vue作为渐进式前端框架,在管理系统中展现出了极高的开发效率;MySQL则是中小型系统数据库选型的默认选择。三者组合形成了一个技术成熟度极高、社区支持完善的全栈解决方案。
我在实际开发中发现,学科竞赛管理系统这类垂直领域的管理平台有几个典型痛点:多角色权限控制复杂(学生、教师、管理员)、赛事流程状态多变、作品提交与评审环节耦合度高。这套源码的价值在于,它用合理的架构设计解决了这些业务难题,同时保持了代码的整洁性和扩展性。
2. 技术栈深度解析
2.1 SpringBoot后端设计要点
采用2.7.x稳定版本,主要依赖包括:
- spring-boot-starter-web(RESTful接口)
- spring-boot-starter-data-jpa(数据持久化)
- spring-boot-starter-security(认证授权)
- lombok(简化POJO)
核心配置示例:
java复制@SpringBootApplication
@EnableJpaAuditing
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class BackendApplication {
public static void main(String[] args) {
SpringApplication.run(BackendApplication.class, args);
}
}
特别值得关注的是权限控制方案。系统采用RBAC模型,通过JWT实现无状态认证。我在实际部署时发现,需要特别注意token的刷新机制设计——原生的Spring Security配置会导致token过期后用户体验骤降。建议在SecurityConfig中增加以下配置:
java复制@Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(new JwtAuthenticationFilter(),
UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
2.2 Vue前端架构亮点
前端采用Vue 3 + Element Plus组合,项目结构清晰:
code复制src/
├── api/ # 接口封装
├── assets/ # 静态资源
├── components/ # 公共组件
├── router/ # 路由配置
├── store/ # Vuex状态管理
├── utils/ # 工具函数
└── views/ # 页面组件
路由守卫的实现是权限控制的关键点。在router/index.js中,通过全局前置守卫实现动态路由:
javascript复制router.beforeEach((to, from, next) => {
const hasToken = localStorage.getItem('token')
if (to.matched.some(record => record.meta.requiresAuth)) {
if (!hasToken) {
next('/login')
} else {
if (to.matched.some(record => record.meta.roles)) {
const roles = store.getters.roles
if (checkRoles(to.meta.roles, roles)) {
next()
} else {
next('/403')
}
} else {
next()
}
}
} else {
next()
}
})
2.3 MySQL数据库设计
典型表结构包括:
- 用户表(user)
- 角色表(role)
- 竞赛表(competition)
- 参赛记录表(participation)
- 评审表(review)
特别注意竞赛状态机的设计:
sql复制CREATE TABLE `competition` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(100) NOT NULL,
`description` text,
`start_time` datetime NOT NULL,
`end_time` datetime NOT NULL,
`status` enum('DRAFT','PUBLISHED','REGISTERING','ONGOING','FINISHED') NOT NULL DEFAULT 'DRAFT',
`max_participants` int DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
3. 关键业务逻辑实现
3.1 竞赛报名流程
核心时序:
- 学生提交报名申请(含附件上传)
- 系统生成待审核记录
- 指导教师审批
- 管理员最终确认
后端采用状态模式处理流程流转:
java复制public interface CompetitionState {
void handleRegistration(Competition competition, Registration registration);
void cancelRegistration(Competition competition, Registration registration);
}
@Service
@RequiredArgsConstructor
public class DraftState implements CompetitionState {
private final RegistrationRepository registrationRepo;
@Override
@Transactional
public void handleRegistration(Competition competition, Registration registration) {
registration.setStatus(RegistrationStatus.PENDING);
registrationRepo.save(registration);
// 发送通知给指导老师
notificationService.sendToTeacher(...);
}
}
3.2 作品评审模块
采用策略模式支持多维度评分:
java复制public interface ScoringStrategy {
ScoreResult calculate(Submission submission, ReviewCriteria criteria);
}
@Service
@Qualifier("weightedScoring")
public class WeightedScoringStrategy implements ScoringStrategy {
@Override
public ScoreResult calculate(Submission submission, ReviewCriteria criteria) {
double total = criteria.getItems().stream()
.mapToDouble(item -> {
Double score = submission.getScores().get(item.getId());
return score != null ? score * item.getWeight() : 0;
}).sum();
return new ScoreResult(total, generateComment(submission));
}
}
前端评分组件关键实现:
vue复制<template>
<el-form-item
v-for="item in criteria"
:key="item.id"
:label="item.name"
:prop="'scores.' + item.id">
<el-rate
v-model="form.scores[item.id]"
:max="item.maxScore"
show-score
:score-template="{value}分">
</el-rate>
</el-form-item>
</template>
4. 部署与运维实践
4.1 开发环境搭建
后端推荐使用IntelliJ IDEA,需配置:
- Lombok插件
- Spring Boot DevTools
- Database工具连接MySQL
前端环境准备:
bash复制# 安装依赖
npm install
# 启动开发服务器
npm run dev
4.2 生产部署方案
推荐使用Docker Compose编排:
yaml复制version: '3'
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: competition
volumes:
- mysql_data:/var/lib/mysql
backend:
build: ./backend
ports:
- "8080:8080"
depends_on:
- mysql
frontend:
build: ./frontend
ports:
- "80:80"
Nginx配置示例(前端静态资源+API反向代理):
nginx复制server {
listen 80;
server_name yourdomain.com;
location / {
root /usr/share/nginx/html;
try_files $uri $uri/ /index.html;
}
location /api {
proxy_pass http://backend:8080;
proxy_set_header Host $host;
}
}
4.3 常见问题排查
- 跨域问题:确保后端配置了正确的CORS
java复制@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
- 文件上传大小限制:
yaml复制spring:
servlet:
multipart:
max-file-size: 10MB
max-request-size: 10MB
- Vue生产环境API地址配置:
javascript复制// .env.production
VUE_APP_API_BASE_URL = '/api'
5. 扩展与二次开发建议
5.1 功能增强方向
- 微信小程序接入:通过uni-app改造前端
- 自动化评审:集成NLP算法对文本类作品初筛
- 可视化看板:使用ECharts展示赛事数据
5.2 性能优化方案
- 缓存策略:
java复制@Cacheable(value = "competitions", key = "#status")
public List<Competition> findByStatus(String status) {
return repository.findByStatus(status);
}
- 数据库索引优化:
sql复制ALTER TABLE participation
ADD INDEX idx_comp_user (competition_id, user_id);
- 前端懒加载:
javascript复制const JudgeView = () => import('./views/JudgeView.vue')
5.3 监控与日志
推荐集成Spring Boot Actuator:
yaml复制management:
endpoints:
web:
exposure:
include: health,info,metrics
前端错误监控(Sentry示例):
javascript复制import * as Sentry from '@sentry/vue'
Sentry.init({
dsn: 'your_dsn',
integrations: [new BrowserTracing()],
tracesSampleRate: 0.2
})
这套系统在实际教学管理中使用时,建议特别注意数据备份策略。我曾遇到过因服务器故障导致赛事数据丢失的情况,现在坚持采用"本地每日全量备份+实时binlog同步到OSS"的双保险方案。具体可通过以下crontab实现:
bash复制0 2 * * * mysqldump -uroot -p$PASSWORD competition | gzip > /backups/comp_$(date +\%Y\%m\%d).sql.gz
