1. 项目背景与核心价值
电影评论网站是当下互联网内容生态的重要组成部分。作为一个典型的Web应用,它需要处理用户生成内容(UGC)、实时交互、数据关联查询等核心需求。传统单体架构在应对这类场景时往往面临扩展性差、前后端耦合度高的问题。
这个项目采用SpringBoot+Vue的前后端分离架构,实现了以下核心价值:
- 技术栈组合合理:SpringBoot提供稳健的后端服务,Vue实现动态前端交互,两者通过RESTful API解耦
- 完整功能闭环:从用户注册登录到评论发布、电影信息管理,覆盖典型业务场景
- 生产级代码规范:包含异常处理、日志记录、安全防护等企业级开发要素
- 可扩展性强:模块化设计便于后续添加推荐算法、社交功能等扩展
2. 技术选型与架构设计
2.1 后端技术栈解析
SpringBoot 2.7.x 作为基础框架,主要优势在于:
- 自动配置:通过starter依赖快速集成MyBatis、Redis等组件
- 内嵌Tomcat:简化部署流程,开发阶段可直接运行
- Actuator端点:提供应用健康监控能力
MyBatis 3.5.x 作为ORM层选择,相比Hibernate:
- SQL可控性强:适合需要精细优化查询的场景
- 动态SQL能力:通过
/ 等标签灵活构建查询 - 与SpringBoot生态集成度高
MySQL 8.0 作为关系型数据库:
- 采用InnoDB引擎,事务支持完善
- 为评论表建立全文索引,优化搜索性能
- 配置连接池(HikariCP)控制数据库连接资源
2.2 前端技术方案
Vue 3.x 组合式API带来以下优势:
- Composition API:逻辑关注点更好组织
- Vite构建工具:开发阶段热更新速度极快
- Pinia状态管理:替代Vuex的轻量级方案
Element Plus UI组件库选择考虑:
- 表单组件丰富:适合后台管理系统开发
- 主题定制方便:通过SCSS变量快速调整样式
- TypeScript支持完善
2.3 系统架构图
code复制[前端层]
Vue.js应用
├── 用户界面
├── 状态管理
└── Axios HTTP客户端
[网关层]
Nginx反向代理
├── 静态资源托管
└── API路由转发
[后端层]
SpringBoot应用
├── Controller:REST端点
├── Service:业务逻辑
├── Mapper:数据访问
└── Entity:持久化对象
[数据层]
MySQL数据库
├── 用户表
├── 电影表
└── 评论表
3. 核心功能实现细节
3.1 用户认证模块
采用JWT(JSON Web Token)实现无状态认证:
java复制// JWT工具类核心方法
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
return Jwts.builder()
.setClaims(claims)
.setSubject(userDetails.getUsername())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.signWith(SignatureAlgorithm.HS512, SECRET)
.compact();
}
// Spring Security配置
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated()
.and()
.addFilter(new JwtAuthenticationFilter(authenticationManager()))
.addFilter(new JwtAuthorizationFilter(authenticationManager()));
}
前端处理方案:
javascript复制// axios请求拦截器
instance.interceptors.request.use(config => {
const token = localStorage.getItem('token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
})
// 登录后处理
const handleLogin = async () => {
const res = await loginApi(formData)
localStorage.setItem('token', res.data.token)
router.push('/')
}
3.2 电影评论功能
数据库设计关键点:
sql复制CREATE TABLE `comment` (
`id` bigint NOT NULL AUTO_INCREMENT,
`content` text COLLATE utf8mb4_unicode_ci NOT NULL,
`user_id` bigint NOT NULL,
`movie_id` bigint NOT NULL,
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FULLTEXT KEY `ft_content` (`content`),
KEY `idx_movie` (`movie_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
后端分页查询实现:
java复制@GetMapping("/comments")
public PageResult<CommentVO> getComments(
@RequestParam Long movieId,
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer size) {
PageHelper.startPage(page, size);
List<Comment> comments = commentMapper.selectByMovieId(movieId);
PageInfo<Comment> pageInfo = new PageInfo<>(comments);
List<CommentVO> vos = comments.stream()
.map(this::convertToVO)
.collect(Collectors.toList());
return new PageResult<>(pageInfo.getTotal(), vos);
}
3.3 电影管理后台
Vue组件关键逻辑:
vue复制<template>
<el-table :data="tableData" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55"></el-table-column>
<el-table-column prop="title" label="电影名称"></el-table-column>
<el-table-column prop="year" label="上映年份"></el-table-column>
<el-table-column label="操作">
<template #default="scope">
<el-button @click="handleEdit(scope.row)">编辑</el-button>
<el-button type="danger" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</template>
<script setup>
const loadData = async () => {
const res = await getMovieList({
page: currentPage.value,
size: pageSize.value
})
tableData.value = res.data.list
total.value = res.data.total
}
</script>
4. 安全防护与性能优化
4.1 SQL注入防护
MyBatis中禁止使用${}进行参数拼接:
xml复制<!-- 错误示范 -->
<select id="findByTitle" parameterType="String" resultType="Movie">
SELECT * FROM movie WHERE title = '${title}'
</select>
<!-- 正确做法 -->
<select id="findByTitle" parameterType="String" resultType="Movie">
SELECT * FROM movie WHERE title = #{title}
</select>
针对奇安信扫描报告的处理:
- 所有用户输入参数进行正则校验
- 使用PreparedStatement执行SQL
- 对XSS攻击进行过滤:
java复制public String cleanXSS(String value) {
return StringEscapeUtils.escapeHtml4(value);
}
4.2 缓存策略设计
采用Redis缓存热门电影数据:
java复制@Cacheable(value = "movies", key = "#id")
public Movie getById(Long id) {
return movieMapper.selectById(id);
}
@CacheEvict(value = "movies", key = "#movie.id")
public void updateMovie(Movie movie) {
movieMapper.updateById(movie);
}
配置说明:
properties复制# application.properties
spring.cache.type=redis
spring.redis.host=localhost
spring.redis.port=6379
spring.cache.redis.time-to-live=3600s
4.3 前端性能优化
- 路由懒加载:
javascript复制const routes = [
{
path: '/movie/:id',
component: () => import('../views/MovieDetail.vue')
}
]
- 图片懒加载:
vue复制<template>
<img v-lazy="movie.poster" alt="movie poster">
</template>
<script setup>
import { VueLazyload } from 'vue-lazyload'
app.use(VueLazyload, {
preLoad: 1.3,
attempt: 3
})
</script>
5. 开发环境搭建指南
5.1 后端环境准备
- JDK 11+ 安装验证:
bash复制java -version
# 输出应包含"11.x.x"
- Maven配置阿里云镜像:
xml复制<!-- settings.xml -->
<mirror>
<id>aliyunmaven</id>
<mirrorOf>*</mirrorOf>
<name>阿里云公共仓库</name>
<url>https://maven.aliyun.com/repository/public</url>
</mirror>
- IDEA中配置Lombok插件:
- 安装Lombok插件
- 开启注解处理:Settings → Build → Compiler → Annotation Processors
5.2 前端开发环境
- Node.js 16+ 安装:
bash复制nvm install 16
nvm use 16
- VSCode推荐插件:
- Volar (Vue 3官方支持)
- ESLint
- Prettier - Code formatter
- Vue VSCode Snippets
- 解决跨域问题开发配置:
javascript复制// vite.config.js
export default defineConfig({
server: {
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true
}
}
}
})
6. 部署方案与上线准备
6.1 后端打包与运行
- 生成可执行JAR:
bash复制mvn clean package -DskipTests
- 生产环境启动命令:
bash复制nohup java -jar \
-Dspring.profiles.active=prod \
-Dserver.port=8080 \
movie-comment.jar > app.log 2>&1 &
- 关键启动参数说明:
- -Xms512m -Xmx1024m:JVM内存配置
- -Dspring.config.location:外部配置文件路径
- -Dlogging.file.name:日志文件位置
6.2 前端构建与部署
- 生产环境构建:
bash复制npm run build
- Nginx配置示例:
nginx复制server {
listen 80;
server_name example.com;
location / {
root /var/www/movie-comment;
try_files $uri $uri/ /index.html;
}
location /api {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
}
}
- 启用Gzip压缩:
nginx复制gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
7. 常见问题排查手册
7.1 MyBatis映射问题
症状:查询返回null但SQL能查到数据
排查步骤:
- 检查实体类字段名与数据库是否一致(驼峰转下划线)
- 确认@Column注解或resultMap配置正确
- 查看MyBatis日志:logging.level.org.mybatis=DEBUG
7.2 Vue响应式失效
典型场景:数组更新后视图不刷新
解决方案:
javascript复制// 错误做法
this.items[0] = newValue
// 正确做法
this.items.splice(0, 1, newValue)
// 或使用Vue.set
this.$set(this.items, 0, newValue)
7.3 事务不生效问题
可能原因:
- 方法不是public
- 同类方法调用不走代理
- 异常类型非RuntimeException且未指定rollbackFor
正确示例:
java复制@Transactional(rollbackFor = Exception.class)
public void updateMovie(Movie movie) throws BusinessException {
// ...
}
8. 项目扩展方向建议
- 电影推荐功能:
- 基于用户历史行为的协同过滤
- 使用Redis的Sorted Set实现实时排行榜
- 社交化扩展:
- 用户关注关系
- 评论点赞/回复功能
- WebSocket实时通知
- 数据分析模块:
- 使用ELK收集用户行为日志
- 生成电影热度趋势报表
- 微服务改造:
- 将用户服务、电影服务拆分为独立模块
- 使用Spring Cloud Alibaba实现服务治理
实际开发中我发现,分页查询的性能对用户体验影响很大。针对MySQL大数据量表,推荐使用"游标分页"替代传统LIMIT分页:
sql复制-- 传统分页(深度分页性能差)
SELECT * FROM comment WHERE movie_id = ? ORDER BY create_time DESC LIMIT 10000, 20
-- 优化方案(基于上次查询的最后ID)
SELECT * FROM comment
WHERE movie_id = ? AND id < ?
ORDER BY create_time DESC
LIMIT 20
