尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Java毕设实战:SpringBoot+Vue全栈项目源码调试与部署指南

Java毕设实战:SpringBoot+Vue全栈项目源码调试与部署指南 在Java毕设和课设的圈子里“基于SpringBootVue的XX平台管理系统”已经是最常见的选题方向之一。这套BB平台管理系统技术栈正好落在SpringBoot、Vue、MyBatis、MySQL这四件套上前后端分离包含用户、文章、分类、评论、公告等完整业务闭环是典型的拿来就能用的入门级全栈项目。这篇内容我会结合自己实际调试和二次开发的经验把项目结构、数据库设计、环境搭建、分页缓存、路由调试、常见报错排查从头到尾过一遍尤其是那些源码里不会写、但跑项目一定会踩的细节。准备做毕业设计、课程设计或者想找一个完整项目练手的同学这篇文章应该能帮你省下不少折腾的时间。1. 项目定位与技术选型思路1.1 这套源码到底是一个什么样的系统先解释一下“BB平台”。在大多数源码站和毕设场景里BB平台指的是博客内容管理系统英文Blog Platform的缩写。它不同于电商、秒杀那种高并发系统核心业务很清晰用户注册登录、文章发布与编辑、分类和标签管理、评论互动、公告通知外加后台管理员的用户管理和内容审核。一层层拆开看每个功能都是JavaWeb开发的高频知识点组合在一起就构成了一个完整的管理系统。这套源码适合谁如果你是刚学完Java基础、Servlet或SSM想看看企业里常见的SpringBoot项目长什么样它比单纯看教程有价值得多。如果你是准备毕设需要快速跑通一个功能完整、能写论文、能答辩的项目它同样是个稳妥选择。它的功能模块数量不会多到让你看不懂也不会少到评委觉得工作量不够属于刚刚好的范围。从技术形态上看它采用前后端分离架构后端是SpringBoot提供的RESTful API前端是Vue负责页面渲染和交互。前端的页面包括登录注册页、文章列表页、文章详情页、后台管理页后端的接口包括登录鉴权、文章CRUD、分类管理、评论管理、用户管理等。整套系统跑通以后你可以很清晰地看到一次请求从前端点击按钮开始经过axios、Vue路由、后端Controller、Service、Mapper最后到MySQL返回数据再一路回到前端渲染成页面的全过程。1.2 为什么是SpringBootVueMyBatisMySQL很多同学会问这个技术栈是不是过时了我的观点是它不但不过时反而是目前国内中小型项目里覆盖率最高的一套组合。SpringBoot解决了传统SSM项目配置繁琐的问题。以前搭一个SpringMVC项目要写web.xml、spring.xml、springmvc.xml还要手动部署到Tomcat。SpringBoot把这些全部简化了内嵌Tomcat一个main方法直接启动约定优于配置适合快速开发。更重要的是招聘市场上SpringBoot几乎是Java后端岗位的必备技能面试问SpringBoot自动配置、Starter原理、常用注解的比例极高。Vue作为前端框架核心优势是组件化和响应式数据绑定。页面被拆分成一个个组件数据变了视图自动更新开发体验比jQuery时代好太多。从前端就业市场的角度Vue在国内企业中的占有率依然很高而且相比React中文文档完善、上手曲线平缓对后端出身、前端基础薄弱的同学非常友好。MyBatis的存在感在于它把SQL执行和Java对象的映射做到了可控。相比Hibernate的完全自动化MyBatis允许你自己写SQL复杂查询、动态SQL都能精细控制这在业务逻辑复杂的系统里非常实用。国内大量存量项目用MyBatis所以MyBatis的面试题——缓存机制、分页插件、#{}和${}的区别——几乎成了后端面试的保留节目。MySQL就更不用说了免费、开源、稳定、社区资料多配合Navicat或命令行工具建库建表做数据操作都很快。整套组合的学习成本也合理有Java基础和一点点HTML、JS知识就能啃动。相比SpringCloud微服务、高并发中间件那一套这个项目是“跳一跳能够到”的难度适合做进阶跳板。2. 系统架构与核心模块拆解2.1 前后端分离的工程全景拿到源码先别急着点运行我建议先花20分钟把目录结构看一遍。典型的工程分为backend和frontend两个目录或者叫server和web。后端部分遵循SpringBoot标准分层控制器、业务层、数据访问层一目了然bb-platform ├── backend # 后端 SpringBoot 工程 │ ├── src/main/java │ │ └── com/bb/platform │ │ ├── controller # 接口层接收请求、返回结果 │ │ ├── service # 业务层处理核心逻辑 │ │ └── mapper # 数据访问层MyBatis Mapper接口 │ ├── src/main/resources │ │ ├── mapper # MyBatis XML 文件 │ │ └── application.yml # 核心配置文件 │ └── pom.xml ├── frontend # 前端 Vue 工程 │ ├── src │ │ ├── api # 封装 axios 请求 │ │ ├── router # 路由配置 │ │ ├── views # 页面组件 │ │ ├── components # 公共组件 │ │ └── main.js # Vue 入口文件 │ ├── package.json │ └── vue.config.js └── sql # 数据库初始化脚本后端的请求处理链路是Controller接收前端请求调用Service层处理业务Service通过Mapper接口访问数据库。Controller只负责参数接收和结果封装不写业务逻辑Service层负责事务和业务规则Mapper只做数据读写。这种分层的好处是职责清晰出了问题很容易定位。前端的数据流动可以类比成一个“点菜”过程。Vue页面是菜单界面axios是把订单送到后厨的传菜员后端的Controller是大堂经理Service是厨师长MyBatis是后厨和仓库之间的调度员。用户在前端点击“查询文章”axios携带参数请求后端接口后端层层处理最后从MySQL取回数据再按JSON格式返回前端拿到数据填充表格。2.2 数据库设计核心表与关键字段这套系统的数据库表数量一般在6张左右我把最常见的设计梳理一下。以下是简化版的建表脚本直接执行就能用-- 用户表 CREATE TABLE t_user ( id int NOT NULL AUTO_INCREMENT COMMENT 主键, username varchar(50) NOT NULL COMMENT 用户名, password varchar(255) NOT NULL COMMENT 密码(加密存储), nickname varchar(50) DEFAULT COMMENT 昵称, avatar varchar(255) DEFAULT COMMENT 头像地址, role tinyint DEFAULT 0 COMMENT 角色: 0普通用户 1管理员, status tinyint DEFAULT 1 COMMENT 状态: 1正常 0禁用, create_time datetime DEFAULT NULL COMMENT 创建时间, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表; -- 文章表 CREATE TABLE t_article ( id int NOT NULL AUTO_INCREMENT COMMENT 主键, title varchar(200) NOT NULL COMMENT 标题, content longtext COMMENT 内容(富文本HTML), cover varchar(255) DEFAULT COMMENT 封面图, category_id int DEFAULT NULL COMMENT 分类ID, user_id int DEFAULT NULL COMMENT 作者ID, views int DEFAULT 0 COMMENT 浏览量, status tinyint DEFAULT 0 COMMENT 状态: 0草稿 1发布 2删除, create_time datetime DEFAULT NULL, update_time datetime DEFAULT NULL, PRIMARY KEY (id), KEY idx_category (category_id), KEY idx_user (user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT文章表;设计上有几个细节值得注意。一是分类表和文章表是典型的一对多关系文章表存category_id外键查询的时候用连表JOIN取分类名称。二是用户表和文章表也是一对多关系文章表存user_id表示作者。三是评论表一般结构为id、article_id、user_id、content、create_time它同时关联用户和文章所以经常会出现多表联查的场景。密码字段一定不要明文存储哪怕是毕设项目也要有安全意识。常见做法是用MD5加盐或者BCrypt加密登录时将用户输入的密码加密后与数据库比对。文章内容用longtext类型存储HTML富文本前端用v-html指令渲染这里就牵扯到XSS过滤的问题后面我会单独提。2.3 后端业务闭环认证、权限与文章发布现在把核心业务串起来看。用户从前端输入用户名密码点击登录Vue端调用/api/user/login接口后端Service接收到请求后根据用户名查库、比对密码验证通过后签发一个JWT令牌返回给前端。前端把这个令牌存在localStorage之后每次请求都在请求头里带上Authorization: Bearer token。后端的拦截器负责拦截需要登录才能访问的接口从请求头取出token并解析验证。手动实现拦截器并不复杂核心逻辑如下public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token request.getHeader(Authorization); if (StringUtils.hasText(token) token.startsWith(Bearer )) { token token.substring(7); try { // 解析token把用户信息放入ThreadLocal或request作用域 Claims claims JwtUtil.parseToken(token); request.setAttribute(userId, claims.get(userId)); return true; } catch (Exception e) { // token无效或过期返回401 response.setStatus(401); return false; } } // 未携带token返回401 response.setStatus(401); return false; }文章发布流程也是这样一条链路前端富文本编辑器生成HTML内容axios通过POST请求提交title、content、categoryId等字段后端Controller用文章实体接收Service设置当前登录用户为作者、状态为草稿或发布Mapper执行insert最后返回自增主键。整个过程跑通你也就理解了JWT认证、事务控制和MyBatis插入返回主键这几个常被面试官追问的知识点。3. 从零跑通环境准备与项目启动实操3.1 环境版本选型先看这张对照表跑通这套源码最大的障碍说白了就是环境不一致。很多源码站只给了源码没告诉你自己的JDK、Node、MySQL是什么版本结果一启动就报错。我把经过验证的兼容版本整理成了一张表按这个配置基本不会出大问题组件推荐版本说明JDK8 或 11看源码基于SpringBoot 2.x还是3.x2.x用8或113.x必须17Maven3.6.3用于后端依赖下载和打包Node.js14~18Vue2项目用14/16Vue3项目建议16.18或18npm6.14跟随Node版本建议用淘宝镜像源MySQL5.7 或 8.08.0需要改驱动类名和时区配置IDEIDEA 2021需要安装Lombok插件这里重点说一下容易翻车的点。SpringBoot 3.x相比2.x有一处重大变化就是javax包名换成了jakarta如果你的JDK是8硬跑3.x项目会直接编译失败。很多2025年的“最新源码”可能直接上了SpringBoot 3所以要看清pom.xml里的版本再决定装哪个JDK。Vue项目同理如果package.json里依赖了node-sassNode版本太高会编译报错因为它依赖的node-sass编译二进制可能根本不支持你的Node环境。MySQL驱动是另一个经典问题。MySQL 5.7时代驱动类名是com.mysql.jdbc.Driver8.0之后变成了com.mysql.cj.jdbc.Driver同时连接地址要加serverTimezoneAsia/Shanghai不然会报时区错误。具体的数据库连接配置示例是这样spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/bb_platform?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 1234563.2 后端启动改配置、导脚本、起服务按下启动按钮之前先把准备工作做完。第一步用Navicat或命令行工具新建一个名为bb_platform的数据库字符集选择utf8mb4然后把sql目录下的初始化脚本整个执行一遍。执行之前扫一眼脚本内容确认表名和字段名后面连接数据库时能减少很多不必要的困惑。第二步打开后端项目的application.yml把数据库用户名和密码改成你自己的。不要只改密码不改用户名更不要用默认的空密码却不知道root用户其实设置了密码。这里改错是最常见的启动失败原因。第三步如果你的IDE还没装Lombok插件一定要装。现在写单子很多源码用Getter、Setter、Data等注解简化实体类代码没装插件的话实体类方法会全部报红虽然不是真正编译不过但特别影响心情。第四步启动项目。主类上的SpringBootApplication注解标记的入口类运行main方法。看到类似Tomcat started on port(s): 8080的日志就是启动成功。如果8080被占用修改application.yml里的server.port比如改成9000。前端调试时记得在代理配置里同步改。3.3 前端启动依赖安装、代理配置与登录验证后端跑起来之后打开frontend目录在终端执行依赖安装。npm在国内下载依赖很慢建议先配置镜像源npm config set registry https://registry.npmmirror.com然后执行npm install npm run serve如果一切顺利几秒钟后就能看到编译成功的提示浏览器访问localhost:8080弹出登录页。但这里有一半的概率会遇到跨域问题因为前端地址是8080后端是另外一个端口浏览器默认禁止跨域访问。解决方案是配置Vue的代理在vue.config.js里把请求转发到后端module.exports { devServer: { proxy: { /api: { target: http://localhost:9000, changeOrigin: true } } } };这样配置以后前端发请求的地址是/api/user/login代理会把它转发到http://localhost:9000/api/user/login浏览器看到的始终是同源请求跨域问题就绕开了。等整个登录注册流程能跑通说明后端、数据库、前端已经完全打通了。4. MyBatis持久层从分页插件到二级缓存4.1 手写XML还是MyBatis-Plus先说清楚很多拿到这套源码的同学都会发现Mapper层有的是接口加XML有的是纯注解。两者各有适用场景。注解适合简单CRUD比如按主键查询、单表插入一两行就能解决XML适合复杂动态SQL比如多表联查、条件拼接、批量更新。我这套源码里复杂的文章列表查询就是XML实现的例如文章列表需要关联分类表和用户表select idselectArticleList resultMapArticleResultMap SELECT a.*, c.name AS category_name, u.nickname AS author_name FROM t_article a LEFT JOIN t_category c ON a.category_id c.id LEFT JOIN t_user u ON a.user_id u.id where if testcategoryId ! null AND a.category_id #{categoryId} /if if testkeyword ! null and keyword ! AND (a.title LIKE CONCAT(%, #{keyword}, %) OR a.content LIKE CONCAT(%, #{keyword}, %)) /if AND a.status 1 /where ORDER BY a.create_time DESC /select这里有几个经验点。一是resultMap比resultType更常用因为数据库字段名下划线风格如create_time和Java属性驼峰风格createTime不同虽然可以在全局配置里开启驼峰映射但多表关联时的嵌套对象还是resultMap更清晰。二是#{}和${}的区别是面试高频考点#{}预编译防SQL注入${}直接拼接SQL只适合用来动态传表名、排序字段这类场景。三是多表查询出来的列别名比如category_name需要通过resultMap映射到Vo对象的categoryName属性。4.2 分页插件PageHelper的正确用法与三个大坑文章列表、评论列表这类功能必然要分页。手动写LIMIT也不是不行但要自己处理页码、总条数、总页数比较繁琐。所以源码里通常用PageHelper分页插件。引入依赖也很简单dependency groupIdcom.github.pagehelper/groupId artifactIdpagehelper-spring-boot-starter/artifactId version1.4.7/version /dependency使用时的经典写法是public PageResultArticleVO getPage(int pageNum, int pageSize, Integer categoryId) { PageHelper.startPage(pageNum, pageSize); ListArticleVO list articleMapper.selectArticleList(categoryId); PageInfoArticleVO pageInfo new PageInfo(list); return PageResult.of(pageInfo); }PageHelper的底层原理是拦截器它会把startPage之后的第一个SQL语句拦截下来自动拼接LIMIT同时执行一条COUNT语句统计总数。用起来流畅但必须注意三个大坑。第一个坑startPage必须紧跟查询语句中间一旦插入了其他查询或修改操作分页就会加在错误的那条SQL上导致数据完全不对。第二个坑分页插件只能拦截一条SQL如果业务方法里先查了一次再在循环里查询只有第一次查询分页生效。第三个坑多表联查时会发现统计的总页数和实际不符因为COUNT的时候PageHelper默认统计的是当前这条SQL的总数解决办法是手动写count查询或调整SQL。4.3 一级缓存、二级缓存与数据库切换MyBatis的缓存机制是面试官最爱问的点刚好这个项目里也会有体现。一级缓存默认开启它是SqlSession级别的缓存同一个SqlSession内重复查询相同SQL会直接命中缓存不查数据库。它的生命周期很短一个事务结束缓存就没了实际项目中感受不明显。二级缓存是Mapper级别的跨SqlSession缓存需要手动开启。开启方式是在Mapper XML里加一行cache evictionLRU flushInterval60000 size512 readOnlytrue/同时实体类要实现Serializable接口因为二级缓存可能会存到磁盘或通过网络共享。开启之后两次不同的SqlSession执行同一查询第二次就能从缓存直接命中减少数据库压力。但这里我要提个醒文章这种更新频率不高的数据开二级缓存还行评论、浏览量这种实时性要求高的数据开缓存会导致用户看到旧数据。实际开发里更推荐Redis做缓存层MyBatis的二级缓存反而用得不多。另外有人问过MyBatis能不能支持各种国产数据库其实Mapper层跟数据库厂商基本解耦真正要动的是数据库驱动类名、方言分页SQL和部分特殊函数其他代码不需要大面积重写。5. Vue前端路由、组件与调试三板斧5.1 路由参数传递这三种场景必须分清Vue前端最核心的模块一个是路由一个是页面交互。文章详情页需要知道当前文章ID分类列表页需要知道当前分类ID这些都是通过路由参数传递的。常见的传参方式有三种初学者一定要分清楚。动态路由传参的典型配置是{ path: /article/:id, name: ArticleDetail, component: ArticleDetail }跳转时用this.$router.push(/article/ id)详情页里用this.$route.params.id读取参数。这种方式URL比较美观适合文章详情这种有明确主键的场景。查询参数方式则是通过?categoryId1这种形式传参页面用this.$route.query.categoryId获取。它适合筛选列表、搜索分页这种参数较多且不固定在URL路径里的场景。还有一个是编程式导航的完整写法this.$router.push({ name: ArticleDetail, params: { id: row.id } });新手最常犯的错是把params和query混用用params却配了query的路径表达式结果拿不到参数。我的建议很简单路径模板里有冒号占位符的用params没有占位符、需要拼接URL参数的用query。另外还有一个重点params传参配合路由刷新后丢失是常见击破点需要看具体业务接受程度一般会从后端重新拉取详情数据影响不大。5.2 富文本编辑器、文件上传与M3U8播放博客发布文章必然要接富文本编辑器。常见的选择是vue-quill-editor或wangEditor集成方式都差不多npm安装依赖然后在组件里注册并绑定v-model。富文本编辑器产生的是一段HTML保存到数据库时后端要做好长度校验和XSS过滤不能什么标签都放行。图片上传也是文章发布里的高频需求。最简单的做法是把图片转成Base64直接塞进content但这样文章一长数据库体积会很难看。推荐做法是前端用FormData把文件POST到后端的文件上传接口后端用MultipartFile接收保存到本地磁盘或MinIO对象存储再把访问URL返回给前端编辑器插入该URL。后端接口大概长这样PostMapping(/api/upload/image) public ResultString uploadImage(RequestParam(file) MultipartFile file) { // 校验文件大小、类型重命名文件保存到指定目录 String url fileService.save(file); return Result.success(url); }另外提一个可能遇到的需求如果这个平台后来要加视频课程功能你会发现M3U8格式的流媒体视频不能直接用video标签打开。HLS直播流和切片视频需要hls.js库来处理前端引入hls.js之后通过Hls.isSupported()判断浏览器支持情况再调用hls.loadSource(url)加载视频源。这个虽然不是博客系统的标配但算是很常见的扩展点。5.3 Vue DevTools、Network面板与跨域调试前端调试的工具我真心推荐两个。一个是Vue DevTools浏览器插件安装之后打开控制台可以看到所有组件的props、data、computed还能事件溯源、时间旅行调试排查数据为什么没渲染出来非常高效。另一个就是浏览器自带的Network面板所有请求方法的类型、状态码、响应时间、请求参数、返回数据一目了然。我排查问题的时候习惯先把操作复现一遍然后在Network里找到对应请求先看状态码再看响应数据基本能锁定是前端问题还是后端问题。如果状态码是401说明登录凭证失效或未携带检查token有没有存在localStorage、请求拦截器有没有在header里塞token。如果状态码是404看后端Controller有没有对应接口、路径是否匹配。如果状态码是500拿着后端控制台的异常堆栈去搜绝大多数都有答案。跨域问题再补充一句开发环境用vue.config.js代理即可但生产环境构建之后代理是不生效的。生产环境通常是把前端dist目录交给Nginx托管然后在Nginx里配置反向代理把/api开头的请求转发到后端的SpringBoot服务。只要配置正确跨域问题在生产环境同样可以避免。6. 常见问题与排查技巧实录6.1 启动阶段高频报错速查表我在帮人远程调试时发现大量时间花在环境问题上。下面这些报错和对应的解决方案可以说是跑SpringBootVue项目的必背清单报错现象原因排查解决方案8080端口被占用其他程序占端口改server.port或用netstat -ano查占用进程Maven依赖下载失败网络或中央仓库慢换成阿里云镜像使用Maven国内仓库java: 程序包javax不存在JDK版本与SpringBoot版本不匹配3.x项目用JDK17确认导入JDK版本Unknown initial character set index连接URL没指定编码url加characterEncodingutf8The server time zone valueMySQL驱动版本或时区问题url加serverTimezoneAsia/ShanghaiAccess denied for user root数据库账号密码错误核对application.yml里的用户名密码java.sql.SQLSyntaxErrorExceptionSQL脚本没执行完整或表名写错重新执行建表脚本核对表名前缀Cannot load driver class驱动依赖缺失或类名写错检查pom依赖确认driver-class-namenode-sass版本与Node不兼容前端编译报错Node换14/16版本或改用sass替代axios请求跨域前后端端口不一致配置vue.config.js proxy代理这里面最容易被忽视的是Maven依赖下载问题。很多项目pom.xml里依赖好几层个别jar包下载失败会导致一大片类找不到。解决方案是在maven的settings.xml里配置阿里云镜像如果公司或学校内网有私有仓库优先用内网仓库速度会快很多。6.2 运行期问题跨域、乱码、登录失效与安全过滤启动成功不代表万事大吉运行期的问题更考验排查能力。第一个是中文乱码页面和数据库里看到的全是问号或乱码字符。问题一般出在三个地方MySQL数据库或表本身字符集不是utf8mb4、连接URL缺失characterEncodingutf8、页面本身没声明UTF-8。逐个排查基本都能解决。第二个是登录失效的问题。JWT令牌过期是比较正常的设计前端需要在axios响应拦截器里统一处理401状态比如弹窗提示“登录已过期请重新登录”然后跳转到登录页清空localStorage。如果发现token明明没过期却一直401检查一下后端解析token时是不是把Bearer前缀也纳入了验证逻辑。第三个是安全过滤问题这个很多学生项目会忽略。文章内容是富文本HTML如果不对标签做白名单过滤用户可能插入恶意脚本导致存储型XSS。完全可以利用SpringBoot的全局过滤器对Content-Type为application/json的请求统一解析对富文本字段里的script、iframe等危险标签做剥离或转义。我之前处理过一个上传PDF的场景安全性要求更高全局过滤器要解析multipart请求里的文本字段流程会比纯JSON复杂一些但原理一致过滤不是业务逻辑而是横切关注点。6.3 从开发到部署构建打包与上线本地跑通之后很多同学还想把项目部署到服务器给答辩或演示用。后端打包很简单mvn clean package -DskipTests执行完会在target目录下生成一个jar包比如bb-platform-0.0.1-SNAPSHOT.jar。然后在服务器上执行java -jar启动即可。如果你的项目用了外部配置文件构建时注意配置文件路径别让服务器上的数据库地址和本地不一致。前端打包更简单npm run build生成的dist目录就是纯静态文件把它放到Nginx的html目录下配置nginx.conf里的server块并加上代理转发规则。我遇到过一个有意思需求有同学只剩jar包找不到原始工程代码了想找回项目结构这种情况下确实可以用反编译工具查看class文件但要提醒一句反编译只能用于自己拥有版权的项目代码拿别人的项目反编译是绝对不行的涉及版权和合规风险。最后再分享一个我自己的习惯每次动手改代码之前先把原始源码备份一份。调试过程中改乱了配置、改错了代码随时可以回退重来。这套BB平台管理系统结构很典型把用户、文章、评论这三条业务线逐行看明白你对SpringBoot和Vue的掌握会完全不一样。跑通之后再自己动手往里面加功能比如接入Redis缓存、增加操作日志、引入MyBatis-Plus简化开发每一步都是实打实的技术亮点。编程这东西看别人跑十遍都不如自己从头到尾跑通一遍来得实在。
返回列表