基于Spring Boot和Vue3的社区养老服务平台设计与实现
前阵子去社区办事看到工作人员一边接电话一边在微信群里回复“张阿姨的助餐明天安排”“李爷爷的血压数据要更新”桌上还压着厚厚一叠纸质工单。社区养老的需求真实存在但供需之间缺一条顺畅的数字通道。这就是我动手做“基于Spring Boot和Vue3的社区养老服务平台”的起因。整个项目定位很明确不是做一套花哨的演示系统而是把老人、家属、服务人员、社区管理者这几类角色的日常真实场景串起来。后端用Spring Boot前端用Vue3包含服务下单、健康档案、志愿活动、工单调度、消息通知等模块。如果你正在做一个前后端分离的实战项目或者准备在毕业设计里完整呈现一个系统的设计与实现这篇文章里我会把需求拆解、技术选型、前后端关键实现、以及联调部署时踩过的坑都摊开讲希望能帮你少走几步弯路。1. 需求梳理养老服务平台不是把线下业务硬搬到线上很多人拿到这类题目第一反应是“做个老人信息管理CRUD”。如果真这么做系统做出来大概率没人用。社区养老服务的核心不是“管理”而是“服务流转”——一个需求从产生、派单、执行到反馈整个过程必须闭环否则跟Excel表格没区别。1.1 从三类角色的真实场景倒推功能我一开始就列了用户角色而不是先画ER图。三端角色是这样的老人/家属端查看服务项目助餐、助洁、助医、陪诊、康复理疗、在线下单、查看订单进度、绑定健康档案、报名社区活动、紧急求助。服务人员端查看派给自己的工单、接单、上门打卡、填写服务记录、提交完成反馈。社区管理员端审核并管理老人档案、维护服务项目、调度工单、处理投诉反馈、发布活动与公告、查看运营统计。这个划分直接决定了后面表结构和接口的粒度。比如服务项目表必须包含价格、时长、所需人员资质描述因为下单页要展示这些信息工单分配时管理员也要据此判断派给谁。1.2 核心业务链路从下单到服务完成的状态机业务上最重要的是一条工单状态链路我把它设计成待接单 - 已接单 - 服务中 - 已完成 ↘ 已取消 ↘ 已退款退款申请/审核为什么一定要有状态机因为订单状态一旦散落在各个接口里随意修改后面统计和追溯会非常痛苦。比如管理员要查“今天所有未完成的助医工单”如果状态字段是字符串乱写的SQL都写不干净。我在代码里用枚举统一状态流转并且把状态变更记录单独存一张表每笔单子从创建到完成都有迹可循也方便以后做服务评价回访。需求阶段还需要明确一个边界不做App、不做小程序优先做响应式Web。原因是社区场景下老人端常常要由子女或社工协助操作浏览器访问最轻量管理端则在PC上使用桌面布局为主。移动端后续可以再套壳或单独做小程序。2. 技术选型与整体架构为什么选Spring Boot加Vue3技术选型不是“谁流行选谁”而是团队熟悉度、社区生态、出问题好不好搜、学习曲线平不平的权衡。这套组合到今天依然很稳但细节上有不少值得说明的地方。2.1 后端选型与版本之间的取舍后端我采用Spring Boot 2.7.x搭配MyBatis-Plus、MySQL 8、RedisJDK用的8/11都可以跑。版本这里要重点说一下Spring Boot 3.x已经发布很久但如果你要用一些老牌第三方组件比如某些代码生成器、activiti工作流它们的适配可能还停留在2.x2.7.x是2.x系列的最终维护版本稳定性足够生态兼容性也最好适合项目需要快速落地的情况。MyBatis-Plus的价值在于单表CRUD几乎不用写SQL分页插件用起来也顺手。不过你要接受一个现实一旦涉及多表关联查询、复杂统计还是要自己写XML不能指望框架通吃。我在服务订单列表页就实现了多表关联查询查询条件是订单状态、服务类型、老人姓名模糊匹配、下单时间范围这种SQL放MP的Wrapper里写比较别扭直接写在Mapper XML里反而清晰。Redis在这个项目里主要做了两件事缓存验证码以及存放登录token实现单点退出时的失效控制。为什么token不纯靠JWT无状态解密因为有些场景需要主动让token失效比如管理员禁用某个服务人员账号。用Redis存一份有剩余有效期的token退出登录或账号被禁用时删掉即可。2.2 前端选型Vue3不是“换皮Vue2”前端选型是Vue3 Vite Element Plus Pinia Axios ECharts。Vue3最大的变化是组合式API废弃了Options API那套data/methods/computed分开写的方式改成按业务逻辑聚合代码。举个很直观的例子登录页要处理账号密码校验、验证码倒计时、登录状态跳转用Options API就得在data、methods、watch里来回翻用setup函数可以把这段业务逻辑完整写在一块。状态管理选了Pinia而不是Vuex原因很简单Pinia的API更简单没有mutations这个概念直接改state就行TypeScript支持也更好。对于这个项目我当时用Pinia管理的内容包括token、用户信息、当前角色权限列表、未读消息数量。如果你之前只熟Vuex也不慌Pinia上手通常半小时。Vite替代Webpack的最大体验提升是开发服务器启动速度和热更新速度尤其项目到后期组件多了以后Webpack每次改动都要编译小几秒甚至十几秒Vite基本毫秒级刷新。但要提醒Vite生产环境构建时对依赖预构建有要求偶尔会因为某个库版本不兼容报错解决方式通常是optimizeDeps配置里强制include或exclude。2.3 前后端分离下的工程布局与接口约定整个项目分三个目录server后端Spring Boot、web前端管理端老人端H5、doc数据库脚本、接口文档。前后端分离的项目最重要的不是代码写多好而是提前约定好接口规范。我统一约定的返回格式是{ code: 200, message: success, data: {} }分页接口的data里统一是records当前页数据、total总条数、current当前页码、size每页条数四个字段。前端axios封装里直接解构这个结构所有页面的获取列表逻辑都能复用。接口路径统一以/api开头比如/api/service/order/page、/api/health/record/list这样在nginx层做反向代理转发、在网关层做路径匹配都方便。3. 后端落地数据库设计、接口规范与关键业务实现后端部分是整个系统的地基。这一节我按“表结构怎么定、统一返回怎么做、核心业务怎么落地、异步消息怎么处理”的顺序来讲基本覆盖了一个中小型管理系统的通用套路。3.1 数据模型设计思路数据库我用UTF-8mb4字符集因为要存老人的姓名、地址等内容用utf8mb4才能完整支持生僻字和特殊符号。核心表包括表名用途关键字段sys_user平台用户表用户名/手机号/密码/角色ID/状态elder_info老人档案表姓名/身份证号/住址/紧急联系人/基础病史service_item服务项目表名称/类别/单价/时长/描述/状态service_order服务工单表订单号/老人ID/服务项ID/服务人员ID/状态/金额/地址order_status_log工单状态变更日志订单ID/旧状态/新状态/操作人/备注health_record健康档案表老人ID/血压/血糖/心率/身高体重/测量时间activity_info社区活动表活动名称/时间/地点/名额/已报名数activity_signup活动报名表活动ID/老人ID/报名时间message_notice消息通知表接收人ID/标题/内容/类型/是否已读这里最核心的坑在于老人档案和用户是分开的。sys_user保存登录账号信息elder_info保存老人档案详情。一个用户可以是老人本人也可以是老人的家属家属要能同时看到多位老人的健康档案和服务记录。所以我在sys_user里加了relation_type字段区分本人/子女/其他家属再通过family_bind这类关联表维护家属与老人的绑定关系。3.2 统一返回结构与全局异常处理这块属于“前期不做好后期改到哭”的环节。Spring Boot默认的异常信息返回给前端非常不规整比如参数校验失败抛MethodArgumentNotValidException前端拿到的是Spring默认错误结构而正常业务又返回自己定义的Result对象前端就要写两套解析逻辑。我的做法是定义统一的结果类Data public class ResultT implements Serializable { private Integer code; private String message; private T data; public static T ResultT ok(T data) { ResultT r new Result(); r.setCode(200); r.setMessage(success); r.setData(data); return r; } public static T ResultT fail(Integer code, String message) { ResultT r new Result(); r.setCode(code); r.setMessage(message); return r; } }再结合RestControllerAdvice做全局异常处理。业务异常码我规划成几位数的有意义码段比如40001代表参数非法、40003代表订单状态不允许该操作、40101代表未登录或登录过期、40301代表无权限、50001代表系统异常。这样前端拿到code后就能做对应逻辑比如40101弹出重新登录框40003则提示“当前订单状态不能取消”。3.3 服务订单状态机的代码落地状态机我没有引入Spring StateMachine那套重框架因为项目里的工单流程相对简单用枚举加前置校验就足够。订单状态枚举Getter public enum OrderStatus { PENDING(0, 待接单), ACCEPTED(1, 已接单), SERVING(2, 服务中), COMPLETED(3, 已完成), CANCELLED(4, 已取消), REFUNDING(5, 退款中); private final Integer code; private final String desc; OrderStatus(Integer code, String desc) { this.code code; this.desc desc; } }每次状态变更都经过一个统一入口Transactional(rollbackFor Exception.class) public void changeOrderStatus(Long orderId, OrderStatus from, OrderStatus to, Long operatorId, String remark) { // 校验订单已存在 // 校验旧状态是否等于from // 执行update状态 // 插入order_status_log // 如果是已完成额外触发服务评价提醒或消息通知 }这样做的价值是什么订单状态被严格约束接口A不能越过接口B直接修改状态。管理员取消订单、服务人员接单、老人取消订单走的是同一个状态变更逻辑日志完整以后出纠纷能查得清清楚楚。而且有了order_status_log前端订单详情页可以直接展示时间线用户能直观看到工单走到哪一步。3.4 健康档案与消息通知的异步处理健康档案的数据来源比较复杂有手动录入、接入血压计等IoT设备上报、也有家属代填。设计上我提供一个通用的健康记录接收接口字段包含老人ID、类型血压/血糖/心率等、数值、单位、测量时间、来源。服务人员上门服务时会把现场测量的数据录入系统形成一条健康记录前端按时间倒序展示异常值比如收缩压高于180会标红提醒。消息通知这里我踩过一个坑原本是直接在业务代码里同步调用发送接口比如下单成功后立即给服务人员发通知。结果每次下单接口响应都慢了几百毫秒赶上有活动的时候通知多用户反馈“下单按钮转圈很久”。后来把消息发送改成异步Spring Boot里用Async注解加一个自定义线程池下单成功后主流程只负责保存订单和状态日志发送通知由异步线程处理。这里要注意Async是Spring代理机制实现的同类内部调用不生效最好单独放到一个NoticeService里并注入调用。4. 前端实现Vue3组合式API下怎么组织代码前端部分内容很多我只挑最核心的几块工程初始化、登录状态持久化、业务页面组合式封装、动态菜单路由。每块对应一个你在实际项目中几乎肯定会遇到的场景。4.1 初始化Vite工程与基础配置创建项目用官方脚手架npm create vitelatest web -- --template vue然后安装依赖vue-router、pinia、axios、element-plus、element-plus/icons-vue、echarts。vite.config.js里我主要配置了路径别名和开发代理import { defineConfig } from vite import vue from vitejs/plugin-vue import path from path export default defineConfig({ plugins: [vue()], resolve: { alias: { : path.resolve(__dirname, src) } }, server: { port: 5173, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } })前端代码里所有请求都走/api开发环境由Vite代理转发到本机8080后端。生产环境则由nginx统一转发。这样前端代码里不需要区分开发/生产环境地址避免了一大堆写死的IP。这里顺带提一个常见的“Vite创建后一启动就报错”问题如果你看到Uncaught SyntaxError: Invalid or unexpected token大概率是vue插件版本和Vite版本不匹配或者某个依赖引入了非标准JS语法。解决方式是升级vitejs/plugin-vue到当前Vite大版本对应的最新版本然后删掉node_modules和package-lock.json重装。4.2 登录页与Pinia持久化的实践登录表单提交后后端返回token和用户基本信息。我没把用户信息一股脑塞进localStorage而是只存token用户详情昵称、角色、权限列表、关联老人ID通过/api/user/info接口获取。Pinia里设置一个userStoreexport const useUserStore defineStore(user, { state: () ({ token: localStorage.getItem(token) || , userInfo: null, role: , permissions: [] }), actions: { async login(loginForm) { const res await loginApi(loginForm) this.token res.data.token localStorage.setItem(token, this.token) }, async fetchUserInfo() { const res await getUserInfoApi() this.userInfo res.data this.role res.data.role this.permissions res.data.permissions }, logout() { this.token this.userInfo null this.role this.permissions [] localStorage.removeItem(token) } } })axios封装里做了请求拦截器每次请求都从store取出token并放到请求头Authorization响应拦截器则统一处理错误码如果遇到40101就清除本地状态并跳回登录页。这个设计能解决一个很常见的问题用户刷新页面后token还在但用户信息丢了。我通过路由守卫里判断“有token但userInfo为空”时重新调用fetchUserInfo()来恢复状态。虽然多了一次网络请求但每次刷新页面都会重新验证身份安全性和体验都能接受。4.3 服务下单页与订单列表的前端组织服务下单页用的是卡片式布局左侧展示所有服务项目点击某个项目弹出详情抽屉里面包含价格、时长、服务内容说明、服务人员资质要求底部是“立即下单”按钮。下单表单里需要选择预约时间、填写服务地址、提交备注。这个页面我用组合式函数composition function拆了一个useServiceOrderexport function useServiceOrder() { const orderForm reactive({ serviceItemId: null, elderId: null, serviceTime: , address: , remark: }) const submitting ref(false) const submitOrder async () { if (!orderForm.serviceItemId) return ElMessage.warning(请选择服务项目) if (!orderForm.elderId) return ElMessage.warning(请选择老人档案) submitting.value true try { await createOrderApi(orderForm) ElMessage.success(下单成功) } catch (e) { ElMessage.error(e.message || 下单失败) } finally { submitting.value false } } return { orderForm, submitting, submitOrder } }好处在于这个组合式函数可以同时被老人端H5下单页和管理端“代下单”抽屉复用。管理端代下单大概率是社区工作人员帮老人操作前端组件不同但业务逻辑完全一致。如果把这些都写在SFC单文件组件里两个页面就会复制粘贴一大坨代码后面改需求要改两处。订单列表页则是老生常谈的分页表格。前端el-table配合el-pagination每次页码变化请求/api/service/order/page?current${page}size${size}。状态列用el-tag显示不同颜色待接单是警告色、服务中是蓝色、已完成是绿色、已取消是灰色。这里有一个细节前端不要自己根据“0/1/2”猜状态文字后端接口返回的应该是statusCode加statusName前端只负责展示。这样后端调整状态文案时前端不用发版。4.4 管理端动态菜单和角色路由管理端左侧菜单不是写死的而是根据登录用户角色动态生成。管理员看到全部菜单服务人员只看到“我的工单、服务评价、个人档案”老人账号则进入老人端页面。我采用了动态添加路由的方案路由守卫里根据角色加载对应的路由表用router.addRoute动态注册然后侧边栏根据可访问的路由配置生成菜单。动态路由有一个需要注意的问题dev环境下直接刷新页面Vue Router可能因为路由还没注册完而跳到404我当时的解决方式是刷新后先请求用户信息和权限列表拿到后再动态注册路由最后用next({...to, replace: true})重进一遍目标路由保证路由表已完整注册。“vue3路由跳转组件内容渲染不显示”这类问题大概率也出在路由配置或者keep-alive缓存上。如果你用了keep-alive包裹router-view组件名称和路由配置里的name不一致会导致缓存不生效或内容不更新另外动态路由如果没在退出时重置切换账号可能出现菜单串了、路由重复。我后来在用户退出登录时调用了resetRouter()把动态添加的路由逐个移除避免这些隐患。5. 权限闭环JWT认证、拦截器与前端按钮级控制权限是这类系统最容易做“表面功夫”的地方。很多项目只在后端用拦截器判断“是否登录”到按钮级就不管了导致页面上一堆按钮点了才报“无权限”。我的目标是把权限做到全链路后端每个接口有权限校验前端菜单和按钮都能根据权限数组隐藏。5.1 JWT生成与解析的版本选择JWT这块我用的io.jsonwebtokenjjwt库。这里必须提醒版本坑jjwt 0.9.x和0.11.x的API差异很大0.9.x用Jwts.builder().setClaims()0.11.x则改成Jwts.builder().claims()而且0.11.x之后对签名密钥长度有硬性要求HS256至少需要256bit也就是32字节用少于32字节的字符串当密钥会直接抛异常。我一度被这个“密钥太短”的报错卡了很久。我的token里只放了userId和role两个关键信息不把手机号、地址等敏感信息放进去因为JWT默认只是Base64编码不是加密payload是明文可读的。登录态的主动失效靠Redis保存token的“有效白名单”实现。生成token时把token字符串、用户ID、过期时间存到Redis每次请求拦截器里先查Redis存在且未过期才放行。5.2 后端拦截器与白名单设计用Spring的HandlerInterceptor实现登录和权限拦截。在WebMvcConfigurer里注册拦截器同时配置白名单Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(authInterceptor) .addPathPatterns(/**) .excludePathPatterns( /api/auth/login, /api/auth/captcha, /api/auth/register, /file/**, /error ); }权限校验在拦截器里判断也可以更进一步用RequirePermission(sys:user:add)注解做方法级权限控制。我项目里是拦截器做登录校验自定义注解做权限校验两段逻辑分开后续维护比较清晰。白名单这里有个容易漏的情况文件上传预览目录如果不放行页面上图片全部加载不出来看起来是“前端跨域”“图片路径错误”其实是被拦截器拦截了。我当时排查了很久才发现是拦截器把/file/**给拦了。所以任何静态资源和公共接口都要第一时间放进白名单。5.3 前端按钮级权限控制后端权限到位后前端按钮级控制我用了一个自定义指令v-permission。用户在登录后获取到的权限码数组类似[service:order:create, service:order:cancel, sys:user:resetPwd]然后根据按钮需要的权限码判断是否移除DOM。// directives/permission.js export const permission { mounted(el, binding) { const required binding.value const userStore useUserStore() const hasPermission userStore.permissions.includes(required) if (!hasPermission) { el.parentNode?.removeChild(el) } } }页面上这样用el-button v-permissionservice:order:cancel取消订单/el-button这里要注意v-permission只是前端体验层的隐藏后端接口权限才是最终防线。理论上用户完全可以通过浏览器控制台手动调用接口所以后端必须保证“没有权限码就返回40301”前端隐藏只是让人看不到入口。这套权限体系上线后管理员分配角色时就非常灵活。比如服务人员角色只能看到“我的工单”和“消息通知”没有“系统管理”入口志愿者角色只能看到“活动报名”相关页面。权限码和角色菜单关联配置一次后基本不用改代码。6. 联调与部署阶段踩过的坑跨域、端口、413和打包如果你以为开发完就万事大吉那是没被联调和部署毒打过。这一节我记录了几个最典型的坑几乎每个前后端分离项目都会遇到而且报错信息往往很迷惑。6.1 跨域问题与idea启动时不显示端口号的排查开发环境用Vite代理转发所以本地联调时跨域基本不发生。但如果有人直接从前端地址访问后端地址还是会遇到CORS。我在后端加了一个全局CORS配置类允许的源、请求头、请求方法都写清楚主要为了支持某些自动化测试工具直接调接口。“IDEA启动Spring Boot项目不显示端口号”这个问题在群聊里被问了无数次我自己也遇到过。真实原因通常是项目不是通过Spring Boot的main方法启动的而是用Tomcat外部部署IDEA里没有启动内嵌容器或者application.yml的server.port配置没有被正确识别比如配置文件加载顺序问题再有就是某些日志配置把启动日志过滤了端口号那行在日志输出里看不到。还有一个很容易忽略的点如果项目里引入了spring-boot-starter-web但又设置了web-application-type: none内嵌Tomcat根本不会启动。排查时先看控制台有没有Tomcat started on port字样没有就说明启动流程没走到那一步优先看依赖和启动类注解。6.2 文件上传413与nginx配置系统里老人上传体检报告、头像、身份证照片是刚需。开发环境上传一切正常部署到服务器后只要上传超过一定大小就报413 Request Entity Too Large。这个错翻译过来是nginx层限制了请求体大小默认只有1MB而我们通常会上传几MB的图片。解决办法是在nginx的server块里加client_max_body_size 20m;同时后端的Spring Boot也设置一下上传大小限制spring: servlet: multipart: max-file-size: 20MB max-request-size: 20MB修改后NGINX要重启加载不是单纯reload配置就行因为client_max_body_size属于请求体大小限制reload不能保证旧worker完全释放。这个坑让我浪费过一个晚上明明配置加了好像没生效。6.3 前端history路由刷新404Vue3路由默认用createWebHistory时前端访问/service/order/list在页面内跳转没问题但只要刷新或者直接输入地址nginx找不到对应的静态文件就返回404。原因很简单nginx配置的是静态文件服务没有把非文件路径的请求全部重写到index.html。nginx配置location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; }这样所有前端路由都先尝试找真实文件找不到就返回index.html由Vue Router接管。还有一个细节如果部署在子路径下比如/admin/Vite的base要配置成/admin/Vue Route的createWebHistory也要传对应的base参数不然打包后页面资源全是404。6.4 本地正常、服务器不正常的经典差异这种“本地没事上线就出问题”的案例层出不穷。我遇到过的有三类第一类是时间问题。服务器时区不是Asia/Shanghai导致前端显示的服务时间比实际少8小时。我在集成层的application-prod.yml里统一指定了spring.jackson.time-zoneGMT8同时数据库连接串上加了serverTimezoneAsia/Shanghai两处都配置才完全正常。第二类是字符集问题。MySQL连接串没有加characterEncodingutf8mb4导致老人姓名、地址中的生僻字插入后乱码。注意url连接参数要写成useUnicodetruecharacterEncodingutf8mb4并且数据库建库时字段也要用utf8mb4。第三类是上传目录权限。程序放到服务器上跑上传文件的目录如果运行用户没有写权限接口报“文件名不能为空”之类莫名其妙的错误。我后来把上传目录指定为数据盘下一个独立目录设置好权限并把nginx里访问/file/**映射到该目录才彻底解决。6.5 关于Vue3打包后出现冗余或报错打包阶段还要留意一个点如果你从Vue2转过来经常习惯性在vue.config.js里写配置但Vite项目用的是vite.config.js两者字段完全不同别拿着老文档抄。还有Vue3发布模式对模板编译更严格某些不规范的写法在开发模式下可能不报错生产构建时会直接抛错。遇到这类问题就老老实实看构建日志定位到具体文件的具体行列通常是标签闭合不对、重复的key或者某个变量未定义。我项目里还出现过打包后登录页正常、跳转后部分页面空白的情况排查后发现是路由懒加载的组件在打包后因异步chunk加载失败导致。通常是两个原因一是publicPath/base路径不对二是nginx没配置gzip但静态资源请求比较大偶发超时。解决办法是调整base配置并检查nginx的try_files配置。7. 项目跑通后的思考还能往哪些方向延伸平台跑通、部署上线后我发现这个项目的价值不只是“能做一套系统”而是形成了一套可复制的套路后续无论是做别的领域项目还是继续深化养老场景都有东西可以直接复用。7.1 从“线上化”到“智能化”的几个可行方向当前系统的核心是业务线上化下一步可以考虑预警和推荐。比如健康档案表里已经有历史血压数据可以加一个定时任务扫描最近一周的数据如果发现连续三天血压偏高就给家属和管理员发预警通知。这个功能在养老场景下非常实际子女最担心的就是父母在家出事却没人知道。另一个方向是接入IoT设备。社区如果给老人配发了智能手环或紧急呼叫按钮设备的数据会以MQTT协议上报需要在后端增加一个消息接收模块。做这个功能时现在的健康记录表结构和/api/health/record/add接口基本可以复用只是数据来源改成设备。这就是设计时表结构拆分得比较细的好处。7.2 沉淀可复用的中台能力这个项目做完后我拆出了几个独立可复用的子模块统一的字典管理、权限模板、订单状态机生成器、通用上传组件、后端导出Excel工具类。换一个领域做“基于Spring Boot Vue3的物业维修平台”直接把权限和字典挪过去再调整一下订单状态机和业务表开发周期可以缩短三分之一。这套思路适合所有以“工单/审核/流转”为核心的平台比如维修、政务预约、企业IT服务台都能套用。7.3 关于真实部署和运维的一点提醒如果你只是做毕设或演示部署到本地或一台云服务器即可但如果你要交给社区实际用运维上的坑会开始出现。比如数据库备份必须定时做老人档案和服务记录都属于敏感数据建议备份文件加密存储日志要采集到统一平台方便出问题时排查HTTPS证书要及时续期。还有一点容易被忽略老人端界面的字体要大、按钮要少、点击区域要够大很多老人用户不会用复杂交互做“防呆设计”比加高级动效重要得多。最后再分享一个我个人的体会做这类平台最大的收获不是代码量而是真正理解了一个真实系统如何把业务流程、用户体验、权限边界和技术架构揉在一起。社区养老服务的数字化还很早期供需之间需要打磨的细节非常多这套Spring Boot加Vue3的组合在这个场景里跑得足够稳后续就算要扩展成小程序、App甚至接入大模型做智能问答底子也不容易被推翻。如果正在做类似系统建议先把核心闭环做扎实再考虑炫酷功能。