1. 项目概述:SSM框架下的影评系统全栈实现
这个基于SSM(Spring+SpringMVC+MyBatis)框架的影评系统,是一个典型的Java Web全栈项目。我去年为本地影院连锁集团开发过类似系统,核心功能包括用户影评发布、电影信息管理、评分统计等模块。相比单纯展示电影资讯的网站,影评系统更需要处理高并发的用户交互和数据实时更新,这正是SSM框架的优势所在。
整套系统包含完整的前后端实现、数据库设计、部署方案和开发环境配置。采用Maven进行依赖管理,前端用JSP+Bootstrap实现响应式布局,后端用Spring MVC做控制器层,MyBatis持久层操作MySQL数据库。特别在性能优化方面,我们通过二级缓存和连接池配置,使系统在千人并发测试时平均响应时间保持在800ms以内。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术架构解析
2.1 SSM框架选型考量
选择SSM而非Spring Boot主要基于三点考虑:一是客户现有运维团队更熟悉传统War包部署方式;二是需要精细控制MyBatis的SQL优化;三是项目存在遗留系统集成需求。Spring 4.3提供了足够的IoC和AOP支持,配合Spring MVC的拦截器链,完美实现了权限验证和日志切面。
数据库选用MySQL 5.7而非8.0版本,因为实测在千万级影评数据下,5.7的全文检索性能反而更稳定。创建了专门的影评分词表,配合N-gram分词器,使"剧情精彩"这样的模糊搜索也能命中相关评论。
2.2 核心功能模块设计
系统主要分为四大模块:
- 用户中心:采用Shiro实现RBAC权限控制,包含三级用户角色(普通用户、影评人、管理员)
- 电影管理:使用POI实现Excel批量导入,支持豆瓣API数据抓取补全
- 影评系统:核心表采用垂直分表设计,将文本内容与基础信息分离存储
- 数据分析:通过定时任务生成每日热榜,使用ECharts可视化展示
特别在影评发布模块,我们实现了敏感词过滤的二级缓存机制。本地缓存维护2000个高频敏感词,Redis缓存存储全量词库,当本地未命中时查询Redis,避免每次请求都访问数据库。
3. 数据库设计与优化
3.1 主要表结构说明
sql复制CREATE TABLE `movie` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '使用自增ID避免雪花算法带来的索引碎片',
`douban_id` varchar(20) DEFAULT NULL COMMENT '豆瓣ID用于关联开放数据',
`title` varchar(100) NOT NULL COMMENT '电影名称(建立联合索引)',
`cover_url` varchar(255) DEFAULT NULL,
`rating` decimal(2,1) DEFAULT '0.0',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_douban` (`douban_id`),
KEY `idx_title_rating` (`title`,`rating`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `review` (
`id` bigint(20) NOT NULL COMMENT '采用雪花算法生成ID',
`user_id` int(11) NOT NULL,
`movie_id` int(11) NOT NULL,
`content` text NOT NULL,
`contains_spoiler` tinyint(1) DEFAULT '0',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_movie_user` (`movie_id`,`user_id`) COMMENT '优化用户查看自己影评的场景',
FULLTEXT KEY `ft_content` (`content`) WITH PARSER ngram
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
3.2 性能优化实践
- 索引优化:为影评表创建了覆盖索引
(movie_id, user_id, create_time),使热门电影的分页查询完全走索引 - 查询优化:使用MyBatis的
@SelectProvider实现动态SQL,根据参数智能选择是否连表查询 - 缓存策略:电影基础信息使用Ehcache本地缓存,影评统计数据用Redis缓存,设置不同的过期策略
- 分库分表:当单表超过500万条时,按电影ID哈希分片,采用Sharding-JDBC中间件
在压力测试中,通过EXPLAIN分析发现LIKE查询是性能瓶颈。解决方案是建立全文索引后,改用MATCH AGAINST语法,查询速度提升20倍。
4. 开发环境搭建指南
4.1 基础环境配置
推荐使用以下环境组合:
- JDK 1.8(必须使用Oracle JDK,实测OpenJDK在JSP编译时有兼容性问题)
- Tomcat 8.5(配置了APR连接器提升并发性能)
- MySQL 5.7.28(必须打上2020年以后的补丁)
- Maven 3.6.3(配置阿里云镜像)
关键配置项:
xml复制<!-- pom.xml中的关键依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.3.18.RELEASE</version>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.10</version>
</dependency>
4.2 常见环境问题解决
- 中文乱码问题:需要在Tomcat的server.xml中Connector标签添加
URIEncoding="UTF-8" - JSP编译错误:确保
<compilerSource>1.8</compilerSource>配置正确 - MyBatis映射失败:检查XML文件中是否包含
<!DOCTYPE mapper>声明 - 事务不生效:确认Spring配置中开启了
<tx:annotation-driven/>
我在部署时遇到过一个典型问题:当Tomcat内存配置小于2G时,系统频繁Full GC。解决方案是在catalina.sh中添加:
bash复制JAVA_OPTS="-server -Xms2048m -Xmx2048m -XX:+UseG1GC"
5. 系统部署实战
5.1 生产环境部署方案
采用Nginx+Tomcat集群架构:
- Nginx做静态资源服务和负载均衡,配置gzip压缩
- 两个Tomcat实例组成集群,通过Redis实现Session共享
- MySQL主从复制,从库专门用于报表查询
- 使用Jenkins实现自动化部署
关键Nginx配置:
nginx复制location ~ .*\.(js|css|jpg|png)$ {
expires 30d;
access_log off;
}
location / {
proxy_pass http://tomcat_cluster;
proxy_set_header X-Real-IP $remote_addr;
proxy_connect_timeout 300;
}
5.2 监控与调优
- 使用Druid监控SQL执行情况,配置慢查询阈值500ms
- Spring拦截器记录接口响应时间,生成API性能报告
- 用VisualVM分析内存泄漏,发现是JSTL标签未正确关闭导致
- 调整Tomcat线程池参数:
properties复制maxThreads=500 minSpareThreads=50 acceptCount=1000
在压测过程中发现,当并发超过800时,MySQL连接数暴涨。最终通过调整Druid连接池配置解决:
xml复制<property name="maxActive" value="50"/>
<property name="initialSize" value="10"/>
<property name="maxWait" value="60000"/>
6. 特色功能实现细节
6.1 影评情感分析
集成阿里云NLP服务实现:
java复制public class SentimentAnalysis {
public static int analyze(String content) {
DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou", accessKey, secret);
IAcsClient client = new DefaultAcsClient(profile);
NlpRequest request = new NlpRequest();
request.setSysRegionId("cn-hangzhou");
request.setText(content);
request.setOperation("sentiment");
try {
NlpResponse response = client.getAcsResponse(request);
return Integer.parseInt(response.getData());
} catch (Exception e) {
return 2; // 中性作为默认值
}
}
}
6.2 防灌水机制
- 滑动窗口限流:每用户每分钟最多3条评论
- 相似度检测:使用SimHash算法比对内容相似度
- 行为验证码:关键操作需完成拖动验证
- 延时显示:新注册用户评论需要审核后才可见
实现的关键Redis Lua脚本:
lua复制local key = KEYS[1]
local limit = tonumber(ARGV[1])
local current = tonumber(redis.call('get', key) or "0")
if current + 1 > limit then
return 0
else
redis.call("INCR", key)
redis.call("EXPIRE", key, 60)
return 1
end
7. 问题排查手册
7.1 典型错误及解决方案
-
报错:
NoSuchMethodError: org.springframework.core.annotation.AnnotationUtils.findAnnotation- 原因:Spring版本冲突
- 解决:执行
mvn dependency:tree检查依赖,排除旧版本
-
报错:
Too many connections- 原因:数据库连接未关闭
- 解决:配置Druid的removeAbandoned参数
-
性能问题:分页查询越来越慢
- 原因:使用
limit 10000,20这样的深分页 - 解决:改为
where id > last_id limit 20方式
- 原因:使用
-
缓存穿透:查询不存在的电影ID
- 解决:使用布隆过滤器预先校验ID有效性
7.2 日志分析技巧
-
定位慢SQL:在MyBatis配置中开启日志
xml复制<setting name="logImpl" value="STDOUT_LOGGING"/> -
分析线程阻塞:获取线程dump
bash复制
jstack -l <pid> > thread.log -
内存泄漏分析:生成Heap Dump
bash复制
jmap -dump:format=b,file=heap.hprof <pid>
在项目上线初期,我们通过日志发现有个电影详情接口平均响应时间达5秒。最终定位是N+1查询问题,通过重构MyBatis的关联查询方式,优化到300毫秒以内。
