前后端分离的养老院管理系统,这个选题其实挺有代表性的。养老院管理系统属于典型的“信息管理系统”业务场景,技术栈固定、模块清晰、权限分明,非常适合用来完整走一遍 SpringBoot + Vue3 + MyBatis + MySQL 的实战流程。我基于实际开发经验,把这个项目的源码结构、模块设计、数据库建模、前后端对接、本地部署启动以及常见问题排查完整梳理一遍,希望能给正在做同类项目的同学提供一份可以抄作业的参考。无论你是毕业设计需要,还是想快速搭建一套可用性强的管理后台,这篇文章都值得认真看完。
1. 项目整体设计与模块拆解
1.1 养老院管理系统的核心业务范畴
养老院管理系统,说白了就是一套“人、财、物”三位一体的信息管理平台。这里的人,不只是老人,还有护工、管理人员和家属;财,涉及床位费、护理费、餐饮费、医疗费用等多种计费维度;物,则是床位资源、药品库存、设备的维护记录。
我在梳理这个项目时,第一反应是把它拆成几个核心域:长者的基本信息管理、入住与退住流程管理、床位资源管理、护理任务与排班管理、健康档案与生命体征记录、收费与退费结算、家属沟通记录、系统用户与角色权限。不要一上来就追求大而全的面面俱到,而是先把“住得进来、管得住人、算得清账、陪得好老”这四件核心事做扎实。
1.2 用户角色与权限模型设计
与普通电商后台不同,养老院系统的角色模型更贴近“组织架构 + 岗位职责”。我建议在设计阶段就划分出超级管理员、院长、护士长、护理员、财务人员、社工/客服、家属(只读或有限授权),这些角色对应的菜单权限和数据权限都不同。
这个项目的权限设计重点是“数据权限”。比如护士长可以看全楼的护理记录,但普通护理员只能维护自己负责楼层或自己名下老人的记录;财务人员只管费用台账,不应该触及护理录入。在前后端分离架构下,后端负责接口鉴权,前端负责菜单路由控制,两者结合才能避免低权限用户绕过页面直接请求接口。我通常的做法是后端用注解 + 拦截器统一控制接口访问权限,前端用动态路由按角色渲染菜单,双保险最稳妥。
1.3 为什么坚持选择前后端分离架构
项目标题里明确写了“前后端分离”,这个选择是有实际考量的。养老院的网络环境往往并不理想,局域网或低带宽场景很常见,前后端分离能够降低单次页面刷新带来的服务器压力,同时方便后续把前端部署到独立的静态服务器,后端只提供 JSON 接口,天然适合未来拓展 App 或小程序端。
另外,前后端分离在实际开发效率上也更有优势。前端可以并行开发页面逻辑,后端可以专注设计 API 和业务规则,只要提前约定好接口文档,整个开发周期能缩短不少。缺点是前期的工程化配置会稍微繁琐一些,比如跨域配置、本地代理转发、Token 鉴权方案等都需要尽早确定,但项目跑顺之后,带来的维护便利性是值得的。
2. 核心技术选型与版本选择的背后逻辑
2.1 后端框架:SpringBoot 版本到底选哪个
这个项目用的是 SpringBoot,这是 Java 后端最主流的基础框架。很多初学者拿到一份老教程,直接照着 2.x 版本的写法去搭项目,结果发现依赖下载报错、yml 配置自动提示失效、启动失败,最后把问题归咎于“版本太新不兼容”。
实际上,SpringBoot 版本的选择核心是“兼容性 + 稳定性”。我在这个项目里推荐使用SpringBoot 2.7.x,原因很实际:2.7 是 2.x 系列的最后一个功能分支,大多数社区资料、培训课程、第三方中间件(比如 Druid、PageHelper)都对它做了充分适配,同时它又支持 Java 8,这对大量还在使用 JDK 8 的机器非常友好。如果选 3.x 甚至更高版本,一方面强制要求 JDK 17+,另一方面很多老派 MyBatis 相关 starter 的命名和自动配置方式都变了,新手上手成本明显增加。
如果非要使用 SpringBoot 3.x 版本,也不是不行,但要注意选择对应的mybatis-spring-boot-starter版本,并启用jakarta命名空间。对于以“快速构建管理系统”为目标的项目来说,我更建议稳定优先,先把业务跑通,再来升级换代。
2.2 为什么选择 MyBatis 而不是 JPA
现在 Java 圈子做持久层,主要就是 MyBatis 和 JPA 两大派系。这个项目选型定的 MyBatis,我的理由其实很直白:管理系统里报表和统计查询特别多,像“统计各楼层入住率”、按月份汇总护理工时、筛选未缴费老人名单。这类多表联查、动态拼接条件的 SQL,用 MyBatis 的 XML 文件维护起来更加直观可控。
MyBatis 的优势在于“SQL 自由度”。当一个查询条件多达五六个维度时,MyBatis 的动态 SQL(<if>、<where>、<foreach>)写起来非常趁手,而且 SQL 审计方便,DBA 也乐意看。JPA 在单表 CRUD 上确实省事,但涉及复杂查询时要么写 JPQL,要么退回到原生 SQL,反而绕了远路。
2.3 前端选型:Vue3 + Element Plus 组合
Vue3 现在已经非常成熟,如果你是从零开始做管理系统,完全没必要再回头看 Vue2。Vue3 的组合式 API(Composition API)带来了更灵活的逻辑组织方式,配合 Vite 的开发调试体验,热更新速度快到几乎无感。
管理后台的前端组件库,我最常用的组合是Vue3 + Element Plus。Element Plus 就是 Vue2 时代 Element UI 的升级版,对表格、表单、弹窗、分页、树控件的封装非常完善,和后台管理系统的页面形态高度契合。表格的分页、排序、多选,弹窗的表单校验,这些高频需求都有现成方案,写起来比从零手搓高效得多。
2.4 MySQL 数据库选择与存储引擎
MySQL 是这个项目的数据库底座,也是标题里明确的技术栈成员。个人项目或毕业设计场景下,使用MySQL 8.0是比较推荐的版本。相比 5.7,8.0 的窗口函数、公共表表达式(WITH 子句)、默认字符集 utf8mb4 都更加好用,而且官方持续维护安全补丁,稳定性和性能都有保障。
数据库默认使用 InnoDB 存储引擎,事务支持和行级锁是硬指标。养老院系统里涉及费用扣减、床位分配这类数据一致性敏感的操作,没有事务保障很容易出现并发问题。表结构设计上严格遵循“一表一业务域”的原则,主键统一用自增 ID 作为业务主键,同时给外键关联字段加上普通索引,这些细节在后续“数据库设计”章节我会展开讲。
3. 数据库设计:核心表结构与关系分析
3.1 长者信息表的设计思路
老人表是系统的核心主表,我一般命名为elder,字段上除了姓名、性别、身份证号、联系电话这类基础信息之外,特别建议加上这些容易被忽略的字段:老人紧急联系人及电话、入住日期、床号ID、护理等级(自理/半自理/全护理)、健康状态摘要、家属ID(关联家属表)。其中“入住状态”字段非常关键,建议用 tinyint 标识,0表示空床/待入住,1表示在住,2表示已退住,后续所有统计都基于这个字段做过滤。
户籍地址和现居住址也要分开存,紧急联系人的关系字段不要只存姓名,最好关联到家属用户表,因为这个项目里家属是有登录入口的,虽然权限受限,但查看老人健康记录、接收账单通知都需要做关联。
3.2 床位与楼层资源管理
床位管理是养老院特有的业务。我设计的是“房间-床位”二级结构,而不是单一的床号字段。房间表存楼层、房号、房间类型(单人/双人/多人间)、房间定价;床位表挂房间ID,额外存床位的当前状态(空闲/已入住/维修中)。这样一个设计带来两个好处:一是收费规则可以按房间类型灵活设定,二是楼层和房间可以做成树形结构展示,前端展示体验更好。
注意床位编号和房间号不建议合并成一个字符串字段,否则后续要做床位统计和调房操作的时候,字符串拆分的痛苦会让你想重做表。
3.3 健康档案与生命体征记录
老人健康管理需要两张表:健康档案主表health_profile和体检/体征记录表vital_sign_record。档案主表存老人的过敏史、既往病史、慢病管理方案、主治医生建议等静态信息;体征记录表则按时间维度存血压、心率、血糖、体温等动态数据,每次老人例行体检或日常测量后追加一条记录。
这两张表的分工要明确:档案是基线,记录是动态变化。前端展示时,健康档案页显示“最新一次体征记录 + 历史趋势图”,医生端则看完整档案详情。每次测量记录都建议带上录入人和记录时间,方便追溯护理责任。
3.4 护理任务与排班记录
护理排班在业务上比较复杂,因为养老院是按“护理等级 + 老人居住楼层”分配护理员。护理任务表nursing_task存储每天的护理任务,包括任务类型(喂药、翻身、助浴、复健等)、执行日期、执行状态(待执行/已完成/已跳过)。nursing_schedule表则记录护理员的排班,包括排班日期、早中晚班次、负责楼层。
我的建议是任务和排班分开设计。排班是“人”的安排,任务是“事”的拆解。如果合并成一张表,查询逻辑会非常混乱,后续统计每个护理员的工作负荷也会变得很麻烦。实际项目里我还会加一个“老人生日提醒”功能,就是从老人表里查近7天生日的数据,推送给社工做活动安排。
3.5 费用管理与收费记录
费用模块我会拆成三个表:fee_item费用项目表(床位费、护理费、伙食费、医疗费)、charge_record收费记录表、refund_record退费记录表。计费的思路是每月初自动生成待缴账单:根据老人的房间类型定价、护理等级定价、当月入住天数自动计算床位费和护理费,加上伙食费和额外医疗消费,生成月度账单。
关于时间重叠的收费计算,说个设计机巧:数据库里存“入住日期”和“退住日期”,计算当月费用时先处理入住日期再根据退住状态拆月计费。比如老人15号入住,当月只收15号之后的天数,下月正常收整月。退住当月则只收到退住当天为止,未消费的预缴项走退款流程。这套规则在代码里用一个公共计算模块实现,不要散落在各个业务方法中。
4. 后端核心实现与代码展开
4.1 项目初始化与基础配置
后端工程的初始化方式,我这里直接用 Spring Initializr 生成基础骨架,或者直接从一个已有的 clean 模板项目复制改造。包结构建议按“功能模块分包”而不是“技术分层分包”。我之前吃过按 controller/service/mapper 分包的亏,项目小还好,项目一大了找代码就是灾难。现在更推荐的做法是:controller、service、mapper、entity、dto、vo作为顶层包,下面按业务模块再分,比如controller/elder、service/charge。
基础配置集中在application.yml中,数据源建议使用 HikariCP,这是 SpringBoot 默认内置的连接池,性能表现优秀,不需要额外引入其他连接池组件。配置示例:
server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/eldercare_system?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true username: root password: 123456 hikari: maximum-pool-size: 10 minimum-idle: 5 connection-timeout: 30000 mybatis: mapper-locations: classpath:mapper/**/*.xml type-aliases-package: com.eldercare.entity configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImplmap-underscore-to-camel-case: true这个配置很关键,它能让数据库的elder_name自动映射到实体类的elderName,少写很多冗余的映射配置。StdOutImpl是将 SQL 打印到控制台,开发阶段建议开启,排查问题的时候能看到完整的 SQL 语句和参数值。
4.2 统一响应体与全局异常处理
后端和前端的交互,格式一定要统一。我习惯定义一个通用返回体Result<T>,字段包括code(状态码)、message(提示信息)、data(返回数据)、timestamp(时间戳)。成功时 code 为 200,失败时按业务错误码区分。这样前端只需要对返回包装做一次统一拦截处理,不用每个接口单独判断。
全局异常处理用@RestControllerAdvice来实现,分别捕获参数校验异常(MethodArgumentNotValidException)、业务异常(自定义BusinessException)、未登录(NotLoginException)以及数据库异常。不要在 Controller 里写 try-catch 包业务,然后把堆栈抛给前端看,这种做法既不安全也没有用户体验。全局异常处理是前后端分离项目的底线工程。
4.3 MyBatis 动态 SQL 实战
查询老人列表时,搜索条件往往是不固定的:按姓名模糊查询、按护理等级精确筛选、按入住状态筛选、按楼层筛选,还可能要求按入住日期范围排序。这种场景就是 MyBatis 动态 SQL 的主场。以老人列表分页查询为例,Mapper XML 大致长这样:
<select id="selectElderPage" resultType="com.eldercare.entity.Elder"> SELECT e.*, r.floor_no, r.room_name, b.bed_no FROM elder e LEFT JOIN bed b ON e.bed_id = b.id LEFT JOIN room r ON b.room_id = r.id <where> <if test="keyword != null and keyword != ''"> AND (e.elder_name LIKE CONCAT('%', #{keyword}, '%') OR e.id_card LIKE CONCAT('%', #{keyword}, '%')) </if> <if test="careLevel != null"> AND e.care_level = #{careLevel} </if> <if test="status != null"> AND e.status = #{status} </if> <if test="floorNo != null"> AND r.floor_no = #{floorNo} </if> </where> ORDER BY e.create_time DESC </select>LEFT JOIN 比关联子查询效率更好,特别是在大表场景下。模糊查询使用LIKE CONCAT('%', #{keyword}, '%')的方式,不用手动拼接字符串,SQL 注入的防线就在这一层起作用了。
4.4 登录认证与权限控制的落地实现
登录认证我采用的是 JWT(JSON Web Token)方案,这也是目前前后端分离项目最主流的做法。用户登录成功后,后端签发一个带过期时间的 Token,前端保存到 localStorage 或内存中,后续每次请求在请求头Authorization字段带上这个 Token。
后端用一个拦截器(HandlerInterceptor)统一校验 Token 合法性,解析出用户ID、角色信息,存入 ThreadLocal 方便后续业务代码直接获取当前登录用户。放行白名单包括登录接口、验证码接口和文件下载接口。这里有个容易忽略的细节:Token 过期前,前端应该通过响应拦截器统一处理 401 状态码,自动跳转到登录页,否则用户看到的是毫无提示的报错页。
角色的菜单权限,我是直接用数据库配置:菜单表sys_menu存前端路由需要的名称、路径、组件、图标、排序、父ID,角色菜单关联表存可见菜单集合。登录时一次查询出该角色可见的菜单树返回给前端,前端动态注册路由,这样后端只需要校验接口权限,前端只展示有权限的菜单入口,体验和安全性兼顾。
4.5 文件上传与图片预览
养老院系统里,老人生病、体检、入住合同等等这些场景大多需要上传图片或者 PDF 附件做存档。SpringBoot 处理文件上传,使用MultipartFile接收文件,存储到本地目录,并给文件生成唯一的文件名,图片访问路径通过静态资源映射暴露出去,不要直接把用户原始文件名保存到数据库里,因为原始文件名可能携带路径信息、恶意字符,甚至有重名覆盖的风险。
如果是集群部署,建议后续把文件存储切到对象存储服务,但在单机项目阶段,本地存储完全够用。敏感资料,比如合同和身份证照片,不建议在浏览器直接暴露原始访问链接,而是通过接口校验权限后再返回文件流,这个小细节直接影响合规审计。
5. 前端项目的工程化与关键页面实现
5.1 Vite 创建 Vue3 项目与目录结构
我推荐直接用 Vite 创建项目,这是当前 Vue3 项目的最佳实践。先执行一句命令,然后按提示选择 Vue + JavaScript 或 Vue + TypeScript 模板。项目创建后,前端目录结构我习惯按类型和功能混合组织:
src/ ├── api/ // 接口请求模块 ├── assets/ // 静态资源 ├── components/ // 通用组件 ├── layout/ // 布局组件 ├── router/ // 路由配置 ├── store/ // Pinia 状态管理 ├── utils/ // 工具函数 ├── views/ // 页面视图 ├── App.vue └── main.js组件库安装 Element Plus 之后,在 main.js 里全局注册(全量引入即可,做管理后台不需要刻意优化打包体积,交互优先)。
5.2 动态路由与菜单权限控制
Vue Router 使用addRoute方法动态添加路由,这是一套成熟的路由权限方案。用户登录成功后,后端返回菜单集合,前端根据菜单数据动态生成普通用户的可访问路由表,并把这些路由通过router.addRoute()注册到路由实例中。
这里需要注意顺序问题:刷新页面时路由已经动态添加完毕,但如果用户直接刷新浏览器,前端应用每次都会重新执行登录态检查和动态路由加载。所以动态路由的加载逻辑要放在全局前置守卫中,否则刷新后就找不到路由组件,页面白屏。
5.3 基于 Axios 的请求封装与认证拦截
前端的所有 HTTP 请求通过 Axios 封装成一个模块,这样做的好处是:在请求拦截器里统一添加Authorization头、统一加时间戳参数;在响应拦截器里统一处理错误码。比如后端返回 code 200 直接返回数据体,业务码 500 弹出全局错误提示,401 清除登录态并跳回登录页。
封装示例逻辑:
import axios from 'axios' import { ElMessage } from 'element-plus' import router from '@/router' import { getToken, removeToken } from '@/utils/auth' const service = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL, timeout: 15000 }) service.interceptors.request.use(config => { const token = getToken() if (token) { config.headers['Authorization'] = `Bearer ${token}` } return config }) service.interceptors.response.use( response => { const res = response.data if (res.code !== 200) { ElMessage.error(res.message || '请求失败') if (res.code === 401) { removeToken() router.push('/login') } return Promise.reject(new Error(res.message)) } return res }, error => { ElMessage.error(error.response?.data?.message || '网络异常') return Promise.reject(error) } )VITE_API_BASE_URL通过 Vite 环境变量配置,开发环境用代理转发到本地后端,生产环境直接指向网关地址。这里最不能省的是 401 的全局处理逻辑,我在实际开发中见过太多“Token 过期后页面卡死无反应”的惨案,根源基本都在这里。
5.4 核心管理页面实现思路
老人管理页面是最典型的 CRUD 页面,采用“搜索区 + 表格区 + 分页区 + 弹窗表单”结构。搜索区使用el-form的inline属性,支持姓名、护理等级、状态的组合筛选;表格区展示老人列表关键字段;操作列放“详情”“编辑”“入住/退住”“查看健康档案”“记账”这些按钮。弹窗表单里用el-form的rules做表单校验,提交前统一校验再调用接口。
表格分页参数和查询参数建议用响应式对象管理:
const queryParams = reactive({ pageNum: 1, pageSize: 10, keyword: '', careLevel: '', status: '' })后端 PageHelper 或手写 LIMIT 分页都可以,但不要自行拼接 SQL,规范做法是 Mapper 接口传递@Param索引参数,XML 里用 LIMIT 完成分页。
5.5 ECharts 可视化大屏与统计数据
养老院管理系统中,管理者需要直观看到关键运营指标:总床位数、在住老人数、入住率、男女比例、护理等级分布、月度费用收入趋势、楼层入住情况。这些图表通过 ECharts + Vue3 封装实现,常放在首页作为仪表盘。
ECharts 图表组件化很关键,避免每个图表都独立 init 和 setOption,可以封装一个通用的ChartCard.vue,接收optionprop 和heightprop,内部完成 init、setOption、resize 监听。注意在组件卸载时调用dispose释放实例,否则路由切换后会出现内存泄漏或图表实例重复创建的问题。
6. 本地部署启动与数据库初始化
6.1 MySQL 环境安装与数据库建库
如果你本地还没有 MySQL,第一步是下载对应版本的 MySQL 并安装。安装完成后,打开命令行或 MySQL Workbench,创建一个专用数据库:
CREATE DATABASE eldercare_system DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE eldercare_system;注意 mysql 8.0 默认字符集已经是 utf8mb4,但显式声明还是更为稳妥。项目根目录下通常附带sql/init.sql或eldercare_system.sql,直接用source命令或者在图形化工具中执行全部脚本即可完成建表。
执行完初始化脚本后,可以执行SHOW TABLES;确认表都建出来了。同时建议顺便插入一条测试管理员账号,登录后先看看页面能不能正常拉取数据。
6.2 后端启动与接口自测
后端启动很简单,在 IDEA 中直接运行主启动类。启动成功后,日志中会看到端口监听,比如Tomcat started on port(s): 8080。
这时候用浏览器访问http://localhost:8080/api/...测试接口,如果配置了 SpringDoc / Swagger,也可以直接访问 swagger-ui 页面调试接口。我用得比较多的方式,是用 Apifox 或 Postman 建一个登录请求,获取 Token 后带 Token 请求业务接口,这样能快速验证 Token 鉴权链路是否通畅。
6.3 前端启动与跨域配置
前端要执行依赖安装和启动命令:
npm install npm run dev如果依赖下载缓慢,建议临时使用国内镜像源配置。启动成功后默认端口一般是 5173,浏览器会自动打开开发地址。
开发环境下前后端跨域是必须处理的典型问题。我这里推荐在 Vite 配置里添加开发代理,用/api前缀标识后端接口:
server: { port: 5173, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } }配置代理后,前端请求的 baseURL 即可设置为/api,这个阶段既避免了跨域问题,也方便生产环境用 Nginx 或网关统一转发,接口具体地址不在前端代码里写死,维护成本更低。
6.4 生产环境构建与部署思路
开发调试完成之后,前端构建静态资源使用:
npm run build产物输出在dist目录。部署时把dist目录扔到 Nginx 或者任何静态文件服务器里,用 Nginx 同时承担静态资源服务和 API 反向代理:
server { listen 80; server_name yourdomain.com; location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://localhost:8080/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }try_files这一行是单页应用历史路由模式部署的必备配置,如果漏掉,用户刷新页面就会 404。这个坑几乎每个前端部署都会踩一次,我特意放在这里提醒。
6.5 前后端联调的关键节点梳理
联调阶段最容易卡住的是字段不一致问题。联调前先看后端的接口文档或实体类,明确字段名称、类型、嵌套结构。比如时间和日期字段在前端是否需要格式化、枚举状态是数字还是字符串、分页返回对象的具体结构,这些都要在开始写代码前对齐。
我习惯的联调步骤是:先调通登录接口,拿到 Token;再用 Token 调通一个列表查询接口;验证分页参数、搜索条件、数据格式无误后,再继续推进剩余模块。如果一开始就把接口全部写死到页面里,联调发现问题时再逐个排查,费时费力。
7. 常见问题与排查技巧实录
7.1 数据库连接失败的多种可能
数据库连不上是项目启动最常见的问题。先看报错信息,如果是Access denied for user,说明用户名或密码不对,或者该用户没有对应数据库的访问权限;如果是Communications link failure,多半是数据库服务没启动,或者端口不是默认的 3306;时区报错则在 JDBC URL 里加serverTimezone=Asia/Shanghai。
如果本机装了多个版本的 MySQL,用 3306 端口冲突导致服务起不来,这也是我踩过的坑。建议保留一个服务实例,或者给不同实例指定不同的端口,避免无意义的排查时间消耗。
7.2 MyBatis 映射文件路径与绑定异常
启动时报Invalid bound statement (not found),十有八九是 Mapper 接口和 XML 文件没有正确对应。检查三个点:application.yml里mybatis.mapper-locations是否扫描到 XML 路径;XML 文件的namespace是否完整等于 Mapper 接口的全限定名;XML 中的 statement id 是否与接口方法名一致。
这个报错几乎不会自动消失,每次排查按这个顺序来就好。另外,Maven 项目要注意 XML 文件如果在src/main/java下面,修改pom.xml的<resources>配置,让 XML 文件打进 classpath,否则运行打包后的 Jar 会同样报找不到映射。
7.3 前端页面空白或数据渲染异常
前端页面空白,查看浏览器控制台最常见的就是路由匹配不到组件,也就是No match found for location,说明动态路由添加逻辑有问题——刷新后路由丢失。另一个常见情况是接口 404 或 500,前端没有做错误拦截提示,页面看起来就白屏了。调试时先打开 Network 标签页看具体请求状态码,再逐层排查。
字段渲染异常大多是变量名对不上,或者后端返回的数据结构嵌套层级深了,模板里路径写错。在拿到接口真实响应结构后,先手动把假数据替换成真实数据结构再调试。
7.4 跨域请求被拦截问题
本地开发时浏览器报 CORS 错误,如果你已经在前端配置了 Vite 代理,那大概率是请求路径没走代理,而是直接请求了http://localhost:8080这个完整地址,导致没有命中代理规则。检查请求 URL 是否以/api开头,别在 Axios 的 baseURL 里硬编码端口号。
如果在后端单独加了@CrossOrigin或全局跨域配置,同时前端也配置了代理,两套机制叠加反而会出问题。我的建议是:开发环境只配前端代理,生产环境用 Nginx 转发,后端尽量不要开全局跨域,从源头避免安全隐患。
7.5 JWT Token 失效与登录状态丢失排查
Token 失效跳转逻辑没生效,优先排查响应拦截器的 401 判断是否写对。还要检查后端是否对每个请求都正确校验了Authorization头。以下几点常见错误:前端 Token 存储的 key 和后端解析时不一致;Token 过期时间设置太短导致开发时频繁掉线;时间服务器时间不对导致 JWT 的时间戳校验失败。开发阶段可以把过期时间适当调长,比如 24 小时,避免频繁登录干扰开发节奏。
8. 扩展思路与项目复盘总结
养老院管理系统的核心价值不在于代码有多炫,而在于它完整呈现了一个业务管理系统从数据库设计、后端接口开发、前端页面搭建到部署上线的全链路能力。做完这套项目,你基本能够掌握 Java 全栈开发的常见流程,往后再接到任何类似的“某行业管理系统”需求,都能快速迁移。
扩展方向上,这个项目可以继续演进:引入 Redis 做缓存,把热点数据查询的响应时间降下来;接入 WebSocket 实现老人异常生命体征的实时告警;增加移动端 H5 或微信小程序给家属使用;引入工作流引擎处理请假、用章、审批等内部流程。这些后续迭代都以当前的基础版本为底座,架构设计时留的余地越大,后期演进就越从容。
我个人实际操作这个项目最大的体会是:不要小看“管理系统”这四个字背后的复杂度。真正的难点从来不是 CRUD 怎么写,而是业务规则的梳理、表结构的反复推敲、异常场景的补全。把这些基本功练扎实了,框架和组件库再怎么换代,你都能稳稳接住。最后再说个实用技巧:写代码前多花一小时把数据库表和接口列表理清楚,比写代码中途返工改表省下的时间要多得多。这个经验适用于任何一次项目开发,推荐你先从这次的养老院管理系统开始体会。