1. 项目背景与需求分析
小学阶段图形化编程竞赛辅导网站的设计源于当前编程教育低龄化的趋势。Scratch等图形化编程工具已成为全球小学信息技术课程的主流选择,但针对竞赛辅导的专项平台仍存在空白。传统线下辅导受限于时间和空间,而现有在线平台又缺乏系统性训练体系。
这个SpringBoot项目需要解决三个核心痛点:
- 竞赛资源分散:学生需要在不同平台寻找练习题、视频教程和往届作品
- 学习进度不可视:教师难以跟踪每位学生的训练情况和薄弱环节
- 作品分享渠道缺失:优秀作品缺乏展示空间,无法形成良性竞争氛围
2. 技术架构设计
2.1 整体技术栈选型
采用SpringBoot 2.7 + MySQL 8.0 + Thymeleaf前后端不分离架构。选择考量:
- 教学场景下不需要复杂的前端框架,Thymeleaf模板引擎足够满足交互需求
- SpringBoot的快速开发特性适合课程设计周期
- 图形化编程作品需要存储代码文件和素材,MySQL的BLOB类型适合小文件存储
graph TD A[客户端] --> B[Nginx] B --> C[SpringBoot应用] C --> D[MySQL] C --> E[Redis缓存] D --> F[作品文件存储]2.2 核心功能模块
// 主要实体类关系示例 @Entity public class User { @Id @GeneratedValue private Long id; private String username; @Enumerated(EnumType.STRING) private UserType type; // STUDENT/TEACHER @OneToMany(mappedBy = "author") private List<ProgrammingProject> projects; } @Entity public class Contest { @Id @GeneratedValue private Long id; private String title; @Temporal(TemporalType.DATE) private Date deadline; @OneToMany private List<Problem> problems; }3. 关键实现细节
3.1 图形化代码存储方案
采用双重存储策略:
- 数据库存储JSON格式的块定义(便于快速检索)
- 文件系统存储完整.sb3项目文件(保持兼容性)
// 文件上传处理逻辑示例 @PostMapping("/upload") public String handleUpload(@RequestParam MultipartFile file) { String originalName = file.getOriginalFilename(); String extension = originalName.substring(originalName.lastIndexOf('.')); String storedName = UUID.randomUUID() + extension; Path dest = Paths.get("uploads/"+storedName); Files.copy(file.getInputStream(), dest, StandardCopyOption.REPLACE_EXISTING); Project project = new Project(); project.setFileName(storedName); projectRepository.save(project); return "redirect:/projects"; }3.2 实时评判系统
通过解析Scratch JSON实现自动化评判:
- 提取角色数量、代码块类型等基础指标
- 检测特定算法实现(如排序可视化)
- 运行预设测试用例验证功能完整性
# 评判脚本示例(实际用Java实现) def evaluate(project): score = 0 with zipfile.ZipFile(project) as zf: with zf.open('project.json') as f: data = json.load(f) # 检查是否使用列表 if any('data_listcontents' in block for block in data['blocks'].values()): score += 20 # 检查是否有循环结构 if any(block['opcode'] in ['control_repeat','control_forever'] for block in data['blocks'].values()): score += 30 return score4. 数据库设计优化
4.1 核心表结构
CREATE TABLE users ( id BIGINT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) UNIQUE NOT NULL, password CHAR(60) NOT NULL, -- BCrypt加密 avatar MEDIUMBLOB, role ENUM('STUDENT','TEACHER','ADMIN') NOT NULL ); CREATE TABLE projects ( id BIGINT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(100) NOT NULL, description TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, author_id BIGINT NOT NULL, contest_id BIGINT, file_path VARCHAR(255) NOT NULL, thumbnail MEDIUMBLOB, FOREIGN KEY (author_id) REFERENCES users(id), FOREIGN KEY (contest_id) REFERENCES contests(id) );4.2 性能优化措施
- 作品表垂直分表:将不常用的description字段分离
- 使用Redis缓存热门作品数据
- 为常用查询建立复合索引:
CREATE INDEX idx_project_search ON projects(title, created_at);5. 典型业务场景实现
5.1 作品展示页分页查询
@GetMapping("/gallery") public String gallery( @RequestParam(defaultValue = "1") int page, @RequestParam(defaultValue = "10") int size, Model model) { Pageable pageable = PageRequest.of(page-1, size, Sort.by("createdAt").descending()); Page<Project> projects = projectRepository.findAll(pageable); model.addAttribute("projects", projects); return "gallery"; }5.2 竞赛自动排名
@Scheduled(cron = "0 0 18 * * ?") // 每天18点更新 public void updateRankings() { List<Contest> activeContests = contestRepository .findByDeadlineAfter(LocalDate.now()); activeContests.forEach(contest -> { List<ProjectRanking> rankings = projectRepository .findByContestId(contest.getId()) .stream() .map(p -> new ProjectRanking(p, calculateScore(p))) .sorted(comparing(ProjectRanking::getScore).reversed()) .collect(Collectors.toList()); redisTemplate.opsForValue().set( "contest_rank:"+contest.getId(), rankings); }); }6. 部署注意事项
6.1 生产环境配置
# application-prod.yml spring: datasource: url: jdbc:mysql://db-server:3306/code_contest?useSSL=false username: prod_user password: ${DB_PASSWORD} servlet: multipart: max-file-size: 10MB max-request-size: 20MB storage: upload-dir: /var/contest/uploads6.2 安全防护措施
- 使用Spring Security实现RBAC
@Configuration @EnableWebSecurity public class SecurityConfig { @Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/admin/**").hasRole("ADMIN") .antMatchers("/submit/**").hasAnyRole("STUDENT","TEACHER") .anyRequest().permitAll() .and() .formLogin() .loginPage("/login") .defaultSuccessUrl("/dashboard"); return http.build(); } }- 文件上传安全检查
public boolean isSafeFile(MultipartFile file) { String[] allowed = {".sb3",".jpg",".png"}; String filename = file.getOriginalFilename().toLowerCase(); return Arrays.stream(allowed) .anyMatch(ext -> filename.endsWith(ext)); }7. 教学实践反馈
经过一学期的实际使用,系统表现出以下特点:
- 学生端亮点:
- 作品集功能让学习进度可视化
- 内置的代码规范检查器减少常见错误
- 积分排行榜激发竞争意识
- 教师端优势:
- 批量导入题目功能节省备课时间
- 抄袭检测算法识别相似项目
- 自动化生成班级学习报告
典型问题解决方案:
- 大文件上传超时 → 增加Nginx配置
client_max_body_size 20m;- 并发提交导致评分延迟 → 引入消息队列
@RabbitListener(queues = "judge.queue") public void handleJudgeRequest(Project project) { // 异步评判逻辑 }项目源码已包含完整文档和Docker部署脚本,特别适合作为:
- 计算机专业课程设计案例
- 毕业设计基础框架
- 编程教育机构教学平台原型