ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

SpringBoot+Vue毕设源码拆解:从跑通到真正掌握项目核心

SpringBoot+Vue毕设源码拆解:从跑通到真正掌握项目核心

很多人拿到一套 SpringBoot + Vue 的毕设源码,第一件事是导入 IDEA、改个数据库密码、Run 起来,看到页面跳出来就长出一口气,截图保存,当作完工。我见过好几回这种场景,包括这套「七彩云南文化旅游网站平台」。项目名后缀写得很诱人:完整项目源码 + SQL脚本 + 接口文档,Java Web 毕设。噱头是完整的,但大多数人只是把源码跑通了,SQL脚本导完就扔一边,接口文档压根没打开。这太可惜了。

作为一个看过不少项目包、也帮人梳理过代码的人,我宁愿先泼一盆冷水:跑通 demo 只算热身,把项目的每一层剥开、重新讲明白,那才是毕设和面试真正要的东西。

所以这篇我想分享的,不是怎么下载、怎么双击运行这种保姆级教程,而是拿到「完整项目源码 + SQL脚本 + 接口文档」之后,应该用什么样的顺序去拆解、去理解、去二次开发。如果你正在消化一套类似的 Java Web 前后端分离项目,无论是七彩云南文旅网站还是别的什么业务,这套思路都通用。

1. 拿到项目包的第一步:先拆目录,别急着点运行

1.1 源码、SQL脚本、接口文档,三样东西各自承担什么角色

一个完整的 Java Web 毕设项目包,或者说任何能拿出来展示的前后端分离项目,本质上是三样东西的组合:源码、数据库脚本、接口文档。这三样对应着三种完全不同的信息,缺了谁都不完整。

源码是项目的骨骼和血肉。后端工程决定了接口能力,前端工程决定了页面表现。没有源码,一切免谈。但很多人忽略了一点:源码只能告诉你"代码是怎么写的",不能告诉你"这个系统到底要完成哪些业务"。业务逻辑的源头,其实在数据库里。

SQL脚本是地基。它包含了建库、建表、初始化数据。你只有把脚本导进 MySQL,后端才能连上库,前端才有数据可显示。更重要的是,表结构本身就是最权威的"业务说明书":有哪些用户角色、有哪些业务对象、对象之间是什么关系,看表比看代码快得多。

接口文档是地图。它告诉你后端暴露了哪些 URL、每个 URL 接收什么参数、返回什么结构。一个项目如果只有代码没有接口文档,排查问题就像没地图走山路;反过来,如果你能把接口文档和代码互相印证,整个项目的脉络会非常清楚。

所以拿到项目包后正确的动作,不是我刚才说的"导入即运行",而是先分别打开这三样东西,各花十分钟,把项目的地图先印在脑子里。

1.2 五分钟摸清项目底细:从依赖清单判断技术栈

拆目录第一步,是看后端工程的 pom.xml 和前端工程的 package.json。这两个文件是项目的"物料清单",你在里面能看到作者用了哪些技术,也能顺带判断项目的复杂程度。

后端 pom.xml 里常见的依赖就那么几类,我举几个典型的:

依赖作用为什么关键
spring-boot-starter-webWeb 基础能力没有它 SpringBoot 起不来
mybatis-plus 或 mybatis数据库访问决定 Mapper 层写法
mysql-connector-javaMySQL 驱动版本要跟数据库匹配
spring-boot-starter-data-redisRedis 缓存有它说明项目用了缓存,部署时要多启动一个服务
jjwt 或 java-jwtToken 鉴权说明登录方案是 JWT 而不是 Session
lombok简化实体类代码里大量 @Data,没有 IDE 插件会编译报错
hutool 或 commons-lang3工具集常用工具方法都从这里来

前端 package.json 里则要看这几项:

依赖作用判断结果
vue版本 2 还是 3决定你阅读代码的语法预期
element-ui / element-plus组件库对应 Vue2 / Vue3
axiosHTTP 请求库所有接口调用都靠它
vue-router路由前端页面跳转
vuex / pinia状态管理登录状态、用户信息一般放这里
echarts图表后台统计页面大概率用它

看依赖不是让你背包名,而是让你在动代码之前就知道:第一,这个项目技术栈的成熟度是否匹配你的水平;第二,你本地环境需要准备哪些东西。比如依赖里有 Redis,你就得保证本机或服务器上有 Redis 服务;前端用的是 Vue2 + Element UI,你就不能用 Vue3 的语法习惯去硬套。

1.3 工程目录结构:每个包都是干什么的

后端目录一般长这样:

src/main/java/com/xxx/ ├── controller/ # 接收请求,返回结果 ├── service/ # 业务逻辑 │ └── impl/ # 业务实现类 ├── mapper/ # MyBatis 接口 ├── entity/ # 数据库实体类 ├── dto/ # 接收参数对象 ├── vo/ # 返回给前端的对象 ├── config/ # 配置类,如拦截器、跨域、资源映射 ├── utils/ # 工具类,如JWT、日期处理 └── common/ # 统一返回结果、全局异常

前端目录一般长这样:

src/ ├── api/ # 所有接口请求函数,按模块拆文件 ├── router/ # 路由配置文件 ├── store/ # 状态管理 ├── views/ # 页面组件 ├── components/ # 复用组件 ├── utils/ # axios实例、工具函数 └── assets/ # 静态资源

这套结构是前后端分离项目最标准的组织方式。后端按技术分层,前端按页面和功能分模块。你不需要把每个文件都看完,但最好能画出这样一张图:请求从哪个页面发起,经哪个接口进入后端,落到哪张表。这张图画出来,项目就懂了一半。

2. 从"七彩云南"需求到数据表:核心业务与表结构设计

2.1 文旅平台的需求闭环:前台展示与后台管理缺一不可

七彩云南文化旅游网站平台,表面看是个"文旅门户",但作为毕设项目,它的核心亮点在于形成了一个业务闭环:普通游客在网站上浏览景点、阅读攻略、查看旅游路线、预订酒店、下单购买;管理员在后台维护这些内容、处理订单、管理用户和评论。前台展示给游客,后台管理给运营者,两个前台面共用一个后端。

为什么要这样做?因为毕设评价的是"完整度"。一个只能展示景点列表的网站,撑死算个静态页面;有了用户体系、订单体系、评论体系和后台管理,它才是一个"系统"。这也是为什么绝大多数 Java Web 毕设都采用"门户+管理后台"的设计——业务完整,结构清晰,答辩时也容易讲。

放到这个具体项目里,前台页面通常包括:首页轮播和热门推荐、景点展示与详情、民族文化攻略文章、旅游线路、酒店民宿、个人中心;后台则包括:景点管理、攻略管理、线路管理、酒店管理、评论管理、订单管理、用户管理、数据统计。我判断数据统计页在这个项目里是加分项,因为"七彩云南"这类平台很适合用 ECharts 展示热门景点排行、订单趋势这类图表,视觉效果好,技术上也说得通。

2.2 核心数据表怎么设计:一张表一个职责

表结构是项目里最值得花时间研究的文件,因为它承载了全部业务语义。我基于这类项目的常见设计,把至少会出现的核心表拆给你看。

系统用户表(sys_user):存放游客和管理员。

字段类型说明
idbigint主键,自增
usernamevarchar(50)登录名,唯一
passwordvarchar(100)加密后的密码串
nicknamevarchar(50)昵称
avatarvarchar(255)头像地址
phonevarchar(20)手机号
rolevarchar(20)USER / ADMIN,区分前台用户和管理员
create_timedatetime注册时间

密码不存明文,这是常识。项目里一般用 BCrypt 或 MD5+盐加密。你导入 SQL 后看到一长串看不懂的字符串,那才是正常的。

景点表(scenic):文旅平台的主角。

字段类型说明
idbigint主键
namevarchar(100)景点名称
covervarchar(255)封面图
imagestext详情轮播图,多个URL用逗号分隔
locationvarchar(255)所在地
pricedecimal(10,2)门票价格
gradevarchar(20)评分,前端可做星级展示
open_timevarchar(100)开放时间
descriptiontext详细介绍
click_countint点击量,用于热门排序
statusint上下架状态
create_timedatetime录入时间

这里注意 images 字段:用 text 存多个图片路径是毕设里的常见做法,简单直观。生产系统可能要建关联表,但对这个项目的体量,text 够用,答辩也能解释清楚。

攻略表(strategy):承载文化内容,也就是"七彩云南"里最有文化味道的模块。

字段类型说明
idbigint主键
titlevarchar(200)标题
covervarchar(255)封面
author_idbigint作者,关联用户表
contentlongtext正文
view_countint浏览量
like_countint点赞量
statusint发布状态
create_timedatetime发布时间

旅游路线表(route):除了展示线路信息,还要存行程安排。

字段类型说明
idbigint主键
namevarchar(200)线路名称
daysint天数
pricedecimal(10,2)价格
covervarchar(255)封面图
itinerarytext行程安排,存 JSON 或多行文本
statusint上下架状态
create_timedatetime创建时间

itinerary 用 text 存 JSON 是我比较推荐的做法。比如第1天去哪、第2天去哪,用 JSON 数组存,后端原样读写,前端 JSON.parse 之后就能渲染成时间轴,简单且灵活。

酒店表(hotel):字段和景点类似:房型名称、地址、封面图、价格、星级、简介、状态。

订单表(order):这是业务闭环的核心。我的建议是设计成"通用订单":product_type 区分是线路还是酒店还是门票,product_id 指向具体产品,同时冗余产品名称和价格快照。

字段类型说明
idbigint主键
order_novarchar(50)订单号,一般用时间戳+随机数生成
user_idbigint下单用户
product_typevarchar(20)line / hotel / ticket
product_idbigint产品ID
product_namevarchar(200)产品名称快照
product_pricedecimal(10,2)成交价格快照
total_pricedecimal(10,2)订单总价
statusint0待支付 1已支付 2已完成 3已取消
create_timedatetime下单时间

冗余 name 和 price 是刻意的:用户下单那一刻的行情要固化成快照,哪怕管理员以后改了价格,订单记录依然是历史真实情况。这个设计细节在答辩时提出来,会显得你对业务有思考。

评论表(comment)和收藏表(favorite)我放到一起说。它们不单独为景点、攻略、酒店各建一套,而是用 target_type 和 target_id 区分评论或收藏的对象。这样一套表能复用,代码也统一。评论表比收藏表多几个字段:content 正文、parent_id 支持回复、create_time、回复用户等。

还有个容易被忽略的点:逻辑外键。毕设里我强烈建议不要在数据库层面建太多物理外键。外键虽然能保证数据一致性,但会给删除操作带来一堆麻烦。你的代码里用"逻辑关联"就够了:比如订单表里有 user_id,就知道它关联用户,但不要真的写 FOREIGN KEY。答辩时如果老师问,你可以说"为了保证数据操作灵活性和性能,采用逻辑外键,在业务层保证数据一致性",这个回答是站得住的。

2.3 SQL脚本的正确读法:字符集、初始化数据与常用改动点

打开 SQL 脚本,先不要急着全文执行。重点看三样东西。

第一是建库语句。库的字符集一般要设成 utf8mb4,排序规则用 utf8mb4_general_ci 或 utf8mb4_unicode_ci。utf8mb4 比 utf8 多支持一些特殊字符,当前后端页面要存表情或生僻字时不会乱码。如果脚本里建的库不是 utf8mb4,建议手动改掉。

第二是初始化数据。脚本里通常会插入一个管理员账号和一批演示数据。你要找到管理员账号的用户名和加密后的密码。比如 admin 用户对应的密文,你不需要理解它,但要确保登录时能用。如果密码是 BCrypt 加密,你甚至可以自己生成一串新的密文替换进去,把管理员密码改成你熟悉的,答辩演示时更顺手。

第三是各张表的 CREATE TABLE 语句。重点看 id、create_time、status 这些公共字段是否齐备,以及有没有统一的命名习惯。命名一致说明代码大概率也是规范的,你阅读时会更顺畅。

3. SpringBoot后端的硬骨头:分层、鉴权、缓存与上传

3.1 Controller-Service-Mapper:三层架构怎么理解

很多初学者看后端代码,第一反应是"怎么这么多层,绕来绕去"。我用一个生活化类比来拆:Controller 是前台接待,Service 是业务主管,Mapper 是库房管理员。

前台接待(Controller)只负责收单和回复:客人说"我要查景点列表",接待把需求记下来,转给业务主管;主管把结果拿回来,接待再原样递出去。所以 Controller 里通常只有两件事:把请求参数整理给 Service,把 Service 的返回结果包装成统一结构。

业务主管(Service)负责真正的业务决策:查哪些表、怎么组合数据、要不要缓存、是否要做权限校验。拿"热门景点列表"来说,Service 接收"取前十条、按点击量排序"这个指令,然后决定是直接查数据库还是先看缓存,最终把组装好的景点列表返给 Controller。

库房管理员(Mapper)只跟 SQL 打交道:查景点表、查评论表、插入订单。它不关心业务,只负责"数据的存取"。

三层分开的最大好处是:职责单一、好测试、好替换。你改 SQL 不用动 Controller,你新增一个返回字段不用动 Mapper。理解了这个结构,看 SpringBoot 后端代码就有了解剖图。

代码上还要注意统一返回结构。项目里必然有一个 Result 类,大概长这样:

public class Result<T> { private Integer code; private String message; private T data; public static <T> Result<T> success(T data) { Result<T> r = new Result<>(); r.code = 200; r.message = "操作成功"; r.data = data; return r; } public static <T> Result<T> error(String msg) { Result<T> r = new Result<>(); r.code = 500; r.message = msg; return r; } }

这个类的作用是给所有接口提供统一的"包裹层"。前端 axios 拦截器只需要判断 code 是不是 200,就能决定渲染数据还是弹错误提示。你到项目里搜一下 "class Result" 或 "class R",找到它,整个后端的响应规范就清楚了。

3.2 登录与鉴权:JWT的落地细节

前后端分离项目的登录,基本绕不开 JWT。Session 方案在跨域和扩展性上都不占优,JWT 让后端无状态,前端拿到 token 后每次请求带着它,后端验一下签名就能确认身份。毕设用 JWT 是最稳的选择。

登录的流程是这样:用户提交用户名密码到 /api/login,后端验证通过后,用 JWT 工具类生成一串 token,返回给前端。前端把 token 存到 localStorage,之后每次请求在 Header 里带上 Authorization 字段。

后端要做的校验逻辑,一般写在一个拦截器里:

public class JwtInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token = request.getHeader("Authorization"); if (token == null || !JwtUtil.verify(token)) { response.setStatus(401); response.getWriter().write("{\"code\":401,\"message\":\"未登录或登录已过期\"}"); return false; } Integer userId = JwtUtil.getUserId(token); request.setAttribute("userId", userId); return true; } }

然后通过一个 WebMvcConfigurer 注册拦截器,同时配置白名单。白名单包括登录接口、注册接口、景点列表等不需要登录就能访问的接口;那些需要身份的操作(下单、评论、收藏、后台管理)则统统进拦截器。

这里有个小小的设计判断:拦截器里验完 token 后,把 userId 放进 request 的 attribute 里,后面的 Controller 就能随时取出当前登录人。这个操作看代码时如果看到 request.getAttribute("userId"),你就知道这是从哪来的了。

3.3 Redis缓存热点数据:代码不难,思路要讲清楚

很多毕设项目都喜欢加 Redis,因为它是加分点。但如果你只是引入了依赖,代码里根本没用到,答辩反而会减分。七彩云南文旅平台这种场景,适合缓存的东西其实非常明确:首页轮播图、热门景点 TOP10、攻略置顶列表。

为什么缓存这些?因为它们高频读取、变化不频繁。一个人访问首页,可能要拉取轮播图、热门景点、推荐线路等好几组数据。如果没有缓存,每一次刷新都是一堆 SQL 打到数据库;有了缓存,第一次查询从数据库取,放进 Redis,之后 30 分钟内直接读缓存。

代码模式也非常成熟,业界叫 Cache Aside Pattern。核心逻辑就两步:读的时候先查缓存,缓存没有就查数据库,然后回填缓存;写的时候删掉对应缓存,而不是更新缓存。删除比更新简单,也能避免一堆并发问题。

public List<Scenic> hotList() { String key = "scenic:hot"; Object cacheObj = redisTemplate.opsForValue().get(key); if (cacheObj != null) { return (List<Scenic>) cacheObj; } List<Scenic> list = scenicMapper.selectHotList(); redisTemplate.opsForValue().set(key, list, 30, TimeUnit.MINUTES); return list; }

这段代码就是热门景点列表的典型实现。你不需要背代码,但要把"先查缓存、缓存未命中、查库回填、设置过期时间"这个流程讲出来,这比写出代码更能体现理解深度。

3.4 图片上传:本地存储的完整套路

文旅网站离不开图片:景点封面、攻略插图、酒店房间图。项目里的图片上传接口通常叫 /api/upload,前端用 file 组件选择文件,后端接收 MultipartFile,保存到本地磁盘,返回图片的访问路径。

上传的核心逻辑其实就三件事:重命名、分目录、存相对路径。重命名用 UUID 是标准做法,避免用户上传的文件名乱码或重复;分目录按日期建文件夹,方便管理;数据库里存相对路径,比如 /upload/20250612/xxx.jpg,前端拿它拼接完整访问地址。

我在代码里给一个最简骨架:

@PostMapping("/upload") public Result<String> upload(@RequestParam("file") MultipartFile file) { String originalName = file.getOriginalFilename(); String ext = originalName.substring(originalName.lastIndexOf(".")); String fileName = UUID.randomUUID().toString().replace("-", "") + ext; String dateDir = new SimpleDateFormat("yyyyMMdd").format(new Date()); String relativePath = "/upload/" + dateDir + "/" + fileName; File dir = new File(uploadPath + "/" + dateDir); if (!dir.exists()) { dir.mkdirs(); } file.transferTo(new File(uploadPath, relativePath)); return Result.success(relativePath); }

uploadPath 是配置文件里指定的一个本地磁盘目录。为了让浏览器能直接访问这些图片,项目里会有一个资源配置,把 /upload/** 映射到 uploadPath。这一步如果漏了,后端能存图但前端永远显示不出来——这是本地图片方案里最常见的坑,部署章节我再展开说。

3.5 接口文档的正确打开方式

接口文档是沟通前后端的桥梁。拿到文档后,我不会从头到尾逐行读,而是按业务顺序走一遍:登录接口、注册接口、景点列表、景点详情、发表评论、创建订单、后台新增景点、修改景点。

每看一个接口,就去后端代码里找到对应的 Controller 方法。比如文档写着 "GET /api/scenic/list 分页查询景点列表",你就 Ctrl+Shift+F 搜索 "/scenic/list",定位到方法,看它怎么接收参数、怎么调 Service、返回什么东西。

这个方法非常笨,但极其有效。走完一遍,你对项目的理解会超过很多直接运行的人。更重要的是,答辩时如果老师问"你这个接口返回什么结构",你能脱口而出"统一 Result,data 是列表或分页对象"而不是愣住。

4. Vue前端不只是套页面:接口封装、路由守卫与页面套路

4.1 先判断Vue2还是Vue3:决定你的阅读方式

拿到前端代码,我先看 package.json 里的 vue 版本。这个判断特别重要,因为 Vue2 和 Vue3 的代码风格差别很大:Vue2 用 this.xxx 拿数据、用 methods 定义函数、用 mounted 生命周期;Vue3 用 setup、ref、reactive、onMounted。

看项目时如果发现是 Vue2 + Element UI,你就用"选项式 API"的思路去读;如果是 Vue3 + Element Plus,代码里大概率是"组合式 API"。别把这俩混在一起,否则你会觉得代码神秘得像天书。

这个判断还有一个用处:它决定了你百度报错时搜索方向。Vue2 的 bug 和 Vue3 的 bug 完全不是一回事,很多人卡在编译报错上,就是因为用错了版本的检索词。

4.2 Axios封装:一个文件搞定请求与token

几乎每个成熟的前端项目都会在 utils 目录下放一个 request.js,把 axios 实例和拦截器封装好。这个文件值得花十分钟彻底读懂,因为所有页面调接口都会经过它。

import axios from 'axios' const request = axios.create({ baseURL: '/api', timeout: 10000 }) request.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers['Authorization'] = token } return config }) request.interceptors.response.use( res => { if (res.data.code === 200) { return res.data } return Promise.reject(new Error(res.data.message)) }, err => { return Promise.reject(err) } ) export default request

这个文件的逻辑非常清晰:request 拦截器统一加 token,response 拦截器统一解包。所有请求函数调用它,拿到的是 res.data,也就是后端 Result 的 body,直接用 data 渲染页面,不需要每个页面重复判断 code。

同时你要注意 baseURL 是 '/api'。这个 /api 是前端开发服务器的代理前缀,不是后端真正的端口地址。真正转发到后端靠的是 vue.config.js 里的 devServer.proxy:

module.exports = { devServer: { port: 3000, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } } }

前端跑在 3000 端口,后端跑在 8080 端口。浏览器访问前端时,遇到 /api 开头的请求,开发服务器帮你转给后端 8080,从而绕过跨域限制。这就是前后端分离项目联调的核心原理:devServer 代理解决跨域,无需后端配 CORS。

4.3 路由守卫与后台权限控制

前端路由一般会分成两块:游客访问的门户页面,和管理员进的后台页面。目录结构里通常是一个前台 Layout 组件包着首页、景点列表、攻略列表等页面,另一个后台 Layout 组件包着各类管理页面。

那怎么防止普通游客直接访问 /admin 开头的后台页面?答案就是全局路由守卫。核心代码非常短:

router.beforeEach((to, from, next) => { const token = localStorage.getItem('token') if (to.path.startsWith('/admin') && !token) { next('/login') } else { next() } })

这段逻辑是:没有 token,用游客身份访问后台就直接踢回登录页。至于 token 是管理员还是普通用户的,这里没有细究。毕设里这么做完全可以接受,因为管理员的身份通过 token 里的角色字段来校验,后端接口也会做二次拦截。

我会再往前多想一步:前端做路由守卫只是用户体验层面的保护,真正的安全必须靠后端拦截器。因为前端代码在浏览器里是透明的,任何人都能构造请求。这个"前端限制只是方便、后端校验才是安全"的观点,答辩时能主动说出来,是很加分的。

4.4 首页、列表、详情、后台CRUD的通用套路

前端页面看起来多,拆开看其实就是几种固定套路。

首页套路:mounted 时并行调用三四个接口,把轮播图、热门景点、推荐线路的数据填充到页面上。代码里通常是一堆 this.list = res.data 或类似的赋值。

列表页套路:一个搜索框 + 一个表格或卡片列表 + 一个分页器。查询参数放在 data 里,点击搜索或切换页码时重新调接口。常见接口参数是 pageNum、pageSize、keyword。

详情页套路:根据路由参数 id 调详情接口,下面再调评论列表接口。发表评论就是 POST 表单,收藏就是 POST /favorite,按钮点击时判断是否登录。

后台 CRUD 套路最固定:打开页面拉列表,每一行有编辑和删除按钮,顶部有新增按钮。新增和编辑共用一个弹窗表单,提交时调保存接口;删除要弹确认框,确认后调删除接口。这个套路你在项目里熟悉一遍,以后做任何后台管理系统都通用。

如果想在项目上做出自己的风格,我会优先建议改首页布局或者后台的统计页。首页是门面,视觉改动大;统计页可以用 ECharts 把订单趋势、景点热度排行做成图表,工作量不大,答辩演示时很出彩。

5. 本地部署的六个坑与答辩前的快速熟悉路线

5.1 环境版本怎么定:先看项目再装环境

部署环境的选型原则是:先看项目依赖,再装对应版本,不要盲目装最新版。后端一般需要 JDK 8 或 11、Maven 3.6+、MySQL 5.7 或 8.0、Redis。前端需要 Node.js,版本取决于项目用的是 Vue2 还是 Vue3,以及脚手架工具版本。

我给你一个参考清单,具体还要以项目里的配置为准:

软件参考版本说明
JDK1.8 或 11看 pom.xml 里的 java.version
Maven3.6.3+低于3.6可能拉不到某些依赖
MySQL5.7 或 8.0看驱动版本,8.0 驱动连 5.7 没问题
Redis5.0+后端依赖里有 spring-boot-starter-data-redis 就必须启动
Node.js14 或 16Vue2 项目用高版本 Node 容易出问题
IDEIDEA 2021+需要安装 Lombok 插件

5.2 六个最常见的部署坑及解决方法

这些坑都是我见过的高频问题,你跑任何类似的 SpringBoot+Vue 项目都可能碰上。

坑一:前端依赖安装失败,或者 node-sass 编译报错。这基本是 Node 版本太高导致的。老项目里的 node-sass 在 Node 新版环境下编译不过。解决思路有三个:换降级 Node 版本;或者把 node-sass 换成 sass(如果项目代码写法兼容);或者用镜像源重新 npm install。我个人的做法是先看 package.json 里的依赖版本,再决定用哪个 Node 版本,而不是装最新的。

坑二:后端启动时报数据库连接错误。通常是因为你在 application.yml 里配的密码和本地 MySQL 密码不一致,或者 MySQL 服务没启动。先把 3306 端口通不通、用户名密码对不对这些最基本的问题排查完,再去看更复杂的东西。

坑三:SQL 脚本导入时报编码错误。导入脚本时数据库客户端可能按默认字符集解析,遇到中文就乱码。解决办法是建库时明确指定 utf8mb4,导入前执行 SET NAMES utf8mb4,并且确认 SQL 文件本身是 UTF-8 编码保存的。

坑四:前端能启动,但请求接口报 404 或跨域错误。先看前端请求的 URL 是什么。如果是 /api 开头的路径,看 vue.config.js 里的代理 target 是否指向后端实际端口。最常见的低级错误是后端端口改成了 8081,代理还写着 8080。

坑五:图片上传成功,但浏览器访问不到。这个我上面提过,多半是后端没有做资源映射,也就是把 /upload/** 映射到本地磁盘目录的配置没生效。检查一下 config 包里有没有 WebMvcConfigurer 的实现,以及映射路径是否写对。

坑六:Redis 相关报错导致启动失败。后端代码依赖 Redis 缓存,但本地根本没启动 Redis 服务。先把 Redis-server 跑起来,或者在配置里把缓存相关的自动配置排除(不过这不是正道,我建议直接把服务启动)。

5.3 用接口文档做一次全链路自测

项目跑起来后,别急着点页面。我建议用 Apifox 或 Postman 把接口文档里的接口按顺序调用一遍,就当自己是个前端开发者。

顺序是:先调登录接口,拿到 token;用 token 调景点列表、攻略列表;找一个景点 ID,查详情、加收藏、发表评论;创建订单并推进订单状态;用管理员身份调后台接口,新增一个景点,再打开前台页面,看景点是否正常展示。这个过程能把前后端串起来,也能验证你对接口文档的理解。

如果某个接口调用返回 500,别慌,打开后端控制台看异常堆栈。这个排查过程本身就是锻炼,能帮你发现很多"代码能跑但逻辑不一定对"的问题。

5.4 把源码变成自己的:答辩前四步走

拿到源码只是买了张入场券,答辩前必须完成这四步。

第一步,对着 SQL 脚本,能不看任何提示说出核心表名和几个关键字段。不需要背全表,但至少要知道:用户表、景点表、订单表、评论表各自叫什么,主键是什么,订单怎么关联产品。

第二步,对着接口文档,能把每个核心接口映射到后端代码。打开 IDEA,搜接口路径,指出对应的 Controller 方法。能说出"登录接口在 AuthController 的 login 方法,它调用 UserService 里的 checkLogin,成功生成 JWT,失败抛异常",这就过关了。

第三步,完整操作一遍核心流程:管理员登录后台,新增一个景点,上传图片,然后去前台看到这个景点出现在列表里,点进去能看详情。这个流程能跑通,说明你对项目的理解已经从"代码"到了"功能"。

第四步,准备三个为什么。为什么用 SpringBoot 不用 SSM?答:自动配置、内置容器、生态成熟、开发效率高。为什么用 JWT 不用 Session?答:前后端分离、无状态、适合多端扩展。为什么用 Redis 缓存?答:热点数据读多写少,降低数据库压力,提高响应速度。每个问题准备两句话,再加一个具体例子,基本就能撑起答辩的核心环节。

我在实际帮人梳理项目的过程中,见过太多人把时间花在"复制粘贴跑通"上,结果被老师问一个"你数据库几张表"就卡住。这套快速熟悉路线,按顺序走完,至少能把被动挨打变成主动表达。

最后再分享一点自己的体会。源码跑通只是热身,真正的收获在于你能讲清楚一条数据从"管理员在后台新增一个景点"到"游客在首页看到它并下单"走过的每一步。如果你能做到这一点,不管项目原来是谁写的,它都已经是你脑子里的东西了。这比多抄几个项目,值钱得多。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进