ARTICLE DETAIL

资讯详情

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

基于SpringBoot+Vue+MyBatis的养老管理系统核心架构与实战解析

基于SpringBoot+Vue+MyBatis的养老管理系统核心架构与实战解析 1. 养老机构的业务痛点这套系统到底在解决什么问题做了这么多年企业级管理系统我越来越觉得养老机构管理系统是个特别容易被低估的项目。很多人一听敬老院管理第一反应是不就是个CRUD吗但真正扎进去才发现养老机构的业务复杂度一点不比制造业ERP低只是它的业务语言不同。1.1 长者信息管理为何是业务的地基敬老院管理的核心对象不是订单、不是库存而是活生生的老人。这种业务模型和常规企业管理有本质区别一位长者从入院评估、签订协议、分配床位、建立健康档案到日常护理、用餐管理、医疗记录再到请假外出、退住结算信息是一环扣一环的。大多数养老机构以前靠Excel和纸质台账管理最大的问题不是记录不全而是信息孤岛。护理部维护一份健康档案财务部维护一份缴费记录行政部又有一份入住台账三份数据经常对不上。尤其到了月底对账的时候明明长者在住财务那边却查不到对应的床位费记录这种扯皮我见过太多次。所以这套SpringBootVueMyBatisMySQL架构的敬老院管理系统本质上做的是两件事第一把散落在各岗位手里的纸质记录数字化第二把入院—在住—退住全生命周期串成一条可追踪的业务主线。系统不是一个简单的信息登记工具而是养老机构的数字化运营底盘。1.2 从床位分配到费用结算流程中的人与事与普通的管理系统不同敬老院系统里的每个模块都不是孤立的。比如床位管理这个模块表面上只是维护一张床位表实际上它要联动长者入住申请、楼层区域、护理等级、收费标准等多个维度。床位的一个状态变化——从空闲到已入住——背后是一整套审批和登记流程。同样的费用模块也不只是记一笔账。老年人住养涉及的费用类型很多床位费、护理费、伙食费、医疗押金、空调费、临时照护费等每种费用的计费周期和计算方式都不一样。有的按天算有的按月算有的是一次性预收。这意味着费用模块必须和入住天数、护理等级、服务项目实时联动。再有就是请假和外出管理。养老机构对长者的外出管控是非常严格的家属接老人外出要登记回来要销假超时未归系统要能自动标记预警。这类功能如果靠人工盯很容易出现疏漏。系统把这些人与事之间的联动关系理顺了管理效率的提升是立竿见影的。1.3 系统覆盖的核心角色与业务闭环从角色角度看这套系统覆盖了敬老院的全部核心岗位系统管理员负责基础数据维护、账号分配、角色权限配置前台/客服负责接待咨询、登记入住意向、办理入住/退住手续护理部负责护理等级评估、护理记录填报、健康档案更新财务人员负责费用录入、账单生成、收款退费、月度对账院长/管理层通过报表看板掌握全院入住率、营收情况、护理质量这些角色不是各干各的而是通过业务流程串联在一起。前台办理入住后系统自动通知护理部安排护理等级评估同时通知财务启动费用计费。退住时系统自动核算该退的费用财务确认后流程闭环。这套源码全貌我后面会逐步拆解从技术架构到数据库设计从后端接口到前端页面从部署上线到避坑经验尽量讲得细一点帮你在拿到源码后能够快速跑起来、改得动。2. SpringBootVueMyBatisMySQL技术选型背后的真实考量每次看到SpringBootVueMyBatisMySQL这个组合我都觉得这不仅是技术选型更是一种市场选择的结果。它之所以成为敬老院管理系统这类项目的标配背后有几个非常现实的原因。2.1 后端选择SpringBoot的价值快速搭建与生态成熟SpringBoot在Java后端领域的位置已经不需要多说了。选择它的第一个理由就是开发效率。敬老院管理系统虽然业务场景复杂但功能形态上仍然是典型的业务管理系统——增删改查、流程审批、数据统计这些场景SpringBoot有非常成熟的解决方案。第二个理由是生态。做企业级项目必然要面对权限控制、日志记录、文件上传、定时任务这些通用需求SpringBoot生态里的Starter几乎覆盖了所有场景。比如Spring Security处理登录授权Spring Task处理定时任务比如每天凌晨自动生成当天的餐饮统计Spring AOP做操作日志这些都不需要从零造轮子。第三个理由是从业者供给充足。这个因素听起来不那么技术但在真实项目里非常重要。养老机构管理系统这类项目后期往往需要本地化定制开发使用SpringBoot意味着任何一个Java开发都能接手维护。如果用了小众框架后期找人都是个问题。2.2 前端为什么用Vue渐进式框架更适合业务系统管理系统的前端和C端产品的需求完全不同。后台管理页面追求的是开发速度快、表单交互多、数据展示直观、后期能快速调整。Vue的渐进式特性恰恰非常适合这类项目。这套系统采用的是Vue 2 Element UI的组合或者Vue 3 Element Plus两者都可以。如果你拿到的是Vue 2版本也别急着升级Vue 2的生态非常成熟组件库稳定遇到问题随便一搜就有答案。但从长远看新项目用Vue 3 Vite Element Plus会更好编译速度快组合式API写业务逻辑更清晰。这里想多说一句关于组件库的选择。管理系统的UI不需要花哨核心要求是表单组件齐全、表格组件性能稳定、弹窗/抽屉交互顺手。Element系列在国内管理系统市场占有率极高遇到问题几乎都能找到解决方案这是其它UI库暂时比不了的。2.3 MyBatis还是MyBatis-Plus数据访问层的取舍这套技术栈里MyBatis是个非常关键的角色。大家知道MyBatis的核心优势是SQL可控性强、灵活度高。对于复杂业务SQL可以精细地写到每一条where条件对于简单业务MyBatis-Plus提供的BaseMapper可以让你少写大量重复代码。如果只用了原生MyBatis那你需要自己处理很多基础的单表CRUD操作代码量会大不少。如果在MyBatis基础上集成了MyBatis-Plus那开发效率会提升一个档次。具体来说MyBatis-Plus提供了条件构造器QueryWrapper/LambdaQueryWrapper免去手写XML通用分页插件PaginationInnerInterceptor自动填充功能createTime、updateTime自动写入逻辑删除支持适合长者档案这类需要保留历史数据的场景在做敬老院管理系统时我强烈建议用MyBatis-Plus。原因很实在像长者档案的列表查询可能需要同时按姓名、入院日期、护理等级、床位状态等多个条件组合筛选用QueryWrapper比手写动态SQL轻松得多。2.4 MySQL版本与字符集不可忽视的底层细节数据库层面MySQL 5.7和8.0都是可选方案。我的建议是新部署的环境直接用MySQL 8.0性能更好窗口函数、CTE这些新特性在某些统计报表场景非常有用。但要注意MySQL 8.0的默认认证插件是caching_sha2_password如果使用较老版本的JDBC驱动连接会报认证失败。配套使用mysql-connector-java 8.0.x版本就能解决。字符集是我每次都要重点提醒的。建库时一定指定utf8mb4而不是utf8。原因在于utf8在MySQL里最多存储3个字节像生僻字、部分特殊符号比如一些长者名字里会出现的罕见字需要4个字节存储用utf8就会出现乱码或插入失败。建库SQL建议CREATE DATABASE elder_system DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;排序规则用utf8mb4_general_ci还是utf8mb4_unicode_ci在大多数业务系统里没有明显区别。如果对排序准确性有要求就用unicode_ci否则general_ci性能略优。3. 数据库设计拆解从长者档案到费用账单的来龙去脉拿到任何一套管理系统源码第一件事不是看代码而是看数据库设计。表结构就是业务的实体模型把表结构看懂了整个系统的业务逻辑就通了七成。下面按业务模块梳理这套系统的核心表设计思路。3.1 长者档案表业务的地基长者档案是整套系统的根数据。设计这张表时最核心的考量是哪些字段是永久属性哪些字段是状态属性要区分清楚。CREATE TABLE elder_info ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 主键ID, elder_no VARCHAR(32) NOT NULL UNIQUE COMMENT 长者编号, elder_name VARCHAR(50) NOT NULL COMMENT 姓名, gender TINYINT DEFAULT 1 COMMENT 性别 1男 2女, id_card VARCHAR(18) COMMENT 身份证号, birthday DATE COMMENT 出生日期, phone VARCHAR(20) COMMENT 联系电话, emergency_contact VARCHAR(50) COMMENT 紧急联系人, emergency_phone VARCHAR(20) COMMENT 紧急联系电话, health_status VARCHAR(255) COMMENT 健康状况摘要, care_level TINYINT DEFAULT 1 COMMENT 护理等级 1自理 2半护 3全护, bed_id BIGINT COMMENT 当前床位ID, status TINYINT DEFAULT 0 COMMENT 状态 0未入住 1在住 2已退住, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, deleted TINYINT DEFAULT 0 COMMENT 逻辑删除标记 ) COMMENT 长者信息表;几个设计细节值得说明。第一elder_no使用独立的业务编号而不是直接使用自增ID原因是长者编号要打印在腕带、床头卡上自增ID暴露了业务规模且不美观业务编号可以自定义规则比如入住年份流水号。第二deleted字段是逻辑删除。长者档案是一个机构必须长期保存的数据物理删除可能会造成审计问题。用逻辑删除既不影响列表查询又能保留历史数据。第三care_level这个字段在真正复杂的系统里应该拆成评估明细表因为护理等级是有有效期的每半年可能重新评估一次。但作为核心表的一部分保留当前等级字段能简化日常查询。3.2 床位与入住业务状态流转建模床位表的设计核心是房间-床位两级结构。一个房间有多个床位床位关联房间编号、床位号、床位类型单人间/双人间/多人间、当前状态。CREATE TABLE bed_info ( id BIGINT PRIMARY KEY AUTO_INCREMENT, room_no VARCHAR(20) NOT NULL COMMENT 房间号, bed_no VARCHAR(20) NOT NULL COMMENT 床位号, bed_type TINYINT COMMENT 床位类型 1单人间 2双人间 3三人间 4多人间, floor_no INT COMMENT 所在楼层, area_id BIGINT COMMENT 区域ID自理区/失能区/认知症专区, status TINYINT DEFAULT 0 COMMENT 状态 0空闲 1已入住 2维修 3预留, elder_id BIGINT COMMENT 当前入住长者ID, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) COMMENT 床位信息表;入住业务涉及两张关联表入住登记表和退住记录表。我见过很多系统把入住和退住做成一个字段更新在长者表里这样会丢失历史入住记录。正确做法是单独建一张入住记录表长者每次入住生成一条记录包含入住日期、入住时护理等级、入住时费用标准、经办人等信息。退住时更新这条记录的退住日期和退住原因同时在长者表上更新当前状态。这个设计的价值在后面对账和统计时能体现出来。比如按月统计入住率如果入住记录里没有准确的入住和退住日期就只能拿当前在住人数来算而无法算出本月累计服务人次平均入住天数这些更有管理价值的指标。3.3 费用与账单预收、退款、月结费用模块的表设计是这套系统的重头戏。养老机构的收费模式决定了它不能只做简单的应收-实收记账。我建议至少拆三张表费用项目表、长者账单表、收款记录表。CREATE TABLE fee_item ( id BIGINT PRIMARY KEY AUTO_INCREMENT, item_name VARCHAR(50) NOT NULL COMMENT 费用项目名称, item_type TINYINT COMMENT 费用类型 1月度固定 2按天计算 3一次性, unit_price DECIMAL(10,2) COMMENT 单价, billing_cycle TINYINT COMMENT 账期 1按月 2按天, enabled TINYINT DEFAULT 1 ) COMMENT 费用项目表; CREATE TABLE elder_bill ( id BIGINT PRIMARY KEY AUTO_INCREMENT, elder_id BIGINT NOT NULL, bill_no VARCHAR(32) NOT NULL COMMENT 账单编号, fee_item_id BIGINT NOT NULL, amount DECIMAL(10,2) NOT NULL COMMENT 应收金额, start_date DATE COMMENT 计费开始日期, end_date DATE COMMENT 计费结束日期, status TINYINT DEFAULT 0 COMMENT 状态 0待收款 1已收款 2已退款 3已作废, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) COMMENT 长者账单表; CREATE TABLE payment_record ( id BIGINT PRIMARY KEY AUTO_INCREMENT, bill_id BIGINT NOT NULL, elder_id BIGINT NOT NULL, pay_amount DECIMAL(10,2) NOT NULL, pay_method TINYINT COMMENT 支付方式 1现金 2微信 3支付宝 4银行卡 5家属代付, operator_id BIGINT COMMENT 经办人, pay_time DATETIME DEFAULT CURRENT_TIMESTAMP ) COMMENT 收款记录表;这种设计的精妙之处在于账单和收款分离。账单描述的是应该收多少钱收款记录描述的是实际收了多少钱。月底对账时只要把这两个维度对比就能快速发现欠费和错收。系统里还需要支持预收款功能很多家属是一次性预缴一个季度费用的每一笔收款要先进入预收账户再按月摊销到具体账单中。3.4 系统权限表RBAC模型的轻量落地后台管理系统的权限设计要兼顾安全性和灵活性RBAC基于角色的访问控制是最成熟的方案至少需要五张表用户表、角色表、菜单表、用户角色关联表、角色菜单关联表。针对敬老院系统有几个业务场景对权限划分有特殊要求。护理人员应该只能查看和编辑自己负责区域的老人信息不能看到全院财务数据财务人员能看到费用的应收实收但不能修改护理记录院长可以看所有数据但操作轨迹要留痕。这些需求落到设计上就是每个核心业务表都要有数据权限字段比如area_id、operator_id配合后端切面拦截实现行级数据过滤。4. 后端核心模块实现登录、入住、统计这三个硬骨头数据库设计好了业务逻辑就成功了一半。后端开发的骨架基本是固定的Controller接收请求Service处理业务Mapper操作数据库。接下来挑三个最具代表性的模块讲讲实现思路和容易踩坑的地方。4.1 JWT登录与Token拦截器前后端分离的会话机制前后端分离架构下Session方案已经不那么适用了。跨域请求、移动端接入、多端登录这些场景JWTJSON Web Token是更便捷的选择。登录流程很清晰用户提交账号密码后端校验通过后签发Token返回前端前端后续请求在Header中携带Token后端通过拦截器统一校验。Component public class JwtInterceptor implements HandlerInterceptor { Autowired private StringRedisTemplate redisTemplate; Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 放行登录接口和静态资源 String uri request.getRequestURI(); if (uri.contains(/login) || uri.contains(/captcha)) { return true; } String token request.getHeader(Authorization); if (StringUtils.isBlank(token)) { throw new BusinessException(401, 未登录或登录已过期); } // 解析token Claims claims JwtUtil.parseToken(token); // 校验token是否在有效期内Redis中是否存在 String redisToken redisTemplate.opsForValue().get(TOKEN_ claims.get(userId)); if (!token.equals(redisToken)) { throw new BusinessException(401, 登录状态已失效请重新登录); } // 将用户信息放入ThreadLocal供Service层获取当前操作用户 UserContext.set(claims.get(userId)); return true; } }有几个关键点必须注意。第一Token注销问题。JWT本身是无状态的服务端无法主动让其失效。解决方案是登录成功时把Token存入Redis并设置过期时间登出时删除Redis中的Token。拦截器每次校验时对比请求Token和Redis中的Token是否一致不一致则拒绝访问。第二Token过期时间。管理系统的Token有效期不宜过长建议2-8小时。时间太短用户操作途中频繁掉线体验很差时间太长安全风险高。会话过程中如果用户一直在操作可以让前端定时调用一个refresh接口来续期。第三密码加密。老项目用MD5填一下数据库是常有的事但正规的企业级项目必须用BCrypt。Spring Security自带的BCryptPasswordEncoder就很好用注意加盐参数每次加密结果不同能防止彩虹表攻击。4.2 长者入住流程的接口设计事务与状态一致性入住流程是敬老院系统中最容易出现数据不一致的业务。一次完整的入住操作要同时完成以下几件事在elder_info中更新长者的状态为在住在bed_info中更新床位状态为已入住关联长者ID在checkin_record中创建一条入住记录在elder_bill中生成初始费用账单押金、首月床位费等任何一个步骤失败都可能导致数据错乱。比如床位状态变成了已入住但长者状态还是未入住后台就会看到有个床位被占用了但不知道谁住着。这种场景必须使用Transactional事务注解Transactional(rollbackFor Exception.class) public Long checkIn(CheckinRequest request) { // 1. 校验长者信息是否存在、状态是否为未入住 // 2. 校验床位状态是否为空闲 // 3. 生成入住单号 // 4. 更新长者状态 // 5. 更新床位状态 // 6. 插入入住登记记录 // 7. 生成初始费用账单 return checkinId; }这里有个注意事项Transactional默认只对RuntimeException回滚如果代码里抛的是自定义的BusinessException而它继承自Exception事务不会自动回滚。所以务必要设置rollbackFor Exception.class或者让BusinessException继承RuntimeException。事务与并发还有一层关系要处理好。系统里两个前台同时给同一个床位办理入住后提交的那个请求必须在床位状态校验时被拦截。MyBatis-Plus的乐观锁插件是一个解决方案用version字段做版本控制更新时带上版本号版本不一致则更新失败。这种方式比SQL层面的SELECT ... FOR UPDATE更轻量更适合管理系统。4.3 统计报表的SQL写法入住率、营收、护理工作量的聚合院长最关心的几个数字当前入住率、月度营收、新增入住人数、退住人数。这些统计在SQL层面并不复杂但要算得准有几点需要格外注意。先说入住率。合理的口径是当月实际使用床位天数 / 当月可提供床位天数。举个例子某月有30天全院100张床位某张床在前10天空置、后20天有人住那这张床的入住率是20/3066.7%而不是简单看月底时这张床是否有人。口径不同数据会差很多。SQL可以这样写SELECT DATE_FORMAT(ri.start_date, %Y-%m) AS month, SUM( DATEDIFF( LEAST(IFNULL(ri.end_date, CURDATE()), LAST_DAY(ri.end_date)), GREATEST(ri.start_date, DATE_FORMAT(ri.end_date, %Y-%m-01)) ) ) AS total_occupied_days, COUNT(DISTINCT b.id) * DAY(LAST_DAY(NOW())) AS total_available_days FROM checkin_record ri LEFT JOIN bed_info b ON 11 GROUP BY DATE_FORMAT(ri.start_date, %Y-%m);再说营收统计。要注意区分应收和实收两者会差很多。应收是账单生成金额实收是实际收到的钱。如果只看实收就会漏掉欠费的情况如果只看应收就会把预收的款项误算进当期收入。正确做法是把两个口径都统计出来生成对账报表财务人员一看便知。最后是关于多表统计的性能。业务量上去之后报表查询会涉及大量关联和聚合计算。建议设计独立的统计表通过定时任务每天凌晨汇总前一日的数据。这种预计算的思路能保证报表页面秒开而不是在用户查看报表时现场跑大SQL。5. 前端Vue项目实施从目录结构到接口联调的经验前端的开发体验直接影响这套系统的交付效率。敬老院管理系统的前端页面量不小长者档案、床位管理、入住登记、费用管理、护理记录、系统设置每个模块都涉及列表页、表单页、详情页、弹窗。如果没有一套清晰的工程组织方式代码会迅速失控。5.1 目录设计与路由权限模块化是命脉拿到源码后建议先看前端项目的目录结构。一个合理的Vue项目目录大约是这样的src/ ├── api/ # 接口请求模块 │ ├── elder.js # 长者档案接口 │ ├── bed.js # 床位管理接口 │ ├── checkin.js # 入住管理接口 │ ├── fee.js # 费用管理接口 │ └── system.js # 系统管理接口 ├── assets/ # 静态资源 ├── components/ # 通用组件 │ ├── PaginationTable.vue # 封装表格分页组件 │ ├── DictSelect.vue # 数据字典下拉框 │ └── ImageUpload.vue # 图片上传组件 ├── router/ # 路由配置 ├── store/ # 状态管理 ├── utils/ │ ├── request.js # Axios封装 │ └── auth.js # Token存取 ├── views/ │ ├── elder/ # 长者管理页面 │ ├── bed/ # 床位管理页面 │ ├── checkin/ # 入住管理页面 │ ├── fee/ # 费用管理页面 │ ├── statistical/ # 统计报表页面 │ └── system/ # 系统管理页面 └── App.vue路由权限控制是个容易忽略的重点。管理系统的前端路由不能只靠router.beforeEach判断是否登录还要加上角色权限过滤。每个菜单项配置meta.roles数组路由守卫里判断当前用户的角色是否在允许列表中router.beforeEach((to, from, next) { if (to.meta to.meta.roles) { const userRoles store.getters.roles const hasPermission to.meta.roles.some(role userRoles.includes(role)) if (!hasPermission) { next(/403) return } } next() })5.2 列表页与表单页的封装避免重复代码的工程化思路管理系统里80%的页面是左侧查询条件中间表格底部弹窗表单的形态。如果每个页面都从零写一遍模板和方法工作量巨大且后期维护困难。实际项目里建议对高频场景做基础封装。一个PaginationTable组件把搜索表单、表格展示、分页逻辑统一封装。父页面只需要传入查询字段配置和列配置组件内部处理搜索、重置、分页、排序通过emit事件向父页面反馈操作动作。这样开发一个新模块的后台页面只需要写一个配置文件和一个处理逻辑的脚本开发效率能提升一倍以上。弹窗表单同样建议统一封装。用el-dialog包裹表单通过open(type, row)方法区分新增和编辑模式。提交成功后统一刷新列表错误信息通过Message.error展示。注意Dialog关闭后要重置表单校验状态避免下次打开时出现残留的红色错误提示。5.3 Axios封装与统一错误处理前后端配合的关键节点前端最核心的基础设施是对Axios的封装。好的封装能避免大量重复代码同时统一全局错误处理。做法是在request.js中创建一个Axios实例设置baseURL、超时时间、请求拦截器和响应拦截器。const service axios.create({ baseURL: process.env.VUE_APP_BASE_API, timeout: 15000 }) // 请求拦截器注入Token service.interceptors.request.use(config { const token getToken() if (token) { config.headers[Authorization] token } return config }, error Promise.reject(error)) // 响应拦截器统一处理业务错误码和HTTP错误码 service.interceptors.response.use(response { const res response.data if (res.code ! 200) { Message.error(res.message || 请求失败) if (res.code 401) { // 登录失效跳转登录页 store.dispatch(resetToken) location.reload() } return Promise.reject(new Error(res.message)) } return res }, error { Message.error(error.message || 网络异常) return Promise.reject(error) })这个封装有几个好处第一业务代码里不需要每次都写Loading和错误处理第二登录失效场景全局拦截不需要每个页面单独判断第三配合后端的统一响应格式{ code: 200, data: {}, message: success }前端的类型处理会非常清爽。5.4 实际联调中遇到的高频问题前后端联调是老生常谈但有一套系统跑起来之后总会遇到几个共性问题。第一个是跨域问题。开发环境下Vue默认跑在8080端口后端跑在9090端口前端请求后端口必然跨域。最省事的办法是在后端配置跨域过滤器或者在SpringBoot启动类里实现WebMvcConfigurer的addCorsMappings方法Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE) .allowCredentials(true) .maxAge(3600); }生产环境下一般通过Nginx反向代理解决跨域前端请求同源路径Nginx把/api前缀的请求转发到后端服务。这个我会在部署章节展开说。第二个是时间格式问题。Java后端的LocalDateTime默认序列化格式是2024-01-01T10:30:00前端要展示成2024-01-01 10:30就需要额外处理。最干净的做法是在后端全局配置JSON序列化规则用Jackson的LocalDateTimeSerializer统一转换。前端不用做任何特殊处理直接展示即可。第三是分页参数默认值问题。MyBatis-Plus的分页插件要求必须传入current和size两个参数前端如果不传很容易出现点击搜索后第一页又变回第一页的bug。实际上前端搜索请求必须同时携带分页参数和查询条件。建议通过封装好的PaginationTable统一管理避免各个页面各写一遍。6. 部署上线与排坑实录从开发环境到服务器实战很多人在本地跑通源码后就觉得万事大吉一放到服务器上就各种状况。部署这个环节环境差异导致的问题五花八门但绝大多数都可以提前规避。讲讲一套完整的部署路径和常见坑。6.1 环境准备版本对照是关键先把环境版本对齐能省掉一半的坑。我推荐一套经过验证的组合组件版本说明JDK1.8 或 11系统使用SpringBoot 2.x则用JDK 8SpringBoot 3.x必须JDK 17Maven3.6.3依赖管理必需Node.js14.xVue2/ 16.xVue3Vite前端打包必需MySQL5.7 或 8.0建议8.0注意认证插件Nginx1.18静态文件服务与反向代理一个高频报错是JDK版本不匹配。SpringBoot 2.7在JDK 17下能跑但部分老版本依赖会有兼容问题SpringBoot 3.x在JDK 8下直接启动失败。拿到源码后先看pom.xml里的spring-boot-starter-parent版本和java.version属性然后安装对应的JDK版本。这个检查能在部署前省下大把排查时间。6.2 前后端打包与Nginx反向代理配置后端打包很简单mvn clean package -DskipTests打包完成后target目录下生成elder-system-1.0.0.jar通过java -jar启动。生产环境不建议直接在前台运行使用nohup或者配置systemd服务来启动nohup java -jar elder-system-1.0.0.jar --spring.profiles.activeprod logs/app.log 21 前端打包npm install npm run build打包完成后生成dist目录里面是纯静态文件放到Nginx的html目录下。Nginx配置中一个关键点是把/api路径反向代理到后端服务server { listen 80; server_name your-domain.com; root /usr/share/nginx/html/dist; index index.html; location / { try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:9090/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }这里的try_files $uri $uri/ /index.html;配置很重要Vue是单页应用前端路由由JS控制如果不加这个配置刷新一个非首页的路径时Nginx会返回404。proxy_pass http://127.0.0.1:9090/;这个斜杠也要注意。如果后端接口路径是/login前端请求的是/api/login那proxy_pass结尾加斜杠后Nginx会把/api/login转发为/login去掉/api前缀。如果不加斜杠则会把完整路径/api/login转发给后端。6.3 常见启动报错的排查链路部署阶段冒出的问题我按出现频率从高到低列几个典型的。问题一数据库连接失败。报错信息通常是Access denied for user rootlocalhost或Communications link failure。前者是账号密码错误或权限问题后者多半是MySQL没有启动或端口不对。还有一种隐蔽情况是MySQL 8.0的认证插件问题如果application.yml里配置的Username/Password没问题但连接还是失败检查一下MySQL用户的认证插件类型SELECT user, host, plugin FROM mysql.user WHERE user root;如果插件是caching_sha2_password连接驱动的版本必须匹配。问题二端口占用。后端默认配置的server.port可能是8080或9090服务器上这个端口可能已被其他进程占用。用netstat -tlnp | grep 9090查看占用情况如果没有特殊要求直接换一个端口更省事。问题三前端页面能打开但所有接口请求404或502。这种情况基本是Nginx反向代理配置问题。先在服务器上测试后端接口是否正常curl -X POST http://127.0.0.1:9090/login如果本地curl正常但通过域名访问返回502检查Nginx的proxy_pass配置和后端服务监听地址。后端服务如果只监听了127.0.0.1Nginx在同一台服务器上访问是没问题的如果后端在另一台服务器需要确认防火墙和安全组是否放行了对应端口。问题四前后端时间字段差8小时。这个问题相当经典。数据库连接串里需要加上serverTimezoneAsia/Shanghai同时JVM启动参数加上-Duser.timezoneGMT8。生产环境如果发现时间对不上多半是这两个地方之一没配置。7. 二次开发与功能扩展的方向源码跑通了只是开始落地到实际的养老机构几乎一定会遇到定制需求。根据我的经验下面几个方向是这类型项目最常被要求扩展的。7.1 对接智能硬件与物联网设备养老服务行业正在快速智能化。很多机构已经部署了智能床垫、跌倒检测雷达、心率手环、门磁传感器等设备。管理系统如果能对接这些设备的数据就能实现很多增值功能智能床垫实时监测长者离床状态夜间离床超时自动告警心率手环数据每5分钟同步异常数值系统自动生成护理工单门磁与请假数据联动未经登记的出门自动触发通知技术上设备数据上报一般走MQTT协议后端引入MQTT客户端订阅设备主题解析后写入数据库。告警规则可以通过Spring Boot的定时任务扫描或者使用规则引擎处理。这套源码预留的接口风格如果规范扩展这些能力并不难。7.2 子女端小程序家庭沟通的数字化养老机构的核心服务对象不只是长者还有他们的子女。子女最关心的是父母的健康状态、护理记录、费用明细以及和护理人员之间的沟通。在现有系统基础上扩展一个小程序端是比较常见的需求。小程序端用户是老人的子女通过系统生成的邀请码绑定长者档案。绑定后可以查看长者近期的护理记录和体检报告每日餐饮照片和活动照片费用账单和缴费进度在线发起视频探视预约这类扩展对后端来说主要是多一套小程序端的接口。鉴权方式可以采用微信登录长辈绑定码的双层设计。前端页面用uni-app开发一套代码可以同时发布到微信小程序和支付宝小程序。7.3 数据大屏与运营驾驶舱养老机构的管理层越来越重视数据可视化。一个大屏上展示全院入住率、护理人员工作量、今日出入院动态、营收趋势图是很多机构管理者的刚需。数据大屏的实现思路就是在现有统计接口的基础上再开发一套专门面向大屏的聚合接口。大屏页面上可以不依赖Element UI直接用ECharts渲染图表配上深色背景和数据自动刷新逻辑。这类页面通常放在独立的Nginx目录下用单独的域名或路径访问避免和管理系统互相干扰。技术细节上大屏涉及的时间维度和业务口径比报表页面更复杂。比如今日实时入住率需要统计当前时刻的实际在住人数除以总床位数这要求后端接口的查询条件是实时的不像日报一样可以提前预计算。所以大屏接口建议单独开发不要复用报表的预计算表。8. 拿到源码后建议从哪里开始看最后聊点实在的。如果你刚拿到这套源码面对那么多文件夹和代码文件很容易不知道从哪下手。我个人建议的阅读顺序是这样的可以帮你快速建立全局认知。第一步先建库跑起来。执行SQL脚本建库建表修改application.yml里的数据库连接配置启动后端服务再启动前端项目。第一次成功跑起来你就能直观感受到整套系统的全貌。第二步看数据库表结构。把核心表的字段过一遍对照业务理解每个字段的含义。特别关注elder_info、bed_info、checkin_record、elder_bill这四张表之间的关联关系它们构成了系统的业务主线。第三步跟一条核心业务链路。从前端页面出发比如模拟新增长者→分配床位→办理入住→生成账单这条路径。每操作一步后端对应走哪个Controller的哪个方法写到了哪张表。把这条链路捋通了你就掌握了这套系统的核心逻辑。第四步再研究扩展点。看看系统的字典管理是怎么实现的日志记录是怎样的切面文件上传存储在哪里这些通用功能以后大概率要改。这套源码的技术栈虽然主流但真正让它区别于教学Demo的是业务细节的处理长者档案的逻辑删除、入住历史的留痕、床位状态与长者状态的联动、费用账单与收款记录的分离。这些设计都是实战中打磨出来的值得慢慢体会。
返回列表