☰
SpringBoot+Vue电影评论系统全栈项目设计与实现解析
2026/10/22 8:27:09 网站建设 项目流程

做电影类全栈项目这几年,SpringBoot + Vue 这套组合是我用得最顺手也最愿意推荐的方案。它不像某些框架那样上来就有一堆概念要学,但胜在功能边界清楚、社区资料多、一个人完全能Hold住前后端,特别适合拿来做课程设计、毕业设计,或者往简历里写一个“能跑通、能讲深、能扩展”的个人项目。这次要拆解的是一套基于 SpringBoot + Vue + MyBatis + MySQL 的电影评论网站管理系统,后台管理用户和电影数据,前台展示电影列表和评论交互,整体代码整理成了可以直接下载运行的完整工程,今天把设计思路、实现细节、运行步骤和我在实际写码过程中踩过的坑全部拿出来聊聊。

这套系统本身并不复杂,核心场景就是:用户注册登录后,浏览电影信息、搜索片单、发表评论、给电影打分、收藏喜欢的影片;管理员进入后台维护电影条目、审核评论、管理用户账号。但麻雀虽小五脏俱全,里面涵盖了用户认证、分页查询、条件搜索、多表关联、冗余字段设计、前后端联调等一堆Web开发的常见考点,每个环节都能往深处问、也能往深处讲。

如果你正在准备Web开发相关的工作面试,或者需要一个“代码质量能讲透、功能演示有亮点、后续还能继续扩展”的完整项目,这篇文章可以帮你少走几条弯路。

1. 项目为何这样搭建:技术选型与模块划分背后的思考

1.1 SpringBoot+Vue组合是当前全栈开发的稳妥答案

先说后端为什么选SpringBoot。这个框架最大的价值在于把Spring原本那套繁琐的XML配置全部自动化了,写接口的时候只需要关注业务逻辑本身。配合Maven管理依赖,一个注解就能启动内嵌容器,部署的时候直接打成可执行包扔服务器上运行,省掉了单独装Web容器这一步。对经常要在本地同时开好几个项目的开发者来说,这个体验上的提升不是一点半点。

再看MyBatis。它和SpringBoot整合后有这样一个优势:SQL语句依旧掌握在自己手里,尤其遇到多表关联查询、动态条件拼装这类场景,写起来非常直观。举个例子,电影和评论是一对多关系,评论和用户是多对一关系,如果让全自动ORM框架去处理这种关联对象的拼装,很容易产生N+1问题,查一遍列表可能带出一堆额外查询。MyBatis允许手写一条带关联查询的SQL,通过resultMap控制结果集的映射,性能和可读性都可控。当然,代价是你得自己维护SQL,但对这类业务逻辑明确的项目来说,这个代价完全是值得的。

前端选了Vue,理由很简单:务实。Vue的上手曲线不是最陡的,但组件化复用能力足够应对信息管理型项目,而且生态里的路由、状态管理、UI组件库、构建工具都非常成熟。做用户端页面也好、做后台管理界面也好,都能找到现成方案直接套用。

这四个技术点组合在一起,刚好形成一条完整的数据链路:用户在浏览器打开Vue页面,页面里的JS代码向后端发送请求,SpringBoot的Controller接收请求后调用Service层处理业务,Service层通过MyBatis的Mapper接口操作MySQL里的数据,再把结果逐层返回给前端渲染。链路清晰,每层职责单一,面试时按这条链路往下讲,非常容易让对方理解。

1.2 电影评论系统的功能边界与用户角色

动手写代码之前,我习惯先把系统拆成两个视角,这样可以明确哪些接口是给谁用的。

普通用户端要提供这些能力:注册、登录、退出、修改个人资料;浏览电影列表,支持按分类、地区、年代筛选,支持关键词搜索;查看电影详情页,包括基本信息、简介、海报、综合评分以及全部评论;发表评论、回复评论、删除自己的评论;给电影打分(1到10分),系统对所有用户评分取平均值;收藏电影,在个人中心查看收藏列表。

管理员端要提供这些能力:登录后台后维护电影分类;管理电影信息,上传海报、填写导演主演简介、设置上映年份和地区;审核评论,对违规内容做隐藏或删除;管理用户账号,查看注册列表、禁用恶意账号。

这套系统没有拆成两个独立工程,而是共用一套后端服务、一套前端页面,通过登录鉴权和路由控制来区分用户看到的界面。这个决策背后的逻辑是:两个角色虽然在UI层面差异很大,但底层数据模型和大部分业务逻辑都是同一套,拆成两个系统意味着重复代码翻倍、维护成本翻倍。正确的做法是把权限字段设计进数据模型,在接口层通过拦截规则控制谁能访问什么。后期想加新角色,只需要扩展角色字段和对应的拦截规则,不需要改架构。

2. 数据库设计与MyBatis集成细节

2.1 数据表设计:从电影到评论的完整链路

数据库我命名为movie_comment。核心表有六张:用户表、分类表、电影表、评论表、收藏表,如果想做后续扩展,还可以单独建点赞表。

用户表 user:

  • id 主键自增
  • username varchar(50) 唯一,登录用
  • password varchar(255) 存加密后的密文,不存明文
  • nickname varchar(50) 昵称,未填时默认用用户名
  • avatar varchar(255) 头像地址
  • role tinyint,0普通用户、1管理员
  • created_time datetime 注册时间

分类表 category:

  • id、name、sort排序号

电影表 movie:

  • id 主键
  • title varchar(255) 电影名
  • cover varchar(255) 海报URL
  • director varchar(100) 导演
  • actors varchar(500) 主演,多个用逗号拼接存储
  • region varchar(50) 地区
  • year int 上映年份
  • duration int 时长,分钟
  • description text 简介
  • category_id int 分类id
  • rating double 综合评分(冗余字段)
  • comment_count int 评论数(冗余字段)

评论表 comment:

  • id 主键
  • movie_id int 关联电影
  • user_id int 关联用户
  • content varchar(500) 评论内容
  • rating tinyint 打分
  • parent_id int,0表示顶层评论,非0表示回复某条顶层评论
  • like_count int 点赞数,简单场景先用字段记录
  • created_time datetime

收藏表 favorite:

  • id 主键
  • user_id int
  • movie_id int
  • created_time datetime
  • 对 (user_id, movie_id) 建唯一索引,防止重复收藏

表结构里有几个点值得展开。第一个是电影表里的 rating 和 comment_count 为什么要冗余?我第一版设计没有这两个字段,每次电影列表页要展示评分,就得去评论表里做AVG聚合统计。数据量小时没问题,数据量涨上去,列表页每条电影都带一次子查询,响应时间会明显飙升。所以后来改成在电影表里直接存综合评分和评论数,用户新增评分时同步更新这两个字段,列表页查询就变成了普通字段查询,速度提升明显。

更新综合评分的公式这样算:新评分结果 = (当前评分 * 当前评论数 + 用户打分) / (当前评论数 + 1),四舍五入保留一位小数。更新后评论数加1。每次用户打分都做一次这个原子更新,而不是把数据库所有评论重新聚合一遍。

第二个是评论表里的 parent_id 设计。做评论功能最容易踩的坑是无限层级回复,A回B、B回C、C回A,这种结构在MySQL里用递归查询非常痛苦,而且在页面上渲染多层嵌套树也麻烦。我采用的是两级结构:顶层评论和顶层评论下的回复。回复的parent_id指向顶层评论的id,查询时先取出该电影所有评论,在内存里按照parent_id组装成评论和回复的树形结构,再一次性返回给前端。大多数电影评论场景这个深度完全够用,而且这个设计给未来做“楼中楼”扩展留了口子——只要把parent语义从“指向顶层”改成“指向任意父级”,再补前端递归渲染即可。

2.2 MyBatis映射与动态SQL的巧用

SpringBoot集成MyBatis走的是starter路线,引入依赖后框架会自动完成核心配置,只需要在Mapper接口上加注解,或者用启动类上的 @MapperScan 扫描整个mapper包。

下面是 application.yml 里的关键配置,我建了一套比较完整的模板,可以直接参考:

server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/movie_comment?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai username: root password: 改成你自己的密码 driver-class-name: com.mysql.cj.jdbc.Driver jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8 mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.movie.entity configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

map-underscore-to-camel-case 这个配置很关键,它让数据库的 created_time 字段自动映射成实体类的 createdTime 属性,不用每个字段都手写手工映射。但注意,如果手写了resultMap,哪怕只写了部分字段,驼峰映射就不再自动生效,容易遇到查询能返回记录但Java对象属性为null的诡异问题。

动态SQL是MyBatis里最值得深入研究的部分,也是电影列表页搜索功能的核心难点。电影列表的筛选项很多:电影名模糊搜索、分类筛选、年份筛选、地区筛选,这些条件可能任意组合出现,也可能一个都没有。如果在Java代码里用字符串拼接SQL,又难看又容易出错,还容易埋SQL注入的雷。在Mapper XML里这样写就干净得多:

<select id="selectMovieList" resultType="Movie"> SELECT id, title, cover, director, actors, region, year, duration, category_id AS categoryId, rating, comment_count FROM movie <where> <if test="keyword != null and keyword != ''"> AND title LIKE CONCAT('%', #{keyword}, '%') </if> <if test="categoryId != null"> AND category_id = #{categoryId} </if> <if test="year != null"> AND year = #{year} </if> <if test="region != null and region != ''"> AND region = #{region} </if> </where> ORDER BY rating DESC, comment_count DESC </select>

注意模糊搜索为什么用 CONCAT('%', #{keyword}, '%') 而不是直接写 '%${keyword}%'。这涉及到MyBatis里两个占位符的区别:${} 是纯字符串替换,传过来的值会直接拼进SQL语句,等于给了用户一个注入通道;#{} 是预编译占位符,参数通过数据库驱动绑定进去,安全得多。建议所有查询都使用 #{},需要动态切换字段名、排序字段这种没法用占位符的场景,才用 ${},并且要加上白名单校验。这个细节面试经常被问到,也是实际开发必须刻进DNA的意识。

3. 后端服务:SpringBoot接口开发的实操要点

3.1 登录鉴权:JWT的应用链路

登录认证是Web项目绕不开的一环,这套系统用的方案是JWT无状态认证。选择JWT而不是传统Session,原因是SpringBoot默认内嵌容器以独立服务方式运行,集群部署时Session需要额外处理会话共享;而JWT把用户信息加密签名后放在令牌里,后端不需要存会话状态,天然适合水平扩展。

先说明密码存储。用户注册时,密码绝不能明文入库,我用BCrypt算法生成加盐哈希。BCrypt最大的特点是同一个密码每次生成的哈希值都不同,但校验时传原始密码进去,它能验证是否匹配。好处是即便数据库泄露,攻击者拿到哈希也无法反推明文密码。

登录流程是:用户提交手机号和密码,后端根据业务逻辑查出用户,BCrypt校验密码匹配,通过后生成一个JWT令牌,令牌里携带userId和role字段,用签名算法加工后返回给前端。前端把token保存在本地,之后每个需要鉴权的请求都在请求头里加 Authorization: Bearer token。后端写一个拦截器,对需要登录的接口路径统一解析token,合法才放行。

拦截器的核心代码逻辑如下:

public class AuthInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token = request.getHeader("Authorization"); if (token != null && token.startsWith("Bearer ")) { token = token.substring(7); Claims claims = JwtUtil.parseToken(token); if (claims != null) { request.setAttribute("userId", claims.get("userId")); request.setAttribute("role", claims.get("role")); return true; } } response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); return false; } }

注册拦截器的时候,需要明确放行哪些路径,比如登录、注册、电影列表、电影详情页,这些接口不登录也能访问;发表评论、收藏、个人中心、后台管理接口必须带token。管理员接口再加一道角色校验,我在Controller方法上用自定义注解标记“需要管理员权限”,拦截器里解析到该注解时再检查role是否等于1,不是则返回无权限。

3.2 评论功能背后的逻辑与并发问题

发表评论的接口,请求参数包含movieId、content、rating,回复场景下还有parentId。功能看着简单,但实现时有几个细节值得单独讲。

第一点是评论内容必须做HTML标签过滤。如果没有处理,用户可以在评论里写入JavaScript代码片段,其他用户加载评论列表时这段脚本就会被浏览器执行,这就是典型的存储型XSS攻击。最省事的方案是引入Jsoup库,用白名单校验的方式清洗内容,只保留纯文本,丢掉所有标签:

String safeContent = Jsoup.clean(content, Safelist.none());

第二点是评论后更新电影表的评分和评论数。前面说过rating是冗余字段,新增评论时要同步更新。更新SQL写成原子操作:

UPDATE movie SET rating = ROUND((rating * comment_count + #{rating}) / (comment_count + 1), 1), comment_count = comment_count + 1 WHERE id = #{movieId}

为什么要把计算公式写进SQL而不是先查回来在Java里计算?因为高并发场景下,两个用户同时打分会同时读到旧值,各自计算后再写回,后写回的一方会覆盖先写回的一方的结果。把计算逻辑放进一条SQL,数据库层面保证了原子性,这个问题就避免了。这是我在一次并发问题排查里总结出来的经验。

第三点是删除评论时的级联处理。如果删掉一条顶层评论,它下面挂着的所有回复如果还在,前端就会渲染出悬空的回复。我用的是先查子评论再一起删除,也就是代码层面做两次删除操作,包在事务里保证要么全删要么全不删。

3.3 电影列表分页查询

电影列表接口的分页,我用的PageHelper插件,它是MyBatis生态里最常用的分页方案。代码里用起来很简洁:

PageHelper.startPage(pageNum, pageSize); List<MovieVO> list = movieMapper.selectMovieList(query); PageInfo<MovieVO> pageInfo = new PageInfo<>(list);

PageHelper的原理是拦截下一条要执行的SQL,自动在后面拼接上LIMIT语句。所以startPage调用之后必须紧接着执行查询,中间不能有其它数据库操作,否则分页会加到错误的SQL上。这是PageHelper最经典的误用场景,也是我踩过的坑。比如有人在startPage和查询之间加了一行日志操作,而日志又去查了数据库,分页效果就会神秘消失。

返回给前端的PageInfo对象里包含了总记录数、总页数、当前页、每页大小和当前页的列表数据,前端直接用这些字段渲染分页组件即可。

4. 前端Vue实现:从页面到交互

4.1 前端工程结构与路由设计

前端这边我用的是Vue 3 + Vite + Vue Router + Pinia。Vite相比老式构建工具最大的优势是启动速度快,开发体验好,也是目前官方推荐的新项目默认方案。

创建工程可以直接用一行命令:

npm create vite@latest movie-front -- --template vue

工程目录我习惯按功能模块而不是按页面类型分:

src/ ├── api/ # 所有后端接口请求封装 │ ├── auth.js │ ├── movie.js │ ├── comment.js │ └── user.js ├── assets/ ├── components/ # 通用组件 │ ├── MovieCard.vue │ ├── CommentItem.vue │ └── Pagination.vue ├── router/ │ └── index.js ├── store/ │ └── user.js ├── views/ │ ├── Home.vue │ ├── MovieList.vue │ ├── MovieDetail.vue │ ├── Login.vue │ ├── Register.vue │ ├── Profile.vue │ └── admin/ │ ├── AdminLayout.vue │ ├── MovieManage.vue │ ├── CategoryManage.vue │ └── CommentManage.vue └── main.js

路由设计上,普通页面和管理后台放在同一套路由体系里,管理后台统一挂在 /admin 下面用子路由嵌套。关键点是通过路由meta标记权限要求:

const routes = [ { path: '/', component: Home }, { path: '/movie/list', component: MovieList }, { path: '/movie/:id', component: MovieDetail }, { path: '/login', component: Login }, { path: '/register', component: Register }, { path: '/profile', component: Profile, meta: { requiresAuth: true } }, { path: '/admin', component: AdminLayout, meta: { requiresAuth: true, requiresAdmin: true }, children: [ { path: '', redirect: '/admin/movie' }, { path: 'movie', component: MovieManage }, { path: 'category', component: CategoryManage }, { path: 'comment', component: CommentManage } ] } ]

路由守卫在跳转前统一拦截检查:

router.beforeEach((to, from, next) => { const userStore = useUserStore(); const isLoggedIn = userStore.isLoggedIn; if (to.meta.requiresAuth && !isLoggedIn) { next('/login'); } else if (to.meta.requiresAdmin && userStore.role !== 1) { next('/'); } else { next(); } });

必须明确一点:前端的路由守卫只是UI层的体验优化,真正的安全校验在后端接口。普通用户用浏览器插件改掉前端状态,直接调管理后台接口,如果后端没做权限校验,一样能拿到数据。所以不要把前端守卫当作唯一防线。

4.2 与后端的数据交互和跨域处理

Axios封装能让每个页面少写很多重复代码。我在 api/request.js 里创建了统一实例,配置基础URL和请求拦截器。

请求拦截器负责自动带token,响应拦截器负责统一异常处理,比如token过期时收到401状态码,就清除本地token并跳转登录页。这个统一处理非常实用,之前我写过不带拦截器的版本,每个接口调用点都要写一遍try-catch,代码重复量很大。

import axios from 'axios'; const request = axios.create({ baseURL: '/api', timeout: 10000 }); request.interceptors.request.use((config) => { const token = localStorage.getItem('token'); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }); request.interceptors.response.use( (response) => response.data, (error) => { if (error.response && error.response.status === 401) { localStorage.removeItem('token'); window.location.href = '/login'; } return Promise.reject(error); } );

关于跨域问题,这是前后端分离项目里最容易让新手卡住的一步,我详细说一遍。前端开发服务器默认跑在8000端口,后端在8080端口,浏览器里两个端口不同就属于跨域请求。解决办法有两个方向。

后端方向是开启CORS配置,允许指定域名来源访问:

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

前端方向是利用Vite开发服务器的代理功能,把 /api 开头的请求转发到后端地址。这种方式在开发环境更常见,而且与生产环境的部署结构一致——生产环境通常用Nginx做反向代理,前后端在浏览器看来属于同一个源,不存在跨域问题。Vite的代理配置:

export default defineConfig({ plugins: [vue()], server: { port: 8000, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } } });

这里有个容易弄混的细节:前端发请求时URL写 /api/xxx,代理服务把请求转发到后端地址 /api/xxx,如果后端Controller里又加了 /api 前缀,最终后端收到的路径就是 /api/api/xxx。我自己习惯的约定是:后端Controller统一写 /api 前缀,前端代理只转发 /api,target保持后端根地址,这样最终请求路径是完整的,不会出现重复前缀。关键是想清楚整个请求链条,而不是死记硬背。

4.3 电影卡片、评论区和收藏功能的前端实现

电影列表页我抽了一个MovieCard组件渲染单张电影卡片,展示海报、标题、类型、评分和简介摘要。评分数字我做了颜色区分,8分以上红色系、6到8分黄色系、6分以下灰色系。这类视觉细节单看不显眼,但整页卡片放在一起会明显提升质感。

电影详情页是全项目功能最密集的页面。从上到下依次是电影基本信息区、评分区、评论区。评分区显示综合评分、评分人数,以及当前用户的打分入口。评论区采用分页加载,每页10条评论,顶层评论下面嵌套它的回复列表,整体按时间倒序排列。看一部电影的评论是低频操作,用传统分页完全够用,不需要上无限滚动那套复杂交互。

发表评论的表单组件用Vue的双向绑定管理内容、评分和回复对象。未登录用户点击“发表评论”时直接跳登录页,登录成功后再自动跳回当前页面。这个小交互的实现方式是:跳转登录时带一个redirect参数,登录页完成后跳转到redirect指定的地址。做完整之后整个评论流程会顺滑很多。

收藏功能的交互上,电影详情页有一个收藏按钮。进入页面时调接口查询当前用户是否已收藏,如果已收藏按钮显示“已收藏”,点击后调用取消收藏接口;未收藏则显示“收藏”,点击后调收藏接口。收藏状态在Pinia的store里统一维护,切页面之后不需要重新查询,减小无效请求。

5. 运行调试与问题排查实录

5.1 本地启动全流程

项目拿到手后,本地完整跑起来的流程我按四步拆解。

第一步,初始化数据库。找到项目附带SQL脚本文件,脚本里有建库语句、建表语句和测试数据。打开MySQL服务后,用数据库管理工具导入脚本,也可以命令行执行:

mysql -u root -p < movie_comment.sql

导入后检查一下六个表是否都建出来了,再随便看一眼 movie 表里有没有测试数据。

第二步,启动后端服务。打开application.yml,把数据库用户名和密码改成自己的本地配置,然后在项目根目录执行:

mvn clean package -DskipTests

或者直接在开发工具里点运行启动类的main方法。如果用开发工具导入项目时,要选Maven工程,让工具自动拉取依赖,第一次拉依赖会慢一些,配置国内镜像源的话通常两分钟内能完成。

第三步,启动前端。在movie-front目录下执行:

npm install npm run dev

启动成功后终端会显示访问地址 http://localhost:8000。

第四步,浏览器打开前端地址,注册普通账号,登录后就能体验浏览、评论、收藏完整流程。想体验管理员功能需要把user表里对应用户的role字段改成1,然后重新登录,导航栏就会多出后台管理入口。

5.2 我遇到并解决过的4个典型报错

报错一:后端启动报错提示无法构建DataSource。

排查思路是:SpringBoot版本升级后常见的一个坑是引入了持久层依赖但没有配置数据源信息。逐项检查application.yml里的数据库连接是否配置、用户名密码是否正确、驱动类是否引入。注意MySQL 8的驱动类名是 com.mysql.cj.jdbc.Driver,老项目里常见的 com.mysql.jdbc.Driver 在新版本驱动里已经移除了。

报错二:接口返回的中文全是乱码。

多数原因是数据库连接串缺少 characterEncoding=utf-8,数据库表默认字符集不是utf8系。检查连接URL参数、数据库字符集和表字符集这三处,MySQL 8默认字符集是utf8mb4,但如果用老版本数据库建表时选了其它字符集,数据读出来就会乱。还有一种情况是idea控制台显示乱码,那是控制台编码问题,和接口返回乱码是两回事。

报错三:浏览器报跨域错误,Vite代理不生效。

这类问题有个特征:浏览器Network面板里看到的请求地址是 http://localhost:8000/api/xxx,但请求没能正确转发。先确认vite.config.js里的proxy配置是否修改,注意修改配置文件后需要重启开发服务器,热更新不会自动生效。再确认后端接口路径和代理匹配,如果前端请求路径与后端Controller路径对不上,也会出现404或转发失败。

报错四:PageHelper分页失效,SQL里没有LIMIT。

原因几乎都是startPage和查询之间有其它数据库操作,破坏了分页插件对下一条SQL的拦截。严格按照 startPage -> mapper查询 -> PageInfo 三行连写的习惯,中间不要夹任何东西。另外确认MyBatis配置里没有关闭插件,PageHelper需要作为插件注册到MyBatis配置里。

这些问题排完,项目基本就能顺畅运行。部署到真实服务器时还需要注意几件事:数据库连接地址改成服务器环境对应的地址;前端执行 npm run build 打包,生成的 dist 目录交给Nginx托管;后端打成jar包后用 nohup 方式后台运行,考虑加一个系统服务脚本保证崩溃重启。

6. 从课程设计到更完整产品的优化路径

6.1 这个项目还能补上哪些短板

系统跑通之后如果站在“能否上线使用”的角度审视,会发现还有几个明显可以优化的方向。

第一个是图片存储。当前电影海报字段存的是URL,开发环境用外部链接或者放在本地资源目录,上线后更好的做法是用独立的对象存储服务,或者干脆让本地上传文件落到独立目录,由Nginx静态映射来访问。这样后端服务与应用文件解耦,扩容时不用迁移数据。

第二个是搜索能力。当前电影搜索用MySQL的LIKE模糊查询,几千条数据没问题,但数据量到几万条以后会有明显性能衰减,而且不支持中文分词。如果想把这个项目作为求职作品向面试官展示,建议补一层更专业的搜索方案,或者至少用MySQL全文索引做一个升级版本,重点体现性能优化能力。

第三个是评论互动能力。当前点赞只是一个计数,没有记录谁点了赞,也无法防止一个用户对同一条评论重复点赞。升级方向是新建一张点赞记录表,存commentId和userId,加上唯一索引保证一个用户只能点一次,同时用事务保证点赞和计数同步更新。

6.2 三个值得动手加的小功能

给这套系统加哪些功能能最快提升完成度?我个人的建议是优先做这三个。

第一,热门电影排行榜。基于评分和评论数做加权排序,可以用最朴素的 rating * comment_count 公式,也可以玩一点数学技巧用评分乘以评论数的对数。关键是想清楚这个排序公式的业务含义,融合了口碑和热度两个维度,上线之后这个功能还能做成独立接口,给首页做展示。

第二,个人中心数据可视化。统计用户发表的评论数、收藏的电影数、给出评分的偏好分布,用图表库画几个简单的柱状图、饼图。简历里写“使用图表库实现用户行为数据可视化”比单纯写增删改查听起来有技术含量得多,而且实现也不复杂,是性价比很高的加分项。

第三,热点数据加缓存。首页推荐区域的热门电影列表,可以用缓存组件缓存起来,设置过期时间,比如10分钟。缓存存在时直接返回,不存在时回源数据库查询再写入缓存。这样一个简单的优化,简历里能写,答辩里能讲,生产环境也能落地,三全其美。

这几个扩展方向做完,项目的深度就会明显提升。我做项目评审时,最看重的不是候选人堆了多少功能,而是能不能讲清楚每个设计背后为什么这样做。如果连“为什么评分字段要冗余”“为什么评论要分页”“为什么SQL要写预编译占位符”都讲不明白,功能堆得再多也经不起面试追问。


最后说点个人感受。这套电影评论系统我前后整理了三版:第一版功能全但代码乱,第二版结构清晰但没考虑性能,第三版才到既能演示又有内涵的状态。最大的经验是,技术选型稳扎稳打比追求新奇重要得多。SpringBoot + Vue + MyBatis + MySQL 这套组合听起来不花哨,但每一层都能往深处讲,每一块都能带出完整的知识体系。如果你正在做类似的系统,别急着往上堆功能,先把代码结构和数据关系想透,遇到报错误就耐心走一遍排查流程。项目本身做完了固然好,但真正值钱的是做项目的过程中建立起来的排查链路和思考方式,面试官能感受到那种“真动手写过”的状态。

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

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

立即咨询