大家好,我是老K。又是一年毕业设计季,每年这个时候,总有不少同学在后台问我:“SpringBoot 选题太普通怎么办?”“前端 + 后端做了个管理系统,答辩时怎么讲出亮点?”
如果你也有同样的焦虑,那今天这篇文章应该能帮上忙。我围绕一个非常典型的毕设项目——“SpringBoot + AI大模型影视评论舆情分析可视化平台”,完整拆解整个项目的技术架构、核心代码、数据库设计、AI 大模型如何接入、数据可视化如何落地,以及答辩时的高频问题。
需要提前说明的是:这个项目虽然是用“影视评论”作为业务场景,但整体架构完全可以迁移到电商评论、旅游点评、新闻评论、社交舆情等任何文本舆情分析场景。读完本文,你不仅能做出一个能运行的毕设项目,更能理解背后的技术链路:数据采集 → 数据清洗 → AI 情感分析 → 数据落库 → 可视化展示。
1. 项目背景与核心概念
1.1 为什么选“影视评论舆情分析”这个方向
做毕设选题时,最重要的原则是:业务场景贴近生活,技术栈有展示空间。
影视评论(豆瓣短评、猫眼评论、微博影评等)天然具有以下特点:
- 数据量可观:一部热门电影的评论数量轻松破万,天然构成“大数据”场景。
- 文本内容丰富:用户评论包含情感倾向(好评/差评)、话题焦点(剧情、演技、特效、配乐)、多样表达方式。
- 分析价值明显:通过舆情分析,可以判断一部电影的公众口碑走向、热度趋势、负面焦点。
- 展示效果好:饼图、折线图、词云图、情感分布图……可视化维度非常多。
对于毕业设计答辩来说,这个项目可以从“数据采集→后端接口→AI分析→可视化展示”形成完整闭环,每个环节都有东西可讲。
1.2 系统整体技术架构
本项目的核心架构可以用一句话概括:“前端展示 + 后端服务 + AI 分析引擎 + 数据存储”四层结构。
| 层次 | 技术选型 | 职责 |
|---|---|---|
| 前端展示层 | Vue.js + ECharts | 数据可视化、舆情看板、交互管理 |
| 后端服务层 | Spring Boot 2.x / 3.x | 提供 RESTful API、业务逻辑、认证鉴权 |
| AI 分析层 | 大模型 API 调用(文本情感分析、关键词抽取) | 对评论文本进行情感判断和主题抽取 |
| 数据存储层 | MySQL + Redis | 持久化影视数据、评论数据、分析结果;缓存高频查询 |
1.3 功能模块划分
整个平台围绕“舆情分析”这个核心,拆分为以下功能模块:
- 影视信息管理:影片名称、导演、演员、上映时间、类型、地区等基础信息维护。
- 评论数据采集:通过爬虫或外部数据导入方式获取影视评论原始数据。
- AI 情感分析引擎:调用大模型接口,对每一条评论进行情感极性判断(正向/中性/负向)和情感分值输出。
- 舆情可视化看板:
- 评论数量趋势图(折线图)
- 情感分布占比(饼图)
- 高频关键词词云
- 评分与评论数排行(柱状图)
- 负面评论明细列表
- 系统管理:用户登录、角色权限、操作日志。
注意:爬虫模块在毕设中要严格遵守合法合规要求。建议使用公开数据集或官方开放 API(如豆瓣官方 API、IMDb 开放数据)作为数据来源,不要大规模爬取受版权保护的完整评论内容,更不要将爬取数据用于商业用途。
2. 环境准备与版本说明
2.1 开发环境总览
下面是本文推荐的一组稳定环境组合。你本机的版本不一定完全一致,但思路相同,重点是理解配置过程:
| 工具 | 推荐版本 | 说明 |
|---|---|---|
| JDK | 1.8 或 11 | Spring Boot 2.x 使用 JDK 8+,Spring Boot 3.x 需要 JDK 17+ |
| Maven | 3.6+ | 依赖管理和项目构建 |
| Spring Boot | 2.7.x 或 3.x | 根据 JDK 版本选择 |
| MySQL | 5.7 / 8.0 | 数据存储 |
| Redis | 5.x+ | 缓存,加速热点数据查询 |
| Vue | 2.x / 3.x | 前端框架 |
| ECharts | 5.x | 数据可视化图表库 |
| 开发工具 | IntelliJ IDEA / VSCode | 后端和前端开发 |
2.2 如何选择 Spring Boot 版本
很多同学在这个地方容易踩坑。Spring Boot 版本不是越高越好,取决于你的 JDK 版本:
- 如果你本机安装的是JDK 8,那么请使用Spring Boot 2.7.x,这是最后一个支持 JDK 8 的 2.x 版本。
- 如果你安装的是JDK 17或更高版本,可以使用Spring Boot 3.x,但要注意部分第三方依赖(如 MyBatis 老版本、某些网上的封装工具类)可能不兼容。
本项目以 Spring Boot 2.7.x 为例进行说明。如果你的环境是 Spring Boot 3.x,主要差异集中在 Jakarta 命名空间(javax → jakarta),代码中涉及的地方我会特别标注。
<!-- pom.xml 中 Spring Boot 父依赖 --> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.18</version> <relativePath/> </parent>2.3 项目结构规划
后端采用经典的分层架构,包结构如下:
com.example.movieanalysis ├── MovieAnalysisApplication.java # 启动类 ├── common/ # 通用工具、常量、统一返回 │ ├── Result.java │ └── ResultCode.java ├── config/ # 配置类 ├── controller/ # 控制层 │ ├── MovieController.java │ ├── CommentController.java │ ├── AnalysisController.java │ └── UserController.java ├── service/ # 业务层 │ ├── MovieService.java │ ├── CommentService.java │ ├── AnalysisService.java │ └── AiAnalysisService.java ├── mapper/ # MyBatis 数据访问层 ├── entity/ # 实体类 └── dto/ # 数据传输对象3. 数据库设计与核心表结构
3.1 数据库设计原则
舆情分析平台的数据表设计需要考虑三个核心问题:
- 存储什么:影视基本信息、评论原文、AI 分析后的情感结果。
- 查询什么:按电影 ID 查评论、按日期统计评论量、按情感值分组统计。
- 如何关联:评论表通过 movie_id 关联电影表。
3.2 核心表结构
电影信息表 t_movie
CREATE TABLE `t_movie` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `movie_name` varchar(200) NOT NULL COMMENT '电影名称', `director` varchar(100) DEFAULT NULL COMMENT '导演', `actors` varchar(500) DEFAULT NULL COMMENT '主演,逗号分隔', `genres` varchar(100) DEFAULT NULL COMMENT '类型,如:动作/科幻', `release_date` date DEFAULT NULL COMMENT '上映日期', `rating` decimal(3,1) DEFAULT NULL COMMENT '平台评分', `cover_url` varchar(500) DEFAULT NULL COMMENT '海报URL', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='电影信息表';评论信息表 t_comment
CREATE TABLE `t_comment` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `movie_id` bigint(20) NOT NULL COMMENT '关联电影ID', `user_name` varchar(100) DEFAULT NULL COMMENT '评论用户', `content` text COMMENT '评论内容', `comment_date` datetime DEFAULT NULL COMMENT '评论日期', `source` varchar(50) DEFAULT NULL COMMENT '来源平台:douban/maoyan/weibo', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_movie_id` (`movie_id`), KEY `idx_comment_date` (`comment_date`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='评论信息表';情感分析结果表 t_sentiment_analysis
CREATE TABLE `t_sentiment_analysis` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `comment_id` bigint(20) NOT NULL COMMENT '关联评论ID', `sentiment_label` varchar(20) DEFAULT NULL COMMENT '情感标签:positive/neutral/negative', `sentiment_score` decimal(5,4) DEFAULT NULL COMMENT '情感分值,范围0~1,越接近1越正向', `ai_model` varchar(100) DEFAULT NULL COMMENT '使用的大模型名称', `analysis_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_comment_id` (`comment_id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='AI情感分析结果表';用户表 t_user
CREATE TABLE `t_user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL COMMENT '用户名', `password` varchar(255) NOT NULL COMMENT '密码,BCrypt加密', `role` varchar(20) DEFAULT 'USER' COMMENT '角色:ADMIN/USER', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='系统用户表';这里有一个非常重要的设计细节:为什么情感分析结果单独拆一张表,而不是直接加到 t_comment 表中?
两个原因:
- 解耦:AI 分析结果可能会因为模型升级而重新生成,独立表方便重新跑批。
- 性能:评论表本身已经很大,如果每条评论附带多个分析字段,查询统计时会变慢。
4. AI 大模型接入与情感分析实现
4.1 大模型在舆情分析中的角色
在传统的舆情分析项目中,情感分析通常使用基于词典的方法(SnowNLP、BosonNLP)或者训练一个简单的文本分类模型。但这些方式在新领域、口语化表达、反讽等场景下准确率有限。
引入 AI 大模型后,核心优势在于:
- 零样本或少样本学习:不需要标注大量训练语料,直接通过提示词让模型输出情感判断。
- 语义理解能力强:能识别“绝了”“拴Q”“YYDS”等网络用语的真实情感倾向。
- 支持批量分析:可以将多条评论拼接为 Prompt 批量提交,减少 API 调用次数。
4.2 大模型 API 接入通用实现
无论是接入国内大模型(文心一言、通义千问、智谱 GLM、DeepSeek)还是国外模型,调用流程基本都是:
- 注册账户,获取 API Key。
- 构造 HTTP 请求,携带 Prompt 文本。
- 解析模型返回的 JSON 响应。
- 将结果映射为业务对象。
下面给出一个通用的AiAnalysisService实现。这里以 OpenAI 兼容接口为例,国内大多数模型都提供兼容格式,你只需要替换apiKey、baseUrl和model三个配置即可。
// 文件路径:src/main/java/com/example/movieanalysis/service/AiAnalysisService.java @Service public class AiAnalysisService { @Value("${ai.api-key}") private String apiKey; @Value("${ai.base-url}") private String baseUrl; @Value("${ai.model}") private String model; private static final Logger log = LoggerFactory.getLogger(AiAnalysisService.class); /** * 分析单条评论的情感倾向 * @param content 评论文本 * @return 情感分析结果 */ public SentimentResult analyzeSentiment(String content) { if (StringUtils.isBlank(content)) { return SentimentResult.neutral(); } // 1. 构造 Prompt String prompt = buildPrompt(content); // 2. 调用大模型 API String response = callAiApi(prompt); // 3. 解析返回结果 return parseResponse(response); } private String buildPrompt(String content) { return "你是一名影视评论情感分析专家。请分析下面这条电影评论的情感倾向," + "输出JSON格式:{\"sentiment\": \"positive|neutral|negative\", \"score\": 0.0~1.0}\n\n" + "评论内容:" + content; } private String callAiApi(String prompt) { // 构造请求体 Map<String, Object> requestBody = new HashMap<>(); requestBody.put("model", model); requestBody.put("messages", List.of( Map.of("role", "user", "content", prompt) )); requestBody.put("temperature", 0.0); try { String jsonBody = new ObjectMapper().writeValueAsString(requestBody); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); headers.setBearerAuth(apiKey); HttpEntity<String> request = new HttpEntity<>(jsonBody, headers); RestTemplate restTemplate = new RestTemplate(); ResponseEntity<String> response = restTemplate.exchange( baseUrl + "/chat/completions", HttpMethod.POST, request, String.class ); return response.getBody(); } catch (Exception e) { log.error("调用AI接口失败,评论内容: {}", content, e); // 降级:返回中性结果 return "{\"sentiment\": \"neutral\", \"score\": 0.5}"; } } private SentimentResult parseResponse(String response) { try { ObjectMapper mapper = new ObjectMapper(); JsonNode root = mapper.readTree(response); String content = root.path("choices").get(0).path("message").path("content").asText(); JsonNode analysis = mapper.readTree(cleanJsonContent(content)); return new SentimentResult(analysis.path("sentiment").asText(), analysis.path("score").asDouble()); } catch (Exception e) { log.error("解析AI返回结果失败: {}", response, e); return SentimentResult.neutral(); } } private String cleanJsonContent(String content) { // 有些模型会输出 ```json ``` 代码块标记,需要清理 return content.replace("```json", "").replace("```", "").trim(); } }对应的配置项,在application.yml中:
ai: api-key: your-api-key-here base-url: https://api.example.com/v1 model: your-model-name这里强调一下:不同大模型厂商的 API 地址和请求格式可能不同,示例代码是 OpenAI 兼容格式。实际接入时,以你选择的服务商文档为准。不要把 API Key 写在代码里提交到 GitHub,建议通过环境变量或配置文件外部化管理。
4.3 批量分析评论数据
单条评论逐个调用大模型接口效率太低,实际项目中推荐批量处理:
- 将多条评论拼接到一个 Prompt 中,让模型一次性返回数组。
- 使用线程池并发调用,控制 QPS 避免触发限流。
- 已分析的评论记录分析状态,避免重复调用浪费成本。
// 批量分析实现,使用线程池并发处理 @Component public class BatchAnalysisService { private final AiAnalysisService aiAnalysisService; private final CommentMapper commentMapper; private final SentimentMapper sentimentMapper; // 自定义线程池,根据实际情况调整参数 private final ExecutorService executor = Executors.newFixedThreadPool(8); public void batchAnalyzeByMovie(Long movieId) { List<Comment> comments = commentMapper.selectByMovieId(movieId); CountDownLatch latch = new CountDownLatch(comments.size()); for (Comment comment : comments) { executor.submit(() -> { try { // 1. 调用AI分析 SentimentResult result = aiAnalysisService.analyzeSentiment(comment.getContent()); // 2. 保存结果 SentimentAnalysis analysis = new SentimentAnalysis(); analysis.setCommentId(comment.getId()); analysis.setSentimentLabel(result.getSentiment()); analysis.setSentimentScore(result.getScore()); analysis.setAiModel("your-model-name"); sentimentMapper.insert(analysis); // 3. 更新评论状态 commentMapper.updateAnalysisStatus(comment.getId(), 1); } catch (Exception e) { log.error("评论{}分析失败", comment.getId(), e); } finally { latch.countDown(); } }); } try { latch.await(10, TimeUnit.MINUTES); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } }4.4 AI 接入的降级兜底策略
在毕设答辩演示时,最怕出现“AI 接口超时、key 额度用尽”导致系统直接报错。这里给大家一个非常重要的实战经验:AI 接口必须做降级处理。
建议方案:
- 本地词典预判:先用 SnowNLP 或自建情感词典跑一遍分数,大模型接口失败时直接返回本地结果。
- 缓存结果:同一评论文本的 AI 分析结果放入 Redis,重复请求不重复调用 API。
- 失败重试 + 熔断:连续失败 3 次后,直接熔断并转向本地分析。
5. 后端接口设计与业务实现
5.1 统一返回结果封装
为了前后端数据交互规范,后端所有接口统一返回Result<T>结构。
// 文件路径:src/main/java/com/example/movieanalysis/common/Result.java public class Result<T> { private Integer code; private String message; private T data; public static <T> Result<T> success(T data) { Result<T> result = new Result<>(); result.setCode(200); result.setMessage("success"); result.setData(data); return result; } public static <T> Result<T> error(Integer code, String message) { Result<T> result = new Result<>(); result.setCode(code); result.setMessage(message); return result; } // getter/setter 省略 }5.2 舆情统计接口
这是整个系统最核心的接口。前端看板页面需要展示以下数据:
- 某部电影的整体评论数、均值情感分数。
- 日期维度的评论量趋势。
- 情感分布占比。
对应接口设计如下:
// 文件路径:src/main/java/com/example/movieanalysis/controller/AnalysisController.java @RestController @RequestMapping("/api/analysis") public class AnalysisController { @Resource private AnalysisService analysisService; /** * 按电影ID获取舆情看板统计信息 */ @GetMapping("/overview/{movieId}") public Result<OverviewVO> getOverview(@PathVariable Long movieId) { return Result.success(analysisService.getOverview(movieId)); } /** * 按电影ID获取评论趋势(按天统计) */ @GetMapping("/trend/{movieId}") public Result<List<TrendVO>> getTrend(@PathVariable Long movieId) { return Result.success(analysisService.getTrendByDate(movieId)); } /** * 按电影ID获取情感分布 */ @GetMapping("/sentiment/{movieId}") public Result<Map<String, Long>> getSentimentDistribution(@PathVariable Long movieId) { return Result.success(analysisService.getSentimentDistribution(movieId)); } }5.3 SQL 统计实现
在AnalysisServiceImpl中,核心的统计 SQL 如下:
-- 查询某电影的情感分布 SELECT a.sentiment_label AS label, COUNT(*) AS count FROM t_comment c INNER JOIN t_sentiment_analysis a ON c.id = a.comment_id WHERE c.movie_id = #{movieId} GROUP BY a.sentiment_label;-- 查询某电影的每日评论趋势 SELECT DATE_FORMAT(c.comment_date, '%Y-%m-%d') AS date, COUNT(*) AS count FROM t_comment c WHERE c.movie_id = #{movieId} GROUP BY DATE_FORMAT(c.comment_date, '%Y-%m-%d') ORDER BY date;-- 查询负面评论明细(Top N) SELECT c.user_name, c.content, c.comment_date, a.sentiment_score FROM t_comment c INNER JOIN t_sentiment_analysis a ON c.id = a.comment_id WHERE c.movie_id = #{movieId} AND a.sentiment_label = 'negative' ORDER BY a.sentiment_score ASC LIMIT #{limit};这里的统计 SQL 在 MyBatis Mapper 中通过@Select注解或 XML 文件实现。
5.4 SpringBoot + MyBatis 配置
application.yml中的数据库配置:
spring: datasource: url: jdbc:mysql://localhost:3306/movie_analysis?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: your-password driver-class-name: com.mysql.cj.jdbc.Driver redis: host: localhost port: 6379 database: 0 mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.movieanalysis.entity configuration: map-underscore-to-camel-case: true注意:如果你的 MySQL 是 5.x 版本,数据库驱动用
com.mysql.cj.jdbc.Driver没问题;如果遇到时区问题,可以去掉serverTimezone参数或改为Asia/Shanghai。
6. 前端数据可视化实现
6.1 前端技术选型
前端推荐采用 Vue + Element UI + ECharts 的组合:
- Vue:负责页面交互和数据绑定。
- Element UI:提供表格、表单、菜单等基础组件。
- ECharts:实现词云、折线图、饼图、柱状图等可视化图表。
6.2 舆情看板页面实现
以“影视舆情总览看板”为例,页面布局为:
- 顶部:电影选择下拉框、整体统计卡片(总评论数、正面占比、负面占比、平均情感分)。
- 中间左侧:情感分布饼图。
- 中间右侧:评论数趋势折线图。
- 底部左侧:评论高频词词云。
- 底部右侧:负面评论 Top 列表。
核心代码示例(Vue 组件):
// 文件路径:src/views/Dashboard.vue(核心片段) <template> <div class="dashboard"> <!-- 统计卡片 --> <el-row :gutter="20"> <el-col :span="6"> <el-card shadow="hover"> <div class="stat-item"> <div class="stat-label">总评论数</div> <div class="stat-value">{{ overview.totalComments }}</div> </div> </el-card> </el-col> <el-col :span="6"> <el-card shadow="hover"> <div class="stat-item"> <div class="stat-label">正面评论占比</div> <div class="stat-value">{{ overview.positiveRate }}%</div> </div> </el-card> </el-col> <el-col :span="6"> <el-card shadow="hover"> <div class="stat-item"> <div class="stat-label">负面评论占比</div> <div class="stat-value">{{ overview.negativeRate }}%</div> </div> </el-card> </el-col> <el-col :span="6"> <el-card shadow="hover"> <div class="stat-item"> <div class="stat-label">平均情感分</div> <div class="stat-value">{{ overview.avgScore }}</div> </div> </el-card> </el-col> </el-row> <!-- 图表区域 --> <el-row :gutter="20" style="margin-top: 20px;"> <el-col :span="12"> <el-card shadow="hover" title="情感分布"> <div ref="sentimentChart" style="height: 350px;"></div> </el-card> </el-col> <el-col :span="12"> <el-card shadow="hover" title="评论趋势"> <div ref="trendChart" style="height: 350px;"></div> </el-card> </el-col> </el-row> </div> </template> <script> import * as echarts from 'echarts'; export default { name: 'Dashboard', data() { return { movieId: null, overview: { totalComments: 0, positiveRate: 0, negativeRate: 0, avgScore: 0 } }; }, methods: { async loadData(movieId) { // 1. 加载总览数据 const { data: overview } = await axios.get(`/api/analysis/overview/${movieId}`); this.overview = overview; // 2. 加载并渲染情感分布饼图 const { data: sentimentData } = await axios.get(`/api/analysis/sentiment/${movieId}`); this.renderSentimentChart(sentimentData); // 3. 加载并渲染趋势折线图 const { data: trendData } = await axios.get(`/api/analysis/trend/${movieId}`); this.renderTrendChart(trendData); }, renderSentimentChart(data) { const chart = echarts.init(this.$refs.sentimentChart); chart.setOption({ tooltip: { trigger: 'item' }, series: [{ type: 'pie', radius: ['40%', '70%'], data: [ { value: data.positive, name: '正面' }, { value: data.neutral, name: '中性' }, { value: data.negative, name: '负面' } ], label: { formatter: '{b}: {d}%' } }] }); }, renderTrendChart(data) { const chart = echarts.init(this.$refs.trendChart); chart.setOption({ xAxis: { type: 'category', data: data.dates }, yAxis: { type: 'value' }, series: [{ type: 'line', smooth: true, data: data.counts, areaStyle: { opacity: 0.3 } }], tooltip: { trigger: 'axis' } }); } } }; </script>6.3 词云图实现
词云图是舆情项目的亮点模块。ECharts 官方的词云图示例在 5.x 版本后变成了echarts-wordcloud扩展包,需要单独安装:
npm install echarts echarts-wordcloud核心配置如下:
import 'echarts-wordcloud'; const chart = echarts.init(this.$refs.wordCloudChart); chart.setOption({ series: [{ type: 'wordCloud', shape: 'circle', left: 'center', top: 'center', width: '90%', height: '90%', sizeRange: [14, 60], rotationRange: [0, 0], drawOutOfBound: false, data: this.wordCloudData // 格式: [{ name: '演技', value: 132 }, ...] }] });词云数据从后端接口获取,接口内部使用 AI 模型或分词工具(如 HanLP、Jieba)对评论进行关键词抽取,按词频统计后返回。
7. 系统安全与用户认证
7.1 登录认证实现
毕设项目通常不需要复杂的微服务安全框架,使用 JWT + Spring Security 或简单拦截器即可。这里推荐使用 Spring Security + JWT 组合,既展示技术深度,答辩时也有内容可讲。
核心依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency>注意:如果使用 Spring Boot 3.x,
jjwt 0.9.1可能不兼容 JDK 17 的 JAXB 模块,需要额外引入javax.xml.bind:jaxb-api,或换用io.jsonwebtoken:jjwt:0.12.x版本。
JWT 工具类核心代码:
// 文件路径:src/main/java/com/example/movieanalysis/common/JwtUtils.java @Component public class JwtUtils { @Value("${jwt.secret}") private String secret; @Value("${jwt.expire-hours}") private Long expireHours; /** * 生成Token */ public String generateToken(String username, String role) { return Jwts.builder() .setSubject(username) .claim("role", role) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + expireHours * 3600_000L)) .signWith(SignatureAlgorithm.HS256, secret) .compact(); } /** * 解析Token,获取用户名 */ public String parseToken(String token) { return Jwts.parser() .setSigningKey(secret) .parseClaimsJws(token) .getBody() .getSubject(); } }7.2 安全边界提醒
作为毕设项目,这里必须提醒几个安全底线:
- 密码存储:使用 BCrypt 加密,不要用 MD5 存密码。
- API Key 保护:大模型的 API Key 绝不能放在前端代码中,必须存储在后端配置。
- SQL 注入:使用 MyBatis 的
#{}参数绑定,不要用${}拼接 SQL。 - 接口权限:涉及管理操作的接口必须做登录校验。
8. 项目运行与部署实战
8.1 本地快速启动步骤
下面是从零到运行的最简流程:
# 1. 创建数据库并导入脚本 mysql -uroot -p source /path/to/sql/init.sql; # 2. 启动 Redis(如果使用缓存) redis-server /path/to/redis.conf # 3. 修改后端 application.yml 配置数据库和AI接口 # 4. 启动后端 mvn spring-boot:run # 5. 启动前端 cd frontend npm install npm run serve启动成功后访问:
- 前端页面:
http://localhost:8081 - 后端接口:
http://localhost:8080/api/... - Swagger 接口文档(如集成):
http://localhost:8080/swagger-ui.html
8.2 前后端分离部署方案
如果你的毕设要求部署到服务器上展示,推荐两种方式:
方式一:前后端分开部署
- 后端打包为 jar 包,使用
java -jar movie-analysis.jar运行。 - 前端
npm run build生成 dist 静态文件,使用 Nginx 托管,并通过 Nginx 反向代理后端接口。
Nginx 配置示例:
server { listen 80; server_name your-server-ip; 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/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }方式二:Spring Boot 内置静态资源访问
将前端构建后的dist目录放到src/main/resources/static/下,重新打包后直接访问后端端口即可。这种方式适合毕设演示,但不推荐大型项目使用。
9. 常见问题与排查思路
以我自己的经验,这个项目最容易遇到以下问题:
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
项目启动报错:Field userMapper was not injected | Mapper 接口没有被扫描 | 启动类添加@MapperScan("com.example.movieanalysis.mapper") |
| 调用 AI 接口报 401 或 403 | API Key 错误或已过期 | 检查application.yml中的api-key配置,查看服务商控制台确认额度 |
| 前端跨域请求失败 | 后端未配置 CORS | 后端添加跨域配置类实现WebMvcConfigurer重写addCorsMappings |
| 数据库中文乱码 | JDBC URL 未指定 UTF-8,或建库时未指定字符集 | URL 添加characterEncoding=utf8,建库语句指定DEFAULT CHARSET=utf8mb4 |
| 情感分析结果全部为 neutral | 大模型返回 JSON 格式解析失败,走了降级逻辑 | 在parseResponse方法打断点,查看模型返回的原始内容 |
| Spring Boot 3.x 下 JWT 报错 | javax 包名已变更为 jakarta | 换用 Spring Boot 2.7.x 或调整 JWT 库版本 |
| Redis 连接失败 | Redis 服务未启动或密码配置错误 | redis-cli ping检查连接,确认spring.redis.password配置 |
9.1 跨域配置示例
// 文件路径:src/main/java/com/example/movieanalysis/config/CorsConfig.java @Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOriginPatterns("*") .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("*") .allowCredentials(true) .maxAge(3600); } }9.2 AI 接口超时问题
默认的RestTemplate没有设置超时时间,生产环境会导致线程被无限阻塞。建议使用RestTemplate自定义配置:
@Configuration public class RestTemplateConfig { @Bean public RestTemplate restTemplate() { SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); factory.setConnectTimeout(5000); factory.setReadTimeout(15000); return new RestTemplate(factory); } }10. 最佳实践与工程建议
10.1 数据层面
- 评论数据建议提前清洗:去重(同一条评论可能来自不同来源)、去 HTML 标签、过滤广告。
- 大模型分析考虑设置批处理状态:
0=待分析, 1=分析中, 2=已完成, 3=失败,失败任务支持重跑。 - 定期清理过期缓存,避免 Redis 内存暴涨。
10.2 AI 成本控制
毕设项目经费有限,AI 接口调用需要注意:
- 使用限定 token 长度的模型,对于短评,一次只传必要文本。
- 设置温度 temperature=0,让输出尽量稳定。
- 对同一电影的评论,在测试阶段只分析前 500 条,够展示即可。
- 可在 Redis 中缓存每条评论的分析结果,避免重复消耗额度。
10.3 代码维护建议
- 后端所有返回给前端的时间字段,建议统一格式为
yyyy-MM-dd HH:mm:ss,避免前端时间格式化麻烦。 - 大模型相关的提示词、API 配置全部抽取到配置项,不要写死在代码里。
- 日志中打印 AI 调用耗时和请求参数,方便答辩时展示优化思路。
11. 答辩与项目展示技巧
这个项目在答辩时,建议从以下三个维度组织讲稿:
维度一:技术难点
- AI 大模型如何与 SpringBoot 生态无缝集成。
- 情感分析的准确率如何保障,模型返回内容格式不稳定时如何容错。
- 数据库查询性能优化,比如评论表超过 10 万行后如何通过索引和分页保障响应。
维度二:业务闭环
- 从原始评论数据到可视化看板,整个数据流转链路要讲清楚。
- 突出“AI 不是噱头”,而是真正解决了传统词典分析解决不了的问题。
维度三:项目痛点
- 大模型 API 调用为什么需要降级策略。
- 情感分析结果为什么要独立于评论表存储。
12. 总结与后续扩展方向
本篇文章从零开始,围绕“SpringBoot + AI大模型影视评论舆情分析平台”这个毕设项目,完整拆解了系统架构、数据库设计、AI 情感分析实现、后端接口、前端可视化、部署流程和常见问题。核心内容可以简单概括为一条链路:评论文本 → 大模型情感分析 → 数据库存储 → API 接口 → ECharts 可视化。
如果你已经把这个项目跑通,接下来可以考虑以下几个扩展方向:
- 加入定时任务:使用 Spring 的
@Scheduled定时拉取最新评论并自动分析,实现舆情实时监控。 - 引入 ES 搜索引擎:评论数据量大幅增长后,使用 Elasticsearch 做全文检索和分析,聚合速度远高于 MySQL。
- 多模型对比:接入两个以上大模型,对同一批评论做情感分析一致性比对,这在高档毕设中是很大的加分项。
- 热度预警:根据负面评论的增长率设置预警阈值,当某部电影负面舆情激增时,系统自动告警。
最后,还是那句老话:毕设项目的核心不是堆砌多少新技术,而是把每一个技术点讲清楚、能落地、可演示。把本文中 AI 接入的降级策略、表结构设计、可视化看板做好,你已经可以平稳通过大多数学校的毕设答辩了。
如果这篇文章对你有帮助,欢迎收藏备用。如果你在搭建过程中遇到了本文没有覆盖到的问题,也可以在评论区描述你的错误日志和运行环境,我会尽力帮你分析排查。