资讯详情

资讯详情

Java+SSM与Python+Django构建老年公寓信息管理系统全解析

准备做老年公寓信息管理系统这个题目的同学应该都有体会网上一搜要么是纯数据库脚本要么是截图都看不清的讲解视频真正常见的业务怎么串起来、代码结构怎么组织、部署时哪里最容易出问题很少有人完整讲清楚。这篇文章就把我用 JavaSSM 做主系统、PythonDjango 做辅助服务来实现的老年公寓信息管理系统从需求梳理到落地的完整思路、关键代码和踩坑点一次讲透希望能让你少走几天弯路。我的整体方案是这样的Java 侧用 SSMSpring、SpringMVC、MyBatis负责任务管理、入住退住、费用结算这类核心业务Django 跑在另一个端口负责定时提醒、统计报表、Excel 导出这些需要 Python 生态更方便的辅助功能。两个系统共用同一个 MySQL 数据库各取所长也不会把代码搞乱。下面按业务、数据库、后端实现、跨服务协作、部署调试这条线展开。1. 老年公寓的业务地图先搞清楚到底要管什么1.1 养老机构日常运营中真正需要数字化的环节很多人在做这类系统时第一反应就是给老人建个档案、加个床位表、写几个增删改查页面。这个方向没错但远远不够。我接这个项目时先去对接过的 A 机构实地了解了一圈发现他们的日常管理痛点非常明确基本都是手工表格和微信群在支撑一是入住接待。老人入院前要登记身份信息、紧急联系人、病史、过敏药物、体检报告以前这些资料散落在纸质档案夹里想查某个老人的体检日期得翻半天。二是床位管理。养老公寓的床位有单人间、双人间、护理床而且同一个房间可能住着不同护理等级的老人。床位被占、空床、维修中的状态经常更新不及时前台被家属问还有没有朝南的房间时只能凭记忆回答。三是护理记录。护理员每天要给老人做晨检、翻身、喂药、洗澡协助这些动作需要留痕。如果只靠口头交接班出了问题说不清楚。四是费用结算。床位费、护理费、餐饮费按不同周期生成账单有的按月、有的按季度还有押金、临时加收的护理耗材费。手工算账不仅容易出错催缴也很麻烦。五是家属沟通。子女最想知道的是老人的健康状态、有没有异常情况系统如果能在体检异常、欠费、生日时自动提醒家属会明显提升机构的服务口碑。把这些需求归纳一下系统的核心模块就清楚了老人档案管理、床位管理、入住退住登记、健康档案、护理记录、费用账单与缴费、家属管理、通知提醒、统计报表。后面所有表设计都是围绕这几个模块展开的。1.2 角色与权限不同人看到和操作的内容完全不一样系统不是给一个人用的实际使用角色至少有四类权限设计的出发点就是谁管什么数据管理员管全局。员工账号分配、床位价格与费用规则配置、系统参数设置、所有业务数据的查看权。前台接待办理入住、退住、换床登记负责缴费并录入缴费单更新家属联系信息。护理员录入护理记录填写日常巡视结果查看自己负责的老人列表。医护人员维护健康档案、体检指标录入用药提醒和注意事项。还有一类家属角色可以做成 Web 登录查看老人的部分信息也可以做成消息推送。考虑到毕设项目和演示效果我建议至少做一个简单的家属查询页面让家属输入手机号后验证码登录查看老人的健康记录和缴费详情。这样既体现了业务完整性答辩时也更有说服力。权限实现层面SSM 中我用的是拦截器 角色注解每个 Controller 方法上标注RequiresRole(admin)拦截器在进入方法前检查 Session 里的角色标识没有权限直接跳转到 403 页面。这个方案比引入 Shiro 要轻量逻辑也透明适合 SSM 项目的课程设计体量。2. 数据模型与入住流程的状态流转2.1 核心表设计清单能少建表但千万别缺关键字段数据库设计是整个项目的地基。我把核心表列出来字段只写关键部分可以直接照着建。老人信息表elderlyid、姓名、性别、出生日期、身份证号、联系电话、紧急联系人、紧急联系人电话、病史摘要、过敏记录、护理等级自理/半自理/全护理、照片 URL、状态待入住/在住/已退住。床位表bedid、房间号、床位编号、床位类型单人间/双人间/护理床、楼层、朝向、状态空闲/占用/维修、当前入住老人 id。注意当前入住老人 id 是冗余字段目的下面讲并发时会说明。入住登记表checkin_recordid、老人 id、床位 id、入住日期、押金、月费用总额、护理等级、状态在住/已退住、退住日期。健康档案表health_recordid、老人 id、记录日期、体温、血压、血糖、心率、体检机构、备注。这里做的是一次体检一条记录的流水式存储便于画趋势图。护理记录表nursing_recordid、老人 id、护理员 id、记录日期、护理项目翻身/喂药/洗澡/巡视、内容描述。账单表payment_recordid、老人 id、账单类型床位费/护理费/餐饮费/押金、应收金额、账单周期起始日、账单周期结束日、缴费状态未缴/已缴/逾期、缴费时间、收款人。家属关系表family_relationid、老人 id、家属姓名、关系、电话、是否主联系人。通知表noticeid、老人 id、通知类型生日/缴费/健康异常、内容、已读标记、创建时间。这张表是 Django 定时任务写入、SSM 读取展示的衔接点。这里想强调一个很多人忽略的细节健康档案和护理记录都要带记录日期而不是只存创建时间。因为护理员补录前一天记录是常见操作如果字段用create_time业务语义就不对了统计 今天有没有完成晨检 也会出错。2.2 入住、换床、退住的完整状态流转业务状态如果不梳理清楚写代码时会反复改条件。我最后落地的状态逻辑是这样的老人状态有四个REGISTERED登记待入住、CHECKED_IN在住、CHECKED_OUT已退住。其中登记待入住表示资料已录入但还没有分配床位。之所以单独留这个状态是因为有些老人是先预订过几天才入院。入住流程登记资料 - 选择床位 - 床位从空闲改为占用 - 老人状态改为在住 - 生成一条入住记录 - 根据床位类型和护理等级自动计算月费用并生成首月账单。这几个动作之间是有先后依赖的我会在下一节讲事务怎么写。换床流程原床位释放为空闲 - 新床位改为占用 - 更新老人与当前床位关系 - 在原入住记录里追加一条换床备注同时生成一条床位变更日志。这里不要直接删除原入住记录否则后面对账时拿不到这个老人住过哪些床位的历史。退住流程发起退住申请 - 校验是否存在未缴账单有欠费必须先处理或挂账- 释放床位 - 更新老人状态 - 在原入住记录上写退住日期和状态并结算押金扣除应补费用后原路退回或转抵。退住后老人的基础档案建议保留只改状态方便以后再次入住或家属查询历史。我用一张表把这几个状态和关键约束列出来方便你自己实现时对照状态/事件前置条件动作后置状态登记老人无录入资料待入住入住分配有待入住记录、床位空闲占用床位、生成入住记录与首月账单在住换床在住释放旧床、占用新床在住床位变化退住在住、无未缴账单释放床位、结算押金已退住重新入住已退住、资料完整走入住流程在住2.3 金额和时间字段最不容易被注意但最容易翻车的地方费用字段必须用DECIMAL(10,2)而不是FLOAT这一点几乎所有老师都会强调但每年都有人踩。浮点数在 Java 和 MySQL 里的精度误差在累计多笔账单后会明显暴露出来比如 0.10.2 不等于 0.3 的问题。MyBatis 中对应 Java 类型用BigDecimal计算时通过BigDecimal.valueOf()来构造避免直接new BigDecimal(double)带来的虚假精度。日期字段建议统一用DATE或DATETIME不要用字符串。因为后面统计本月入住率近 7 天健康记录都需要日期比较字符串比较在格式不统一时非常痛苦。另外所有记录日期我统一在 Java 代码里用LocalDate.now()生成而不是依赖数据库的CURRENT_TIMESTAMP这样在测试环境导入历史数据时更可控。还有一点和业务强相关账单周期不要只存一个费用月份字符串而要把起始日和结束日分开存。比如某老人 3 月 15 日入住首月账单周期是 3 月 15 日到 4 月 14 日金额按天折算。如果只存2025-03后面想统计每天的在住人数和应收收入数据就不够用了。3. SSM 端关键实现从登录到多表事务3.1 分层结构与模块划分工程结构我按业务模块而不是按技术层来分包这样找代码更快。基础包结构如下controller、service、mapper、entity、common通用拦截器、工具类、统一返回结果。业务模块用包名前缀区分比如elderly、bed、checkin、payment、health、nursing。entity对应数据库表字段字段名用驼峰MyBatis 里开启mapUnderscoreToCamelCasetrue下划线字段名自动映射到驼峰属性省掉一大半手写resultMap的工作。Service 层我坚持一个原则Controller 里不直接操作多个 Mapper 完成跨表业务。以入住办理为例涉及老人、床位、入住记录、账单四张表必须在同一个checkinService.doCheckin()方法内完成并且加上Transactional。这样一旦中间某一步失败数据库不会出现床位占了但入住记录没生成这种脏数据。3.2 登录认证与权限拦截登录逻辑不复杂查询用户表比对密码不要明文存至少用 MD5 加盐能上 BCrypt 更好成功后把用户 id、用户名、角色写入 Session。随后注册一个HandlerInterceptor在preHandle里做三件事检查 Session 是否过期检查当前请求路径是否在白名单如登录页、静态资源、Django 提供的公共 API如果 Controller 方法上有自定义角色注解再比对角色。一个小点拦截器默认是拦截所有路径的但页面里要引用的 CSS、JS、图片、上传后的文件目录必须放行。我当时就因为在配置里漏了/static/**和/upload/**导致登录页样式加载不出来排查了一会儿才意识到是拦截器把静态资源给挡了。3.3 条件分页查询SQL 拼接最容易出错老人列表页、账单列表页都需要条件分页查询比如按姓名、按护理等级、按入住状态筛选。我用 MyBatis 的where标签来动态拼接避免手动拼 SQL 出现WHERE 后面直接跟 AND的语法错误。核心 XML 片段大致长这样select idselectElderlyPage resultTypeElderly SELECT * FROM elderly where if testname ! null and name ! AND name LIKE CONCAT(%, #{name}, %) /if if testcareLevel ! null and careLevel ! AND care_level #{careLevel} /if if teststatus ! null and status ! AND status #{status} /if /where ORDER BY create_time DESC LIMIT #{offset}, #{pageSize} /select注意LIMIT的偏移量要在 Service 层算好前端传页码pageNum和每页条数pageSize后端算 offset不要直接让前端透传limit和offset更不要把表名、排序字段参数化地拼进 SQL会引入 SQL 注入风险。3.4 入住办理的多表事务处理这是整个系统业务最重的一个方法也直接体现你对 SSM 事务的理解。我写下核心逻辑伪代码Transactional(rollbackFor Exception.class) public void doCheckin(CheckinRequest req) { Elderly elderly elderlyMapper.selectById(req.getElderlyId()); if (!REGISTERED.equals(elderly.getStatus())) { throw new BusinessException(老人当前状态不能办理入住); } Bed bed bedMapper.selectByIdForUpdate(req.getBedId()); if (!EMPTY.equals(bed.getStatus())) { throw new BusinessException(床位已被占用或维修中); } bedMapper.updateStatus(req.getBedId(), OCCUPIED); elderlyMapper.updateStatus(req.getElderlyId(), CHECKED_IN); CheckinRecord record new CheckinRecord(); record.setElderlyId(req.getElderlyId()); record.setBedId(req.getBedId()); record.setCheckinDate(LocalDate.now()); record.setStatus(ACTIVE); checkinMapper.insert(record); PaymentRecord bill buildFirstMonthBill(req); paymentMapper.insert(bill); }selectByIdForUpdate是SELECT ... FOR UPDATE把床位行锁住防止两个人同时办理入住抢同一张床。如果怕锁影响性能也可以用条件更新的方式直接执行UPDATE bed SET statusOCCUPIED WHERE id#{bedId} AND statusEMPTY返回受影响行数如果为 0 说明床位已经被占。这种写法更轻也更符合实际并发量很低的场景。3.5 文件上传与下载的细节体检报告、老人证件这类文件实际项目中建议传到服务器本地目录或对象存储数据库只存相对路径。我在本地用的是 Tomcat 下的upload/目录并通过 WebMvc 配置把该目录映射成静态资源Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/upload/**) .addResourceHandler(file: uploadDir); }文件上传时注意两个问题一是限制文件大小和类型防止有人传 exe 或超大文件SpringMVC 的CommonsMultipartResolver里设置maxUploadSize二是上传文件的重命名不要用用户原始文件名直接落盘因为中文名和重名都可能出问题我用时间戳加随机数重命名。下载时通过ResponseEntitybyte[]返回同时设置Content-Disposition为附件方式。4. Django 辅助服务为什么一个 SSM 项目里还要有 Python4.1 用 Django 解决的四个实际问题最初我也想过纯 SSM 一把梭但做到统计报表和定时任务时发现很多功能用 Java 也能写但要写很多样板代码。后来我决定引入 Django不是因为标题里写了两个框架就这样设计而是它确实能解决四个具体问题第一定时任务。生日提醒、缴费到期提醒、体检到期提醒本质是周期性扫描数据并生成通知。Java 里用 Quartz 或 Spring Task 也能做但 Django 里我用 APScheduler 几行代码就能跑起来而且重启策略和日志观察很直观。第二复杂统计接口。比如统计每个月的入住率各护理等级的人数占比最近半年费用收入趋势Django 的 ORM 配合日期分组非常顺手尤其在需要按周、按月聚合的场景。第三Excel 导出。护理员每月要交一份手工报表机构也经常要求导出老人花名册、费用明细。Python 的 openpyxl 比 Java 的 POI 写起来省事很多尤其是合并单元格和样式设置。第四后续扩展空间。如果后面要做营养配餐推荐、健康风险预警这些偏算法和数据处理的功能放 Python 侧更自然。通过预留 Django API项目未来的可扩展性会好很多。4.2 两个技术栈如何协作而不搞乱架构这是最容易被问为什么不统一技术栈的地方。我的设计很简单两个服务共用一个 MySQL 数据库但分工明确。SSM 是主系统维护所有业务表的读写。Django 是辅助服务它对业务表只读或者只往辅助表比如notice里写入。Django 通过settings.py直接连接同一个 MySQL 库而不是通过 SSM 的接口拿数据这样最直接也最稳定不增加网络开销。对外接口分两类一类是 SSM 页面 AJAX 直接调 Django 的 REST API比如仪表盘上的统计图表数据我给 Django 配了 CORS只允许本机来源另一类是 Django 定时任务写notice表SSM 登录后查询notice表展示在页面上。比如页面右上角的铃铛图标点开就是 Django 生成的提醒列表。这里要特别注意Django 侧连接数据库的账号不要用 SSM 的超级账号我另建了一个只读账号和只对notice表有写权限的账号。就算 Django 服务出了问题也不会影响主业务表的数据安全。4.3 一个具体例子缴费到期提醒的完整落地方案我用缴费到期提醒来说明这种协作模式。逻辑是这样每月 25 号Django 扫描所有在住老人的账单找出下月未生成账单的在住老人给他们生成下月账单同时扫描本月底前未缴费的账单对超期 3 天仍未缴费的往notice表写一条欠费提醒并更新账单状态为OVERDUE。Django 侧核心代码示意# tasks.py from apscheduler.schedulers.blocking import BlockingScheduler from datetime import date, timedelta from django.utils import timezone from .models import Elderly, PaymentRecord, Notice def generate_next_month_bills(): today date.today() for elderly in Elderly.objects.filter(statusCHECKED_IN): bill PaymentRecord( elderlyelderly, bill_typeMONTHLY_FEE, amountelderly.monthly_fee, period_starttoday timedelta(days1), period_endtoday timedelta(days31), pay_statusUNPAID, ) bill.save() def create_overdue_notices(): deadline timezone.now().date() - timedelta(days3) overdue_bills PaymentRecord.objects.filter( pay_statusUNPAID, period_end__ltdeadline ) for bill in overdue_bills: Notice.objects.create( elderlybill.elderly, notice_typePAYMENT, contentf{bill.elderly.name} 的 {bill.period_start} 至 {bill.period_end} 账单已逾期, read_flagFalse, ) bill.pay_status OVERDUE bill.save() scheduler BlockingScheduler() scheduler.add_job(generate_next_month_bills, cron, day25, hour9, minute0) scheduler.add_job(create_overdue_notices, cron, hour10, minute0) scheduler.start()实际的调度执行放在一个独立的 Python 进程里作为 Django 项目的一个 manage.py 命令启动。开发环境直接跑部署环境用 supervisor 看护一下就行。这个方案简单也符合大多数课程设计的部署条件。5. 部署调试与高频踩坑照着做能省半天5.1 环境与版本兼容这是新手第一道坎老生常谈但必须再说一遍JDK 版本、Tomcat 版本、Maven 依赖版本这三者必须匹配。我用的是 JDK 8、Tomcat 8.5。如果你用 JSP 作为视图Tomcat 10 默认的 Jakarta 命名空间会导致javax.servlet报错所以要么用 Tomcat 9 的老命名要么改依赖。这个坑每年都有大量同学踩。SSM 的 Maven 依赖容易出现 Spring 版本不一致的问题建议所有 Spring 相关依赖spring-core、spring-webmvc、spring-jdbc、spring-tx统一用同一个版本号比如 5.3.x。MyBatis 用 3.5.xmybatis-spring 用 2.x注意这两个版本需要配套。Django 侧的坑主要在 MySQL 驱动Python 3 环境推荐用pymysql并在项目__init__.py里加入import pymysql pymysql.install_as_MySQLdb()否则 Django 连接 MySQL 会报No module named MySQLdb。5.2 中文乱码的三处设置少一处都会出问题第一个是数据库连接串要加useUnicodetruecharacterEncodingutf8不加大多数字符存储进去就乱码。第二个是 JSP 页面顶部要写pageEncodingUTF-8同时响应的 ContentType 也带上 charset。第三个是表单 POST 请求的编码过滤器在 web.xml 里配置 Spring 提供的CharacterEncodingFilter强制 UTF-8。另外一个容易忽略的是 Tomcat 的 URI 编码。如果 GET 请求参数里带中文可能会在 Tomcat 8 以下出现乱码需要在server.xml的 Connector 上配置URIEncodingUTF-8。这个不配好搜索老人姓名时会非常痛苦。5.3 跨服务接口调用与安全边界SSM 页面要调 Django 的统计接口我在 Django 里用到了django-cors-headers来允许跨域。但是要注意开发和部署时的跨域配置不一样。开发时允许http://localhost:8080即可部署时如果 SSM 和 Django 都在同一台服务器可以让 Nginx 统一转发SSM 页面上相对路径请求Nginx 按前缀把/api/转发到 Django这样不会出现跨域问题也更整洁。给两个服务之间加一层简单的验证也是值得的。Django 接口里我要求请求头带一个自定义的X-Internal-Token值由 SSM 配置常量里保存两边约定好。虽然不是强安全方案但比裸奔暴露接口好太多。5.4 床位并发分配比想象中更值得花心思我在做并发测试时发现一个真实问题两个管理员几乎同时为不同老人办理入住如果只检查 床位状态是空闲 再更新会出现两人都看到同一张床空闲的情况。解决办法前面在事务部分已经提到最推荐的是用条件更新UPDATE bed SET statusOCCUPIED, current_elderly_id#{elderlyId} WHERE id#{bedId} AND statusEMPTY然后判断影响行数如果为 1 则继续后续操作否则直接抛出床位已被占用。这个方式不需要显式加锁代码也简单。另外我还给pay_status和elderly.status这类关键状态列加了约束尽量从数据库层面兜底。6. 演示、文档与扩展让项目看起来完整且有思考6.1 一套 10 分钟就能走完的演示路径验收或答辩时不要东点一下西点一下要设计一条业务闭环。我建议的路径是管理员登录先展示首页仪表盘今日入住人数、空床数、待处理提醒这一屏就能让评委快速了解系统规模。新增一位老人登记上传头像和体检报告状态变为待入住。进入床位管理选择一个空闲床位执行入住办理。随后立即展示生成的当月账单和入住记录。到健康档案页录入一条体检数据然后在护理记录里补一条今天的护理记录。回到账单管理对刚才生成的账单执行缴费操作观察账单状态从未缴变为已缴。打开通知中心展示 Django 定时任务生成的生日提醒和欠费提醒。最后打开统计报表页展示月度入住率、收入趋势图。如果时间充裕再演示退住流程强调退住时释放床位、结算押金、保留历史档案这三个点。整条路径覆盖了系统的全部核心模块逻辑顺畅评委跟着走一遍就对系统有了完整概念。6.2 论文和调试文档怎么组织才不空洞配套文档是这类项目的重要组成不要写成一堆截图的堆砌。我写的论文结构是需求分析角色用例、业务流程图、非功能需求- 系统设计总体架构、功能模块划分、数据库设计、接口设计- 系统实现按模块贴核心代码并解释- 系统测试功能测试用例表、并发测试、兼容性测试。每一章都要能和代码对应上答辩时老师随便指一个功能你都能说出它对应哪张表、哪个 Service 方法。调试文档我建议按照部署手册 常见问题对照表来写。部署手册要写到任何人拿到项目压缩包照着文档就能在 30 分钟内把环境跑起来。常见问题对照表列出我前面提到的那些坑比如 Tomcat 10 和 JDK 8 不兼容、中文乱码、MySQL 驱动找不到。这样调试文档本身就有实用价值而不是形式化的附录。6.3 后续可以扩展的方向如果还有精力我建议从三个方向中选一个做延伸一是移动端做个家属端小程序或 H5老人健康数据实时推送二是硬件对接接入手环类设备采集心率、步数三是算法优化比如根据老人的健康档案自动调整护理等级或推荐餐饮方案。这些方向都能在现有 SSM Django 架构上自然扩展因为 Django 侧已经预留了 API 和定时任务能力不需要推翻重做。最后分享一点个人体会这类管理系统表面看是技术问题实际上业务规则的梳理才是决定代码质量的关键。入住、退住、换床、缴费这些状态流转如果一开始没想清楚写出来的代码就会满屏 if-else 还漏洞百出。建议动手写代码前先花一到两天把业务表、状态、角色之间的逻辑在纸上画明白后面写起来会顺手很多。调试时把接口文档维护好每完成一个模块就记录一下请求参数和返回结果最后写调试文档时你一定会感谢当时勤奋的自己。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →