
1. 项目整体设计与技术选型为什么用SSM做影评站1.1 项目背景与核心需求做在线影评网站说难不难说简单也真不简单。我接到这个项目的时候甲方需求很明确要一个能展示电影信息、用户能登录后写影评、打分、收藏电影的完整平台管理员还要能管理电影分类、审核评论、查看用户情况。说白了就是一个典型的内容管理系统加上用户交互模块。这种需求在课程设计、毕业设计甚至一些小企业的内部项目中都非常常见所以我把整个开发过程整理出来给正在做同类项目的朋友一个参考。从标题就能看到这个项目走的是SSM技术栈也就是Spring、Spring MVC、MyBatis三件套。这套组合在Java Web开发里属于经典中的经典虽然现在Spring Boot已经很流行了但SSM能帮你把底层原理吃得更透——手动配置事务、手动管理SqlSession、手动写XML映射文件每一次配置都是一次对框架机制的复习。实际跑下来我对Spring IOC容器、AOP切面、MyBatis动态SQL的理解比之前光看教程要深刻得多。这类影评平台的核心里面有两条线一条是用户看到的包括注册登录、浏览电影、查看详情、写影评、打分另一条是管理员看到的包括电影信息维护、评论审核删除、用户管理、数据概览。两套逻辑都要落地到同一个数据库里而SSM恰好能把这种分层的业务逻辑处理得很干净。Controller接收请求、Service处理业务、Mapper操作数据库职责清晰调起Bug来也好定位。1.2 技术选型SSM框架组合的理由选SSM而不用更时髦的Spring Boot很多人觉得是老古董但做这个项目恰恰需要这种老古董带来的掌控感。Spring Boot自动配置虽然省事但遇到问题的时候你很难说清楚数据源是怎么初始化的、事务是怎么代理的。而在SSM里一切都是显式的——spring-mvc.xml、spring-mybatis.xml、web.xml三个配置文件把整个应用的骨架撑起来每个Bean的创建、每个切面的织入点、每个Mapper的扫描路径全都清清楚楚。Spring在SSM里管什么管对象。所有Service、Controller、DAO的实例都由Spring容器统一创建和管理用到哪个就注入哪个。Spring MVC管什么管请求路由和页面跳转。前端发来的HTTP请求由DispatcherServlet分发到对应Controller方法处理完再返回视图。MyBatis管什么管数据库操作。写SQL查询再把结果集自动映射成Java对象。举个例子用户登录这个最简单的功能在SSM里的完整链路是这样的浏览器提交用户名密码→DispatcherServlet拦截到请求→根据RequestMapping找到LoginController的login方法→方法调用UserService的login方法→UserService调UserMapper的selectByUsername方法→MyBatis执行XML里写的SQL语句→结果逐层返回最终视图渲染出登录成功或失败的页面。这条链路环环相扣每一步都有明确的类和方法对应排查问题的时候能顺着调用栈一路看下去这就是SSM最大的价值——结构清晰可追溯性强。2. 数据库设计与核心表结构解析2.1 实体关系梳理影评平台的数据模型核心实体其实就那几个用户、电影、影评、电影分类再加上收藏关系。实体之间最关键的关联是一个用户可以写多条影评一条影评只属于一个用户一个电影可以有多条影评一条影评只针对一个电影一个用户还可以收藏多部电影。这就是典型的一对多和多对多关系落到数据库设计上我们需要建五张表其中收藏关系通过中间表来实现。我当时先用ER图把所有实体和关系画出来再转成表结构。这个习惯强烈推荐保留——直接上手建表很容易漏掉外键关系后面写关联查询时才发现缺字段返工成本特别高。画ER图的工具用免费的即可我平时用draw.io拖拽几下就能完成关键是思路清晰之后再动手。设计原则就一条宁可多建一张表也不要把所有数据塞在一张表里。比如电影和分类如果直接把分类名写在电影表里那以后改个分类名就要批量UPDATE所有电影记录数据冗余和一致性问题会把人折磨疯。拆成两张表电影表存category_id分类表存分类名用外键关联问题自然解决。2.2 核心表字段设计要点先说说用户表t_user字段基本就是老几样id、username、password、nickname、avatar、role、create_time。特别注意两点第一password存的是MD5加密后的密文绝对不允许明文入库一旦数据库泄露用户在所有网站上的密码都会遭殃这个底线不能破第二role字段我用int类型0表示普通用户1表示管理员判断权限时直接比较数值比字符串效率高也不会出现大小写不一致的问题。电影表t_movie是信息量最大的表字段包括id、title、cover_url、director、actors、category_id、description、release_date、avg_score、status。这里的avg_score字段需要单独说一下——很多新手会把评分直接算出来存到这里但更好的做法是先建一张影评表每次用户打分时实时计算平均值再UPDATE到avg_score字段。这样页面展示排行时不需要跑聚合函数直接按avg_score排序就行响应速度快很多。更新时机就选在用户提交影评之后事务里同时插入影评记录并更新电影平均分保证数据一致性。影评表t_review是整个平台最核心的业务表字段有id、movie_id、user_id、content、rating、like_count、status、create_time。status字段用来做评论审核0待审核、1已通过、2已驳回管理员权限就在这里体现。为什么需要审核因为影评是公开内容如果不做任何过滤垃圾广告、敏感词、剧透内容会直接展示给所有用户体验极差。我见过不少项目偷懒不做审核结果上线没几天就被恶意灌水搞崩了所以这个字段最少要留出来。收藏表用关系型中间表t_favorite字段只有id、user_id、movie_id、create_time联合唯一约束加在(user_id, movie_id)上防止重复收藏。五张表之间的关系在MyBatis映射文件里通过resultMap配置关联查询一对多、多对一都能处理得很顺畅。关于索引我建议在t_review表的movie_id和user_id上建联合索引因为这是高频查询条件在t_movie表的avg_score上建普通索引排行查询会快很多。数据量少的时候感觉不出来但一旦影评数据过万有无索引的查询速度差别是数量级的。3. 核心功能模块的实现细节3.1 用户注册登录与会话管理用户模块是整套系统的大门体验好坏直接影响用户留存。注册流程我用的是经典三步校验用户名是否重复、MD5加密密码、插入用户记录。校验和插入之间理论上存在并发问题同时注册同一个用户名可能双双通过校验解决办法是在设计表的时候给username加唯一索引数据库层面兜底比代码层面判断可靠得多。登录这块我用HttpSession来管理用户状态。用户登录成功之后把用户ID和用户名塞进session后续每个Controller方法里需要判断是否登录时直接从session里取取不到就跳转到登录页。为了让代码更优雅我写了一个自定义拦截器HandlerInterceptor在preHandle方法里统一做登录校验白名单放行登录页、注册页、电影列表等不需要登录就能访问的路径其他请求一律拦截。这样每个Controller方法里就不用重复写session判空逻辑了。这里有个小坑值得提醒session的有效期和浏览器关闭策略密切相关默认30分钟无操作会被服务器回收。如果用户写了半天的影评提交时才发现session过期那种挫败感足以让他卸载你的应用。我采用了一种比较简单的解决方案——前端用Ajax统一捕获所有返回401或302且跳转逻辑异常的响应弹出登录状态已过期请重新登录确认后带着当前页面URL跳转登录页登录成功后再跳回来。虽然不能解决根本问题但能大幅度减少用户的无谓操作。3.2 电影展示与分页检索电影列表页是所有用户访问量最大的页面性能优化显得格外重要。我用MyBatis的分页插件PageHelper一行代码就能实现物理分页PageHelper.startPage(pageNum, pageSize)放在Mapper查询方法之前即可插件会自动拦截接下来的SQL并追加LIMIT子句同时返回一个Page对象里面有total、pageNum、pageSize等分页信息。检索功能支持三种维度按电影标题模糊搜索、按分类筛选、按评分排序。MyBatis的动态SQL在这里发挥了大作用比如模糊搜索的SQL可以写成这样select idselectMoviesByCondition resultMapBaseResultMap SELECT * FROM t_movie where if testtitle ! null and title ! AND title LIKE CONCAT(%, #{title}, %) /if if testcategoryId ! null AND category_id #{categoryId} /if /where if testsortField ! null and sortField ! ORDER BY ${sortField} DESC /if /selectwhere标签会自动处理首条条件前面的AND关键字if标签按需拼接条件既灵活又安全。特别注意排序字段用${}而不是#{}因为排序字段是表字段名不能被预编译参数占位符替代但这样也就引入了SQL注入风险——绝对不能让用户自由传入字段名必须是后端固定枚举值做白名单校验后再拼接。电影详情页要展示的信息很密集基本信息、简介、平均分、所有影评列表。这里需要做表关联查询一条SQL同时查出电影信息和分类名影评部分单独查出来循环展示。关于封面图片我建议把图片文件上传到服务器指定目录数据库只存URL路径这样的好处是数据库体积不会越来越大图片加载也可以通过nginx做静态资源加速不用走Tomcat的应用层。3.3 影评发布与评分机制写影评是这个平台最重要的用户行为因为它同时涉及影评表插入和电影评分更新。我把它放在一个事务里加上Transactional注解任何一步抛出异常都会整体回滚。Transactional public void addReview(Review review) { // 1. 插入影评记录 reviewMapper.insert(review); // 2. 重新计算这部电影的平均分 Double avgScore reviewMapper.selectAvgScoreByMovieId(review.getMovieId()); Movie movie new Movie(); movie.setId(review.getMovieId()); movie.setAvgScore(avgScore); movieMapper.updateScore(movie); }评分机制这里有一个设计细节需要说明我用的是1到10分的整数评分滑动条控件在前端展示用户选择后显示对应星标。为什么不用经典的5星制因为10分制契合国内多数影评平台豆瓣也是十分制用户对分数的感知更精确评分区分度更高不会出现一群电影扎堆在8分以上的情况。当然这个完全看产品需求如果甲方明确要五星制改一下前端展示和评分范围校验就行后端逻辑不受影响。影评列表展示时除了内容正文还可以显示每条影评的点赞数。点赞功能我用了一张独立的like表用户点过赞之后状态码从0变1变回0表示取消。这里最需要注意的是防止重复点赞我通过数据库唯一约束(user_id, review_id)来保证同一用户针对同一条影评只能有一条记录再用乐观锁配合点赞数更新避免并发操作时数字丢失更新。3.4 管理员后台与评论审核管理员后台是另一个独立的功能模块权限控制要和前台严格区分。我的方案是管理员登录后session里的role值为1后台的每个请求在拦截器里同时校验是否登录以及role是否为1不满足就跳转403页面。后台首页展示统计数字卡片用简单的SELECT COUNT来统计用户总数、电影总数、影评总数用平均分排行找出评分最高的10部电影用最近影评列表做动态刷新。评论审核功能是管理员最频繁的操作。待审核列表把status为0的评论全部查出来管理员一条条看觉得没问题就点通过敏感内容就点驳回。驳回的时候还可以填原因用户下次登录会看到自己的影评被驳回以及原因反馈这种提示机制能有效引导用户发言规范。批量操作我用了复选框加批量更新Vue前端收集所有选中的ID存到数组里通过Ajax传给后端后端用foreach拼接动态SQL做批量UPDATE。MySQL默认是允许这种跨多条记录的更新的只要在JDBC连接URL里加上allowMultiQueriestrue就可以如果用的是MyBatis的foreach标签生成多条UPDATE语句那就要确保数据库驱动支持批量执行。4. 开发环境搭建与调试部署全流程4.1 环境准备与版本选型这个项目需要的开发环境说复杂不复杂说简单也需要稍微准备一下。我列一张表你照着配就行工具/软件版本建议说明JDK1.8SSM项目的标准配置太新的JDK版本可能会有兼容问题IDEIntelliJ IDEA 2022社区版足够用别花钱买旗舰版Maven3.6依赖管理和项目构建Tomcat8.5或9.0Servlet容器注意9.0之后包名有变化MySQL5.7或8.08.0需要调整驱动包和时区配置Navicat任意版本图形化操作数据库也可以用MySQL WorkbenchJDK建议用1.8原因很简单SSM项目最活跃的年代就是Java 8的时代网上能搜到的绝大多数技术问答、配置教程、踩坑案例都是基于JDK 8的遇到问题你能更高效地找到答案。用JDK 11或17不是不行但Spring老版本、Tomcat老版本、MyBatis老版本是否兼容就得自己排查了纯属给自己找麻烦。4.2 从零配置Maven与IDEA项目结构Maven的核心作用就是管依赖。在pom.xml里引入spring-webmvc、mybatis、mybatis-spring、druid连接池、jstl、jackson-databind这些核心依赖后Maven会自动把依赖的依赖也拉下来完全不用你手动去各大网站下载jar包这一套流程已经非常成熟了。建议在IDEA里设置一下Maven的镜像源如果用阿里云私服否则首次加载依赖会慢得让人怀疑人生。项目结构我习惯用Maven的标准布局这也是SSM项目的标配src ├── main │ ├── java │ │ └── com.example.movie │ │ ├── controller │ │ ├── service │ │ ├── mapper │ │ ├── entity │ │ └── interceptor │ ├── resources │ │ ├── mapper │ │ ├── jdbc.properties │ │ ├── spring-mvc.xml │ │ ├── spring-mybatis.xml │ │ └── mybatis-config.xml │ └── webapp │ ├── WEB-INF │ │ ├── web.xml │ │ └── views │ └── static │ ├── css │ ├── js │ └── images └── test └── javaentity层放数据库表对应的JavaBeanmapper层放接口和XML映射文件service层放业务逻辑接口和实现类controller层接收请求并返回视图。分层的价值在排错时体现得最明显页面数据不对先看Controller有没有传给视图正确参数参数正确但SQL报错去Mapper XML里看SQL语句整个排查链路非常清晰。配置文件里有一个地方特别容易踩坑那就是数据库连接信息。Spring在xml里加载jdbc.properties里面配置driver、url、username、password。MySQL 8.0的数据库驱动换成com.mysql.cj.jdbc.Driver连接URL必须加serverTimezoneAsia/Shanghai参数否则会报时区错误。用MySQL 5.7的话驱动用com.mysql.jdbc.Driver不加时区参数也能跑起来。这两个版本混着来的话报错信息能让人一头雾水排查半天发现只是版本不匹配。4.3 本地调试与线上部署的完整流程调试阶段最常用的手段就是断点调试和日志输出。IDEA的Debug模式启动Tomcat后在Java代码行号旁点一下就能设断点运行到那一行会自动暂停然后可以逐行查看变量的值。这个方法排查空指针异常和参数传递问题特别好用。配合log4j2日志框架在Service层的入口和出口各打一条日志记录入参和出参线上出问题的时候翻日志比盲猜效率高太多了。本地调试通过之后部署上线就是把项目打成WAR包放到Tomcat的webapps目录下。在IDEA里选择Build → Build Artifacts → Build或者用Maven的package命令会自动在target目录下生成war包。然后把war包丢到服务器的Tomcat/webapps目录启动Tomcat它会自动解压并部署。这里我要重点聊聊部署过程中的一个经典坑——数据库连接失败。本地连的是本地MySQL服务器连的是服务器的MySQL两者可能在版本上、字符集上、用户权限上都不一样。部署前一定要在服务器上测试一下应用服务器是否能连通数据库服务器尤其要注意MySQL 8.0默认的认证插件是caching_sha2_password老版本的JDBC驱动可能不认识这时候需要在数据库执行ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY password来修改认证方式或者换新版的JDBC驱动。前端静态资源加载不出来也是高发问题。CSS、JS、图片加载不出来的原因十有八九是项目发布路径不对。如果Tomcat部署后的应用路径是http://xxx:8080/movie/那静态资源的路径就要写成${pageContext.request.contextPath}/static/css/style.css绝对不能写绝对路径/static/css/style.css。前者的contextPath会自动拼接上/movie后者会直接去服务器根目录找自然就404了。5. 常见问题与排查技巧实录5.1 数据库连接与中文乱码问题第一个高发问题是数据库连接失败最典型的报错是Access denied for user rootlocalhost原因基本就是账号密码错误或者用户没有远程访问权限。本地连不上检查密码服务器上连不上检查user表里的host字段是否允许非本机IP访问。另外防火墙也要排查MySQL默认端口3306如果被防火墙拦着怎么连都白搭。中文乱码是另一个巨恶心的坑。从浏览器看到的是你好写进数据库变成了ä½ å¥½这一串乱码问题的根源在于字符集不一致。解决方案分三处进行代码层面确保项目所有文件都是UTF-8编码IDEA右下角能看编码格式连接层面在JDBC的URL里加上characterEncodingutf8数据库层面建库时指定DEFAULT CHARSETutf8mb4utf8mb4比utf8多了生僻字和表情符号的支持写影评时万一用户用了emojiutf8mb4能正常存储和显示utf8会直接报错。Tomcat层面也要配一下在server.xml的连接器里加上URIEncodingUTF-8属性否则GET请求带中文参数时会出现乱码。POST请求的中文乱码可以通过在web.xml里配置CharacterEncodingFilter过滤器来解决设置强制请求和响应都使用UTF-8。这是一个全局过滤器必须在第一个位置注册否则可能因为过滤器链顺序问题导致后面的过滤器拿到的是乱码数据。5.2 前端与后端联调的常见报错前后端联调时最让人抓狂的就是跨域问题和JSON解析问题。跨域问题只在前后端完全分离部署时会遇到。如果前端页面跑在http://localhost:8080后端接口跑在http://localhost:9090浏览器的同源策略会直接拒绝请求。解决方案分三种第一最简单粗暴的用Nginx做反向代理把不同端口映射到同一个域名下前端感觉不到跨域的存在第二后端在响应头里加Access-Control-Allow-Origin: *谁都能跨但安全性差第三用Spring MVC拦截器统一管理跨域配置。我这个项目用的是前后端写在一起的单体架构所以这一块踩坑不多如果是前后端分离项目跨域问题是绕不开的第一座大山。JSON解析报错最常见的是HttpMessageNotReadableException大概率是前端传的JSON格式不对或者字段类型匹配不上。比如后端接收的是带Date类型的字段前端传来的是字符串2024-05-01Jackson反序列化时如果不指定格式就会报错。解决办法是在对应字段上加上JsonFormat(pattern yyyy-MM-dd HH:mm:ss)注解或者在Jackson配置里设置全局日期格式。5.3 部署上线的隐藏雷区与性能优化部署上线之后还有几个隐藏雷区我几乎每次做SSM项目都会碰到在这里统一说一下。第一个是MySQL连接超时问题。MySQL默认的wait_timeout是8小时如果应用在8小时内没有访问数据库连接会被MySQL服务端强制断开。这时候应用第一次访问数据库就拿了一个失效的连接直接报Communications link failure。解决办法在Druid连接池配置里把testWhileIdle设为truetimeBetweenEvictionRunsMillis设为60000让连接池每分钟检测一次空闲连接是否有效无效就踢掉。也可以直接把MySQL的wait_timeout调大但应用层面的检测更稳妥不依赖数据库配置。第二个是内存溢出问题。Tomcat默认的JVM内存分配比较保守用户量上来之后容易OOM。这个项目里影评列表如果一次性加载全量数据很容易把内存撑爆。代码层面要用分页查询限制每次加载的数据量JVM层面在Tomcat的catalina.sh里调整一下参数-Xms512m -Xmx1024m -XX:MaxPermSize256m根据服务器的物理内存大小合理分配。第三个是SQL注入问题。SSM框架天然防注入的地方在于MyBatis的#{}是预编译占位符传入的参数只作为值绑定不会拼进SQL结构里。但前文提到排序字段用${}的情况以及使用字符串拼接SQL的场景都留下了注入漏洞。有个常识性建议永远不要相信前端传来的任何参数哪怕它在隐藏域里也不行。排序字段先枚举白名单再拼接进去模糊搜索用#{keyword}而不是% ${keyword} %。6. 这份项目资源的完整内容与使用建议标题里提到这个项目附带程序源码数据库调试部署开发环境实际拿到手之后大概包含这么几块IDEA里可以直接打开的完整项目源码数据库的建库建表SQL脚本以及一份Word格式的项目论文。论文的撰写格式是按照课程设计或毕业设计的标准来的包含绪论、需求分析、系统设计、数据库设计、系统实现、系统测试这么几大章节一万字以上框架搭得很完整直接拿来改成你自己的语言风格就能用。这里我必须强调一点——论文一定要自己改不要直接交原封不动的版本。你拿到手的项目和你的课程要求、学校模板、个人语言习惯都可能有差异把这些差异补上论文才算真正属于你自己的。我之前见过不少同学因为偷懒直接交原版导致查重不过、被判定抄袭最后折腾半天才解决得不偿失。调试部署这一块也会配一份操作手册大概包括用IDEA导入项目、配置Maven、修改jdbc.properties里的数据库连接信息、导入SQL脚本、启动Tomcat、用浏览器访问系统这些步骤。跟着操作手册走一遍基本20分钟就能把项目跑起来。需要注意的点是手册里的连接信息or默认密码不一定是你的环境能用的改成本地MySQL的用户名和密码即可。建议的学习路径是先把项目跑起来看看到底长什么样然后顺着一个功能追代码比如用户写影评从JSP页面追到Controller再追到Service再追到XML里的SQL最后再对照论文看系统设计的思路这样能在最短时间内把SSM框架的完整调用链路吃透。很多人拿到项目就直接改代码交作业等于把最有价值的源码学习机会浪费掉了。7. 扩展与迭代的思路影评平台这种项目后续想继续迭代的话可以扩展的方向其实挺多的。从技术角度说可以引入Redis做缓存把电影列表和排行榜这种高频读取的热点数据放到Redis里数据库压力能降一个量级可以引入Elasticsearch做全文检索现在的SQL模糊搜索在数据量过万后性能会明显下降换成ES的倒排索引搜索响应速度那是质的飞跃可以引入消息队列做影评的异步审核用户提交影评后立刻返回成功审核结果通过回调通知用户体验更流畅。从产品角度说可以增加用户的个人主页展示他写过的所有影评、收藏过的电影、获得的点赞数可以增加电影的推荐系统基于用户的历史评分做协同过滤给他推荐可能感兴趣的电影可以增加影评的点赞评论功能让用户之间能互动起来可以增加热门话题或者榜单功能做运营活动。就算完全不做技术扩展把这个项目重写一遍Spring Boot版本也是一个很好的练习。你会发现有Spring Boot帮你搞定一堆自动配置之后整个项目的代码量能少三分之一唯一要操心的是业务逻辑本身。先把SSM版本吃透再上手Spring Boot你会理解Spring Boot到底自动化了哪些东西而不是只会无脑套模板我之前就是这么一步步学过来的。最后再分享一个小技巧给这个项目写测试数据的时候不要用abc123或者测试这种毫无意义的数据去真实影评网站扒一批真实电影的信息和影评进去整个系统的展示效果完全不一样。真实数据能让你更直观地发现分页长度、列表样式、评分排序这些地方存在的问题我用这个方法找出过不止三五个当时觉得差不多行了的UI细节问题。