ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

SpringBoot+Vue+MyBatis+MySQL电影评论网站全栈开发实战

SpringBoot+Vue+MyBatis+MySQL电影评论网站全栈开发实战 做电影评论网站这个项目我前前后后折腾了差不多三周。从最初只是想搞个能跑通的课程设计到后来把用户注册、电影检索、影评发表、评分排行、后台管理全串起来整个过程踩了不少坑也把SpringBoot Vue MyBatis MySQL这条技术链真正吃透了。这套组合在2025年依然是Java全栈入门最稳的搭配没有之一。这个电影评论网站管理系统能解决的核心问题很直接给用户一个看电影、查电影、写影评、看评分的集中地同时给管理员一套对电影信息和评论内容进行后台管理的完整工具。适合准备毕业设计的学生、想积累第一个完整前后端分离项目的初学者以及面试前需要拿一个能讲清楚的项目来练手的Java开发。我把整个项目的架构设计、数据库表结构、后端接口实现、前端页面落地、部署联调和排坑过程全部整理出来这篇内容会特别长但每一段都是实际跑通过的方案不是拿来充数的理论。1. 项目整体架构与设计思路1.1 为什么选前后端分离这套组合先聊架构选择。电影评论网站这种业务形态核心操作是“查电影”和“写评论”读多写少页面交互不算复杂但对响应速度和操作流畅度有一定要求。用前后端分离架构后端只负责提供接口返回JSON数据前端单独运行负责渲染和交互两者通过HTTP协议通信。这样做的直接好处是前端同学可以完全独立开发页面后端只关注接口逻辑两边可以并行推进。技术选型这块我是有对比之后才定下来的。SpringBoot解决的是Java后端环境配置繁琐的问题不用再像传统SSM那样堆一堆XML配置文件一个注解加上自动配置机制就能把项目跑起来。Vue作为前端框架数据双向绑定和组件化开发能很快把页面搭出来而且国内文档和社区资源非常丰富遇到问题基本都能搜到答案。MyBatis是我特意选的持久层框架它对SQL的控制非常灵活像电影列表的多条件筛选这类复杂查询手写SQL比对任何自动化ORM都更清晰可控。MySQL则是免费开源的关系型数据库配合Navicat这类可视化工具管理数据非常方便学生党也不用担心授权费用的问题。前后端分离还有一个实际好处就是部署方式灵活。后端打包成jar包跑在服务器上前端打包成静态文件扔给Nginx托管两边互不影响。我见很多同学喜欢把前端打包后直接扔进后端的static目录里确实能省事但后续如果再想加功能、单独更新前端就会很麻烦所以不推荐。1.2 项目功能模块划分整个系统按角色分为两类使用者普通用户和管理员。用户端功能围绕“看电影 - 写评论”这条主链路设计注册登录后可以浏览电影列表、按分类或关键词搜索电影、查看电影的详情介绍、发表影评、给电影打分、删除自己发布的影评。管理员端则是完全独立的一套后台操作管理电影信息增删改查、管理电影分类、审核或删除不当评论、查看用户列表。为什么功能要这么划分我自己第一次做这类项目时很容易犯的毛病是功能设计的太多结果每个都做不深。电影评论网站的核心价值就两个点电影信息能不能展示得清晰完整评论互动能不能做得流畅顺滑。所以我把精力集中在这两个核心点上其他的像个人中心、收藏列表这类边缘功能全部砍掉。项目目录方面后端的目录结构我觉得可以模仿SpringBoot官方推荐的包划分方式controller层只负责接收请求和返回结果service层处理业务逻辑mapper层专门写数据库操作。前端用Vue脚手架创建项目views放页面组件components放可复用组件router配置路由api目录统一封装对后端接口的请求。这样的结构一开始看起来好像多了一些文件夹但真正跑起来之后维护成本比其他乱写一气的项目要低得多。1.3 核心流程梳理整个系统最核心的流程是“用户发表影评”。这个流程涉及用户登录状态校验、电影是否存在校验、评论内容入库、电影评分更新四个环节。我把它单独列出来是有原因的评论功能涉及多张表的联动操作是最容易出事务问题的场景。用户从前端进入电影详情页写完影评内容后点击发布前端带着token和电影ID、评分、评论内容向后端发起请求。后端接收到请求后先通过拦截器校验token是否有效再从数据库查电影是否存在接着把评论数据插入评论表最后根据新评论的评分重新计算该电影的平均分并更新电影表。整个过程中的任意一步失败都需要保证数据不能处于半更新状态所以必须在service方法上开启事务。这个流程拆开看每一步都不复杂但把它们串起来需要想清楚数据表之间的关系。这也直接决定了我后面设计数据库表结构时的思路把评论和电影评分设计成联动更新的方式而不是在电影表里单独维护一个评分字段却不管数据来自哪里那样数据很容易出现不一致。2. 数据库设计与核心功能拆解2.1 数据表结构设计数据库设计是这类项目最容易走弯路的地方。我第一版设计时把用户、电影、分类、评论各建了一张表但后来发现评论表需要一个字段关联用户和电影评分也要跟着评论一起记录于是又加了一个score字段。最终定下来五张核心表用户表user、电影表film、分类表category、评论表comment、管理员表admin。其中user表的核心字段包括id主键自增、username用户名、password密码加密存储、nickname昵称、avatar头像地址、create_time注册时间。film表字段比较多包括id、title电影名称、cover封面图地址、director导演、actors主演、category_id分类ID关联分类表、description剧情简介、release_date上映日期、score平均评分、status上下架状态。category表就简单了id和name两个字段。comment表是整个系统数据交互最频繁的一张表我贴一下实际建表SQL供参考CREATE TABLE comment ( id int(11) NOT NULL AUTO_INCREMENT, film_id int(11) NOT NULL COMMENT 电影ID, user_id int(11) NOT NULL COMMENT 用户ID, content text COMMENT 评论内容, score decimal(2,1) NOT NULL DEFAULT 8.0 COMMENT 用户评分, like_count int(11) DEFAULT 0 COMMENT 点赞数, create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 发表时间, PRIMARY KEY (id), KEY idx_film_id (film_id), KEY idx_user_id (user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;索引加到film_id和user_id上是因为评论列表的查询条件基本都是以电影ID查所有评论或者查某个人发表过的所有评论这两个字段被查询的频率极高。如果前期不加索引数据量小的时候感觉不出来等评论过万后查询速度会肉眼可见的下降这点一定要提前注意。2.2 表关系与数据联动逻辑五张表之间的关系用一句话概括用户和电影之间是多对多关联通过评论表作为中间桥梁电影和分类是多对一关系一部电影只能属于一个分类一个分类下可以有多部电影。评论表同时外联用户表和电影表这决定了评论数据查询时至少需要用到两次联表查询。实际业务中电影列表页需要展示每部电影的封面、名称、分类名和平均评分这时候单查film表是不够的需要关联category表取出分类名称。电影详情页需要展示评论列表且每条评论要显示发表人的昵称和头像这时候就需要关联user表。这些关联查询我全部放在了MyBatis的XML里手写SQL后面会详细讲怎么写。这里特别提醒一点评论表的content字段类型我用了text而不是varchar。原因很简单varchar默认最大长度是255个字符有时候够用但用户写一条几百字的影评就会触发数据超出最大长度报错。text类型可以存更多内容虽然查询时无法走索引但评论内容本来就不需要作为查询条件所以这个取舍是对的。2.3 核心功能拆解清单把整理好的功能模块列出来方便对照开发进度前台用户端注册登录用户注册时检查用户名是否重复登录成功签发token电影列表分页展示全部电影支持按分类筛选、按关键词模糊搜索、按评分排序电影详情展示电影完整信息、所属分类、平均评分评论发布登录状态下发表影评、打分并更新电影平均分我的评论查看自己发表过的评论支持删除后台管理端电影管理新增电影、编辑电影信息、上架下架、删除分类管理增加分类、修改分类名、删除分类评论管理查看全站评论、删除违规评论用户管理查看注册用户列表禁用恶意用户提示功能拆解一定要在编码前完成每个功能里涉及的表、接口、页面都写清楚开发过程中就不会出现“做着做着不知道接下来干什么”的情况。3. 后端核心实现详解3.1 SpringBoot项目初始化与配置创建后端工程我用的是Spring Initializr选SpringBoot 2.7.x版本搭配Java 8。为什么不用SpringBoot 3.x主要是3.x强制要求Java 17很多学校的机器和学习资料还停留在Java 8而且MyBatis和Druid连接池等组件在2.7.x下的兼容性问题更少对新手更友好。pom.xml里需要引入的核心依赖有spring-boot-starter-webWeb支持、mybatis-spring-boot-starterMyBatis整合、mysql-connector-javaMySQL驱动、lombok简化实体类代码、druid-spring-boot-starter数据库连接池、spring-boot-starter-validation参数校验、jjwt生成和解析token。关于token机制我多说一句现在比较老的教程还在用session方案前后端分离后session天然不适用因为前端可能是手机端也可能是浏览器端后端不可能都维护住会话。用JWT方案用户登录成功后后端签发一个带有效期的token以后每次请求前端都带上这个token后端解析校验即可无状态也天然支持跨域。application.yml配置是项目跑起来的关键重点看一下数据源和MyBatis配置server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/film_comment?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse username: root password: 123456 type: com.alibaba.druid.pool.DruidDataSource mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.filmcomment.entity configuration: map-underscore-to-camel-case: truemap-underscore-to-camel-case这个配置极其重要。数据库里的字段命名习惯是下划线风格比如create_timeJava实体类属性命名习惯是驼峰风格比如createTime。开了这个配置后MyBatis在查询结果映射到实体类时会自动把下划线转成驼峰省去了大量resultMap手写映射的工作。我记得第一次做项目时没开这个配置查询出来的对象一直有字段是null排查了很久才发现是映射问题希望后面的人不要再踩这个坑。3.2 MyBatis通用实现与动态SQL实体类用lombok的Data注解就能省去所有getter和setter的繁琐代码但要注意lombok在编译期会自动生成方法如果IDE没有安装lombok插件会报错找不到方法。这属于前置问题提前把插件装好。Mapper层接口我习惯按模块划分UserMapper、FilmMapper、CategoryMapper、CommentMapper。每个Mapper接口对应一个XML文件放在resources/mapper目录下XML的namespace必须对应接口的全限定名这个错了启动时会直接报错。动态SQL是MyBatis最强大的地方我用电影列表的多条件筛选来举例。需求是用户可以选择某一个分类也可以输入关键词搜索片名还可以按照评分排序这三个条件任意组合这时候用if标签动态拼接查询条件select idselectFilmList parameterTypemap resultTypeFilm select f.*, c.name as categoryName from film f left join category c on f.category_id c.id where if testcategoryId ! null and f.category_id #{categoryId} /if if testkeyword ! null and keyword ! and f.title like concat(%, #{keyword}, %) /if /where if testorderByScore order by f.score desc /if /select这里有几个细节值得注意where标签会自动帮你去掉第一个条件前面多余的and这个设计很贴心。left join比内连接合适因为如果某部电影没有关联分类内连接会把这条数据丢掉而左连接能保留电影信息最多分类名为null。like的模糊查询推荐用concat拼接而不是直接写死%包围因为直接写%#{keyword}%会被MyBatis当成字符串字面量解析匹配不出来任何数据这个坑是新手高发区。3.3 评论发布的事务处理评论发布是整个后端最核心的接口它同时操作了comment表和film表。业务逻辑要求先查电影是否存在并是上架状态然后插入评论记录最后根据该电影所有评论的平均分更新film表的score字段。这三个操作必须全部成功或全部失败比如评论插入成功但更新平均分失败就会导致电影评分一直不准确所以service方法上必须加Transactional注解。SpringBoot中只需要引入spring-boot-starter-jdbc在方法上标注Transactional即可容器会自动开启数据库事务。具体实现可以分成两步来计算平均分先从comment表里查这部电影所有评分的平均值再update到film表。用聚合函数实现的SQL是这样select avg(score) from comment where film_id #{filmId}关于事务我不想多讲理论但有一个实际经历值得分享。第一次实现时我忘记在方法上加Transactional测试时故意在插入评论参数里写了一个过长的content结果MyBatis插入失败抛了异常但平均分更新的语句本身不受影响于是出现了电影平均分变了而评论却没有进去的诡异现象。排查了一下午才意识到是事务缺失所以后面每写一个涉及多表操作的service方法我都会第一时间检查有没有事务注解。3.4 JWT登录鉴权和拦截器配置JWT登录实现起来就是生成token和校验token两个动作。用户登录成功后我用用户的id和username作为载荷加上过期时间我设置的是24小时通过jjwt生成一个字符串token返回给前端。问题在于后端如何校验每次请求里的token。我用SpringBoot拦截器实现写一个LoginInterceptor实现HandlerInterceptor接口在preHandle方法里从请求头取出token字段用jjwt解析校验解析失败直接返回401。然后把解析出来的用户ID放入request的attribute里后续controller可以从request里直接拿到当前登录用户ID不用再把用户ID通过前端传过来。拦截器注册时需要配置放行规则。登录接口、注册接口、电影列表接口、电影详情接口这些不需要登录就能访问所以必须放行。但发表评论、删除评论、后台管理这些接口则需要全部拦截。用代码写就是注册拦截器时用addPathPatterns指定拦截路径然后excludePathPatterns把公共接口排除掉。这里要特别小心如果配置错了拦截顺序很可能出现登录接口也被拦截导致前端完全无法登录的情况。4. 前端页面落地实战4.1 Vue项目搭建与路由规划前端我用Vue2 Vue Router Element UI这套组合虽然不是Vue3但Vue2对新手更友好生态成熟Element UI的组件直接就能用页面效果也不差。创建前端项目用官方脚手架Vue CLI命令vue create film-comment-web安装依赖时勾选Router和CSS预处理器即可。装好基础框架后先规划路由而不是先写页面。路由配置我分为两块一块是用户端的首页/、电影列表/films、电影详情/films/:id、登录/login、注册/register、我的评论/my-comments。一块是后台管理的后台布局路由/admin下挂电影管理/admin/films、分类管理/admin/categories、评论管理/admin/comments、用户管理/admin/users。路由为什么要单独规划因为后台管理的页面布局和前台的完全不同后台是一个侧边栏加一个主内容区而前台是header加中间内容。这种布局差异如果混在同一个路由层级里就不容易管理。我采用嵌套路由来处理/admin路由下挂一个AdminLayout组件里面放侧边栏和内容区域再通过 把子路由的内容渲染到内容区域。Vue Router还有一个常用功能是路由守卫我在全局前置守卫里加了token判断实现的效果是访问需要登录的页面比如我的评论、后台管理时如果本地没有token直接跳转到登录页。后台管理还需要额外判断当前用户角色是否为管理员因为普通用户的token是不带管理员标志的这个判断写起来很简单思路要理清楚。4.2 核心组件封装与页面实现前端页面里复用性最高的组件是电影卡片组件FilmCard。列表页每个电影都是一张卡片包含封面图、电影名、分类标签、平均评分。这个组件接收一个film对象作为prop点击卡片跳转到对应详情页。封装成组件的好处是首页电影推荐、列表页、搜索结果页都能用不用每个页面都重复写一套HTML。首页的设计我做了一个轮播图展示推荐的电影封面用了Element UI的Carousel组件。轮播图下面放三个Tabs分别展示最新上架、高分推荐、热门评论。这三个数据都来自后端不同接口页面挂载时并行请求三个接口等数据到了再渲染。这里需要注意异步的时序问题用Promise.all一次性等三个接口全部返回避免页面出现某个Tab数据未加载完而其他Tab已经渲染出来的情况。电影详情页是功能最密的一个页面顶部是电影基本信息区封面、片名、导演、主演、简介右边大号字体显示平均评分。往下是评论区分两部分登录用户可以写影评并打分评分用Element UI的rate组件点击星星输入1到10分的整数下方是历史评论列表每条评论展示用户头像昵称、评论内容、打分、点赞数和发表时间。如果当前登录用户就是这条评论的作者显示删除按钮。写评论的提交逻辑这里有个小细节前端把评分和评论内容一起提交给后端后端先插入评论再更新电影的平均分。前端收到成功响应后需要做两件事清空当前评论表单重新拉取评论列表和电影详情评分。很多新手在提交成功后就只清空表单导致用户看到新评论并不能马上出现在列表中体验会比较差。4.3 Axios封装与跨域解决方案前后端分离后前端调用后端接口必然遇到跨域问题。开发环境中我采用Vue CLI自带的代理方式在vue.config.js里配置module.exports { devServer: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } }这样前端请求/api/films时实际是转发到后端http://localhost:8080/api/films浏览器觉得你是同源的就不会触发跨域限制。这种方式只在开发环境有效部署到生产环境后一般用Nginx反向代理来完成同样的转发逻辑。开发时用代理还有一个好处后端地址只需要写相对路径/api/xxx部署上线几乎不用改前端代码。Axios请求需要统一封装我在src/api目录下建了一个request.js文件创建axios实例、设置baseURL为/api、请求拦截器里从localStorage取token并加到请求头、响应拦截器里统一处理错误状态码。后端返回401时清掉本地token并跳转登录页。这个封装做好了每个页面组件里调用接口的代码就非常清爽只需要调用api方法然后处理返回数据。5. 部署联调与常见问题排查5.1 从本地联调到打包部署整个项目从前端联调到最终可部署运行经历的阶段大概是后端单测接口用Postman- 两个服务同时本地启动联调 - 前端打包静态文件 后端jar包部署。联调阶段最常见的问题其实就是跨域上面配置好代理之后基本不存在。真正麻烦的是接口报错比如404和500。404多半是接口路径写错了前端调的地址和后端controller的映射路径对不上还有可能是后端接口加了/api前缀而前端没带。500则大部分是后端代码异常需要看后端控制台堆栈日志。所以我强烈建议联调阶段一前一后两个控制台都开着窗口前端报错看浏览器F12的Network面板后端看控制台日志基本能很快定位问题。打包部署的时候后端用mvn clean package命令打包target目录下会生成一个jar文件。生产环境启动命令是java -jar film-comment-0.0.1.jar建议用nohup方式后台运行并指定日志输出文件。前端打包命令是npm run build生成的dist目录是一个纯静态文件集可以直接交给Nginx托管。Nginx同时承担两个职责托管前端静态页面以及把/api开头的请求转发给后端服务配置大概是这样server { listen 80; server_name localhost; location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080; } }try_files那句很关键因为Vue是单页应用前端路由切换不会真的让浏览器发起页面请求但用户手动刷新或直接输入网址时浏览器会向服务器请求对应路径比如/films/1。Nginx如果找不到这个文件就会返回404try_files可以把所有不存在的路径都重新指向index.html让Vue Router接管并渲染对应页面。5.2 常用功能源码级排查jar包反编译参考这里单独讲一个小技能很多人拿到别人的项目源码或者拿到一个jar部署包但丢了源码时会很头疼。实际上SpringBoot打出来的jar包是可以用工具反编译回来的虽然不能100%还原原始代码但拿来看核心逻辑、对照参考是完全够用的。我常用的是jd-gui这款工具操作流程分三步先把jar包用解压工具打开SpringBoot的jar包结构是BOOT-INF/classes下面是我们的class文件BOOT-INF/lib下面是第三方依赖jar包。只需要把BOOT-INF/classes这个目录里的class文件拖到jd-gui里它就能直接反编译成Java源码。反编译出来的代码虽然变量名、注释会丢失但方法结构、SQL映射、逻辑判断都能看清楚对于理解实现思路足够了。如果是看MyBatis的XML文件class文件是看不到的MyBatis的XML被打包在BOOT-INF/classes/mapper目录下直接解压就能拿到这个不涉及反编译是明文文件。不过这里要提醒一句反编译别人项目的源码只能用来学习和自我提升如果需要在此基础上修改发布是要确认授权情况的。我更建议把反编译当作“看不懂的地方就参考一下”的辅助手段尽量基于自己写的代码做开发。5.3 高频问题排查速查表把项目开发中最可能遇到的几类问题统一整理成表格问题现象常见原因处理方式后端启动失败提示数据库连接失败MySQL未启动url中时区配置不对密码错误确认MySQL服务已启动url中加serverTimezoneAsia/Shanghai核对账号密码前端登录后刷新页面就掉登录状态token只存在内存变量中页面刷新内存被清空token存localStorage请求拦截器每次从localStorage读查询电影列表分类筛选条件不生效前端参数名和后端POJO属性名不一致前端统一用categoryId传参避免大小写混用插入评论后电影评分没有更新service方法未加Transactional在方法上补上Transactional注解接口返回500后端报SQL语法错误MyBatis XML中SQL写错在application.yml配置mybatis.configuration.log-impl打印SQL日志图片无法显示图片地址是绝对路径或跨域图片使用相对路径或配置静态资源映射打印SQL日志这个技巧我要重点强调在MyBatis配置里加上mybatis: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl启动后每次执行SQL都会在控制台打印完整的SQL语句和参数排查SQL写错、参数绑定失败这类问题会快非常多。正式上线前可以关掉这个日志避免信息过多杂乱。5.4 让项目再进一步的可扩展点如果做完这套基础版本后还想往上延伸有几个方向在现有架构上可以平滑扩展。第一个是引入Redis做热门电影缓存像首页的高分推荐、最新电影列表这些访问频繁的数据从Redis里读可以显著减轻数据库压力。第二个是给评论增加回复功能这只需要在comment表上增加一个parent_id字段查询评论列表时通过子查询把回复拼进去前端再做一层嵌套展示。第三个是引入MinIO对象存储来处理电影封面和海报上传比直接把图片存在本地磁盘更适合真实场景。第四个是给电影接入m3u8格式的预告片播放前端用video.js配合hls.js插件就能播放这也是一些播放器场景的常见需求。每个扩展点都不需要对现有代码做大改动这也得益于最初架构时预留了controller-service-mapper三层结构的余地。真正好的练手项目做完能跑通只是第一步能在跑通的基础上做增量扩展才会把一个项目真正吃透。6. 创作完成最后分享一点个人体会这个项目做完之后我最大的感受是能解决问题比会用新框架重要。SpringBoot Vue MyBatis MySQL这个组合每一项单独拎出来都不算难学但把它们串起来做完整项目中间会有大量“不是不会而是不知道问题出在哪”的时刻。数据库表要不要加索引、事务注解忘没加、前端跨域配置写错位置、路由守卫拦截了公共接口这些坑每个都真实存在也只有亲手踩过才会记得住。我也把整个项目的源码结构按后端、前端、数据库脚本三个部分归档好配合这篇博文的步骤任何人只要照着配置环境、启动MySQL、建库导入SQL、改数据源密码、安装依赖跑前后端就能把这个电影评论网站完整复现出来。后续如果再有人问我毕业设计或者练习项目怎么做我一定还会推荐这套方案因为它是经过验证的、真正能从0跑通到1的路线。
返回列表