news 2026/9/13 5:15:40

SSM框架MOOC教学平台实战:数据库设计、查询链路与部署解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SSM框架MOOC教学平台实战:数据库设计、查询链路与部署解析

简介:这是一份基于SSM框架的MOOC在线教学平台毕设项目,技术栈采用Spring、SpringMVC、MyBatis整合架构,搭配MySQL数据库,运行环境为JDK、Eclipse与Tomcat,面向计算机相关专业毕业生及需要项目实战的Java学习者,用于解决课程设计与毕业设计缺乏可运行完整源码的痛点。压缩包共3个文件,包含txt项目说明、sql数据库脚本以及完整项目源码zip,资源总大小62.88MB,结构清晰,便于部署调试。目前已有1573人学习下载。项目涵盖学生、教师、系统管理员三种角色,前台模块包括新闻公告、教学通知、我的课程、我的成绩、在线测试、错题集、教学视频与留言交流等,后台则提供学生、教师、院系、班级、课程、章节、公告、测试题、成绩、视频课件及留言管理功能,整体界面美观、操作简单,经过严格调试可直接运行。它既适合快速完成毕设,也适合通过实际项目掌握SSM开发全流程。

1. 基于 SSM 的 MOOC 在线教学平台,核心是要跑起来,不是要造概念

做在线教学平台最怕的不是业务复杂,而是表结构先乱掉。基于 SSM 的 MOOC 在线教学平台(项目源码 + 数据库脚本)这类毕业设计题,表面上是一个可运行的教学后台,实际检验的是对 Spring、SpringMVC、MyBatis 三个框架的整合能力。很多人拿到源码第一件事就是启动,结果卡在数据库脚本导入失败、Mapper 扫描不到、Tomcat 部署上下文不一致这类问题上。下面不重复框架官网的 Hello World,直接从源码目录拆起,把数据库脚本和课程查询链路一起讲清楚。适合正在写毕业设计、准备后端面试项目、或者打算给团队搭一个内部培训系统的人参照。

2. 从 SSM 源码目录看项目分层的正确姿势

2.1 为什么毕业设计仍然选 SSM

先承认一个事实:现在新起的后端项目大多用 Spring Boot 或 Spring Cloud,SSM 在互联网企业里的新建占比已经不高。但在两种场景里 SSM 依然常见:一是高校毕设和内部实训项目,评估方要看到手写的 XML 配置,而不是自动装配的“黑盒”;二是老系统改造,SSM 单体里保留了大量可以逐行断点调试的业务代码。纯 Servlet + JDBC 已经喂不饱“线上选课、进度统计、权限判断”这些需求,直接用 Spring Boot 又很难讲清楚框架整合时机,因此 SSM 正好处在“能做东西、又能讲原理”的位置。

下面的对比不是结论,而是一个选型判断依据:

维度SSM 单体Spring Boot 单体微服务方案
配置可见性XML 全量可见约定优于配置服务间通信成本高
部署单元war 包 / 外部 Tomcat内置容器 Jar多服务多镜像
学习成本需要理解 Bean 生命周期与代理机制上手快额外理解注册中心与链路追踪
面试表达能讲 AOP、IOC、Mapper 代理主聊自动装配主聊服务治理
对 MOOC 项目的适配度

选型建议:如果评估方强调“源码可读、分层规范”,SSM 就是最稳妥的答案。要在一份源码里实现“课程发布—选课—学习记录—个人中心”这四个闭环,SSM 的代码量完全可维护,关键是要把 controller 和 dao 的边界划清楚。

2.2 从目录树看 controller、service、dao 的边界

一份典型的 ssm-mooc 项目源码目录是这样的:

ssm-mooc/ ├── src/main │ ├── java/com/example/mooc │ │ ├── controller # 接收 HTTP 请求 │ │ ├── service # 业务逻辑接口 │ │ │ └── impl # 业务逻辑实现 │ │ ├── dao # MyBatis 的 Mapper 接口 │ │ ├── entity # 数据库表对应的实体 │ │ ├── vo # 给前端使用的视图对象 │ │ └── interceptor # 登录/权限拦截 │ ├── resources │ │ ├── spring │ │ │ ├── spring-mvc.xml # SpringMVC 相关配置 │ │ │ └── spring-dao.xml # 数据源与 MyBatis 配置 │ │ └── mapper # MyBatis 的 XML 映射文件 │ └── webapp │ ├── WEB-INF │ │ └── web.xml │ └── static └── sql └── mooc.sql

目录拆分的核心约束是:controller 只做参数接收、参数校验和结果封装;service 做事务与业务判断;dao 只负责 SQL 与对象映射。很多人把业务逻辑写在 controller 里,导致后面加权限拦截时不得不改控制器签名。SSM 的规范不在于接口写得有多抽象,而在于每一层可以独立测试,至少 dao 的 SQL 要能通过一个独立查询验证结果。

各层职责可以归纳为下面几点:

  • controller:读取参数、调用 service、返回 ResultVO 统一结果。
  • service impl:组合多个 dao、加 @Transactional、完成课程状态判断。
  • dao:接口方法对应 mapper XML 里的一个 SQL。
  • entity:与数据库表字段对应,不再写计算逻辑。
  • vo:面向界面拼装数据,比如 CourseVO 里把 teacherId 合并成 teacherName。

2.3 数据源与 Mapper 扫描配置里容易被改坏的 3 个参数

在 resources/spring/spring-dao.xml 中,SSM 的数据源和 MyBatis 核心配置集中在三块:

<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="url" value="jdbc:mysql://localhost:3306/mooc_db?useUnicode=true&amp;characterEncoding=utf8mb4&amp;useSSL=false"/> <property name="username" value="root"/> <property name="password" value="root"/> </bean> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="mapperLocations" value="classpath:mapper/*.xml"/> </bean> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.example.mooc.dao"/> </bean>

这里最容易改坏的三个位置。第一,mapperLocations 的路径必须和 resources/mapper 下的 XML 文件匹配,路径写错时项目启动不会报错,只有调用接口时才抛 Invalid bound statement 异常。第二,URL 里 characterEncoding=utf8mb4 和 useSSL=false 要用 & 符号连接,而且等了 XML 转义后要写成 &,否则配置解析直接失败。第三,如果使用的是 MySQL 8 驱动,连接串最后要补 serverTimezone=Asia/Shanghai,否则时间戳映射会出现 GMT 时区偏移。

如果项目用到了声明式事务,还需要在 spring-service.xml 中加入 DataSourceTransactionManager,并开启<tx:annotation-driven transaction-manager="transactionManager"/>。后面第 4 章会用到的 @Transactional 注解,就是靠这个 manager 生成的代理对象生效的。此处配置缺失时,注解不会报错,但数据库操作不会回滚,是 SSM 项目里最难排查的静默故障。

3. MOOC 核心表结构与数据库脚本的导入顺序

3.1 用户、课程、选课三张主表的关系怎么设计

MOOC 平台先要区分三类身份:学生、讲师、管理员。数据库脚本里建议只建一张 user 表,通过 role 字段区分,而不是拆出 student 和 teacher 两张表。原因很简单:讲师和学生都要登录,公共字段高度重叠;拆表之后,课程查询和权限拦截都要多一次联合判断。user 表的核心字段应包含 id、username、password、nickname、avatar_url、role、university、create_time。password 字段在演示项目里存 MD5 或 BCrypt 摘要,建议选择 BCrypt,至少不要在数据库脚本里出现明文密码。

课程表与用户表的关系是 course.teacher_id 指向 user.id,表示课程归属某个讲师,也方便后面做“我的课程”列表。选课关系用 course_enrollment 表记录,一个用户可对应多门课程,一门课程可被多个用户选择,是典型的多对多关系表。课时表 lesson 挂在 course 下面,形成课程到课时的一对多嵌套。学习记录表再记录 user、course、lesson 三者的关联,完整支撑进度统计。

3.2 course 表的字段与状态字段设计

课程表是整个数据库脚本的灵魂,字段设计可以直接照这份 DDL 展开:

CREATE TABLE `course` ( `id` INT(11) NOT NULL AUTO_INCREMENT, `title` VARCHAR(120) NOT NULL COMMENT '课程标题', `subtitle` VARCHAR(255) DEFAULT NULL COMMENT '副标题', `cover_img` VARCHAR(255) DEFAULT NULL COMMENT '封面图路径', `teacher_id` INT(11) NOT NULL COMMENT '讲师ID', `category_id` INT(11) DEFAULT NULL COMMENT '课程分类ID', `price` DECIMAL(10,2) NOT NULL DEFAULT '0.00' COMMENT '售价,0为免费', `status` TINYINT(1) NOT NULL DEFAULT '0' COMMENT '课程状态:0待发布 1已上架 2已下架', `student_count` INT(11) NOT NULL DEFAULT '0' COMMENT '选课人数,冗余字段', `introduction` TEXT COMMENT '课程简介', `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_teacher_id` (`teacher_id`), KEY `idx_status` (`status`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='课程表';

几个字段值得说明。status 用 TINYINT 而不是 VARCHAR,是为了避免“上架”和“下架”在不同文档里写法不一致,代码中可以用常量类或枚举做映射。student_count 是冗余设计,列表页直接展示,不用每次 count 选课表,缺点是报名和退课时要同步维护该字段。teacher_id 建索引是因为业务上最常出现“按讲师查看课程”的查询,status 建索引则是为了支撑首页只展示已上架课程的过滤条件。

3.3 study_record 学习记录表与进度统计

选课完成不等于学习完成,MOOC 平台比普通课程网站多一张学习记录表:

CREATE TABLE `study_record` ( `id` INT(11) NOT NULL AUTO_INCREMENT, `user_id` INT(11) NOT NULL COMMENT '学生ID', `course_id` INT(11) NOT NULL COMMENT '课程ID', `lesson_id` INT(11) NOT NULL COMMENT '课时ID', `current_position` INT(11) NOT NULL DEFAULT '0' COMMENT '播放位置,单位秒', `total_duration` INT(11) NOT NULL DEFAULT '0' COMMENT '课时总时长,单位秒', `finished` TINYINT(1) NOT NULL DEFAULT '0' COMMENT '是否学完该课时', `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_user_lesson` (`user_id`, `lesson_id`), KEY `idx_course_id` (`course_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='课时学习记录';

核心是唯一键 uk_user_lesson,它保证一个学生在一个课时下只有一条记录,避免视频播放器上报时不断插入新行。前端播放器每 5 秒上报一次播放位置,业务侧通过 current_position 与 total_duration 的比例判断该课时是否学完。整套进度统计的 SQL 可以这样写:

SELECT c.title, COUNT(DISTINCT sr.user_id) AS study_user_count, ROUND(AVG(sr.current_position / sr.total_duration) * 100, 2) AS avg_progress FROM study_record sr JOIN course c ON sr.course_id = c.id GROUP BY sr.course_id ORDER BY avg_progress DESC;

AVG 函数在 total_duration 为 0 时会出现除零错误,业务层要在上报接口中过滤掉 total_duration 小于等于 0 的记录。统计口径上,一名用户可能只点开视频 10 秒,也会被计入 study_user_count,所以这个指标适合叫“学习人数”而不是“完课人数”。

3.4 数据库脚本导入时的注意点

拿到项目源码往往带一份 mooc.sql,但不同人的 MySQL 环境不一样,导入时会遇到三类常见报错。第一是外键顺序:脚本里先执行课程表再执行用户表,外键约束会失败,建议导入前在脚本开头加SET FOREIGN_KEY_CHECKS=0;,导入后再恢复。第二是字符集:Windows 命令行直接 source 导入 SQL 容易出现乱码,最好明确使用 utf8mb4。第三是字段兼容性:datetime 的 DEFAULT CURRENT_TIMESTAMP 在 MySQL 5.6 之前不支持,老版本要先删掉相关子句。

提示:导入前先执行SHOW VARIABLES LIKE 'character_set_database';确认库编码,再执行 source 脚本,能省去大部分乱码和索引报错。

4. 用 SpringMVC + MyBatis 跑通一条课程列表查询链路

4.1 Controller 接住浏览器请求并返回 JSON

整条链路从浏览器地址开始,比如/api/course/page?pageNum=1&pageSize=10&keyword=Java。SpringMVC 的 DispatcherServlet 将请求映射到 CourseController:

@RestController @RequestMapping("/api/course") public class CourseController { @Autowired private CourseService courseService; @GetMapping("/page") public ResultVO page(@RequestParam(defaultValue = "1") Integer pageNum, @RequestParam(defaultValue = "10") Integer pageSize, @RequestParam(required = false) String keyword) { PageInfo<CourseVO> pageInfo = courseService.pageCourse(pageNum, pageSize, keyword); return ResultVO.success(pageInfo); } }

@RestController 是 @Controller 与 @ResponseBody 的合并写法,返回值直接交给 Jackson 解析成 JSON。三个参数里 pageNum 和 pageSize 有默认值,keyword 允许为空,这样页面刚打开时不带搜索条件也能正常返回列表。如果不用 @RequestParam 而是直接写 HttpServletRequest,代码也能跑,但可读性差,也不利于前端对接。ResultVO 是统一返回值结构,一般包含 code、msg、data 三个字段,前端不需要对成功失败各写一套解析逻辑。

4.2 Service 层的事务边界与 PageHelper 分页

Service 层不建议直接返回 List。列表接口还需要总条数和总页数,直接返回 List 时前端就得再拉一次统计接口。常见做法是让 Service 声明返回 PageInfo:

public interface CourseService { PageInfo<CourseVO> pageCourse(Integer pageNum, Integer pageSize, String keyword); }

实现类中注意两点。PageHelper.startPage 必须放在查询语句之前,同一个方法里要紧跟着执行 Mapper 查询,中间不要插其他 SQL,否则分页会截错数据。@Transactional(rollbackFor = Exception.class) 放在实现类上,方法内任意异常都会触发回滚。只读查询不强制加事务,但不会出错,SSM 会通过事务管理器统一生成代理。

@Service @Transactional(rollbackFor = Exception.class) public class CourseServiceImpl implements CourseService { @Autowired private CourseMapper courseMapper; @Override public PageInfo<CourseVO> pageCourse(Integer pageNum, Integer pageSize, String keyword) { PageHelper.startPage(pageNum, pageSize); List<CourseVO> list = courseMapper.selectCoursePage(keyword); return new PageInfo<>(list); } }

参数说明:pageSize 建议做一次上限拦截,超过 100 时强制改回 100,防止有人用大分页拖慢数据库;PageHelper 会自动改写待统计 total 的查询,所以不需要手写 count SQL。

4.3 Mapper XML 的动态 SQL 与字段映射

DAO 层是接口加 XML 的组合。接口定义:

@Repository public interface CourseMapper { List<CourseVO> selectCoursePage(@Param("keyword") String keyword); }

XML 里用 resultType 映射到 CourseVO。下划线转驼峰可以在 mybatis-config.xml 里打开 mapUnderscoreToCamelCase,让 course_id 自动映射到 courseId。但带 JOIN 的查询里字段来源混乱,建议用别名显式控制。

<select id="selectCoursePage" resultType="com.example.mooc.vo.CourseVO"> SELECT c.id, c.title, c.cover_img, c.price, c.status, u.nickname AS teacherName, c.student_count FROM course c LEFT JOIN `user` u ON c.teacher_id = u.id <where> <if test="keyword != null and keyword != ''"> AND (c.title LIKE CONCAT('%', #{keyword}, '%') OR u.nickname LIKE CONCAT('%', #{keyword}, '%')) </if> </where> ORDER BY c.create_time DESC </select>

两个容易出错的细节:user 是 MySQL 保留字,JOIN 时必须加反引号,不同 MySQL 版本对未加反引号的 user 表容忍度不一致。#{keyword}是预编译占位符,不要在 XML 里直接拼接字符串,否则数据库注入漏洞就出现了。resultType 写 VO 之后,用 AS 将 u.nickname 明确命名为 teacherName,避免 mapUnderscoreToCamelCase 对带表前缀的联动列产生歧义。

4.4 前端页面用 fetch 接后端的典型写法

SSM 项目的 webapp 目录下常会看到 JSP,但列表页如果要在不刷新整个页面的情况下加载课程,直接让接口返回 JSON 更简单。页面加载时调用:

fetch('/api/course/page?pageNum=1&pageSize=12') .then(res => res.json()) .then(data => { const list = data.data.list; list.forEach(course => { const card = document.createElement('div'); card.className = 'course-card'; card.innerHTML = `<img src="${course.coverImg}"> <div class="title">${course.title}</div> <div class="teacher">${course.teacherName}</div>`; document.querySelector('#courseList').appendChild(card); }); });

这个写法跳过了 JSP 标签库和模板引擎的传参,前端只管渲染,后端只负责 JSON。代价是没有 SEO,但 MOOC 平台的课程列表需要登录才能观看,公开搜索的价值本身不高。本地联调时记得把 HTML 放到 webapp/static 下用 Tomcat 访问,直接右键文件用浏览器打开会出现地址不匹配,请求拿不到数据。

5. 最终部署与自检:从源码到可演示的 MOOC 项目

5.1 在 IDEA 中导入与导出数据库脚本的推荐顺序

评估方拿到项目后最常做的事是:先导入数据库脚本,再启动 web 项目,然后打开浏览器点页面。这一步替你做得好不好,直接决定第一印象。命令行导出比在可视化工具里点击按钮更容易控制字符编码:

mysqldump -uroot -p --default-character-set=utf8mb4 --databases mooc_db > mooc.sql

在 IDEA 的 Database 面板中右键数据库,选择 Export with mysqldump 时,要勾上 Insert 数据和 Drop Table if exists 这类选项,这样另一台电脑导入时不会因为残留表而中断。导出完成后打开脚本文件,确认中文没有乱码,再检查第一行是否包含SET NAMES utf8mb4;,否则遇到冷僻字或特殊符号会导入失败。导入方如果在执行时遇到字段长度超限,优先看 UTF-8 的 VARCHAR 长度定义,一般把 VARCHAR(120) 换成 VARCHAR(200) 就能解决。

5.2 用外部 Tomcat 部署 war 包的简洁路径

拿到项目源码后先在 IDEA 中打开,等 Maven 依赖下载完成后执行:

mvn clean package -Dmaven.test.skip=true

打包结果在 target 目录下,文件名通常是 ssm-mooc.war。把它复制到 Tomcat 的 webapps 目录,启动 Tomcat 后访问http://localhost:8080/ssm-mooc/。上下文路径默认和 war 包文件名一致,尽量不要随意改。如果 8080 端口被占用,在 conf/server.xml 里改 Connector 的 port 属性,重启后再访问对应端口。

5.3 写一个 health 接口快速验证环境是否就绪

部署完踩到连接错误时,很多人反复重启 Tomcat,不如在源码里留一个自检接口:

@RestController @RequestMapping("/health") public class HealthController { @Autowired private CourseService courseService; @GetMapping public Map<String, Object> health() { Map<String, Object> result = new HashMap<>(); result.put("time", LocalDateTime.now().toString()); try { result.put("courseCount", courseService.countAll()); result.put("database", "ok"); } catch (Exception e) { result.put("database", "fail"); result.put("reason", e.getMessage()); } return result; } }

访问/health时,time 和 courseCount 两个字段足够完成 80% 的环境排查。如果 courseCount 正常返回,说明数据源、Mapper、Service 三层链路都通了。如果只有 time 正常而 database 字段为 fail,优先检查 MySQL 服务是否启动、密码是否匹配、mooc.sql 是否完整导入,再用 Navicat 单独执行一次SELECT COUNT(*) FROM course;验证连接权限。接口调到返回 ok 后再往前端页面对接,整个 SSM 项目的启动顺序问题基本就排除了。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/13 5:15:37

基于SSM+Vue的高校就业管理系统设计与实现

1. 项目概述高校就业管理系统是当前教育信息化建设中的重要组成部分&#xff0c;它直接关系到毕业生就业数据的精准统计、就业服务的便捷提供以及就业质量的科学评估。基于SSM&#xff08;SpringSpringMVCMyBatis&#xff09;和Vue.js技术栈开发的这套系统&#xff0c;通过前后…

作者头像 李华
网站建设 2026/9/13 5:15:29

GPT6不存在?揭穿AI模型虚假宣传的真相

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/13 5:14:25

SAP ABAP批次特性值读写:CL20N与BAPI_OBJCL最佳实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/13 5:11:19

Hugging Face与NVIDIA合作真相:非收购,而是GPU加速协同实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华