前后端分离做了三五年,接手的项目没有一百也有八十,但每次遇到“SpringBoot + Vue + MyBatis + MySQL”这套组合还是会认真看两眼。原因很简单,这套技术栈太典型了——既不像纯JSP老项目那样已经是上个时代的东西,又不像微服务那套动不动就走注册中心、配置中心,它恰恰是当前中小型管理系统最主流的形态。今天拆的这个综合小区管理系统,就是一套非常标准的全套代码:后端SpringBoot提供接口,前端Vue做单页应用,MyBatis负责数据库交互,MySQL存数据,源码完整、部署链路清晰,很适合拿来当实战项目练手,或者作为毕业设计、简历项目的底子去改。这篇文章我会把项目架构、核心代码逻辑、数据库设计、部署步骤和排坑经验全部过一遍,保证你看完自己就能把这个项目跑起来,甚至改造成自己的东西。
1. 整体设计与技术选型思路
1.1 小区管理系统到底管什么
名字叫“综合小区管理系统”,本质上就是一个多模块的管理后台,核心对象是小区里的“人、房、钱、事”四样东西。
先看人:业主档案,包括业主姓名、手机号、身份证、家庭成员、车辆信息、租客信息等。再看房:楼栋、单元、楼层、房号、建筑面积、户型、朝向,房子和业主之间是一对多的关系——一个业主名下可以挂好几套房,一套房也可能住着业主和租客两个人。然后是钱:物业费按面积按月计算,水费电费燃气费代收,停车费按月或按次收取,每一笔钱都要有台账,还得支持用Excel导出对账。最后是事:报修工单、投诉建议、访客登记、公告通知,这类流程性质的数据最考验CRUD功底的细节,比如状态流转、处理人分配、时间节点记录。
把功能模块列出来基本就是:
- 系统管理:用户、角色、权限、菜单管理
- 业主管理:业主信息维护、房产关联、家庭成员
- 房屋管理:楼栋房屋信息、售租状态维护
- 车位管理:固定车位、临停车位、车辆进出记录
- 收费管理:物业费、水电费、停车费账单生成与收缴
- 报修管理:业主报修、物业派单、维修完成确认
- 公告管理:小区公告、通知公告发布与展示
- 数据统计:缴费率、报修量、收支汇总等基础统计图
我之所以先列功能清单,是因为后面所有技术选型、数据库设计、接口划分都是围着这些功能转的。你看它和常见的电商后台、OA系统高度相似,这就是为什么这种项目适合当练习样本——把一套吃透了,其他系统都是换皮。
1.2 为什么是SpringBoot + Vue这套组合
说实话这套组合不是最“炫”的——没有微服务、没有高并发中间件,但它非常务实。SpringBoot最大的价值不是技术多牛,而是把Spring那套繁琐的配置几乎全部替你干完了。你不需要再写一堆XML配置文件,也不需要纠结Tomcat怎么集成,打包直接就是一个可运行的Jar包,这对部署来说是大解放。
Vue目前仍然是国内前端市场占有率最高的框架之一,而且它对后端出身的人特别友好:单文件组件(SFC)把HTML、JS、CSS写在一个.vue文件里,模板语法又非常接近HTML语义,学习曲线不算陡。更关键的是Vue的生态足够成熟——Element UI一个组件库把后台管理系统需要的表格、表单、弹窗、树形控件全包了,开发效率非常客观。
MyBatis的选择也很有意思。搞Java的都知道,JPA也好、MyBatis-Plus也好,在SQL可控性上都不如原生MyBatis来得直接。小区管理系统里有大量报表类查询,比如按月统计各楼栋的物业费收缴情况、统计报修单的处理时长,这种SQL用MyBatis写动态SQL非常舒服,每个条件可以精确控制,出问题也好排查。
MySQL这家RDBMS就不用多说了,开源、稳定、学习资料多、部署成本低。这套项目里用到MySQL 5.7或者8.0都没问题,只要能跑通一组标准的建库建表脚本就行。
1.3 前后端分离架构解决什么问题
我说一个最直观的感受:没有前后端分离的年代,每个页面都几乎是一个“孤岛”——后端把数据塞进ModelAndView,前端拿到的是渲染好的HTML,一旦页面交互复杂起来,前端脚本会非常臃肿,后端模板也会越来越难维护。前后端分离之后,后端关心的是“提供什么数据”,前端关心的是“如何展示和交互”,两者的边界非常清晰。
对开发节奏的提升也很明显。前端改页面再也不用等后端重启,后端加接口也不影响前端开发。两边只需要约定好接口的URL、入参和返回值,就可以并行推进。实际在这个项目里,联调阶段只需要对一下接口文档,双方各自修改Bug就行,效率比老式的JSP试错方式高出一个量级。
代价也不是没有。最大的坑就是跨域问题:前端页面跑在Nginx上,接口跑在SpringBoot上,浏览器安全策略会拦截跨域请求。解决方案后面会细说,这里先记住一个核心原则——接口这一侧统一配置CORS,或者用Nginx反向代理把/api开头的请求转发到后端服务,这两个都是成熟做法。
2. 数据库设计与后端核心实现
2.1 数据表结构与关系梳理
这个项目的数据库设计并不复杂,但你一定要把表之间的关系理清楚,不然写到联表查询时会非常痛苦。我在纸上先把核心表列一下,大概就几张:
sys_user:系统登录用户,字段有user_id, username, password, real_name, role_id, statussys_role:角色表,一个用户对应一个角色,简化版本不需要做多对多owner_info:业主信息表,owner_id, owner_name, phone, id_card, house_id, member_counthouse_info:房屋表,house_id, building_no, unit_no, floor_no, room_no, area, owner_idhouse_owner_rel:很推荐的一张关联表,因为一套房子可能被买卖、出租,业主和房屋的关系其实是多对多的,单独建关联表后期会很省事property_fee:物业费账单,fee_id, house_id, period(账期),amount, status(未缴/已缴),pay_timerepair_order:报修单,order_id, house_id, reporter_id, content, status, assignee, create_time, finish_timeparking_space:车位表,space_id, position_no, type(固定/临时), owner_id, statusnotice_info:公告,notice_id, title, content, publish_time, publisher
设计取舍上有一个点很值得说。权限这块如果只是做毕业设计或者个人项目,用“用户表里存一个角色ID”就够了,不要一开始就上RBAC五张表。等你要往简历项目上写、想面试时强调“权限设计”的时候,再扩展成用户-角色-菜单三张表加两张关联表,那才是个加分点。这个项目的完整版里做了用户、角色、菜单三张表的权限模型,菜单用父子层级表示,前端根据路由表动态生成菜单,算是够用的方案。
字段类型上注意几点:金额字段不要用float/double,会有精度问题,要用DECIMAL(10,2);联系电话这类用VARCHAR(20),身份证号用VARCHAR(18),这些看着是细节,实际写代码时经常在这里翻车。
2.2 SpringBoot后端工程结构
拿到源码后第一件事是先看包结构,这个项目的包结构完全按照三层架构来分的:
com.example.community ├── controller # 接口层,接收HTTP请求,返回JSON ├── service # 业务层,写业务逻辑 │ └── impl ├── mapper # 数据访问层接口,对应MyBatis Mapper ├── entity # 数据库实体类,对应每张表 ├── dto # 请求/响应数据传输对象 ├── config # 配置类,比如跨域配置、拦截器配置 ├── utils # 通用工具类(JWT工具、日期工具等) └── common # 统一返回结果封装,全局异常处理这套分层虽然老生常谈,但它是经过大量项目验证的。Controller层一定不能写业务代码,只负责接收参数、调用Service、包装返回;Service层写事务逻辑,比如生成物业费账单时要先检查房屋状态再插入账单记录,这些都要用@Transactional包起来;Mapper层只做数据库交互,一个方法对应一个SQL操作。
项目里统一返回结果类我建议一定保留,不要图省事每个接口都直接返回Map。正常项目里会定义一个Result类,里面包含code、msg、data三个字段,比如Result.success(data)、Result.error("参数错误")。这样做的好处是前端axios拦截器可以统一判断code,不需要在每个页面里处理错误分支。
2.3 MyBatis关键配置与动态SQL实战
MyBatis在这套项目里的核心配置有几个地方容易踩坑。第一个是application.yml里数据库连接和驼峰映射的配置:
spring: datasource: url: jdbc:mysql://127.0.0.1:3306/community_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.community.entity configuration: map-underscore-to-camel-case: truemap-underscore-to-camel-case: true这一行特别关键。数据库字段是house_id,Java实体属性是houseId,不开启驼峰映射的话查询结果就是null,而且这种坑很隐蔽——启动不报错、查询不报错,就是字段值全是空的。我见过太多新手在这个问题上卡一两天。
第二个重点是MyBatis的动态SQL。比如报修订单的列表查询,需要支持按状态筛选、按关键字搜索、按时间范围搜索,条件组合不确定,就必须用动态SQL:
<select id="selectRepairList" resultType="com.example.community.dto.RepairDTO"> select * from repair_order <where> <if test="status != null and status != ''"> and status = #{status} </if> <if test="keyword != null and keyword != ''"> and (house_no like concat('%', #{keyword}, '%') or reporter_name like concat('%', #{keyword}, '%')) </if> <if test="startTime != null"> and create_time >= #{startTime} </if> <if test="endTime != null"> and create_time <= #{endTime} </if> </where> order by create_time desc </select><where>标签会自动处理第一个条件前面的and,这个特性在模糊搜索组合场景下是救命的。另外注意concat('%', #{keyword}, '%')这种写法是为了防止SQL注入,永远不要用字符串拼接的方式组装SQL条件。
2.4 登录认证与权限拦截的实现思路
登录这块完整版做的是JWT方案,流程也不复杂:用户提交用户名密码,后端校验通过后用用户ID和角色信息生成一个token返回给前端;前端把token存在localStorage里,每次请求时放在HTTP请求头的Authorization字段里;后端写一个拦截器,在请求进入Controller之前先校验token,校验失败直接返回401。
这个拦截器是核心,用HandlerInterceptor实现的最简单的版本:
public class JwtInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 放行登录接口 if (request.getRequestURI().contains("/api/login")) { return true; } String token = request.getHeader("Authorization"); // 校验token,不通过则返回401 return JwtUtil.verifyToken(token, response); } }权限控制则叠一层角色判断:比如收费管理的接口只允许ADMIN角色访问,业主端接口放开给普通业主用户。在拦截器里拿到token解析出的角色,对比当前接口要求的权限码即可。这种设计已经能覆盖多数管理系统的权限需求,开发成本低、面试也能讲得清楚。
3. 前端Vue实现与联调细节
3.1 Vue工程搭建与目录规划
前端用的是Vue 2 + Element UI的组合,虽然Vue 3已经出了很多年,但Vue 2的存量项目和资料量依然巨大,选择它并不落伍,遇到问题也更容易搜到解决方案。工程目录是标准的vue-cli脚手架:
src ├── api # 所有接口请求封装,按模块拆文件 ├── assets # 静态资源 ├── components # 公共组件 ├── router # 路由配置 ├── store # Vuex状态管理 ├── views # 页面视图组件 └── App.vue我特别想强调api目录的重要性。很多新手习惯在页面里直接写axios.get('http://localhost:8080/api/xxx'),这是坏习惯。正确做法是把每个接口都封装成函数,统一放在api文件夹里,比如api/fee.js里写:
import request from '@/utils/request' export function getFeeList(params) { return request({ url: '/fee/list', method: 'get', params }) }这样做好处非常多:接口路径集中管理、每个模块的接口一目了然、以后后端接口改动时只需改一个文件,不用满项目翻字符串。这个习惯对我的提升非常明显,早期项目里接口散落各处,每次联调改路径都是噩梦。
3.2 路由、登录态与Vuex状态管理
路由配置分两块:静态路由和动态路由。静态路由只有登录页和404页,其他页面都由动态路由生成。登录成功拿到当前用户的角色和权限码,前端根据权限码动态拼接出用户可以访问的路由表,再用router.addRoutes()注册路由。这也是动态菜单的实现原理——管理端看到“收费管理”,业主端看不到,因为路由表本来就不一样。
Vuex在这里存三个关键东西:token、用户信息、动态生成的路由表。刷新页面时Vuex数据会丢失,所以需要从localStorage里再读一次token,并重新拉取用户信息、重新生成路由。这个处理不当会导致一个经典问题:用户刷新页面后白屏,或者跳回登录页。解决思路是自己写一个init方法放在路由守卫里,每次导航前检查store里有没有用户信息,没有就从localStorage恢复。
导航守卫的作用很直接,在router.beforeEach里做登录拦截:
router.beforeEach((to, from, next) => { const token = localStorage.getItem('token') if (!token && to.path !== '/login') { next('/login') } else if (token && to.path === '/login') { next('/') } else { next() } })这个版本的守卫逻辑简单清晰。需要细化的地方是要同时校验“token是否存在”和“用户信息是否已加载”,如果token在但用户信息没了,要去拉用户信息才能继续跳转,否则动态路由还没生成,直接跳转会白屏。
3.3 axios封装与跨域联调
axios封装是每次前后端联调的基石。项目中utils/request.js这个文件的职责很明确:创建axios实例、配置baseURL、设置请求拦截器(自动带上token)、响应拦截器(统一处理业务错误码和HTTP错误码)。响应拦截器里最常用的逻辑是:如果code为401,说明登录过期,清掉本地登录信息并跳转到登录页。
关于跨域,开发阶段和后端联调时可以直接使用Vue CLI自带的代理功能,在vue.config.js里配置:
module.exports = { devServer: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } } }这样前端请求/api/login就等价于请求http://localhost:8080/api/login,浏览器看起来是同一个源,不存在跨域。后端层面的跨域配置也要同步加上,两套方案一起做,部署到生产环境时由Nginx反向代理接管,就不会有跨域问题。
3.4 典型列表页面的实现逻辑
拿业主列表页来说,这个页面的实现模式几乎可以复制到项目里的所有模块。页面上有三样东西:搜索条件栏、表格、分页器。搜索栏的表单数据就是查询参数,点击“查询”按钮时重新调用列表接口;表格部分直接用el-table绑定从接口返回的数组;分页器的当前页和每页条数都作为查询参数传给后端。
前端需要和后端协商统一的返回结构。这套项目的分页返回格式是:{ code: 200, data: { list: [], total: 100 } }。前端axios响应拦截器解包后,页面拿到的就是data.list和data.total。这类规范只要在项目开始时定下来,后面每个页面写起来都很快。
表单弹窗是另一个核心模式:新增和编辑共用一个el-dialog,进入编辑时把当前行的数据深拷贝到表单对象里,打开新增时清空表单。提交时根据form.id是否存在来判断是新增还是更新,对应调用不同的接口。这个套路在管理系统里出现频率极高,熟练了之后做功能开发速度会很快。
4. 从源码到上线:完整部署教程
4.1 部署环境准备清单
工程要在本地跑起来,需要先装好几样基础环境,我把版本和用途列在下面:
| 软件 | 推荐版本 | 用途 |
|---|---|---|
| JDK | 1.8 及以上(推荐1.8) | 编译运行SpringBoot项目 |
| Maven | 3.6+ | 后端依赖管理与打包 |
| Node.js | 14+ | 前端依赖安装与构建 |
| MySQL | 5.7 / 8.0 | 数据库存储 |
| Nginx | 1.18+ | 托管前端静态文件、反向代理接口 |
| Navicat或MySQL命令行 | 任意 | 执行SQL脚本、检查数据 |
版本匹配上有几个容易踩的坑。JDK不要装太新的版本,如果用的SpringBoot 2.x,JDK 11或者8都合适;JDK 17以上有些老版本的Spring依赖会出兼容问题。MySQL 8.0的驱动是com.mysql.cj.jdbc.Driver,MySQL 5.7用com.mysql.jdbc.Driver或者直接都用cj驱动,但注意8.0的url里必须要带serverTimezone参数,否则会报时区错误。
4.2 数据库初始化与导入
数据库这步最简单,也最不能出岔子。打开Navicat新建数据库,命名为community_db,字符集选择utf8mb4,排序规则选utf8mb4_general_ci,然后把项目里的sql/community_db.sql文件拖进去执行。这里我提醒一句:一定要看清楚SQL脚本最后有没有默认数据,尤其是管理员账号。如果脚本里没有,你自己要手动插入一条用户,不然登录永远进不去。
执行成功之后建议先随便查两张表确认有数据,比如:
SELECT * FROM sys_user; SELECT * FROM house_info LIMIT 5;这能快速验证表结构是否完整、默认数据是否导进去了。
4.3 后端打包与启动
后端是标准的Maven工程,打包命令极其简单。在项目根目录(有pom.xml的那个目录)执行:
mvn clean package -DskipTests这个命令会跳过单元测试直接打包,如果测试代码写得有问题会卡住打包流程。打包完成后在target目录下会生成一个community-server.jar。启动命令:
java -jar target/community-server.jar或者放后台运行(Linux环境下):
nohup java -jar community-server.jar > server.log 2>&1 &启动成功会看到SpringBoot的Logo和控制台日志。验证接口是否可以访问,随便试一个:
curl http://localhost:8080/api/login返回JSON错误信息就说明服务已经起来了。需要注意端口是否被占用,默认是8080,如果被占了去application.yml里改server.port。
4.4 前端构建与Nginx托管
前端构建也非常简单,在项目根目录(有package.json的那个目录)执行:
npm install npm run buildnpm install首次执行会慢一些,网络差的话可以用淘宝镜像npm config set registry https://registry.npmmirror.com加速。构建完成后生成dist目录,里面是纯静态文件——index.html、css文件、js文件。这一步的核心思路就是:把那堆静态文件交给Nginx托管,Nginx同时负责把/api的请求代理给后端服务。
我在nginx/conf/nginx.conf里的配置通常是这样的:
server { listen 80; server_name localhost; # 前端静态文件 location / { root /usr/local/nginx/html/community; index index.html; try_files $uri $uri/ /index.html; # 解决history路由404 } # 后端接口反向代理 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; } }配置里最关键的就是try_files $uri $uri/ /index.html;这一行。如果项目采用的是history模式路由,刷新一个子页面(比如/fee/list)时Nginx会在静态目录里找/fee/list这个文件,找不到就404了。加了这个配置之后,找不到文件时Nginx统一返回index.html,Vue接管路由自己匹配页面,404问题彻底解决。
配置好之后重新加载Nginx:
nginx -t nginx -s reload浏览器访问http://localhost,输管理员账号密码,能进系统就算部署成功。整套流程走下来,前后端分离项目从源码到线上运行大概半小时就能搞定。
4.5 部署方案对比:单机还是容器化
如果只是本地部署或者放一台云服务器,上面这种Nginx + Jar包的方案是最省事的。维护成本低,排查问题直接看日志。但如果你想把项目做得更规范一点,可以用Docker来跑MySQL、SpringBoot、Nginx三件套。我提供一个思路:每个服务一个Docker容器,用docker-compose.yml在一台机器上编排,好处是环境隔离,数据库配置、Java运行环境都不需要再手动安装。
不过对于大多数场景,我建议先按非容器化的方式跑通一遍。因为容器化虽然漂亮,但排错门槛高一些。比如容器里的时区问题、MySQL容器数据持久化问题、网络模型问题,没经验的话坑也不少。先把裸部署流程吃透,再上容器,这样遇到问题至少知道是哪一层出的问题。
5. 高频踩坑与排查经验实录
5.1 跨域请求失败的排查思路
现象:前端打开页面后,所有请求都报blocked by CORS policy,或者浏览器控制台出现Response to preflight request doesn't pass access control check。
这是前后端分离项目里出场率最高的Bug,没有之一。排查顺序我建议先看后端:是不是Controller或全局配置里打了@CrossOrigin注解,或者专门配置了CorsFilter。如果没有,最快的方式是加一个全局CORS配置类:
@Configuration public class CorsConfig { @Bean public CorsFilter corsFilter() { CorsConfiguration config = new CorsConfiguration(); config.addAllowedOriginPattern("*"); config.addAllowedMethod("*"); config.addAllowedHeader("*"); config.setAllowCredentials(true); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", config); return new CorsFilter(source); } }注意addAllowedOriginPattern("*")和setAllowCredentials(true)一起用的时候,不要用addAllowedOrigin("*"),有些Spring版本会直接拦截这个组合。如果后端配置没问题,那就看Nginx的反向代理是否生效,确认请求是不是真正打到了后端端口上。
5.2 刷新页面后404
现象:登录页能进,主页面能进,但一旦刷新/house/list之类的子路由,页面一片空白或者404。
原因基本就是history模式与Nginx静态服务不兼容。检查nginx.conf里有没有try_files那行。如果项目里使用的是hash模式路由,则没有这个问题,但URL会带/#/。生产环境我更推荐history模式,配合try_files解决刷新问题,URL美观,也贴近真实项目习惯。
还有一个小坑:try_files $uri $uri/ /index.html;这行配置里的路径,如果前端静态文件不是放在Nginx的html根目录下,root路径要和实际路径完全一致,否则首页都打不开。
5.3 数据库连接报错一堆
现象:后端启动时报UnknownHostException、The server time zone value 'XXX' is unrecognized、或者Access denied for user。
这类报错90%是application.yml配置问题。我遇到过的情况有这几类:
- 密码包含特殊字符比如
@、$,在yaml里没有加引号,导致解析出错 - MySQL 8.0没加
serverTimezone=Asia/Shanghai参数,报时区错误 - 用MySQL 5.7却加了
cj驱动,或者反过来驱动版本不匹配 - 数据库用户名没有远程访问权限,只允许localhost连接
最直接的排查方式是把application.yml里的配置挨个核对一遍,然后用Navicat手动连接一次,能连上再跑后端。能连上说明配置和数据库都没问题,连不上就照着提示一项项改。
5.4 MyBatis查询结果字段全是null
这个坑我在2.3里已经提前说了一遍,但排查时太容易忽略,值得单拎出来强调。SQL执行没报错,查询结果有记录,但Java对象里的字段就是null。第一反应看实体类字段名和数据库字段名是否一一对应,再看application.yml里有没有开启map-underscore-to-camel-case: true。如果不开启,就要在XML里每个字段手动写别名,或者用<resultMap>来映射,那工作量直接翻倍。建议从一开始就把这个配置加上。
5.5 SpringBoot端口被占用
现象:后端启动提示Port 8080 was already in use。
排查方式:
# 查找占用8080的进程 netstat -ano | grep 8080拿到进程ID后杀掉即可。也可以直接换端口,改server.port。如果项目要部署到环境上,建议用端口大于10000的,比如18080,避开一些常见软件占用的端口。
5.6 前端构建报错
npm install阶段最常遇到两类问题。一类是依赖包下载超时,解决办法是换淘宝镜像;另一类是版本冲突,比如Node版本过高导致node-sass安装失败,解决办法是把Node降到项目要求的版本(很多老Vue项目要求Node 14或16),或者改用vue-cli-service对应的官方依赖版本。还有一类是npm run build时内存溢出,会看到JavaScript heap out of memory的报错,这时给Node加大内存:
NODE_OPTIONS=--max-old-space-size=4096 npm run build这个命令在Linux和Windows的cmd里写法略有区别,Windows上可以先设置环境变量再执行构建。
5.7 部署上线后的性能体感优化
项目跑通之后,如果感觉页面加载偏慢,我建议做三件性价比很高的事。第一,前端路由懒加载——每个页面组件单独打包成JS文件,而不是全部塞进一个app.js里,首屏体积能小一半还多。第二,后端给查询频率高的接口加缓存,比如公告列表、房屋下拉列表这种,用简单的Caffeine本地缓存就够。第三,MySQL里给常用的where条件的字段建索引,比如house_info.owner_id、property_fee.house_id、repair_order.status,这几个索引字段能明显加快查询速度。
这三件事做完,即使是几百户的小区数据量,体验也会非常流畅。而且这些优化点都能写进简历项目描述里,面试时能讲出“为什么要做”和“怎么做”的细节,比只说“参与了开发”要有说服力得多。
最后分享一点个人经验
说句实在话,这类管理系统项目在技术难度上不算高,它考验的更多是工程规范性和细节处理能力。我见过太多人拿到源码只会闷头运行,跑通就算完事。我更建议你按这个顺序去消化项目:第一遍把项目跑起来,熟悉整个链路如何串联;第二遍断点调试核心流程,比如登录后JWT如何生成和校验、缴费单生成的事务逻辑;第三遍尝试自己加一个模块,比如“访客管理”,从建表、写接口、写页面全流程过一遍。三轮下来,你对SpringBoot + Vue这套体系的掌握程度会远超只会背面试题的人。等到你面试,别人问“项目里怎么解决跨域”“动态路由怎么做的”“为什么MyBatis慢查询查不出数据”,你都能从实际经历里举例子回答,这才是这套源码真正值钱的地方。