news 2026/9/20 14:48:48

SpringBoot+Vue新闻推荐系统实战:从开发到Docker部署

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SpringBoot+Vue新闻推荐系统实战:从开发到Docker部署

简介:本资源是一套完整的新闻推荐系统毕业设计项目,面向计算机专业本科生及全栈开发初学者,解决前后端分离架构下推荐功能落地的实战难题。压缩包共742个文件,14.82MB,涵盖90个Java后端核心类(含SpringBoot控制器、Service与JPA实体)、38个Vue单文件组件(如update-password.vue、IndexHeader.vue等)、153个JS交互逻辑与162个SVG图标资源,辅以bat部署脚本、SQL建表语句及yml配置文件,结构清晰、模块解耦。已有207人学习下载,配套提供详细部署说明文档、全流程演示视频及源码逐层解析,覆盖RESTful接口设计、JWT权限控制、MySQL数据建模与Vue动态渲染等关键环节,可直接用于课程设计复现或毕设二次开发。

1. 一个能跑通的新闻推荐系统,为什么比“Hello World”更值得你花三天时间搭起来?

刚拿到这个 SpringBoot + Vue 新闻推荐系统的压缩包时,我下意识点开index.html.bak和一堆.vue.bak文件——结果发现不是备份出错,而是项目刻意保留了开发过程中的中间态痕迹:.bak文件里藏着路由配置被注释掉的旧逻辑、update-password.vue.bak里埋着 JWT 刷新令牌的早期实现、IndexAsideStatic.vue.bak则记录了从动态菜单切换到静态侧边栏的决策过程。这说明它不是一个“一键生成”的脚手架玩具,而是一个真实迭代过的教学型工程。它解决的不是“怎么显示一条新闻”,而是“当用户连续刷 7 分钟首页、点击 3 次‘不感兴趣’、又在搜索框输入‘AI’后回车”这一连串行为如何被后端捕获、打标、召回并实时重排——整套链路覆盖了推荐系统最核心的「行为采集 → 特征构建 → 算法调用 → 接口响应 → 前端渲染」闭环。适合两类人:一类是正在写毕业设计、需要在答辩时讲清楚“为什么用协同过滤而不是内容推荐”的本科生;另一类是工作 2~4 年、想补全全栈链路认知的后端或前端工程师——你不需要从零推导矩阵分解公式,但必须能看懂RecommendController.java@PostMapping("/recommend")方法如何把userIdcontext封装成 Feign 调用参数,再对照RecommendService.jsthis.$http.post('/api/recommend', { userId, context })验证前后端字段对齐是否一致。


2. SpringBoot 后端:从1-install.bat到可调试的推荐接口

2.1 为什么选 SpringBoot 而不是纯 Spring MVC?关键在pom.xml的三处精简

项目未使用 Spring Initializr 生成的标准结构,而是手动维护pom.xml。打开该文件,你会在<dependencies>中发现三个非默认但高度针对性的依赖:

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <!-- 注意:此处无 version 标签 --> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency>

提示:SpringBoot 的spring-boot-starter-*依赖默认继承父 POM 中定义的版本号(本项目为2.7.18),因此无需显式声明 version。若强行添加错误版本(如3.2.0),会导致mvnw.cmd执行1-install.bat时在org.springframework.boot:spring-boot-starter-web:3.2.0处报Could not resolve错误——这是新手部署失败的第一高发点。

这三者共同构成最小可行后端骨架:starter-web内置 Tomcat 并自动注册DispatcherServletstarter-data-jpaapplication.yml中的数据库配置映射为DataSourceBean,并让NewsRepository接口无需 XML 即可获得findAll()findById()等方法;mysql-connector-java则确保运行时能加载 JDBC 驱动。对比纯 Spring MVC,省去了web.xml配置、@EnableWebMvc注解、InternalResourceViewResolver手动注册等 12+ 行样板代码。

2.21-install.bat的真实作用:不只是mvn clean install

双击1-install.bat,控制台会依次执行:

mvn clean mvn compile mvn test mvn package

但关键在package阶段——它触发spring-boot-maven-plugin插件,将target/news-recommend-0.0.1-SNAPSHOT.jar打包为可执行 Fat Jar。此时 jar 包内嵌lib/目录,包含所有依赖(包括tomcat-embed-core-9.0.83.jar)。验证方式:解压该 jar,进入BOOT-INF/lib/,执行dir /b | findstr "tomcat"应返回tomcat-embed-core-9.0.83.jar。若缺失,则说明pom.xml中插件配置有误(本项目已预置正确配置,位于<build><plugins>下)。

2.3 推荐接口的完整链路:从 HTTP 请求到 MySQL 查询

/api/recommend接口为例,其调用链如下:

2.3.1 控制器层:RecommendController.java
@RestController @RequestMapping("/api") public class RecommendController { @Autowired private RecommendService recommendService; @PostMapping("/recommend") public Result<List<NewsDTO>> getRecommendations(@RequestBody RecommendRequest request) { List<News> newsList = recommendService.getRecommendations( request.getUserId(), request.getContext() ); return Result.success(newsList.stream() .map(NewsDTO::fromEntity) .collect(Collectors.toList())); } }

参数说明:@RequestBody强制要求前端发送 JSON,且字段名必须与RecommendRequest类中userId(Long)、context(String)完全一致;Result<T>是项目自定义的统一响应包装类,避免每个接口重复写{ "code":200, "data": [...] }

2.3.2 服务层:RecommendService.java的策略选择
@Service public class RecommendService { @Autowired private NewsRepository newsRepository; public List<News> getRecommendations(Long userId, String context) { if ("search".equals(context)) { return newsRepository.findByTitleContaining(context.replace("search:", "")); } else if (userId != null) { return newsRepository.findTop10ByUserIdOrderByClickCountDesc(userId); } else { return newsRepository.findTop10ByPublishTimeDesc(); } } }

关键逻辑:context字段承载业务语义——值为"search:AI"时走标题模糊查询;为null时返回全局热点;否则按用户历史点击量降序取 Top10。这种轻量级策略虽未接入 Spark 或 Flink,但已覆盖新闻推荐中最常见的三种场景,且全部基于 JPA 的@Query注解实现,无需手写 SQL。

2.3.3 数据访问层:NewsRepository.java的隐式 SQL 生成
public interface NewsRepository extends JpaRepository<News, Long> { List<News> findByTitleContaining(String keyword); List<News> findTop10ByUserIdOrderByClickCountDesc(Long userId); List<News> findTop10ByPublishTimeDesc(); }

原理说明:JPA 根据方法名自动解析 SQL。例如findTop10ByUserIdOrderByClickCountDesc会被翻译为:

SELECT * FROM news WHERE user_id = ? ORDER BY click_count DESC LIMIT 10

若数据库表名为t_news而非news,需在News.java实体类上添加@Table(name="t_news"),否则查询将返回空列表——这是部署后“推荐列表为空”的首要排查点。


3. Vue 前端:从2-run.bat到动态渲染的推荐卡片流

3.12-run.bat启动的是什么?不是 Node.js 服务,而是内存编译代理

2-run.bat的核心命令是:

npm run serve

这会启动 Vue CLI 的开发服务器(默认端口8080),但关键在于vue.config.js中的代理配置:

module.exports = { devServer: { proxy: { '/api': { target: 'http://localhost:8081', // SpringBoot 默认端口 changeOrigin: true, pathRewrite: { '^/api': '' } } } } }

逻辑说明:当浏览器访问http://localhost:8080/api/recommend时,Vue 开发服务器会将请求代理http://localhost:8081/recommend,并自动移除路径前缀/api。这意味着前端代码中所有 API 调用都应以/api/开头(如axios.post('/api/recommend', {...})),而非硬编码http://localhost:8081——这是前后端分离架构下规避跨域问题的标准解法。

3.2IndexAsideStatic.vue.bak揭示的路由设计真相

打开IndexAsideStatic.vue.bak,可见被注释掉的动态路由代码:

<!-- <router-link v-for="item in menuList" :key="item.path" :to="item.path" > {{ item.name }} </router-link> -->

而当前生效的IndexAsideStatic.vue使用静态 HTML:

<router-link to="/home">首页</router-link> <router-link to="/search">搜索</router-link> <router-link to="/profile">我的</router-link>

原因分析:项目为降低复杂度,放弃基于用户角色动态生成菜单(需后端返回menuList),改用静态路由。但router/index.js中仍保留异步路由守卫:

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

这意味着/profile页面仍受权限控制——meta.requiresAuth: true在路由定义中已设置,localStorage.getItem('token')由登录接口写入。若跳转/profile时页面空白,首先检查localStorage是否存在token字段。

3.3 新闻卡片组件:NewsCard.vue的响应式数据流

src/components/NewsCard.vue是前端渲染核心,其props定义直接对应后端NewsDTO

<template> <div class="news-card"> <h3>{{ news.title }}</h3> <p class="summary">{{ news.summary }}</p> <div class="meta"> <span>{{ news.source }}</span> <span>{{ formatDate(news.publishTime) }}</span> <button @click="handleDislike(news.id)">不感兴趣</button> </div> </div> </template> <script> export default { name: 'NewsCard', props: { news: { type: Object, required: true, // 对应 NewsDTO 的字段:title, summary, source, publishTime } }, methods: { handleDislike(id) { this.$http.post('/api/dislike', { newsId: id }) .then(() => { this.$message.success('已标记不感兴趣'); // 触发父组件重新请求推荐列表 this.$emit('refresh'); }); } } } </script>

关键细节:props.newspublishTimeString类型(如"2024-05-20T08:30:00"),formatDate()方法需处理 ISO 8601 时间戳。若卡片显示Invalid Date,检查utils/date.js中是否遗漏new Date(str).toLocaleDateString()的容错判断。


4. 前后端联调:用curl和浏览器开发者工具定位 90% 的通信问题

4.1 验证后端接口是否就绪:绕过前端直击 SpringBoot

在 SpringBoot 启动成功后(控制台输出Tomcat started on port(s): 8081),用curl发送原始请求:

curl -X POST http://localhost:8081/api/recommend \ -H "Content-Type: application/json" \ -d '{"userId":1,"context":"search:人工智能"}'

预期响应:HTTP 200 + JSON 数组。若返回404,检查RecommendController@RequestMapping("/api")是否与 URL 前缀匹配;若返回400,说明@RequestBody解析失败,检查 JSON 中userId是否为数字而非字符串("1"会报错);若返回500,查看控制台异常堆栈,90% 源于NewsRepository查询时userIdnull导致 SQLWHERE user_id = NULL(应改为IS NULL,但 JPA 不支持,需改用@Query自定义 SQL)。

4.2 前端网络请求追踪:在 Chrome 中锁定问题环节

打开浏览器开发者工具(F12)→ Network 标签页 → 点击首页触发推荐请求:

  • 查看recommend请求的Headers:确认Request URLhttp://localhost:8080/api/recommendProvisional headers are shown提示代理正常;
  • 查看Preview标签:若显示{"code":500,"msg":"Internal Server Error"},说明后端抛异常,需查 SpringBoot 日志;
  • 查看Response标签:若为空白,检查vue.config.js代理target是否指向8081(而非8080);
  • 查看 ** initiator** 列:若显示NewsList.vue:42,说明问题在NewsList.vuethis.$http.post()调用处,检查data参数是否漏传userId

4.3 数据库初始化:schema.sqldata.sql的执行顺序陷阱

项目根目录下存在schema.sql(建表语句)和data.sql(初始新闻数据)。SpringBoot 默认在启动时自动执行它们,但需满足两个条件:

  1. application.yml中启用spring.sql.init.mode=always
  2. SQL 文件必须放在src/main/resources/目录下(本项目已放置)

常见错误:手动将 SQL 文件放在src/main/sql/下,导致启动时无任何提示但数据库为空。解决方案:剪切schema.sqldata.sqlresources/目录,重启应用。


5. 生产部署实战:用3-build.bat生成 Docker 镜像并验证端口映射

5.13-build.bat的四步构建流程

3-build.bat执行以下操作:

REM 步骤1:构建前端静态资源 cd frontend && npm run build && cd .. REM 步骤2:将 dist 目录复制到后端 resources/static xcopy /E /Y frontend\dist\* backend\src\main\resources\static\ REM 步骤3:打包后端为可执行 jar cd backend && mvn clean package && cd .. REM 步骤4:构建 Docker 镜像 docker build -t news-recommend .

关键点:xcopy命令将frontend/dist/(Vue 构建产物)复制到backend/src/main/resources/static/,使 SpringBoot 的ResourceHttpRequestHandler能直接提供index.html。若跳过此步,访问http://localhost:8081将返回 404,因为后端找不到静态入口文件。

5.2Dockerfile的精简设计与端口暴露

项目Dockerfile内容如下:

FROM openjdk:17-jre-slim VOLUME /tmp ARG JAR_FILE=backend/target/news-recommend-0.0.1-SNAPSHOT.jar COPY ${JAR_FILE} app.jar EXPOSE 8081 ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]

参数说明:EXPOSE 8081仅声明容器内端口,实际映射需在docker run时指定-p 8081:8081-Djava.security.egd参数加速 Java 随机数生成,避免容器内启动卡顿;VOLUME /tmp为 SpringBoot 内嵌 Tomcat 创建临时文件提供挂载点。

5.3 一键部署验证:三行命令完成环境隔离

在项目根目录执行:

# 1. 构建镜像(首次需 3~5 分钟) docker build -t news-recommend . # 2. 运行容器(映射宿主机 8081 端口) docker run -d -p 8081:8081 --name news-app news-recommend # 3. 验证服务可用性 curl http://localhost:8081/api/health

预期输出:{"status":"UP"}。若返回Failed to connect,检查 Docker 是否运行(docker info)、端口是否被占用(netstat -ano | findstr :8081)、容器是否异常退出(docker logs news-app)。容器日志中出现Started NewsRecommendApplication即表示启动成功。

注意:application.yml中的数据库连接地址需从localhost:3306改为host.docker.internal:3306(Windows/macOS)或172.17.0.1:3306(Linux),否则容器内无法访问宿主机 MySQL。本项目application-prod.yml已预置该配置,部署时需激活--spring.profiles.active=prod

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/20 14:46:55

ESP32 + MAX30102 实战:从传感器原理到心率血氧监测全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/20 14:45:39

销售团队CRM选型与落地实战:数据清洗、权限设计与自动化流程

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/20 14:44:06

Agent 工具 30+ 上下文混淆?TaoToken 这样改模型 Base URL

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/20 14:43:16

2026年Flash还能用吗?4399老游戏运行与安装全指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/20 14:42:44

open-code-review:基于CLI与git diff的开源代码评审范式

1. 这不是另一个“AI代码审查工具”&#xff0c;而是一套可落地的开源协作范式“open-code-review”这个词乍看像某个新出的SaaS产品名&#xff0c;其实它根本不是软件名称&#xff0c;而是一种正在被一线团队自发实践、快速沉淀下来的工程协作模式——把代码审查&#xff08;c…

作者头像 李华
网站建设 2026/9/20 14:39:50

10 分钟用 TaoToken 跑通 Aider Polyglot 的 Python 重构练习

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华