ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于SpringBoot+Vue3的养老平台开发:技术选型与前后端分离实践

基于SpringBoot+Vue3的养老平台开发:技术选型与前后端分离实践 去年年初我接了一个连锁养老机构的系统需求。对方说得直白老人档案还是纸质加Excel护工排班靠微信群喊家属想知道老人当天血压怎么样得打电话问前台。他们要上一个养老智慧服务平台技术栈要主流、代码要能交付、后面还得自己改。我最后定下来的方案就是标题里这套Java SpringBoot 做后端Vue3 做前端MyBatis 操作业务数据MySQL 做存储前后端彻底分离。这套系统从需求确认到上线跑了三个多月目前接入两个社区站点几百位老人的档案、健康记录、服务工单都在上面正常流转。这篇文章我就把这套系统的业务拆解、技术选型背后的逻辑、表结构设计、前端开发实践以及联调部署阶段踩过的坑一次性写完。1. 养老智慧服务平台的项目全貌从业务需求到系统边界1.1 甲方真正的诉求和管理痛点如果只看标题你可能会以为这只是一个普通的CRUD后台。但养老平台比较特殊它既要有传统管理系统的档案、审批、统计又要处理健康这种半实时数据。我从需求调研里总结出三类核心诉求。第一档案数字化。老人基本信息、入住时间、护理等级、家属联系方式、病史与过敏史这些必须能在系统里快速查到同时要有权限控制——不是所有护工都能看老人的完整病历。这和普通员工管理系统有很大区别健康档案的隐私等级明显更高。第二服务流程线上化。从家属发起助餐、助浴、陪诊申请到管理员生成工单、护工接单、服务完成回执再到运营者月底统计服务量。这条链路如果断了系统就是个摆设。所以从第一天起我就把订单—工单—回执这条链路当成平台的主干功能来设计所有页面都围绕它展开。第三健康数据的可感知。机构内日常会测血压、心率、体温如果这些数据能自动入库家属端就能远程看到趋势这也是智慧养老最加分的地方。甲方甚至提出过能不能对接智能手环当时条件不成熟但我把健康数据的采集接口设计成独立模块后面接设备时不用改表结构。1.2 四类角色如何构成业务闭环我按使用角色把系统划分成四块避免一开始就掉进功能堆砌的坑。这个项目的角色关系比常规后台复杂因为有老人、家属、护工、运营者四方参与每一方的诉求都不一样。角色核心操作前端形态平台管理员老人入住登记、工单调度、权限分配、服务项目管理管理后台PC端护工/服务人员接收工单、上报服务状态、录入健康测量值H5移动页面家属/老人本人查看健康档案、发起服务预约、查看账单微信H5/小程序运营者/机构管理层查看入住率、服务量、人员负荷、健康异常统计管理后台只读报表这张角色表直接决定了权限模型和前端页面数量。平台管理员是管理后台的重度用户护工端必须极简因为护工群体普遍对电脑操作不熟悉。家属端则是展示为主操作路径要短。搞明白这四类人各自的习惯比一开始就想着把页面做漂亮重要得多。1.3 前后端分离不是赶时髦而是这个项目确实需要很多人问我为什么不用传统的服务端渲染模板这个项目如果放在十年前用Thymeleaf或JSP也一样能做。但这里有几个现实原因让前后端分离成了必然选择。第一前端不止一个。管理后台是Vue3的SPA家属端和护工端以后大概率要套壳成小程序或原生App。如果后端页面模板和业务逻辑揉在一起每加一个客户端就要动一遍服务端代码代价太高。第二部署环境多变。这类项目交付到客户机房有的拿一台Windows Server有的用阿里云Linux甚至遇到过客户只有一台普通PC的情况。前后端分离之后后端只要打一个jar包前端构建成静态文件丢到Nginx里就行迁移成本极低。第三前后端分工明确。后端只暴露JSON接口前端专注交互。这个项目迭代快甲方经常今天提一个字段、明天加一个按钮前后端分离时改动局部页面的成本明显更低。这也是我后来做所有中小型管理系统的默认姿势。2. 技术选型背后的取舍SpringBoot、Vue3、MyBatis、MySQL如何组合才不浪费2.1 SpringBoot版本怎么定才是真稳妥标题写的是SpringBoot但没写明版本。这里其实藏着一个最常见的坑Spring Boot 3.x 需要 JDK 17而且包名从 javax 迁到了 jakarta很多老依赖的 starter 还没跟上。养老平台面对的往往是客户已有的服务器上面可能装的是 JDK 8、Windows Server 2012你不可能让客户给你升级全套环境。我当时选的是 Spring Boot 2.7.x JDK 8/11。这个决策考虑是Spring Boot 2.7 还在社区维护期HikariCP、Druid、MyBatis Starter、PageHelper 这些依赖的兼容性都已经被大量生产项目验证过。如果你确实要上3.x也要先确认团队里没有老代码依赖 javax 命名空间否则上线前光是改 import 就够你喝一壶的。维度Spring Boot 2.7.xSpring Boot 3.xJDK要求8/1117包名规范javaxjakarta生态兼容老依赖基本全适配部分中间件starter需要升级适合场景服务器环境不可控的交付项目全新云原生、可控JDK环境另外Maven工程里务必锁版本用spring-boot-parent统一管理版本即可。团队里如果有多个成员依赖版本不一致导致的我本地是好的服务器却跑不起来绝对是你联调阶段的头号敌人。2.2 Vue3 Vite Element Plus这套前端组合省钱又省心Vue3这边我采用的组合是 Vue 3 Vite Pinia Vue Router Element Plus。相比Vue2时代Vue3最大的变化是组合式API和响应式代理。新项目直接上script setup语法糖组件逻辑复用抽到 composables 里比选项式写得清爽太多。有一点务必提醒Vite 对 Node.js 版本有要求官方建议至少 Node 16最好用 18/20 LTS。如果你在Windows上用老版本Node跑npm run dev大概率会报一个ERR_OSSL_EVP_UNSUPPORTED这时候不用折腾直接换Node版本就好。Element Plus是Vue3配套的组件库管理后台的表单、表格、弹窗、分页都能直接复用开发效率高。如果你不想被Element Plus绑死可以用Naive UI或者Ant Design Vue但对养老系统的后台管理场景Element Plus的文档和社区活跃度是三者里最省心的。2.3 MyBatis vs MyBatis-Plus手写SQL的边界在哪里标题里写的是MyBatis实际项目中我也保留了不少手写SQL。理由很直接养老平台的查询大多是组合条件、多表关联、报表统计。比如查询本月每个站点各服务类型的完成工单数并按护工排序这种SQL你用MyBatis-Plus的 LambdaQueryWrapper 写能累死而且可读性极差。我的建议是混用纯单表CRUD可以交给MyBatis-Plus如果你决定引入涉及复杂关联和统计的查询全部写在XML的select里并且把SQL命名为selectServiceStatsForReport这种看一眼就知道干什么的名字。MyBatis的动态SQL是它真正的核心资产if、where、foreach配合起来做多条件筛选十分顺手。必须强调一个安全习惯写动态SQL时拼接条件一律用#{}占位不要用${}。${}是直接字符串替换虽然能实现动态表名、动态排序列名的场景但一旦用户输入被拼进去就是SQL注入漏洞。我之前排查过的很多安全问题八成都出在有人图省事用了${}。2.4 MySQL版本和表设计的三个前置决定MySQL这一层标题没有写版本但我在建库前就要定死三件事。一是存储引擎。全部使用 InnoDB支持事务、行级锁、外键约束对接业务系统这是底线。MyISAM那套只适合读多写少的内部日志业务表千万别用。二是字符集。统一用utf8mb4而不是utf8。utf8在MySQL里最多存3字节存不了emoji和某些生僻字老人姓名里的生僻字可不是小概率事件。排序规则建议utf8mb4_general_ci或utf8mb4_unicode_ci前者性能略好后者排序更规范看团队习惯。三是连接方式。如果客户环境是MySQL 5.7JDBC连接串里要带上useSSLfalseallowPublicKeyRetrievaltrueserverTimezoneAsia/Shanghai否则第一次连接就可能被caching_sha2_password认证插件搞到抓狂。8.0默认用caching_sha2_passwordJDBC驱动版本也得匹配8.0.33 以上的MySQL Connector/J比较稳。3. 数据库与服务端核心设计老人档案、健康数据、订单流转的表关系3.1 老人、家属、护工先把人的关系建模想清楚养老系统最核心的实体是老人档案但它不是一张孤立的表。我的设计思路是elder_info作为老人主表字段包括姓名、身份证号、性别、出生日期、入住日期、护理等级、紧急联系人、病史说明、状态在住/出院/已故。身份证号要做加密存储或脱敏展示接口返回时默认隐藏中间8位。elder_family是老人与家属的关联表。一个老人可以关联多个家属一个家属也可能关联多个老人比如儿子同时是父母两位老人的联系人所以中间表是必要的而不是给老人表加几个家属字段了事。staff_info是护工和服务人员表包含工种、技能标签、排班状态。排班逻辑第一版不需要做复杂的班次轮转用一张简单的staff_schedule记录每日在岗人员即可。这样既满足业务又不被排班算法拖死。实际上人表的设计一定要考虑查询场景。例家属端登录后要立刻看到他关联的所有老人的健康概览SQL需要从elder_family反查elder_info再关联health_metric_daily。所以我在elder_family的(family_mobile, elder_id)上建了联合索引这个查询在几百人规模下毫无压力。3.2 健康监测数据表明细流水加定时聚合的折中方案健康数据是养老平台相对特殊的一块。护工每天早中晚会给老人测量血压、心率、血氧、体温如果全部用明细流水表存数据量增长很快查询趋势图又必须做聚合。我采用的折中方式是一张health_record_detail存原始测量流水每条记录带老人ID、测量类型、测量值、测量时间、录入人另建一张health_metric_daily存每天的聚合结果当日最高/最低/平均血压等定时任务每天凌晨跑一次聚合。这个设计有几个实际好处。明细表保证数据的可追溯性和审计要求聚合表让趋势图查询秒开家属端看血压曲线时不用去扫百万级流水。如果以后接入IoT设备设备上报的数据可以直接落明细表聚合逻辑不变。索引方面明细表在(elder_id, measure_time)建联合索引聚合表在(elder_id, metric_date)建唯一索引实际接口响应速度很理想。3.3 服务订单和工单的状态机设计这是平台的主干服务预约和派单是整个平台的主干业务我建议把订单和工单分开建模。service_order是家属/老人发起的服务订单包含服务类型助餐、助浴、陪诊、保洁等、预约时间、备注、状态状态枚举为待审核、已确认、已完成、已取消。service_work_order是管理员把订单拆解或直接派给护工的工单包含接单人、计划执行时间、实际完成时间、结果说明、状态状态枚举为待接单、执行中、已完成、已驳回。service_type是服务项目分类表方便运营者调整服务项目和定价不用改代码。最值得留意的是状态流转的校验。我在Service层写了一个专门的状态机校验类比如已取消的订单不能再次进入待审核完成服务必须有回执描述这类规则都在Service层校验。别只看着前端按钮能不能点就完事——前端只是交互层后端必须兜底。实际开发中很多项目死在后端不校验、前端随便调最后状态全乱。3.4 RBAC权限模型落地不要让每个接口都自己写鉴权权限模型选用了经典RBACsys_user、sys_role、sys_menu、sys_user_role、sys_role_menu。SpringBoot里用拦截器加自定义注解来控制接口权限而不是每个Controller方法里复制粘贴判断代码。在实现上我维护了两个缓存点。一个把登录用户的角色和权限码列表放在Redis里key是user:perms:{userId}过期时间跟着登录态走另一个是菜单表按角色生成的路由信息前端登录成功后通过/user/info接口一次性拿到动态路由再注册到Vue Router里。这个权限模型对养老平台很够用甚至有点过剩。但多花一个礼拜做权限设计能帮你省下后续半年的售后。管理员三天两头提这个菜单只有站长角色能看这个按钮只有超级管理员能点如果没有RBAC你的代码会被各种if (user.getRole() 2)塞满改权限时改到想骂人。4. Vue3管理后台开发实践路由权限、接口封装、组件化4.1 目录结构和管理后台的项目骨架先提醒一个Vue3新手必踩的坑reactive包裹的数组如果你直接整体替换响应式会丢失。必须用 ref 或者把替换动作改成 splice、push。我在做老人列表的时候每次从接口拿新数据回来如果用reactive({ list: [] })然后state.list res.list页面是不会刷新的。统一用const tableData ref([])然后tableData.value res.list才稳。管理后台开发这种整体赋值太常见了这个坑提前踩掉能省不少时间。管理后台的目录结构重点看几个关键目录。src/api下按模块拆分接口定义文件比如elder.js、order.js、health.js每个文件导出一个对象对象里是接口方法。src/views按业务模块分页面文件夹管理后台的页面可以粗分成列表页、表单页、详情页三种模板。src/stores放Pinia状态登录态、用户信息、权限列表放在这里页面组件里通过useUserStore()访问。src/router放路由定义静态路由部分写登录页、404等动态路由部分在登录后按权限注册。4.2 动态路由与按钮级权限的实践动态路由的核心是后端返回菜单和权限码前端根据权限码决定菜单项是否渲染、按钮是否可点击。我的做法是登录成功后后端返回用户拥有的menuList和perms数组。前端在全局守卫router.beforeEach里判断如果还没拉过用户信息就先拉再根据menuList用addRoute动态挂载业务路由页面里的按钮比如删除老人、“派单”、“导出Excel”用自定义指令v-perm包一层没有权限码就直接移除DOM元素。这里有一个反复踩的坑动态路由如果注册时机不对刷新页面路由表会先被清空出现白屏一秒的现象。解决思路是在Pinia里存一个isRoutesLoaded标记刷新时判断如果已加载就不重复注册。整体顺序建议先在main.js里创建router实例但不挂载业务路由等登录接口返回后再addRoute这套流程多调试几遍形成自己项目的稳定模式就好。4.3 axios实例与拦截器统一处理Token和错误提示管理后台的HTTP层我封了一个request.js。创建axios实例baseURL指向后端的/api前缀请求拦截器里从Pinia拿到token加到Authorization头注意用Bearer拼接响应拦截器里统一解包后端返回体后端固定返回{ code, message, data }结构。code 200直接返回datacode 401时清空登录态、跳转登录页其他错误码统一弹ElMessage提示。这个封装的收益是业务代码里不用每个请求都写.catch处理错误接口层只写const list await getElderPage(params)错误提示和登录态失效交给拦截器统一处理。看起来是小事但几十个页面写下来少掉的重复代码非常可观。还有一个细节文件下载接口不能用普通JSON请求处理要单独用blob方式来接收响应不然导出的Excel会变成一串乱码。我在导出服务工单报表时专门踩过后来前端在拦截器里判断responseType blob就直接返回原始响应这才算把坑填平。4.4 页面快速开发的套路表格、表单、弹窗三件套后台管理页面的开发节奏90%都是搜索区表格区弹窗表单。我总结了一套自己的最快套路搜索区由几个el-input、el-select加一个查询按钮组成查询参数统一放在一个queryParams响应式对象里表格区用el-table绑定列表数据列用el-table-column操作列里放编辑详情删除按钮弹窗表单用el-dialog里嵌el-form编辑和新增共用同一个弹窗打开时根据是否有id来决定是 create 还是 update。这套模式配合Element Plus的el-form校验规则基本可以半天出一个页面。但页面多了以后要注意抽象公共组件比如老人下拉选择器、服务类型选择器、分页组件都抽到src/components下避免五个页面里复制六份差不多的代码。等到后面接家属端小程序时这些组件虽然用不上但后端接口和权限模型可以直接复用前端写起来会快很多。5. 联调与部署阶段的实战排坑跨域、时区、缓存、事务5.1 跨域问题最烦但最好解决的坑前后端分离项目本地联调时前端跑http://localhost:5173后端跑http://localhost:8081第一件事就是跨域。我见过团队里有人图省事直接在每一个Controller上加CrossOrigin这是非常糟糕的做法第一代码重复第二一旦出现跨域以外的复杂情况比如网关转发这种注解方式根本无法复用。正确的做法是在后端写一个全局CorsFilter配置allowedOrigins、allowedMethods、allowedHeaders明确放开哪些域名。注意allowedOrigins不要写*因为涉及携带Cookie的认证请求时allowCredentials(true)不能和*共存。规范的做法是把允许的来源写在配置文件里部署时按环境修改。如果用了Nginx部署也可以直接在Nginx层做反向代理把/api转发到后端前端请求同源压根不需要CORS。生产环境推荐用Nginx方案开发环境用CorsFilter更方便。我在交付文档里专门把这个点写清楚了后来接手的人按这个配基本零报错。5.2 时区问题数据库时间比北京慢了8小时凡是做前后端分离时间问题一定逃不掉。我项目里出现过几次时间对不上的怪现象前端显示的创建时间是凌晨而不是下午投诉接踵而至。根因是serverTimezone没配Asia/ShanghaiMySQL驱动按服务器默认时区解析时间导致相差8小时。排查路径分享一下先看MySQL的time_zone和system_time_zone确认数据库所在时区再看JDBC连接串的serverTimezone参数是否明确指定最后看后端返回前端的JSON序列化格式比如LocalDateTime默认序列化可能输出yyyy-MM-ddTHH:mm:ss前端如果不按这个格式解析会显示错。我的统一约定是后端用一个统一JSON序列化配置把所有LocalDateTime格式化为yyyy-MM-dd HH:mm:ss前端直接当字符串展示别在前端再用new Date(字符串)转一遍。转多了时区偏移问题必然出现尤其是部署在不同云平台时服务器时区不统一的概率非常高。5.3 MyBatis缓存一级缓存带来的偶发性脏数据热词里有mybatis缓存和mybatis面试题但实际项目里缓存坑也很常见。MyBatis默认开着一级缓存SqlSession级别同一个会话里执行同一条SQL会复用之前的结果。当你在一个业务方法里先查了老人列表随后修改了某条数据再查同一条列表如果没有主动清缓存查到的可能是旧数据。碰到这种问题的标准做法是理解一级缓存的生命周期它在SqlSession关闭后就失效。SpringBoot集成MyBatis时每个方法默认一个SqlSession通常方法结束就关闭所以多数情况下不会踩到。但一旦你用了Transactional把多个查询包在同一个事务里SqlSession会被复用一级缓存才会真的发挥作用。我遇到的实际案例就是在事务里调了一个自动生成编号的方法编号生成了两次第二次查出来和第一次一样排查半天才定位到是一级缓存。二级缓存默认不开启开了以后要特别注意缓存刷新策略涉及增删改的表配置flushCachetrue。项目简单的话建议干脆不启动二级缓存Redis做分布式缓存完全够用别给自己找额外的维护负担。5.4 事务不生效的几个典型场景绕开这些才能保证数据一致养老平台的订单和工单都涉及金额和状态事务用不好会出大问题。我复盘过的典型失效场景有四个。第一Transactional加在private方法上。Spring基于代理实现事务private方法不走代理注定不生效必须放在public方法上。第二同类内部方法调用。this.saveOrder()调到同类里的Transactional方法代理不参与事务不生效。解决办法是注入自身代理或者把事务方法放到另一个Bean里。第三异常被捕获。方法里try-catch把RuntimeException吞了事务回滚无从谈起要么让异常抛出去要么手动setRollbackOnly()。第四没指定回滚异常。默认只对RuntimeException和Error回滚如果业务方法抛了一个自定义CheckedException直接不会回滚规范做法是Transactional(rollbackFor Exception.class)。我在这个项目里把涉及订单创建、工单派发、健康记录入库的方法都加了Transactional(rollbackFor Exception.class)并且要求Service层内部不能越权调用自己的方法就是为了一次性避开上面四个坑。注意Transactional即便加了rollbackFor Exception.class也只在当前线程内生效。如果你在事务方法里异步调用了另一个线程去更新数据那部分是管不到的。5.5 部署到服务器宝塔Docker部署SpringBoot的注意点部署路径大致是本地mvn clean package打出jar包写一个Dockerfile基础镜像用eclipse-temurin:8-jre对应JDK8把jar拷进去ENV TZAsia/Shanghai设置时区VOLUME /logs挂载日志目录用宝塔面板的Docker管理器拉取镜像、映射端口。注意宿主机和容器端口不要冲突。另外服务器的内存和连接数经常被忽视。SpringBoot默认内嵌Tomcat最大线程数200养老平台平时并发不高不需要调但MySQL的连接池最大连接数建议在配置里显式写清楚防止高峰期连接耗尽。我用的是HikariCPmaximum-pool-size设的20minimum-idle设的5实测够用。字符集和时区在Docker容器里跑起来后再验证一遍不然数据调来调去出差错的问题还会复发。6. 源码交付后的二次开发指引从跑通到真正落地6.1 接手这套源码的正确顺序不管你是买源码的还是团队里接手的第一周建议按这个顺序走先看数据库脚本把表结构理清楚重点看老人档案、服务订单、工单、健康数据这四组核心表的关系再把后端启动起来用Postman调通登录接口拿到token后挨个把核心模块的列表接口跑一遍接着启动Vue3前端登录管理后台按老人档案→服务项目→服务预约→工单派发→健康记录→报表统计的顺序点一遍页面这个顺序就是整个平台的业务主线。最后确定好第一轮要改的需求从列表页开始改再到表单再到权限。列表页最简单的字段增减能让团队快速建立信心不要一上来就动订单状态机。我见过太多人拿到源码第一天就想改核心状态流转逻辑结果把自己绕进去连原有逻辑都没吃透。6.2 那些看起来高大上的扩展到底该不该上最近总有同学问SpringBoot整合Flink值不值得学、能不能往这个平台里加或者用ActiveMQ做异步消息行不行。我的看法是养老平台如果只有几千个老人、每天几千条工单Flink和Kafka这类重组件完全没必要。先把定时任务Spring Scheduled和MySQL处理好数据量真上来再迁系统设计的时候把采集层做成可替换的接口就行。真正值得优先扩展的是这三样家属端小程序或App管理后台只是内网工具家属端的远程查看和预约才是感知最强的入口前端单独开发即可消息通知工单派发、健康异常告警用MQ或WebSocket推送提醒比让护工隔一小时刷一次页面靠谱报表可视化管理后台的统计报表接入ECharts把服务量、健康趋势、站点负荷做成图表运营者很吃这一套。至于OFD查看、分词搜索这类需求如果有电子健康档案和搜索功能可以按需集成但不要为了技术丰富而盲目加模块。任何多余的技术选型都会变成后续的维护负担这是我在多个交付项目里反复验证过的教训。6.3 给接手开发的人最后几点忠告第一不要绕过权限体系。见过太多人为了快速加功能直接写一个Controller不校验权限等于给系统开了后门。第二接口文档要维护好。前后端分离后接口是第二份合同用Apifox或OpenAPI自动生成文档每次改接口同步更新否则三个月后没人敢动这个接口。第三备份策略一定要交代给客户。MySQL的定时备份用crontab加mysqldump即可数据是养老机构最核心的资产没有之一。这套源码我跑了一年多看着它在两个社区站点完成几百位老人的日常流转最大的体会有两点一是技术栈本身没有高低之分SpringBoot、Vue3、MyBatis、MySQL这套组合在中小型交付项目里极其顺手难的是把业务逻辑做扎实、把状态流转做严谨二是前后端分离之后真正决定项目未来的是接口的稳定性和权限模型的清晰度而不是某个新框架。如果你正准备拿这套源码做二次开发先把主线功能跑通、把权限配明白、把备份落地再谈花哨的扩展方向就不会走偏。
返回列表