
如果你正在找一套能直接拿来改、能跑通、能写进简历或毕业设计的全栈管理系统源码SpringBoot2 Vue3 MyBatis-Plus MySQL8.0 这套养老院管理系统恰好就是典型的“前后端分离 权限管理 CRUD 业务闭环”的项目形态。这套组合这两年几乎是 Java Web 领域出镜率最高的搭配从毕设选题到企业内部实训项目都在用它。我的看法是与其东拼西凑找各种零散的教程不如直接拿到一套结构完整的源码再读懂它的设计逻辑在此基础上做二次开发和功能扩展反而是最快的学习路径。这篇文章我会围绕这套养老院管理系统从整体模块拆分、技术选型逻辑、核心功能实现、常见问题排查这几个角度展开把我自己动手拆解和重构这类项目时的经验一并写出来。不管你是刚学完 SSM 想进阶前后端分离还是准备交毕业设计或者工作中需要快速搭一套后台管理框架这篇文章的内容应该都能帮你省下不少时间。1. 项目全貌与设计思路1.1 养老院管理系统到底要管什么养老院管理系统这类业务系统表面上看起来就是一堆增删改查但真正拆开需求之后你会发现它的业务实体之间关联性很强恰好适合拿来练手完整的前后端交互逻辑。一个典型的养老院管理系统核心要管的业务实体大概有这几类老人信息管理姓名、性别、年龄、身份证号、入住时间、房间号、床位号、家属联系方式、健康状态、护理等级。护工管理护工编号、姓名、手机号、负责区域、值班安排、工作记录。床位与房间管理房间号、床位号、床位状态空置/已入住、房间类型单人间/双人间/多人间、楼层。费用管理床位费、护理费、餐饮费、医疗费按月生成账单记录缴费状态。健康档案体检记录、用药记录、血压血糖数据、过敏史。访客登记来访人信息、与被访老人关系、来访时间、离开时间。系统管理用户账号、角色权限、菜单管理、操作日志。这些模块之间有着清晰的从属关系比如老人必须关联床位而床位必须属于某个房间费用账单必须关联到某个老人。这种关联关系在数据库表设计阶段就要梳理清楚不然写代码的时候会频繁遇到联表查询或数据不一致的问题。从角色划分的角度来看系统至少需要三种角色系统管理员负责全局配置和账号管理护工人员负责日常护理记录的录入和维护老人家属可能需要一个只读的登录入口去查看老人的健康档案和费用情况。当然如果你只是做毕设也可以把家属端省掉只保留管理员和护工两个角色但我的建议是做一个轻量的家属端登录哪怕只是查看信息也能在答辩或者简历上多点亮点。1.2 技术选型背后的逻辑这套系统的技术栈是 SpringBoot2 Vue3 MyBatis-Plus MySQL8.0这个组合不是随便拼起来的每一层都有很现实的选择理由。先看后端。SpringBoot2 是目前 Java Web 领域最稳妥的版本社区生态极其成熟各种坑几乎都被踩平了你搜一个问题基本都能找到答案。SpringBoot3 虽然已经出来一段时间了但它基于 JDK17 和 Jakarta EE很多老项目依赖和教程都还没完全迁移过去对于学习项目和老版本中间件兼容来说SpringBoot2 依然是更稳的选择。如果你以后打算进企业做维护SpringBoot2 的存量项目也远多于 SpringBoot3。和 SpringBoot2 搭配的持久层框架MyBatis-Plus 是当前效率最高、代码量最少的选择。传统 MyBatis 需要手写大量 XML 映射文件而 MyBatis-Plus 在继承 BaseMapper 之后单表的 CRUD 几乎不用写 SQL条件构造器 QueryWrapper 也能覆盖绝大多数查询场景。它提供的分页插件、逻辑删除、字段自动填充、乐观锁插件正好都是管理系统里高频用到的能力。对比 JPA 来说MyBatis-Plus 保留了 SQL 的可控性复杂的多表查询你可以自己写 SQL不像 JPA 那样遇到复杂查询就头大。前端选 Vue3 而不是 Vue2核心原因是 Composition API 和 setup 语法糖带来的代码组织能力提升。管理系统通常包含大量列表页、表单页、弹窗交互如果用 Vue2 的 Options API一个稍微复杂的页面动辄几百行代码逻辑散落在 data、methods、watch 里维护起来相当吃力。Vue3 的 Composition API 允许你按功能模块组织代码比如用户列表页可以把请求数据的逻辑、搜索逻辑、分页逻辑分别封装可读性和复用性都上了一个台阶。同时 Vite 带来的开发体验提升也很明显冷启动基本是秒开。MySQL8.0 相比 5.7带来了窗口函数、公用表表达式CTE、更好的 JSON 支持、utf8mb4 默认字符集、以及更完善的索引优化器。对这套养老院系统来说MySQL8.0 的窗口函数可以很优雅地解决一些排名、环比计算的问题CTE 也能让复杂的多表统计查询变得更加清晰。而且现在新安装的 MySQL 基本都是 8.0教程和运维资料也都在往新版迁移没有必要再用 5.7 去折腾兼容性。建议如果你需要把 MySQL8.0 部署在本地开发环境直接用 Docker 是最高效的方式一条命令就能跑起来不用处理 Windows 和 Linux 上的安装差异。后面我会具体说一条实测可用的命令。2. 核心技术点拆解与实操要点2.1 SpringBoot2 后端骨架搭建要点拿到一套源码之后别急着运行先看它的包结构。一个合理的 SpringBoot2 后端工程通常会按职责分层而不是按功能模块堆文件。以这套养老院系统为例推荐的包结构是这样的com.example.eldercare ├── controller // 接口层接收请求、返回结果 ├── service // 业务层处理核心逻辑 │ └── impl // 业务实现类 ├── mapper // 数据访问层MyBatis-Plus 的 Mapper 接口 ├── entity // 数据库实体对象 ├── dto // 请求参数对象 ├── vo // 返回视图对象 ├── config // 配置类如 MyBatis-Plus 分页插件、跨域配置 ├── common // 通用类统一返回结果、全局异常处理、常量 ├── utils // 工具类JWT工具、日期工具等 └── interceptor // 拦截器登录认证、日志记录这套结构的核心思想是单向依赖Controller 只调 ServiceService 只调 Mapper实体类不依赖任何层。如果你发现某个 Controller 里直接注入了一个 Mapper那这段代码就是不合格的因为业务逻辑泄漏到了接口层后续要加缓存或者改逻辑的时候会非常痛苦。统一返回结果封装是后端工程必做的一件事。我见过太多接口返回格式不一致的问题有的成功返回 data 字段有的返回 rows 字段前端对接的人得为每个接口单独写适配逻辑。正确做法是定义一个 Result 类包含 code、message、data 三个字段所有接口无论成功失败都返回这个结构。比如Data public class ResultT { private Integer code; private String message; private T data; public static T ResultT success(T data) { ResultT result new Result(); result.setCode(200); result.setMessage(success); result.setData(data); return result; } public static T ResultT error(String message) { ResultT result new Result(); result.setCode(500); result.setMessage(message); return result; } }与之配套的是一个全局异常处理器用 RestControllerAdvice 注解拦截所有 Controller 层抛出的异常。业务方法里遇到参数不合法、数据不存在之类的情况直接抛自定义的 BusinessException由全局处理器统一捕获并转成 Result 返回。这样 Controller 里的代码可以写得很干净不用到处 try-catch。后端的登录认证是管理系统里躲不开的环节这套项目在实现时选的是 JWT 方案。流程是用户提交用户名密码后端校验通过后签发一个带过期时间的 token前端拿到 token 存在本地之后每次请求带上 Authorization 请求头后端通过拦截器解析 token 判断用户身份。JWT 方案相比传统的 Session 方案最大的好处是后端无状态不依赖内存存储水平扩展时不需要考虑 Session 同步问题。当然JWT 也有一个要注意的点就是 token 一旦签发在过期之前是无法主动废除的如果用户改了密码或被管理员禁用账号旧 token 在过期前依然有效。如果项目对安全性要求高可以考虑引入 Redis 做 token 黑名单但学习项目里做到 JWT 解析鉴权这一步已经够用。2.2 MyBatis-Plus 的高效玩法MyBatis-Plus 在日常 CRUD 开发里能帮你省掉至少一半的代码量但这些便利背后有几个关键点必须吃透否则会踩到效率反而不如手写 SQL 的坑。最核心的用法是继承 BaseMapper。你的 Mapper 接口只要写成这样public interface ElderMapper extends BaseMapperElder { // 单表 CRUD 不需要写任何方法 }就自动拥有selectById、selectList、insert、updateById、deleteById这些方法。配合 ServiceImplService 层的实现也变得非常薄大部分情况下只需要调用几下父类方法即可完成业务。比如老人入驻时要做几件事更新床位状态、生成一条入住记录、计算当月费用。这个逻辑写在 Service 层使用 Transactional 事务注解包起来任何一步失败都会整体回滚保证数据一致性。条件构造器是 MyBatis-Plus 最值得花时间掌握的部分。日常的按条件查询比如“查所有 80 岁以上的男性老人按入住时间倒序”用 LambdaQueryWrapper 一行就能表达LambdaQueryWrapperElder wrapper new LambdaQueryWrapper(); wrapper.ge(Elder::getAge, 80) .eq(Elder::getGender, 男) .orderByDesc(Elder::getCheckInTime); ListElder elders elderMapper.selectList(wrapper);用 Lambda 表达式引用实体字段好处是编译期就能检查字段名是否正确不会出现因为字符串写错导致运行期才报 SQL 错误的情况。这个特性在重构时尤其有用你改了实体字段名如果用的是 LambdaQueryWrapperIDE 会帮你找出所有引用点而用字符串写法的 QueryWrapper 只能靠测试去发现。分页功能是管理系统列表页的刚需。MyBatis-Plus 分页需要两步先注册分页插件再调用分页方法。注册插件的方式是在配置类里加上Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; }分页插件的作用是拦截你执行的 SQL自动拼接 LIMIT 语句并执行一次 COUNT 查询。如果你忘了注册这个插件selectPage方法返回的数据里 total 字段会一直是 0而且查出来的其实是全量数据页面上的分页功能自然就失效了。这是新手最容易踩的坑后面我会专门说到。逻辑删除功能在管理系统里也很实用。比如删除一条老人入住记录如果物理删掉后续审计就查不到历史了。MyBatis-Plus 的 TableLogic 注解可以让 delete 操作变成 update 操作把一个自定义的 deleted 字段置为 1查询时自动过滤已删除的数据。这个机制的核心价值是让你既能做“软删除”又不用在每个查询 SQL 里手写WHERE deleted 0。但要注意逻辑删除会让一些依赖物理行数统计的场景变得复杂比如统计数据总数时如果没过滤 deleted 字段结果就会偏大。字段自动填充也是我强烈推荐使用的一个特性。数据库表里一般都会有create_time和update_time字段以前每次插入或更新都要手动 set写多了就烦。通过 TableField(fill FieldFill.INSERT) 注解配合自定义的 MetaObjectHandler 处理器MyBatis-Plus 会在执行 insert 和 update 时自动填入当前时间你的业务代码里完全不用再处理这两个字段。2.3 Vue3 前端核心实现思路Vue3 项目如果是从零搭现在的主流方式是使用 Vite 官方脚手架。和 Vue2 时代常用的 Vue CLI 相比Vite 最大的优势是开发服务器启动极快而且修改代码后的热更新几乎是即时生效的不需要等待 Webpack 重新编译整个项目。团队开发时这种开发体验的差距会直接体现在效率上。前端的工程化结构建议和业务页面分开整理。如果把所有组件都堆在 views 文件夹里页面一多就乱成一锅粥。合理的做法是src/api 目录下按业务模块拆封接口请求文件src/views 目录下按页面拆组件src/components 目录下放通用组件src/router 放路由配置src/utils 放请求封装和工具函数。axios 封装是前端项目里必须做的一步。不做封装的直接后果是每个页面请求都要重复写请求头、处理错误状态码、拼接 token代码冗余而且容易出 bug。推荐的封装逻辑是创建一个 axios 实例设置 baseURL 和超时时间用请求拦截器统一注入 token用响应拦截器统一处理业务错误码和 HTTP 状态码。当后端返回 401 时前端自动跳转到登录页并清空本地存储的用户信息这个逻辑写在响应拦截器里一次到位不用每个页面重复写。路由守卫配合后端角色权限做页面访问控制也是这套系统的关键环节。Vue Router 提供了全局前置守卫beforeEach每次路由跳转前先检查本地有没有 token没有就跳到登录页有 token 再检查路由配置里的 meta 字段是否包含当前用户角色不包含就跳到 403 页面。这套逻辑可以保证哪怕用户手动修改了前端的路由地址也无法访问没有权限的页面。拿养老院系统的老人列表页举例一个完整的前端页面大致包含三个部分搜索表单区、数据表格区、弹窗表单区。搜索表单区包含姓名、年龄范围、床位状态等筛选条件数据表格区使用分页组件展示老人信息行内提供编辑、详情、删除按钮弹窗表单区用于新增和编辑老人信息表单校验规则用 Element Plus 的属性配置实现。页面的 Composition API 逻辑可以拆成四段响应式数据定义、页面初始化数据请求、搜索/重置逻辑、新增/编辑/删除操作逻辑每段都封装成函数代码清清爽爽。3. 实操过程与核心环节实现3.1 环境准备与项目初始化动手跑这套系统之前先把环境准备好。我给出一份实测可行的清单组件推荐版本备注JDK8 或 11SpringBoot2.7.x 在 JDK8 和 11 下运行稳定Maven3.6.3 以上建议使用阿里云镜像加速依赖下载Node.js16.14.x 或 18.xVite3/Vite4 对 Node 版本有最低要求MySQL8.0.x注意时区设置IDEIDEA 2023建议安装 Lombok 插件前端包管理npm 或 pnpmpnpm 安装依赖更快MySQL8.0 的安装Windows 用户可以直接用安装包Mac 和 Linux 用户我建议直接上 Docker。这里推荐一条我实测过很多次的命令开箱即用docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORDroot123456 \ -e TZAsia/Shanghai \ -v mysql_data:/var/lib/mysql \ mysql:8.0这段命令里有几个细节值得注意TZAsia/Shanghai环境变量解决了容器默认时区是 UTC 的问题避免插入数据时的日期时间比北京时间少 8 小时-v mysql_data:/var/lib/mysql是数据卷持久化容器删了数据还在不会因为 docker rm 而丢失数据库内容。使用 Docker 部署 MySQL8.0 的好处是环境干净卸载也方便但要注意防火墙不能占用 3306 端口否则容器启动成功数据库连不上的情况又会出现。数据库连接串的配置也需要重点标记。MySQL8.0 和 5.7 在 JDBC 驱动和连接参数上有差异最明显的体现是驱动类从com.mysql.jdbc.Driver变成了com.mysql.cj.jdbc.Driver而且新版驱动默认不会帮你处理时区所以连接串里必须显式声明 serverTimezone。保险的配置是这样的spring: datasource: url: jdbc:mysql://localhost:3306/eldercare?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue username: root password: root123456 driver-class-name: com.mysql.cj.jdbc.DriveruseSSLfalse是因为本地开发环境不需要 SSL 加密连接不加这个参数新版驱动会打印大量告警日志。allowPublicKeyRetrievaltrue则是因为 MySQL8.0 的 caching_sha2_password 认证插件在非 SSL 连接下需要客户端主动获取服务端公钥才能完成身份认证。3.2 核心模块实现示例老人档案管理一套管理系统拿来之后读懂一个核心模块的完整链路其他模块基本就能触类旁通了。我拿老人档案管理来做拆解因为它是整个系统的数据基础其他模块都围绕它展开。先看数据库表设计。老人基本信息表的建表 SQL 关键片段如下CREATE TABLE elder ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 主键ID, name VARCHAR(50) NOT NULL COMMENT 姓名, gender TINYINT NOT NULL COMMENT 性别 1男 2女, age INT NOT NULL COMMENT 年龄, id_card VARCHAR(18) NOT NULL COMMENT 身份证号, phone VARCHAR(20) COMMENT 老人本人电话, bed_id BIGINT COMMENT 床位ID, room_id BIGINT COMMENT 房间ID, care_level TINYINT COMMENT 护理等级 1自理 2半自理 3全护理, health_status VARCHAR(200) COMMENT 健康状况描述, check_in_time DATETIME COMMENT 入住时间, deleted TINYINT DEFAULT 0 COMMENT 逻辑删除标记, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT老人信息表;这里面有两个设计细节想重点说一下。第一个是逻辑删除字段。我在前面提到过 TableLogic 的逻辑删除方案数据库层面这个字段默认值是 0删除操作执行时实际上是把 deleted 从 0 改成 1。字段设计上最好给默认值并且索引要考虑到查询条件里带上 deleted0 的情况避免全表扫描。第二个是 bed_id 和 care_level 的设计。床位和老人是一对一关系老人入住时检查床位状态是否为空入住后把床位状态改为已住。护理等级这种枚举值数据库里存 TINYINT 数字前端通过字典映射显示文字比如 1 显示为“自理”、2 显示为“半自理”、3 显示为“全护理”。这种做法的好处是灵活新增一个护理等级只需要改前端字典不用改数据库结构也不用改后端逻辑。实体类写好后Mapper 接口一行就能继承 CRUD 能力。Service 层的核心逻辑集中在“入住办理”这个业务动作上。我当时重构这段逻辑时把它的处理步骤总结成了四步查询床位状态如果已被占用则抛出业务异常插入老人基本信息更新床位状态为已入住生成第一条费用记录默认以当前月第一天为计费开始时间。这四个步骤必须在一个事务里执行。如果不加事务前两步执行成功后第三步报错数据就出现了“老人存在但床位没绑定”的脏数据。用 Transactional 注解就可以避免这种局面。Controller 层的接口设计走 RESTful 风格列表接口用 GET 请求加分页参数新增用 POST修改用 PUT删除用 DELETE。参数用 DTO 对象接收通过 JSR303 的 Validated 注解做基础校验服务端校验是安全底线前端校验只是优化体验。前端的新增老人页面用的是 Element Plus 的 el-form 组件。表单校验规则配置在 rules 对象里比如姓名必填、身份证号格式匹配 18 位、年龄范围在 0 到 120 之间。提交时先执行 validate 方法通过后才调用新增接口。整个链路的调用顺序是el-form 提交事件 → api 模块的 addElder 函数 → axios PUT 请求 → 后端 Controller 接收参数 → Service 处理业务 → Mapper 执行 SQL → 返回 Result 对象 → 前端弹窗提示成功。分页列表的实现是另一个重点。前端请求参数包含 pageNum、pageSize、name、gender、careLevel 等字段后端用 Page 对象接收MyBatis-Plus 自动分页最后返回一个包含 total 和 records 的分页结果对象。前端表格用 el-pagination 组件current-page 和 page-size 分别绑定到对应的响应式变量change 事件触发时重新请求数据。这套流程在管理系统里几乎是标准模板一个项目里会重复出现几十次值得一次吃透后面就是复制改参数的事。3.3 权限体系与登录认证实现养老院系统的权限模型基于 RBAC基于角色的访问控制设计涉及五张核心表用户表、角色表、菜单表、用户角色关联表、角色菜单关联表。用户只能通过绑定角色来获得权限角色通过菜单表配置来决定能看到哪些页面、操作哪些按钮。后端登录接口的逻辑我梳理一下关键步骤public LoginVO login(LoginDTO dto) { // 1. 调用用户服务查询用户 User user userService.getByUsername(dto.getUsername()); if (user null) { throw new BusinessException(用户名或密码错误); } // 2. 密码加盐比对 if (!passwordEncoder.matches(dto.getPassword(), user.getPassword())) { throw new BusinessException(用户名或密码错误); } // 3. 查询用户角色和权限标识 ListString roles userService.getRolesByUserId(user.getId()); ListString perms userService.getPermsByUserId(user.getId()); // 4. 生成 JWT token String token jwtUtils.generateToken(user.getId(), user.getUsername(), roles); // 5. 组装登录结果 LoginVO vo new LoginVO(); vo.setToken(token); vo.setUserInfo(user); vo.setRoles(roles); vo.setPerms(perms); return vo; }密码加密使用 BCrypt 算法而不是 MD5。原因很简单MD5 是哈希算法彩虹表攻击可以轻松反查弱密码而 BCrypt 内部会自动加盐同一密码每次加密出来的密文都不一样安全性高出好几个量级。这个知识点在技术面试里经常被问到值得花时间深入理解。数据库里的用户信息表除了基础账号字段还应该包含状态字段用来标记账号是否被禁用。登录时如果状态为禁用直接拦截防止被踢出系统的用户在 JWT 有效期内继续访问接口。前端路由守卫的实现核心思路是在beforeEach里检查有没有 token同时检查路由 meta 里配置的角色是否与当前用户匹配。代码示例如下router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (!token) { if (to.path /login) { next() } else { next(/login) } return } // 根据后端返回的角色判断是否有权访问 const requiredRoles to.meta.roles if (requiredRoles !requiredRoles.includes(store.state.user.role)) { next(/403) return } next() })还有一个细节退出登录时除了清除本地 token最好调用后端接口记录操作日志。以后审计翻记录时这一步能帮你省掉不少排查纠纷的时间。按钮级权限控制使用 Vue3 的自定义指令或权限函数实现。比如“删除老人档案”这个按钮只对管理员角色可见前端通过 v-permission 指令做判断。需要注意的是按钮级权限只是在体验层面做了隐藏真正的安全边界必须由后端接口的权限校验来兜底前端隐藏按钮并不能防止有人直接调接口。4. 常见问题与排查技巧实录4.1 新手最常踩的坑这套技术栈在国内用得极其广泛网上相关的资料也很多但恰恰因为人多踩坑的方式也五花八门。我盘点几个这个项目里出现频率最高的问题附带排查思路你可以把这些整理成自己的速查表。问题一MySQL8.0 连接报错启动后端项目时控制台出现Public Key Retrieval is not allowed或者Unable to load authentication plugin caching_sha2_password核心原因就是连接串没带 allowPublicKeyRetrievaltrue 参数或者驱动版本不是 8.x 的 mysql-connector-java。解决方法是确保 pom.xml 里驱动依赖版本是 8.0.x同时在 JDBC 连接串里加上 allowPublicKeyRetrievaltrue。还有一个常见表现是The server time zone value GMT08:00 is unrecognized这是时区问题连接串加 serverTimezoneAsia/Shanghai 即可。问题二MyBatis-Plus 分页失效接口返回的分页对象里 total 为 0或者查询结果没有执行 limit最常见的原因是 MybatisPlus 分页插件没有注册。SpringBoot2 环境下如果使用的是分页插件拦截器一定要确认配置类被 Spring 扫描到。如果插件配置类在启动类所在包的子包之外可以手动在启动类上添加 MapperScan 或 ComponentScan 注解。这个问题的排查思路是看控台有没有打印带 LIMIT 字样的 SQL有说明插件生效没有说明插件没注册成功。问题三Vue3 前端跨域开发环境下前端地址是 http://localhost:5173后端接口是 http://localhost:8080浏览器会拦截跨域请求。解决方法有两种第一种是在后端写一个 WebMvcConfigurer 配置类重写 addCorsMappings 方法配置允许的跨域来源第二种是在 Vite 配置里写 proxy 代理把 /api 开头的请求转发到后端端口。我推荐第二种方式因为前端代理在生产环境部署时可以通过 Nginx 同样配置来实现一套思路走到黑而且不需要后端代码做额外处理。Vite 代理的配置示例// vite.config.js export default { server: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } }配好之后前端接口请求地址统一写/api/xxx开发环境由 Vite 转发生产环境由 Nginx 转发后端 Controller 的 RequestMapping 路径保持一致就行。问题四axios 请求拦截器里拿不到 token每次请求自动带 token 的逻辑写在请求拦截器里从 localStorage 读取。但要注意如果 token 过期或本地没有 token有些接口依然会被发出去后端返回 401 后响应拦截器需要处理这个状态码清理本地存储并跳转到登录页。这个逻辑如果漏写了会出现页面跳转正常但所有接口请求依然在控制台报错或者登录过期后用户还在当前页面白屏的情况。我在实际开发中习惯把 401 处理封装成一个函数在响应拦截器里统一调用不要散落在各个页面。问题五本地启动时端口占用SpringBoot2 默认占 8080 端口如果本地已有其他服务占用启动会直接报端口冲突。排查命令# Windows netstat -ano | findstr 8080 # Linux / Mac lsof -i :8080找到对应 PID 后杀掉进程或者在 application.yml 里换一个端口。如果前后端都在本机调试我建议直接把后端端口改成 8081 之类的非默认端口省得和别人共享机器的时候互相打架。4.2 项目上线前的检查清单如果你希望这套养老院系统不只是停留在本地开发而是真正部署到服务器上下面这个检查清单是我自己项目上线前都会过一遍的每次都能筛出一些问题。后端方面第一件事是关掉 SpringBoot 的调试日志级别生产环境用 INFO 或 WARN 就够避免日志文件被写入大量调试信息。第二件事是配置好 knife4j 或 swagger 接口文档的访问权限防止生产环境暴露所有接口信息。第三件事是数据库参数要调整比如连接池大小、超时时间这些配置在本地开发时无所谓但线上并发一上来就很有所谓了。前端方面用 Vite 构建的命令是npm run build产物会输出到 dist 目录。部署时你可以用 Nginx 托管前端文件同时反向代理后端的 API 请求。Nginx 的配置可以参考server { listen 80; server_name your-domain.com; location / { root /usr/share/nginx/html/dist; index index.html; 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; } }这里有两个关键点。一个是try_files $uri $uri/ /index.html;必须配置否则 Vue Router 使用 history 模式时用户刷新任意非首页路由都会出现 404另一个是/api/前缀转发到后端端口这个要和前端封装的 axios baseURL 保持一致。生产环境的跨域问题就是通过 Nginx 反向代理天然解决的前后端属于同源请求。数据库备份策略也是上线前必须考虑的。MySQL8.0 最实用的备份方式是用 mysqldumpmysqldump -uroot -p eldercare backup_$(date %Y%m%d).sql配合 crontab 定时任务每天凌晨把数据库备份到服务器磁盘再同步一份到对象存储或异地机器就基本可靠了。千万别把所有鸡蛋放在一个篮子里我曾见过服务器磁盘坏了导致所有备份数据一起没了的案例。实用建议把这个系统的源码跑通之后不要止步于运行起来。建议你亲手把老人档案模块的表结构改一改比如增加一个“紧急联系人”表把原来的单个家属电话改成多联系人结构然后从前端页面到后端接口全链路改一遍。这样一轮实操下来你对前后端交互、数据库设计、权限控制的掌握程度会远超单纯把源码运行起来的效果。我个人做这类项目的体会是源码本身只是一个起点真正有价值的是读懂它设计上的取舍和实现上的思路。SpringBoot2 加 Vue3 这套组合在目前的环境下依然是学习 Java Web 全栈开发的黄金选型它既不会像 SSM 纯前后端不分离那样让人感觉跟不上时代又能保证足够多的参考资料和成熟方案。拿到所谓“含文档”的源码后耐心把文档里描述的模块一个个跑通再尝试改一个功能点、加一个模块这个过程的收获会非常大。最后分享一个我在实操中反复验证过的小技巧这种管理系统项目建议你在本地把代码跑通之后再用它做一次从零搭建的练习不看源码只参考表结构和接口文档一步步从 SpringBoot 工程初始化写到前端页面联调。走完这一轮你对这套技术栈的掌握程度和动手能力已经可以应对大部分初级全栈开发的日常工作场景了。