1. 项目构思与技术选型:为什么用SpringBoot做养老服务平台
先说结论:Java毕设选型,SpringBoot确实是目前最稳、最省心、最容易出效果的方向之一。这不是客套话,是我连续带过好几个毕业设计项目之后的真实感受。养老服务平台这个题目之所以值得拿出来写一篇完整复盘,是因为它足够“落地”——涉及用户端和管理端的双角色权限、健康数据的上报与回传、服务预约的完整状态流转、还有工单和费用这类典型业务闭环。把这一套啃下来,你对SpringBoot后端开发的理解基本就立住了。
我最初拿到这个题目的时候,第一反应是先确认它的业务边界。养老服务平台不是一个全能的智慧养老大系统,而是一个聚焦在“老人—家属—管理员”三方协作场景下的服务管理平台。老人端能看公告、提交健康数据、预约养老服务;家属端能绑定老人、查看照护动态、代下单;管理端(平台运营人员)能维护老人档案、处理预约工单、查看健康趋势。说白了,核心就是“把线下的养老服务流程搬到线上”。
关于技术栈的选择,很多人纠结要不要上SpringCloud或者微服务,我直接说结论:毕设场景千万不要上微服务。微服务治理带来的复杂度(注册中心、配置中心、分布式事务)会把你拖垮,而且答辩的时候反而容易被追问出破绽。单体应用 + SpringBoot + MyBatis-Plus + Redis + MySQL这套组合,足够支撑当前业务规模,清晰度还高。
技术选型列表:
- 后端框架:SpringBoot 2.7.x,稳定且资料多。
- 持久层:MyBatis-Plus,查询构建方便,自带分页插件。
- 数据库:MySQL 8.0,UTF-8mb4编码。
- 缓存组件:Redis,用于验证码、Token刷新、热点公告缓存。
- 权限方案:Sa-Token或Spring Security二选一,建议Sa-Token,上手快。
- 前端模板:Thymeleaf管理端 + 微信小程序端(原生或uni-app)。
这套选型最大的优点是——它能讲出东西来。答辩时老师问“为什么用Redis”“为什么不用JWT而用Sa-Token”“缓存一致性问题怎么解决”,你都有得聊。
2. 核心功能模块拆解:从三方角色视角倒推需求
2.1 用户端:老人与家属的体验优先
老人端的需求核心是“简单、直接、大按钮”。我们平时做后台系统习惯了表格+侧边栏,但老人端的界面完全不是这个概念。它要的是首页大字公告、一键呼叫按钮、健康数据打卡,以及服务预约的卡片式入口。
家属端多一个“绑定关系”的概念。一位老人可以绑定多位家属(子女),每位家属能看到老人的健康档案、预约记录、费用明细和照护评价。这里有个功能设计细节值得注意:绑定老人必须走审核流程。不是谁输入身份证号就能绑上,管理员在后台审核确认亲属关系后,绑定才生效。这个设计表面上增加了一步操作,实际上避免了信息泄露的合规风险,也是答辩时可以主动提的亮点。
服务预约模块的状态流转,我建议做成:待受理 → 已分配 → 服务中 → 已完成 → 已评价。每一步都记录操作人和时间戳,形成可追溯的审计日志。你想想,线下服务如果出了问题,线上平台至少要能还原“谁在什么时间干了什么”。
2.2 管理端:工单流转与看板统计
管理端的功能清单比较常规:用户管理、老人档案管理、服务项目管理、预约工单管理、公告发布、健康数据管理、费用结算。难点不在CRUD,而在关联逻辑。
举个例子,管理员受理预约后,服务工单就要自动关联排序算法(按距离、按服务类型、按当前工单量)推荐给合适的照护人员。照护人员扫码或点击接单后,工单状态才能流转。这套逻辑看似简单,但如果用纯if-else写,状态一多就乱。建议用状态机模式。SpringBoot里不用引额外框架,自己定义一个枚举实现状态流转校验即可,代码干净还好解释。
管理端首页必须放统计看板:今日预约量、待处理工单、服务完成率、老人健康异常数、近七日平台活跃度。这一块是答辩加分项,也是让我后面做前端部署时有东西可以展示的门面。
2.3 共享模块:公告、健康数据与消息推送
健康数据模块,我设计的是老人每日打卡晨间血压、血糖、心率,系统会将记录与基础阈值比对,异常时自动标记并推送给绑定的家属和管理员。推送通道直接对接阿里云短信或者微信模板消息。考虑到毕设场景不一定都申请得了短信签名,消息通知做成站内信 + 可扩展接口是更稳妥的做法。
公告模块支持管理员富文本编辑,发布后缓存到Redis并设置过期时间——这里是标准的缓存穿透/击穿练习素材。
3. 数据库设计要点:不同表之间的关联关系如何规划
数据库设计直接决定了后面代码的舒服程度。我建表的总体原则是:用户表分离 + 角色关联统一 + 业务表独立。
标准的三张核心表不能少:
- 用户表:存放登录账号、密码(BCrypt加密)、手机号、头像、状态
- 角色表与用户角色关联表:角色分为ADMIN、STAFF、FAMILY、ELDER
- 老人档案表:姓名、身份证号、紧急联系人、基础疾病史、护工ID
业务表再做拆分:服务项目表、预约工单表、健康数据表、评价表、公告表、费用流水表、操作日志表。
3.1 一个容易忽略的字段:逻辑删除
但凡涉及用户数据的平台,物理删除必须被禁止。所有业务表都要加deleted字段(0未删除,1已删除),配合MyBatis-Plus的@TableLogic注解,一行配置全局生效。这个细节在毕设答辩里特别好用——老师一问“用户注销了怎么办”,这个问题你早就已经解决掉了。
3.2 索引设计:查询性能从源头优化
老人档案表的身份证号字段必须唯一索引;预约工单表的老人ID + 状态字段建联合索引;健康数据表按老人ID + 记录日期建联合索引。
当时我用测试数据跑过一轮,建索引前查询10万级数据要300多ms,建索引后降到20ms以内,效果立竿见影。作业里要有这个对比数据,比空吹优化强得多。
3.3 核心表结构参考
预约工单表设计的核心字段(供参考):
- id:主键
- order_no:业务单号(年月日+随机数生成)
- elder_id:老人档案ID
- service_type:服务类型(助浴/助餐/陪诊/日常照护)
- appoint_time:预约时间
- staff_id:分配的照护人员
- status:状态(待受理/已分配/服务中/已完成/已评价)
- fee_amount:费用金额
- address:服务地址
- remark:备注
- create_time、update_time、deleted
费用流水表单独建,记录每次工单的费用变更记录,实现财务可对账。
4. 关键代码实现:认证授权与预约状态流转
4.1 登录认证:Sa-Token的集成思路
Sa-Token的集成比起SpringSecurity简单得多。起步依赖加一个,配置拦截器,登录成功返回token交给前端存储,之后的请求头里带上token即可。核心代码如下:
@Configuration public class SaTokenConfigure implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new SaInterceptor(handle -> StpUtil.checkLogin())) .addPathPatterns("/**") .excludePathPatterns("/api/auth/login", "/api/auth/register", "/error"); } }登录接口里配套做Redis缓存验证码校验(可以选Redis淘汰策略)。Sa-Token默认把会话数据存在内存中,但毕设我习惯改成Redis存储,这样重启服务不丢登录态,演示的时候体验更顺滑。
4.2 预约状态流转的状态机实现
状态机模式是让代码结构清晰的关键。核心是用枚举定义当前状态和可流转到哪些状态。
public enum OrderStatus { PENDING(0, "待受理") { @Override public boolean canTransferTo(OrderStatus target) { return target == ASSIGNED; } }, ASSIGNED(1, "已分配") { @Override public boolean canTransferTo(OrderStatus target) { return target == IN_SERVICE || target == CANCELLED; } }, IN_SERVICE(2, "服务中") { @Override public boolean canTransferTo(OrderStatus target) { return target == COMPLETED; } }, COMPLETED(3, "已完成") { @Override public boolean canTransferTo(OrderStatus target) { return target == EVALUATED; } }, EVALUATED(4, "已评价") { @Override public boolean canTransferTo(OrderStatus target) { return false; } }, CANCELLED(5, "已取消") { @Override public boolean canTransferTo(OrderStatus target) { return false; } }; private final int code; private final String desc; public abstract boolean canTransferTo(OrderStatus target); public void transfer(OrderStatus target) { if (!canTransferTo(target)) { throw new BusinessException("非法状态流转: " + this.desc + " -> " + target.desc); } } }在Service层调用时,状态流转必须配合Spring事务,先检查再更新,防止并发下产生脏数据。这里可以用数据库乐观锁版本号字段兜底(update ... set status = ? where id = ? and version = ?),双保险。
4.3 健康数据异常自动标记逻辑
健康数据打卡后调用一个独立的策略类,对不同指标分别判断异常等级。我的做法是定义HealthRiskChecker接口,每个指标一个实现类(血压、血糖、心率),列表循环执行。后期新增指标只需要在配置中心注册新的实现,不需要改核心代码。这就是开闭原则的实际落地。
@Component public class BloodPressureChecker implements HealthRiskChecker { @Override public RiskLevel check(HealthRecord record) { if (record.getSystolic() >= 180 || record.getDiastolic() >= 110) { return RiskLevel.HIGH; } if (record.getSystolic() >= 140 || record.getDiastolic() >= 90) { return RiskLevel.MEDIUM; } return RiskLevel.NORMAL; } }异常记录产生后,同步异步调用消息服务,通知绑定的家属。异步操作用Spring的@Async,记得配置自定义线程池,避免默认的SimpleAsyncTaskExecutor无限创建线程。
5. 部署方案与实操演示:从Windows到Linux服务器的平滑迁移
5.1 本地开发环境搭建
本地开发建议用Docker起MySQL和Redis,命令很简单:
docker run -d --name mysql8 -p 3306:3306 -e MYSQL_ROOT_PASSWORD=root mysql:8.0 docker run -d --name redis -p 6379:6379 redis:7.0好处是数据库版本和线上保持一致,不会出现“本地好好的,线上启动就报错”的魔幻问题。项目里用application.yml统一管理配置,区分dev和prod环境。数据库连接串、Redis地址这些敏感信息,在prod里用环境变量覆盖,不要写死在配置文件里。
5.2 Linux服务器部署步骤
服务器最低配2核4G就够跑这个项目。
代码打包:
mvn clean package -DskipTests打出来的jar包在target目录下。服务器上创建专门的运行用户,不要用root直跑。
启动命令建议用nohup:
nohup java -Xms512m -Xmx1024m -jar elder-care-platform.jar --spring.profiles.active=prod > app.log 2>&1 &优雅停机的配置别忘了:
server: shutdown: graceful spring: lifecycle: timeout-per-shutdown-phase: 20s5.3 Nginx反向代理与HTTPS配置
前端静态资源(管理端页面、小程序API统一走HTTPS)用Nginx做代理,Java只监听内网端口。Nginx配置示例:
server { listen 443 ssl; server_name your-domain.com; ssl_certificate /etc/nginx/ssl/server.pem; ssl_certificate_key /etc/nginx/ssl/server.key; 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; } }如果你的部署环境是学校机房,没有域名,可以使用IP直连,HTTP模式。那需要在配置里把Cookie的secure属性关掉,否则浏览器不会保存登录态。
5.4 演示视频录制心得
演示视频是这个类型项目的标配交付物。我录视频时踩过几个坑:
- 一定要重启后拍摄。不要用一段长时间运行的IDE,重启后第一个画面必须是正常启动日志,然后快速进入操作。这样拍出来的视频才真实。
- 按用户旅程录制:登录 → 老人档案录入 → 家属绑定审核 → 用户预约服务 → 管理端派单 → 服务完成评价 → 健康数据异常提醒。整个流程串起来讲。
- 屏幕录制1080p + 60帧,录完用剪映压字幕,交代清楚当前在操作什么。
提示:部署说明文档的关键不是贴命令本身,而是标注清楚每条命令的作用和可能遇到的错误。纯贴命令行会让小白无从下手,你要按“操作步骤 + 预期结果 + 错误排查”的结构写。
6. 常见问题与排查技巧实录
6.1 接口突然返回401
刚开始部署到服务器时,管理端页面跳转API全401。排查思路往下走:
- 前端确认请求头Authorization已传token
- 后端日志看Sa-Token校验到哪一步失败
- Redis里看有没有token记录(端口或密码配置错会连不上Redis)
八成原因是Redis配置没对齐——服务器上的Redis默认只绑定了127.0.0.1,外网访问不了,或者没设置密码导致拒绝连接。
6.2 数据库中文乱码
建库时必须指定UTF-8mb4,不管是在Navicat里建还是命令行建:
CREATE DATABASE elder_care_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;连接串里也要加参数:
jdbc:mysql://localhost:3306/elder_care_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai这类问题在Windows MySQL 8.0上一旦忘了指定,插入用户姓名“张大爷”就会变成“????”。教训很深。
6.3 MyBatis-Plus分页插件失效
分页无效十次里有九次是拦截器没注册。新版MyBatis-Plus要手动定义PaginationInnerInterceptor:
@Configuration public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }另外Page对象要作为第一个参数传入Mapper方法才是分页,传在中间或者其他位置不会生效。
7. 个人总结与经验分享
最后分享一个我在做这个项目时最有感触的地方:不要只写代码,一定要把自己带入“平台运营者”的角色去思考。
比如预约时间冲突的问题,最初的版本服务时间段全靠人工排,后来我把安排改成了时间段预占用——同一照护人员在同一时间段只能被分给一个工单。这个约束很简单,但恰恰是平台能否规模化运作的根基。答辩的时候你把“为什么要有这个约束”讲清楚,比聊十个酷炫接口都有说服力。
另外,关于交付物这块,我建议论文(LW)不要最后再赶。项目开发到核心模块的时候,一边写代码一边整理截图和日志,论文素材自然就有了。等系统做完,论文的大纲已经等于完成了一半,这样质量高,压力也小。
这个养老服务平台项目,真正做完之后你会打通很多认知:数据表该怎么设计、权限怎么能更安全、部署怎么才能稳定、状态流转再怎么复杂也能理顺。它不是一个“看起来高大上”的选题,但是一个能让你真正上手操作并学到东西的实战题目。如果你想快速跑通一套类似的完整系统,源码加部署文档加演示视频参照着做,会帮你省掉大量调试环境绕弯的时间。