做毕设或者课设选型的时候,我见过太多人卡在同一个问题上:到底选什么技术栈,才能既拿得出手、又不会把自己逼疯?如果你正在考虑做一个信息展示类的管理系统,SpringBoot + Vue + MySQL 这个组合,几乎可以称得上是国内 Java 方向的“标准答案”。用这套技术栈做一个汽车资讯网站管理平台,后端负责接口和数据处理,前端负责页面交互和渲染,既能完整体现前后端分离的工程思想,又不会因为过度复杂而卡在某个环节出不来。这篇文章就围绕这个项目的整理思路、核心实现和踩坑实录展开,把我做类似项目时沉淀下来的一线经验写透,无论是拿来交作业、应付答辩,还是纯粹想学点真东西,都值得读完。
1. 项目整体设计:先搞清楚这套平台到底要做什么
1.1 为什么偏偏是 SpringBoot + Vue 这套组合
先聊聊选型逻辑。很多人在毕设开题时,会把“用什么框架”当成第一件大事,其实这是本末倒置的。真正该先想清楚的,是你要做的系统有哪些角色、哪些页面、哪些数据要流动。把这些梳理出来,再去反推技术方案,会发现 SpringBoot + Vue 几乎是水到渠成的选择。
没有任何贬低 SSM(Spring + SpringMVC + MyBatis)的意思,但用 SSM 写一个管理系统,你得自己配一堆 XML、处理繁琐的拦截器和视图解析器,光是把项目跑起来就能劝退一批人。SpringBoot 的出现,本质上就是把“约定优于配置”做到极致,内嵌 Tomcat、自动装配、YAML 配置文件一出,整个后端的开发节奏能快一倍。Vue 这边则是把前端从“用 jQuery 手动操作 DOM”的泥潭里拉出来,组件化开发、响应式数据绑定、Vue Router 管理路由,前后端各司其职,接口一对接,联调效率特别高。
选这套组合还有一层现实考虑:招聘市场上 Java 后端岗位的主流要求就是 Spring Boot 全栈或者 Spring Cloud 微服务,Vue 是国内前端框架里受众最广的之一。用一个项目同时覆盖这两块,无论是对简历还是对实际面试,都是实打实的加分项。MySQL 更不用多说,开源免费、资料多、生态成熟,5.7 和 8.0 版本都有海量社区讨论,踩坑也能快速找到答案。
1.2 功能模块:资讯网站管理平台不是“只要有个列表就行”
很多学生做资讯类系统,上来就写“新闻列表 + 新闻详情”,做到最后发现页面少得可怜,答辩时被老师几个问题问穿。这里我强调一个观点:资讯类系统的核心不是“展示”,而是“管理”。用户端看到的那些页面,只是冰山一角,真正决定项目工作量和技术含量的,是后端管理平台如何支撑这些内容的上线、编辑、审核和清理。
所以这个汽车资讯网站管理平台,我建议按两条业务线拆模块:
- 用户端(前台展示):首页轮播推荐、资讯分类导航、资讯列表分页、资讯详情展示、评论发表与查看、资讯收藏、按关键字搜索。
- 管理端(后台维护):管理员登录认证、仪表盘统计、资讯发布与编辑、分类管理、用户管理、评论审核与删除、轮播图配置。
管理端是整张业务大网的“中控台”,所有用户端能看到的资讯、轮播图、分类,都是在后台录入后通过接口渲染出去的。设计模块时,除非是高频使用的核心数据,否则不建议让管理员直接操作数据库表,而是把所有数据变更收敛到管理接口里,这样日志好记录、权限好控制,也方便后续扩展审核流。
1.3 角色与权限:管理员和普通用户的分界线在哪
权限设计这块,很多毕设会做得过于简陋,比如“所有接口都不鉴权”或者“就分 admin 和 user 两个角色,但在代码里没区分”。我的建议是,不用做复杂的 RBAC(基于角色的权限控制),但至少要把“登录后才能操作”和“只能是管理员才能操作”这两层边界划清楚。
以这个项目为例,用户端的大部分查询接口(资讯列表、资讯详情、轮播图)可以匿名访问,但评论和收藏必须要求登录,而管理端的全部接口都必须经过管理员身份的校验。实现方式上,使用 Spring Boot + JWT 做一个拦截器,对 /api/admin/** 路径进行全局校验,非管理员直接返回 401。前端路由也要做对应处理,未登录访问后台页面时跳转到登录页。
实战心得:很多同学做权限,只做了“后端接口有没有校验”,前端完全没管,结果管理员路由依然能打开,只是接口报错而已。作为完整的前后端分离项目,前端路由守卫和后端拦截器要配套使用,用户体验才完整。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 数据库设计:一张表设计得好不好,直接影响开发效率
2.1 核心表结构:六张表撑起整个平台
数据库表是项目的“地基”,如果表设计不对,后面前后端联调会反复返工。做资讯类平台,核心表我可以直接列出来,你们做参考时根据自己需求微调字段即可。
user用户表:id、username、password(存加密后的密文)、nickname、avatar、role(区分 admin / user)、create_time。category分类表:id、name、sort(排序权重)、create_time。汽车资讯的分类一般有“新车发布”“车型评测”“导购推荐”“用车养车”“行业动态”这几类。article资讯表:id、category_id、title、cover_image、summary、content、author、views(浏览量)、status(0草稿 / 1已发布)、create_time、update_time。banner轮播图表:id、image_url、title、link_url、sort、status、create_time。comment评论表:id、article_id、user_id、content、create_time、status(0待审核 / 1通过 / 2删除)。collect收藏表:id、article_id、user_id、create_time。
这里你有两个字段设计上的经典决策点需要留意。第一个是 article 表里要不要直接冗余 category_name 分类名称?我的经验是不冗余,只存 category_id,通过查询时 JOIN 出来。这样分类名称如果改了,资讯里不会出现旧名称残留,数据一致性更好。第二个是资讯正文 content 用什么类型?如果只发普通图文,TEXT 类型大概 64KB,已经足够。如果以后要放长图文或嵌入代码,建议直接上 LONGTEXT,但要注意 MySQL 的 max_allowed_packet 配置,否则批量导入时会报 Packet for query is too large 的错误。
2.2 索引设计:毕设也需要一点点性能意识
毕设的数据量大概率很小,但答辩时老师一定会问“有没有建索引”。永远不要在数据库层面敷衍这个问题,因为你不知道答辩老师会追问到哪一层。简单说,建立索引的依据是查询的 WHERE 条件和排序字段。
article 表里,status 和 category_id 是高频过滤条件,建议建普通索引;create_time 经常用来按时间排序,也建议加入索引。comment 表的 article_id 是典型的外键关联查询字段,必须加索引。collect 表则建议在 user_id 和 article_id 上建联合唯一索引,防止同一个用户重复收藏同一条资讯。
不加索引在数据量小的时候看不出来问题,但索引这个点一旦在答辩时被问到,你能答出“联合索引最左前缀原则”或者“为什么不要在每个字段上都建索引”,就已经超过绝大部分本科毕设的水平了。
2.3 外键到底用不用:我的建议是逻辑外键
很多教材里强调外键约束,做毕设时学生也会习惯性地在表上建 FOREIGN KEY。我的实际做法是:不建物理外键,只保留逻辑外键。原因很简单,MyBatis-Plus 这类 ORM 框架对物理外键没有特殊支持,反而在删除分类时,如果分类下还有资讯,物理外键会直接报错阻塞。而逻辑外键配合后端代码判断,比如删除分类前先检查有没有关联资讯,既能保证数据完整,也让开发体验顺滑得多。
3. 后端核心实现:SpringBoot 如何把接口做得干净利落
3.1 统一返回结果:前后端对接的“通用语言”
前后端分离项目最忌讳的就是后端接口返回格式不统一,一会儿直接返回 Map,一会儿返回 String,前端每个请求都要单独处理解析逻辑,开发效率低到崩溃。做项目第一件事,就是先定义一个统一响应体。
java复制@Data
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("操作成功");
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;
}
}
有了这个 Result,后端所有接口返回值都统一为 Result<具体类型>,前端拿到响应后先判断 code 是否等于 200,再做后续逻辑。这个约定要从项目第一天就定好,否则接口写了十几个后再来改,排查成本极高。
3.2 登录认证:JWT 的无状态设计怎么落地
传统单体应用用 Session 保存登录状态,前后端分离后有两个毛病:一是跨域场景下 Cookie 管理麻烦,二是服务端需要维护会话存储。JWT(JSON Web Token)的核心理念是把用户信息加密后生成令牌,服务端不存状态,只要验签通过就信任令牌内容。
实际落地时,登录接口校验用户名密码成功后,用 jjwt 库生成 token,把用户 id、用户名、角色塞进 token 的 claims 里,设置过期时间。前端拿到 token 后存到 localStorage,之后每次请求都在请求头里带上 Authorization: Bearer <token>。后端写一个拦截器或者 Spring MVC 的 HandlerInterceptor,对所有需要鉴权的接口做 token 解析。
java复制public class JwtInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (request.getMethod().equals("OPTIONS")) {
return true;
}
String token = request.getHeader("Authorization");
if (token != null && token.startsWith("Bearer ")) {
token = token.substring(7);
try {
Claims claims = JwtUtil.parseToken(token);
request.setAttribute("userId", claims.get("userId"));
request.setAttribute("role", claims.get("role"));
return true;
} catch (Exception e) {
response.setStatus(401);
response.getWriter().write("token无效或已过期");
return false;
}
}
response.setStatus(401);
response.getWriter().write("未登录");
return false;
}
}
3.3 接口实现:以“资讯发布”为例跑通全链路
资讯发布是管理端最核心的功能,涉及文件上传、数据组装、持久化。后端接收前端传来的表单数据,包含标题、分类、封面图 URL、摘要、正文,先做基础校验,再设置 createTime 和 status(草稿还是直接发布),最后调用 Service 层写入数据库。
一个容易忽略的细节是,封面图往往是通过单独的文件上传接口先处理好,把返回的 URL 再和表单数据一起提交。不要把图片的 Base64 字符串直接塞进资讯表,否则数据库体积迅速膨胀,查询性能也会明显下降。图片上传接口建议用服务端本地磁盘存储,返回相对路径,前端使用该路径时再由后端做静态资源映射。
3.4 MyBatis-Plus:效率与规范的平衡点
在 Spring Boot 项目里操作 MySQL,最省心的是 MyBatis-Plus,它对 CRUD 做了内置封装,单表操作连 SQL 都不用写。对于这个项目,我建议的姿势是:实体类、Mapper、Service、Controller 四层结构,每张表对应一套标准代码。
核心配置里需要注意的是,MyBatis-Plus 默认会把驼峰属性映射到下划线字段,只要数据库字段用下划线命名,Java 字段用驼峰命名,就无需手动写 @TableField 映射。分页查询用内置的分页插件,配置一个 MybatisPlusInterceptor 就好,不用自己拼 LIMIT。
yaml复制mybatis-plus:
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
logic-delete-field: deleted
logic-delete-value: 1
logic-not-delete-value: 0
4. 前端实现:Vue 项目怎么组织才能不烂尾
4.1 项目结构与路由拆分
创建 Vue 项目时,建议直接分两个大目录:views/user 放前台页面,views/admin 放后台管理页面。这样代码结构清晰,也方便路由守卫做统一拦截。
路由设计上,用户端可访问的页面包括首页、资讯列表、资讯详情、我的收藏,管理端包括登录页、仪表盘、资讯管理、分类管理、评论管理、用户管理。这里要特别注意区分:用户端页面和管理端页面虽然都在同一个 Vue 项目里,但二者是两种不同的布局风格,建议用嵌套路由分别定义布局组件,避免在页面里到处写 if 判断。
javascript复制const routes = [
{
path: '/',
component: UserLayout,
children: [
{ path: '', component: Home },
{ path: 'news', component: NewsList },
{ path: 'news/:id', component: NewsDetail }
]
},
{
path: '/admin',
component: AdminLayout,
redirect: '/admin/dashboard',
children: [
{ path: 'dashboard', component: Dashboard },
{ path: 'article', component: ArticleManage }
]
}
];
另外强烈建议使用“路由懒加载”的方式,也就是把 component 写成 () => import('@/views/...')。这能显著优化首屏加载速度,因为用户打开首页时只需要加载首页相关的 JS 文件,不会一次性把整个后台管理代码全部下载下来。很多新手打包完发现 index.js 有 2MB 以上,多半就是没有做代码分割。
4.2 Axios 封装:拦截器是前后端联调的生命线
不封装 Axios,直接在组件里写 this.$http.get(...),这个项目很快就会陷入“到处散落请求代码”的混乱状态。统一封装的核心工作有三件:设置 baseURL、请求拦截器注入 token、响应拦截器统一处理错误码。
javascript复制import axios from 'axios';
import { ElMessage } from 'element-plus';
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 => {
const res = response.data;
if (res.code !== 200) {
ElMessage.error(res.message || '请求失败');
return Promise.reject(new Error(res.message));
}
return res;
},
error => {
if (error.response && error.response.status === 401) {
localStorage.removeItem('token');
window.location.href = '/admin/login';
}
ElMessage.error(error.response?.data?.message || '网络异常');
return Promise.reject(error);
}
);
export default request;
4.3 Vue 播放 m3u8 流的小技巧
既然是汽车资讯网站,少不了会有视频评测类的需求。虽然纯 Vue 项目直接放 MP4 链接也行,但做视频资讯时,时不时的会碰到 m3u8 格式的视频流。Vue 播放 m3u8 的标准做法是基于 hls.js 库,先判断浏览器原生是否支持 HLS(Safari 一般原生支持),不支持的情况用 hls.js 自动转码播放。
javascript复制import Hls from 'hls.js';
export function playM3u8(videoEl, url) {
if (videoEl.canPlayType('application/vnd.apple.mpegurl')) {
videoEl.src = url;
} else if (Hls.isSupported()) {
const hls = new Hls();
hls.loadSource(url);
hls.attachMedia(videoEl);
} else {
console.error('当前浏览器不支持 m3u8 播放');
}
}
需要注意,hls.js 处理跨域视频流时,视频服务器必须配置 CORS 响应头,否则会有加载失败的问题。做毕设时如果你本地没有视频源,也可以直接用小体积 MP4 代替,展示原理即可。
5. 环境配置与部署:把项目跑起来才是硬道理
5.1 环境准备清单
做项目前一定要把环境整理干净,否则后续每写一行代码都可能被环境问题打断。我的建议环境清单如下:
- JDK 8 或 11(不建议一上来就 JDK 17,除非你确定 SpringBoot 版本兼容)
- Maven 3.6+,配置阿里云镜像仓库,不然下载依赖的速度会让你怀疑人生
- MySQL 5.7 或 8.0,5.7 资料更多,8.0 功能更强,毕设两个都行
- Node.js 14+,npm 或 cnpm,Vue 2 用 Node 14 很稳,Vue 3 + Vite 建议 Node 16+
- IDEA(后端)、VSCode(前端),两个都装好对应插件
Maven 卡在下载依赖是很多同学第一次崩溃的原因。在 settings.xml 里加上阿里云镜像:
xml复制<mirror>
<id>aliyunmaven</id>
<mirrorOf>central</mirrorOf>
<name>阿里云公共仓库</name>
<url>https://maven.aliyun.com/repository/public</url>
</mirror>
5.2 SpringBoot 版本选择:不要盲目追新
开发前要确认一个核心问题:SpringBoot 版本和 JDK 版本的对应关系。SpringBoot 2.7.x 默认支持 JDK 8,而 SpringBoot 3.x 开始强制要求 JDK 17。不少同学在 IDEA 里创建项目时,图省事直接用最新版 SpringBoot,结果本地 JDK 还是 8,项目一启动就报 UnsupportedClassVersionError。
我的建议是,如果只是做毕设,就老老实实选 2.7.x 版本,稳定、教程多、社区问答丰富。如果你想让简历加分,倒是可以试试 3.x + JDK 17,但要有一个心理准备:很多第三方 starter 对新版本的适配可能不完善,出现问题需要自己排查。
5.3 前后端联调与部署:Nginx 反向代理配置
开发模式下,前端通过 Vite 或 Webpack 的代理转发解决跨域请求。比如 Vue 项目在 vue.config.js 里配置:
javascript复制module.exports = {
devServer: {
port: 3000,
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true
}
}
}
};
生产环境下,用 Nginx 托管前端打包后的静态文件,并把 /api 请求反向代理到后端服务。这里有一个经典坑:Vue 路由默认是 history 模式,如果 Nginx 只配置了静态文件路径,刷新 /admin/dashboard 页面时会 404,必须加上 try_files 配置。
nginx复制server {
listen 80;
server_name localhost;
root /var/www/html;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
location /api/ {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
5.4 图片上传的本地存储与访问
开发阶段做图片上传,最简单可靠的方案是存到服务器本地一个 /upload 目录,同时在后端配置静态资源映射。SpringBoot 里写一个配置类:
java复制@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/upload/**")
.addResourceHandler("file:" + uploadPath + "/");
}
}
这样一来,前端回显图片时的相对路径 /upload/xxx.jpg 就能被正确解析。不推荐用本地存储做项目的原因只有一个:存储不可扩展,且服务器重启后文件容易丢失。但毕设项目没有高并发和海量数据,本地存储是最务实的选择。
6. 从代码到面试:答辩与求职两不误
6.1 毕设答辩的高频追问
答辩时间通常有限,老师不会真的从第一行代码看到最后一行,他们更关注的是:项目是不是你自己写的、你对关键技术有没有理解。高频问题基本集中在下面几个方向:
- 前后端分离项目如何处理跨域?
- JWT 的认证流程是什么?token 过期了怎么办?
- 数据表怎么设计的?查询用到了哪些索引?
- 项目怎么部署的?Nginx 在其中的作用是什么?
- 如果并发量变大,哪些地方会成为瓶颈?
第一个问题,跨域的本质是浏览器同源策略,开发环境通过代理转发解决,生产环境通过 Nginx 反向代理解决。第二个问题,JWT 的过期策略一般是前端拦截 401 后跳登录页,或者做 token 刷新机制,毕设级别做前者就够了。第三个问题,能把自己的表结构和索引设计清晰地说出来,要点在于讲清楚“为什么这么设计”。第四个问题则要强调 Nginx 的动静分离和反向代理特性。
6.2 这个项目如何延伸成简历亮点
如果只是“做出来一个系统”,简历上写“熟悉 SpringBoot”,面试官不会觉得有多特别。真正加分的做法是:把技术深度挖出来,把项目里的一个点讲透。比如“我在项目中基于 JWT 实现了一套无状态登录认证,并利用拦截器统一校验权限”,比“项目中有登录功能”高级得多。再比如“使用 MyBatis-Plus 实现单表 CRUD 和分页,并通过更新时间字段做乐观锁,避免并发编辑冲突”,这些都是能体现思考深度的点。
更进一步的延伸方向,可以把单机部署升级成 Docker 容器化,把图片本地存储换成云存储,把定时统计资讯浏览量改成使用 Redis 缓存热点数据。这些不一定是必须实现的,但在面试中说出来,会让面试官觉得你有主动扩展的意识。
6.3 一次“从零到一”的开发节奏建议
给正在做课设或毕设的同学一个实在的开发节奏建议。第一周把环境配好,数据库脚本写完,后端骨架跑通;第二周把后端管理端接口全部写完;第三周把前端用户端页面做完,联调通基本流程;第四周完善后台管理功能、处理异常情况、准备文档和答辩 PPT。不要把时间耗在纠结类名、表名这些细枝末节上,先把“查得到、发得出、播得了”的主流程跑通,后面再慢慢打磨。
7. 常见问题与解决方法速查表
我整理了一份我在实际开发和答疑过程中频繁遇到的典型问题,直接按表格给出排查思路,方便你遇到问题时快速定位。
| 现象 | 可能原因 | 解决方法 |
|---|---|---|
| Maven 依赖下载缓慢或失败 | 未配置国内镜像,或网络被限制 | 修改 settings.xml,配置阿里云公共仓库镜像 |
启动提示 Port 8080 was already in use |
端口被其他进程占用 | netstat -ano 找到占用进程,换端口或在配置文件中修改 server.port |
数据库连接失败 Access denied for user 'root'@'localhost' |
用户名密码错误,或 MySQL 服务未启动 | 检查 application.yml 中的数据库账号密码,确认 MySQL 服务已启动 |
| 前后端联调接口 404 | 后端接口路径不对,或 nginx 未正确代理 | 检查后端 Controller 的 @RequestMapping 值和前端 baseURL 是否匹配,生产环境检查 Nginx 的 location 配置 |
| 上传图片后回显 404 | 静态资源映射未配置,或文件路径不对 | 确认 addResourceHandlers 映射路径,确认上传文件实际保存路径 |
| 前端页面刷新后 404 | Vue Router 开启 history 模式,但服务器未配置 try_files | Nginx 加 try_files $uri $uri/ /index.html; |
| token 失效后接口报 401,前端无响应 | 响应拦截器未处理 401,或 token 未注入请求头 | 在 Axios 响应拦截器检测 401,清除本地 token,跳转登录页 |
| 时间字段显示为 UTC 或相差 8 小时 | JDBC 连接串未设置时区 | 在数据库连接 URL 中加入 serverTimezone=Asia/Shanghai |
| 评论区出现 HTML 标签展示 | 前端未做内容转义,用户提交了富文本 | 前端插入内容时使用纯文本渲染,或在后端做 XSS 过滤 |
8. 写在最后的一点个人心得
做了几个类似的信息管理平台之后,我最大的体会是:技术选型只是开始,真正的功夫花在把“接口约定”“数据表设计”“异常处理”这些看起来不起眼的细节做扎实。很多人觉得毕设项目嘛,能跑就行,所以代码拼凑、逻辑应付。但我想说,一个项目能不能真正给你带来成长,很大程度上取决于你愿不愿意在答辩前把每一条接口的边界情况都测一遍,愿不愿意在写数据表时多想一步索引和唯一约束。
做这个汽车资讯网站管理平台,最值钱的部分其实不是你能把前端页面做得多么花哨,而是你亲手打通“用户访问 → 前端渲染 → 后端接口 → 数据库存取”这整条链路后建立的全局认知。以后无论是去小公司做全栈,还是在大厂做某一端的专项,这种全链路感知都会成为你快速上手的底气。
最后分享一个小经验:项目做完以后,不要急着删代码,把 README 写好,注明启动步骤、默认账号密码、核心功能说明。这份文档到你答辩前、到明年复习面试题时、到以后想做横向扩展时,都会派上大用场。动手吧,代码这东西,敲起来一切问题就都不是问题了。
