基于Spring Boot+Vue的影评管理系统设计与实现
2026/9/10 17:52:49 网站建设 项目流程

写这篇影评管理系统项目总结的时候,我正好在整理毕设答辩用的演示视频。回看当初从零搭起来的那套代码,感慨挺多的。想当年选这个题目,纯粹是因为喜欢看电影,以为写个“管理电影评论”的系统应该不难。真正动手才发现,影评管理系统比想象中复杂得多——它不是一个简单的增删改查,背后牵扯到用户体系、内容审核、评分统计、前后端交互、权限控制,每一项都能单独拎出来讲半天。

这篇博客就把我当时做毕设的完整思路写出来,从需求分析到数据库设计,从后端核心代码到前端页面交互,再到部署上线和答辩准备。内容尽量还原我当时实际开发的过程,包括踩过的坑,希望能给正在做类似选题或者正在纠结毕设方向的学弟学妹一些参考。这套项目源码我打包放在文末,需要的自取。

1. 需求分析先行:影评系统到底要管什么

很多人做毕设第一件事就是开IDE写代码,这是大忌。我先把需求文档梳理清楚,花了两天时间,后面写代码反而特别顺畅。影评管理系统听起来小众,但仔细拆解以后会发现,它其实就是“用户——内容——管理”三件事,比淘宝商城简单,又比课程设计丰富,难度刚好卡在毕设舒适区。

1.1 用户角色拆解

系统里必须先明确有哪几类人使用,每类人能干什么事。我是这么拆的:

  • 游客:尚未注册登录的身份,可以浏览电影列表、查看公开影评,但无法发布任何内容。游客角色的存在主要是为了扩大浏览入口,也方便答辩时演示“未登录拦截”逻辑。
  • 注册用户:系统的核心内容生产者,可以登录、浏览电影详情、发表影评、对影评点赞、管理自己发布过的内容。这里要注意,注册用户和管理员的权限边界必须先在需求里写清楚,不然写代码的时候很容易乱。
  • 系统管理员:拥有后台管理权限,能对电影信息进行增加、删除、修改、查询,更重要的是对用户提交的影评进行审核。影评不是发了就立即展示,而是先进入“待审核”状态,管理员通过以后才公开可见。

角色拆完之后,我突然想明白了整个系统的核心逻辑——这不是一个单纯写评论的网站,而是一个有“内容管理”属性的平台。所以后面所有功能设计都围绕“内容生产”和“内容合规”两条主线走。

1.2 功能模块划分与优先级

需求到手后,一定要排优先级。毕设时间有限,不可能所有功能都做到尽善尽美。我当时把功能分成“必须做”“应该做”“加分项”三个等级:

优先级功能模块说明
必须做注册、登录、退出用户身份认证,所有内容操作的前置条件
必须做电影列表展示、电影详情查看系统的基础信息流入口
必须做发表影评、我的影评管理核心业务功能,影响影评的数据结构设计
必须做管理员登录、影评审核管理体现“管理”二字,答辩高频提问点
应该做电影管理(增加、修改、删除)管理员维护电影信息的功能
应该做影评点赞增加互动性,让系统不显得单调
应该做用户个人中心展示用户名、注册时间、发表过的影评等
加分项分类浏览、关键词搜索丰富浏览入口
加分项数据统计仪表盘管理员后台展示用户数、影评数趋势

这样一梳理,我就大概知道代码要写哪些部分了。

1.3 业务流程中的状态流转

影评审核这个流程是系统里最有“管理”味的部分,值得认真对待。我当时定义了两个维度:

  • 审核状态:待审核(0) -> 审核通过(1) / 审核驳回(2)
  • 公开状态:公开(1)、隐藏(0)

审核状态和公开状态是两回事。审核通过后默认公开,但是管理员在特殊情况下可以把某条影评“隐藏”,隐藏以后前端列表看不到,但数据还在库里。这个设计在答辩的时候老师很感兴趣,问我“为什么不直接删除”,我回答“保留数据用于统计分析,同时避免用户恶意刷屏影响其他用户浏览,隐藏比删除更容易追溯”,老师点点头。

2. 技术选型与开发环境搭建,别在第一步就翻车

技术选型这块我研究了不少。毕设的传统路线是JSP+Servlet+MySQL,但这个组合写出来的代码维护性差,页面和后端逻辑糊在一起。我最终选择的是Spring Boot + MyBatis-Plus + Vue这套前后端分离的方案,理由有三:

  • 第一,Spring Boot是现在Java后端开发的主流框架,用它会让你在简历上更有的写;
  • 第二,前后端分离开发时,接口责任明确,前端不用等后端写完就能并行开发;
  • 第三,这套技术栈学习资料多,遇到问题搜索引擎基本都能解决,对毕设党极其友好。

2.1 后端项目结构与启动准备

后端我直接用Spring Initializr生成的基础工程,JDK用的1.8,Spring Boot版本选的2.7.x,稳定且资料多。项目结构是这样的:

src/main/java/com/example/moviecomment/ ├── config/ # 配置类,如CORS跨域配置、拦截器注册 ├── controller/ # 控制层,接收前端HTTP请求 ├── service/ # 业务逻辑层,处理核心业务 ├── mapper/ # 数据访问层,继承MyBatis-Plus的BaseMapper ├── entity/ # 实体类,对应数据库表结构 ├── common/ # 通用类,统一返回结果、状态枚举、异常处理 └── Application.java # 启动类

application.yml里关键的配置就几项:数据源、MyBatis-Plus日志、端口号。有一个细节必须提——数据库时区问题。我一开始配置数据库连接串时没有加serverTimezone=Asia/Shanghai,导致查询出来的时间比实际时间早8个小时,排查了好久。后来在jdbc url后面加上?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai才解决。

2.2 前端工程搭建

前端我用Vue CLI创建工程,配合Element UI组件库。前端的核心目录结构:

src/ ├── api/ # 接口封装(axios统一请求管理) ├── router/ # 路由配置(含前端路由守卫) ├── store/ # 全局状态管理(vuex,存用户登录信息) ├── views/ # 页面组件(登录、电影列表、详情、后台管理等) └── main.js # 前端入口

选Element UI是因为它对后台管理系统特别友好,表格、弹窗、表单验证都是现成的。这里提一个实用技巧:登录后的用户信息不要只存在前端store里,刷新页面就丢了,我会同时存一份到localStorage,刷新后从localStorage恢复用户状态。

2.3 前后端联调前置配置

前后端分离开发必然碰到跨域问题。我前端在8080端口跑,后端在8081端口,直接访问必定跨域。我的解决方案是在后端加一个CORS配置类:

@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOrigins("http://localhost:8080") .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("*") .allowCredentials(true); } }

另外前端在axios请求里也配置了withCredentials: true,这样跨域请求才能携带Cookie。同时把axios的基础路径设置成/api,然后在vue.config.js里配置devServer的代理,把/api开头的请求转发到http://localhost:8081。两个方案叠加,开发阶段就没再碰过跨域问题。

注意:上线部署时记得把CORS配置里的allowedOrigins改成实际域名,或者干脆去掉跨域配置、用Nginx反向代理,这比开放跨域更安全。

3. 数据库设计与实现:六张表撑起整个系统

数据库设计是毕设的高分区域。我一开始尝试过只建四张表(用户、电影、影评、管理员),但越写越别扭——点赞记录没地方存。后来扩成六张表:

  • t_user:用户表
  • t_movie:电影表
  • t_comment:影评表
  • t_like:点赞关系表
  • t_admin:管理员表
  • t_category:电影分类表

3.1 用户表与电影表

用户表字段设计:

字段名类型说明
idbigint主键,自增
usernamevarchar(50)用户名,唯一索引
passwordvarchar(255)加密后的密码
nicknamevarchar(50)昵称
avatarvarchar(255)头像地址
roletinyint角色(这里其实可以并入管理员表,但为了扩展性我留了role字段)
create_timedatetime注册时间

这里重点说下密码。明文存密码是绝对不能接受的,毕设也不能这么糊弄。我用的加密方式是MD5加盐。每个用户注册的时候生成一个随机盐值,存进数据库。密码存储格式是盐值 + 密码的MD5值,校验的时候先从库里取出盐值,然后重新拼接计算MD5对比。这样即使数据库泄漏,攻击者也无法直接还原原始密码。

电影表字段设计:

字段名类型说明
idbigint主键
titlevarchar(100)电影名称
directorvarchar(50)导演
actorsvarchar(255)主演
category_idbigint分类ID,外键
covervarchar(255)海报URL
release_datedate上映日期
areavarchar(50)制片地区
synopsistext电影简介,可能比较长
ratingdecimal(2,1)综合评分(由影评的平均分计算得到)

3.2 影评表——整个系统最核心的表

影评表是设计重心,字段也最多:

字段名类型说明
idbigint主键
user_idbigint评论用户ID,外键关联用户表
movie_idbigint评论电影ID,外键关联电影表
ratingtinyint用户给的评分(1-5)
contenttext影评内容
like_countint点赞数,冗余字段,减少实时统计压力
statustinyint审核状态:0待审核 1已通过 2已驳回
is_publictinyint是否公开:1公开 0隐藏
create_timedatetime发布时间
audit_timedatetime审核时间
audit_uservarchar(50)审核人

影评表设计时有个反复考虑的点:用户评分和影评内容是分开的字段,但展示的时候是绑在一起的。这样设计的好处是后续做“评分排行榜”时只要查rating字段即可,不用去parse文中的内容。

3.3 点赞关系表

点赞表主要是防止用户重复点赞:

CREATE TABLE `t_like` ( `id` bigint NOT NULL AUTO_INCREMENT, `user_id` bigint NOT NULL, `comment_id` bigint NOT NULL, `create_time` datetime DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uk_user_comment` (`user_id`, `comment_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

唯一键uk_user_comment保证同一个用户对同一条影评只能点赞一次。当用户再次点“赞”的时候,前端会提示“您已点赞”,而不是再插入一条。我还在点赞接口里做了判断:如果记录已存在就返回“取消点赞”?没有,这里做的是后端拦截。后来我发现完整的交互应该是支持“取消点赞”的,所以接口里处理了两种情况:有记录就删除记录、点赞数减一,没有记录就新增记录、点赞数加一。这个交互功能写在接口注释里,答辩时展示前端效果很加分。

提示:给影评点赞数加一个like_count冗余字段是典型的空间换时间思路。如果不加,每次展示影评列表就得count一次点赞表,数据量大了以后会非常慢。牺牲一个字段的存储空间,换来的是查询速度的提升,这在真实项目中也是常见做法。

4. 后端核心功能实现:每个模块都是一堂课

代码部分,我按功能模块逐个实现,每个模块我都总结一下当时觉得最值得记录的细节。

4.1 注册登录与JWT鉴权

登录认证我最终选择了JWT(JSON Web Token),没有用老式的Session方案。理由主要是前后端分离时Session不好共享,还要考虑跨域Cookie携带问题,JWT把用户信息加密成一个token字符串返回给前端,前端每次请求都在Header里带上,后端拦截器验签即可,天然适合前后端分离架构。

JWT工具类核心就两个方法:

public static String generateToken(User user) { // 使用jjwt库生成,设置主体为用户名,设置过期时间为24小时 return Jwts.builder() .setSubject(user.getUsername()) .claim("userId", user.getId()) .claim("role", user.getRole()) .setExpiration(new Date(System.currentTimeMillis() + 86400000)) .signWith(SignatureAlgorithm.HS512, SECRET_KEY) .compact(); } public static Claims parseToken(String token) { return Jwts.parser() .setSigningKey(SECRET_KEY) .parseClaimsJws(token) .getBody(); }

这里的SECRET_KEY我放在了application.yml配置里,没有硬编码在代码中。答辩时如果老师问“token过期了怎么办”,你可以回答说“前端axios拦截器拦截到401状态后,清除本地存储并跳转到登录页让用户重新登录”——这个回答会显得你考虑问题很周全。

JWT拦截器也是必须的。我的LoginInterceptor只拦截需要登录的接口,比如发布影评、点赞、个人中心相关路径,而电影列表和详情这类公开接口不拦截。拦截器里从请求头取token字段,解析失败则返回401未授权。

4.2 影评发布接口与评分校验

发布影评是系统的核心业务,涉及多个表操作:

  1. 校验用户是否登录(拦截器已做)
  2. 校验评分是否在1-5范围内,内容不能为空且长度不超过500字
  3. 检查该用户是否已经对同一部电影评过分(防止重复评论)
  4. 插入影评记录,状态设为待审核
  5. 更新电影表的综合评分(重新计算该电影所有已通过影评的平均分)
  6. 返回结果给前端

检查重复评论的地方值得特别说一下。我一开始没有加“同一用户对同一电影只能评论一次”的限制,结果测试的时候发现自己能对同一步电影发好几条影评,评分也各自独立,逻辑上很混乱。后来在影评表加了唯一约束uk_user_movie,同时在代码里也查了一遍,双保险。

我在查询的时候用到了MyBatis-Plus的条件构造器,非常方便:

LambdaQueryWrapper<Comment> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(Comment::getUserId, userId) .eq(Comment::getMovieId, movieId); long count = commentMapper.selectCount(wrapper);

4.3 管理员审核影评的完整流程

管理端后端逻辑比用户端多一点,但本质还是CRUD。审核功能的实现:

  • 管理员登录后进入后台管理界面,默认列出所有“待审核”状态的影评
  • 每行影评展示用户、电影、评分、内容、提交时间
  • 操作按钮:通过、驳回
  • 通过:设置status=1、is_public=1,记录审核时间和审核人
  • 驳回:设置status=2,同时将is_public=0,前端不展示

审核通过后,我之前在“发布影评”流程里提到的那步“更新电影评分”也要重新触发——因为新通过一条评论后,电影的平均分需要刷新。所以我在审核通过的方法里调用了refreshMovieRating(movieId)

这里有个小坑:刷新电影评分方法里,查询平均分时要过滤掉非“已通过”状态的评论,这个条件容易丢。我当时第一次实现的时候忘了加status条件,结果把待审核的评分也统计进去了,导致前端展示的评分和列表里实际能看到的影评对不上。

4.4 用户主页与我的影评

用户登录后点击“我的影评”,前端调用后端接口/api/user/comments,后端从JWT中解析出userId,查询该用户的所有影评并按时间倒序返回。每条影评附带电影名称和海报URL,这样用户看到的不只是一堆文字。

这套逻辑本身不难,但要提醒一个点:多表联查时如何优雅地补充关联信息。我在写影评的VO类时,不用频繁join查询,而是在Service层分步查询:

  1. 查影评列表(只查comment表)
  2. 收集所有影评涉及的电影ID集合
  3. selectBatchIds一次性查出所有电影信息
  4. 在内存中拼装成VO返回前端

这样避免了对每一条影评都发起一次电影查询的N+1问题,性能好很多。代码看起来多一些,但数据库压力小。

5. 前端页面与交互逻辑:让影评系统“活”起来

后端写得再好,页面丑也会影响答辩效果。前端的重点我放在几个核心页面上。

5.1 注册登录页面与状态保持

登录页和注册页用Element UI的表单组件,做非空校验。登录成功后,前端把返回的token和用户昵称存到localStorage,同时跳转到首页。

为了防止未登录用户直接访问个人中心,我在前端路由守卫里加了判断:

router.beforeEach((to, from, next) => { const token = localStorage.getItem('token'); if (to.meta.requiresAuth && !token) { next('/login'); } else { next(); } });

5.2 电影列表页与详情页

电影列表页右侧放分类筛选,顶部放搜索框,下方是电影卡片流。分类和搜索都通过el-selectel-input绑定参数,向后端传categoryIdkeyword,后端使用MyBatis-Plus的分页插件返回数据。

电影详情页是整个系统的门面。上半部分是电影海报、导演、演员、简介和当前综合评分,下半部分是影评列表。每个影评卡片显示用户头像、昵称、星级评分、影评内容、点赞按钮和发布时间。信息层级一定要清晰,不然看起来就是一堆文字淹没了重点。

5.3 写影评组件与Star评分组件

观影评列表的时候,页面上有一个“写影评”的按钮,点击后弹出对话框:

  • 选择评分(1-5星),用的是Element UI的el-rate组件
  • 输入影评内容(textarea,限制500字)

提交的接口就是后端那个“发布影评”接口。这里要注意一个体验细节:已经评过分的电影,再次点“写影评”要提示“您已经评论过这部电影”,并直接跳到该用户对此电影的影评位置。这个体验优化很细小,但答辩时老师亲测会觉得你项目做得完整。

5.4 后台管理页面的设计

管理员的电影管理页面:用了el-table渲染所有电影,行内操作有“编辑”“删除”,顶部有“新增电影”按钮。新增/编辑用同一个对话框,表单校验必须有——电影名不能为空。

管理员审核页面:默认Tab显示“待审核”,分页展示待审核影评列表;另一个Tab显示“已通过”,支持按电影名搜索;第三个Tab是“已驳回”,可以查看历史记录。审核通过/驳回按钮就在每行的操作栏里。我把这些数据统计做成卡片放在顶部:待审核数、本周新增影评数、总用户数、总电影数,页面更丰满,也展示了数据统计能力。

6. 代码结构与部署上线,高分毕设的底蕴

6.1 源码架构盘点

这套项目的源码结构如下:

film-comment-system/ ├── backend/ # Spring Boot后端 │ ├── src/main/java │ ├── src/main/resources │ │ ├── application.yml │ │ └── sql/ │ └── pom.xml ├── frontend/ # Vue前端 │ ├── src │ ├── package.json │ └── vue.config.js └── README.md # 项目说明与启动文档

强烈建议在源码里放一个README.md,说明项目背景、技术栈、启动步骤、默认账号密码。这不仅是给答辩老师看的,也是给未来的你回忆用的。我当时还写了一篇简短的“项目部署文档”,部署的时候照着操作就行。

6.2 本地运行与部署

本地运行:后端直接mvn spring-boot:run启动在8081端口,前端npm run serve启动在8080端口。

服务器部署:我用了最稳妥的方案——前端打包成静态文件后交给Nginx托管,后端打包成jar包交给Java运行。

# 前端构建 npm run build # 生成dist目录 # 后端打包 mvn clean package -DskipTests # 生成jar包 # 上传到服务器后启动 java -jar film-comment-backend.jar --spring.profiles.active=prod

Nginx配置里有几个关键点:

server { listen 80; server_name your-domain.com; # 前端静态文件 root /var/www/film-comment/dist; index index.html; # 后端接口反向代理 location /api/ { proxy_pass http://127.0.0.1:8081/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }

这样前端请求/api/xxx时,Nginx自动转发给后端服务,跨域问题彻底消失,后端也不需要再开放CORS。

6.3 答辩前的最终检查

答辩前我做了三件事,非常有必要:

  1. 整理接口文档:用Postman导出接口集合,把每个物理接口的请求方式、参数、返回值写清楚。答辩老师其实不会逐个测接口,但看到你有文档会觉得你很规范。
  2. 准备边界演示用例:比如“未登录用户点发表影评被路由守卫拦到登录页”“重复评论提示已评过分”“管理员驳回后前端列表立刻消失”这几条,演示效果非常突出。
  3. 跑一遍全流程:从注册新用户,到登录、浏览电影、发表影评,再到管理员登录审核、查看数据。要保证这套主流程没有任何报错,最好录个演示视频备用,防止现场网络出问题。

7. 踩过的坑与复盘:帮你少走三个月弯路

回顾整个毕设过程,最有价值的可能不是功能代码本身,而是那几个让人头疼的Bug和如何找到解决方案的过程。

7.1 时间字段差8小时

国产服务器和本地开发环境都遇到过这个问题。原因是MySQL时区与Java应用的默认时区不一致。解决方案是数据库连接串加上serverTimezone=Asia/Shanghai,同时在MySQL里设置default-time-zone = '+08:00'。另外前端拿到的时间字段建议统一用时间戳传回去,或者用createTime字符串格式化,避免时区二次偏移。

7.2 评论内容里的特殊字符

用户在评论里输入<script>标签或表情符号时,前端展示会出问题。表情符号是能正常保存的,前提是数据库连接和表设计的字符集都用utf8mb4;而script标签相关的内容则需要在后端做HTML转义或直接拒绝。我选择在发布评论接口里直接用白名单校验:只允许中文、英文、数字、常见标点、换行。虽然功能上略显激进,但对毕设场景是够用的,也避免了XSS安全类问题。

7.3 列表分页的total不准确

MyBatis-Plus的分页插件,当传入pageNumpageSize时,返回的total是满足条件的总记录数,这个没问题。问题出在我手动在查询条件里追加了status = 已通过的过滤条件,但前端列表页展示的电影评分却来自“所有状态”的平均分,两边数据对不上。后来统一成“过滤条件保持一致”,分数和评论数才一致。

7.4 不要过度设计

我最早把项目架构设计得极其复杂,加上了Redis缓存、RabbitMQ消息队列、Spring Cloud微服务。结果写了两周发现自己根本hold不住,还影响进度。回头删掉那些花架子,用最朴素的方式把功能做扎实,反而越写越有成就感。毕设的核心目的是展示你四年所学,不是展示你能驾驭多高的架构。把CRUD写利索、把流程理清晰、把代码写规范,这个分数就不会低。

影评管理系统说到底是“用户生成内容”模式的典型代表,它比普通的管理系统多了一层“审核”的业务逻辑,又比电商系统少了复杂的订单流程,非常适合作为JavaWeb方向的毕设选题。整个系统做完以后,我对Spring Boot的自动配置原理、MyBatis-Plus的条件构造器、前端Vue的组件通信以及前后端联调的整套流程都有了更深的体会。

如果你也选了这套题目,或者准备往这个方向做,建议你带着自己的思考去改代码——加一个“热门影评排行榜”,做“关注用户动态流”,甚至对接一个外部电影API自动填充电影信息,都是不错的扩展方向。本项目的完整源码我已经整理好了,包含数据库初始化脚本、后端完整代码、前端页面源码和部署文档,需要的朋友在公众号“一点毕设”后台回复“影评管理系统53058”就能获取。

最后分享一个个人体会:毕设最大的收获不是那个“优”的评分,而是过程中培养出的“把一个模糊问题拆解成明确模块”的能力。这种能力在以后真正的工作里,比任何框架和语法都值钱。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询