1. 项目背景与核心价值
这个双创竞赛管理系统是专门为高校大学生创新创业竞赛设计的全流程数字化解决方案。我在实际开发过程中发现,传统竞赛申报存在几个痛点:纸质材料容易丢失、评审进度不透明、路演安排混乱。而市面上通用OA系统又无法满足竞赛特有的需求,比如项目分组评审、路演时间预约、评委打分统计等场景。
系统采用前后端分离架构,后端基于SpringBoot 2.7.3(兼容JDK8和JDK17),前端使用Vue 3 + Element Plus。这种技术组合在大学生毕设中非常典型,既能体现完整的技术栈,又不会过度复杂。特别适合计算机相关专业学生作为综合实践项目,涵盖了用户权限管理、文件上传、数据可视化等常见业务场景。
提示:项目源码中已包含完整的RBAC权限控制模块,支持五种角色(学生、导师、院系管理员、校级管理员、评委)的精细化权限分配,这是同类毕设项目中经常被忽略的关键点。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术架构详解
2.1 后端技术栈设计
SpringBoot框架选型经过多方考量:
- 相比原生Spring MVC减少了70%以上的XML配置
- 内嵌Tomcat服务器简化部署
- 自动配置机制让新手也能快速上手
- 丰富的Starter依赖(项目中使用spring-boot-starter-web、spring-boot-starter-data-jpa等)
数据库选用MySQL 8.0,主要考虑到:
- 高校实验室环境普遍支持MySQL
- 完善的文档和社区支持
- 与JPA的Hibernate实现配合良好
java复制// 典型的数据层接口示例
public interface ProjectRepository extends JpaRepository<CompetitionProject, Long> {
@Query("SELECT p FROM CompetitionProject p WHERE p.college = :college AND p.status = 'SUBMITTED'")
Page<CompetitionProject> findSubmittedByCollege(
@Param("college") String college,
Pageable pageable);
}
2.2 前端技术选型
Vue 3组合式API相比Options API更适合复杂业务逻辑:
- 更好的TypeScript支持
- 逻辑关注点更集中
- 代码复用性更高
Element Plus组件库的选择依据:
- 中文文档完善
- 预制表单验证规则
- 丰富的表格功能(支持分页、排序、筛选)
javascript复制// 典型的路由配置
const routes = [
{
path: '/project/:id',
component: ProjectDetail,
meta: { requiresAuth: true, roles: ['STUDENT', 'TEACHER'] }
}
]
3. 核心功能实现
3.1 项目申报流程
申报流程采用状态机设计模式,定义6种状态:
- DRAFT(草稿)
- SUBMITTED(已提交)
- FIRST_REVIEW(初审中)
- FINAL_REVIEW(终审中)
- APPROVED(已立项)
- REJECTED(已驳回)
状态转换通过策略模式实现,避免复杂的if-else嵌套:
java复制public interface StateHandler {
void handle(CompetitionProject project, String operation);
}
@Service
@RequiredArgsConstructor
public class SubmitHandler implements StateHandler {
private final ProjectRepository repository;
@Override
@Transactional
public void handle(CompetitionProject project, String operation) {
if (!"SUBMIT".equals(operation)) return;
project.setStatus(ProjectStatus.SUBMITTED);
project.setSubmitTime(LocalDateTime.now());
repository.save(project);
// 触发消息通知
notificationService.notifyMentor(project);
}
}
3.2 路演管理模块
路演预约采用时间槽设计:
- 将每天划分为8个时段(09:00-12:00,14:00-17:00)
- 每个时段最多安排5个项目
- 使用乐观锁解决并发预约问题
sql复制-- 路演时间表结构
CREATE TABLE `roadshow_slot` (
`id` bigint NOT NULL AUTO_INCREMENT,
`start_time` datetime NOT NULL,
`end_time` datetime NOT NULL,
`location` varchar(50) NOT NULL,
`max_projects` int DEFAULT '5',
`version` int DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
4. 数据库设计与优化
4.1 核心表结构
主要实体关系采用三范式设计:
- 用户表(sys_user)
- 项目表(competition_project)
- 评审表(project_review)
- 路演表(roadshow_schedule)
sql复制-- 项目表包含的典型字段
CREATE TABLE `competition_project` (
`id` bigint NOT NULL AUTO_INCREMENT,
`project_name` varchar(100) NOT NULL,
`category` enum('INNOVATION','STARTUP','SOCIAL') NOT NULL,
`abstract` text,
`attachment_url` varchar(255),
`status` varchar(20) NOT NULL DEFAULT 'DRAFT',
`submit_time` datetime,
`creator_id` bigint NOT NULL,
`college` varchar(50) NOT NULL,
PRIMARY KEY (`id`),
FOREIGN KEY (`creator_id`) REFERENCES `sys_user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
4.2 查询性能优化
针对常见慢查询的解决方案:
- 项目列表分页:使用JPA的Pageable接口
- 评委打分统计:建立物化视图
- 文件检索:添加FULLTEXT索引
java复制// 使用Specification实现复杂查询
public class ProjectSpecs {
public static Specification<CompetitionProject> statusEquals(ProjectStatus status) {
return (root, query, cb) -> cb.equal(root.get("status"), status);
}
public static Specification<CompetitionProject> collegeEquals(String college) {
return (root, query, cb) -> cb.equal(root.get("college"), college);
}
}
// 使用示例
repository.findAll(
where(statusEquals(ProjectStatus.SUBMITTED))
.and(collegeEquals("计算机学院")),
PageRequest.of(0, 10, Sort.by("submitTime").descending())
);
5. 系统安全防护
5.1 常见安全措施
- 密码加密:BCryptPasswordEncoder
- XSS防护:Jackson的HTML转义
- CSRF防护:Spring Security默认启用
- SQL注入:使用JPA参数化查询
java复制@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final UserDetailsService userDetailsService;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/api/public/**").permitAll()
.antMatchers("/api/student/**").hasRole("STUDENT")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/dashboard")
.and()
.rememberMe()
.key("uniqueAndSecret")
.tokenValiditySeconds(86400);
}
}
5.2 文件上传安全
针对毕设常见的文件上传漏洞防护:
- 文件类型白名单校验
- 病毒扫描(集成ClamAV)
- 文件重命名(UUID + 后缀)
- 独立文件服务器隔离
java复制@RestController
@RequestMapping("/api/upload")
public class FileUploadController {
private static final Set<String> ALLOWED_TYPES = Set.of(
"application/pdf",
"application/msword",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document"
);
@PostMapping
public ResponseEntity<String> upload(
@RequestParam("file") MultipartFile file,
@CurrentUser User user) {
if (!ALLOWED_TYPES.contains(file.getContentType())) {
throw new IllegalFileTypeException();
}
String newName = UUID.randomUUID() +
FilenameUtils.getExtension(file.getOriginalFilename());
Path path = Paths.get("/opt/uploads", newName);
Files.copy(file.getInputStream(), path, StandardCopyOption.REPLACE_EXISTING);
return ResponseEntity.ok("/download/" + newName);
}
}
6. 项目部署实践
6.1 开发环境配置
推荐使用Docker Compose一键启动依赖服务:
yaml复制version: '3'
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: competition
ports:
- "3306:3306"
volumes:
- mysql_data:/var/lib/mysql
redis:
image: redis:6
ports:
- "6379:6379"
volumes:
mysql_data:
6.2 生产环境建议
高校实验室常见部署方案:
- 物理服务器:2核4G配置足够支持500并发
- 使用Nginx作为反向代理
- 开启Gzip压缩减少前端资源体积
- 配置HTTPS证书(Let's Encrypt免费)
nginx复制server {
listen 443 ssl;
server_name competition.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
location /api {
proxy_pass http://localhost:8080;
proxy_set_header Host $host;
}
location / {
root /var/www/html;
try_files $uri $uri/ /index.html;
}
}
7. 毕设开发建议
7.1 代码规范要点
- 遵循阿里巴巴Java开发手册
- 前端使用ESLint + Prettier
- 提交信息采用Conventional Commits
- 接口文档使用Swagger UI
java复制@RestController
@RequestMapping("/api/projects")
@Tag(name = "项目管理", description = "双创项目申报接口")
public class ProjectController {
@Operation(summary = "获取项目详情")
@GetMapping("/{id}")
public ResponseEntity<ProjectVO> getProject(
@Parameter(description = "项目ID") @PathVariable Long id) {
// ...
}
}
7.2 答辩常见问题
根据指导经验,评委常关注:
- 如何保证评审公平性?(回答:采用双盲评审机制)
- 系统最大支持多少并发?(回答:JMeter压测结果)
- 与同类系统相比的创新点?(回答:定制化的路演预约算法)
注意:一定要准备系统演示的备用方案,比如录屏或静态页面,防止现场网络问题
我在实际开发中遇到最棘手的问题是路演时间冲突检测,最终解决方案是使用数据库的排他锁结合Redis缓存:
java复制public RoadshowSlot bookSlot(Long slotId, Long projectId) {
String lockKey = "roadshow:lock:" + slotId;
// 获取分布式锁
boolean locked = redisTemplate.opsForValue()
.setIfAbsent(lockKey, "1", Duration.ofSeconds(30));
if (!locked) {
throw new ConcurrentBookingException();
}
try {
RoadshowSlot slot = slotRepository.findById(slotId)
.orElseThrow(NotFoundException::new);
if (slot.getBookedCount() >= slot.getMaxProjects()) {
throw new SlotFullException();
}
slot.setBookedCount(slot.getBookedCount() + 1);
return slotRepository.save(slot);
} finally {
redisTemplate.delete(lockKey);
}
}
