1. 项目概述
作为一名长期从事高校信息化建设的开发者,我深知大学生竞赛管理工作的痛点。传统的人工管理方式不仅效率低下,而且容易出错,特别是在报名高峰期,老师们常常被各种表格和邮件淹没。基于这个背景,我决定开发一套基于Spring Boot的大学生竞赛管理系统,帮助高校实现竞赛管理的数字化和智能化。
这个系统采用了前后端分离的架构,后端使用Spring Boot框架,前端采用Vue.js,数据库选用MySQL。系统主要面向三类用户:学生、教师和管理员,为每类用户提供了针对性的功能模块。经过三个月的开发和测试,系统已经在我校试运行,效果显著提升了竞赛管理效率。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 系统架构设计
2.1 技术选型
在技术选型上,我主要考虑了以下几个因素:
- 开发效率:Spring Boot的自动配置和约定优于配置的特性可以大大减少开发时间
- 性能需求:系统需要支持高并发访问,特别是在报名和成绩查询高峰期
- 维护成本:选择主流技术栈可以降低后期维护难度
最终确定的技术栈如下:
- 后端:Spring Boot 2.7 + Spring Security + JWT
- 前端:Vue 3 + Element Plus
- 数据库:MySQL 8.0
- 缓存:Redis
- 部署:Docker + Nginx
2.2 系统架构图
系统采用典型的三层架构:
code复制┌───────────────────────────────────────┐
│ 客户端层 │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐│
│ │ Web │ │ Mobile │ │ Admin ││
│ └─────────┘ └─────────┘ └─────────┘│
└───────────────────────────────────────┘
↑↓
┌───────────────────────────────────────┐
│ 应用层 │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐│
│ │ API网关 │ │业务逻辑层│ │安全认证层││
│ └─────────┘ └─────────┘ └─────────┘│
└───────────────────────────────────────┘
↑↓
┌───────────────────────────────────────┐
│ 数据层 │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐│
│ │ MySQL │ │ Redis │ │ 文件存储││
│ └─────────┘ └─────────┘ └─────────┘│
└───────────────────────────────────────┘
3. 核心功能实现
3.1 学生模块实现
3.1.1 注册登录功能
学生注册采用了手机号+验证码的方式,后端使用Spring Security进行密码加密存储。关键代码如下:
java复制@PostMapping("/register")
public Result register(@RequestBody UserRegisterDTO dto) {
// 验证验证码
if(!verifyCodeService.checkCode(dto.getPhone(), dto.getCode())) {
return Result.error("验证码错误");
}
// 密码加密
String encodedPassword = passwordEncoder.encode(dto.getPassword());
// 创建用户
User user = new User();
user.setUsername(dto.getPhone());
user.setPassword(encodedPassword);
user.setPhone(dto.getPhone());
user.setRole(Role.STUDENT);
userService.save(user);
return Result.success();
}
3.1.2 竞赛报名功能
报名功能需要考虑并发控制,我使用了数据库乐观锁来解决这个问题:
java复制@Transactional
public boolean signUpCompetition(Long competitionId, Long studentId) {
// 检查报名人数是否已满
Competition competition = competitionRepository.findById(competitionId)
.orElseThrow(() -> new BusinessException("竞赛不存在"));
if(competition.getCurrentCount() >= competition.getMaxCount()) {
throw new BusinessException("报名人数已满");
}
// 检查是否已报名
if(registrationRepository.existsByCompetitionIdAndStudentId(competitionId, studentId)) {
throw new BusinessException("已报名该竞赛");
}
// 创建报名记录
Registration registration = new Registration();
registration.setCompetitionId(competitionId);
registration.setStudentId(studentId);
registration.setStatus(RegistrationStatus.PENDING);
registrationRepository.save(registration);
// 更新竞赛报名人数(使用乐观锁)
int updated = competitionRepository.incrementCurrentCount(competitionId, competition.getVersion());
if(updated == 0) {
throw new OptimisticLockException("报名冲突,请重试");
}
return true;
}
3.2 教师模块实现
3.2.1 题库管理功能
教师可以上传竞赛题库,系统支持多种题型(单选、多选、填空、编程题等)。上传的文件会先保存到临时目录,然后解析入库:
java复制public void importQuestions(Long teacherId, MultipartFile file) {
// 检查文件类型
String filename = file.getOriginalFilename();
if(!filename.endsWith(".xlsx") && !filename.endsWith(".xls")) {
throw new BusinessException("只支持Excel文件");
}
// 保存临时文件
Path tempFile = Files.createTempFile("question_import_", ".xlsx");
file.transferTo(tempFile);
// 解析Excel
List<Question> questions = excelParser.parseQuestions(tempFile);
// 批量保存
questionRepository.saveAll(questions.stream()
.peek(q -> q.setCreatorId(teacherId))
.collect(Collectors.toList()));
// 删除临时文件
Files.deleteIfExists(tempFile);
}
3.2.2 评分功能
评分功能需要考虑评分标准和权重,我设计了一个灵活的评分规则引擎:
java复制public class ScoreEngine {
private List<ScoreRule> rules;
public ScoreResult calculate(Submission submission, List<ScoreRule> customRules) {
ScoreResult result = new ScoreResult();
// 应用系统默认规则
rules.forEach(rule -> rule.apply(submission, result));
// 应用竞赛自定义规则
if(customRules != null) {
customRules.forEach(rule -> rule.apply(submission, result));
}
return result;
}
}
3.3 管理员模块实现
3.3.1 权限管理
系统使用RBAC模型进行权限控制,管理员可以灵活配置角色和权限:
java复制@Entity
@Table(name = "sys_role")
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String code;
@ManyToMany
@JoinTable(name = "sys_role_permission",
joinColumns = @JoinColumn(name = "role_id"),
inverseJoinColumns = @JoinColumn(name = "permission_id"))
private Set<Permission> permissions = new HashSet<>();
}
3.3.2 数据统计功能
管理员可以查看各类统计数据,我使用了Spring Data JPA的聚合查询:
java复制public interface CompetitionStatsRepository extends JpaRepository<Competition, Long> {
@Query("SELECT new com.example.dto.CompetitionStatsDTO(" +
"c.type, COUNT(c), AVG(c.participantCount), MAX(c.participantCount)) " +
"FROM Competition c GROUP BY c.type")
List<CompetitionStatsDTO> statsByType();
@Query("SELECT new com.example.dto.MonthlyStatsDTO(" +
"YEAR(c.startTime), MONTH(c.startTime), COUNT(c)) " +
"FROM Competition c GROUP BY YEAR(c.startTime), MONTH(c.startTime)")
List<MonthlyStatsDTO> statsByMonth();
}
4. 数据库设计
4.1 核心表结构
系统共设计了28张表,以下是部分核心表的设计:
用户相关表
sql复制CREATE TABLE `user` (
`id` bigint NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL COMMENT '用户名',
`password` varchar(100) NOT NULL COMMENT '密码',
`phone` varchar(20) COMMENT '手机号',
`email` varchar(100) COMMENT '邮箱',
`avatar` varchar(255) COMMENT '头像',
`status` tinyint NOT NULL DEFAULT '1' COMMENT '状态:0-禁用,1-正常',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_username` (`username`),
UNIQUE KEY `idx_phone` (`phone`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
竞赛信息表
sql复制CREATE TABLE `competition` (
`id` bigint NOT NULL AUTO_INCREMENT,
`title` varchar(100) NOT NULL COMMENT '竞赛名称',
`type` varchar(50) NOT NULL COMMENT '竞赛类型',
`level` varchar(20) COMMENT '竞赛级别',
`start_time` datetime NOT NULL COMMENT '开始时间',
`end_time` datetime NOT NULL COMMENT '结束时间',
`max_participants` int DEFAULT 0 COMMENT '最大参与人数',
`current_participants` int DEFAULT 0 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,
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`version` int NOT NULL DEFAULT '0' COMMENT '乐观锁版本号',
PRIMARY KEY (`id`),
KEY `idx_type` (`type`),
KEY `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
5. 系统部署
5.1 环境准备
系统需要以下运行环境:
- JDK 11+
- MySQL 8.0+
- Redis 6.0+
- Node.js 14+
5.2 部署步骤
- 数据库初始化:
bash复制mysql -u root -p < db/schema.sql
mysql -u root -p < db/data.sql
- 后端服务启动:
bash复制# 打包
mvn clean package -DskipTests
# 运行
java -jar target/competition-system-1.0.0.jar
- 前端服务启动:
bash复制# 安装依赖
npm install
# 开发模式运行
npm run serve
# 生产环境构建
npm run build
- Nginx配置:
nginx复制server {
listen 80;
server_name competition.example.com;
location / {
root /path/to/frontend/dist;
try_files $uri $uri/ /index.html;
}
location /api/ {
proxy_pass http://localhost:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
6. 开发经验分享
6.1 性能优化
在实际开发中,我遇到了几个性能瓶颈并找到了解决方案:
- 竞赛列表查询慢:当竞赛数量达到上千条时,列表查询明显变慢。通过添加适当的索引和引入缓存解决了这个问题:
java复制@Cacheable(value = "competition", key = "#type + '_' + #status")
public List<Competition> listByTypeAndStatus(String type, Integer status) {
return competitionRepository.findByTypeAndStatus(type, status);
}
- 报名高峰期系统响应慢:在报名开始后的几分钟内,系统会收到大量请求。我采用了以下措施:
- 使用Redis实现分布式锁防止超报
- 对报名接口进行限流
- 将部分非实时操作异步化处理
6.2 安全实践
系统安全是重中之重,我采取了以下安全措施:
- 接口防刷:对关键接口(如报名、评分)进行频率限制:
java复制@RateLimiter(value = 10, key = "#studentId")
@PostMapping("/signup")
public Result signUp(@RequestParam Long competitionId,
@RequestParam Long studentId) {
// 报名逻辑
}
-
SQL注入防护:全程使用JPA或MyBatis的参数化查询,避免拼接SQL。
-
XSS防护:前端使用vue-sanitize对用户输入进行过滤,后端也对敏感字符进行转义。
6.3 测试策略
为了保证系统质量,我建立了完整的测试体系:
-
单元测试:使用JUnit + Mockito对核心业务逻辑进行测试,覆盖率达到了85%以上。
-
集成测试:使用TestContainers进行数据库相关的集成测试。
-
压力测试:使用JMeter模拟高并发场景,确保系统稳定性。
一个典型的测试用例:
java复制@Test
@DisplayName("测试并发报名场景")
void testConcurrentSignUp() throws InterruptedException {
int threadCount = 100;
ExecutorService executor = Executors.newFixedThreadPool(threadCount);
CountDownLatch latch = new CountDownLatch(threadCount);
Long competitionId = createTestCompetition(100); // 创建一个限额100人的竞赛
for (int i = 0; i < threadCount; i++) {
Long studentId = createTestStudent();
executor.execute(() -> {
try {
competitionService.signUp(competitionId, studentId);
} finally {
latch.countDown();
}
});
}
latch.await();
Competition comp = competitionRepository.findById(competitionId).get();
assertEquals(100, comp.getCurrentParticipants()); // 确保没有超报
}
7. 项目总结
这个项目的开发过程让我收获颇丰。从最初的调研到最终上线,历时三个月,期间遇到了各种技术挑战。最大的收获是学会了如何在有限时间内做出技术决策,平衡功能需求和技术债务。
系统目前已经在我校运行了半年,支撑了12场大型竞赛的报名和管理工作,注册用户超过5000人。从使用反馈来看,主要优点有:
- 报名流程从原来的3天缩短到1小时内完成
- 评分工作效率提升了60%
- 数据统计的准确性大大提高
未来计划增加的功能包括:
- 移动端APP开发
- 竞赛智能推荐功能
- 更强大的数据分析看板
这个项目让我深刻体会到,一个好的管理系统不仅要技术过硬,更要真正理解用户需求,解决实际问题。在开发过程中,我多次与教务处老师和学生代表沟通,不断调整功能设计,这也是项目最终能成功落地的重要原因。
