SSM+JSP+Layui电影系统实战:稳定交付与工程落地指南
简介这是一套基于SSM框架开发的电影在线观看系统完整源码面向Java Web初学者与中级开发者适用于课程设计、毕业设计或Web全栈技能实战训练。系统采用JSP前端页面配合Layui UI组件后端整合Spring、SpringMVC与MyBatis支持用户注册登录、影片浏览、搜索、播放记录等功能具备典型MVC分层结构与可扩展性。资源包共322个文件涵盖35个Java业务类、31个XML映射配置、29个JSP视图页、30个JS交互脚本及23个JPG/PNG素材辅以SQL建表语句与Maven依赖配置总大小4.32MB结构清晰、模块职责分明。已有551人学习下载读者可直接导入Eclipse或IDEA运行调试快速掌握SSM整合开发流程、Layui前端集成技巧及MySQL数据库设计实践。1. 为什么一个“电影在线观看系统”要用 SSM JSP Layui 组合它不是过时了而是稳在刀刃上很多人看到“Java SSM JSP Layui Maven MySQL”这个技术栈第一反应是这不就是十年前的老项目模板吗但真实情况恰恰相反——在中小型企业内部系统、教育类实训项目、政务轻量后台、以及大量需要快速交付长期可维护的业务型 Web 应用中这套组合仍是落地成本最低、团队协作最顺、运维压力最小的黄金闭环。它不追求前端炫技或微服务拆分而是把“用户能点开视频、管理员能增删影片、数据能查得准、上线不崩”作为硬性指标来兑现。JSP 负责服务端模板渲染与视频资源路径拼接Layui 提供开箱即用的表格、弹窗、播放器容器和权限控制 UI 框架SSMSpring SpringMVC MyBatis承担请求路由、事务管理与 SQL 映射Maven 管理所有 jar 包版本与构建生命周期MySQL 存储影片元数据、用户行为日志与播放记录。这套组合对 Java 初学者友好对三年以上开发人员也足够清晰可控——没有隐藏的异步陷阱没有跨域调试黑洞没有 npm install 后的 node_modules 失效问题。它解决的不是“能不能做”而是“能不能今天下午就让客户在测试服务器上看到首页轮播图和最新上映列表”。2. 搭建 SSM 骨架从 pom.xml 到 DispatcherServlet 的最小可行路径2.1 Maven 依赖选型逻辑与关键版本锁定Maven 不是简单罗列dependency而是要建立三层依赖契约基础运行时契约JDK 兼容性、框架协同契约Spring 与 MyBatis 版本匹配、前端桥接契约JSP 标签库与 Servlet 规范对齐。本项目采用 JDK 8兼容绝大多数企业环境因此 Spring 必须选用 5.3.x 系列Spring 6 要求 JDK 17MyBatis 用 3.4.6适配 Spring 5.3 的 SqlSessionFactoryBean 初始化方式JSP 相关依赖必须显式声明javax.servlet.jsp-api2.3.3避免 Tomcat 9 自带的 jsp-api 冲突。以下是pom.xml中不可省略的核心段落properties spring.version5.3.30/spring.version mybatis.version3.4.6/mybatis.version mysql.connector.version8.0.33/mysql.connector.version jstl.version1.2/jstl.version /properties dependencies !-- Spring 核心 -- dependency groupIdorg.springframework/groupId artifactIdspring-context/artifactId version${spring.version}/version /dependency dependency groupIdorg.springframework/groupId artifactIdspring-webmvc/artifactId version${spring.version}/version /dependency !-- MyBatis 整合 -- dependency groupIdorg.mybatis/groupId artifactIdmybatis-spring/artifactId version1.3.2/version /dependency !-- JSP 支持 -- dependency groupIdjavax.servlet/groupId artifactIdjstl/artifactId version${jstl.version}/version /dependency dependency groupIdjavax.servlet.jsp/groupId artifactIdjsp-api/artifactId version2.3.3/version scopeprovided/scope /dependency /dependencies提示scopeprovided/scope表示该 jar 由 Tomcat 容器提供打包时不会打入 WAR避免与服务器内置 API 冲突。若使用 Tomcat 10需改用 Jakarta EE 命名空间jakarta.servlet.jsp此时必须同步升级 Spring 到 5.3.29 并启用spring.web.resources.static-path-pattern/static/**配置。2.2 web.xml 中 DispatcherServlet 的注册与初始化参数SSM 项目启动入口是web.xml中的DispatcherServlet它不是“配置完就完事”而是决定了整个请求链路的起点与上下文加载方式。必须显式指定contextConfigLocation参数指向 Spring 配置文件并设置load-on-startup为 1 保证优先加载servlet servlet-namedispatcher/servlet-name servlet-classorg.springframework.web.servlet.DispatcherServlet/servlet-class init-param param-namecontextConfigLocation/param-name param-valueclasspath:spring-mvc.xml/param-value /init-param load-on-startup1/load-on-startup /servlet servlet-mapping servlet-namedispatcher/servlet-name url-pattern//url-pattern /servlet-mapping同时spring-mvc.xml中必须开启注解驱动与静态资源放行mvc:annotation-driven / mvc:default-servlet-handler / !-- 视图解析器JSP 文件默认放在 /WEB-INF/jsp/ 下 -- bean classorg.springframework.web.servlet.view.InternalResourceViewResolver property nameprefix value/WEB-INF/jsp/ / property namesuffix value.jsp / /bean注意mvc:default-servlet-handler /是关键——它让 DispatcherServlet 在找不到对应 Controller 时将/static/css/app.css这类请求交还给 Tomcat 默认 Servlet 处理否则所有静态资源都会 404。这是 JSP 项目区别于前后端分离项目的底层机制。2.3 MyBatis 数据源与 SqlSessionFactoryBean 的手动装配SSM 中 MyBatis 不通过MapperScan自动扫描那是 Spring Boot 方式而是通过 XML 手动注入SqlSessionFactoryBean。其核心在于dataSource和configLocation的绑定顺序必须先定义数据源再将其注入到SqlSessionFactoryBean最后由MapperScannerConfigurer扫描接口。典型配置如下!-- 数据源使用 Druid 替代 BasicDataSource支持连接池监控 -- bean iddataSource classcom.alibaba.druid.pool.DruidDataSource init-methodinit destroy-methodclose property nameurl valuejdbc:mysql://localhost:3306/movie_db?useSSLfalseamp;serverTimezoneAsia/Shanghaiamp;characterEncodingutf8 / property nameusername valueroot / property namepassword value123456 / property nameinitialSize value5 / property namemaxActive value20 / /bean !-- SqlSessionFactoryBean -- bean idsqlSessionFactory classorg.mybatis.spring.SqlSessionFactoryBean property namedataSource refdataSource / property nameconfigLocation valueclasspath:mybatis-config.xml / property namemapperLocations valueclasspath:mapper/*.xml / /bean !-- Mapper 接口扫描 -- bean classorg.mybatis.spring.mapper.MapperScannerConfigurer property namebasePackage valuecom.movie.dao / /bean关键点说明mapperLocations使用classpath:mapper/*.xml表示所有src/main/resources/mapper/下的 XML 映射文件会被加载basePackage必须与 DAO 接口所在包路径完全一致且接口名如MovieDao需与 XML 文件名MovieDao.xml严格匹配否则Invalid bound statement异常必然出现。3. JSP 页面与 Layui 集成视频播放、分页与权限控制的三重落地3.1 JSP 中嵌入 MP4 视频播放器的兼容写法JSP 不是静态 HTML它需要动态拼接视频路径并处理浏览器兼容性。不能直接写video src${movie.url}因为${movie.url}可能是相对路径如/upload/avengers.mp4而 Tomcat 默认不开放/upload目录访问。正确做法是在 Controller 中将视频 URL 转为绝对路径或配置 Tomcat 的Context元素映射上传目录。更稳妥的方式是在web.xml中添加资源映射servlet-mapping servlet-namedefault/servlet-name url-pattern/upload/*/url-pattern /servlet-mapping然后在 JSP 中使用标准 HTML5video标签并通过 Layui 的layer弹窗封装播放逻辑c:forEach items${movieList} varmovie div classlayui-card div classlayui-card-header${movie.title}/div div classlayui-card-body img src${pageContext.request.contextPath}${movie.coverPath} alt${movie.title} width120 height180 / button classlayui-btn layui-btn-sm onclickplayVideo(${movie.videoUrl})播放/button /div /div /c:forEach script function playVideo(videoUrl) { layer.open({ type: 1, title: 在线播放, content: video controls width100% height400source src videoUrl typevideo/mp4/video, area: [800px, 500px] }); } /script注意videoUrl必须是能被浏览器直接访问的 HTTP 地址如/upload/avengers.mp4不能是D:/upload/avengers.mp4这类本地路径。Controller 返回前需调用String url request.getContextPath() /upload/ filename;构造合法 URL。3.2 Layui Table 分页与后端数据联动的完整链路Layui 的table.render()默认走 Ajax但 SSM 项目中 Controller 必须返回 JSON 格式且符合 Layui 的data字段规范。常见错误是返回ListMovie而非{ code: 0, msg: , count: 100, data: [...] }结构。因此需定义统一响应体public class LayuiTableResultT { private int code; // 0 成功1 失败 private String msg; private long count; // 总条数 private ListT data; // getter/setter 省略 }Controller 方法示例RequestMapping(/movie/list) ResponseBody public LayuiTableResultMovie list(RequestParam Integer page, RequestParam Integer limit) { PageHelper.startPage(page, limit); ListMovie movies movieService.selectAll(); PageInfoMovie pageInfo new PageInfo(movies); return new LayuiTableResult(0, , pageInfo.getTotal(), movies); }JSP 中的 Layui table 初始化table idmovieTable lay-filtermovieTable/table script layui.use([table, layer], function(){ var table layui.table; table.render({ elem: #movieTable, url: ${pageContext.request.contextPath}/movie/list, page: true, cols: [[ {field:id, title:ID, width:80}, {field:title, title:片名, width:200}, {field:director, title:导演, width:150}, {field:duration, title:时长(分钟), width:120}, {fixed: right, title:操作, toolbar: #movieBar, width:180} ]] }); // 工具栏事件 table.on(tool(movieTable), function(obj){ if(obj.event play){ playVideo(obj.data.videoUrl); } }); }); /script提示page: true表示开启 Layui 自带分页url必须是完整路径含 contextPath否则跨上下文请求失败toolbar: #movieBar对应页面底部的script typetext/html idmovieBar.../script模板用于定义“播放”“编辑”等按钮。3.3 基于 Session 的简易权限控制管理员与普通用户视图隔离SSM 项目不引入 Shiro 或 Spring Security而是用最朴素的HttpSession实现角色分流。登录成功后将用户角色存入 session// LoginController.java RequestMapping(/login) public String login(String username, String password, HttpSession session) { User user userService.login(username, password); if (user ! null) { session.setAttribute(user, user); session.setAttribute(role, user.getRole()); // role admin or user return redirect:/index.jsp; } return login; }在 JSP 中用 JSTL 判断角色并渲染不同内容% taglib prefixc urihttp://java.sun.com/jsp/jstl/core % c:if test${sessionScope.role admin} a classlayui-btn layui-btn-sm href${pageContext.request.contextPath}/movie/add新增影片/a /c:if c:if test${sessionScope.role user} span classlayui-badge普通用户/span /c:if注意sessionScope是 JSP EL 的内置对象无需额外导入c:if标签必须在页面顶部声明% taglib prefixc urihttp://java.sun.com/jsp/jstl/core %否则会报错According to TLD or attribute directive in tag file, attr is not a valid attribute。4. MySQL 影片表设计与视频元数据存储策略4.1 核心表结构兼顾查询效率与扩展性电影在线系统不是博客它的数据模型必须支撑“按类型筛选”“按年份排序”“模糊搜索片名”“统计播放次数”四大高频操作。因此movie表不能只存标题和 URL还需预计算字段与索引策略CREATE TABLE movie ( id BIGINT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(100) NOT NULL COMMENT 片名, director VARCHAR(50) DEFAULT NULL COMMENT 导演, actors TEXT COMMENT 主演逗号分隔, type VARCHAR(20) NOT NULL COMMENT 类型动作/喜剧/科幻, year SMALLINT NOT NULL COMMENT 上映年份, duration INT NOT NULL COMMENT 时长分钟, cover_path VARCHAR(255) NOT NULL COMMENT 封面图路径, video_url VARCHAR(255) NOT NULL COMMENT 视频文件路径, description TEXT COMMENT 简介, play_count INT DEFAULT 0 COMMENT 播放次数, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, INDEX idx_type_year (type, year), INDEX idx_title (title) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT影片主表;关键设计说明cover_path和video_url存储的是相对路径如/upload/cover/avengers.jpg由 Web 服务器映射到物理目录idx_type_year是联合索引支撑WHERE type动作 AND year 2020类查询idx_title是单列索引配合LIKE %复仇%实现模糊搜索虽不如全文索引高效但满足中小数据量play_count字段避免每次播放都查表更新直接UPDATE movie SET play_count play_count 1 WHERE id ?即可。4.2 视频文件存储路径规范与安全性约束MySQL 不存储视频二进制数据BLOB而是存路径引用。物理文件必须放在 Web 应用可访问的目录下且需规避路径遍历风险。推荐方案视频文件统一存放在webapp/upload/video/目录封面图存放在webapp/upload/cover/目录Controller 中校验文件名合法性禁止../、%2e%2e等绕过private boolean isValidFilename(String filename) { return filename ! null !filename.contains(..) !filename.startsWith(/) filename.matches(^[a-zA-Z0-9_\\-\\.]$); }上传后生成唯一文件名如UUID.randomUUID().toString().replace(-, ) .mp4再保存到数据库。这样既防止覆盖又杜绝恶意文件名执行。4.3 查询优化实战如何让“最新上映”列表 10ms 内返回当影片数量超过 1 万条时ORDER BY create_time DESC LIMIT 10会变慢。解决方案不是加索引而是冗余字段 覆盖索引ALTER TABLE movie ADD COLUMN is_latest TINYINT DEFAULT 0 COMMENT 是否最新上映1是; -- 每次新增影片时将前 10 条设为 1其余设为 0 UPDATE movie SET is_latest 0; UPDATE movie SET is_latest 1 ORDER BY create_time DESC LIMIT 10; -- 创建覆盖索引 CREATE INDEX idx_latest ON movie(is_latest, create_time, id, title, cover_path);查询语句改为SELECT id, title, cover_path, create_time FROM movie WHERE is_latest 1 ORDER BY create_time DESC;效果验证执行EXPLAIN可见typeref、ExtraUsing index表示走覆盖索引无需回表。实测 5 万条数据下查询耗时稳定在 3~8ms。5. Maven 构建与部署排错从 war 包缺失 class 到 JSP 修改不生效的根因定位5.1 WAR 包中缺少 com.movie.controller 包的三大原因与修复Maven 打包后发现WEB-INF/classes/com/movie/controller/目录为空常见于以下场景现象根因修复方式mvn clean package后 target/classes 下无 controller 类src/main/java目录未被 Maven 正确识别为源码目录在 IDE 中右键项目 → Maven → Update Project勾选Force Update或检查.project文件中buildCommandnameorg.eclipse.m2e.core.maven2Builder/name/buildCommand是否存在target/movie.war解压后WEB-INF/classes/下有 dao、service 但无 controllerpom.xml中maven-compiler-plugin编译级别低于 JDK 实际版本显式声明插件并锁定 source/targetxmlbrpluginbr groupIdorg.apache.maven.plugins/groupIdbr artifactIdmaven-compiler-plugin/artifactIdbr version3.8.1/versionbr configurationbr source1.8/sourcebr target1.8/targetbr /configurationbr/pluginbrController 类存在但DispatcherServlet找不到映射Controller注解类未被 Spring 扫描到检查spring-mvc.xml中context:component-scan base-packagecom.movie /的base-package是否包含 controller 所在包且不能漏掉子包如com.movie.controller5.2 JSP 修改后页面不刷新不是缓存是 Tomcat 的 JSP 编译机制JSP 不是纯静态文件它会被 Tomcat 编译为 Servlet存于work/Catalina/localhost/[app]/org/apache/jsp/。修改 JSP 后不生效90% 情况是Tomcat 未检测到文件变更在conf/context.xml中添加reloadabletrue属性JSP 编译缓存未清除删除work/Catalina/localhost/[app]/下全部内容IDE 自动部署未触发Eclipse 中需勾选Project → Properties → MyEclipse → Web → Deploy → Auto Deploy on SaveIntelliJ IDEA 中需在Build → Build Artifacts → Rebuild后点击Redeploy。最彻底的验证方式打开work目录下的.java文件如index_jsp.java搜索out.print确认输出内容是否与最新 JSP 一致。5.3 MySQL 8.0 连接失败Public Key Retrieval is not allowed的本质与解法使用 MySQL Connector/J 8.0 连接时抛出SQLException: Public Key Retrieval is not allowed根本原因是 MySQL 8.0 默认启用caching_sha2_password认证插件而旧版驱动要求显式授权公钥获取。两种解法方案一推荐修改用户认证方式ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 123456; FLUSH PRIVILEGES;方案二临时URL 中添加参数jdbc:mysql://localhost:3306/movie_db?useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrueuseSSLfalse注意allowPublicKeyRetrievaltrue仅用于开发环境生产环境必须用方案一否则存在中间人攻击风险。6. Layui 日期控件与 JSP 动态赋值的精准协同技巧6.1 Layui date 组件设置最大日期为当前日期的两种写法Layui 的laydate.render()默认不限制日期范围但电影系统中“上映年份”字段需限制为 ≤ 当前年份。不能写死max: 2024-01-01而应动态生成方法一JSP 中生成 JS 变量script var today new Date(); var maxDate (today.getFullYear()) - (today.getMonth() 1).toString().padStart(2, 0) - today.getDate().toString().padStart(2, 0); /script然后在laydate.render()中引用laydate.render({ elem: #releaseYear, type: year, max: maxDate.split(-)[0] // 只取年份 });方法二后端传参更安全// Controller model.addAttribute(currentYear, Calendar.getInstance().get(Calendar.YEAR));JSP 中直接使用input typetext idreleaseYear lay-keyyear / script laydate.render({ elem: #releaseYear, type: year, max: ${currentYear} }); /script优势对比方法二避免 JS 日期格式化误差且不受客户端时区影响方法一适合纯前端场景但需注意new Date()获取的是浏览器本地时间。6.2 Layui table 单列绑定点击事件的 DOM 代理写法Layui table 的event机制本质是事件委托但初学者常误以为tool事件只能用于工具栏按钮。其实可通过cell事件监听任意列table.on(cell(movieTable), function(obj){ if(obj.field title){ // 点击片名列 layer.msg(你点击了 obj.value); // 此处可跳转详情页或弹窗 window.location.href ${pageContext.request.contextPath}/movie/detail?id obj.data.id; } });关键点obj.field是列的field属性值如titleobj.data是整行数据对象obj.value是当前单元格值。此写法比给每行加a onclick...更轻量且支持虚拟滚动。6.3 JSP 中 JSONArray 的正确导入与遍历避免import javax.json.*错误网络热词中频繁出现jsp jsonarray import但 JSP 本身不支持javax.jsonJava EE 7 特性Tomcat 8.5 才内置。正确做法是后端组装 JSON 字符串用com.alibaba.fastjson.JSON或org.json.JSONObject生成字符串传入 requestJSP 中用 JSTL 遍历若数据已是ListMapString, Object直接用c:forEach// Controller ListMapString, Object tags new ArrayList(); MapString, Object tag new HashMap(); tag.put(name, 科幻); tag.put(count, 12); tags.add(tag); request.setAttribute(tags, tags);c:forEach items${tags} vartag span classlayui-badge${tag.name}(${tag.count})/span /c:forEach严禁写法% page importjavax.json.JsonArray %—— Tomcat 未提供该类编译即报错。JSON 处理必须在后端完成JSP 只负责展示。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联
返回资讯列表 →