每年毕业季都有不少人抱着笔记本来找我聊毕设选题,当看到“敬老院管理系统”这种题目时,大部分人的第一反应是:Java+SpringBoot,老套路,CRUD拼一拼就完事。但真的动手做下去才发现,这个题目真正考察的不是增删改查,而是你能否把一个业务流程完整、身份角色复杂、数据关联紧密的行业场景,用工程化的方式落地成一个能演示、能答辩、能说清楚设计理由的Web项目。我最近刚完整重写了一遍敬老院综合管理平台,从需求梳理到数据库设计,从接口联调到部署演示,前前后后花了两周时间。这篇文章就按我自己的实操过程来复盘,把那些文档里不会写的判断逻辑、版本陷阱、答辩追问点都摊开讲,给准备做这个方向的计算机专业同学一个可参考的完整路径。
1. 毕设选题的价值判断:敬老院管理系统究竟在考什么
1.1 业务场景复杂但边界清晰,适合展示工程能力
很多同学选“敬老院管理系统”是因为觉得它简单,其实恰恰相反。敬老院管理涉及的对象不止“老人”一个实体,它天然带有老人、家属、护工、床位、费用、健康档案、护理任务这些主数据,还牵扯入住、转床、退住、请假外出、体检异常提醒等状态流转。主数据多、状态多、权限层级多,反而是最好的毕设素材。
但它的边界又是清晰的:不涉及支付网关对接、不涉及复杂推荐算法、不涉及高并发流量治理。你只需要把业务逻辑讲清楚,把模块功能做扎实,把数据库关系设计规范,就已经达到本科毕设的上游水平。评审老师最反感的是“堆功能却讲不出业务闭环”,最欣赏的是“一个核心业务线走通,并且有数据支撑”。
1.2 评审老师会重点考察的几个维度
我根据多次模拟答辩的经验,总结出老师最关心的四个维度:
- 需求理解:你是否真的知道敬老院日常运营要管什么,还是只是凭空捏造菜单。
- 数据建模:表与表之间的关系是否合理,字段是否冗余,状态如何流转。
- 权限与安全:不同角色(管理员、护工、财务、家属)能看到什么、能操作什么。
- 技术落地:SpringBoot 的自动配置、MyBatis-Plus 的使用、事务控制、异常处理等细节是否有真实体现。
这四条对应到系统里,就是你得能画出完整的用例图、ER图,并且代码里的 @Transactional、@PreAuthorize、自定义异常处理器不是摆设。
1.3 和普通“xx管理系统”的本质区别
同样是管理系统,敬老院和“图书管理系统”“学生管理系统”最大的差异在于:养老行业有自己独特的业务规则。比如:
- 老人入住前必须做健康评估,评估结果影响护理等级和床位安排。
- 床位状态不是简单的空/满,还有“预定”“保洁中”“维修中”“隔离观察”等状态。
- 护理任务按班次流转,交接班需要有记录。
- 费用按月计算,但入住和退住可能发生在月中,必须按天折算。
- 老人外出、家属探访、临时用药都需要留痕,方便事后追溯。
如果只是做一个“老人表+床位表+CRUD”,那和图书管理没有区别,答辩时很容易被问倒。我在设计系统时,把大量精力放在状态机设计和业务规则校验上,这也是本文后面会反复强调的内容。
2. 技术选型与项目骨架:SpringBoot + MyBatis-Plus 的组合为什么稳
2.1 技术栈确定的逻辑
题目给了硬性要求:Java + SpringBoot + Web。在这个前提下,周边技术选型其实有充分的自主空间,我的选择如下:
| 组件 | 选型 | 理由 |
|---|---|---|
| 后端框架 | Spring Boot 2.7.x | 稳定版,兼容 JDK8/JDK11,网上资料多,不容易踩版本坑 |
| ORM | MyBatis-Plus 3.5.x | 内置 CRUD、分页、逻辑删除,降低手写 SQL 的工作量 |
| 权限认证 | Spring Security + JWT | 无状态认证,适合前后端分离,也方便答辩时展示对安全的理解 |
| 前端 | Vue 3 + Element Plus + Vite | 组件丰富,页面搭建快,对做毕设的学生友好 |
| 数据库 | MySQL 8.0 | 免费,功能全,MySQL 连接驱动和方言都很成熟 |
| 报表导出 | EasyExcel | 阿里开源,写 Excel 不耗内存,答辩演示效果好 |
| 构建工具 | Maven | 约定大于配置,依赖管理清晰 |
为什么不用 Spring Boot 3.x?当时也是纠结过。Spring Boot 3 要求 JDK17,且javax包名改为jakarta,Spring Security 6 的配置方式也变了。如果你平时用的是 JDK8,直接上 3.x 会有一堆兼容性报错。毕设求稳,我建议除非你非常熟悉新版本,否则就用 2.7.x + JDK11,功能完全够,参考资料也最多。
2.2 前端必须前后端分离吗
不一定。用 Thymeleaf 做服务端渲染,也能实现整套 Web 管理平台,而且不需要处理跨域问题。但既然题目强调“Web 版综合管理平台”,我更推荐前后端分离。原因很实在:
- 前端组件库(Element Plus)能快速做出表格、表单、弹窗、日期选择器,比手写 HTML+JS 高效太多。
- 接口文档天然成为前后端协作的契约,答辩时能展示 RESTful API 设计能力。
- 部署时可以分开部署,也可以打包在一起,灵活性高。
当然,前后端分离也意味着要处理跨域、Token 存储、路由守卫这些问题,但这些恰恰是加分点。
2.3 项目包结构设计与启动配置
我习惯的结构是这样:
com.example.geriatric ├── common // 通用结果封装、异常处理、常量、工具类 ├── config // MyBatis-Plus、Security、Cors 等配置类 ├── controller // 接收请求,参数校验,返回统一 Result ├── service // 业务逻辑,事务边界 ├── mapper // MyBatis-Plus 的 Mapper 接口 ├── entity // 数据库实体,对应表结构 ├── dto // 前端交互的数据传输对象,避免直接暴露实体 └── vo // 视图对象,比如仪表盘统计数据、床位分布统计控制层只做参数接收和结果返回,业务逻辑全部下沉到 Service 层,事务注解标在 Service 实现类上。这个结构不是花架子,它直接影响你后面能不能快速定位 bug、能不能加分项地加上 Redis 缓存或消息队列。
2.4 Spring Initializr 搭建步骤
我建议直接在 Spring Initializr 网站生成基础项目,选择 Java 8/11、Maven、Spring Web、Validation、Security、MySQL Driver、Lombok。生成之后手动加入 MyBatis-Plus 和 EasyExcel 的依赖。
如果需要手搓核心依赖,pom.xml 里大概是这样:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.3.1</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>easyexcel</artifactId> <version>3.3.2</version> </dependency>注意 MyBatis-Plus 版本不能和 Spring Boot 差太多,否则自动配置加载不到。
3. 需求分析与核心功能模块拆解:从入住到退住的完整链路
3.1 老人信息档案:不是一张表能搞定的事
最核心的表是elder,但字段不能只放姓名、性别、身份证号。我按业务域拆成几部分:
- 基本身份信息:姓名、性别、出生日期、身份证号、户籍地址、民族。
- 入住信息:入住日期、护理等级、合同编号、押金金额、房间床号、状态(在院/外出/退住)。
- 健康信息:既往病史、过敏史、当前用药(可能是多选或关联子表)、自理能力评分。
- 紧急联系人:姓名、关系、电话、地址,通常要维护多个联系人。
健康评估建议单独建一张表health_assessment,每次入住或者每季度复评都生成一条记录,包含评估人、评估时间、评估项打分、建议护理等级。这样做的好处是:护理等级可以追溯,答辩时有理有据。
3.2 床位管理:状态流转与预定/入住/退住
床位表bed不要简单搞一个status枚举(空/占用)。养老服务里,床位状态至少有这些:
- 空闲(AVAILABLE)
- 已预定(RESERVED)
- 已入住(OCCUPIED)
- 保洁中(CLEANING)
- 维修中(MAINTENANCE)
- 隔离中(ISOLATION)
如果只用一个字符串字段,代码里到处写魔法值,后面维护会很痛苦。我的做法是在数据库里用status存字符串编码,同时配一张字典表sys_dict记录所有状态的含义。代码里再用常量类或枚举统一管理。
更重要的是床位变更记录。老人从 A 床转到 B 床,不是简单把两个床位状态改掉就完事,要记录一条bed_change_log:老人ID、原床位、新床位、变更原因、操作人、变更时间。这样以后有人问“这个老人之前住过哪个床”,你有据可查。
3.3 护工排班与任务分配:养老系统的核心难点
护工排班是拉开项目档次的功能。简单做法是给护工关联一个班次字段,但真实场景中,护工每天按早、中、晚三班轮转,且每个班次要负责固定区域的老人。建议设计两张表:
nurse_schedule:护工ID、日期、班次类型(早/中/晚)、负责区域。care_task:老人ID、护工ID、任务类型(喂药、翻身、擦洗、送餐)、计划时间、实际完成时间、完成状态。
这样既能看到“今天哪个护工负责哪些老人”,也能看到“该护工的任务完成率”。答辩时可以结合前端日历组件展示排班日历,这个演示效果非常加分。
3.4 健康监测与体检记录
健康数据录入是另一个亮点模块。除了每次体检的数值字段(血压、血糖、心率、体温),还要有趋势图展示。后端可以提供一个接口:按老人ID和时间范围查询健康记录,返回折线图所需的日期和数值集合。前端用 ECharts 画折线图,看起来比纯表格专业很多。
如果老人的某项指标连续异常,系统应该能生成一条提醒,交给护士长角色处理。这里可以引入一个简单的规则判断,比如“收缩压连续三次高于160则标记异常”,而不是等人工发现。
3.5 费用管理:月费计算和账单
费用管理相对麻烦,因为入住和退住可能发生在任意日期。我的设计是:
- 费用项目表
fee_item:护理费、餐费、床位费、水电费、医疗耗材等,每个项目有单价、计费单位(月/天/次)。 - 月度账单表
monthly_bill:老人ID、账单月份、应收总额、实收金额、优惠金额、状态(未缴/已缴/免单)。 - 账单明细表
bill_detail:每条明细对应一个费用项目,金额按实际入住天数折算。
计算逻辑放在 Service 层,月末跑批生成下月账单。如果是月中入住,根据入住日期按天折算;退住后自动生成退住结算单,把押金扣减后剩余部分退还给家属。这样费用模块就不再是“手工输入一个数字”,而是有完整的账务流转。
3.6 系统管理与统计报表
系统管理包括用户、角色、菜单权限。我推荐用 Spring Security 注解实现方法级权限控制:
@PreAuthorize("hasRole('ADMIN')") @PostMapping("/elder") public Result addElder(@RequestBody ElderDTO elderDTO) { ... }角色至少要有:超级管理员、院长/管理员、护士站、护工、财务、家属。家属端甚至可以做成单独的 H5 小页面,查看老人健康报告和账单,如果时间不够可以在答辩时说成“后续扩展”。
统计报表建议用仪表盘呈现:今日在院人数、床位使用率、护理任务完成率、当月营收、异常健康提醒数。SQL 离不开COUNT和GROUP BY,但不要全塞在控制器里,统一封装在StatisticsService。
4. 数据库设计的几个关键决策:为什么我的表不是简单的增删改查
4.1 实体关系建模的思路
核心关系图可以这样理解:
elder(老人)与bed(床位):多对一,一个床位同时最多只能有一个在院老人,但历史入住记录一对一存在elder_bed_history中。elder与family_member(家属):一对多,一个老人可以有多个联系人。nurse与care_task:一对多,一个护工有多条任务。employee与role:多对多,通过sys_user_role关联。elder与monthly_bill:一对多。
我比较注意的一点是:不要用太多“万能关联表”来代替应有的业务语义。例如“老人和护工”确实可以做成多对多,但如果你给了护工和老人的关联关系,却解释不清这个关联是“负责”还是“曾经护理过”,那宁可用care_task来承载这种关系,至少在业务上能自洽。
4.2 状态字段用枚举还是字典表
我两种都用了。代码里高频使用的状态用枚举类,比如床位状态BedStatusEnum,护工任务状态TaskStatusEnum;需要维护中文名称、展示给前端标签的,比如费用类型、老人护理等级,用字典表。
这样设计的原因很简单:枚举可以避免代码里出现写着写着就“忘了有哪些取值”的问题,IDE 也能提示;字典表则可以不用改代码就调整显示文本。两者各司其职,表结构里存统一编码,前端拿到编码后通过字典接口翻译成中文。
4.3 时间线数据如何存储
养老业务对时间特别敏感:入住记录、退住记录、健康评估、费用调整、请假外出,都是时间线数据。我的习惯是:
- 每张业务表都包含
create_time、update_time,由 MyBatis-Plus 自动填充。 - 涉及状态变更的主表额外维护
change_log子表,比如bed_change_log、status_change_log。 - 不做物理删除,统一使用逻辑删除字段
deleted,MyBatis-Plus 的@TableLogic会自动在查询时加上deleted=0条件。
逻辑删除尤其重要,因为你要保留历史记录。退住的老人不是从库里去掉了,而是status变成“已退住”,deleted仍未删除。
4.4 MyBatis-Plus 生成 SQL 与实际手工调整
我在最开始用代码生成器快速生成实体、Mapper、Service,然后手动调整那些关联查询。MyBatis-Plus 自带的BaseMapper只解决单表 CRUD,像“查询老人列表并带上床位名称和护理等级”这种跨表需求,就需要自定义 SQL。
推荐在 Mapper 里写注解 SQL,比如:
@Select("SELECT e.id, e.name, e.gender, b.bed_no, d.dict_label AS care_level " + "FROM elder e " + "LEFT JOIN bed b ON e.current_bed_id = b.id " + "LEFT JOIN sys_dict d ON d.dict_type = 'care_level' AND d.dict_value = e.care_level " + "WHERE e.deleted = 0 " + "AND (e.name LIKE CONCAT('%', #{keyword}, '%') OR e.id_card LIKE CONCAT('%', #{keyword}, '%')) " + "ORDER BY e.create_time DESC") IPage<ElderVO> selectElderPage(Page<?> page, @Param("keyword") String keyword);复杂的多条件查询建议用 XML 文件,Mapper 接口上写方法是void或返回实体,XML 里用<where>、<if>动态拼接,可读性更高。
5. 关键功能实现细节:登录鉴权、并发入住、费用计算
5.1 JWT + Spring Security 的落地方式
登录鉴权我是用 JWT 加 Spring Security 过滤器链实现的。大概流程:
- 用户提交账号密码,后端通过
AuthenticationManager校验。 - 校验成功后,生成一个带角色信息的 JWT 令牌返回给前端。
- 前端将 Token 存在
localStorage,每次请求在请求头加Authorization: Bearer <token>。 - 后端配置 JWT 认证过滤器,解析 Token,并将用户信息塞进
SecurityContextHolder。 - 方法上使用
@PreAuthorize做细粒度权限控制。
JWT 的核心代码可以这样写:
String token = Jwts.builder() .setSubject(user.getUsername()) .claim("role", user.getRole()) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 8)) .signWith(SignatureAlgorithm.HS256, secretKey) .compact();关于静态密钥,别写在代码里,放到application.yml配置文件中,答辩时可以强调这是“可配置化”设计。JWT 的缺点是 token 失效控制困难,但用在毕设场景足够了。如果被问到,就说生产级方案会引入 Redis 黑名单。
5.2 床位状态变更的事务控制:避免同一张床被两个人同时入住
这个问题经常被忽略,但很容易在答辩现场被问“你如何保证数据一致性”。
想象这样一个场景:两个护工同时在两个窗口操作,看到床位“空闲”,都点了入住,如果不加控制,这个床位就会被分配给两位老人。
解决方案分三层:
- 数据库层:在
bed表加乐观锁版本号version,更新时用WHERE id=? AND version=?,如果影响行数为0则代表床位已被别人改过。 - 代码层:在修改床位状态之前,再次查询床位状态,确认是空闲才能执行。
- 事务层:整个入住流程(分配床位->创建入住记录->更新老人状态->生成待缴费账单)要放在一个
@Transactional(rollbackFor = Exception.class)方法里。
我最推荐的其实还是数据库行锁,比如:
SELECT * FROM bed WHERE id = #{bedId} FOR UPDATE;在一个事务内锁住床位记录,其他事务必须等它提交完才能操作同一个床位。这样实现简单,阐述也明晰。
5.3 费用计算模块的设计
费用计算最容易写乱。我之前第一版是直接在 service 里写死计算规则,结果后面补了“减免额度”功能之后,整个方法变成一团乱麻。第二版重构后,我把费用计算拆成策略模式:
FeeCalculator接口,定义calculate(BillContext context)。- 每个费用项一个实现类,比如
NursingFeeCalculator、MealFeeCalculator、BedFeeCalculator。 - 根据
fee_item.type动态从ApplicationContext拿到对应策略执行。
计算逻辑大致是:金额 = 单价 × 计费天数 / 计费周期天数。如果是包月,就按月费处以当月总天数再乘以实际入住天数;如果是按次,就直接用单价乘以次数。
这样设计的好处是:以后新增一个费用项目,不用改老代码,只加一个新策略类就行。这种扩展性在答辩中很容易成为“项目亮点”。
5.4 导出Excel:用 EasyExcel 化简代码
毕设系统里导出功能几乎是标配。我用 EasyExcel 替代传统的 POI,写起来简单且内存友好。
List<ElderExcelVO> list = elderService.listElderExportData(); String fileName = URLEncoder.encode("老人名单", "UTF-8") + ".xlsx"; EasyExcel.write(response.getOutputStream(), ElderExcelVO.class) .sheet("老人名单") .doWrite(list);注意导出 Excel 时不要在 Controller 层直接返回对象的 JSON 格式,要直接操作HttpServletResponse的输出流。响应头需要设置Content-Type为application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,否则前端下载下来可能是一堆乱码。
5.5 定时任务处理每日健康提醒
我用 Spring 自带的@Scheduled实现两类定时任务:
- 每天早上8点,查询当天需要执行健康测量的老人列表,生成护理任务写入
care_task。 - 每天晚上10点检查健康异常记录,如果某个老人有未处理的异常提醒,自动给护士长用户发送站内信。
@Scheduled默认是单线程串行执行的,如果任务超过执行周期会阻塞,所以我在配置里设置了线程池:
@Configuration @EnableScheduling public class SchedulingConfig implements SchedulingConfigurer { @Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { taskRegistrar.setScheduler(Executors.newScheduledThreadPool(5)); } }这个细节很容易被忽略,但体现了你对并发任务调度的了解。
6. 调试、测试与部署:如何让毕设项目看起来像“正经产品”
6.1 后端统一返回结构与异常处理
一个成熟的项目,前端不会依赖后端零散的返回值。我在common包里定义了统一返回类Result<T>:
public class Result<T> { private Integer code; private String message; private T data; // success/fail 静态工厂 }同时用@RestControllerAdvice做全局异常处理:
- 业务异常
BusinessException:返回code=400和自定义错误信息。 - 参数校验异常
MethodArgumentNotValidException:把字段错误信息拼接成字符串返回。 - 其他异常:返回
code=500,并打印日志。
有了这套结构,前端只需要判断code是否等于 200,省去大量分支判断,答辩时也显得项目规范。
6.2 单元测试与接口冒烟测试
毕设不要求覆盖率多高,但至少要针对核心业务写几个测试。我重点写了两个:
- 费用计算的单元测试:给定入住时间、退住时间和单价,断言计算结果。
- 床位分配的事务测试:模拟两个线程同时分配同一床位,断言只有一个成功。
用 H2 内存数据库做测试可以减少环境依赖,但 H2 和 MySQL 在方言上还有差异。所以我实际采用 Testcontainers 启动 MySQL 容器做集成测试,虽然重一点,但结果更可信。如果不想折腾,直接在测试配置里连接同一个 MySQL 实例,前提是不要污染生产数据。
6.3 部署:本地打包 + 云服务器 + 一键启动
前后端分离项目的部署方式很多。我用最简单的一种:
- 后端:Maven 打 jar 包,放到服务器,用
nohup java -jar启动。 - 前端:
npm run build生成 dist 目录,用 Nginx 托管,并配置代理/api到后端端口。 - 数据库:用 MySQL 导入初始 SQL 文件。
Nginx 的关键配置就这一段:
server { listen 80; server_name your_domain; location / { root /usr/share/nginx/html; 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; } }如果不想买服务器,也可以直接用本机演示,把端口映射一下,或者部署到免费的容器托管平台,但要注意数据安全问题。我更建议在答辩前自己搭一个本地 Linux 虚拟机演示,至少不会因为网络波动导致现场事故。
6.4 演示数据准备技巧
演示数据是很多同学忽略的。一个充满“张三、李四、测试123”的系统,给老师的印象会大打折扣。我的做法是写一个DataInitializer类,在应用启动时检测到数据库为空,自动写入一批贴近真实的数据:
- 老人名字:王秀英、张建国、李桂兰等,年龄分布在 70-90 岁。
- 家属关系:儿子/女儿/孙子/孙女,电话格式合法。
- 床位编号遵循楼层规则,比如“1-01-02”表示1号楼1层2床。
- 健康数据生成连续三个月的记录,保证折线图有数据。
- 费用账单生成最近三个月的已缴、未缴记录。
演示数据不需要很多,每条业务线有10条左右就够了,关键是“看着真实”。
7. 答辩经验:评审老师最常追问的5个问题与应答思路
7.1 为什么选用 JWT 而不是 Session
这个几乎是必问的。我的回答思路是:
- 前后端分离后,后端不维护会话状态,扩展性更好。
- JWT 携带用户身份和角色信息,服务端验签即可,不需要查数据库。
- 缺点是 token 无法主动失效,所以生产环境会配合 Redis 黑名单或短时 token + refresh token 机制。
要尽量避免回答“因为大家都用 JWT”这种理由,一定要讲清楚你理解两种方案的本质差异。
7.2 同一个老人能否同时住两个床位?如何防止
这个问题考察事务和并发控制。回答时先明确业务规则:在院老人只能拥有一个有效床位。然后说明你用数据库行锁 + 事务保证了同一床位不会并发分配给不同老人,同时用唯一索引(elder.current_bed_id在同一个状态的约束)确保老人同一时刻只有一条在院记录。
如果能现场画出并发时序图,会非常有说服力。
7.3 高并发场景下你的系统会怎样
不要硬吹。合理的回答是:
- 当前系统面向的是单养老院内部使用,并发量并不高,所以单体架构已经足够。
- 如果未来扩展到区域多院区平台,首要优化的是数据库连接池、加上 Redis 缓存热点数据、Nginx 负载均衡、动静分离。
- 对于床位的秒杀式并发,可以用分布式锁保证互斥。
这样的回答既诚实,也体现你有架构演进意识。
7.4 如何证明项目不是抄的或有含金量
最好的证明方式是现场改代码。我建议在答辩前一晚把自己最关键的一个 Service 类再读一遍,确保能流畅说出每一行的意图。挑一个最能体现设计思想的点,比如费用计算策略模式,现场加一种新的费用类型,然后运行测试。如果能做到这一步,老师基本不会再追问“你参与了多少”。
7.5 后续如果上线,最需要优化的是哪些点
我准备了三个方向:
- 数据结构:增加软删除和操作日志表,满足合规审计要求。
- 安全:加入接口限流、敏感字段加密存储、传输层 HTTPS。
- 运维:增加 Prometheus 监控指标,统一日志收集。
这些问题没有标准答案,关键是不慌,把设计思路讲完整。
8. 踩坑记录:SpringBoot版本、MyBatis-Plus、前端跨域
8.1 SpringBoot 3.x 和 JDK 版本错位
我第一次搭脚手架时没注意,电脑默认 JDK17,直接配了 SpringBoot 3.2,结果项目一启动就报错,查了半天发现是javax.servlet变成jakarta.servlet,Spring Security 6 配置类也被重构了。后来果断降到 SpringBoot 2.7.18 + JDK11 才稳定下来。
这里给个明确建议:先确认本机 JDK 再选 SpringBoot 版本。JDK8 就用 2.7 以下,JDK11 可以用 2.7,JDK17 才建议用 3.x。不要为了追新而浪费大量时间在环境排查上。
8.2 MyBatis-Plus 自动填充和逻辑删除的坑
自动填充功能需要自己实现MetaObjectHandler接口,如果忘了配置,create_time字段就一直是 null。还有逻辑删除,设置了@TableLogic之后,如果写了 SQL 里带deleted条件,很容易拼出AND deleted=0 AND deleted=0,虽然不会报错,但看起来很业余。
解决办法是:只查询单表用 MyBatis-Plus,多表关联自己写 SQL 时手动加deleted=0,不要依赖全局逻辑删。
8.3 前后端分离跨域问题
开发时前端跑在 5173 端口,后端跑在 8080 端口,浏览器会拦截跨域请求。我遇到过配置了@CrossOrigin还是报错的情况,因为 Spring Security 的过滤器链优先级更高,跨域配置必须同时放在 SecurityConfig 里。
推荐的解决方式是在后端写一个统一的 CorsConfigurationSource 并注册到 SecurityConfig,然后在 Nginx 里也配置add_header Access-Control-Allow-Origin,这样开发和部署都能畅通。
8.4 Web 页面 PDF 打印与 EasyExcel 导出对比
有些功能要求在网页上打印护理单和报表。我建议直接用浏览器的window.print()打印特定区域,配合 CSS 的@media print隐藏按钮和菜单。这种方式实现成本最低。如果需要导出 PDF,可以引入前端html2pdf.js。EasyExcel 更适用于导出结构化 Excel 表格,比如月度账单、老人台账,两者并不冲突,按需求场景选用。
我在实际项目里导出下载失败过一次,原因是后端返回了 JSON 格式的异常信息,前端却按二进制流处理。所以全局异常处理里要判断当前请求是否希望返回文件,如果是文件类型,直接输出错误信息到响应流,或者返回一个标准 JSON 错误码,由前端提示。
最后说几句实在话
做敬老院管理系统,最大的收获不是学会怎么写 CRUD,而是摸清了从需求到交付的一条完整链路:你要理解养老行业的业务规则,要把一条业务线从数据库字段一路设计到前端页面,要处理并发下的一致性,还要给每个设计决策找到理由。这些能力放到任何 Java 后端岗位面试里,都是可以直接拿来说的项目经验。最后再分享一个小技巧:不管系统做得有多复杂,答辩前一定要自己完整走一遍“新来一个老人 -> 登记入住 -> 分配床位 -> 生成首月账单 -> 录入健康数据 -> 护工完成任务 -> 月末生成账单 -> 退住结算”这条路径,用真实数据跑通。流程顺了,心里就有底;被问住了,至少还能从容地演示一遍。