少儿编程管理系统开发实战:Spring Boot+MySQL排课与作品上传
简介这是一套面向课程设计、毕业设计及SSM框架初学者的Web少儿编程管理系统源码包。项目基于JavaSSMSpring/SpringMVC/MyBatisPlus与VueElementUI前后端分离实现后端采用JDK1.8与MySQL5.7覆盖用户信息管理、图片素材、视频素材等核心模块并附有绪论、技术介绍、系统分析到系统实现等完整论文章节适合用于理解从需求分析到开发落地的全流程。压缩包共836个文件约17.21MB包含131个Java后端源码、49个Vue组件、164个JavaScript脚本与53个CSS样式以及图片、动图、SVG图标等页面素材结构完整可按后端、前端、配置与文档分类查找。目前已有492人学习下载可作为SSM项目实战参考或二次开发基础。除完整业务代码外还包含MyBatis等配置文件、运行安装脚本及PDF/Word版设计文档便于快速搭建环境并对照论文进行调试是一份能直接上手的学习型项目资源也常被用于课程报告与毕业答辩的辅助支撑。1. 少儿编程管理系统不难做一个 Java 后端配上网页端就能把排课、作业和作品管起来“少儿编程系统”这几年在 Java 课程设计、毕业设计和少儿编程机构内部反复出现它不是一个在线编程 IDE而是一套基于 Web 的教务管理系统管学员报名、班级排课、教师布置 Scratch/Python 作业、学生上传作品、家长查看进度。这类系统用一套很常规的 Java 技术栈就能覆盖——Spring Boot 做后端MyBatis 操作 MySQL前端用 Vue 或服务端渲染页面所以标题后面总是跟着“基于 Web”“java”和“源码”。这篇文章我会按自己实际做这类系统的顺序拆开讲先把业务对象整理成表结构再把 Spring Boot 工程跑起来接着实现排课和作品上传两个核心接口最后把最容易翻车的五个部署问题一次性说清楚。适合两类人一类是拿它做课程设计或毕设的开发者另一类是机构里想摆脱 Excel 排课的教务老师。2. 设计少儿编程管理系统的数据模型先想清楚排课、学员、作品三个业务对象2.1 四张核心表先建好学员、班级、排课、作品很多人在“少儿编程系统设计与实现”这类题目上开局就写登录页面结果数据模型撑不起业务。少儿编程管理系统和普通内容管理系统的区别在两点一是按学期/班级做周期排课二是学员要反复提交作品并接受评价。别的功能可以先放这四张表必须先定下来。CREATE TABLE student ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 学员ID, name VARCHAR(32) NOT NULL COMMENT 学员姓名, age TINYINT DEFAULT NULL COMMENT 年龄, course_type VARCHAR(16) NOT NULL DEFAULT scratch COMMENT 课程类型: scratch/python/cpp, parent_name VARCHAR(32) DEFAULT NULL COMMENT 家长称呼, parent_phone VARCHAR(16) NOT NULL COMMENT 家长电话, class_id BIGINT DEFAULT NULL COMMENT 所在班级ID, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, KEY idx_class_id (class_id), KEY idx_parent_phone (parent_phone) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT少儿编程学员表; CREATE TABLE class_info ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 班级ID, class_name VARCHAR(64) NOT NULL COMMENT 班级名称如 秋季Scratch进阶1班, course_type VARCHAR(16) NOT NULL COMMENT 课程类型, grade_level VARCHAR(16) DEFAULT NULL COMMENT 年级要求, teacher_id BIGINT NOT NULL COMMENT 授课教师ID, start_date DATE NOT NULL COMMENT 开课日期, total_weeks INT NOT NULL DEFAULT 16 COMMENT 总周数, class_hours_per_week DECIMAL(3,1) NOT NULL DEFAULT 2.0 COMMENT 每周课时数, capacity INT NOT NULL DEFAULT 12 COMMENT 班级容量, enabled TINYINT NOT NULL DEFAULT 1 COMMENT 1启用 0停用 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT班级表; CREATE TABLE course_schedule ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 排课ID, class_id BIGINT NOT NULL COMMENT 班级ID, week_index INT NOT NULL COMMENT 班级内第几周从1开始, weekday TINYINT NOT NULL COMMENT 周几1周一, start_time TIME NOT NULL COMMENT 开始时间, end_time TIME NOT NULL COMMENT 结束时间, classroom VARCHAR(32) DEFAULT NULL COMMENT 教室, teacher_id BIGINT NOT NULL COMMENT 实际授课教师, enabled TINYINT NOT NULL DEFAULT 1 COMMENT 1有效 0停用, UNIQUE KEY uk_class_week (class_id, week_index) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT排课表; CREATE TABLE work_submission ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 作品ID, student_id BIGINT NOT NULL COMMENT 学员ID, class_id BIGINT NOT NULL COMMENT 班级ID, week_index INT NOT NULL COMMENT 提交时对应第几周, assignment_name VARCHAR(64) NOT NULL COMMENT 作业名称, file_url VARCHAR(255) NOT NULL COMMENT 文件访问路径, file_type VARCHAR(32) DEFAULT NULL COMMENT 文件类型, score DECIMAL(5,1) DEFAULT NULL COMMENT 教师评分, comment VARCHAR(255) DEFAULT NULL COMMENT 教师评语, submitted_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 提交时间 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT作品提交表;这里有两个容易被新手忽略的设计意图。第一student.class_id直接指向班级一个学员在一学期内只属于一个主班如果学员同时上 Scratch 和 Python 两门课宁可拆成两条学员记录也不要给学员表做多对多映射否则后续排课、出勤、作品统计都会变成一层套一层的 join。第二course_schedule.week_index存的是“班级内第几周”不是自然周后面第 5 章会专门讲这个字段带来的跨周问题。四个表都用utf8mb4作品文件名如果带中文或者学生姓名是生僻字普通utf8会直接报错或乱码这个不用商量。2.2 排课表为什么用“班级内第几周”而不是日期区间少儿编程机构最常见的教学节奏是一个班 16 周每周固定一天上课中间可能因为节假日停课、补课。如果排课表里直接存lesson_date日期一旦停课调整整张表都要改如果存week_index weekday停课只需要把当周记录的enabled置为 0补课再新建一条带备注的记录原来的数据链路不受影响。course_schedule表里的UNIQUE KEY uk_class_week (class_id, week_index)表示一个班一周只上一次课。如果机构有每周两次的班级就要把这个唯一索引改成(class_id, week_index, weekday, start_time)否则第二次课永远插不进去。这是一个很典型的“表结构没想清楚写代码到一半回来改表”的坑。查询某一周全校课表时用班级表和教师表关联SELECT cs.week_index, cs.weekday, cs.start_time, cs.end_time, cs.classroom, ci.class_name, tu.real_name AS teacher_name FROM course_schedule cs JOIN class_info ci ON ci.id cs.class_id LEFT JOIN sys_user tu ON tu.id cs.teacher_id WHERE cs.week_index #{weekIndex} AND cs.enabled 1 ORDER BY cs.weekday, cs.start_time;weekday用 1 到 7 表示周一至周日前端拿到数字后自己转中文不要在数据库里存“周三”这种字符串。start_time和end_time用 MySQL 的TIME类型后面的课程冲突校验可以直接用和比较如果用VARCHAR存时间接口层还得先把字符串解析成时间对象纯属给自己加工作量。2.3 账号与角色管理员、教师、家长怎么放进一张表这套系统有三类登录角色机构管理员、授课教师、家长。很多教程一上来就给你五张 RBAC 权限表但少儿编程管理系统的真实角色不超过五种一张用户表加一个role字段完全够用还省掉大量联表查询。CREATE TABLE sys_user ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 用户ID, username VARCHAR(32) NOT NULL UNIQUE COMMENT 登录账号, password VARCHAR(64) NOT NULL COMMENT 密码摘要不存明文, role VARCHAR(16) NOT NULL COMMENT 角色: admin/teacher/parent, real_name VARCHAR(32) DEFAULT NULL COMMENT 姓名, phone VARCHAR(16) DEFAULT NULL COMMENT 手机号, teacher_id BIGINT DEFAULT NULL COMMENT 教师身份时关联的教师档案ID, enabled TINYINT NOT NULL DEFAULT 1 COMMENT 1启用 0停用 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT系统用户表;表名叫sys_user而不是user是因为user是 MySQL 在不同版本里有概率出现的保留字ORM 生成 SQL 时不加反引号就可能触发语法错误换掉表名是一劳永逸的做法。三种角色的可见范围按下面这张表控制就够了角色登录身份可见范围典型操作admin机构运营人员所有班级、教师、学员、作品创建班级、分配教师、查看统计报表teacher授课老师自己带的班级、班级学员、学员作品维护排课、布置作业、评分写评语parent学员家长自己孩子的课表、作品、成绩查看课表、查看作品、提交请假申请家长和学员的关联我直接用student.parent_phone完成一个家长可能有两个孩子同时在这个机构上课同一手机号能查出多条学员记录。家长端登录时先用sys_user.phone反查student不需要单独维护家长-学生关系表。等机构做到学员量大、一个孩子对应多个家长再补关系表也来得及第一版越简单越容易跑通。3. 用 Spring Boot 把少儿编程系统工程跑起来前后端分离的最小可运行代码3.1 依赖与项目结构一张 pom.xml 说清楚关键选型创建一个基于 Web 的 Java 工程最省事的方案是 Spring Boot 加 MyBatis-Plus。MyBatis-Plus 的价值在单表 CRUD 不需要写 XML查询条件直接用 Lambda 构造器这对排课、作品这类字段固定的业务非常友好。parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId relativePath/ /parent dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependenciesspring-boot-starter-web同时提供接口开发和文件上传所需的MultipartFile支持MyBatis-Plus 的 starter 会自动装配 SqlSessionFactoryMySQL 驱动放 runtime 作用域打包时不会把驱动源码带进去Lombok 给实体类省掉 getter/setter。版本号不写死用你本机 Spring Initializr 生成父工程时选定的版本即可避免不同 Spring Boot 版本之间出现兼容问题。工程目录我习惯这样拆kidscode/ ├── pom.xml └── src/main/ ├── java/com/example/kidscode/ │ ├── KidscodeApplication.java │ ├── controller/ # 接口层只做参数接收和结果封装 │ ├── service/ # 业务逻辑排课冲突校验放在这里 │ ├── mapper/ # 数据访问层 │ ├── entity/ # 和数据库表对应的实体类 │ ├── config/ # 拦截器、静态资源映射 │ └── common/ # 统一返回结果、异常、常量 └── resources/ ├── application.yml └── mapper/ # 复杂 SQL 放这里实体类上只需要加TableName(student)MyBatis-Plus 就会把类名和表名对应起来。用 MyBatis-Plus 的代码生成器还能根据数据库表反向生成实体、Mapper 和 Service很多课程设计项目都是先定表结构再生成代码比自己手写实体快得多。3.2 配置 application.yml数据源、文件上传限制与字符编码server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/kidscode?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: 你的数据库密码 servlet: multipart: max-file-size: 20MB max-request-size: 50MB mybatis-plus: mapper-locations: classpath*:mapper/**/*.xml configuration: map-underscore-to-camel-case: true app: upload-dir: ./upload连接串里的serverTimezoneAsia/Shanghai必须加MySQL 8 驱动如果检测不到服务器时区会抛异常。map-underscore-to-camel-case负责把数据库里的class_id自动映射成 Java 实体里的classId否则你每个字段都要写TableField。max-file-size设为 20MB 是因为 Scratch 项目文件经常包含多张素材图片压缩包超过 10MB 很常见如果只做作业图片5MB 就够。app.upload-dir是自定义配置指向本地磁盘的上传目录第 4 章文件上传会用到。这里还要注意一个老坑如果你把表设计里预留了deleted逻辑删除字段在mybatis-plus.global-config.db-config下配置logic-delete-field即可如果表里没有这个字段就不要配置否则所有删除操作都会变成 update排查半天都找不到删除失败的原因。3.3 登录接口与访问拦截让三种角色各回各家少儿编程管理系统的并发量不算高登录 token 我用最简单的方式实现登录成功后生成一个随机 UUID 存到内存 Map拦截器里查 Map。不引入 JWT 依赖课程设计和机构内部后台足够用缺点是服务重启后所有登录状态失效但换来的是代码少、没有版本兼容问题。Service public class LoginService { private final MapString, Long tokenMap new ConcurrentHashMap(); public String login(LoginDTO dto) { SysUser user userMapper.selectOne(Wrappers.SysUserlambdaQuery() .eq(SysUser::getUsername, dto.getUsername()) .eq(SysUser::getPassword, DigestUtils.md5Hex(dto.getPassword()))); if (user null) { throw new BizException(账号或密码错误); } String token UUID.randomUUID().toString().replace(-, ); tokenMap.put(token, user.getId()); return token; } public Long getUserId(String token) { return tokenMap.get(token); } }密码用DigestUtils.md5Hex做摘要存储只在课程设计里够用真实机构项目建议升级成 BCrypt。ConcurrentHashMap保证多线程读写 token 不竞争。接下来写拦截器把 userId 塞进请求属性里后面的接口直接RequestAttribute(userId)取Component public class LoginInterceptor implements HandlerInterceptor { private final LoginService loginService; public LoginInterceptor(LoginService loginService) { this.loginService loginService; } Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { String token request.getHeader(Authorization); Long userId loginService.getUserId(token); if (userId null) { response.setStatus(401); return false; } request.setAttribute(userId, userId); return true; } }拦截器注册到 WebMvcConfigurer 时把/api/login排除掉其他/api/**都走登录校验。角色判断不要写在拦截器里在 Service 层根据sys_user.role做业务校验更灵活因为家长和教师可能访问同一个接口但看到不同范围的数据。4. 少儿编程管理系统的核心接口排课接口与作品上传的代码实现4.1 排课接口冲突校验先把“同老师同教室同时段”挡住排课是这套系统里业务规则最密集的接口。机构老师最怕的就是两个班被排到同一时间同一个老师或者同一间教室被两个班同时使用。接口接收的参数包括班级ID、第几周、周几、开始结束时间和教室先查班级是否存在并校验周次再查冲突public void addSchedule(ScheduleCreateDTO dto) { ClassInfo cls classInfoMapper.selectById(dto.getClassId()); if (cls null) { throw new BizException(班级不存在); } if (dto.getWeekIndex() cls.getTotalWeeks()) { throw new BizException(排课周次超过班级总周数); } Long count courseScheduleMapper.selectCount(Wrappers.CourseSchedulelambdaQuery() .eq(CourseSchedule::getWeekIndex, dto.getWeekIndex()) .eq(CourseSchedule::getWeekday, dto.getWeekday()) .eq(CourseSchedule::getEnabled, 1) .and(w - w .eq(CourseSchedule::getTeacherId, dto.getTeacherId()) .or() .eq(CourseSchedule::getClassroom, dto.getClassroom()))); if (count 0) { throw new BizException(该教师或该教室在同一时段已有排课); } CourseSchedule schedule new CourseSchedule(); schedule.setClassId(dto.getClassId()); schedule.setWeekIndex(dto.getWeekIndex()); schedule.setWeekday(dto.getWeekday()); schedule.setStartTime(dto.getStartTime()); schedule.setEndTime(dto.getEndTime()); schedule.setClassroom(dto.getClassroom()); schedule.setTeacherId(dto.getTeacherId()); courseScheduleMapper.insert(schedule); }冲突查询里最关键的是.and(w - w.eq(...).or().eq(...))它会把两个条件用括号包起来生成 SQL 是teacher_id ? OR classroom ?而不是把前面的周次条件一起 OR 进去。时间字段直接比较大小判断两个时间段是否重叠标准写法是新开始时间 旧结束时间 AND 新结束时间 旧开始时间等于号都不需要。这个公式在会议室预订、课程排期、甚至订单时段校验里通用是这类系统的核心骨架。4.2 作品上传与回显文件存本地磁盘URL 通过静态映射暴露作品上传我不用数据库 BLOB 存文件原因有两个一是 Base64 或二进制大字段会让表体积膨胀课程表、学员表的查询性能跟着受牵连二是少儿编程作品以图片、短视频、Scratch 项目包为主存磁盘后直接给 Nginx 这类静态服务托管备份也简单。课程设计阶段用 Spring Boot 自己的静态资源映射就够了。RestController RequestMapping(/api/work) public class WorkController { Value(${app.upload-dir}) private String uploadDir; PostMapping(/upload) public ResultString upload(RequestParam(file) MultipartFile file) throws IOException { if (file.isEmpty()) { return Result.error(文件为空); } String originalName FilenameUtils.getName(file.getOriginalFilename()); String ext originalName.substring(originalName.lastIndexOf(.)); String fileName UUID.randomUUID().toString().replace(-, ) ext; String datePath DateTimeFormatter.ofPattern(yyyyMMdd).format(LocalDate.now()); File dir new File(uploadDir, datePath); if (!dir.exists()) { dir.mkdirs(); } file.transferTo(new File(dir, fileName)); return Result.ok(/files/ datePath / fileName); } }文件名用 UUID 重命名是为了避免两个学生上传同一个main.sb3互相覆盖。FilenameUtils.getName会把浏览器传过来的路径去掉只保留真正的文件名防止上传目录被路径穿越。文件落盘之后把/files/...的访问路径存进work_submission.file_url前端不管是展示图片还是给家长下载都用这个路径拼完整地址。要让/files/**能被浏览器访问需要配置静态资源映射Configuration public class WebConfig implements WebMvcConfigurer { Value(${app.upload-dir}) private String uploadDir; Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/files/**) .addResourceLocations(file: uploadDir /); } }addResourceLocations的路径结尾必须带/否则 Spring 会认为它不是一个目录前缀映射直接失效。上传目录建议放在工程外部比如./upload或服务器上的/data/kidscode/upload这样以后升级系统 jar 包不会把学生作品一起删掉。4.3 家长端查询一个手机号关联多个孩子家长端接口不做任何写操作只负责把当前登录家长的孩子、孩子所在的班级、孩子的作品列表一起返回。登录用户的手机号从sys_user表取然后用手机号反查学员表GetMapping(/api/parent/student-works) public ResultListParentStudentVO listStudentWorks(Long userId) { String phone userService.getPhoneById(userId); ListStudent students studentService.list( Wrappers.StudentlambdaQuery().eq(Student::getParentPhone, phone)); ListParentStudentVO result students.stream().map(student - { ParentStudentVO vo new ParentStudentVO(); BeanUtils.copyProperties(student, vo); vo.setWorks(workService.list( Wrappers.WorkSubmissionlambdaQuery() .eq(WorkSubmission::getStudentId, student.getId()) .orderByDesc(WorkSubmission::getWeekIndex))); return vo; }).collect(Collectors.toList()); return Result.ok(result); }userId从拦截器塞进 request 的属性里取得不信任前端传来的家长 ID避免家长拼一个别人的 ID 看到别的孩子信息。这个接口故意做了 N1 查询因为一个家长最多两三个孩子每单查询都走主键索引如果机构做大了再改成一次查出所有作品后按studentId分组性能和代码结构都能兼顾。5. 少儿编程系统从开发到部署的五个常见坑现象、原因与解决5.1 前端刷新页面返回 404后端路由没有兜底现象前端页面点菜单跳转都正常但部署后按 F5 刷新或者直接把某个页面地址发给别人打开就是 404 白页。原因前后端分离项目里前端路由切换是 JavaScript 控制的F5 刷新时浏览器把完整路径当成请求发给后端后端没有对应的 ControllerSpring Boot 默认当成找不到资源。解决加一个过滤器把所有不是/api开头、也不带文件后缀的请求转发到index.html让前端路由接管。Component public class SpaFallbackFilter extends OncePerRequestFilter { Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String uri request.getRequestURI(); if (!uri.startsWith(/api) !uri.contains(.)) { request.getRequestDispatcher(/index.html).forward(request, response); return; } filterChain.doFilter(request, response); } }这个过滤器只处理页面路由。/api/**一定会被放行静态资源文件带.js、.css、.png后缀也不会被误拦截。要注意如果前端路由本身是多级路径/class/student/list这类地址同样满足条件会被正确转发。5.2 排课跨周翻车week_index 按自然周算导致秋季班排到错误日期现象排课记录明明存的是第 5 周周三日历视图上却显示在国庆假期的日期。原因course_schedule.week_index是班级内的第几周不是当年的自然周序号必须从班级的start_date开课日期开始偏移计算。解决用开课日期加上(weekIndex - 1)周LocalDate lessonDate classInfo.getStartDate().plusWeeks(schedule.getWeekIndex() - 1L);这段代码放在生成日历视图的 Service 里weekIndex是排课记录里的值start_date是班级表的开课字段。这样即使班级从 9 月开学跨到第二年 1 月只要start_date正确每周的日期都不会错乱。课程具体是周二还是周六再用lessonDate.plusDays(weekday - 1L)算出当周那一天的日期。5.3 作品图片能上传不能访问文件落盘了但 URL 404现象上传接口返回成功数据库里也能看到/files/20251013/xxx.png浏览器打开这个地址就是 404。原因文件确实写到磁盘了但 Spring 没有把/files/**映射到上传目录或者 Linux 服务器上目录权限不足文件写进去了但读不出来。解决先确认文件在磁盘上的位置再检查映射配置。ls -l ./upload/20251013/ curl -I http://localhost:8080/files/20251013/xxx.png如果ls能看到文件但curl返回 404问题一定在addResourceLocations的路径上。Windows 下要写成file:D:/upload/Linux 下写成file:/data/kidscode/upload/结尾的反斜杠和目录写权限是最容易漏的两个细节。我第一次做这个功能时就是少了末尾的/整整排查了一个下午。5.4 MySQL 保留字冲突表名叫 user 或 grade 时建表成功查询报错现象建表语句执行成功但 MyBatis-Plus 查询时报 SQL 语法错误错误信息里能看到关键字。原因user、grade、order、group这些词在 MySQL 不同版本里是保留字ORM 自动生成的 SQL 不会给表名加反引号。解决表名避开保留字统一加业务前缀比如sys_user、class_info如果是已经上线的库临时救急可以用反引号包起来SELECT * FROM user;我用真实项目里的教训总结表设计阶段就把所有表名列个清单拿 MySQL 官方保留字列表跑一遍比写完代码再回来改表名省力一百倍。这个坑在 MySQL 8.0 下尤其容易触发因为 8.0 的保留字比 5.7 多了不少。5.5 本地能连数据库部署到云服务器就连不上现象本地java -jar跑得好好的部署到云服务器后接口一直报连接超时或拒绝连接。原因MySQL 默认只允许本机访问127.0.0.1云服务器上的数据库账号 host 是localhost另外连接串没带serverTimezone参数时MySQL 8 驱动启动阶段就异常。解决先在服务器上允许远程连接并创建一个 host 为%的账号CREATE USER kids% IDENTIFIED BY 这里填密码; GRANT ALL ON kidscode.* TO kids%;然后检查数据库配置文件的bind-address是不是0.0.0.0最后确认云平台的安全组或服务器防火墙允许访问 3306 端口。连接串保持第 3 章的写法serverTimezoneAsia/Shanghai和useSSLfalse两个参数一起带上。这一步做完数据库类问题基本不会卡住部署。6. 给少儿编程管理系统做一次真实数据演练批量导入、接口验证和周历视图6.1 先用 SQL 批量导入一位老师的班级数据开发环境手工一条条插入数据太慢我一般在建完表之后直接用 SQL 把演示班的数据一次性铺好。假设要建一个“秋季Scratch进阶1班”并给这个班导入 8 个学员INSERT INTO student (name, age, course_type, parent_name, parent_phone, class_id) SELECT 小星, 8, scratch, 星妈, 13800000001, id FROM class_info WHERE class_name 秋季Scratch进阶1班;这条 SQL 的巧妙处在于不需要在 insert 里写死班级 ID直接从class_info查出来。真实机构迁移 Excel 数据时也是同样的思路先导班级表再导学员表最后导排课和作品顺序反了外键就崩。6.2 用 curl 模拟一条“登录→排课→上传作品”的完整链路写完接口后我会用 curl 把核心链路过一遍不打开前端页面也能验证后端逻辑curl -X POST http://localhost:8080/api/login \ -H Content-Type: application/json \ -d {username:admin,password:123456} curl -X POST http://localhost:8080/api/schedule \ -H Authorization: 上面返回的token \ -H Content-Type: application/json \ -d {classId:1,weekIndex:1,weekday:3,startTime:18:30,endTime:20:00,teacherId:2,classroom:A101}第一条命令拿到 token第二条命令创建一条周三晚上的排课。如果第二条返回“该教师或该教室在同一时段已有排课”说明冲突校验生效了再用同一时间换个教室再提交一次应该成功。6.3 做一个周历视图一条 SQL 生成班级十六周的课表最后一个进阶功能是把排课表和班级的开课日期拼成日历视图。核心是算出每一周对应的具体日期SELECT cs.week_index, DATE_ADD(ci.start_date, INTERVAL (cs.week_index - 1) WEEK) AS week_start, ci.class_name, cs.weekday, cs.start_time, cs.end_time FROM course_schedule cs JOIN class_info ci ON ci.id cs.class_id WHERE ci.id 1 AND cs.enabled 1 ORDER BY cs.week_index;week_start算出来是开课日期所在的周一再根据cs.weekday加天数就是当次课的真实日期。前端拿到这个结果直接渲染日历不用在 Java 里循环算日期。我自己做这个项目时最深刻的教训是作品上传路径和回显 URL 对不上后来把所有文件访问路径统一放常量类管理再没翻过车。你按这六章把数据模型、接口、部署链路走完已经能得到一个能接受真实数据验收的少儿编程管理系统。希望帮到你。本文还有配套的精品资源点击获取